Fiddling with the fonts, part 1: Making the Chinese characters larger


Let's pay a quick visit to our continuing dictionary project. One of the things you may have noticed is that the Chinese characters are unreadably small. Let's fix that by making them larger.

class RootWindow : public Window
{
public:
 virtual LPCTSTR ClassName() { return TEXT("Scratch"); }
 static RootWindow *Create();
 RootWindow();
 ~RootWindow();
 ...
private:
 HWND m_hwndLV;
 HWND m_hwndEdit;
 HWND m_hwndLastFocus;
 HFONT m_hfChinese;
 int  m_cyEdit;
 ...
}

RootWindow::RootWindow()
 : m_hfChinese(NULL)
{
}

RootWindow::~RootWindow()
{
 if (m_hfChinese) DeleteObject(m_hfChinese);
}

LRESULT RootWindow::OnCreate()
{
 ...
 ListView_SetExtendedListViewStyleEx(m_hwndLV,
                                     LVS_EX_FULLROWSELECT,
                                     LVS_EX_FULLROWSELECT);

 LOGFONT lf;
 if (!GetObject(GetWindowFont(m_hwndLV), sizeof(lf), &lf)) {
  return -1;
 }
 lf.lfHeight += lf.lfHeight / 2; // 50% bigger
 m_hfChinese = CreateFontIndirect(&lf);
 if (!m_hfChinese) return -1;
 SetWindowFont(m_hwndLV, m_hfChinese, FALSE);

 LVCOLUMN lvc;
 ...
}

This magnifies the font in the list view by 50% by taking the current font, increasing the height, and creating a new font, which we select into the list view.

This works in that the font is indeed bigger, but it's bigger even for the English part, and that larger-than-normal English font looks kind of out of place. The English was perfectly fine at its original size, after all. It was only the Chinese we wanted to enlarge. (This will become more important later on when we turn the program into a dynamic translator.)

We'll look at this problem next time.

[Raymond is currently away; this message was pre-recorded.]

Comments (16)
  1. Bryan says:

    Whee, owner-drawn list items! ;-)

    (Or at least, that’s how I’d make the one set of characters larger. Maybe there’s a better way, though; I’m sure that if so, the next post in this series will show it to us. I’d like to see it, if it exists.)

  2. Alan De Smet says:

    Interesting, but I’m far more curious about the core issue: Some languages (like Chinese) need to be larger to be legible. Do Chinese language editions of Windows default to larger fonts? If so, when English text is displayed on such a system, would it look exceptionally large to me?

    Relatedly, you simply picked a multiplier ("50% bigger looks good to me"). As far as I know there is no way to ask "how big does this need to be to be legible", or "how big does this character set want to be".

    An interesting problem, and not one I’d considered before (having never needed to worry about any non-European languages).

  3. oldnewthing says:

    Owner-draw would break accessibility.

    Alan De Smit: I don’t know the answer. It’s an interesting question. If I have time I may ask around.

  4. Nathan Evans says:

    (lf.lfHeight + lf.lfHeight) / 2 = lf.lfHeight. That’s just adding two halves together to make the whole again.

    lf.lfHeight * 1.5 is a 50% increase.

  5. J says:

    Operator precedence.

    lf.lfHeight = lf.lfHeight + (lf.lfHeight / 2);

    That is, indeed, adding 50% to the original value.

  6. Norman Diamond says:

    Wow, almost exactly something that I had to contend with yesterday and am wondering why. I’m using MFC but I think under the covers it’s doing the same APIs:

    > if (!GetObject(GetWindowFont(m_hwndLV), sizeof(lf), &lf)) {

    > return -1;

    > }

    > lf.lfHeight += lf.lfHeight / 2; // 50% bigger

    > m_hfChinese = CreateFontIndirect(&lf);

    > if (!m_hfChinese) return -1;

    > SetWindowFont(m_hwndLV, m_hfChinese, FALSE);

    When I do code like that for a window on the screen, it works. When I do code like that for a printed page, it shrinks the text instead of enlarging it. Why?

    In several of many tests, I commented out the line of code that did the same as this:

    > lf.lfHeight += lf.lfHeight / 2; // 50% bigger

    So the newly created (indirectly) font was exactly identical to the original font, and the text on the printed page shrank even more.

    Eventually I discovered that I had to do this:

    @ lf.lfHeight = lf.lfHeight * 80 / 18;

    just in order to get the result to print the same size as if I’d entirely omitted what should be a noop here.

    Why? And why only on printers but not on windows on the screen?

    Of course after discovering that, I could proceed to make the change I really wanted in the logical font (and it worked).

    Thursday, September 15, 2005 2:23 PM by Alan De Smet

    > Some languages (like Chinese) need to be

    > larger to be legible. Do Chinese language

    > editions of Windows default to larger fonts?

    The default in Japanese is 9. I’ve read that default in the US is 8 (I’ve installed a few from MSDN but never checked that) and I’ve read that the default for Chinese is the same as for Japanese.

    > If so, when English text is displayed on

    > such a system, would it look exceptionally

    > large to me?

    I don’t know how to answer that question. On a Japanese system, English text is the same height as Japanese text, though of course the average width is only half as wide. On a US system from MSDN, usually I open Control Panel – Regional Options and set as much as possible to match Japanese. On such a US system, in Notepad and Visual Studio 6 I also have to set the individual application’s options to use a Japanese font — with one exception. The exception is that in Windows XP MSDN US version, Notepad displays Japanese characters even when I haven’t set a Japanese font, and in such cases Notepad picks a Japanese font just for those characters but it picks an unreadably small size. If I set a font myself then Notepad uses a consistent size (height) for both Japanese and English.

  7. Cheong says:

    Alan De Smit:

    With my observation, Chinese characters are displayed somehow larger by occupying some space between lines. If normal character size of English char is 8, then for Chinese it’s 12.

    The observation is made through typing with "Fixedsys" font in Notepad.(I want fixed width font for clearer comparison)

  8. Norman-

    Could it be that you’ve got calculations for the sizes in pixels and you’re seeing the difference in DPI between the monitor and printer?

  9. Norman Diamond says:

    Thursday, September 15, 2005 10:47 PM by Nicholas Allen

    > Norman- Could it be that you’ve got

    > calculations for the sizes in pixels and

    > you’re seeing the difference in DPI between

    > the monitor and printer?

    It looks like that, but how can I know? I’m not sure whether to repeat that in several tests I did no calculation at all, I left the logfont structure completely unchanged, and yet the new font came out roughly 23% of the size of the original font. Obviously Windows wants me to give it a different size number than Windows gave to me (except that for windows on the screen it doesn’t), so how can I find out what units it’s giving me and what units it wants back? All I can find in MSDN is that they’re "logical units". Let’s leave for some other time how to figure out what’s logical about it, first I just need to know what it is.

  10. Marcel says:

    The whole lfHeight thing is explained in the Petzold (at least in my old Win95 edition), but essentially it’s this: "If you need a font with a specific size in points, this size must be converted into logical units and given as a negative number to lfHeight"

  11. ChrisR says:

    Norman, take a look at this article:

    http://www.windevnet.com/documents/s=7609/win0210g/0210g.htm

    Also, you may want to look up Q74299 in Microsoft’s knowledge base.

  12. asdf says:

    When I do code like that for a window on the screen, it works. When I do code like that for a printed page, it shrinks the text instead of enlarging it. Why?

    That sounds to me like the display DC is returning a positive number but the printer DC is returning a negative one and you’re just blindly adding to it.

    > Eventually I discovered that I had to do this:

    > @ lf.lfHeight = lf.lfHeight * 80 / 18;

    > just in order to get the result to print the same size as if I’d entirely omitted what should be a noop here.

    This sounds like you’re operating on the units of some other coordinate space as opposed whatever the printer DC is on when outputting text. It’s hard to tell what you’re doing wrong without looking at the code. GDI is really kludgy and counter-intuitive in some places.

    > All I can find in MSDN is that they’re "logical units". Let’s leave for some other time how to figure out what’s logical about it, first I just need to know what it is.

    The best way to explain logical and device coordinates are that 1 unit of device coordinates are the smallest you can possibly render at on a device. Consider it to be a pixel or a dot on a printer. Logical coordinates are the units most GDI functions work with based on some matrix:

    http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnargdi/html/msdn_mapping.asp

    The majority of the time, the logical coordinates are either a 1-1 mapping between device and logical coordinates (MM_TEXT) or 1/1440th of an inch (MM_TWIPS) which is based on LOGPIXELSY of the device’s device caps (I can’t remember but I believe it is a fixed value even if you play with the viewport/window extents).

  13. Norman Diamond says:

    ChrisR, thank you for pointers to those two documents, and it looks like they will be useful if I ever need someday to set a known point size and convert it to units that a printer DC will use. But they don’t say why I had to do such a calculation in the first place, when I don’t know and don’t want to know the point size.

    asdf, thank you for your effort, but I don’t understand. During debugging I displayed a message box showing the value of the lfHeight field and it was +18. Some experimentation revealed that changing the +18 to +80 would restore it to its original size. All of this involves the printer DC that MFC hands to an OnPrint() method. During this experimentation and in the desired code and final code for this, there is no reference to the display DC at all.

    An unrelated method did something with an Edit Box’s font (I’m away from the code right now and might be misremembering what MFC calls this type). There it was not necessary to examine or alter the lfHeight field.

    How can I know what units or what coordinate space I’m playing with? And why are the units or coordinate space different when MFC gives them to me vs. when I give the exact same LOGFONT structure back to MFC? At one point in debugging my OnPrint method I kept a call to get a pointer logfont from the CDC instance and a call to tell the same CDC instance to select the exact same logfont back into it, with all the rest of the code commented out. No changes whatsoever in the logfont, only changes in what MFC was thinking (if not the Win32 API).

    Maybe or maybe not one of those coordinate systems was based on the smallest dot that could be rendered on the printer, but I think it’s pretty obvious that both weren’t. The printer’s capabilities didn’t shrink dots by a factor of more than 4 during that time. I didn’t try getting device caps but why should I even have to?

  14. asdf says:

    I’m going to assume both the display and printer are using MM_TEXT. I’m assuming your display is 96 dpi so 18 logical units (aka pixels in MM_TEXT) is 3/16ths of an inch (or 13.5 points) on this. Since you had to multiply it by 80/18 to make it match up to 3/16ths of an inch on your printer it looks like the printer is set to around 420 dpi. So you’re essentially working with dots in all device spaces when you want to be using physical measurements. There are 3 ways to do this:

    1. Scale the printer’s matrix so that it uses the same DPI as the display. Set the map mode to MM_ISOTROPIC on the printer and then mess with the window/viewport extents of it so it matches based on both devices LOGPIXELSY device caps.

    2. Leave the matrix alone and scale all the coordinates you pass to GDI functions. This is what you were doing by multiplying by 80/18. But it’s kind of lame hardcoding that magic value as opposed to reading them from LOGPIXELSY (not to mention it will also work on monitors/printers with a different DPI ratio than yours).

    3. Use the same coordinate system for printing and display devices. Most WYSIWYG apps use MM_TWIPS for this but an interesting thing to note is that the next version of mozilla will do something along the lines of http://wiki.mozilla.org/Mozilla2:Units

    All 3 of these have different tradeoffs and you will definitely hit a wall if you use certain GDI functions that use device coordinates instead of logical ones (especially if you support non-NT OSes with even worse GDI capabilities). Since logical coordinates are essentially broken, I find it’s much easier to do a combination of 2 and 3 where you use MM_TEXT for all devices and work with some other unit internally and then do the mapping to device coordinates yourself when calling the GDI functions.

    But anyway, now you can see why you have to scale it on the printer. I think I misinterpreted what you meant when you said that adding to it shrunk the font so you can disregard that (but just be aware of it). As to why the edit box font works, I have no idea, I’ve never used MFC. Trace the code paths and compare what MFC is doing to what you’re doing, I’m guessing they’re doing a conversion somewhere or some other magic to make it happen. I did a tiny bit of reading on MFC/printing and it looks like their DC class has 2 HDCs: one for the device you’re drawing to and another to handle the attributes. I’m guessing they do that to make print preview work without doing anything special except being careful, make sure you’re not getting bitten by that.

  15. Norman Diamond says:

    Sunday, September 18, 2005 10:47 PM by asdf

    > I’m going to assume both the display and

    > printer are using MM_TEXT.

    I’m not sure how to answer, besides the fact that I didn’t try to inspect that setting. For the display, maybe MFC is giving me a logfont with MM_TEXT and then using the same MM_TEXT setting when I give it back. For the printer, maybe MFC is using MM_TEXT in one direction, but obviously not in both directions. If MFC were using a consistent unit in both directions then I wouldn’t have to change lfHeight from 18 to 80, I could just leave it at whatever value it was without even inspecting the value.

    > I’m assuming your display is 96 dpi so 18

    > logical units (aka pixels in MM_TEXT) is

    > 3/16ths of an inch (or 13.5 points) on this.

    Opening the display properties and details, it does say 96 dpi, though I haven’t calculated whether it’s accurate or not. The default font size is 9 (this is a Japanese Windows system), 9 of what unit I don’t know, and how 18 logical units or 13.5 points are computed from this 9 I also don’t know.

    > Since you had to multiply it by 80/18 to

    > make it match up to 3/16ths of an inch on

    > your printer it looks like the printer is

    > set to around 420 dpi.

    But the printer’s DC still isn’t always set to 420 dpi. When MFC calls my OnPrint method, one of its parameters is a pointer to a CDC (MFC’s version of DC), and I call a method in the CDC to get a pointer to its logfont. At that time the height is +18. Ordinarily it prints at a pretty ordinary size (same as the screen or not I didn’t try to measure, but pretty ordinary). Now if I do not change the logfont at all but create a new CFont and select that in, the +18 stays the same but now the result prints at a miniature unreadable size. This is where I have to multiply by 80/18 in order to just stay where I started.

    > But it’s kind of lame hardcoding that magic

    > value as opposed to reading them from

    > LOGPIXELSY (not to mention it will also work

    > on monitors/printers with a different DPI

    > ratio than yours).

    If a 17-inch monitor and 19-inch monitor both have 1280×1024 resolution then their actual DPI differs, and if someone sets their monitor to 1024×768 then their DPI differs too, but I assuredly do not want to magnify or shrink the printed page according to who ordered the printout that day.

    Now different printers with different DPIs could surely be a problem and yes indeed I hate doing that magic calculation. I wish I didn’t have to touch the lfHeight field at all. I don’t have to know the display’s lfHeight, I don’t do any calculations with it, I don’t touch it. I wish the printer behaved the same way. The printer’s default would be fine, if MFC would just remember the same unit that it was using when it gave me the logfont. All I want as far as size is concerned is for the printout to occur with the same ordinary font size that it ordinarily got by default anyway, which was pretty reasonable.

    The *purpose* why I get the logfont, alter it, and give it back, is that the default proportional font doesn’t suit the presentation of the data involved and a fixed-width font does it better. But before even making that change, I had to get the size to be what it was in the first place.

    > But anyway, now you can see why you have to

    > scale it on the printer.

    No, I do not yet see why I have to scale the printer font size to a different printer font size when I’m not doing anything to change units or anything like that. To repeat, during some debugging runs, I didn’t change anything at all in the logfont, and I promise you I don’t understand why I have to scale the exact same font in the exact same printer DC from what MFC gave me to what MFC takes back from me.

    > As to why the edit box font works, I have no

    > idea, I’ve never used MFC.

    Well OK I can understand that. An expert who never used MFC has no idea why some other part of MFC works properly and consistently with itself ^u^ Perhaps I should tell MFC to surrender other underlying Win32 components besides just the logfont and I should call the APIs directly. If I have time to try this, I’ll let you know if the magic calculation can go away.

    (By the way I didn’t simply multiply by a magic constant (80 / 18) because that would be an integer and would get truncated too early for this magic calculation. I multiplied by 80 and then divided by 18. I think the lfHeight field is a long but haven’t rechecked it this week yet.)

  16. Norman Diamond says:

    OK, I had time for another experiment. Again pDC is a parameter which MFC passes to the application’s OnPrint method, and its type is *CDC pointing to MFC’s encapsulation of a DC.

    x = pDC->GetMapMode();

    display a message box: its value was 1

    CFont *pfontOld = pDC->GetCurrentFont();

    pfontOld->GetLogFont(&lfPrinter);

    CFont fontNew;

    fontNew.CreateFontIndirect(&lfPrinter);

    pfontOld = pDC->SelectObject(&fontNew);

    x = pDC->GetMapMode();

    display a message box: its value was still 1

    Now look in the MSDN index for things like MM_HIENGLISH and see how many pages it finds for you. Non-users of MFC will be even more pleased than I was, (not). I finally found the MM_ values defined, not in an MFC header file, but in WINGDI.H. Value 1 is MM_TEXT.

    So MFC said it was giving me font height 18 in mapping MM_TEXT and said that the 18 I gave back to it was still in mapping MM_TEXT, but the printed size was less than 1/4 of what it used to be.

Comments are closed.