Cleanup of geometry code
This commit is contained in:
parent
a096544d2f
commit
2d84803cec
8 changed files with 127 additions and 103 deletions
|
@ -3,7 +3,7 @@ import zlib from "zlib";
|
||||||
|
|
||||||
import { encode, decode } from "@msgpack/msgpack";
|
import { encode, decode } from "@msgpack/msgpack";
|
||||||
|
|
||||||
const Edition = 26;
|
const Edition = 27;
|
||||||
const MAX_AGE = 16;
|
const MAX_AGE = 16;
|
||||||
class GfEntry {
|
class GfEntry {
|
||||||
constructor(age, value) {
|
constructor(age, value) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import { Ot } from "ot-builder";
|
import { Ot } from "ot-builder";
|
||||||
|
|
||||||
|
import { Vec2 } from "../../support/geometry/point.mjs";
|
||||||
|
|
||||||
export function convertGsub(table, glyphs) {
|
export function convertGsub(table, glyphs) {
|
||||||
return ConvertGsubGposImpl(GsubHandlers, Ot.Gsub.Table, table, glyphs);
|
return ConvertGsubGposImpl(GsubHandlers, Ot.Gsub.Table, table, glyphs);
|
||||||
}
|
}
|
||||||
|
@ -304,7 +306,7 @@ function convertMarkRecords(marks, mm, store) {
|
||||||
const g = store.glyphs.queryByName(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)] = Vec2.from(mark);
|
||||||
out.set(g, { markAnchors: markAnchors });
|
out.set(g, { markAnchors: markAnchors });
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
|
|
|
@ -153,6 +153,8 @@ export class DiSpiroGeometry extends GeometryBase {
|
||||||
expander.initializeNormals();
|
expander.initializeNormals();
|
||||||
expander.iterateNormals();
|
expander.iterateNormals();
|
||||||
expander.iterateNormals();
|
expander.iterateNormals();
|
||||||
|
expander.iterateNormals();
|
||||||
|
expander.iterateNormals();
|
||||||
this.m_cachedExpansionResults = expander.expand();
|
this.m_cachedExpansionResults = expander.expand();
|
||||||
return this.m_cachedExpansionResults;
|
return this.m_cachedExpansionResults;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,16 @@
|
||||||
import { mix } from "../utils.mjs";
|
import { mix } from "../utils.mjs";
|
||||||
|
|
||||||
|
export class Vec2 {
|
||||||
|
constructor(x, y) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
static from(z) {
|
||||||
|
return new Vec2(z.x, z.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export class Point {
|
export class Point {
|
||||||
constructor(type, x, y) {
|
constructor(type, x, y) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
|
@ -186,3 +186,10 @@ export function Interpolator(blender, restParameters) {
|
||||||
for (const prop in restParameters) interpolator[prop] = restParameters[prop];
|
for (const prop in restParameters) interpolator[prop] = restParameters[prop];
|
||||||
return interpolator;
|
return interpolator;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class ImportanceControlKnot extends ControlKnot {
|
||||||
|
constructor(type, x, y, unimportant) {
|
||||||
|
super(type, x, y, null);
|
||||||
|
this.unimportant = unimportant;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import * as SpiroJs from "spiro";
|
import * as SpiroJs from "spiro";
|
||||||
|
|
||||||
import { linreg } from "../utils.mjs";
|
import { linreg, mix } from "../utils.mjs";
|
||||||
|
|
||||||
|
import { Vec2 } from "./point.mjs";
|
||||||
|
import { ControlKnot } from "./spiro-control.mjs";
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
@ -9,109 +12,92 @@ export class SpiroExpander {
|
||||||
this.m_gizmo = gizmo;
|
this.m_gizmo = gizmo;
|
||||||
this.m_contrast = contrast;
|
this.m_contrast = contrast;
|
||||||
this.m_closed = closed;
|
this.m_closed = closed;
|
||||||
this.m_biKnots = [];
|
|
||||||
for (const knot of biKnots) {
|
this.m_biKnotsU = Array.from(biKnots);
|
||||||
this.m_biKnots.push(knot.withGizmo(gizmo));
|
this.m_biKnotsT = biKnots.map(k => k.withGizmo(gizmo));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
initializeNormals() {
|
initializeNormals() {
|
||||||
const normalRectifier = new NormalRectifier(this.m_biKnots, this.m_gizmo);
|
const normalRectifier = new NormalRectifier(this.m_biKnotsT, this.m_gizmo);
|
||||||
SpiroJs.spiroToArcsOnContext(this.m_biKnots, this.m_closed, normalRectifier);
|
SpiroJs.spiroToArcsOnContext(this.m_biKnotsT, this.m_closed, normalRectifier);
|
||||||
}
|
}
|
||||||
iterateNormals() {
|
iterateNormals() {
|
||||||
const centerBone = this.getPass2Knots();
|
const centerBone = this.getPass2Knots();
|
||||||
const normalRectifier = new NormalRectifier(this.m_biKnots, this.m_gizmo);
|
const normalRectifier = new NormalRectifier(this.m_biKnotsT, this.m_gizmo);
|
||||||
SpiroJs.spiroToArcsOnContext(centerBone, this.m_closed, normalRectifier);
|
SpiroJs.spiroToArcsOnContext(centerBone, this.m_closed, normalRectifier);
|
||||||
}
|
}
|
||||||
getPass2Knots() {
|
getPass2Knots() {
|
||||||
const expanded = this.expand(this.m_contrast);
|
const expanded = this.expand(this.m_contrast);
|
||||||
const middles = [];
|
const middles = [];
|
||||||
for (let j = 0; j < this.m_biKnots.length; j++) {
|
for (let j = 0; j < this.m_biKnotsT.length; j++) {
|
||||||
const lhs = this.m_gizmo.unapply(expanded.lhs[j]);
|
const lhs = expanded.lhs[j];
|
||||||
const rhs = this.m_gizmo.unapply(expanded.rhs[j]);
|
const rhs = expanded.rhs[j];
|
||||||
middles[j] = {
|
middles[j] = new ControlKnot(
|
||||||
x: 0.5 * (lhs.x + rhs.x),
|
this.m_biKnotsT[j].type,
|
||||||
y: 0.5 * (lhs.y + rhs.y),
|
mix(lhs.x, rhs.x, 0.5),
|
||||||
type: this.m_biKnots[j].type,
|
mix(lhs.y, rhs.y, 0.5)
|
||||||
unimportant: this.m_biKnots[j].unimportant
|
);
|
||||||
};
|
|
||||||
}
|
}
|
||||||
return middles;
|
return middles;
|
||||||
}
|
}
|
||||||
expand() {
|
expand() {
|
||||||
const lhs = [],
|
const lhs = [],
|
||||||
rhs = [];
|
rhs = [],
|
||||||
// Initialize knots
|
lhsUntransformed = [],
|
||||||
for (let j = 0; j < this.m_biKnots.length; j++) {
|
|
||||||
const knot = this.m_biKnots[j];
|
|
||||||
lhs[j] = {
|
|
||||||
type: knot.type,
|
|
||||||
unimportant: knot.unimportant,
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
rhs[j] = {
|
|
||||||
type: reverseKnotType(knot.type),
|
|
||||||
unimportant: knot.unimportant,
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Create important knots
|
|
||||||
for (let j = 0; j < this.m_biKnots.length; j++) {
|
|
||||||
const knot = this.m_biKnots[j];
|
|
||||||
if (knot.unimportant) continue;
|
|
||||||
let dx, dy;
|
|
||||||
if (knot.proposedNormal) {
|
|
||||||
dx = knot.proposedNormal.x;
|
|
||||||
dy = knot.proposedNormal.y;
|
|
||||||
} else {
|
|
||||||
dx = normalX(knot.origTangent, this.m_contrast);
|
|
||||||
dy = normalY(knot.origTangent, this.m_contrast);
|
|
||||||
}
|
|
||||||
lhs[j].x = knot.x + knot.d1 * dx;
|
|
||||||
lhs[j].y = knot.y + knot.d1 * dy;
|
|
||||||
rhs[j].x = knot.x - knot.d2 * dx;
|
|
||||||
rhs[j].y = knot.y - knot.d2 * dy;
|
|
||||||
}
|
|
||||||
this.interpolateUnimportantKnots(lhs, rhs);
|
|
||||||
|
|
||||||
const lhsUntransformed = [],
|
|
||||||
rhsUntransformed = [];
|
rhsUntransformed = [];
|
||||||
for (const z of lhs) {
|
|
||||||
const u = this.m_gizmo.unapply(z);
|
for (let j = 0; j < this.m_biKnotsT.length; j++) {
|
||||||
lhsUntransformed.push({ type: z.type, x: u.x, y: u.y });
|
const knot = this.m_biKnotsT[j];
|
||||||
|
lhs[j] = new ControlKnot(knot.type, 0, 0);
|
||||||
|
rhs[j] = new ControlKnot(reverseKnotType(knot.type), 0, 0);
|
||||||
|
lhsUntransformed[j] = new ControlKnot(knot.type, 0, 0);
|
||||||
|
rhsUntransformed[j] = new ControlKnot(reverseKnotType(knot.type), 0, 0);
|
||||||
}
|
}
|
||||||
for (const z of rhs) {
|
|
||||||
const u = this.m_gizmo.unapply(z);
|
for (let j = 0; j < this.m_biKnotsT.length; j++) {
|
||||||
rhsUntransformed.push({ type: z.type, x: u.x, y: u.y });
|
const knotT = this.m_biKnotsT[j];
|
||||||
|
if (knotT.unimportant) continue;
|
||||||
|
let dx, dy;
|
||||||
|
if (knotT.proposedNormal) {
|
||||||
|
dx = knotT.proposedNormal.x;
|
||||||
|
dy = knotT.proposedNormal.y;
|
||||||
|
} else {
|
||||||
|
dx = normalX(knotT.origTangent, this.m_contrast);
|
||||||
|
dy = normalY(knotT.origTangent, this.m_contrast);
|
||||||
|
}
|
||||||
|
lhs[j].x = knotT.x + knotT.d1 * dx;
|
||||||
|
lhs[j].y = knotT.y + knotT.d1 * dy;
|
||||||
|
rhs[j].x = knotT.x - knotT.d2 * dx;
|
||||||
|
rhs[j].y = knotT.y - knotT.d2 * dy;
|
||||||
|
|
||||||
|
this.m_gizmo.unapplyToSink(lhs[j], lhsUntransformed[j]);
|
||||||
|
this.m_gizmo.unapplyToSink(rhs[j], rhsUntransformed[j]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.interpolateUnimportantKnots(lhs, rhs, lhsUntransformed, rhsUntransformed);
|
||||||
return { lhs, rhs, lhsUntransformed, rhsUntransformed };
|
return { lhs, rhs, lhsUntransformed, rhsUntransformed };
|
||||||
}
|
}
|
||||||
interpolateUnimportantKnots(lhs, rhs) {
|
interpolateUnimportantKnots(lhsT, rhsT, lhsU, rhsU) {
|
||||||
for (let j = 0; j < this.m_biKnots.length; j++) {
|
for (let j = 0; j < this.m_biKnotsU.length; j++) {
|
||||||
const knot = this.m_biKnots[j];
|
const knotU = this.m_biKnotsU[j];
|
||||||
if (!knot.unimportant) continue;
|
if (!knotU.unimportant) continue;
|
||||||
let jBefore, jAfter;
|
let jBefore, jAfter;
|
||||||
for (jBefore = j - 1; cyNth(this.m_biKnots, jBefore).unimportant; jBefore--);
|
for (jBefore = j - 1; cyNth(this.m_biKnotsU, jBefore).unimportant; jBefore--);
|
||||||
for (jAfter = j + 1; cyNth(this.m_biKnots, jAfter).unimportant; jAfter++);
|
for (jAfter = j + 1; cyNth(this.m_biKnotsU, jAfter).unimportant; jAfter++);
|
||||||
const knotBefore = this.m_gizmo.unapply(cyNth(this.m_biKnots, jBefore)),
|
|
||||||
knotAfter = this.m_gizmo.unapply(cyNth(this.m_biKnots, jAfter)),
|
const knotUBefore = cyNth(this.m_biKnotsU, jBefore),
|
||||||
ref = this.m_gizmo.unapply(knot),
|
knotUAfter = cyNth(this.m_biKnotsU, jAfter),
|
||||||
lhsBefore = this.m_gizmo.unapply(cyNth(lhs, jBefore)),
|
lhsUBefore = cyNth(lhsU, jBefore),
|
||||||
lhsAfter = this.m_gizmo.unapply(cyNth(lhs, jAfter)),
|
lhsUAfter = cyNth(lhsU, jAfter),
|
||||||
rhsBefore = this.m_gizmo.unapply(cyNth(rhs, jBefore)),
|
rhsUBefore = cyNth(rhsU, jBefore),
|
||||||
rhsAfter = this.m_gizmo.unapply(cyNth(rhs, jAfter));
|
rhsUAfter = cyNth(rhsU, jAfter);
|
||||||
const lhsTf = this.m_gizmo.applyXY(
|
|
||||||
linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x),
|
lhsU[j].x = linreg(knotUBefore.x, lhsUBefore.x, knotUAfter.x, lhsUAfter.x, knotU.x);
|
||||||
linreg(knotBefore.y, lhsBefore.y, knotAfter.y, lhsAfter.y, ref.y)
|
lhsU[j].y = linreg(knotUBefore.y, lhsUBefore.y, knotUAfter.y, lhsUAfter.y, knotU.y);
|
||||||
);
|
rhsU[j].x = linreg(knotUBefore.x, rhsUBefore.x, knotUAfter.x, rhsUAfter.x, knotU.x);
|
||||||
const rhsTf = this.m_gizmo.applyXY(
|
rhsU[j].y = linreg(knotUBefore.y, rhsUBefore.y, knotUAfter.y, rhsUAfter.y, knotU.y);
|
||||||
linreg(knotBefore.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x),
|
|
||||||
linreg(knotBefore.y, rhsBefore.y, knotAfter.y, rhsAfter.y, ref.y)
|
this.m_gizmo.applyToSink(lhsU[j], lhsT[j]);
|
||||||
);
|
this.m_gizmo.applyToSink(rhsU[j], rhsT[j]);
|
||||||
(lhs[j].x = lhsTf.x), (lhs[j].y = lhsTf.y);
|
|
||||||
(rhs[j].x = rhsTf.x), (rhs[j].y = rhsTf.y);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +114,7 @@ class NormalRectifier {
|
||||||
}
|
}
|
||||||
arcTo(arc, x, y) {
|
arcTo(arc, x, y) {
|
||||||
if (this.m_nKnotsProcessed === 1) {
|
if (this.m_nKnotsProcessed === 1) {
|
||||||
const d = this.m_gizmo.applyOffsetXY(arc.deriveX0, arc.deriveY0);
|
const d = new Vec2(arc.deriveX0, arc.deriveY0);
|
||||||
if (isTangentValid(d)) {
|
if (isTangentValid(d)) {
|
||||||
this.m_biKnots[0].origTangent = d;
|
this.m_biKnots[0].origTangent = d;
|
||||||
} else {
|
} else {
|
||||||
|
@ -136,7 +122,7 @@ class NormalRectifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (this.m_biKnots[this.m_nKnotsProcessed]) {
|
if (this.m_biKnots[this.m_nKnotsProcessed]) {
|
||||||
const d = this.m_gizmo.applyOffsetXY(arc.deriveX1, arc.deriveY1);
|
const d = new Vec2(arc.deriveX1, arc.deriveY1);
|
||||||
if (isTangentValid(d)) {
|
if (isTangentValid(d)) {
|
||||||
this.m_biKnots[this.m_nKnotsProcessed].origTangent = d;
|
this.m_biKnots[this.m_nKnotsProcessed].origTangent = d;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { Vec2 } from "./point.mjs";
|
||||||
|
|
||||||
export class Transform {
|
export class Transform {
|
||||||
constructor(xx, yx, xy, yy, x, y) {
|
constructor(xx, yx, xy, yy, x, y) {
|
||||||
this.xx = xx;
|
this.xx = xx;
|
||||||
|
@ -13,15 +15,24 @@ export class Transform {
|
||||||
static Translate(x, y) {
|
static Translate(x, y) {
|
||||||
return new Transform(1, 0, 0, 1, x, y);
|
return new Transform(1, 0, 0, 1, x, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
applyX(x, y) {
|
||||||
|
return x * this.xx + y * this.yx + this.x;
|
||||||
|
}
|
||||||
|
applyY(x, y) {
|
||||||
|
return x * this.xy + y * this.yy + this.y;
|
||||||
|
}
|
||||||
|
applyXY(x, y) {
|
||||||
|
return new Vec2(this.applyX(x, y), this.applyY(x, y));
|
||||||
|
}
|
||||||
|
applyToSink(pt, sink) {
|
||||||
|
sink.x = this.applyX(pt.x, pt.y);
|
||||||
|
sink.y = this.applyY(pt.x, pt.y);
|
||||||
|
}
|
||||||
apply(pt) {
|
apply(pt) {
|
||||||
return this.applyXY(pt.x, pt.y);
|
return this.applyXY(pt.x, pt.y);
|
||||||
}
|
}
|
||||||
applyXY(x, y) {
|
|
||||||
return {
|
|
||||||
x: x * this.xx + y * this.yx + this.x,
|
|
||||||
y: x * this.xy + y * this.yy + this.y
|
|
||||||
};
|
|
||||||
}
|
|
||||||
applyOffset(delta) {
|
applyOffset(delta) {
|
||||||
return this.applyOffsetXY(delta.x, delta.y);
|
return this.applyOffsetXY(delta.x, delta.y);
|
||||||
}
|
}
|
||||||
|
@ -31,14 +42,18 @@ export class Transform {
|
||||||
y: deltaX * this.xy + deltaY * this.yy
|
y: deltaX * this.xy + deltaY * this.yy
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
unapply(pt) {
|
|
||||||
|
unapplyToSink(pt, sink) {
|
||||||
const xx = pt.x - this.x;
|
const xx = pt.x - this.x;
|
||||||
const yy = pt.y - this.y;
|
const yy = pt.y - this.y;
|
||||||
const denom = this.xx * this.yy - this.xy * this.yx;
|
const denom = this.xx * this.yy - this.xy * this.yx;
|
||||||
return {
|
sink.x = (xx * this.yy - yy * this.yx) / denom;
|
||||||
x: (xx * this.yy - yy * this.yx) / denom,
|
sink.y = (yy * this.xx - xx * this.xy) / denom;
|
||||||
y: (yy * this.xx - xx * this.xy) / denom
|
}
|
||||||
};
|
unapply(pt) {
|
||||||
|
let sink = new Vec2(0, 0);
|
||||||
|
this.unapplyToSink(pt, sink);
|
||||||
|
return sink;
|
||||||
}
|
}
|
||||||
inverse() {
|
inverse() {
|
||||||
const denom = this.xx * this.yy - this.xy * this.yx;
|
const denom = this.xx * this.yy - this.xy * this.yx;
|
||||||
|
@ -51,6 +66,7 @@ export class Transform {
|
||||||
-(-this.x * this.xy + this.y * this.xx) / denom
|
-(-this.x * this.xy + this.y * this.xx) / denom
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
toString() {
|
toString() {
|
||||||
return `[[${this.xx} ${this.xy}] [${this.yx} ${this.yy}]] + [[${this.x}] [${this.y}]]`;
|
return `[[${this.xx} ${this.xy}] [${this.yx} ${this.yy}]] + [[${this.x}] [${this.y}]]`;
|
||||||
}
|
}
|
||||||
|
@ -61,9 +77,9 @@ export class Transform {
|
||||||
return this.isTranslate(tfm) && tfm.x === 0 && tfm.y === 0;
|
return this.isTranslate(tfm) && tfm.x === 0 && tfm.y === 0;
|
||||||
}
|
}
|
||||||
static Combine(...tfms) {
|
static Combine(...tfms) {
|
||||||
let z00 = { x: 0, y: 0 };
|
let z00 = new Vec2(0, 0);
|
||||||
let z10 = { x: 1, y: 0 };
|
let z10 = new Vec2(1, 0);
|
||||||
let z01 = { x: 0, y: 1 };
|
let z01 = new Vec2(0, 1);
|
||||||
for (const tfm of tfms) {
|
for (const tfm of tfms) {
|
||||||
z00 = tfm.apply(z00);
|
z00 = tfm.apply(z00);
|
||||||
z10 = tfm.apply(z10);
|
z10 = tfm.apply(z10);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Anchor } from "../geometry/anchor.mjs";
|
import { Anchor } from "../geometry/anchor.mjs";
|
||||||
import * as Geom from "../geometry/index.mjs";
|
import * as Geom from "../geometry/index.mjs";
|
||||||
import { Point } from "../geometry/point.mjs";
|
import { Point, Vec2 } from "../geometry/point.mjs";
|
||||||
import { Transform } from "../geometry/transform.mjs";
|
import { Transform } from "../geometry/transform.mjs";
|
||||||
|
|
||||||
export class Glyph {
|
export class Glyph {
|
||||||
|
@ -76,7 +76,7 @@ export class Glyph {
|
||||||
includeGlyph(g, copyAnchors, copyWidth) {
|
includeGlyph(g, copyAnchors, copyWidth) {
|
||||||
if (g instanceof Function) throw new Error("Unreachable");
|
if (g instanceof Function) throw new Error("Unreachable");
|
||||||
// Combine anchors and get offset
|
// Combine anchors and get offset
|
||||||
let shift = { x: 0, y: 0 };
|
let shift = new Vec2(0, 0);
|
||||||
this.combineMarks(g, shift);
|
this.combineMarks(g, shift);
|
||||||
this.includeGlyphImpl(g, shift.x, shift.y);
|
this.includeGlyphImpl(g, shift.x, shift.y);
|
||||||
if (g.isMarkSet) throw new Error("Invalid component to be introduced.");
|
if (g.isMarkSet) throw new Error("Invalid component to be introduced.");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue