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) {
for (const g of glyphStore.glyphs()) {
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;
}
}
if (g.geometry) g.geometry.suppressNaN();
}
}
@ -31,32 +24,38 @@ function suppressNaN(glyphStore) {
function regulateGlyphStore(skew, glyphStore) {
for (const g of glyphStore.glyphs()) {
if (!g.semanticInclusions || !g.contours) continue;
if (g.isPureComposite()) regulateCompositeGlyph(glyphStore, g);
if (g.geometry.isEmpty()) continue;
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) {
const references = [];
for (const sr of g.semanticInclusions) {
const refs = g.geometry.asReferences();
if (!refs) return false;
for (const sr of refs) {
const gn = glyphStore.queryNameOf(sr.glyph);
if (!gn || sr.glyph.autoRefPriority < 0) return;
references.push({ glyph: gn, x: sr.x, y: sr.y });
if (!gn || sr.glyph.autoRefPriority < 0) return false;
}
g.semanticInclusions = [];
g.contours = [];
g.references = references;
return true;
}
function regulateSimpleGlyph(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;
let cs = g.geometry.asContours();
for (const contour of cs) for (const z of contour) z.x -= z.y * skew;
cs = simplifyContours(cs);
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 Point = require("../../support/point");
class NamedGlyphStore {
class MappedGlyphStore {
constructor() {
this.m_nameMapping = new Map();
this.m_mapping = new Map();
}
declare(name) {
declare(name, source) {
const g = new Ot.Glyph();
g.name = name;
this.m_mapping.set(name, g);
this.m_nameMapping.set(name, g);
this.m_mapping.set(source, g);
}
query(name) {
return this.m_mapping.get(name);
queryBySourceGlyph(source) {
return this.m_mapping.get(source);
}
queryByName(name) {
return this.m_nameMapping.get(name);
}
decideOrder() {
const gs = Ot.ListGlyphStoreFactory.createStoreFromList([...this.m_mapping.values()]);
return gs.decideOrder();
}
fill(name, data) {
const g = this.query(name);
if (!g) return;
fill(name, source) {
const g = this.queryBySourceGlyph(source);
if (!g) throw new Error("Unreachable");
g.horizontal = { start: 0, end: data.advanceWidth };
if (data.references && data.references.length) {
this.fillReferences(g, data);
} else if (data.contours && data.contours.length) {
this.fillContours(g, data);
// Fill metrics
g.horizontal = { start: 0, end: source.advanceWidth };
// Fill Geometry
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();
for (const ref of data.references) {
const target = this.query(ref.glyph);
if (!target) continue;
for (const ref of rs) {
const target = this.queryBySourceGlyph(ref.glyph);
if (!target) throw new Error("Unreachable");
const tfm = Ot.Glyph.Transform2X3.Translate(ref.x, ref.y);
gl.items.push(new Ot.Glyph.TtReference(target, tfm));
}
g.geometry = gl;
}
fillContours(g, data) {
fillContours(g, contours) {
const cs = new Ot.Glyph.ContourSet();
for (const c of data.contours) {
for (const c of contours) {
const c1 = [];
for (const z of c) {
c1.push(
@ -67,21 +77,21 @@ function convertGlyphs(gsOrig) {
.map(([j, gn, g]) => [j, gn, queryOrderingUnicode(gsOrig, g), g])
.sort(byRank);
const gs = new NamedGlyphStore();
const gs = new MappedGlyphStore();
const cmap = new Ot.Cmap.Table();
for (const [origIndex, name, uOrd, g] of sortedEntries) {
gs.declare(name);
const us = gsOrig.queryUnicodeOf(g);
for (const [origIndex, name, uOrd, gSrc] of sortedEntries) {
gs.declare(name, gSrc);
const us = gsOrig.queryUnicodeOf(gSrc);
if (us) {
for (const u of us) {
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 };
}

View file

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

View file

@ -89,14 +89,9 @@ export all : define [buildGlyphs para recursive recursiveCodes] : begin
define [warnAboutBrokenGlyph g ensuredGlyphName saveGlyphName] : begin
local complexity 0
local broken false
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 g.geometry : set complexity : g.geometry.suppressNaN
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 'Family' para.naming.family para.naming.weight para.naming.width para.naming.slope
if saveGlyphName : throw : new Error "Overcomplicated \(saveGlyphName)"

View file

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

View file

@ -47,7 +47,7 @@ glyph-block Symbol-Geometric-Plain : for-width-kinds WideWidth1
begin 0
local outlines : glyph-proc : begin
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 b c.(j)
include : dispiro

View file

@ -21,7 +21,7 @@ glyph-block Symbol-Math-APL : begin
local corners : new-glyph : glyph-proc
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
define x z.x
define y z.y

View file

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

View file

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

View file

@ -70,7 +70,7 @@ define-macro set-mark-anchor : syntax-rules
define-macro set-base-anchor : syntax-rules
`[set-base-anchor @::args] {'.syntactic-closure' `[currentGlyph.setBaseAnchor @::args] env}
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
define-macro new-glyph : syntax-rules

View file

@ -4,19 +4,122 @@ const Transform = require("./transform");
const Point = require("./point");
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 {
constructor(_identifier) {
this._m_identifier = _identifier;
this.contours = [];
this.geometry = new GeometryStore();
this.advanceWidth = 500;
this.autoRefPriority = 0;
this.markAnchors = {};
this.baseAnchors = {};
this.gizmo = Transform.Id();
this.semanticInclusions = [];
this.dependencies = [];
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() {
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.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) {
@ -105,56 +194,26 @@ module.exports = class Glyph {
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) {
if (g._m_identifier) {
this.includeGlyphComponentImpl(g, shiftX, shiftY);
} else if (!g._m_identifier && g.isPureComposite()) {
for (const sr of g.semanticInclusions)
this.includeGlyphComponentImpl(sr.glyph, sr.x + shiftX, sr.y + shiftY);
if (!g.geometry.isEmpty()) this.geometry.addReference(g, shiftX, shiftY);
} else if (!g._m_identifier && g.geometry.asReferences()) {
for (const sr of g.geometry.asReferences()) {
if (!sr.glyph.geometry.isEmpty())
this.geometry.addReference(sr.glyph, sr.x + shiftX, sr.y + shiftY);
}
} else {
this.includeGeometry(g, 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
});
this.includeContours(g.geometry.asContours(), shiftX, shiftY);
}
}
includeGeometry(geom, shiftX, shiftY) {
if (!geom || !geom.contours || !geom.contours.length) return null;
return this.includeContours(geom.contours, shiftX, shiftY);
}
includeContours(contours, shiftX, shiftY) {
let newContours = [];
for (const contour of contours) {
includeContours(cs, shiftX, shiftY) {
for (const contour of cs) {
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));
this.contours.push(c);
newContours.push(c);
this.geometry.addContour(c);
}
return newContours;
}
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];
}
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;
}
this.geometry.applyTranslate(tfm.x, tfm.y);
} else {
// Applying a non-trivial inclusion will unlink all the SIs
this.semanticInclusions = [];
this.geometry.applyTransform(tfm);
}
if (alsoAnchors) {
for (const k in this.baseAnchors)
@ -199,34 +249,29 @@ module.exports = class Glyph {
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];
const csThis = this.geometry.asContours();
const csDst = dst.geometry.asContours();
if (csThis.length !== csDst.length) return;
for (let j = 0; j < csThis.length; j++) {
const c1 = csThis[j],
c2 = csDst[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 j = 0; j < csThis.length; j++) {
const c1 = csThis[j],
c2 = csDst[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.type !== z2.type) return;
}
}
this.semanticInclusions = [
{
glyph: dst,
x: 0,
y: 0,
contours: [...this.contours]
}
];
this.geometry = new GeometryStore();
this.geometry.addReference(dst, 0, 0);
rankSet.add(this);
}
clearGeometry() {
this.contours = [];
this.semanticInclusions = [];
this.geometry = new GeometryStore();
}
// Anchors