Fix leaning marks placement for reversed k/F/P (#2150).

This commit is contained in:
be5invis 2024-01-03 19:11:15 -08:00
parent 4364beed2b
commit 4e9555b8fd
7 changed files with 43 additions and 79 deletions

View file

@ -3,3 +3,4 @@
* Fix attachment of descender parts of Cyrillic Lower Ha/X under `x` = `cursive` (#2142). * Fix attachment of descender parts of Cyrillic Lower Ha/X under `x` = `cursive` (#2142).
* Make the Eng part in LATIN SMALL LETTER FENG DIGRAPH always connected to the f part (#2143). * Make the Eng part in LATIN SMALL LETTER FENG DIGRAPH always connected to the f part (#2143).
* Fix top bar shape in CYRILLIC CAPITAL LETTER DJE (#2145). * Fix top bar shape in CYRILLIC CAPITAL LETTER DJE (#2145).
* Fix leaning marks placement for reversed k/F/P (#2150).

View file

@ -692,8 +692,16 @@ glyph-block Autobuild-Transformed : begin
include : Ungizmo include : Ungizmo
set currentGlyph.gizmo [Transform.Id] set currentGlyph.gizmo [Transform.Id]
include : ScaleAround (currentGlyph.advanceWidth / 2) 0 (-1) 1 include : ScaleAround (currentGlyph.advanceWidth / 2) 0 (-1) 1
set currentGlyph.gizmo GlobalTransform
include GlobalTransform include GlobalTransform
# Mirror the leaning marks
local bLeaningAbove : g1.gizmo.unapplyIfPresent currentGlyph.baseAnchors.leaningAbove
local bLeaningBelow : g1.gizmo.unapplyIfPresent currentGlyph.baseAnchors.leaningBelow
if bLeaningAbove : set-base-anchor 'leaningAbove' (currentGlyph.advanceWidth - bLeaningAbove.x) bLeaningAbove.y
if bLeaningBelow : set-base-anchor 'leaningBelow' (currentGlyph.advanceWidth - bLeaningBelow.x) bLeaningBelow.y
link-relations relSets link-relations relSets
glyph-block Autobuild-Transformed-Texture : begin glyph-block Autobuild-Transformed-Texture : begin

View file

@ -14,7 +14,7 @@ export function finalizeGlyphs(cache, para, glyphStore) {
function regulateGlyphStore(cache, skew, glyphStore) { function regulateGlyphStore(cache, skew, glyphStore) {
const compositeMemo = new Map(); const compositeMemo = new Map();
for (const g of glyphStore.glyphs()) { for (const g of glyphStore.glyphs()) {
if (g.geometry.isEmpty()) continue; if (!(g.geometry.measureComplexity() & Geom.CPLX_NON_EMPTY)) continue;
if (!regulateCompositeGlyph(glyphStore, compositeMemo, g)) { if (!regulateCompositeGlyph(glyphStore, compositeMemo, g)) {
g.geometry = g.geometry.unlinkReferences(); g.geometry = g.geometry.unlinkReferences();
} }

View file

@ -47,7 +47,7 @@ class MappedGlyphStore {
g.horizontal = { start: 0, end: source.advanceWidth }; g.horizontal = { start: 0, end: source.advanceWidth };
// Fill Geometry // Fill Geometry
if (!source.geometry.isEmpty()) { if (source.geometry.measureComplexity() & Geom.CPLX_NON_EMPTY) {
const rs = source.geometry.asReferences(); const rs = source.geometry.asReferences();
if (rs) { if (rs) {
this.fillReferences(g, rs); this.fillReferences(g, rs);

View file

@ -4,7 +4,7 @@ import zlib from "zlib";
import * as CurveUtil from "@iosevka/geometry/curve-util"; import * as CurveUtil from "@iosevka/geometry/curve-util";
import { encode, decode } from "@msgpack/msgpack"; import { encode, decode } from "@msgpack/msgpack";
const Edition = 31; const Edition = 32;
const MAX_AGE = 16; const MAX_AGE = 16;
class GfEntry { class GfEntry {
constructor(age, value) { constructor(age, value) {

View file

@ -10,6 +10,11 @@ import { QuadifySink } from "./quadify.mjs";
import { SpiroExpander } from "./spiro-expand.mjs"; import { SpiroExpander } from "./spiro-expand.mjs";
import { Transform } from "./transform.mjs"; import { Transform } from "./transform.mjs";
export const CPLX_NON_EMPTY = 0x01; // A geometry tree that is not empty
export const CPLX_NON_SIMPLE = 0x02; // A geometry tree that contains non-simple contours
export const CPLX_BROKEN = 0x04; // A geometry tree that contains broken contours, like having points with NaN coordinates
export const CPLX_UNKNOWN = 0xff;
export class GeometryBase { export class GeometryBase {
asContours() { asContours() {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
@ -17,9 +22,6 @@ export class GeometryBase {
asReferences() { asReferences() {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }
producesSimpleContours() {
return false;
}
getDependencies() { getDependencies() {
throw new Error("Unimplemented"); throw new Error("Unimplemented");
} }
@ -29,11 +31,8 @@ export class GeometryBase {
filterTag(fn) { filterTag(fn) {
return this; return this;
} }
isEmpty() {
return true;
}
measureComplexity() { measureComplexity() {
return 0; return CPLX_UNKNOWN;
} }
toShapeStringOrNull() { toShapeStringOrNull() {
return null; return null;
@ -59,14 +58,14 @@ export class ContourSetGeometry extends GeometryBase {
filterTag(fn) { filterTag(fn) {
return this; return this;
} }
isEmpty() {
return !this.m_contours.length;
}
measureComplexity() { measureComplexity() {
for (const z of this.m_contours) { let cp = this.m_contours.length > 0 ? CPLX_NON_EMPTY : 0;
if (!isFinite(z.x) || !isFinite(z.y)) return 0xffff; for (const c of this.m_contours) {
for (const z of c) {
if (!isFinite(z.x) || !isFinite(z.y)) cp |= CPLX_BROKEN;
} }
return this.m_contours.length; }
return cp;
} }
toShapeStringOrNull() { toShapeStringOrNull() {
return Format.struct( return Format.struct(
@ -108,14 +107,12 @@ export class SpiroGeometry extends GeometryBase {
filterTag(fn) { filterTag(fn) {
return this; return this;
} }
isEmpty() {
return !this.m_knots.length;
}
measureComplexity() { measureComplexity() {
let cplx = CPLX_NON_EMPTY | CPLX_NON_SIMPLE;
for (const z of this.m_knots) { for (const z of this.m_knots) {
if (!isFinite(z.x) || !isFinite(z.y)) return 0xffff; if (!isFinite(z.x) || !isFinite(z.y)) cplx |= CPLX_BROKEN;
} }
return this.m_knots.length; return cplx;
} }
toShapeStringOrNull() { toShapeStringOrNull() {
return Format.struct( return Format.struct(
@ -183,14 +180,12 @@ export class DiSpiroGeometry extends GeometryBase {
filterTag(fn) { filterTag(fn) {
return this; return this;
} }
isEmpty() {
return !this.m_biKnots.length;
}
measureComplexity() { measureComplexity() {
let cplx = CPLX_NON_EMPTY | CPLX_NON_SIMPLE;
for (const z of this.m_biKnots) { for (const z of this.m_biKnots) {
if (!isFinite(z.x) || !isFinite(z.y)) return 0xffff; if (!isFinite(z.x) || !isFinite(z.y)) cplx |= CPLX_BROKEN;
} }
return this.m_biKnots.length; return cplx;
} }
toShapeStringOrNull() { toShapeStringOrNull() {
return Format.struct( return Format.struct(
@ -218,27 +213,17 @@ export class ReferenceGeometry extends GeometryBase {
); );
} }
asContours() { asContours() {
if (this.isEmpty()) return [];
return this.unwrap().asContours(); return this.unwrap().asContours();
} }
asReferences() { asReferences() {
if (this.isEmpty()) return [];
return [{ glyph: this.m_glyph, x: this.m_x, y: this.m_y }]; return [{ glyph: this.m_glyph, x: this.m_x, y: this.m_y }];
} }
producesSimpleContours() {
return this.unwrap().producesSimpleContours();
}
getDependencies() { getDependencies() {
return [this.m_glyph]; return [this.m_glyph];
} }
filterTag(fn) { filterTag(fn) {
if (this.isEmpty()) return null;
return this.unwrap().filterTag(fn); return this.unwrap().filterTag(fn);
} }
isEmpty() {
if (!this.m_glyph || !this.m_glyph.geometry) return true;
return this.m_glyph.geometry.isEmpty();
}
measureComplexity() { measureComplexity() {
return this.m_glyph.geometry.measureComplexity(); return this.m_glyph.geometry.measureComplexity();
} }
@ -264,9 +249,6 @@ export class TaggedGeometry extends GeometryBase {
asReferences() { asReferences() {
return this.m_geom.asReferences(); return this.m_geom.asReferences();
} }
producesSimpleContours() {
return this.m_geom.producesSimpleContours();
}
getDependencies() { getDependencies() {
return this.m_geom.getDependencies(); return this.m_geom.getDependencies();
} }
@ -274,9 +256,6 @@ export class TaggedGeometry extends GeometryBase {
if (!fn(this.m_tag)) return null; if (!fn(this.m_tag)) return null;
else return new TaggedGeometry(this.m_geom.filterTag(fn), this.m_tag); else return new TaggedGeometry(this.m_geom.filterTag(fn), this.m_tag);
} }
isEmpty() {
return this.m_geom.isEmpty();
}
measureComplexity() { measureComplexity() {
return this.m_geom.measureComplexity(); return this.m_geom.measureComplexity();
} }
@ -312,9 +291,6 @@ export class TransformedGeometry extends GeometryBase {
result.push({ glyph, x: x + this.m_transform.x, y: y + this.m_transform.y }); result.push({ glyph, x: x + this.m_transform.x, y: y + this.m_transform.y });
return result; return result;
} }
producesSimpleContours() {
return this.m_geom.producesSimpleContours();
}
getDependencies() { getDependencies() {
return this.m_geom.getDependencies(); return this.m_geom.getDependencies();
} }
@ -323,11 +299,11 @@ export class TransformedGeometry extends GeometryBase {
if (!e) return null; if (!e) return null;
return new TransformedGeometry(e, this.m_transform); return new TransformedGeometry(e, this.m_transform);
} }
isEmpty() {
return this.m_geom.isEmpty();
}
measureComplexity() { measureComplexity() {
return this.m_geom.measureComplexity(); return (
(Transform.isPositive(this.m_transform) ? 0 : CPLX_NON_SIMPLE) |
this.m_geom.measureComplexity()
);
} }
unlinkReferences() { unlinkReferences() {
const unwrapped = this.m_geom.unlinkReferences(); const unwrapped = this.m_geom.unlinkReferences();
@ -367,9 +343,6 @@ export class RadicalGeometry extends GeometryBase {
asReferences() { asReferences() {
return null; return null;
} }
producesSimpleContours() {
return this.m_geom.producesSimpleContours();
}
getDependencies() { getDependencies() {
return this.m_geom.getDependencies(); return this.m_geom.getDependencies();
} }
@ -378,9 +351,6 @@ export class RadicalGeometry extends GeometryBase {
if (!e) return null; if (!e) return null;
return new RadicalGeometry(e); return new RadicalGeometry(e);
} }
isEmpty() {
return this.m_geom.isEmpty();
}
measureComplexity() { measureComplexity() {
return this.m_geom.measureComplexity(); return this.m_geom.measureComplexity();
} }
@ -426,11 +396,6 @@ export class CombineGeometry extends GeometryBase {
} }
return results; return results;
} }
producesSimpleContours() {
if (this.m_parts.length === 0) return true;
if (this.m_parts.length > 1) return false;
return this.m_parts[0].producesSimpleContours();
}
getDependencies() { getDependencies() {
let results = []; let results = [];
for (const part of this.m_parts) { for (const part of this.m_parts) {
@ -448,13 +413,10 @@ export class CombineGeometry extends GeometryBase {
} }
return new CombineGeometry(filtered); return new CombineGeometry(filtered);
} }
isEmpty() {
for (const part of this.m_parts) if (!part.isEmpty()) return false;
return true;
}
measureComplexity() { measureComplexity() {
let s = 0; let s = 0;
for (const part of this.m_parts) s += part.measureComplexity(); for (const part of this.m_parts) s |= part.measureComplexity();
return s;
} }
unlinkReferences() { unlinkReferences() {
let parts = []; let parts = [];
@ -526,10 +488,6 @@ export class BooleanGeometry extends GeometryBase {
if (i > 0) sink.push({ type: "operator", operator: this.m_operator }); if (i > 0) sink.push({ type: "operator", operator: this.m_operator });
} }
} }
producesSimpleContours() {
return this.m_operands.length > 1;
}
asReferences() { asReferences() {
return null; return null;
} }
@ -550,13 +508,10 @@ export class BooleanGeometry extends GeometryBase {
} }
return new BooleanGeometry(this.m_operator, filtered); return new BooleanGeometry(this.m_operator, filtered);
} }
isEmpty() {
for (const operand of this.m_operands) if (!operand.isEmpty()) return false;
return true;
}
measureComplexity() { measureComplexity() {
let s = 0; let s = CPLX_NON_SIMPLE;
for (const operand of this.m_operands) s += operand.measureComplexity(); for (const operand of this.m_operands) s |= operand.measureComplexity();
return s;
} }
unlinkReferences() { unlinkReferences() {
if (this.m_operands.length === 0) return new CombineGeometry([]); if (this.m_operands.length === 0) return new CombineGeometry([]);
@ -587,7 +542,7 @@ export class SimplifyGeometry extends GeometryBase {
asContours() { asContours() {
// Produce simplified arcs // Produce simplified arcs
let arcs = CurveUtil.convertShapeToArcs(this.m_geom.asContours()); let arcs = CurveUtil.convertShapeToArcs(this.m_geom.asContours());
if (!this.m_geom.producesSimpleContours()) { if (this.m_geom.measureComplexity() & CPLX_NON_SIMPLE) {
arcs = TypoGeom.Boolean.removeOverlap( arcs = TypoGeom.Boolean.removeOverlap(
arcs, arcs,
TypoGeom.Boolean.PolyFillType.pftNonZero, TypoGeom.Boolean.PolyFillType.pftNonZero,
@ -616,9 +571,6 @@ export class SimplifyGeometry extends GeometryBase {
filterTag(fn) { filterTag(fn) {
return new SimplifyGeometry(this.m_geom.filterTag(fn)); return new SimplifyGeometry(this.m_geom.filterTag(fn));
} }
isEmpty() {
return this.m_geom.isEmpty();
}
measureComplexity() { measureComplexity() {
return this.m_geom.measureComplexity(); return this.m_geom.measureComplexity();
} }

View file

@ -80,6 +80,9 @@ export class Transform {
static isIdentity(tfm) { static isIdentity(tfm) {
return this.isTranslate(tfm) && tfm.x === 0 && tfm.y === 0; return this.isTranslate(tfm) && tfm.x === 0 && tfm.y === 0;
} }
static isPositive(tfm) {
return tfm.xx * tfm.yy - tfm.xy * tfm.yx > 0;
}
static Combine(...tfms) { static Combine(...tfms) {
let z00 = new Vec2(0, 0); let z00 = new Vec2(0, 0);
let z10 = new Vec2(1, 0); let z10 = new Vec2(1, 0);