Skip to content

Commit 2ae732f

Browse files
authored
Merge pull request #92 from dummy-index/feature/adaptive_bearing
add adaptive side-bearing calculation
2 parents 8584d7a + bf7a566 commit 2ae732f

18 files changed

Lines changed: 6845 additions & 6770 deletions

xkcd-script/font/xkcd-script.otf

-7.98 KB
Binary file not shown.

xkcd-script/font/xkcd-script.sfd

Lines changed: 6772 additions & 6729 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

-7.89 KB
Binary file not shown.

xkcd-script/font/xkcd-script.woff

-2.66 KB
Binary file not shown.

xkcd-script/generator/pt5_svg_to_font.py

Lines changed: 65 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -235,10 +235,67 @@ def pad_glyph(c):
235235
# Put horizontal padding around the glyph. I choose a number here that looks reasonable,
236236
# there are far more sophisticated means of doing this (like looking at the original image,
237237
# and calculating how much space there should be).
238-
space = 20
239-
scaled_width = c.boundingBox()[2]
240-
c.width = int(round(scaled_width + 2 * space))
241-
t = psMat.translate(space, 0)
238+
space = 0
239+
rspace = 20
240+
c.font['_pad_space'].width = space + rspace
241+
bbox = c.boundingBox()
242+
if c.glyphname in list('gjpqy'):
243+
# Recalculate the bounding box by excluding the tail of the glyph
244+
# Do not remove the glyph's tail if it is too close to the baseline
245+
capxrange = c.foreground.xBoundsAtY(-40, 600)
246+
if c.glyphname == 'j':
247+
bbox = tuple([(capxrange[0] + bbox[0])/2, bbox[1], capxrange[1], bbox[3]])
248+
else:
249+
bbox = tuple([capxrange[0], bbox[1], capxrange[1], bbox[3]])
250+
if c.glyphname == 'f':
251+
# Recalculate the bounding box by excluding the arm of the glyph
252+
# Restrict the arm so that it does not pierce through the stem of the next glyph
253+
xxrange = c.foreground.xBoundsAtY(0, 420)
254+
bbox = tuple([xxrange[0], bbox[1], max(xxrange[1], bbox[2] - (rspace + space + 0.12 * 600)), bbox[3]])
255+
lflatness = c.foreground.yBoundsAtX(bbox[0] - 1, bbox[0] + 20)
256+
rflatness = c.foreground.yBoundsAtX(bbox[2] - 20, bbox[2] + 1)
257+
roughness = []
258+
for i in range(4):
259+
roughness.append(c.foreground.xBoundsAtY(100 + 100 * i, 150 + 100 * i) or tuple([bbox[2], bbox[0]]))
260+
lroughness = np.sqrt(np.median([(roughness[i][0] - bbox[0])**2 for i in range(4)]))
261+
rroughness = np.sqrt(np.median([(bbox[2] - roughness[i][1])**2 for i in range(4)]))
262+
add_left = 0
263+
if lflatness[1] - lflatness[0] < 0.25 * 600:
264+
add_left = 0
265+
elif lroughness >= 35:
266+
add_left = 0
267+
elif lroughness >= 20:
268+
add_left = 5
269+
elif lroughness >= 10:
270+
add_left = 10
271+
elif lroughness >= 5:
272+
add_left = 15
273+
else:
274+
add_left = 20
275+
add_right = 0
276+
if rflatness[1] - rflatness[0] < 0.25 * 600:
277+
add_right = 0
278+
elif rroughness >= 35:
279+
add_right = 0
280+
elif rroughness >= 20:
281+
add_right = 5
282+
elif rroughness >= 10:
283+
add_right = 10
284+
elif rroughness >= 5:
285+
add_right = 15
286+
else:
287+
add_right = 20
288+
if c.glyphname == 'i':
289+
add_left += 10
290+
add_right += 10
291+
may_too_wide1 = list('aebdpr')
292+
if c.glyphname in may_too_wide1:
293+
if bbox[2] - bbox[0] > 370:
294+
add_left -= 5
295+
add_right -= 10
296+
scaled_width = bbox[2]
297+
c.width = round(scaled_width + rspace + space / 2 + add_right)
298+
t = psMat.translate(round((-bbox[0]) + space / 2 + add_left), 0)
242299
c.transform(t)
243300

244301

@@ -264,6 +321,10 @@ def charname(char):
264321
font.hhea_descent = -270; font.hhea_descent_add = False
265322
font.hhea_linegap = 77
266323

324+
# Information to be conveyed to the next stage.
325+
# I wanted to use font.persistent, but it causes an error. Instead, I use a dummy glyph.
326+
font.createChar(-1, '_pad_space')
327+
267328
# Per-character size scaling applied after changeWeight, to fine-tune individual glyphs
268329
# that end up slightly too large despite correct stroke weight.
269330
_per_char_operation = {

xkcd-script/generator/pt7_font_properties.py

Lines changed: 8 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
applies properties, saves.
77
"""
88
import fontforge
9-
import psMat
109
import unicodedata
1110

1211
font_fname = '../generated/xkcd-script-pt7.sfd'
@@ -60,40 +59,6 @@ def _expand_with_variants(font, chars):
6059
return result
6160

6261

63-
# ---------------------------------------------------------------------------
64-
# Advance-width overrides
65-
# ---------------------------------------------------------------------------
66-
67-
# f's lsb (17) leaves too much space before the stem; shift left 12 units.
68-
# Width is reduced by 110 total to bring natural spacing after the crossbar
69-
# into line (~90 units for fo), leaving arm clearance handled by kern rules.
70-
_f = font['f']
71-
_f.transform(psMat.translate(-12, 0))
72-
_f.width -= 110
73-
font['r'].transform(psMat.translate(10, 0))
74-
75-
76-
# g's bowl (above baseline) starts ~19 units further right than other rounded
77-
# letters. Shift the whole glyph left by 90 to bring bowl_lsb to ~20, matching
78-
# e/n/o. Width is reduced by 30 to leave a moderate right sidebearing (~25).
79-
_g = font['g']
80-
_g.transform(psMat.translate(-145, 0))
81-
_g.width -= 30
82-
83-
84-
# j
85-
_j = font['j']
86-
_j.transform(psMat.translate(-115, 0))
87-
_j.width -= 25
88-
89-
90-
# r's arm extends to ~x=402 but the default advance (440) leaves too much
91-
# whitespace; reduce to give a slight negative right sidebearing.
92-
_r = font['r']
93-
_r.transform(psMat.translate(-30, 0))
94-
_r.width = 385
95-
96-
9762
# ---------------------------------------------------------------------------
9863
# Kerning
9964
# ---------------------------------------------------------------------------
@@ -133,10 +98,15 @@ def expand(chars, left_side):
13398
return expanded
13499
font.autoKern('kern', sep, expand(left, left_side=True), expand(right, left_side=False), **kwargs)
135100

101+
a = font['_pad_space'].width
102+
a = max(a - 20, 0)
103+
104+
# autoKern looks at the outline, so even if you change the padding, it absorbs all of it.
105+
# Use `+a` when you want to link the spacing after kerning to the padding.
136106
kern(150, ['/', '\\'], ['/', '\\'])
137-
kern(60, ['s'], set(lower) - {'j', 'f'}, minKern=50)
107+
kern(60+a, ['s'], set(lower) - {'j', 'f'}, minKern=50)
138108
# x has diagonal strokes that leave visual space on its left side.
139-
kern(90, set(lower) - {'f'}, ['x'], minKern=40)
109+
kern(90+a, set(lower) - {'f'}, ['x'], minKern=40)
140110
# F/E are separated from T/J so they can use a tighter target gap.
141111
kern(130, ['F'], set(all_chars) - {'f', 'j'})
142112
kern(140, ['E'], ['V', 'W', 'Y'])
@@ -149,6 +119,7 @@ def expand(chars, left_side):
149119

150120

151121
autokern(font)
122+
font.removeGlyph(font['_pad_space'])
152123

153124

154125
# ---------------------------------------------------------------------------
-337 Bytes
Loading
-37 Bytes
Loading
-52 Bytes
Loading
-160 Bytes
Loading

0 commit comments

Comments
 (0)