Skip to content

Instantly share code, notes, and snippets.

@pschanely
Created April 12, 2022 12:32
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pschanely/7df86fdc4150141487d1615a0398b562 to your computer and use it in GitHub Desktop.
Save pschanely/7df86fdc4150141487d1615a0398b562 to your computer and use it in GitHub Desktop.
Approximate Character Width Computation
import json
from pprint import pprint
import re
from statistics import stdev, mean
# These magic strings are part of ".afm" files, which hold
# statistics about a font.
helvetica_text = '''C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
C 33 ; WX 278 ; N exclam ; B 90 0 187 718 ;
C 34 ; WX 355 ; N quotedbl ; B 70 463 285 718 ;
C 35 ; WX 556 ; N numbersign ; B 28 0 529 688 ;
C 36 ; WX 556 ; N dollar ; B 32 -115 520 775 ;
C 37 ; WX 889 ; N percent ; B 39 -19 850 703 ;
C 38 ; WX 667 ; N ampersand ; B 44 -15 645 718 ;
C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
C 40 ; WX 333 ; N parenleft ; B 68 -207 299 733 ;
C 41 ; WX 333 ; N parenright ; B 34 -207 265 733 ;
C 42 ; WX 389 ; N asterisk ; B 39 431 349 718 ;
C 43 ; WX 584 ; N plus ; B 39 0 545 505 ;
C 44 ; WX 278 ; N comma ; B 87 -147 191 106 ;
C 45 ; WX 333 ; N hyphen ; B 44 232 289 322 ;
C 46 ; WX 278 ; N period ; B 87 0 191 106 ;
C 47 ; WX 278 ; N slash ; B -17 -19 295 737 ;
C 48 ; WX 556 ; N zero ; B 37 -19 519 703 ;
C 49 ; WX 556 ; N one ; B 101 0 359 703 ;
C 50 ; WX 556 ; N two ; B 26 0 507 703 ;
C 51 ; WX 556 ; N three ; B 34 -19 522 703 ;
C 52 ; WX 556 ; N four ; B 25 0 523 703 ;
C 53 ; WX 556 ; N five ; B 32 -19 514 688 ;
C 54 ; WX 556 ; N six ; B 38 -19 518 703 ;
C 55 ; WX 556 ; N seven ; B 37 0 523 688 ;
C 56 ; WX 556 ; N eight ; B 38 -19 517 703 ;
C 57 ; WX 556 ; N nine ; B 42 -19 514 703 ;
C 58 ; WX 278 ; N colon ; B 87 0 191 516 ;
C 59 ; WX 278 ; N semicolon ; B 87 -147 191 516 ;
C 60 ; WX 584 ; N less ; B 48 11 536 495 ;
C 61 ; WX 584 ; N equal ; B 39 115 545 390 ;
C 62 ; WX 584 ; N greater ; B 48 11 536 495 ;
C 63 ; WX 556 ; N question ; B 56 0 492 727 ;
C 64 ; WX 1015 ; N at ; B 147 -19 868 737 ;
C 65 ; WX 667 ; N A ; B 14 0 654 718 ;
C 66 ; WX 667 ; N B ; B 74 0 627 718 ;
C 67 ; WX 722 ; N C ; B 44 -19 681 737 ;
C 68 ; WX 722 ; N D ; B 81 0 674 718 ;
C 69 ; WX 667 ; N E ; B 86 0 616 718 ;
C 70 ; WX 611 ; N F ; B 86 0 583 718 ;
C 71 ; WX 778 ; N G ; B 48 -19 704 737 ;
C 72 ; WX 722 ; N H ; B 77 0 646 718 ;
C 73 ; WX 278 ; N I ; B 91 0 188 718 ;
C 74 ; WX 500 ; N J ; B 17 -19 428 718 ;
C 75 ; WX 667 ; N K ; B 76 0 663 718 ;
C 76 ; WX 556 ; N L ; B 76 0 537 718 ;
C 77 ; WX 833 ; N M ; B 73 0 761 718 ;
C 78 ; WX 722 ; N N ; B 76 0 646 718 ;
C 79 ; WX 778 ; N O ; B 39 -19 739 737 ;
C 80 ; WX 667 ; N P ; B 86 0 622 718 ;
C 81 ; WX 778 ; N Q ; B 39 -56 739 737 ;
C 82 ; WX 722 ; N R ; B 88 0 684 718 ;
C 83 ; WX 667 ; N S ; B 49 -19 620 737 ;
C 84 ; WX 611 ; N T ; B 14 0 597 718 ;
C 85 ; WX 722 ; N U ; B 79 -19 644 718 ;
C 86 ; WX 667 ; N V ; B 20 0 647 718 ;
C 87 ; WX 944 ; N W ; B 16 0 928 718 ;
C 88 ; WX 667 ; N X ; B 19 0 648 718 ;
C 89 ; WX 667 ; N Y ; B 14 0 653 718 ;
C 90 ; WX 611 ; N Z ; B 23 0 588 718 ;
C 91 ; WX 278 ; N bracketleft ; B 63 -196 250 722 ;
C 92 ; WX 278 ; N backslash ; B -17 -19 295 737 ;
C 93 ; WX 278 ; N bracketright ; B 28 -196 215 722 ;
C 94 ; WX 469 ; N asciicircum ; B -14 264 483 688 ;
C 95 ; WX 556 ; N underscore ; B 0 -125 556 -75 ;
C 96 ; WX 222 ; N quoteleft ; B 65 470 169 725 ;
C 97 ; WX 556 ; N a ; B 36 -15 530 538 ;
C 98 ; WX 556 ; N b ; B 58 -15 517 718 ;
C 99 ; WX 500 ; N c ; B 30 -15 477 538 ;
C 100 ; WX 556 ; N d ; B 35 -15 499 718 ;
C 101 ; WX 556 ; N e ; B 40 -15 516 538 ;
C 102 ; WX 278 ; N f ; B 14 0 262 728 ; L i fi ; L l fl ;
C 103 ; WX 556 ; N g ; B 40 -220 499 538 ;
C 104 ; WX 556 ; N h ; B 65 0 491 718 ;
C 105 ; WX 222 ; N i ; B 67 0 155 718 ;
C 106 ; WX 222 ; N j ; B -16 -210 155 718 ;
C 107 ; WX 500 ; N k ; B 67 0 501 718 ;
C 108 ; WX 222 ; N l ; B 67 0 155 718 ;
C 109 ; WX 833 ; N m ; B 65 0 769 538 ;
C 110 ; WX 556 ; N n ; B 65 0 491 538 ;
C 111 ; WX 556 ; N o ; B 35 -14 521 538 ;
C 112 ; WX 556 ; N p ; B 58 -207 517 538 ;
C 113 ; WX 556 ; N q ; B 35 -207 494 538 ;
C 114 ; WX 333 ; N r ; B 77 0 332 538 ;
C 115 ; WX 500 ; N s ; B 32 -15 464 538 ;
C 116 ; WX 278 ; N t ; B 14 -7 257 669 ;
C 117 ; WX 556 ; N u ; B 68 -15 489 523 ;
C 118 ; WX 500 ; N v ; B 8 0 492 523 ;
C 119 ; WX 722 ; N w ; B 14 0 709 523 ;
C 120 ; WX 500 ; N x ; B 11 0 490 523 ;
C 121 ; WX 500 ; N y ; B 11 -214 489 523 ;
C 122 ; WX 500 ; N z ; B 31 0 469 523 ;
C 123 ; WX 334 ; N braceleft ; B 42 -196 292 722 ;
C 124 ; WX 260 ; N bar ; B 94 -225 167 775 ;
C 125 ; WX 334 ; N braceright ; B 42 -196 292 722 ;
C 126 ; WX 584 ; N asciitilde ; B 61 180 523 326 ;'''
times_new_text ='''C 32 ; WX 250 ; N space ; B 0 0 0 0 ;
C 33 ; WX 333 ; N exclam ; B 130 -9 238 676 ;
C 34 ; WX 408 ; N quotedbl ; B 77 431 331 676 ;
C 35 ; WX 500 ; N numbersign ; B 5 0 496 662 ;
C 36 ; WX 500 ; N dollar ; B 44 -87 457 727 ;
C 37 ; WX 833 ; N percent ; B 61 -13 772 676 ;
C 38 ; WX 778 ; N ampersand ; B 42 -13 750 676 ;
C 39 ; WX 333 ; N quoteright ; B 79 433 218 676 ;
C 40 ; WX 333 ; N parenleft ; B 48 -177 304 676 ;
C 41 ; WX 333 ; N parenright ; B 29 -177 285 676 ;
C 42 ; WX 500 ; N asterisk ; B 69 265 432 676 ;
C 43 ; WX 564 ; N plus ; B 30 0 534 506 ;
C 44 ; WX 250 ; N comma ; B 56 -141 195 102 ;
C 45 ; WX 333 ; N hyphen ; B 39 194 285 257 ;
C 46 ; WX 250 ; N period ; B 70 -11 181 100 ;
C 47 ; WX 278 ; N slash ; B -9 -14 287 676 ;
C 48 ; WX 500 ; N zero ; B 24 -14 476 676 ;
C 49 ; WX 500 ; N one ; B 111 0 394 676 ;
C 50 ; WX 500 ; N two ; B 30 0 475 676 ;
C 51 ; WX 500 ; N three ; B 43 -14 431 676 ;
C 52 ; WX 500 ; N four ; B 12 0 472 676 ;
C 53 ; WX 500 ; N five ; B 32 -14 438 688 ;
C 54 ; WX 500 ; N six ; B 34 -14 468 684 ;
C 55 ; WX 500 ; N seven ; B 20 -8 449 662 ;
C 56 ; WX 500 ; N eight ; B 56 -14 445 676 ;
C 57 ; WX 500 ; N nine ; B 30 -22 459 676 ;
C 58 ; WX 278 ; N colon ; B 81 -11 192 459 ;
C 59 ; WX 278 ; N semicolon ; B 80 -141 219 459 ;
C 60 ; WX 564 ; N less ; B 28 -8 536 514 ;
C 61 ; WX 564 ; N equal ; B 30 120 534 386 ;
C 62 ; WX 564 ; N greater ; B 28 -8 536 514 ;
C 63 ; WX 444 ; N question ; B 68 -8 414 676 ;
C 64 ; WX 921 ; N at ; B 116 -14 809 676 ;
C 65 ; WX 722 ; N A ; B 15 0 706 674 ;
C 66 ; WX 667 ; N B ; B 17 0 593 662 ;
C 67 ; WX 667 ; N C ; B 28 -14 633 676 ;
C 68 ; WX 722 ; N D ; B 16 0 685 662 ;
C 69 ; WX 611 ; N E ; B 12 0 597 662 ;
C 70 ; WX 556 ; N F ; B 12 0 546 662 ;
C 71 ; WX 722 ; N G ; B 32 -14 709 676 ;
C 72 ; WX 722 ; N H ; B 19 0 702 662 ;
C 73 ; WX 333 ; N I ; B 18 0 315 662 ;
C 74 ; WX 389 ; N J ; B 10 -14 370 662 ;
C 75 ; WX 722 ; N K ; B 34 0 723 662 ;
C 76 ; WX 611 ; N L ; B 12 0 598 662 ;
C 77 ; WX 889 ; N M ; B 12 0 863 662 ;
C 78 ; WX 722 ; N N ; B 12 -11 707 662 ;
C 79 ; WX 722 ; N O ; B 34 -14 688 676 ;
C 80 ; WX 556 ; N P ; B 16 0 542 662 ;
C 81 ; WX 722 ; N Q ; B 34 -178 701 676 ;
C 82 ; WX 667 ; N R ; B 17 0 659 662 ;
C 83 ; WX 556 ; N S ; B 42 -14 491 676 ;
C 84 ; WX 611 ; N T ; B 17 0 593 662 ;
C 85 ; WX 722 ; N U ; B 14 -14 705 662 ;
C 86 ; WX 722 ; N V ; B 16 -11 697 662 ;
C 87 ; WX 944 ; N W ; B 5 -11 932 662 ;
C 88 ; WX 722 ; N X ; B 10 0 704 662 ;
C 89 ; WX 722 ; N Y ; B 22 0 703 662 ;
C 90 ; WX 611 ; N Z ; B 9 0 597 662 ;
C 91 ; WX 333 ; N bracketleft ; B 88 -156 299 662 ;
C 92 ; WX 278 ; N backslash ; B -9 -14 287 676 ;
C 93 ; WX 333 ; N bracketright ; B 34 -156 245 662 ;
C 94 ; WX 469 ; N asciicircum ; B 24 297 446 662 ;
C 95 ; WX 500 ; N underscore ; B 0 -125 500 -75 ;
C 96 ; WX 333 ; N quoteleft ; B 115 433 254 676 ;
C 97 ; WX 444 ; N a ; B 37 -10 442 460 ;
C 98 ; WX 500 ; N b ; B 3 -10 468 683 ;
C 99 ; WX 444 ; N c ; B 25 -10 412 460 ;
C 100 ; WX 500 ; N d ; B 27 -10 491 683 ;
C 101 ; WX 444 ; N e ; B 25 -10 424 460 ;
C 102 ; WX 333 ; N f ; B 20 0 383 683 ; L i fi ; L l fl ;
C 103 ; WX 500 ; N g ; B 28 -218 470 460 ;
C 104 ; WX 500 ; N h ; B 9 0 487 683 ;
C 105 ; WX 278 ; N i ; B 16 0 253 683 ;
C 106 ; WX 278 ; N j ; B -70 -218 194 683 ;
C 107 ; WX 500 ; N k ; B 7 0 505 683 ;
C 108 ; WX 278 ; N l ; B 19 0 257 683 ;
C 109 ; WX 778 ; N m ; B 16 0 775 460 ;
C 110 ; WX 500 ; N n ; B 16 0 485 460 ;
C 111 ; WX 500 ; N o ; B 29 -10 470 460 ;
C 112 ; WX 500 ; N p ; B 5 -217 470 460 ;
C 113 ; WX 500 ; N q ; B 24 -217 488 460 ;
C 114 ; WX 333 ; N r ; B 5 0 335 460 ;
C 115 ; WX 389 ; N s ; B 51 -10 348 460 ;
C 116 ; WX 278 ; N t ; B 13 -10 279 579 ;
C 117 ; WX 500 ; N u ; B 9 -10 479 450 ;
C 118 ; WX 500 ; N v ; B 19 -14 477 450 ;
C 119 ; WX 722 ; N w ; B 21 -14 694 450 ;
C 120 ; WX 500 ; N x ; B 17 0 479 450 ;
C 121 ; WX 500 ; N y ; B 14 -218 475 450 ;
C 122 ; WX 444 ; N z ; B 27 0 418 450 ;
C 123 ; WX 480 ; N braceleft ; B 100 -181 350 680 ;
C 124 ; WX 200 ; N bar ; B 67 -218 133 782 ;
C 125 ; WX 480 ; N braceright ; B 130 -181 380 680 ;
C 126 ; WX 541 ; N asciitilde ; B 40 183 502 323 ;'''
arial_text ='''C 32 ; WX 278 ; N space ; B 0 0 0 0 ;
C 33 ; WX 278 ; N exclam ; B 86 0 195 716 ;
C 34 ; WX 355 ; N quotedbl ; B 46 462 308 716 ;
C 35 ; WX 556 ; N numbersign ; B 10 -12 543 728 ;
C 36 ; WX 556 ; N dollar ; B 36 -103 509 782 ;
C 37 ; WX 889 ; N percent ; B 58 -26 828 728 ;
C 38 ; WX 667 ; N ampersand ; B 43 -17 644 728 ;
C 39 ; WX 191 ; N quotesingle ; B 44 462 144 716 ;
C 40 ; WX 333 ; N parenleft ; B 61 -210 297 728 ;
C 41 ; WX 333 ; N parenright ; B 61 -210 297 728 ;
C 42 ; WX 389 ; N asterisk ; B 31 423 354 728 ;
C 43 ; WX 584 ; N plus ; B 56 116 528 589 ;
C 44 ; WX 278 ; N comma ; B 83 -142 189 100 ;
C 45 ; WX 333 ; N hyphen ; B 32 215 302 303 ;
C 46 ; WX 278 ; N period ; B 91 0 191 100 ;
C 47 ; WX 278 ; N slash ; B 0 -12 278 728 ;
C 48 ; WX 556 ; N zero ; B 42 -12 508 719 ;
C 49 ; WX 556 ; N one ; B 109 0 373 719 ;
C 50 ; WX 556 ; N two ; B 29 0 503 719 ;
C 51 ; WX 556 ; N three ; B 42 -13 511 719 ;
C 52 ; WX 556 ; N four ; B 13 0 508 716 ;
C 53 ; WX 556 ; N five ; B 42 -12 516 706 ;
C 54 ; WX 556 ; N six ; B 38 -12 510 719 ;
C 55 ; WX 556 ; N seven ; B 47 0 511 707 ;
C 56 ; WX 556 ; N eight ; B 41 -12 512 719 ;
C 57 ; WX 556 ; N nine ; B 42 -12 512 719 ;
C 58 ; WX 278 ; N colon ; B 90 0 190 519 ;
C 59 ; WX 278 ; N semicolon ; B 83 -142 189 519 ;
C 60 ; WX 584 ; N less ; B 55 110 529 595 ;
C 61 ; WX 584 ; N equal ; B 56 204 528 503 ;
C 62 ; WX 584 ; N greater ; B 55 110 529 595 ;
C 63 ; WX 556 ; N question ; B 44 0 506 728 ;
C 64 ; WX 1015 ; N at ; B 54 -210 979 729 ;
C 65 ; WX 667 ; N A ; B -1 0 668 716 ;
C 66 ; WX 667 ; N B ; B 73 0 614 716 ;
C 67 ; WX 722 ; N C ; B 50 -12 683 728 ;
C 68 ; WX 722 ; N D ; B 77 0 669 716 ;
C 69 ; WX 667 ; N E ; B 79 0 613 716 ;
C 70 ; WX 611 ; N F ; B 82 0 565 716 ;
C 71 ; WX 778 ; N G ; B 53 -12 715 728 ;
C 72 ; WX 722 ; N H ; B 80 0 642 716 ;
C 73 ; WX 278 ; N I ; B 93 0 188 716 ;
C 74 ; WX 500 ; N J ; B 27 -12 422 716 ;
C 75 ; WX 667 ; N K ; B 73 0 665 716 ;
C 76 ; WX 556 ; N L ; B 73 0 521 716 ;
C 77 ; WX 833 ; N M ; B 74 0 757 716 ;
C 78 ; WX 722 ; N N ; B 76 0 640 716 ;
C 79 ; WX 778 ; N O ; B 48 -12 733 729 ;
C 80 ; WX 667 ; N P ; B 77 0 624 716 ;
C 81 ; WX 778 ; N Q ; B 43 -56 741 729 ;
C 82 ; WX 722 ; N R ; B 79 0 709 716 ;
C 83 ; WX 667 ; N S ; B 45 -12 615 728 ;
C 84 ; WX 611 ; N T ; B 23 0 591 716 ;
C 85 ; WX 722 ; N U ; B 79 -12 642 716 ;
C 86 ; WX 667 ; N V ; B 4 0 659 716 ;
C 87 ; WX 944 ; N W ; B 12 0 933 716 ;
C 88 ; WX 667 ; N X ; B 4 0 661 716 ;
C 89 ; WX 667 ; N Y ; B 3 0 659 716 ;
C 90 ; WX 611 ; N Z ; B 20 0 586 716 ;
C 91 ; WX 278 ; N bracketleft ; B 68 -199 262 716 ;
C 92 ; WX 278 ; N backslash ; B 0 -12 278 728 ;
C 93 ; WX 278 ; N bracketright ; B 19 -199 213 716 ;
C 94 ; WX 469 ; N asciicircum ; B 26 337 443 728 ;
C 95 ; WX 556 ; N underscore ; B -15 -199 567 -135 ;
C 96 ; WX 333 ; N grave ; B 43 583 227 720 ;
C 97 ; WX 556 ; N a ; B 36 -12 514 530 ;
C 98 ; WX 556 ; N b ; B 65 -12 515 716 ;
C 99 ; WX 500 ; N c ; B 39 -12 491 530 ;
C 100 ; WX 556 ; N d ; B 34 -12 484 716 ;
C 101 ; WX 556 ; N e ; B 37 -12 515 530 ;
C 102 ; WX 278 ; N f ; B 9 0 312 728 ;
C 103 ; WX 556 ; N g ; B 32 -210 489 530 ;
C 104 ; WX 556 ; N h ; B 66 0 488 716 ;
C 105 ; WX 222 ; N i ; B 66 0 154 716 ;
C 106 ; WX 222 ; N j ; B -46 -210 153 716 ;
C 107 ; WX 500 ; N k ; B 66 0 496 716 ;
C 108 ; WX 222 ; N l ; B 64 0 152 716 ;
C 109 ; WX 833 ; N m ; B 66 0 769 530 ;
C 110 ; WX 556 ; N n ; B 66 0 487 530 ;
C 111 ; WX 556 ; N o ; B 33 -12 519 530 ;
C 112 ; WX 556 ; N p ; B 66 -199 516 530 ;
C 113 ; WX 556 ; N q ; B 35 -199 484 530 ;
C 114 ; WX 333 ; N r ; B 65 0 347 530 ;
C 115 ; WX 500 ; N s ; B 31 -12 461 530 ;
C 116 ; WX 278 ; N t ; B 18 -7 271 700 ;
C 117 ; WX 556 ; N u ; B 64 -12 484 519 ;
C 118 ; WX 500 ; N v ; B 13 0 488 519 ;
C 119 ; WX 722 ; N w ; B 3 0 714 519 ;
C 120 ; WX 500 ; N x ; B 7 0 493 519 ;
C 121 ; WX 500 ; N y ; B 16 -210 491 519 ;
C 122 ; WX 500 ; N z ; B 20 0 479 519 ;
C 123 ; WX 334 ; N braceleft ; B 28 -210 311 728 ;
C 124 ; WX 260 ; N bar ; B 92 -210 168 728 ;
C 125 ; WX 334 ; N braceright ; B 23 -210 306 728 ;
C 126 ; WX 584 ; N asciitilde ; B 42 272 542 432 ;
C 128 ; WX 556 ; N uni20AC ; B -14 -12 541 728 ;'''
def parse(text: str) -> dict[str, float]:
chmap = {}
for line in text.split("\n"):
match = re.match(r'C (\d+) ; WX (\d+)', line)
k, v = map(int, match.groups())
if k < 128 and chr(k).isprintable():
chmap[k] = v
return {chr(k): v/chmap[ord('m')] for (k,v) in chmap.items()}
helveticamap = parse(helvetica_text)
timesmap = parse(times_new_text)
arialmap = parse(arial_text)
middlemap = {}
err = []
for k, tv in timesmap.items():
hv = helveticamap[k]
av = arialmap[k]
mv = round((tv + hv) / 2., 3)
middlemap[k] = mv
err.append(abs(mv - av) / av)
arial_avg = mean(arialmap.values())
print(json.dumps(middlemap))
print('Mean percentage error using constant width: ', mean(abs(arial_avg - v) for v in arialmap.values()))
print('Mean percentage error using approximate per-character widths: ', mean(err))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment