pa_font 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -39
- package/USAGE.md +9 -0
- package/dist/paFont.cjs +212 -9
- package/dist/paFont.cjs.map +1 -1
- package/dist/paFont.js +212 -9
- package/dist/paFont.js.map +1 -1
- package/paFont.d.ts +8 -0
- package/package.json +1 -1
package/dist/paFont.js
CHANGED
|
@@ -556,13 +556,14 @@ function toArrayBuffer(value) {
|
|
|
556
556
|
//#endregion
|
|
557
557
|
//#region src/paFont/shape.js
|
|
558
558
|
var PAShape = class {
|
|
559
|
-
constructor({ text, parts, contours, glyphs, bbox, metrics, edgeEpsilon }) {
|
|
559
|
+
constructor({ text, parts, contours, glyphs, bbox, metrics, edgeEpsilon, rawData }) {
|
|
560
560
|
this.text = text;
|
|
561
561
|
this.parts = parts;
|
|
562
562
|
this.bbox = bbox;
|
|
563
563
|
this.metrics = metrics;
|
|
564
564
|
this.edgeEpsilon = edgeEpsilon;
|
|
565
565
|
this.raw = {
|
|
566
|
+
...rawData ?? {},
|
|
566
567
|
contours,
|
|
567
568
|
glyphs
|
|
568
569
|
};
|
|
@@ -595,12 +596,23 @@ var PAShape = class {
|
|
|
595
596
|
toRegions(options = {}) {
|
|
596
597
|
return createRegionCollection(this.toShape(options));
|
|
597
598
|
}
|
|
599
|
+
toRegionViews(options = {}) {
|
|
600
|
+
return this.toShape(options)._getRegionViews();
|
|
601
|
+
}
|
|
598
602
|
openHoles(width) {
|
|
599
603
|
const slitWidth = normalizePositive(width, 0);
|
|
600
604
|
if (slitWidth <= 0 || !this.parts.some((part) => part.holes.length > 0)) return this;
|
|
601
605
|
const cacheKey = toCacheKey(slitWidth);
|
|
602
606
|
const cached = this._cache.openHoles.get(cacheKey);
|
|
603
607
|
if (cached) return cached;
|
|
608
|
+
const glyphVariant = createGlyphDerivedShape(this, {
|
|
609
|
+
openWidth: slitWidth,
|
|
610
|
+
step: 0
|
|
611
|
+
});
|
|
612
|
+
if (glyphVariant) {
|
|
613
|
+
this._cache.openHoles.set(cacheKey, glyphVariant);
|
|
614
|
+
return glyphVariant;
|
|
615
|
+
}
|
|
604
616
|
const geometryEpsilon = resolveGeometryEpsilon(slitWidth);
|
|
605
617
|
const shape = createDerivedShape(this, this.parts.map((part) => openPartWithSlit(part, slitWidth, geometryEpsilon)));
|
|
606
618
|
this._cache.openHoles.set(cacheKey, shape);
|
|
@@ -612,6 +624,14 @@ var PAShape = class {
|
|
|
612
624
|
const cacheKey = toCacheKey(spacing);
|
|
613
625
|
const cached = this._cache.resample.get(cacheKey);
|
|
614
626
|
if (cached) return cached;
|
|
627
|
+
const glyphVariant = createGlyphDerivedShape(this, {
|
|
628
|
+
openWidth: 0,
|
|
629
|
+
step: spacing
|
|
630
|
+
});
|
|
631
|
+
if (glyphVariant) {
|
|
632
|
+
this._cache.resample.set(cacheKey, glyphVariant);
|
|
633
|
+
return glyphVariant;
|
|
634
|
+
}
|
|
615
635
|
const shape = createDerivedShape(this, this.parts.map((part) => resamplePart(part, spacing)));
|
|
616
636
|
this._cache.resample.set(cacheKey, shape);
|
|
617
637
|
return shape;
|
|
@@ -673,8 +693,13 @@ function createTextShape(layout, opts, fontInstance) {
|
|
|
673
693
|
const parts = [];
|
|
674
694
|
const contours = [];
|
|
675
695
|
const glyphs = [];
|
|
696
|
+
const sourceLayoutGlyphs = Array.isArray(layout.glyphs) ? layout.glyphs.slice() : [];
|
|
697
|
+
const variantOptions = {
|
|
698
|
+
openWidth: normalizePositive(opts.openWidth, 0),
|
|
699
|
+
step: normalizePositive(opts.step, 0)
|
|
700
|
+
};
|
|
676
701
|
layout.glyphs.forEach((item, glyphPosition) => {
|
|
677
|
-
const translated = translateGlyphGeometry(fontInstance.
|
|
702
|
+
const translated = translateGlyphGeometry(fontInstance._getGlyphGeometryVariant(item.glyph, opts), item.x, item.y, glyphPosition);
|
|
678
703
|
parts.push(...translated.parts);
|
|
679
704
|
contours.push(...translated.contours);
|
|
680
705
|
glyphs.push({
|
|
@@ -698,11 +723,21 @@ function createTextShape(layout, opts, fontInstance) {
|
|
|
698
723
|
...layout.metrics,
|
|
699
724
|
bbox
|
|
700
725
|
},
|
|
701
|
-
edgeEpsilon: opts.edgeEpsilon
|
|
726
|
+
edgeEpsilon: opts.edgeEpsilon,
|
|
727
|
+
rawData: {
|
|
728
|
+
fontInstance,
|
|
729
|
+
sourceLayout: {
|
|
730
|
+
glyphs: sourceLayoutGlyphs,
|
|
731
|
+
metrics: { ...layout.metrics }
|
|
732
|
+
},
|
|
733
|
+
sourceOptions: extractShapeSourceOptions(opts),
|
|
734
|
+
variantOptions
|
|
735
|
+
}
|
|
702
736
|
});
|
|
703
737
|
}
|
|
704
738
|
function createGlyphShape(shape, glyphPosition) {
|
|
705
739
|
const glyphMeta = shape.raw.glyphs?.[glyphPosition] ?? null;
|
|
740
|
+
const sourceGlyph = shape.raw.sourceLayout?.glyphs?.[glyphPosition] ?? null;
|
|
706
741
|
const parts = copyParts(shape.parts.filter((part) => part.glyphPosition === glyphPosition));
|
|
707
742
|
const contours = copyContours((shape.raw.contours ?? []).filter((contour) => contour.glyphPosition === glyphPosition));
|
|
708
743
|
const bbox = combineRects(parts.map((part) => part.bbox)) ?? (glyphMeta?.bbox ? { ...glyphMeta.bbox } : emptyRect());
|
|
@@ -723,14 +758,28 @@ function createGlyphShape(shape, glyphPosition) {
|
|
|
723
758
|
width: bbox.w,
|
|
724
759
|
bbox
|
|
725
760
|
},
|
|
726
|
-
edgeEpsilon: shape.edgeEpsilon
|
|
761
|
+
edgeEpsilon: shape.edgeEpsilon,
|
|
762
|
+
rawData: sourceGlyph && shape.raw.fontInstance ? {
|
|
763
|
+
fontInstance: shape.raw.fontInstance,
|
|
764
|
+
sourceLayout: {
|
|
765
|
+
glyphs: [sourceGlyph],
|
|
766
|
+
metrics: {
|
|
767
|
+
x: glyphMeta?.x ?? shape.metrics?.x ?? 0,
|
|
768
|
+
y: glyphMeta?.y ?? shape.metrics?.y ?? 0,
|
|
769
|
+
size: glyphMeta?.size ?? shape.metrics?.size ?? 0,
|
|
770
|
+
width: glyphMeta?.bbox?.w ?? bbox.w
|
|
771
|
+
}
|
|
772
|
+
},
|
|
773
|
+
sourceOptions: shape.raw.sourceOptions ?? null,
|
|
774
|
+
variantOptions: { ...shape.raw.variantOptions ?? zeroShapeVariant() }
|
|
775
|
+
} : void 0
|
|
727
776
|
});
|
|
728
777
|
}
|
|
729
778
|
function createDerivedShape(shape, parts) {
|
|
730
|
-
const
|
|
731
|
-
const
|
|
779
|
+
const bbox = combineRects(parts.map((part) => part.bbox)) ?? emptyRect();
|
|
780
|
+
const partsByGlyphPosition = groupPartsByGlyphPosition(parts);
|
|
732
781
|
const glyphs = (shape.raw.glyphs ?? []).map((glyph, glyphPosition) => {
|
|
733
|
-
const glyphParts =
|
|
782
|
+
const glyphParts = partsByGlyphPosition.get(glyphPosition) ?? [];
|
|
734
783
|
return {
|
|
735
784
|
...glyph,
|
|
736
785
|
bbox: combineRects(glyphParts.map((part) => part.bbox)) ?? (glyph.bbox ? { ...glyph.bbox } : emptyRect()),
|
|
@@ -739,7 +788,7 @@ function createDerivedShape(shape, parts) {
|
|
|
739
788
|
});
|
|
740
789
|
return new PAShape({
|
|
741
790
|
text: shape.text,
|
|
742
|
-
parts
|
|
791
|
+
parts,
|
|
743
792
|
contours: [],
|
|
744
793
|
glyphs,
|
|
745
794
|
bbox,
|
|
@@ -747,7 +796,11 @@ function createDerivedShape(shape, parts) {
|
|
|
747
796
|
...shape.metrics,
|
|
748
797
|
bbox
|
|
749
798
|
},
|
|
750
|
-
edgeEpsilon: shape.edgeEpsilon
|
|
799
|
+
edgeEpsilon: shape.edgeEpsilon,
|
|
800
|
+
rawData: {
|
|
801
|
+
...shape.raw,
|
|
802
|
+
variantOptions: { ...shape.raw.variantOptions ?? zeroShapeVariant() }
|
|
803
|
+
}
|
|
751
804
|
});
|
|
752
805
|
}
|
|
753
806
|
function createRegionCollection(shape) {
|
|
@@ -776,6 +829,16 @@ function copyContours(contours) {
|
|
|
776
829
|
ring: copyRing(contour.ring)
|
|
777
830
|
}));
|
|
778
831
|
}
|
|
832
|
+
function groupPartsByGlyphPosition(parts) {
|
|
833
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
834
|
+
parts.forEach((part) => {
|
|
835
|
+
if (typeof part.glyphPosition !== "number") return;
|
|
836
|
+
const bucket = grouped.get(part.glyphPosition) ?? [];
|
|
837
|
+
bucket.push(part);
|
|
838
|
+
grouped.set(part.glyphPosition, bucket);
|
|
839
|
+
});
|
|
840
|
+
return grouped;
|
|
841
|
+
}
|
|
779
842
|
function extractGlyphText(text, glyphPosition, glyphCount) {
|
|
780
843
|
if (glyphCount <= 1) return text;
|
|
781
844
|
return Array.from(text ?? "")[glyphPosition] ?? "";
|
|
@@ -804,6 +867,19 @@ function normalizeShapeOptions(options = {}) {
|
|
|
804
867
|
openWidth: normalizePositive(options.openWidth, 0)
|
|
805
868
|
};
|
|
806
869
|
}
|
|
870
|
+
function extractShapeSourceOptions(opts) {
|
|
871
|
+
return {
|
|
872
|
+
size: opts.size,
|
|
873
|
+
flatten: opts.flatten,
|
|
874
|
+
edgeEpsilon: opts.edgeEpsilon,
|
|
875
|
+
kerning: opts.kerning,
|
|
876
|
+
letterSpacing: opts.letterSpacing,
|
|
877
|
+
tracking: opts.tracking,
|
|
878
|
+
script: opts.script,
|
|
879
|
+
language: opts.language,
|
|
880
|
+
features: opts.features
|
|
881
|
+
};
|
|
882
|
+
}
|
|
807
883
|
function resolveShapeVariant(shape, options = {}) {
|
|
808
884
|
const normalized = normalizeShapeOptions(options);
|
|
809
885
|
if (normalized.step <= 0 && normalized.openWidth <= 0) return shape;
|
|
@@ -816,9 +892,51 @@ function resolveShapeVariant(shape, options = {}) {
|
|
|
816
892
|
shape._cache.shapes.set(cacheKey, next);
|
|
817
893
|
return next;
|
|
818
894
|
}
|
|
895
|
+
function deriveGlyphGeometryVariant(geometry, options = {}) {
|
|
896
|
+
const normalized = normalizeShapeOptions(options);
|
|
897
|
+
if (normalized.step <= 0 && normalized.openWidth <= 0) return geometry;
|
|
898
|
+
let parts = geometry.parts;
|
|
899
|
+
if (normalized.openWidth > 0) {
|
|
900
|
+
const geometryEpsilon = resolveGeometryEpsilon(normalized.openWidth);
|
|
901
|
+
parts = parts.map((part) => openPartWithSlit(part, normalized.openWidth, geometryEpsilon));
|
|
902
|
+
}
|
|
903
|
+
if (normalized.step > 0) parts = parts.map((part) => resamplePart(part, normalized.step));
|
|
904
|
+
return {
|
|
905
|
+
...geometry,
|
|
906
|
+
contours: [],
|
|
907
|
+
parts,
|
|
908
|
+
bbox: combineRects(parts.map((part) => part.bbox)) ?? emptyRect()
|
|
909
|
+
};
|
|
910
|
+
}
|
|
819
911
|
function toCacheKey(value) {
|
|
820
912
|
return normalizePositive(value, 0).toFixed(6);
|
|
821
913
|
}
|
|
914
|
+
function zeroShapeVariant() {
|
|
915
|
+
return {
|
|
916
|
+
openWidth: 0,
|
|
917
|
+
step: 0
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
function createGlyphDerivedShape(shape, variant) {
|
|
921
|
+
if (!canUseGlyphDerivedShape(shape)) return null;
|
|
922
|
+
return createTextShape({
|
|
923
|
+
text: shape.text,
|
|
924
|
+
glyphs: shape.raw.sourceLayout.glyphs,
|
|
925
|
+
metrics: shape.raw.sourceLayout.metrics
|
|
926
|
+
}, {
|
|
927
|
+
...shape.raw.sourceOptions,
|
|
928
|
+
...mergeShapeVariantOptions(shape.raw.variantOptions, variant)
|
|
929
|
+
}, shape.raw.fontInstance);
|
|
930
|
+
}
|
|
931
|
+
function canUseGlyphDerivedShape(shape) {
|
|
932
|
+
return shape.raw?.fontInstance != null && shape.raw?.sourceOptions != null && shape.raw?.sourceLayout != null && Array.isArray(shape.raw.sourceLayout.glyphs);
|
|
933
|
+
}
|
|
934
|
+
function mergeShapeVariantOptions(previous, next) {
|
|
935
|
+
return {
|
|
936
|
+
openWidth: normalizePositive(next.openWidth, previous?.openWidth ?? 0),
|
|
937
|
+
step: normalizePositive(next.step, previous?.step ?? 0)
|
|
938
|
+
};
|
|
939
|
+
}
|
|
822
940
|
function resolveGeometryEpsilon(width) {
|
|
823
941
|
return Math.max(1e-4, normalizePositive(width, 1) * .001);
|
|
824
942
|
}
|
|
@@ -3180,9 +3298,11 @@ var DEFAULT_LINE_HEIGHT_RATIO = 1.2;
|
|
|
3180
3298
|
var HUGE_LAYOUT_WIDTH = 1e9;
|
|
3181
3299
|
var JUSTIFY_EPSILON = 1e-6;
|
|
3182
3300
|
var QUOTE_RE = /"/g;
|
|
3301
|
+
var SHARED_MEASURE_CACHE_LIMIT = 2048;
|
|
3183
3302
|
var sharedMeasureContext = null;
|
|
3184
3303
|
var sharedWordSegmenter = null;
|
|
3185
3304
|
var sharedGraphemeSegmenter = null;
|
|
3305
|
+
var sharedMeasureCaches = /* @__PURE__ */ new WeakMap();
|
|
3186
3306
|
function layoutParagraph(fontInstance, text, options = {}, state = {}) {
|
|
3187
3307
|
const normalized = normalizeParagraphOptions(fontInstance, options);
|
|
3188
3308
|
const textValue = String(text ?? "");
|
|
@@ -3862,15 +3982,24 @@ function createTextMeasurer(fontInstance, options) {
|
|
|
3862
3982
|
const cache = /* @__PURE__ */ new Map();
|
|
3863
3983
|
const openTypeMeasurer = createOpenTypeMeasurer(fontInstance, options);
|
|
3864
3984
|
const context = getMeasureContext();
|
|
3985
|
+
const sharedCache = getSharedMeasureCache(fontInstance, "canvas");
|
|
3986
|
+
const sharedKeyPrefix = `${options.font}\u0000`;
|
|
3865
3987
|
return (value) => {
|
|
3866
3988
|
if (value.length === 0) return 0;
|
|
3867
3989
|
if (cache.has(value)) return cache.get(value);
|
|
3990
|
+
const sharedKey = `${sharedKeyPrefix}${value}`;
|
|
3991
|
+
const sharedWidth = readSharedMeasureCache(sharedCache, sharedKey);
|
|
3992
|
+
if (sharedWidth != null) {
|
|
3993
|
+
cache.set(value, sharedWidth);
|
|
3994
|
+
return sharedWidth;
|
|
3995
|
+
}
|
|
3868
3996
|
let width;
|
|
3869
3997
|
if (context) {
|
|
3870
3998
|
context.font = options.font;
|
|
3871
3999
|
width = context.measureText(value).width;
|
|
3872
4000
|
} else width = openTypeMeasurer(value);
|
|
3873
4001
|
cache.set(value, width);
|
|
4002
|
+
writeSharedMeasureCache(sharedCache, sharedKey, width);
|
|
3874
4003
|
return width;
|
|
3875
4004
|
};
|
|
3876
4005
|
}
|
|
@@ -3883,6 +4012,7 @@ function createLazyTextMeasurer(fontInstance, options) {
|
|
|
3883
4012
|
}
|
|
3884
4013
|
function createOpenTypeMeasurer(fontInstance, options) {
|
|
3885
4014
|
const cache = /* @__PURE__ */ new Map();
|
|
4015
|
+
const sharedCache = getSharedMeasureCache(fontInstance, "openType");
|
|
3886
4016
|
const widthOptions = {
|
|
3887
4017
|
x: 0,
|
|
3888
4018
|
y: 0,
|
|
@@ -3896,11 +4026,19 @@ function createOpenTypeMeasurer(fontInstance, options) {
|
|
|
3896
4026
|
language: options.language,
|
|
3897
4027
|
features: options.features
|
|
3898
4028
|
};
|
|
4029
|
+
const sharedKeyPrefix = createOpenTypeMeasureKeyPrefix(widthOptions);
|
|
3899
4030
|
return (value) => {
|
|
3900
4031
|
if (value.length === 0) return 0;
|
|
3901
4032
|
if (cache.has(value)) return cache.get(value);
|
|
4033
|
+
const sharedKey = `${sharedKeyPrefix}${value}`;
|
|
4034
|
+
const sharedWidth = readSharedMeasureCache(sharedCache, sharedKey);
|
|
4035
|
+
if (sharedWidth != null) {
|
|
4036
|
+
cache.set(value, sharedWidth);
|
|
4037
|
+
return sharedWidth;
|
|
4038
|
+
}
|
|
3902
4039
|
const width = measureAdvanceWidth(fontInstance.font, value, widthOptions);
|
|
3903
4040
|
cache.set(value, width);
|
|
4041
|
+
writeSharedMeasureCache(sharedCache, sharedKey, width);
|
|
3904
4042
|
return width;
|
|
3905
4043
|
};
|
|
3906
4044
|
}
|
|
@@ -3916,6 +4054,49 @@ function getMeasureContext() {
|
|
|
3916
4054
|
}
|
|
3917
4055
|
return null;
|
|
3918
4056
|
}
|
|
4057
|
+
function getSharedMeasureCache(fontInstance, bucket) {
|
|
4058
|
+
if (fontInstance == null || typeof fontInstance !== "object" && typeof fontInstance !== "function") return null;
|
|
4059
|
+
let caches = sharedMeasureCaches.get(fontInstance);
|
|
4060
|
+
if (!caches) {
|
|
4061
|
+
caches = {
|
|
4062
|
+
canvas: /* @__PURE__ */ new Map(),
|
|
4063
|
+
openType: /* @__PURE__ */ new Map()
|
|
4064
|
+
};
|
|
4065
|
+
sharedMeasureCaches.set(fontInstance, caches);
|
|
4066
|
+
}
|
|
4067
|
+
return caches[bucket];
|
|
4068
|
+
}
|
|
4069
|
+
function readSharedMeasureCache(cache, key) {
|
|
4070
|
+
if (!cache || !cache.has(key)) return null;
|
|
4071
|
+
const value = cache.get(key);
|
|
4072
|
+
cache.delete(key);
|
|
4073
|
+
cache.set(key, value);
|
|
4074
|
+
return value;
|
|
4075
|
+
}
|
|
4076
|
+
function writeSharedMeasureCache(cache, key, value) {
|
|
4077
|
+
if (!cache) return;
|
|
4078
|
+
if (cache.has(key)) cache.delete(key);
|
|
4079
|
+
cache.set(key, value);
|
|
4080
|
+
if (cache.size > SHARED_MEASURE_CACHE_LIMIT) {
|
|
4081
|
+
const oldestKey = cache.keys().next().value;
|
|
4082
|
+
cache.delete(oldestKey);
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
function createOpenTypeMeasureKeyPrefix(options) {
|
|
4086
|
+
return [
|
|
4087
|
+
options.size,
|
|
4088
|
+
options.kerning ? 1 : 0,
|
|
4089
|
+
options.letterSpacing ?? "",
|
|
4090
|
+
options.tracking ?? "",
|
|
4091
|
+
options.script ?? "",
|
|
4092
|
+
options.language ?? "",
|
|
4093
|
+
serializeMeasureFeatures(options.features)
|
|
4094
|
+
].join("|") + "\0";
|
|
4095
|
+
}
|
|
4096
|
+
function serializeMeasureFeatures(features) {
|
|
4097
|
+
if (!features || typeof features !== "object") return "";
|
|
4098
|
+
return Object.keys(features).sort().map((key) => `${key}:${features[key]}`).join(",");
|
|
4099
|
+
}
|
|
3919
4100
|
function getWordSegmenter() {
|
|
3920
4101
|
if (sharedWordSegmenter == null) sharedWordSegmenter = new Intl.Segmenter(void 0, { granularity: "word" });
|
|
3921
4102
|
return sharedWordSegmenter;
|
|
@@ -4011,6 +4192,10 @@ var paParagraph = class paParagraph {
|
|
|
4011
4192
|
const { layout = "current", ...shapeOptions } = normalizeParagraphShapeOptions(options, "toRegions()");
|
|
4012
4193
|
return this._getBaseShape(layout).toRegions(shapeOptions);
|
|
4013
4194
|
}
|
|
4195
|
+
toRegionViews(options = {}) {
|
|
4196
|
+
const { layout = "current", ...shapeOptions } = normalizeParagraphShapeOptions(options, "toRegionViews()");
|
|
4197
|
+
return this._getBaseShape(layout).toRegionViews(shapeOptions);
|
|
4198
|
+
}
|
|
4014
4199
|
toPoints(options = {}) {
|
|
4015
4200
|
const { layout = "current", ...pointOptions } = normalizeParagraphPointOptions(options);
|
|
4016
4201
|
return this._getBaseShape(layout).toPoints(pointOptions);
|
|
@@ -4146,6 +4331,7 @@ var paFont = class paFont {
|
|
|
4146
4331
|
this.canvasFamily = this.family;
|
|
4147
4332
|
this._glyphTopologyCache = /* @__PURE__ */ new Map();
|
|
4148
4333
|
this._glyphFlatCache = /* @__PURE__ */ new Map();
|
|
4334
|
+
this._glyphVariantCache = /* @__PURE__ */ new Map();
|
|
4149
4335
|
}
|
|
4150
4336
|
static async load(source, options = {}) {
|
|
4151
4337
|
const opts = normalizeLoadOptions(options);
|
|
@@ -4223,10 +4409,27 @@ var paFont = class paFont {
|
|
|
4223
4409
|
}
|
|
4224
4410
|
return this._glyphFlatCache.get(key);
|
|
4225
4411
|
}
|
|
4412
|
+
_getGlyphGeometryVariant(glyph, opts) {
|
|
4413
|
+
const openWidth = normalizeShapeVariantValue(opts.openWidth);
|
|
4414
|
+
const step = normalizeShapeVariantValue(opts.step);
|
|
4415
|
+
if (openWidth <= 0 && step <= 0) return this._getFlattenedGlyph(glyph, opts);
|
|
4416
|
+
const key = `${glyph.index}:${opts.size}:${opts.flatten}:${toShapeVariantKey(openWidth)}:${toShapeVariantKey(step)}`;
|
|
4417
|
+
if (!this._glyphVariantCache.has(key)) this._glyphVariantCache.set(key, deriveGlyphGeometryVariant(this._getFlattenedGlyph(glyph, opts), {
|
|
4418
|
+
openWidth,
|
|
4419
|
+
step
|
|
4420
|
+
}));
|
|
4421
|
+
return this._glyphVariantCache.get(key);
|
|
4422
|
+
}
|
|
4226
4423
|
_layoutText(value, opts) {
|
|
4227
4424
|
return layoutGlyphs(this.font, value, opts);
|
|
4228
4425
|
}
|
|
4229
4426
|
};
|
|
4427
|
+
function normalizeShapeVariantValue(value) {
|
|
4428
|
+
return Number.isFinite(value) && value > 0 ? Number(value) : 0;
|
|
4429
|
+
}
|
|
4430
|
+
function toShapeVariantKey(value) {
|
|
4431
|
+
return normalizeShapeVariantValue(value).toFixed(6);
|
|
4432
|
+
}
|
|
4230
4433
|
async function fetchFontBytes(source) {
|
|
4231
4434
|
const response = await fetch(source);
|
|
4232
4435
|
if (!response.ok) throw new Error(`Failed to load font from ${source}: ${response.status} ${response.statusText}`);
|