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
7 changes: 6 additions & 1 deletion xkcd-script/generator/pt2_character_classification.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import glob
import os
import base64
import sys

import matplotlib
matplotlib.use('agg')
Expand Down Expand Up @@ -111,7 +113,10 @@ def merge(img1, img1_bbox, img2, img2_bbox):
for char_no, (char, bbox, img) in enumerate(line):
char_repr = '-'.join(replacements.get(c, c) for c in char)
hex_repr = '-'.join(str(hex(ord(c))) for c in char)
b64_repr = char.encode('base64')
try:
b64_repr = char.encode('base64')
except LookupError:
b64_repr = base64.b64encode(char.encode('utf-8')).decode('utf-8')

fname = ('char_L{}_P{}_x{}_y{}_x{}_y{}_{b64_repr}.ppm'
''.format(line_no, char_no, *bbox, b64_repr=b64_repr))
Expand Down
1 change: 0 additions & 1 deletion xkcd-script/generator/pt3_ppm_to_svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from __future__ import division
import subprocess
import fontforge

def potrace(input_fname, output_fname):
subprocess.check_call(['potrace', '-s', input_fname, '-o', output_fname])
Expand Down
67 changes: 47 additions & 20 deletions xkcd-script/generator/pt4_svg_to_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,22 @@
import os
import glob
import parse
import base64

def array_ucs(ustr):
work = []
for uch in ustr:
n = ord(uch)
if n >= 0xDC00 and n <= 0xDFFF:
p = ord(work[-1])
if p >= 0xD800 and p <= 0xDBFF:
work[-1] = (((p & 0x03FF) << 10) | (n & 0x03FF)) + 0x10000
else:
raise Exception("surrogate nonpair")
else:
work.append(uch)
return work


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

Expand All @@ -13,7 +29,10 @@

pattern = 'char_L{line:d}_P{position:d}_x{x0:d}_y{y0:d}_x{x1:d}_y{y1:d}_{b64_str}.svg'
result = parse.parse(pattern, os.path.basename(fname))
chars = tuple(result['b64_str'].decode('base64').decode('utf-8'))
try:
chars = tuple(array_ucs(result['b64_str'].decode('base64').decode('utf-8')))
except AttributeError:
chars = tuple(array_ucs(base64.b64decode(result['b64_str'].encode()).decode('utf-8')))
bbox = (result['x0'], result['y0'], result['x1'], result['y1'])
characters.append([result['line'], result['position'], bbox, fname, chars])

Expand All @@ -37,9 +56,9 @@ def basic_font():
font.descent = 256;

# We create a ligature lookup table.
font.addLookup('ligatures', 'gsub_ligature', (), [[b'liga',
[[b'latn',
[b'dflt']]]]])
font.addLookup('ligatures', 'gsub_ligature', (), [['liga',
[['latn',
['dflt']]]]])
font.addLookupSubtable('ligatures', 'liga')

return font
Expand All @@ -50,6 +69,7 @@ def basic_font():
import tempfile
import shutil
import os
import sys


@contextmanager
Expand All @@ -62,7 +82,13 @@ def tmp_symlink(fname):
target = tempfile.mktemp(suffix=os.path.splitext(fname)[1])
fname = os.path.normpath(os.path.abspath(fname))
try:
os.symlink(fname, target)
if os.name == 'nt':
if sys.version_info.major == 2:
shutil.copy(fname, target)
else:
os.link(fname, target)
else:
os.symlink(fname, target)
yield target
finally:
if os.path.exists(target):
Expand Down Expand Up @@ -116,28 +142,28 @@ def create_char(font, chars, fname):
this_line.setdefault('cap-height', []).append(bbox[1])


import numpy as np
def mean(a):
return sum(a) / len(a)

import psMat

def scale_glyph(char, char_bbox, baseline, cap_height):
def scale_glyph(c, char_bbox, baseline, cap_height):
# TODO: The code in this function is convoluted - it can be hugely simplified.
# Essentially, all this function does is figure out how much
# space a normal glyph takes, then looks at how much space *this* glyph takes.
# With that magic ratio in hand, I now look at how much space the glyph *currently*
# takes, and scale it to the full EM. On second thoughts, this function really does
# need to be convoluted, so maybe the code isn't *that* bad...

font = char.font

# Get hold of the bounding box information for the imported glyph.
import_bbox = c.boundingBox()
import_width, import_height = import_bbox[2] - import_bbox[0], import_bbox[3] - import_bbox[1]

# Note that timportOutlines doesn't guarantee glyphs will be put in any particular location,
# so translate to the bottom and middle.

target_baseline = char.font.descent
top = char.font.ascent
target_baseline = c.font.descent
top = c.font.ascent
top_ratio = top / (top + target_baseline)

y_base_delta_baseline = char_bbox[3] - baseline
Expand All @@ -151,15 +177,15 @@ def scale_glyph(char, char_bbox, baseline, cap_height):
# A nice glyph size, in pixels. NOTE: In pixel space, cap_height is smaller than baseline, so make it positive.
full_glyph_size = -(cap_height - baseline) / top_ratio

to_canvas_coord_from_px = full_glyph_size / font.em
to_canvas_coord_from_px = full_glyph_size / c.font.em

anchor_ratio = (top + target_baseline) / height

# pixel scale factor
px_sf = (top + target_baseline) / font.em
px_sf = (top + target_baseline) / c.font.em

frac_of_full_size = (height / full_glyph_size)
import_frac_1000 = font.em / import_height
import_frac_1000 = c.font.em / import_height

t = psMat.scale(frac_of_full_size * import_frac_1000)
c.transform(t)
Expand Down Expand Up @@ -214,7 +240,7 @@ def autokern(font):
all_chars = caps + lower

# Add a kerning lookup table.
font.addLookup('kerning', 'gpos_pair', (), [[b'kern', [[b'latn', [b'dflt']]]]])
font.addLookup('kerning', 'gpos_pair', (), [['kern', [['latn', ['dflt']]]]])
font.addLookupSubtable('kerning', 'kern')

# Everyone knows that two slashes together need kerning... (even if they didn't realise it)
Expand Down Expand Up @@ -243,7 +269,7 @@ def autokern(font):

# Special case - add a vertial pipe by re-using an I, and stretching it a bit.
for line, position, bbox, fname, chars in characters:
if chars == (u'I',) and line == 4:
if chars == ('I',) and line == 4:
characters.append([4, None, bbox, fname, ('|',)])

for line, position, bbox, fname, chars in characters:
Expand All @@ -260,13 +286,13 @@ def autokern(font):

scale_glyph(
c, bbox,
baseline=np.mean(line_features['baseline']),
cap_height=np.mean(line_features['cap-height']))
baseline=mean(line_features['baseline']),
cap_height=mean(line_features['cap-height']))

translate_glyph(
c, bbox,
baseline=np.mean(line_features['baseline']),
cap_height=np.mean(line_features['cap-height']))
baseline=mean(line_features['baseline']),
cap_height=mean(line_features['cap-height']))

# Simplify, then put the vertices on rounded coordinate positions.
c.simplify()
Expand All @@ -284,3 +310,4 @@ def autokern(font):
os.remove(font_fname)
font.generate(font_fname)

font.close()
23 changes: 17 additions & 6 deletions xkcd-script/generator/pt5_gen_reprod_font.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import datetime
import os
import shutil
import time
import calendar

import fontforge

Expand All @@ -16,6 +16,10 @@
ttf = os.path.join(base, 'xkcd-script.ttf')
woff = os.path.join(base, 'xkcd-script.woff')

then_utc = datetime.datetime(2000, 1, 1, 0, 0)
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:
Expand All @@ -27,10 +31,19 @@
continue

if line.startswith('%%CreationDate'):
line = '%%CreationDate: Sat Jan 1 00:00:00 2000\n'
line = '%%CreationDate: ' + then_str + '\n'

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

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

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

if 'Generated by FontForge' in line:
continue
Expand All @@ -44,9 +57,7 @@
os.remove(sfd)
shutil.move(newsfd, sfd)

then = datetime.datetime(2000, 1, 1, 0, 0)
then = time.mktime(then.timetuple())
os.utime(sfd, (then, then))
os.utime(sfd, (then_unix, then_unix))

font = fontforge.open(sfd)

Expand Down