From 635ad0cadba6f830a82fe8ae5f86d5f01b86d399 Mon Sep 17 00:00:00 2001 From: Belleve Date: Mon, 9 Sep 2024 18:29:00 -1000 Subject: [PATCH] Fix Macedonian Cyrillic Gje under italics (#2493). (#2495) * Fix Macedonian Cyrillic Gje under italics (#2493). * Drop Ogonek from manually specified glyphs --- changes/31.6.2.md | 1 + .../font-glyphs/src/auto-build/accents.ptl | 200 +----------------- .../src/auto-build/mark-doppelganger.ptl | 70 ------ .../font-glyphs/src/common/derivatives.ptl | 5 +- packages/font-glyphs/src/index.ptl | 2 +- .../src/letter-like/fraktur/lower-mnu.ptl | 1 - .../font-glyphs/src/letter/accent-builder.ptl | 199 +++++++++++++++++ .../font-glyphs/src/letter/latin/lower-a.ptl | 5 +- .../font-glyphs/src/letter/latin/lower-il.ptl | 4 +- packages/font-glyphs/src/letter/latin/u.ptl | 5 +- .../font-glyphs/src/letter/latin/upper-a.ptl | 5 +- .../font-glyphs/src/letter/latin/upper-e.ptl | 3 +- .../font-glyphs/src/letter/latin/upper-i.ptl | 4 +- packages/font-glyphs/src/letter/shared.ptl | 35 ++- .../font-glyphs/src/marks/doppelganger.ptl | 72 +++++++ packages/font-glyphs/src/marks/index.ptl | 1 + packages/glyph/src/block.mjs | 112 ++++++---- packages/glyph/src/glyph.mjs | 33 +-- 18 files changed, 379 insertions(+), 378 deletions(-) create mode 100644 changes/31.6.2.md delete mode 100644 packages/font-glyphs/src/auto-build/mark-doppelganger.ptl create mode 100644 packages/font-glyphs/src/letter/accent-builder.ptl create mode 100644 packages/font-glyphs/src/marks/doppelganger.ptl diff --git a/changes/31.6.2.md b/changes/31.6.2.md new file mode 100644 index 0000000000..33d491acdd --- /dev/null +++ b/changes/31.6.2.md @@ -0,0 +1 @@ +- Fix Macedonian Cyrillic Gje under italics (#2493). diff --git a/packages/font-glyphs/src/auto-build/accents.ptl b/packages/font-glyphs/src/auto-build/accents.ptl index ed0ba29b75..4ebae4407a 100644 --- a/packages/font-glyphs/src/auto-build/accents.ptl +++ b/packages/font-glyphs/src/auto-build/accents.ptl @@ -12,6 +12,8 @@ glyph-module glyph-block AutoBuild-Accents : begin glyph-block-import Common-Derivatives : query-glyph refer-glyph + glyph-block-import CommonShapes : NameUni + glyph-block-import Letter-Accent-Builder : TransformGlyphCompositionSequence define [suggestName _name] : begin local name _name @@ -27,197 +29,6 @@ glyph-block AutoBuild-Accents : begin set map.(key) amended return amended - # Here, we build a simplified substitution builder that does the mark substitutions - # This is similar to GSUB lookup type 6 but with more flexibility - define flex-params [substParts] : begin - local-parameter : parts - local-parameter : ignore -- MatchUtil.never - local-parameter : backtrack -- {} - local-parameter : input - local-parameter : lookAhead -- {} - local-parameter : replace - local igl 0 - while (igl < parts.length) : begin - local m : substMatch parts igl ignore backtrack input lookAhead - if [not m] - : then : inc igl - : else : begin - local inputGlyphs : ArrayUtil.mapIndexToItems parts m - local producedGlyphs : replace.apply null inputGlyphs - foreach i [range (m.length - 1) downtill 0] : begin - parts.splice m.(i) 1 - ArrayUtil.insertSliceAt parts m.0 producedGlyphs - set igl : m.(m.length - 1) + 1 + producedGlyphs.length - m.length - - return parts - - define [substMatch parts igl ignore backtrack input lookAhead] : begin - if (igl >= parts.length) : return null - if [ignore parts.(igl)] : return null - if [not : input.0 parts.(igl)] : return null - - local m { igl } - - # Check input - local iglInput : igl + 1 - foreach iInput [range 1 input.length] : begin - while (iglInput < parts.length && [ignore parts.(iglInput)]) : inc iglInput - if (iglInput >= parts.length) : return null - if [not : input.(iInput) parts.(iglInput)] : return null - m.push iglInput - inc iglInput - - # Check lookahead - local iglLookAhead iglInput - foreach iLookAhead [range 0 lookAhead.length] : begin - while (iglLookAhead < parts.length && [ignore parts.(iglLookAhead)]) : inc iglLookAhead - if (iglLookAhead >= parts.length) : return null - if [not : lookAhead.(iLookAhead) parts.(iglLookAhead)] : return null - inc iglLookAhead - - # Check backtrack - local iglBacktrack : igl - 1 - foreach iBacktrack [range (backtrack.length - 1) downtill 0] : begin - while (iglBacktrack >= 0 && [ignore parts.(iglBacktrack)]) : dec iglBacktrack - if (iglBacktrack < 0) : return null - if [not : backtrack.(iBacktrack) parts.(iglBacktrack)] : return null - dec iglBacktrack - - return m - - # Match/replace directives - define [dotless g] : begin - local gDotless : query-glyph : Dotless.get g - if [not gDotless] : return null - return {gDotless} - define [isMark k] : function [g] : begin - return : g && g.markAnchors && g.markAnchors.(k) - define [isMarkExcluding k] : function [g] : begin - return : g && g.markAnchors && [Object.keys g.markAnchors].length && !g.markAnchors.(k) - define [hasBaseAnchor k] : function [g] : begin - return : g && g.baseAnchors && g.baseAnchors.(k) - define [produceLeaningMark gnSuppressAnchor] : function [g] : begin - local spacer : query-glyph : LeaningMarkSpacer.get g - local alternative : query-glyph : LeaningMark.get g - if (spacer && alternative) : return { spacer alternative } - return { [query-glyph gnSuppressAnchor] g } - - define [markSubst uk] : begin - local mapping : new Map - foreach { k v } [Object.entries uk] : begin - local gFrom : query-glyph k - local gTo : query-glyph v - mapping.set gFrom gTo - - define [matcher g] : mapping.has g - define [ignore g] : g && g.markAnchors && [Object.keys g.markAnchors].length && ![matcher g] - define [production g] : list ([mapping.get g] || g) - - return : object matcher ignore production - - define [markSplit uk] : begin - local mapping : new Map - foreach { k v } [Object.entries uk] : begin - local gFrom : query-glyph k - local gsTo : v.map query-glyph - mapping.set gFrom gsTo - - define [matcher g] : mapping.has g - define [ignore g] : g && g.markAnchors && [Object.keys g.markAnchors].length && ![matcher g] - define [production g] : begin - console.log g [mapping.get g] - return : [mapping.get g] || { g } - - return : object matcher ignore production - - define [markCombine uk] : begin - local first : new Set - local second : new Set - local mapping : new Map - - foreach { { k1 k2 } v } [items-of uk] : begin - local g1 : query-glyph k1 - local g2 : query-glyph k2 - local g3 : query-glyph v - if (g1 && g2 && g3) : begin - first.add g1 - second.add g2 - - local mm : mapping.get g1 - if [not mm] : begin - set mm : new Map - mapping.set g1 mm - mm.set g2 g3 - - define [matchFirst g] : first.has g - define [matchSecond g] : second.has g - define [production g1 g2] : begin - local mm : mapping.get g1 - if [not mm] : return { g1 g2 } - local g3 : mm.get g2 - if [not g3] : return { g1 g2 } - return { g3 } - - return : object matchFirst matchSecond production - - define iotaLF : markSubst UnicodeKnowledge.iotaBelowToLfTf - define ogonek : markSplit UnicodeKnowledge.ogonekBelowToTRTf - define upperTonos : markSubst UnicodeKnowledge.upperGrekMarkToTonosTf - - define markComposition : markCombine UnicodeKnowledge.markCompositionTf - - define [subParts parts] : begin - ### Keep the semantics here synchronized with `ccmp` feature - - # Handle dotless form - substParts parts - ignore -- [isMarkExcluding 'above'] - input -- {dotless} - lookAhead -- {[isMark 'above']} - replace -- dotless - - # Handle iota subscript - substParts parts - ignore -- iotaLF.ignore - backtrack -- {[hasBaseAnchor 'lf']} - input -- {iotaLF.matcher} - replace -- iotaLF.production - - # Handle ogonek - substParts parts - ignore -- ogonek.ignore - backtrack -- {[hasBaseAnchor 'trailing']} - input -- {ogonek.matcher} - replace -- ogonek.production - - # Handle mark combinations (Greek) - substParts parts - input -- { markComposition.matchFirst markComposition.matchSecond } - replace -- markComposition.production - - # Handle upper Greek Tonos marks - substParts parts - backtrack -- {[hasBaseAnchor 'grekUpperTonos']} - input -- {upperTonos.matcher} - replace -- upperTonos.production - - # Handle leaning marks - substParts parts - ignore -- [isMarkExcluding 'above'] - backtrack -- {[MatchUtil.either [hasBaseAnchor 'leaningAbove'] [isMark 'leaningAbove']]} - input -- {[isMark 'above']} - replace -- [produceLeaningMark 'mark/suppressLeaningAboveAnchor'] - substParts parts - ignore -- [isMarkExcluding 'below'] - backtrack -- {[MatchUtil.either [hasBaseAnchor 'leaningBelow'] [isMark 'leaningBelow']]} - input -- {[isMark 'below']} - replace -- [produceLeaningMark 'mark/suppressLeaningBelowAnchor'] - - define [pad _s n] : begin - local s _s - while (s.length < n) : s = '0' + s - return s - local foundDecompositions {.} local goalCodes : range 0x0000 0x1FFFF @@ -228,7 +39,7 @@ glyph-block AutoBuild-Accents : begin local customDecomp UnicodeKnowledge.decompOverrides.(code) local str : String.fromCodePoint code - local nfd : str.normalize 'NFD' + local nfd : str.normalize 'NFD' if customDecomp : then : begin @@ -247,8 +58,8 @@ glyph-block AutoBuild-Accents : begin : else : parts.push part if (allFound && parts.length) : begin - local glyphName : 'u' + [code.toString 16 :.padStart 4 '0'] - subParts parts + local glyphName : NameUni code + TransformGlyphCompositionSequence parts set foundDecompositions.(glyphName) { glyphName code parts } local s_goalName nothing @@ -266,7 +77,6 @@ glyph-block AutoBuild-Accents : begin define construction : glyph-proc include s_parts.0 AS_BASE ALSO_METRICS local nonTrivial : AnyDerivingCv.hasNonDerivingVariants s_parts.0 - if nonTrivial : console.log s_parts.0 foreach part [items-of : s_parts.slice 1] : if part : begin include part if (part.markAnchors && part.markAnchors.bottomRight) : begin diff --git a/packages/font-glyphs/src/auto-build/mark-doppelganger.ptl b/packages/font-glyphs/src/auto-build/mark-doppelganger.ptl deleted file mode 100644 index f3bcf2ba3c..0000000000 --- a/packages/font-glyphs/src/auto-build/mark-doppelganger.ptl +++ /dev/null @@ -1,70 +0,0 @@ -$$include '../meta/macros.ptl' - -import [mix linreg clamp fallback] from "@iosevka/util" -import [TieMark AnyDerivingCv ScheduleLeaningMark LeaningMark LeaningMarkSpacer] from "@iosevka/glyph/relation" - -import [DesignParameters] from "../meta/aesthetics.mjs" - -extern Set - -glyph-module - -glyph-block Mark-Doppelganger : if [not recursive] : begin - glyph-block-import CommonShapes - glyph-block-import Common-Derivatives - glyph-block-import Mark-Adjustment : TieAnchorMap LeaningAnchorMap - - define [DeriveMarkChange gr gn akFrom akTo] : begin - DeriveMeshT {gn} AnyDerivingCv : function [gns] : begin - local srcGn gns.0 - local src : query-glyph srcGn - local toGN : gr.amendName srcGn - if [not : query-glyph toGN] : begin - create-glyph toGN : glyph-proc - include [refer-glyph srcGn] AS_BASE ALSO_METRICS - set currentGlyph.markAnchors.(akTo) currentGlyph.markAnchors.(akFrom) - currentGlyph.deleteMarkAnchor akFrom - set currentGlyph.baseAnchors.(akTo) currentGlyph.baseAnchors.(akFrom) - currentGlyph.deleteBaseAnchor akFrom - gr.set src toGN - return toGN - - - do : foreach { u gn g } [glyphStore.encodedEntries] : DeriveTieMarks gn g - : where : [DeriveTieMarks gn g] : begin - local selection null - foreach { akFrom akTo } [items-of TieAnchorMap] : begin - if (!selection && g.markAnchors.(akFrom)) : begin - set selection { akFrom akTo } - - if selection : begin - local { akFrom akTo } selection - DeriveMarkChange TieMark gn akFrom akTo - - - local spacerGlyphSet : new Set - do : foreach { u gn g } [glyphStore.encodedEntries] : DeriveLeaningMark gn g - : where : [DeriveLeaningMark gn g] : begin - local selection null - foreach { akFrom akTo } [items-of LeaningAnchorMap] : begin - if (!selection && g.markAnchors.(akFrom) && [ScheduleLeaningMark.get g]) : begin - set selection { akFrom akTo } - - if selection : begin - local { akFrom akTo } selection - - # Build the doppelganger - DeriveMarkChange LeaningMark gn akFrom akTo - - # build spacer glyph - local deltaX : Math.round : g.baseAnchors.(akFrom).x - g.markAnchors.(akFrom).x - local deltaY : Math.round : g.baseAnchors.(akFrom).y - g.markAnchors.(akFrom).y - local spacerGn "spacerGlyph{\(akTo)}{\(deltaX)}{\(deltaY)}" - - LeaningMarkSpacer.set g spacerGn - - if [not : spacerGlyphSet.has spacerGn] : begin - spacerGlyphSet.add spacerGn - create-glyph spacerGn : glyph-proc - include g AS_BASE ALSO_METRICS - currentGlyph.clearGeometry diff --git a/packages/font-glyphs/src/common/derivatives.ptl b/packages/font-glyphs/src/common/derivatives.ptl index 02468f6da2..ccac6faede 100644 --- a/packages/font-glyphs/src/common/derivatives.ptl +++ b/packages/font-glyphs/src/common/derivatives.ptl @@ -15,7 +15,7 @@ glyph-block Common-Derivatives : begin if suffix : begin local dstName : shapeFrom + '.' + suffix local dstGlyph : query-glyph dstName - if dstGlyph : g.dependsOn dstGlyph + if dstGlyph : g.addVariantForRecursiveBuild dstGlyph if (follow === primaryFollow && para.enableCvSs && pv.tag && pv.rank) : begin pv.set g dstName @@ -35,7 +35,6 @@ glyph-block Common-Derivatives : begin if [not fromGlyph] : throw : new Error "Cannot find glyph '\(fromGlyphName)'" include fromGlyph AS_BASE ALSO_METRICS - currentGlyph.dependsOn fromGlyph currentGlyph.cloneRankFromGlyph fromGlyph ApplyCv currentGlyph shapeFrom [resolveMainFollow follow] [resolveAllFollow follow] para @@ -106,7 +105,7 @@ glyph-block Common-Derivatives : begin linksGnMap.set key gnDerivedTo local gDerivedTo : query-glyph gnDerivedTo - if (gSource && gDerivedTo) : gSource.dependsOn gDerivedTo + if (gSource && gDerivedTo) : gSource.addVariantForRecursiveBuild gDerivedTo # Link related derivatives foreach { gr from to } [items-of mesh] : begin diff --git a/packages/font-glyphs/src/index.ptl b/packages/font-glyphs/src/index.ptl index 4cb9d4b835..df4768fa6d 100644 --- a/packages/font-glyphs/src/index.ptl +++ b/packages/font-glyphs/src/index.ptl @@ -74,6 +74,7 @@ export : define [buildGlyphs para recursive] : begin run-glyph-module "./marks/index.mjs" # Unified letters + run-glyph-module "./letter/accent-builder.mjs" run-glyph-module "./letter/shared.mjs" run-glyph-module "./letter/latin.mjs" run-glyph-module "./letter/greek.mjs" @@ -102,7 +103,6 @@ export : define [buildGlyphs para recursive] : begin # Auto-builds if [not recursive] : begin run-glyph-module "./auto-build/recursive-build.mjs" - run-glyph-module "./auto-build/mark-doppelganger.mjs" run-glyph-module "./auto-build/accents.mjs" run-glyph-module "./auto-build/transformed.mjs" run-glyph-module "./auto-build/composite.mjs" diff --git a/packages/font-glyphs/src/letter-like/fraktur/lower-mnu.ptl b/packages/font-glyphs/src/letter-like/fraktur/lower-mnu.ptl index 7e85fae0c5..a7e96b1bed 100644 --- a/packages/font-glyphs/src/letter-like/fraktur/lower-mnu.ptl +++ b/packages/font-glyphs/src/letter-like/fraktur/lower-mnu.ptl @@ -7,7 +7,6 @@ glyph-module glyph-block LetterLike-Fraktur-Lower-MNU : begin glyph-block-import Common-Derivatives glyph-block-import CommonShapes - glyph-block-import Letter-Shared : CreateAccentedComposition glyph-block-import Mark-Shared-Metrics : markMiddle markDotsRadius glyph-block-import Mark-Above : StdAnchors aboveMarkTop aboveMarkBot aboveMarkMid aboveMarkStack glyph-block-import LetterLike-Fraktur-Common : LowerDf S M fraktur-stroke change-pen diff --git a/packages/font-glyphs/src/letter/accent-builder.ptl b/packages/font-glyphs/src/letter/accent-builder.ptl new file mode 100644 index 0000000000..12dd194686 --- /dev/null +++ b/packages/font-glyphs/src/letter/accent-builder.ptl @@ -0,0 +1,199 @@ +$$include '../meta/macros.ptl' + +import [fallback ArrayUtil MatchUtil] from "@iosevka/util" +import [Dotless LeaningMark LeaningMarkSpacer] from "@iosevka/glyph/relation" +import as UnicodeKnowledge from "../meta/unicode-knowledge.mjs" + +extern Map +extern Set + +glyph-module + +glyph-block Letter-Accent-Builder : begin + glyph-block-import Common-Derivatives : query-glyph refer-glyph + + ### Perform in-place transformation of the glyphs used to build accented letters + glyph-block-export TransformGlyphCompositionSequence + define [TransformGlyphCompositionSequence parts] : begin + ### Keep the semantics here synchronized with `ccmp` feature + # Handle dotless form + substParts parts + ignore -- [isMarkExcluding 'above'] + input -- {dotless} + lookAhead -- {[isMark 'above']} + replace -- dotless + + # Handle iota subscript + substParts parts + ignore -- iotaLF.ignore + backtrack -- {[hasBaseAnchor 'lf']} + input -- {iotaLF.matcher} + replace -- iotaLF.production + + # Handle ogonek + substParts parts + ignore -- ogonek.ignore + backtrack -- {[hasBaseAnchor 'trailing']} + input -- {ogonek.matcher} + replace -- ogonek.production + + # Handle mark combinations (Greek) + substParts parts + input -- { markComposition.matchFirst markComposition.matchSecond } + replace -- markComposition.production + + # Handle upper Greek Tonos marks + substParts parts + backtrack -- {[hasBaseAnchor 'grekUpperTonos']} + input -- {upperTonos.matcher} + replace -- upperTonos.production + + # Handle leaning marks + substParts parts + ignore -- [isMarkExcluding 'above'] + backtrack -- {[MatchUtil.either [hasBaseAnchor 'leaningAbove'] [isMark 'leaningAbove']]} + input -- {[isMark 'above']} + replace -- [produceLeaningMark 'mark/suppressLeaningAboveAnchor'] + substParts parts + ignore -- [isMarkExcluding 'below'] + backtrack -- {[MatchUtil.either [hasBaseAnchor 'leaningBelow'] [isMark 'leaningBelow']]} + input -- {[isMark 'below']} + replace -- [produceLeaningMark 'mark/suppressLeaningBelowAnchor'] + + # Here, we build a simplified substitution builder that does the mark substitutions + # This is similar to GSUB lookup type 6 but with more flexibility + define flex-params [substParts] : begin + local-parameter : parts + local-parameter : ignore -- MatchUtil.never + local-parameter : backtrack -- {} + local-parameter : input + local-parameter : lookAhead -- {} + local-parameter : replace + local igl 0 + while (igl < parts.length) : begin + local m : substMatch parts igl ignore backtrack input lookAhead + if [not m] + : then : inc igl + : else : begin + local inputGlyphs : ArrayUtil.mapIndexToItems parts m + local producedGlyphs : replace.apply null inputGlyphs + foreach i [range (m.length - 1) downtill 0] : begin + parts.splice m.(i) 1 + ArrayUtil.insertSliceAt parts m.0 producedGlyphs + set igl : m.(m.length - 1) + 1 + producedGlyphs.length - m.length + + return parts + + define [substMatch parts igl ignore backtrack input lookAhead] : begin + if (igl >= parts.length) : return null + if [ignore parts.(igl)] : return null + if [not : input.0 parts.(igl)] : return null + + local m { igl } + + # Check input + local iglInput : igl + 1 + foreach iInput [range 1 input.length] : begin + while (iglInput < parts.length && [ignore parts.(iglInput)]) : inc iglInput + if (iglInput >= parts.length) : return null + if [not : input.(iInput) parts.(iglInput)] : return null + m.push iglInput + inc iglInput + + # Check lookahead + local iglLookAhead iglInput + foreach iLookAhead [range 0 lookAhead.length] : begin + while (iglLookAhead < parts.length && [ignore parts.(iglLookAhead)]) : inc iglLookAhead + if (iglLookAhead >= parts.length) : return null + if [not : lookAhead.(iLookAhead) parts.(iglLookAhead)] : return null + inc iglLookAhead + + # Check backtrack + local iglBacktrack : igl - 1 + foreach iBacktrack [range (backtrack.length - 1) downtill 0] : begin + while (iglBacktrack >= 0 && [ignore parts.(iglBacktrack)]) : dec iglBacktrack + if (iglBacktrack < 0) : return null + if [not : backtrack.(iBacktrack) parts.(iglBacktrack)] : return null + dec iglBacktrack + + return m + + # Match/replace directives + define [dotless g] : begin + local gDotless : query-glyph : Dotless.get g + if [not gDotless] : return null + return {gDotless} + define [isMark k] : function [g] : begin + return : g && g.markAnchors && g.markAnchors.(k) + define [isMarkExcluding k] : function [g] : begin + return : g && g.markAnchors && [Object.keys g.markAnchors].length && !g.markAnchors.(k) + define [hasBaseAnchor k] : function [g] : begin + return : g && g.baseAnchors && g.baseAnchors.(k) + + define [markSubst uk] : begin + local mapping : new Map + foreach { k v } [Object.entries uk] : begin + local gFrom : query-glyph k + local gTo : query-glyph v + mapping.set gFrom gTo + + define [matcher g] : mapping.has g + define [ignore g] : g && g.markAnchors && [Object.keys g.markAnchors].length && ![matcher g] + define [production g] : list ([mapping.get g] || g) + + return : object matcher ignore production + + define [markSplit uk] : begin + local mapping : new Map + foreach { k v } [Object.entries uk] : begin + local gFrom : query-glyph k + local gsTo : v.map query-glyph + mapping.set gFrom gsTo + + define [matcher g] : mapping.has g + define [ignore g] : g && g.markAnchors && [Object.keys g.markAnchors].length && ![matcher g] + define [production g] : [mapping.get g] || { g } + + return : object matcher ignore production + + define [markCombine uk] : begin + local first : new Set + local second : new Set + local mapping : new Map + + foreach { { k1 k2 } v } [items-of uk] : begin + local g1 : query-glyph k1 + local g2 : query-glyph k2 + local g3 : query-glyph v + if (g1 && g2 && g3) : begin + first.add g1 + second.add g2 + + local mm : mapping.get g1 + if [not mm] : begin + set mm : new Map + mapping.set g1 mm + mm.set g2 g3 + + define [matchFirst g] : first.has g + define [matchSecond g] : second.has g + define [production g1 g2] : begin + local mm : mapping.get g1 + if [not mm] : return { g1 g2 } + local g3 : mm.get g2 + if [not g3] : return { g1 g2 } + return { g3 } + + return : object matchFirst matchSecond production + + define iotaLF : markSubst UnicodeKnowledge.iotaBelowToLfTf + define ogonek : markSplit UnicodeKnowledge.ogonekBelowToTRTf + define upperTonos : markSubst UnicodeKnowledge.upperGrekMarkToTonosTf + + define markComposition : markCombine UnicodeKnowledge.markCompositionTf + + define [produceLeaningMark gnSuppressAnchor] : function [g] : begin + local spacer : query-glyph : LeaningMarkSpacer.get g + local alternative : query-glyph : LeaningMark.get g + if (spacer && alternative) : return { spacer alternative } + return { [query-glyph gnSuppressAnchor] g } diff --git a/packages/font-glyphs/src/letter/latin/lower-a.ptl b/packages/font-glyphs/src/letter/latin/lower-a.ptl index e34c9f5a47..22029c3171 100644 --- a/packages/font-glyphs/src/letter/latin/lower-a.ptl +++ b/packages/font-glyphs/src/letter/latin/lower-a.ptl @@ -9,7 +9,7 @@ glyph-block Letter-Latin-Lower-A : begin glyph-block-import CommonShapes glyph-block-import Common-Derivatives glyph-block-import Mark-Shared-Metrics : markHalfStroke - glyph-block-import Letter-Shared : CreateAccentedComposition CreateOgonekComposition + glyph-block-import Letter-Shared : CreateAccentedComposition glyph-block-import Letter-Shared : CreateTurnedLetter glyph-block-import Letter-Shared-Shapes : SerifFrame OBarLeft OBarRight ArcStartSerif glyph-block-import Letter-Shared-Shapes : RightwardTailedBar InvRightwardTailedBar @@ -287,6 +287,3 @@ glyph-block Letter-Latin-Lower-A : begin CreateAccentedComposition 'aDieresis' 0xE4 'a' 'dieresisAbove' CreateAccentedComposition 'aSbRsbUnderlineBelow' null 'a' 'sbRsbUnderlineBelow' CreateAccentedComposition 'aRightHalfRingTR' 0x1E9A 'a' 'rightHalfCircleTR' - - # Ognoek shapes - CreateOgonekComposition 'aOgonek' 0x105 'a' diff --git a/packages/font-glyphs/src/letter/latin/lower-il.ptl b/packages/font-glyphs/src/letter/latin/lower-il.ptl index da16856266..e5b8433986 100644 --- a/packages/font-glyphs/src/letter/latin/lower-il.ptl +++ b/packages/font-glyphs/src/letter/latin/lower-il.ptl @@ -10,7 +10,7 @@ glyph-block Letter-Latin-Lower-I : begin glyph-block-import Common-Derivatives glyph-block-import Mark-Adjustment : LeaningAnchor ExtendBelowBaseAnchors glyph-block-import Letter-Shared : CreateAccentedComposition CreateMultiAccentedComposition - glyph-block-import Letter-Shared : CreateCommaCaronComposition CreateOgonekComposition + glyph-block-import Letter-Shared : CreateCommaCaronComposition glyph-block-import Letter-Shared : CreateTurnedLetter glyph-block-import Letter-Shared-Shapes : FlatHookDepth DiagTail glyph-block-import Letter-Shared-Shapes : CurlyTail BeltOverlay PalatalHook @@ -298,7 +298,6 @@ glyph-block Letter-Latin-Lower-I : begin link-reduced-variant 'dotlessi/sansSerif' 'dotlessi' MathSansSerif select-variant 'dotlessi/compLigRight' (shapeFrom -- 'dotlessi') select-variant 'dotlessiRetroflexHook' (follow -- 'dotlessi') - CreateOgonekComposition 'iOgonek.dotless' null 'dotlessi' CreateAccentedComposition 'i' 'i' 'dotlessi' 'tittleAbove' CreateAccentedComposition 'i/sansSerif' null 'dotlessi/sansSerif' 'tittleAbove' @@ -328,7 +327,6 @@ glyph-block Letter-Latin-Lower-I : begin CreateAccentedComposition 'dotlessiBarOver' null 'dotlessi' 'barOver' CreateAccentedComposition 'iBarOver' 0x268 'dotlessiBarOver' 'tittleAbove' - CreateAccentedComposition 'iOgonek' 0x12F 'iOgonek.dotless' 'tittleAbove' CreateAccentedComposition 'iRetroflexHook' 0x1D96 'dotlessiRetroflexHook' 'tittleAbove' do "l glyphs" diff --git a/packages/font-glyphs/src/letter/latin/u.ptl b/packages/font-glyphs/src/letter/latin/u.ptl index b9e3ee0212..b5e0c7b30f 100644 --- a/packages/font-glyphs/src/letter/latin/u.ptl +++ b/packages/font-glyphs/src/letter/latin/u.ptl @@ -10,7 +10,7 @@ glyph-block Letter-Latin-U : begin glyph-block-import Common-Derivatives glyph-block-import Mark-Shared-Metrics : markHalfStroke glyph-block-import Mark-Adjustment : LeaningAnchor - glyph-block-import Letter-Shared : CreateAccentedComposition CreateOgonekComposition + glyph-block-import Letter-Shared : CreateAccentedComposition glyph-block-import Letter-Shared : SetGrekUpperTonos glyph-block-import Letter-Shared-Shapes : nShoulder RightwardTailedBar DToothlessRise SerifFrame glyph-block-import Letter-Shared-Shapes : CyrDescender CyrTailDescender RetroflexHook VerticalHook @@ -452,6 +452,3 @@ glyph-block Letter-Latin-U : begin include : Translate 0 (SB / 2) CreateAccentedComposition 'uDieresisSideways' 0x1D1E 'uDieresisSidewaysBase' 'uDieresisSidewaysMark' - - CreateOgonekComposition 'UOgonek' 0x172 'U' - CreateOgonekComposition 'uOgonek' 0x173 'u' diff --git a/packages/font-glyphs/src/letter/latin/upper-a.ptl b/packages/font-glyphs/src/letter/latin/upper-a.ptl index 5c861102b9..4833cf1041 100644 --- a/packages/font-glyphs/src/letter/latin/upper-a.ptl +++ b/packages/font-glyphs/src/letter/latin/upper-a.ptl @@ -11,7 +11,8 @@ glyph-block Letter-Latin-Upper-A : begin glyph-block-import CommonShapes glyph-block-import Common-Derivatives glyph-block-import Mark-Shared-Metrics : markHalfStroke - glyph-block-import Letter-Shared : SetGrekUpperTonos CreateOgonekComposition CreateTurnedLetter + glyph-block-import Letter-Shared : CreateAccentedComposition + glyph-block-import Letter-Shared : SetGrekUpperTonos CreateTurnedLetter glyph-block-import Letter-Shared-Shapes : SerifFrame glyph-block-import Letter-Latin-V : VShapeOutline VShape VCornerHalfWidth @@ -185,5 +186,3 @@ glyph-block Letter-Latin-Upper-A : begin BBVInnerMaskShape SB RightSB 1 1 CAP HBar.t 0 Width (CAP - XH / 2) BBS include : FlipAround Middle (CAP / 2) - - CreateOgonekComposition 'AOgonek' 0x104 'A' diff --git a/packages/font-glyphs/src/letter/latin/upper-e.ptl b/packages/font-glyphs/src/letter/latin/upper-e.ptl index 77d52f713a..4bb29da852 100644 --- a/packages/font-glyphs/src/letter/latin/upper-e.ptl +++ b/packages/font-glyphs/src/letter/latin/upper-e.ptl @@ -9,7 +9,7 @@ glyph-block Letter-Latin-Upper-E : begin glyph-block-import CommonShapes glyph-block-import Common-Derivatives glyph-block-import Mark-Shared-Metrics : markHalfStroke - glyph-block-import Letter-Shared : SetGrekUpperTonos CreateOgonekComposition CreateTurnedLetter + glyph-block-import Letter-Shared : CreateAccentedComposition SetGrekUpperTonos CreateTurnedLetter glyph-block-import Letter-Latin-Upper-F : xMidBarShrink yMidBar EFVJutLength define kSB 1 @@ -101,4 +101,3 @@ glyph-block Letter-Latin-Upper-E : begin VBar.l (xEBarLeft + BBD) 0 CAP BBS # Ognoek shapes - CreateOgonekComposition 'EOgonek' 0x118 'E' diff --git a/packages/font-glyphs/src/letter/latin/upper-i.ptl b/packages/font-glyphs/src/letter/latin/upper-i.ptl index b001b8b01c..5cf388f59c 100644 --- a/packages/font-glyphs/src/letter/latin/upper-i.ptl +++ b/packages/font-glyphs/src/letter/latin/upper-i.ptl @@ -8,7 +8,7 @@ glyph-module glyph-block Letter-Latin-Upper-I : begin glyph-block-import CommonShapes glyph-block-import Common-Derivatives - glyph-block-import Letter-Shared : SetGrekUpperTonos CreateAccentedComposition CreateOgonekComposition + glyph-block-import Letter-Shared : SetGrekUpperTonos CreateAccentedComposition glyph-block-import Mark-Adjustment : ExtendAboveBaseAnchors define [ISeriflessShape df top bot jut] : glyph-proc @@ -73,5 +73,3 @@ glyph-block Letter-Latin-Upper-I : begin include : HBar.b (Middle - BBD / 2 - Jut) (Middle + BBD / 2 + Jut) 0 BBS CreateAccentedComposition 'smcpIBarOver' 0x1D7B 'smcpI' 'barOver' - - CreateOgonekComposition 'IOgonek' 0x12E 'I' diff --git a/packages/font-glyphs/src/letter/shared.ptl b/packages/font-glyphs/src/letter/shared.ptl index cad4c16558..30fb093728 100644 --- a/packages/font-glyphs/src/letter/shared.ptl +++ b/packages/font-glyphs/src/letter/shared.ptl @@ -12,36 +12,25 @@ glyph-block Letter-Shared : begin glyph-block-import CommonShapes glyph-block-import Common-Derivatives glyph-block-import Mark-Adjustment : TurnMarks LeaningAnchorMap + glyph-block-import Letter-Accent-Builder : TransformGlyphCompositionSequence + + glyph-block-export CreateAccentedComposition + define [CreateAccentedComposition dst u srcGid accentGid] + CreateMultiAccentedComposition dst u srcGid { accentGid } glyph-block-export CreateMultiAccentedComposition define [CreateMultiAccentedComposition dst u gnSrc gnAccents] derive-multi-part-glyphs dst u { gnSrc :: gnAccents } : function [gns gr] : glyph-proc - local { gnBase :: gnAccents } gns + local glyphParts : gns.map : function [gn] : query-glyph gn + TransformGlyphCompositionSequence glyphParts - include [refer-glyph gnBase] AS_BASE ALSO_METRICS - foreach gnAccent [items-of gnAccents] : begin - local gAccent : query-glyph gnAccent - currentGlyph.includeMarkWithLeaningSupport gAccent LeaningAnchorMap + include glyphParts.0 AS_BASE ALSO_METRICS + foreach part [items-of : glyphParts.slice 1] : if part : include part if (!gr) : begin - if (gnAccents.length === 1 && [gnAccents.0.endsWith 'tittleAbove']) - : then : Dotless.set currentGlyph gnBase - : else : CvDecompose.set currentGlyph { gnSrc :: gnAccents } - - glyph-block-export CreateAccentedComposition - define [CreateAccentedComposition dst u srcGid accentGid] - CreateMultiAccentedComposition dst u srcGid { accentGid } - - glyph-block-export CreateOgonekComposition - define [CreateOgonekComposition dst u srcGid] - derive-multi-part-glyphs dst u { srcGid 'ogonekBelow' 'ogonekTR/spacer' 'ogonekTR' } : function [gns gr] : glyph-proc - local { base markBelow spacer markTR } gns - include [refer-glyph base] AS_BASE ALSO_METRICS - if currentGlyph.baseAnchors.trailing - then : begin - include [refer-glyph spacer] - include [refer-glyph markTR] - else : include [refer-glyph markBelow] + if (gns.length === 2 && [gns.1.endsWith 'tittleAbove']) + : then : Dotless.set currentGlyph gns.0 + : else : CvDecompose.set currentGlyph gns glyph-block-export CreateCommaCaronComposition define [CreateCommaCaronComposition dst u sourceGid offset] diff --git a/packages/font-glyphs/src/marks/doppelganger.ptl b/packages/font-glyphs/src/marks/doppelganger.ptl new file mode 100644 index 0000000000..f4b3145530 --- /dev/null +++ b/packages/font-glyphs/src/marks/doppelganger.ptl @@ -0,0 +1,72 @@ +$$include '../meta/macros.ptl' + +import [mix linreg clamp fallback] from "@iosevka/util" +import [TieMark AnyDerivingCv ScheduleLeaningMark LeaningMark LeaningMarkSpacer] from "@iosevka/glyph/relation" + +import [DesignParameters] from "../meta/aesthetics.mjs" + +extern Set + +glyph-module + +glyph-block Mark-Doppelganger : begin + glyph-block-import CommonShapes + glyph-block-import Common-Derivatives + glyph-block-import Mark-Adjustment : TieAnchorMap LeaningAnchorMap + + define [DeriveMarkChange gr gn akFrom akTo] : begin + DeriveMeshT {gn} AnyDerivingCv : function [gns] : begin + local srcGn gns.0 + local src : query-glyph srcGn + local toGN : gr.amendName srcGn + if [not : query-glyph toGN] : begin + create-glyph toGN : glyph-proc + include [refer-glyph srcGn] AS_BASE ALSO_METRICS + set currentGlyph.markAnchors.(akTo) currentGlyph.markAnchors.(akFrom) + currentGlyph.deleteMarkAnchor akFrom + set currentGlyph.baseAnchors.(akTo) currentGlyph.baseAnchors.(akFrom) + currentGlyph.deleteBaseAnchor akFrom + + src.addVariantForRecursiveBuild [query-glyph toGN] + gr.set src toGN + return toGN + + define [DeriveSpacer gr gn] : begin + DeriveMeshT {gn} AnyDerivingCv : function [gns] : begin + local srcGn gns.0 + local src : query-glyph srcGn + local toGN : gr.amendName srcGn + + if [not : query-glyph toGN] : begin + create-glyph toGN : glyph-proc + include [refer-glyph srcGn] AS_BASE ALSO_METRICS + currentGlyph.clearGeometry + + src.addVariantForRecursiveBuild [query-glyph toGN] + gr.set src toGN + return toGN + + # Building the doppelganger and spacer glyph for leaning marks + do : foreach { gn g } [glyphStore.namedEntries] : begin + DeriveTieMarks gn g + DeriveLeaningMark gn g + : where + [PickTieMarkClasses g] : begin + foreach { akFrom akTo } [items-of TieAnchorMap] : begin + if (g.markAnchors.(akFrom)) : begin + return { akFrom akTo } + return null + [DeriveTieMarks gn g] : begin + local cls : PickTieMarkClasses g + if cls : DeriveMarkChange TieMark gn cls.0 cls.1 + + [PickLeaningMarkClasses g] : begin + foreach { akFrom akTo } [items-of LeaningAnchorMap] : begin + if ([ScheduleLeaningMark.get g] && g.markAnchors.(akFrom)) : begin + return { akFrom akTo } + return null + [DeriveLeaningMark gn g] : begin + local cls : PickLeaningMarkClasses g + if cls : begin + DeriveMarkChange LeaningMark gn cls.0 cls.1 + DeriveSpacer LeaningMarkSpacer gn diff --git a/packages/font-glyphs/src/marks/index.ptl b/packages/font-glyphs/src/marks/index.ptl index 9ccce20641..361fe525ba 100644 --- a/packages/font-glyphs/src/marks/index.ptl +++ b/packages/font-glyphs/src/marks/index.ptl @@ -13,3 +13,4 @@ export : define [apply] : begin run-glyph-module "./composite.mjs" run-glyph-module "./adjust.mjs" + run-glyph-module "./doppelganger.mjs" diff --git a/packages/glyph/src/block.mjs b/packages/glyph/src/block.mjs index 686119f83d..d98af9d70d 100644 --- a/packages/glyph/src/block.mjs +++ b/packages/glyph/src/block.mjs @@ -49,9 +49,13 @@ export class RecursiveBuildFilter { } } +const DEP_TRAVERSE_PENDING = 1, + DEP_TRAVERSE_CHECKED = 2; + export class DependencyManager { constructor() { this.glyphToGlyph = new WeakMap(); + this.glyphToGlyphVariant = new WeakMap(); this.glyphToBlock = new WeakMap(); this.blockToGlyph = new Map(); } @@ -63,6 +67,14 @@ export class DependencyManager { } s.add(dependency); } + addVariantDependency(dependent, dependency) { + let s = this.glyphToGlyphVariant.get(dependent); + if (!s) { + s = new Set(); + this.glyphToGlyphVariant.set(dependent, s); + } + s.add(dependency); + } hasGlyphToGlyphDependency(dependent, dependency) { return this.hasGlyphToGlyphDependencyImpl(new Set(), dependent, dependency); } @@ -84,47 +96,6 @@ export class DependencyManager { return false; } - traverseGlyphDependenciesImpl(glyphs, fBlockwiseExpand) { - let state = new Map(); - const PENDING = 1, - CHECKED = 2; - - for (const glyph of glyphs) state.set(glyph, PENDING); - - // When fBlockwiseExpand is true, we need to expand the initial glyph set - // to include all glyphs in the same block. - if (fBlockwiseExpand) { - let blocks = new Set(); - for (const glyph of glyphs) { - let b = this.glyphToBlock.get(glyph); - if (b) blocks.add(b); - } - for (const b of blocks) { - const glyphs = this.blockToGlyph.get(b); - if (glyphs) { - for (const g of glyphs) state.set(g, PENDING); - } - } - } - - // Traverse the dependency graph - for (;;) { - let found = false; - for (const [glyph, s] of state) { - if (s !== PENDING) continue; - const deps = this.glyphToGlyph.get(glyph); - if (deps) { - for (const g of deps) state.set(g, PENDING); - found = true; - } - state.set(glyph, CHECKED); - } - if (!found) break; - } - - return state; - } - traverseDependencies(glyphs) { const gGlyphGraph = this.traverseGlyphDependenciesImpl(glyphs, false); const gBlockGraph = this.traverseGlyphDependenciesImpl(glyphs, true); @@ -141,6 +112,56 @@ export class DependencyManager { return new RecursiveBuildFilter(glyphIdFilter, blockIdFilter); } + + traverseGlyphDependenciesImpl(glyphs, fBlockwiseExpand) { + let state = new Map(); + for (const glyph of glyphs) if (glyph) state.set(glyph, DEP_TRAVERSE_PENDING); + + for (let cycle = 0; cycle < 64; cycle++) { + let szBefore = state.size; + this.expandeByVariants(state); + if (fBlockwiseExpand) this.blockwiseExpandGlyphs(state); + this.traverseDirectDependenciesImpl(state); + if (state.size === szBefore) break; + } + + return state; + } + expandeByVariants(state) { + for (const glyph of state.keys()) { + const vs = this.glyphToGlyphVariant.get(glyph); + if (!vs) continue; + for (const v of vs) if (v && !state.has(v)) state.set(v, DEP_TRAVERSE_PENDING); + } + } + blockwiseExpandGlyphs(state) { + let blocks = new Set(); + for (const glyph of state.keys()) { + let b = this.glyphToBlock.get(glyph); + if (b) blocks.add(b); + } + for (const b of blocks) { + const glyphs = this.blockToGlyph.get(b); + if (!glyphs) continue; + for (const g of glyphs) if (g && !state.has(g)) state.set(g, DEP_TRAVERSE_PENDING); + } + } + traverseDirectDependenciesImpl(state) { + // Traverse the dependency graph + for (let cycle = 0; cycle < 64; cycle++) { + let found = false; + for (const [glyph, s] of state) { + if (s !== DEP_TRAVERSE_PENDING) continue; + const deps = this.glyphToGlyph.get(glyph); + if (deps) { + for (const g of deps) if (g) state.set(g, DEP_TRAVERSE_PENDING); + found = true; + } + state.set(glyph, DEP_TRAVERSE_CHECKED); + } + if (!found) break; + } + } } export class GlyphBlock { @@ -191,7 +212,14 @@ export class GlyphSaveSink { ); } + saveFalse($1, $2, contents) { + return this.saveImpl(true, $1, $2, contents); + } save($1, $2, contents) { + return this.saveImpl(false, $1, $2, contents); + } + + saveImpl(fForce, $1, $2, contents) { // Figure out the glyph name and unicode to save let saveGlyphName = null; let unicode = null; @@ -204,7 +232,7 @@ export class GlyphSaveSink { } // If we are in a recursive build run, and the glyph is not needed, skip it - if (saveGlyphName && !this.glyphIsNeeded(saveGlyphName)) return; + if (!fForce && saveGlyphName && !this.glyphIsNeeded(saveGlyphName)) return; // Create the glyph object & include the contents const glyphObject = new Glyph(saveGlyphName); diff --git a/packages/glyph/src/glyph.mjs b/packages/glyph/src/glyph.mjs index 6f67b000e0..e80fcea9e3 100644 --- a/packages/glyph/src/glyph.mjs +++ b/packages/glyph/src/glyph.mjs @@ -5,8 +5,6 @@ import { Anchor } from "@iosevka/geometry/anchor"; import { Vec2 } from "@iosevka/geometry/point"; import { Transform } from "@iosevka/geometry/transform"; -import { ScheduleLeaningMark } from "./relation.mjs"; - export class Glyph { constructor(identifier) { this._m_identifier = identifier; @@ -66,12 +64,18 @@ export class Glyph { // Dependency dependsOn(glyph) { if (!this._m_dependencyManager) return; + if (!glyph) throw new TypeError("Dependency glyph is null or undefined"); this._m_dependencyManager.addDependency(this, glyph); } hasDependency(other) { if (!this._m_dependencyManager) return false; return this._m_dependencyManager.hasGlyphToGlyphDependency(this, other); } + addVariantForRecursiveBuild(variantGlyph) { + if (!this._m_dependencyManager) return; + if (!variantGlyph) throw new TypeError("Variant glyph is null or undefined"); + this._m_dependencyManager.addVariantDependency(this, variantGlyph); + } // Copying cloneFromGlyph(g) { @@ -107,11 +111,6 @@ export class Glyph { if (copyAnchors) this.copyAnchors(g); if (copyWidth && g.advanceWidth >= 0) this.advanceWidth = g.advanceWidth; } - includeMarkWithLeaningSupport(g, lm) { - let shift = new Vec2(0, 0); - this.combineMarks(g, shift, lm); - this.includeGlyphImpl(g, shift.x, shift.y); - } includeGlyphImpl(g, shiftX, shiftY) { if (g._m_identifier) { this.includeGeometry(new Geom.ReferenceGeometry(g, shiftX, shiftY)); @@ -162,21 +161,15 @@ export class Glyph { } // Anchors - combineMarks(g, shift, lm) { + combineMarks(g, shift) { if (!g.markAnchors) return; - const fScheduledLeaning = lm && ScheduleLeaningMark.get(g); for (const mk in g.markAnchors) { // Find the base mark class and anchor const baseThisN = this.baseAnchors[mk]; if (!baseThisN) continue; // Find the leaning base mark class and anchor, if any - let mkLeaning = mk; - if (fScheduledLeaning) { - for (const [mkT, mkLeaningT] of lm) - if (mk === mkT && this.baseAnchors[mkLeaningT]) mkLeaning = mkLeaningT; - } - const baseThisL = this.baseAnchors[mkLeaning]; + const baseThisL = this.baseAnchors[mk]; if (!baseThisL) continue; // Find the mark anchor in mark glyph @@ -195,15 +188,7 @@ export class Glyph { baseThisN.x - markThat.x + baseDerived.x, baseThisN.y - markThat.y + baseDerived.y, ); - if (mkNewMark === mk) { - fSuppress = false; - if (mkLeaning !== mk) { - this.baseAnchors[mkLeaning] = new Anchor( - baseThisL.x - markThat.x + baseDerived.x, - baseThisL.y - markThat.y + baseDerived.y, - ); - } - } + if (mkNewMark === mk) fSuppress = false; } } if (fSuppress) delete this.baseAnchors[mk];