char/iocgn-mono
iosevka variant inspired by Simple Köln-Bonn
git clone https://git.t4t.associates/char/iocgn-mono
2ba32a9
main
1import { BooleanGeometry } from '@iosevka/geometry' ; 2import * as TypoGeom from 'typo-geom' ; 3import { arc , capsule , outline , reverseContour } from './iocgn-shapes.mjs' ; 4 5const PI = Math . PI ; 6 7function rect ( l , b , r , t ) { 8return [[ l , b ], [ l , t ], [ r , t ], [ r , b ]]; 9} 10 11function strokeSegment ( a , b , stroke ) { 12const length = Math . hypot ( b [ 0 ] - a [ 0 ], b [ 1 ] - a [ 1 ]); 13const ux = ( b [ 0 ] - a [ 0 ]) / length , uy = ( b [ 1 ] - a [ 1 ]) / length ; 14const nx = - uy * stroke / 2 , ny = ux * stroke / 2 ; 15// Overlap tangent joins slightly; exact touching can leave zero-area contours. 16a = [ a [ 0 ] - ux , a [ 1 ] - uy ]; 17b = [ b [ 0 ] + ux , b [ 1 ] + uy ]; 18return [[ a [ 0 ] + nx , a [ 1 ] + ny ], [ b [ 0 ] + nx , b [ 1 ] + ny ], 19[ b [ 0 ] - nx , b [ 1 ] - ny ], [ a [ 0 ] - nx , a [ 1 ] - ny ]]; 20} 21 22function roundedPolygon ( points , radius ) { 23const contour = []; 24for ( let i = 0 ; i < points . length ; i ++ ) { 25const p = points [ i ], prev = points [( i + points . length - 1 ) % points . length ]; 26const next = points [( i + 1 ) % points . length ]; 27const a = Math . hypot ( prev [ 0 ] - p [ 0 ], prev [ 1 ] - p [ 1 ]); 28const b = Math . hypot ( next [ 0 ] - p [ 0 ], next [ 1 ] - p [ 1 ]); 29const u = [( prev [ 0 ] - p [ 0 ]) / a , ( prev [ 1 ] - p [ 1 ]) / a ]; 30const v = [( next [ 0 ] - p [ 0 ]) / b , ( next [ 1 ] - p [ 1 ]) / b ]; 31const half = Math . acos ( u [ 0 ] * v [ 0 ] + u [ 1 ] * v [ 1 ]) / 2 ; 32const trim = Math . min ( radius / Math . tan ( half ), a * .49 , b * .49 ); 33const r = trim * Math . tan ( half ), length = Math . hypot ( u [ 0 ] + v [ 0 ], u [ 1 ] + v [ 1 ]); 34const cx = p [ 0 ] + ( u [ 0 ] + v [ 0 ]) / length * r / Math . sin ( half ); 35const cy = p [ 1 ] + ( u [ 1 ] + v [ 1 ]) / length * r / Math . sin ( half ); 36const entry = [ p [ 0 ] + u [ 0 ] * trim , p [ 1 ] + u [ 1 ] * trim ]; 37const from = Math . atan2 ( entry [ 1 ] - cy , entry [ 0 ] - cx ); 38const to = Math . atan2 ( p [ 1 ] + v [ 1 ] * trim - cy , p [ 0 ] + v [ 0 ] * trim - cx ); 39const turn = Math . atan2 ( Math . sin ( to - from ), Math . cos ( to - from )); 40contour . push ( entry , ...arc ( cx , cy , r , from , from + turn )); 41} 42return contour ; 43} 44 45// Dimensions belong to the 500-cell proof, not the proportional reference's advances. 46export function asciiContours ( width , s ) { 47const weight = ( s - 75 ) / 30 ; 48const mix = ( a , b ) => a + ( b - a ) * weight ; 49const cap = 735 , bottom = - 120 , mid = Math . round (( cap + bottom ) / 2 ); 50const shapes = {}; 51 52{ 53const w = mix ( 357 , 378 ), l = 40 - w / 2 , r = l + w ; 54const arm = r - mix ( 23 , 31 ), bar = cap / 2 ; 55shapes . F = [[[ l , 0 ], [ l , cap ], [ r , cap ], [ r , cap - s ], 56[ l + s , cap - s ], [ l + s , bar + s / 2 ], [ arm , bar + s / 2 ], 57[ arm , bar - s / 2 ], [ l + s , bar - s / 2 ], [ l + s , 0 ]]]; 58} 59{ 60const radius = mix ( 156.5 , 178.5 ), cx = - 15 , cy = radius + mix ( - 12 , - 10 ); 61const l = cx - radius , r = cx + radius , barLeft = l + mix ( 22 , 42 ); 62const end = mix ( 178 , 220 ); 63shapes . J = [[[ r , cap ], [ r , cy ], ...arc ( cx , cy , radius , 0 , - PI ), 64[ l , end ], [ l + s , end ], [ l + s , cy ], ...arc ( cx , cy , radius - s , PI , 2 * PI ), 65[ r - s , cap - s ], [ barLeft , cap - s ], [ barLeft , cap ]]]; 66} 67{ 68const w = Math . min ( 472 , mix ( 363 , 441 )), l = 12 - s / 2 , r = l + s ; 69const tip = mix ( - 190 , - 214 ), flag = .94 * s * Math . SQRT2 ; 70shapes [ '1' ] = [ rect ( - w / 2 , 0 , w / 2 , s ), rect ( l , 0 , r , cap ), 71[[ - 24 , cap ], [ tip , cap + tip + 24 ], [ tip + flag / 2 , cap + tip + 24 - flag / 2 ], 72[ l , cap + l + 24 - flag ], [ r , cap ]]. reverse ()]; 73} 74{ 75const r = mix ( 200.5 , 204.5 ), foot = mix ( - 54 , - 35 ), inner = foot - s ; 76shapes [ '7' ] = [[[ - r , cap ], [ r , cap ], [ r , cap - s ], 77[ r , 570 , r - 65 , 480 , r - 120 , 385 ], 78[ r - 185 , 272 , foot + 23 , 143 , foot , 0 ], [ inner , 0 ], 79[ inner + 23 , 150 , inner + 78 , 300 , r - 180 , 405 ], 80[ r - 115 , 517 , r - s - 2 , 610 , r - s , cap - s ], [ - r , cap - s ]]]; 81} 82 83// Slashes retain Airport's Regular weight as well as its angle and vertical extent. 84{ 85const slope = .419 ; 86const stroke = s < 75 ?67.17 * s / 75 :s > 105 ?105.07 * s / 105 :mix ( 67.17 , 105.07 ); 87const half = stroke * Math . hypot ( 1 , slope ) / 2 , run = slope * ( cap + 60 ) / 2 ; 88shapes [ '/' ] = [ roundedPolygon ([[ - run - half , - 60 ], [ run - half , cap ], 89[ run + half , cap ], [ - run + half , - 60 ]], Math . max ( stroke * .16 , mix ( 11.25 , 31.25 )))]; 90} 91{ 92const slope = .165 , stroke = .9 * s * Math . hypot ( 1 , slope ), spacing = mix ( 94 , 91 ); 93const low = mix ( 256 , 262.5 ), high = cap - low ; 94const halfBar = ( 472 - slope * ( high - low )) / 2 ; 95shapes [ '#' ] = []; 96for ( const x of [ - spacing , spacing ]) { 97const b = x - slope * cap / 2 , t = x + slope * cap / 2 ; 98shapes [ '#' ]. push ([[ b - stroke / 2 , 0 ], [ t - stroke / 2 , cap ], 99[ t + stroke / 2 , cap ], [ b + stroke / 2 , 0 ]]); 100} 101for ( const y of [ low , high ]) { 102const x = slope * ( y - cap / 2 ); 103shapes [ '#' ]. push ( rect ( x - halfBar , y - s / 2 , x + halfBar , y + s / 2 )); 104} 105} 106{ 107const stroke = s < 75 ?48 * s / 75 :mix ( 48 , 76 ); 108const l = - 236 , r = 236 , top = 650 , b = mix ( - 130 , - 116 ); 109const radius = 224 , tc = top - radius , bc = b + radius , end = - 62 * PI / 180 ; 110const shift = mix ( 20 , 10 ), aBottom = mix ( 30 , 40 ); 111const aLeft = mix ( - 136 , - 122 ) + shift , aRight = mix ( 80 , 108 ) + shift ; 112const aTop = mix ( 490 , 502 ), aRadius = mix ( 96 , 100 ); 113const stemTop = aTop - Math . max ( 24 , mix ( 24 , 60 )); 114const hook = ( r - aRight + stroke ) / 2 , hx = r - hook , hy = aBottom + hook ; 115shapes [ '@' ] = [ 116[[ aRight , stemTop ], [ aRight , hy ], ...arc ( hx , hy , hook - stroke , PI , 2 * PI ), 117[ r - stroke , tc ], ...arc ( 12 , tc , radius - stroke , 0 , PI / 2 ), 118[ - 12 , top - stroke ], ...arc ( - 12 , tc , radius - stroke , PI / 2 , PI ), 119[ l + stroke , bc ], ...arc ( - 12 , bc , radius - stroke , PI , 3 * PI / 2 ), 120[ 12 , b + stroke ], ...arc ( 12 , bc , radius - stroke , - PI / 2 , end ), 121[ 12 + radius * Math . cos ( end ), bc + radius * Math . sin ( end )], 122 ...arc ( 12 , bc , radius , end , - PI / 2 ), [ - 12 , b ], 123 ...arc ( - 12 , bc , radius , 3 * PI / 2 , PI ), [ l , tc ], 124 ...arc ( - 12 , tc , radius , PI , PI / 2 ), [ 12 , top ], 125 ...arc ( 12 , tc , radius , PI / 2 , 0 ), [ r , hy ], 126 ...arc ( hx , hy , hook , 0 , - PI ), [ aRight - stroke , stemTop ]], 127capsule ( aLeft , aRight , aBottom , aTop , aRadius ), 128capsule ( aLeft + stroke , aRight - stroke , aBottom + stroke , aTop - stroke , 129aRadius - stroke , true ), 130]; 131} 132{ 133const stroke = mix ( 52 , 62 ), run = .419 * cap / 2 , half = stroke * Math . hypot ( 1 , .419 ) / 2 ; 134shapes [ '%' ] = [ roundedPolygon ([[ - run - half , 0 ], [ run - half , cap ], 135[ run + half , cap ], [ - run + half , 0 ]], Math . max ( stroke * .16 , mix ( 12 , 20 )))]; 136for ( const [ l , b , r , t ] of [[ - 236 , 374 , - 50 , 724 ], [ 50 , 0 , 236 , 350 ]]) { 137shapes [ '%' ]. push ( capsule ( l , r , b , t , 93 ), 138capsule ( l + stroke , r - stroke , b + stroke , t - stroke , 93 - stroke , true )); 139} 140} 141 142{ 143const radius = mix ( 201 , 210 ), terminal = mix ( 34 , 52.5 ), w = radius + terminal ; 144const l = - w / 2 , r = w / 2 , cx = l + radius ; 145shapes [ '(' ] = [[[ r , cap ], [ r , cap - s ], [ cx , cap - s ], 146 ...arc ( cx , cap - radius , radius - s , PI / 2 , PI ), [ l + s , bottom + radius ], 147 ...arc ( cx , bottom + radius , radius - s , PI , 3 * PI / 2 ), 148[ r , bottom + s ], [ r , bottom ], [ cx , bottom ], 149 ...arc ( cx , bottom + radius , radius , 3 * PI / 2 , PI ), [ l , cap - radius ], 150 ...arc ( cx , cap - radius , radius , PI , PI / 2 )]]; 151} 152{ 153const w = mix ( 223.5 , 252 ), l = - w / 2 , r = w / 2 ; 154shapes [ '[' ] = [[[ l , bottom ], [ l , cap ], [ r , cap ], [ r , cap - s ], 155[ l + s , cap - s ], [ l + s , bottom + s ], [ r , bottom + s ], [ r , bottom ]]]; 156} 157{ 158const w = mix ( 346.5 , 367.5 ), l = - w / 2 , r = w / 2 ; 159const radius = mix ( 201 , 210 ), cx = r - mix ( 34 , 52.5 ), stem = cx - radius ; 160const waist = ( cap + bottom ) / 2 , upper = waist + mix ( 133 , 120.9 ), lower = 2 * waist - upper ; 161const turn = upper - waist - s * mix ( 1 , .9 ) / 2 , tx = stem - turn ; 162const angle = Math . asin (( upper - waist ) / ( turn + s )); 163shapes [ '{' ] = [[[ r , cap ], [ r , cap - s ], [ cx , cap - s ], 164 ...arc ( cx , cap - radius , radius - s , PI / 2 , PI ), [ stem + s , upper ], 165 ...arc ( tx , upper , turn + s , 0 , - angle ), ...arc ( tx , lower , turn + s , angle , 0 ), 166[ stem + s , bottom + radius ], ...arc ( cx , bottom + radius , radius - s , PI , 3 * PI / 2 ), 167[ r , bottom + s ], [ r , bottom ], [ cx , bottom ], 168 ...arc ( cx , bottom + radius , radius , 3 * PI / 2 , PI ), [ stem , lower ], 169 ...arc ( tx , lower , turn , 0 , PI / 2 ), [ l , waist - s * mix ( 1 , .9 ) / 2 ], 170[ l , waist + s * mix ( 1 , .9 ) / 2 ], [ tx , waist + s * mix ( 1 , .9 ) / 2 ], 171 ...arc ( tx , upper , turn , - PI / 2 , 0 ), [ stem , cap - radius ], 172 ...arc ( cx , cap - radius , radius , PI , PI / 2 )]]; 173} 174{ 175const l = mix ( - 149 , - 156 ), half = mix ( 234 , 231 ), t = mid + half , b = mid - half ; 176const inner = l + s * mix ( .985 , 1.068 ), right = inner + half / .95 ; 177const outer = l + ( half - s / 2 ) / .95 ; 178shapes [ '<' ] = [[[ l , mid - s / 2 ], [ l , mid + s / 2 ], [ outer , t ], [ right , t ], 179[ inner , mid ], [ right , b ], [ outer , b ]]]; 180} 181{ 182const cx = - 60 , lowerRadius = Math . min ( 210 , mix ( 200 , 210 )), cy = lowerRadius - mix ( 12 , 10 ); 183const headRadius = mix ( 145 , 157.5 ), headCy = mix ( 746 , 745 ) - headRadius ; 184// The rising diagonal must meet both bowls tangentially. 185const turnCy = cy + ( lowerRadius + headRadius - s ) * Math . SQRT2 ; 186const theta = PI + Math . atan ( .8 ), diagonal = 3 * PI / 4 ; 187const headOuter = [ cx + headRadius * Math . cos ( theta ), turnCy + headRadius * Math . sin ( theta )]; 188const down = [ cx + ( headRadius - s / 2 ) * Math . cos ( theta ), turnCy + ( headRadius - s / 2 ) * Math . sin ( theta )]; 189const endY = - s / 2 * Math . sin ( theta ); 190const upperJoin = [ cx - ( lowerRadius - s / 2 ) / Math . SQRT2 , cy + ( lowerRadius - s / 2 ) / Math . SQRT2 ]; 191const headJoin = [ cx + ( headRadius - s / 2 ) / Math . SQRT2 , turnCy - ( headRadius - s / 2 ) / Math . SQRT2 ]; 192const lowerJoin = [ cx + ( lowerRadius - s / 2 ) / Math . SQRT2 , cy - ( lowerRadius - s / 2 ) / Math . SQRT2 ]; 193const armY = mix ( 251 , 270 ); 194shapes [ '&' ] = [ 195[ headOuter , ...arc ( cx , turnCy , headRadius , theta , PI ), [ cx - headRadius , headCy ], 196 ...arc ( cx , headCy , headRadius , PI , 0 ), [ cx + headRadius , turnCy ], 197 ...arc ( cx , turnCy , headRadius , 0 , - PI / 4 ), 198[ cx + ( headRadius - s ) / Math . SQRT2 , turnCy - ( headRadius - s ) / Math . SQRT2 ], 199 ...arc ( cx , turnCy , headRadius - s , - PI / 4 , 0 ), [ cx + headRadius - s , headCy ], 200 ...arc ( cx , headCy , headRadius - s , 0 , PI ), [ cx - headRadius + s , turnCy ], 201 ...arc ( cx , turnCy , headRadius - s , PI , theta )], 202[[ cx + lowerRadius / Math . SQRT2 , cy - lowerRadius / Math . SQRT2 ], 203 ...arc ( cx , cy , lowerRadius , 7 * PI / 4 , diagonal ), 204[ cx - ( lowerRadius - s ) / Math . SQRT2 , cy + ( lowerRadius - s ) / Math . SQRT2 ], 205 ...arc ( cx , cy , lowerRadius - s , diagonal , 7 * PI / 4 )], 206strokeSegment ( upperJoin , headJoin , s ), 207strokeSegment ( lowerJoin , [ lowerJoin [ 0 ] + armY - lowerJoin [ 1 ], armY ], s ), 208strokeSegment ( down , [ down [ 0 ] + .8 * ( down [ 1 ] - endY ), endY ], s ), 209]. map ( c => c . map ( p => p . map (( v , i ) => i % 2 ?v :v * .875 ))); 210} 211for ( const [ left , right ] of [[ '/' , '\\' ], [ '(' , ')' ], [ '[' , ']' ], [ '{' , '}' ], [ '<' , '>' ]]) { 212shapes [ right ] = shapes [ left ]. map ( c => reverseContour ( c . map ( p => p . map (( v , i ) => i % 2 ?v :- v )))); 213} 214 215const dot = mix ( 111 , 147 ), colonTop = mix ( 490 , 504 ); 216{ 217const stroke = mix ( 97 , 132 ), height = mix ( 269 , 294 ), offset = mix ( 94.5 , 105 ); 218const tip = stroke * .88 / 2 ; 219const quote = [[ - tip , cap - height ], [ - stroke / 2 , cap ], 220[ stroke / 2 , cap ], [ tip , cap - height ]]; 221shapes [ "'" ] = [ quote ]; 222shapes [ '"' ] = [ - offset , offset ]. map ( x => quote . map (([ px , y ]) => [ px + x , y ])); 223const graveHeight = mix ( 209 , 235 ), graveStroke = mix ( 93 , 126 ); 224const radius = Math . max ( graveStroke * .16 , mix ( 16 , 31 )); 225const angle = mix ( 51 , 48 ) * PI / 180 , c = Math . cos ( angle ), d = Math . sin ( angle ); 226// Account for the rotated fillets so the visible top stays at cap height. 227const length = ( graveHeight - graveStroke * c + 2 * radius * ( c + d - 1 )) / d ; 228shapes [ '`' ] = [ roundedPolygon ( rect ( - length / 2 , - graveStroke / 2 , length / 2 , graveStroke / 2 ) 229. map (([ x , y ]) => [ x * c + y * d , cap - graveHeight / 2 - x * d + y * c ]), radius )]; 230} 231const dotContour = rect ( - dot / 2 , 0 , dot / 2 , dot ); 232shapes [ '.' ] = [ dotContour ]; 233shapes [ ':' ] = [ dotContour , rect ( - dot / 2 , colonTop - dot , dot / 2 , colonTop )]; 234shapes [ '!' ] = [ dotContour , rect ( - s / 2 , mix ( 212 , 210 ), s / 2 , cap )]; 235{ 236const radius = mix ( 190 , 199.5 ), top = mix ( 746 , 745 ), cy = top - radius ; 237const cx = 11 , l = - cx - radius , r = cx + radius , neck = - 22 ; 238const nl = neck - s / 2 , nr = neck + s / 2 ; 239shapes [ '?' ] = [[[ l , cy ], ...arc ( - cx , cy , radius , PI , PI / 2 ), [ cx , top ], 240 ...arc ( cx , cy , radius , PI / 2 , 0 ), [ r , 400 , nr , 390 , nr , 244 ], 241[ nr , 211 ], [ nl , 211 ], [ nl , 244 ], [ nl , 400 , r - s , 440 , r - s , cy ], 242 ...arc ( cx , cy , radius - s , 0 , PI / 2 ), [ - cx , top - s ], 243 ...arc ( - cx , cy , radius - s , PI / 2 , PI ), [ l + s , cy - 12 ], [ l , cy - 12 ]], 244rect ( neck - dot / 2 , 0 , neck + dot / 2 , dot )]; 245const bar = mix ( 56 , 84 ) / 2 , overhang = mix ( 89 , 84 ); 246shapes [ '$' ] = [ rect ( - bar , - overhang , bar , 30 ), rect ( - bar , cap - 30 , bar , cap + overhang )]; 247} 248{ 249const radius = Math . min ( dot * .95 , mix ( 100 , 115 )), stroke = mix ( 56 , 73 ), cx = dot / 2 - radius ; 250const comma = [[ - dot / 2 , dot ], [ dot / 2 , dot ], [ dot / 2 , 0 ], 251 ...arc ( cx , 0 , radius , 0 , - PI / 2 ), [ - dot / 2 , - radius ], 252[ - dot / 2 , - radius + stroke ], [ cx , - radius + stroke ], 253 ...arc ( cx , 0 , radius - stroke , - PI / 2 , 0 ), [ - dot / 2 , 0 ]]; 254shapes [ ',' ] = [ comma ]; 255shapes [ ';' ] = [ comma , shapes [ ':' ][ 1 ]]; 256} 257shapes [ '|' ] = [ rect ( - s / 2 , bottom , s / 2 , cap )]; 258{ 259const w = mix ( 445 , 462 ), half = w / 2 ; 260shapes [ '+' ] = [ rect ( - half , mid - s / 2 , half , mid + s / 2 ), 261rect ( - s / 2 , mid - half , s / 2 , mid + half )]; 262shapes [ '*' ] = []; 263for ( const angle of [ 0 , PI / 3 , 2 * PI / 3 ]) { 264const c = Math . cos ( angle ), d = Math . sin ( angle ); 265shapes [ '*' ]. push ( rect ( - 234 , - s / 2 , 234 , s / 2 ). map (([ x , y ]) => 266[ x * c - y * d , mid + x * d + y * c ])); 267} 268const equalWidth = mix ( 403 , 441 ), distance = mix ( 89 , 100 ); 269shapes [ '=' ] = [ rect ( - equalWidth / 2 , mid - distance - s / 2 , equalWidth / 2 , mid - distance + s / 2 ), 270rect ( - equalWidth / 2 , mid + distance - s / 2 , equalWidth / 2 , mid + distance + s / 2 )]; 271const dash = mix ( 334 , 315 ), underscore = Math . min ( 472 , mix ( 468 , 462 )); 272shapes [ '-' ] = [ rect ( - dash / 2 , mid - s / 2 , dash / 2 , mid + s / 2 )]; 273shapes [ '_' ] = [ rect ( - underscore / 2 , - mix ( 56 , 84 ), underscore / 2 , 0 )]; 274} 275{ 276const w = mix ( 383 , 435 ), b = mix ( 334 , 315 ), slope = ( cap - b ) / ( w / 2 ); 277const thick = s * Math . hypot ( 1 , slope ) / slope ; 278shapes [ '^' ] = [[[ - w / 2 , b ], [ - thick / 2 , cap ], [ thick / 2 , cap ], [ w / 2 , b ], 279[ w / 2 - thick , b ], [ 0 , cap - thick * slope / 2 ], [ - w / 2 + thick , b ]]]; 280} 281{ 282const w = mix ( 212.5 , 220.5 ), h = s / 2 , cy = mix ( 278 , 273 ); 283shapes [ '~' ] = [[[ - w , cy - 35 + h ], 284[ - w * .61 , cy + 85 + h , - w * .31 , cy + 35 + h , 0 , cy + h ], 285[ w * .31 , cy - 35 + h , w * .61 , cy - 85 + h , w , cy + 35 + h ], 286[ w , cy + 35 - h ], 287[ w * .61 , cy - 85 - h , w * .31 , cy - 35 - h , 0 , cy - h ], 288[ - w * .31 , cy + 35 - h , - w * .61 , cy + 85 - h , - w , cy - 35 - h ]]]; 289} 290return Object . fromEntries ( Object . entries ( shapes ). map (([ char , contours ]) => [ char , 291contours . map ( c => c . map ( p => p . map (( v , i ) => i % 2 ?v :v + width / 2 )))])); 292} 293 294export function refineAscii ( store , metrics , para ) { 295for ( const [ char , contours ] of Object . entries ( asciiContours ( metrics . Width , para . stroke ))) { 296const glyph = store . queryByUnicode ( char . codePointAt ( 0 )); 297if ( ! glyph ) continue ; 298glyph . clearGeometry (); 299if ( char === '$' ) glyph . includeGlyph ( store . queryByNameEnsured ( 'S/dollarKernelStd' )); 300outline ( contours ). applyToGlyph ( glyph ); 301// ContourSetGeometry alone is assumed simple; merge touching primitives before rounding. 302glyph . geometry = new BooleanGeometry ( TypoGeom . Boolean . ClipType . ctUnion , [ glyph . geometry ]); 303} 304}