Iosevka/buildglyphs.patel

415 lines
17 KiB
Plaintext

import 'unorm' as unorm
import './support/glyph' as Glyph
import './support/stroke' as Stroke
import './support/spirokit' as spirokit
import './support/transform' as : Transform && [object [transformPoint tp] [untransform utp] inverse]
import './support/anchor' as Anchor
extern global
### File inclusion macro
define-macro $$include : syntax-rules
{'$$include' {'.quote' file}} : begin
local path : require 'path'
local fs : require 'fs'
local f0 [[env.macros.get 'input-path']].1
local parse [require './syntax.js'].parse
local absolutePath : path.resolve [path.dirname f0] [formOf file]
local input : fs.readFileSync absolutePath 'utf-8'
local ast : parse input {.within {.file absolutePath .input input}}
return {'.syntactic-closure' ast env}
otherwise `nothing
# A symbol block defines a group of similar glyphs. They may expose some functions used in glyphs in other blocks.
define-macro symbol-block : syntax-rules
`[symbol-block @name @::steps] : let [t : env.newt] : dirty `[begin [let : set @t : begin @::[steps.map formOf]] [progress @name] @t]
### Necessary macros
# A glyph construction is a function which "modifies" a glyph.
define-macro glyph-construction : syntax-rules
`[glyph-construction @::steps] {'.syntactic-closure' `[lambda [] [begin \\
local currentGlyph this
begin @::[steps.map formOf]
return nothing
]] env}
# Remap Glyph's methods to macros in order to simplify writing
define-macro set-width : syntax-rules
`[set-width @::args] {'.syntactic-closure' `[currentGlyph.set-width @::args] env}
define-macro start-from : syntax-rules
`[start-from @::args] {'.syntactic-closure' `[currentGlyph.start-from @::args] env}
define-macro line-to : syntax-rules
`[line-to @::args] {'.syntactic-closure' `[currentGlyph.line-to @::args] env}
define-macro curve-to : syntax-rules
`[curve-to @::args] {'.syntactic-closure' `[currentGlyph.curve-to @::args] env}
define-macro cubic-to : syntax-rules
`[cubic-to @::args] {'.syntactic-closure' `[currentGlyph.cubic-to @::args] env}
define-macro include : syntax-rules
`[include glyphs.(@name) @::args] : dirty `[includeGlyphPart currentGlyph glyphs @name @::args]
`[include @::args] {'.syntactic-closure' `[currentGlyph.include @::args] env}
define-macro set-anchor : syntax-rules
`[set-anchor @::args] {'.syntactic-closure' `[currentGlyph.set-anchor @::args] env}
define-macro apply-transform : syntax-rules
`[apply-transform @::args] {'.syntactic-closure' `[currentGlyph.apply-transform @::args] env}
define-macro reverse-last : syntax-rules
`[reverse-last @::args] {'.syntactic-closure' `[currentGlyph.reverse-last @::args] env}
define-macro eject-contour : syntax-rules
`[eject-contour @::args] {'.syntactic-closure' `[currentGlyph.eject-contour @::args] env}
define-macro tag-contour : syntax-rules
`[tag-contour @::args] {'.syntactic-closure' `[currentGlyph.tag-contour @::args] env}
define-macro retag-contour : syntax-rules
`[retag-contour @::args] {'.syntactic-closure' `[currentGlyph.retag-contour @::args] env}
define-macro assign-unicode : syntax-rules
`[assign-unicode @code] {".syntactic-closure" `[begin \\
currentGlyph.assign-unicode @code
set unicodeGlyphs.(currentGlyph.unicode.((currentGlyph.unicode.length - 1))) currentGlyph
] env}
define [includeGlyphPart cg gs nm] : begin
if [not gs.(nm)] : throw : new Error "Glyph \(nm) is not defined, which is used for \(cg.name)."
return : cg.include.apply cg [{gs.(nm)}.concat [{}.slice.call arguments 3]]
### COMMON FUNCTIONS
define [mix a b p] : a + (b - a) * p
define [linreg x0 y0 x1 y1 x] : y0 + (x - x0) * (y1 - y0) / (x1 - x0)
define [bilinear x0 x1 y0 y1 z00 z01 z10 z11 x y] : linreg
* y0
linreg x0 z00 x1 z10 x
* y1
linreg x0 z01 x1 z11 x
* y
define [clamp l h x] : if (x < l) l : if (x > h) h x
define [fallback] : begin
for [local j 0] (j < arguments.length) [inc j] : if (arguments.(j) !== nothing) : return arguments.(j)
return nothing
define [TempFont] {.glyf {} .head {.} .hhea {.} ."OS/2" {.} .name {.} .post {.}}
define [normalize a] : begin
local m : Math.hypot a.x a.y
return {.x (a.x / m) .y (a.y / m)}
define [anchorDeriv] : begin
local h {.}
foreach a [items-of arguments] : foreach k [items-of [Object.keys a.anchors]] : begin
set h.(k) : new Anchor a.anchors.(k).x a.anchors.(k).y a.anchors.(k).type a.anchors.(k).mbx a.anchors.(k).mby
return {.anchors h}
# contour tagging
define [tagged tag component] : begin
set component.tag tag
return component
export as build : define [buildFont para recursive recursiveCodes] : begin
define variantSelector para.variantSelector
define font this
define glyphList font.glyf
define glyphs {.}
define unicodeGlyphs {}
define UPM 1000
# Progress indicator
define [progress status] : if [not recursive] : begin
console.log " \(font.name.uniqueSubFamily) : Done \(status)"
if (global && global.gc) : global.gc
# Key metrics
define WIDTH para.width
define SB para.sb
define CAP para.cap
define XH para.xheight
define DESCENDER : fallback para.descender (XH - CAP)
# Key metrics for symbols
local parenTop ((XH * 0.625) + (CAP - XH) * 2.5)
local parenBot ((XH * 0.625) - (CAP - XH) * 2.5)
local parenMid [mix parenTop parenBot 0.5]
local operTop : mix parenMid parenTop 0.8
local operBot : mix parenMid parenBot 0.8
# Transform constructors
define [Italify angle shift] : begin
local slope [Math.tan ([fallback angle para.italicangle] / 180 * Math.PI)]
return : new Transform 1 slope 0 1 [fallback shift : -slope * parenMid] 0
define [Upright angle shift] [Italify angle shift :.inverse]
define [Scale sx sy] : new Transform sx 0 0 [fallback sy sx] 0 0
define [Translate x y] : new Transform 1 0 0 1 x y
define [Rotate angle] : new Transform [Math.cos angle] (-[Math.sin angle]) [Math.sin angle] [Math.cos angle] 0 0
define globalTransform : Italify para.italicAngle
define deGlobalTransform : inverse globalTransform
define CORRECTION_HX : [fallback para.contrast 1] / [Math.sqrt (1 - globalTransform.yx * globalTransform.yx)]
# Orient parameters
define UPWARD {.x (-CORRECTION_HX) .y 0}
define DOWNWARD {.x CORRECTION_HX .y 0}
define RIGHTWARD {.x globalTransform.yx .y 1}
define LEFTWARD {.x (- globalTransform.yx) .y (-1)}
# Style parameters
define O para.overshoot
define OX para.overshootx
define OXHOOK para.oxhook
define HOOK para.hook
define AHOOK para.ahook
define SHOOK para.shook
define RHOOK para.rhook
define JHOOK para.jhook
define FHOOK para.fhook
define HOOKX para.hookx
define SMOOTH para.smooth
define SMALLSMOOTH para.smallsmooth
define STROKE para.stroke
define CONTRAST para.contrast
define DOTSIZE : fallback para.dotsize STROKE
define PERIODSIZE : fallback para.periodsize DOTSIZE
define BARPOS : fallback para.barpos 0.5
define GBARPOS : fallback para.gbarpos 0.5
define PBARPOS : fallback para.pbarpos 0.5
define EBARPOS : fallback para.ebarpos BARPOS
define OVERLAYPOS para.overlaypos
define FIVEBARPOS para.fivebarpos
define LONGJUT para.longjut
define JUT para.jut
define VJUT para.vjut
define ACCENT para.accent
define ACCENTX para.accentx
define CTHIN : fallback para.cthin 0.75
define CTHINB : fallback para.cthinb 0.5
define SLAB para.slab
define TAILADJX : WIDTH * 0.2
define TAILADJY : XH * 0.25
define LBALANCE : LONGJUT * 0.04
define IBALANCE : fallback para.ibalance (LONGJUT * 0.04)
define JBALANCE : fallback para.jbalance 0
define JBALANCE2 : fallback para.jbalance2 (STROKE / 2 + LBALANCE)
define TBALANCE : fallback para.tbalance JBALANCE
define TBALANCE2 : fallback para.tbalance2 TBALANCE
define RBALANCE : fallback para.rbalance (JBALANCE * 0.3)
define RBALANCE2 : fallback para.rbalance2 0
#define SBALANCE : fallback para.sbalance 0.52
define FBALANCE : fallback para.fbalance 0
define ONEBALANCE : fallback para.onebalance 0
# derived metrics
define OXE : OX - O
define FULLWIDTH : if para.cjkSpacing (WIDTH * 2) WIDTH
define VSTROKE : STROKE * CONTRAST
define ESS : STROKE * [fallback para.essx CONTRAST]
define XO : XH - O
define CAPO : CAP - O
define HALFSTROKE : STROKE / 2
define RIGHTSB : WIDTH - SB
define FWRSB : FULLWIDTH - SB
define MIDDLE : WIDTH / 2
define FWMIDDLE : FULLWIDTH / 2
define CAPMIDDLE : CAP / 2
define CAP_SMOOTH : CAP - SMOOTH
define DOTRADIUS : DOTSIZE / 2
define PERIODRADIUS : PERIODSIZE / 2
define SIDEJUT : JUT - HALFSTROKE * CORRECTION_HX
define SMOOTHA : SMOOTH - globalTransform.yx * para.smoothadjust
define SMOOTHB : SMOOTH + globalTransform.yx * para.smoothadjust
define SMALLSMOOTHA : SMALLSMOOTH - globalTransform.yx * para.smoothadjust
define SMALLSMOOTHB : SMALLSMOOTH + globalTransform.yx * para.smoothadjust
define CORRECTION_VX globalTransform.yx
define CORRECTION_VS : STROKE * globalTransform.yx
define CORRECTION_OMIDX : globalTransform.yx * 1.3
define CORRECTION_OMIDS : STROKE * CORRECTION_OMIDX
# Blackness parameters
# We will estimate blackness using lower-case 'e'
define WHITENESS : ((XH - STROKE * 3) * (RIGHTSB - SB) * (1 / 3)) / (XH * (RIGHTSB - SB))
define [adviceBlackness crowdedness] : Math.min STROKE ((RIGHTSB - SB) * (1 - WHITENESS) / (crowdedness * CORRECTION_HX))
define MVERTSTROKE : adviceBlackness : fallback para.lllcrowdedness (3 + 1 / 3)
define OVERLAYSTROKE : adviceBlackness 3.75
define OPERATORSTROKE : adviceBlackness 3.2
define SHOULDERFINE : adviceBlackness 10
define SUPERNESS : fallback para.superness 2
define [superxy x] : Math.pow (1 - [Math.pow x SUPERNESS]) (1 / SUPERNESS)
define [adviceSSmooth y sign] : begin
local ss : y * 0.22 + STROKE * 0.22 + 0.02 * (RIGHTSB - SB)
return : ss + sign * globalTransform.yx * para.smoothadjust * (ss / SMALLSMOOTH)
define [adviceGlottalStopSmooth y sign] : ((y - STROKE) * 0.25 + STROKE / 2) + sign * globalTransform.yx * para.smoothadjust
define [shoulderMidSlope _fine _stroke _dir] : 0.5 * CORRECTION_HX * ([fallback _stroke STROKE] - [fallback _fine SHOULDERFINE]) / [fallback _stroke STROKE] + [fallback _dir 1] * globalTransform.yx
# Anchor parameters
define BASE Anchor.BASE
define MARK Anchor.MARK
define AS_BASE 'AS-BASE'
define [tm anchor] : return : new Anchor
* (anchor.x * globalTransform.xx + anchor.y * globalTransform.yx + globalTransform.x)
* (anchor.x * globalTransform.xy + anchor.y * globalTransform.yy + globalTransform.y)
* anchor.type
define markAboveLower {.anchors {.above [tm : new Anchor MIDDLE XH BASE]}}
define markAboveCap {.anchors {.above [tm : new Anchor MIDDLE CAP BASE]}}
define markBelowLower {.anchors {.below [tm : new Anchor MIDDLE DESCENDER BASE]}}
define markBelowZero {.anchors {.below [tm : new Anchor MIDDLE 0 BASE]}}
define markToprightLower {.anchors {.topright [tm : new Anchor RIGHTSB XH BASE]}}
define markToprightCap {.anchors {.topright [tm : new Anchor RIGHTSB CAP BASE]}}
define markBottomrightLower {.anchors {.bottomright [tm : new Anchor RIGHTSB DESCENDER BASE]}}
define markBottomrightZero {.anchors {.bottomright [tm : new Anchor RIGHTSB 0 BASE]}}
define [StdAnchorGroup] : begin
local a : anchorDeriv.apply null arguments
set a.anchors.overlay : new Anchor
* [mix a.anchors.below.x a.anchors.above.x OVERLAYPOS]
* [mix a.anchors.below.y a.anchors.above.y OVERLAYPOS]
* BASE
set a.anchors.slash : new Anchor
* [mix a.anchors.below.x a.anchors.above.x 0.5]
* [mix a.anchors.below.y a.anchors.above.y 0.5]
* BASE
return a
define capitalMarks : StdAnchorGroup markAboveCap markBelowZero markToprightCap markBottomrightZero
define bMarks : StdAnchorGroup markAboveCap markBelowZero markToprightCap markBottomrightZero
define eMarks : StdAnchorGroup markAboveLower markBelowZero markToprightLower markBottomrightZero
define pMarks : StdAnchorGroup markAboveLower markBelowLower markToprightLower markBottomrightLower
define ifMarks : StdAnchorGroup markAboveCap markBelowLower markToprightCap markBottomrightLower
### Glyph slots and dependency profile generation (used for recursive subfonts)
local dependencyProfile {.}
local nTemp 0
local nPending 0
local pickHash : if recursive
then : let [h {.}] : begin
foreach j [items-of recursive] : set h.(j) j
set nPending recursive.length
* h
else nothing
define [getDependencyProfile glyph] : begin
local dp {}
foreach d [items-of glyph.dependencies] : begin
dp.push d
if dependencyProfile.(d): foreach [k : items-of dependencyProfile.(d)] : dp.push k
return dp
define [create-glyph name actions] : piecewise
(name && actions) : begin
if (pickHash && nPending <= 0) : begin
console.log " *** Done recursive build for \[if (recursive.length > 3) [recursive.slice 0 3 :.concat {'...'} :.join ','] [recursive.join ',']] in \(font.name.uniqueSubFamily)"
throw {.glyfMap glyphs}
if (pickHash && [not pickHash.(name)]) : return nothing
define glyphObject [new Glyph name]
glyphList.push glyphObject
glyphs.(name) = glyphObject
glyphObject.set-width WIDTH
glyphObject.gizmo = globalTransform
actions.call glyphObject
set dependencyProfile.(name) : getDependencyProfile glyphObject
dec nPending
return glyphObject
true : begin
local actions arguments.0
local glyphObject [new Glyph ('.temp-' + [set nTemp (nTemp + 1)])]
glyphObject.set-width WIDTH
actions.call glyphObject
return glyphObject
### Spiro constructions
# Basic knots
local [object
g4 g2 corner flat curl close end straight
widths heading unimportant important
alsothru alsothruthem bezcontrols quadcontrols archv arcvh complexThru
dispiro spiro-outline] : spirokit.SetupBuilders : object globalTransform CONTRAST STROKE Glyph para SUPERNESS
###### HERE WE GO!
### Metadata
# Font names
set font.name.fontFamily para.family
set font.name.fontSubFamily para.style
set font.name.preferredFamily para.family
set font.name.preferredSubFamily para.style
set font.name.uniqueSubFamily "\(para.family) \(para.style) \(para.version) (\(para.codename))"
set font.name.version para.version
set font.name.fullName : if (para.style != 'Regular') (para.family + ' ' + para.style) para.family
set font.name.postScriptName : font.name.fullName.replace [regex ' ' 'g'] '-'
set font.name.copyright para.copyright
set font.name.licence para.licence
# Weight, width and slantness
set font.'OS/2'.usWeightClass para.weight
set font.'OS/2'.bProportion 9 # Monospaced
set font.'OS/2'.bWeight : 1 + para.weight / 100
set font.'OS/2'.fsSelection : [if para.isBold 32 0] + [if para.isItalic 1 0] + [if ([not para.isBold] && [not para.isItalic]) 64 0] + 128
set font.'OS/2'.sFamilyClass : 8 * 0x100 + 9
set font.post.isFixedPitch 1
set font.head.macStyle : [if para.isBold 1 0] + [if para.isItalic 2 0]
# Metric metadata
# Note: we use 1000upm in design, and (1000 * upmsacle)upm in production, to avoid rounding error.
let [asc : 1250 * CAP / (CAP - DESCENDER)] [desc : 1250 * DESCENDER / (CAP - DESCENDER)] : begin
set font.head.unitsPerEm 1000
set font.hhea.ascent asc
set font.'OS/2'.usWinAscent asc
set font.'OS/2'.sTypoAscender asc
set font.hhea.descent desc
set font.'OS/2'.usWinDescent [Math.abs desc]
set font.'OS/2'.sTypoDescender desc
set font.hhea.lineGap (CAP * 0.2)
set font.'OS/2'.sTypoLineGap (CAP * 0.2)
set font.'OS/2'.sxHeight XH
set font.'OS/2'.sCapHeight CAP
set font.post.italicAngle (0 - para.italicangle)
# Necessary notdef glyph
create-glyph '.notdef' : glyph-construction
start-from SB 0
line-to SB CAP
line-to RIGHTSB CAP
line-to RIGHTSB 0
start-from (SB + STROKE) STROKE
line-to (RIGHTSB - STROKE) STROKE
line-to (RIGHTSB - STROKE) (CAP - STROKE)
line-to (SB + STROKE) (CAP - STROKE)
# Space
create-glyph 'space' : glyph-construction
set-width WIDTH
assign-unicode ' '
include eMarks
### HERE WE GO
$$include 'glyphs/common-shapes.patel'
$$include 'glyphs/overmarks.patel'
# Unified letters
$$include 'glyphs/letters-unified-basic.patel'
$$include 'glyphs/letters-unified-extended.patel'
# Numbers
$$include 'glyphs/numbers.patel' ; progress 'Numbers'
# Symbols
$$include 'glyphs/symbol-punctuation.patel'
$$include 'glyphs/symbol-math.patel'
$$include 'glyphs/symbol-letter.patel'
$$include 'glyphs/symbol-geometric.patel'
$$include 'glyphs/symbol-other.patel'
# Autobuilds
$$include 'glyphs/autobuilds.patel'
set font.glyfMap glyphs
return font