Fix segfault in general_textout with variable-pitch fonts: we can't
[u/mdw/putty] / windows / window.c
index b00865a..9060bd1 100644 (file)
@@ -99,7 +99,7 @@ static int process_clipdata(HGLOBAL clipdata, int unicode);
 /* Window layout information */
 static void reset_window(int);
 static int extra_width, extra_height;
-static int font_width, font_height, font_dualwidth;
+static int font_width, font_height, font_dualwidth, font_varpitch;
 static int offset_width, offset_height;
 static int was_zoomed = 0;
 static int prev_rows, prev_cols;
@@ -317,6 +317,7 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 {
     WNDCLASS wndclass;
     MSG msg;
+    HRESULT hr;
     int guess_width, guess_height;
 
     hinst = inst;
@@ -355,6 +356,18 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
     init_flashwindow();
 
     /*
+     * Initialize COM.
+     */
+    hr = CoInitialize(NULL);
+    if (hr != S_OK && hr != S_FALSE) {
+        char *str = dupprintf("%s Fatal Error", appname);
+       MessageBox(NULL, "Failed to initialize COM subsystem",
+                  str, MB_OK | MB_ICONEXCLAMATION);
+       sfree(str);
+       return 1;
+    }
+
+    /*
      * Process the command line.
      */
     {
@@ -381,14 +394,21 @@ int WINAPI WinMain(HINSTANCE inst, HINSTANCE prev, LPSTR cmdline, int show)
 
        /*
         * Process a couple of command-line options which are more
-        * easily dealt with before the line is broken up into
-        * words. These are the soon-to-be-defunct @sessionname and
-        * the internal-use-only &sharedmemoryhandle, neither of
-        * which are combined with anything else.
+        * easily dealt with before the line is broken up into words.
+        * These are the old-fashioned but convenient @sessionname and
+        * the internal-use-only &sharedmemoryhandle, neither of which
+        * are combined with anything else.
         */
        while (*p && isspace(*p))
            p++;
        if (*p == '@') {
+            /*
+             * An initial @ means that the whole of the rest of the
+             * command line should be treated as the name of a saved
+             * session, with _no quoting or escaping_. This makes it a
+             * very convenient means of automated saved-session
+             * launching, via IDM_SAVEDSESS or Windows 7 jump lists.
+             */
            int i = strlen(p);
            while (i > 1 && isspace(p[i - 1]))
                i--;
@@ -867,6 +887,9 @@ void cleanup_exit(int code)
     }
     shutdown_help();
 
+    /* Clean up COM. */
+    CoUninitialize();
+
     exit(code);
 }
 
@@ -1237,7 +1260,6 @@ static void exact_textout(HDC hdc, int x, int y, CONST RECT *lprc,
     gcpr.lpGlyphs = (void *)buffer;
     gcpr.lpClass = (void *)classbuffer;
     gcpr.nGlyphs = cbCount;
-
     GetCharacterPlacementW(hdc, lpString, cbCount, 0, &gcpr,
                           FLI_MASK | GCP_CLASSIN | GCP_DIACRITIC);
 
@@ -1301,7 +1323,8 @@ for(k=0;k<j-i;k++)debug((" U+%04X", lpString[i+k]));
 debug(("\n           rect: [%d,%d %d,%d]\n", newrc.left, newrc.top, newrc.right, newrc.bottom));
 }
 #endif
-           exact_textout(hdc, xp, y, &newrc, lpString+i, j-i, lpDx+i, opaque);
+           exact_textout(hdc, xp, y, &newrc, lpString+i, j-i,
+                          font_varpitch ? NULL : lpDx+i, opaque);
        } else {
 #ifdef FIXME_REMOVE_BEFORE_CHECKIN
 {
@@ -1316,7 +1339,8 @@ debug(("\n           rect: [%d,%d %d,%d]\n", newrc.left, newrc.top, newrc.right,
            newrc.top = lprc->top;
            newrc.bottom = lprc->bottom;
            ExtTextOutW(hdc, xp, y, ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
-                       &newrc, lpString+i, j-i, lpDx+i);
+                       &newrc, lpString+i, j-i,
+                        font_varpitch ? NULL : lpDx+i);
        }
 
        i = j;
@@ -1329,6 +1353,37 @@ debug(("general_textout: done, xn=%d\n", xn));
     assert(xn - x >= lprc->right - lprc->left);
 }
 
+static int get_font_width(HDC hdc, const TEXTMETRIC *tm)
+{
+    int ret;
+    /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
+    if (!(tm->tmPitchAndFamily & TMPF_FIXED_PITCH)) {
+        ret = tm->tmAveCharWidth;
+    } else {
+#define FIRST '0'
+#define LAST '9'
+        ABCFLOAT widths[LAST-FIRST + 1];
+        int j;
+
+        font_varpitch = TRUE;
+        font_dualwidth = TRUE;
+        if (GetCharABCWidthsFloat(hdc, FIRST, LAST, widths)) {
+            ret = 0;
+            for (j = 0; j < lenof(widths); j++) {
+                int width = (int)(0.5 + widths[j].abcfA +
+                                  widths[j].abcfB + widths[j].abcfC);
+                if (ret < width)
+                    ret = width;
+            }
+        } else {
+            ret = tm->tmMaxCharWidth;
+        }
+#undef FIRST
+#undef LAST
+    }
+    return ret;
+}
+
 /*
  * Initialise all the fonts we will need initially. There may be as many as
  * three or as few as one.  The other (potentially) twenty-one fonts are done
@@ -1395,11 +1450,18 @@ static void init_fonts(int pick_width, int pick_height)
 
     GetObject(fonts[FONT_NORMAL], sizeof(LOGFONT), &lfont);
 
+    /* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
+    if (!(tm.tmPitchAndFamily & TMPF_FIXED_PITCH)) {
+        font_varpitch = FALSE;
+        font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
+    } else {
+        font_varpitch = TRUE;
+        font_dualwidth = TRUE;
+    }
     if (pick_width == 0 || pick_height == 0) {
        font_height = tm.tmHeight;
-       font_width = tm.tmAveCharWidth;
+        font_width = get_font_width(hdc, &tm);
     }
-    font_dualwidth = (tm.tmAveCharWidth != tm.tmMaxCharWidth);
 
 #ifdef RDB_DEBUG_PATCH
     debug(23, "Primary font H=%d, AW=%d, MW=%d",
@@ -1486,7 +1548,7 @@ static void init_fonts(int pick_width, int pick_height)
     for (i = 0; i < 3; i++) {
        if (fonts[i]) {
            if (SelectObject(hdc, fonts[i]) && GetTextMetrics(hdc, &tm))
-               fontsize[i] = tm.tmAveCharWidth + 256 * tm.tmHeight;
+               fontsize[i] = get_font_width(hdc, &tm) + 256 * tm.tmHeight;
            else
                fontsize[i] = -i;
        } else
@@ -1555,7 +1617,7 @@ static void another_font(int fontno)
        CreateFont(font_height * (1 + !!(fontno & FONT_HIGH)), x, 0, 0, w,
                   FALSE, u, FALSE, c, OUT_DEFAULT_PRECIS,
                   CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg.font_quality),
-                  FIXED_PITCH | FF_DONTCARE, s);
+                  DEFAULT_PITCH | FF_DONTCARE, s);
 
     fontflag[fontno] = 1;
 }
@@ -1950,6 +2012,7 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
     static int ignore_clip = FALSE;
     static int need_backend_resize = FALSE;
     static int fullscr_on_max = FALSE;
+    static int processed_resize = FALSE;
     static UINT last_mousemove = 0;
 
     switch (message) {
@@ -2044,7 +2107,6 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                                           / MENU_SAVED_STEP) + 1;
                    if (sessno < (unsigned)sesslist.nsessions) {
                        char *session = sesslist.sessions[sessno];
-                       /* XXX spaces? quotes? "-load"? */
                        cl = dupprintf("putty @%s", session);
                        inherit_handles = FALSE;
                        freecl = TRUE;
@@ -2627,7 +2689,8 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
         * 1) Keep the sizetip uptodate
         * 2) Make sure the window size is _stepped_ in units of the font size.
         */
-       if (cfg.resize_action != RESIZE_FONT && !is_alt_pressed()) {
+        if (cfg.resize_action == RESIZE_TERM ||
+            (cfg.resize_action == RESIZE_EITHER && !is_alt_pressed())) {
            int width, height, w, h, ew, eh;
            LPRECT r = (LPRECT) lParam;
 
@@ -2733,13 +2796,36 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                          cfg.win_name_always ? window_name : icon_name);
        if (wParam == SIZE_RESTORED || wParam == SIZE_MAXIMIZED)
            SetWindowText(hwnd, window_name);
-        if (wParam == SIZE_RESTORED)
+        if (wParam == SIZE_RESTORED) {
+            processed_resize = FALSE;
             clear_full_screen();
+            if (processed_resize) {
+                /*
+                 * Inhibit normal processing of this WM_SIZE; a
+                 * secondary one was triggered just now by
+                 * clear_full_screen which contained the correct
+                 * client area size.
+                 */
+                return 0;
+            }
+        }
         if (wParam == SIZE_MAXIMIZED && fullscr_on_max) {
             fullscr_on_max = FALSE;
+            processed_resize = FALSE;
             make_full_screen();
+            if (processed_resize) {
+                /*
+                 * Inhibit normal processing of this WM_SIZE; a
+                 * secondary one was triggered just now by
+                 * make_full_screen which contained the correct client
+                 * area size.
+                 */
+                return 0;
+            }
         }
 
+        processed_resize = TRUE;
+
        if (cfg.resize_action == RESIZE_DISABLED) {
            /* A resize, well it better be a minimize. */
            reset_window(-1);
@@ -2765,15 +2851,22 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT message,
                 reset_window(0);
             } else if (wParam == SIZE_RESTORED && was_zoomed) {
                 was_zoomed = 0;
-                if (cfg.resize_action == RESIZE_TERM)
-                    term_size(term, prev_rows, prev_cols, cfg.savelines);
-                if (cfg.resize_action != RESIZE_FONT)
+                if (cfg.resize_action == RESIZE_TERM) {
+                    w = (width-cfg.window_border*2) / font_width;
+                    if (w < 1) w = 1;
+                    h = (height-cfg.window_border*2) / font_height;
+                    if (h < 1) h = 1;
+                    term_size(term, h, w, cfg.savelines);
+                    reset_window(2);
+                } else if (cfg.resize_action != RESIZE_FONT)
                     reset_window(2);
                 else
                     reset_window(0);
             } else if (wParam == SIZE_MINIMIZED) {
                 /* do nothing */
-           } else if (cfg.resize_action != RESIZE_FONT && !is_alt_pressed()) {
+           } else if (cfg.resize_action == RESIZE_TERM ||
+                       (cfg.resize_action == RESIZE_EITHER &&
+                        !is_alt_pressed())) {
                 w = (width-cfg.window_border*2) / font_width;
                 if (w < 1) w = 1;
                 h = (height-cfg.window_border*2) / font_height;
@@ -3144,7 +3237,10 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
     int force_manual_underline = 0;
     int fnt_width, char_width;
     int text_adjust = 0;
+    int xoffset = 0;
+    int maxlen, remaining, opaque;
     static int *IpDx = 0, IpDxLEN = 0;
+    int *IpDxReal;
 
     lattr &= LATTR_MODE;
 
@@ -3284,111 +3380,155 @@ void do_text_internal(Context ctx, int x, int y, wchar_t *text, int len,
     if (line_box.right > font_width*term->cols+offset_width)
        line_box.right = font_width*term->cols+offset_width;
 
-    /* We're using a private area for direct to font. (512 chars.) */
-    if (ucsdata.dbcs_screenfont && (text[0] & CSET_MASK) == CSET_ACP) {
-       /* Ho Hum, dbcs fonts are a PITA! */
-       /* To display on W9x I have to convert to UCS */
-       static wchar_t *uni_buf = 0;
-       static int uni_len = 0;
-       int nlen, mptr;
-       if (len > uni_len) {
-           sfree(uni_buf);
-           uni_len = len;
-           uni_buf = snewn(uni_len, wchar_t);
-       }
-
-       for(nlen = mptr = 0; mptr<len; mptr++) {
-           uni_buf[nlen] = 0xFFFD;
-           if (IsDBCSLeadByteEx(ucsdata.font_codepage, (BYTE) text[mptr])) {
-               char dbcstext[2];
-               dbcstext[0] = text[mptr] & 0xFF;
-               dbcstext[1] = text[mptr+1] & 0xFF;
-               IpDx[nlen] += char_width;
-               MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
-                                   dbcstext, 2, uni_buf+nlen, 1);
-               mptr++;
-           }
-           else
-           {
-               char dbcstext[1];
-               dbcstext[0] = text[mptr] & 0xFF;
-               MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
-                                   dbcstext, 1, uni_buf+nlen, 1);
-           }
-           nlen++;
-       }
-       if (nlen <= 0)
-           return;                    /* Eeek! */
-
-       ExtTextOutW(hdc, x,
-                   y - font_height * (lattr == LATTR_BOT) + text_adjust,
-                   ETO_CLIPPED | ETO_OPAQUE, &line_box, uni_buf, nlen, IpDx);
-       if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
-           SetBkMode(hdc, TRANSPARENT);
-           ExtTextOutW(hdc, x - 1,
-                       y - font_height * (lattr ==
-                                          LATTR_BOT) + text_adjust,
-                       ETO_CLIPPED, &line_box, uni_buf, nlen, IpDx);
-       }
+    if (font_varpitch) {
+        /*
+         * If we're using a variable-pitch font, we unconditionally
+         * draw the glyphs one at a time and centre them in their
+         * character cells (which means in particular that we must
+         * disable the IpDx mechanism). This gives slightly odd but
+         * generally reasonable results.
+         */
+        xoffset = char_width / 2;
+        SetTextAlign(hdc, TA_TOP | TA_CENTER | TA_NOUPDATECP);
+        IpDxReal = NULL;
+        maxlen = 1;
+    } else {
+        /*
+         * In a fixed-pitch font, we draw the whole string in one go
+         * in the normal way.
+         */
+        xoffset = 0;
+        SetTextAlign(hdc, TA_TOP | TA_LEFT | TA_NOUPDATECP);
+        IpDxReal = IpDx;
+        maxlen = len;
+    }
 
-       IpDx[0] = -1;
-    } else if (DIRECT_FONT(text[0])) {
-       static char *directbuf = NULL;
-       static int directlen = 0;
-       int i;
-       if (len > directlen) {
-           directlen = len;
-           directbuf = sresize(directbuf, directlen, char);
-       }
+    opaque = TRUE;                     /* start by erasing the rectangle */
+    for (remaining = len; remaining > 0;
+         text += len, remaining -= len, x += char_width * len) {
+        len = (maxlen < remaining ? maxlen : remaining);
+
+        /* We're using a private area for direct to font. (512 chars.) */
+        if (ucsdata.dbcs_screenfont && (text[0] & CSET_MASK) == CSET_ACP) {
+            /* Ho Hum, dbcs fonts are a PITA! */
+            /* To display on W9x I have to convert to UCS */
+            static wchar_t *uni_buf = 0;
+            static int uni_len = 0;
+            int nlen, mptr;
+            if (len > uni_len) {
+                sfree(uni_buf);
+                uni_len = len;
+                uni_buf = snewn(uni_len, wchar_t);
+            }
 
-       for (i = 0; i < len; i++)
-           directbuf[i] = text[i] & 0xFF;
+            for(nlen = mptr = 0; mptr<len; mptr++) {
+                uni_buf[nlen] = 0xFFFD;
+                if (IsDBCSLeadByteEx(ucsdata.font_codepage,
+                                     (BYTE) text[mptr])) {
+                    char dbcstext[2];
+                    dbcstext[0] = text[mptr] & 0xFF;
+                    dbcstext[1] = text[mptr+1] & 0xFF;
+                    IpDx[nlen] += char_width;
+                    MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
+                                        dbcstext, 2, uni_buf+nlen, 1);
+                    mptr++;
+                }
+                else
+                {
+                    char dbcstext[1];
+                    dbcstext[0] = text[mptr] & 0xFF;
+                    MultiByteToWideChar(ucsdata.font_codepage, MB_USEGLYPHCHARS,
+                                        dbcstext, 1, uni_buf+nlen, 1);
+                }
+                nlen++;
+            }
+            if (nlen <= 0)
+                return;                       /* Eeek! */
+
+            ExtTextOutW(hdc, x + xoffset,
+                        y - font_height * (lattr == LATTR_BOT) + text_adjust,
+                        ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+                        &line_box, uni_buf, nlen,
+                        IpDxReal);
+            if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
+                SetBkMode(hdc, TRANSPARENT);
+                ExtTextOutW(hdc, x + xoffset - 1,
+                            y - font_height * (lattr ==
+                                               LATTR_BOT) + text_adjust,
+                            ETO_CLIPPED, &line_box, uni_buf, nlen, IpDxReal);
+            }
 
-       ExtTextOut(hdc, x,
-                  y - font_height * (lattr == LATTR_BOT) + text_adjust,
-                  ETO_CLIPPED | ETO_OPAQUE, &line_box, directbuf, len, IpDx);
-       if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
-           SetBkMode(hdc, TRANSPARENT);
+            IpDx[0] = -1;
+        } else if (DIRECT_FONT(text[0])) {
+            static char *directbuf = NULL;
+            static int directlen = 0;
+            int i;
+            if (len > directlen) {
+                directlen = len;
+                directbuf = sresize(directbuf, directlen, char);
+            }
 
-           /* GRR: This draws the character outside it's box and can leave
-            * 'droppings' even with the clip box! I suppose I could loop it
-            * one character at a time ... yuk. 
-            * 
-            * Or ... I could do a test print with "W", and use +1 or -1 for this
-            * shift depending on if the leftmost column is blank...
-            */
-           ExtTextOut(hdc, x - 1,
-                      y - font_height * (lattr ==
-                                         LATTR_BOT) + text_adjust,
-                      ETO_CLIPPED, &line_box, directbuf, len, IpDx);
-       }
-    } else {
-       /* And 'normal' unicode characters */
-       static WCHAR *wbuf = NULL;
-       static int wlen = 0;
-       int i;
+            for (i = 0; i < len; i++)
+                directbuf[i] = text[i] & 0xFF;
+
+            ExtTextOut(hdc, x + xoffset,
+                       y - font_height * (lattr == LATTR_BOT) + text_adjust,
+                       ETO_CLIPPED | (opaque ? ETO_OPAQUE : 0),
+                       &line_box, directbuf, len, IpDxReal);
+            if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
+                SetBkMode(hdc, TRANSPARENT);
+
+                /* GRR: This draws the character outside its box and
+                 * can leave 'droppings' even with the clip box! I
+                 * suppose I could loop it one character at a time ...
+                 * yuk.
+                 * 
+                 * Or ... I could do a test print with "W", and use +1
+                 * or -1 for this shift depending on if the leftmost
+                 * column is blank...
+                 */
+                ExtTextOut(hdc, x + xoffset - 1,
+                           y - font_height * (lattr ==
+                                              LATTR_BOT) + text_adjust,
+                           ETO_CLIPPED, &line_box, directbuf, len, IpDxReal);
+            }
+        } else {
+            /* And 'normal' unicode characters */
+            static WCHAR *wbuf = NULL;
+            static int wlen = 0;
+            int i;
+
+            if (wlen < len) {
+                sfree(wbuf);
+                wlen = len;
+                wbuf = snewn(wlen, WCHAR);
+            }
 
-       if (wlen < len) {
-           sfree(wbuf);
-           wlen = len;
-           wbuf = snewn(wlen, WCHAR);
-       }
+            for (i = 0; i < len; i++)
+                wbuf[i] = text[i];
+
+            /* print Glyphs as they are, without Windows' Shaping*/
+            general_textout(hdc, x + xoffset,
+                            y - font_height * (lattr==LATTR_BOT) + text_adjust,
+                            &line_box, wbuf, len, IpDx,
+                            opaque && !(attr & TATTR_COMBINING));
+
+            /* And the shadow bold hack. */
+            if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
+                SetBkMode(hdc, TRANSPARENT);
+                ExtTextOutW(hdc, x + xoffset - 1,
+                            y - font_height * (lattr ==
+                                               LATTR_BOT) + text_adjust,
+                            ETO_CLIPPED, &line_box, wbuf, len, IpDxReal);
+            }
+        }
 
-       for (i = 0; i < len; i++)
-           wbuf[i] = text[i];
-
-       /* print Glyphs as they are, without Windows' Shaping*/
-       general_textout(hdc, x, y - font_height * (lattr == LATTR_BOT) + text_adjust,
-                       &line_box, wbuf, len, IpDx, !(attr & TATTR_COMBINING));
-
-       /* And the shadow bold hack. */
-       if (bold_mode == BOLD_SHADOW && (attr & ATTR_BOLD)) {
-           SetBkMode(hdc, TRANSPARENT);
-           ExtTextOutW(hdc, x - 1,
-                       y - font_height * (lattr ==
-                                          LATTR_BOT) + text_adjust,
-                       ETO_CLIPPED, &line_box, wbuf, len, IpDx);
-       }
+        /*
+         * If we're looping round again, stop erasing the background
+         * rectangle.
+         */
+        SetBkMode(hdc, TRANSPARENT);
+        opaque = FALSE;
     }
     if (lattr != LATTR_TOP && (force_manual_underline ||
                               (und_mode == UND_LINE