Iosevka/buildglyphs.patel
2015-08-17 12:28:19 +08:00

415 lines
17 KiB
Plaintext

define Glyph [require './support/glyph'].Glyph
define Stroke [require './support/stroke'].Stroke
define tp [require './support/transform'].transformPoint
define inverse [require './support/transform'].inverse
define libspiro : require 'libspiro-js'
### 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 [fallback] : for [local j 0] [j < arguments.length] [inc j] : if [arguments`j !== nothing] : return arguments`j
define emptyFontStr : JSON.stringify [require './empty.json']
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 ast : parse [fs.readFileSync absolutePath 'utf-8'] (.included true)
return ('.syntactic-closure' ast env)
}
otherwise @`nothing
}
define [buildFont para recursive] : begin {
define variantSelector para.variantSelector
define font : JSON.parse emptyFontStr
define glyphList font.glyf
define glyphs (.'.notdef' glyphList.0)
define unicodeGlyphs ()
# metrics
define DESCENDER para.descender
define WIDTH para.width
define CAP para.cap
define XH para.xheight
define O para.o
define OXHOOK para.oxhook
define SB para.sb
define HOOK para.hook
define AHOOK para.ahook
define SHOOK para.shook
define RHOOK para.rhook
define HOOKX para.hookx
define SMOOTH para.smooth
define SMALLSMOOTH para.smallsmooth
define STROKE para.stroke
define DOTSIZE para.dotsize
define PERIODSIZE para.periodsize
define BARPOS para.barpos
define GBARPOS para.gbarpos
define FIVEBARPOS para.fivebarpos
define LONGJUT para.longjut
define JUT para.jut
define ACCENT para.accent
define ACCENTX para.accentx
# Common 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]
# Transform constructors
define [Italify angle shift] : begin {
local slope [Math.tan [[fallback angle para.italicangle] / 180 * Math.PI]]
return (.xx 1 .yx slope .xy 0 .yy 1 .x [fallback shift : -slope * parenMid] .y 0)
}
define [Upright angle shift] [inverse : Italify angle shift]
define [Scale sx sy] (.xx sx .yx 0 .xy 0 .yy [fallback sy sx] .x 0 .y 0)
define [Translate x y] (.xx 1 .yx 0 .xy 0 .yy 1 .x x .y y)
define [Rotate angle] (.xx [Math.cos angle] .yx [-[Math.sin angle]] .xy [Math.sin angle] .yy [Math.cos angle] .x 0 .y 0)
define globalTransform : Italify para.italicAngle
define ITALICCOR : 1 / [Math.sqrt [1 - globalTransform.yx * globalTransform.yx]]
define UPWARD (.x [-ITALICCOR] .y 0)
define DOWNWARD (.x ITALICCOR .y 0)
define RIGHTWARD (.x globalTransform.yx .y 1)
define LEFTWARD (.x [- globalTransform.yx] .y [-1])
# derived metrics
define XO : XH - O
define CAPO : CAP - O
define HALFSTROKE : STROKE / 2
define RIGHTSB : WIDTH - SB
define MIDDLE : WIDTH / 2
define CAPMIDDLE : CAP / 2
define CAP_SMOOTH : CAP - SMOOTH
define DOTRADIUS : DOTSIZE / 2
define PERIODRADIUS : PERIODSIZE / 2
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 ITALICCORS : STROKE * globalTransform.yx
define OMIDCOR : globalTransform.yx * 1.2
define OMIDCOR_S : STROKE * OMIDCOR
# style parameters
define EBARPOS : para.ebarpos || BARPOS
define KAPPA para.kappa
define COKAPPA : 1 - KAPPA
define BKAPPA : para.bkappa || KAPPA + 0.1
define CKAPPA : para.ckappa || BKAPPA
define COBKAPPA : 1 - BKAPPA
define KAPPA_HOOK : fallback para.kappa_hook [BKAPPA + 0.1]
define KAPPA_AHOOK : fallback para.kappa_ahook KAPPA_HOOK
define KAPPA_RHOOK : fallback para.kappa_rhook KAPPA_HOOK
define TAILADJX : WIDTH * 0.2
define TAILADJY : XH * 0.25
define TAILADJKAPPA 0.75
define TAILADJSX : WIDTH * 0.2
define TAILADJSY 0
define TAILADJSKAPPA 0.75
define ILBALANCE : LONGJUT * 0.04
define JBALANCE : para.jbalance || HALFSTROKE + ILBALANCE
define TBALANCE : para.tbalance || JBALANCE
define TBALANCE2 : para.tbalance2 || TBALANCE
define RBALANCE : para.rbalance || JBALANCE * 0.3
define SBALANCE : fallback para.sbalance 0.52
define serifed : not [not para.serif]
# Blackness parameters
# We will estimate blackness using lower-case 'e'
define WHITENESS : [[XH - STROKE * 2.5] * [RIGHTSB - SB] * [1 / 3]] / [XH * [RIGHTSB - SB]]
define [adviceBlackness crowdedness] : Math.min STROKE [[RIGHTSB - SB] * [1 - WHITENESS] / crowdedness]
define OPERATORSTROKE : adviceBlackness 3.25
# Anchor parameters
define BASE 'base'
define MARK 'mark'
define MARKBASE 'markbase'
define AS_BASE 'AS-BASE'
define [tm anchor] : return (
.x [anchor.x * globalTransform.xx + anchor.y * globalTransform.yx + globalTransform.x]
.y [anchor.x * globalTransform.xy + anchor.y * globalTransform.yy + globalTransform.y]
.type anchor.type
)
define markAboveLower (.anchors (.above [tm (.x MIDDLE .y XH .type BASE)]))
define markAboveCap (.anchors (.above [tm (.x MIDDLE .y CAP .type BASE)]))
define markBelowLower (.anchors (.below [tm (.x MIDDLE .y DESCENDER .type BASE)]))
define markBelowZero (.anchors (.below [tm (.x MIDDLE .y 0 .type BASE)]))
define markToprightLower (.anchors (.topright [tm (.x RIGHTSB .y XH .type BASE)]))
define markToprightCap (.anchors (.topright [tm (.x RIGHTSB .y CAP .type BASE)]))
define markBottomrightLower (.anchors (.bottomright [tm (.x RIGHTSB .y DESCENDER .type BASE)]))
define markBottomrightZero (.anchors (.bottomright [tm (.x RIGHTSB .y 0 .type BASE)]))
define [anchorDeriv] : begin {
local h (.)
foreach a [items-of arguments] : foreach k [items-of [Object.keys a.anchors]] : begin {
set h.(k) (.)
set (.x h.(k).x .y h.(k).y .type h.(k).type .mbx h.(k).mbx .mby h.(k).mby) a.anchors.(k)
}
return (.anchors h)
}
define [StdAnchorGroup] : begin {
local a : anchorDeriv.apply null arguments
set a.anchors.overlay (.type BASE .x [mix a.anchors.below.x a.anchors.above.x BARPOS] .y [mix a.anchors.below.y a.anchors.above.y BARPOS])
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 markBelowZero markToprightLower markBottomrightLower
define ifMarks : StdAnchorGroup markAboveCap markBelowZero markToprightCap markBottomrightLower
Stroke.bindParameters para
### 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
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.'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.head.macStyle : [if para.isBold 1 0] + [if para.isItalic 2 0]
### Metric metadata
# Note: we use 1000upm in design, and 4096upm in production
define upmscale : fallback para.upmscale 1
set font.head.unitsPerEm 1000
set font.hhea.ascent [CAP + ACCENT * 1.5]
set font.'OS/2'.usWinAscent [CAP + ACCENT * 1.5]
set font.'OS/2'.sTypoAscender [CAP + ACCENT * 1.5]
set font.hhea.descent [DESCENDER - ACCENT * 0.5]
set font.'OS/2'.usWinDescent [Math.abs [DESCENDER - ACCENT * 0.5]]
set font.'OS/2'.sTypoDescender [DESCENDER - ACCENT * 0.5]
set font.hhea.lineGap [CAP * 0.2]
set font.'OS/2'.sTypoLineGap [CAP * 0.2]
set font.'OS/2'.sxHeight XH
set font.post.italicAnvle [0 - para.italicangle]
### Spiro constructions
define [flatten knots] : begin {
local a ()
foreach p [items-of knots] : piecewise {
[p <@ Array] : set a : a.concat [flatten p]
true : a.push p
}
return a
}
define [afInterpolate before after args] : G2 [mix before.x after.x args.rx] [mix before.y after.y args.ry] VIRTUAL
define [G4 x y f] (.x x .y y .type 'g4' .af f)
define [G2 x y f] (.x x .y y .type 'g2' .af f)
define [LEFT x y f] (.x x .y y .type 'left' .af f)
define [RIGHT x y f] (.x x .y y .type 'right' .af f)
define [CLOSE f] (.type 'close' .af f)
define [PREPARE f] (.type 'prepare' .af f)
define [WIDTHS l r] : lambda [] : this.set-width l r
define [VIRTUAL] : this.points.[this.points.length - 1].subdivided = true
define [INTERPOLATE rx ry] (.type 'interpolate' .rx rx .ry ry .af afInterpolate)
define [spiro] : begin {
local s : new Stroke
s.set-transform globalTransform
local knots : ().slice.call arguments 0
local closed false
local lastaf
if [knots.0 && knots.0.type === 'prepare'] : begin {
knots.0.af.call s
set knots : knots.slice 1
}
if [knots.[knots.length - 1] && knots.[knots.length - 1].type === 'close'] : begin {
set closed true
set lastaf knots.[knots.length - 1].af
set knots : knots.slice 0 [-1]
}
set knots : flatten knots
if closed : knots.push knots.0
foreach j [range 0 knots.length] : if [knots.(j) && knots.(j).type === 'interpolate'] : begin {
set knots.(j) : knots.(j).af.call s knots.[j - 1] knots.[j + 1] knots.(j)
}
if closed : knots.pop
set knots : flatten knots
s.set-samples 1
libspiro.spiroToBezierOnContext knots closed s
if lastaf : lastaf.call s
return s
}
### Necessary macros
define-macro glyph-construction : syntax-rules {
@`[glyph-construction @::steps] ('.syntactic-closure' @`[lambda [] [begin {
local currentGlyph this
local set-width : this.set-width.bind this
local assign-unicode : lambda [code] : begin {
currentGlyph.assign-unicode code
set unicodeGlyphs.(currentGlyph.unicode`[currentGlyph.unicode.length - 1]) currentGlyph
}
local start-from : this.start-from.bind this
local line-to : this.line-to.bind this
local curve-to : this.curve-to.bind this
local cubic-to : this.cubic-to.bind this
local arc-hv-to : this.arc-hv-to.bind this
local arc-vh-to : this.arc-vh-to.bind this
local put-shapes : this.put-shapes.bind this
local reverse-last : this.reverse-last.bind this
local include : this.include.bind this
local create-stroke : this.create-stroke.bind this
local set-anchor : this.set-anchor.bind this
local apply-transform : this.apply-transform.bind this
# Actually I can define them using macros, but it makes compilation slow.
# define-macro set-width : syntax-rules { @`[set-width @::args] @`[currentGlyph.set-width @::args] }
# define-macro start-from : syntax-rules { @`[start-from @::args] @`[currentGlyph.start-from @::args] }
# define-macro line-to : syntax-rules { @`[line-to @::args] @`[currentGlyph.line-to @::args] }
# define-macro curve-to : syntax-rules { @`[curve-to @::args] @`[currentGlyph.curve-to @::args] }
# define-macro cubic-to : syntax-rules { @`[cubic-to @::args] @`[currentGlyph.cubic-to @::args] }
# define-macro include : syntax-rules { @`[include @::args] @`[currentGlyph.include @::args] }
# define-macro create-stroke : syntax-rules { @`[create-stroke @::args] @`[currentGlyph.create-stroke @::args] }
# define-macro set-anchor : syntax-rules { @`[set-anchor @::args] @`[currentGlyph.set-anchor @::args] }
# define-macro apply-transform : syntax-rules { @`[apply-transform @::args] @`[currentGlyph.apply-transform @::args] }
# define-macro reverse-last : syntax-rules { @`[reverse-last @::args] @`[currentGlyph.reverse-last @::args] }
local [dont-export] : set currentGlyph.dontExport true
this.gizmo = globalTransform
begin @::[steps.map formOf]
return nothing
}]] env)
}
local dependencyProfile (.)
local nTemp 0
local pickHash : if recursive {
then : let [h (.)] : begin {
foreach j [items-of recursive] : set h.(j) j
* h
}
else nothing
}
define [create-glyph name actions] : piecewise {
[name && actions] : begin {
if [pickHash && [not pickHash.(name)]] : return nothing
process.stderr.write : "Building /" + name + [if recursive " (recursive)" ""] + " for " + para.family + ' ' + para.style + "\n"
set dependencyProfile`name ()
define glyphObject [new Glyph name]
glyphObject.set-width WIDTH
glyphList.push glyphObject
glyphs`name = glyphObject
actions.call glyphObject
foreach d [items-of glyphObject.dependencies] : begin {
local allAliases : Object.keys glyphs :.filter [[k] -> [glyphs`k === glyphs`d]]
dependencyProfile`name = [dependencyProfile`name.concat dependencyProfile`d allAliases]
}
return glyphObject
}
true : begin {
local actions arguments.0
local glyphObject [new Glyph ['.temp-' + [set nTemp [inc nTemp]]]]
glyphObject.set-width WIDTH
actions.call glyphObject
return glyphObject
}
}
define [select-variant name unicode default] : begin {
if [pickHash && [not pickHash.(name)]] : return nothing
local variant : variantSelector`name || default
local chosenGlyph glyphs`[name + '.' + variant]
create-glyph name : glyph-construction {
include chosenGlyph AS_BASE
if unicode : assign-unicode unicode
}
local allAliases : Object.keys glyphs :.filter [[k] -> [glyphs`k === glyphs.(chosenGlyph.name)]]
set dependencyProfile`name : allAliases.concat dependencyProfile.(chosenGlyph.name)
}
define [italic-variant name unicode] : create-glyph name : glyph-construction {
if [para.italicangle > 0] {
then : include glyphs.[name + '.italic'] AS_BASE
else : include glyphs.[name + '.upright'] AS_BASE
}
if unicode : assign-unicode unicode
}
define [alias newid unicode oldid] : create-glyph newid : glyph-construction {
if unicode : assign-unicode unicode
include glyphs`oldid AS_BASE
}
###### HERE WE GO!
create-glyph 'space' : glyph-construction {
set-width WIDTH
assign-unicode ' '
include eMarks
}
# ASCII
$$include 'glyphs/common-shapes.patel'
$$include 'glyphs/overmarks.patel'
$$include 'glyphs/latin-basic-capital.patel'
$$include 'glyphs/latin-basic-lower.patel'
$$include 'glyphs/numbers.patel'
$$include 'glyphs/symbol-ascii.patel'
# Extended
$$include 'glyphs/greek.patel'
$$include 'glyphs/cyrillic-basic.patel'
$$include 'glyphs/latin-extend-basis.patel'
$$include 'glyphs/latin-extend-decorated.patel'
$$include 'glyphs/cyrillic-extended.patel'
$$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'
$$include 'glyphs/autobuilds.patel'
### Zoom all glyphs by para.upmscale
set font.head.unitsPerEm : upmscale * font.head.unitsPerEm
set font.hhea.ascent : upmscale * font.hhea.ascent
set font.'OS/2'.usWinAscent : upmscale * font.'OS/2'.usWinAscent
set font.'OS/2'.sTypoAscender : upmscale * font.'OS/2'.sTypoAscender
set font.hhea.descent : upmscale * font.hhea.descent
set font.'OS/2'.usWinDescent : upmscale * font.'OS/2'.usWinDescent
set font.'OS/2'.sTypoDescender : upmscale * font.'OS/2'.sTypoDescender
set font.hhea.lineGap : upmscale * font.hhea.lineGap
set font.'OS/2'.sTypoLineGap : upmscale * font.'OS/2'.sTypoLineGap
set font.'OS/2'.sxHeight : upmscale * font.'OS/2'.sxHeight
if [upmscale != 1] : foreach glyph [items-of glyphList] : begin {
glyph.advanceWidth = glyph.advanceWidth * upmscale
if glyph.contours: foreach contour [items-of glyph.contours] : foreach point [items-of contour] : begin {
point.x = point.x * upmscale
point.y = point.y * upmscale
}
}
set font.glyfMap glyphs
return font
}
exports.build = buildFont