Optimize the build speed by producing simpler arcs when converting spiro to outline (#2272)
This commit is contained in:
parent
4f2f0d973c
commit
a0c8c9be0b
8 changed files with 154 additions and 40 deletions
8
package-lock.json
generated
8
package-lock.json
generated
|
@ -3670,9 +3670,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/spiro": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/spiro/-/spiro-3.0.0.tgz",
|
||||
"integrity": "sha512-UEhtLWA8fDQuExOKpT3FLa7Rk238G5Bm3wGAxbvnah3H2X6yEL4blIkAsc38wNwMXBwQFRYE6l0Q9X0t1izOxA==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/spiro/-/spiro-3.0.1.tgz",
|
||||
"integrity": "sha512-lqnP2ng7lDrJXWSvO29FZ6zKpAkzCH6F0zmFjxQKFN4DhkoZUQDBWhwv5G8a22iSWGL2pjrbjWusp2eK3Jaj9g==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
}
|
||||
|
@ -4360,7 +4360,7 @@
|
|||
"version": "29.0.6",
|
||||
"dependencies": {
|
||||
"@iosevka/util": "29.0.6",
|
||||
"spiro": "^3.0.0",
|
||||
"spiro": "^3.0.1",
|
||||
"typo-geom": "^0.15.1"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
$$include '../../meta/macros.ptl'
|
||||
|
||||
import [OCCURRENT_PRECISION] from "@iosevka/geometry/curve-util"
|
||||
import [mix linreg clamp fallback] from "@iosevka/util"
|
||||
import [DesignParameters] from "../../meta/aesthetics.mjs"
|
||||
|
||||
|
@ -38,6 +39,8 @@ glyph-block Symbol-Geometric-Plain : for-width-kinds WideWidth1
|
|||
s * Geom.Size * [fallback pp.size 1] - in * sw
|
||||
in * sw
|
||||
|
||||
define [pointsAreNotClose a b] : begin
|
||||
return : [Math.abs (a.x - b.x)] > OCCURRENT_PRECISION || [Math.abs (a.y - b.y)] > OCCURRENT_PRECISION
|
||||
define [ConvexWhitePolygonImpl fn props] : begin
|
||||
local pp : fallback props {.}
|
||||
local sh : new-glyph : fn
|
||||
|
@ -50,7 +53,7 @@ glyph-block Symbol-Geometric-Plain : for-width-kinds WideWidth1
|
|||
foreach c [items-of : sh.geometry.toContours] : foreach j [range 0 c.length] : begin
|
||||
local a c.[if j (j - 1) (c.length - 1)]
|
||||
local b c.(j)
|
||||
include : dispiro
|
||||
if [pointsAreNotClose a b] : include : dispiro
|
||||
disable-contrast
|
||||
widths.center ([fallback pp.sw GeometryStroke] * 2)
|
||||
corner [mix a.x b.x (-2)] [mix a.y b.y (-2)]
|
||||
|
|
|
@ -21,7 +21,8 @@ function regulateGlyphStore(cache, skew, glyphStore) {
|
|||
function flattenSimpleGlyph(cache, skew, g) {
|
||||
try {
|
||||
let gSimplified;
|
||||
if (skew) {
|
||||
const needsTransform = g.gizmo ? !Transform.isTranslate(g.gizmo) : skew != 0;
|
||||
if (needsTransform) {
|
||||
const tfBack = g.gizmo ? g.gizmo.inverse() : new Transform(1, -skew, 0, 1, 0, 0);
|
||||
const tfForward = g.gizmo ? g.gizmo : new Transform(1, +skew, 0, 1, 0, 0);
|
||||
gSimplified = new Geom.TransformedGeometry(
|
||||
|
|
|
@ -1,20 +1,20 @@
|
|||
{
|
||||
"name": "@iosevka/geometry",
|
||||
"version": "29.0.6",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.mjs",
|
||||
"./anchor": "./src/anchor.mjs",
|
||||
"./box": "./src/box.mjs",
|
||||
"./segment": "./src/segment.mjs",
|
||||
"./curve-util": "./src/curve-util.mjs",
|
||||
"./point": "./src/point.mjs",
|
||||
"./transform": "./src/transform.mjs",
|
||||
"./spiro-control": "./src/spiro-control.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iosevka/util": "29.0.6",
|
||||
"spiro": "^3.0.0",
|
||||
"typo-geom": "^0.15.1"
|
||||
}
|
||||
"name": "@iosevka/geometry",
|
||||
"version": "29.0.6",
|
||||
"private": true,
|
||||
"exports": {
|
||||
".": "./src/index.mjs",
|
||||
"./anchor": "./src/anchor.mjs",
|
||||
"./box": "./src/box.mjs",
|
||||
"./segment": "./src/segment.mjs",
|
||||
"./curve-util": "./src/curve-util.mjs",
|
||||
"./point": "./src/point.mjs",
|
||||
"./transform": "./src/transform.mjs",
|
||||
"./spiro-control": "./src/spiro-control.mjs"
|
||||
},
|
||||
"dependencies": {
|
||||
"@iosevka/util": "29.0.6",
|
||||
"spiro": "^3.0.1",
|
||||
"typo-geom": "^0.15.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import crypto from "crypto";
|
||||
|
||||
import * as Format from "@iosevka/util/formatter";
|
||||
import * as TypoGeom from "typo-geom";
|
||||
|
||||
|
@ -7,7 +5,7 @@ import * as CurveUtil from "./curve-util.mjs";
|
|||
import { Point } from "./point.mjs";
|
||||
import { QuadifySink } from "./quadify.mjs";
|
||||
import { SpiroExpander } from "./spiro-expand.mjs";
|
||||
import { spiroToOutline } from "./spiro-to-outline.mjs";
|
||||
import { spiroToOutlineWithSimplification } from "./spiro-to-outline.mjs";
|
||||
import { strokeArcs } from "./stroke.mjs";
|
||||
import { Transform } from "./transform.mjs";
|
||||
|
||||
|
@ -114,7 +112,7 @@ export class SpiroGeometry extends CachedGeometry {
|
|||
this.m_gizmo = gizmo;
|
||||
}
|
||||
toContoursImpl() {
|
||||
return spiroToOutline(this.m_knots, this.m_closed, this.m_gizmo);
|
||||
return spiroToOutlineWithSimplification(this.m_knots, this.m_closed, this.m_gizmo);
|
||||
}
|
||||
toReferences() {
|
||||
return null;
|
||||
|
@ -182,10 +180,10 @@ export class DiSpiroGeometry extends CachedGeometry {
|
|||
this.m_biKnots,
|
||||
);
|
||||
expander.initializeNormals();
|
||||
expander.iterateNormals();
|
||||
expander.iterateNormals();
|
||||
expander.iterateNormals();
|
||||
expander.iterateNormals();
|
||||
for (let r = 0; r < 8; r++) {
|
||||
let d = expander.iterateNormals();
|
||||
if (d < 1e-8) break;
|
||||
}
|
||||
return expander.expand();
|
||||
}
|
||||
toReferences() {
|
||||
|
|
|
@ -9,6 +9,10 @@ export class Vec2 {
|
|||
static from(z) {
|
||||
return new Vec2(z.x, z.y);
|
||||
}
|
||||
|
||||
static scaleFrom(s, z) {
|
||||
return new Vec2(s * z.x, s * z.y);
|
||||
}
|
||||
}
|
||||
|
||||
export class Point {
|
||||
|
|
|
@ -23,6 +23,7 @@ export class SpiroExpander {
|
|||
const centerBone = this.getPass2Knots();
|
||||
const normalRectifier = new NormalRectifier(this.m_biKnotsT, this.m_gizmo);
|
||||
SpiroJs.spiroToArcsOnContext(centerBone, this.m_closed, normalRectifier);
|
||||
return normalRectifier.totalDelta / normalRectifier.nKnotsProcessed;
|
||||
}
|
||||
getPass2Knots() {
|
||||
const expanded = this.expand(this.m_contrast);
|
||||
|
@ -105,36 +106,49 @@ class NormalRectifier {
|
|||
constructor(stage1ControlKnots, gizmo) {
|
||||
this.m_gizmo = gizmo;
|
||||
this.m_biKnots = stage1ControlKnots;
|
||||
this.m_nKnotsProcessed = 0;
|
||||
|
||||
this.nKnotsProcessed = 0;
|
||||
this.totalDelta = 0;
|
||||
}
|
||||
beginShape() {}
|
||||
endShape() {}
|
||||
moveTo(x, y) {
|
||||
this.m_nKnotsProcessed += 1;
|
||||
this.nKnotsProcessed += 1;
|
||||
}
|
||||
arcTo(arc, x, y) {
|
||||
if (this.m_nKnotsProcessed === 1) {
|
||||
if (this.nKnotsProcessed === 1) {
|
||||
const d = new Vec2(arc.deriveX0, arc.deriveY0);
|
||||
if (isTangentValid(d)) {
|
||||
this.m_biKnots[0].origTangent = d;
|
||||
this.updateKnotTangent(this.m_biKnots[0], d);
|
||||
} else {
|
||||
throw new Error("NaN angle detected.");
|
||||
}
|
||||
}
|
||||
if (this.m_biKnots[this.m_nKnotsProcessed]) {
|
||||
if (this.m_biKnots[this.nKnotsProcessed]) {
|
||||
const d = new Vec2(arc.deriveX1, arc.deriveY1);
|
||||
if (isTangentValid(d)) {
|
||||
this.m_biKnots[this.m_nKnotsProcessed].origTangent = d;
|
||||
this.updateKnotTangent(this.m_biKnots[this.nKnotsProcessed], d);
|
||||
} else {
|
||||
throw new Error("NaN angle detected.");
|
||||
}
|
||||
}
|
||||
this.m_nKnotsProcessed += 1;
|
||||
this.nKnotsProcessed += 1;
|
||||
}
|
||||
|
||||
updateKnotTangent(knot, d) {
|
||||
if (isTangentValid(knot.origTangent)) {
|
||||
this.totalDelta +=
|
||||
(d.x - knot.origTangent.x) * (d.x - knot.origTangent.x) +
|
||||
(d.y - knot.origTangent.y) * (d.y - knot.origTangent.y);
|
||||
} else {
|
||||
this.totalDelta += 4;
|
||||
}
|
||||
knot.origTangent = d;
|
||||
}
|
||||
}
|
||||
|
||||
function isTangentValid(d) {
|
||||
return isFinite(d.x) && isFinite(d.y);
|
||||
return d && isFinite(d.x) && isFinite(d.y);
|
||||
}
|
||||
|
||||
function normalX(tangent, contrast) {
|
||||
|
|
|
@ -1,9 +1,103 @@
|
|||
import * as SpiroJs from "spiro";
|
||||
import * as TypoGeom from "typo-geom";
|
||||
|
||||
import * as CurveUtil from "./curve-util.mjs";
|
||||
import { Vec2 } from "./point.mjs";
|
||||
|
||||
export function spiroToOutline(knots, fClosed, gizmo) {
|
||||
const s = new CurveUtil.BezToContoursSink(gizmo);
|
||||
SpiroJs.spiroToBezierOnContext(knots, fClosed, s, CurveUtil.GEOMETRY_PRECISION);
|
||||
return s.contours;
|
||||
}
|
||||
|
||||
export function spiroToOutlineWithSimplification(knots, fClosed, gizmo) {
|
||||
const simplifier = new SpiroSimplifier(knots);
|
||||
SpiroJs.spiroToArcsOnContext(knots, fClosed, simplifier);
|
||||
const sink = new CurveUtil.BezToContoursSink(gizmo);
|
||||
TypoGeom.ShapeConv.transferGenericShapeAsBezier(
|
||||
[simplifier.combinedArcs],
|
||||
sink,
|
||||
CurveUtil.GEOMETRY_PRECISION,
|
||||
);
|
||||
return sink.contours;
|
||||
}
|
||||
|
||||
class SpiroSimplifier {
|
||||
constructor(knots) {
|
||||
this.m_knots = knots;
|
||||
this.m_ongoingArcs = [];
|
||||
this.m_nKnotsProcessed = 0;
|
||||
|
||||
this.combinedArcs = [];
|
||||
}
|
||||
beginShape() {}
|
||||
endShape() {
|
||||
this.flushArcs();
|
||||
}
|
||||
moveTo(x, y) {
|
||||
this.m_nKnotsProcessed += 1;
|
||||
}
|
||||
arcTo(arc) {
|
||||
this.m_ongoingArcs.push(arc);
|
||||
if (
|
||||
this.m_knots[this.m_nKnotsProcessed] &&
|
||||
!this.m_knots[this.m_nKnotsProcessed].unimportant
|
||||
) {
|
||||
this.flushArcs();
|
||||
}
|
||||
this.m_nKnotsProcessed += 1;
|
||||
}
|
||||
flushArcs() {
|
||||
if (!this.m_ongoingArcs.length) return;
|
||||
if (this.m_ongoingArcs.length === 1) {
|
||||
this.combinedArcs.push(this.m_ongoingArcs[0]);
|
||||
} else {
|
||||
this.combinedArcs.push(new SpiroSequenceArc(this.m_ongoingArcs));
|
||||
}
|
||||
this.m_ongoingArcs = [];
|
||||
}
|
||||
}
|
||||
|
||||
class SpiroSequenceArc {
|
||||
constructor(segments) {
|
||||
let totalLength = 0;
|
||||
let stops = [];
|
||||
for (let j = 0; j < segments.length; j++) {
|
||||
stops[j] = totalLength;
|
||||
totalLength += segments[j].arcLength;
|
||||
}
|
||||
for (let j = 0; j < segments.length; j++) {
|
||||
stops[j] = stops[j] / totalLength;
|
||||
}
|
||||
this.m_segments = segments;
|
||||
this.m_stops = stops;
|
||||
}
|
||||
|
||||
eval(t) {
|
||||
const j = segTSearch(this.m_stops, t);
|
||||
const tBefore = this.m_stops[j];
|
||||
const tNext = j < this.m_stops.length - 1 ? this.m_stops[j + 1] : 1;
|
||||
const tRelative = (t - tBefore) / (tNext - tBefore);
|
||||
return this.m_segments[j].eval(tRelative);
|
||||
}
|
||||
|
||||
derivative(t) {
|
||||
const j = segTSearch(this.m_stops, t);
|
||||
const tBefore = this.m_stops[j];
|
||||
const tNext = j < this.m_stops.length - 1 ? this.m_stops[j + 1] : 1;
|
||||
const tRelative = (t - tBefore) / (tNext - tBefore);
|
||||
return Vec2.scaleFrom(1 / (tNext - tBefore), this.m_segments[j].derivative(tRelative));
|
||||
}
|
||||
}
|
||||
|
||||
function segTSearch(stops, t) {
|
||||
if (t < 0) return 0;
|
||||
let l = 0,
|
||||
r = stops.length;
|
||||
while (l < r) {
|
||||
let m = (l + r) >>> 1;
|
||||
if (stops[m] > t) r = m;
|
||||
else l = m + 1;
|
||||
}
|
||||
return r - 1;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue