shape-text 0.1.0

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 (108) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +264 -0
  3. package/dist/geometry/get-band-intervals-from-polygon.d.ts +2 -0
  4. package/dist/geometry/get-band-intervals-from-polygon.js +44 -0
  5. package/dist/geometry/get-band-intervals-from-polygon.js.map +1 -0
  6. package/dist/geometry/get-polygon-bounds.d.ts +2 -0
  7. package/dist/geometry/get-polygon-bounds.js +18 -0
  8. package/dist/geometry/get-polygon-bounds.js.map +1 -0
  9. package/dist/geometry/get-x-intersections-at-y.d.ts +2 -0
  10. package/dist/geometry/get-x-intersections-at-y.js +15 -0
  11. package/dist/geometry/get-x-intersections-at-y.js.map +1 -0
  12. package/dist/index.d.ts +11 -0
  13. package/dist/index.js +11 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/layout/layout-dense-fill-pass.d.ts +23 -0
  16. package/dist/layout/layout-dense-fill-pass.js +64 -0
  17. package/dist/layout/layout-dense-fill-pass.js.map +1 -0
  18. package/dist/layout/layout-flow-lines-in-compiled-shape.d.ts +13 -0
  19. package/dist/layout/layout-flow-lines-in-compiled-shape.js +42 -0
  20. package/dist/layout/layout-flow-lines-in-compiled-shape.js.map +1 -0
  21. package/dist/layout/layout-text-in-compiled-shape.d.ts +2 -0
  22. package/dist/layout/layout-text-in-compiled-shape.js +13 -0
  23. package/dist/layout/layout-text-in-compiled-shape.js.map +1 -0
  24. package/dist/layout/layout-text-in-shape.d.ts +2 -0
  25. package/dist/layout/layout-text-in-shape.js +40 -0
  26. package/dist/layout/layout-text-in-shape.js.map +1 -0
  27. package/dist/layout/resolve-flow-layout.d.ts +2 -0
  28. package/dist/layout/resolve-flow-layout.js +31 -0
  29. package/dist/layout/resolve-flow-layout.js.map +1 -0
  30. package/dist/layout/resolve-max-fill-helpers.d.ts +3 -0
  31. package/dist/layout/resolve-max-fill-helpers.js +18 -0
  32. package/dist/layout/resolve-max-fill-helpers.js.map +1 -0
  33. package/dist/layout/resolve-max-fill-layout.d.ts +2 -0
  34. package/dist/layout/resolve-max-fill-layout.js +34 -0
  35. package/dist/layout/resolve-max-fill-layout.js.map +1 -0
  36. package/dist/layout/resolve-sequential-shape-regions.d.ts +3 -0
  37. package/dist/layout/resolve-sequential-shape-regions.js +95 -0
  38. package/dist/layout/resolve-sequential-shape-regions.js.map +1 -0
  39. package/dist/render/escape-xml-text.d.ts +1 -0
  40. package/dist/render/escape-xml-text.js +9 -0
  41. package/dist/render/escape-xml-text.js.map +1 -0
  42. package/dist/render/normalize-shape-decoration.d.ts +2 -0
  43. package/dist/render/normalize-shape-decoration.js +41 -0
  44. package/dist/render/normalize-shape-decoration.js.map +1 -0
  45. package/dist/render/render-layout-to-svg.d.ts +2 -0
  46. package/dist/render/render-layout-to-svg.js +88 -0
  47. package/dist/render/render-layout-to-svg.js.map +1 -0
  48. package/dist/render/render-svg-shadow-filter.d.ts +10 -0
  49. package/dist/render/render-svg-shadow-filter.js +16 -0
  50. package/dist/render/render-svg-shadow-filter.js.map +1 -0
  51. package/dist/shape/build-text-mask-bands-from-alpha.d.ts +8 -0
  52. package/dist/shape/build-text-mask-bands-from-alpha.js +73 -0
  53. package/dist/shape/build-text-mask-bands-from-alpha.js.map +1 -0
  54. package/dist/shape/compile-polygon-shape-for-layout.d.ts +2 -0
  55. package/dist/shape/compile-polygon-shape-for-layout.js +26 -0
  56. package/dist/shape/compile-polygon-shape-for-layout.js.map +1 -0
  57. package/dist/shape/compile-shape-for-layout.d.ts +2 -0
  58. package/dist/shape/compile-shape-for-layout.js +18 -0
  59. package/dist/shape/compile-shape-for-layout.js.map +1 -0
  60. package/dist/shape/compile-text-mask-shape-for-layout.d.ts +2 -0
  61. package/dist/shape/compile-text-mask-shape-for-layout.js +162 -0
  62. package/dist/shape/compile-text-mask-shape-for-layout.js.map +1 -0
  63. package/dist/shape/render-text-mask-raster.d.ts +13 -0
  64. package/dist/shape/render-text-mask-raster.js +29 -0
  65. package/dist/shape/render-text-mask-raster.js.map +1 -0
  66. package/dist/shape/resolve-text-mask-shape-size.d.ts +18 -0
  67. package/dist/shape/resolve-text-mask-shape-size.js +67 -0
  68. package/dist/shape/resolve-text-mask-shape-size.js.map +1 -0
  69. package/dist/shape/segment-text-mask-graphemes.d.ts +6 -0
  70. package/dist/shape/segment-text-mask-graphemes.js +20 -0
  71. package/dist/shape/segment-text-mask-graphemes.js.map +1 -0
  72. package/dist/text/create-browser-canvas-2d-context.d.ts +2 -0
  73. package/dist/text/create-browser-canvas-2d-context.js +21 -0
  74. package/dist/text/create-browser-canvas-2d-context.js.map +1 -0
  75. package/dist/text/create-canvas-text-measurer.d.ts +2 -0
  76. package/dist/text/create-canvas-text-measurer.js +11 -0
  77. package/dist/text/create-canvas-text-measurer.js.map +1 -0
  78. package/dist/text/layout-next-line-from-dense-repeated-text.d.ts +2 -0
  79. package/dist/text/layout-next-line-from-dense-repeated-text.js +63 -0
  80. package/dist/text/layout-next-line-from-dense-repeated-text.js.map +1 -0
  81. package/dist/text/layout-next-line-from-prepared-text.d.ts +2 -0
  82. package/dist/text/layout-next-line-from-prepared-text.js +57 -0
  83. package/dist/text/layout-next-line-from-prepared-text.js.map +1 -0
  84. package/dist/text/layout-next-line-from-repeated-text.d.ts +2 -0
  85. package/dist/text/layout-next-line-from-repeated-text.js +51 -0
  86. package/dist/text/layout-next-line-from-repeated-text.js.map +1 -0
  87. package/dist/text/layout-text-line-helpers.d.ts +4 -0
  88. package/dist/text/layout-text-line-helpers.js +17 -0
  89. package/dist/text/layout-text-line-helpers.js.map +1 -0
  90. package/dist/text/normalize-text-style-to-font.d.ts +6 -0
  91. package/dist/text/normalize-text-style-to-font.js +31 -0
  92. package/dist/text/normalize-text-style-to-font.js.map +1 -0
  93. package/dist/text/prepare-dense-repeat-fill-pattern.d.ts +2 -0
  94. package/dist/text/prepare-dense-repeat-fill-pattern.js +13 -0
  95. package/dist/text/prepare-dense-repeat-fill-pattern.js.map +1 -0
  96. package/dist/text/prepare-stream-repeat-fill-pattern.d.ts +2 -0
  97. package/dist/text/prepare-stream-repeat-fill-pattern.js +16 -0
  98. package/dist/text/prepare-stream-repeat-fill-pattern.js.map +1 -0
  99. package/dist/text/prepare-text-for-layout.d.ts +2 -0
  100. package/dist/text/prepare-text-for-layout.js +22 -0
  101. package/dist/text/prepare-text-for-layout.js.map +1 -0
  102. package/dist/text/segment-text-for-layout.d.ts +3 -0
  103. package/dist/text/segment-text-for-layout.js +43 -0
  104. package/dist/text/segment-text-for-layout.js.map +1 -0
  105. package/dist/types.d.ts +200 -0
  106. package/dist/types.js +2 -0
  107. package/dist/types.js.map +1 -0
  108. package/package.json +79 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-text-line-helpers.js","sourceRoot":"","sources":["../../src/text/layout-text-line-helpers.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,iBAAiB,CAC/B,KAA0B,EAC1B,KAAa,EACb,GAAW;IAEX,OAAO,KAAK,CAAC,oBAAoB,CAAC,GAAG,CAAE,GAAG,KAAK,CAAC,oBAAoB,CAAC,KAAK,CAAE,CAAA;AAC9E,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC9B,KAA0B,EAC1B,KAAa,EACb,GAAW;IAEX,OAAO,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AACnD,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,KAA0B,EAC1B,KAAa,EACb,QAAgB;IAEhB,MAAM,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAA;IAE5C,KAAK,IAAI,GAAG,GAAG,KAAK,GAAG,CAAC,EAAE,GAAG,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE,CAAC;QAC/D,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;QAClD,IAAI,KAAK,GAAG,cAAc,EAAE,CAAC;YAC3B,OAAO,GAAG,KAAK,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAA;QAC1C,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC,SAAS,CAAC,MAAM,CAAA;AAC/B,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { ResolvedTextStyle, TextStyleInput } from '../types.js';
2
+ export declare function normalizeTextStyleToFont(textStyle: TextStyleInput): ResolvedTextStyle;
3
+ export declare function resolveLayoutTextStyle(options: {
4
+ font?: string;
5
+ textStyle?: TextStyleInput;
6
+ }): ResolvedTextStyle;
@@ -0,0 +1,31 @@
1
+ function assertFinitePositiveSize(size) {
2
+ if (!Number.isFinite(size) || size <= 0) {
3
+ throw new Error('textStyle.size must be a finite positive number');
4
+ }
5
+ }
6
+ export function normalizeTextStyleToFont(textStyle) {
7
+ if (textStyle.family.trim().length === 0) {
8
+ throw new Error('textStyle.family must be a non-empty string');
9
+ }
10
+ assertFinitePositiveSize(textStyle.size);
11
+ return {
12
+ family: textStyle.family,
13
+ size: textStyle.size,
14
+ weight: textStyle.weight ?? 400,
15
+ style: textStyle.style ?? 'normal',
16
+ color: textStyle.color,
17
+ font: `${textStyle.style ?? 'normal'} ${String(textStyle.weight ?? 400)} ${textStyle.size}px ${textStyle.family}`,
18
+ };
19
+ }
20
+ export function resolveLayoutTextStyle(options) {
21
+ if (options.textStyle !== undefined) {
22
+ return normalizeTextStyleToFont(options.textStyle);
23
+ }
24
+ if (options.font !== undefined && options.font.trim().length > 0) {
25
+ return {
26
+ font: options.font,
27
+ };
28
+ }
29
+ throw new Error('layout text requires either font or textStyle');
30
+ }
31
+ //# sourceMappingURL=normalize-text-style-to-font.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalize-text-style-to-font.js","sourceRoot":"","sources":["../../src/text/normalize-text-style-to-font.ts"],"names":[],"mappings":"AAEA,SAAS,wBAAwB,CAAC,IAAY;IAC5C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,EAAE,CAAC;QACxC,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAA;IACpE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,wBAAwB,CAAC,SAAyB;IAChE,IAAI,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzC,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAA;IAChE,CAAC;IAED,wBAAwB,CAAC,SAAS,CAAC,IAAI,CAAC,CAAA;IAExC,OAAO;QACL,MAAM,EAAE,SAAS,CAAC,MAAM;QACxB,IAAI,EAAE,SAAS,CAAC,IAAI;QACpB,MAAM,EAAE,SAAS,CAAC,MAAM,IAAI,GAAG;QAC/B,KAAK,EAAE,SAAS,CAAC,KAAK,IAAI,QAAQ;QAClC,KAAK,EAAE,SAAS,CAAC,KAAK;QACtB,IAAI,EAAE,GAAG,SAAS,CAAC,KAAK,IAAI,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,IAAI,GAAG,CAAC,IAAI,SAAS,CAAC,IAAI,MAAM,SAAS,CAAC,MAAM,EAAE;KAClH,CAAA;AACH,CAAC;AAED,MAAM,UAAU,sBAAsB,CAAC,OAGtC;IACC,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACpC,OAAO,wBAAwB,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IACpD,CAAC;IAED,IAAI,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjE,OAAO;YACL,IAAI,EAAE,OAAO,CAAC,IAAI;SACnB,CAAA;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;AAClE,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { PreparedLayoutToken, TextMeasurer } from '../types.js';
2
+ export declare function prepareDenseRepeatFillPattern(text: string, font: string, measurer: TextMeasurer): PreparedLayoutToken;
@@ -0,0 +1,13 @@
1
+ import { createMeasuredWordToken } from './segment-text-for-layout.js';
2
+ export function prepareDenseRepeatFillPattern(text, font, measurer) {
3
+ const normalized = text.replace(/\s+/gu, '');
4
+ if (normalized.length === 0) {
5
+ throw new Error('dense autoFill requires at least one non-whitespace grapheme');
6
+ }
7
+ const pattern = createMeasuredWordToken(normalized, value => measurer.measureText(value, font));
8
+ if (pattern.width <= 0) {
9
+ throw new Error('dense autoFill requires measurable graphemes');
10
+ }
11
+ return pattern;
12
+ }
13
+ //# sourceMappingURL=prepare-dense-repeat-fill-pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare-dense-repeat-fill-pattern.js","sourceRoot":"","sources":["../../src/text/prepare-dense-repeat-fill-pattern.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAEtE,MAAM,UAAU,6BAA6B,CAC3C,IAAY,EACZ,IAAY,EACZ,QAAsB;IAEtB,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAA;IAE5C,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,8DAA8D,CAAC,CAAA;IACjF,CAAC;IAED,MAAM,OAAO,GAAG,uBAAuB,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;IAC/F,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAA;IACjE,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { PreparedLayoutToken, TextMeasurer } from '../types.js';
2
+ export declare function prepareStreamRepeatFillPattern(text: string, font: string, measurer: TextMeasurer): PreparedLayoutToken;
@@ -0,0 +1,16 @@
1
+ import { createMeasuredWordToken } from './segment-text-for-layout.js';
2
+ function normalizeRepeatStreamText(text) {
3
+ return text.replace(/\r\n?/gu, '\n').replace(/[\n\t\f\v]+/gu, ' ');
4
+ }
5
+ export function prepareStreamRepeatFillPattern(text, font, measurer) {
6
+ const normalized = normalizeRepeatStreamText(text);
7
+ if (normalized.length === 0) {
8
+ throw new Error('stream autoFill requires at least one grapheme');
9
+ }
10
+ const pattern = createMeasuredWordToken(normalized, value => measurer.measureText(value, font));
11
+ if (pattern.width <= 0) {
12
+ throw new Error('stream autoFill requires measurable graphemes');
13
+ }
14
+ return pattern;
15
+ }
16
+ //# sourceMappingURL=prepare-stream-repeat-fill-pattern.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare-stream-repeat-fill-pattern.js","sourceRoot":"","sources":["../../src/text/prepare-stream-repeat-fill-pattern.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,MAAM,8BAA8B,CAAA;AAEtE,SAAS,yBAAyB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,OAAO,CAAC,eAAe,EAAE,GAAG,CAAC,CAAA;AACpE,CAAC;AAED,MAAM,UAAU,8BAA8B,CAC5C,IAAY,EACZ,IAAY,EACZ,QAAsB;IAEtB,MAAM,UAAU,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAA;IAElD,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAA;IACnE,CAAC;IAED,MAAM,OAAO,GAAG,uBAAuB,CAAC,UAAU,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;IAC/F,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAA;IAClE,CAAC;IAED,OAAO,OAAO,CAAA;AAChB,CAAC"}
@@ -0,0 +1,2 @@
1
+ import type { PreparedLayoutText, TextMeasurer } from '../types.js';
2
+ export declare function prepareTextForLayout(text: string, font: string, measurer: TextMeasurer): PreparedLayoutText;
@@ -0,0 +1,22 @@
1
+ import { createMeasuredWordToken, segmentTextForLayout } from './segment-text-for-layout.js';
2
+ export function prepareTextForLayout(text, font, measurer) {
3
+ const segmented = segmentTextForLayout(text);
4
+ const tokens = segmented.map(token => {
5
+ if (token.kind === 'newline') {
6
+ return {
7
+ kind: 'newline',
8
+ text: token.text,
9
+ width: 0,
10
+ graphemes: [],
11
+ graphemePrefixWidths: [0],
12
+ };
13
+ }
14
+ return createMeasuredWordToken(token.text, value => measurer.measureText(value, font));
15
+ });
16
+ return {
17
+ font,
18
+ spaceWidth: measurer.measureText(' ', font),
19
+ tokens,
20
+ };
21
+ }
22
+ //# sourceMappingURL=prepare-text-for-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prepare-text-for-layout.js","sourceRoot":"","sources":["../../src/text/prepare-text-for-layout.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,uBAAuB,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAA;AAE5F,MAAM,UAAU,oBAAoB,CAClC,IAAY,EACZ,IAAY,EACZ,QAAsB;IAEtB,MAAM,SAAS,GAAG,oBAAoB,CAAC,IAAI,CAAC,CAAA;IAC5C,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE;QACnC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;YAC7B,OAAO;gBACL,IAAI,EAAE,SAAkB;gBACxB,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,KAAK,EAAE,CAAC;gBACR,SAAS,EAAE,EAAE;gBACb,oBAAoB,EAAE,CAAC,CAAC,CAAC;aAC1B,CAAA;QACH,CAAC;QAED,OAAO,uBAAuB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAA;IACxF,CAAC,CAAC,CAAA;IAEF,OAAO;QACL,IAAI;QACJ,UAAU,EAAE,QAAQ,CAAC,WAAW,CAAC,GAAG,EAAE,IAAI,CAAC;QAC3C,MAAM;KACP,CAAA;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { PreparedLayoutToken } from '../types.js';
2
+ export declare function segmentTextForLayout(text: string): Array<Pick<PreparedLayoutToken, 'kind' | 'text'>>;
3
+ export declare function createMeasuredWordToken(text: string, measureWord: (value: string) => number): PreparedLayoutToken;
@@ -0,0 +1,43 @@
1
+ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
2
+ function segmentGraphemes(word) {
3
+ return Array.from(graphemeSegmenter.segment(word), segment => segment.segment);
4
+ }
5
+ export function segmentTextForLayout(text) {
6
+ const normalizedLines = text
7
+ .replace(/\r\n?/g, '\n')
8
+ .split('\n')
9
+ .map(line => line.replace(/[^\S\n]+/g, ' ').trim());
10
+ const tokens = [];
11
+ for (let lineIndex = 0; lineIndex < normalizedLines.length; lineIndex++) {
12
+ const line = normalizedLines[lineIndex];
13
+ if (line.length > 0) {
14
+ const words = line.split(' ');
15
+ for (let wordIndex = 0; wordIndex < words.length; wordIndex++) {
16
+ const word = words[wordIndex];
17
+ if (word.length === 0)
18
+ continue;
19
+ tokens.push({ kind: 'word', text: word });
20
+ }
21
+ }
22
+ if (lineIndex < normalizedLines.length - 1) {
23
+ tokens.push({ kind: 'newline', text: '\n' });
24
+ }
25
+ }
26
+ return tokens;
27
+ }
28
+ export function createMeasuredWordToken(text, measureWord) {
29
+ const graphemes = segmentGraphemes(text);
30
+ const graphemePrefixWidths = [0];
31
+ for (let index = 0; index < graphemes.length; index++) {
32
+ const width = measureWord(graphemes[index]);
33
+ graphemePrefixWidths.push(graphemePrefixWidths[index] + width);
34
+ }
35
+ return {
36
+ kind: 'word',
37
+ text,
38
+ width: graphemePrefixWidths[graphemePrefixWidths.length - 1],
39
+ graphemes,
40
+ graphemePrefixWidths,
41
+ };
42
+ }
43
+ //# sourceMappingURL=segment-text-for-layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"segment-text-for-layout.js","sourceRoot":"","sources":["../../src/text/segment-text-for-layout.ts"],"names":[],"mappings":"AAEA,MAAM,iBAAiB,GAAG,IAAI,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC,CAAA;AAEpF,SAAS,gBAAgB,CAAC,IAAY;IACpC,OAAO,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;AAChF,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,eAAe,GAAG,IAAI;SACzB,OAAO,CAAC,QAAQ,EAAE,IAAI,CAAC;SACvB,KAAK,CAAC,IAAI,CAAC;SACX,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IAErD,MAAM,MAAM,GAAsD,EAAE,CAAA;IAEpE,KAAK,IAAI,SAAS,GAAG,CAAC,EAAE,SAAS,GAAG,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC;QACxE,MAAM,IAAI,GAAG,eAAe,CAAC,SAAS,CAAE,CAAA;QAExC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YAC7B,KAAK,IAAI,SAAS,GAAG,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC;gBAC9D,MAAM,IAAI,GAAG,KAAK,CAAC,SAAS,CAAE,CAAA;gBAC9B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;oBAAE,SAAQ;gBAC/B,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;YAC3C,CAAC;QACH,CAAC;QAED,IAAI,SAAS,GAAG,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3C,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;QAC9C,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAA;AACf,CAAC;AAED,MAAM,UAAU,uBAAuB,CACrC,IAAY,EACZ,WAAsC;IAEtC,MAAM,SAAS,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAA;IACxC,MAAM,oBAAoB,GAAG,CAAC,CAAC,CAAC,CAAA;IAEhC,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,SAAS,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC;QACtD,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC,KAAK,CAAE,CAAC,CAAA;QAC5C,oBAAoB,CAAC,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAE,GAAG,KAAK,CAAC,CAAA;IACjE,CAAC;IAED,OAAO;QACL,IAAI,EAAE,MAAM;QACZ,IAAI;QACJ,KAAK,EAAE,oBAAoB,CAAC,oBAAoB,CAAC,MAAM,GAAG,CAAC,CAAE;QAC7D,SAAS;QACT,oBAAoB;KACrB,CAAA;AACH,CAAC"}
@@ -0,0 +1,200 @@
1
+ export type ShapeTextPoint = {
2
+ x: number;
3
+ y: number;
4
+ };
5
+ export type Interval = {
6
+ left: number;
7
+ right: number;
8
+ };
9
+ export type TextMaskShapeTextMode = 'whole-text' | 'per-character';
10
+ export type TextMaskShapeSizeMode = 'fit-content' | 'fixed';
11
+ export type TextMaskShapeFitContentSize = {
12
+ mode?: 'fit-content';
13
+ padding?: number;
14
+ };
15
+ export type TextMaskShapeFixedSize = {
16
+ mode: 'fixed';
17
+ width: number;
18
+ height: number;
19
+ padding?: number;
20
+ };
21
+ export type TextMaskShapeSize = TextMaskShapeFitContentSize | TextMaskShapeFixedSize;
22
+ export type PolygonShape = {
23
+ kind: 'polygon';
24
+ points: ShapeTextPoint[];
25
+ };
26
+ export type TextMaskShape = {
27
+ kind: 'text-mask';
28
+ text: string;
29
+ font: string;
30
+ size?: TextMaskShapeSize;
31
+ shapeTextMode?: TextMaskShapeTextMode;
32
+ maskScale?: number;
33
+ alphaThreshold?: number;
34
+ };
35
+ export type ShapeInput = PolygonShape | TextMaskShape;
36
+ export type TextMeasurer = {
37
+ measureText(text: string, font: string): number;
38
+ };
39
+ export type TextStyleInput = {
40
+ family: string;
41
+ size: number;
42
+ weight?: number | string;
43
+ style?: 'normal' | 'italic' | 'oblique';
44
+ color?: string;
45
+ };
46
+ export type ResolvedTextStyle = {
47
+ font: string;
48
+ family?: string;
49
+ size?: number;
50
+ weight?: number | string;
51
+ style?: 'normal' | 'italic' | 'oblique';
52
+ color?: string;
53
+ };
54
+ export type ShapeShadowInput = {
55
+ color?: string;
56
+ blur: number;
57
+ offsetX?: number;
58
+ offsetY?: number;
59
+ };
60
+ export type ResolvedShapeShadow = {
61
+ color: string;
62
+ blur: number;
63
+ offsetX: number;
64
+ offsetY: number;
65
+ };
66
+ export type ShapeStyleInput = {
67
+ backgroundColor?: string;
68
+ borderColor?: string;
69
+ borderWidth?: number;
70
+ shadow?: ShapeShadowInput;
71
+ };
72
+ export type ResolvedShapeStyle = {
73
+ backgroundColor?: string;
74
+ borderColor: string;
75
+ borderWidth: number;
76
+ shadow?: ResolvedShapeShadow;
77
+ };
78
+ export type PreparedLayoutToken = {
79
+ kind: 'word' | 'newline';
80
+ text: string;
81
+ width: number;
82
+ graphemes: string[];
83
+ graphemePrefixWidths: number[];
84
+ };
85
+ export type PreparedLayoutText = {
86
+ font: string;
87
+ spaceWidth: number;
88
+ tokens: PreparedLayoutToken[];
89
+ };
90
+ export type LayoutCursor = {
91
+ tokenIndex: number;
92
+ graphemeIndex: number;
93
+ };
94
+ export type LayoutLineRange = {
95
+ text: string;
96
+ width: number;
97
+ start: LayoutCursor;
98
+ end: LayoutCursor;
99
+ };
100
+ export type ShapeTextLine = LayoutLineRange & {
101
+ x: number;
102
+ top: number;
103
+ baseline: number;
104
+ slot: Interval;
105
+ font?: string;
106
+ fillPass?: 1 | 2;
107
+ };
108
+ export type ShapeBounds = {
109
+ left: number;
110
+ top: number;
111
+ right: number;
112
+ bottom: number;
113
+ };
114
+ export type CompiledShapeBand = {
115
+ top: number;
116
+ bottom: number;
117
+ intervals: Interval[];
118
+ };
119
+ export type CompiledShapeDebugView = {
120
+ kind: 'polygon';
121
+ points: ShapeTextPoint[];
122
+ } | {
123
+ kind: 'text';
124
+ text: string;
125
+ font: string;
126
+ x: number;
127
+ baseline: number;
128
+ };
129
+ export type CompiledShapeRegion = {
130
+ index: number;
131
+ grapheme: string;
132
+ bounds: ShapeBounds;
133
+ bands: CompiledShapeBand[];
134
+ debugView: CompiledShapeDebugView;
135
+ };
136
+ export type CompiledShapeBands = {
137
+ kind: ShapeInput['kind'];
138
+ source: ShapeInput;
139
+ bounds: ShapeBounds;
140
+ bandHeight: number;
141
+ minSlotWidth: number;
142
+ bands: CompiledShapeBand[];
143
+ regions?: CompiledShapeRegion[];
144
+ debugView: CompiledShapeDebugView;
145
+ };
146
+ export type ShapeTextLayout = {
147
+ font: string;
148
+ textStyle?: ResolvedTextStyle;
149
+ lineHeight: number;
150
+ shape: ShapeInput;
151
+ compiledShape: CompiledShapeBands;
152
+ bounds: ShapeBounds;
153
+ lines: ShapeTextLine[];
154
+ exhausted: boolean;
155
+ autoFill: boolean;
156
+ };
157
+ export type CompileShapeForLayoutOptions = {
158
+ shape: ShapeInput;
159
+ lineHeight: number;
160
+ minSlotWidth?: number;
161
+ };
162
+ export type LayoutTextInCompiledShapeOptions = {
163
+ text: string;
164
+ compiledShape: CompiledShapeBands;
165
+ measurer: TextMeasurer;
166
+ align?: 'left' | 'center';
167
+ baselineRatio?: number;
168
+ autoFill?: boolean;
169
+ } & ({
170
+ font: string;
171
+ textStyle?: TextStyleInput;
172
+ } | {
173
+ font?: string;
174
+ textStyle: TextStyleInput;
175
+ });
176
+ export type LayoutTextInShapeOptions = {
177
+ text: string;
178
+ lineHeight: number;
179
+ shape: ShapeInput;
180
+ measurer: TextMeasurer;
181
+ align?: 'left' | 'center';
182
+ minSlotWidth?: number;
183
+ baselineRatio?: number;
184
+ autoFill?: boolean;
185
+ } & ({
186
+ font: string;
187
+ textStyle?: TextStyleInput;
188
+ } | {
189
+ font?: string;
190
+ textStyle: TextStyleInput;
191
+ });
192
+ export type RenderLayoutToSvgOptions = {
193
+ padding?: number;
194
+ background?: string;
195
+ textFill?: string;
196
+ shapeStroke?: string;
197
+ shapeFill?: string;
198
+ shapeStyle?: ShapeStyleInput;
199
+ showShape?: boolean;
200
+ };
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,79 @@
1
+ {
2
+ "name": "shape-text",
3
+ "version": "0.1.0",
4
+ "description": "Browser-first TypeScript library for shape-paragraph layout inside geometry and value-derived shapes, rendered to SVG.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "sideEffects": false,
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/tqdat410/shape-text.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/tqdat410/shape-text/issues"
23
+ },
24
+ "homepage": "https://github.com/tqdat410/shape-text#readme",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ },
28
+ "engines": {
29
+ "node": ">=18",
30
+ "bun": ">=1.0.0"
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "LICENSE"
36
+ ],
37
+ "scripts": {
38
+ "build": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-library-build.mjs')\"",
39
+ "typecheck": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('node_modules/typescript/bin/tsc',['-p','tsconfig.json','--noEmit'])\"",
40
+ "test": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('node_modules/vitest/vitest.mjs',['run'])\"",
41
+ "test:coverage": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('node_modules/vitest/vitest.mjs',['run','--coverage'])\"",
42
+ "check": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-project-check.mjs')\"",
43
+ "e2e": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('node_modules/@playwright/test/cli.js',['test'])\"",
44
+ "e2e:ui": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('node_modules/@playwright/test/cli.js',['test','--ui'])\"",
45
+ "e2e:headed": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('node_modules/@playwright/test/cli.js',['test','--headed'])\"",
46
+ "e2e:debug": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('node_modules/@playwright/test/cli.js',['test','--debug'])\"",
47
+ "demo": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-demo-app.mjs')\"",
48
+ "demo:dev": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-demo-app.mjs')\"",
49
+ "demo:build": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-demo-app.mjs',['--build'])\"",
50
+ "demo:preview": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-demo-app.mjs',['--preview'])\"",
51
+ "prepack": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-library-build.mjs')\"",
52
+ "publish:npm": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-manual-npm-publish.mjs', process.argv.slice(1))\" --",
53
+ "ship:check": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/run-library-ship-check.mjs')\"",
54
+ "release:check-tag": "node -e \"const path=require('node:path');let pkg=process.env.npm_package_json||'';if(pkg.charCodeAt(0)===92&&pkg.charCodeAt(1)===92&&pkg.charCodeAt(2)===63&&pkg.charCodeAt(3)===92) pkg=pkg.slice(4);require(path.join(path.dirname(pkg),'scripts','run-node-script-from-package-root.cjs')).run('scripts/validate-release-tag.mjs', process.argv.slice(1))\""
55
+ },
56
+ "keywords": [
57
+ "svg",
58
+ "text-layout",
59
+ "shape-text",
60
+ "shape-paragraph",
61
+ "polygon",
62
+ "typography"
63
+ ],
64
+ "author": "Admin",
65
+ "license": "MIT",
66
+ "devDependencies": {
67
+ "@playwright/test": "^1.58.2",
68
+ "@types/node": "^25.5.0",
69
+ "@types/react": "^19.2.2",
70
+ "@types/react-dom": "^19.2.2",
71
+ "@vitejs/plugin-react": "^5.1.0",
72
+ "@vitest/coverage-v8": "^3.2.4",
73
+ "react": "^19.2.0",
74
+ "react-dom": "^19.2.0",
75
+ "typescript": "^5.9.3",
76
+ "vite": "^7.1.12",
77
+ "vitest": "^3.2.4"
78
+ }
79
+ }