"use strict"; const Transform = require("./transform"); const Point = require("./point"); const Anchor = require("./anchor"); module.exports = class Glyph { constructor(_identifier) { this._m_identifier = _identifier; this.contours = []; this.advanceWidth = 500; this.autoRefPriority = 0; this.markAnchors = {}; this.baseAnchors = {}; this.gizmo = Transform.Id(); this.semanticInclusions = []; this.dependencies = []; this.defaultTag = null; } get name() { throw new TypeError("Glyph::name has been deprecated"); } get unicode() { throw new TypeError("Glyph::unicode has been deprecated"); } set unicode(x) { throw new TypeError("Glyph::unicode has been deprecated"); } // PTL pattern matching static unapply(obj, arity) { if (obj instanceof Glyph) return [obj]; else return null; } // Metrics setWidth(w) { this.advanceWidth = w; } // Dependency dependsOn(glyph) { if (glyph._m_identifier) this.dependencies.push(glyph._m_identifier); 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 include(component, copyAnchors, copyWidth) { if (!component) { throw new Error("Unreachable: Attempt to include a Null or Undefined"); } else if (component instanceof Function) { const t = this.defaultTag; if (component.tag) this.defaultTag = component.tag; component.call(this, copyAnchors, copyWidth); this.defaultTag = t; return; } else if (component instanceof Transform) { this.applyTransform(component, copyAnchors); } else if (component.isMarkSet) { this.copyAnchors(component); } else if (component instanceof Glyph) { this.includeGlyph(component, copyAnchors, copyWidth); } else { throw new Error("Invalid component to be introduced."); } } includeGlyph(g, copyAnchors, copyWidth) { if (g instanceof Function) throw new Error("Unreachable"); // Combine anchors and get offset let shift = { x: 0, y: 0 }; if (g.markAnchors) { for (const m in this.baseAnchors) { this.combineAnchor(shift, this.baseAnchors[m], g.markAnchors[m], g.baseAnchors); } } const newContours = this.includeGeometry(g, shift.x, shift.y); if (copyAnchors || g.isMarkSet) this.copyAnchors(g); if (copyWidth && g.advanceWidth >= 0) this.advanceWidth = g.advanceWidth; this.dependsOn(g); if (g._m_identifier && newContours && newContours.length) { this.semanticInclusions.push({ glyph: g, x: shift.x, y: shift.y, contours: newContours }); } } cloneFromGlyph(g) { this.includeGlyph(g, true, true); this.cloneRelationFromGlyph(g); this.cloneRankFromGlyph(g); } cloneRelationFromGlyph(g) { this.shortName = g.shortName; this.related = g.related; } cloneRankFromGlyph(g) { this.autoRefPriority = g.autoRefPriority; this.glyphRank = g.glyphRank; this.avoidBeingComposite = g.avoidBeingComposite; } includeGeometry(geom, shiftX, shiftY) { if (!geom || !geom.contours || !geom.contours.length) return null; if (this.includeGeometryAsTransparentReferences(geom, shiftX, shiftY)) return null; return this.includeGeometryImpl(geom, shiftX, shiftY); } 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; } includeGeometryAsTransparentReferences(geom, shiftX, shiftY) { if (!(geom instanceof Glyph && !geom._m_identifier)) return false; if (!geom.isPureComposite()) return false; for (const sr of geom.semanticInclusions) { const cs = this.includeGeometryImpl(sr.glyph, sr.x + shiftX, sr.y + shiftY); if (cs) { this.semanticInclusions.push({ glyph: sr.glyph, x: sr.x + shiftX, y: sr.y + shiftY, contours: cs }); } } return true; } includeGeometryImpl(geom, shiftX, shiftY) { let newContours = []; for (const contour of geom.contours) { let c = []; c.tag = contour.tag || geom.tag || this.defaultTag; for (const z of contour) c.push(Point.translated(z, shiftX, shiftY)); this.contours.push(c); newContours.push(c); } return newContours; } combineAnchor(shift, baseThis, markThat, basesThat) { if (!baseThis || !markThat) return; shift.x = baseThis.x - markThat.x; shift.y = baseThis.y - markThat.y; if (basesThat) { for (const bk in basesThat) { this.baseAnchors[bk] = new Anchor( shift.x + basesThat[bk].x, shift.y + basesThat[bk].y ); } } } copyAnchors(g) { if (g.markAnchors) for (const k in g.markAnchors) this.markAnchors[k] = g.markAnchors[k]; if (g.baseAnchors) for (const k in g.baseAnchors) this.baseAnchors[k] = g.baseAnchors[k]; } 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)) { for (const sr of this.semanticInclusions) { sr.x += tfm.x; sr.y += tfm.y; } } else { // Applying a non-trivial inclusion will unlink all the SIs this.semanticInclusions = []; } if (alsoAnchors) { for (const k in this.baseAnchors) this.baseAnchors[k] = Anchor.transform(tfm, this.baseAnchors[k]); for (const k in this.markAnchors) this.markAnchors[k] = Anchor.transform(tfm, this.markAnchors[k]); } } tryBecomeMirrorOf(dst, rankSet) { if (rankSet.has(this) || rankSet.has(dst)) return; if (this.contours.length !== dst.contours.length) return; for (let j = 0; j < this.contours.length; j++) { const c1 = this.contours[j], c2 = dst.contours[j]; if (c1.length !== c2.length) return; } for (let j = 0; j < this.contours.length; j++) { const c1 = this.contours[j], c2 = dst.contours[j]; for (let k = 0; k < c1.length; k++) { const z1 = c1[k], z2 = c2[k]; if (z1.x !== z2.x || z1.y !== z2.y || z1.on !== z2.on) return; } } this.semanticInclusions = [ { glyph: dst, x: 0, y: 0, contours: [...this.contours] } ]; rankSet.add(this); } // Anchors setBaseAnchor(id, x, y) { this.baseAnchors[id] = new Anchor(x, y).transform(this.gizmo); } setMarkAnchor(id, x, y, mbx, mby) { this.markAnchors[id] = new Anchor(x, y).transform(this.gizmo); if (mbx != null && mby != null) { this.baseAnchors[id] = new Anchor(mbx, mby).transform(this.gizmo); } } deleteBaseAnchor(id) { delete this.baseAnchors[id]; } deleteMarkAnchor(id) { delete this.markAnchors[id]; } };