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).
* 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 leaning marks placement for reversed k/F/P (#2150).

View file

@ -692,8 +692,16 @@ glyph-block Autobuild-Transformed : begin
include : Ungizmo
set currentGlyph.gizmo [Transform.Id]
include : ScaleAround (currentGlyph.advanceWidth / 2) 0 (-1) 1
set currentGlyph.gizmo 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
glyph-block Autobuild-Transformed-Texture : begin

View file

@ -14,7 +14,7 @@ export function finalizeGlyphs(cache, para, glyphStore) {
function regulateGlyphStore(cache, skew, glyphStore) {
const compositeMemo = new Map();
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)) {
g.geometry = g.geometry.unlinkReferences();
}

View file

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

View file

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

View file

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

View file

@ -80,6 +80,9 @@ export class Transform {
static isIdentity(tfm) {
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) {
let z00 = new Vec2(0, 0);
let z10 = new Vec2(1, 0);