Skip to content

Commit bccccaa

Browse files
committed
Add anchor point for brevecomb, adjust position of all combinings, add mark2mark
1 parent dafd11e commit bccccaa

10 files changed

Lines changed: 1328 additions & 1267 deletions

xkcd-script/font/xkcd-script.otf

34.3 KB
Binary file not shown.

xkcd-script/font/xkcd-script.sfd

Lines changed: 1284 additions & 1244 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

xkcd-script/font/xkcd-script.ttf

436 Bytes
Binary file not shown.

xkcd-script/font/xkcd-script.woff

-23.9 KB
Binary file not shown.

xkcd-script/generator/pt5_svg_to_font.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,6 @@ def charname(char):
331331
font.hhea_descent = -270; font.hhea_descent_add = False
332332
font.hhea_linegap = 77
333333

334-
# Information to be conveyed to the next stage.
335-
# I wanted to use font.persistent, but it causes an error. Instead, I use a dummy glyph.
336-
font.createChar(-1, '_pad_space')
337-
font['_pad_space'].width = SPACE + RSPACE
338-
339334
# Per-character size scaling applied after changeWeight, to fine-tune individual glyphs
340335
# that end up slightly too large despite correct stroke weight.
341336
_per_char_operation = {
@@ -443,6 +438,19 @@ def charname(char):
443438
c = font.createChar(0x000D, 'nonmarkingreturn') # U+000D: carriage return, zero-width; required by OpenType
444439
c.width = 256
445440

441+
# Information to be conveyed to the next stage.
442+
font.createChar(-1, '_pad_space')
443+
font['_pad_space'].width = SPACE + RSPACE
444+
font.createChar(-1, '_typical_bbox')
445+
c = fontforge.contour()
446+
l = SPACE // 2 + 20
447+
r = SPACE // 2 + 400 - 20
448+
_ascender_top = font['l'].boundingBox()[3]
449+
_descender_bottom = font['p'].boundingBox()[1]
450+
c.moveTo(l, _descender_bottom).lineTo(l, _ascender_top).lineTo(r, _ascender_top).lineTo(r, _descender_bottom).lineTo(l, _descender_bottom)
451+
font['_typical_bbox'].foreground += c
452+
font['_typical_bbox'].width = 400 + SPACE + RSPACE
453+
446454

447455
# ---------------------------------------------------------------------------
448456
# Glyphs imported from xkcd comic images

xkcd-script/generator/pt6_derived_chars.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,6 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
398398
_bb = _breve_mark.boundingBox()
399399
_breve_mark.transform(psMat.translate(-(_bb[0] + _bb[2]) / 2, 0))
400400
_breve_mark.transform(psMat.scale(0.5, 1))
401-
_breve_mark.transform(psMat.translate(-220, 0))
402401

403402
# --- Marks composed from existing mark glyphs ---
404403

@@ -460,8 +459,11 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
460459
# Without GPOS anchors the renderer places marks at their native coordinates,
461460
# so we translate here to ensure they appear above even tall letters like l/L/b/h.
462461
# (Pre-composed glyphs are unaffected — _place_above computes its own dy.)
463-
_ascender_top = font['l'].boundingBox()[3]
462+
# Inter-stage contract: all above combining marks sit on top-center of _typical_bbox
463+
_typical_bbox = font['_typical_bbox'].boundingBox()
464+
_ascender_top = _typical_bbox[3]
464465
_combining_gap = 20
466+
_center_top = (_typical_bbox[0] + _typical_bbox[2]) / 2 - font['_typical_bbox'].width
465467

466468
for cp, mark in [
467469
(0x0300, _grave_mark),
@@ -479,37 +481,39 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
479481
c = font.createMappedChar(cp)
480482
c.clear()
481483
mark_bb = font[mark.glyphname].boundingBox()
484+
dx = _center_top - (mark_bb[0] + mark_bb[2]) / 2
482485
dy = _ascender_top + _combining_gap - mark_bb[1]
483-
c.addReference(mark.glyphname, psMat.translate(0, dy))
486+
c.addReference(mark.glyphname, psMat.translate(dx, dy))
484487
c.width = 0
485488

486489
# Below combining marks share the macron-below shape at different vertical positions.
487-
_descender_bottom = font['p'].boundingBox()[1]
490+
_descender_bottom = font['_typical_bbox'].boundingBox()[1]
488491
_mb_bb = font['_macron_below_mark'].boundingBox()
492+
_center_bottom = (_typical_bbox[0] + _typical_bbox[2]) / 2 - font['_typical_bbox'].width
489493

490494
# U+0331 ◌̱ COMBINING MACRON BELOW — below the descender.
491495
_c0331 = font.createMappedChar(0x0331)
492496
_c0331.clear()
493-
_c0331.addReference('_macron_below_mark', psMat.translate(0, _descender_bottom - _combining_gap - _mb_bb[3]))
497+
_c0331.addReference('_macron_below_mark', psMat.translate(_center_bottom, _descender_bottom - _combining_gap - _mb_bb[3]))
494498
_c0331.width = 0
495499

496500
# U+0332 ◌̲ COMBINING LOW LINE — just below the baseline (underline position).
497501
_c0332 = font.createMappedChar(0x0332)
498502
_c0332.clear()
499-
_c0332.addReference('_macron_below_mark', psMat.translate(0, -_combining_gap - _mb_bb[3]))
503+
_c0332.addReference('_macron_below_mark', psMat.translate(_center_bottom, -_combining_gap - _mb_bb[3]))
500504
_c0332.width = 0
501505

502506
# U+0320 ◌̠ COMBINING MINUS SIGN BELOW — halfway between baseline and descender.
503507
_c0320 = font.createMappedChar(0x0320)
504508
_c0320.clear()
505-
_c0320.addReference('_macron_below_mark', psMat.translate(0, _descender_bottom // 2 - _combining_gap - _mb_bb[3]))
509+
_c0320.addReference('_macron_below_mark', psMat.translate(_center_bottom, _descender_bottom // 2 - _combining_gap - _mb_bb[3]))
506510
_c0320.width = 0
507511

508512
# U+0327 ◌̧ COMBINING CEDILLA — hook cedilla shape, positioned below descender.
509513
_hc_bb = font['_hook_cedilla_mark'].boundingBox()
510514
_c0327 = font.createMappedChar(0x0327)
511515
_c0327.clear()
512-
_c0327.addReference('_hook_cedilla_mark', psMat.translate(0, _descender_bottom - _combining_gap - _hc_bb[3]))
516+
_c0327.addReference('_hook_cedilla_mark', psMat.translate(_center_bottom, _descender_bottom - _combining_gap - _hc_bb[3]))
513517
_c0327.width = 0
514518

515519

xkcd-script/generator/pt7_font_properties.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -173,30 +173,39 @@ def getkern(left, right):
173173
font.addLookup('above', 'gpos_mark2base', (), [['mark', [['latn', ['dflt']]]]])
174174
font.addLookupSubtable('above', 'above_sub')
175175
font.addAnchorClass('above_sub', 'above')
176+
font.addLookup('mkmk', 'gpos_mark2mark', (), [['mkmk', [['latn', ['dflt']]]]], 'above')
177+
font.addLookupSubtable('mkmk', 'abovechain_sub')
178+
font.addAnchorClass('abovechain_sub', 'abovechain')
176179

177180
# Combining mark codepoints registered in pt6, paired with their private glyph names.
178181
# The anchor sits at the bottom-centre of each combining mark glyph, adjusted by
179182
# a per-mark y offset: positive = mark sits lower (less gap), negative = higher (more gap).
180183
_COMBINING = [
181184
(0x0300, '_grave_mark', 0),
182185
(0x0301, '_acute_mark', 0),
183-
(0x0302, '_circumflex_mark', 300), # was too high
184-
(0x0303, '_tilde_mark', 270), # was too high
185-
(0x0304, '_macron_mark', -90), # was too low
186-
(0x0307, '_dot_above_mark', -30), # too low → raise
186+
(0x0302, '_circumflex_mark', 0),
187+
(0x0303, '_tilde_mark', 0),
188+
(0x0304, '_macron_mark', 0),
189+
(0x0307, '_dot_above_mark', 0),
187190
(0x0308, '_diaeresis_mark', 0),
188-
(0x030A, '_ring_above_mark', 45), # could be a little closer
191+
(0x030A, '_ring_above_mark', 0),
189192
(0x030B, '_double_acute_mark', 0),
190-
(0x030C, '_caron_mark', 280), # was too high
193+
(0x030C, '_caron_mark', 0),
194+
(0x0306, '_breve_mark', 0),
191195
]
192196

197+
# Inter-stage contract: all above combining marks sit on top-center of _typical_bbox.
198+
# "Sit on" doesn't necessarily mean centering the bounding box. That design decision was
199+
# made in Stage 6, and here we just make sure not to mess up the results in the x direction.
200+
_typical_bbox = font['_typical_bbox'].boundingBox()
201+
cx = (_typical_bbox[0] + _typical_bbox[2]) / 2 - font['_typical_bbox'].width
202+
_MKMK_GAP = 20
193203
for cp, private_name, y_offset in _COMBINING:
194204
mark_glyph = font[cp]
195-
# Use the private mark glyph's bbox (the encoded glyph is a composite whose
196-
# bbox FontForge may not resolve; the private mark is a plain outline).
197-
bb = font[private_name].boundingBox()
198-
cx = (bb[0] + bb[2]) / 2
205+
bb = font[cp].boundingBox()
199206
mark_glyph.addAnchorPoint('above', 'mark', cx, bb[1] + y_offset)
207+
mark_glyph.addAnchorPoint('abovechain', 'mark', cx, bb[1] + y_offset)
208+
mark_glyph.addAnchorPoint('abovechain', 'basemark', cx, bb[3] + _MKMK_GAP)
200209

201210
# j's dot is to the right of the body centre; combining marks should sit above the dot.
202211
_j_layer = font['j'].foreground
-11 Bytes
Loading
36 Bytes
Loading
-62 Bytes
Loading

0 commit comments

Comments
 (0)