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": {
|
"node_modules/spiro": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/spiro/-/spiro-3.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/spiro/-/spiro-3.0.1.tgz",
|
||||||
"integrity": "sha512-UEhtLWA8fDQuExOKpT3FLa7Rk238G5Bm3wGAxbvnah3H2X6yEL4blIkAsc38wNwMXBwQFRYE6l0Q9X0t1izOxA==",
|
"integrity": "sha512-lqnP2ng7lDrJXWSvO29FZ6zKpAkzCH6F0zmFjxQKFN4DhkoZUQDBWhwv5G8a22iSWGL2pjrbjWusp2eK3Jaj9g==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
}
|
}
|
||||||
|
@ -4360,7 +4360,7 @@
|
||||||
"version": "29.0.6",
|
"version": "29.0.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iosevka/util": "29.0.6",
|
"@iosevka/util": "29.0.6",
|
||||||
"spiro": "^3.0.0",
|
"spiro": "^3.0.1",
|
||||||
"typo-geom": "^0.15.1"
|
"typo-geom": "^0.15.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
$$include '../../meta/macros.ptl'
|
$$include '../../meta/macros.ptl'
|
||||||
|
|
||||||
|
import [OCCURRENT_PRECISION] from "@iosevka/geometry/curve-util"
|
||||||
import [mix linreg clamp fallback] from "@iosevka/util"
|
import [mix linreg clamp fallback] from "@iosevka/util"
|
||||||
import [DesignParameters] from "../../meta/aesthetics.mjs"
|
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
|
s * Geom.Size * [fallback pp.size 1] - in * sw
|
||||||
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
|
define [ConvexWhitePolygonImpl fn props] : begin
|
||||||
local pp : fallback props {.}
|
local pp : fallback props {.}
|
||||||
local sh : new-glyph : fn
|
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
|
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 a c.[if j (j - 1) (c.length - 1)]
|
||||||
local b c.(j)
|
local b c.(j)
|
||||||
include : dispiro
|
if [pointsAreNotClose a b] : include : dispiro
|
||||||
disable-contrast
|
disable-contrast
|
||||||
widths.center ([fallback pp.sw GeometryStroke] * 2)
|
widths.center ([fallback pp.sw GeometryStroke] * 2)
|
||||||
corner [mix a.x b.x (-2)] [mix a.y b.y (-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) {
|
function flattenSimpleGlyph(cache, skew, g) {
|
||||||
try {
|
try {
|
||||||
let gSimplified;
|
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 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);
|
const tfForward = g.gizmo ? g.gizmo : new Transform(1, +skew, 0, 1, 0, 0);
|
||||||
gSimplified = new Geom.TransformedGeometry(
|
gSimplified = new Geom.TransformedGeometry(
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
{
|
{
|
||||||
"name": "@iosevka/geometry",
|
"name": "@iosevka/geometry",
|
||||||
"version": "29.0.6",
|
"version": "29.0.6",
|
||||||
"private": true,
|
"private": true,
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./src/index.mjs",
|
".": "./src/index.mjs",
|
||||||
"./anchor": "./src/anchor.mjs",
|
"./anchor": "./src/anchor.mjs",
|
||||||
"./box": "./src/box.mjs",
|
"./box": "./src/box.mjs",
|
||||||
"./segment": "./src/segment.mjs",
|
"./segment": "./src/segment.mjs",
|
||||||
"./curve-util": "./src/curve-util.mjs",
|
"./curve-util": "./src/curve-util.mjs",
|
||||||
"./point": "./src/point.mjs",
|
"./point": "./src/point.mjs",
|
||||||
"./transform": "./src/transform.mjs",
|
"./transform": "./src/transform.mjs",
|
||||||
"./spiro-control": "./src/spiro-control.mjs"
|
"./spiro-control": "./src/spiro-control.mjs"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iosevka/util": "29.0.6",
|
"@iosevka/util": "29.0.6",
|
||||||
"spiro": "^3.0.0",
|
"spiro": "^3.0.1",
|
||||||
"typo-geom": "^0.15.1"
|
"typo-geom": "^0.15.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
import crypto from "crypto";
|
|
||||||
|
|
||||||
import * as Format from "@iosevka/util/formatter";
|
import * as Format from "@iosevka/util/formatter";
|
||||||
import * as TypoGeom from "typo-geom";
|
import * as TypoGeom from "typo-geom";
|
||||||
|
|
||||||
|
@ -7,7 +5,7 @@ import * as CurveUtil from "./curve-util.mjs";
|
||||||
import { Point } from "./point.mjs";
|
import { Point } from "./point.mjs";
|
||||||
import { QuadifySink } from "./quadify.mjs";
|
import { QuadifySink } from "./quadify.mjs";
|
||||||
import { SpiroExpander } from "./spiro-expand.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 { strokeArcs } from "./stroke.mjs";
|
||||||
import { Transform } from "./transform.mjs";
|
import { Transform } from "./transform.mjs";
|
||||||
|
|
||||||
|
@ -114,7 +112,7 @@ export class SpiroGeometry extends CachedGeometry {
|
||||||
this.m_gizmo = gizmo;
|
this.m_gizmo = gizmo;
|
||||||
}
|
}
|
||||||
toContoursImpl() {
|
toContoursImpl() {
|
||||||
return spiroToOutline(this.m_knots, this.m_closed, this.m_gizmo);
|
return spiroToOutlineWithSimplification(this.m_knots, this.m_closed, this.m_gizmo);
|
||||||
}
|
}
|
||||||
toReferences() {
|
toReferences() {
|
||||||
return null;
|
return null;
|
||||||
|
@ -182,10 +180,10 @@ export class DiSpiroGeometry extends CachedGeometry {
|
||||||
this.m_biKnots,
|
this.m_biKnots,
|
||||||
);
|
);
|
||||||
expander.initializeNormals();
|
expander.initializeNormals();
|
||||||
expander.iterateNormals();
|
for (let r = 0; r < 8; r++) {
|
||||||
expander.iterateNormals();
|
let d = expander.iterateNormals();
|
||||||
expander.iterateNormals();
|
if (d < 1e-8) break;
|
||||||
expander.iterateNormals();
|
}
|
||||||
return expander.expand();
|
return expander.expand();
|
||||||
}
|
}
|
||||||
toReferences() {
|
toReferences() {
|
||||||
|
|
|
@ -9,6 +9,10 @@ export class Vec2 {
|
||||||
static from(z) {
|
static from(z) {
|
||||||
return new Vec2(z.x, z.y);
|
return new Vec2(z.x, z.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static scaleFrom(s, z) {
|
||||||
|
return new Vec2(s * z.x, s * z.y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Point {
|
export class Point {
|
||||||
|
|
|
@ -23,6 +23,7 @@ export class SpiroExpander {
|
||||||
const centerBone = this.getPass2Knots();
|
const centerBone = this.getPass2Knots();
|
||||||
const normalRectifier = new NormalRectifier(this.m_biKnotsT, 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);
|
||||||
|
return normalRectifier.totalDelta / normalRectifier.nKnotsProcessed;
|
||||||
}
|
}
|
||||||
getPass2Knots() {
|
getPass2Knots() {
|
||||||
const expanded = this.expand(this.m_contrast);
|
const expanded = this.expand(this.m_contrast);
|
||||||
|
@ -105,36 +106,49 @@ class NormalRectifier {
|
||||||
constructor(stage1ControlKnots, gizmo) {
|
constructor(stage1ControlKnots, gizmo) {
|
||||||
this.m_gizmo = gizmo;
|
this.m_gizmo = gizmo;
|
||||||
this.m_biKnots = stage1ControlKnots;
|
this.m_biKnots = stage1ControlKnots;
|
||||||
this.m_nKnotsProcessed = 0;
|
|
||||||
|
this.nKnotsProcessed = 0;
|
||||||
|
this.totalDelta = 0;
|
||||||
}
|
}
|
||||||
beginShape() {}
|
beginShape() {}
|
||||||
endShape() {}
|
endShape() {}
|
||||||
moveTo(x, y) {
|
moveTo(x, y) {
|
||||||
this.m_nKnotsProcessed += 1;
|
this.nKnotsProcessed += 1;
|
||||||
}
|
}
|
||||||
arcTo(arc, x, y) {
|
arcTo(arc, x, y) {
|
||||||
if (this.m_nKnotsProcessed === 1) {
|
if (this.nKnotsProcessed === 1) {
|
||||||
const d = new Vec2(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.updateKnotTangent(this.m_biKnots[0], d);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("NaN angle detected.");
|
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);
|
const d = new Vec2(arc.deriveX1, arc.deriveY1);
|
||||||
if (isTangentValid(d)) {
|
if (isTangentValid(d)) {
|
||||||
this.m_biKnots[this.m_nKnotsProcessed].origTangent = d;
|
this.updateKnotTangent(this.m_biKnots[this.nKnotsProcessed], d);
|
||||||
} else {
|
} else {
|
||||||
throw new Error("NaN angle detected.");
|
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) {
|
function isTangentValid(d) {
|
||||||
return isFinite(d.x) && isFinite(d.y);
|
return d && isFinite(d.x) && isFinite(d.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalX(tangent, contrast) {
|
function normalX(tangent, contrast) {
|
||||||
|
|
|
@ -1,9 +1,103 @@
|
||||||
import * as SpiroJs from "spiro";
|
import * as SpiroJs from "spiro";
|
||||||
|
import * as TypoGeom from "typo-geom";
|
||||||
|
|
||||||
import * as CurveUtil from "./curve-util.mjs";
|
import * as CurveUtil from "./curve-util.mjs";
|
||||||
|
import { Vec2 } from "./point.mjs";
|
||||||
|
|
||||||
export function spiroToOutline(knots, fClosed, gizmo) {
|
export function spiroToOutline(knots, fClosed, gizmo) {
|
||||||
const s = new CurveUtil.BezToContoursSink(gizmo);
|
const s = new CurveUtil.BezToContoursSink(gizmo);
|
||||||
SpiroJs.spiroToBezierOnContext(knots, fClosed, s, CurveUtil.GEOMETRY_PRECISION);
|
SpiroJs.spiroToBezierOnContext(knots, fClosed, s, CurveUtil.GEOMETRY_PRECISION);
|
||||||
return s.contours;
|
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