html2canvas-pro 2.1.0 → 2.1.1

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 (186) hide show
  1. package/dist/html2canvas-pro.esm.js +21 -7
  2. package/dist/html2canvas-pro.esm.js.map +1 -1
  3. package/dist/html2canvas-pro.js +21 -7
  4. package/dist/html2canvas-pro.js.map +1 -1
  5. package/dist/html2canvas-pro.min.js +3 -3
  6. package/dist/lib/core/cache-storage.js +2 -2
  7. package/dist/lib/core/features.js +2 -2
  8. package/dist/lib/render/canvas/background-renderer.js +6 -0
  9. package/dist/lib/render/canvas/canvas-renderer.js +5 -1
  10. package/dist/lib/render/canvas/foreignobject-renderer.js +5 -1
  11. package/package.json +3 -11
  12. package/dist/lib/invariant.js +0 -9
  13. package/dist/types/invariant.d.ts +0 -1
  14. package/src/__tests__/index.ts +0 -99
  15. package/src/config.ts +0 -107
  16. package/src/core/__mocks__/cache-storage.ts +0 -1
  17. package/src/core/__mocks__/context.ts +0 -19
  18. package/src/core/__mocks__/features.ts +0 -8
  19. package/src/core/__mocks__/logger.ts +0 -17
  20. package/src/core/__tests__/cache-storage.test.ts +0 -205
  21. package/src/core/__tests__/cache-storage.ts +0 -278
  22. package/src/core/__tests__/logger.ts +0 -29
  23. package/src/core/__tests__/validator.ts +0 -359
  24. package/src/core/bitwise.ts +0 -1
  25. package/src/core/cache-storage.ts +0 -315
  26. package/src/core/context.ts +0 -31
  27. package/src/core/debugger.ts +0 -32
  28. package/src/core/features.ts +0 -222
  29. package/src/core/logger.ts +0 -64
  30. package/src/core/origin-checker.ts +0 -57
  31. package/src/core/performance-monitor.ts +0 -241
  32. package/src/core/render-element.ts +0 -272
  33. package/src/core/util.ts +0 -1
  34. package/src/core/validator.ts +0 -593
  35. package/src/css/index.ts +0 -427
  36. package/src/css/layout/__mocks__/bounds.ts +0 -6
  37. package/src/css/layout/bounds.ts +0 -79
  38. package/src/css/layout/text.ts +0 -161
  39. package/src/css/property-descriptor.ts +0 -49
  40. package/src/css/property-descriptors/__tests__/background-tests.ts +0 -65
  41. package/src/css/property-descriptors/__tests__/clip-path.test.ts +0 -280
  42. package/src/css/property-descriptors/__tests__/font-family.ts +0 -25
  43. package/src/css/property-descriptors/__tests__/image-rendering-integration.test.ts +0 -153
  44. package/src/css/property-descriptors/__tests__/image-rendering-performance.test.ts +0 -175
  45. package/src/css/property-descriptors/__tests__/image-rendering.test.ts +0 -72
  46. package/src/css/property-descriptors/__tests__/paint-order.ts +0 -87
  47. package/src/css/property-descriptors/__tests__/text-shadow.ts +0 -94
  48. package/src/css/property-descriptors/__tests__/transform-tests.ts +0 -18
  49. package/src/css/property-descriptors/background-clip.ts +0 -30
  50. package/src/css/property-descriptors/background-color.ts +0 -9
  51. package/src/css/property-descriptors/background-image.ts +0 -27
  52. package/src/css/property-descriptors/background-origin.ts +0 -31
  53. package/src/css/property-descriptors/background-position.ts +0 -38
  54. package/src/css/property-descriptors/background-repeat.ts +0 -44
  55. package/src/css/property-descriptors/background-size.ts +0 -27
  56. package/src/css/property-descriptors/border-color.ts +0 -13
  57. package/src/css/property-descriptors/border-radius.ts +0 -19
  58. package/src/css/property-descriptors/border-style.ts +0 -34
  59. package/src/css/property-descriptors/border-width.ts +0 -20
  60. package/src/css/property-descriptors/box-shadow.ts +0 -60
  61. package/src/css/property-descriptors/clip-path.ts +0 -271
  62. package/src/css/property-descriptors/color.ts +0 -9
  63. package/src/css/property-descriptors/content.ts +0 -26
  64. package/src/css/property-descriptors/counter-increment.ts +0 -43
  65. package/src/css/property-descriptors/counter-reset.ts +0 -36
  66. package/src/css/property-descriptors/direction.ts +0 -23
  67. package/src/css/property-descriptors/display.ts +0 -117
  68. package/src/css/property-descriptors/duration.ts +0 -14
  69. package/src/css/property-descriptors/float.ts +0 -29
  70. package/src/css/property-descriptors/font-family.ts +0 -38
  71. package/src/css/property-descriptors/font-size.ts +0 -9
  72. package/src/css/property-descriptors/font-style.ts +0 -25
  73. package/src/css/property-descriptors/font-variant.ts +0 -12
  74. package/src/css/property-descriptors/font-weight.ts +0 -26
  75. package/src/css/property-descriptors/image-rendering.ts +0 -33
  76. package/src/css/property-descriptors/letter-spacing.ts +0 -25
  77. package/src/css/property-descriptors/line-break.ts +0 -22
  78. package/src/css/property-descriptors/line-height.ts +0 -22
  79. package/src/css/property-descriptors/list-style-image.ts +0 -19
  80. package/src/css/property-descriptors/list-style-position.ts +0 -22
  81. package/src/css/property-descriptors/list-style-type.ts +0 -179
  82. package/src/css/property-descriptors/margin.ts +0 -13
  83. package/src/css/property-descriptors/mix-blend-mode.ts +0 -35
  84. package/src/css/property-descriptors/object-fit.ts +0 -39
  85. package/src/css/property-descriptors/opacity.ts +0 -15
  86. package/src/css/property-descriptors/overflow-wrap.ts +0 -22
  87. package/src/css/property-descriptors/overflow.ts +0 -34
  88. package/src/css/property-descriptors/padding.ts +0 -14
  89. package/src/css/property-descriptors/paint-order.ts +0 -42
  90. package/src/css/property-descriptors/position.ts +0 -30
  91. package/src/css/property-descriptors/quotes.ts +0 -57
  92. package/src/css/property-descriptors/rotate.ts +0 -34
  93. package/src/css/property-descriptors/text-align.ts +0 -26
  94. package/src/css/property-descriptors/text-decoration-color.ts +0 -9
  95. package/src/css/property-descriptors/text-decoration-line.ts +0 -38
  96. package/src/css/property-descriptors/text-decoration-style.ts +0 -32
  97. package/src/css/property-descriptors/text-decoration-thickness.ts +0 -30
  98. package/src/css/property-descriptors/text-overflow.ts +0 -23
  99. package/src/css/property-descriptors/text-shadow.ts +0 -52
  100. package/src/css/property-descriptors/text-transform.ts +0 -27
  101. package/src/css/property-descriptors/text-underline-offset.ts +0 -27
  102. package/src/css/property-descriptors/transform-origin.ts +0 -29
  103. package/src/css/property-descriptors/transform.ts +0 -74
  104. package/src/css/property-descriptors/visibility.ts +0 -25
  105. package/src/css/property-descriptors/webkit-line-clamp.ts +0 -30
  106. package/src/css/property-descriptors/webkit-text-stroke-color.ts +0 -8
  107. package/src/css/property-descriptors/webkit-text-stroke-width.ts +0 -15
  108. package/src/css/property-descriptors/word-break.ts +0 -25
  109. package/src/css/property-descriptors/writing-mode.ts +0 -37
  110. package/src/css/property-descriptors/z-index.ts +0 -27
  111. package/src/css/syntax/__tests__/tokernizer-tests.ts +0 -29
  112. package/src/css/syntax/parser.ts +0 -188
  113. package/src/css/syntax/tokenizer.ts +0 -822
  114. package/src/css/type-descriptor.ts +0 -7
  115. package/src/css/types/__tests__/color-tests.ts +0 -147
  116. package/src/css/types/__tests__/image-tests.ts +0 -239
  117. package/src/css/types/angle.ts +0 -86
  118. package/src/css/types/color-math.ts +0 -22
  119. package/src/css/types/color-spaces/a98.ts +0 -86
  120. package/src/css/types/color-spaces/p3.ts +0 -92
  121. package/src/css/types/color-spaces/pro-photo.ts +0 -87
  122. package/src/css/types/color-spaces/rec2020.ts +0 -90
  123. package/src/css/types/color-spaces/srgb.ts +0 -87
  124. package/src/css/types/color-utilities.ts +0 -452
  125. package/src/css/types/color.ts +0 -485
  126. package/src/css/types/functions/-prefix-linear-gradient.ts +0 -35
  127. package/src/css/types/functions/-prefix-radial-gradient.ts +0 -106
  128. package/src/css/types/functions/-webkit-gradient.ts +0 -69
  129. package/src/css/types/functions/__tests__/radial-gradient.ts +0 -69
  130. package/src/css/types/functions/counter.ts +0 -511
  131. package/src/css/types/functions/gradient.ts +0 -206
  132. package/src/css/types/functions/linear-gradient.ts +0 -28
  133. package/src/css/types/functions/radial-gradient.ts +0 -101
  134. package/src/css/types/image.ts +0 -120
  135. package/src/css/types/index.ts +0 -1
  136. package/src/css/types/length-percentage.ts +0 -137
  137. package/src/css/types/length.ts +0 -7
  138. package/src/css/types/time.ts +0 -20
  139. package/src/dom/__mocks__/document-cloner.ts +0 -22
  140. package/src/dom/__tests__/dom-normalizer.test.ts +0 -133
  141. package/src/dom/__tests__/element-container.test.ts +0 -129
  142. package/src/dom/document-cloner.ts +0 -929
  143. package/src/dom/dom-normalizer.ts +0 -133
  144. package/src/dom/element-container.ts +0 -75
  145. package/src/dom/elements/li-element-container.ts +0 -10
  146. package/src/dom/elements/ol-element-container.ts +0 -12
  147. package/src/dom/elements/select-element-container.ts +0 -10
  148. package/src/dom/elements/textarea-element-container.ts +0 -9
  149. package/src/dom/node-parser.ts +0 -177
  150. package/src/dom/node-type-guards.ts +0 -70
  151. package/src/dom/replaced-elements/canvas-element-container.ts +0 -15
  152. package/src/dom/replaced-elements/iframe-element-container.ts +0 -55
  153. package/src/dom/replaced-elements/image-element-container.ts +0 -16
  154. package/src/dom/replaced-elements/index.ts +0 -5
  155. package/src/dom/replaced-elements/input-element-container.ts +0 -105
  156. package/src/dom/replaced-elements/pseudo-elements.ts +0 -0
  157. package/src/dom/replaced-elements/svg-element-container.ts +0 -23
  158. package/src/dom/text-container.ts +0 -42
  159. package/src/global.d.ts +0 -19
  160. package/src/index.ts +0 -82
  161. package/src/invariant.ts +0 -5
  162. package/src/options.ts +0 -55
  163. package/src/render/__tests__/object-fit.test.ts +0 -85
  164. package/src/render/background.ts +0 -298
  165. package/src/render/bezier-curve.ts +0 -47
  166. package/src/render/border.ts +0 -165
  167. package/src/render/bound-curves.ts +0 -388
  168. package/src/render/box-sizing.ts +0 -31
  169. package/src/render/canvas/__tests__/background-renderer.test.ts +0 -72
  170. package/src/render/canvas/__tests__/border-renderer.test.ts +0 -24
  171. package/src/render/canvas/__tests__/effects-renderer.test.ts +0 -32
  172. package/src/render/canvas/__tests__/text-renderer.test.ts +0 -471
  173. package/src/render/canvas/background-renderer.ts +0 -271
  174. package/src/render/canvas/border-renderer.ts +0 -224
  175. package/src/render/canvas/canvas-path.ts +0 -31
  176. package/src/render/canvas/canvas-renderer.ts +0 -641
  177. package/src/render/canvas/effects-renderer.ts +0 -130
  178. package/src/render/canvas/foreignobject-renderer.ts +0 -53
  179. package/src/render/canvas/text-renderer.ts +0 -700
  180. package/src/render/effects.ts +0 -75
  181. package/src/render/font-metrics.ts +0 -72
  182. package/src/render/object-fit.ts +0 -100
  183. package/src/render/path.ts +0 -37
  184. package/src/render/renderer-interface.ts +0 -28
  185. package/src/render/stacking-context.ts +0 -386
  186. 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
- });