#!/usr/bin/env python3
"""Brinewake Regular: original ink-cut display serif, drawn here in 1000 units.

Build: uv run --with-requirements scripts/requirements-font-build.txt \
    python scripts/generate-brinewake-font.py
No existing font is read. Cubic contours, wedge serifs and optical corrections
are authored below. Booleans remove overlaps before conversion to TrueType.
The sampled curves have sub-unit chord error at display sizes.
"""
from __future__ import annotations
import os
from pathlib import Path
os.environ.setdefault("SOURCE_DATE_EPOCH", "1788825600")
from fontTools.fontBuilder import FontBuilder
from fontTools.feaLib.builder import addOpenTypeFeaturesFromString
from fontTools.pens.basePen import BasePen
from fontTools.pens.ttGlyphPen import TTGlyphPen
from fontTools.svgLib.path import parse_path
from shapely.geometry import Polygon, LineString, Point, box
from shapely.geometry.polygon import orient
from shapely.affinity import translate, scale, rotate
from shapely.ops import unary_union

SOURCE = Path(__file__).resolve()
OUT = SOURCE.parent if SOURCE.name == 'brinewake-source.py' else SOURCE.parents[1] / 'public' / 'fonts'

class Outline(BasePen):
    def __init__(self):
        super().__init__(None)
        self.parts = []
        self.points = []
    def _moveTo(self, p): self.points = [p]
    def _lineTo(self, p): self.points.append(p)
    def _curveToOne(self, a, b, c):
        p = self.points[-1]
        for i in range(1, 49):
            t = i / 48; u = 1 - t
            self.points.append(tuple(u**3*p[j]+3*u*u*t*a[j]+3*u*t*t*b[j]+t**3*c[j] for j in (0, 1)))
    def _closePath(self):
        self.parts.append(Polygon(self.points)); self.points = []
    def _endPath(self): self._closePath()

def path(d):
    pen = Outline(); parse_path(d, pen)
    return unary_union(pen.parts)

def union(*g): return unary_union(g)
def poly(*p): return Polygon(p)
def move(g, x=0, y=0): return translate(g, xoff=x, yoff=y)
def resize(g, x=1, y=1): return scale(g, xfact=x, yfact=y, origin=(0,0))
def stroke(points, width): return LineString(points).buffer(width/2, cap_style=2, join_style=2)
def dot(x,y,r=48): return Point(x,y).buffer(r, quad_segs=24)

def foot(x, w=115, y=0):
    # Short inner edge and a knife-like outer edge; a concave bracket, not a slab.
    return move(path(f'M {x-34} 0 L {x+w+31} 0 L {x+w+27} 22 C {x+w-8} 26 {x+w-8} 38 {x+w} 84 L {x} 84 C {x+8} 37 {x+4} 25 {x-30} 19 Z'),0,y)

def stem(x, top=700, bottom=0, w=115, serif=True):
    body = path(f'M {x} {bottom} L {x+w} {bottom} C {x+w-16} {bottom+160} {x+w-12} {top-120} {x+w} {top} L {x-31} {top-29} L {x-29} {top-48} L {x} {top-46} Z')
    return union(body, foot(x,w,bottom)) if serif else body

def bowl(w=510, h=490):
    # Asymmetric outer mass and a counter pulled upward/rightward. Round forms
    # overshoot both alignment zones; their shoulders are not ellipse offsets.
    outer=path('M 262 -12 C 99 -15 17 76 21 232 C 24 395 113 503 266 503 C 427 508 506 401 499 258 C 493 94 409 -7 262 -12 Z')
    inner=path('M 247 31 C 332 20 370 107 378 237 C 386 377 352 458 279 461 C 199 465 152 380 143 250 C 134 104 169 40 247 31 Z')
    return resize(outer.difference(inner),w/510,h/490)

def open_bowl(w=510,h=490):
    g=bowl(w,h)
    cut=poly((w*.72,h*.34),(w+10,h*.28),(w+10,h*.80),(w*.74,h*.70))
    return g.difference(cut)

G={}
def put(c,g,width): G[c]=(g,width)
# Lowercase: a real two-storey a, single-storey g, hooked f and long descenders.
put('o',bowl(),530)
put('c',open_bowl(),500)
# The crossbar meets the outer shoulder with overlap, not a tangent seam.
put('e',union(bowl().difference(poly((356,110),(520,148),(520,272),(357,249))),path('M 105 263 L 494 289 L 493 246 L 105 224 Z')),520)
put('a',union(path('M 52 408 C 117 485 218 513 313 493 C 419 471 454 423 449 306 L 441 71 C 443 31 462 27 487 43 L 491 18 C 403 -35 344 -9 331 40 L 331 303 C 338 409 306 456 249 455 C 200 455 169 423 152 379 L 52 408 Z'),resize(bowl(),.70,.53)),510)
put('b',union(stem(49,740,w=112,serif=False),move(resize(bowl(),.86,1),57)),535)
put('d',union(move(resize(bowl(),.87,1),-2),stem(377,740,w=112)),555)
put('p',union(stem(50,490,-220,w=111),move(resize(bowl(),.87,1),58)),540)
put('q',union(resize(bowl(),.87,1),stem(378,490,-220,w=111)),540)
# Shoulder is drawn as an arch with a broad falling right-hand stroke.
shoulder=path('M 113 0 L 113 320 C 152 436 219 509 305 501 C 398 493 427 437 425 324 L 423 64 L 434 0 L 300 0 L 308 311 C 311 405 298 443 259 441 C 200 438 155 369 144 288 L 144 0 Z')
put('n',union(stem(49,490,w=110),shoulder,foot(308,115)),495)
put('h',union(stem(49,740,w=110),shoulder,foot(308,115)),495)
put('m',union(stem(49,490,w=106),resize(shoulder,.88,1),move(resize(shoulder,.88,1),241),foot(271,105),foot(512,105)),687)
put('u',union(path('M 37 490 L 170 490 L 164 186 C 159 86 174 48 214 48 C 273 49 318 118 331 210 L 355 211 C 327 62 254 -24 171 -12 C 80 2 51 54 53 166 L 57 424 L 28 437 Z'),stem(334,490,w=110)),510)
put('r',union(stem(49,490,w=110),path('M 139 279 C 171 456 264 541 349 484 L 326 370 C 236 429 183 371 156 264 Z')),385)
put('l',stem(55,740),260)
put('i',union(stem(55,490),dot(109,652,56)),260)
put('j',union(stem(74,490,-97,serif=False),path('M 74 70 L 189 70 L 188 -56 C 188 -199 101 -251 4 -215 L 15 -128 C 79 -164 92 -125 83 -58 Z'),dot(130,652,56)),265)
put('f',union(stem(98,487,-5),path('M 98 397 L 98 528 C 96 692 170 783 303 741 L 279 636 C 200 689 193 646 203 546 L 211 397 Z'),box(25,436,291,477)),330)
put('t',union(path('M 85 448 L 84 626 L 196 676 L 192 474 L 305 474 L 294 433 L 192 433 L 184 130 C 181 47 228 40 288 86 L 300 58 C 233 -14 158 -35 107 23 C 81 50 72 84 76 155 Z'),box(22,433,192,474)),335)
put('g',union(resize(bowl(),.88,1),path('M 387 490 L 509 519 L 505 486 L 473 470 L 473 -24 C 473 -179 375 -247 231 -232 C 160 -226 102 -197 57 -165 L 102 -83 C 171 -166 223 -196 280 -191 C 343 -184 368 -131 364 -36 L 367 144 Z')),550)
# The s is a ribbon, not a stroked centreline: cut terminals face inward.
s=path('M 419 461 L 403 349 L 379 352 C 366 425 318 463 256 460 C 180 458 158 402 182 361 C 215 306 353 288 401 230 C 477 139 408 0 263 -12 C 160 -23 76 11 36 48 L 54 166 L 76 163 C 91 75 156 28 226 30 C 302 32 330 78 303 126 C 269 186 120 198 72 277 C 9 383 97 502 250 504 C 316 505 379 486 419 461 Z')
put('s',s,475)
# Diagonal letters carry unequal strokes. Flat wedges give the serif rhythm.
def diagonal(a,b,w): return stroke([a,b],w)
def wedge(x,y,w=130): return poly((x-w/2,y),(x+w/2,y),(x+w/2-9,y+23),(x-w/2+9,y+23))
put('v',union(poly((23,490),(153,490),(286,111),(423,490),(467,490),(281,-12),(234,-12)),wedge(86,467),wedge(430,467,108)),500)
put('w',union(poly((17,490),(138,490),(233,134),(324,413),(299,490),(415,490),(518,134),(629,490),(671,490),(515,-12),(466,-12),(347,324),(234,-12),(185,-12)),wedge(74,467),wedge(649,467,106)),700)
put('y',union(G['v'][0],path('M 268 75 L 313 77 L 224 -154 C 195 -225 125 -259 52 -218 L 81 -129 C 129 -165 163 -158 183 -109 Z')),500)
put('x',union(poly((30,490),(170,490),(431,0),(291,0)),diagonal((414,490),(43,0),36),wedge(80,0),wedge(373,0),wedge(86,467),wedge(408,467,98)),480)
# A flush blade replaces the first proof's stair-stepped diagonal serif.
put('k',union(stem(49,740),poly((150,196),(428,490),(466,490),(172,172)),path('M 232 285 L 301 343 C 342 229 381 119 448 0 L 308 0 C 287 106 263 210 232 285 Z'),wedge(384,0)),490)
put('z',union(poly((29,490),(449,490),(449,459),(168,38),(329,40),(415,128),(439,123),(423,0),(13,0),(13,30),(293,453),(148,451),(56,371),(32,375))),475)
# Capitals are broader than the lowercase, with restrained wedge serifs.
put('O',bowl(695,700),725)
put('C',open_bowl(695,700),700)
put('G',union(open_bowl(695,700),stem(494,326,8,w=111,serif=False),box(397,300,635,341)),735)
put('Q',union(bowl(695,700),path('M 375 139 C 466 64 536 -56 699 -65 L 688 -103 C 529 -147 426 -10 360 105 Z')),745)
put('I',union(stem(67),wedge(123,678,184)),300)
put('H',union(stem(59),stem(498),box(145,327,533,372)),695)
put('L',union(stem(59),path('M 115 0 L 571 0 L 589 163 L 559 166 C 510 75 480 44 372 42 L 115 42 Z')),635)
put('E',union(stem(59),path('M 100 700 L 565 700 L 575 554 L 549 552 C 510 637 476 657 391 657 L 100 657 Z'),path('M 100 0 L 574 0 L 592 157 L 565 158 C 509 73 482 44 381 43 L 100 43 Z'),box(120,324,457,365),poly((432,435),(458,437),(458,252),(432,256),(412,333))),640)
put('F',union(stem(59),path('M 100 700 L 565 700 L 575 554 L 549 552 C 510 637 476 657 391 657 L 100 657 Z'),box(120,334,457,375),poly((432,445),(458,447),(458,262),(432,266),(412,343))),615)
put('T',union(stem(271),path('M 19 700 L 641 700 L 650 535 L 621 534 C 575 637 544 657 440 657 L 224 657 C 127 657 88 635 42 534 L 13 535 Z')),670)
put('D',union(stem(59),path('M 138 700 L 310 700 C 545 700 661 564 653 356 C 645 128 525 0 293 0 L 138 0 L 138 43 L 277 43 C 442 41 521 157 523 350 C 525 536 451 657 297 657 L 138 657 Z')),705)
put('P',union(stem(59),path('M 128 700 L 326 700 C 515 704 614 626 606 477 C 599 327 468 277 323 289 L 135 289 L 135 332 L 303 332 C 419 324 479 376 481 479 C 483 583 433 657 319 657 L 128 657 Z')),655)
put('R',union(G['P'][0],path('M 288 316 L 414 334 C 455 240 506 114 555 61 C 586 26 613 22 645 28 L 653 5 C 592 -19 503 -8 468 29 C 405 94 351 216 288 316 Z')),690)
put('B',union(stem(59),path('M 130 700 L 334 700 C 513 702 595 631 581 510 C 575 428 510 384 454 367 C 546 352 622 293 613 188 C 602 57 510 0 329 0 L 129 0 L 129 43 L 308 43 C 431 40 488 103 489 202 C 490 294 439 342 321 342 L 133 342 L 133 385 L 302 385 C 404 384 457 431 462 516 C 467 607 421 657 318 657 L 130 657 Z')),670)
put('S',resize(s,1.30,700/490),640)
put('J',union(stem(378,700,164,serif=False),path('M 378 231 L 493 231 L 493 201 C 493 53 411 -17 278 -12 C 160 -8 95 44 64 104 L 144 179 C 175 83 220 33 277 33 C 344 33 387 89 378 192 Z'),box(228,658,526,700)),565)
put('U',union(path('M 45 700 L 179 700 L 166 245 C 163 105 219 42 327 41 C 445 40 500 118 500 254 L 500 700 L 544 700 L 544 250 C 544 78 464 -14 302 -13 C 119 -11 51 69 51 238 Z'),wedge(101,678,175),wedge(521,678,133)),615)
put('A',union(poly((17,0),(280,700),(352,716),(641,0),(507,0),(276,595),(59,0)),box(153,216,475,258),wedge(63,0,120),wedge(568,0,175)),665)
put('V',union(poly((22,700),(161,700),(366,138),(570,700),(616,700),(356,-12),(302,-12)),wedge(88,678,175),wedge(588,678,132)),645)
put('W',union(poly((20,700),(155,700),(313,159),(457,592),(424,700),(555,700),(726,158),(882,700),(927,700),(716,-12),(662,-12),(478,519),(305,-12),(252,-12)),wedge(82,678,170),wedge(489,678,170),wedge(899,678,120)),960)
put('N',union(stem(52,w=44),stem(507,w=44),poly((65,700),(181,700),(543,131),(543,0),(507,0),(89,641))),625)
put('M',union(stem(51,w=44),stem(641,w=112),poly((58,700),(185,700),(416,168),(658,700),(710,700),(414,0),(380,0),(85,653))),835)
put('K',union(stem(59),poly((160,250),(566,700),(614,700),(181,222)),path('M 255 359 L 379 441 C 450 271 538 99 639 0 L 489 0 C 405 128 329 268 255 359 Z'),wedge(561,0,175)),690)
put('X',union(poly((32,700),(179,700),(617,0),(470,0)),diagonal((580,700),(56,0),43),wedge(86,0,145),wedge(538,0,175),wedge(103,678,175),wedge(570,678,145)),675)
put('Y',union(stem(280,307),poly((20,700),(162,700),(364,325),(574,700),(621,700),(385,284),(280,284)),wedge(89,678,174),wedge(588,678,130)),655)
put('Z',union(poly((37,700),(617,700),(617,670),(198,43),(451,44),(580,173),(608,169),(589,0),(21,0),(21,30),(441,657),(199,656),(72,540),(45,544))),655)
# Figures: proportional lining numerals, independently drawn at cap height.
put('0',bowl(580,700),620)
put('1',union(stem(215),poly((51,572),(257,708),(319,700),(257,624),(64,548)),foot(166,217)),445)
put('2',path('M 32 554 C 98 674 205 725 332 708 C 470 690 540 586 512 476 C 483 357 326 259 169 99 L 359 101 C 427 101 454 116 485 176 L 514 170 L 497 0 L 28 0 L 28 37 C 123 170 326 363 371 474 C 418 594 370 661 291 663 C 214 666 157 608 131 535 Z'),565)
put('3',path('M 52 620 C 125 698 240 725 350 700 C 473 672 504 596 483 512 C 466 440 400 389 340 372 C 453 356 524 284 510 183 C 492 37 352 -30 211 -12 C 130 -3 65 26 29 70 L 96 161 C 142 78 197 31 270 34 C 350 38 395 103 391 190 C 387 292 327 341 215 340 L 172 340 L 172 382 L 204 382 C 311 382 365 443 370 527 C 375 615 339 667 270 666 C 206 665 160 622 129 556 Z'),560)
put('4',union(poly((353,710),(397,710),(75,245),(528,245),(528,198),(24,198),(24,239)),box(310,0,421,710),foot(310,111)),570)
put('5',path('M 98 700 L 505 700 L 481 592 L 139 601 L 114 401 C 190 446 280 455 359 427 C 487 384 536 272 495 152 C 451 26 319 -29 192 -9 C 115 4 59 32 22 78 L 92 165 C 137 82 191 36 256 35 C 342 33 391 110 384 216 C 377 327 306 373 236 371 C 179 370 137 350 94 311 L 60 329 Z'),555)
put('6',union(move(resize(bowl(),1.02,.88),0,-1),path('M 25 239 C 25 533 175 729 412 721 L 443 680 C 278 673 184 571 162 409 L 142 240 Z')),565)
put('7',path('M 27 700 L 541 700 L 541 664 C 351 409 299 203 304 0 L 177 0 C 198 244 309 437 462 597 L 184 594 C 111 593 94 563 60 501 L 29 506 Z'),575)
put('8',union(resize(bowl(),.98,.77),move(resize(bowl(),.89,.68),23,375)),555)
put('9',union(move(resize(bowl(),1.02,.88),0,270),path('M 510 480 C 510 187 360 -10 123 -1 L 92 39 C 257 46 351 148 373 310 L 393 479 Z')),565)
# ASCII punctuation and typographic punctuation. No invented language coverage.
put(' ',Polygon(),260)
put('.',dot(78,48),175)
put(',',union(dot(86,51),path('M 124 63 C 143 -28 102 -90 39 -121 L 25 -99 C 72 -63 74 -32 62 4 Z')),185)
put(':',union(dot(78,48),dot(78,356)),175)
put(';',union(G[','][0],dot(86,356)),185)
put('!',union(path('M 37 701 L 169 701 L 122 198 L 81 198 Z'),dot(104,48)),220)
put('?',union(path('M 23 567 C 70 675 168 725 279 708 C 399 690 462 601 436 500 C 417 424 338 375 273 332 C 239 310 224 269 228 200 L 184 200 C 169 291 190 349 238 399 C 299 462 325 512 314 574 C 304 635 260 670 214 662 C 164 654 138 608 123 547 Z'),dot(207,48)),475)
put('-',poly((25,263),(297,280),(291,222),(19,205)),330)
put('_',box(12,-160,480,-119),500)
put('–',box(24,231,500,274),525)
put('—',box(24,231,850,274),875)
put('+',union(box(26,270,481,314),box(231,65,276,520)),525)
put('=',union(box(26,181,481,225),box(26,364,481,408)),525)
put('/',diagonal((23,-126),(357,733),43),405)
put('\\',diagonal((23,733),(357,-126),43),405)
put('|',box(66,-180,111,740),180)
put("'",path('M 34 700 L 126 700 L 105 502 L 60 502 Z'),165)
put('"',union(G["'"][0],move(G["'"][0],155)),320)
put('’',move(G[','][0],0,600),185)
put('‘',move(resize(G[','][0],-1,-1),165,610),185)
put('”',union(G['’'][0],move(G['’'][0],167)),350)
put('“',union(G['‘'][0],move(G['‘'][0],167)),350)
put('(',path('M 248 759 L 271 736 C 171 618 137 477 135 303 C 133 119 171 -49 263 -188 L 238 -210 C 79 -60 22 112 25 302 C 28 495 90 651 248 759 Z'),305)
put(')',move(resize(G['('][0],-1,1),295),305)
put('[',union(box(44,-192,140,735),box(44,695,272,735),box(44,-192,272,-152)),310)
put(']',move(resize(G['['][0],-1,1),305),310)
put('{',path('M 275 742 L 275 709 C 152 713 188 558 180 452 C 173 359 122 299 67 278 C 128 257 169 202 180 111 C 193 -9 150 -166 275 -159 L 275 -195 C 72 -211 85 -49 85 84 C 84 188 51 242 18 255 L 18 299 C 57 318 84 371 85 468 C 87 605 68 761 275 742 Z'),315)
put('}',move(resize(G['{'][0],-1,1),305),315)
put('<',poly((450,518),(477,479),(108,285),(477,89),(450,50),(24,268),(24,303)),520)
put('>',move(resize(G['<'][0],-1,1),500),520)
put('^',poly((32,448),(246,710),(289,710),(502,448),(458,426),(268,645),(77,426)),545)
put('~',path('M 26 226 C 109 380 214 242 318 263 C 364 272 389 301 419 327 L 447 300 C 363 145 259 283 155 263 C 109 254 84 225 54 199 Z'),480)
put('*',union(*[rotate(poly((169,710),(226,710),(210,556),(186,556)),a,origin=(198,538)) for a in range(0,360,72)]),405)
put('#',union(diagonal((153,0),(292,700),44),diagonal((348,0),(487,700),44),box(56,208,515,268),box(93,432,552,492)),600)
put('$',union(move(resize(s,1.12,1.30),18,20),box(254,-95,291,779)),575)
put('%',union(move(resize(bowl(),.43,.54),0,442),move(resize(bowl(),.43,.54),343,0),diagonal((83,0),(488,700),41)),620)
# path() unions independent contours; carve the ampersand counters explicitly.
amp_outer=path('M 597 0 L 456 0 L 380 82 C 297 -5 194 -36 101 11 C -5 65 8 211 88 300 L 154 369 C 62 480 52 569 105 644 C 173 742 315 736 372 653 C 433 565 366 465 265 409 L 432 192 C 477 267 491 327 480 362 L 436 375 L 434 399 L 626 399 L 623 373 L 572 361 C 548 280 516 215 460 155 Z')
put('&',amp_outer.difference(path('M 227 450 C 293 498 327 562 300 622 C 279 670 225 677 194 642 C 157 600 177 521 227 450 Z M 183 334 C 91 239 111 121 172 79 C 232 37 303 66 351 117 Z')),665)
put('@',union(move(resize(G['a'][0],.70,.78),155,125),path('M 515 268 C 510 172 554 138 605 184 C 676 247 693 449 595 562 C 496 677 308 701 177 591 C 45 482 28 264 109 127 C 194 -18 390 -42 544 40 L 564 10 C 390 -96 157 -66 62 83 C -41 245 1 492 145 619 C 290 746 517 727 649 590 C 773 460 752 235 649 151 C 558 77 452 104 447 212 Z')),800)
put('`',poly((51,734),(152,699),(214,575),(180,558)),280)
put('…',union(G['.'][0],move(G['.'][0],175),move(G['.'][0],350)),525)
for c in ('\u00a0',): put(c,Polygon(),260)

# Fixed glyph names keep the cmap, source proof and positioning table legible.
def name(c):
    if c.isascii() and c.isalpha(): return c
    return f'uni{ord(c):04X}'

def tt(g):
    pen=TTGlyphPen(None)
    if not g.is_empty:
        assert g.is_valid, 'Invalid authored outline'
        shapes=list(g.geoms) if g.geom_type=='MultiPolygon' else [g]
        for p in shapes:
            p=orient(p.simplify(.30,preserve_topology=True),sign=-1)
            for ring in (p.exterior,*p.interiors):
                pts=[(round(x),round(y)) for x,y in list(ring.coords)[:-1]]
                clean=[]
                for pt in pts:
                    if not clean or clean[-1]!=pt: clean.append(pt)
                if len(set(clean))<3: continue
                pen.moveTo(clean[0])
                for pt in clean[1:]: pen.lineTo(pt)
                pen.closePath()
    glyph = pen.glyph()
    glyph.recalcBounds(None)
    return glyph

def build():
    OUT.mkdir(parents=True,exist_ok=True)
    chars=sorted(G,key=ord)
    order=['.notdef']+[name(c) for c in chars]
    glyphs={'.notdef':tt(box(40,0,460,700).difference(box(88,48,412,652)))}
    metrics={'.notdef':(500,40)}
    for c in chars:
        g,w=G[c]; glyphs[name(c)]=tt(g)
        metrics[name(c)]=(w,glyphs[name(c)].xMin if not g.is_empty else 0)
    fb=FontBuilder(1000,isTTF=True)
    fb.setupGlyphOrder(order); fb.setupCharacterMap({ord(c):name(c) for c in chars})
    fb.setupGlyf(glyphs); fb.setupHorizontalMetrics(metrics)
    fb.setupHorizontalHeader(ascent=820,descent=-270,lineGap=0)
    fb.setupNameTable({'familyName':'Brinewake','styleName':'Regular','uniqueFontIdentifier':'Brinewake-Regular-0.1','fullName':'Brinewake Regular','psName':'Brinewake-Regular','version':'Version 0.100','copyright':'Original outlines drawn for cameron.stream, 2026.','description':'An original ink-cut display serif. Basic Latin, lining figures and selected punctuation. Designed with Co and Letta Code.','licenseDescription':'Permission is granted to use, copy, modify and redistribute this font, including commercially. Modified versions must use a different family name. Provided without warranty.'})
    fb.setupOS2(sTypoAscender=820,sTypoDescender=-270,sTypoLineGap=0,usWinAscent=820,usWinDescent=270,sxHeight=490,sCapHeight=700,usWeightClass=400,fsType=0,fsSelection=0x40)
    fb.setupPost(); fb.setupMaxp()
    pairs={
        'AV':-65,'AW':-48,'AY':-62,'AT':-42,'VA':-62,'WA':-44,'YA':-65,'TA':-47,
        'To':-54,'Te':-54,'Ta':-44,'Tr':-22,'Tu':-32,'Ty':-22,'Yo':-62,'Ye':-62,'Ya':-55,
        'Vo':-38,'Va':-33,'Ve':-38,'Wo':-23,'Wa':-22,'We':-23,'LT':-36,'LV':-42,'LY':-48,
        'PA':-37,'FA':-35,'Fo':-21,'Fe':-21,'Po':-14,'Ry':-18,'Ro':-14,
        'ov':-17,'ow':-12,'oy':-18,'vo':-21,'ve':-21,'va':-19,'wo':-12,'we':-12,'wa':-10,
        'yo':-20,'ye':-20,'ya':-18,'rt':-8,'ry':-9,'ro':-8,'rv':-9,'ff':-10,'fi':-8,
        'T.':-60,'V.':-58,'W.':-45,'Y.':-65,'v.':-36,'w.':-25,'y.':-33,
        'T,':-60,'V,':-58,'W,':-45,'Y,':-65,'v,':-36,'w,':-25,'y,':-33,
    }
    fea='languagesystem DFLT dflt; languagesystem latn dflt; feature kern {\n'
    fea+='\n'.join(f'pos {name(p[0])} {name(p[1])} {v};' for p,v in pairs.items())
    addOpenTypeFeaturesFromString(fb.font,fea+'\n} kern;')
    fb.font.recalcTimestamp=False
    fb.save(OUT/'brinewake-regular.ttf')
    fb.font.flavor='woff2'; fb.save(OUT/'brinewake-regular.woff2')
    # Ship the exact editable source alongside the binaries. This copy remains
    # runnable: it writes fonts beside itself when downloaded into a fonts folder.
    (OUT/'brinewake-source.py').write_bytes(Path(__file__).read_bytes())
    print(f'Brinewake: {len(chars)} encoded characters; {len(pairs)} kern pairs; TTF + WOFF2')

if __name__=='__main__': build()
