Bug 59162 - Fixed width font alignment problems for Japanese (and other languages I suspect)
Summary: Fixed width font alignment problems for Japanese (and other languages I suspect)
Status: RESOLVED FIXED
Alias: None
Product: konsole
Classification: Applications
Component: general (show other bugs)
Version: unspecified
Platform: Gentoo Packages Linux
: NOR normal
Target Milestone: ---
Assignee: Konsole Developer
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2003-05-31 04:56 UTC by Ken Deeter
Modified: 2003-06-04 11:14 UTC (History)
0 users

See Also:
Latest Commit:
Version Fixed In:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Ken Deeter 2003-05-31 04:56:40 UTC
Version:            (using KDE KDE 3.1.2)
Installed from:    Gentoo Packages
Compiler:          gcc 3.2 
OS:          Linux

This may be a Qt problem, I've done a fair bit of fidgeting, and I can see a solution, but it would likely make the rendering of the terminal quite slow.

Here's the problem. Say I'm running in a japanese locale. In konsole, i can choose a custom font such as, say "Bitstream Vera Sans Mono". In the font preview in the font dialog, it displays the correct font for english characters, and then it also displays from Japanese characters, which are brought in from the font Kochi Gothic on my system. This is likely because the underlying fontconfig provides Kochi Gothic as an alternative, in case the requested font doesn't cover those certain characters. (I believe the problem is similar when Qt uses its own font replacement)

Up to this point, all is fine and dandy except for a few points. Because there is no relationship between the bitstream font and the kochi font, the typical relationship that is true with Japanese fixed width fonts (i.e. one half-width character is exactly half the width of one full width character) is not true.

On top of this QPainter::drawText() will draw the combined font (Bitstream + Kochi) without respecting the fact that a CJK character should occupy exactly twice
as much space as an alphanumeric one, even though konsole specifically asks for a fixed pitch font. Rather, it will give each character only the space the glyph takes up, as is done with proportional fonts.

The effects of this are as follows:

Say I have a line of text on my terminal that has some mixed english/japanese characters, with all the same graphical attributes. The way that konsole would render this, would be using a single drawText() function. Unfortunately, since the drawText() does not enforce the CJK = 2 * alphanumeric width rule (even if the font was explicity requested as fixed pitch), every character that occurs after the first CJK character will be out of place (the problem occurs with the first CJK character because it is the first "offending" one. I.e. it will not fit into the scheme that konsole uses to calculate it's terminal cell spacing). The degree to in which it is misaligned depends on the ratio of CJK to alphanumeric characters in the current mixed font.

The obvious solution, one might think, is to just use a Japanese font that is carefully designed so that width(CJK char) == 2 * width(alphanumeric). Unfortunately, (and I think this is a Qt bug) if I choose Kochi Gothic in the font dialog directly, then for some reason, every alphanumeric character gets twice as much space as it needs (so they looked spaced out). Because the current console also knows about full/half width characters, in this case, every half-width character gets one full space, and every full-width character gets TWO full spaces, making things just shoot of the side of the screen.

The real fix is probably to fix how Qt behaves. (and btw, QChar really needs a function that says whether a char is full-width or not, as this is defined in the Unicode standard annex, and quite essential to a lot of typesetting algorithms) However, it can also be fixed in konsole, by calling a drawText for each character individually, starting from each actual correct grid position. If this is done, then when it is the case that width(CJK char) < 2 * width(alphanumeric char) then, all CJK chars will be rendered with some extra space between them (but at least they will be aligned!). When the relationship is a ">" instead of "<" There will probably be some overlap, as this means the CJK chars are too wide, but I don't think this happens much in practice.. you would have to be using very "thin" western characters (super condensed)

Ideally, when you call drawText() with a font that is supposed to be fixed pitch, Qt should enforce the CJK = 2*alphanumeric rule, but I am skeptical that this will be fixed soon.

BTW, this problem also causes the screen to become garbled when only parts of it need to be redrawn (like when doing selection, or when an XIM client window disappears above it, etc etc). You get little fragments of characters that only go away with a full redraw. The real annoyance though, is that things that are supposed to line up across lines using the CJK = 2*alphanumeric rule do not, making column-aligned data hard to read.
Comment 1 Ken Deeter 2003-05-31 04:57:20 UTC
btw, I'm also on the konsole-devel list, so please feel free to ask me anything if clarifcation is needed. 
Comment 2 Waldo Bastian 2003-05-31 09:48:20 UTC
Subject: Re: [Konsole-devel]   Fixed width font alignment problems for Japanese (and other languages I suspect)

Does it help if you change
	fixed_font = true;
to
	fixed_font = false;

in TEWidget::fontChange(), in kdebase/konsole/konsole/TEWidget.cpp ?

Cheers,
Waldo
Comment 3 Ken Deeter 2003-05-31 11:29:06 UTC
I don't have a quick way to test this right now. 
 
I'm assuming this will let me choose a properly spaced japanese font in the font dialog? Won't this allow 
people to potentially choose proportional western fonts though? 
Comment 4 Waldo Bastian 2003-06-02 11:32:42 UTC
Subject: kdebase/konsole/konsole

CVS commit by waba: 

CCMAIL: 59162-done@bugs.kde.org
* Fix the fixed-width with proportional-font drawing routine
* Always use it in the presence of double-width chars.
Thanks to Ken Deeter for his excellent analysis of the problem.


  M +37 -8     TEWidget.cpp   1.197


--- kdebase/konsole/konsole/TEWidget.cpp  #1.196:1.197
@@ -237,10 +237,9 @@ void TEWidget::fontChange(const QFont &)
   // "Base character width on widest ASCII character. This prevents too wide
   //  characters in the presence of double wide (e.g. Japanese) characters."
-  int fw;
   // Get the width from representative normal width characters
   font_w = qRound((double)fm.width(REPCHAR)/(double)strlen(REPCHAR));
 
   fixed_font = true;
-  fw = fm.width(REPCHAR[0]);
+  int fw = fm.width(REPCHAR[0]);
   for(unsigned int i=1; i< strlen(REPCHAR); i++){
     if (fw != fm.width(REPCHAR[i])){
@@ -456,9 +455,20 @@ void TEWidget::drawAttrStr(QPainter &pai
       // The meaning of y differs between different versions of QPainter::drawText!!
       int y = rect.y(); // top of rect
+      unsigned int nc=0;
+      int w;
       for(unsigned int i=0;i<str.length();i++)
       {
         drawstr = str.at(i);
         // Add double of the width if next c is 0;
-        int w = (attr+i+1)->c ? font_w : font_w * 2;
+        if ((attr+nc+1)->c)
+        {
+          w = font_w;
+          nc++;
+        }
+        else
+        {
+          w = font_w*2;
+          nc+=2;
+        }
         paint.drawText(x,y, w, font_h, Qt::AlignHCenter | Qt::DontClip, drawstr, -1);
         x += w;
@@ -495,9 +505,20 @@ void TEWidget::drawAttrStr(QPainter &pai
           // The meaning of y differs between different versions of QPainter::drawText!!
           int y = rect.y(); // top of rect
+          unsigned int nc=0;
+          int w;
           for(unsigned int i=0;i<str.length();i++)
           {
              drawstr = str.at(i);
              // Add double of the width if next c is 0;
-             int w = (attr+i+1)->c ? font_w : font_w * 2;
+            if ((attr+nc+1)->c)
+            {
+              w = font_w;
+              nc++;
+            }
+            else
+            {
+              w = font_w*2;
+              nc+=2;
+            }
              paint.drawText(x,y, w, font_h, Qt::AlignHCenter | Qt::DontClip, drawstr, -1);
              x += w;
@@ -610,5 +631,8 @@ HCNT("setImage");
           c = ext[x+len].c;
           if (!c)
+          {
+            fixed_font = false;
             continue; // Skip trailing part of multi-col chars.
+          }
 
           if (ext[x+len].f != cf || ext[x+len].b != cb || ext[x+len].r != cr ||
@@ -790,8 +814,13 @@ void TEWidget::paintContents(QPainter &p
         if (c)
           disstrU[p++] = fontMap(c);
+        else
+          fixed_font = false;
         len++;
       }
       if ((x+len < columns) && (!image[loc(x+len,y)].c))
+      {
+        fixed_font = false;
         len++; // Adjust for trailing part of multi-column char
+      }
 
       if (!isBlinkEvent || (cr & RE_BLINK))


Comment 5 Ken Deeter 2003-06-04 11:14:15 UTC
Is there a reason why the same code happens in two separate places? Seems like we should isolate the 
rendering part in a separate inline function, to improve readability and also to make it easier for newcomers 
to read the code (It took me a bit to figure out the same rendering code was happening in two separate 
places).