char/iocgn-mono
iosevka variant inspired by Simple Köln-Bonn
git clone https://git.t4t.associates/char/iocgn-mono
a3bd267
main
1import { outline } from './iocgn-shapes.mjs' ; 2import config from './iocgn-config.mjs' ; 3 4function strokePath ( points , width ) { 5const normals = points . slice ( 1 ). map (([ x , y ], i ) => { 6const dx = x - points [ i ][ 0 ], dy = y - points [ i ][ 1 ]; 7const length = Math . hypot ( dx , dy ); 8return [ - dy / length , dx / length ]; 9}); 10const offsets = [ normals [ 0 ]. map ( v => v * width / 2 )]; 11for ( let i = 1 ; i < normals . length ; i ++ ) { 12const [ ax , ay ] = normals [ i - 1 ], [ bx , by ] = normals [ i ]; 13const scale = width / 2 / ( 1 + ax * bx + ay * by ); 14offsets . push ([( ax + bx ) * scale , ( ay + by ) * scale ]); 15} 16offsets . push ( normals . at ( - 1 ). map ( v => v * width / 2 )); 17return [ ...points . map (([ x , y ], i ) => [ x + offsets [ i ][ 0 ], y + offsets [ i ][ 1 ]]), 18 ...points . map (([ x , y ], i ) => [ x - offsets [ i ][ 0 ], y - offsets [ i ][ 1 ]]). reverse ()]; 19} 20 21function arrowHead ( x , y , degrees , width , length = 100 ) { 22const angle = degrees * Math . PI / 180 , dx = Math . cos ( angle ), dy = Math . sin ( angle ); 23return strokePath ([[ x - length * dx - length * dy , y - length * dy + length * dx ], 24[ x , y ], [ x - length * dx + length * dy , y - length * dy - length * dx ]], width ); 25} 26 27function arcPoints ( cx , cy , radius , from , to , steps ) { 28return Array . from ({ length :steps + 1 }, ( _ , i ) => { 29const angle = ( from + ( to - from ) * i / steps ) * Math . PI / 180 ; 30return [ cx + radius * Math . cos ( angle ), cy + radius * Math . sin ( angle )]; 31}); 32} 33 34export function refineArrows ( store ) { 35// Preserve the approved stroke weight, including native TrueType rounding. 36const ys = store . queryByUnicodeEnsured ( 45 ). geometry . toContours (). flat (). map ( p => Math . round ( p . y )); 37const width = ( Math . max ( ...ys ) - Math . min ( ...ys )) * .72 ; 38const shapes = { 39'→' :[ strokePath ([[ 70 , 340 ], [ 415 , 340 ]], width ), arrowHead ( 420 , 340 , 0 , width )], 40'↔' :[ strokePath ([[ 85 , 340 ], [ 415 , 340 ]], width ), 41arrowHead ( 80 , 340 , 180 , width , 90 ), arrowHead ( 420 , 340 , 0 , width , 90 )], 42'⇒' :[ ...[ 292 , 388 ]. map ( y => strokePath ([[ 75 , y ], [ 372 , y ]], width * .75 )), 43arrowHead ( 420 , 340 , 0 , width * .75 , 112 )], 44'⇔' :[ ...[ 298 , 382 ]. map ( y => strokePath ([[ 125 , y ], [ 375 , y ]], width * .75 )), 45arrowHead ( 80 , 340 , 180 , width * .75 , 90 ), arrowHead ( 420 , 340 , 0 , width * .75 , 90 )], 46'⇥' :[ strokePath ([[ 65 , 340 ], [ 390 , 340 ]], width ), arrowHead ( 395 , 340 , 0 , width , 95 ), 47strokePath ([[ 460 , 200 ], [ 460 , 480 ]], width * .55 )], 48'➜' :[ strokePath ([[ 65 , 340 ], [ 400 , 340 ]], width * 1.15 ), 49arrowHead ( 410 , 340 , 0 , width * 1.15 , 105 )], 50'❯' :[ strokePath ([[ 185 , 470 ], [ 315 , 340 ], [ 185 , 210 ]], width * 1.15 )], 51'↩' :[ strokePath ([[ 80 , 245 ], ...arcPoints ( 345 , 325 , 80 , - 90 , 90 , 20 ), [ 95 , 405 ]], width ), 52arrowHead ( 90 , 405 , 180 , width , 90 )], 53'↴' :[ strokePath ([[ 70 , 500 ], [ 325 , 500 ], [ 325 , 180 ]], width ), 54arrowHead ( 325 , 175 , - 90 , width , 90 )], 55'↵' :[ strokePath ([[ 360 , 590 ], [ 360 , 220 ], [ 95 , 220 ]], width ), 56arrowHead ( 90 , 220 , 180 , width , 90 )], 57}; 58const circle = arcPoints ( 250 , 340 , 165 , - 15 , - 315 , 40 ); 59shapes [ '↻' ] = [ strokePath ( circle , width * .85 ), arrowHead ( ...circle . at ( - 1 ), - 45 , width * .85 , 75 )]; 60for ( const [ char , y , sign , start ] of [[ '↰' , 440 , 1 , 140 ], [ '↲' , 280 , - 1 , 590 ]]) { 61shapes [ char ] = [ strokePath ([[ 360 , start ], ...arcPoints ( 300 , y , 60 , 0 , sign * 90 , 12 ), 62[ 95 , y + sign * 60 ]], width ), arrowHead ( 90 , y + sign * 60 , 180 , width , 90 )]; 63} 64for ( const [ char , source , degrees ] of [ 65[ '←' , '→' , 180 ], [ '↑' , '→' , 90 ], [ '↓' , '→' , - 90 ], 66[ '↖' , '→' , 135 ], [ '↗' , '→' , 45 ], [ '↘' , '→' , - 45 ], [ '↙' , '→' , - 135 ], 67[ '↕' , '↔' , 90 ], [ '⇐' , '⇒' , 180 ], [ '⇑' , '⇒' , 90 ], [ '⇓' , '⇒' , - 90 ], 68]) { 69const angle = degrees * Math . PI / 180 , c = Math . cos ( angle ), s = Math . sin ( angle ); 70shapes [ char ] = shapes [ source ]. map ( contour => contour . map (([ x , y ]) => 71[ 250 + ( x - 250 ) * c - ( y - 340 ) * s , 340 + ( x - 250 ) * s + ( y - 340 ) * c ])); 72} 73for ( const [ char , source ] of [[ '⇤' , '⇥' ], [ '❮' , '❯' ], [ '↺' , '↻' ], [ '↪' , '↩' ], [ '↱' , '↰' ], [ '↳' , '↲' ]]) { 74shapes [ char ] = shapes [ source ]. map ( contour => contour . map (([ x , y ]) => [ 500 - x , y ])); 75} 76for ( const char of config . terminal . arrows ) { 77const contours = shapes [ char ], points = contours . flat (); 78const xs = points . map ( p => p [ 0 ]), ys = points . map ( p => p [ 1 ]); 79const x0 = Math . min ( ...xs ), x1 = Math . max ( ...xs ), y0 = Math . min ( ...ys ), y1 = Math . max ( ...ys ); 80// Uniform scaling keeps arrowheads undistorted; vertical arrows are rotated peers. 81const scale = 500 / ( '↑↓↕⇑⇓' . includes ( char ) ?y1 - y0 :x1 - x0 ); 82const glyph = store . queryByUnicodeEnsured ( char . codePointAt ( 0 )); 83glyph . koelnPreserveTopology = true ; 84glyph . clearGeometry (); 85outline ( contours . map ( contour => contour . map (([ x , y ]) => 86[ 250 + scale * ( x - ( x0 + x1 ) / 2 ), 87340 + scale * ( y - ( y0 + y1 ) / 2 )]))). applyToGlyph ( glyph ); 88} 89}