Skip to content
Draft
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 added xkcd-script/font/xkcd-script-mono.otf
Binary file not shown.
26,367 changes: 26,367 additions & 0 deletions xkcd-script/font/xkcd-script-mono.sfd

Large diffs are not rendered by default.

Binary file added xkcd-script/font/xkcd-script-mono.ttf
Binary file not shown.
Binary file added xkcd-script/font/xkcd-script-mono.woff
Binary file not shown.
Binary file modified xkcd-script/font/xkcd-script.otf
Binary file not shown.
4,051 changes: 2,816 additions & 1,235 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.
148 changes: 135 additions & 13 deletions xkcd-script/generator/pt5_svg_to_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
import os
import glob
import parse
import string
import unicodedata

SPACE = 0
RSPACE = 20
MONO = True

fnames = sorted(glob.glob('../generated/characters/char_*.svg'))

Expand Down Expand Up @@ -94,6 +96,11 @@ def create_char(font, chars, fname):
c = font.createMappedChar(chars[0])
else:
c = font.createMappedChar(ord(chars[0]))
elif len(chars) >= 3 and chars[1] == '.':
# variant glyph.
variant_name = ''.join(chars)

c = font.createChar(-1, variant_name)
else:
# Multiple characters - this is a ligature. We need to register this in the
# ligature lookup table we created. Not all font-handling libraries will do anything
Expand All @@ -105,6 +112,7 @@ def create_char(font, chars, fname):

c = font.createChar(-1, ligature_name)

c.removePosSub('liga')
c.addPosSub('liga', ligature_tuple)

c.clear()
Expand Down Expand Up @@ -243,15 +251,16 @@ def pad_glyph(c):
space = SPACE
rspace = RSPACE
bbox = c.boundingBox()
if c.glyphname in list('gjpqy'):
glyphname = c.glyphname.split('.')[0]
if glyphname in list('gjpqy'):
# Recalculate the bounding box by excluding the tail of the glyph
# Do not remove the glyph's tail if it is too close to the baseline
capxrange = c.foreground.xBoundsAtY(-40, 600)
if c.glyphname == 'j':
if glyphname == 'j':
bbox = tuple([(capxrange[0]*2 + bbox[0])/3, bbox[1], capxrange[1], bbox[3]])
else:
bbox = tuple([capxrange[0], bbox[1], capxrange[1], bbox[3]])
if c.glyphname == 'f':
if glyphname == 'f':
# Recalculate the bounding box by excluding the arm of the glyph
# Restrict the arm so that it does not pierce through the stem of the next glyph
xxrange = c.foreground.xBoundsAtY(0, 420)
Expand Down Expand Up @@ -292,11 +301,11 @@ def pad_glyph(c):
add_right = 15
else:
add_right = 20
if c.glyphname == 'i':
if glyphname == 'i':
add_left += 10
add_right += 10
may_too_wide1 = list('aebdpr')
if c.glyphname in may_too_wide1:
if glyphname in may_too_wide1:
if bbox[2] - bbox[0] + add_left + add_right >= 398:
add_left -= 10
add_right -= 10
Expand Down Expand Up @@ -336,6 +345,7 @@ def charname(char):
_per_char_operation = {
('q',): psMat.compose(psMat.scale(0.92), psMat.translate(0, 20)),
('x',): psMat.translate(0, 20),
('i', '.', 'm', 'o', 'n', 'o'): psMat.translate(0, -20),
('j',): psMat.translate(0, -20),
('A',): psMat.translate(0, -10),
('N',): psMat.translate(0, -10),
Expand All @@ -358,15 +368,8 @@ def charname(char):
('I', ): dict(line=4),
}

for line, position, bbox, fname, chars in characters:
if chars in special_choices:
spec = special_choices[chars]
spec_line = spec.get('line', any)
if spec_line is not any and spec_line != line:
continue

c = create_char(font, chars, fname)

def fit_glyph(c, line, position, bbox, fname, chars):
scale_glyph(
c, bbox,
baseline=_baselines[line],
Expand Down Expand Up @@ -430,6 +433,81 @@ def charname(char):
c.simplify()
c.round()


# ---------------------------------------------------------------------------
# Pass 1: Import the glyphs and check out their features.
# ---------------------------------------------------------------------------
chosen_by_fname = {}
small_caps = {}
left_small = {}
most_monospace = {}
ADDITIONAL_FOR_WEIGHTING = 40
# courier-like monospacing: 0.6em (em=856 internal units ∴0.6em=514)
_monowidth = 856 * 0.6
for line, position, bbox, fname, chars in characters:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is for the non-mono form too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, lines 343 to 351 remain as before; it's a loop that converts all SVG files into glyphs, and glyphs with the same name are overwritten so that only the last one remains. Oh, that means at that moment, since the glyph exists, I can just copy and paste it into *.mono.
By the way, I don’t like writing things like ' + 60 * _line_fgs / 856' twice, so I want to reconsider the method for listing monospaced glyphs.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll choose glyphs in two passes. The outlines we import the first time will be completely thrown away, but keeping all the outlines in the font object is a hassle, so I avoided it.

if chars in special_choices:
spec = special_choices[chars]
spec_line = spec.get('line', any)
if spec_line is not any and spec_line is None:
continue
if spec_line is not any and spec_line == line:
chosen_by_fname[chars] = fname
else:
chosen_by_fname[chars] = fname
# The 'V' in L7P29 corresponds to left_small, but just like in the 2017 version,
# we'll process the fname order at the top so that it's adopted based on fname order.

c = create_char(font, chars, fname)
fit_glyph(c, line, position, bbox, fname, chars)
_bb_after = c.boundingBox()
if len(chars) == 1 and chars[0] in string.ascii_uppercase:
leftyrange = c.foreground.yBoundsAtX(0, c.width / 2)
if _bb_after[3] <= 600 * 0.85:
small_caps[chars] = fname
continue # exclude from other features
elif leftyrange[1] <= 600 * 0.85:
left_small[chars] = fname
continue # exclude from other features
defending = most_monospace.get(chars, [10.0, ''])
_width = (c.width + ADDITIONAL_FOR_WEIGHTING) / _monowidth
if _bb_after[3] > 660:
pass
elif _width <= 1:
if defending[0] > 1 or defending[0] < _width:
most_monospace[chars] = [_width, fname]
elif defending[0] > _width:
most_monospace[chars] = [_width, fname]

# ---------------------------------------------------------------------------
# Pass 2: Import the glyphs with the proper names.
# ---------------------------------------------------------------------------
for line, position, bbox, fname, chars in characters:
if chars in special_choices:
spec = special_choices[chars]
spec_line = spec.get('line', any)
if spec_line is not any and spec_line is None:
continue

addr = None
if small_caps.get(chars, '') == fname:
addr = '.sc'
if left_small.get(chars, '') == fname:
addr = '.Tx'
if most_monospace.get(chars, [10.0, ''])[1] == fname:
addr = '.mono'
if chosen_by_fname[chars] == fname:
addr = ''
if addr is None:
continue
if addr != '':
if len(chars) == 1:
c = create_char(font, tuple(chars[0] + addr), fname)
fit_glyph(c, line, position, bbox, fname, tuple(chars[0] + addr))
else:
c = create_char(font, chars, fname)
fit_glyph(c, line, position, bbox, fname, chars)


c = font.createMappedChar(32)
c.width = 256

Expand Down Expand Up @@ -900,3 +978,47 @@ def _import_math_centered(name, cp, target_top, weight_delta=0, dst_name=None):
if os.path.exists(font_fname):
os.remove(font_fname)
font.save(font_fname)


# ---------------------------------------------------------------------------
# Select mono glyphs as base set, and save
# ---------------------------------------------------------------------------

if MONO:
font_fname = '../generated/xkcd-script-mono-pt5.sfd'

font.fontname = 'xkcdScriptMono'
font.familyname = 'xkcd Script Monospace'
font.fullname = 'xkcd-Script-Monospace-Regular'

c = font.createChar(-1, '_monospace_width')
c.width = int(round(0.6 * font.em))

_need_backup = list('Ii')
for gname in (string.ascii_uppercase + string.ascii_lowercase):
fromname = None
if gname == 'I':
fromname = 'I_hyphen_p_r_o_n_o_u_n'
elif gname + '.mono' in font:
if gname in list('il') or abs(font[gname + '.mono'].width - font[gname].width) >= 15:
fromname = gname + '.mono'
if fromname:
if gname in _need_backup:
font.createChar(-1, gname + '.sansserif')
font.selection.select(gname)
font.copy()
font.selection.select(gname + '.sansserif')
font.paste()
font.selection.select(fromname)
font.cut()
font.selection.select(gname)
font.paste()

font.removeLookup('ligatures')
panosetuple = font.os2_panose
panosetuple = tuple([2]) + panosetuple[1:3] + tuple([9]) + panosetuple[4::]
font.os2_panose = panosetuple

if os.path.exists(font_fname):
os.remove(font_fname)
font.save(font_fname)
54 changes: 47 additions & 7 deletions xkcd-script/generator/pt6_derived_chars.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,36 @@
Reads the base SFD produced by pt5_svg_to_font.py, adds derived glyphs, saves.
"""
import math
import sys
import fontforge
import psMat

font_fname = '../generated/xkcd-script-pt6.sfd'
font = fontforge.open('../generated/xkcd-script-pt5.sfd')
if len(sys.argv) >= 2:
bodyname = sys.argv[1]
else:
bodyname = 'xkcd-script'

font_fname = f'../generated/{bodyname}-pt6.sfd'
font = fontforge.open(f'../generated/{bodyname}-pt5.sfd')

def fix_width(c, width):
space1 = (width - c.width) // 2
space2 = width - c.width - space1
if c.width == 0: # combination chars
space1 = width - c.width
space2 = 0
c.width = c.width + space1
t = psMat.translate(space2, 0)
c.transform(t)

_pad_space = font['_pad_space'].width

if '_monospace_width' in font:
_monospace_width = font['_monospace_width'].width

font.selection.all()
for c in font.selection.byGlyphs:
fix_width(c, _monospace_width)


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -277,9 +302,10 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
c.width = 0

# Vertical pipe: re-use the I glyph (same stroke, same weight).
c = font.createChar(-1, 'I.sansserif')
c.addReference('I')
c.width = font['I'].width
if 'I.sansserif' not in font:
c = font.createChar(-1, 'I.sansserif')
c.addReference('I')
c.width = font['I'].width
pipe = font.createMappedChar(ord('|'))
pipe.clear()
pipe.addReference('I.sansserif', psMat.compose(psMat.scale(1, 1.3), psMat.translate(0, -0.2 * font.ascent)))
Expand Down Expand Up @@ -398,6 +424,7 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
_bb = _breve_mark.boundingBox()
_breve_mark.transform(psMat.translate(-(_bb[0] + _bb[2]) / 2, 0))
_breve_mark.transform(psMat.scale(0.5, 1))
_breve_mark.width = 0

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

Expand Down Expand Up @@ -636,7 +663,7 @@ def _make_lslash(font, cp, base_name, crossbar_name, y_frac, x_center):
c.width = font[base_name].width
base_bb = font[base_name].boundingBox()
target_y = base_bb[1] + (base_bb[3] - base_bb[1]) * y_frac
c.addReference(crossbar_name, psMat.translate(x_center, target_y))
c.addReference(crossbar_name, psMat.translate(round(base_bb[0]) - 20 + x_center, target_y))
return c


Expand Down Expand Up @@ -901,7 +928,7 @@ def _make_eng(font, cp, base_name, comma_name, x_frac=0.88, x_offset=0, y_offset

# ij U+0133 / IJ U+0132: Dutch IJ digraph ligatures.
# Position so the ink edges have the same gap as adjacent letters would after kerning (~40 units).
for cp, left, right in [(0x0133, 'i', 'j'), (0x0132, 'I', 'J')]:
for cp, left, right in [(0x0133, 'i', 'j'), (0x0132, 'I.sansserif', 'J')]:
_ij = font.createMappedChar(cp)
_ij.clear()
_ij.addReference(left)
Expand Down Expand Up @@ -1530,4 +1557,17 @@ def make_display_operator(font, src_name, dst_name, target_h, weight=0, rbear=0)
# Save
# ---------------------------------------------------------------------------

if '_monospace_width' in font:
font.selection.all()
for c in font.selection.byGlyphs:
fix_width(c, _monospace_width)

# Microsoft's suggestion
font.addLookup('fixcomb', 'gpos_single', (), [['mark', [['latn', ['dflt']]]], ['mkmk', [['latn', ['dflt']]]]])
font.addLookupSubtable('fixcomb', 'fixcombinigchars_sub')
font.selection.select(('ranges', None), 0x0300, 0x0332)
for c in font.selection.byGlyphs:
c.addPosSub('fixcombinigchars_sub', 0, 0, -_monospace_width, 0)


font.save(font_fname)
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 @@ -5,11 +5,17 @@
Reads the SFD produced by pt6_derived_chars.py (which has all glyphs + the
math cmap aliases), applies properties, saves.
"""
import sys
import fontforge
import unicodedata

font_fname = '../generated/xkcd-script-pt7.sfd'
font = fontforge.open('../generated/xkcd-script-pt6.sfd')
if len(sys.argv) >= 2:
bodyname = sys.argv[1]
else:
bodyname = 'xkcd-script'

font_fname = f'../generated/{bodyname}-pt7.sfd'
font = fontforge.open(f'../generated/{bodyname}-pt6.sfd')


# ---------------------------------------------------------------------------
Expand Down Expand Up @@ -162,7 +168,8 @@ def getkern(left, right):
kern(35+a, ['L'], set(roman) - {'j'}, onlyCloser=True, touch=True)


autokern(font)
if '_monospace_width' not in font:
autokern(font)
font.removeGlyph(font['_pad_space'])


Expand Down
10 changes: 7 additions & 3 deletions xkcd-script/generator/pt8_gen_reprod_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"""
import datetime
import os
import sys
import calendar
from pathlib import Path

Expand All @@ -32,9 +33,12 @@
_then_unix = calendar.timegm(datetime.datetime(2000, 1, 1, 0, 0).timetuple())


NAME = 'xkcd-script'
SFD_IN = GENERATED + 'xkcd-script-pt7.sfd'
SFD_OUT = FONT_DIR + 'xkcd-script.sfd' # canonical, committed
if len(sys.argv) >= 2:
NAME = sys.argv[1]
else:
NAME = 'xkcd-script'
SFD_IN = GENERATED + NAME + '-pt7.sfd'
SFD_OUT = FONT_DIR + NAME + '.sfd' # canonical, committed


# ---------------------------------------------------------------------------
Expand Down
3 changes: 3 additions & 0 deletions xkcd-script/generator/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ set -ex
[ "$FROM" -le 4 ] && $RUN_CTXT python3 pt4_additional_sources.py
[ "$FROM" -le 5 ] && $RUN_CTXT python3 pt5_svg_to_font.py
[ "$FROM" -le 6 ] && $RUN_CTXT python3 pt6_derived_chars.py
[ "$FROM" -le 6 ] && $RUN_CTXT python3 pt6_derived_chars.py xkcd-script-mono
[ "$FROM" -le 7 ] && $RUN_CTXT python3 pt7_font_properties.py
[ "$FROM" -le 7 ] && $RUN_CTXT python3 pt7_font_properties.py xkcd-script-mono
# TODO: pt8_gen_reprod_font.py should logically be pt9 since it runs after
# pt8_derivatives.py; left as pt8_ to minimise diff noise for the MathJax PR.
[ "$FROM" -le 8 ] && $RUN_CTXT python3 pt8_derivatives.py
[ "$FROM" -le 8 ] && $RUN_CTXT python3 pt8_gen_reprod_font.py
[ "$FROM" -le 8 ] && $RUN_CTXT python3 pt8_gen_reprod_font.py xkcd-script-mono
Binary file modified xkcd-script/samples/charmap_basic_latin.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_greek_and_coptic.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_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 added xkcd-script/samples/handwriting-mono.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/handwriting.png

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For some reason, the outlines of Ē and Ő ended up changing. And it happened back at the pt5 stage. I feel like the newer version of Ő is the correct one. It might be a FontForge bug.

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added xkcd-script/samples/ipsum-mono.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/ipsum.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/kerning.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/mathjax3/accents-zoo.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/mathjax3/binomial-theorem.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/mathjax3/derivative-definition.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/mathjax3/display-int-exp.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/mathjax3/fourier-transform.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/mathjax3/gaussian-integral.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/mathjax3/greek-letters.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/mathjax3/inline-ops.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/mathjax3/piecewise-abs.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/mathjax3/piecewise-big.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/mathjax3/piecewise-single.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/mathjax3/product-rule.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/mathjax3/quadratic-formula.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/mathjax3/sqrt-digit-radicand.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/mathjax3/stretchy-arrows.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/mathjax3/tall-integral-frac.png
Binary file modified xkcd-script/samples/mathjax3/tall-integral-sum.png
Loading