Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified xkcd-script/font/xkcd-script.otf
Binary file not shown.
4,505 changes: 2,213 additions & 2,292 deletions xkcd-script/font/xkcd-script.sfd

Large diffs are not rendered by default.

Binary file modified xkcd-script/font/xkcd-script.ttf
Binary file not shown.
Binary file modified xkcd-script/font/xkcd-script.woff
Binary file not shown.
4 changes: 3 additions & 1 deletion xkcd-script/generator/pt5_svg_to_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,8 @@ def _import_comic_glyph(font, name, svg_path, target_top, weight_delta=0):
g.transform(psMat.translate(-bb[0] + 20, 0))

if weight_delta:
g.correctDirection()
g.simplify(0.25) # tight tolerance: reduce redundant points before overlap resolution
g.removeOverlap()
g.changeWeight(weight_delta)

Expand Down Expand Up @@ -581,7 +583,7 @@ def _import_comic_glyph(font, name, svg_path, target_top, weight_delta=0):
# Save
# ---------------------------------------------------------------------------

font_fname = '../font/xkcd-script.sfd'
font_fname = '../generated/xkcd-script-pt5.sfd'

if not os.path.exists(os.path.dirname(font_fname)):
os.makedirs(os.path.dirname(font_fname))
Expand Down
38 changes: 35 additions & 3 deletions xkcd-script/generator/pt6_derived_chars.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
import fontforge
import psMat

font_fname = '../font/xkcd-script.sfd'
font = fontforge.open(font_fname)
font_fname = '../generated/xkcd-script-pt6.sfd'
font = fontforge.open('../generated/xkcd-script-pt5.sfd')


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -306,6 +306,36 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
_dotlessi_glyph.foreground = _dotless_layer
_dotlessi_glyph.width = font['i'].width

# dotlessj (U+0237): j without the dot, for ĵ ǰ etc. to avoid dot + accent stack.
_j_layer = font['j'].foreground
_j_dot_ymin = max(min(p.y for p in c) for c in _j_layer)
_dotlessj_glyph = font.createMappedChar(0x0237)
_dotlessj_layer = fontforge.layer()
for c in _j_layer:
if min(p.y for p in c) < _j_dot_ymin:
_dotlessj_layer += c
_dotlessj_glyph.foreground = _dotlessj_layer
_dotlessj_glyph.width = font['j'].width

# Marks above j/dotlessj should be centered over the dot position, not the body centre.
# The body centre is at (bb[0]+bb[2])/2; the dot was further right.
_j_dot_xs = [p.x for c in _j_layer for p in c if min(p.y for p in c) >= _j_dot_ymin]
_j_dot_cx = (min(_j_dot_xs) + max(_j_dot_xs)) / 2
_j_body_bb = _dotlessj_glyph.boundingBox()
_j_body_cx = (_j_body_bb[0] + _j_body_bb[2]) / 2
_j_x_adj = int(round(_j_dot_cx - _j_body_cx))

# longs (U+017F LATIN SMALL LETTER LONG S): dotless j rotated ~175° and placed
# on the baseline. The descender curve of j becomes the top hook of long s.
_longs_glyph = font.createMappedChar(0x017F)
_longs_glyph.foreground = _dotlessj_glyph.foreground.dup()
_longs_glyph.transform(psMat.rotate(math.radians(175)))
_longs_glyph.addExtrema('only_good_rm')
_, _longs_ymin, _, _ = _longs_glyph.boundingBox()
_longs_glyph.transform(psMat.translate(0, -_longs_ymin))
_longs_glyph.left_side_bearing = 20
_longs_glyph.right_side_bearing = 40

# Double acute: two acute-mark references shifted apart.
_double_acute_mark = font.createChar(-1, '_double_acute_mark')
_double_acute_mark.clear()
Expand Down Expand Up @@ -404,6 +434,7 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
]:
_make_accented(font, cp, base, '_caron_mark', gap=8)
_make_accented(font, 0x01D0, 'dotlessi', '_caron_mark', gap=40)
_make_accented(font, 0x01F0, 'uni0237', '_caron_mark', gap=40, x_adj=_j_x_adj) # ǰ — dotless to avoid dot+caron stack

# Ring above: å Ů / ů
for cp, base in [(0x00E5, 'a'), (0x016E, 'U'), (0x016F, 'u')]:
Expand Down Expand Up @@ -629,9 +660,10 @@ def _make_eng(font, cp, base_name, comma_name, x_frac=0.88, x_offset=0, y_offset
(0x00C2, 'A'), (0x00CA, 'E'), (0x00CE, 'I'), (0x00D4, 'O'), (0x00DB, 'U'),
(0x0108, 'C'), (0x011C, 'G'), (0x0124, 'H'), (0x0134, 'J'), (0x015C, 'S'), (0x0174, 'W'), (0x0176, 'Y'),
(0x00E2, 'a'), (0x00EA, 'e'), (0x00EE, 'dotlessi'), (0x00F4, 'o'), (0x00FB, 'u'),
(0x0109, 'c'), (0x011D, 'g'), (0x0125, 'h'), (0x0135, 'j'), (0x015D, 's'), (0x0175, 'w'), (0x0177, 'y'),
(0x0109, 'c'), (0x011D, 'g'), (0x0125, 'h'), (0x015D, 's'), (0x0175, 'w'), (0x0177, 'y'),
]:
_accented(cp, base, '_circumflex_mark')
_make_accented(font, 0x0135, 'uni0237', '_circumflex_mark', x_adj=_j_x_adj) # ĵ — dotless to avoid dot+circumflex stack

# Grave: À È Ì Ò Ù Ỳ / à è ì ò ù ỳ + Ẁ ẁ Ǹ ǹ
for cp, base in [
Expand Down
13 changes: 10 additions & 3 deletions xkcd-script/generator/pt7_font_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import fontforge
import unicodedata

font_fname = '../font/xkcd-script.sfd'
font = fontforge.open(font_fname)
font_fname = '../generated/xkcd-script-pt7.sfd'
font = fontforge.open('../generated/xkcd-script-pt6.sfd')


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -163,6 +163,13 @@ def expand(chars, left_side):
cx = (bb[0] + bb[2]) / 2
mark_glyph.addAnchorPoint('above', 'mark', cx, bb[1] + y_offset)

# j's dot is to the right of the body centre; combining marks should sit above the dot.
_j_layer = font['j'].foreground
_j_dot_ymin = max(min(p.y for p in c) for c in _j_layer)
_j_dot_xs = [p.x for c in _j_layer for p in c if min(p.y for p in c) >= _j_dot_ymin]
_j_dot_cx = (min(_j_dot_xs) + max(_j_dot_xs)) / 2
_J_BASE_NAMES = frozenset({'j', 'uni0237'})

# Base anchors at top-centre + gap for every letter in the font.
_BASE_GAP = 20
for glyph in font.glyphs():
Expand All @@ -173,7 +180,7 @@ def expand(chars, left_side):
bb = glyph.boundingBox()
if bb[2] <= bb[0]: # empty / unresolved composite
continue
cx = (bb[0] + bb[2]) / 2
cx = _j_dot_cx if glyph.glyphname in _J_BASE_NAMES else (bb[0] + bb[2]) / 2
glyph.addAnchorPoint('above', 'base', cx, bb[3] + _BASE_GAP)


Expand Down
91 changes: 54 additions & 37 deletions xkcd-script/generator/pt8_gen_reprod_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"""
import datetime
import os
import shutil
import calendar

import fontforge
Expand All @@ -13,8 +12,8 @@


base = '../font/'
sfd = os.path.join(base, 'xkcd-script.sfd')
newsfd = os.path.join(base, 'new-xkcd-script.sfd')
sfd_in = '../generated/xkcd-script-pt7.sfd'
sfd_out = os.path.join(base, 'xkcd-script.sfd')
otf = os.path.join(base, 'xkcd-script.otf')
ttf = os.path.join(base, 'xkcd-script.ttf')
woff = os.path.join(base, 'xkcd-script.woff')
Expand All @@ -23,49 +22,45 @@
then_str = 'Sat Jan 1 00:00:00 2000'
then_unix = calendar.timegm(then_utc.timetuple())

if True:
content = []
with open(sfd, 'rb') as fh_in:
for line in fh_in:
try:
line = line.decode('utf-8')
except UnicodeDecodeError:
content.append(line)
continue

if line.startswith('%%CreationDate'):
line = '%%CreationDate: ' + then_str + '\n'
content = []
with open(sfd_in, 'rb') as fh_in:
for line in fh_in:
try:
line = line.decode('utf-8')
except UnicodeDecodeError:
content.append(line)
continue

if line.startswith('CreationTime:'):
line = 'CreationTime: ' + str(then_unix) + '\n'
if line.startswith('%%CreationDate'):
line = '%%CreationDate: ' + then_str + '\n'

if line.startswith('ModificationTime:'):
line = 'ModificationTime: ' + str(then_unix) + '\n'
if line.startswith('CreationTime:'):
line = 'CreationTime: ' + str(then_unix) + '\n'

if line.startswith('XUID:'):
line = 'XUID: [-1]\n'
if line.startswith('ModificationTime:'):
line = 'ModificationTime: ' + str(then_unix) + '\n'

if 'Created with FontForge (http://fontforge.org)' in line:
if line.startswith('UComments:'):
line = 'UComments: "Created with FontForge (http://fontforge.org)"\n'
else:
line = '% Created with FontForge (http://fontforge.org)\n'
if line.startswith('XUID:'):
line = 'XUID: [-1]\n'

if 'Generated by FontForge' in line:
continue
if 'Created with FontForge (http://fontforge.org)' in line:
if line.startswith('UComments:'):
line = 'UComments: "Created with FontForge (http://fontforge.org)"\n'
else:
line = '% Created with FontForge (http://fontforge.org)\n'

content.append(line.encode('utf-8'))
if 'Generated by FontForge' in line:
continue

with open(newsfd, 'wb') as fh_out:
for line in content:
fh_out.write(line)
content.append(line.encode('utf-8'))

os.remove(sfd)
shutil.move(newsfd, sfd)
with open(sfd_out, 'wb') as fh_out:
for line in content:
fh_out.write(line)

os.utime(sfd, (then_unix, then_unix))
os.utime(sfd_out, (then_unix, then_unix))

font = fontforge.open(sfd)
font = fontforge.open(sfd_out)

# Pin the UniqueID so FontForge doesn't embed a build date (breaks reproducibility).
font.sfnt_names = (('English (US)', 'UniqueID', 'xkcd Script'), )
Expand Down Expand Up @@ -111,7 +106,12 @@ def _inline_subrs(program, subrs, bias):
_ref_subrs = _ref_top.Private.Subrs
_ref_bias = _subr_bias(len(_ref_subrs))

_new_otf = _TTFont(otf)
_new_otf = _TTFont(otf, recalcTimestamp=False)

# Pin the UniqueID so FontForge doesn't embed a build date (breaks reproducibility).
_new_otf.sfnt_names = (('English (US)', 'UniqueID', 'xkcd Script'), )
_new_otf.xuid = "-1"

_new_cs = _new_otf['CFF '].cff.topDictIndex[0].CharStrings

_ref_glyphs = _ref_otf.getGlyphSet()
Expand All @@ -133,4 +133,21 @@ def _inline_subrs(program, subrs, bias):
_new_cs[_name].program = _inlined
_new_cs[_name].bytecode = None

# Desubroutinize every charstring that was not frozen above (e.g. newly
# added glyphs whose bytecode still contains callsubr tokens from FontForge).
# This makes the output stable: on the next CI run those charstrings are
# already inlined in the reference, so _inline_subrs is a no-op and the
# saved bytes are identical.
_new_top = _new_otf['CFF '].cff.topDictIndex[0]
_new_subrs = getattr(_new_top.Private, 'Subrs', [])
_new_bias = _subr_bias(len(_new_subrs))
for _name in _new_cs.keys():
_cs = _new_cs[_name]
if _cs.bytecode is not None:
_cs.decompile()
if _cs.program is None:
continue
_cs.program = _inline_subrs(_cs.program, _new_subrs, _new_bias)
_cs.bytecode = None

_new_otf.save(otf)
Binary file modified xkcd-script/samples/charmap_latin_1_supplement.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified xkcd-script/samples/charmap_latin_extended_a.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified xkcd-script/samples/charmap_latin_extended_b.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified xkcd-script/samples/charmap_non_latin_other.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading