Skip to content
Merged
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.
1,631 changes: 1,037 additions & 594 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.
72 changes: 72 additions & 0 deletions xkcd-script/generator/pt6_derived_chars.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,16 @@ def _make_cedilla(font, cp, base_name, gap=8):
c.addReference('comma', _place_below(font, base_name, 'comma', gap))


def _make_macron_below(font, cp, base_name, gap=15):
if cp in _SKIP_CPS:
return
c = font.createMappedChar(cp)
c.clear()
c.addReference(base_name)
c.width = font[base_name].width
c.addReference('_macron_below_mark', _place_below(font, base_name, '_macron_below_mark', gap))


def _make_dot_below(font, cp, base_name, gap=15):
if cp in _SKIP_CPS:
return
Expand Down Expand Up @@ -254,6 +264,10 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
# Single dot below: same dot shape, used for Vietnamese dot-below characters.
_dot_below_mark = make_mark(font, '_dot_below_mark', extract_top_contours(font, 0x00DC, 2)[:1])

# Macron below: same stroke as the macron above, used for Semitic transliteration.
_macron_below_mark = make_mark(font, '_macron_below_mark', extract_top_contours(font, 0x0112, 1))
_macron_below_mark.changeWeight(20)


# Macron: topmost stroke from Ē, with weight correction.
_macron_mark = make_mark(font, '_macron_mark', extract_top_contours(font, 0x0112, 1))
Expand All @@ -272,6 +286,14 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
# Tilde.
_tilde_mark = _make_weighted_mark(font, 'asciitilde', _mark_scale, 35, '_tilde_mark')

# Breve: parenleft rotated 90° CCW — the arc gives the right bowl shape for ˘.
_breve_mark = _make_weighted_mark(font, 'parenleft', _mark_scale, 35, '_breve_mark')
_breve_mark.transform(psMat.rotate(math.radians(90)))
_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.transform(psMat.translate(-220, 0))

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

# dotlessi (U+0131): i without the dot, so í etc. don't stack dot + acute.
Expand Down Expand Up @@ -316,6 +338,7 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
(0x030A, _ring_mark),
(0x030B, _double_acute_mark),
(0x030C, _caron_mark),
(0x0306, _breve_mark), # ̆ combining breve
]:
c = font.createMappedChar(cp)
c.clear()
Expand All @@ -324,6 +347,28 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
c.addReference(mark.glyphname, psMat.translate(0, dy))
c.width = 0

# Below combining marks share the macron-below shape at different vertical positions.
_descender_bottom = font['p'].boundingBox()[1]
_mb_bb = font['_macron_below_mark'].boundingBox()

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

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

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


# ---------------------------------------------------------------------------
# Accented character tables
Expand Down Expand Up @@ -365,6 +410,17 @@ def _accented(cp, base_name, mark_name, gap=20, x_adj=0):
for cp, base in [(0x00E5, 'a'), (0x016E, 'U'), (0x016F, 'u')]:
_make_accented(font, cp, base, '_ring_above_mark')

# Breve: Ă Ĕ Ğ Ĭ Ŏ Ŭ / ă ĕ ğ ĭ ŏ ŭ (Romanian, Turkish, Belarusian, Esperanto, transliteration)
for cp, base in [
(0x0102, 'A'), (0x0114, 'E'), (0x011E, 'G'), (0x012C, 'I'), (0x014E, 'O'), (0x016C, 'U'),
]:
_make_accented(font, cp, base, '_breve_mark', gap=20)
for cp, base in [
(0x0103, 'a'), (0x0115, 'e'), (0x011F, 'g'), (0x014F, 'o'), (0x016D, 'u'),
]:
_make_accented(font, cp, base, '_breve_mark', gap=8)
_make_accented(font, 0x012D, 'dotlessi', '_breve_mark', gap=40) # ĭ — dotless to avoid dot+breve stack

# Ď Ť (uppercase): caron above, like other uppercase caron letters.
for cp, base in [(0x010E, 'D'), (0x0164, 'T')]:
_make_accented(font, cp, base, '_caron_mark')
Expand Down Expand Up @@ -627,6 +683,14 @@ def _make_eng(font, cp, base_name, comma_name, x_frac=0.88, x_offset=0, y_offset
]:
_make_cedilla(font, cp, base)

# Comma below: Ș ș Ț ț (Romanian — U+0219/U+021B are the canonical forms;
# U+015F/U+0163 cedilla variants already exist above)
for cp, base in [
(0x0218, 'S'), (0x0219, 's'),
(0x021A, 'T'), (0x021B, 't'),
]:
_make_cedilla(font, cp, base)

# Macron: Ā Ī Ō Ū / ā ī ō ū ē (Ē skipped — hand-drawn)
for cp, base in [
(0x0100, 'A'), (0x012A, 'I'), (0x014C, 'O'), (0x016A, 'U'), (0x0112, 'E'),
Expand All @@ -649,6 +713,14 @@ def _make_eng(font, cp, base_name, comma_name, x_frac=0.88, x_offset=0, y_offset
]:
_make_dot_below(font, cp, base)

# Macron below: Ḏ Ḻ Ṉ Ṟ Ṯ Ẕ / ḏ ḻ ṉ ṟ ṯ ẕ (Semitic transliteration)
for cp, base in [
(0x1E0E, 'D'), (0x1E3A, 'L'), (0x1E48, 'N'), (0x1E5E, 'R'), (0x1E6E, 'T'), (0x1E94, 'Z'),
(0x1E0F, 'd'), (0x1E3B, 'l'), (0x1E49, 'n'), (0x1E6F, 't'), (0x1E95, 'z'),
]:
_make_macron_below(font, cp, base)
_make_macron_below(font, 0x1E5F, 'r', gap=45) # ṟ — r sits low, needs extra gap

# 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')]:
Expand Down
16 changes: 15 additions & 1 deletion xkcd-script/generator/pt7_font_properties.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,21 @@
# ---------------------------------------------------------------------------

def _base_char(c):
"""Return the base letter for an accented character (e.g. É → E)."""
"""Return the base letter for an accented character (e.g. É → E).

Characters without a Unicode canonical decomposition (e.g. ø, ł) are
handled via the manual table below.
"""
_no_decomp = {
'ø': 'o', 'Ø': 'O',
'ł': 'l', 'Ł': 'L',
'đ': 'd', 'Đ': 'D',
'ħ': 'h', 'Ħ': 'H',
'ŧ': 't', 'Ŧ': 'T',
'ð': 'd', 'Ð': 'D',
}
if c in _no_decomp:
return _no_decomp[c]
decomp = unicodedata.decomposition(c)
if decomp and not decomp.startswith('<'):
return chr(int(decomp.split()[0], 16))
Expand Down
Binary file modified xkcd-script/samples/charmap_combining_diacritical_marks.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