Move font building related files to font-src
This commit is contained in:
parent
9a2f862631
commit
c48bc20aa2
56 changed files with 18 additions and 18 deletions
33
font-src/gen/build-font.js
Normal file
33
font-src/gen/build-font.js
Normal file
|
@ -0,0 +1,33 @@
|
|||
"use strict";
|
||||
|
||||
const EmptyFont = require("./empty-font.js");
|
||||
const buildGlyphs = require("./build-glyphs.js");
|
||||
const finalizeFont = require("./finalize/index");
|
||||
|
||||
const { buildOtl } = require("../otl/index");
|
||||
const { assignFontNames } = require("../meta/naming");
|
||||
const { setFontMetrics } = require("../meta/aesthetics");
|
||||
|
||||
module.exports = function (para) {
|
||||
const font = EmptyFont();
|
||||
const gs = buildGlyphs(para);
|
||||
|
||||
assignFontNames(para, gs.metrics, font);
|
||||
setFontMetrics(para, gs.metrics, font);
|
||||
|
||||
const otl = buildOtl(para, gs.glyphs, gs.glyphList, gs.unicodeGlyphs);
|
||||
font.GSUB = otl.GSUB;
|
||||
font.GPOS = otl.GPOS;
|
||||
font.GDEF = otl.GDEF;
|
||||
|
||||
// Regulate
|
||||
const excludeChars = new Set();
|
||||
if (para.excludedCharRanges) {
|
||||
for (const [start, end] of para.excludedCharRanges) {
|
||||
for (let p = start; p <= end; p++) excludeChars.add(p);
|
||||
}
|
||||
}
|
||||
|
||||
finalizeFont(para, [...gs.glyphList], excludeChars, font);
|
||||
return font;
|
||||
};
|
153
font-src/gen/build-glyphs.ptl
Normal file
153
font-src/gen/build-glyphs.ptl
Normal file
|
@ -0,0 +1,153 @@
|
|||
import '../support/glyph' as Glyph
|
||||
import '../support/point' as Point
|
||||
import './kits/spiro-kit' as spirokit
|
||||
import './kits/boole-kit' as BooleKit
|
||||
import '../support/anchor' as Anchor
|
||||
import '../support/monotonic-interpolate' as smoothreg
|
||||
|
||||
import [mix linreg clamp fallback] from '../support/utils'
|
||||
import [calculateMetrics setFontMetrics MarksetDiv GenDivFrame] from '../meta/aesthetics'
|
||||
|
||||
$$include '../meta/macros.ptl'
|
||||
|
||||
define [$NamedParameterPair$ l r] : begin
|
||||
set this.left l
|
||||
set this.right r
|
||||
return this
|
||||
|
||||
define [$donothing$] nothing
|
||||
|
||||
# contour tagging
|
||||
define [tagged tag component] : begin
|
||||
set component.tag tag
|
||||
return component
|
||||
|
||||
export all : define [buildGlyphs para recursive recursiveCodes] : begin
|
||||
define variantSelector para.variantSelector
|
||||
local glyphList {}
|
||||
local glyphMap {.}
|
||||
local unicodeGlyphs {}
|
||||
|
||||
define metrics : calculateMetrics para
|
||||
define [object globalTransform UPM Middle CAP XH SB RightSB Contrast Stroke Superness Width TanSlope OverlayPos Descender symbolMid parenTop parenBot operTop operBot plusTop plusBot tackTop tackBot adviceBlackness MVertStrokeD] metrics
|
||||
|
||||
define fontMetrics {.head {.} .hhea {.} .OS_2 {.} .post {.}}
|
||||
setFontMetrics para metrics fontMetrics
|
||||
|
||||
# Anchor parameters
|
||||
define {AS_BASE ALSO_METRICS} {'AS-BASE' 'ALSO-METRICS'}
|
||||
|
||||
define MarkSet : MarksetDiv 1 metrics
|
||||
define DivFrame : GenDivFrame metrics
|
||||
|
||||
### Glyph slots and dependency profile generation (used for recursive subfonts)
|
||||
local dependencyProfile {.}
|
||||
local nTemp 0
|
||||
define [newtemp] : set nTemp (nTemp + 1)
|
||||
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 [createAndSaveGlyphImpl saveGlyphName unicode actions] : begin
|
||||
if (saveGlyphName && pickHash && [not pickHash.(saveGlyphName)]) : return nothing
|
||||
|
||||
local ensuredGlyphName : if saveGlyphName saveGlyphName ('.temp-' + [newtemp])
|
||||
if para.verbose : console.log ensuredGlyphName
|
||||
|
||||
local glyphObject [new Glyph ensuredGlyphName]
|
||||
glyphObject.setWidth Width
|
||||
glyphObject.gizmo = globalTransform
|
||||
|
||||
if saveGlyphName : begin
|
||||
glyphList.push glyphObject
|
||||
if (saveGlyphName.0 != '.' && glyphMap.(saveGlyphName))
|
||||
throw : new Error "Glyph \(saveGlyphName) already exists"
|
||||
glyphMap.(saveGlyphName) = glyphObject
|
||||
if unicode : $assignUnicodeImpl$ glyphObject unicode
|
||||
|
||||
actions.call glyphObject
|
||||
|
||||
local complexity : [(glyphObject.contours || {}).map (x => x.length)].reduce ([a b] => (a + b)) 0
|
||||
if (complexity > 0x7fff) : console.log 'Possible broken shape found in' ensuredGlyphName 'Complexity' complexity
|
||||
|
||||
if saveGlyphName : begin
|
||||
set dependencyProfile.(saveGlyphName) : getDependencyProfile glyphObject
|
||||
dec nPending
|
||||
|
||||
return glyphObject
|
||||
|
||||
define [create-glyph] : match [Array.prototype.slice.call arguments 0]
|
||||
`[@name @unicode @actions] : return : createAndSaveGlyphImpl name unicode actions
|
||||
`[@name @actions] : return : createAndSaveGlyphImpl name null actions
|
||||
`[@actions] : return : createAndSaveGlyphImpl null null actions
|
||||
|
||||
define [$assignUnicodeImpl$ g unicode] : begin
|
||||
g.assignUnicode unicode
|
||||
set unicodeGlyphs.(g.unicode.((g.unicode.length - 1))) g
|
||||
|
||||
define [$save$ _name unicode] : begin
|
||||
define t this
|
||||
define name : if [not _name] ('--autoname-' + [newtemp]) _name
|
||||
local g : create-glyph name [lambda]
|
||||
if g : begin
|
||||
g.include t AS_BASE
|
||||
if (t <@ Glyph) : begin
|
||||
set g.advanceWidth t.advanceWidth
|
||||
set g.shortName t.shortName
|
||||
set g.autoRefPriority t.autoRefPriority
|
||||
set g.glyphRank t.glyphRank
|
||||
set g.avoidBeingComposite t.avoidBeingComposite
|
||||
set g.related t.related
|
||||
if name : set dependencyProfile.(name) : getDependencyProfile g
|
||||
if (g && unicode) : $assignUnicodeImpl$ g unicode
|
||||
return g
|
||||
|
||||
### Spiro constructions
|
||||
# Basic knots
|
||||
define spirofns : spirokit.SetupBuilders : object globalTransform Contrast Stroke Glyph para Superness
|
||||
define booleFns : BooleKit.SetupBuilders : object globalTransform Glyph
|
||||
|
||||
# IDKY, but wrapping "metrics" prevents Node.js on Arch modifying it.
|
||||
define $$Capture$$ : object [metrics : Object.create metrics] $NamedParameterPair$ $donothing$ para recursive recursiveCodes variantSelector glyphMap glyphList unicodeGlyphs create-glyph $save$ spirofns booleFns MarkSet AS_BASE ALSO_METRICS pickHash dependencyProfile getDependencyProfile buildGlyphs newtemp tagged DivFrame fontMetrics $assignUnicodeImpl$
|
||||
|
||||
### HERE WE GO
|
||||
run-glyph-module '../glyphs/common-shapes.js'
|
||||
run-glyph-module '../glyphs/space.js'
|
||||
run-glyph-module '../glyphs/overmarks.js'
|
||||
|
||||
# Unified letters
|
||||
run-glyph-module '../glyphs/letter-latin.js'
|
||||
run-glyph-module '../glyphs/letter-greek.js'
|
||||
run-glyph-module '../glyphs/letter-cyrillic.js'
|
||||
|
||||
# Numbers
|
||||
run-glyph-module '../glyphs/numbers.js'
|
||||
|
||||
# Symbols
|
||||
run-glyph-module '../glyphs/symbol-punctuation.js'
|
||||
run-glyph-module '../glyphs/symbol-arrow.js'
|
||||
run-glyph-module '../glyphs/symbol-geometric.js'
|
||||
run-glyph-module '../glyphs/symbol-math.js'
|
||||
run-glyph-module '../glyphs/symbol-letter.js'
|
||||
run-glyph-module '../glyphs/symbol-braille.js'
|
||||
run-glyph-module '../glyphs/symbol-mosaic.js'
|
||||
run-glyph-module '../glyphs/symbol-other.js'
|
||||
|
||||
# Autobuilds
|
||||
run-glyph-module '../glyphs/autobuild-accents.js'
|
||||
run-glyph-module '../glyphs/autobuild-composite.js'
|
||||
run-glyph-module '../glyphs/autobuild-transformed.js'
|
||||
|
||||
return : object metrics [glyphs glyphMap] glyphList unicodeGlyphs
|
||||
|
132
font-src/gen/empty-font.js
Normal file
132
font-src/gen/empty-font.js
Normal file
|
@ -0,0 +1,132 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = function () {
|
||||
return {
|
||||
head: {
|
||||
checkSumAdjustment: 369537602,
|
||||
flags: 11,
|
||||
fontDirectionHint: 2,
|
||||
fontRevision: 1,
|
||||
glyphDataFormat: 0,
|
||||
indexToLocFormat: 0,
|
||||
lowestRecPPEM: 8,
|
||||
macStyle: 0,
|
||||
magickNumber: 1594834165,
|
||||
created: 3562553439,
|
||||
modified: 3562553439,
|
||||
unitsPerEm: 1000,
|
||||
version: 1,
|
||||
xMax: 306,
|
||||
xMin: 34,
|
||||
yMax: 682,
|
||||
yMin: 0
|
||||
},
|
||||
hhea: {
|
||||
advanceWidthMax: 374,
|
||||
ascender: 812,
|
||||
caretOffset: 0,
|
||||
caretSlopeRise: 1,
|
||||
caretSlopeRun: 0,
|
||||
descender: -212,
|
||||
lineGap: 92,
|
||||
metricDataFormat: 0,
|
||||
minLeftSideBearing: 34,
|
||||
minRightSideBearing: 68,
|
||||
numOfLongHorMetrics: 1,
|
||||
reserved0: 0,
|
||||
reserved1: 0,
|
||||
reserved2: 0,
|
||||
reserved3: 0,
|
||||
version: 1,
|
||||
xMaxExtent: 306
|
||||
},
|
||||
OS_2: {
|
||||
achVendID: "BE5N",
|
||||
panose: [2, 0, 5, 9, 0, 0, 0, 0, 0, 0],
|
||||
fsSelection: 192,
|
||||
fsType: 0,
|
||||
sCapHeight: 0,
|
||||
sFamilyClass: 0,
|
||||
sTypoAscender: 812,
|
||||
sTypoDescender: -212,
|
||||
sTypoLineGap: 92,
|
||||
sxHeight: 792,
|
||||
ulUnicodeRange1: 1,
|
||||
ulUnicodeRange2: 268435456,
|
||||
ulUnicodeRange3: 0,
|
||||
ulUnicodeRange4: 0,
|
||||
usBreakChar: 32,
|
||||
usDefaultChar: 0,
|
||||
usFirstCharIndex: 0,
|
||||
usLastCharIndex: 0,
|
||||
usMaxContext: 1,
|
||||
usWeightClass: 400,
|
||||
usWidthClass: 5,
|
||||
usWinAscent: 812,
|
||||
usWinDescent: 212,
|
||||
version: 4,
|
||||
xAvgCharWidth: 500,
|
||||
yStrikeoutPosition: 265,
|
||||
yStrikeoutSize: 51,
|
||||
ySubscriptXOffset: 0,
|
||||
ySubscriptXSize: 665,
|
||||
ySubscriptYOffset: 143,
|
||||
ySubscriptYSize: 716,
|
||||
ySuperscriptXOffset: 0,
|
||||
ySuperscriptXSize: 665,
|
||||
ySuperscriptYOffset: 491,
|
||||
ySuperscriptYSize: 716,
|
||||
ulCodePageRange1: {
|
||||
latin1: true,
|
||||
latin2: true,
|
||||
greek: true,
|
||||
cyrillic: true,
|
||||
turkish: true,
|
||||
vietnamese: true,
|
||||
macRoman: true
|
||||
},
|
||||
ulCodePageRange2: {
|
||||
cp852: true,
|
||||
cp850: true,
|
||||
ascii: true
|
||||
}
|
||||
},
|
||||
name: {
|
||||
fontFamily: "node-sfnt",
|
||||
fontSubFamily: "regular",
|
||||
fullName: "node-sfnt",
|
||||
postScriptName: "node-sfnt",
|
||||
uniqueSubFamily: "node-sfnt blank font",
|
||||
version: "Version 1.0"
|
||||
},
|
||||
post: {
|
||||
version: 2,
|
||||
isFixedPitch: false,
|
||||
italicAngle: 0,
|
||||
maxMemType1: 1,
|
||||
maxMemType42: 0,
|
||||
minMemType1: 0,
|
||||
minMemType42: 0,
|
||||
postoints: 65411,
|
||||
underlinePosition: -50,
|
||||
underlineThickness: 50
|
||||
},
|
||||
maxp: {
|
||||
version: 1.0,
|
||||
numGlyphs: 0,
|
||||
maxPoints: 0,
|
||||
maxContours: 0,
|
||||
maxCompositePoints: 0,
|
||||
maxCompositeContours: 0,
|
||||
maxZones: 2,
|
||||
maxTwilightPoints: 0,
|
||||
maxStorage: 0,
|
||||
maxFunctionDefs: 0,
|
||||
maxInstructionDefs: 0,
|
||||
maxStackElements: 0,
|
||||
maxSizeOfInstructions: 0,
|
||||
maxComponentElements: 0,
|
||||
maxComponentDepth: 0
|
||||
}
|
||||
};
|
||||
};
|
133
font-src/gen/finalize/autoref.js
Normal file
133
font-src/gen/finalize/autoref.js
Normal file
|
@ -0,0 +1,133 @@
|
|||
"use strict";
|
||||
|
||||
const Point = require("../../support/point");
|
||||
|
||||
function delta(a, b) {
|
||||
return Math.round((a - b) * 32);
|
||||
}
|
||||
|
||||
function contourHash(c) {
|
||||
if (!c || c.length < 2) return ".";
|
||||
let lx = c[0].x,
|
||||
ly = c[0].y;
|
||||
let buf = "";
|
||||
for (let j = 1; j < c.length; j++) {
|
||||
const z = c[j];
|
||||
buf += `${z.on ? "o" : "f"}${z.cubic ? "c" : "q"}${delta(z.x, lx)},${delta(z.y, ly)};`;
|
||||
(lx = z.x), (ly = z.y);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
function match(g1, g2, _n) {
|
||||
for (let j = 0; j + g1.contours.length <= g2.contours.length; j++) {
|
||||
let found = true;
|
||||
for (let k = j; k < g2.contours.length && k - j < g1.contours.length; k++) {
|
||||
if (
|
||||
g1.contours[k - j].hash !== g2.contours[k].hash ||
|
||||
!(
|
||||
k <= j ||
|
||||
(delta(g1.contours[k - j][0].x, g1.contours[k - j - 1][0].x) ===
|
||||
delta(g2.contours[k][0].x, g2.contours[k - 1][0].x) &&
|
||||
delta(g1.contours[k - j][0].y, g1.contours[k - j - 1][0].y) ===
|
||||
delta(g2.contours[k][0].y, g2.contours[k - 1][0].y))
|
||||
)
|
||||
) {
|
||||
found = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) {
|
||||
const refX = g2.contours[j][0].x - g1.contours[0][0].x || 0;
|
||||
const refY = g2.contours[j][0].y - g1.contours[0][0].y || 0;
|
||||
if (Math.abs(refY) > 1 && g1.advanceWidth > 1) {
|
||||
continue;
|
||||
}
|
||||
if (!g2.references) g2.references = [];
|
||||
g2.references.push({
|
||||
glyph: g1.name,
|
||||
_n: _n,
|
||||
x: refX,
|
||||
y: refY,
|
||||
roundToGrid: false
|
||||
});
|
||||
g2.contours.splice(j, g1.contours.length);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function unlinkRef(g, dx, dy, glyf) {
|
||||
let contours = g.contours.map(c => c.map(z => new Point(z.x + dx, z.y + dy, z.on, z.cubic)));
|
||||
if (g.references)
|
||||
for (let r of g.references) {
|
||||
contours = contours.concat(unlinkRef(glyf[r._n], r.x + dx, r.y + dy, glyf));
|
||||
}
|
||||
return contours;
|
||||
}
|
||||
|
||||
function autoref(gs, excludeUnicodeSet) {
|
||||
suppressNaN(gs);
|
||||
|
||||
for (let j = 0; j < gs.length; j++) {
|
||||
const g = gs[j];
|
||||
if (g.contours) {
|
||||
for (let k = 0; k < g.contours.length; k++) {
|
||||
const contour = g.contours[k];
|
||||
contour.hash = contourHash(contour);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Refl-referencify, forward.
|
||||
for (let j = 0; j < gs.length; j++) {
|
||||
if (!gs[j].contours.length || (gs[j].references && gs[j].references.length)) continue;
|
||||
for (let k = j + 1; k < gs.length; k++) {
|
||||
if (gs[j].contours.length === gs[k].contours.length) {
|
||||
match(gs[j], gs[k], j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// referencify, backward
|
||||
for (let j = 0; j < gs.length; j++) {
|
||||
if (gs[j].autoRefPriority < 0) continue;
|
||||
if (!gs[j].contours.length) continue;
|
||||
if (gs[j].references && gs[j].references.length) continue;
|
||||
for (let k = gs.length - 1; k >= 0; k--) {
|
||||
if (gs[j].contours.length > gs[k].contours.length) continue;
|
||||
if (
|
||||
gs[j].contours.length === gs[k].contours.length &&
|
||||
!(gs[k].references && gs[k].references.length)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
while (match(gs[j], gs[k], j)) "pass";
|
||||
}
|
||||
}
|
||||
|
||||
// unlink composite
|
||||
for (let j = 0; j < gs.length; j++) {
|
||||
if (!gs[j].references || gs[j].references.length === 0) continue;
|
||||
if (!gs[j].avoidBeingComposite && gs[j].contours.length === 0) continue;
|
||||
gs[j].contours = unlinkRef(gs[j], 0, 0, gs);
|
||||
gs[j].references = [];
|
||||
}
|
||||
}
|
||||
|
||||
function suppressNaN(glyf) {
|
||||
for (let j = 0; j < glyf.length; j++) {
|
||||
let g = glyf[j];
|
||||
if (!g.contours) continue;
|
||||
for (let k = 0; k < g.contours.length; k++) {
|
||||
let contour = g.contours[k];
|
||||
for (let z of contour) {
|
||||
if (!isFinite(z.x)) z.x = 0;
|
||||
if (!isFinite(z.y)) z.y = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = autoref;
|
288
font-src/gen/finalize/gc.js
Normal file
288
font-src/gen/finalize/gc.js
Normal file
|
@ -0,0 +1,288 @@
|
|||
"use strict";
|
||||
|
||||
module.exports = function gcFont(gs, excludedChars, restFont, cfg) {
|
||||
markSweepOtl(restFont.GSUB);
|
||||
markSweepOtl(restFont.GPOS);
|
||||
const sink = mark(gs, excludedChars, restFont, cfg);
|
||||
sweep(gs, restFont, sink);
|
||||
};
|
||||
|
||||
function markSweepOtl(table) {
|
||||
if (!table || !table.features || !table.lookups) return;
|
||||
const accessibleLookupsIds = new Set();
|
||||
markLookups(table, accessibleLookupsIds);
|
||||
let lookups1 = {};
|
||||
for (const l in table.lookups) {
|
||||
if (accessibleLookupsIds.has(l)) lookups1[l] = table.lookups[l];
|
||||
}
|
||||
table.lookups = lookups1;
|
||||
|
||||
let features1 = {};
|
||||
for (let f in table.features) {
|
||||
const feature = table.features[f];
|
||||
if (!feature) continue;
|
||||
const featureFiltered = [];
|
||||
for (const l of feature) if (accessibleLookupsIds.has(l)) featureFiltered.push(l);
|
||||
if (!featureFiltered.length) continue;
|
||||
features1[f] = featureFiltered;
|
||||
}
|
||||
table.features = features1;
|
||||
}
|
||||
function markLookups(table, sink) {
|
||||
if (!table || !table.features) return;
|
||||
for (let f in table.features) {
|
||||
const feature = table.features[f];
|
||||
if (!feature) continue;
|
||||
for (const l of feature) sink.add(l);
|
||||
}
|
||||
let loop = 0,
|
||||
lookupSetChanged = false;
|
||||
do {
|
||||
lookupSetChanged = false;
|
||||
let sizeBefore = sink.size;
|
||||
for (const l of Array.from(sink)) {
|
||||
const lookup = table.lookups[l];
|
||||
if (!lookup || !lookup.subtables) continue;
|
||||
if (lookup.type === "gsub_chaining" || lookup.type === "gpos_chaining") {
|
||||
for (let st of lookup.subtables) {
|
||||
if (!st || !st.apply) continue;
|
||||
for (const app of st.apply) sink.add(app.lookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
loop++;
|
||||
lookupSetChanged = sizeBefore !== sink.size;
|
||||
} while (loop < 0xff && lookupSetChanged);
|
||||
}
|
||||
|
||||
function mark(gs, excludedChars, restFont, cfg) {
|
||||
const sink = markInitial(gs, excludedChars);
|
||||
while (markStep(sink, restFont, cfg));
|
||||
return sink;
|
||||
}
|
||||
|
||||
function markInitial(gs, excludedChars) {
|
||||
let sink = new Set();
|
||||
for (const g of gs) {
|
||||
if (g.glyphRank > 0) sink.add(g.name);
|
||||
if (!g || !g.unicode) continue;
|
||||
for (const u of g.unicode) if (!excludedChars.has(u)) sink.add(g.name);
|
||||
}
|
||||
return sink;
|
||||
}
|
||||
|
||||
function markStep(sink, restFont, cfg) {
|
||||
const glyphCount = sink.size;
|
||||
if (restFont.GSUB) {
|
||||
for (const l in restFont.GSUB.lookups) {
|
||||
const lookup = restFont.GSUB.lookups[l];
|
||||
if (!lookup || !lookup.subtables) continue;
|
||||
for (let st of lookup.subtables) {
|
||||
markSubtable(sink, lookup.type, st, cfg);
|
||||
}
|
||||
}
|
||||
}
|
||||
const glyphCount1 = sink.size;
|
||||
return glyphCount1 > glyphCount;
|
||||
}
|
||||
|
||||
function markSubtable(sink, type, st, cfg) {
|
||||
switch (type) {
|
||||
case "gsub_single":
|
||||
for (const k in st) if (sink.has(k) && st[k]) sink.add(st[k]);
|
||||
break;
|
||||
case "gsub_multiple":
|
||||
for (const k in st) if (sink.has(k) && st[k]) for (const g of st[k]) sink.add(g);
|
||||
break;
|
||||
case "gsub_alternate":
|
||||
if (!cfg || !cfg.ignoreAltSub) {
|
||||
for (const k in st) if (sink.has(k) && st[k]) for (const g of st[k]) sink.add(g);
|
||||
}
|
||||
break;
|
||||
case "gsub_ligature":
|
||||
for (const sub of st.substitutions) {
|
||||
let check = true;
|
||||
for (const g of sub.from) if (!sink.has(g)) check = false;
|
||||
if (check && sub.to) sink.add(sub.to);
|
||||
}
|
||||
break;
|
||||
case "gsub_chaining":
|
||||
break;
|
||||
case "gsub_reverse":
|
||||
if (st.match && st.to) {
|
||||
const matchCoverage = st.match[st.inputIndex];
|
||||
for (let j = 0; j < matchCoverage.length; j++) {
|
||||
if (sink.has(matchCoverage[j]) && st.to[j]) sink.add(st.to[j]);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function sweep(gs, restFont, sink) {
|
||||
filterInPlace(gs, g => sink.has(g.name));
|
||||
sweepOtl(restFont.GSUB, sink);
|
||||
sweepOtl(restFont.GPOS, sink);
|
||||
}
|
||||
|
||||
function sweepOtl(table, sink) {
|
||||
if (!table || !table.lookups) return;
|
||||
for (const lid in table.lookups) {
|
||||
const lookup = table.lookups[lid];
|
||||
if (!lookup.subtables) continue;
|
||||
const newSubtables = [];
|
||||
for (const st of lookup.subtables) {
|
||||
const keep = sweepSubtable(st, lookup.type, sink);
|
||||
if (keep) newSubtables.push(st);
|
||||
}
|
||||
lookup.subtables = newSubtables;
|
||||
}
|
||||
}
|
||||
|
||||
function sweepSubtable(st, type, gs) {
|
||||
switch (type) {
|
||||
case "gsub_single":
|
||||
return sweep_GsubSingle(st, gs);
|
||||
case "gsub_multiple":
|
||||
case "gsub_alternate":
|
||||
return sweep_GsubMultiple(st, gs);
|
||||
case "gsub_ligature":
|
||||
return sweep_GsubLigature(st, gs);
|
||||
case "gsub_chaining":
|
||||
return sweep_GsubChaining(st, gs);
|
||||
case "gsub_reverse":
|
||||
return sweep_gsubReverse(st, gs);
|
||||
case "gpos_mark_to_base":
|
||||
case "gpos_mark_to_mark":
|
||||
return sweep_gposMark(st, gs);
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function sweep_GsubSingle(st, gs) {
|
||||
let nonEmpty = false;
|
||||
let from = Object.keys(st);
|
||||
for (const gidFrom of from) {
|
||||
if (!gs.has(gidFrom) || !gs.has(st[gidFrom])) {
|
||||
delete st[gidFrom];
|
||||
} else {
|
||||
nonEmpty = true;
|
||||
}
|
||||
}
|
||||
return nonEmpty;
|
||||
}
|
||||
|
||||
function sweep_GsubMultiple(st, gs) {
|
||||
let nonEmpty = false;
|
||||
let from = Object.keys(st);
|
||||
for (const gidFrom of from) {
|
||||
let include = gs.has(gidFrom);
|
||||
if (st[gidFrom]) {
|
||||
for (const gidTo of st[gidFrom]) {
|
||||
include = include && gs.has(gidTo);
|
||||
}
|
||||
} else {
|
||||
include = false;
|
||||
}
|
||||
if (!include) {
|
||||
delete st[gidFrom];
|
||||
} else {
|
||||
nonEmpty = true;
|
||||
}
|
||||
}
|
||||
return nonEmpty;
|
||||
}
|
||||
|
||||
function sweep_GsubLigature(st, gs) {
|
||||
if (!st.substitutions) return false;
|
||||
let newSubst = [];
|
||||
for (const rule of st.substitutions) {
|
||||
let include = true;
|
||||
if (!gs.has(rule.to)) include = false;
|
||||
for (const from of rule.from) if (!gs.has(from)) include = false;
|
||||
if (include) newSubst.push(rule);
|
||||
}
|
||||
st.substitutions = newSubst;
|
||||
return true;
|
||||
}
|
||||
|
||||
function sweep_GsubChaining(st, gs) {
|
||||
const newMatch = [];
|
||||
for (let j = 0; j < st.match.length; j++) {
|
||||
newMatch[j] = [];
|
||||
for (let k = 0; k < st.match[j].length; k++) {
|
||||
const gidFrom = st.match[j][k];
|
||||
if (gs.has(gidFrom)) {
|
||||
newMatch[j].push(gidFrom);
|
||||
}
|
||||
}
|
||||
if (!newMatch[j].length) return false;
|
||||
}
|
||||
st.match = newMatch;
|
||||
return true;
|
||||
}
|
||||
|
||||
function sweep_gsubReverse(st, gs) {
|
||||
const newMatch = [],
|
||||
newTo = [];
|
||||
for (let j = 0; j < st.match.length; j++) {
|
||||
newMatch[j] = [];
|
||||
for (let k = 0; k < st.match[j].length; k++) {
|
||||
const gidFrom = st.match[j][k];
|
||||
let include = gs.has(gidFrom);
|
||||
if (j === st.inputIndex) {
|
||||
include = include && gs.has(st.to[k]);
|
||||
if (include) {
|
||||
newMatch[j].push(gidFrom);
|
||||
newTo.push(st.to[k]);
|
||||
}
|
||||
} else {
|
||||
if (include) newMatch[j].push(gidFrom);
|
||||
}
|
||||
}
|
||||
if (!newMatch[j].length) return false;
|
||||
}
|
||||
st.match = newMatch;
|
||||
st.to = newTo;
|
||||
return true;
|
||||
}
|
||||
|
||||
function sweep_gposMark(st, gs) {
|
||||
let marks = st.marks || {},
|
||||
newMarks = {},
|
||||
hasMarks = false;
|
||||
let bases = st.bases || {},
|
||||
newBases = {},
|
||||
hasBases = true;
|
||||
|
||||
for (const gid in marks) {
|
||||
if (gs.has(gid) && marks[gid]) {
|
||||
newMarks[gid] = marks[gid];
|
||||
hasMarks = true;
|
||||
}
|
||||
}
|
||||
for (const gid in bases) {
|
||||
if (gs.has(gid) && bases[gid]) {
|
||||
newBases[gid] = bases[gid];
|
||||
hasBases = true;
|
||||
}
|
||||
}
|
||||
st.marks = newMarks;
|
||||
st.bases = newBases;
|
||||
return hasMarks && hasBases;
|
||||
}
|
||||
|
||||
function filterInPlace(a, condition) {
|
||||
let i = 0,
|
||||
j = 0;
|
||||
|
||||
while (i < a.length) {
|
||||
const val = a[i];
|
||||
if (condition(val, i, a)) a[j++] = val;
|
||||
i++;
|
||||
}
|
||||
|
||||
a.length = j;
|
||||
return a;
|
||||
}
|
171
font-src/gen/finalize/index.js
Normal file
171
font-src/gen/finalize/index.js
Normal file
|
@ -0,0 +1,171 @@
|
|||
"use strict";
|
||||
|
||||
const autoRef = require("./autoref");
|
||||
const TypoGeom = require("typo-geom");
|
||||
const Point = require("../../support/point");
|
||||
|
||||
const CurveUtil = require("../../support/curve-util");
|
||||
const { AnyCv } = require("../../support/gr");
|
||||
const gcFont = require("./gc");
|
||||
|
||||
module.exports = function finalizeFont(para, glyphList, excludedCodePoints, font) {
|
||||
forceMonospaceIfNeeded(para, glyphList);
|
||||
gcFont(glyphList, excludedCodePoints, font, {});
|
||||
extractGlyfCmap(regulateGlyphList(para, glyphList), font);
|
||||
};
|
||||
|
||||
function forceMonospaceIfNeeded(para, glyphList) {
|
||||
if (!para.forceMonospace || para.spacing > 0) return;
|
||||
const unitWidth = Math.round(para.width);
|
||||
let i = 0,
|
||||
j = 0;
|
||||
for (; i < glyphList.length; i++) {
|
||||
const g = glyphList[i];
|
||||
g.advanceWidth = Math.round(g.advanceWidth || 0);
|
||||
if (g.advanceWidth === 0 || g.advanceWidth === unitWidth) glyphList[j++] = g;
|
||||
}
|
||||
glyphList.length = j;
|
||||
}
|
||||
|
||||
function extractGlyfCmap(glyphList, font) {
|
||||
const glyf = {};
|
||||
const cmap = {};
|
||||
for (let g of glyphList) {
|
||||
glyf[g.name] = g;
|
||||
if (!g.unicode) continue;
|
||||
|
||||
for (let u of g.unicode) {
|
||||
if (isFinite(u - 0)) cmap[u] = g.name;
|
||||
}
|
||||
}
|
||||
font.glyf = glyf;
|
||||
font.cmap = cmap;
|
||||
}
|
||||
|
||||
function regulateGlyphList(para, gs) {
|
||||
const skew = Math.tan(((para.slantAngle || 0) / 180) * Math.PI);
|
||||
|
||||
const excludeUnicode = new Set();
|
||||
excludeUnicode.add(0x80);
|
||||
for (let c = 0x2500; c <= 0x259f; c++) excludeUnicode.add(c);
|
||||
|
||||
// autoref
|
||||
for (let j = 0; j < gs.length; j++) {
|
||||
gs[j].glyphOrder = j;
|
||||
if (AnyCv.query(gs[j]).length) gs[j].autoRefPriority = -1;
|
||||
if (gs[j].unicode) {
|
||||
for (const u of gs[j].unicode) {
|
||||
if (excludeUnicode.has(u)) gs[j].avoidBeingComposite = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
gs.sort(byGlyphPriority);
|
||||
autoRef(gs, excludeUnicode);
|
||||
|
||||
// regulate
|
||||
for (let g of gs) regulateGlyph(g, skew);
|
||||
|
||||
// reorder
|
||||
return gs.sort(byRank);
|
||||
}
|
||||
|
||||
function byGlyphPriority(a, b) {
|
||||
const pri1 = a.autoRefPriority || 0;
|
||||
const pri2 = b.autoRefPriority || 0;
|
||||
if (pri1 > pri2) return -1;
|
||||
if (pri1 < pri2) return 1;
|
||||
if (a.contours && b.contours && a.contours.length < b.contours.length) return 1;
|
||||
if (a.contours && b.contours && a.contours.length > b.contours.length) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
function byRank(a, b) {
|
||||
return (b.glyphRank || 0) - (a.glyphRank || 0) || (a.glyphOrder || 0) - (b.glyphOrder || 0);
|
||||
}
|
||||
|
||||
function regulateGlyph(g, skew) {
|
||||
if (!g.contours || !g.contours.length) return;
|
||||
for (const contour of g.contours) for (const z of contour) z.x -= z.y * skew;
|
||||
g.contours = simplifyContours(g.contours);
|
||||
for (const contour of g.contours) for (const z of contour) z.x += z.y * skew;
|
||||
}
|
||||
|
||||
function simplifyContours(source) {
|
||||
const sink = new FairizedShapeSink();
|
||||
|
||||
TypoGeom.transferGenericShape(
|
||||
TypoGeom.Fairize.fairizeBezierShape(
|
||||
TypoGeom.Boolean.removeOverlap(
|
||||
CurveUtil.convertShapeToArcs(source),
|
||||
TypoGeom.Boolean.PolyFillType.pftNonZero,
|
||||
1 << 17
|
||||
)
|
||||
),
|
||||
sink,
|
||||
FINAL_SIMPLIFY_TOLERANCE
|
||||
);
|
||||
|
||||
return sink.contours;
|
||||
}
|
||||
|
||||
const FINAL_SIMPLIFY_RESOLUTION = 16;
|
||||
const FINAL_SIMPLIFY_TOLERANCE = 2 / FINAL_SIMPLIFY_RESOLUTION;
|
||||
|
||||
class FairizedShapeSink {
|
||||
constructor() {
|
||||
this.contours = [];
|
||||
this.lastContour = [];
|
||||
}
|
||||
beginShape() {}
|
||||
endShape() {
|
||||
if (this.lastContour.length > 2) {
|
||||
const zFirst = this.lastContour[0],
|
||||
zLast = this.lastContour[this.lastContour.length - 1];
|
||||
if (zFirst.on && zLast.on && zFirst.x === zLast.x && zFirst.y === zLast.y) {
|
||||
this.lastContour.pop();
|
||||
}
|
||||
this.contours.push(this.lastContour);
|
||||
}
|
||||
this.lastContour = [];
|
||||
}
|
||||
moveTo(x, y) {
|
||||
this.endShape();
|
||||
this.lineTo(x, y);
|
||||
}
|
||||
lineTo(x, y) {
|
||||
const z = Point.cornerFromXY(x, y).round(FINAL_SIMPLIFY_RESOLUTION);
|
||||
if (this.lastContour.length >= 2) {
|
||||
const a = this.lastContour[this.lastContour.length - 2],
|
||||
b = this.lastContour[this.lastContour.length - 1];
|
||||
if (isLineExtend(a, b, z)) {
|
||||
this.lastContour.pop();
|
||||
this.lastContour.push(z);
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.lastContour.push(z);
|
||||
}
|
||||
arcTo(arc, x, y) {
|
||||
const offPoints = TypoGeom.Quadify.auto(arc, FINAL_SIMPLIFY_TOLERANCE);
|
||||
if (offPoints) {
|
||||
for (const z of offPoints)
|
||||
this.lastContour.push(Point.offFrom(z).round(FINAL_SIMPLIFY_RESOLUTION));
|
||||
}
|
||||
this.lineTo(x, y);
|
||||
}
|
||||
}
|
||||
function isLineExtend(a, b, c) {
|
||||
return (
|
||||
a.on &&
|
||||
b.on &&
|
||||
c.on &&
|
||||
((aligned(a.x, b.x, c.x) && between(a.y, b.y, c.y)) ||
|
||||
(aligned(a.y, b.y, c.y) && between(a.x, b.x, c.x)))
|
||||
);
|
||||
}
|
||||
function aligned(a, b, c) {
|
||||
return a === b && b === c;
|
||||
}
|
||||
function between(a, b, c) {
|
||||
return (a <= b && b <= c) || (a >= b && b >= c);
|
||||
}
|
29
font-src/gen/kits/boole-kit.ptl
Normal file
29
font-src/gen/kits/boole-kit.ptl
Normal file
|
@ -0,0 +1,29 @@
|
|||
import 'typo-geom' as TypoGeom
|
||||
import '../../support/curve-util' as CurveUtil
|
||||
|
||||
export : define [SetupBuilders args] : begin
|
||||
define [object Glyph globalTransform] args
|
||||
define [Boole operator] : lambda [] : let [k : {}.slice.call arguments 0] : lambda [dontinc] : begin
|
||||
local g : new Glyph
|
||||
set g.gizmo : this.gizmo || globalTransform
|
||||
if (k.length == 0) : return g
|
||||
|
||||
g.include k.0
|
||||
set g.contours : g.contours.map CurveUtil.convertContourToCubic
|
||||
foreach [item : items-of : k.slice 1] : begin
|
||||
local g1 : new Glyph
|
||||
set g1.gizmo : this.gizmo || globalTransform
|
||||
g1.include item
|
||||
set g1.contours : g1.contours.map CurveUtil.convertContourToCubic
|
||||
local c1 : TypoGeom.Boolean.combine operator [CurveUtil.convertShapeToArcs g.contours] [CurveUtil.convertShapeToArcs g1.contours] TypoGeom.Boolean.PolyFillType.pftNonZero TypoGeom.Boolean.PolyFillType.pftNonZero 16384
|
||||
local ctx : new CurveUtil.ArcFlattener
|
||||
TypoGeom.transferBezArcShape c1 ctx
|
||||
set g.contours ctx.contours
|
||||
this.includeGlyph g
|
||||
return g
|
||||
|
||||
define union : Boole TypoGeom.Boolean.ClipType.ctUnion
|
||||
define intersection : Boole TypoGeom.Boolean.ClipType.ctIntersection
|
||||
define difference : Boole TypoGeom.Boolean.ClipType.ctDifference
|
||||
|
||||
return [object union intersection difference]
|
199
font-src/gen/kits/spiro-kit.ptl
Normal file
199
font-src/gen/kits/spiro-kit.ptl
Normal file
|
@ -0,0 +1,199 @@
|
|||
import 'spiro' as SpiroJs
|
||||
import '../../support/spiro-expand' as SpiroExpansionContext
|
||||
import '../../support/curve-util' as CurveUtil
|
||||
import '../../support/transform' as Transform
|
||||
import '../../support/utils' as [object fallback mix bez2 bez3]
|
||||
|
||||
export : define [SetupBuilders args] : begin
|
||||
define [object para Glyph Contrast globalTransform Stroke Superness] args
|
||||
|
||||
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 [corner x y f] {.x x .y y .type 'corner' .af f}
|
||||
define [flat x y f] {.x x .y y .type 'left' .af f}
|
||||
define [curl x y f] {.x x .y y .type 'right' .af f}
|
||||
define [close f] {.type 'close' .af f}
|
||||
define [end f] {.type 'end' .af f}
|
||||
|
||||
define straight {.l flat .r curl}
|
||||
|
||||
#directional bi-knots
|
||||
let
|
||||
directions {{.name 'up' .x 0 .y 1}, {.name 'down' .x 0 .y (-1)}, {.name 'left' .x (-1) .y 0}, {.name 'right' .x 1 .y 0}}
|
||||
adhensions {{.name 'start' .l 0 .r 0.01}, {.name 'mid', .l (-0.005) .r 0.005}, {.name 'end', .l (-0.01) .r 0}}
|
||||
knottypes {g4, g2, corner, straight}
|
||||
foreach [direction : items-of directions] : let [d direction] : begin
|
||||
foreach [knottype : items-of knottypes] : let [kt knottype] : begin
|
||||
set kt.(d.name) {.}
|
||||
foreach [adh : items-of adhensions] : let [a adh] : begin
|
||||
set kt.(d.name).(a.name) : lambda [x y f] : list
|
||||
[fallback kt.l kt] (x + d.x * a.l) (y + d.y * a.l) f
|
||||
[fallback kt.r kt] (x + d.x * a.r) (y + d.y * a.r) f
|
||||
|
||||
# Aux functions
|
||||
define [widths l r] : lambda [] : if this.setWidth : this.setWidth l r
|
||||
define [widths.lhs w] : widths [fallback w Stroke] 0
|
||||
define [widths.rhs w] : widths 0 [fallback w Stroke]
|
||||
define [widths.center w] : widths ([fallback w Stroke] / 2) ([fallback w Stroke] / 2)
|
||||
|
||||
# Gizmo handler
|
||||
define [disable-gizmo] : lambda [] : set this.gizmo [Transform.Id]
|
||||
define [disable-contrast] : lambda [] : set this.contrast 1
|
||||
|
||||
define [heading d] : lambda [] : if (this.headsTo) : this.headsTo d
|
||||
define [widths.heading l r d] : lambda [] : begin
|
||||
if this.setWidth : this.setWidth l r
|
||||
if this.headsTo : this.headsTo d
|
||||
define [widths.lhs.heading w d] : lambda [] : begin
|
||||
if this.setWidth : this.setWidth [fallback w Stroke] 0
|
||||
if this.headsTo : this.headsTo d
|
||||
define [widths.rhs.heading w d] : lambda [] : begin
|
||||
if this.setWidth : this.setWidth 0 [fallback w Stroke]
|
||||
if this.headsTo : this.headsTo d
|
||||
define [widths.center.heading w d] : lambda [] : begin
|
||||
if this.setWidth : this.setWidth ([fallback w Stroke] / 2) ([fallback w Stroke] / 2)
|
||||
if this.headsTo : this.headsTo d
|
||||
define [unimportant] : begin
|
||||
if (this.points && this.points.length && this.points.(this.points.length - 1)) : this.points.(this.points.length - 1).subdivided = true
|
||||
if (this.controlKnots && this.controlKnots.length && this.controlKnots.(this.controlKnots.length - 1)) : this.controlKnots.(this.controlKnots.length - 1).unimportant = true
|
||||
define [important] nothing
|
||||
|
||||
# Interpolation pesudoknots
|
||||
define [afInterpolate before after args] : g4
|
||||
mix before.x after.x args.rx
|
||||
mix before.y after.y args.ry
|
||||
fallback args.raf unimportant
|
||||
define [afInterpolateG2 before after args] : g2
|
||||
mix before.x after.x args.rx
|
||||
mix before.y after.y args.ry
|
||||
fallback args.raf unimportant
|
||||
define [afInterpolateThem before after args] : begin
|
||||
local innerKnots {}
|
||||
foreach {rx ry rt} [items-of args.rs] : innerKnots.push : [fallback args.ty g2]
|
||||
mix before.x after.x rx
|
||||
mix before.y after.y ry
|
||||
piecewise
|
||||
(args.raf && args.raf.blend && (rt != nothing)) [args.raf.blend rt]
|
||||
args.raf args.raf
|
||||
true unimportant
|
||||
return innerKnots
|
||||
|
||||
define [alsoThru rx ry raf] {.type 'interpolate' .rx rx .ry ry .raf raf .af afInterpolate}
|
||||
set alsoThru.g2 : lambda [rx ry raf] {.type 'interpolate' .rx rx .ry ry .raf raf .af afInterpolateG2}
|
||||
define [alsoThruThem rs raf ty] {.type 'interpolate' .rs rs .raf raf .ty ty .af afInterpolateThem}
|
||||
define [bezControlsImpl x1 y1 x2 y2 samples raf ty] : begin
|
||||
local rs {}
|
||||
foreach j [range 1 samples] : rs.push : list
|
||||
bez3 0 x1 x2 1 (j / samples)
|
||||
bez3 0 y1 y2 1 (j / samples)
|
||||
j / samples
|
||||
alsoThruThem rs raf
|
||||
define [bezcontrols x1 y1 x2 y2 _samples raf]
|
||||
bezControlsImpl x1 y1 x2 y2 [fallback _samples 3] raf
|
||||
define [quadcontrols x1 y1 _samples raf]
|
||||
bezControlsImpl (x1 * 2 / 3) (y1 * 2 / 3) [mix 1 x1 (2 / 3)] [mix 1 y1 (2 / 3)] [fallback _samples 3] raf
|
||||
set quadcontrols.g4 : lambda [x1 y1 _samples raf]
|
||||
bezControlsImpl (x1 * 2 / 3) (y1 * 2 / 3) [mix 1 x1 (2 / 3)] [mix 1 y1 (2 / 3)] [fallback _samples 3] raf g4
|
||||
|
||||
define DEFAULT_STEPS 6
|
||||
define {jhv, jvh} : let [cache {}] : begin
|
||||
local [build samples _superness] : begin
|
||||
local superness : fallback _superness Superness
|
||||
local hv {}
|
||||
local vh {}
|
||||
foreach [j : range 1 samples] : begin
|
||||
local theta : j / samples * Math.PI / 2
|
||||
local c : Math.pow [Math.cos theta] (2 / superness)
|
||||
local s : Math.pow [Math.sin theta] (2 / superness)
|
||||
hv.push { s (1 - c) }
|
||||
vh.push { (1 - c) s }
|
||||
return {.hv hv .vh vh}
|
||||
local [hv samples _superness] : begin
|
||||
if (_superness) : return [build samples _superness].hv
|
||||
if (!cache.(samples)) : set cache.(samples) : build samples _superness
|
||||
return cache.(samples).hv
|
||||
local [vh samples _superness] : begin
|
||||
if (_superness) : return [build samples _superness].vh
|
||||
if (!cache.(samples)) : set cache.(samples) : build samples _superness
|
||||
return cache.(samples).vh
|
||||
list hv vh
|
||||
define [archv samples superness] : alsoThruThem [jhv [fallback samples DEFAULT_STEPS] superness]
|
||||
define [arcvh samples superness] : alsoThruThem [jvh [fallback samples DEFAULT_STEPS] superness]
|
||||
|
||||
define [complexThru] : begin
|
||||
local a : {}.slice.call arguments
|
||||
return {.type 'interpolate' .af [lambda [before after args] : begin \\
|
||||
local ks {}
|
||||
foreach knot [items-of a] : ks.push [knot.af.call this before after knot]
|
||||
return ks
|
||||
]}
|
||||
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 [prepareSpiroKnots _knots s] : begin
|
||||
local closed false
|
||||
local lastafs {}
|
||||
local knots _knots
|
||||
while (knots.0 && knots.0 <@ Function) : begin
|
||||
knots.0.call s
|
||||
set knots : knots.slice 1
|
||||
while (knots.(knots.length - 1) && (knots.(knots.length - 1).type === 'close' || knots.(knots.length - 1).type === 'end')) : begin
|
||||
set closed : knots.(knots.length - 1).type === 'close'
|
||||
lastafs.push 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
|
||||
return {.knots [flatten knots] .closed closed .lastafs lastafs}
|
||||
|
||||
define QUAD false
|
||||
define PRECISION 0.5
|
||||
define [dispiro] : let [args : {}.slice.call arguments 0] : lambda [dontinc] : begin
|
||||
local s : new SpiroExpansionContext
|
||||
set s.gizmo : this.gizmo || globalTransform
|
||||
local {.knots knots .closed closed .lastafs lastafs} : prepareSpiroKnots [{}.slice.call args 0] s
|
||||
foreach knot [items-of knots] : let [ty knot.type] [af knot.af] : begin
|
||||
set knot.af : lambda [] : begin
|
||||
this.setType ty
|
||||
if af : af.apply this args
|
||||
SpiroJs.spiroToArcsOnContext knots closed s
|
||||
foreach af [items-of lastafs] : if af : af.call s
|
||||
|
||||
local {.lhs lhs .rhs rhs} : s.expand [fallback s.contrast Contrast]
|
||||
if closed : then
|
||||
local g : new CurveUtil.ArcFlattener
|
||||
SpiroJs.spiroToBezierOnContext [lhs.slice 0 (-1)] true g QUAD PRECISION
|
||||
local lhsContour g.contours.0
|
||||
set g.contours {}
|
||||
SpiroJs.spiroToBezierOnContext [rhs.reverse :.slice 0 (-1)] true g QUAD PRECISION
|
||||
local rhsContour g.contours.0
|
||||
set g.contours {[lhsContour.concat rhsContour]}
|
||||
: else : begin
|
||||
local g : new CurveUtil.ArcFlattener
|
||||
lhs.0.type = rhs.0.type = lhs.(lhs.length - 1).type = rhs.(rhs.length - 1).type = 'corner'
|
||||
SpiroJs.spiroToBezierOnContext [lhs.concat : rhs.reverse] true g QUAD PRECISION
|
||||
|
||||
set g.knots knots
|
||||
set g.lhsKnots lhs
|
||||
set g.rhsKnots rhs
|
||||
this.includeGeometry g 0 0
|
||||
return g
|
||||
|
||||
define [spiro-outline] : let [k : {}.slice.call arguments 0] : lambda [dontinc] : begin
|
||||
local g : new CurveUtil.ArcFlattener (this.gizmo || globalTransform)
|
||||
local {.knots knots .closed closed .lastafs lastafs} : prepareSpiroKnots k g
|
||||
SpiroJs.spiroToBezierOnContext knots closed g QUAD PRECISION
|
||||
foreach af [items-of lastafs] : if af : af.call g
|
||||
this.includeGeometry g 0 0
|
||||
return g
|
||||
|
||||
return [object
|
||||
g4 g2 corner flat curl close end straight
|
||||
widths disable-gizmo disable-contrast heading unimportant important
|
||||
alsoThru alsoThruThem bezcontrols quadcontrols archv arcvh complexThru
|
||||
dispiro spiro-outline]
|
Loading…
Add table
Add a link
Reference in a new issue