Refactor geometry system

This commit is contained in:
be5invis 2021-02-01 19:25:55 -08:00
parent 855a812758
commit 3fd1ebfec3
11 changed files with 208 additions and 159 deletions

View file

@ -16,14 +16,7 @@ function finalizeGlyphs(para, glyphStore) {
function suppressNaN(glyphStore) { function suppressNaN(glyphStore) {
for (const g of glyphStore.glyphs()) { for (const g of glyphStore.glyphs()) {
if (!g.contours) continue; if (g.geometry) g.geometry.suppressNaN();
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;
}
}
} }
} }
@ -31,32 +24,38 @@ function suppressNaN(glyphStore) {
function regulateGlyphStore(skew, glyphStore) { function regulateGlyphStore(skew, glyphStore) {
for (const g of glyphStore.glyphs()) { for (const g of glyphStore.glyphs()) {
if (!g.semanticInclusions || !g.contours) continue; if (g.geometry.isEmpty()) continue;
if (g.isPureComposite()) regulateCompositeGlyph(glyphStore, g); if (!regulateCompositeGlyph(glyphStore, g)) {
const cs = g.geometry.asContours();
g.clearGeometry();
for (const c of cs) g.geometry.addContour(c);
}
}
for (const g of glyphStore.glyphs()) {
if (!g.geometry.asReferences()) regulateSimpleGlyph(g, skew);
} }
for (const g of glyphStore.glyphs()) regulateSimpleGlyph(g, skew);
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////
function regulateCompositeGlyph(glyphStore, g) { function regulateCompositeGlyph(glyphStore, g) {
const references = []; const refs = g.geometry.asReferences();
for (const sr of g.semanticInclusions) { if (!refs) return false;
for (const sr of refs) {
const gn = glyphStore.queryNameOf(sr.glyph); const gn = glyphStore.queryNameOf(sr.glyph);
if (!gn || sr.glyph.autoRefPriority < 0) return; if (!gn || sr.glyph.autoRefPriority < 0) return false;
references.push({ glyph: gn, x: sr.x, y: sr.y });
} }
return true;
g.semanticInclusions = [];
g.contours = [];
g.references = references;
} }
function regulateSimpleGlyph(g, skew) { function regulateSimpleGlyph(g, skew) {
if (!g.contours || !g.contours.length) return; let cs = g.geometry.asContours();
for (const contour of g.contours) for (const z of contour) z.x -= z.y * skew; for (const contour of cs) for (const z of contour) z.x -= z.y * skew;
g.contours = simplifyContours(g.contours); cs = simplifyContours(cs);
for (const contour of g.contours) for (const z of contour) z.x += z.y * skew; for (const contour of cs) for (const z of contour) z.x += z.y * skew;
g.clearGeometry();
for (const c of cs) g.geometry.addContour(c);
} }
/////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////

View file

@ -1,48 +1,58 @@
const { Ot } = require("ot-builder"); const { Ot } = require("ot-builder");
const Point = require("../../support/point"); const Point = require("../../support/point");
class NamedGlyphStore { class MappedGlyphStore {
constructor() { constructor() {
this.m_nameMapping = new Map();
this.m_mapping = new Map(); this.m_mapping = new Map();
} }
declare(name) { declare(name, source) {
const g = new Ot.Glyph(); const g = new Ot.Glyph();
g.name = name; g.name = name;
this.m_mapping.set(name, g); this.m_nameMapping.set(name, g);
this.m_mapping.set(source, g);
} }
query(name) { queryBySourceGlyph(source) {
return this.m_mapping.get(name); return this.m_mapping.get(source);
}
queryByName(name) {
return this.m_nameMapping.get(name);
} }
decideOrder() { decideOrder() {
const gs = Ot.ListGlyphStoreFactory.createStoreFromList([...this.m_mapping.values()]); const gs = Ot.ListGlyphStoreFactory.createStoreFromList([...this.m_mapping.values()]);
return gs.decideOrder(); return gs.decideOrder();
} }
fill(name, data) { fill(name, source) {
const g = this.query(name); const g = this.queryBySourceGlyph(source);
if (!g) return; if (!g) throw new Error("Unreachable");
g.horizontal = { start: 0, end: data.advanceWidth }; // Fill metrics
if (data.references && data.references.length) { g.horizontal = { start: 0, end: source.advanceWidth };
this.fillReferences(g, data);
} else if (data.contours && data.contours.length) { // Fill Geometry
this.fillContours(g, data); if (source.geometry.isEmpty()) return;
const rs = source.geometry.asReferences();
if (rs) {
this.fillReferences(g, rs);
} else {
this.fillContours(g, source.geometry.asContours());
} }
} }
fillReferences(g, data) { fillReferences(g, rs) {
const gl = new Ot.Glyph.GeometryList(); const gl = new Ot.Glyph.GeometryList();
for (const ref of data.references) { for (const ref of rs) {
const target = this.query(ref.glyph); const target = this.queryBySourceGlyph(ref.glyph);
if (!target) continue; if (!target) throw new Error("Unreachable");
const tfm = Ot.Glyph.Transform2X3.Translate(ref.x, ref.y); const tfm = Ot.Glyph.Transform2X3.Translate(ref.x, ref.y);
gl.items.push(new Ot.Glyph.TtReference(target, tfm)); gl.items.push(new Ot.Glyph.TtReference(target, tfm));
} }
g.geometry = gl; g.geometry = gl;
} }
fillContours(g, data) { fillContours(g, contours) {
const cs = new Ot.Glyph.ContourSet(); const cs = new Ot.Glyph.ContourSet();
for (const c of data.contours) { for (const c of contours) {
const c1 = []; const c1 = [];
for (const z of c) { for (const z of c) {
c1.push( c1.push(
@ -67,21 +77,21 @@ function convertGlyphs(gsOrig) {
.map(([j, gn, g]) => [j, gn, queryOrderingUnicode(gsOrig, g), g]) .map(([j, gn, g]) => [j, gn, queryOrderingUnicode(gsOrig, g), g])
.sort(byRank); .sort(byRank);
const gs = new NamedGlyphStore(); const gs = new MappedGlyphStore();
const cmap = new Ot.Cmap.Table(); const cmap = new Ot.Cmap.Table();
for (const [origIndex, name, uOrd, g] of sortedEntries) { for (const [origIndex, name, uOrd, gSrc] of sortedEntries) {
gs.declare(name); gs.declare(name, gSrc);
const us = gsOrig.queryUnicodeOf(g); const us = gsOrig.queryUnicodeOf(gSrc);
if (us) { if (us) {
for (const u of us) { for (const u of us) {
if (isFinite(u - 0) && u) { if (isFinite(u - 0) && u) {
cmap.unicode.set(u, gs.query(name)); cmap.unicode.set(u, gs.queryBySourceGlyph(gSrc));
} }
} }
} }
} }
for (const [origIndex, name, uOrd, g] of sortedEntries) gs.fill(name, g); for (const [origIndex, name, uOrd, gSrc] of sortedEntries) gs.fill(name, gSrc);
return { glyphs: gs, cmap }; return { glyphs: gs, cmap };
} }

View file

@ -35,8 +35,8 @@ const GsubSingleHandler = {
fill(dst, src, store) { fill(dst, src, store) {
const st = src.substitutions; const st = src.substitutions;
for (const k in st) { for (const k in st) {
const from = store.glyphs.query(k); const from = store.glyphs.queryByName(k);
const to = store.glyphs.query(st[k]); const to = store.glyphs.queryByName(st[k]);
if (from && to) dst.mapping.set(from, to); if (from && to) dst.mapping.set(from, to);
} }
} }
@ -48,7 +48,7 @@ const GsubMultipleHandler = {
fill(dst, src, store) { fill(dst, src, store) {
const st = src.substitutions; const st = src.substitutions;
for (const k in st) { for (const k in st) {
const from = store.glyphs.query(k); const from = store.glyphs.queryByName(k);
const to = mapGlyphListAll(st[k], store); const to = mapGlyphListAll(st[k], store);
if (!from || !to) continue; if (!from || !to) continue;
dst.mapping.set(from, to); dst.mapping.set(from, to);
@ -68,7 +68,7 @@ const GsubLigatureHandler = {
fill(dst, src, store) { fill(dst, src, store) {
const st = src.substitutions; const st = src.substitutions;
for (const { from: _from, to: _to } of st) { for (const { from: _from, to: _to } of st) {
const to = store.glyphs.query(_to); const to = store.glyphs.queryByName(_to);
const from = mapGlyphListAll(_from, store); const from = mapGlyphListAll(_from, store);
if (!from || !to) continue; if (!from || !to) continue;
dst.mapping.push({ from, to }); dst.mapping.push({ from, to });
@ -114,7 +114,7 @@ const GsubReverseHandler = {
{ {
const m1 = new Set(); const m1 = new Set();
for (let k = 0; k < st.match[j].length; k++) { for (let k = 0; k < st.match[j].length; k++) {
const gFrom = store.glyphs.query(st.match[j][k]); const gFrom = store.glyphs.queryByName(st.match[j][k]);
if (gFrom) m1.add(gFrom); if (gFrom) m1.add(gFrom);
} }
if (!m1.size) continue out; if (!m1.size) continue out;
@ -123,8 +123,8 @@ const GsubReverseHandler = {
if (j === doSubAt) { if (j === doSubAt) {
for (let k = 0; k < st.match[j].length; k++) { for (let k = 0; k < st.match[j].length; k++) {
const gFrom = store.glyphs.query(st.match[j][k]); const gFrom = store.glyphs.queryByName(st.match[j][k]);
const gTo = store.glyphs.query(st.to[k]); const gTo = store.glyphs.queryByName(st.to[k]);
if (!gFrom) continue; if (!gFrom) continue;
if (gTo) { if (gTo) {
replacement.set(gFrom, gTo); replacement.set(gFrom, gTo);
@ -142,7 +142,7 @@ const GsubReverseHandler = {
function mapGlyphListAll(gl, store) { function mapGlyphListAll(gl, store) {
const out = []; const out = [];
for (const item of gl) { for (const item of gl) {
const fg = store.glyphs.query(item); const fg = store.glyphs.queryByName(item);
if (!fg) return null; if (!fg) return null;
out.push(fg); out.push(fg);
} }
@ -151,7 +151,7 @@ function mapGlyphListAll(gl, store) {
function mapGlyphListSome(gl, store) { function mapGlyphListSome(gl, store) {
const out = []; const out = [];
for (const item of gl) { for (const item of gl) {
const fg = store.glyphs.query(item); const fg = store.glyphs.queryByName(item);
if (!fg) continue; if (!fg) continue;
out.push(fg); out.push(fg);
} }
@ -204,7 +204,7 @@ function convertMarkRecords(marks, mm, store) {
const out = new Map(); const out = new Map();
for (const gn in marks) { for (const gn in marks) {
const mark = marks[gn]; const mark = marks[gn];
const g = store.glyphs.query(gn); const g = store.glyphs.queryByName(gn);
if (!g) continue; if (!g) continue;
let markAnchors = []; let markAnchors = [];
markAnchors[mm.get(mark.class)] = { x: mark.x, y: mark.y }; markAnchors[mm.get(mark.class)] = { x: mark.x, y: mark.y };
@ -216,7 +216,7 @@ function convertBaseRecords(bases, mm, store) {
const out = new Map(); const out = new Map();
for (const gn in bases) { for (const gn in bases) {
const baseObj = bases[gn]; const baseObj = bases[gn];
const g = store.glyphs.query(gn); const g = store.glyphs.queryByName(gn);
if (!g) continue; if (!g) continue;
const baseArray = []; const baseArray = [];
for (const bkStr in baseObj) { for (const bkStr in baseObj) {
@ -319,7 +319,7 @@ function convertGdef(otdGdef, glyphs) {
const gdef = new Ot.Gdef.Table(); const gdef = new Ot.Gdef.Table();
gdef.glyphClassDef = new Map(); gdef.glyphClassDef = new Map();
for (const gn in otdGdef.glyphClassDef) { for (const gn in otdGdef.glyphClassDef) {
const g = glyphs.query(gn); const g = glyphs.queryByName(gn);
if (g) gdef.glyphClassDef.set(g, otdGdef.glyphClassDef[gn]); if (g) gdef.glyphClassDef.set(g, otdGdef.glyphClassDef[gn]);
} }
return gdef; return gdef;

View file

@ -89,14 +89,9 @@ export all : define [buildGlyphs para recursive recursiveCodes] : begin
define [warnAboutBrokenGlyph g ensuredGlyphName saveGlyphName] : begin define [warnAboutBrokenGlyph g ensuredGlyphName saveGlyphName] : begin
local complexity 0 local complexity 0
local broken false if g.geometry : set complexity : g.geometry.suppressNaN
if g.contours : begin
foreach [c : items-of g.contours] : foreach [z : items-of c] : begin
inc complexity
if [not : isFinite z.x] : set broken true
if [not : isFinite z.y] : set broken true
if ([not recursive] && (broken || complexity > 4096)) : begin if ([not recursive] && complexity > 4096) : begin
console.log 'Possible broken shape found in' ensuredGlyphName 'Complexity' complexity console.log 'Possible broken shape found in' ensuredGlyphName 'Complexity' complexity
console.log 'Family' para.naming.family para.naming.weight para.naming.width para.naming.slope console.log 'Family' para.naming.family para.naming.weight para.naming.width para.naming.slope
if saveGlyphName : throw : new Error "Overcomplicated \(saveGlyphName)" if saveGlyphName : throw : new Error "Overcomplicated \(saveGlyphName)"

View file

@ -221,7 +221,7 @@ glyph-block Marks : begin
define cs : new BezToContoursSink define cs : new BezToContoursSink
ShapeConv.transferGenericShapeAsBezier {{inner outer}} cs GEOMETRY_PRECISION ShapeConv.transferGenericShapeAsBezier {{inner outer}} cs GEOMETRY_PRECISION
currentGlyph.includeGeometry cs 0 0 currentGlyph.includeContours cs.contours 0 0
create-glyph 'tildeAbove' 0x303 : glyph-proc create-glyph 'tildeAbove' 0x303 : glyph-proc
set-width 0 set-width 0

View file

@ -47,7 +47,7 @@ glyph-block Symbol-Geometric-Plain : for-width-kinds WideWidth1
begin 0 begin 0
local outlines : glyph-proc : begin local outlines : glyph-proc : begin
set this.gizmo : Translate 0 0 set this.gizmo : Translate 0 0
foreach c [items-of sh.contours] : foreach j [range 0 c.length] : begin foreach c [items-of : sh.geometry.asContours] : foreach j [range 0 c.length] : begin
local a c.[if j (j - 1) (c.length - 1)] local a c.[if j (j - 1) (c.length - 1)]
local b c.(j) local b c.(j)
include : dispiro include : dispiro

View file

@ -21,7 +21,7 @@ glyph-block Symbol-Math-APL : begin
local corners : new-glyph : glyph-proc local corners : new-glyph : glyph-proc
set this.gizmo : Translate 0 0 set this.gizmo : Translate 0 0
foreach [c : items-of overlay.contours] : foreach [z : items-of c] : do foreach [c : items-of : overlay.geometry.asContours] : foreach [z : items-of c] : do
if (z.type === Point.Type.Corner) : begin if (z.type === Point.Type.Corner) : begin
define x z.x define x z.x
define y z.y define y z.y

View file

@ -29,7 +29,7 @@ export : define [SetupBuilders args] : begin
local g1 : new Glyph local g1 : new Glyph
set g1.gizmo : g.gizmo || GlobalTransform set g1.gizmo : g.gizmo || GlobalTransform
g1.include p g1.include p
return : CurveUtil.convertShapeToArcs g1.contours return : CurveUtil.convertShapeToArcs : g1.geometry.asContours
define union : Boole TypoGeom.Boolean.ClipType.ctUnion define union : Boole TypoGeom.Boolean.ClipType.ctUnion
define intersection : Boole TypoGeom.Boolean.ClipType.ctIntersection define intersection : Boole TypoGeom.Boolean.ClipType.ctIntersection

View file

@ -206,14 +206,14 @@ export : define [SetupBuilders args] : begin
set g.knots knots set g.knots knots
set g.lhsKnots lhs set g.lhsKnots lhs
set g.rhsKnots rhs set g.rhsKnots rhs
this.includeGeometry g 0 0 this.includeContours g.contours 0 0
return g return g
define [spiro-outline] : let [k : {}.slice.call arguments 0] : lambda [] : begin define [spiro-outline] : let [k : {}.slice.call arguments 0] : lambda [] : begin
local g : new CurveUtil.BezToContoursSink (this.gizmo || GlobalTransform) local g : new CurveUtil.BezToContoursSink (this.gizmo || GlobalTransform)
local { .knots knots .closed closed } : prepareSpiroKnots k g local { .knots knots .closed closed } : prepareSpiroKnots k g
convertSpiroToBezier knots closed g convertSpiroToBezier knots closed g
this.includeGeometry g 0 0 this.includeContours g.contours 0 0
return g return g
return [object return [object

View file

@ -70,7 +70,7 @@ define-macro set-mark-anchor : syntax-rules
define-macro set-base-anchor : syntax-rules define-macro set-base-anchor : syntax-rules
`[set-base-anchor @::args] {'.syntactic-closure' `[currentGlyph.setBaseAnchor @::args] env} `[set-base-anchor @::args] {'.syntactic-closure' `[currentGlyph.setBaseAnchor @::args] env}
define-macro eject-contour : syntax-rules define-macro eject-contour : syntax-rules
`[eject-contour @::args] {'.syntactic-closure' `[currentGlyph.ejectContour @::args] env} `[eject-contour @::args] {'.syntactic-closure' `[currentGlyph.geometry.ejectContour @::args] env}
###### Canvas-based mechanism ###### Canvas-based mechanism
define-macro new-glyph : syntax-rules define-macro new-glyph : syntax-rules

View file

@ -4,19 +4,122 @@ const Transform = require("./transform");
const Point = require("./point"); const Point = require("./point");
const Anchor = require("./anchor"); const Anchor = require("./anchor");
class GeometryStore {
constructor() {
this.m_contours = [];
this.m_references = [];
}
addContour(c) {
this.m_contours.push(c);
}
addReference(glyph, x, y) {
this.m_references.push({ glyph, x, y });
}
asContours() {
let result = [];
for (const c of this.m_contours) {
const c1 = [...c];
if (c.tag) c1.tag = c.tag;
result.push(c1);
}
for (const r of this.m_references) {
for (const c of r.glyph.geometry.asContours()) {
let c1 = [];
for (const z of c) c1.push(Point.fromXY(z.type, z.x + r.x, z.y + r.y));
if (c.tag) c1.tag = c.tag;
result.push(c1);
}
}
return result;
}
asReferences() {
if (this.m_contours && this.m_contours.length) return null;
if (!this.m_references.length) return null;
return this.m_references;
}
applyTranslate(shiftX, shiftY) {
for (const c of this.m_contours) {
for (let k = 0; k < c.length; k++) {
c[k] = Point.translated(c[k], shiftX, shiftY);
}
}
for (const r of this.m_references) {
r.x += shiftX;
r.y += shiftY;
}
}
applyTransform(tfm) {
const cs = this.asContours();
for (const c of cs) {
for (let k = 0; k < c.length; k++) {
c[k] = Point.transformed(tfm, c[k]);
}
}
this.m_contours = cs;
this.m_references.length = 0;
}
reTagContour(oldTag, newTag) {
for (const c of this.m_contours) {
if (c.tag === oldTag) c.tag = newTag;
}
}
ejectContour(tag) {
const cs = this.asContours();
let i = 0,
j = 0;
for (; i < cs.length; i++) if (!cs[i].tag || cs[i].tag !== tag) cs[j++] = cs[i];
cs.length = j;
this.m_contours = cs;
this.m_references = [];
}
suppressNaN() {
let broken = false,
complexity = 0;
for (const c of this.m_contours) {
for (const z of c) {
complexity++;
if (!isFinite(z.x)) {
broken = true;
z.x = 0;
}
if (!isFinite(z.y)) {
broken = true;
z.y = 0;
}
}
}
return broken ? 0xffff : complexity;
}
isEmpty() {
return !this.m_contours.length && !this.m_references.length;
}
}
module.exports = class Glyph { module.exports = class Glyph {
constructor(_identifier) { constructor(_identifier) {
this._m_identifier = _identifier; this._m_identifier = _identifier;
this.contours = []; this.geometry = new GeometryStore();
this.advanceWidth = 500; this.advanceWidth = 500;
this.autoRefPriority = 0; this.autoRefPriority = 0;
this.markAnchors = {}; this.markAnchors = {};
this.baseAnchors = {}; this.baseAnchors = {};
this.gizmo = Transform.Id(); this.gizmo = Transform.Id();
this.semanticInclusions = [];
this.dependencies = []; this.dependencies = [];
this.defaultTag = null; this.defaultTag = null;
} }
get contours() {
throw new TypeError("Glyph::contours has been deprecated");
}
get semanticInclusions() {
throw new TypeError("Glyph::semanticInclusions has been deprecated");
}
get name() { get name() {
throw new TypeError("Glyph::name has been deprecated"); throw new TypeError("Glyph::name has been deprecated");
} }
@ -40,20 +143,6 @@ module.exports = class Glyph {
if (glyph._m_identifier) this.dependencies.push(glyph._m_identifier); if (glyph._m_identifier) this.dependencies.push(glyph._m_identifier);
if (glyph.dependencies) for (const dep of glyph.dependencies) this.dependencies.push(dep); if (glyph.dependencies) for (const dep of glyph.dependencies) this.dependencies.push(dep);
} }
// Contour Tagging
reTagContour(oldTag, newTag) {
for (const c of this.contours) if (c.tag === oldTag) c.tag = newTag;
}
ejectContour(tag) {
let i = 0,
j = 0;
for (; i < this.contours.length; i++) {
if (!this.contours[i].tag || this.contours[i].tag !== tag)
this.contours[j++] = this.contours[i];
}
this.contours.length = j;
this.semanticInclusions = [];
}
// Inclusion // Inclusion
include(component, copyAnchors, copyWidth) { include(component, copyAnchors, copyWidth) {
if (!component) { if (!component) {
@ -105,56 +194,26 @@ module.exports = class Glyph {
this.avoidBeingComposite = g.avoidBeingComposite; this.avoidBeingComposite = g.avoidBeingComposite;
} }
isPureComposite() {
if (!this.semanticInclusions || !this.semanticInclusions.length) return false;
const origContourSet = new Set(this.contours);
let handledContours = new Set();
for (const sr of this.semanticInclusions) {
for (const c of sr.contours) {
if (!origContourSet.has(c) || handledContours.has(c)) return false;
handledContours.add(c);
}
}
for (const c of this.contours) if (!handledContours.has(c)) return false;
return true;
}
includeGlyphImpl(g, shiftX, shiftY) { includeGlyphImpl(g, shiftX, shiftY) {
if (g._m_identifier) { if (g._m_identifier) {
this.includeGlyphComponentImpl(g, shiftX, shiftY); if (!g.geometry.isEmpty()) this.geometry.addReference(g, shiftX, shiftY);
} else if (!g._m_identifier && g.isPureComposite()) { } else if (!g._m_identifier && g.geometry.asReferences()) {
for (const sr of g.semanticInclusions) for (const sr of g.geometry.asReferences()) {
this.includeGlyphComponentImpl(sr.glyph, sr.x + shiftX, sr.y + shiftY); if (!sr.glyph.geometry.isEmpty())
this.geometry.addReference(sr.glyph, sr.x + shiftX, sr.y + shiftY);
}
} else { } else {
this.includeGeometry(g, shiftX, shiftY); this.includeContours(g.geometry.asContours(), shiftX, shiftY);
}
}
includeGlyphComponentImpl(g, shiftX, shiftY) {
const newContours = this.includeGeometry(g, shiftX, shiftY);
if (newContours && newContours.length) {
this.semanticInclusions.push({
glyph: g,
x: shiftX,
y: shiftY,
contours: newContours
});
} }
} }
includeGeometry(geom, shiftX, shiftY) { includeContours(cs, shiftX, shiftY) {
if (!geom || !geom.contours || !geom.contours.length) return null; for (const contour of cs) {
return this.includeContours(geom.contours, shiftX, shiftY);
}
includeContours(contours, shiftX, shiftY) {
let newContours = [];
for (const contour of contours) {
let c = []; let c = [];
c.tag = contour.tag || contours.tag || this.defaultTag; c.tag = contour.tag || cs.tag || this.defaultTag;
for (const z of contour) c.push(Point.translated(z, shiftX, shiftY)); for (const z of contour) c.push(Point.translated(z, shiftX, shiftY));
this.contours.push(c); this.geometry.addContour(c);
newContours.push(c);
} }
return newContours;
} }
combineAnchor(shift, baseThis, markThat, basesThat) { combineAnchor(shift, baseThis, markThat, basesThat) {
@ -175,19 +234,10 @@ module.exports = class Glyph {
if (g.baseAnchors) for (const k in g.baseAnchors) this.baseAnchors[k] = g.baseAnchors[k]; if (g.baseAnchors) for (const k in g.baseAnchors) this.baseAnchors[k] = g.baseAnchors[k];
} }
applyTransform(tfm, alsoAnchors) { applyTransform(tfm, alsoAnchors) {
for (const c of this.contours) {
for (let k = 0; k < c.length; k++) {
c[k] = Point.transformed(tfm, c[k]);
}
}
if (Transform.isTranslate(tfm)) { if (Transform.isTranslate(tfm)) {
for (const sr of this.semanticInclusions) { this.geometry.applyTranslate(tfm.x, tfm.y);
sr.x += tfm.x;
sr.y += tfm.y;
}
} else { } else {
// Applying a non-trivial inclusion will unlink all the SIs this.geometry.applyTransform(tfm);
this.semanticInclusions = [];
} }
if (alsoAnchors) { if (alsoAnchors) {
for (const k in this.baseAnchors) for (const k in this.baseAnchors)
@ -199,34 +249,29 @@ module.exports = class Glyph {
tryBecomeMirrorOf(dst, rankSet) { tryBecomeMirrorOf(dst, rankSet) {
if (rankSet.has(this) || rankSet.has(dst)) return; if (rankSet.has(this) || rankSet.has(dst)) return;
if (this.contours.length !== dst.contours.length) return; const csThis = this.geometry.asContours();
for (let j = 0; j < this.contours.length; j++) { const csDst = dst.geometry.asContours();
const c1 = this.contours[j], if (csThis.length !== csDst.length) return;
c2 = dst.contours[j]; for (let j = 0; j < csThis.length; j++) {
const c1 = csThis[j],
c2 = csDst[j];
if (c1.length !== c2.length) return; if (c1.length !== c2.length) return;
} }
for (let j = 0; j < this.contours.length; j++) { for (let j = 0; j < csThis.length; j++) {
const c1 = this.contours[j], const c1 = csThis[j],
c2 = dst.contours[j]; c2 = csDst[j];
for (let k = 0; k < c1.length; k++) { for (let k = 0; k < c1.length; k++) {
const z1 = c1[k], const z1 = c1[k],
z2 = c2[k]; z2 = c2[k];
if (z1.x !== z2.x || z1.y !== z2.y || z1.type !== z2.type) return; if (z1.x !== z2.x || z1.y !== z2.y || z1.type !== z2.type) return;
} }
} }
this.semanticInclusions = [ this.geometry = new GeometryStore();
{ this.geometry.addReference(dst, 0, 0);
glyph: dst,
x: 0,
y: 0,
contours: [...this.contours]
}
];
rankSet.add(this); rankSet.add(this);
} }
clearGeometry() { clearGeometry() {
this.contours = []; this.geometry = new GeometryStore();
this.semanticInclusions = [];
} }
// Anchors // Anchors