1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-25 01:02:24 +00:00

Support for using variable-pitch fonts for the terminal on Windows.

Done in much the same way as it is in the GTK front end: the character
cell width is determined using the font's digits (which seems to give
generally not-too-offensive spacing in most cases, at the expense of
Ms and Ws typically overhanging a bit into adjacent cells) and each
character is centred in its cell. Overhangs never leave permanent
droppings on the window, because the existing work done in r5003
handles them just fine even in this stressful scenario.

There's a hacky new checkbox in the Appearance panel to make
variable-pitch fonts appear in the font selector (they still don't by
default, because I still think it's _usually_ not What You Want); the
checkbox state is not actually stored as part of a saved session, but
it should be automatically ticked when reloading a session that's got
a variable pitch font selected.

(I'm half-expecting a potential flurry of requests for this feature in
the wake of http://xkcd.com/840/ , so I thought I'd pre-empt them :-)

[originally from svn r9063]
[r5003 == ba470dec5e]
This commit is contained in:
Simon Tatham 2010-12-29 14:11:25 +00:00
parent 7dab9e5573
commit 00b32eda3c
6 changed files with 274 additions and 112 deletions

View File

@ -1096,10 +1096,15 @@ works in any of the cursor modes.
\cfg{winhelp-topic}{appearance.font}
This option allows you to choose what font, in what \I{font size}size,
the PuTTY terminal window uses to display the text in the session. You
will be offered a choice from all the fixed-width fonts installed on the
system. (VT100-style terminal handling can only deal with fixed-width
fonts.)
the PuTTY terminal window uses to display the text in the session.
By default, you will be offered a choice from all the fixed-width
fonts installed on the system, since VT100-style terminal handling
expects a fixed-width font. If you tick the box marked \q{Allow
selection of variable-pitch fonts}, however, PuTTY will offer
variable-width fonts as well: if you select one of these, the font
will be coerced into fixed-size character cells, which will probably
not look very good (but can work OK with some fonts).
\S{config-mouseptr} \q{Hide \i{mouse pointer} when typing in window}

View File

@ -30,6 +30,16 @@ static void help_handler(union control *ctrl, void *dlg,
}
}
static void variable_pitch_handler(union control *ctrl, void *dlg,
void *data, int event)
{
if (event == EVENT_REFRESH) {
dlg_checkbox_set(ctrl, dlg, !dlg_get_fixed_pitch_flag(dlg));
} else if (event == EVENT_VALCHANGE) {
dlg_set_fixed_pitch_flag(dlg, !dlg_checkbox_get(ctrl, dlg));
}
}
void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help,
int midsession, int protocol)
{
@ -177,6 +187,8 @@ void win_setup_config_box(struct controlbox *b, HWND *hwndp, int has_help,
*/
s = ctrl_getset(b, "Window/Appearance", "font",
"Font settings");
ctrl_checkbox(s, "Allow selection of variable-pitch fonts", NO_SHORTCUT,
HELPCTX(appearance_font), variable_pitch_handler, I(0));
ctrl_radiobuttons(s, "Font quality:", 'q', 2,
HELPCTX(appearance_font),
dlg_stdradiobutton_handler,

View File

@ -1955,8 +1955,8 @@ int winctrl_handle_command(struct dlgparam *dp, UINT msg,
cf.lStructSize = sizeof(cf);
cf.hwndOwner = dp->hwnd;
cf.lpLogFont = &lf;
cf.Flags = CF_FIXEDPITCHONLY | CF_FORCEFONTEXIST |
CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
cf.Flags = (dp->fixed_pitch_fonts ? CF_FIXEDPITCHONLY : 0) |
CF_FORCEFONTEXIST | CF_INITTOLOGFONTSTRUCT | CF_SCREENFONTS;
if (ChooseFont(&cf)) {
strncpy(fs.name, lf.lfFaceName,
@ -2321,6 +2321,8 @@ void dlg_fontsel_set(union control *ctrl, void *dlg, FontSpec fs)
(fs.height < 0 ? "pixel" : "point"));
SetDlgItemText(dp->hwnd, c->base_id+1, buf);
sfree(buf);
dlg_auto_set_fixed_pitch_flag(dp);
}
void dlg_fontsel_get(union control *ctrl, void *dlg, FontSpec *fs)
@ -2466,6 +2468,57 @@ int dlg_coloursel_results(union control *ctrl, void *dlg,
return 0;
}
void dlg_auto_set_fixed_pitch_flag(void *dlg)
{
struct dlgparam *dp = (struct dlgparam *)dlg;
Config *cfg = (Config *)dp->data;
HFONT font;
HDC hdc;
TEXTMETRIC tm;
int is_var;
/*
* Attempt to load the current font, and see if it's
* variable-pitch. If so, start off the fixed-pitch flag for the
* dialog box as false.
*
* We assume here that any client of the dlg_* mechanism which is
* using font selectors at all is also using a normal 'Config *'
* as dp->data.
*/
font = CreateFont(0, 0, 0, 0, FW_DONTCARE, FALSE, FALSE, FALSE,
DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
CLIP_DEFAULT_PRECIS, FONT_QUALITY(cfg->font_quality),
FIXED_PITCH | FF_DONTCARE, cfg->font.name);
hdc = GetDC(NULL);
if (font && hdc && SelectObject(hdc, font) && GetTextMetrics(hdc, &tm)) {
/* Note that the TMPF_FIXED_PITCH bit is defined upside down :-( */
is_var = (tm.tmPitchAndFamily & TMPF_FIXED_PITCH);
} else {
is_var = FALSE; /* assume it's basically normal */
}
if (hdc)
ReleaseDC(NULL, hdc);
if (font)
DeleteObject(font);
if (is_var)
dp->fixed_pitch_fonts = FALSE;
}
int dlg_get_fixed_pitch_flag(void *dlg)
{
struct dlgparam *dp = (struct dlgparam *)dlg;
return dp->fixed_pitch_fonts;
}
void dlg_set_fixed_pitch_flag(void *dlg, int flag)
{
struct dlgparam *dp = (struct dlgparam *)dlg;
dp->fixed_pitch_fonts = flag;
}
struct perctrl_privdata {
union control *ctrl;
void *data;
@ -2493,6 +2546,7 @@ void dp_init(struct dlgparam *dp)
dp->hwnd = NULL;
dp->wintitle = dp->errtitle = NULL;
dp->privdata = newtree234(perctrl_privdata_cmp);
dp->fixed_pitch_fonts = TRUE;
}
void dp_add_tree(struct dlgparam *dp, struct winctrls *wc)

View File

@ -649,6 +649,7 @@ int do_config(void)
dp.wintitle = dupprintf("%s Configuration", appname);
dp.errtitle = dupprintf("%s Error", appname);
dp.data = &cfg;
dlg_auto_set_fixed_pitch_flag(&dp);
dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
ret =
@ -682,6 +683,7 @@ int do_reconfig(HWND hwnd, int protcfginfo)
dp.wintitle = dupprintf("%s Reconfiguration", appname);
dp.errtitle = dupprintf("%s Error", appname);
dp.data = &cfg;
dlg_auto_set_fixed_pitch_flag(&dp);
dp.shortcuts['g'] = TRUE; /* the treeview: `Cate&gory' */
ret = SaneDialogBox(hinst, MAKEINTRESOURCE(IDD_MAINBOX), NULL,

View 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;
@ -1260,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);
@ -1352,6 +1351,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
@ -1418,11 +1448,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",
@ -1509,7 +1546,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
@ -1578,7 +1615,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;
}
@ -3198,7 +3235,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;
@ -3338,111 +3378,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);
}
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);
}
for (i = 0; i < len; i++)
directbuf[i] = text[i] & 0xFF;
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);
/* 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 - 1,
y - font_height * (lattr ==
LATTR_BOT) + text_adjust,
ETO_CLIPPED, &line_box, directbuf, len, 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 {
/* And 'normal' unicode characters */
static WCHAR *wbuf = NULL;
static int wlen = 0;
int i;
/*
* 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;
}
if (wlen < len) {
sfree(wbuf);
wlen = len;
wbuf = snewn(wlen, WCHAR);
}
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);
for (i = 0; i < len; i++)
wbuf[i] = text[i];
/* 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);
}
/* 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));
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! */
/* 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);
}
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);
}
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);
}
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);
}
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, IpDxReal,
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);
}
}
/*
* 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

View File

@ -316,6 +316,7 @@ struct dlgparam {
struct { unsigned char r, g, b, ok; } coloursel_result; /* 0-255 */
tree234 *privdata; /* stores per-control private data */
int ended, endresult; /* has the dialog been ended? */
int fixed_pitch_fonts; /* are we constrained to fixed fonts? */
};
/*
@ -374,6 +375,10 @@ void fwdsetter(struct ctlpos *cp, int listid, char *stext, int sid,
char *btext, int bid,
char *r1text, int r1id, char *r2text, int r2id);
void dlg_auto_set_fixed_pitch_flag(void *dlg);
int dlg_get_fixed_pitch_flag(void *dlg);
void dlg_set_fixed_pitch_flag(void *dlg, int flag);
#define MAX_SHORTCUTS_PER_CTRL 16
/*