fnt+png→@tf.body.sh
 1	#!/usr/bin/env sh
 2	
 3	cat ~/fnt+png→@tf/fnt+png→@tf.sh.html
 4	#vim -c "colorscheme morning" -c "set nowrap" -c TOhtml -c q ~/fnt+png→@tf/fnt+png→@tf.sh
 5	
 6	#$(bat ~/fnt+png→@tf/fnt+png→@tf.sh | aha)
  1 #!/usr/bin/env zsh
  2 
  3 # fnt+png→@tf converts fonts drawn for the playdate using tools such as
  4 # https://play.date/caps/ to otf, ttf, or any font file managed by fontforge
  5 # using phosphor
  6 
  7 # made in bebou
  8 # september~november 2025
  9 
 10 # Dependencies (tested on nixos, see shell.nix)
 11 #   phosphor https://gitlab.com/emilegreis/phosphor
 12 #   jq
 13 #   imagemagick
 14 #   ascii-image-converter
 15 #   fontforge
 16 #       python3
 17 #       fonttools
 18 #       fontforge
 19 #       svgwrite
 20 #       pyyaml
 21 #       python-frontmatter
 22 
 23 # TODO
 24 # decouple html from the rest
 25 # read ascend line from glyph
 26 # build composable glyphs
 27 # -h/usage
 29 
 30 disclaimer() { clear; printf "\
 31 COLLECTIVE CONDITIONS FOR REUSE (see LICENSE)
 32 
 33 By typing YES, I confirm the gesture I'm about to perform does NOT go against
 34 any conditions of use stated by initial authors of the font: "; read allowed;
 35 
 36 case "$allowed" in # user's ethic
 37     [Yy]*) echo;;
 38     *) echo "Not today !"; exit 1;;
 39 esac
 40 }
 41 
 42 # nix-shell
 43 #       ╲
 44 #       zsh
 45 #         ╲
 46 #   fnt+png→@tf.sh
 47 #   _______╱╲_____________________________
 48 #  │                                      │
 49 #  │  for each font.png with metrics.fnt  │
 50 #  │            ╱               ╱╲        │
 51 #  │  ┌──────────┐         glyph  width   │
 52 #  │  │0123456789│           %     15     │
 53 #  │  │ !"#$%&'()│           <     9      │
 54 #  │  │*+,-.╱:;<=│           =     8      │
 55 #  │  │>?@ABCDEFG│           >     9      │
 56 #  │  │HIJKLMNOPQ│     +     ?     10     │
 57 #  │  │RSTUVWXYZ[│           @     13     │
 58 #  │  │╲]^_`abcde│           A     13     │
 59 #  │  │fghijklmno│           B     11     │
 60 #  │  │pqrstuvwxy│           C     14     │
 61 #  │  │z{│}~¥…™�v│           ⋮     ⋮      │
 62 #  │  └──────────┘                        │
 63 #  │_________________      _______________│
 64 #                    ╲    ╱
 65 #_____________________╲  ╱___________________________________  python  ________
 66 #                      ╲╱                             │
 67 #         magick           ascii-       for COLS      │     phosphor.py
 68 #          -bg white       image-         for ROWS    │
 69 #          -rm alpha       converter        awk       │
 70 #                                                     │
 71 #    table.png   >  table.bmp   >  table.txt   >  glyph.zns   →  .otf .ttf
 72 #                                                     │
 73 #    ▄▀▄▀▄▀▄▀▄▀     ▇▇▇▇▇▇▇▇▇▇     ..........     ....│.....     ··········
 74 #    ▄▀▄▀▄▀▄▀▄▀     ▇▇▇▇▇▇▇▇▇▇     ..........     ..........     ··········
 75 #    ▄▀▄░░░░▀▄▀     ▇▇▇    ▇▇▇     ...@@@@...     ...####...     ···????···
 76 #    ▄▀░░░░░░▄▀     ▇▇      ▇▇     ..@@@@@@..     ..######..     ··??????··
 77 #    ▄▀▄▀▄▀░░▄▀     ▇▇▇▇▇▇  ▇▇     ......@@..     ......##..     ······??··
 78 #    ▄▀▄░░░░░▄▀  →  ▇▇▇     ▇▇  →  ...@@@@@..  →  ...#####..  →  ···?????··
 79 #    ▄▀░░▄▀░░▄▀     ▇▇  ▇▇  ▇▇     ..@@..@@..     ..##..##..     ··??··??··
 80 #    ▄▀░░░░░░▄▀     ▇▇      ▇▇     ..@@@@@@..     ..######..     ··??????··
 81 #    ▄▀▄░░░░░▄▀     ▇▇▇     ▇▇     ...@@@@@..     ...#####..     ···?????··
 82 #    ▄▀▄▀▄▀▄▀▄▀     ▇▇▇▇▇▇▇▇▇▇     ..........     ..........     ··········
 83 #    ▄▀▄▀▄▀▄▀▄▀     ▇▇▇▇▇▇▇▇▇▇     ..........     ..........     ··········
 84 #                                                     │
 85 #_____________________________________________________│________________________
 86 
 87 unicode_lookup () { # Caching unicode points from .fnt
 88 find . -name "*.fnt" | while read fnt; do
 89     awk 'length($1) == 1 || $1 == "space" {printf "%s\n", $1}' $fnt
 90 done |
 91 sort fnt | uniq | while read char; do
 92     unicode -s "$char" --brief
 93 done | tee /dev/stderr | sed "s!U+!u!g" > unicode.lookup
 94 }
 95 
 96 if [ ! -f unicode.lookup ]; then
 97     echo "unicode.lookup not found, generating lookup…"
 98     unicode_lookup
 99     echo "done."
100 fi
101 
102 ID=0 # counter for applying fonts to specimen (css)
103 extract_glyphs_from_txt() {
104     ROWS=$1; COLS=$2; DIR=$3; NAME=$4; W=$5; H=$6; BEARING=$7
105     ID=$((ID + 1))
106 
107     START=$(date +%s.%N)
108     cat <<- % >> $SPECIMEN
109     <style>
110     @font-face {
111         font-family: "$NAME";
112         src: url("$DIR/$NAME/$NAME.ttf") format("truetype");
113         src: url("$DIR/$NAME/$NAME.otf") format("opentype");
114     }
115     section:nth-of-type($ID){font-family: "$NAME"; }
116     </style>
117     <section>
118     <h1 contenteditable="true">$NAME</h1>
119     <p>$COLS*$ROWS, $W*$H px, tracking=$TRACKING</p>
120     <p>$png</p>
121     $(
122         if [ -e "${fnt%.*}.fea" ]; then
123             echo "<details><summary>${fnt%.*}.fea</summary>"
124             echo "<pre>"
125             cat "${fnt%.*}.fea"
126             echo "</pre></details>"
127         fi
128     )
129     <img src="$png" alt="$png">
130     <table>
131     %
132 
133     printf "%s" "$NAME"
134     printf "\n"
135 
136     N=1
137     for y in $(seq 1 $ROWS); do
138         printf "<tr>" >> $SPECIMEN
139         h=$((y * H - H))
140         h_h=$((h + H))
141         for x in $(seq 1 $COLS); do
142             w=$((x * W - W + 1))
143             chasse=$(sed -n "${N} p" $DIR/$NAME/widths)
144             if [[ $chasse -gt $W ]]; then chasse="$W"; fi
145             CHAR=$(sed -n "${N} p" $DIR/$NAME/labels)
146             AGL=$(awk -v C="$CHAR" '$1 == C {printf "%s\n", $2; exit}' unicode.lookup)
147             if [[ $CHAR = "u0020" ]]; then AGL="u0020"; CHAR=" " fi
148             printf " %s |" $CHAR
149             printf "<td>%s<span>%s %s → %s</span></td>" $CHAR $CHAR $AGL $chasse >> $SPECIMEN
150             cat <<- % > "$DIR/$NAME/glyphs/$AGL.zns"
151             ---
152             name: $AGL
153             bearing_right: $BEARING
154             ---
155             $(awk -v h="$h" -v H="$H" -v w="$w" -v W="$W" -v c="$chasse" -v t="$BEARING" \
156                 'NR > h && NR <= h+H { print substr($0, w, W) }' \
157                 "$DIR/$NAME/$NAME.txt")
158             %
159             N=$((N + 1))
160         done
161         printf "\n"
162         printf "</tr>\n" >> $SPECIMEN
163     done
164     printf "</table></section>\n" >> $SPECIMEN
165     STOP=$(echo $(date +%s.%N) - $START | bc | cut -c -5)
166     printf "%ss\n" $STOP
167     printf "\n"
168 }
169 
170 #DIST=playdateFonts$(date +%s
171 #IN
172 #rm -rf $DIST
173 
174 TOTAL_START=$(date +%s.%N)
175 
176 disclaimer
177 
178 DIST=out
179 rm -rf Fonts/
180 unzip Fonts.zip
181 SPECIMEN=specimen.html
182 cat << % > $SPECIMEN
183 <!DOCTYPE html>
184 <html>
185     <head>
186         <title>playdate fonts $(date)</title>
187         <meta charset="utf-8" />
188         <style>
189         h1 {font-weight:normal}
190         img{
191         image-rendering: pixelated;
192         border:1px solid gray
193         }
194         span, p {font-family:monospace;}
195         td span {display:none;position:absolute;pointer-events:none}
196         td:hover span {display:block;background:white;}
197         td:hover{color:fuchsia}
198         section{border-top:1px solid black}
199         </style>
200     </head>
201     <body>
202     <p>$(date)</p>
203 %
204 KERN_COEF=48
205 find . -name "*.png" -printf '%P\n' | # https://stackoverflow.com/a/2596736
206 grep -v Numerals |
207 grep -E "[0-9]+-[0-9]+" |
208 while read png; do
209     fnt=$(echo "$png" | sed -r "s!(.*)-table.*!\1.fnt!g")
210 
211     NAME=$(basename "$png" | sed -r "s!(.*)-table.*!\1!g")
212     DIR=$(dirname "$png")
213 
214     FAMILY=$(echo "$png" | rev | cut -d/ -f 2 | rev | sed 's/-[0-9].*//')
215     echo $FAMILY
216     DIMS=$(echo "$png" | grep -oE "[0-9]+-[0-9]+")
217     png_LENGTH=$(identify -format "%[fx:w]" "$png")
218     png_HEIGHT=$(identify -format "%[fx:h]" "$png")
219     W=$(echo $DIMS | cut -d- -f 1)
220     H=$(echo $DIMS | cut -d- -f 2)
221     COLS=$((png_LENGTH / W))
222     ROWS=$((png_HEIGHT / H))
223     FONT_NAME=$(echo "$png" | sed -r "s!^.*/(.*)-table.*!\1!g; s!^font-!!g")
224 
225     if grep -q -e "--metrics" "$fnt"; then # ugly+++ :-)
226         grep -e "--metrics" "$fnt" | sed "s!--metrics=!!g" |
227         jq -rc '.pairs' |
228         sed -re 's!^\{!!g; s!\}$!!g;' |
229         sed -re 's!,"(..)"!\n"\1"!g;' |
230         sed -re 's@"(.)(.)":\[(-?.),.*@\\\1 \\\2 \3@g'|
231         sed '
232             s/\\0/zero/g
233             s/\\1/one/g
234             s/\\2/two/g
235             s/\\3/three/g
236             s/\\4/four/g
237             s/\\5/five/g
238             s/\\6/six/g
239             s/\\7/seven/g
240             s/\\8/eight/g
241             s/\\9/nine/g
242             s/\\\./period/g
243             s/\\,/comma/g
244             s/\\'\''/quotesingle/g
245             s/\\;/semicolon/g
246             s/\\:/colon/g
247             s/\\-/hyphen/g
248             s/\\ /space/g
249             s/\\?/question/g
250             s/\\!/exclam/g
251             s/\\‼/exclamdbl/g
252             s/\xef\xb8\x8e//g
253             s/\\\//slash/g
254             s/\\//g
255             '| tee pairs |
256         awk -v kc="$KERN_COEF" '{printf "\t\tpos @%s @%s %s;\n", $1, $2, $3 * kc}
257             ' > kerning_pairs #"${fnt%.*}.fea"
258 
259 sed -re 's@([[:alpha:]]*) ([[:alpha:]]*).*@\1\n\2@g' pairs | sort | uniq |
260 sed -re 's!([[:alpha:]]*)!\t\t@\1 = [\\\1 ];!g' > lookups
261 
262 cat << % | sed "/@[ 8]/d"  > "${fnt%.*}.fea" 
263 lookup kern {
264     lookupflag 0;
265 $(cat lookups)
266 $(cat kerning_pairs)
267 } kern;
268 
269 feature kern {
270   script DFLT;
271      language dflt ;
272       lookup kern;
273   script grek;
274      language dflt ;
275       lookup kern;
276   script latn;
277      language dflt ;
278       lookup kern;
279 } kern;
280 %
281     fi
282 
283     TRACKING=$(grep tracking $fnt | cut -d= -f2)
284     if [ -z "$TRACKING" ]; then TRACKING="1"; fi
285     #BEARING=$(echo $TRACKING / 2 | bc)
286 
287     mkdir -p "$DIR/$NAME/glyphs"
288     mkdir -p "$DIR/$NAME/svg"
289 
290     awk 'NR > 10 && /^$/{exit}; length($1) == 1 || $1 == "space" {printf "%s\n", $1}' $fnt | sed "s/space/u0020/g" > "$DIR/$NAME/labels"
291     awk 'NR > 10 && /^$/{exit}; length($1) == 1 || $1 == "space" {printf "%s\n", $2}' $fnt | sed "s/space/u0020/g" > "$DIR/$NAME/widths"
292 
293     # TODO ascenders and descenders guessing
294     # if   [[   ]]; then DESCENT_CHAR_REF=u0058      #X
295     # elif [[   ]]; then DESCENT_CHAR_REF=u0078      #x
296     # elif [[   ]]; then DESCENT_CHAR_REF=u0030; fi  #0
297     # DESCENT=$(tac "$OUT_FONT_PATH/$DESCENT_CHAR_REF.zns" | awk '{ #if $0 has @ print NR exit }')
298     # ASCENT= $(cat "$OUT_FONT_PATH/$DESCENT_CHAR_REF.zns" | awk '{ #if $0 has @ print NR exit }')
299 
300     #if [ -e "${fnt%.*}.fea" ]; then fea = "--features ${fnt%.*}.fea"; fi
301     #python ./phosphor/phosphor.py --glyphs "$DIR/$NAME/glyphs/"*.zns \
302     #   --ascent $H --descent 3 --top $H \
303     #   $fea \
304     #   --familyname "$FAMILY" --fontname "$NAME" \
305     #   --destination "$PWD/$DIR/$NAME"
306     fea="${fnt%.*}.fea"
307     magick "$png" -background white -alpha remove "$DIR/$NAME/$NAME.bmp"
308     ascii-image-converter "$DIR/$NAME/$NAME.bmp" -d $png_LENGTH,$png_HEIGHT -m ".#" -n > "$DIR/$NAME/$NAME.txt"
309     extract_glyphs_from_txt $ROWS $COLS "$DIR" "$NAME" $W $H $TRACKING
310     if [ -e "${fnt%.*}.fea" ]; then
311     python ./phosphor/phosphor.py --glyphs "$DIR/$NAME/glyphs/"*.zns \
312         --ascent $H --descent 3 --top $H \
313         --features "$fea" \
314         --familyname "$FAMILY" --fontname "$NAME" \
315         --destination "$PWD/$DIR/$NAME"
316     else
317     python ./phosphor/phosphor.py --glyphs "$DIR/$NAME/glyphs/"*.zns \
318         --ascent $H --descent 3 --top $H \
319         --familyname "$FAMILY" --fontname "$NAME" \
320         --destination "$PWD/$DIR/$NAME"
321     fi
322         #--features kerning.fea \
323 
324     mv "$fnt" "$png" "$DIR/$NAME"
325 done
326 
327 cat << % >> $SPECIMEN
328     </body>
329 </html>
330 %
331 
332 # INFO
333 TOTAL_STOP=$(echo $(date +%s.%N) - $TOTAL_START | bc | cut -c -5)
334 cat << % | tee report
335 took $TOTAL_STOP seconds
336 $(uname -a)
337 $(magick -version | head -n 1 | sed "s/^Version: //g")
338 ascii-image-converter $(ascii-image-converter -v)
339 $(awk -V  | head -n 1)
340 $(grep -V | head -n 1)
341 $(sed --v | head -n 1)
342 %