June, 2006

  • The Old New Thing

    Why did the Add or Remove Programs control panel try to guess all that information?

    • 38 Comments

    As we saw earlier, the "Add or Remove Programs" control panel used several heuristics to attempt to determine things like program size and frequency of user. Why did it bother doing this at all?

    At the time the feature was added, disk space was not cheap like it is today. One of the problems users were having was running out of disk space and not knowing what they could safely delete. Thus was born the Disk Cleanup utility, which attempted to guide the user through various things that could be deleted in order to make disk space available.

    In addition to cleaning up temporary files, you could also remove programs that weren't being used. But how do you know which programs you weren't using? (Maybe you were using a program without realizing it because it ran automatically.) And how do you know how much disk space would be recovered if you removed a program? That's where the program size and frequency of use heuristics came in. By providing this information (or at least trying to), the "Add or Remove Programs" control panel could help users decide which programs to remove.

    Of course, nowadays, with hard drives in the hundreds-of-gigabytes range, disk space has become so cheap as to be nearly free. The need to remove programs to make more disk space available is largely gone, but the feature remains as a vestigial organ.

  • The Old New Thing

    What does the CS_OWNDC class style do?

    • 34 Comments

    Recall that window DCs are most commonly used only temporarily. If you need to draw into a window, you call BeginPaint or, if outside of a paint cycle, GetDC, although painting outside of a paint cycle is generally to be avoided. The window manager produces a DC for the window and returns it. You use the DC, then restore it to its original state and return it to the window manager with EndPaint (or ReleaseDC). Internally, the window manager keeps a small cache of DCs which it dips into when people come asking for a window DC, and when the DC is returned, it goes back into the cache. Since window DCs are used only temporarily, the number of outstanding DCs is typically not more than a handful, and a small cache is sufficient to satisfy DC demands in a normally-running system.

    If you register a window class and include the CS_OWNDC flag in the class styles, then the window manager creates a DC for the window and puts it into the DC cache with a special tag that means "Do not purge this DC from the DC cache because it's the CS_OWNDC for this window." If you call BeginPaint or GetDC to get a DC for a CS_OWNDC window, then that DC will always be found and returned (since it was marked as "never purge"). The consequences of this are good, bad, and worse.

    The good part is that since the DC has been created specially for the window and is never purged, you don't have to worry about "cleaning up the DC" before returning it to the cache. Whenever you call BeginPaint or GetDC for a CS_OWNDC window, you always get that special DC back. Indeed, that's the whole point of CS_OWNDC windows: You can create a CS_OWNDC window, get its DC, set it up the way you like it (selecting fonts, setting colors, etc.), and even if you release the DC and get it again later, you will get that same DC back and it will be just the way you left it.

    The bad part is that you're taking something that was meant to be used only temporarily (a window DC) and using it permanently. Early versions of Windows had a very low limit for DCs (eight or so), so it was crucial that DCs be released as soon as they weren't needed. That limit has since been raised significantly, but the underlying principle remains: DCs should not be allocated carelessly. You may have noticed that the implementation of CS_OWNDC still uses the DC cache; it's just that those DCs get a special marking so the DC manager knows to treat them specially. This means that a large number of CS_OWNDC DCs end up "polluting" the DC cache, slowing down future calls to functions like BeginPaint and ReleaseDC that need to search through the DC cache.

    (Why wasn't the DC manager optimized to handle the case of a large number of CS_OWNDC DCs? First, as I already noted, the original DC manager didn't have to worry about the case of a large number of DCs since the system simply couldn't even create that many in the first place. Second, even after the limit on the number of DCs was raised, there wasn't much point in rewriting the DC manager to optimize the handling of CS_OWNDC DCs since programmers were already told to use CS_OWNDC sparingly. This is one of the practicalities of software engineering: You can do only so much. Everything you decide to do comes at the expense of something else. It's hard to justify optimizing a scenario that programmers were told to avoid and which they in fact were already avoiding. You don't optimize for the case where somebody is abusing your system. It's like spending time designing a car's engine so it maintained good gas mileage when the car has no oil.)

    The worse part is that most windowing framework libraries and nearly all sample code assume that your windows are not CS_OWNDC windows. Consider the following code that draws text in two fonts, using the first font to guide the placement of characters in the second. It looks perfectly fine, doesn't it?

    void FunnyDraw(HWND hwnd, HFONT hf1, HFONT hf2)
    {
     HDC hdc1 = GetDC(hwnd);
     HFONT hfPrev1 = SelectFont(hdc1, hf1);
     UINT taPrev1 = SetTextAlign(hdc1, TA_UPDATECP);
     MoveToEx(hdc1, 0, 0, NULL);
      
     HDC hdc2 = GetDC(hwnd);
     HFONT hfPrev2 = SelectFont(hdc2, hf2);
      
     for (LPTSTR psz = TEXT("Hello"); *psz; psz++) {
      POINT pt;
      GetCurrentPositionEx(hdc1, &pt);
      TextOut(hdc2, pt.x, pt.y + 30, psz, 1);
      TextOut(hdc1, 0, 0, psz, 1);
     }
      
     SelectFont(hdc1, hfPrev1);
     SelectFont(hdc2, hfPrev2);
      
     SetTextAlign(hdc1, taPrev1);
      
     ReleaseDC(hwnd, hdc1);
     ReleaseDC(hwnd, hdc2);
    }
    

    We get two DCs for the window. In the first we select our first font; in the second, we select the second. In the first DC, we also set the text alignment to TA_UPDATECP which means that the coordinates passed to the TextOut function will be ignored. Instead the text will be drawn starting at the "current position" and the "current position" will be updated to the end of the string, so that the next call to TextOut will resume where the previous one left off.

    Once the two DCs are set up, we draw our string one character at a time. We query the first DC for the current position and draw the character in the second font at that same x-coordinate (but a bit lower), then we draw the character in the first font (which also advances the current position).

    After the text drawing loop is done, we restore the states of the two DCs as part of the standard bookkeeping.

    The intent of the function is to draw something like this, where the first font is bigger than the second.

    Hello
    Hello

    And if the window is not CS_OWNDC that's what you get. You can try it out by calling it from our scratch program:

    HFONT g_hfBig;
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
     LOGFONT lf;
     GetObject(GetStockFont(ANSI_VAR_FONT),
               sizeof(lf), &lf);
     lf.lfHeight *= 2;
     g_hfBig = CreateFontIndirect(&lf);
     return g_hfBig != NULL;
    }
    
    void
    OnDestroy(HWND hwnd)
    {
     if (g_hfBig) DeleteObject(g_hfBig);
     PostQuitMessage(0);
    }
    
    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
     FunnyDraw(hwnd, g_hfBig,
                     GetStockFont(ANSI_VAR_FONT));
    }
    

    But if the window is CS_OWNDC, then bad things happen. Try it yourself by changing the line wc.style = 0; to wc.style = CS_OWNDC; You get the following unexpected output:

    HHeelllloo

    Of course, if you understand how CS_OWNDC works, this is hardly unexpected at all. The key to understanding is remembering that when the window is CS_OWNDC then GetDC just returns the same DC back no matter how many times you call it. Now all you have to do is walk through the FunnyDraw function remembering that hdc1 and hdc2 are in fact the same thing.

    void FunnyDraw(HWND hwnd, HFONT hf1, HFONT hf2)
    {
     HDC hdc1 = GetDC(hwnd);
     HFONT hfPrev1 = SelectFont(hdc1, hf1);
     UINT taPrev1 = SetTextAlign(hdc1, TA_UPDATECP);
     MoveToEx(hdc1, 0, 0, NULL);
    

    So far, execution of the function is pretty normal.

     HDC hdc2 = GetDC(hwnd);
    

    Since the window is a CS_OWNDC window, the DC that is returned in hdc2 is the same one that was returned in hdc1. In other words, hdc1 == hdc2! Now things get exciting.

     HFONT hfPrev2 = SelectFont(hdc2, hf2);
    

    Since hdc1 == hdc2, what this really does is deselect the font hf1 from the DC and select the font hf2 instead.

     for (LPTSTR psz = TEXT("Hello"); *psz; psz++) {
      POINT pt;
      GetCurrentPositionEx(hdc1, &pt);
      TextOut(hdc2, pt.x, pt.y + 30, psz, 1);
      TextOut(hdc1, 0, 0, psz, 1);
     }
    

    Now this loop completely falls apart. At the first iteration, we retrieve the current position from the DC, which returns (0, 0) since we haven't moved it yet. We then draw the letter "H" at position (0, 30) into the second DC. But since the second DC is the same as the first one, what really happens is that we are calling TextOut into a DC that is in TA_UPDATECP mode. Thus, the coordinates are ignored, the letter "H" is displayed (in the second font), and the current position is updated to be after the "H". Finally, we draw the "H" into the first DC (which is the same as the second). We think we're drawing it with the first font, but in fact we're drawing with the second font. We think we're drawing at (0, 0), but in fact we're drawing at (x, 0), where x is the width of the letter "H", because the call to TextOut(hdc2, ...) updated the current position.

    Thus, each time through the loop, the next character in the string is displayed twice, all in the second font.

    But wait, the disaster isn't finished yet. Look at our cleanup code:

     SelectFont(hdc1, hfPrev1);
    

    This restores the original font into the DC.

     SelectFont(hdc2, hfPrev2);
    

    This re-selects the first font! We failed to restore the DC to its original state and ended up putting a "corrupted" DC into the cache.

    That's why I described CS_OWNDC as "worse". It takes code that used to work and breaks it by violating assumptions that most people make (usually without realizing it) about DCs.

    And you thought CS_OWNDC was bad. Next time I'll talk about the disaster that is known as CS_CLASSDC.

  • The Old New Thing

    Fumbling around in the dark and stumbling across the wrong solution

    • 68 Comments

    I don't mean to pick on this series of entries, but it illustrates an interesting pattern of stumbling across the wrong "solution".

    The series begins by attempting to trigger the system's monitor blank timeout by posting a message to the desktop window. As we saw earlier, the desktop window is a very special window and as a rule should be avoided, since it won't behave like windows created by applications. In particular, the author tried to post a message to the desktop window. This used to work in the historically open world of the window manager, but security and robustness concerns have come to take priority over compatibility. In Windows XP SP2, the desktop window resists being disabled because programs were doing it inadvertently, and it appears that the desktop also resists having messages posted to it. My guess is that this was done as a way to strengthen protection against shatter attacks. This did improve robustness and stability, but it also broke the article's dubious PostMessage hack.

    Enter round three, wherein the author fumbled around for other windows the monitor blank timeout message could be posted to, and eventually the author found that posting the message to the mysterious window HWND_TOPMOST = -1 seemed to do the trick.

    I knew in the back of my mind that people developed software this way, but the hopeful part of my brain continued to wish that it was merely taking place in a fantasy world. Making up intentionally invalid parameters and seeing what happens falls into the category of malicious goofing around, not in the realm of software engineering and design. Even if you find something that seems to work, you certainly wouldn't design a product around it!

    (Similarly, I've seen people ask questions like "What does message 49251 mean?" This is the reverse case: Seeing a made-up number and attempting to assign meaning to it. Message numbers starting at 0xC000 (decimal 49152) are messages registered via RegisterWindowMessage. The numerical value of the message associated with a registered window message is unpredictable and varies from desktop to desktop. The only guarantee is that it will remain consistent within a single desktop.)

    If you look more carefully at what the author stumbled across, you'll see that the "solution" is actually another bug. It so happens that the numerical value -1 for a window handle is suspiciously close to the value of HWND_BROADCAST:

    #define HWND_BROADCAST  ((HWND)0xffff)
    

    It so happens that internally, the window manager supports (HWND)-1 as an alternative value for HWND_BROADCAST. (I leave you to speculate why.) As a result, what the author actually is doing is broadcasting the monitor power-off message to all top-level windows! As we saw before, broadcasting messages is a very dangerous business, and in this case, the author is just lucky that all the windows on the desktop interpret the message the same way, that it is safe to process the message multiple times, and none of the windows perform any special filtering for that message. (Another author stumbled across the same incorrect "solution" but didn't provide any insight into the process by which the result was arrived at. Yet another author sort of found some issues but didn't quite put them all together.)

    For example, a presentation program might want to suppress monitor power-off when it is the foreground window by trapping the message and turning the monitor back on. If such a program happens to be running, broadcasting the power-off message to all top-level windows would turn off the monitor for all the windows that deferred to system default behavior, but when that presentation program received the message, it would turn the monitor back on. Now you're at the mercy of the order in which the windows process that broadcast message. When the presentation program processes the message, the monitor will turn back on, and if that program happens to be the last one to process the message (say, it got paged out and was slow to page back in), then the monitor will merely blink off and back on.

    The correct solution is not to post messages to random windows. If you want the message to go through window message default processing, create a window and process it yourself. Don't try to trick some other window (or in this case, hundreds of other windows simultaneously) into doing it for you.

  • The Old New Thing

    An auto-reset event is just a stupid semaphore

    • 31 Comments

    When you create an event with the CreateEvent function, you get to specify whether you want an auto-reset event or a manual-reset event.

    Manual-reset events are easy to understand: If the event is clear, then a wait on the event is not satisfied. If the event is set, then a wait on the event succeeds. Doesn't matter how many people are waiting for the event; they all behave the same way, and the state of the event is unaffected by how many people are waiting for it.

    Auto-reset events are more confusing. Probably the easiest way to think about them is as if they were semaphores with a maximum token count of one. If the event is clear, then a wait on the event is not satisfied. If the event is set, then one waiter succeeds and the event is reset; the other waiters keep waiting. (And from our discussion of PulseEvent, you already know that it is indeterminate which waiter will be released if there is more than one.)

    The gotcha with auto-reset events is the case where you set an event that is already set. Since an event has only two states (set and reset), setting an event that is already set has no effect. If you are using an event to control a resource producer/consumer model, then the "setting an event that is already set" case will result in you appearing to "lose" a token. Consider the following intended pattern:

    ProducerConsumer
    Wait
    Produce work
    SetEvent
    Wake up and reset event
    Do work
    Produce work
    Wait
    SetEvent
    Wake up and reset event
    Do work
    ... ...

    But what if the timing doesn't quite come out? What if the consumer thread is a little slow to do the work (or the producer thread is a little fast in generating it):

    ProducerConsumer
    Wait
    Produce work
    SetEvent
    Wake up and reset event
    Produce work
    SetEvent
    Do work
    Produce work
    SetEvent (has no effect)
    Wait satisfied immediately
    Reset event
    Do work
    Wait

    Notice that the producer produced three work items, but the consumer performed only two of them. The third SetEvent had no effect since the event was already set. (You have the same problem if you try to increase a semaphore's token count past its maximum.) If you want the number of wakes to match the number of sets, then you need to use a semaphore with a maximum token count as high as the maximum number of outstanding work items you will support.

    Moral of the story: Know your tools, know their limits, and use the right tool for the right job.

  • The Old New Thing

    A single-instance program is its own denial of service

    • 35 Comments

    There are many ways a program can establish itself as a single-instance program; I won't go into them here. But once people head down the path of a single-instance program, they bump into another issue in their security review: Denial of service.

    We are using a named mutex with a fixed name in order to detect whether another copy of the program is running. But that also means an attacker can create the mutex first, thereby preventing our program from running at all! How can I prevent this type of denial of service attack?

    If the attacker is running in the same security context as your program is (or would be) running in, then there is nothing you can do. Whatever "secret handshake" you come up with to determine whether another copy of your program is running, the attacker can mimic it. Since it is running in the correct security context, it can do anything that the "real" program can do.

    But look at the big picture: A single-instance program is its own denial of service! After all, the first instance of the program is preventing the second instance from running. Your program requirements are themselves a security vulnerability. Consequently, you cannot protect against yourself perfectly against a denial of service since a denial of service is what you want in the first place.

    What you can do is to understand and narrow the scope of your vulnerability. Clearly you can't protect yourself from an attacker running at the same security privilege, but you can still protect yourself against unprivileged attackers running at other security privileges. This means using securable objects as part of your handshake. Non-administrative users should not be able to prevent other users from running the program, for example. The worst thing that non-administrative users should be allowed to do is to make their own lives miserable.

  • The Old New Thing

    Coding in-place tooltips

    • 17 Comments

    Today we'll look at how to implement in-place tooltips. These are tooltips that appear when the user hovers the mouse over a string that cannot be displayed in its entirety. The tooltip overlays the partially-displayed text and provides the remainder of the text that had been truncated. The keys to this technique are the TTN_SHOW notification (which lets you adjust the positioning of a tooltip before it is shown) and the TTM_ADJUSTRECT message which tells you precisely where you need the tooltip to be.

    Start with our scratch program and add the following:

    HFONT g_hfTT;
    HWND g_hwndTT;
    RECT g_rcText;
    LPCTSTR g_pszText = TEXT("Lorem ipsum dolor sit amet.");
    const int c_xText = 50;
    const int c_yText = 50;
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
     g_hwndTT = CreateWindowEx(WS_EX_TRANSPARENT, TOOLTIPS_CLASS, NULL,
                               TTS_NOPREFIX,
                               0, 0, 0, 0,
                               hwnd, NULL, g_hinst, NULL);
     if (!g_hwndTT) return FALSE;
    
     g_hfTT = GetStockFont(ANSI_VAR_FONT);
     SetWindowFont(g_hwndTT, g_hfTT, FALSE);
    
     HDC hdc = GetDC(hwnd);
     HFONT hfPrev = SelectFont(hdc, g_hfTT);
     SIZE siz;
     GetTextExtentPoint(hdc, g_pszText, lstrlen(g_pszText), &siz);
     SetRect(&g_rcText, c_xText, c_yText,
                        c_xText + siz.cx, c_yText + siz.cy);
     SelectFont(hdc, hfPrev);
     ReleaseDC(hwnd, hdc);
    
     TOOLINFO ti = { sizeof(ti) };
     ti.uFlags = TTF_TRANSPARENT | TTF_SUBCLASS;
     ti.hwnd = hwnd;
     ti.uId = 0;
     ti.lpszText = const_cast<LPTSTR>(g_pszText);
     ti.rect = g_rcText;
     SendMessage(g_hwndTT, TTM_ADDTOOL, 0, (LPARAM)&ti);
    
     return TRUE;
    }
    
    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
     HFONT hfPrev = SelectFont(pps->hdc, g_hfTT);
     TextOut(pps->hdc, g_rcText.left, g_rcText.top,
             g_pszText, lstrlen(g_pszText));
     SelectFont(pps->hdc, hfPrev);
    }
    

    After declaring a few variables, we dig into our computations at window creation. We create the tooltip window, passing ourselves as the owner window. (Passing ourselves as the owner window is important in order to get proper Z-order behavior. I refer the reader to the fifth of my "Five Things Every Win32 Developer Should Know" topics for further details.) We then obtain our font and set it into the tooltip control so that the tooltip renders in the same font we do. (I'll take up more complex font manipulation in a future entry.) We then measure our text in the target font and set the g_rcText rectangle to the dimensions of that text. We use that rectangle to establish the boundaries of a tool in the tooltip control. By setting the TTF_SUBCLASS flag, we indicate that the tooltip control should subclass our window in order to intercept mouse messages. This is a convenience to avoid us having to use the TTM_RELAYEVENT message to forward the mouse messages manually. This hooks up the tooltip.

    Painting the content is a simple matter of selecting the font and drawing the text.

    Run this program and hover over the text. The tooltip appears, but it's in the wrong place. Aside from that, though, things are working as expected. The tooltip has the correct font, it fires only when the mouse is over the text itself, and it dismisses when the mouse leaves the text. Let's position the tooltip:

    LRESULT
    OnTooltipShow(HWND hwnd, NMHDR *pnm)
    {
     RECT rc = g_rcText;
     MapWindowRect(hwnd, NULL, &rc);
     SendMessage(pnm->hwndFrom, TTM_ADJUSTRECT, TRUE, (LPARAM)&rc);
     SetWindowPos(pnm->hwndFrom, 0, rc.left, rc.top, 0, 0,
       SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
     return TRUE; // suppress default positioning
    }
    
    LRESULT
    OnNotify(HWND hwnd, int idFrom, NMHDR *pnm)
    {
     if (pnm->hwndFrom == g_hwndTT) {
      switch (pnm->code) {
      case TTN_SHOW:
       return OnTooltipShow(hwnd, pnm);
      }
     }
     return 0;
    }
    
    // Add to WndProc
        HANDLE_MSG(hwnd, WM_NOTIFY, OnNotify);
    

    The TTN_SHOW notification is sent when the tooltip is about to be displayed. We respond to the notification by mapping the text rectangle to screen coordinates and using the TTM_ADJUSTRECT message to expand the rectangle to include all the margins and borders that the tooltip control will place around the text. That way, when we position the tooltip at that location, the margins and borders match up precisely, and the text appears at the desired location. It is important to return TRUE to indicate to the tooltip control that we took care of positioning the window and it should not do its default positioning.

    When you run this program, you will find one more problem: Tooltip animations are still taking place, which is particularly distracting if the animation is a slide animation. This is easy to fix: Tweak the way we create the tooltip control.

     g_hwndTT = CreateWindowEx(WS_EX_TRANSPARENT, TOOLTIPS_CLASS, NULL,
                               TTS_NOPREFIX | TTS_NOANIMATE,
                               0, 0, 0, 0,
                               hwnd, NULL, g_hinst, NULL);
    

    The TTS_NOANIMATE style suppress animations, which means that the tooltip simply pops into place, exactly what we want.

    So there you have it—the basics of in-place tooltips. Of course, there are many details you may wish to deal with, such as showing the tooltip only if the string is clipped. But those issues are independent of in-place tooltips, so I won't go into them here. We'll look at selected aspects of tooltips in future installments.

  • The Old New Thing

    Pitfalls of transparent rendering of anti-aliased fonts

    • 41 Comments

    Windows provides a variety of technologies for rendering monochrome text on color displays, taking advantage of display characteristics to provide smoother results. These include grayscale anti-aliasing as well as the more advanced ClearType technique. Both of these methods read from the background pixels to decide what pixels to draw in the foreground. This means that rendering text requires extra attention.

    If you draw text with an opaque background, there is no problem because you are explicitly drawing the background pixels as part of the text-drawing call, so the results are consistent regardless of what the previous background pixels were. But if you draw text with a transparent background, then you must make sure the background pixels that you draw against are the ones you really want.

    The most common way people mess this up is by drawing text multiple times. I've seen programs which draw text darker and darker the longer you use it. We'll see here how this can happen and what you need to do to avoid it. Start with the scratch program and make these changes:

    HFONT g_hfAntialias;
    HFONT g_hfClearType;
    
    BOOL
    OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
    {
     g_hfAntialias = CreateFont(-20, 0, 0, 0, FW_NORMAL, 0, 0, 0,
        DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        ANTIALIASED_QUALITY, DEFAULT_PITCH, TEXT("Tahoma"));
     g_hfClearType = CreateFont(-20, 0, 0, 0, FW_NORMAL, 0, 0, 0,
        DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
        CLEARTYPE_QUALITY, DEFAULT_PITCH, TEXT("Tahoma"));
     return g_hfAntialias && g_hfClearType;
    }
    
    void
    OnDestroy(HWND hwnd)
    {
     if (g_hfAntialias) DeleteObject(g_hfAntialias);
     if (g_hfClearType) DeleteObject(g_hfClearType);
     PostQuitMessage(0);
    }
    
    
    void MultiPaint(HDC hdc, int x, int y, int n)
    {
     LPCTSTR psz = TEXT("The quick brown fox jumps over the lazy dog.");
     int cch = lstrlen(psz);
     for (int i = 0; i < n; i++) {
       TextOut(hdc, x, y, psz, cch);
     }
    }
    
    void
    PaintContent(HWND hwnd, PAINTSTRUCT *pps)
    {
     int iModePrev = SetBkMode(pps->hdc, TRANSPARENT);
     HFONT hfPrev = SelectFont(pps->hdc, g_hfAntialias);
     MultiPaint(pps->hdc, 10,  0, 1);
     MultiPaint(pps->hdc, 10, 20, 2);
     MultiPaint(pps->hdc, 10, 40, 3);
     SelectFont(pps->hdc, g_hfClearType);
     MultiPaint(pps->hdc, 10, 80, 1);
     MultiPaint(pps->hdc, 10,100, 2);
     MultiPaint(pps->hdc, 10,120, 3);
     SelectFont(pps->hdc, hfPrev);
     SetBkMode(pps->hdc, iModePrev);
    }
    

    This program creates two fonts, one with anti-aliased (grayscale) quality and another with ClearType quality. (I have no idea why people claim that there is no thread-safe way to enable ClearType on an individual basis. We're doing it just fine here.)

    Run this program and take a close look at the results. Observe that in each set of three rows of text, the more times we overprint, the darker the text. In particular, notice that overprinting the anti-aliased font makes the result significantly uglier and uglier!

    What went wrong?

    The first time we drew the text, the background was a solid fill of the window background color. But when the text is drawn over itself, the background it sees is the previous text output. When the algorithm decides that "This pixel should be drawn by making the existing pixel 50% darker," it actually comes out 75% darker since the pixel is darkened twice. And if you draw it three times, the pixel comes out 88% darker.

    When you draw text, draw it exactly once, and draw it over the background you ultimately want. This allows the anti-aliasing and ClearType engines to perform their work with accurate information.

    The programs that darken the text are falling afoul of the overprinting problem. When the programs decide that some screen content needs to be redrawn (for example, if the focus rectangle needs to be added or removed), they "save time" by refraining from erasing the background and merely drawing the text again (but with/without the focus rectangle). Unfortunately, if you don't erase the background, then the text ends up drawn over a previous copy of itself, resulting in darkening.

    The solution is to draw text over the correct background. If you don't know what background is on the screen right now, then you need to erase it in order to set it to a known state. Otherwise, you will be blending text against an unknown quantity, which leads to inconsistent (and ugly) results.

    If you keep your eagle eyes open, you can often spot another case where people make the overprinting mistake: When text in a control (say, a check box) becomes darker and darker the more times you tab through it. This happens when programs don't pay close attention to the flags passed in the DRAWITEMSTRUCT that is passed to the WM_DRAWITEM message. For example, some people simply draw the entire item in response to the WM_DRAWITEM message, even though the window manager passed the ODA_FOCUS flag, indicating that you should only draw or erase the focus rectangle. This is not a problem if drawing the entire item includes erasing the background, but if you assume that the WM_ERASEBKGND message had erased the background, then you will end up overprinting your text in the case where you were asked only to draw the focus rectangle. In that case, the control is not erased; all you have to do is draw the focus rectangle. If you also draw the text, you are doing what the MultiPaint function did: Drawing text over text, and the result is text that gets darker each time it repaints.

  • The Old New Thing

    Understanding what significant digits really mean

    • 40 Comments

    A double-precision floating point number carries fifteen significant digits. What does this really mean?

    I multiplied 0.619207 by 10,000,000 and got 6192069.999999991 instead of 6192070. That's only six significant digits; where's my fifteen?

    Talking about significant digits is really just a shorthand for talking about relative precision. "Fifteen significant digits" means that the representational resolution is one part in 1015. It doesn't mean that the first fifteen digits are correct. (If you spend more time with numerical analysis, you can even see people talking about things like "five and a half significant digits". If the meaning of "significant digits" were literal, how could you have half a digit?)

    The relative error in the above computation is 9 / 6192070000000000 = 1.5 × 10-15, which is consistent with about fifteen significant digits. (And that's assuming the decimal representations are exact, which they aren't.)

    Even if you take a literalist interpretation of significant digits, the values are still good to fifteen digits. Remember that 0.99999... = 1, and therefore the values

    6192069.999999991
    6192069.999999999...

    agree to fifteen significant digits, as promised.

    Now, if you're a hyperliteralist and refuse to accept that 0.99999... = 1, then you are forced to accept that the only possible numbers of significant digits are zero and infinity. Consider a computation whose result is the value 1 exactly, and that the computation is performed to N significant digits (with N > 0). Since you do not accept that 0.9 agrees with 1.0 to even one significant digit, the only values that agree with 1.0 to at least one significant digit must be of the form "one point something". Let's call the result 1 + ε with 0 ≤ ε < 1. Now subtract this result from 2.0, yielding 1 − ε. Again, since you do not accept that 0.9 agrees with 1.0 to even one significant digit, in order for this result to be good to N significant digits (N > 0), the result must be of the form "one point something". Let's call that result 1 + δ with 0 ≤ δ < 1.

    Therefore, we have 1 − ε = 1 + δ and therefore, ε = −δ. Since both δ and ε are greater than or equal to zero, the only way for this equation to be satisfied is to have ε = δ = 0. Consequently, the only number which is equal to 1 to any nonzero number of significant digits (if you subscribe to the hyperliteral definition of significant digits) would be 1 itself. In other words, the only positive number of significant digits is infinity. And I think we all agree that if the only valid numbers of significant digits are zero and infinity, then the whole concept of significant digits would become kind of silly.

    That's why significant digits don't use the hyperliteralist definition.

  • The Old New Thing

    Why can't you say </script> in a script block?

    • 61 Comments

    Because it ends the script block, of course. Duh, what's so hard about that?

    Because if you have script that generates script, you'll find yourself caught out if you're not careful. For example, you can't say

    document.write("<SCRIPT>blahblah</SCRIPT>");
    

    in a script block because the HTML parser will see the </SCRIPT> and conclude that your script block is over. In other words, the script block extends as far as the highlighted section below:

    <SCRIPT>
    document.write("<SCRIPT>blahblah</SCRIPT>");
    </SCRIPT><!-- mismatched tag -->
    

    The parser doesn't understand "quoted strings" or "comments" or anything like that. It just looks for the nine characters "<", "/", "S", "C", "R", "I", "P", "T", and ">". When it sees them, it decides that the script block is over and returns to HTML parsing.

    Why doesn't the parser understand quoted string?

    Well, in order to parse quoted strings, you have to be able to parse comments:

    <SCRIPT>
    /* unmatched quotation mark " ignored since it's in a comment */
    </SCRIPT><!-- you might expect this to end the script block -->
    

    But every language has a different comment syntax. JScript uses /* ... */ and //, Visual Basic uses ', perl uses #, and so on. And even if you got comments figured out, you also would need to know how to parse quoted strings. Perl, for example, has a very large vocabulary for expressing quoted strings, from the simple "..." and '...' to the idiosyncratic qq:...:. And I lied about the JScript comment and quotation syntax; it's actually more complicated than I suggested:

    /"//"</SCRIPT>is this inside or outside quotes?
    

    That first quotation mark is itself quoted and does not count as a "beginning of quoted string" marker. And the // sequence is not a comment marker. The first slash in the // sequence ends the regular expression, and the second is a division operator.

    It would be unreasonable to expect the HTML parser to be able to understand every language both present and future. (At least not until clairvoyance has been perfected.)

    <SCRIPT>
    'is this a quoted string?'</SCRIPT>
    Is this inside or outside the script block?
    '<SCRIPT>' is this a new script block
    or the continuation of the previous one?
    </SCRIPT>
    

    One "solution" would be to require all languages to conform to one of a fixed number of quotation and comment syntaxes. Nevermind that not even JScript conforms to the basic syntax, as we saw above, thanks to the complicated quotation rules implied by regular expression shorthand. And do you really want all HTML parsers to understand perl?

    Another "solution" would be to have the language processor do the parsing and tell the HTML parser where the </SCRIPT> tag is. This has its own problems, however. First, it means that the HTML parser would still have to load the language parser even for DEFER script blocks, which sort of defeats one of the purposes of DEFER. Even worse, it means that a web page that used a language that the system didn't support would become unparseable:

    <SCRIPT LANG="unknown-language">
    Lorem ipsum dolor sit amet,
    ...
    

    If a language parser were required to locate the end of the script block, it would be impossible to parse past this point.

    So how do you work around this aspect of HTML parsing? You have to find an alternate way of expressing the string you want. Typically, this is done by breaking in up into two strings that you then reassemble:

    document.write("<SCRIPT>blahblah</SCRI"+"PT>");
    
  • The Old New Thing

    The subtle usability considerations of conference nametags

    • 44 Comments

    When you go to a conference or some other event where everybody wears a nametag, pay closer attention to the nametag design. There are many subtle usability mistakes that I see far too often.

    First of all, is your name easy to read? It's called a nametag, after all; the name and affiliation of the wearer should be the most prominent thing on the tag. I've been to events where the most prominent thing on the nametag was the name of the conference itself. Hey, everybody presumably already knows what conference they're attending. It's printed on the agenda sheet, it's printed on the tote bag, it's printed on every sign at the venue, it's even printed on the pens you gave out, for goodness' sake. Tell them something they don't know: Who they are talking to. (Corollary: Don't put the name at the bottom of the tag.)

    Okay, now that you've got the name on the nametag in a position and style where it actually serves its purpose, you have to make sure the tag is visible when worn. Most computer events use a lanyard-style nametag. If the lanyard length is not adjustable, then you have a new problem: You have to make the cord long enough to go around the wearer's head. But once you do that, the cord is now so long that the nametag itself hangs around the wearer's belly-button. This is already awkward enough, but if the conference entails sit-down meetings, the nametag will end up into the wearer's lap. And if you have the meetings at tables, the nametag will disappear beneath the surface of table. A nametag that you can't see isn't doing its job.

    (Diagrams require a VML-enabled browser.)

    Can flip

    Stable

    Great, you have a name on the nametag that people can see, you are keeping the tag visible, you think you're home free. But wait, how is your nametag mounted to the lanyard? Nearly all lanyard nametags I've seen are mounted from a single clip or hole at the top center. With this design, the nametag can easily flip around, pushing the person's name into their chest and showing the nametag's backside to the rest of the world. One solution to this problem is to make the nametag reversible, so that even if it flips, the name is still visible. Another solution is to mount the nametag from two holes, one in each top corner. In this manner, the nametag becomes flip-resistant.


    Just a few little details in nametag design. But you'd be surprised how many people miss them. (The PDC nametags are the only one in recent memory that addressed all three problems.)

Page 1 of 4 (34 items) 1234