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
This commit is contained in:
Belleve 2024-07-28 03:30:59 -10:00 committed by GitHub
parent 61ad3c365c
commit bff9e0b1c5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 496 additions and 8 deletions

6
changes/31.0.1.md Normal file
View file

@ -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`).

View file

@ -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": {

View file

@ -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"

View file

@ -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]

View file

@ -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 \\

View file

@ -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,
};
}

View file

@ -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) {

View file

@ -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();

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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);