From 3ef9aaa2e25c3e81089c60f11f657ca07070822f Mon Sep 17 00:00:00 2001 From: be5invis Date: Tue, 2 Mar 2021 18:37:15 -0800 Subject: [PATCH] Move boolean composition out of kits. --- font-src/kits/boole-kit.ptl | 42 +++++++-------------- font-src/kits/spiro-kit.ptl | 9 ++--- font-src/support/curve-util.js | 13 +++++++ font-src/support/geometry.js | 63 +++++++++++++++++++++++++++++++- font-src/support/glyph.js | 8 ++-- font-src/support/spiro-expand.js | 6 +++ package.json | 4 +- 7 files changed, 103 insertions(+), 42 deletions(-) diff --git a/font-src/kits/boole-kit.ptl b/font-src/kits/boole-kit.ptl index f0db02467..0cc86a56f 100644 --- a/font-src/kits/boole-kit.ptl +++ b/font-src/kits/boole-kit.ptl @@ -1,38 +1,22 @@ import 'typo-geom' as TypoGeom import '../support/curve-util' as CurveUtil +import '../support/geometry' as [object BooleanGeometry] export : define [SetupBuilders args] : begin define [object Glyph GlobalTransform] args - define [Boole operator] : begin - define [BooleImplFn] : begin - define k : {}.slice.call arguments 0 - return : lambda [] : begin - local contourArcs : getArcsFromProc this k.0 - if (k.length === 1) : set contourArcs : TypoGeom.Boolean.removeOverlap - begin contourArcs - begin TypoGeom.Boolean.PolyFillType.pftNonZero - begin CurveUtil.BOOLE_RESOLUTION + define [impl operator operands] : begin + return : function [] : begin + local operandGeometries {} + foreach operand [items-of operands] : begin + local g1 : new Glyph + set g1.gizmo : this.gizmo || GlobalTransform + g1.include operand + operandGeometries.push g1.geometry - foreach [item : items-of : k.slice 1] : begin - set contourArcs : TypoGeom.Boolean.combine operator - begin contourArcs - getArcsFromProc this item - begin TypoGeom.Boolean.PolyFillType.pftNonZero - begin TypoGeom.Boolean.PolyFillType.pftNonZero - begin CurveUtil.BOOLE_RESOLUTION + this.includeGeometry : new BooleanGeometry operator operandGeometries - local ctx : new CurveUtil.BezToContoursSink - TypoGeom.ShapeConv.transferBezArcShape contourArcs ctx - this.includeContours ctx.contours 0 0 - - define [getArcsFromProc g p] : begin - local g1 : new Glyph - set g1.gizmo : g.gizmo || GlobalTransform - g1.include p - return : CurveUtil.convertShapeToArcs : g1.geometry.asContours - - define union : Boole TypoGeom.Boolean.ClipType.ctUnion - define intersection : Boole TypoGeom.Boolean.ClipType.ctIntersection - define difference : Boole TypoGeom.Boolean.ClipType.ctDifference + define [union] : impl TypoGeom.Boolean.ClipType.ctUnion [{}.slice.call arguments 0] + define [intersection] : impl TypoGeom.Boolean.ClipType.ctIntersection [{}.slice.call arguments 0] + define [difference] : impl TypoGeom.Boolean.ClipType.ctDifference [{}.slice.call arguments 0] return [object union intersection difference] diff --git a/font-src/kits/spiro-kit.ptl b/font-src/kits/spiro-kit.ptl index 022742013..4c2f8e1bb 100644 --- a/font-src/kits/spiro-kit.ptl +++ b/font-src/kits/spiro-kit.ptl @@ -63,9 +63,7 @@ export : define [SetupBuilders args] : begin define [widths.center.heading w d] : lambda [] : begin if this.setWidth : this.setWidth ([fallback w Stroke] / 2) ([fallback w Stroke] / 2) if this.headsTo : this.headsTo d - define [unimportant] : begin - if (this.points && this.points.length && this.points.(this.points.length - 1)) : this.points.(this.points.length - 1).subdivided = true - if (this.controlKnots && this.controlKnots.length && this.controlKnots.(this.controlKnots.length - 1)) : this.controlKnots.(this.controlKnots.length - 1).unimportant = true + define [unimportant] : if this.setUnimportant : this.setUnimportant define [important] nothing # Interpolation pesudoknots @@ -175,8 +173,7 @@ export : define [SetupBuilders args] : begin return { .knots knots .closed closed } define [convertSpiroToBezier knots closed ctx] : begin - define CUBIC false - return : SpiroJs.spiroToBezierOnContext knots closed ctx CUBIC CurveUtil.GEOMETRY_PRECISION + return : SpiroJs.spiroToBezierOnContext knots closed ctx CurveUtil.SPIRO_PRECISION define [dispiro] : let [args : {}.slice.call arguments 0] : lambda [] : begin define CLOSED true @@ -220,4 +217,4 @@ export : define [SetupBuilders args] : begin g4 g2 corner flat curl close end straight widths disable-contrast heading unimportant important alsoThru alsoThruThem bezcontrols quadcontrols archv arcvh complexThru - dispiro spiro-outline] \ No newline at end of file + dispiro spiro-outline] diff --git a/font-src/support/curve-util.js b/font-src/support/curve-util.js index 78ff46127..1bc67327b 100644 --- a/font-src/support/curve-util.js +++ b/font-src/support/curve-util.js @@ -4,6 +4,7 @@ const TypoGeom = require("typo-geom"); const Point = require("./point"); const Transform = require("./transform"); +exports.SPIRO_PRECISION = 1 / 2; exports.GEOMETRY_PRECISION = 1 / 4; exports.RECIP_GEOMETRY_PRECISION = 4; exports.BOOLE_RESOLUTION = 0x4000; @@ -34,6 +35,18 @@ exports.OffsetCurve = class OffsetCurve { } }; +exports.ReverseCurve = class ReverseCurve { + constructor(original) { + this.m_original = original; + } + eval(t) { + return this.m_original.eval(1 - t); + } + derivative(t) { + return -this.m_original.derivative(1 - t); + } +}; + exports.convertShapeToArcs = function convertShapeToArcs(shape) { return shape.map(convertContourToArcs); }; diff --git a/font-src/support/geometry.js b/font-src/support/geometry.js index f01a9f17e..0431c4e9d 100644 --- a/font-src/support/geometry.js +++ b/font-src/support/geometry.js @@ -2,6 +2,8 @@ const Point = require("./point"); const Transform = require("./transform"); +const CurveUtil = require("./curve-util"); +const TypoGeom = require("typo-geom"); class GeometryBase { asContours() { @@ -132,7 +134,9 @@ class TransformedGeometry extends GeometryBase { return result; } filterTag(fn) { - return new TransformedGeometry(this.m_geom.filterTag(fn), this.m_transform); + const e = this.m_geom.filterTag(fn); + if (!e) return null; + return new TransformedGeometry(e, this.m_transform); } isEmpty() { return this.m_geom.isEmpty(); @@ -192,6 +196,62 @@ class CombineGeometry extends GeometryBase { } } +class BooleanGeometry extends GeometryBase { + constructor(operator, operands) { + super(); + this.m_operator = operator; + this.m_operands = operands; + this.m_resolved = null; + } + asContours() { + if (this.m_resolved) return this.m_resolved; + this.m_resolved = this.asContoursImpl(); + return this.m_resolved; + } + asContoursImpl() { + let arcs = CurveUtil.convertShapeToArcs(this.m_operands[0].asContours()); + if (this.m_operands.length < 2) { + arcs = TypoGeom.Boolean.removeOverlap( + arcs, + TypoGeom.Boolean.PolyFillType.pftNonZero, + CurveUtil.BOOLE_RESOLUTION + ); + } + for (let j = 1; j < this.m_operands.length; j++) { + arcs = TypoGeom.Boolean.combine( + this.m_operator, + arcs, + CurveUtil.convertShapeToArcs(this.m_operands[j].asContours()), + TypoGeom.Boolean.PolyFillType.pftNonZero, + TypoGeom.Boolean.PolyFillType.pftNonZero, + CurveUtil.BOOLE_RESOLUTION + ); + } + const ctx = new CurveUtil.BezToContoursSink(); + TypoGeom.ShapeConv.transferBezArcShape(arcs, ctx); + return ctx.contours; + } + asReferences() { + return null; + } + filterTag(fn) { + let filtered = []; + for (const operand of this.m_operands) { + const fp = operand.filterTag(fn); + if (fp) filtered.push(fp); + } + return new BooleanGeometry(this.m_operator, filtered); + } + isEmpty() { + for (const operand of this.m_operands) if (!operand.isEmpty()) return false; + return true; + } + measureComplexity() { + let s = 0; + for (const operand of this.m_operands) s += operand.measureComplexity(); + } +} + function combineWith(a, b) { if (a instanceof CombineGeometry) { return a.with(b); @@ -206,5 +266,6 @@ exports.ReferenceGeometry = ReferenceGeometry; exports.TaggedGeometry = TaggedGeometry; exports.TransformedGeometry = TransformedGeometry; exports.CombineGeometry = CombineGeometry; +exports.BooleanGeometry = BooleanGeometry; exports.combineWith = combineWith; diff --git a/font-src/support/glyph.js b/font-src/support/glyph.js index 988aafb81..170d77c27 100644 --- a/font-src/support/glyph.js +++ b/font-src/support/glyph.js @@ -95,15 +95,15 @@ module.exports = class Glyph { this.avoidBeingComposite = g.avoidBeingComposite; } - combineGeometryImpl(g) { + includeGeometry(g) { if (this.ctxTag) g = new Geom.TaggedGeometry(g, this.ctxTag); this.geometry = Geom.combineWith(this.geometry, g); } includeGlyphImpl(g, shiftX, shiftY) { if (g._m_identifier) { - this.combineGeometryImpl(new Geom.ReferenceGeometry(g, shiftX, shiftY)); + this.includeGeometry(new Geom.ReferenceGeometry(g, shiftX, shiftY)); } else { - this.combineGeometryImpl( + this.includeGeometry( new Geom.TransformedGeometry(g.geometry, Transform.Translate(shiftX, shiftY)) ); } @@ -116,7 +116,7 @@ module.exports = class Glyph { for (const z of contour) c.push(Point.translated(z, shiftX, shiftY)); parts.push(new Geom.ContourGeometry(c)); } - this.combineGeometryImpl(new Geom.CombineGeometry(parts)); + this.includeGeometry(new Geom.CombineGeometry(parts)); } applyTransform(tfm, alsoAnchors) { diff --git a/font-src/support/spiro-expand.js b/font-src/support/spiro-expand.js index 52d5a80fb..42f7746ee 100644 --- a/font-src/support/spiro-expand.js +++ b/font-src/support/spiro-expand.js @@ -53,6 +53,10 @@ module.exports = class SpiroExpansionContext { const k0 = this.controlKnots[this.controlKnots.length - 1]; if (k0) k0.type = type; } + setUnimportant() { + const k0 = this.controlKnots[this.controlKnots.length - 1]; + if (k0) k0.unimportant = true; + } expand(contrast) { if (contrast == null) contrast = 1 / 0.9; const lhs = [], @@ -100,6 +104,7 @@ module.exports = class SpiroExpansionContext { rhsAfter = this.gizmo.unapply(rhs[jAfter]); lhs[j] = { + unimportant: true, type: knot.type, ...this.gizmo.apply({ x: linreg(knotBefore.x, lhsBefore.x, knotAfter.x, lhsAfter.x, ref.x), @@ -107,6 +112,7 @@ module.exports = class SpiroExpansionContext { }) }; rhs[j] = { + unimportant: true, type: reverseKnotType(knot.type), ...this.gizmo.apply({ x: linreg(knotBefore.x, rhsBefore.x, knotAfter.x, rhsAfter.x, ref.x), diff --git a/package.json b/package.json index d9d0d5e46..bed352d33 100644 --- a/package.json +++ b/package.json @@ -17,10 +17,10 @@ "otb-ttc-bundle": "^1.0.0", "patel": "^0.34.0", "semver": "^7.3.4", - "spiro": "^2.0.0", + "spiro": "^3.0.0", "stylus": "^0.54.8", "toposort": "^2.0.2", - "typo-geom": "^0.11.0", + "typo-geom": "^0.12.0", "verda": "^1.2.1", "wawoff2": "^1.0.2", "which": "^2.0.2"