@nasser-sw/fabric 7.0.1-beta1 → 7.0.1-beta10
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/0 +0 -0
- package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
- package/debug/{konva → konva-master}/README.md +7 -3
- package/debug/{konva → konva-master}/package.json +1 -1
- package/debug/{konva → konva-master}/release.sh +1 -4
- package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
- package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
- package/dist/index.js +1853 -288
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +1853 -288
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +1853 -288
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +1853 -288
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/shapes/Line.d.ts +33 -86
- package/dist/src/shapes/Line.d.ts.map +1 -1
- package/dist/src/shapes/Line.min.mjs +1 -1
- package/dist/src/shapes/Line.min.mjs.map +1 -1
- package/dist/src/shapes/Line.mjs +405 -159
- package/dist/src/shapes/Line.mjs.map +1 -1
- package/dist/src/shapes/Polyline.d.ts +7 -0
- package/dist/src/shapes/Polyline.d.ts.map +1 -1
- package/dist/src/shapes/Polyline.min.mjs +1 -1
- package/dist/src/shapes/Polyline.min.mjs.map +1 -1
- package/dist/src/shapes/Polyline.mjs +48 -16
- package/dist/src/shapes/Polyline.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts +19 -0
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +302 -16
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +43 -1
- package/dist/src/shapes/Textbox.d.ts.map +1 -1
- package/dist/src/shapes/Textbox.min.mjs +1 -1
- package/dist/src/shapes/Textbox.min.mjs.map +1 -1
- package/dist/src/shapes/Textbox.mjs +521 -67
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/shapes/Triangle.d.ts +27 -2
- package/dist/src/shapes/Triangle.d.ts.map +1 -1
- package/dist/src/shapes/Triangle.min.mjs +1 -1
- package/dist/src/shapes/Triangle.min.mjs.map +1 -1
- package/dist/src/shapes/Triangle.mjs +72 -12
- package/dist/src/shapes/Triangle.mjs.map +1 -1
- package/dist/src/text/examples/arabicTextExample.d.ts +60 -0
- package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
- package/dist/src/text/measure.d.ts +9 -0
- package/dist/src/text/measure.d.ts.map +1 -1
- package/dist/src/text/measure.min.mjs +1 -1
- package/dist/src/text/measure.min.mjs.map +1 -1
- package/dist/src/text/measure.mjs +175 -4
- package/dist/src/text/measure.mjs.map +1 -1
- package/dist/src/text/overlayEditor.d.ts.map +1 -1
- package/dist/src/text/overlayEditor.min.mjs +1 -1
- package/dist/src/text/overlayEditor.min.mjs.map +1 -1
- package/dist/src/text/overlayEditor.mjs +155 -9
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/scriptUtils.d.ts +142 -0
- package/dist/src/text/scriptUtils.d.ts.map +1 -0
- package/dist/src/text/scriptUtils.min.mjs +2 -0
- package/dist/src/text/scriptUtils.min.mjs.map +1 -0
- package/dist/src/text/scriptUtils.mjs +212 -0
- package/dist/src/text/scriptUtils.mjs.map +1 -0
- package/dist/src/util/misc/cornerRadius.d.ts +70 -0
- package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
- package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
- package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
- package/dist/src/util/misc/cornerRadius.mjs +181 -0
- package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts +10 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
- package/dist-extensions/src/shapes/Line.d.ts +33 -86
- package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
- package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts +19 -0
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +43 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
- package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
- package/dist-extensions/src/text/measure.d.ts +9 -0
- package/dist-extensions/src/text/measure.d.ts.map +1 -1
- package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
- package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
- package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
- package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
- package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
- package/fabric-test-editor.html +3552 -0
- package/fabric-test2.html +647 -0
- package/fabric.ts +182 -182
- package/fonts/STV Bold.ttf +0 -0
- package/fonts/STV Light.ttf +0 -0
- package/fonts/STV Regular.ttf +0 -0
- package/package.json +164 -164
- package/src/shapes/Line.ts +484 -157
- package/src/shapes/Polyline.ts +70 -29
- package/src/shapes/Text/Text.ts +317 -19
- package/src/shapes/Textbox.ts +544 -12
- package/src/shapes/Triangle.spec.ts +76 -0
- package/src/shapes/Triangle.ts +85 -15
- package/src/text/measure.ts +200 -50
- package/src/text/overlayEditor.ts +164 -12
- package/src/util/misc/cornerRadius.spec.ts +141 -0
- package/src/util/misc/cornerRadius.ts +269 -0
- /package/debug/{konva → konva-master}/LICENSE +0 -0
- /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
- /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
- /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
- /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
- /package/debug/{konva → konva-master}/src/Container.ts +0 -0
- /package/debug/{konva → konva-master}/src/Context.ts +0 -0
- /package/debug/{konva → konva-master}/src/Core.ts +0 -0
- /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
- /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
- /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Global.ts +0 -0
- /package/debug/{konva → konva-master}/src/Group.ts +0 -0
- /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Node.ts +0 -0
- /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
- /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
- /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
- /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
- /package/debug/{konva → konva-master}/src/Util.ts +0 -0
- /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
- /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
- /package/debug/{konva → konva-master}/src/index.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
- /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/types.ts +0 -0
- /package/debug/{konva → konva-master}/tsconfig.json +0 -0
- /package/debug/{konva → konva-master}/tsconfig.test.json +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Triangle.mjs","sources":["../../../src/shapes/Triangle.ts"],"sourcesContent":["import { classRegistry } from '../ClassRegistry';\nimport { FabricObject } from './Object/FabricObject';\nimport type { FabricObjectProps, SerializedObjectProps } from './Object/types';\nimport type { TClassProperties, TOptions } from '../typedefs';\nimport type { ObjectEvents } from '../EventTypeDefs';\n\nexport const triangleDefaultValues: Partial<TClassProperties<Triangle>> = {\n width: 100,\n height: 100,\n};\n\nexport class Triangle<\n Props extends TOptions<
|
|
1
|
+
{"version":3,"file":"Triangle.mjs","sources":["../../../src/shapes/Triangle.ts"],"sourcesContent":["import { classRegistry } from '../ClassRegistry';\nimport { FabricObject, cacheProperties } from './Object/FabricObject';\nimport type { FabricObjectProps, SerializedObjectProps } from './Object/types';\nimport type { TClassProperties, TOptions } from '../typedefs';\nimport type { ObjectEvents } from '../EventTypeDefs';\nimport {\n applyCornerRadiusToPolygon,\n renderRoundedPolygon,\n generateRoundedPolygonPath,\n} from '../util/misc/cornerRadius';\n\nexport const triangleDefaultValues: Partial<TClassProperties<Triangle>> = {\n width: 100,\n height: 100,\n cornerRadius: 0,\n};\n\ninterface UniqueTriangleProps {\n cornerRadius: number;\n}\n\nexport interface SerializedTriangleProps\n extends SerializedObjectProps,\n UniqueTriangleProps {}\n\nexport interface TriangleProps extends FabricObjectProps, UniqueTriangleProps {}\n\nconst TRIANGLE_PROPS = ['cornerRadius'] as const;\n\nexport class Triangle<\n Props extends TOptions<TriangleProps> = Partial<TriangleProps>,\n SProps extends SerializedTriangleProps = SerializedTriangleProps,\n EventSpec extends ObjectEvents = ObjectEvents,\n >\n extends FabricObject<Props, SProps, EventSpec>\n implements TriangleProps\n{\n /**\n * Corner radius for rounded triangle corners\n * @type Number\n */\n declare cornerRadius: number;\n\n static type = 'Triangle';\n\n static cacheProperties = [...cacheProperties, ...TRIANGLE_PROPS];\n\n static ownDefaults = triangleDefaultValues;\n\n static getDefaults(): Record<string, any> {\n return { ...super.getDefaults(), ...Triangle.ownDefaults };\n }\n\n /**\n * Constructor\n * @param {Object} [options] Options object\n */\n constructor(options?: Props) {\n super();\n Object.assign(this, Triangle.ownDefaults);\n this.setOptions(options);\n }\n\n /**\n * Get triangle points as an array of XY coordinates\n * @private\n */\n private _getTrianglePoints() {\n const widthBy2 = this.width / 2;\n const heightBy2 = this.height / 2;\n\n return [\n { x: -widthBy2, y: heightBy2 }, // bottom left\n { x: 0, y: -heightBy2 }, // top center\n { x: widthBy2, y: heightBy2 }, // bottom right\n ];\n }\n\n /**\n * @private\n * @param {CanvasRenderingContext2D} ctx Context to render on\n */\n _render(ctx: CanvasRenderingContext2D) {\n if (this.cornerRadius > 0) {\n // Render rounded triangle\n const points = this._getTrianglePoints();\n const roundedCorners = applyCornerRadiusToPolygon(points, this.cornerRadius);\n renderRoundedPolygon(ctx, roundedCorners, true);\n } else {\n // Render sharp triangle (original implementation)\n const widthBy2 = this.width / 2;\n const heightBy2 = this.height / 2;\n\n ctx.beginPath();\n ctx.moveTo(-widthBy2, heightBy2);\n ctx.lineTo(0, -heightBy2);\n ctx.lineTo(widthBy2, heightBy2);\n ctx.closePath();\n }\n\n this._renderPaintInOrder(ctx);\n }\n\n /**\n * Returns object representation of an instance\n * @param {Array} [propertiesToInclude] Any properties that you might want to additionally include in the output\n * @return {Object} object representation of an instance\n */\n toObject<\n T extends Omit<Props & TClassProperties<this>, keyof SProps>,\n K extends keyof T = never,\n >(propertiesToInclude: K[] = []): Pick<T, K> & SProps {\n return super.toObject([...TRIANGLE_PROPS, ...propertiesToInclude]);\n }\n\n /**\n * Returns svg representation of an instance\n * @return {Array} an array of strings with the specific svg representation\n * of the instance\n */\n _toSVG() {\n if (this.cornerRadius > 0) {\n // Generate rounded triangle as path\n const points = this._getTrianglePoints();\n const roundedCorners = applyCornerRadiusToPolygon(points, this.cornerRadius);\n const pathData = generateRoundedPolygonPath(roundedCorners, true);\n return ['<path ', 'COMMON_PARTS', `d=\"${pathData}\" />`];\n } else {\n // Original sharp triangle implementation\n const widthBy2 = this.width / 2;\n const heightBy2 = this.height / 2;\n const points = `${-widthBy2} ${heightBy2},0 ${-heightBy2},${widthBy2} ${heightBy2}`;\n return ['<polygon ', 'COMMON_PARTS', 'points=\"', points, '\" />'];\n }\n }\n}\n\nclassRegistry.setClass(Triangle);\nclassRegistry.setSVGClass(Triangle);\n"],"names":["triangleDefaultValues","width","height","cornerRadius","TRIANGLE_PROPS","Triangle","FabricObject","getDefaults","ownDefaults","constructor","options","Object","assign","setOptions","_getTrianglePoints","widthBy2","heightBy2","x","y","_render","ctx","points","roundedCorners","applyCornerRadiusToPolygon","renderRoundedPolygon","beginPath","moveTo","lineTo","closePath","_renderPaintInOrder","toObject","propertiesToInclude","arguments","length","undefined","_toSVG","pathData","generateRoundedPolygonPath","_defineProperty","cacheProperties","classRegistry","setClass","setSVGClass"],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage Example: Enhanced Arabic Text Support
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates how to use the improved text measurement system
|
|
5
|
+
* for better Arabic font support and bounding box calculations.
|
|
6
|
+
*/
|
|
7
|
+
import { FabricText } from '../../shapes/Text/Text';
|
|
8
|
+
/**
|
|
9
|
+
* Example 1: Basic Arabic text with enhanced layout
|
|
10
|
+
*/
|
|
11
|
+
declare function createArabicTextWithEnhancedLayout(): FabricText<{
|
|
12
|
+
fontFamily: string;
|
|
13
|
+
fontSize: number;
|
|
14
|
+
fill: string;
|
|
15
|
+
enableAdvancedLayout: true;
|
|
16
|
+
direction: "rtl";
|
|
17
|
+
textAlign: string;
|
|
18
|
+
letterSpacing: number;
|
|
19
|
+
lineHeight: number;
|
|
20
|
+
}, import("../../shapes/Text/Text").SerializedTextProps, import("../../EventTypeDefs").ObjectEvents>;
|
|
21
|
+
/**
|
|
22
|
+
* Example 2: Comparing legacy vs enhanced measurement
|
|
23
|
+
*/
|
|
24
|
+
declare function compareMeasurementSystems(): {
|
|
25
|
+
legacy: FabricText<{
|
|
26
|
+
fontFamily: string;
|
|
27
|
+
fontSize: number;
|
|
28
|
+
enableAdvancedLayout: false;
|
|
29
|
+
}, import("../../shapes/Text/Text").SerializedTextProps, import("../../EventTypeDefs").ObjectEvents>;
|
|
30
|
+
enhanced: FabricText<{
|
|
31
|
+
fontFamily: string;
|
|
32
|
+
fontSize: number;
|
|
33
|
+
enableAdvancedLayout: true;
|
|
34
|
+
}, import("../../shapes/Text/Text").SerializedTextProps, import("../../EventTypeDefs").ObjectEvents>;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Example 3: Manual measurement of Arabic characters
|
|
38
|
+
*/
|
|
39
|
+
declare function measureArabicCharacters(): void;
|
|
40
|
+
/**
|
|
41
|
+
* Example 4: Mixed script text (Arabic + English)
|
|
42
|
+
*/
|
|
43
|
+
declare function createMixedScriptText(): FabricText<{
|
|
44
|
+
fontFamily: string;
|
|
45
|
+
fontSize: number;
|
|
46
|
+
fill: string;
|
|
47
|
+
enableAdvancedLayout: true;
|
|
48
|
+
direction: "ltr";
|
|
49
|
+
textAlign: string;
|
|
50
|
+
}, import("../../shapes/Text/Text").SerializedTextProps, import("../../EventTypeDefs").ObjectEvents>;
|
|
51
|
+
/**
|
|
52
|
+
* Example 5: Font metrics comparison across scripts
|
|
53
|
+
*/
|
|
54
|
+
declare function compareFontMetricsAcrossScripts(): void;
|
|
55
|
+
/**
|
|
56
|
+
* Main function to run all examples
|
|
57
|
+
*/
|
|
58
|
+
declare function runArabicTextExamples(): void;
|
|
59
|
+
export { runArabicTextExamples, createArabicTextWithEnhancedLayout, compareMeasurementSystems, measureArabicCharacters, createMixedScriptText, compareFontMetricsAcrossScripts, };
|
|
60
|
+
//# sourceMappingURL=arabicTextExample.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arabicTextExample.d.ts","sourceRoot":"","sources":["../../../../src/text/examples/arabicTextExample.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAKpD;;GAEG;AACH,iBAAS,kCAAkC;;;;;;;;;qGA2B1C;AAED;;GAEG;AACH,iBAAS,yBAAyB;;;;;;;;;;;EAmCjC;AAED;;GAEG;AACH,iBAAS,uBAAuB,SA6B/B;AAED;;GAEG;AACH,iBAAS,qBAAqB;;;;;;;qGAqB7B;AAED;;GAEG;AACH,iBAAS,+BAA+B,SA8BvC;AAED;;GAEG;AACH,iBAAS,qBAAqB,SAsB7B;AAGD,OAAO,EACL,qBAAqB,EACrB,kCAAkC,EAClC,yBAAyB,EACzB,uBAAuB,EACvB,qBAAqB,EACrB,+BAA+B,GAChC,CAAC"}
|
|
@@ -127,5 +127,14 @@ export declare function estimateTextWidth(text: string, options: MeasurementOpti
|
|
|
127
127
|
* Check if font is loaded and ready for measurement
|
|
128
128
|
*/
|
|
129
129
|
export declare function isFontReady(fontFamily: string): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Detect if a font lacks English glyph support
|
|
132
|
+
* These fonts should use browser-native measurement instead of Fabric's character-by-character measurement
|
|
133
|
+
*/
|
|
134
|
+
export declare function fontLacksEnglishGlyphs(fontFamily: string): boolean;
|
|
135
|
+
/**
|
|
136
|
+
* Cached version of font glyph detection
|
|
137
|
+
*/
|
|
138
|
+
export declare function fontLacksEnglishGlyphsCached(fontFamily: string): boolean;
|
|
130
139
|
export {};
|
|
131
140
|
//# sourceMappingURL=measure.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measure.d.ts","sourceRoot":"","sources":["../../../src/text/measure.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,KAAK,GAAG,KAAK,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,kBAAmB,SAAQ,mBAAmB;IAC7D,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,wBAAwB,CAAC,EAAE,MAAM,CAAC;CACnC;AAmBD;;GAEG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,kBAAkB,EAC3B,GAAG,CAAC,EAAE,wBAAwB,GAC7B,mBAAmB,CA8BrB;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,MAAM,EAChB,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,OAAO,EAAE,kBAAkB,EAC3B,GAAG,CAAC,EAAE,wBAAwB,GAC7B,kBAAkB,
|
|
1
|
+
{"version":3,"file":"measure.d.ts","sourceRoot":"","sources":["../../../src/text/measure.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{defineProperty as t}from"../../_virtual/_rollupPluginBabelHelpers.min.mjs";import{createCanvasElementFor as e}from"../util/misc/dom.min.mjs";let
|
|
1
|
+
import{defineProperty as t}from"../../_virtual/_rollupPluginBabelHelpers.min.mjs";import{createCanvasElementFor as e}from"../util/misc/dom.min.mjs";let n=null;function s(){if(!n){const t=e({width:0,height:0});n=t.getContext("2d")}return n}function i(t,e,n){const i=l.get(t,e);if(i)return i;const c=n||s();r(c,e);const h=c.measureText(t),o=a(e),u={width:h.width,height:o.lineHeight,ascent:o.ascent,descent:o.descent,baseline:o.ascent};return l.set(t,e,u),u}function c(t,e,n,c){const a=i(t,n,c);if(!e)return{...a,kernedWidth:a.width};const h=`${e}${t}`,o=d.get(h,n);if(o)return{...a,kernedWidth:o};const u=s();r(u,n);const l=u.measureText(e+t).width-i(e,n,u).width;return d.set(h,n,l),{...a,kernedWidth:l}}function a(t){var e,n,i,c;const a=h(t),o=f.get(a);if(o)return o;const u=s();r(u,t);const l=function(t){const e=s();if("undefined"!=typeof document&&"fonts"in document)try{if(!document.fonts.check(`16px ${t}`))return"M"}catch(t){return"M"}const n=[{char:"م",script:"Arabic"},{char:"א",script:"Hebrew"},{char:"अ",script:"Devanagari"},{char:"ا",script:"Urdu"},{char:"ک",script:"Persian"},{char:"த",script:"Tamil"},{char:"ก",script:"Thai"},{char:"М",script:"Cyrillic"},{char:"Ω",script:"Greek"},{char:"M",script:"Latin"}];e.font=`16px ${t}`;const i=e.measureText("M").width;for(const t of n){const n=e.measureText(t.char);if(n.width>0&&Math.abs(n.width-i)>.1)return t.char}return"M"}(t.fontFamily),d=u.measureText(l),g=t.fontSize,m=null!==(e=d.fontBoundingBoxAscent)&&void 0!==e?e:.91*g,p=null!==(n=d.fontBoundingBoxDescent)&&void 0!==n?n:.21*g,x={ascent:m,descent:p,lineHeight:g,baseline:"alphabetic",fontBoundingBoxAscent:m,fontBoundingBoxDescent:p,actualBoundingBoxAscent:null!==(i=d.actualBoundingBoxAscent)&&void 0!==i?i:.716*g,actualBoundingBoxDescent:null!==(c=d.actualBoundingBoxDescent)&&void 0!==c?c:0};return f.set(a,x),x}function r(t,e){const n=h(e);t.font=n,e.letterSpacing&&"letterSpacing"in t&&(t.letterSpacing=`${e.letterSpacing}px`),e.direction&&(t.direction=e.direction),t.textBaseline="alphabetic"}function h(t){const{fontStyle:e,fontWeight:n,fontSize:s,fontFamily:i}=t;return`${e} ${n} ${s}px ${!i.includes(" ")||i.includes('"')||i.includes("'")?i:`"${i}"`}`}class o{constructor(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:1e3;t(this,"cache",new Map),t(this,"maxSize",void 0),t(this,"hits",0),t(this,"misses",0),this.maxSize=e}get(t){const e=this.cache.get(t);if(e)return e.timestamp=Date.now(),this.hits++,e.value;this.misses++}set(t,e){if(this.cache.size>=this.maxSize){const t=this.findOldestKey();t&&this.cache.delete(t)}this.cache.set(t,{value:e,timestamp:Date.now()})}findOldestKey(){let t,e=1/0;for(const[n,s]of this.cache.entries())s.timestamp<e&&(e=s.timestamp,t=n);return t}clear(){this.cache.clear(),this.hits=0,this.misses=0}getStats(){const t=this.hits+this.misses;return{size:this.cache.size,hitRate:t>0?this.hits/t:0,hits:this.hits,misses:this.misses}}}class u{constructor(){t(this,"cache",new o(1e3))}getCacheKey(t,e){return`${h(e)}|${t}|${e.letterSpacing||0}`}get(t,e){const n=this.getCacheKey(t,e);return this.cache.get(n)}set(t,e,n){const s=this.getCacheKey(t,e);this.cache.set(s,n)}clear(){this.cache.clear()}getStats(){return this.cache.getStats()}}const l=new u,d=new class{constructor(){t(this,"cache",new o(5e3))}getCacheKey(t,e){return`${h(e)}|${t}`}get(t,e){const n=this.getCacheKey(t,e);return this.cache.get(n)}set(t,e,n){const s=this.getCacheKey(t,e);this.cache.set(s,n)}clear(){this.cache.clear()}getStats(){return this.cache.getStats()}},f=new class{constructor(){t(this,"cache",new Map)}get(t){return this.cache.get(t)}set(t,e){this.cache.set(t,e)}clear(){this.cache.clear()}getStats(){return{size:this.cache.size}}};function g(){l.clear(),d.clear(),f.clear()}function m(t){if("undefined"==typeof document)return!1;const e=t.toLowerCase();if(["stv","arabic","naskh","thuluth","kufi","diwani","nastaliq","kufic","hijazi","madinah","makkah"].some(t=>e.includes(t)))return!0;const n=s();n.font=`16px ${t}`;const i=["A","B","C","a","b","c","M","W"],c=i.map(t=>n.measureText(t).width);n.font="16px Arial, sans-serif";const a=i.map(t=>n.measureText(t).width);let r=0;for(let t=0;t<i.length;t++)Math.abs(c[t]-a[t])<.5&&r++;return r>=.7*i.length}"undefined"!=typeof document&&"fonts"in document&&document.fonts.addEventListener("loadingdone",()=>{g()});const p=new Map;function x(t){if(p.has(t))return p.get(t);const e=m(t);return p.set(t,e),e}export{u as MeasurementCache,g as clearAllCaches,m as fontLacksEnglishGlyphs,x as fontLacksEnglishGlyphsCached,f as fontMetricsCache,a as getFontMetrics,d as kerningCache,i as measureGrapheme,c as measureGraphemeWithKerning,l as measurementCache};
|
|
2
2
|
//# sourceMappingURL=measure.min.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measure.min.mjs","sources":["../../../src/text/measure.ts"],"sourcesContent":["/**\r\n * Advanced Text Measurement System\r\n * \r\n * Provides precise text measurement with caching, font metrics,\r\n * and DPI awareness for optimal performance and accuracy.\r\n */\r\n\r\nimport { createCanvasElementFor } from '../util/misc/dom';\r\n\r\nexport interface MeasurementOptions {\r\n fontFamily: string;\r\n fontSize: number;\r\n fontStyle: string;\r\n fontWeight: string | number;\r\n letterSpacing?: number;\r\n direction?: 'ltr' | 'rtl';\r\n}\r\n\r\nexport interface GraphemeMeasurement {\r\n width: number;\r\n height: number;\r\n ascent: number;\r\n descent: number;\r\n baseline: number;\r\n}\r\n\r\nexport interface KerningMeasurement extends GraphemeMeasurement {\r\n kernedWidth: number; // width accounting for kerning with previous char\r\n}\r\n\r\nexport interface FontMetrics {\r\n ascent: number;\r\n descent: number;\r\n lineHeight: number;\r\n baseline: string;\r\n fontBoundingBoxAscent?: number;\r\n fontBoundingBoxDescent?: number;\r\n actualBoundingBoxAscent?: number;\r\n actualBoundingBoxDescent?: number;\r\n}\r\n\r\n// Global measurement context - reused for performance\r\nlet measurementContext: CanvasRenderingContext2D | null = null;\r\n\r\n/**\r\n * Get or create the shared measurement context\r\n */\r\nfunction getMeasurementContext(): CanvasRenderingContext2D {\r\n if (!measurementContext) {\r\n const canvas = createCanvasElementFor({\r\n width: 0,\r\n height: 0,\r\n });\r\n measurementContext = canvas.getContext('2d')!;\r\n }\r\n return measurementContext;\r\n}\r\n\r\n/**\r\n * Measure a single grapheme\r\n */\r\nexport function measureGrapheme(\r\n grapheme: string,\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D\r\n): GraphemeMeasurement {\r\n // Check cache first\r\n const cached = measurementCache.get(grapheme, options);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n // Use provided context or get global one\r\n const context = ctx || getMeasurementContext();\r\n \r\n // Set font properties\r\n applyFontStyle(context, options);\r\n \r\n // Measure the grapheme\r\n const metrics = context.measureText(grapheme);\r\n const fontMetrics = getFontMetrics(options);\r\n \r\n // Calculate comprehensive measurements\r\n const measurement: GraphemeMeasurement = {\r\n width: metrics.width,\r\n height: fontMetrics.lineHeight,\r\n ascent: fontMetrics.ascent,\r\n descent: fontMetrics.descent,\r\n baseline: fontMetrics.ascent,\r\n };\r\n \r\n // Cache the result\r\n measurementCache.set(grapheme, options, measurement);\r\n \r\n return measurement;\r\n}\r\n\r\n/**\r\n * Measure a grapheme with kerning relative to previous character\r\n */\r\nexport function measureGraphemeWithKerning(\r\n grapheme: string,\r\n previousGrapheme: string | undefined,\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D\r\n): KerningMeasurement {\r\n // Get individual measurement\r\n const individual = measureGrapheme(grapheme, options, ctx);\r\n \r\n // If no previous character, kerning width equals regular width\r\n if (!previousGrapheme) {\r\n return {\r\n ...individual,\r\n kernedWidth: individual.width,\r\n };\r\n }\r\n \r\n // Check kerning cache\r\n const kerningPair = `${previousGrapheme}${grapheme}`;\r\n const cachedKerning = kerningCache.get(kerningPair, options);\r\n if (cachedKerning) {\r\n return {\r\n ...individual,\r\n kernedWidth: cachedKerning,\r\n };\r\n }\r\n \r\n // Use provided context or get global one\r\n const context = ctx || getMeasurementContext();\r\n applyFontStyle(context, options);\r\n \r\n // Measure the pair\r\n const pairWidth = context.measureText(previousGrapheme + grapheme).width;\r\n const previousWidth = measureGrapheme(previousGrapheme, options, context).width;\r\n const kernedWidth = pairWidth - previousWidth;\r\n \r\n // Cache kerning result\r\n kerningCache.set(kerningPair, options, kernedWidth);\r\n \r\n return {\r\n ...individual,\r\n kernedWidth,\r\n };\r\n}\r\n\r\n/**\r\n * Get font metrics for layout calculations\r\n */\r\nexport function getFontMetrics(options: MeasurementOptions): FontMetrics {\r\n const cacheKey = getFontDeclaration(options);\r\n const cached = fontMetricsCache.get(cacheKey);\r\n if (cached) {\r\n return cached;\r\n }\r\n \r\n const context = getMeasurementContext();\r\n applyFontStyle(context, options);\r\n \r\n // Use 'M' as sample character for metrics\r\n const metrics = context.measureText('M');\r\n const fontSize = options.fontSize;\r\n \r\n // Calculate metrics with fallbacks\r\n const fontBoundingBoxAscent = metrics.fontBoundingBoxAscent ?? fontSize * 0.91;\r\n const fontBoundingBoxDescent = metrics.fontBoundingBoxDescent ?? fontSize * 0.21;\r\n const actualBoundingBoxAscent = metrics.actualBoundingBoxAscent ?? fontSize * 0.716;\r\n const actualBoundingBoxDescent = metrics.actualBoundingBoxDescent ?? 0;\r\n \r\n const result: FontMetrics = {\r\n ascent: fontBoundingBoxAscent,\r\n descent: fontBoundingBoxDescent,\r\n lineHeight: fontSize,\r\n baseline: 'alphabetic',\r\n fontBoundingBoxAscent,\r\n fontBoundingBoxDescent,\r\n actualBoundingBoxAscent,\r\n actualBoundingBoxDescent,\r\n };\r\n \r\n fontMetricsCache.set(cacheKey, result);\r\n return result;\r\n}\r\n\r\n/**\r\n * Apply font styling to canvas context\r\n */\r\nfunction applyFontStyle(ctx: CanvasRenderingContext2D, options: MeasurementOptions): void {\r\n const fontDeclaration = getFontDeclaration(options);\r\n ctx.font = fontDeclaration;\r\n \r\n if (options.letterSpacing) {\r\n // Modern browsers support letterSpacing\r\n if ('letterSpacing' in ctx) {\r\n (ctx as any).letterSpacing = `${options.letterSpacing}px`;\r\n }\r\n }\r\n \r\n if (options.direction) {\r\n ctx.direction = options.direction;\r\n }\r\n \r\n ctx.textBaseline = 'alphabetic';\r\n}\r\n\r\n/**\r\n * Generate font declaration string\r\n */\r\nfunction getFontDeclaration(options: MeasurementOptions): string {\r\n const { fontStyle, fontWeight, fontSize, fontFamily } = options;\r\n \r\n // Normalize font family (add quotes if needed)\r\n const normalizedFamily = fontFamily.includes(' ') && \r\n !fontFamily.includes('\"') && \r\n !fontFamily.includes(\"'\")\r\n ? `\"${fontFamily}\"`\r\n : fontFamily;\r\n \r\n return `${fontStyle} ${fontWeight} ${fontSize}px ${normalizedFamily}`;\r\n}\r\n\r\n/**\r\n * LRU Cache implementation for measurements\r\n */\r\nclass LRUCache<T> {\r\n private cache = new Map<string, { value: T; timestamp: number }>();\r\n private maxSize: number;\r\n private hits = 0;\r\n private misses = 0;\r\n\r\n constructor(maxSize = 1000) {\r\n this.maxSize = maxSize;\r\n }\r\n\r\n get(key: string): T | undefined {\r\n const entry = this.cache.get(key);\r\n if (entry) {\r\n // Update timestamp for LRU\r\n entry.timestamp = Date.now();\r\n this.hits++;\r\n return entry.value;\r\n }\r\n this.misses++;\r\n return undefined;\r\n }\r\n\r\n set(key: string, value: T): void {\r\n // Remove oldest entries if at capacity\r\n if (this.cache.size >= this.maxSize) {\r\n const oldestKey = this.findOldestKey();\r\n if (oldestKey) {\r\n this.cache.delete(oldestKey);\r\n }\r\n }\r\n\r\n this.cache.set(key, {\r\n value,\r\n timestamp: Date.now(),\r\n });\r\n }\r\n\r\n private findOldestKey(): string | undefined {\r\n let oldestKey: string | undefined;\r\n let oldestTime = Infinity;\r\n\r\n for (const [key, entry] of this.cache.entries()) {\r\n if (entry.timestamp < oldestTime) {\r\n oldestTime = entry.timestamp;\r\n oldestKey = key;\r\n }\r\n }\r\n\r\n return oldestKey;\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n this.hits = 0;\r\n this.misses = 0;\r\n }\r\n\r\n getStats(): { size: number; hitRate: number; hits: number; misses: number } {\r\n const total = this.hits + this.misses;\r\n return {\r\n size: this.cache.size,\r\n hitRate: total > 0 ? this.hits / total : 0,\r\n hits: this.hits,\r\n misses: this.misses,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Advanced measurement cache with font-aware keys\r\n */\r\nexport class MeasurementCache {\r\n private cache = new LRUCache<GraphemeMeasurement>(1000);\r\n\r\n getCacheKey(grapheme: string, options: MeasurementOptions): string {\r\n const fontDecl = getFontDeclaration(options);\r\n const letterSpacing = options.letterSpacing || 0;\r\n return `${fontDecl}|${grapheme}|${letterSpacing}`;\r\n }\r\n\r\n get(grapheme: string, options: MeasurementOptions): GraphemeMeasurement | undefined {\r\n const key = this.getCacheKey(grapheme, options);\r\n return this.cache.get(key);\r\n }\r\n\r\n set(grapheme: string, options: MeasurementOptions, measurement: GraphemeMeasurement): void {\r\n const key = this.getCacheKey(grapheme, options);\r\n this.cache.set(key, measurement);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return this.cache.getStats();\r\n }\r\n}\r\n\r\n/**\r\n * Kerning cache for character pairs\r\n */\r\nclass KerningCache {\r\n private cache = new LRUCache<number>(5000); // More entries for pairs\r\n\r\n getCacheKey(pair: string, options: MeasurementOptions): string {\r\n const fontDecl = getFontDeclaration(options);\r\n return `${fontDecl}|${pair}`;\r\n }\r\n\r\n get(pair: string, options: MeasurementOptions): number | undefined {\r\n const key = this.getCacheKey(pair, options);\r\n return this.cache.get(key);\r\n }\r\n\r\n set(pair: string, options: MeasurementOptions, kerning: number): void {\r\n const key = this.getCacheKey(pair, options);\r\n this.cache.set(key, kerning);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return this.cache.getStats();\r\n }\r\n}\r\n\r\n/**\r\n * Font metrics cache\r\n */\r\nclass FontMetricsCache {\r\n private cache = new Map<string, FontMetrics>();\r\n\r\n get(fontDeclaration: string): FontMetrics | undefined {\r\n return this.cache.get(fontDeclaration);\r\n }\r\n\r\n set(fontDeclaration: string, metrics: FontMetrics): void {\r\n this.cache.set(fontDeclaration, metrics);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return {\r\n size: this.cache.size,\r\n };\r\n }\r\n}\r\n\r\n// Global cache instances\r\nexport const measurementCache = new MeasurementCache();\r\nexport const kerningCache = new KerningCache();\r\nexport const fontMetricsCache = new FontMetricsCache();\r\n\r\n/**\r\n * Clear all measurement caches\r\n */\r\nexport function clearAllCaches(): void {\r\n measurementCache.clear();\r\n kerningCache.clear();\r\n fontMetricsCache.clear();\r\n}\r\n\r\n/**\r\n * Get combined cache statistics\r\n */\r\nexport function getCacheStats() {\r\n return {\r\n measurement: measurementCache.getStats(),\r\n kerning: kerningCache.getStats(),\r\n fontMetrics: fontMetricsCache.getStats(),\r\n };\r\n}\r\n\r\n/**\r\n * Batch measure multiple graphemes efficiently\r\n */\r\nexport function batchMeasureGraphemes(\r\n graphemes: string[],\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D\r\n): GraphemeMeasurement[] {\r\n const context = ctx || getMeasurementContext();\r\n applyFontStyle(context, options);\r\n \r\n // Separate cached and uncached measurements\r\n const results: GraphemeMeasurement[] = new Array(graphemes.length);\r\n const uncachedIndices: number[] = [];\r\n \r\n // Check cache for all graphemes\r\n graphemes.forEach((grapheme, index) => {\r\n const cached = measurementCache.get(grapheme, options);\r\n if (cached) {\r\n results[index] = cached;\r\n } else {\r\n uncachedIndices.push(index);\r\n }\r\n });\r\n \r\n // Measure uncached graphemes\r\n const fontMetrics = getFontMetrics(options);\r\n uncachedIndices.forEach(index => {\r\n const grapheme = graphemes[index];\r\n const metrics = context.measureText(grapheme);\r\n \r\n const measurement: GraphemeMeasurement = {\r\n width: metrics.width,\r\n height: fontMetrics.lineHeight,\r\n ascent: fontMetrics.ascent,\r\n descent: fontMetrics.descent,\r\n baseline: fontMetrics.ascent,\r\n };\r\n \r\n measurementCache.set(grapheme, options, measurement);\r\n results[index] = measurement;\r\n });\r\n \r\n return results;\r\n}\r\n\r\n/**\r\n * Estimate text width without full layout (for performance)\r\n */\r\nexport function estimateTextWidth(\r\n text: string,\r\n options: MeasurementOptions\r\n): number {\r\n // Use average character width for estimation\r\n const avgChar = 'n'; // Representative character\r\n const avgMeasurement = measureGrapheme(avgChar, options);\r\n const letterSpacing = options.letterSpacing || 0;\r\n \r\n return text.length * (avgMeasurement.width + letterSpacing);\r\n}\r\n\r\n/**\r\n * Check if font is loaded and ready for measurement\r\n */\r\nexport function isFontReady(fontFamily: string): boolean {\r\n if (typeof document === 'undefined') return true;\r\n \r\n if ('fonts' in document) {\r\n return document.fonts.check(`16px ${fontFamily}`);\r\n }\r\n \r\n // Fallback - assume font is ready\r\n return true;\r\n}"],"names":["measurementContext","getMeasurementContext","canvas","createCanvasElementFor","width","height","getContext","measureGrapheme","grapheme","options","ctx","cached","measurementCache","get","context","applyFontStyle","metrics","measureText","fontMetrics","getFontMetrics","measurement","lineHeight","ascent","descent","baseline","set","measureGraphemeWithKerning","previousGrapheme","individual","kernedWidth","kerningPair","cachedKerning","kerningCache","_metrics$fontBounding","_metrics$fontBounding2","_metrics$actualBoundi","_metrics$actualBoundi2","cacheKey","getFontDeclaration","fontMetricsCache","fontSize","fontBoundingBoxAscent","fontBoundingBoxDescent","result","actualBoundingBoxAscent","actualBoundingBoxDescent","fontDeclaration","font","letterSpacing","direction","textBaseline","fontStyle","fontWeight","fontFamily","includes","LRUCache","constructor","maxSize","arguments","length","undefined","_defineProperty","this","Map","key","entry","cache","timestamp","Date","now","hits","value","misses","size","oldestKey","findOldestKey","delete","oldestTime","Infinity","entries","clear","getStats","total","hitRate","MeasurementCache","getCacheKey","pair","kerning"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"measure.min.mjs","sources":["../../../src/text/measure.ts"],"sourcesContent":["/**\r\n * Advanced Text Measurement System\r\n *\r\n * Provides precise text measurement with caching, font metrics,\r\n * and DPI awareness for optimal performance and accuracy.\r\n */\r\n\r\nimport { createCanvasElementFor } from '../util/misc/dom';\r\n\r\nexport interface MeasurementOptions {\r\n fontFamily: string;\r\n fontSize: number;\r\n fontStyle: string;\r\n fontWeight: string | number;\r\n letterSpacing?: number;\r\n direction?: 'ltr' | 'rtl';\r\n}\r\n\r\nexport interface GraphemeMeasurement {\r\n width: number;\r\n height: number;\r\n ascent: number;\r\n descent: number;\r\n baseline: number;\r\n}\r\n\r\nexport interface KerningMeasurement extends GraphemeMeasurement {\r\n kernedWidth: number; // width accounting for kerning with previous char\r\n}\r\n\r\nexport interface FontMetrics {\r\n ascent: number;\r\n descent: number;\r\n lineHeight: number;\r\n baseline: string;\r\n fontBoundingBoxAscent?: number;\r\n fontBoundingBoxDescent?: number;\r\n actualBoundingBoxAscent?: number;\r\n actualBoundingBoxDescent?: number;\r\n}\r\n\r\n// Global measurement context - reused for performance\r\nlet measurementContext: CanvasRenderingContext2D | null = null;\r\n\r\n/**\r\n * Get or create the shared measurement context\r\n */\r\nfunction getMeasurementContext(): CanvasRenderingContext2D {\r\n if (!measurementContext) {\r\n const canvas = createCanvasElementFor({\r\n width: 0,\r\n height: 0,\r\n });\r\n measurementContext = canvas.getContext('2d')!;\r\n }\r\n return measurementContext;\r\n}\r\n\r\n/**\r\n * Measure a single grapheme\r\n */\r\nexport function measureGrapheme(\r\n grapheme: string,\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D,\r\n): GraphemeMeasurement {\r\n // Check cache first\r\n const cached = measurementCache.get(grapheme, options);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n // Use provided context or get global one\r\n const context = ctx || getMeasurementContext();\r\n\r\n // Set font properties\r\n applyFontStyle(context, options);\r\n\r\n // Measure the grapheme\r\n const metrics = context.measureText(grapheme);\r\n const fontMetrics = getFontMetrics(options);\r\n\r\n // Calculate comprehensive measurements\r\n const measurement: GraphemeMeasurement = {\r\n width: metrics.width,\r\n height: fontMetrics.lineHeight,\r\n ascent: fontMetrics.ascent,\r\n descent: fontMetrics.descent,\r\n baseline: fontMetrics.ascent,\r\n };\r\n\r\n // Cache the result\r\n measurementCache.set(grapheme, options, measurement);\r\n\r\n return measurement;\r\n}\r\n\r\n/**\r\n * Measure a grapheme with kerning relative to previous character\r\n */\r\nexport function measureGraphemeWithKerning(\r\n grapheme: string,\r\n previousGrapheme: string | undefined,\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D,\r\n): KerningMeasurement {\r\n // Get individual measurement\r\n const individual = measureGrapheme(grapheme, options, ctx);\r\n\r\n // If no previous character, kerning width equals regular width\r\n if (!previousGrapheme) {\r\n return {\r\n ...individual,\r\n kernedWidth: individual.width,\r\n };\r\n }\r\n\r\n // Check kerning cache\r\n const kerningPair = `${previousGrapheme}${grapheme}`;\r\n const cachedKerning = kerningCache.get(kerningPair, options);\r\n if (cachedKerning) {\r\n return {\r\n ...individual,\r\n kernedWidth: cachedKerning,\r\n };\r\n }\r\n\r\n // Use provided context or get global one\r\n const context = ctx || getMeasurementContext();\r\n applyFontStyle(context, options);\r\n\r\n // Measure the pair\r\n const pairWidth = context.measureText(previousGrapheme + grapheme).width;\r\n const previousWidth = measureGrapheme(\r\n previousGrapheme,\r\n options,\r\n context,\r\n ).width;\r\n const kernedWidth = pairWidth - previousWidth;\r\n\r\n // Cache kerning result\r\n kerningCache.set(kerningPair, options, kernedWidth);\r\n\r\n return {\r\n ...individual,\r\n kernedWidth,\r\n };\r\n}\r\n\r\n/**\r\n * Get a representative character for font metrics measurement\r\n * Uses canvas to test which scripts the font actually supports\r\n */\r\nfunction getRepresentativeCharacter(fontFamily: string): string {\r\n const context = getMeasurementContext();\r\n \r\n // Wait for font to be ready if possible\r\n if (typeof document !== 'undefined' && 'fonts' in document) {\r\n try {\r\n // Check if font is ready, if not, use fallback immediately\r\n if (!document.fonts.check(`16px ${fontFamily}`)) {\r\n return 'M'; // Use safe fallback while font loads\r\n }\r\n } catch (e) {\r\n // Font check failed, use fallback\r\n return 'M';\r\n }\r\n }\r\n \r\n // Test characters for different scripts\r\n const testChars = [\r\n { char: 'م', script: 'Arabic' }, // Arabic\r\n { char: 'א', script: 'Hebrew' }, // Hebrew \r\n { char: 'अ', script: 'Devanagari' }, // Hindi/Sanskrit\r\n { char: 'ا', script: 'Urdu' }, // Urdu\r\n { char: 'ک', script: 'Persian' }, // Persian\r\n { char: 'த', script: 'Tamil' }, // Tamil\r\n { char: 'ก', script: 'Thai' }, // Thai\r\n { char: 'М', script: 'Cyrillic' }, // Cyrillic\r\n { char: 'Ω', script: 'Greek' }, // Greek\r\n { char: 'M', script: 'Latin' } // Latin (fallback)\r\n ];\r\n \r\n // Set the font\r\n context.font = `16px ${fontFamily}`;\r\n \r\n // Test each character to see which ones render properly\r\n // Use a more robust width check to avoid false positives\r\n const fallbackWidth = context.measureText('M').width;\r\n \r\n for (const test of testChars) {\r\n const metrics = context.measureText(test.char);\r\n \r\n // Character is valid if it has width and isn't just a fallback glyph\r\n if (metrics.width > 0 && Math.abs(metrics.width - fallbackWidth) > 0.1) {\r\n return test.char;\r\n }\r\n }\r\n \r\n // Fallback to Latin 'M'\r\n return 'M';\r\n}\r\n\r\n/**\r\n * Get font metrics for layout calculations\r\n */\r\nexport function getFontMetrics(options: MeasurementOptions): FontMetrics {\r\n const cacheKey = getFontDeclaration(options);\r\n const cached = fontMetricsCache.get(cacheKey);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const context = getMeasurementContext();\r\n applyFontStyle(context, options);\r\n\r\n // Use representative character based on font's primary script\r\n const sample = getRepresentativeCharacter(options.fontFamily);\r\n const metrics = context.measureText(sample);\r\n const fontSize = options.fontSize;\r\n\r\n // Calculate metrics with fallbacks\r\n const fontBoundingBoxAscent =\r\n metrics.fontBoundingBoxAscent ?? fontSize * 0.91;\r\n const fontBoundingBoxDescent =\r\n metrics.fontBoundingBoxDescent ?? fontSize * 0.21;\r\n const actualBoundingBoxAscent =\r\n metrics.actualBoundingBoxAscent ?? fontSize * 0.716;\r\n const actualBoundingBoxDescent = metrics.actualBoundingBoxDescent ?? 0;\r\n\r\n const result: FontMetrics = {\r\n ascent: fontBoundingBoxAscent,\r\n descent: fontBoundingBoxDescent,\r\n lineHeight: fontSize,\r\n baseline: 'alphabetic',\r\n fontBoundingBoxAscent,\r\n fontBoundingBoxDescent,\r\n actualBoundingBoxAscent,\r\n actualBoundingBoxDescent,\r\n };\r\n\r\n fontMetricsCache.set(cacheKey, result);\r\n return result;\r\n}\r\n\r\n/**\r\n * Apply font styling to canvas context\r\n */\r\nfunction applyFontStyle(\r\n ctx: CanvasRenderingContext2D,\r\n options: MeasurementOptions,\r\n): void {\r\n const fontDeclaration = getFontDeclaration(options);\r\n ctx.font = fontDeclaration;\r\n\r\n if (options.letterSpacing) {\r\n // Modern browsers support letterSpacing\r\n if ('letterSpacing' in ctx) {\r\n (ctx as any).letterSpacing = `${options.letterSpacing}px`;\r\n }\r\n }\r\n\r\n if (options.direction) {\r\n ctx.direction = options.direction;\r\n }\r\n\r\n ctx.textBaseline = 'alphabetic';\r\n}\r\n\r\n/**\r\n * Generate font declaration string\r\n */\r\nfunction getFontDeclaration(options: MeasurementOptions): string {\r\n const { fontStyle, fontWeight, fontSize, fontFamily } = options;\r\n\r\n // Normalize font family (add quotes if needed)\r\n let normalizedFamily =\r\n fontFamily.includes(' ') &&\r\n !fontFamily.includes('\"') &&\r\n !fontFamily.includes(\"'\")\r\n ? `\"${fontFamily}\"`\r\n : fontFamily;\r\n\r\n // Note: Font fallbacks are handled in the rendering phase only\r\n // to avoid affecting measurement calculations for text wrapping\r\n\r\n return `${fontStyle} ${fontWeight} ${fontSize}px ${normalizedFamily}`;\r\n}\r\n\r\n/**\r\n * LRU Cache implementation for measurements\r\n */\r\nclass LRUCache<T> {\r\n private cache = new Map<string, { value: T; timestamp: number }>();\r\n private maxSize: number;\r\n private hits = 0;\r\n private misses = 0;\r\n\r\n constructor(maxSize = 1000) {\r\n this.maxSize = maxSize;\r\n }\r\n\r\n get(key: string): T | undefined {\r\n const entry = this.cache.get(key);\r\n if (entry) {\r\n // Update timestamp for LRU\r\n entry.timestamp = Date.now();\r\n this.hits++;\r\n return entry.value;\r\n }\r\n this.misses++;\r\n return undefined;\r\n }\r\n\r\n set(key: string, value: T): void {\r\n // Remove oldest entries if at capacity\r\n if (this.cache.size >= this.maxSize) {\r\n const oldestKey = this.findOldestKey();\r\n if (oldestKey) {\r\n this.cache.delete(oldestKey);\r\n }\r\n }\r\n\r\n this.cache.set(key, {\r\n value,\r\n timestamp: Date.now(),\r\n });\r\n }\r\n\r\n private findOldestKey(): string | undefined {\r\n let oldestKey: string | undefined;\r\n let oldestTime = Infinity;\r\n\r\n for (const [key, entry] of this.cache.entries()) {\r\n if (entry.timestamp < oldestTime) {\r\n oldestTime = entry.timestamp;\r\n oldestKey = key;\r\n }\r\n }\r\n\r\n return oldestKey;\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n this.hits = 0;\r\n this.misses = 0;\r\n }\r\n\r\n getStats(): { size: number; hitRate: number; hits: number; misses: number } {\r\n const total = this.hits + this.misses;\r\n return {\r\n size: this.cache.size,\r\n hitRate: total > 0 ? this.hits / total : 0,\r\n hits: this.hits,\r\n misses: this.misses,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Advanced measurement cache with font-aware keys\r\n */\r\nexport class MeasurementCache {\r\n private cache = new LRUCache<GraphemeMeasurement>(1000);\r\n\r\n getCacheKey(grapheme: string, options: MeasurementOptions): string {\r\n const fontDecl = getFontDeclaration(options);\r\n const letterSpacing = options.letterSpacing || 0;\r\n return `${fontDecl}|${grapheme}|${letterSpacing}`;\r\n }\r\n\r\n get(\r\n grapheme: string,\r\n options: MeasurementOptions,\r\n ): GraphemeMeasurement | undefined {\r\n const key = this.getCacheKey(grapheme, options);\r\n return this.cache.get(key);\r\n }\r\n\r\n set(\r\n grapheme: string,\r\n options: MeasurementOptions,\r\n measurement: GraphemeMeasurement,\r\n ): void {\r\n const key = this.getCacheKey(grapheme, options);\r\n this.cache.set(key, measurement);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return this.cache.getStats();\r\n }\r\n}\r\n\r\n/**\r\n * Kerning cache for character pairs\r\n */\r\nclass KerningCache {\r\n private cache = new LRUCache<number>(5000); // More entries for pairs\r\n\r\n getCacheKey(pair: string, options: MeasurementOptions): string {\r\n const fontDecl = getFontDeclaration(options);\r\n return `${fontDecl}|${pair}`;\r\n }\r\n\r\n get(pair: string, options: MeasurementOptions): number | undefined {\r\n const key = this.getCacheKey(pair, options);\r\n return this.cache.get(key);\r\n }\r\n\r\n set(pair: string, options: MeasurementOptions, kerning: number): void {\r\n const key = this.getCacheKey(pair, options);\r\n this.cache.set(key, kerning);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return this.cache.getStats();\r\n }\r\n}\r\n\r\n/**\r\n * Font metrics cache\r\n */\r\nclass FontMetricsCache {\r\n private cache = new Map<string, FontMetrics>();\r\n\r\n get(fontDeclaration: string): FontMetrics | undefined {\r\n return this.cache.get(fontDeclaration);\r\n }\r\n\r\n set(fontDeclaration: string, metrics: FontMetrics): void {\r\n this.cache.set(fontDeclaration, metrics);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return {\r\n size: this.cache.size,\r\n };\r\n }\r\n}\r\n\r\n// Global cache instances\r\nexport const measurementCache = new MeasurementCache();\r\nexport const kerningCache = new KerningCache();\r\nexport const fontMetricsCache = new FontMetricsCache();\r\n\r\n// Set up font loading listener to clear caches when fonts change\r\nif (typeof document !== 'undefined' && 'fonts' in document) {\r\n document.fonts.addEventListener('loadingdone', () => {\r\n // Clear all caches when fonts finish loading\r\n clearAllCaches();\r\n });\r\n}\r\n\r\n/**\r\n * Clear all measurement caches\r\n */\r\nexport function clearAllCaches(): void {\r\n measurementCache.clear();\r\n kerningCache.clear();\r\n fontMetricsCache.clear();\r\n}\r\n\r\n/**\r\n * Get combined cache statistics\r\n */\r\nexport function getCacheStats() {\r\n return {\r\n measurement: measurementCache.getStats(),\r\n kerning: kerningCache.getStats(),\r\n fontMetrics: fontMetricsCache.getStats(),\r\n };\r\n}\r\n\r\n/**\r\n * Batch measure multiple graphemes efficiently\r\n */\r\nexport function batchMeasureGraphemes(\r\n graphemes: string[],\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D,\r\n): GraphemeMeasurement[] {\r\n const context = ctx || getMeasurementContext();\r\n applyFontStyle(context, options);\r\n\r\n // Separate cached and uncached measurements\r\n const results: GraphemeMeasurement[] = new Array(graphemes.length);\r\n const uncachedIndices: number[] = [];\r\n\r\n // Check cache for all graphemes\r\n graphemes.forEach((grapheme, index) => {\r\n const cached = measurementCache.get(grapheme, options);\r\n if (cached) {\r\n results[index] = cached;\r\n } else {\r\n uncachedIndices.push(index);\r\n }\r\n });\r\n\r\n // Measure uncached graphemes\r\n const fontMetrics = getFontMetrics(options);\r\n uncachedIndices.forEach((index) => {\r\n const grapheme = graphemes[index];\r\n const metrics = context.measureText(grapheme);\r\n\r\n const measurement: GraphemeMeasurement = {\r\n width: metrics.width,\r\n height: fontMetrics.lineHeight,\r\n ascent: fontMetrics.ascent,\r\n descent: fontMetrics.descent,\r\n baseline: fontMetrics.ascent,\r\n };\r\n\r\n measurementCache.set(grapheme, options, measurement);\r\n results[index] = measurement;\r\n });\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * Estimate text width without full layout (for performance)\r\n */\r\nexport function estimateTextWidth(\r\n text: string,\r\n options: MeasurementOptions,\r\n): number {\r\n // Use average character width for estimation\r\n const avgChar = 'n'; // Representative character\r\n const avgMeasurement = measureGrapheme(avgChar, options);\r\n const letterSpacing = options.letterSpacing || 0;\r\n\r\n return text.length * (avgMeasurement.width + letterSpacing);\r\n}\r\n\r\n/**\r\n * Check if font is loaded and ready for measurement\r\n */\r\nexport function isFontReady(fontFamily: string): boolean {\r\n if (typeof document === 'undefined') return true;\r\n\r\n if ('fonts' in document) {\r\n return document.fonts.check(`16px ${fontFamily}`);\r\n }\r\n\r\n // Fallback - assume font is ready\r\n return true;\r\n}\r\n\r\n/**\r\n * Detect if a font lacks English glyph support\r\n * These fonts should use browser-native measurement instead of Fabric's character-by-character measurement\r\n */\r\nexport function fontLacksEnglishGlyphs(fontFamily: string): boolean {\r\n if (typeof document === 'undefined') return false;\r\n \r\n // Known fonts that lack English glyphs\r\n const knownNonEnglishFonts = [\r\n 'stv', 'arabic', 'naskh', 'thuluth', 'kufi', 'diwani',\r\n 'nastaliq', 'kufic', 'hijazi', 'madinah', 'makkah'\r\n ];\r\n \r\n const lowerFontFamily = fontFamily.toLowerCase();\r\n \r\n // Check known list first\r\n if (knownNonEnglishFonts.some(font => lowerFontFamily.includes(font))) {\r\n return true;\r\n }\r\n \r\n // Dynamic glyph support detection\r\n const context = getMeasurementContext();\r\n context.font = `16px ${fontFamily}`;\r\n \r\n // Test English characters\r\n const englishChars = ['A', 'B', 'C', 'a', 'b', 'c', 'M', 'W'];\r\n const fallbackFont = 'Arial, sans-serif';\r\n \r\n // Measure with target font\r\n const targetWidths = englishChars.map(char => context.measureText(char).width);\r\n \r\n // Measure with fallback font\r\n context.font = `16px ${fallbackFont}`;\r\n const fallbackWidths = englishChars.map(char => context.measureText(char).width);\r\n \r\n // If most measurements are identical, the font likely doesn't have English glyphs\r\n let identicalCount = 0;\r\n for (let i = 0; i < englishChars.length; i++) {\r\n if (Math.abs(targetWidths[i] - fallbackWidths[i]) < 0.5) {\r\n identicalCount++;\r\n }\r\n }\r\n \r\n const lacksSupportThreshold = englishChars.length * 0.7; // 70% identical = lacks support\r\n const lacksSupport = identicalCount >= lacksSupportThreshold;\r\n \r\n \r\n return lacksSupport;\r\n}\r\n\r\n// Cache for font glyph detection results\r\nconst fontGlyphCache = new Map<string, boolean>();\r\n\r\n/**\r\n * Cached version of font glyph detection\r\n */\r\nexport function fontLacksEnglishGlyphsCached(fontFamily: string): boolean {\r\n if (fontGlyphCache.has(fontFamily)) {\r\n return fontGlyphCache.get(fontFamily)!;\r\n }\r\n \r\n const result = fontLacksEnglishGlyphs(fontFamily);\r\n fontGlyphCache.set(fontFamily, result);\r\n return result;\r\n}\r\n"],"names":["measurementContext","getMeasurementContext","canvas","createCanvasElementFor","width","height","getContext","measureGrapheme","grapheme","options","ctx","cached","measurementCache","get","context","applyFontStyle","metrics","measureText","fontMetrics","getFontMetrics","measurement","lineHeight","ascent","descent","baseline","set","measureGraphemeWithKerning","previousGrapheme","individual","kernedWidth","kerningPair","cachedKerning","kerningCache","_metrics$fontBounding","_metrics$fontBounding2","_metrics$actualBoundi","_metrics$actualBoundi2","cacheKey","getFontDeclaration","fontMetricsCache","sample","fontFamily","document","fonts","check","e","testChars","char","script","font","fallbackWidth","test","Math","abs","getRepresentativeCharacter","fontSize","fontBoundingBoxAscent","fontBoundingBoxDescent","result","actualBoundingBoxAscent","actualBoundingBoxDescent","fontDeclaration","letterSpacing","direction","textBaseline","fontStyle","fontWeight","includes","LRUCache","constructor","maxSize","arguments","length","undefined","_defineProperty","this","Map","key","entry","cache","timestamp","Date","now","hits","value","misses","size","oldestKey","findOldestKey","delete","oldestTime","Infinity","entries","clear","getStats","total","hitRate","MeasurementCache","getCacheKey","pair","kerning","clearAllCaches","fontLacksEnglishGlyphs","lowerFontFamily","toLowerCase","some","englishChars","targetWidths","map","fallbackWidths","identicalCount","i","addEventListener","fontGlyphCache","fontLacksEnglishGlyphsCached","has"],"mappings":"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"}
|
|
@@ -94,6 +94,97 @@ function measureGraphemeWithKerning(grapheme, previousGrapheme, options, ctx) {
|
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Get a representative character for font metrics measurement
|
|
99
|
+
* Uses canvas to test which scripts the font actually supports
|
|
100
|
+
*/
|
|
101
|
+
function getRepresentativeCharacter(fontFamily) {
|
|
102
|
+
const context = getMeasurementContext();
|
|
103
|
+
|
|
104
|
+
// Wait for font to be ready if possible
|
|
105
|
+
if (typeof document !== 'undefined' && 'fonts' in document) {
|
|
106
|
+
try {
|
|
107
|
+
// Check if font is ready, if not, use fallback immediately
|
|
108
|
+
if (!document.fonts.check(`16px ${fontFamily}`)) {
|
|
109
|
+
return 'M'; // Use safe fallback while font loads
|
|
110
|
+
}
|
|
111
|
+
} catch (e) {
|
|
112
|
+
// Font check failed, use fallback
|
|
113
|
+
return 'M';
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Test characters for different scripts
|
|
118
|
+
const testChars = [{
|
|
119
|
+
char: 'م',
|
|
120
|
+
script: 'Arabic'
|
|
121
|
+
},
|
|
122
|
+
// Arabic
|
|
123
|
+
{
|
|
124
|
+
char: 'א',
|
|
125
|
+
script: 'Hebrew'
|
|
126
|
+
},
|
|
127
|
+
// Hebrew
|
|
128
|
+
{
|
|
129
|
+
char: 'अ',
|
|
130
|
+
script: 'Devanagari'
|
|
131
|
+
},
|
|
132
|
+
// Hindi/Sanskrit
|
|
133
|
+
{
|
|
134
|
+
char: 'ا',
|
|
135
|
+
script: 'Urdu'
|
|
136
|
+
},
|
|
137
|
+
// Urdu
|
|
138
|
+
{
|
|
139
|
+
char: 'ک',
|
|
140
|
+
script: 'Persian'
|
|
141
|
+
},
|
|
142
|
+
// Persian
|
|
143
|
+
{
|
|
144
|
+
char: 'த',
|
|
145
|
+
script: 'Tamil'
|
|
146
|
+
},
|
|
147
|
+
// Tamil
|
|
148
|
+
{
|
|
149
|
+
char: 'ก',
|
|
150
|
+
script: 'Thai'
|
|
151
|
+
},
|
|
152
|
+
// Thai
|
|
153
|
+
{
|
|
154
|
+
char: 'М',
|
|
155
|
+
script: 'Cyrillic'
|
|
156
|
+
},
|
|
157
|
+
// Cyrillic
|
|
158
|
+
{
|
|
159
|
+
char: 'Ω',
|
|
160
|
+
script: 'Greek'
|
|
161
|
+
},
|
|
162
|
+
// Greek
|
|
163
|
+
{
|
|
164
|
+
char: 'M',
|
|
165
|
+
script: 'Latin'
|
|
166
|
+
} // Latin (fallback)
|
|
167
|
+
];
|
|
168
|
+
|
|
169
|
+
// Set the font
|
|
170
|
+
context.font = `16px ${fontFamily}`;
|
|
171
|
+
|
|
172
|
+
// Test each character to see which ones render properly
|
|
173
|
+
// Use a more robust width check to avoid false positives
|
|
174
|
+
const fallbackWidth = context.measureText('M').width;
|
|
175
|
+
for (const test of testChars) {
|
|
176
|
+
const metrics = context.measureText(test.char);
|
|
177
|
+
|
|
178
|
+
// Character is valid if it has width and isn't just a fallback glyph
|
|
179
|
+
if (metrics.width > 0 && Math.abs(metrics.width - fallbackWidth) > 0.1) {
|
|
180
|
+
return test.char;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Fallback to Latin 'M'
|
|
185
|
+
return 'M';
|
|
186
|
+
}
|
|
187
|
+
|
|
97
188
|
/**
|
|
98
189
|
* Get font metrics for layout calculations
|
|
99
190
|
*/
|
|
@@ -107,8 +198,9 @@ function getFontMetrics(options) {
|
|
|
107
198
|
const context = getMeasurementContext();
|
|
108
199
|
applyFontStyle(context, options);
|
|
109
200
|
|
|
110
|
-
// Use
|
|
111
|
-
const
|
|
201
|
+
// Use representative character based on font's primary script
|
|
202
|
+
const sample = getRepresentativeCharacter(options.fontFamily);
|
|
203
|
+
const metrics = context.measureText(sample);
|
|
112
204
|
const fontSize = options.fontSize;
|
|
113
205
|
|
|
114
206
|
// Calculate metrics with fallbacks
|
|
@@ -160,7 +252,11 @@ function getFontDeclaration(options) {
|
|
|
160
252
|
} = options;
|
|
161
253
|
|
|
162
254
|
// Normalize font family (add quotes if needed)
|
|
163
|
-
|
|
255
|
+
let normalizedFamily = fontFamily.includes(' ') && !fontFamily.includes('"') && !fontFamily.includes("'") ? `"${fontFamily}"` : fontFamily;
|
|
256
|
+
|
|
257
|
+
// Note: Font fallbacks are handled in the rendering phase only
|
|
258
|
+
// to avoid affecting measurement calculations for text wrapping
|
|
259
|
+
|
|
164
260
|
return `${fontStyle} ${fontWeight} ${fontSize}px ${normalizedFamily}`;
|
|
165
261
|
}
|
|
166
262
|
|
|
@@ -312,5 +408,80 @@ const measurementCache = new MeasurementCache();
|
|
|
312
408
|
const kerningCache = new KerningCache();
|
|
313
409
|
const fontMetricsCache = new FontMetricsCache();
|
|
314
410
|
|
|
315
|
-
|
|
411
|
+
// Set up font loading listener to clear caches when fonts change
|
|
412
|
+
if (typeof document !== 'undefined' && 'fonts' in document) {
|
|
413
|
+
document.fonts.addEventListener('loadingdone', () => {
|
|
414
|
+
// Clear all caches when fonts finish loading
|
|
415
|
+
clearAllCaches();
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
/**
|
|
420
|
+
* Clear all measurement caches
|
|
421
|
+
*/
|
|
422
|
+
function clearAllCaches() {
|
|
423
|
+
measurementCache.clear();
|
|
424
|
+
kerningCache.clear();
|
|
425
|
+
fontMetricsCache.clear();
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Detect if a font lacks English glyph support
|
|
430
|
+
* These fonts should use browser-native measurement instead of Fabric's character-by-character measurement
|
|
431
|
+
*/
|
|
432
|
+
function fontLacksEnglishGlyphs(fontFamily) {
|
|
433
|
+
if (typeof document === 'undefined') return false;
|
|
434
|
+
|
|
435
|
+
// Known fonts that lack English glyphs
|
|
436
|
+
const knownNonEnglishFonts = ['stv', 'arabic', 'naskh', 'thuluth', 'kufi', 'diwani', 'nastaliq', 'kufic', 'hijazi', 'madinah', 'makkah'];
|
|
437
|
+
const lowerFontFamily = fontFamily.toLowerCase();
|
|
438
|
+
|
|
439
|
+
// Check known list first
|
|
440
|
+
if (knownNonEnglishFonts.some(font => lowerFontFamily.includes(font))) {
|
|
441
|
+
return true;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Dynamic glyph support detection
|
|
445
|
+
const context = getMeasurementContext();
|
|
446
|
+
context.font = `16px ${fontFamily}`;
|
|
447
|
+
|
|
448
|
+
// Test English characters
|
|
449
|
+
const englishChars = ['A', 'B', 'C', 'a', 'b', 'c', 'M', 'W'];
|
|
450
|
+
const fallbackFont = 'Arial, sans-serif';
|
|
451
|
+
|
|
452
|
+
// Measure with target font
|
|
453
|
+
const targetWidths = englishChars.map(char => context.measureText(char).width);
|
|
454
|
+
|
|
455
|
+
// Measure with fallback font
|
|
456
|
+
context.font = `16px ${fallbackFont}`;
|
|
457
|
+
const fallbackWidths = englishChars.map(char => context.measureText(char).width);
|
|
458
|
+
|
|
459
|
+
// If most measurements are identical, the font likely doesn't have English glyphs
|
|
460
|
+
let identicalCount = 0;
|
|
461
|
+
for (let i = 0; i < englishChars.length; i++) {
|
|
462
|
+
if (Math.abs(targetWidths[i] - fallbackWidths[i]) < 0.5) {
|
|
463
|
+
identicalCount++;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
const lacksSupportThreshold = englishChars.length * 0.7; // 70% identical = lacks support
|
|
467
|
+
const lacksSupport = identicalCount >= lacksSupportThreshold;
|
|
468
|
+
return lacksSupport;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Cache for font glyph detection results
|
|
472
|
+
const fontGlyphCache = new Map();
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Cached version of font glyph detection
|
|
476
|
+
*/
|
|
477
|
+
function fontLacksEnglishGlyphsCached(fontFamily) {
|
|
478
|
+
if (fontGlyphCache.has(fontFamily)) {
|
|
479
|
+
return fontGlyphCache.get(fontFamily);
|
|
480
|
+
}
|
|
481
|
+
const result = fontLacksEnglishGlyphs(fontFamily);
|
|
482
|
+
fontGlyphCache.set(fontFamily, result);
|
|
483
|
+
return result;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
export { MeasurementCache, clearAllCaches, fontLacksEnglishGlyphs, fontLacksEnglishGlyphsCached, fontMetricsCache, getFontMetrics, kerningCache, measureGrapheme, measureGraphemeWithKerning, measurementCache };
|
|
316
487
|
//# sourceMappingURL=measure.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"measure.mjs","sources":["../../../src/text/measure.ts"],"sourcesContent":["/**\r\n * Advanced Text Measurement System\r\n * \r\n * Provides precise text measurement with caching, font metrics,\r\n * and DPI awareness for optimal performance and accuracy.\r\n */\r\n\r\nimport { createCanvasElementFor } from '../util/misc/dom';\r\n\r\nexport interface MeasurementOptions {\r\n fontFamily: string;\r\n fontSize: number;\r\n fontStyle: string;\r\n fontWeight: string | number;\r\n letterSpacing?: number;\r\n direction?: 'ltr' | 'rtl';\r\n}\r\n\r\nexport interface GraphemeMeasurement {\r\n width: number;\r\n height: number;\r\n ascent: number;\r\n descent: number;\r\n baseline: number;\r\n}\r\n\r\nexport interface KerningMeasurement extends GraphemeMeasurement {\r\n kernedWidth: number; // width accounting for kerning with previous char\r\n}\r\n\r\nexport interface FontMetrics {\r\n ascent: number;\r\n descent: number;\r\n lineHeight: number;\r\n baseline: string;\r\n fontBoundingBoxAscent?: number;\r\n fontBoundingBoxDescent?: number;\r\n actualBoundingBoxAscent?: number;\r\n actualBoundingBoxDescent?: number;\r\n}\r\n\r\n// Global measurement context - reused for performance\r\nlet measurementContext: CanvasRenderingContext2D | null = null;\r\n\r\n/**\r\n * Get or create the shared measurement context\r\n */\r\nfunction getMeasurementContext(): CanvasRenderingContext2D {\r\n if (!measurementContext) {\r\n const canvas = createCanvasElementFor({\r\n width: 0,\r\n height: 0,\r\n });\r\n measurementContext = canvas.getContext('2d')!;\r\n }\r\n return measurementContext;\r\n}\r\n\r\n/**\r\n * Measure a single grapheme\r\n */\r\nexport function measureGrapheme(\r\n grapheme: string,\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D\r\n): GraphemeMeasurement {\r\n // Check cache first\r\n const cached = measurementCache.get(grapheme, options);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n // Use provided context or get global one\r\n const context = ctx || getMeasurementContext();\r\n \r\n // Set font properties\r\n applyFontStyle(context, options);\r\n \r\n // Measure the grapheme\r\n const metrics = context.measureText(grapheme);\r\n const fontMetrics = getFontMetrics(options);\r\n \r\n // Calculate comprehensive measurements\r\n const measurement: GraphemeMeasurement = {\r\n width: metrics.width,\r\n height: fontMetrics.lineHeight,\r\n ascent: fontMetrics.ascent,\r\n descent: fontMetrics.descent,\r\n baseline: fontMetrics.ascent,\r\n };\r\n \r\n // Cache the result\r\n measurementCache.set(grapheme, options, measurement);\r\n \r\n return measurement;\r\n}\r\n\r\n/**\r\n * Measure a grapheme with kerning relative to previous character\r\n */\r\nexport function measureGraphemeWithKerning(\r\n grapheme: string,\r\n previousGrapheme: string | undefined,\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D\r\n): KerningMeasurement {\r\n // Get individual measurement\r\n const individual = measureGrapheme(grapheme, options, ctx);\r\n \r\n // If no previous character, kerning width equals regular width\r\n if (!previousGrapheme) {\r\n return {\r\n ...individual,\r\n kernedWidth: individual.width,\r\n };\r\n }\r\n \r\n // Check kerning cache\r\n const kerningPair = `${previousGrapheme}${grapheme}`;\r\n const cachedKerning = kerningCache.get(kerningPair, options);\r\n if (cachedKerning) {\r\n return {\r\n ...individual,\r\n kernedWidth: cachedKerning,\r\n };\r\n }\r\n \r\n // Use provided context or get global one\r\n const context = ctx || getMeasurementContext();\r\n applyFontStyle(context, options);\r\n \r\n // Measure the pair\r\n const pairWidth = context.measureText(previousGrapheme + grapheme).width;\r\n const previousWidth = measureGrapheme(previousGrapheme, options, context).width;\r\n const kernedWidth = pairWidth - previousWidth;\r\n \r\n // Cache kerning result\r\n kerningCache.set(kerningPair, options, kernedWidth);\r\n \r\n return {\r\n ...individual,\r\n kernedWidth,\r\n };\r\n}\r\n\r\n/**\r\n * Get font metrics for layout calculations\r\n */\r\nexport function getFontMetrics(options: MeasurementOptions): FontMetrics {\r\n const cacheKey = getFontDeclaration(options);\r\n const cached = fontMetricsCache.get(cacheKey);\r\n if (cached) {\r\n return cached;\r\n }\r\n \r\n const context = getMeasurementContext();\r\n applyFontStyle(context, options);\r\n \r\n // Use 'M' as sample character for metrics\r\n const metrics = context.measureText('M');\r\n const fontSize = options.fontSize;\r\n \r\n // Calculate metrics with fallbacks\r\n const fontBoundingBoxAscent = metrics.fontBoundingBoxAscent ?? fontSize * 0.91;\r\n const fontBoundingBoxDescent = metrics.fontBoundingBoxDescent ?? fontSize * 0.21;\r\n const actualBoundingBoxAscent = metrics.actualBoundingBoxAscent ?? fontSize * 0.716;\r\n const actualBoundingBoxDescent = metrics.actualBoundingBoxDescent ?? 0;\r\n \r\n const result: FontMetrics = {\r\n ascent: fontBoundingBoxAscent,\r\n descent: fontBoundingBoxDescent,\r\n lineHeight: fontSize,\r\n baseline: 'alphabetic',\r\n fontBoundingBoxAscent,\r\n fontBoundingBoxDescent,\r\n actualBoundingBoxAscent,\r\n actualBoundingBoxDescent,\r\n };\r\n \r\n fontMetricsCache.set(cacheKey, result);\r\n return result;\r\n}\r\n\r\n/**\r\n * Apply font styling to canvas context\r\n */\r\nfunction applyFontStyle(ctx: CanvasRenderingContext2D, options: MeasurementOptions): void {\r\n const fontDeclaration = getFontDeclaration(options);\r\n ctx.font = fontDeclaration;\r\n \r\n if (options.letterSpacing) {\r\n // Modern browsers support letterSpacing\r\n if ('letterSpacing' in ctx) {\r\n (ctx as any).letterSpacing = `${options.letterSpacing}px`;\r\n }\r\n }\r\n \r\n if (options.direction) {\r\n ctx.direction = options.direction;\r\n }\r\n \r\n ctx.textBaseline = 'alphabetic';\r\n}\r\n\r\n/**\r\n * Generate font declaration string\r\n */\r\nfunction getFontDeclaration(options: MeasurementOptions): string {\r\n const { fontStyle, fontWeight, fontSize, fontFamily } = options;\r\n \r\n // Normalize font family (add quotes if needed)\r\n const normalizedFamily = fontFamily.includes(' ') && \r\n !fontFamily.includes('\"') && \r\n !fontFamily.includes(\"'\")\r\n ? `\"${fontFamily}\"`\r\n : fontFamily;\r\n \r\n return `${fontStyle} ${fontWeight} ${fontSize}px ${normalizedFamily}`;\r\n}\r\n\r\n/**\r\n * LRU Cache implementation for measurements\r\n */\r\nclass LRUCache<T> {\r\n private cache = new Map<string, { value: T; timestamp: number }>();\r\n private maxSize: number;\r\n private hits = 0;\r\n private misses = 0;\r\n\r\n constructor(maxSize = 1000) {\r\n this.maxSize = maxSize;\r\n }\r\n\r\n get(key: string): T | undefined {\r\n const entry = this.cache.get(key);\r\n if (entry) {\r\n // Update timestamp for LRU\r\n entry.timestamp = Date.now();\r\n this.hits++;\r\n return entry.value;\r\n }\r\n this.misses++;\r\n return undefined;\r\n }\r\n\r\n set(key: string, value: T): void {\r\n // Remove oldest entries if at capacity\r\n if (this.cache.size >= this.maxSize) {\r\n const oldestKey = this.findOldestKey();\r\n if (oldestKey) {\r\n this.cache.delete(oldestKey);\r\n }\r\n }\r\n\r\n this.cache.set(key, {\r\n value,\r\n timestamp: Date.now(),\r\n });\r\n }\r\n\r\n private findOldestKey(): string | undefined {\r\n let oldestKey: string | undefined;\r\n let oldestTime = Infinity;\r\n\r\n for (const [key, entry] of this.cache.entries()) {\r\n if (entry.timestamp < oldestTime) {\r\n oldestTime = entry.timestamp;\r\n oldestKey = key;\r\n }\r\n }\r\n\r\n return oldestKey;\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n this.hits = 0;\r\n this.misses = 0;\r\n }\r\n\r\n getStats(): { size: number; hitRate: number; hits: number; misses: number } {\r\n const total = this.hits + this.misses;\r\n return {\r\n size: this.cache.size,\r\n hitRate: total > 0 ? this.hits / total : 0,\r\n hits: this.hits,\r\n misses: this.misses,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Advanced measurement cache with font-aware keys\r\n */\r\nexport class MeasurementCache {\r\n private cache = new LRUCache<GraphemeMeasurement>(1000);\r\n\r\n getCacheKey(grapheme: string, options: MeasurementOptions): string {\r\n const fontDecl = getFontDeclaration(options);\r\n const letterSpacing = options.letterSpacing || 0;\r\n return `${fontDecl}|${grapheme}|${letterSpacing}`;\r\n }\r\n\r\n get(grapheme: string, options: MeasurementOptions): GraphemeMeasurement | undefined {\r\n const key = this.getCacheKey(grapheme, options);\r\n return this.cache.get(key);\r\n }\r\n\r\n set(grapheme: string, options: MeasurementOptions, measurement: GraphemeMeasurement): void {\r\n const key = this.getCacheKey(grapheme, options);\r\n this.cache.set(key, measurement);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return this.cache.getStats();\r\n }\r\n}\r\n\r\n/**\r\n * Kerning cache for character pairs\r\n */\r\nclass KerningCache {\r\n private cache = new LRUCache<number>(5000); // More entries for pairs\r\n\r\n getCacheKey(pair: string, options: MeasurementOptions): string {\r\n const fontDecl = getFontDeclaration(options);\r\n return `${fontDecl}|${pair}`;\r\n }\r\n\r\n get(pair: string, options: MeasurementOptions): number | undefined {\r\n const key = this.getCacheKey(pair, options);\r\n return this.cache.get(key);\r\n }\r\n\r\n set(pair: string, options: MeasurementOptions, kerning: number): void {\r\n const key = this.getCacheKey(pair, options);\r\n this.cache.set(key, kerning);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return this.cache.getStats();\r\n }\r\n}\r\n\r\n/**\r\n * Font metrics cache\r\n */\r\nclass FontMetricsCache {\r\n private cache = new Map<string, FontMetrics>();\r\n\r\n get(fontDeclaration: string): FontMetrics | undefined {\r\n return this.cache.get(fontDeclaration);\r\n }\r\n\r\n set(fontDeclaration: string, metrics: FontMetrics): void {\r\n this.cache.set(fontDeclaration, metrics);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return {\r\n size: this.cache.size,\r\n };\r\n }\r\n}\r\n\r\n// Global cache instances\r\nexport const measurementCache = new MeasurementCache();\r\nexport const kerningCache = new KerningCache();\r\nexport const fontMetricsCache = new FontMetricsCache();\r\n\r\n/**\r\n * Clear all measurement caches\r\n */\r\nexport function clearAllCaches(): void {\r\n measurementCache.clear();\r\n kerningCache.clear();\r\n fontMetricsCache.clear();\r\n}\r\n\r\n/**\r\n * Get combined cache statistics\r\n */\r\nexport function getCacheStats() {\r\n return {\r\n measurement: measurementCache.getStats(),\r\n kerning: kerningCache.getStats(),\r\n fontMetrics: fontMetricsCache.getStats(),\r\n };\r\n}\r\n\r\n/**\r\n * Batch measure multiple graphemes efficiently\r\n */\r\nexport function batchMeasureGraphemes(\r\n graphemes: string[],\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D\r\n): GraphemeMeasurement[] {\r\n const context = ctx || getMeasurementContext();\r\n applyFontStyle(context, options);\r\n \r\n // Separate cached and uncached measurements\r\n const results: GraphemeMeasurement[] = new Array(graphemes.length);\r\n const uncachedIndices: number[] = [];\r\n \r\n // Check cache for all graphemes\r\n graphemes.forEach((grapheme, index) => {\r\n const cached = measurementCache.get(grapheme, options);\r\n if (cached) {\r\n results[index] = cached;\r\n } else {\r\n uncachedIndices.push(index);\r\n }\r\n });\r\n \r\n // Measure uncached graphemes\r\n const fontMetrics = getFontMetrics(options);\r\n uncachedIndices.forEach(index => {\r\n const grapheme = graphemes[index];\r\n const metrics = context.measureText(grapheme);\r\n \r\n const measurement: GraphemeMeasurement = {\r\n width: metrics.width,\r\n height: fontMetrics.lineHeight,\r\n ascent: fontMetrics.ascent,\r\n descent: fontMetrics.descent,\r\n baseline: fontMetrics.ascent,\r\n };\r\n \r\n measurementCache.set(grapheme, options, measurement);\r\n results[index] = measurement;\r\n });\r\n \r\n return results;\r\n}\r\n\r\n/**\r\n * Estimate text width without full layout (for performance)\r\n */\r\nexport function estimateTextWidth(\r\n text: string,\r\n options: MeasurementOptions\r\n): number {\r\n // Use average character width for estimation\r\n const avgChar = 'n'; // Representative character\r\n const avgMeasurement = measureGrapheme(avgChar, options);\r\n const letterSpacing = options.letterSpacing || 0;\r\n \r\n return text.length * (avgMeasurement.width + letterSpacing);\r\n}\r\n\r\n/**\r\n * Check if font is loaded and ready for measurement\r\n */\r\nexport function isFontReady(fontFamily: string): boolean {\r\n if (typeof document === 'undefined') return true;\r\n \r\n if ('fonts' in document) {\r\n return document.fonts.check(`16px ${fontFamily}`);\r\n }\r\n \r\n // Fallback - assume font is ready\r\n return true;\r\n}"],"names":["measurementContext","getMeasurementContext","canvas","createCanvasElementFor","width","height","getContext","measureGrapheme","grapheme","options","ctx","cached","measurementCache","get","context","applyFontStyle","metrics","measureText","fontMetrics","getFontMetrics","measurement","lineHeight","ascent","descent","baseline","set","measureGraphemeWithKerning","previousGrapheme","individual","kernedWidth","kerningPair","cachedKerning","kerningCache","pairWidth","previousWidth","_metrics$fontBounding","_metrics$fontBounding2","_metrics$actualBoundi","_metrics$actualBoundi2","cacheKey","getFontDeclaration","fontMetricsCache","fontSize","fontBoundingBoxAscent","fontBoundingBoxDescent","actualBoundingBoxAscent","actualBoundingBoxDescent","result","fontDeclaration","font","letterSpacing","direction","textBaseline","fontStyle","fontWeight","fontFamily","normalizedFamily","includes","LRUCache","constructor","maxSize","arguments","length","undefined","_defineProperty","Map","key","entry","cache","timestamp","Date","now","hits","value","misses","size","oldestKey","findOldestKey","delete","oldestTime","Infinity","entries","clear","getStats","total","hitRate","MeasurementCache","getCacheKey","fontDecl","KerningCache","pair","kerning","FontMetricsCache"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"measure.mjs","sources":["../../../src/text/measure.ts"],"sourcesContent":["/**\r\n * Advanced Text Measurement System\r\n *\r\n * Provides precise text measurement with caching, font metrics,\r\n * and DPI awareness for optimal performance and accuracy.\r\n */\r\n\r\nimport { createCanvasElementFor } from '../util/misc/dom';\r\n\r\nexport interface MeasurementOptions {\r\n fontFamily: string;\r\n fontSize: number;\r\n fontStyle: string;\r\n fontWeight: string | number;\r\n letterSpacing?: number;\r\n direction?: 'ltr' | 'rtl';\r\n}\r\n\r\nexport interface GraphemeMeasurement {\r\n width: number;\r\n height: number;\r\n ascent: number;\r\n descent: number;\r\n baseline: number;\r\n}\r\n\r\nexport interface KerningMeasurement extends GraphemeMeasurement {\r\n kernedWidth: number; // width accounting for kerning with previous char\r\n}\r\n\r\nexport interface FontMetrics {\r\n ascent: number;\r\n descent: number;\r\n lineHeight: number;\r\n baseline: string;\r\n fontBoundingBoxAscent?: number;\r\n fontBoundingBoxDescent?: number;\r\n actualBoundingBoxAscent?: number;\r\n actualBoundingBoxDescent?: number;\r\n}\r\n\r\n// Global measurement context - reused for performance\r\nlet measurementContext: CanvasRenderingContext2D | null = null;\r\n\r\n/**\r\n * Get or create the shared measurement context\r\n */\r\nfunction getMeasurementContext(): CanvasRenderingContext2D {\r\n if (!measurementContext) {\r\n const canvas = createCanvasElementFor({\r\n width: 0,\r\n height: 0,\r\n });\r\n measurementContext = canvas.getContext('2d')!;\r\n }\r\n return measurementContext;\r\n}\r\n\r\n/**\r\n * Measure a single grapheme\r\n */\r\nexport function measureGrapheme(\r\n grapheme: string,\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D,\r\n): GraphemeMeasurement {\r\n // Check cache first\r\n const cached = measurementCache.get(grapheme, options);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n // Use provided context or get global one\r\n const context = ctx || getMeasurementContext();\r\n\r\n // Set font properties\r\n applyFontStyle(context, options);\r\n\r\n // Measure the grapheme\r\n const metrics = context.measureText(grapheme);\r\n const fontMetrics = getFontMetrics(options);\r\n\r\n // Calculate comprehensive measurements\r\n const measurement: GraphemeMeasurement = {\r\n width: metrics.width,\r\n height: fontMetrics.lineHeight,\r\n ascent: fontMetrics.ascent,\r\n descent: fontMetrics.descent,\r\n baseline: fontMetrics.ascent,\r\n };\r\n\r\n // Cache the result\r\n measurementCache.set(grapheme, options, measurement);\r\n\r\n return measurement;\r\n}\r\n\r\n/**\r\n * Measure a grapheme with kerning relative to previous character\r\n */\r\nexport function measureGraphemeWithKerning(\r\n grapheme: string,\r\n previousGrapheme: string | undefined,\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D,\r\n): KerningMeasurement {\r\n // Get individual measurement\r\n const individual = measureGrapheme(grapheme, options, ctx);\r\n\r\n // If no previous character, kerning width equals regular width\r\n if (!previousGrapheme) {\r\n return {\r\n ...individual,\r\n kernedWidth: individual.width,\r\n };\r\n }\r\n\r\n // Check kerning cache\r\n const kerningPair = `${previousGrapheme}${grapheme}`;\r\n const cachedKerning = kerningCache.get(kerningPair, options);\r\n if (cachedKerning) {\r\n return {\r\n ...individual,\r\n kernedWidth: cachedKerning,\r\n };\r\n }\r\n\r\n // Use provided context or get global one\r\n const context = ctx || getMeasurementContext();\r\n applyFontStyle(context, options);\r\n\r\n // Measure the pair\r\n const pairWidth = context.measureText(previousGrapheme + grapheme).width;\r\n const previousWidth = measureGrapheme(\r\n previousGrapheme,\r\n options,\r\n context,\r\n ).width;\r\n const kernedWidth = pairWidth - previousWidth;\r\n\r\n // Cache kerning result\r\n kerningCache.set(kerningPair, options, kernedWidth);\r\n\r\n return {\r\n ...individual,\r\n kernedWidth,\r\n };\r\n}\r\n\r\n/**\r\n * Get a representative character for font metrics measurement\r\n * Uses canvas to test which scripts the font actually supports\r\n */\r\nfunction getRepresentativeCharacter(fontFamily: string): string {\r\n const context = getMeasurementContext();\r\n \r\n // Wait for font to be ready if possible\r\n if (typeof document !== 'undefined' && 'fonts' in document) {\r\n try {\r\n // Check if font is ready, if not, use fallback immediately\r\n if (!document.fonts.check(`16px ${fontFamily}`)) {\r\n return 'M'; // Use safe fallback while font loads\r\n }\r\n } catch (e) {\r\n // Font check failed, use fallback\r\n return 'M';\r\n }\r\n }\r\n \r\n // Test characters for different scripts\r\n const testChars = [\r\n { char: 'م', script: 'Arabic' }, // Arabic\r\n { char: 'א', script: 'Hebrew' }, // Hebrew \r\n { char: 'अ', script: 'Devanagari' }, // Hindi/Sanskrit\r\n { char: 'ا', script: 'Urdu' }, // Urdu\r\n { char: 'ک', script: 'Persian' }, // Persian\r\n { char: 'த', script: 'Tamil' }, // Tamil\r\n { char: 'ก', script: 'Thai' }, // Thai\r\n { char: 'М', script: 'Cyrillic' }, // Cyrillic\r\n { char: 'Ω', script: 'Greek' }, // Greek\r\n { char: 'M', script: 'Latin' } // Latin (fallback)\r\n ];\r\n \r\n // Set the font\r\n context.font = `16px ${fontFamily}`;\r\n \r\n // Test each character to see which ones render properly\r\n // Use a more robust width check to avoid false positives\r\n const fallbackWidth = context.measureText('M').width;\r\n \r\n for (const test of testChars) {\r\n const metrics = context.measureText(test.char);\r\n \r\n // Character is valid if it has width and isn't just a fallback glyph\r\n if (metrics.width > 0 && Math.abs(metrics.width - fallbackWidth) > 0.1) {\r\n return test.char;\r\n }\r\n }\r\n \r\n // Fallback to Latin 'M'\r\n return 'M';\r\n}\r\n\r\n/**\r\n * Get font metrics for layout calculations\r\n */\r\nexport function getFontMetrics(options: MeasurementOptions): FontMetrics {\r\n const cacheKey = getFontDeclaration(options);\r\n const cached = fontMetricsCache.get(cacheKey);\r\n if (cached) {\r\n return cached;\r\n }\r\n\r\n const context = getMeasurementContext();\r\n applyFontStyle(context, options);\r\n\r\n // Use representative character based on font's primary script\r\n const sample = getRepresentativeCharacter(options.fontFamily);\r\n const metrics = context.measureText(sample);\r\n const fontSize = options.fontSize;\r\n\r\n // Calculate metrics with fallbacks\r\n const fontBoundingBoxAscent =\r\n metrics.fontBoundingBoxAscent ?? fontSize * 0.91;\r\n const fontBoundingBoxDescent =\r\n metrics.fontBoundingBoxDescent ?? fontSize * 0.21;\r\n const actualBoundingBoxAscent =\r\n metrics.actualBoundingBoxAscent ?? fontSize * 0.716;\r\n const actualBoundingBoxDescent = metrics.actualBoundingBoxDescent ?? 0;\r\n\r\n const result: FontMetrics = {\r\n ascent: fontBoundingBoxAscent,\r\n descent: fontBoundingBoxDescent,\r\n lineHeight: fontSize,\r\n baseline: 'alphabetic',\r\n fontBoundingBoxAscent,\r\n fontBoundingBoxDescent,\r\n actualBoundingBoxAscent,\r\n actualBoundingBoxDescent,\r\n };\r\n\r\n fontMetricsCache.set(cacheKey, result);\r\n return result;\r\n}\r\n\r\n/**\r\n * Apply font styling to canvas context\r\n */\r\nfunction applyFontStyle(\r\n ctx: CanvasRenderingContext2D,\r\n options: MeasurementOptions,\r\n): void {\r\n const fontDeclaration = getFontDeclaration(options);\r\n ctx.font = fontDeclaration;\r\n\r\n if (options.letterSpacing) {\r\n // Modern browsers support letterSpacing\r\n if ('letterSpacing' in ctx) {\r\n (ctx as any).letterSpacing = `${options.letterSpacing}px`;\r\n }\r\n }\r\n\r\n if (options.direction) {\r\n ctx.direction = options.direction;\r\n }\r\n\r\n ctx.textBaseline = 'alphabetic';\r\n}\r\n\r\n/**\r\n * Generate font declaration string\r\n */\r\nfunction getFontDeclaration(options: MeasurementOptions): string {\r\n const { fontStyle, fontWeight, fontSize, fontFamily } = options;\r\n\r\n // Normalize font family (add quotes if needed)\r\n let normalizedFamily =\r\n fontFamily.includes(' ') &&\r\n !fontFamily.includes('\"') &&\r\n !fontFamily.includes(\"'\")\r\n ? `\"${fontFamily}\"`\r\n : fontFamily;\r\n\r\n // Note: Font fallbacks are handled in the rendering phase only\r\n // to avoid affecting measurement calculations for text wrapping\r\n\r\n return `${fontStyle} ${fontWeight} ${fontSize}px ${normalizedFamily}`;\r\n}\r\n\r\n/**\r\n * LRU Cache implementation for measurements\r\n */\r\nclass LRUCache<T> {\r\n private cache = new Map<string, { value: T; timestamp: number }>();\r\n private maxSize: number;\r\n private hits = 0;\r\n private misses = 0;\r\n\r\n constructor(maxSize = 1000) {\r\n this.maxSize = maxSize;\r\n }\r\n\r\n get(key: string): T | undefined {\r\n const entry = this.cache.get(key);\r\n if (entry) {\r\n // Update timestamp for LRU\r\n entry.timestamp = Date.now();\r\n this.hits++;\r\n return entry.value;\r\n }\r\n this.misses++;\r\n return undefined;\r\n }\r\n\r\n set(key: string, value: T): void {\r\n // Remove oldest entries if at capacity\r\n if (this.cache.size >= this.maxSize) {\r\n const oldestKey = this.findOldestKey();\r\n if (oldestKey) {\r\n this.cache.delete(oldestKey);\r\n }\r\n }\r\n\r\n this.cache.set(key, {\r\n value,\r\n timestamp: Date.now(),\r\n });\r\n }\r\n\r\n private findOldestKey(): string | undefined {\r\n let oldestKey: string | undefined;\r\n let oldestTime = Infinity;\r\n\r\n for (const [key, entry] of this.cache.entries()) {\r\n if (entry.timestamp < oldestTime) {\r\n oldestTime = entry.timestamp;\r\n oldestKey = key;\r\n }\r\n }\r\n\r\n return oldestKey;\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n this.hits = 0;\r\n this.misses = 0;\r\n }\r\n\r\n getStats(): { size: number; hitRate: number; hits: number; misses: number } {\r\n const total = this.hits + this.misses;\r\n return {\r\n size: this.cache.size,\r\n hitRate: total > 0 ? this.hits / total : 0,\r\n hits: this.hits,\r\n misses: this.misses,\r\n };\r\n }\r\n}\r\n\r\n/**\r\n * Advanced measurement cache with font-aware keys\r\n */\r\nexport class MeasurementCache {\r\n private cache = new LRUCache<GraphemeMeasurement>(1000);\r\n\r\n getCacheKey(grapheme: string, options: MeasurementOptions): string {\r\n const fontDecl = getFontDeclaration(options);\r\n const letterSpacing = options.letterSpacing || 0;\r\n return `${fontDecl}|${grapheme}|${letterSpacing}`;\r\n }\r\n\r\n get(\r\n grapheme: string,\r\n options: MeasurementOptions,\r\n ): GraphemeMeasurement | undefined {\r\n const key = this.getCacheKey(grapheme, options);\r\n return this.cache.get(key);\r\n }\r\n\r\n set(\r\n grapheme: string,\r\n options: MeasurementOptions,\r\n measurement: GraphemeMeasurement,\r\n ): void {\r\n const key = this.getCacheKey(grapheme, options);\r\n this.cache.set(key, measurement);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return this.cache.getStats();\r\n }\r\n}\r\n\r\n/**\r\n * Kerning cache for character pairs\r\n */\r\nclass KerningCache {\r\n private cache = new LRUCache<number>(5000); // More entries for pairs\r\n\r\n getCacheKey(pair: string, options: MeasurementOptions): string {\r\n const fontDecl = getFontDeclaration(options);\r\n return `${fontDecl}|${pair}`;\r\n }\r\n\r\n get(pair: string, options: MeasurementOptions): number | undefined {\r\n const key = this.getCacheKey(pair, options);\r\n return this.cache.get(key);\r\n }\r\n\r\n set(pair: string, options: MeasurementOptions, kerning: number): void {\r\n const key = this.getCacheKey(pair, options);\r\n this.cache.set(key, kerning);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return this.cache.getStats();\r\n }\r\n}\r\n\r\n/**\r\n * Font metrics cache\r\n */\r\nclass FontMetricsCache {\r\n private cache = new Map<string, FontMetrics>();\r\n\r\n get(fontDeclaration: string): FontMetrics | undefined {\r\n return this.cache.get(fontDeclaration);\r\n }\r\n\r\n set(fontDeclaration: string, metrics: FontMetrics): void {\r\n this.cache.set(fontDeclaration, metrics);\r\n }\r\n\r\n clear(): void {\r\n this.cache.clear();\r\n }\r\n\r\n getStats() {\r\n return {\r\n size: this.cache.size,\r\n };\r\n }\r\n}\r\n\r\n// Global cache instances\r\nexport const measurementCache = new MeasurementCache();\r\nexport const kerningCache = new KerningCache();\r\nexport const fontMetricsCache = new FontMetricsCache();\r\n\r\n// Set up font loading listener to clear caches when fonts change\r\nif (typeof document !== 'undefined' && 'fonts' in document) {\r\n document.fonts.addEventListener('loadingdone', () => {\r\n // Clear all caches when fonts finish loading\r\n clearAllCaches();\r\n });\r\n}\r\n\r\n/**\r\n * Clear all measurement caches\r\n */\r\nexport function clearAllCaches(): void {\r\n measurementCache.clear();\r\n kerningCache.clear();\r\n fontMetricsCache.clear();\r\n}\r\n\r\n/**\r\n * Get combined cache statistics\r\n */\r\nexport function getCacheStats() {\r\n return {\r\n measurement: measurementCache.getStats(),\r\n kerning: kerningCache.getStats(),\r\n fontMetrics: fontMetricsCache.getStats(),\r\n };\r\n}\r\n\r\n/**\r\n * Batch measure multiple graphemes efficiently\r\n */\r\nexport function batchMeasureGraphemes(\r\n graphemes: string[],\r\n options: MeasurementOptions,\r\n ctx?: CanvasRenderingContext2D,\r\n): GraphemeMeasurement[] {\r\n const context = ctx || getMeasurementContext();\r\n applyFontStyle(context, options);\r\n\r\n // Separate cached and uncached measurements\r\n const results: GraphemeMeasurement[] = new Array(graphemes.length);\r\n const uncachedIndices: number[] = [];\r\n\r\n // Check cache for all graphemes\r\n graphemes.forEach((grapheme, index) => {\r\n const cached = measurementCache.get(grapheme, options);\r\n if (cached) {\r\n results[index] = cached;\r\n } else {\r\n uncachedIndices.push(index);\r\n }\r\n });\r\n\r\n // Measure uncached graphemes\r\n const fontMetrics = getFontMetrics(options);\r\n uncachedIndices.forEach((index) => {\r\n const grapheme = graphemes[index];\r\n const metrics = context.measureText(grapheme);\r\n\r\n const measurement: GraphemeMeasurement = {\r\n width: metrics.width,\r\n height: fontMetrics.lineHeight,\r\n ascent: fontMetrics.ascent,\r\n descent: fontMetrics.descent,\r\n baseline: fontMetrics.ascent,\r\n };\r\n\r\n measurementCache.set(grapheme, options, measurement);\r\n results[index] = measurement;\r\n });\r\n\r\n return results;\r\n}\r\n\r\n/**\r\n * Estimate text width without full layout (for performance)\r\n */\r\nexport function estimateTextWidth(\r\n text: string,\r\n options: MeasurementOptions,\r\n): number {\r\n // Use average character width for estimation\r\n const avgChar = 'n'; // Representative character\r\n const avgMeasurement = measureGrapheme(avgChar, options);\r\n const letterSpacing = options.letterSpacing || 0;\r\n\r\n return text.length * (avgMeasurement.width + letterSpacing);\r\n}\r\n\r\n/**\r\n * Check if font is loaded and ready for measurement\r\n */\r\nexport function isFontReady(fontFamily: string): boolean {\r\n if (typeof document === 'undefined') return true;\r\n\r\n if ('fonts' in document) {\r\n return document.fonts.check(`16px ${fontFamily}`);\r\n }\r\n\r\n // Fallback - assume font is ready\r\n return true;\r\n}\r\n\r\n/**\r\n * Detect if a font lacks English glyph support\r\n * These fonts should use browser-native measurement instead of Fabric's character-by-character measurement\r\n */\r\nexport function fontLacksEnglishGlyphs(fontFamily: string): boolean {\r\n if (typeof document === 'undefined') return false;\r\n \r\n // Known fonts that lack English glyphs\r\n const knownNonEnglishFonts = [\r\n 'stv', 'arabic', 'naskh', 'thuluth', 'kufi', 'diwani',\r\n 'nastaliq', 'kufic', 'hijazi', 'madinah', 'makkah'\r\n ];\r\n \r\n const lowerFontFamily = fontFamily.toLowerCase();\r\n \r\n // Check known list first\r\n if (knownNonEnglishFonts.some(font => lowerFontFamily.includes(font))) {\r\n return true;\r\n }\r\n \r\n // Dynamic glyph support detection\r\n const context = getMeasurementContext();\r\n context.font = `16px ${fontFamily}`;\r\n \r\n // Test English characters\r\n const englishChars = ['A', 'B', 'C', 'a', 'b', 'c', 'M', 'W'];\r\n const fallbackFont = 'Arial, sans-serif';\r\n \r\n // Measure with target font\r\n const targetWidths = englishChars.map(char => context.measureText(char).width);\r\n \r\n // Measure with fallback font\r\n context.font = `16px ${fallbackFont}`;\r\n const fallbackWidths = englishChars.map(char => context.measureText(char).width);\r\n \r\n // If most measurements are identical, the font likely doesn't have English glyphs\r\n let identicalCount = 0;\r\n for (let i = 0; i < englishChars.length; i++) {\r\n if (Math.abs(targetWidths[i] - fallbackWidths[i]) < 0.5) {\r\n identicalCount++;\r\n }\r\n }\r\n \r\n const lacksSupportThreshold = englishChars.length * 0.7; // 70% identical = lacks support\r\n const lacksSupport = identicalCount >= lacksSupportThreshold;\r\n \r\n \r\n return lacksSupport;\r\n}\r\n\r\n// Cache for font glyph detection results\r\nconst fontGlyphCache = new Map<string, boolean>();\r\n\r\n/**\r\n * Cached version of font glyph detection\r\n */\r\nexport function fontLacksEnglishGlyphsCached(fontFamily: string): boolean {\r\n if (fontGlyphCache.has(fontFamily)) {\r\n return fontGlyphCache.get(fontFamily)!;\r\n }\r\n \r\n const result = fontLacksEnglishGlyphs(fontFamily);\r\n fontGlyphCache.set(fontFamily, result);\r\n return result;\r\n}\r\n"],"names":["measurementContext","getMeasurementContext","canvas","createCanvasElementFor","width","height","getContext","measureGrapheme","grapheme","options","ctx","cached","measurementCache","get","context","applyFontStyle","metrics","measureText","fontMetrics","getFontMetrics","measurement","lineHeight","ascent","descent","baseline","set","measureGraphemeWithKerning","previousGrapheme","individual","kernedWidth","kerningPair","cachedKerning","kerningCache","pairWidth","previousWidth","getRepresentativeCharacter","fontFamily","document","fonts","check","e","testChars","char","script","font","fallbackWidth","test","Math","abs","_metrics$fontBounding","_metrics$fontBounding2","_metrics$actualBoundi","_metrics$actualBoundi2","cacheKey","getFontDeclaration","fontMetricsCache","sample","fontSize","fontBoundingBoxAscent","fontBoundingBoxDescent","actualBoundingBoxAscent","actualBoundingBoxDescent","result","fontDeclaration","letterSpacing","direction","textBaseline","fontStyle","fontWeight","normalizedFamily","includes","LRUCache","constructor","maxSize","arguments","length","undefined","_defineProperty","Map","key","entry","cache","timestamp","Date","now","hits","value","misses","size","oldestKey","findOldestKey","delete","oldestTime","Infinity","entries","clear","getStats","total","hitRate","MeasurementCache","getCacheKey","fontDecl","KerningCache","pair","kerning","FontMetricsCache","addEventListener","clearAllCaches","fontLacksEnglishGlyphs","knownNonEnglishFonts","lowerFontFamily","toLowerCase","some","englishChars","fallbackFont","targetWidths","map","fallbackWidths","identicalCount","i","lacksSupportThreshold","lacksSupport","fontGlyphCache","fontLacksEnglishGlyphsCached","has"],"mappings":";;;AAyCA;AACA,IAAIA,kBAAmD,GAAG,IAAI;;AAE9D;AACA;AACA;AACA,SAASC,qBAAqBA,GAA6B;EACzD,IAAI,CAACD,kBAAkB,EAAE;IACvB,MAAME,MAAM,GAAGC,sBAAsB,CAAC;AACpCC,MAAAA,KAAK,EAAE,CAAC;AACRC,MAAAA,MAAM,EAAE;AACV,KAAC,CAAC;AACFL,IAAAA,kBAAkB,GAAGE,MAAM,CAACI,UAAU,CAAC,IAAI,CAAE;AAC/C,EAAA;AACA,EAAA,OAAON,kBAAkB;AAC3B;;AAEA;AACA;AACA;AACO,SAASO,eAAeA,CAC7BC,QAAgB,EAChBC,OAA2B,EAC3BC,GAA8B,EACT;AACrB;EACA,MAAMC,MAAM,GAAGC,gBAAgB,CAACC,GAAG,CAACL,QAAQ,EAAEC,OAAO,CAAC;AACtD,EAAA,IAAIE,MAAM,EAAE;AACV,IAAA,OAAOA,MAAM;AACf,EAAA;;AAEA;AACA,EAAA,MAAMG,OAAO,GAAGJ,GAAG,IAAIT,qBAAqB,EAAE;;AAE9C;AACAc,EAAAA,cAAc,CAACD,OAAO,EAAEL,OAAO,CAAC;;AAEhC;AACA,EAAA,MAAMO,OAAO,GAAGF,OAAO,CAACG,WAAW,CAACT,QAAQ,CAAC;AAC7C,EAAA,MAAMU,WAAW,GAAGC,cAAc,CAACV,OAAO,CAAC;;AAE3C;AACA,EAAA,MAAMW,WAAgC,GAAG;IACvChB,KAAK,EAAEY,OAAO,CAACZ,KAAK;IACpBC,MAAM,EAAEa,WAAW,CAACG,UAAU;IAC9BC,MAAM,EAAEJ,WAAW,CAACI,MAAM;IAC1BC,OAAO,EAAEL,WAAW,CAACK,OAAO;IAC5BC,QAAQ,EAAEN,WAAW,CAACI;GACvB;;AAED;EACAV,gBAAgB,CAACa,GAAG,CAACjB,QAAQ,EAAEC,OAAO,EAAEW,WAAW,CAAC;AAEpD,EAAA,OAAOA,WAAW;AACpB;;AAEA;AACA;AACA;AACO,SAASM,0BAA0BA,CACxClB,QAAgB,EAChBmB,gBAAoC,EACpClB,OAA2B,EAC3BC,GAA8B,EACV;AACpB;EACA,MAAMkB,UAAU,GAAGrB,eAAe,CAACC,QAAQ,EAAEC,OAAO,EAAEC,GAAG,CAAC;;AAE1D;EACA,IAAI,CAACiB,gBAAgB,EAAE;IACrB,OAAO;AACL,MAAA,GAAGC,UAAU;MACbC,WAAW,EAAED,UAAU,CAACxB;KACzB;AACH,EAAA;;AAEA;AACA,EAAA,MAAM0B,WAAW,GAAG,CAAA,EAAGH,gBAAgB,CAAA,EAAGnB,QAAQ,CAAA,CAAE;EACpD,MAAMuB,aAAa,GAAGC,YAAY,CAACnB,GAAG,CAACiB,WAAW,EAAErB,OAAO,CAAC;AAC5D,EAAA,IAAIsB,aAAa,EAAE;IACjB,OAAO;AACL,MAAA,GAAGH,UAAU;AACbC,MAAAA,WAAW,EAAEE;KACd;AACH,EAAA;;AAEA;AACA,EAAA,MAAMjB,OAAO,GAAUb,qBAAqB,EAAE;AAC9Cc,EAAAA,cAAc,CAACD,OAAO,EAAEL,OAAO,CAAC;;AAEhC;EACA,MAAMwB,SAAS,GAAGnB,OAAO,CAACG,WAAW,CAACU,gBAAgB,GAAGnB,QAAQ,CAAC,CAACJ,KAAK;EACxE,MAAM8B,aAAa,GAAG3B,eAAe,CACnCoB,gBAAgB,EAChBlB,OAAO,EACPK,OACF,CAAC,CAACV,KAAK;AACP,EAAA,MAAMyB,WAAW,GAAGI,SAAS,GAAGC,aAAa;;AAE7C;EACAF,YAAY,CAACP,GAAG,CAACK,WAAW,EAAErB,OAAO,EAAEoB,WAAW,CAAC;EAEnD,OAAO;AACL,IAAA,GAAGD,UAAU;AACbC,IAAAA;GACD;AACH;;AAEA;AACA;AACA;AACA;AACA,SAASM,0BAA0BA,CAACC,UAAkB,EAAU;AAC9D,EAAA,MAAMtB,OAAO,GAAGb,qBAAqB,EAAE;;AAEvC;EACA,IAAI,OAAOoC,QAAQ,KAAK,WAAW,IAAI,OAAO,IAAIA,QAAQ,EAAE;IAC1D,IAAI;AACF;MACA,IAAI,CAACA,QAAQ,CAACC,KAAK,CAACC,KAAK,CAAC,CAAA,KAAA,EAAQH,UAAU,CAAA,CAAE,CAAC,EAAE;QAC/C,OAAO,GAAG,CAAC;AACb,MAAA;IACF,CAAC,CAAC,OAAOI,CAAC,EAAE;AACV;AACA,MAAA,OAAO,GAAG;AACZ,IAAA;AACF,EAAA;;AAEA;EACA,MAAMC,SAAS,GAAG,CAChB;AAAEC,IAAAA,IAAI,EAAE,GAAG;AAAEC,IAAAA,MAAM,EAAE;GAAU;AAAM;AACrC,EAAA;AAAED,IAAAA,IAAI,EAAE,GAAG;AAAEC,IAAAA,MAAM,EAAE;GAAU;AAAM;AACrC,EAAA;AAAED,IAAAA,IAAI,EAAE,GAAG;AAAEC,IAAAA,MAAM,EAAE;GAAc;AAAE;AACrC,EAAA;AAAED,IAAAA,IAAI,EAAE,GAAG;AAAEC,IAAAA,MAAM,EAAE;GAAQ;AAAQ;AACrC,EAAA;AAAED,IAAAA,IAAI,EAAE,GAAG;AAAEC,IAAAA,MAAM,EAAE;GAAW;AAAK;AACrC,EAAA;AAAED,IAAAA,IAAI,EAAE,GAAG;AAAEC,IAAAA,MAAM,EAAE;GAAS;AAAO;AACrC,EAAA;AAAED,IAAAA,IAAI,EAAE,GAAG;AAAEC,IAAAA,MAAM,EAAE;GAAQ;AAAQ;AACrC,EAAA;AAAED,IAAAA,IAAI,EAAE,GAAG;AAAEC,IAAAA,MAAM,EAAE;GAAY;AAAI;AACrC,EAAA;AAAED,IAAAA,IAAI,EAAE,GAAG;AAAEC,IAAAA,MAAM,EAAE;GAAS;AAAO;AACrC,EAAA;AAAED,IAAAA,IAAI,EAAE,GAAG;AAAEC,IAAAA,MAAM,EAAE;AAAQ,GAAC;GAC/B;;AAED;AACA7B,EAAAA,OAAO,CAAC8B,IAAI,GAAG,CAAA,KAAA,EAAQR,UAAU,CAAA,CAAE;;AAEnC;AACA;EACA,MAAMS,aAAa,GAAG/B,OAAO,CAACG,WAAW,CAAC,GAAG,CAAC,CAACb,KAAK;AAEpD,EAAA,KAAK,MAAM0C,IAAI,IAAIL,SAAS,EAAE;IAC5B,MAAMzB,OAAO,GAAGF,OAAO,CAACG,WAAW,CAAC6B,IAAI,CAACJ,IAAI,CAAC;;AAE9C;AACA,IAAA,IAAI1B,OAAO,CAACZ,KAAK,GAAG,CAAC,IAAI2C,IAAI,CAACC,GAAG,CAAChC,OAAO,CAACZ,KAAK,GAAGyC,aAAa,CAAC,GAAG,GAAG,EAAE;MACtE,OAAOC,IAAI,CAACJ,IAAI;AAClB,IAAA;AACF,EAAA;;AAEA;AACA,EAAA,OAAO,GAAG;AACZ;;AAEA;AACA;AACA;AACO,SAASvB,cAAcA,CAACV,OAA2B,EAAe;AAAA,EAAA,IAAAwC,qBAAA,EAAAC,sBAAA,EAAAC,qBAAA,EAAAC,sBAAA;AACvE,EAAA,MAAMC,QAAQ,GAAGC,kBAAkB,CAAC7C,OAAO,CAAC;AAC5C,EAAA,MAAME,MAAM,GAAG4C,gBAAgB,CAAC1C,GAAG,CAACwC,QAAQ,CAAC;AAC7C,EAAA,IAAI1C,MAAM,EAAE;AACV,IAAA,OAAOA,MAAM;AACf,EAAA;AAEA,EAAA,MAAMG,OAAO,GAAGb,qBAAqB,EAAE;AACvCc,EAAAA,cAAc,CAACD,OAAO,EAAEL,OAAO,CAAC;;AAEhC;AACA,EAAA,MAAM+C,MAAM,GAAGrB,0BAA0B,CAAC1B,OAAO,CAAC2B,UAAU,CAAC;AAC7D,EAAA,MAAMpB,OAAO,GAAGF,OAAO,CAACG,WAAW,CAACuC,MAAM,CAAC;AAC3C,EAAA,MAAMC,QAAQ,GAAGhD,OAAO,CAACgD,QAAQ;;AAEjC;AACA,EAAA,MAAMC,qBAAqB,GAAA,CAAAT,qBAAA,GACzBjC,OAAO,CAAC0C,qBAAqB,MAAA,IAAA,IAAAT,qBAAA,KAAA,MAAA,GAAAA,qBAAA,GAAIQ,QAAQ,GAAG,IAAI;AAClD,EAAA,MAAME,sBAAsB,GAAA,CAAAT,sBAAA,GAC1BlC,OAAO,CAAC2C,sBAAsB,MAAA,IAAA,IAAAT,sBAAA,KAAA,MAAA,GAAAA,sBAAA,GAAIO,QAAQ,GAAG,IAAI;AACnD,EAAA,MAAMG,uBAAuB,GAAA,CAAAT,qBAAA,GAC3BnC,OAAO,CAAC4C,uBAAuB,MAAA,IAAA,IAAAT,qBAAA,KAAA,MAAA,GAAAA,qBAAA,GAAIM,QAAQ,GAAG,KAAK;AACrD,EAAA,MAAMI,wBAAwB,GAAA,CAAAT,sBAAA,GAAGpC,OAAO,CAAC6C,wBAAwB,MAAA,IAAA,IAAAT,sBAAA,KAAA,MAAA,GAAAA,sBAAA,GAAI,CAAC;AAEtE,EAAA,MAAMU,MAAmB,GAAG;AAC1BxC,IAAAA,MAAM,EAAEoC,qBAAqB;AAC7BnC,IAAAA,OAAO,EAAEoC,sBAAsB;AAC/BtC,IAAAA,UAAU,EAAEoC,QAAQ;AACpBjC,IAAAA,QAAQ,EAAE,YAAY;IACtBkC,qBAAqB;IACrBC,sBAAsB;IACtBC,uBAAuB;AACvBC,IAAAA;GACD;AAEDN,EAAAA,gBAAgB,CAAC9B,GAAG,CAAC4B,QAAQ,EAAES,MAAM,CAAC;AACtC,EAAA,OAAOA,MAAM;AACf;;AAEA;AACA;AACA;AACA,SAAS/C,cAAcA,CACrBL,GAA6B,EAC7BD,OAA2B,EACrB;AACN,EAAA,MAAMsD,eAAe,GAAGT,kBAAkB,CAAC7C,OAAO,CAAC;EACnDC,GAAG,CAACkC,IAAI,GAAGmB,eAAe;EAE1B,IAAItD,OAAO,CAACuD,aAAa,EAAE;AACzB;IACA,IAAI,eAAe,IAAItD,GAAG,EAAE;AACzBA,MAAAA,GAAG,CAASsD,aAAa,GAAG,GAAGvD,OAAO,CAACuD,aAAa,CAAA,EAAA,CAAI;AAC3D,IAAA;AACF,EAAA;EAEA,IAAIvD,OAAO,CAACwD,SAAS,EAAE;AACrBvD,IAAAA,GAAG,CAACuD,SAAS,GAAGxD,OAAO,CAACwD,SAAS;AACnC,EAAA;EAEAvD,GAAG,CAACwD,YAAY,GAAG,YAAY;AACjC;;AAEA;AACA;AACA;AACA,SAASZ,kBAAkBA,CAAC7C,OAA2B,EAAU;EAC/D,MAAM;IAAE0D,SAAS;IAAEC,UAAU;IAAEX,QAAQ;AAAErB,IAAAA;AAAW,GAAC,GAAG3B,OAAO;;AAE/D;AACA,EAAA,IAAI4D,gBAAgB,GAClBjC,UAAU,CAACkC,QAAQ,CAAC,GAAG,CAAC,IACxB,CAAClC,UAAU,CAACkC,QAAQ,CAAC,GAAG,CAAC,IACzB,CAAClC,UAAU,CAACkC,QAAQ,CAAC,GAAG,CAAC,GACrB,CAAA,CAAA,EAAIlC,UAAU,CAAA,CAAA,CAAG,GACjBA,UAAU;;AAEhB;AACA;;EAEA,OAAO,CAAA,EAAG+B,SAAS,CAAA,CAAA,EAAIC,UAAU,IAAIX,QAAQ,CAAA,GAAA,EAAMY,gBAAgB,CAAA,CAAE;AACvE;;AAEA;AACA;AACA;AACA,MAAME,QAAQ,CAAI;AAMhBC,EAAAA,WAAWA,GAAiB;AAAA,IAAA,IAAhBC,OAAO,GAAAC,SAAA,CAAAC,MAAA,GAAA,CAAA,IAAAD,SAAA,CAAA,CAAA,CAAA,KAAAE,SAAA,GAAAF,SAAA,CAAA,CAAA,CAAA,GAAG,IAAI;AAAAG,IAAAA,eAAA,CAAA,IAAA,EAAA,OAAA,EALV,IAAIC,GAAG,EAA2C,CAAA;IAAAD,eAAA,CAAA,IAAA,EAAA,SAAA,EAAA,MAAA,CAAA;AAAAA,IAAAA,eAAA,eAEnD,CAAC,CAAA;AAAAA,IAAAA,eAAA,iBACC,CAAC,CAAA;IAGhB,IAAI,CAACJ,OAAO,GAAGA,OAAO;AACxB,EAAA;EAEA5D,GAAGA,CAACkE,GAAW,EAAiB;IAC9B,MAAMC,KAAK,GAAG,IAAI,CAACC,KAAK,CAACpE,GAAG,CAACkE,GAAG,CAAC;AACjC,IAAA,IAAIC,KAAK,EAAE;AACT;AACAA,MAAAA,KAAK,CAACE,SAAS,GAAGC,IAAI,CAACC,GAAG,EAAE;MAC5B,IAAI,CAACC,IAAI,EAAE;MACX,OAAOL,KAAK,CAACM,KAAK;AACpB,IAAA;IACA,IAAI,CAACC,MAAM,EAAE;AACb,IAAA,OAAOX,SAAS;AAClB,EAAA;AAEAnD,EAAAA,GAAGA,CAACsD,GAAW,EAAEO,KAAQ,EAAQ;AAC/B;IACA,IAAI,IAAI,CAACL,KAAK,CAACO,IAAI,IAAI,IAAI,CAACf,OAAO,EAAE;AACnC,MAAA,MAAMgB,SAAS,GAAG,IAAI,CAACC,aAAa,EAAE;AACtC,MAAA,IAAID,SAAS,EAAE;AACb,QAAA,IAAI,CAACR,KAAK,CAACU,MAAM,CAACF,SAAS,CAAC;AAC9B,MAAA;AACF,IAAA;AAEA,IAAA,IAAI,CAACR,KAAK,CAACxD,GAAG,CAACsD,GAAG,EAAE;MAClBO,KAAK;AACLJ,MAAAA,SAAS,EAAEC,IAAI,CAACC,GAAG;AACrB,KAAC,CAAC;AACJ,EAAA;AAEQM,EAAAA,aAAaA,GAAuB;AAC1C,IAAA,IAAID,SAA6B;IACjC,IAAIG,UAAU,GAAGC,QAAQ;AAEzB,IAAA,KAAK,MAAM,CAACd,GAAG,EAAEC,KAAK,CAAC,IAAI,IAAI,CAACC,KAAK,CAACa,OAAO,EAAE,EAAE;AAC/C,MAAA,IAAId,KAAK,CAACE,SAAS,GAAGU,UAAU,EAAE;QAChCA,UAAU,GAAGZ,KAAK,CAACE,SAAS;AAC5BO,QAAAA,SAAS,GAAGV,GAAG;AACjB,MAAA;AACF,IAAA;AAEA,IAAA,OAAOU,SAAS;AAClB,EAAA;AAEAM,EAAAA,KAAKA,GAAS;AACZ,IAAA,IAAI,CAACd,KAAK,CAACc,KAAK,EAAE;IAClB,IAAI,CAACV,IAAI,GAAG,CAAC;IACb,IAAI,CAACE,MAAM,GAAG,CAAC;AACjB,EAAA;AAEAS,EAAAA,QAAQA,GAAoE;IAC1E,MAAMC,KAAK,GAAG,IAAI,CAACZ,IAAI,GAAG,IAAI,CAACE,MAAM;IACrC,OAAO;AACLC,MAAAA,IAAI,EAAE,IAAI,CAACP,KAAK,CAACO,IAAI;MACrBU,OAAO,EAAED,KAAK,GAAG,CAAC,GAAG,IAAI,CAACZ,IAAI,GAAGY,KAAK,GAAG,CAAC;MAC1CZ,IAAI,EAAE,IAAI,CAACA,IAAI;MACfE,MAAM,EAAE,IAAI,CAACA;KACd;AACH,EAAA;AACF;;AAEA;AACA;AACA;AACO,MAAMY,gBAAgB,CAAC;EAAA3B,WAAAA,GAAA;AAAAK,IAAAA,eAAA,CAAA,IAAA,EAAA,OAAA,EACZ,IAAIN,QAAQ,CAAsB,IAAI,CAAC,CAAA;AAAA,EAAA;AAEvD6B,EAAAA,WAAWA,CAAC5F,QAAgB,EAAEC,OAA2B,EAAU;AACjE,IAAA,MAAM4F,QAAQ,GAAG/C,kBAAkB,CAAC7C,OAAO,CAAC;AAC5C,IAAA,MAAMuD,aAAa,GAAGvD,OAAO,CAACuD,aAAa,IAAI,CAAC;AAChD,IAAA,OAAO,GAAGqC,QAAQ,CAAA,CAAA,EAAI7F,QAAQ,CAAA,CAAA,EAAIwD,aAAa,CAAA,CAAE;AACnD,EAAA;AAEAnD,EAAAA,GAAGA,CACDL,QAAgB,EAChBC,OAA2B,EACM;IACjC,MAAMsE,GAAG,GAAG,IAAI,CAACqB,WAAW,CAAC5F,QAAQ,EAAEC,OAAO,CAAC;AAC/C,IAAA,OAAO,IAAI,CAACwE,KAAK,CAACpE,GAAG,CAACkE,GAAG,CAAC;AAC5B,EAAA;AAEAtD,EAAAA,GAAGA,CACDjB,QAAgB,EAChBC,OAA2B,EAC3BW,WAAgC,EAC1B;IACN,MAAM2D,GAAG,GAAG,IAAI,CAACqB,WAAW,CAAC5F,QAAQ,EAAEC,OAAO,CAAC;IAC/C,IAAI,CAACwE,KAAK,CAACxD,GAAG,CAACsD,GAAG,EAAE3D,WAAW,CAAC;AAClC,EAAA;AAEA2E,EAAAA,KAAKA,GAAS;AACZ,IAAA,IAAI,CAACd,KAAK,CAACc,KAAK,EAAE;AACpB,EAAA;AAEAC,EAAAA,QAAQA,GAAG;AACT,IAAA,OAAO,IAAI,CAACf,KAAK,CAACe,QAAQ,EAAE;AAC9B,EAAA;AACF;;AAEA;AACA;AACA;AACA,MAAMM,YAAY,CAAC;EAAA9B,WAAAA,GAAA;AAAAK,IAAAA,eAAA,CAAA,IAAA,EAAA,OAAA,EACD,IAAIN,QAAQ,CAAS,IAAI,CAAC,CAAA;AAAA,EAAA;AAAE;;AAE5C6B,EAAAA,WAAWA,CAACG,IAAY,EAAE9F,OAA2B,EAAU;AAC7D,IAAA,MAAM4F,QAAQ,GAAG/C,kBAAkB,CAAC7C,OAAO,CAAC;AAC5C,IAAA,OAAO,CAAA,EAAG4F,QAAQ,CAAA,CAAA,EAAIE,IAAI,CAAA,CAAE;AAC9B,EAAA;AAEA1F,EAAAA,GAAGA,CAAC0F,IAAY,EAAE9F,OAA2B,EAAsB;IACjE,MAAMsE,GAAG,GAAG,IAAI,CAACqB,WAAW,CAACG,IAAI,EAAE9F,OAAO,CAAC;AAC3C,IAAA,OAAO,IAAI,CAACwE,KAAK,CAACpE,GAAG,CAACkE,GAAG,CAAC;AAC5B,EAAA;AAEAtD,EAAAA,GAAGA,CAAC8E,IAAY,EAAE9F,OAA2B,EAAE+F,OAAe,EAAQ;IACpE,MAAMzB,GAAG,GAAG,IAAI,CAACqB,WAAW,CAACG,IAAI,EAAE9F,OAAO,CAAC;IAC3C,IAAI,CAACwE,KAAK,CAACxD,GAAG,CAACsD,GAAG,EAAEyB,OAAO,CAAC;AAC9B,EAAA;AAEAT,EAAAA,KAAKA,GAAS;AACZ,IAAA,IAAI,CAACd,KAAK,CAACc,KAAK,EAAE;AACpB,EAAA;AAEAC,EAAAA,QAAQA,GAAG;AACT,IAAA,OAAO,IAAI,CAACf,KAAK,CAACe,QAAQ,EAAE;AAC9B,EAAA;AACF;;AAEA;AACA;AACA;AACA,MAAMS,gBAAgB,CAAC;EAAAjC,WAAAA,GAAA;AAAAK,IAAAA,eAAA,CAAA,IAAA,EAAA,OAAA,EACL,IAAIC,GAAG,EAAuB,CAAA;AAAA,EAAA;EAE9CjE,GAAGA,CAACkD,eAAuB,EAA2B;AACpD,IAAA,OAAO,IAAI,CAACkB,KAAK,CAACpE,GAAG,CAACkD,eAAe,CAAC;AACxC,EAAA;AAEAtC,EAAAA,GAAGA,CAACsC,eAAuB,EAAE/C,OAAoB,EAAQ;IACvD,IAAI,CAACiE,KAAK,CAACxD,GAAG,CAACsC,eAAe,EAAE/C,OAAO,CAAC;AAC1C,EAAA;AAEA+E,EAAAA,KAAKA,GAAS;AACZ,IAAA,IAAI,CAACd,KAAK,CAACc,KAAK,EAAE;AACpB,EAAA;AAEAC,EAAAA,QAAQA,GAAG;IACT,OAAO;AACLR,MAAAA,IAAI,EAAE,IAAI,CAACP,KAAK,CAACO;KAClB;AACH,EAAA;AACF;;AAEA;MACa5E,gBAAgB,GAAG,IAAIuF,gBAAgB;MACvCnE,YAAY,GAAG,IAAIsE,YAAY;MAC/B/C,gBAAgB,GAAG,IAAIkD,gBAAgB;;AAEpD;AACA,IAAI,OAAOpE,QAAQ,KAAK,WAAW,IAAI,OAAO,IAAIA,QAAQ,EAAE;AAC1DA,EAAAA,QAAQ,CAACC,KAAK,CAACoE,gBAAgB,CAAC,aAAa,EAAE,MAAM;AACnD;AACAC,IAAAA,cAAc,EAAE;AAClB,EAAA,CAAC,CAAC;AACJ;;AAEA;AACA;AACA;AACO,SAASA,cAAcA,GAAS;EACrC/F,gBAAgB,CAACmF,KAAK,EAAE;EACxB/D,YAAY,CAAC+D,KAAK,EAAE;EACpBxC,gBAAgB,CAACwC,KAAK,EAAE;AAC1B;;AAwFA;AACA;AACA;AACA;AACO,SAASa,sBAAsBA,CAACxE,UAAkB,EAAW;AAClE,EAAA,IAAI,OAAOC,QAAQ,KAAK,WAAW,EAAE,OAAO,KAAK;;AAEjD;EACA,MAAMwE,oBAAoB,GAAG,CAC3B,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EACrD,UAAU,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,QAAQ,CACnD;AAED,EAAA,MAAMC,eAAe,GAAG1E,UAAU,CAAC2E,WAAW,EAAE;;AAEhD;AACA,EAAA,IAAIF,oBAAoB,CAACG,IAAI,CAACpE,IAAI,IAAIkE,eAAe,CAACxC,QAAQ,CAAC1B,IAAI,CAAC,CAAC,EAAE;AACrE,IAAA,OAAO,IAAI;AACb,EAAA;;AAEA;AACA,EAAA,MAAM9B,OAAO,GAAGb,qBAAqB,EAAE;AACvCa,EAAAA,OAAO,CAAC8B,IAAI,GAAG,CAAA,KAAA,EAAQR,UAAU,CAAA,CAAE;;AAEnC;AACA,EAAA,MAAM6E,YAAY,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC;EAC7D,MAAMC,YAAY,GAAG,mBAAmB;;AAExC;AACA,EAAA,MAAMC,YAAY,GAAGF,YAAY,CAACG,GAAG,CAAC1E,IAAI,IAAI5B,OAAO,CAACG,WAAW,CAACyB,IAAI,CAAC,CAACtC,KAAK,CAAC;;AAE9E;AACAU,EAAAA,OAAO,CAAC8B,IAAI,GAAG,CAAA,KAAA,EAAQsE,YAAY,CAAA,CAAE;AACrC,EAAA,MAAMG,cAAc,GAAGJ,YAAY,CAACG,GAAG,CAAC1E,IAAI,IAAI5B,OAAO,CAACG,WAAW,CAACyB,IAAI,CAAC,CAACtC,KAAK,CAAC;;AAEhF;EACA,IAAIkH,cAAc,GAAG,CAAC;AACtB,EAAA,KAAK,IAAIC,CAAC,GAAG,CAAC,EAAEA,CAAC,GAAGN,YAAY,CAACtC,MAAM,EAAE4C,CAAC,EAAE,EAAE;AAC5C,IAAA,IAAIxE,IAAI,CAACC,GAAG,CAACmE,YAAY,CAACI,CAAC,CAAC,GAAGF,cAAc,CAACE,CAAC,CAAC,CAAC,GAAG,GAAG,EAAE;AACvDD,MAAAA,cAAc,EAAE;AAClB,IAAA;AACF,EAAA;EAEA,MAAME,qBAAqB,GAAGP,YAAY,CAACtC,MAAM,GAAG,GAAG,CAAC;AACxD,EAAA,MAAM8C,YAAY,GAAGH,cAAc,IAAIE,qBAAqB;AAG5D,EAAA,OAAOC,YAAY;AACrB;;AAEA;AACA,MAAMC,cAAc,GAAG,IAAI5C,GAAG,EAAmB;;AAEjD;AACA;AACA;AACO,SAAS6C,4BAA4BA,CAACvF,UAAkB,EAAW;AACxE,EAAA,IAAIsF,cAAc,CAACE,GAAG,CAACxF,UAAU,CAAC,EAAE;AAClC,IAAA,OAAOsF,cAAc,CAAC7G,GAAG,CAACuB,UAAU,CAAC;AACvC,EAAA;AAEA,EAAA,MAAM0B,MAAM,GAAG8C,sBAAsB,CAACxE,UAAU,CAAC;AACjDsF,EAAAA,cAAc,CAACjG,GAAG,CAACW,UAAU,EAAE0B,MAAM,CAAC;AACtC,EAAA,OAAOA,MAAM;AACf;;;;"}
|