html2canvas-pro 2.1.0 → 2.2.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 (266) hide show
  1. package/dist/html2canvas-pro.esm.js +10226 -10526
  2. package/dist/html2canvas-pro.esm.js.map +1 -1
  3. package/dist/html2canvas-pro.js +10869 -11171
  4. package/dist/html2canvas-pro.js.map +1 -1
  5. package/dist/html2canvas-pro.min.js +8 -8
  6. package/dist/lib/config.js +0 -22
  7. package/dist/lib/core/cache-storage.js +3 -40
  8. package/dist/lib/core/constants.js +25 -0
  9. package/dist/lib/core/context.js +1 -0
  10. package/dist/lib/core/features.js +3 -2
  11. package/dist/lib/core/validator.js +3 -3
  12. package/dist/lib/css/grouped/background-styles.js +36 -0
  13. package/dist/lib/css/grouped/border-styles.js +75 -0
  14. package/dist/lib/css/grouped/font-styles.js +93 -0
  15. package/dist/lib/css/grouped/layout-styles.js +127 -0
  16. package/dist/lib/css/index.js +74 -46
  17. package/dist/lib/css/layout/text.js +7 -6
  18. package/dist/lib/css/property-descriptors/background-blend-mode.js +41 -0
  19. package/dist/lib/css/property-descriptors/border-image-repeat.js +42 -0
  20. package/dist/lib/css/property-descriptors/border-image-slice.js +45 -0
  21. package/dist/lib/css/property-descriptors/border-image-source.js +21 -0
  22. package/dist/lib/css/property-descriptors/border-radius.js +1 -1
  23. package/dist/lib/css/property-descriptors/box-decoration-break.js +18 -0
  24. package/dist/lib/css/property-descriptors/counter-increment.js +17 -12
  25. package/dist/lib/css/property-descriptors/counter-reset.js +4 -12
  26. package/dist/lib/css/property-descriptors/filter.js +76 -0
  27. package/dist/lib/css/property-descriptors/font-variant-ligatures.js +34 -0
  28. package/dist/lib/css/property-descriptors/object-fit.js +1 -1
  29. package/dist/lib/css/property-descriptors/object-position.js +42 -0
  30. package/dist/lib/css/property-descriptors/visibility.js +1 -1
  31. package/dist/lib/css/property-descriptors/zoom.js +18 -0
  32. package/dist/lib/css/syntax/parser.js +0 -1
  33. package/dist/lib/css/types/color.js +5 -1
  34. package/dist/lib/css/types/functions/repeating-linear-gradient.js +9 -0
  35. package/dist/lib/css/types/image.js +12 -2
  36. package/dist/lib/css/types/length-percentage.js +6 -2
  37. package/dist/lib/css/types/safe-eval.js +80 -0
  38. package/dist/lib/dom/document-cloner.js +23 -163
  39. package/dist/lib/dom/slot-cloner.js +176 -0
  40. package/dist/lib/index.js +1 -17
  41. package/dist/lib/render/canvas/background-renderer.js +169 -30
  42. package/dist/lib/render/canvas/border-image-renderer.js +153 -0
  43. package/dist/lib/render/canvas/canvas-renderer.js +39 -190
  44. package/dist/lib/render/canvas/content-renderer.js +202 -0
  45. package/dist/lib/render/canvas/effects-renderer.js +3 -0
  46. package/dist/lib/render/canvas/foreignobject-renderer.js +5 -1
  47. package/dist/lib/render/canvas/text/text-decoration-renderer.js +99 -0
  48. package/dist/lib/render/canvas/text-renderer.js +100 -224
  49. package/dist/lib/render/effects.js +38 -3
  50. package/dist/lib/render/object-fit.js +19 -15
  51. package/dist/lib/render/stacking-context.js +11 -0
  52. package/dist/types/config.d.ts +0 -10
  53. package/dist/types/core/cache-storage.d.ts +0 -24
  54. package/dist/types/core/constants.d.ts +22 -0
  55. package/dist/types/core/context.d.ts +3 -0
  56. package/dist/types/core/performance-monitor.d.ts +4 -4
  57. package/dist/types/core/validator.d.ts +6 -8
  58. package/dist/types/css/grouped/background-styles.d.ts +16 -0
  59. package/dist/types/css/grouped/border-styles.d.ts +31 -0
  60. package/dist/types/css/grouped/font-styles.d.ts +35 -0
  61. package/dist/types/css/grouped/layout-styles.d.ts +46 -0
  62. package/dist/types/css/index.d.ts +30 -0
  63. package/dist/types/css/property-descriptors/background-blend-mode.d.ts +23 -0
  64. package/dist/types/css/property-descriptors/border-image-repeat.d.ts +12 -0
  65. package/dist/types/css/property-descriptors/border-image-slice.d.ts +10 -0
  66. package/dist/types/css/property-descriptors/border-image-source.d.ts +4 -0
  67. package/dist/types/css/property-descriptors/box-decoration-break.d.ts +6 -0
  68. package/dist/types/css/property-descriptors/counter-increment.d.ts +3 -0
  69. package/dist/types/css/property-descriptors/filter.d.ts +3 -0
  70. package/dist/types/css/property-descriptors/font-variant-ligatures.d.ts +14 -0
  71. package/dist/types/css/property-descriptors/object-position.d.ts +4 -0
  72. package/dist/types/css/property-descriptors/zoom.d.ts +3 -0
  73. package/dist/types/css/types/functions/repeating-linear-gradient.d.ts +4 -0
  74. package/dist/types/css/types/image.d.ts +4 -2
  75. package/dist/types/css/types/safe-eval.d.ts +8 -0
  76. package/dist/types/dom/document-cloner.d.ts +3 -44
  77. package/dist/types/dom/slot-cloner.d.ts +66 -0
  78. package/dist/types/index.d.ts +3 -7
  79. package/dist/types/options.d.ts +11 -0
  80. package/dist/types/render/canvas/background-renderer.d.ts +23 -0
  81. package/dist/types/render/canvas/border-image-renderer.d.ts +18 -0
  82. package/dist/types/render/canvas/canvas-renderer.d.ts +1 -0
  83. package/dist/types/render/canvas/content-renderer.d.ts +44 -0
  84. package/dist/types/render/canvas/text/text-decoration-renderer.d.ts +18 -0
  85. package/dist/types/render/canvas/text-renderer.d.ts +12 -1
  86. package/dist/types/render/effects.d.ts +12 -2
  87. package/dist/types/render/object-fit.d.ts +2 -1
  88. package/dist/types/render/renderer-interface.d.ts +11 -9
  89. package/package.json +7 -20
  90. package/dist/lib/dom/replaced-elements/pseudo-elements.js +0 -0
  91. package/dist/lib/invariant.js +0 -9
  92. package/dist/types/dom/replaced-elements/pseudo-elements.d.ts +0 -0
  93. package/dist/types/invariant.d.ts +0 -1
  94. package/src/__tests__/index.ts +0 -99
  95. package/src/config.ts +0 -107
  96. package/src/core/__mocks__/cache-storage.ts +0 -1
  97. package/src/core/__mocks__/context.ts +0 -19
  98. package/src/core/__mocks__/features.ts +0 -8
  99. package/src/core/__mocks__/logger.ts +0 -17
  100. package/src/core/__tests__/cache-storage.test.ts +0 -205
  101. package/src/core/__tests__/cache-storage.ts +0 -278
  102. package/src/core/__tests__/logger.ts +0 -29
  103. package/src/core/__tests__/validator.ts +0 -359
  104. package/src/core/bitwise.ts +0 -1
  105. package/src/core/cache-storage.ts +0 -315
  106. package/src/core/context.ts +0 -31
  107. package/src/core/debugger.ts +0 -32
  108. package/src/core/features.ts +0 -222
  109. package/src/core/logger.ts +0 -64
  110. package/src/core/origin-checker.ts +0 -57
  111. package/src/core/performance-monitor.ts +0 -241
  112. package/src/core/render-element.ts +0 -272
  113. package/src/core/util.ts +0 -1
  114. package/src/core/validator.ts +0 -593
  115. package/src/css/index.ts +0 -427
  116. package/src/css/layout/__mocks__/bounds.ts +0 -6
  117. package/src/css/layout/bounds.ts +0 -79
  118. package/src/css/layout/text.ts +0 -161
  119. package/src/css/property-descriptor.ts +0 -49
  120. package/src/css/property-descriptors/__tests__/background-tests.ts +0 -65
  121. package/src/css/property-descriptors/__tests__/clip-path.test.ts +0 -280
  122. package/src/css/property-descriptors/__tests__/font-family.ts +0 -25
  123. package/src/css/property-descriptors/__tests__/image-rendering-integration.test.ts +0 -153
  124. package/src/css/property-descriptors/__tests__/image-rendering-performance.test.ts +0 -175
  125. package/src/css/property-descriptors/__tests__/image-rendering.test.ts +0 -72
  126. package/src/css/property-descriptors/__tests__/paint-order.ts +0 -87
  127. package/src/css/property-descriptors/__tests__/text-shadow.ts +0 -94
  128. package/src/css/property-descriptors/__tests__/transform-tests.ts +0 -18
  129. package/src/css/property-descriptors/background-clip.ts +0 -30
  130. package/src/css/property-descriptors/background-color.ts +0 -9
  131. package/src/css/property-descriptors/background-image.ts +0 -27
  132. package/src/css/property-descriptors/background-origin.ts +0 -31
  133. package/src/css/property-descriptors/background-position.ts +0 -38
  134. package/src/css/property-descriptors/background-repeat.ts +0 -44
  135. package/src/css/property-descriptors/background-size.ts +0 -27
  136. package/src/css/property-descriptors/border-color.ts +0 -13
  137. package/src/css/property-descriptors/border-radius.ts +0 -19
  138. package/src/css/property-descriptors/border-style.ts +0 -34
  139. package/src/css/property-descriptors/border-width.ts +0 -20
  140. package/src/css/property-descriptors/box-shadow.ts +0 -60
  141. package/src/css/property-descriptors/clip-path.ts +0 -271
  142. package/src/css/property-descriptors/color.ts +0 -9
  143. package/src/css/property-descriptors/content.ts +0 -26
  144. package/src/css/property-descriptors/counter-increment.ts +0 -43
  145. package/src/css/property-descriptors/counter-reset.ts +0 -36
  146. package/src/css/property-descriptors/direction.ts +0 -23
  147. package/src/css/property-descriptors/display.ts +0 -117
  148. package/src/css/property-descriptors/duration.ts +0 -14
  149. package/src/css/property-descriptors/float.ts +0 -29
  150. package/src/css/property-descriptors/font-family.ts +0 -38
  151. package/src/css/property-descriptors/font-size.ts +0 -9
  152. package/src/css/property-descriptors/font-style.ts +0 -25
  153. package/src/css/property-descriptors/font-variant.ts +0 -12
  154. package/src/css/property-descriptors/font-weight.ts +0 -26
  155. package/src/css/property-descriptors/image-rendering.ts +0 -33
  156. package/src/css/property-descriptors/letter-spacing.ts +0 -25
  157. package/src/css/property-descriptors/line-break.ts +0 -22
  158. package/src/css/property-descriptors/line-height.ts +0 -22
  159. package/src/css/property-descriptors/list-style-image.ts +0 -19
  160. package/src/css/property-descriptors/list-style-position.ts +0 -22
  161. package/src/css/property-descriptors/list-style-type.ts +0 -179
  162. package/src/css/property-descriptors/margin.ts +0 -13
  163. package/src/css/property-descriptors/mix-blend-mode.ts +0 -35
  164. package/src/css/property-descriptors/object-fit.ts +0 -39
  165. package/src/css/property-descriptors/opacity.ts +0 -15
  166. package/src/css/property-descriptors/overflow-wrap.ts +0 -22
  167. package/src/css/property-descriptors/overflow.ts +0 -34
  168. package/src/css/property-descriptors/padding.ts +0 -14
  169. package/src/css/property-descriptors/paint-order.ts +0 -42
  170. package/src/css/property-descriptors/position.ts +0 -30
  171. package/src/css/property-descriptors/quotes.ts +0 -57
  172. package/src/css/property-descriptors/rotate.ts +0 -34
  173. package/src/css/property-descriptors/text-align.ts +0 -26
  174. package/src/css/property-descriptors/text-decoration-color.ts +0 -9
  175. package/src/css/property-descriptors/text-decoration-line.ts +0 -38
  176. package/src/css/property-descriptors/text-decoration-style.ts +0 -32
  177. package/src/css/property-descriptors/text-decoration-thickness.ts +0 -30
  178. package/src/css/property-descriptors/text-overflow.ts +0 -23
  179. package/src/css/property-descriptors/text-shadow.ts +0 -52
  180. package/src/css/property-descriptors/text-transform.ts +0 -27
  181. package/src/css/property-descriptors/text-underline-offset.ts +0 -27
  182. package/src/css/property-descriptors/transform-origin.ts +0 -29
  183. package/src/css/property-descriptors/transform.ts +0 -74
  184. package/src/css/property-descriptors/visibility.ts +0 -25
  185. package/src/css/property-descriptors/webkit-line-clamp.ts +0 -30
  186. package/src/css/property-descriptors/webkit-text-stroke-color.ts +0 -8
  187. package/src/css/property-descriptors/webkit-text-stroke-width.ts +0 -15
  188. package/src/css/property-descriptors/word-break.ts +0 -25
  189. package/src/css/property-descriptors/writing-mode.ts +0 -37
  190. package/src/css/property-descriptors/z-index.ts +0 -27
  191. package/src/css/syntax/__tests__/tokernizer-tests.ts +0 -29
  192. package/src/css/syntax/parser.ts +0 -188
  193. package/src/css/syntax/tokenizer.ts +0 -822
  194. package/src/css/type-descriptor.ts +0 -7
  195. package/src/css/types/__tests__/color-tests.ts +0 -147
  196. package/src/css/types/__tests__/image-tests.ts +0 -239
  197. package/src/css/types/angle.ts +0 -86
  198. package/src/css/types/color-math.ts +0 -22
  199. package/src/css/types/color-spaces/a98.ts +0 -86
  200. package/src/css/types/color-spaces/p3.ts +0 -92
  201. package/src/css/types/color-spaces/pro-photo.ts +0 -87
  202. package/src/css/types/color-spaces/rec2020.ts +0 -90
  203. package/src/css/types/color-spaces/srgb.ts +0 -87
  204. package/src/css/types/color-utilities.ts +0 -452
  205. package/src/css/types/color.ts +0 -485
  206. package/src/css/types/functions/-prefix-linear-gradient.ts +0 -35
  207. package/src/css/types/functions/-prefix-radial-gradient.ts +0 -106
  208. package/src/css/types/functions/-webkit-gradient.ts +0 -69
  209. package/src/css/types/functions/__tests__/radial-gradient.ts +0 -69
  210. package/src/css/types/functions/counter.ts +0 -511
  211. package/src/css/types/functions/gradient.ts +0 -206
  212. package/src/css/types/functions/linear-gradient.ts +0 -28
  213. package/src/css/types/functions/radial-gradient.ts +0 -101
  214. package/src/css/types/image.ts +0 -120
  215. package/src/css/types/index.ts +0 -1
  216. package/src/css/types/length-percentage.ts +0 -137
  217. package/src/css/types/length.ts +0 -7
  218. package/src/css/types/time.ts +0 -20
  219. package/src/dom/__mocks__/document-cloner.ts +0 -22
  220. package/src/dom/__tests__/dom-normalizer.test.ts +0 -133
  221. package/src/dom/__tests__/element-container.test.ts +0 -129
  222. package/src/dom/document-cloner.ts +0 -929
  223. package/src/dom/dom-normalizer.ts +0 -133
  224. package/src/dom/element-container.ts +0 -75
  225. package/src/dom/elements/li-element-container.ts +0 -10
  226. package/src/dom/elements/ol-element-container.ts +0 -12
  227. package/src/dom/elements/select-element-container.ts +0 -10
  228. package/src/dom/elements/textarea-element-container.ts +0 -9
  229. package/src/dom/node-parser.ts +0 -177
  230. package/src/dom/node-type-guards.ts +0 -70
  231. package/src/dom/replaced-elements/canvas-element-container.ts +0 -15
  232. package/src/dom/replaced-elements/iframe-element-container.ts +0 -55
  233. package/src/dom/replaced-elements/image-element-container.ts +0 -16
  234. package/src/dom/replaced-elements/index.ts +0 -5
  235. package/src/dom/replaced-elements/input-element-container.ts +0 -105
  236. package/src/dom/replaced-elements/pseudo-elements.ts +0 -0
  237. package/src/dom/replaced-elements/svg-element-container.ts +0 -23
  238. package/src/dom/text-container.ts +0 -42
  239. package/src/global.d.ts +0 -19
  240. package/src/index.ts +0 -82
  241. package/src/invariant.ts +0 -5
  242. package/src/options.ts +0 -55
  243. package/src/render/__tests__/object-fit.test.ts +0 -85
  244. package/src/render/background.ts +0 -298
  245. package/src/render/bezier-curve.ts +0 -47
  246. package/src/render/border.ts +0 -165
  247. package/src/render/bound-curves.ts +0 -388
  248. package/src/render/box-sizing.ts +0 -31
  249. package/src/render/canvas/__tests__/background-renderer.test.ts +0 -72
  250. package/src/render/canvas/__tests__/border-renderer.test.ts +0 -24
  251. package/src/render/canvas/__tests__/effects-renderer.test.ts +0 -32
  252. package/src/render/canvas/__tests__/text-renderer.test.ts +0 -471
  253. package/src/render/canvas/background-renderer.ts +0 -271
  254. package/src/render/canvas/border-renderer.ts +0 -224
  255. package/src/render/canvas/canvas-path.ts +0 -31
  256. package/src/render/canvas/canvas-renderer.ts +0 -641
  257. package/src/render/canvas/effects-renderer.ts +0 -130
  258. package/src/render/canvas/foreignobject-renderer.ts +0 -53
  259. package/src/render/canvas/text-renderer.ts +0 -700
  260. package/src/render/effects.ts +0 -75
  261. package/src/render/font-metrics.ts +0 -72
  262. package/src/render/object-fit.ts +0 -100
  263. package/src/render/path.ts +0 -37
  264. package/src/render/renderer-interface.ts +0 -28
  265. package/src/render/stacking-context.ts +0 -386
  266. package/src/render/vector.ts +0 -19
@@ -1,471 +0,0 @@
1
- import { ok, strictEqual, deepStrictEqual } from 'assert';
2
- import { TextRenderer, TextRendererDependencies, hasCJKCharacters } from '../text-renderer';
3
- import { Context } from '../../../core/context';
4
- import { Bounds } from '../../../css/layout/bounds';
5
- import { TextBounds } from '../../../css/layout/text';
6
- import { Html2CanvasConfig } from '../../../config';
7
- import { WRITING_MODE } from '../../../css/property-descriptors/writing-mode';
8
-
9
- const createMockContext = (): Context => {
10
- const mockWindow = {
11
- document: {
12
- createElement: (_name: string) => {
13
- let _href = '';
14
- return {
15
- set href(value: string) {
16
- _href = value;
17
- },
18
- get href() {
19
- return _href;
20
- },
21
- get protocol() {
22
- return 'http:';
23
- },
24
- get hostname() {
25
- return 'localhost';
26
- },
27
- get port() {
28
- return '';
29
- }
30
- };
31
- }
32
- },
33
- location: { href: 'http://localhost/' }
34
- } as unknown as Window;
35
-
36
- const config = new Html2CanvasConfig({ window: mockWindow });
37
- return new Context(
38
- {
39
- logging: false,
40
- imageTimeout: 15000,
41
- useCORS: false,
42
- allowTaint: false
43
- },
44
- new Bounds(0, 0, 800, 600),
45
- config
46
- );
47
- };
48
-
49
- describe('TextRenderer', () => {
50
- it('should be instantiated', () => {
51
- const ctx = {
52
- fillStyle: '',
53
- font: '',
54
- save: () => {},
55
- restore: () => {}
56
- } as unknown as CanvasRenderingContext2D;
57
-
58
- const deps: TextRendererDependencies = {
59
- ctx,
60
- context: createMockContext(),
61
- options: { scale: 1 }
62
- };
63
-
64
- const renderer = new TextRenderer(deps);
65
- ok(renderer);
66
-
67
- // Test public methods exist
68
- strictEqual(typeof renderer.renderTextNode, 'function');
69
- strictEqual(typeof renderer.renderTextWithLetterSpacing, 'function');
70
- strictEqual(typeof renderer.createFontStyle, 'function');
71
- });
72
- });
73
-
74
- describe('hasCJKCharacters', () => {
75
- it('should return true for Chinese characters', () => {
76
- strictEqual(hasCJKCharacters('快照'), true);
77
- strictEqual(hasCJKCharacters('截图'), true);
78
- strictEqual(hasCJKCharacters('中文'), true);
79
- });
80
-
81
- it('should return true for Japanese characters', () => {
82
- strictEqual(hasCJKCharacters('ひらがな'), true); // Hiragana
83
- strictEqual(hasCJKCharacters('カタカナ'), true); // Katakana
84
- strictEqual(hasCJKCharacters('漢字'), true); // Kanji
85
- });
86
-
87
- it('should return true for Korean characters', () => {
88
- strictEqual(hasCJKCharacters('한글'), true);
89
- });
90
-
91
- it('should return true for CJK punctuation and symbols', () => {
92
- strictEqual(hasCJKCharacters('。'), true); // CJK full stop (U+3002)
93
- strictEqual(hasCJKCharacters('、'), true); // CJK comma (U+3001)
94
- strictEqual(hasCJKCharacters('「」'), true); // CJK brackets
95
- });
96
-
97
- it('should return true for fullwidth characters (U+FF01–U+FFEF)', () => {
98
- strictEqual(hasCJKCharacters('!'), true); // Fullwidth ! (U+FF01, range start)
99
- strictEqual(hasCJKCharacters('A'), true); // Fullwidth A (U+FF21)
100
- strictEqual(hasCJKCharacters('1'), true); // Fullwidth 1 (U+FF11)
101
- });
102
-
103
- it('should return false for Latin characters', () => {
104
- strictEqual(hasCJKCharacters('Hello'), false);
105
- strictEqual(hasCJKCharacters('SOS'), false);
106
- strictEqual(hasCJKCharacters('abc123'), false);
107
- });
108
-
109
- it('should return false for empty string', () => {
110
- strictEqual(hasCJKCharacters(''), false);
111
- });
112
-
113
- it('should return true for mixed text containing CJK', () => {
114
- strictEqual(hasCJKCharacters('SOS 快照'), true);
115
- });
116
- });
117
-
118
- describe('renderTextWithLetterSpacing', () => {
119
- it('should apply letterSpacing to each character x position (Issue #73 Bug1)', () => {
120
- const fillCalls: Array<{ text: string; x: number; y: number }> = [];
121
- const measureResults: Record<string, number> = { A: 10, B: 12, C: 8 };
122
-
123
- const ctx = {
124
- fillStyle: '',
125
- font: '',
126
- textBaseline: 'alphabetic' as CanvasTextBaseline,
127
- fillText(text: string, x: number, y: number) {
128
- fillCalls.push({ text, x, y });
129
- },
130
- measureText(text: string) {
131
- return { width: measureResults[text] ?? 10 };
132
- }
133
- } as unknown as CanvasRenderingContext2D;
134
-
135
- const deps: TextRendererDependencies = {
136
- ctx,
137
- context: createMockContext(),
138
- options: { scale: 1 }
139
- };
140
-
141
- const renderer = new TextRenderer(deps);
142
- const bounds = new Bounds(100, 50, 200, 25);
143
- const text = new TextBounds('ABC', bounds);
144
- const letterSpacing = 5;
145
- const baseline = 20;
146
-
147
- renderer.renderTextWithLetterSpacing(text, letterSpacing, baseline);
148
-
149
- // Verify letter spacing is added between characters
150
- // A: x=100, B: x=100+10+5=115, C: x=115+12+5=132
151
- strictEqual(fillCalls.length, 3);
152
- strictEqual(fillCalls[0].text, 'A');
153
- strictEqual(fillCalls[0].x, 100);
154
- strictEqual(fillCalls[1].text, 'B');
155
- strictEqual(fillCalls[1].x, 115); // 100 + measureText('A').width(10) + letterSpacing(5)
156
- strictEqual(fillCalls[2].text, 'C');
157
- strictEqual(fillCalls[2].x, 132); // 115 + measureText('B').width(12) + letterSpacing(5)
158
-
159
- // Verify y position uses baseline
160
- fillCalls.forEach((call) => {
161
- strictEqual(call.y, bounds.top + baseline); // 50 + 20 = 70
162
- });
163
- });
164
-
165
- it('should use ideographic baseline for CJK characters (Issue #73 Bug2)', () => {
166
- const baselineChanges: string[] = [];
167
- let currentBaseline: CanvasTextBaseline = 'alphabetic';
168
-
169
- const ctx = {
170
- fillStyle: '',
171
- font: '',
172
- get textBaseline(): CanvasTextBaseline {
173
- return currentBaseline;
174
- },
175
- set textBaseline(value: CanvasTextBaseline) {
176
- currentBaseline = value;
177
- baselineChanges.push(value);
178
- },
179
- fillText(_text: string, _x: number, _y: number) {},
180
- measureText(_text: string) {
181
- return { width: 25 };
182
- }
183
- } as unknown as CanvasRenderingContext2D;
184
-
185
- const deps: TextRendererDependencies = {
186
- ctx,
187
- context: createMockContext(),
188
- options: { scale: 1 }
189
- };
190
-
191
- const renderer = new TextRenderer(deps);
192
- const bounds = new Bounds(0, 0, 100, 25);
193
- const text = new TextBounds('快照', bounds);
194
-
195
- renderer.renderTextWithLetterSpacing(text, 10, 20);
196
-
197
- // Should have switched to ideographic for each CJK char and restored
198
- // Pattern: [ideographic, alphabetic, ideographic, alphabetic]
199
- ok(baselineChanges.includes('ideographic'), 'should switch to ideographic baseline for CJK');
200
- // Should restore alphabetic after each CJK char
201
- const ideographicIdx = baselineChanges.indexOf('ideographic');
202
- strictEqual(baselineChanges[ideographicIdx + 1], 'alphabetic');
203
- });
204
-
205
- it('should not change textBaseline for non-CJK characters', () => {
206
- const baselineChanges: string[] = [];
207
- let currentBaseline: CanvasTextBaseline = 'alphabetic';
208
-
209
- const ctx = {
210
- fillStyle: '',
211
- font: '',
212
- get textBaseline(): CanvasTextBaseline {
213
- return currentBaseline;
214
- },
215
- set textBaseline(value: CanvasTextBaseline) {
216
- currentBaseline = value;
217
- baselineChanges.push(value);
218
- },
219
- fillText(_text: string, _x: number, _y: number) {},
220
- measureText(_text: string) {
221
- return { width: 10 };
222
- }
223
- } as unknown as CanvasRenderingContext2D;
224
-
225
- const deps: TextRendererDependencies = {
226
- ctx,
227
- context: createMockContext(),
228
- options: { scale: 1 }
229
- };
230
-
231
- const renderer = new TextRenderer(deps);
232
- const bounds = new Bounds(0, 0, 100, 25);
233
- const text = new TextBounds('ABC', bounds);
234
-
235
- renderer.renderTextWithLetterSpacing(text, 5, 20);
236
-
237
- // Should not switch to ideographic for Latin characters
238
- ok(!baselineChanges.includes('ideographic'), 'should not switch to ideographic for Latin text');
239
- });
240
-
241
- it('should render whole string in one call when letterSpacing is 0', () => {
242
- const fillCalls: Array<{ text: string; x: number; y: number }> = [];
243
-
244
- const ctx = {
245
- fillStyle: '',
246
- font: '',
247
- textBaseline: 'alphabetic' as CanvasTextBaseline,
248
- fillText(text: string, x: number, y: number) {
249
- fillCalls.push({ text, x, y });
250
- },
251
- measureText(_text: string) {
252
- return { width: 30 };
253
- }
254
- } as unknown as CanvasRenderingContext2D;
255
-
256
- const deps: TextRendererDependencies = {
257
- ctx,
258
- context: createMockContext(),
259
- options: { scale: 1 }
260
- };
261
-
262
- const renderer = new TextRenderer(deps);
263
- const bounds = new Bounds(10, 20, 100, 25);
264
- const text = new TextBounds('Hello', bounds);
265
-
266
- renderer.renderTextWithLetterSpacing(text, 0, 22);
267
-
268
- deepStrictEqual(fillCalls, [{ text: 'Hello', x: 10, y: 42 }]);
269
- });
270
-
271
- it('should handle negative letterSpacing correctly', () => {
272
- const fillCalls: Array<{ text: string; x: number }> = [];
273
-
274
- const ctx = {
275
- fillStyle: '',
276
- font: '',
277
- textBaseline: 'alphabetic' as CanvasTextBaseline,
278
- fillText(text: string, x: number, _y: number) {
279
- fillCalls.push({ text, x });
280
- },
281
- measureText(_text: string) {
282
- return { width: 10 };
283
- }
284
- } as unknown as CanvasRenderingContext2D;
285
-
286
- const deps: TextRendererDependencies = {
287
- ctx,
288
- context: createMockContext(),
289
- options: { scale: 1 }
290
- };
291
-
292
- const renderer = new TextRenderer(deps);
293
- const bounds = new Bounds(100, 0, 50, 20);
294
- const text = new TextBounds('AB', bounds);
295
-
296
- renderer.renderTextWithLetterSpacing(text, -3, 15);
297
-
298
- // A: x=100, B: x=100 + 10 + (-3) = 107
299
- strictEqual(fillCalls[0].x, 100);
300
- strictEqual(fillCalls[1].x, 107);
301
- });
302
-
303
- it('should handle mixed CJK and Latin text with correct baseline per character', () => {
304
- const baselineAtRender: Record<string, string> = {};
305
- let currentBaseline: CanvasTextBaseline = 'alphabetic';
306
-
307
- const ctx = {
308
- fillStyle: '',
309
- font: '',
310
- get textBaseline(): CanvasTextBaseline {
311
- return currentBaseline;
312
- },
313
- set textBaseline(value: CanvasTextBaseline) {
314
- currentBaseline = value;
315
- },
316
- fillText(text: string, _x: number, _y: number) {
317
- baselineAtRender[text] = currentBaseline;
318
- },
319
- measureText(_text: string) {
320
- return { width: 12 };
321
- }
322
- } as unknown as CanvasRenderingContext2D;
323
-
324
- const deps: TextRendererDependencies = {
325
- ctx,
326
- context: createMockContext(),
327
- options: { scale: 1 }
328
- };
329
-
330
- const renderer = new TextRenderer(deps);
331
- const bounds = new Bounds(0, 0, 200, 25);
332
- // Mixed: Latin 'A', CJK '快', Latin 'B'
333
- const text = new TextBounds('A快B', bounds);
334
-
335
- renderer.renderTextWithLetterSpacing(text, 5, 20);
336
-
337
- strictEqual(baselineAtRender['A'], 'alphabetic', 'Latin char should use alphabetic baseline');
338
- strictEqual(baselineAtRender['快'], 'ideographic', 'CJK char should use ideographic baseline');
339
- strictEqual(baselineAtRender['B'], 'alphabetic', 'Latin char after CJK should restore alphabetic baseline');
340
- });
341
-
342
- it('should handle empty string without errors', () => {
343
- const ctx = {
344
- fillStyle: '',
345
- font: '',
346
- textBaseline: 'alphabetic' as CanvasTextBaseline,
347
- fillText(_text: string, _x: number, _y: number) {},
348
- measureText(_text: string) {
349
- return { width: 0 };
350
- }
351
- } as unknown as CanvasRenderingContext2D;
352
-
353
- const deps: TextRendererDependencies = {
354
- ctx,
355
- context: createMockContext(),
356
- options: { scale: 1 }
357
- };
358
-
359
- const renderer = new TextRenderer(deps);
360
- const bounds = new Bounds(0, 0, 100, 25);
361
- const text = new TextBounds('', bounds);
362
-
363
- // Should not throw
364
- renderer.renderTextWithLetterSpacing(text, 5, 20);
365
- renderer.renderTextWithLetterSpacing(text, 0, 20);
366
- });
367
-
368
- it('should render CJK glyphs upright in vertical writing mode', () => {
369
- const fillCalls: Array<{ text: string; x: number; y: number }> = [];
370
- const baselineAtRender: Record<string, string> = {};
371
- let currentBaseline: CanvasTextBaseline = 'alphabetic';
372
-
373
- const ctx = {
374
- fillStyle: '',
375
- font: '',
376
- get textBaseline(): CanvasTextBaseline {
377
- return currentBaseline;
378
- },
379
- set textBaseline(value: CanvasTextBaseline) {
380
- currentBaseline = value;
381
- },
382
- fillText(text: string, x: number, y: number) {
383
- baselineAtRender[text] = currentBaseline;
384
- fillCalls.push({ text, x, y });
385
- },
386
- measureText(_text: string) {
387
- return { width: 20 };
388
- },
389
- save() {},
390
- restore() {},
391
- translate(_x: number, _y: number) {},
392
- rotate(_angle: number) {}
393
- } as unknown as CanvasRenderingContext2D;
394
-
395
- const deps: TextRendererDependencies = {
396
- ctx,
397
- context: createMockContext(),
398
- options: { scale: 1 }
399
- };
400
-
401
- const renderer = new TextRenderer(deps);
402
- const bounds = new Bounds(100, 50, 24, 80);
403
- const text = new TextBounds('縦書', bounds);
404
-
405
- renderer.renderTextWithLetterSpacing(text, 4, 18, WRITING_MODE.VERTICAL_RL);
406
-
407
- deepStrictEqual(fillCalls, [
408
- { text: '縦', x: 100, y: 68 },
409
- { text: '書', x: 100, y: 92 }
410
- ]);
411
- strictEqual(baselineAtRender['縦'], 'ideographic');
412
- strictEqual(baselineAtRender['書'], 'ideographic');
413
- strictEqual(currentBaseline, 'alphabetic');
414
- });
415
-
416
- it('should rotate Latin glyphs in vertical writing mode', () => {
417
- const operations: string[] = [];
418
- const fillCalls: Array<{ text: string; x: number; y: number }> = [];
419
-
420
- const ctx = {
421
- fillStyle: '',
422
- font: '',
423
- textBaseline: 'alphabetic' as CanvasTextBaseline,
424
- fillText(text: string, x: number, y: number) {
425
- fillCalls.push({ text, x, y });
426
- },
427
- measureText(_text: string) {
428
- return { width: 12 };
429
- },
430
- save() {
431
- operations.push('save');
432
- },
433
- restore() {
434
- operations.push('restore');
435
- },
436
- translate(x: number, y: number) {
437
- operations.push(`translate:${x},${y}`);
438
- },
439
- rotate(angle: number) {
440
- operations.push(`rotate:${angle}`);
441
- }
442
- } as unknown as CanvasRenderingContext2D;
443
-
444
- const deps: TextRendererDependencies = {
445
- ctx,
446
- context: createMockContext(),
447
- options: { scale: 1 }
448
- };
449
-
450
- const renderer = new TextRenderer(deps);
451
- const bounds = new Bounds(40, 10, 24, 60);
452
- const text = new TextBounds('AB', bounds);
453
-
454
- renderer.renderTextWithLetterSpacing(text, 2, 16, WRITING_MODE.VERTICAL_RL);
455
-
456
- deepStrictEqual(fillCalls, [
457
- { text: 'A', x: 0, y: 0 },
458
- { text: 'B', x: 0, y: 0 }
459
- ]);
460
- deepStrictEqual(operations, [
461
- 'save',
462
- 'translate:56,10',
463
- `rotate:${Math.PI / 2}`,
464
- 'restore',
465
- 'save',
466
- 'translate:56,24',
467
- `rotate:${Math.PI / 2}`,
468
- 'restore'
469
- ]);
470
- });
471
- });