Make dispiro results cachable
This commit is contained in:
parent
f892a56532
commit
7bf55f2682
6 changed files with 211 additions and 73 deletions
|
@ -4,7 +4,7 @@ const fs = require("fs-extra");
|
|||
const zlib = require("zlib");
|
||||
const { encode, decode } = require("@msgpack/msgpack");
|
||||
|
||||
const Edition = 10;
|
||||
const Edition = 11;
|
||||
const MAX_AGE = 5;
|
||||
|
||||
class GfEntry {
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
const SpiroJs = require("spiro");
|
||||
const CurveUtil = require("../support/curve-util");
|
||||
const Transform = require("../support/transform");
|
||||
const { SpiroExpansionContext1, SpiroExpansionContext2 } = require("../support/spiro-expand");
|
||||
const { BiKnotCollector } = require("../support/spiro-expand");
|
||||
const { fallback, mix, bez2, bez3 } = require("../support/utils");
|
||||
const { SpiroGeometry, CombineGeometry } = require("../support/geometry");
|
||||
const { SpiroGeometry, DiSpiroGeometry, CombineGeometry } = require("../support/geometry");
|
||||
|
||||
exports.SetupBuilders = function (bindings) {
|
||||
const { Contrast, GlobalTransform, Stroke, Superness } = bindings;
|
||||
|
@ -279,15 +279,34 @@ exports.SetupBuilders = function (bindings) {
|
|||
knots = flatten(s, knots);
|
||||
return { knots, closed };
|
||||
}
|
||||
function iterateNormals(s, closed) {
|
||||
let knotsP2 = s.getPass2Knots(closed, fallback(s.contrast, Contrast));
|
||||
let s2 = new SpiroExpansionContext2(s.controlKnots, s.gizmo);
|
||||
return SpiroJs.spiroToArcsOnContext(knotsP2, closed, s2);
|
||||
|
||||
class DiSpiroProxy {
|
||||
constructor(closed, collector, origKnots) {
|
||||
this.geometry = new DiSpiroGeometry(
|
||||
collector.gizmo,
|
||||
collector.contrast,
|
||||
closed,
|
||||
collector.controlKnots
|
||||
);
|
||||
this.m_origKnots = origKnots;
|
||||
}
|
||||
|
||||
get knots() {
|
||||
return this.m_origKnots;
|
||||
}
|
||||
get lhsKnots() {
|
||||
return this.geometry.expand().lhs;
|
||||
}
|
||||
get rhsKnots() {
|
||||
return this.geometry.expand().rhs;
|
||||
}
|
||||
}
|
||||
|
||||
function dispiro(...args) {
|
||||
return function () {
|
||||
let s = new SpiroExpansionContext1(this.gizmo || GlobalTransform);
|
||||
let { knots, closed } = prepareSpiroKnots([].slice.call(args, 0), s);
|
||||
const gizmo = this.gizmo || GlobalTransform;
|
||||
const collector = new BiKnotCollector(gizmo, Contrast);
|
||||
const { knots, closed } = prepareSpiroKnots([].slice.call(args, 0), collector);
|
||||
for (const knot of knots) {
|
||||
const ty = knot.type;
|
||||
const af = knot.af;
|
||||
|
@ -296,26 +315,10 @@ exports.SetupBuilders = function (bindings) {
|
|||
return af ? af.apply(this, args) : void 0;
|
||||
};
|
||||
}
|
||||
|
||||
SpiroJs.spiroToArcsOnContext(knots, closed, s);
|
||||
iterateNormals(s, closed);
|
||||
iterateNormals(s, closed);
|
||||
const { lhs, rhs } = s.expand(fallback(s.contrast, Contrast));
|
||||
|
||||
if (closed) {
|
||||
this.includeGeometry(
|
||||
new CombineGeometry([
|
||||
new SpiroGeometry(lhs.slice(0, -1), closed, Transform.Id()),
|
||||
new SpiroGeometry(rhs.reverse().slice(0, -1), closed, Transform.Id())
|
||||
])
|
||||
);
|
||||
} else {
|
||||
lhs[0].type = lhs[lhs.length - 1].type = "corner";
|
||||
rhs[0].type = rhs[rhs.length - 1].type = "corner";
|
||||
const allKnots = lhs.concat(rhs.reverse());
|
||||
this.includeGeometry(new SpiroGeometry(allKnots, true, Transform.Id()));
|
||||
}
|
||||
return { knots, lhsKnots: lhs, rhsKnots: rhs };
|
||||
SpiroJs.spiroToArcsOnContext(knots, closed, collector);
|
||||
const dsp = new DiSpiroProxy(closed, collector, knots);
|
||||
this.includeGeometry(dsp.geometry);
|
||||
return dsp;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -324,7 +327,7 @@ exports.SetupBuilders = function (bindings) {
|
|||
const gizmo = this.gizmo || GlobalTransform;
|
||||
const g = new CurveUtil.BezToContoursSink(gizmo);
|
||||
const { knots, closed } = prepareSpiroKnots(args, g);
|
||||
return this.includeGeometry(new SpiroGeometry(knots, closed, gizmo));
|
||||
return this.includeGeometry(new SpiroGeometry(gizmo, closed, knots));
|
||||
};
|
||||
}
|
||||
return {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"use strict";
|
||||
|
||||
const { mix } = require("./utils");
|
||||
|
||||
class Box {
|
||||
|
|
29
font-src/support/formatter.js
Normal file
29
font-src/support/formatter.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
"use strict";
|
||||
|
||||
function struct(leader, ...items) {
|
||||
return "" + leader + "(" + items.join(";") + ")";
|
||||
}
|
||||
function tuple(...items) {
|
||||
return "(" + items.join(";") + ")";
|
||||
}
|
||||
function list(items) {
|
||||
return "{" + items.join(";") + "}";
|
||||
}
|
||||
|
||||
function n(x) {
|
||||
return String(Math.round(x * 0x10000));
|
||||
}
|
||||
|
||||
function typedPoint(z) {
|
||||
return tuple(z.type, n(z.x), n(z.y));
|
||||
}
|
||||
function gizmo(g) {
|
||||
return tuple(n(g.xx), n(g.xy), n(g.yx), n(g.yy), n(g.x), n(g.y));
|
||||
}
|
||||
|
||||
exports.struct = struct;
|
||||
exports.tuple = tuple;
|
||||
exports.list = list;
|
||||
exports.n = n;
|
||||
exports.typedPoint = typedPoint;
|
||||
exports.gizmo = gizmo;
|
|
@ -7,6 +7,8 @@ const SpiroJs = require("spiro");
|
|||
const Point = require("./point");
|
||||
const Transform = require("./transform");
|
||||
const CurveUtil = require("./curve-util");
|
||||
const { SpiroExpander } = require("./spiro-expand");
|
||||
const Format = require("./formatter");
|
||||
|
||||
class GeometryBase {
|
||||
asContours() {
|
||||
|
@ -62,17 +64,12 @@ class ContourGeometry extends GeometryBase {
|
|||
return this.m_points.length;
|
||||
}
|
||||
toShapeStringOrNull() {
|
||||
let s = "ContourGeometry{";
|
||||
for (const z of this.m_points) {
|
||||
s += `(${z.type};${formatN(z.x)};${formatN(z.y)})`;
|
||||
}
|
||||
s += "}";
|
||||
return s;
|
||||
return Format.struct(`ContourGeometry`, Format.list(this.m_points.map(Format.typedPoint)));
|
||||
}
|
||||
}
|
||||
|
||||
class SpiroGeometry extends GeometryBase {
|
||||
constructor(knots, closed, gizmo) {
|
||||
constructor(gizmo, closed, knots) {
|
||||
super();
|
||||
this.m_knots = [];
|
||||
for (const k of knots) {
|
||||
|
@ -105,18 +102,86 @@ class SpiroGeometry extends GeometryBase {
|
|||
return this.m_knots.length;
|
||||
}
|
||||
toShapeStringOrNull() {
|
||||
let s = "SpiroGeometry{{";
|
||||
for (const k of this.m_knots) {
|
||||
s += `(${k.type};${formatN(k.x)};${formatN(k.y)})`;
|
||||
return Format.struct(
|
||||
"SpiroGeometry",
|
||||
Format.gizmo(this.m_gizmo),
|
||||
this.m_closed,
|
||||
Format.list(this.m_knots.map(Format.typedPoint))
|
||||
);
|
||||
}
|
||||
s += "};";
|
||||
s += `${this.m_closed};`;
|
||||
s +=
|
||||
`;{${formatN(this.m_gizmo.xx)},${formatN(this.m_gizmo.xy)},` +
|
||||
`${formatN(this.m_gizmo.yx)},${formatN(this.m_gizmo.yy)},` +
|
||||
`${formatN(this.m_gizmo.x)},${formatN(this.m_gizmo.y)}}`;
|
||||
s += "}";
|
||||
return s;
|
||||
}
|
||||
|
||||
class DiSpiroGeometry extends GeometryBase {
|
||||
constructor(gizmo, contrast, closed, biKnots) {
|
||||
super();
|
||||
|
||||
this.m_biKnots = [];
|
||||
for (const k of biKnots) this.m_biKnots.push(k.clone());
|
||||
|
||||
this.m_closed = closed;
|
||||
this.m_gizmo = gizmo;
|
||||
this.m_contrast = contrast;
|
||||
|
||||
this.m_cachedExpansionResults = null;
|
||||
this.m_cachedContours = null;
|
||||
}
|
||||
|
||||
asContours() {
|
||||
if (this.m_cachedContours) return this.m_cachedContours;
|
||||
const { lhs, rhs } = this.expand();
|
||||
|
||||
let rawGeometry;
|
||||
if (this.m_closed) {
|
||||
rawGeometry = new CombineGeometry([
|
||||
new SpiroGeometry(Transform.Id(), this.m_closed, lhs.slice(0, -1)),
|
||||
new SpiroGeometry(Transform.Id(), this.m_closed, rhs.reverse().slice(0, -1))
|
||||
]);
|
||||
} else {
|
||||
lhs[0].type = lhs[lhs.length - 1].type = "corner";
|
||||
rhs[0].type = rhs[rhs.length - 1].type = "corner";
|
||||
const allKnots = lhs.concat(rhs.reverse());
|
||||
rawGeometry = new SpiroGeometry(Transform.Id(), true, allKnots);
|
||||
}
|
||||
this.m_cachedContours = rawGeometry.asContours();
|
||||
return this.m_cachedContours;
|
||||
}
|
||||
expand() {
|
||||
if (this.m_cachedExpansionResults) return this.m_cachedExpansionResults;
|
||||
const expander = new SpiroExpander(
|
||||
this.m_gizmo,
|
||||
this.m_contrast,
|
||||
this.m_closed,
|
||||
this.m_biKnots.map(k => k.clone())
|
||||
);
|
||||
expander.iterateNormals();
|
||||
expander.iterateNormals();
|
||||
this.m_cachedExpansionResults = expander.expand();
|
||||
return this.m_cachedExpansionResults;
|
||||
}
|
||||
|
||||
asReferences() {
|
||||
return null;
|
||||
}
|
||||
filterTag(fn) {
|
||||
return this;
|
||||
}
|
||||
isEmpty() {
|
||||
return !this.m_biKnots.length;
|
||||
}
|
||||
measureComplexity() {
|
||||
for (const z of this.m_biKnots) {
|
||||
if (!isFinite(z.x) || !isFinite(z.y)) return 0xffff;
|
||||
}
|
||||
return this.m_biKnots.length;
|
||||
}
|
||||
|
||||
toShapeStringOrNull() {
|
||||
return Format.struct(
|
||||
Format.gizmo(this.m_gizmo),
|
||||
Format.n(this.m_contrast),
|
||||
this.m_closed,
|
||||
Format.list(this.m_biKnots.map(z => z.toShapeString()))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,7 +224,7 @@ class ReferenceGeometry extends GeometryBase {
|
|||
toShapeStringOrNull() {
|
||||
let sTarget = this.m_glyph.geometry.toShapeStringOrNull();
|
||||
if (!sTarget) return null;
|
||||
return `ReferenceGeometry{${sTarget};${formatN(this.m_x)};${formatN(this.m_y)}}`;
|
||||
return Format.struct("ReferenceGeometry", sTarget, Format.n(this.m_x), Format.n(this.m_y));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -252,13 +317,7 @@ class TransformedGeometry extends GeometryBase {
|
|||
toShapeStringOrNull() {
|
||||
const sTarget = this.m_geom.toShapeStringOrNull();
|
||||
if (!sTarget) return null;
|
||||
return (
|
||||
`TransformedGeometry{${sTarget};` +
|
||||
`${formatN(this.m_transform.xx)},${formatN(this.m_transform.xy)},` +
|
||||
`${formatN(this.m_transform.yx)},${formatN(this.m_transform.yy)},` +
|
||||
`${formatN(this.m_transform.x)},${formatN(this.m_transform.y)}` +
|
||||
`}`
|
||||
);
|
||||
return Format.struct(TransformedGeometry, sTarget, Format.gizmo(this.m_transform));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -330,7 +389,7 @@ class CombineGeometry extends GeometryBase {
|
|||
if (!sPart) return null;
|
||||
sParts.push(sPart);
|
||||
}
|
||||
return `CombineGeometry{${sParts.join(",")}}`;
|
||||
return Format.struct("CombineGeometry", Format.list(sParts));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -407,7 +466,7 @@ class BooleanGeometry extends GeometryBase {
|
|||
if (!sPart) return null;
|
||||
sParts.push(sPart);
|
||||
}
|
||||
return `BooleanGeometry{${this.m_operator};${sParts.join(",")}}`;
|
||||
return Format.struct("BooleanGeometry", this.m_operator, Format.list(sParts));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -425,12 +484,9 @@ function combineWith(a, b) {
|
|||
}
|
||||
}
|
||||
|
||||
function formatN(x) {
|
||||
return `${Math.round(x * 0x10000)}`;
|
||||
}
|
||||
|
||||
exports.GeometryBase = GeometryBase;
|
||||
exports.SpiroGeometry = SpiroGeometry;
|
||||
exports.DiSpiroGeometry = DiSpiroGeometry;
|
||||
exports.ContourGeometry = ContourGeometry;
|
||||
exports.ReferenceGeometry = ReferenceGeometry;
|
||||
exports.TaggedGeometry = TaggedGeometry;
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
"use strict";
|
||||
|
||||
const SpiroJs = require("spiro");
|
||||
|
||||
const { linreg } = require("./utils");
|
||||
const Format = require("./formatter");
|
||||
|
||||
class BiKnot {
|
||||
constructor(type, x, y, d1, d2) {
|
||||
|
@ -11,15 +16,42 @@ class BiKnot {
|
|||
this.proposedNormal = null;
|
||||
this.unimportant = 0;
|
||||
}
|
||||
clone() {
|
||||
const k1 = new BiKnot(this.type, this.x, this.y, this.d1, this.d2);
|
||||
k1.origTangent = this.origTangent;
|
||||
k1.proposedNormal = this.proposedNormal;
|
||||
k1.unimportant = this.unimportant;
|
||||
return k1;
|
||||
}
|
||||
|
||||
class SpiroExpansionContext1 {
|
||||
constructor(gizmo) {
|
||||
toShapeString() {
|
||||
return Format.tuple(
|
||||
this.type,
|
||||
Format.n(this.x),
|
||||
Format.n(this.y),
|
||||
Format.n(this.d1),
|
||||
Format.n(this.d2),
|
||||
this.origTangent
|
||||
? Format.tuple(Format.n(this.origTangent.x), Format.n(this.origTangent.y))
|
||||
: "",
|
||||
this.proposedNormal
|
||||
? Format.tuple(Format.n(this.proposedNormal.x), Format.n(this.proposedNormal.y))
|
||||
: "",
|
||||
this.unimportant
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class BiKnotCollector {
|
||||
constructor(gizmo, contrast) {
|
||||
this.gizmo = gizmo;
|
||||
this.contrast = contrast;
|
||||
|
||||
this.controlKnots = [];
|
||||
this.defaultD1 = 0;
|
||||
this.defaultD2 = 0;
|
||||
}
|
||||
|
||||
beginShape() {}
|
||||
endShape() {}
|
||||
moveTo(x, y, unimportant) {
|
||||
|
@ -63,11 +95,26 @@ class SpiroExpansionContext1 {
|
|||
const k0 = this.controlKnots[this.controlKnots.length - 1];
|
||||
if (k0) k0.unimportant = 1;
|
||||
}
|
||||
}
|
||||
|
||||
getPass2Knots(closed, contrast) {
|
||||
const expanded = this.expand(contrast);
|
||||
class SpiroExpander {
|
||||
constructor(gizmo, contrast, closed, cks) {
|
||||
this.gizmo = gizmo;
|
||||
this.contrast = contrast;
|
||||
this.closed = closed;
|
||||
this.controlKnots = cks;
|
||||
}
|
||||
|
||||
iterateNormals() {
|
||||
const centerBone = this.getPass2Knots();
|
||||
const normalRectifier = new NormalRectifier(this.controlKnots, this.gizmo);
|
||||
SpiroJs.spiroToArcsOnContext(centerBone, this.closed, normalRectifier);
|
||||
}
|
||||
|
||||
getPass2Knots() {
|
||||
const expanded = this.expand(this.contrast);
|
||||
const middles = [];
|
||||
for (let j = 0; j + (closed ? 1 : 0) < this.controlKnots.length; j++) {
|
||||
for (let j = 0; j + (this.closed ? 1 : 0) < this.controlKnots.length; j++) {
|
||||
const lhs = this.gizmo.unapply(expanded.lhs[j]);
|
||||
const rhs = this.gizmo.unapply(expanded.rhs[j]);
|
||||
middles[j] = {
|
||||
|
@ -79,7 +126,8 @@ class SpiroExpansionContext1 {
|
|||
}
|
||||
return middles;
|
||||
}
|
||||
expand(contrast) {
|
||||
|
||||
expand() {
|
||||
const lhs = [],
|
||||
rhs = [];
|
||||
|
||||
|
@ -93,8 +141,8 @@ class SpiroExpansionContext1 {
|
|||
dx = knot.proposedNormal.x;
|
||||
dy = knot.proposedNormal.y;
|
||||
} else {
|
||||
dx = normalX(knot.origTangent, contrast);
|
||||
dy = normalY(knot.origTangent, contrast);
|
||||
dx = normalX(knot.origTangent, this.contrast);
|
||||
dy = normalY(knot.origTangent, this.contrast);
|
||||
}
|
||||
lhs[j] = {
|
||||
type: knot.type,
|
||||
|
@ -146,7 +194,7 @@ class SpiroExpansionContext1 {
|
|||
}
|
||||
}
|
||||
|
||||
class SpiroExpansionContext2 {
|
||||
class NormalRectifier {
|
||||
constructor(stage1ControlKnots, gizmo) {
|
||||
this.gizmo = gizmo;
|
||||
this.controlKnots = stage1ControlKnots;
|
||||
|
@ -191,5 +239,5 @@ function computeNormalAngle(gizmo, x, y) {
|
|||
return Math.PI / 2 + Math.atan2(tfd.y, tfd.x);
|
||||
}
|
||||
|
||||
exports.SpiroExpansionContext1 = SpiroExpansionContext1;
|
||||
exports.SpiroExpansionContext2 = SpiroExpansionContext2;
|
||||
exports.BiKnotCollector = BiKnotCollector;
|
||||
exports.SpiroExpander = SpiroExpander;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue