Skip to content

Commit 4a6d813

Browse files
committed
Add missing characters from dummy-index's adaptation, and adapt the z weight.
1 parent 2ae732f commit 4a6d813

16 files changed

Lines changed: 1396 additions & 700 deletions

xkcd-script/font/xkcd-script.otf

51.7 KB
Binary file not shown.

xkcd-script/font/xkcd-script.sfd

Lines changed: 1227 additions & 700 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

3.5 KB
Binary file not shown.

xkcd-script/font/xkcd-script.woff

2.61 KB
Binary file not shown.

xkcd-script/generator/pt5_svg_to_font.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,14 @@ def charname(char):
332332
('x',): psMat.translate(0, 20),
333333
}
334334

335+
# Per-character weight nudge applied after the per-line scale correction.
336+
# Positive = thicker, negative = thinner. Mirrors _GREEK_WEIGHT_NUDGE for the
337+
# main alphabet — used when an individual glyph's hand-drawn stroke is
338+
# noticeably heavier or lighter than its neighbours.
339+
_per_char_weight_nudge = {
340+
('z',): -15, # z's diagonal strokes are heavier than the rest of the alphabet
341+
}
342+
335343
# Pick out particular glyphs that are more pleasant than their latter alternatives.
336344
special_choices = {('C', ): dict(line=4),
337345
('G',): dict(line=4),
@@ -387,6 +395,15 @@ def charname(char):
387395
c.transform(psMat.scale(_restore))
388396
c.width = int(round(c.width * _restore))
389397

398+
# Per-character weight nudge: applied to source glyphs whose stroke weight
399+
# is noticeably off from the rest of the alphabet.
400+
_weight_nudge = _per_char_weight_nudge.get(chars)
401+
if _weight_nudge:
402+
c.correctDirection()
403+
c.removeOverlap()
404+
c.changeWeight(_weight_nudge)
405+
c.simplify()
406+
390407
# Per-character size adjustments: scale about the baseline (origin) to reduce
391408
# overall size while preserving stroke weight gained from changeWeight above.
392409
_operation_matrix = _per_char_operation.get(chars)

xkcd-script/generator/pt6_derived_chars.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,26 @@ def _make_ogonek(font, cp, base_name):
195195
c.addReference('comma', psMat.compose(psMat.scale(-1, -1), psMat.translate(dx, dy)))
196196

197197

198+
def _make_spacing_modifier(font, cp, mark_name, y_bottom=None, lsb=40, rsb=40):
199+
"""Spacing modifier letter: reference to mark glyph, repositioned and given advance width.
200+
201+
Translates the mark so its bottom sits at y_bottom and its left ink edge is
202+
at lsb. Default y_bottom is 20 units above the x-height of 'a', placing
203+
the modifier at a natural reading height as if hovering over a lowercase letter.
204+
lsb/rsb: left/right side bearings of the resulting glyph.
205+
"""
206+
if y_bottom is None:
207+
y_bottom = int(round(font['a'].boundingBox()[3] + 20))
208+
g = font.createMappedChar(cp)
209+
g.clear()
210+
mark_bb = font[mark_name].boundingBox()
211+
dx = lsb - mark_bb[0]
212+
dy = y_bottom - mark_bb[1]
213+
g.addReference(mark_name, psMat.translate(dx, dy))
214+
g.width = int(round(mark_bb[2] + dx + rsb))
215+
return g
216+
217+
198218
# Codepoints that already exist as hand-drawn glyphs; skip to avoid overwriting.
199219
_SKIP_CPS = frozenset({
200220
0x00DC, # Ü — hand-drawn
@@ -276,18 +296,44 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
276296
(0x2033, 'quotedbl'), # doubleprime
277297
(0x2035, 'quoteleft'), # backprime
278298
# Dash aliases
299+
(0x2010, 'hyphen'), # Unicode hyphen
279300
(0x2011, 'hyphen'), # non-breaking hyphen
280301
(0x00AD, 'hyphen'), # soft hyphen
281302
(0x2212, 'endash'), # minus sign
282303
(0x2012, 'endash'), # figure dash
283304
(0x2015, 'emdash'), # horizontal bar
305+
# Modifier letter apostrophes (Polynesian, transliteration)
306+
(0x02BB, 'quoteleft'), # modifier letter turned comma ʻ
307+
(0x02BC, 'quoteright'), # modifier letter apostrophe ʼ
284308
]
285309
for codepoint, source_name in ref_aliases:
286310
c = font.createMappedChar(codepoint)
287311
c.addReference(source_name)
288312
c.width = font[source_name].width
289313

290314

315+
# ---------------------------------------------------------------------------
316+
# Inverted punctuation
317+
# ---------------------------------------------------------------------------
318+
319+
# U+00A1 ¡ INVERTED EXCLAMATION MARK — ! flipped vertically.
320+
# U+00BF ¿ INVERTED QUESTION MARK — ? flipped vertically.
321+
# scale(1,-1) reflects about y=0; the translate re-maps [ymin,ymax] back to the
322+
# same vertical range so the glyph stays above the baseline.
323+
for _cp, _gname in [(0x00A1, 'exclam'), (0x00BF, 'question')]:
324+
_src = font[_gname]
325+
_src_bb = _src.boundingBox()
326+
_g = font.createMappedChar(_cp)
327+
_g.clear()
328+
_layer = fontforge.layer()
329+
for _c in _src.foreground:
330+
_layer += _c
331+
_g.foreground = _layer
332+
_g.transform(psMat.compose(psMat.scale(1, -1), psMat.translate(0, _src_bb[1] + _src_bb[3])))
333+
_g.correctDirection()
334+
_g.width = _src.width
335+
336+
291337
# ---------------------------------------------------------------------------
292338
# Combining mark glyphs
293339
# ---------------------------------------------------------------------------
@@ -467,6 +513,24 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
467513
_c0327.width = 0
468514

469515

516+
# ---------------------------------------------------------------------------
517+
# Spacing modifier letters (U+00A8, U+00AF, U+02C6–U+02DD)
518+
# Each is a reference to the corresponding combining mark, repositioned so its
519+
# bottom sits just above the x-height and given an advance width.
520+
# ---------------------------------------------------------------------------
521+
522+
_sm_y = int(round(font['a'].boundingBox()[3] + 20))
523+
524+
_make_spacing_modifier(font, 0x00A8, '_diaeresis_mark', _sm_y) # ¨ spacing diaeresis
525+
_make_spacing_modifier(font, 0x00AF, '_macron_mark', _sm_y) # ¯ spacing macron
526+
_make_spacing_modifier(font, 0x02C6, '_circumflex_mark', _sm_y) # ˆ modifier letter circumflex accent
527+
_make_spacing_modifier(font, 0x02C7, '_caron_mark', _sm_y) # ˇ modifier letter caron
528+
_make_spacing_modifier(font, 0x02D8, '_breve_mark', _sm_y) # ˘ modifier letter breve
529+
_make_spacing_modifier(font, 0x02D9, '_dot_above_mark', _sm_y) # ˙ modifier letter dot above
530+
_make_spacing_modifier(font, 0x02DC, '_tilde_mark', _sm_y) # ˜ modifier letter small tilde
531+
_make_spacing_modifier(font, 0x02DD, '_double_acute_mark', _sm_y) # ˝ modifier letter double acute accent
532+
533+
470534
# ---------------------------------------------------------------------------
471535
# Accented character tables
472536
# ---------------------------------------------------------------------------
@@ -1147,6 +1211,93 @@ def _greek_lc_to_uc(font, lc_cp, uc_cp, snap=True, weight_delta=0):
11471211
_g.width = font[0x25BD].width
11481212

11491213

1214+
# ---------------------------------------------------------------------------
1215+
# Additional composites
1216+
# ---------------------------------------------------------------------------
1217+
1218+
# U with diaeresis + tone mark: Ǖ Ǘ Ǚ Ǜ / ǖ ǘ ǚ ǜ (Pinyin)
1219+
# Ü / ü are used as bases so the new mark floats above the existing dots.
1220+
_Udi = font[0x00DC].glyphname # Ü hand-drawn
1221+
_udi = font[0x00FC].glyphname # ü derived
1222+
_accented(0x01D5, _Udi, '_macron_mark') # Ǖ
1223+
_accented(0x01D6, _udi, '_macron_mark', gap=8) # ǖ
1224+
_accented(0x01D7, _Udi, '_acute_mark') # Ǘ
1225+
_accented(0x01D8, _udi, '_acute_mark', gap=8) # ǘ
1226+
_accented(0x01D9, _Udi, '_caron_mark') # Ǚ
1227+
_accented(0x01DA, _udi, '_caron_mark', gap=8) # ǚ
1228+
_accented(0x01DB, _Udi, '_grave_mark') # Ǜ
1229+
_accented(0x01DC, _udi, '_grave_mark', gap=8) # ǜ
1230+
1231+
# A/a with diaeresis + macron: Ǟ ǟ (Livonian, Skolt Sámi)
1232+
# gap=30/20: the base's bb top already includes the diaeresis dots; extra
1233+
# clearance prevents the macron from pressing against the dots.
1234+
_Adi = font[0x00C4].glyphname # Ä
1235+
_adi = font[0x00E4].glyphname # ä
1236+
_accented(0x01DE, _Adi, '_macron_mark', gap=30) # Ǟ
1237+
_accented(0x01DF, _adi, '_macron_mark', gap=20) # ǟ
1238+
1239+
# AE/ae + macron: Ǣ ǣ (Old Norse transliteration)
1240+
# x_adj shifts the macron rightward: Æ/æ's geometric centre sits left of the
1241+
# visual centre (E side is heavier than A side).
1242+
_Ae_name = font[0x00C6].glyphname # Æ
1243+
_ae_name = font[0x00E6].glyphname # æ
1244+
_accented(0x01E2, _Ae_name, '_macron_mark', x_adj=100) # Ǣ
1245+
_accented(0x01E3, _ae_name, '_macron_mark', gap=25, x_adj=50) # ǣ
1246+
1247+
# G/g + caron: Ǧ ǧ (Skolt Sámi, some transliteration systems)
1248+
# x_adj for ǧ: g's full bounding box includes a wide descender that pulls
1249+
# its geometric centre left of the bowl centre.
1250+
_accented(0x01E6, 'G', '_caron_mark') # Ǧ
1251+
_accented(0x01E7, 'g', '_caron_mark', gap=8, x_adj=80) # ǧ
1252+
1253+
# K/k + caron: Ǩ ǩ (Skolt Sámi)
1254+
_accented(0x01E8, 'K', '_caron_mark') # Ǩ
1255+
_accented(0x01E9, 'k', '_caron_mark', gap=8) # ǩ
1256+
1257+
# G/g + acute: Ǵ ǵ (Upper Sorbian)
1258+
# x_adj for ǵ: same descender-bowl offset as ǧ.
1259+
_accented(0x01F4, 'G', '_acute_mark') # Ǵ
1260+
_accented(0x01F5, 'g', '_acute_mark', gap=20, x_adj=80) # ǵ
1261+
1262+
# A-ring + acute: Ǻ ǻ (Northern Sámi)
1263+
# The acute floats above the existing ring; base bb already includes the ring.
1264+
_Aring_name = font[0x00C5].glyphname # Å
1265+
_aring_name = font[0x00E5].glyphname # å
1266+
_accented(0x01FA, _Aring_name, '_acute_mark') # Ǻ
1267+
_accented(0x01FB, _aring_name, '_acute_mark', gap=8) # ǻ
1268+
1269+
# AE/ae + acute: Ǽ ǽ (Faroese, Danish orthography)
1270+
_accented(0x01FC, _Ae_name, '_acute_mark') # Ǽ
1271+
_accented(0x01FD, _ae_name, '_acute_mark', gap=8) # ǽ
1272+
1273+
# O-stroke + acute: Ǿ ǿ (Faroese, Danish orthography)
1274+
# Ø/ø are derived earlier in this script; their bounding boxes include the stroke.
1275+
_Ost_name = font[0x00D8].glyphname # Ø
1276+
_ost_name = font[0x00F8].glyphname # ø
1277+
_accented(0x01FE, _Ost_name, '_acute_mark') # Ǿ
1278+
_accented(0x01FF, _ost_name, '_acute_mark', gap=8) # ǿ
1279+
1280+
# DZ/dz digraph ligatures: DZ Dz dz and DŽ/Dž/dž: DŽ Dž dž
1281+
# Letters are placed ink-edge to ink-edge with a small gap, like IJ above.
1282+
_Zcaron_name = font[0x017D].glyphname # Ž
1283+
_zcaron_name = font[0x017E].glyphname # ž
1284+
_dz_gap = 20
1285+
for _cp, _left, _right in [
1286+
(0x01F1, 'D', 'Z'), # DZ capital DZ
1287+
(0x01F2, 'D', 'z'), # Dz titlecase Dz
1288+
(0x01F3, 'd', 'z'), # dz lowercase dz
1289+
(0x01C4, 'D', _Zcaron_name), # DŽ capital DŽ
1290+
(0x01C5, 'D', _zcaron_name), # Dž titlecase Dž
1291+
(0x01C6, 'd', _zcaron_name), # dž lowercase dž
1292+
]:
1293+
_g = font.createMappedChar(_cp)
1294+
_g.clear()
1295+
_g.addReference(_left)
1296+
_dx = int(round(font[_left].boundingBox()[2] + _dz_gap - font[_right].boundingBox()[0]))
1297+
_g.addReference(_right, psMat.translate(_dx, 0))
1298+
_g.width = _dx + font[_right].width
1299+
1300+
11501301
# ---------------------------------------------------------------------------
11511302
# Save
11521303
# ---------------------------------------------------------------------------
-31 Bytes
Loading
-28 Bytes
Loading
523 Bytes
Loading
361 Bytes
Loading

0 commit comments

Comments
 (0)