diff --git a/changes/27.3.5.md b/changes/27.3.5.md index 46db7d427..f6a73cc10 100644 --- a/changes/27.3.5.md +++ b/changes/27.3.5.md @@ -3,3 +3,5 @@ * Fix serifs of GREEK LETTER DIGAMMA (`U+03DC`) under `ss12`. * Improve crossbar position of GREEK SMALL LETTER DIGAMMA (`U+03DD`) and add a middle serif under slab. * Refine Greek Capital Sho (U+03F7) glyph (#2079). +* \[Experimental\] Add a font feature for texture control (#2081). + - Currently only available through custom builds with `build-texture-feature = true`. diff --git a/font-src/glyphs/auto-build/transformed.ptl b/font-src/glyphs/auto-build/transformed.ptl index f225de2f9..0a7024da8 100644 --- a/font-src/glyphs/auto-build/transformed.ptl +++ b/font-src/glyphs/auto-build/transformed.ptl @@ -3,7 +3,7 @@ $$include '../../meta/macros.ptl' import [linreg clamp mix fallback] from"../../support/utils.mjs" import [getGrTree IsSuperscript IsSubscript] from"../../support/gr.mjs" -import [AnyCv DotlessOrNot CvDecompose MathSansSerif] from"../../support/gr.mjs" +import [AnyCv DotlessOrNot CvDecompose MathSansSerif Texture] from"../../support/gr.mjs" import [NumeratorForm DenominatorForm] from"../../support/gr.mjs" import [Transform] from"../../support/geometry/transform.mjs" extern Map @@ -693,6 +693,68 @@ glyph-block Autobuild-Transformed : begin link-relations relSets +glyph-block Autobuild-Transformed-Texture : begin + if (!(para.buildTexture && !para.isQuasiProportional)) : return nothing + + glyph-block-import CommonShapes + glyph-block-import Common-Derivatives + glyph-block-import Recursive-Build : Fork + glyph-block-import Autobuild-Transformed-Shared : extendRelatedGlyphs link-relations wrapName + + define [createTextureDerivatives gr extL extR _records] : begin + local { records relSets targetNameMap } : extendRelatedGlyphs gr.key _records + local pendingGlyphs : records.map : [record] => record.1 + + local forkedPara : Object.assign {.} para + if (extL + extR > 0) + : then : forkedPara.diversityM = 1 + extL + extR + : else : begin + forkedPara.diversityF = 1 + extL + extR + forkedPara.diversityI = 1 + extL + extR + forkedPara.diversityII = 1 + extL + extR + + local forked : Fork pendingGlyphs forkedPara + foreach {unicode glyphid} [items-of records] : begin + if [not : query-glyph targetNameMap.(glyphid)] : begin + define glyphT : forked.queryByName glyphid + define glyphO : glyphStore.queryByName glyphid + if (glyphT && glyphO) : begin + if (glyphT.advanceWidth != glyphO.advanceWidth) : begin + gr.set glyphO targetNameMap.(glyphid) + create-glyph targetNameMap.(glyphid) unicode : glyph-proc + include glyphT AS_BASE ALSO_METRICS + set-width Width + if extL : currentGlyph.applyTransform [Translate (-extL * Width) 0] true + + link-relations relSets + + define ranges : list + list 0x41 0x5A + list 0x61 0x7A + list 0xC0 0xFF + list 0x100 0x2AF + list 0x370 0x3FF + list 0x400 0x4FF + list 0x500 0x52F + + define [jobs base] : list + local results {} + foreach {low high} [items-of ranges] : begin + foreach lch [range low till high] : begin + local source : glyphStore.queryNameByUnicode lch + if source : results.push { null source } + return results + + define EXTENSION : 1 / 12 + define SHRINK : -1 / 12 + createTextureDerivatives Texture.ExtL EXTENSION 0 [jobs 0xF000] + createTextureDerivatives Texture.ExtR 0 EXTENSION [jobs 0xF100] + createTextureDerivatives Texture.ExtLR EXTENSION EXTENSION [jobs 0xF200] + createTextureDerivatives Texture.ShrL SHRINK 0 [jobs 0xF300] + createTextureDerivatives Texture.ShrR 0 SHRINK [jobs 0xF400] + createTextureDerivatives Texture.ShrLR SHRINK SHRINK [jobs 0xF500] + + glyph-block Autobuild-Transformed-Mathematical : begin glyph-block-import CommonShapes glyph-block-import Common-Derivatives diff --git a/font-src/otl/gsub-cv-ss.ptl b/font-src/otl/gsub-cv-ss.ptl index f6ad2fe01..a093e0a04 100644 --- a/font-src/otl/gsub-cv-ss.ptl +++ b/font-src/otl/gsub-cv-ss.ptl @@ -137,4 +137,6 @@ export : define [buildCVSS gsub para glyphStore] : begin gsub.endBlock rec + return cvs + define [objectIsNotEmpty obj] : obj && [Object.keys obj].length diff --git a/font-src/otl/gsub-texture.ptl b/font-src/otl/gsub-texture.ptl new file mode 100644 index 000000000..96b2696ad --- /dev/null +++ b/font-src/otl/gsub-texture.ptl @@ -0,0 +1,88 @@ +$$include '../meta/macros.ptl' + +extern Set + +import [Texture CvDecompose] from"../support/gr.mjs" + +# Name-driven feature pairs +export : define [buildGsubTexture gsub glyphStore markGlyphs cvs] : begin + local anyMark : Array.from markGlyphs.all + local rec : gsub.beginBlock + + define txtr : gsub.addCommonFeature : gsub.createFeature 'TXTR' + define { chain-rule reverse-rule } : gsub.ChainRuleBuilder + + local extRFrom { } + local extRTo { } + local extLFrom { } + local extLTo { } + local shrLFrom { } + local shrLTo { } + local shrRFrom { } + local shrRTo { } + + local anyInfluence : new Set + + foreach { gid g } [glyphStore.namedEntries] : if (gid.(0) !== ".") : begin + local extL : Texture.ExtL.get g + local extR : Texture.ExtR.get g + local extLR : Texture.ExtLR.get g + local shrL : Texture.ShrL.get g + local shrR : Texture.ShrR.get g + local shrLR : Texture.ShrLR.get g + + if extR : begin [extRFrom.push gid] [extRTo.push extR] + if extL : begin [extLFrom.push gid] [extLTo.push extL] + if (extR && extLR) : begin [extLFrom.push extR] [extLTo.push extLR] + + if shrL : begin [shrLFrom.push gid] [shrLTo.push shrL] + if shrR : begin [shrRFrom.push gid] [shrRTo.push shrR] + if (shrL && shrLR) : begin [shrRFrom.push shrL] [shrRTo.push shrLR] + + if (extL || extR || shrL || shrR) : anyInfluence.add gid + + define subExtR : gsub.createLookup + .type 'gsub_chaining' + .ignoreGlyphs anyMark + .rules : list + chain-rule + extRFrom ~> extRTo + shrLFrom ~> shrLTo + + define subExtL : gsub.createLookup + .type 'gsub_chaining' + .ignoreGlyphs anyMark + .rules : list + chain-rule + shrRFrom ~> shrRTo + extLFrom ~> extLTo + + txtr.addLookup subExtR + txtr.addLookup subExtL + gsub.setDependency subExtR subExtL + + # Decompose everything if we enable TXTR + if cvs + : then : begin + # Reuse existing Cvdecompose data + foreach cv [cvs.values] : begin + if cv.decompositionLookup : begin + txtr.addLookup cv.decompositionLookup + gsub.setDependency cv.decompositionLookup subExtR + : else : begin + # Built it from scratch + local decompose : gsub.createLookup + .type 'gsub_multiple' + .substitutions {.} + + foreach { gn glyph } [glyphStore.namedEntries] : begin + local parts : CvDecompose.get glyph + if parts : begin + local influenced : anyInfluence.has gn + foreach part [items-of parts] : if [anyInfluence.has part] : set influenced true + if influenced : set decompose.substitutions.(gn) parts + + txtr.addLookup decompose + gsub.setDependency decompose subExtR + + gsub.endBlock rec diff --git a/font-src/otl/index.ptl b/font-src/otl/index.ptl index 3edcec4e6..2ce886683 100644 --- a/font-src/otl/index.ptl +++ b/font-src/otl/index.ptl @@ -10,6 +10,7 @@ import [buildFrac] from"./gsub-frac.mjs" import [buildCVSS] from"./gsub-cv-ss.mjs" import [buildLOCL] from"./gsub-locl.mjs" import [buildGsubThousands] from"./gsub-thousands.mjs" +import [buildGsubTexture] from"./gsub-texture.mjs" import [buildMarkMkmk] from"./gpos-mark-mkmk.mjs" define GDEF_SIMPLE 1 @@ -60,8 +61,9 @@ define [buildGSUB para glyphStore markGlyphs] : begin buildGsubThousands gsub para glyphStore # cv##, ss## + local cvs nothing if para.enableCvSs : begin - buildCVSS gsub para glyphStore + set cvs : buildCVSS gsub para glyphStore # ccmp post cv/ss (for Ogonek shape transform) buildCCMPPostCvSs gsub ccmp glyphStore markGlyphs @@ -70,6 +72,10 @@ define [buildGSUB para glyphStore markGlyphs] : begin # Builds last, but the lookups are added into the beginning of the lookup list buildLOCL gsub para glyphStore + # TXTR, "texture" feature + if (para.buildTexture && !para.isQuasiProportional) : begin + buildGsubTexture gsub glyphStore markGlyphs cvs + gsub.finalize return gsub diff --git a/font-src/support/gr.mjs b/font-src/support/gr.mjs index 9be2d2358..608916478 100644 --- a/font-src/support/gr.mjs +++ b/font-src/support/gr.mjs @@ -23,8 +23,19 @@ export const VS01 = LinkedGlyphProp("VS01"); export const TieMark = LinkedGlyphProp("TieMark"); export const LeaningMark = LinkedGlyphProp("LeaningMark"); export const LeaningMarkSpacer = LinkedGlyphProp("LeaningMarkSpacer"); + +export const Texture = { + ExtL: LinkedGlyphProp("TextureExtL"), + ExtR: LinkedGlyphProp("TextureExtR"), + ExtLR: LinkedGlyphProp("TextureExtLR"), + ShrL: LinkedGlyphProp("TextureShrL"), + ShrR: LinkedGlyphProp("TextureShrR"), + ShrLR: LinkedGlyphProp("TextureShrLR") +}; + function LinkedGlyphProp(key) { return { + key, get(glyph) { if (glyph && glyph.related) return glyph.related[key]; else return null; diff --git a/font-src/support/parameters.mjs b/font-src/support/parameters.mjs index d0fe7d28c..be1e7092d 100644 --- a/font-src/support/parameters.mjs +++ b/font-src/support/parameters.mjs @@ -11,6 +11,7 @@ export function init(data, argv) { applyAlternatesParam(argv, para, data, "slope", "slope"); if (argv.featureControl.noCvSs) para.enableCvSs = false; if (argv.featureControl.noLigation) para.enableLigation = false; + if (argv.featureControl.buildTexture) para.buildTexture = true; return para; } function applyBlendingParam(argv, para, data, key, keyArgv) { diff --git a/utility/export-data/coverage-export/block-data.mjs b/utility/export-data/coverage-export/block-data.mjs index 09fc7baeb..453bbd09a 100644 --- a/utility/export-data/coverage-export/block-data.mjs +++ b/utility/export-data/coverage-export/block-data.mjs @@ -3,7 +3,8 @@ import UnicodeDataIndex from "@unicode/unicode-15.0.0"; export async function collectBlockData() { const BlockData = [ [[0xe0a0, 0xe0df], "Private Use Area — Powerline"], - [[0xee00, 0xee3f], "Private Use Area — Progress Bar"] + [[0xee00, 0xee3f], "Private Use Area — Progress Bar"], + [[0xf000, 0xf8ff], "Private Use Area — Texture"] ]; for (const id of UnicodeDataIndex.Block) { diff --git a/verdafile.mjs b/verdafile.mjs index 4e34841d3..daefd1578 100644 --- a/verdafile.mjs +++ b/verdafile.mjs @@ -230,6 +230,7 @@ const CompositesFromBuildPlan = computed(`metadata:composites-from-build-plan`, return data; }); +// eslint-disable-next-line complexity const FontInfoOf = computed.group("metadata:font-info-of", async (target, fileName) => { const [{ fileNameToBpMap, buildPlans }] = await target.need(BuildPlans); const [version] = await target.need(Version); @@ -261,7 +262,8 @@ const FontInfoOf = computed.group("metadata:font-info-of", async (target, fileNa featureControl: { noCvSs: bp["no-cv-ss"] || false, noLigation: bp["no-ligation"] || false, - exportGlyphNames: bp["export-glyph-names"] || false + exportGlyphNames: bp["export-glyph-names"] || false, + buildTexture: bp["build-texture-feature"] || false }, // Ligations ligations: bp.ligations || null,