@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.
Files changed (181) hide show
  1. package/0 +0 -0
  2. package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
  3. package/debug/{konva → konva-master}/README.md +7 -3
  4. package/debug/{konva → konva-master}/package.json +1 -1
  5. package/debug/{konva → konva-master}/release.sh +1 -4
  6. package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
  7. package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
  8. package/dist/index.js +1853 -288
  9. package/dist/index.js.map +1 -1
  10. package/dist/index.min.js +1 -1
  11. package/dist/index.min.js.map +1 -1
  12. package/dist/index.min.mjs +1 -1
  13. package/dist/index.min.mjs.map +1 -1
  14. package/dist/index.mjs +1853 -288
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/index.node.cjs +1853 -288
  17. package/dist/index.node.cjs.map +1 -1
  18. package/dist/index.node.mjs +1853 -288
  19. package/dist/index.node.mjs.map +1 -1
  20. package/dist/package.json.min.mjs +1 -1
  21. package/dist/package.json.mjs +1 -1
  22. package/dist/src/shapes/Line.d.ts +33 -86
  23. package/dist/src/shapes/Line.d.ts.map +1 -1
  24. package/dist/src/shapes/Line.min.mjs +1 -1
  25. package/dist/src/shapes/Line.min.mjs.map +1 -1
  26. package/dist/src/shapes/Line.mjs +405 -159
  27. package/dist/src/shapes/Line.mjs.map +1 -1
  28. package/dist/src/shapes/Polyline.d.ts +7 -0
  29. package/dist/src/shapes/Polyline.d.ts.map +1 -1
  30. package/dist/src/shapes/Polyline.min.mjs +1 -1
  31. package/dist/src/shapes/Polyline.min.mjs.map +1 -1
  32. package/dist/src/shapes/Polyline.mjs +48 -16
  33. package/dist/src/shapes/Polyline.mjs.map +1 -1
  34. package/dist/src/shapes/Text/Text.d.ts +19 -0
  35. package/dist/src/shapes/Text/Text.d.ts.map +1 -1
  36. package/dist/src/shapes/Text/Text.min.mjs +1 -1
  37. package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
  38. package/dist/src/shapes/Text/Text.mjs +302 -16
  39. package/dist/src/shapes/Text/Text.mjs.map +1 -1
  40. package/dist/src/shapes/Textbox.d.ts +43 -1
  41. package/dist/src/shapes/Textbox.d.ts.map +1 -1
  42. package/dist/src/shapes/Textbox.min.mjs +1 -1
  43. package/dist/src/shapes/Textbox.min.mjs.map +1 -1
  44. package/dist/src/shapes/Textbox.mjs +521 -67
  45. package/dist/src/shapes/Textbox.mjs.map +1 -1
  46. package/dist/src/shapes/Triangle.d.ts +27 -2
  47. package/dist/src/shapes/Triangle.d.ts.map +1 -1
  48. package/dist/src/shapes/Triangle.min.mjs +1 -1
  49. package/dist/src/shapes/Triangle.min.mjs.map +1 -1
  50. package/dist/src/shapes/Triangle.mjs +72 -12
  51. package/dist/src/shapes/Triangle.mjs.map +1 -1
  52. package/dist/src/text/examples/arabicTextExample.d.ts +60 -0
  53. package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
  54. package/dist/src/text/measure.d.ts +9 -0
  55. package/dist/src/text/measure.d.ts.map +1 -1
  56. package/dist/src/text/measure.min.mjs +1 -1
  57. package/dist/src/text/measure.min.mjs.map +1 -1
  58. package/dist/src/text/measure.mjs +175 -4
  59. package/dist/src/text/measure.mjs.map +1 -1
  60. package/dist/src/text/overlayEditor.d.ts.map +1 -1
  61. package/dist/src/text/overlayEditor.min.mjs +1 -1
  62. package/dist/src/text/overlayEditor.min.mjs.map +1 -1
  63. package/dist/src/text/overlayEditor.mjs +155 -9
  64. package/dist/src/text/overlayEditor.mjs.map +1 -1
  65. package/dist/src/text/scriptUtils.d.ts +142 -0
  66. package/dist/src/text/scriptUtils.d.ts.map +1 -0
  67. package/dist/src/text/scriptUtils.min.mjs +2 -0
  68. package/dist/src/text/scriptUtils.min.mjs.map +1 -0
  69. package/dist/src/text/scriptUtils.mjs +212 -0
  70. package/dist/src/text/scriptUtils.mjs.map +1 -0
  71. package/dist/src/util/misc/cornerRadius.d.ts +70 -0
  72. package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
  73. package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
  74. package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
  75. package/dist/src/util/misc/cornerRadius.mjs +181 -0
  76. package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
  77. package/dist-extensions/src/shapes/CustomLine.d.ts +10 -0
  78. package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
  79. package/dist-extensions/src/shapes/Line.d.ts +33 -86
  80. package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
  81. package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
  82. package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
  83. package/dist-extensions/src/shapes/Text/Text.d.ts +19 -0
  84. package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
  85. package/dist-extensions/src/shapes/Textbox.d.ts +43 -1
  86. package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
  87. package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
  88. package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
  89. package/dist-extensions/src/text/measure.d.ts +9 -0
  90. package/dist-extensions/src/text/measure.d.ts.map +1 -1
  91. package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
  92. package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
  93. package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
  94. package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
  95. package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
  96. package/fabric-test-editor.html +3552 -0
  97. package/fabric-test2.html +647 -0
  98. package/fabric.ts +182 -182
  99. package/fonts/STV Bold.ttf +0 -0
  100. package/fonts/STV Light.ttf +0 -0
  101. package/fonts/STV Regular.ttf +0 -0
  102. package/package.json +164 -164
  103. package/src/shapes/Line.ts +484 -157
  104. package/src/shapes/Polyline.ts +70 -29
  105. package/src/shapes/Text/Text.ts +317 -19
  106. package/src/shapes/Textbox.ts +544 -12
  107. package/src/shapes/Triangle.spec.ts +76 -0
  108. package/src/shapes/Triangle.ts +85 -15
  109. package/src/text/measure.ts +200 -50
  110. package/src/text/overlayEditor.ts +164 -12
  111. package/src/util/misc/cornerRadius.spec.ts +141 -0
  112. package/src/util/misc/cornerRadius.ts +269 -0
  113. /package/debug/{konva → konva-master}/LICENSE +0 -0
  114. /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
  115. /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
  116. /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
  117. /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
  118. /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
  119. /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
  120. /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
  121. /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
  122. /package/debug/{konva → konva-master}/src/Container.ts +0 -0
  123. /package/debug/{konva → konva-master}/src/Context.ts +0 -0
  124. /package/debug/{konva → konva-master}/src/Core.ts +0 -0
  125. /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
  126. /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
  127. /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
  128. /package/debug/{konva → konva-master}/src/Global.ts +0 -0
  129. /package/debug/{konva → konva-master}/src/Group.ts +0 -0
  130. /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
  131. /package/debug/{konva → konva-master}/src/Node.ts +0 -0
  132. /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
  133. /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
  134. /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
  135. /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
  136. /package/debug/{konva → konva-master}/src/Util.ts +0 -0
  137. /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
  138. /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
  139. /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
  140. /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
  141. /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
  142. /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
  143. /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
  144. /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
  145. /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
  146. /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
  147. /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
  148. /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
  149. /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
  150. /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
  151. /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
  152. /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
  153. /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
  154. /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
  155. /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
  156. /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
  157. /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
  158. /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
  159. /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
  160. /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
  161. /package/debug/{konva → konva-master}/src/index.ts +0 -0
  162. /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
  163. /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
  164. /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
  165. /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
  166. /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
  167. /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
  168. /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
  169. /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
  170. /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
  171. /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
  172. /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
  173. /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
  174. /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
  175. /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
  176. /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
  177. /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
  178. /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
  179. /package/debug/{konva → konva-master}/src/types.ts +0 -0
  180. /package/debug/{konva → konva-master}/tsconfig.json +0 -0
  181. /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<FabricObjectProps> = Partial<FabricObjectProps>,\n SProps extends SerializedObjectProps = SerializedObjectProps,\n EventSpec extends ObjectEvents = ObjectEvents,\n >\n extends FabricObject<Props, SProps, EventSpec>\n implements FabricObjectProps\n{\n static type = 'Triangle';\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 * @private\n * @param {CanvasRenderingContext2D} ctx Context to render on\n */\n _render(ctx: CanvasRenderingContext2D) {\n const widthBy2 = this.width / 2,\n 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 this._renderPaintInOrder(ctx);\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 const widthBy2 = this.width / 2,\n heightBy2 = this.height / 2,\n points = `${-widthBy2} ${heightBy2},0 ${-heightBy2},${widthBy2} ${heightBy2}`;\n return ['<polygon ', 'COMMON_PARTS', 'points=\"', points, '\" />'];\n }\n}\n\nclassRegistry.setClass(Triangle);\nclassRegistry.setSVGClass(Triangle);\n"],"names":["triangleDefaultValues","width","height","Triangle","FabricObject","getDefaults","ownDefaults","constructor","options","Object","assign","setOptions","_render","ctx","widthBy2","heightBy2","beginPath","moveTo","lineTo","closePath","_renderPaintInOrder","_toSVG","points","_defineProperty","classRegistry","setClass","setSVGClass"],"mappings":";;;;AAMO,MAAMA,qBAA0D,GAAG;AACxEC,EAAAA,KAAK,EAAE,GAAG;AACVC,EAAAA,MAAM,EAAE;AACV;AAEO,MAAMC,QAAQ,SAKXC,YAAY,CAEtB;EAKE,OAAOC,WAAWA,GAAwB;IACxC,OAAO;AAAE,MAAA,GAAG,KAAK,CAACA,WAAW,EAAE;AAAE,MAAA,GAAGF,QAAQ,CAACG;KAAa;AAC5D,EAAA;;AAEA;AACF;AACA;AACA;EACEC,WAAWA,CAACC,OAAe,EAAE;AAC3B,IAAA,KAAK,EAAE;IACPC,MAAM,CAACC,MAAM,CAAC,IAAI,EAAEP,QAAQ,CAACG,WAAW,CAAC;AACzC,IAAA,IAAI,CAACK,UAAU,CAACH,OAAO,CAAC;AAC1B,EAAA;;AAEA;AACF;AACA;AACA;EACEI,OAAOA,CAACC,GAA6B,EAAE;AACrC,IAAA,MAAMC,QAAQ,GAAG,IAAI,CAACb,KAAK,GAAG,CAAC;AAC7Bc,MAAAA,SAAS,GAAG,IAAI,CAACb,MAAM,GAAG,CAAC;IAE7BW,GAAG,CAACG,SAAS,EAAE;AACfH,IAAAA,GAAG,CAACI,MAAM,CAAC,CAACH,QAAQ,EAAEC,SAAS,CAAC;AAChCF,IAAAA,GAAG,CAACK,MAAM,CAAC,CAAC,EAAE,CAACH,SAAS,CAAC;AACzBF,IAAAA,GAAG,CAACK,MAAM,CAACJ,QAAQ,EAAEC,SAAS,CAAC;IAC/BF,GAAG,CAACM,SAAS,EAAE;AAEf,IAAA,IAAI,CAACC,mBAAmB,CAACP,GAAG,CAAC;AAC/B,EAAA;;AAEA;AACF;AACA;AACA;AACA;AACEQ,EAAAA,MAAMA,GAAG;AACP,IAAA,MAAMP,QAAQ,GAAG,IAAI,CAACb,KAAK,GAAG,CAAC;AAC7Bc,MAAAA,SAAS,GAAG,IAAI,CAACb,MAAM,GAAG,CAAC;AAC3BoB,MAAAA,MAAM,GAAG,CAAA,EAAG,CAACR,QAAQ,CAAA,CAAA,EAAIC,SAAS,CAAA,GAAA,EAAM,CAACA,SAAS,CAAA,CAAA,EAAID,QAAQ,CAAA,CAAA,EAAIC,SAAS,CAAA,CAAE;IAC/E,OAAO,CAAC,WAAW,EAAE,cAAc,EAAE,UAAU,EAAEO,MAAM,EAAE,MAAM,CAAC;AAClE,EAAA;AACF;AAACC,eAAA,CAtDYpB,QAAQ,EAAA,MAAA,EAQL,UAAU,CAAA;AAAAoB,eAAA,CARbpB,QAAQ,EAAA,aAAA,EAUEH,qBAAqB,CAAA;AA8C5CwB,aAAa,CAACC,QAAQ,CAACtB,QAAQ,CAAC;AAChCqB,aAAa,CAACE,WAAW,CAACvB,QAAQ,CAAC;;;;"}
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":"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"}
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 s=null;function i(){if(!s){const t=e({width:0,height:0});s=t.getContext("2d")}return s}function n(t,e,s){const n=u.get(t,e);if(n)return n;const c=s||i();a(c,e);const o=c.measureText(t),r=h(e),l={width:o.width,height:r.lineHeight,ascent:r.ascent,descent:r.descent,baseline:r.ascent};return u.set(t,e,l),l}function c(t,e,s,c){const h=n(t,s,c);if(!e)return{...h,kernedWidth:h.width};const o=`${e}${t}`,r=g.get(o,s);if(r)return{...h,kernedWidth:r};const l=i();a(l,s);const u=l.measureText(e+t).width-n(e,s,l).width;return g.set(o,s,u),{...h,kernedWidth:u}}function h(t){var e,s,n,c;const h=o(t),r=d.get(h);if(r)return r;const l=i();a(l,t);const u=l.measureText("M"),g=t.fontSize,m=null!==(e=u.fontBoundingBoxAscent)&&void 0!==e?e:.91*g,f=null!==(s=u.fontBoundingBoxDescent)&&void 0!==s?s:.21*g,p={ascent:m,descent:f,lineHeight:g,baseline:"alphabetic",fontBoundingBoxAscent:m,fontBoundingBoxDescent:f,actualBoundingBoxAscent:null!==(n=u.actualBoundingBoxAscent)&&void 0!==n?n:.716*g,actualBoundingBoxDescent:null!==(c=u.actualBoundingBoxDescent)&&void 0!==c?c:0};return d.set(h,p),p}function a(t,e){const s=o(e);t.font=s,e.letterSpacing&&"letterSpacing"in t&&(t.letterSpacing=`${e.letterSpacing}px`),e.direction&&(t.direction=e.direction),t.textBaseline="alphabetic"}function o(t){const{fontStyle:e,fontWeight:s,fontSize:i,fontFamily:n}=t;return`${e} ${s} ${i}px ${!n.includes(" ")||n.includes('"')||n.includes("'")?n:`"${n}"`}`}class r{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[s,i]of this.cache.entries())i.timestamp<e&&(e=i.timestamp,t=s);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 l{constructor(){t(this,"cache",new r(1e3))}getCacheKey(t,e){return`${o(e)}|${t}|${e.letterSpacing||0}`}get(t,e){const s=this.getCacheKey(t,e);return this.cache.get(s)}set(t,e,s){const i=this.getCacheKey(t,e);this.cache.set(i,s)}clear(){this.cache.clear()}getStats(){return this.cache.getStats()}}const u=new l,g=new class{constructor(){t(this,"cache",new r(5e3))}getCacheKey(t,e){return`${o(e)}|${t}`}get(t,e){const s=this.getCacheKey(t,e);return this.cache.get(s)}set(t,e,s){const i=this.getCacheKey(t,e);this.cache.set(i,s)}clear(){this.cache.clear()}getStats(){return this.cache.getStats()}},d=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}}};export{l as MeasurementCache,d as fontMetricsCache,h as getFontMetrics,g as kerningCache,n as measureGrapheme,c as measureGraphemeWithKerning,u as measurementCache};
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 'M' as sample character for metrics
111
- const metrics = context.measureText('M');
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
- const normalizedFamily = fontFamily.includes(' ') && !fontFamily.includes('"') && !fontFamily.includes("'") ? `"${fontFamily}"` : fontFamily;
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
- export { MeasurementCache, fontMetricsCache, getFontMetrics, kerningCache, measureGrapheme, measureGraphemeWithKerning, measurementCache };
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":"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"}