From bff9e0b1c56c3ade5dc388c4bc4d62d255b1c2f6 Mon Sep 17 00:00:00 2001 From: Belleve Date: Sun, 28 Jul 2024 03:30:59 -1000 Subject: [PATCH] Add frakturs in the Letterlike Symbols (#2438) * * Add characters: - BLACK-LETTER CAPITAL I (`U+2111`). - BLACK-LETTER CAPITAL R (`U+211C`). * Complete frakturs in Letterlike Symbols block * Update geom cache version --- changes/31.0.1.md | 6 + package.json | 6 +- packages/font-glyphs/src/index.ptl | 1 + .../font-glyphs/src/letter-like/fraktur.ptl | 295 ++++++++++++++++++ packages/font-glyphs/src/meta/macros.ptl | 4 +- packages/font-kits/src/spiro-kit.mjs | 23 ++ packages/geometry-cache/src/index.mjs | 2 +- packages/geometry/src/index.mjs | 78 +++++ packages/geometry/src/spiro-pen-expander.mjs | 81 +++++ packages/geometry/src/spiro-to-outline.mjs | 1 - packages/util/src/formatter.mjs | 7 + 11 files changed, 496 insertions(+), 8 deletions(-) create mode 100644 changes/31.0.1.md create mode 100644 packages/font-glyphs/src/letter-like/fraktur.ptl create mode 100644 packages/geometry/src/spiro-pen-expander.mjs diff --git a/changes/31.0.1.md b/changes/31.0.1.md new file mode 100644 index 000000000..968daa742 --- /dev/null +++ b/changes/31.0.1.md @@ -0,0 +1,6 @@ +* Add characters: + - BLACK-LETTER CAPITAL H (`U+210C`). + - BLACK-LETTER CAPITAL I (`U+2111`). + - BLACK-LETTER CAPITAL R (`U+211C`). + - BLACK-LETTER CAPITAL Z (`U+2128`). + - BLACK-LETTER CAPITAL C (`U+212D`). diff --git a/package.json b/package.json index 8692dab47..55dbbf320 100644 --- a/package.json +++ b/package.json @@ -1,16 +1,14 @@ { "name": "@iosevka/monorepo", "version": "31.0.0", - "workspaces": [ - "packages/*", - "tools/*" - ], + "workspaces": ["packages/*", "tools/*"], "scripts": { "build": "verda -f verdafile.mjs", "bump-ver": "node tools/misc/src/update-package-json-version.mjs && npm install && node tools/misc/src/generate-ttfa-ranges.mjs", "clean": "verda -f verdafile.mjs clean", "lint": "eslint", "lint:fix": "eslint --fix", + "copy-char-name-to-markdown": "node tools/misc/src/copy-char-name-to-markdown.mjs", "generate-release-sha-file": "node tools/misc/src/generate-release-sha-file.mjs release-archives/*.zip release-archives/SHA-256.txt" }, "dependencies": { diff --git a/packages/font-glyphs/src/index.ptl b/packages/font-glyphs/src/index.ptl index a6dc06336..cad2e032c 100644 --- a/packages/font-glyphs/src/index.ptl +++ b/packages/font-glyphs/src/index.ptl @@ -120,6 +120,7 @@ export : define [buildGlyphs para recursive] : begin # Letter-likes run-glyph-module "./letter-like/cursive.mjs" + run-glyph-module "./letter-like/fraktur.mjs" # Symbols run-glyph-module "./symbol/shared.mjs" diff --git a/packages/font-glyphs/src/letter-like/fraktur.ptl b/packages/font-glyphs/src/letter-like/fraktur.ptl new file mode 100644 index 000000000..a05e8067f --- /dev/null +++ b/packages/font-glyphs/src/letter-like/fraktur.ptl @@ -0,0 +1,295 @@ +###### Fraktur letterforms +### +### This file is used to define the letterforms of the Fraktur style. +### For simplicity, the letters here will *NOT* support any variants. +### + +$$include '../meta/macros.ptl' + +import [mix fallback] from "@iosevka/util" +import [SpiroPenGeometry] from "@iosevka/geometry" +import [Vec2] from "@iosevka/geometry/point" + +glyph-module + +glyph-block LetterLike-Fraktur-Shared : begin + + # [fraktur-stroke profile ...] will construct a Fraktur stroke from a pen profile and a list + # of control knots. The knots will form a (usually open) spiro path, then the result will be + # the area that the pen tip covers when moving along the path. + glyph-block-export fraktur-stroke + define [fraktur-stroke profile __knots] : begin + local knots : {}.slice.call arguments 1 + return : new FrakturImpl profile knots + + class FrakturImpl + public [new profile knots] : begin + this.profile = profile + this.knots = knots + + public [applyToGlyph glyph] : begin + local c : spiro-collect glyph this.knots + local geom : new SpiroPenGeometry + begin c.gizmo + begin c.collector.closed + this.profile.getPenShape c.gizmo + c.collector.controls.map : function [k] [k.toMono] + return : glyph.includeGeometry geom + + + # A pen profile describes a virtual flat-tip pen. We use a 45-degree arrangement to + # simplify the math. + class FrakturProfile + public [new thick thin] : begin + # .thick is the half length of the flat tip, projected to the X/Y axis + this.thick = 0.25 * [Math.sqrt 2] * thick + # .thin is the half width of the thin tip, projected to the X/Y axis + this.thin = 0.25 * [Math.sqrt 2] * thin + + public [getPenShape gizmo] : begin + local thickTf : gizmo.applyOffsetXY this.thick this.thick + list + new Vec2 (thickTf.x - this.thin) (thickTf.y + this.thin) + new Vec2 (thickTf.x + this.thin) (thickTf.y + this.thin) + new Vec2 (thickTf.x + this.thin) (thickTf.y - this.thin) + new Vec2 (-thickTf.x + this.thin) (-thickTf.y - this.thin) + new Vec2 (-thickTf.x - this.thin) (-thickTf.y - this.thin) + new Vec2 (-thickTf.x - this.thin) (-thickTf.y + this.thin) + + public [xl x] : x + this.thick + public [xr x] : x - this.thick + public [xp l r p] : mix [this.xl l] [this.xr r] p + + public [yb y] : y + this.thick + public [yt y] : y - this.thick + public [yp b t p] : mix [this.yb b] [this.yt t] p + + # Connection to another profile's pen tip position + public [connL otherProfile x] : x - otherProfile.thick + this.thick + public [connR otherProfile x] : x + otherProfile.thick - this.thick + public [connB otherProfile y] : y - otherProfile.thick + this.thick + public [connT otherProfile y] : y + otherProfile.thick - this.thick + + # Stroke widths + define frakThin : 1.0 * [AdviceStroke 8] + define frakThick : 1.0 * Stroke + define frakFine : 1.0 * [AdviceStroke 4] # For decoration + + glyph-block-export S + define S : new FrakturProfile frakThick (0.5 * frakThin) + + glyph-block-export F + define F : new FrakturProfile frakFine (0.5 * frakThin) + + glyph-block-export T + define T : new FrakturProfile frakThin (0.5 * frakThin) + +glyph-block LetterLike-Fraktur : begin + glyph-block-import CommonShapes + glyph-block-import LetterLike-Fraktur-Shared : S F T fraktur-stroke + + define DecoSizeX : 0.15 * (RightSB - SB) + define DecoSizeY : 0.08 * (RightSB - SB) + define FHook : 0.4 * SHook - 0.25 * S.thick + define WaveDepth : 0.4 * SHook - 0.25 * S.thick + define WaveDepthX : 0.375 * SHook + 0.5 * S.thick + define LbFootRise : 0.375 * SHook + 0.375 * S.thick + + define flex-params [HWave] : begin + local-parameter pen + local-parameter w + local-parameter l + local-parameter r + local-parameter b + local-parameter t + + return : fraktur-stroke pen + g2 l b + g2 (l + TINY) (b + TINY) + g2 [mix l r 0.375] [if w (b + w) t] + g2 [mix l r 0.625] [if w (t - w) b] + g2 (r - TINY) (t - TINY) + g2 r t + + define flex-params [VWave] : begin + local-parameter pen + local-parameter w + local-parameter l + local-parameter r + local-parameter b + local-parameter t + + return : fraktur-stroke pen + g2 r t + g2 (r - TINY) (t - TINY) + g2 [if w (r - w) l] [mix b t 0.625] + g2 [if w (l + w) r] [mix b t 0.375] + g2 (l + TINY) (b + TINY) + g2 l b + + define flex-params [VCWave] : begin + local-parameter pen + local-parameter w + local-parameter x + local-parameter b + local-parameter t + + return : fraktur-stroke pen + g2 x t + g2 (x - TINY) (t - TINY) + g2 [pen.xl (x - 0.5 * w)] [mix b t 0.625] + g2 [pen.xr (x + 0.5 * w)] [mix b t 0.375] + g2 (x + TINY) (b + TINY) + g2 x b + + create-glyph "frak/C" 0x212D : glyph-proc + include : MarkSet.capital + + # Top-right stroke + include : fraktur-stroke S + g2 [S.xr RightSB] [S.yt CAP] + g2.left.mid ([S.xp SB RightSB 0.75] + 0.5 * DecoSizeX) ([S.yt CAP] - 0.5 * FHook) + corner ([S.xp SB RightSB 0.5] + DecoSizeX) [S.yt CAP] + corner [S.xp SB RightSB 0.5] ([S.yt CAP] - DecoSizeX) + + # Left and bottom stroke + include : fraktur-stroke S + g4.ld.start [S.xp SB RightSB 0.1] ([S.yt CAP] - 0.1 * ArchDepthA) + flat [S.xl SB] ([S.yt CAP] - 0.6 * ArchDepthA) + curl [S.xl SB] ([S.yb 0] + ArchDepthB) + ~~~ [hookend [S.yb 0] (sw -- S.thick)] + g2 [S.xr RightSB] ([S.yb 0] + SHook) + + # A thin connection between the two strokes + include : fraktur-stroke T + g4.ru.start [T.connR S : S.xp SB RightSB 0.1] [T.connT S : [S.yt CAP] - 0.1 * ArchDepthA] + ~~~ [arch.lhs [T.yt CAP] 0.6 (sw -- T.thick) (blendPre -- null) (blendPost -- null)] + g4.ld.end [S.xp SB RightSB 0.5] ([S.yt CAP] - DecoSizeX) + + # Inner decoration + include : VCWave F (WaveDepthX + 0.625 * S.thick) + x -- [F.connL S : S.xp SB RightSB 0.5] + t -- [F.connB S : [S.yt CAP] - DecoSizeX] + b -- (CAP * 0.375) + + create-glyph "frak/H" 0x210C : glyph-proc + include : MarkSet.capDesc + + local xExt : mix 0 SB 0.25 + local xLeftStem : Math.max (xExt + 1.5 * DecoSizeX) [mix SB RightSB 0] + local ada : 0.6 * ArchDepthA + local adb : 0.6 * ArchDepthB + + # Top and left stroke + include : fraktur-stroke S + g2 [S.xr RightSB] ([S.yt CAP] - FHook) + ~~~ [arch.rhs [S.yt CAP] (sw -- S.thick) (blendPre -- null) (blendPost -- null)] + corner [S.xl xLeftStem] ([S.yt CAP] - adb) + curl [S.xl xLeftStem] [Math.min [S.yt (XH - FHook)] [S.yp 0 CAP 0.5]] + corner [S.xl xExt] ([S.yb 0] + LbFootRise) + g2c.right.end (Middle - DecoSizeX) [S.yb 0] + corner Middle ([S.yb 0] + DecoSizeY) + + # Middle and right stroke + include : fraktur-stroke S + g2 [S.xl xLeftStem] ([S.yt (XH - FHook)] - FHook) + corner [S.xp xLeftStem RightSB 0.625] [S.yt (XH - FHook)] + g2.down.mid [S.xr RightSB] [S.yp Descender(XH - FHook) 0.75] + ~~~ [alsoThru.g2 0.5 0.5] + g2.down.mid [S.xp xLeftStem RightSB 0.75] [S.yp Descender(XH - FHook) 0.25] + g2 [S.xr RightSB] [S.yb Descender] + + create-glyph "frak/I" 0x2111 : glyph-proc + include : MarkSet.capital + + # Top Stroke + include : HWave S + l -- [S.xl SB] + r -- [S.xr RightSB] + b -- [S.yt (CAP - WaveDepth)] + t -- [S.yt CAP] + + # Deocration at top-left + include : VCWave F WaveDepthX + x -- [F.connL S : S.xl SB] + b -- (CAP * 0.625) + t -- [F.connB S : S.yt (CAP - WaveDepth)] + + # Main stroke + include : fraktur-stroke S + g2.ld.start [S.xr RightSB] [S.yt CAP] + g4 [S.xp SB RightSB 0.75] [S.yp ArchDepthA CAP 0.625] + g2 [S.xr RightSB] [S.yb ArchDepthA] + ~~~ [hookend [S.yb 0] (sw -- S.thick)] + g2 [S.xl SB] [S.yb SHook] + + create-glyph "frak/R" 0x211C : glyph-proc + include : MarkSet.capital + + local xExt : mix 0 SB 0.25 + local xLeftStem : Math.max (xExt + 1.5 * DecoSizeX) [mix SB RightSB 0.166] + local ltHook : 0.25 * Hook + local ada : 0.6 * ArchDepthA + local adb : 0.6 * ArchDepthB + + # Deocration at top-left + include : VCWave F WaveDepthX + x -- [F.connL S : S.xl xExt] + b -- (CAP * 0.625) + t -- [F.connB S : S.yt (CAP - ltHook)] + + local xMidStrokeL : S.xl xLeftStem + local xMidStrokeR : S.xp xLeftStem RightSB 0.625 + local yMidStrokeL : S.yp 0 CAP 0.48 + local yMidStrokeR : S.yp 0 CAP 0.55 + + # Left stroke + include : fraktur-stroke S + g2.ru.start [S.xl xExt] [S.yt (CAP - ltHook)] + ~~~ [arch.rhs [S.yt CAP] 0.6 (blendPre -- null)] + flat [S.xl xLeftStem] ([S.yb CAP] - adb) + curl [S.xl xLeftStem] yMidStrokeL + corner [S.xl xExt] ([S.yb 0] + LbFootRise) + g2c.right.end (Middle - DecoSizeX) [S.yb 0] + corner Middle ([S.yb 0] + DecoSizeY) + + # Top-right arch + include : fraktur-stroke S + flat [S.xl xLeftStem] ([S.yb CAP] - adb) + corner [S.xp xLeftStem RightSB 0.75] [S.yt CAP] + g2.down.mid [S.xr RightSB] [mix yMidStrokeR [S.yt CAP] 0.5] + flat xMidStrokeR yMidStrokeR + curl xMidStrokeL yMidStrokeL + + local xLegStart : mix xMidStrokeL xMidStrokeR 0.75 + local yLegStart : mix yMidStrokeL yMidStrokeR 0.75 + local xLegEnd RightSB + local yLegEnd : S.yb 0 + + # Leg + include : fraktur-stroke S + g2 xLegStart yLegStart + g2 [mix xLegStart xMidStrokeR 0.001] [mix yLegStart yMidStrokeR 0.001] + flat [mix xLegStart xLegEnd 0.75] (yLegStart - adb) + curl [mix xLegStart xLegEnd 0.75] (yLegEnd + ada) + corner xLegEnd yLegEnd + corner (xLegEnd + DecoSizeX) (yLegEnd + DecoSizeY) + + create-glyph "frak/Z" 0x2128 : glyph-proc + include : MarkSet.capital + + # Top Stroke + include : HWave S + l -- [S.xl SB] + r -- [S.xr RightSB] + b -- [S.yt (CAP - WaveDepth)] + t -- [S.yt CAP] + + # Bottom Stroke + include : fraktur-stroke S + corner [S.xr RightSB] [S.yt CAP] + cg2.ru.start [S.xp SB RightSB 0.166] [S.yp 0 CAP 0.5] + ~~~ [arch.rhs [S.yp 0 CAP 0.55] 0.375 (sw -- S.thick)] + g2.down.mid [S.xr RightSB] [S.yb ArchDepthA] + ~~~ [hookend [S.yb 0] (sw -- S.thick)] + g2 [S.xl SB] [S.yb SHook] diff --git a/packages/font-glyphs/src/meta/macros.ptl b/packages/font-glyphs/src/meta/macros.ptl index b974ee83f..913a74190 100644 --- a/packages/font-glyphs/src/meta/macros.ptl +++ b/packages/font-glyphs/src/meta/macros.ptl @@ -279,9 +279,9 @@ define-macro glyph-block : syntax-rules AdviceGlottalStopArchDepth StrokeWidthBlend ArchDepthAOf ArchDepthBOf SmoothAdjust MidJutSide MidJutCenter compositeBaseAnchors YSmoothMidR YSmoothMidL HSwToV NarrowUnicodeT WideUnicodeT VERY-FAR TINY] - define spiroFnImports `[g4 g2 corner flat curl close end straight widths + define spiroFnImports `[g4 g2 corner flat curl close end straight g2c cg2 flatc ccurl widths disable-contrast heading unimportant important alsoThru alsoThruThem bezControls - quadControls archv arcvh dispiro spiro-outline] + quadControls archv arcvh dispiro spiro-outline spiro-collect] define booleFnImports `[union intersection difference] dirty `[$GlyphBlocks$.push : lambda [$Capture_Ext$] : begin \\ diff --git a/packages/font-kits/src/spiro-kit.mjs b/packages/font-kits/src/spiro-kit.mjs index bab78cd4f..ece5de0d4 100644 --- a/packages/font-kits/src/spiro-kit.mjs +++ b/packages/font-kits/src/spiro-kit.mjs @@ -89,13 +89,23 @@ export function SetupBuilders(bindings) { const curl = KnotType("right"); const close = f => new TerminateInstruction("close", f); const end = f => new TerminateInstruction("end", f); + const straight = { l: flat, r: curl }; + const g2c = { l: g2, r: corner }; + const cg2 = { l: corner, r: g2 }; + const flatc = { l: flat, r: corner }; + const ccurl = { l: corner, r: curl }; + { let directions = [ { name: "up", x: 0, y: 1 }, { name: "down", x: 0, y: -1 }, { name: "left", x: -1, y: 0 }, { name: "right", x: 1, y: 0 }, + { name: "ru", x: 1, y: 1 }, + { name: "rd", x: 1, y: -1 }, + { name: "lu", x: -1, y: 1 }, + { name: "ld", x: -1, y: -1 }, ]; let adhesions = [ { name: "start", l: 0, r: TINY }, @@ -107,6 +117,10 @@ export function SetupBuilders(bindings) { [g2, g2, g2], [corner, corner, corner], [straight, flat, curl], + [g2c, g2, corner], + [cg2, corner, g2], + [flatc, flat, corner], + [ccurl, corner, curl], ]; for (const [sink, kl, kr] of knotTypes) { for (const d of directions) { @@ -368,6 +382,10 @@ export function SetupBuilders(bindings) { function spiroOutline(...args) { return new SpiroOutlineImpl(bindings, args); } + function spiroCollect(glyph, ...args) { + const spb = new SpiroImplBase(bindings, args); + return spb.createCollector(glyph); + } return { g4, @@ -378,6 +396,10 @@ export function SetupBuilders(bindings) { close, end, straight, + g2c, + cg2, + flatc, + ccurl, widths, heading, "disable-contrast": disableContrast, @@ -391,5 +413,6 @@ export function SetupBuilders(bindings) { arcvh, dispiro, "spiro-outline": spiroOutline, + "spiro-collect": spiroCollect, }; } diff --git a/packages/geometry-cache/src/index.mjs b/packages/geometry-cache/src/index.mjs index 5a28872be..37458b4a0 100644 --- a/packages/geometry-cache/src/index.mjs +++ b/packages/geometry-cache/src/index.mjs @@ -5,7 +5,7 @@ import zlib from "zlib"; import * as CurveUtil from "@iosevka/geometry/curve-util"; import { encode, decode } from "@msgpack/msgpack"; -const Edition = 43; +const Edition = 44; const MAX_AGE = 16; class GfEntry { constructor(age, value) { diff --git a/packages/geometry/src/index.mjs b/packages/geometry/src/index.mjs index 837223a89..136061c1b 100644 --- a/packages/geometry/src/index.mjs +++ b/packages/geometry/src/index.mjs @@ -5,6 +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 { createSpiroPenGeometry } from "./spiro-pen-expander.mjs"; import { spiroToOutlineWithSimplification } from "./spiro-to-outline.mjs"; import { strokeArcs } from "./stroke.mjs"; import { Transform } from "./transform.mjs"; @@ -139,6 +140,83 @@ export class SpiroGeometry extends CachedGeometry { } } +export class SpiroPenGeometry extends CachedGeometry { + constructor(gizmo, closed, pen, knots) { + super(); + this.m_gizmo = gizmo; + this.m_closed = closed; + this.m_knots = knots; + this.m_pen = pen; + } + + toContoursImpl() { + let contours = createSpiroPenGeometry( + this.m_gizmo, + this.m_closed, + this.m_knots, + this.m_pen, + ); + + if (!contours.length) return []; + + let stack = []; + for (const [i, c] of contours.entries()) { + stack.push({ + type: "operand", + fillType: TypoGeom.Boolean.PolyFillType.pftNonZero, + shape: CurveUtil.convertShapeToArcs([c]), + }); + if (i > 0) { + stack.push({ type: "operator", operator: TypoGeom.Boolean.ClipType.ctUnion }); + } + } + + const arcs = TypoGeom.Boolean.combineStack(stack, CurveUtil.BOOLE_RESOLUTION); + const ctx = new CurveUtil.BezToContoursSink(); + TypoGeom.ShapeConv.transferBezArcShape(arcs, ctx); + return ctx.contours; + } + + toReferences() { + return null; + } + getDependencies() { + return null; + } + filterTag(fn) { + return this; + } + + measureComplexity() { + let cplx = CPLX_NON_EMPTY | CPLX_NON_SIMPLE; + for (const z of this.m_pen) { + if (!isFinite(z.x) || !isFinite(z.y)) cplx |= CPLX_BROKEN; + } + for (const z of this.m_knots) { + if (!isFinite(z.x) || !isFinite(z.y)) cplx |= CPLX_BROKEN; + } + return cplx; + } + + hash(h) { + h.beginStruct("SpiroPenGeometry"); + h.gizmo(this.m_gizmo); + h.bool(this.m_closed); + + // Serialize the pen + h.beginArray(this.m_pen.length); + for (const z of this.m_pen) h.point(z); + h.endArray(); + + // Serialize the knots + h.beginArray(this.m_knots.length); + for (const knot of this.m_knots) h.embed(knot); + h.endArray(); + + h.endStruct(); + } +} + export class DiSpiroGeometry extends CachedGeometry { constructor(gizmo, contrast, closed, biKnots) { super(); diff --git a/packages/geometry/src/spiro-pen-expander.mjs b/packages/geometry/src/spiro-pen-expander.mjs new file mode 100644 index 000000000..ab5284ea9 --- /dev/null +++ b/packages/geometry/src/spiro-pen-expander.mjs @@ -0,0 +1,81 @@ +import * as SpiroJs from "spiro"; +import * as CurveUtil from "./curve-util.mjs"; + +import { Point } from "./point.mjs"; + +export function createSpiroPenGeometry(gizmo, closed, knots, pen) { + const collector = new ArcCollector(gizmo, pen); + SpiroJs.spiroToBezierOnContext(knots, closed, collector, CurveUtil.GEOMETRY_PRECISION); + return collector.contoursCollected; +} + +class ArcCollector { + constructor(gizmo, pen) { + this.gizmo = gizmo; + this.lastX = 0; + this.lastY = 0; + this.m_pen = pen; + this.contoursCollected = []; + } + + beginShape() {} + endShape() {} + + moveTo(x, y) { + const lastTf = this.gizmo.applyXY(x, y); + this.lastX = lastTf.x; + this.lastY = lastTf.y; + this.addPenProfileAt(this.lastX, this.lastY); + } + + lineTo(x1, y1) { + const z1 = this.gizmo.applyXY(x1, y1); + for (let i = 0; i < this.m_pen.length; i++) { + let penPrev = this.m_pen[i]; + let penNext = this.m_pen[(i + 1) % this.m_pen.length]; + + const l1 = new Point(Point.Type.Corner, this.lastX + penPrev.x, this.lastY + penPrev.y); + const l2 = new Point(Point.Type.Corner, z1.x + penPrev.x, z1.y + penPrev.y); + const r2 = new Point(Point.Type.Corner, z1.x + penNext.x, z1.y + penNext.y); + const r1 = new Point(Point.Type.Corner, this.lastX + penNext.x, this.lastY + penNext.y); + + this.contoursCollected.push([l1, l2, r2, r1]); + } + this.lastX = z1.x; + this.lastY = z1.y; + this.addPenProfileAt(this.lastX, this.lastY); + } + + cubicTo(x2, y2, x3, y3, x4, y4) { + const z2 = this.gizmo.applyXY(x2, y2); + const z3 = this.gizmo.applyXY(x3, y3); + const z4 = this.gizmo.applyXY(x4, y4); + for (let i = 0; i < this.m_pen.length; i++) { + let penPrev = this.m_pen[i]; + let penNext = this.m_pen[(i + 1) % this.m_pen.length]; + + const l1 = new Point(Point.Type.Corner, this.lastX + penPrev.x, this.lastY + penPrev.y); + const l2 = new Point(Point.Type.CubicStart, z2.x + penPrev.x, z2.y + penPrev.y); + const l3 = new Point(Point.Type.CubicEnd, z3.x + penPrev.x, z3.y + penPrev.y); + const l4 = new Point(Point.Type.Corner, z4.x + penPrev.x, z4.y + penPrev.y); + const r4 = new Point(Point.Type.Corner, z4.x + penNext.x, z4.y + penNext.y); + const r3 = new Point(Point.Type.CubicStart, z3.x + penNext.x, z3.y + penNext.y); + const r2 = new Point(Point.Type.CubicEnd, z2.x + penNext.x, z2.y + penNext.y); + const r1 = new Point(Point.Type.Corner, this.lastX + penNext.x, this.lastY + penNext.y); + + this.contoursCollected.push([l1, l2, l3, l4, r4, r3, r2, r1]); + } + this.lastX = z4.x; + this.lastY = z4.y; + this.addPenProfileAt(this.lastX, this.lastY); + } + + addPenProfileAt(x, y) { + let c = []; + for (let i = 0; i < this.m_pen.length; i++) { + let pen = this.m_pen[i]; + c.push(new Point(Point.Type.Corner, x + pen.x, y + pen.y)); + } + this.contoursCollected.push(c); + } +} diff --git a/packages/geometry/src/spiro-to-outline.mjs b/packages/geometry/src/spiro-to-outline.mjs index 93fd78db3..1155254e8 100644 --- a/packages/geometry/src/spiro-to-outline.mjs +++ b/packages/geometry/src/spiro-to-outline.mjs @@ -2,7 +2,6 @@ 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); diff --git a/packages/util/src/formatter.mjs b/packages/util/src/formatter.mjs index 9925d2700..e4a21b3d0 100644 --- a/packages/util/src/formatter.mjs +++ b/packages/util/src/formatter.mjs @@ -13,6 +13,7 @@ const TAG_END_STRUCT_TYPE = 0x12340008; const TAG_TYPED_POINT = 0x12340010; const TAG_GIZMO = 0x12340011; const TAG_LIST_LENGTH = 0x12340012; +const POINT = 0x12340013; const TAG_EMBED_BEGIN = 0x12340020; const TAG_EMBED_END = 0x12340021; @@ -88,6 +89,12 @@ export class Hasher { return this; } + point(z) { + this.u32(TAG_TYPED_POINT); + this.f64(z.x); + this.f64(z.y); + return this; + } typedPoint(z) { this.u32(TAG_TYPED_POINT); this.u32(z.type);