[Top][All Lists]
[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);
+}
- Margin kerning,
Ludovic Courtès <=