lout-users
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Margin kerning


From: Ludovic Courtès
Subject: Margin kerning
Date: Mon, 27 Jun 2005 16:19:58 +0200
User-agent: Gnus/5.1007 (Gnus v5.10.7) Emacs/21.4 (gnu/linux)

Hi,

Below is a patch against Lout 3.30 (assuming you have already applied
[1]) that adds margin kerning to Lout.  It adds two new options to the
address@hidden' symbol, namely `marginkerning' and `nomarginkerning' which
enable/disable it.  By default, margin kerning is disabled.  The only
thing one needs to do to enable it is add `marginkerning' to
address@hidden', for instance.

A simple example (which, BTW, exhibits a fishy behavior of Lout's object
filling algorithm) is available at:

  http://www.laas.fr/~lcourtes/software/lout/margin-kerning-test.ps
  http://www.laas.fr/~lcourtes/software/lout/margin-kerning-test.pdf
  http://www.laas.fr/~lcourtes/software/lout/margin-kerning-test.lout

A larger example, for comparison purposes, is available at:

  http://www.laas.fr/~lcourtes/software/lout/ubimob2005-paper.pdf
  http://www.laas.fr/~lcourtes/software/lout/ubimob2005-paper-nomk.pdf
  http://www.laas.fr/~lcourtes/software/lout/ubimob2005-paper-pa.pdf
  http://www.laas.fr/~lcourtes/software/lout/ubimob2005-paper-pa-nomk.pdf

(where `pa' stands for `Palatino' and `nomk' for `no margin-kerning').

Jeff will certainly need to review and comment the implementation.
Basically, the patch modifies the object filling service (in z14.c).
When MK is enabled, it looks at the last character of the last word of
each line (resp. the first character of the first word of each line) and
determines whether the height of this character's glyph is "small"
(currently, "small" means less than tree fourth of the height of `x').
If so the word is split into two objects:  a word object without the
last/first character, and a new zero-sized word object which contains
the last/first character.  These two objects are separated by a
zero-width gap (well, almost).

Unfortunately, the way this is implemented for characters located at the
beginning of a line is really unpleasant but I'm sure Jeff will find a
better solution.  :-)

I'd be glad if other people could give it a try and comment on it.

Thanks,
Ludovic.

[1] http://lists.planix.com/pipermail/lout-users/2005q1/003894.html


--- orig/externs.h
+++ mod/externs.h
@@ -574,6 +574,8 @@
 #define        STR_BREAK_NOLAST        AsciiToFull("unbreakablelast")
 #define        STR_BREAK_LAST          AsciiToFull("breakablelast")
 #define        STR_BREAK_SETOUTDENT    AsciiToFull("setoutdent")
+#define STR_BREAK_MARGINKERNING AsciiToFull("marginkerning")
+#define STR_BREAK_NOMARGINKERNING AsciiToFull("nomarginkerning")
 
 #define STR_SPACE_LOUT         AsciiToFull("lout")
 #define STR_SPACE_COMPRESS     AsciiToFull("compress")
@@ -674,6 +676,7 @@
   BOOLEAN      onobreaklast    : 1;    /* no break after last line of para  */
   BOOLEAN      obaselinemark   : 1;    /* baseline metrics                  */
   BOOLEAN      oligatures      : 1;    /* use ligatures                     */
+  BOOLEAN       omarginkerning  : 1;    /* perform margin kerning            */
 } STYLE;
 
 #define        line_gap(x)     (x).osu1.oline_gap
@@ -695,35 +698,37 @@
 #define        nobreaklast(x)  (x).onobreaklast
 #define        baselinemark(x) (x).obaselinemark
 #define        ligatures(x)    (x).oligatures
+#define marginkerning(x) (x).omarginkerning
 #define        yunit(x)        (x).oyunit
 #define        zunit(x)        (x).ozunit
 #define        outdent_len(x)  (x).ooutdent_len
 #define        smallcaps_len(x)(x).osmallcaps_len
 
-#define StyleCopy(x, y)                                                        
\
-( GapCopy(line_gap(x), line_gap(y)),                                   \
-  hyph_style(x) = hyph_style(y),                                       \
-  fill_style(x) = fill_style(y),                                       \
-  display_style(x) = display_style(y),                                 \
-  small_caps(x) = small_caps(y),                                       \
-  GapCopy(space_gap(x), space_gap(y)),                                 \
-  font(x) = font(y),                                                   \
-  colour(x) = colour(y),                                               \
-  texture(x) = texture(y),                                             \
-  outline(x) = outline(y),                                             \
-  language(x) = language(y),                                           \
-  nobreakfirst(x) = nobreakfirst(y),                                   \
-  nobreaklast(x) = nobreaklast(y),                                     \
-  baselinemark(x) = baselinemark(y),                                   \
-  ligatures(x) = ligatures(y),                                         \
-  vadjust(x) = vadjust(y),                                             \
-  hadjust(x) = hadjust(y),                                             \
-  padjust(x) = padjust(y),                                             \
-  space_style(x) = space_style(y),                                     \
-  yunit(x) = yunit(y),                                                 \
-  zunit(x) = zunit(y),                                                 \
-  outdent_len(x) = outdent_len(y),                                     \
-  smallcaps_len(x) = smallcaps_len(y)                                  \
+#define StyleCopy(x, y)                                \
+( GapCopy(line_gap(x), line_gap(y)),           \
+  hyph_style(x) = hyph_style(y),               \
+  fill_style(x) = fill_style(y),               \
+  display_style(x) = display_style(y),         \
+  small_caps(x) = small_caps(y),               \
+  GapCopy(space_gap(x), space_gap(y)),         \
+  font(x) = font(y),                           \
+  colour(x) = colour(y),                       \
+  texture(x) = texture(y),                     \
+  outline(x) = outline(y),                     \
+  language(x) = language(y),                   \
+  nobreakfirst(x) = nobreakfirst(y),           \
+  nobreaklast(x) = nobreaklast(y),             \
+  marginkerning(x) = marginkerning(y),         \
+  baselinemark(x) = baselinemark(y),           \
+  ligatures(x) = ligatures(y),                 \
+  vadjust(x) = vadjust(y),                     \
+  hadjust(x) = hadjust(y),                     \
+  padjust(x) = padjust(y),                     \
+  space_style(x) = space_style(y),             \
+  yunit(x) = yunit(y),                         \
+  zunit(x) = zunit(y),                         \
+  outdent_len(x) = outdent_len(y),             \
+  smallcaps_len(x) = smallcaps_len(y)          \
 )
 
 
@@ -3379,6 +3384,8 @@
 extern void      FontAdvanceCurrentPage(void);
 extern void      FontPageUsed(OBJECT face);
 extern BOOLEAN   FontNeeded(FILE *fp);
+extern  FULL_LENGTH FontGlyphHeight(FONT_NUM fnum, FULL_CHAR chr);
+extern  FULL_LENGTH FontGlyphWidth(FONT_NUM fnum, FULL_CHAR chr);
 
 /*****  z38.c    Character Mappings    **************************************/
 extern MAP_VEC   MapTable[];


--- orig/z11.c
+++ mod/z11.c
@@ -84,6 +84,7 @@
   sprintf(buff, ":C%d:P%d", colour(*style), texture(*style));
   StringCat(res, AsciiToFull(buff));
   StringCat(res, AsciiToFull("]"));
+  /* FIXME:  Handle `marginkerning'.  */
   return res;
 } /* end EchoStyle */
 #endif
@@ -212,6 +213,10 @@
        nobreaklast(*style) = TRUE;
     else if( StringEqual(string(x), STR_BREAK_LAST) )
        nobreaklast(*style) = FALSE;
+    else if( StringEqual(string(x), STR_BREAK_MARGINKERNING) )
+        marginkerning(*style) = TRUE;
+    else if( StringEqual(string(x), STR_BREAK_NOMARGINKERNING) )
+        marginkerning(*style) = FALSE;
     else Error(11, 5, "found unknown option to %s symbol (%s)",
           WARN, &fpos(x), KW_BREAK, string(x));
   }


--- orig/z14.c
+++ mod/z14.c
@@ -176,7 +176,7 @@
                                                                        \
   /* set right link and calculate badness of the new interval */       \
   if( rlink != x )                                                     \
-  {                                                                    \
+  {                                                                    \
     assert( Up(newg) == LastUp(newg), "MoveRightToGap: newg!" );       \
     /* set save_space(newg) now so that it is OK to forget right */    \
     debug0(DOF, DDD, "  MoveRightToGap setting save_space(newg)");     \
@@ -211,7 +211,7 @@
     { if( hyph_allowed )                                               \
       {                                                                        
\
        /* hyphenation is allowed, so add hyph_word to nat_width */     \
-       if( is_word(type(right)) &&                                     \
+       if( is_word(type(right)) &&                                     \
         !(string(right)[StringLength(string(right))-1] == CH_HYPHEN) ) \
         {                                                              \
          /* make sure hyph_word exists and is of the right font */     \
@@ -234,7 +234,8 @@
          }                                                             \
                                                                        \
          mode(gap(newg)) = ADD_HYPH;                                   \
-         I.nat_width += size(hyph_word, COLM);                         \
+         if( marginkerning(save_style(x)) == FALSE )                   \
+           I.nat_width += size(hyph_word, COLM);                       \
          debug0(DOF, DDD, "   adding hyph_word from nat_width");       \
         }                                                              \
       }                                                                        
\
@@ -256,7 +257,7 @@
   if( unbreakable_at_right )  I.class = UNBREAKABLE_RIGHT;             \
   else if( I.class == TIGHT && mode(gap(newg)) == TAB_MODE )           \
     I.class = TOO_TIGHT, I.badness = TOO_TIGHT_BAD;                    \
-  debug0(DOF, DDD, "MoveRightToGap returning.");                               
\
+  debug0(DOF, DDD, "MoveRightToGap returning.");                       \
 }
 
 /*@::IntervalInit(), IntervalShiftRightEnd()@*********************************/
@@ -300,7 +301,7 @@
 /*                                                                           */
 /*****************************************************************************/
 
-#define IntervalShiftRightEnd(I, x, hyph_word, max_width, etc_width)   \
+#define IntervalShiftRightEnd(I, x, hyph_word, max_width, etc_width)   \
 { OBJECT rlink, g, right = nilobj;                                     \
   assert( I.class != AT_END, "IntervalShiftRightEnd: AT_END!" );       \
   rlink = I.rlink;                                                     \
@@ -317,7 +318,9 @@
     /* if hyphenation case, must take away width of hyph_word */       \
     /* and increase the badness to discourage breaks at this point */  \
     if( mode(gap(g)) == ADD_HYPH )                                     \
-    { I.nat_width -= size(hyph_word,COLM);                             \
+    { if( marginkerning(save_style(x)) == FALSE )                      \
+        I.nat_width -= size(hyph_word,COLM);                           \
+                                                                       \
       save_badness(g) += HYPH_BAD_INCR;                                        
\
       debug0(DOF, DDD, "   subtracting hyph_word from nat_width");     \
     }                                                                  \
@@ -496,6 +499,17 @@
 #endif
 
 
+/* Return true if CHAR's glyph height in font FONTNUM is "small".  A glyph's
+   height is considered small when it is lower than or equal to three fourth
+   of the height of the glyph for `x'.  Initially, I considered that anything
+   strictly smaller than `x' would be enough, but some fonts (namely
+   Palatino) have a glyph for `v' which is slightly smaller than that for
+   `x', hence weird results.  */
+#define SmallGlyphHeight(_fontnum, _char)              \
+  ((FontGlyphHeight((_fontnum), (_char)))              \
+   <= (3 * (FontGlyphHeight((_fontnum), 'x') >> 2)))
+
+
 /*@::FillObject()@************************************************************/
 /*                                                                           */
 /*  FillObject(x, c, multi, can_hyphenate, allow_shrink, extend_unbreakable, */
@@ -748,6 +762,85 @@
       back(y, COLM) = 0;
       fwd(y, COLM) = max_width;
 
+      if( marginkerning( save_style(x) ) )
+      { /* Margin kerning: look at this line's first character.  */
+       OBJECT first_on_line, parent;
+
+       /* Get the first object on this line.  */
+       parent = NextDown(llink);
+       Child(first_on_line, parent);
+       if( is_word( type(first_on_line) ) )
+       {
+         /* It's a word: look at its first character's glyph height.  */
+         FONT_NUM font;
+         FULL_CHAR *word_content;
+
+         font = word_font(first_on_line);
+         word_content = string(first_on_line);
+         if ((word_content[0]) && (SmallGlyphHeight(font, word_content[0])))
+         { OBJECT z;
+           MAPPING m;
+           FULL_CHAR *unacc;
+           FULL_CHAR first_word[2];
+           FULL_LENGTH gwidth;
+
+           debug1(DOF, DD, "   adding margin-kerned character `%c' "
+                  "(beginning of line)", word_content[0]);
+
+           /* Get font information.  */
+           m = font_mapping(finfo[font].font_table);
+           unacc = MapTable[m]->map[MAP_UNACCENTED];
+
+           /* Add the first character.  */
+           first_word[0] = word_content[0];
+           first_word[1] = '\0';
+           z = MakeWord(WORD, first_word, &fpos(y));
+           word_font(z) = word_font(first_on_line);
+           word_colour(z) = word_colour(first_on_line);
+           word_texture(z) = word_texture(first_on_line);
+           word_outline(z) = word_outline(first_on_line);
+           word_language(z) = word_language(first_on_line);
+           word_baselinemark(z) = word_baselinemark(first_on_line);
+           word_ligatures(z) = word_ligatures(first_on_line);
+           word_hyph(z) = hyph_style(save_style(x)) == HYPH_ON;
+           underline(z) = underline(first_on_line);
+           FontWordSize(z);
+
+           /* Make it zero-width.
+              FIXME:  This awful trick allows Z to expand outside of the
+              paragraph itself while still appearing as having a null
+              width.  */
+           gwidth = size(z, COLM);
+           back(z, COLM) = -gwidth;
+           fwd(z, COLM) = gwidth;
+           Link(parent, z);
+
+           /* Add a zero-width gap object.  */
+           New(z, GAP_OBJ);
+           vspace(z) = 0;
+           if (word_content[1])
+             hspace(z) = FontKernLength(font, unacc,
+                                        word_content[0], word_content[1]);
+           else
+             hspace(z) = 0;
+           underline(z) = underline(first_on_line);
+           SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0);
+           Link(parent, z);
+
+           /* Remove the first char from FIRST_ON_LINE and recompute its
+              size.  */
+           {
+             unsigned s, len;
+             len = StringLength(word_content);
+             for (s = 0; s < len - 1; s++)
+               word_content[s] = word_content[s + 1];
+             word_content[len - 1] = '\0';
+           }
+           FontWordSize(first_on_line);
+         }
+       }
+      }
+
       /* if outdented paragraphs, add 2.0f @Wide & to front of new line */
       if( display_style(save_style(x)) == DISPLAY_OUTDENT ||
           display_style(save_style(x)) == DISPLAY_ORAGGED )
@@ -784,17 +877,30 @@
       Child(lgap, llink);
       if( mode(gap(lgap)) == ADD_HYPH )
       { OBJECT z, tmp;
+        FONT_NUM font;
+        MAPPING m;
+       FULL_CHAR *unacc, *word_content;
+       unsigned word_len;
 
        /* find word hyphen attaches to, since need its underline and font */
        Child(tmp, PrevDown(LastDown(x)));  /* last is lgap, so one before */
        debug2(DOF, D, "tmp = %s %s", Image(type(tmp)), EchoObject(tmp));
        assert(is_word(type(tmp)), "FillObject: !is_word(type(tmp))!");
+       word_content = string(tmp);
+       word_len = StringLength(word_content);
+
+       /* get font information */
+       font = word_font(tmp);
+       m = font_mapping(finfo[font].font_table);
+       unacc = MapTable[m]->map[MAP_UNACCENTED];
 
        /* add zero-width gap object */
         New(z, GAP_OBJ);
        debug0(DOF, DD, "   adding hyphen");
        debug0(DOF, DD, "");
-       hspace(z) = vspace(z) = 0;
+       vspace(z) = 0;
+       hspace(z) = FontKernLength(word_font(tmp), unacc,
+                                  word_content[word_len - 1], CH_HYPHEN);
        underline(z) = underline(tmp);
        SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0);
        Link(x, z);
@@ -810,9 +916,92 @@
        word_ligatures(z) = word_ligatures(tmp);
        word_hyph(z) = hyph_style(save_style(x)) == HYPH_ON;
        underline(z) = underline(tmp);
+
        FontWordSize(z);
+       if( marginkerning(save_style(x)) )
+       {
+         /* Margin kerning: set the hyphen's width to zero.  */
+         back(z, COLM) = 0;
+         fwd(z, COLM) = 0;
+       }
+
        Link(x, z);
       }
+      else if( marginkerning(save_style(x)) )
+      {
+       /* Margin kerning: look at the height of this line's last char.  */
+       OBJECT last_on_line;
+
+       /* Get the last object on this line.  */
+       Child(last_on_line, PrevDown(LastDown(x)));
+       if( is_word(type(last_on_line)) )
+       {
+         FONT_NUM  font;
+         FULL_CHAR last_char;
+         FULL_CHAR *word_content;
+         unsigned  word_len;
+
+         font = word_font(last_on_line);
+         word_content = string(last_on_line);
+         word_len = strlen((char *)word_content);
+
+         if (*word_content)
+         {
+           last_char = word_content[word_len - 1];
+
+           if(SmallGlyphHeight(font, last_char))
+           { OBJECT z;
+             MAPPING m;
+             FULL_CHAR *unacc;
+
+             /* Get font information.  */
+             m = font_mapping(finfo[font].font_table);
+             unacc = MapTable[m]->map[MAP_UNACCENTED];
+
+             /* Add a zero-width gap object.  */
+             New(z, GAP_OBJ);
+             debug1(DOF, DD, "   adding margin-kerned character `%c' "
+                    "(end of line)", last_char);
+             vspace(z) = 0;
+             if(word_len > 1)
+               hspace(z) = FontKernLength(font, unacc,
+                                          word_content[word_len - 2],
+                                          last_char);
+             else
+               hspace(z) = 0;
+             underline(z) = underline(last_on_line);
+             SetGap(gap(z), TRUE, FALSE, TRUE, FIXED_UNIT, EDGE_MODE, 0);
+             Link(x, z);
+
+             /* Add the last character.  */
+             z = MakeWord(WORD, &word_content[word_len - 1], &fpos(y));
+             word_font(z) = word_font(last_on_line);
+             word_colour(z) = word_colour(last_on_line);
+             word_texture(z) = word_texture(last_on_line);
+             word_outline(z) = word_outline(last_on_line);
+             word_language(z) = word_language(last_on_line);
+             word_baselinemark(z) = word_baselinemark(last_on_line);
+             word_ligatures(z) = word_ligatures(last_on_line);
+             word_hyph(z) = hyph_style(save_style(x)) == HYPH_ON;
+             underline(z) = underline(last_on_line);
+
+             FontWordSize(z);
+
+             /* Make it zero-width.  */
+             fwd(z, COLM) = 0;
+             back(z, COLM) = 0;
+
+             /* Remove the last char from LAST_ON_LINE and recompute its
+                size.  */
+             word_content[word_len - 1] = '\0';
+             FontWordSize(last_on_line);
+
+             Link(x, z);
+           }
+         }
+       }
+
+      }
 
       /* attach y to res, recycle lgap for gap separating the two lines */
       Link(NextDown(res), y);
@@ -906,7 +1095,7 @@
                word_baselinemark(prev) == word_baselinemark(next) &&
                word_ligatures(prev) == word_ligatures(next) &&
                underline(prev) == underline(next) )
-           { 
+           {
              debug2(DOF, DD, "joining %s with %s", EchoObject(prev),
                EchoObject(next));
              typ = type(prev) == QWORD || type(next) == QWORD ? QWORD : WORD;
@@ -919,7 +1108,9 @@
              word_baselinemark(tmp) = word_baselinemark(prev);
              word_ligatures(tmp) = word_ligatures(prev);
              word_hyph(tmp) = word_hyph(prev);
+
              FontWordSize(tmp);
+
              underline(tmp) = underline(prev);
              MoveLink(ylink, tmp, CHILD);
              DisposeChild(Up(prev));


--- orig/z18.c
+++ mod/z18.c
@@ -89,6 +89,7 @@
   smallcaps_len(InitialStyle)   = 0.7 * FR;            /* i.e. 0.7 scale    */
   nobreakfirst(InitialStyle)   = FALSE;
   nobreaklast(InitialStyle)    = FALSE;
+  marginkerning(InitialStyle)   = FALSE;                /* disabled by dft   */
   baselinemark(InitialStyle)   = FALSE;                /* i.e. not baseline */
   ligatures(InitialStyle)      = TRUE;                 /* i.e. ligatures    */
 

--- orig/z37.c
+++ mod/z37.c
@@ -1984,3 +1984,40 @@
   }
   return first_need;
 } /* end FontNeeded */
+
+
+/* FIXME:  Obviously, the next two functions should rather be inlined.  */
+
+/*@::FontGlyphHeight()@*******************************************************/
+/*                                                                           */
+/*  SHORT_LENGTH FontGlyphHeight(fnum, chr)                                  */
+/*                                                                           */
+/*  Return the height of the glyph that corresponds to character chr in      */
+/*  font fnum.                                                               */
+/*                                                                           */
+/*****************************************************************************/
+
+FULL_LENGTH FontGlyphHeight(FONT_NUM fnum, FULL_CHAR chr)
+{
+  struct metrics *fnt;
+
+  fnt = finfo[fnum].size_table;
+  return (fnt[chr].up - fnt[chr].down);
+}
+
+/*@::FontGlyphWidth()@*******************************************************/
+/*                                                                           */
+/*  SHORT_LENGTH FontGlyphWidth(fnum, chr)                                  */
+/*                                                                           */
+/*  Return the width of the glyph that corresponds to character chr in      */
+/*  font fnum.                                                               */
+/*                                                                           */
+/*****************************************************************************/
+
+FULL_LENGTH FontGlyphWidth(FONT_NUM fnum, FULL_CHAR chr)
+{
+  struct metrics *fnt;
+
+  fnt = finfo[fnum].size_table;
+  return (fnt[chr].right - fnt[chr].left);
+}




reply via email to

[Prev in Thread] Current Thread [Next in Thread]