Iosevka/buildglyphs.ptl
2016-01-13 05:26:50 +08:00

529 lines
21 KiB
Plaintext

import 'unorm' as unorm
import './support/glyph' as Glyph
import './support/point' as Point
import './support/spirokit' as spirokit
import './support/transform' as : Transform && [object [transformPoint tp] [untransform utp] inverse]
import './support/anchor' as Anchor
import './support/monotonic-interpolate' as smoothreg
import './support/fairify' as fairify
extern global
define [suggestGC] : begin
if (global && global.gc) : global.gc
return nothing
### 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
### Autoarg macro
define [$NamedParameterPair$ l r] : begin
set this.left l
set this.right r
return this
define-operator "--" 890 'right' : syntax-rules
`(@l -- @r) [atom l] `[new $NamedParameterPair$ @{".quote" [formOf l]} @r]
define-macro params : syntax-rules
`[params @_pairs @body] : begin
local ta : env.newt
local tb : env.newt
local t : env.newt
local ps `[begin
[local @ta : {}.slice.call arguments 0]
[local @tb {}]
[for [local @t 0] (@t < @ta.length) [inc @t] : if [not : @ta.(@t) <@ $NamedParameterPair$] : @tb.push @ta.(@t)]
]
local aps `[begin]
local dps `[begin]
local j 0
foreach [pf : items-of : formOf _pairs] : begin
local name
if [atom pf] : then
ps.push `[local @pf : fallback @pf (@tb).(@{".quote" j})]
set name pf
: else
ps.push `[local @(pf.0) : fallback @(pf.0) (@tb).(@{".quote" j}) @(pf.1)]
set name pf.0
aps.push `[if (@t && @t <@ $NamedParameterPair$ && @t.left == @{".quote" name}) [set @name @t.right]]
if pf.2 : dps.push `[local @(pf.2) @name]
inc j
ps.push `[foreach [@t : items-of @ta] @aps]
ps.push dps
ps.push : formOf body
return : dirty ps
### Point macro
define-operator "<>" 800 "never" : begin
local tClass [definingEnv.newt 'class']
local m : syntax-rules
`(@x <> @y) `[new @tClass @x @y]
set coinit.initFn : lambda [m] : begin
set m.toPattern : lambda [form env wrapper] : match form
`(@x <> @y) : begin
local p1 [toPattern x env wrapper]
local p2 [toPattern y env wrapper]
object
whether : lambda [t] `(@t && @[p1.whether `(@t.x)] && @[p2.whether `(@t.y)])
assign : lambda [t locallyQ] : ex `[begin
@{".preserve" [p1.assign `(@t.x) locallyQ]}
@{".preserve" [p2.assign `(@t.y) locallyQ]}
] env
set coinit.injectForm `[define [@tClass x y] : begin \\
set this.x x
set this.y y
return nothing
]
return m
# 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]]
###### Canvas-based mechanism
define [$donothing$] nothing
define-macro sketch : syntax-rules
`[sketch @::steps] : begin
if [not externEnv.$nWFGlyphs$] : set externEnv.$nWFGlyphs$ 0
inc externEnv.$nWFGlyphs$
dirty `[[lambda [currentGlyph] [begin \\
if [not currentGlyph] : return nothing
if [glyphList.(glyphList.length - 1).name === @{".quote" (".WF" + externEnv.$nWFGlyphs$)}] : glyphList.pop
begin @::[steps.map formOf]
set dependencyProfile.(currentGlyph.name) : getDependencyProfile currentGlyph
return currentGlyph
]] [create-glyph @{".quote" (".WF" + externEnv.$nWFGlyphs$)} $donothing$]]
define-macro branch : syntax-rules
`[branch @::steps] : begin
if [not externEnv.$nWFGlyphs$] : set externEnv.$nWFGlyphs$ 0
inc externEnv.$nWFGlyphs$
dirty `[[lambda [currentGlyph] [begin \\
if [not currentGlyph] : return nothing
if [glyphList.(glyphList.length - 1).name === @{".quote" (".WF" + externEnv.$nWFGlyphs$)}] : glyphList.pop
begin @::[steps.map formOf]
set dependencyProfile.(currentGlyph.name) : getDependencyProfile currentGlyph
return currentGlyph
]] [create-glyph @{".quote" (".WF" + externEnv.$nWFGlyphs$)} [lambda : begin [this.include currentGlyph true] [set this.advanceWidth currentGlyph.advanceWidth]]]]
define-macro save : syntax-rules
`[save @::args] : dirty `[$save$.call currentGlyph @::args]
### 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 [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 [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
if para.verbose : console.log " \(font.name.uniqueSubFamily) : Done \(status)"
#suggestGC
return nothing
# 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)
define CONTRAST : fallback para.contrast 1
# 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.slantAngle] / 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.slantAngle
define TANSLANT globalTransform.yx
define SINSLANT : Math.sin (para.slantAngle / 180 * Math.PI)
define COSSLANT : Math.cos (para.slantAngle / 180 * Math.PI)
define HVCONTRAST : CONTRAST * COSSLANT + SINSLANT * TANSLANT
# Orient parameters
define UPWARD : (-HVCONTRAST) <> 0
define DOWNWARD : HVCONTRAST <> 0
define RIGHTWARD : TANSLANT <> 1
define LEFTWARD : (- TANSLANT) <> (-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 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 * 0.25 + 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 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 ESS : STROKE * [fallback para.essx CONTRAST]
define ESSQUESTION : STROKE * [fallback para.essxq 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 * HVCONTRAST
define SMOOTHA : SMOOTH - TANSLANT * para.smoothadjust
define SMOOTHB : SMOOTH + TANSLANT * para.smoothadjust
define SMALLSMOOTHA : SMALLSMOOTH - TANSLANT * para.smoothadjust
define SMALLSMOOTHB : SMALLSMOOTH + TANSLANT * para.smoothadjust
define CORRECTION_OMIDX : TANSLANT * [linreg 18 1.3 126 0.9 STROKE]
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 * HVCONTRAST))
define MVERTSTROKE : adviceBlackness : fallback para.lllcrowdedness (3 + 1 / 3)
define OVERLAYSTROKE : adviceBlackness 3.75
define OPERATORSTROKE : adviceBlackness 3.2
define SHOULDERFINE : if para.shoulderfine (STROKE * para.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.21 + STROKE * 0.22 * [clamp 1 2 : linreg 126 1 137 1.025 STROKE] + 0.035 * (RIGHTSB - SB)
return : ss + sign * TANSLANT * para.smoothadjust * (ss / SMALLSMOOTH)
define [adviceGlottalStopSmooth y sign] : ((y - STROKE) * 0.24 + STROKE * 0.625) + sign * TANSLANT * para.smoothadjust
define [shoulderMidSlope _fine _stroke _dir] : 0.5 * HVCONTRAST * ([fallback _stroke STROKE] - [fallback _fine SHOULDERFINE]) / [fallback _stroke STROKE] + [fallback _dir 1] * TANSLANT
# 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 * TANSLANT + 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] : match [Array.prototype.slice.call arguments 0]
`[@name @actions] : begin
if (pickHash && nPending <= 0) : begin
if para.verbose: 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
`[@actions] : begin
local glyphObject [new Glyph ('.temp-' + [set nTemp (nTemp + 1)])]
glyphObject.set-width WIDTH
glyphObject.gizmo = globalTransform
actions.call glyphObject
return glyphObject
define [$save$ name unicode] : begin
local t this
local g : create-glyph name [lambda]
if g : begin
g.include t AS_BASE
set g.advanceWidth t.advanceWidth
set g.shortName t.shortName
set g.dense t.dense
if name : set dependencyProfile.(name) : getDependencyProfile g
if (g && unicode) : begin
g.assign-unicode unicode
set unicodeGlyphs.(g.unicode.((g.unicode.length - 1))) g
return g
### 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 para.family [para.family.trim]
set para.style : [para.style.trim] || "Regular"
set font.name.preferredFamily para.family
set font.name.preferredSubFamily para.style
if (para.style == 'Regular' || para.style == 'Bold' || para.style == 'Italic' || para.style == "Bold Italic") : then
set font.name.fontFamily para.family
set font.name.fontSubFamily para.style
: else
set font.name.fontFamily : para.family + ' ' + [para.style.replace [regex ' Italic$'] '']
set font.name.fontSubFamily : if [[regex ' Italic$'].test para.style] 'Italic' 'Regular'
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.'OS/2'.xAvgCharWidth WIDTH
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 : para.leading * CAP / (CAP - DESCENDER)] [desc : para.leading * 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 DESCENDER
set font.'OS/2'.usWinDescent [Math.abs desc]
set font.'OS/2'.sTypoDescender desc
set font.hhea.lineGap (para.leading - asc + DESCENDER)
set font.'OS/2'.sTypoLineGap (para.leading - asc + desc)
set font.'OS/2'.sxHeight XH
set font.'OS/2'.sCapHeight CAP
set font.post.italicAngle (0 - para.slantAngle)
# Necessary notdef glyph
sketch # .notdef
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)
save '.notdef'
# Space
sketch # space
set-width WIDTH
include eMarks
save 'space' ' '
### HERE WE GO
$$include 'glyphs/common-shapes.ptl'
$$include 'glyphs/overmarks.ptl'
# Unified letters
$$include 'glyphs/letters-unified-basic.ptl'
$$include 'glyphs/letters-unified-extended.ptl'
# Numbers
$$include 'glyphs/numbers.ptl'
# Symbols
$$include 'glyphs/symbol-punctuation.ptl'
$$include 'glyphs/symbol-math.ptl'
$$include 'glyphs/symbol-letter.ptl'
$$include 'glyphs/symbol-geometric.ptl'
$$include 'glyphs/symbol-other.ptl'
# Autobuilds
set font.features {.}
$$include 'glyphs/autobuilds.ptl'
set font.glyfMap glyphs
return font