@mlightcad/mtext-parser 1.0.0 → 1.0.3

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.
@@ -1,733 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const parser_1 = require("./parser");
4
- describe('Utility Functions', () => {
5
- describe('rgb2int', () => {
6
- it('converts RGB tuple to integer', () => {
7
- expect((0, parser_1.rgb2int)([255, 0, 0])).toBe(0x0000ff);
8
- expect((0, parser_1.rgb2int)([0, 255, 0])).toBe(0x00ff00);
9
- expect((0, parser_1.rgb2int)([0, 0, 255])).toBe(0xff0000);
10
- });
11
- });
12
- describe('int2rgb', () => {
13
- it('converts integer to RGB tuple', () => {
14
- expect((0, parser_1.int2rgb)(0x0000ff)).toEqual([255, 0, 0]);
15
- expect((0, parser_1.int2rgb)(0x00ff00)).toEqual([0, 255, 0]);
16
- expect((0, parser_1.int2rgb)(0xff0000)).toEqual([0, 0, 255]);
17
- });
18
- });
19
- describe('caretDecode', () => {
20
- it('handles control characters', () => {
21
- expect((0, parser_1.caretDecode)('^I')).toBe('\t'); // Tabulator
22
- expect((0, parser_1.caretDecode)('^J')).toBe('\n'); // Line feed
23
- expect((0, parser_1.caretDecode)('^M')).toBe(''); // Carriage return is ignored
24
- });
25
- it('handles space after caret', () => {
26
- expect((0, parser_1.caretDecode)('1^ 2')).toBe('1^2');
27
- expect((0, parser_1.caretDecode)('^ ')).toBe('^');
28
- });
29
- it('renders empty square for unknown characters', () => {
30
- expect((0, parser_1.caretDecode)('^!')).toBe('▯');
31
- expect((0, parser_1.caretDecode)('^?')).toBe('▯');
32
- expect((0, parser_1.caretDecode)('^#')).toBe('▯');
33
- expect((0, parser_1.caretDecode)('^a')).toBe('▯');
34
- expect((0, parser_1.caretDecode)('^z')).toBe('▯');
35
- expect((0, parser_1.caretDecode)('^@')).toBe('▯');
36
- expect((0, parser_1.caretDecode)('^A')).toBe('▯');
37
- expect((0, parser_1.caretDecode)('^Z')).toBe('▯');
38
- expect((0, parser_1.caretDecode)('^[')).toBe('▯');
39
- expect((0, parser_1.caretDecode)('^\\')).toBe('▯');
40
- expect((0, parser_1.caretDecode)('^]')).toBe('▯');
41
- expect((0, parser_1.caretDecode)('^^')).toBe('▯');
42
- expect((0, parser_1.caretDecode)('^_')).toBe('▯');
43
- });
44
- it('handles mixed cases', () => {
45
- expect((0, parser_1.caretDecode)('Hello^JWorld')).toBe('Hello\nWorld');
46
- expect((0, parser_1.caretDecode)('Tab^ISpace^ ')).toBe('Tab\tSpace^');
47
- expect((0, parser_1.caretDecode)('^M^J^I')).toBe('\n\t');
48
- expect((0, parser_1.caretDecode)('Text^!More')).toBe('Text▯More');
49
- });
50
- });
51
- describe('escapeDxfLineEndings', () => {
52
- it('escapes line endings', () => {
53
- expect((0, parser_1.escapeDxfLineEndings)('line1\r\nline2')).toBe('line1\\Pline2');
54
- expect((0, parser_1.escapeDxfLineEndings)('line1\nline2')).toBe('line1\\Pline2');
55
- expect((0, parser_1.escapeDxfLineEndings)('line1\rline2')).toBe('line1\\Pline2');
56
- });
57
- });
58
- describe('hasInlineFormattingCodes', () => {
59
- it('detects inline formatting codes', () => {
60
- expect((0, parser_1.hasInlineFormattingCodes)('\\L')).toBe(true);
61
- expect((0, parser_1.hasInlineFormattingCodes)('\\P')).toBe(false);
62
- expect((0, parser_1.hasInlineFormattingCodes)('\\~')).toBe(false);
63
- expect((0, parser_1.hasInlineFormattingCodes)('normal text')).toBe(false);
64
- });
65
- });
66
- });
67
- describe('MTextContext', () => {
68
- let ctx;
69
- beforeEach(() => {
70
- ctx = new parser_1.MTextContext();
71
- });
72
- it('initializes with default values', () => {
73
- expect(ctx.aci).toBe(7);
74
- expect(ctx.rgb).toBeNull();
75
- expect(ctx.align).toBe(parser_1.MTextLineAlignment.BOTTOM);
76
- expect(ctx.fontFace).toEqual({ family: '', style: 'Regular', weight: 400 });
77
- expect(ctx.capHeight).toBe(1.0);
78
- expect(ctx.widthFactor).toBe(1.0);
79
- expect(ctx.charTrackingFactor).toBe(1.0);
80
- expect(ctx.oblique).toBe(0.0);
81
- expect(ctx.paragraph).toEqual({
82
- indent: 0,
83
- left: 0,
84
- right: 0,
85
- align: parser_1.MTextParagraphAlignment.DEFAULT,
86
- tab_stops: [],
87
- });
88
- });
89
- describe('stroke properties', () => {
90
- it('handles underline', () => {
91
- ctx.underline = true;
92
- expect(ctx.underline).toBe(true);
93
- expect(ctx.hasAnyStroke).toBe(true);
94
- ctx.underline = false;
95
- expect(ctx.underline).toBe(false);
96
- expect(ctx.hasAnyStroke).toBe(false);
97
- });
98
- it('handles overline', () => {
99
- ctx.overline = true;
100
- expect(ctx.overline).toBe(true);
101
- expect(ctx.hasAnyStroke).toBe(true);
102
- ctx.overline = false;
103
- expect(ctx.overline).toBe(false);
104
- expect(ctx.hasAnyStroke).toBe(false);
105
- });
106
- it('handles strike-through', () => {
107
- ctx.strikeThrough = true;
108
- expect(ctx.strikeThrough).toBe(true);
109
- expect(ctx.hasAnyStroke).toBe(true);
110
- ctx.strikeThrough = false;
111
- expect(ctx.strikeThrough).toBe(false);
112
- expect(ctx.hasAnyStroke).toBe(false);
113
- });
114
- it('handles multiple strokes', () => {
115
- ctx.underline = true;
116
- ctx.overline = true;
117
- expect(ctx.hasAnyStroke).toBe(true);
118
- ctx.underline = false;
119
- expect(ctx.hasAnyStroke).toBe(true);
120
- ctx.overline = false;
121
- expect(ctx.hasAnyStroke).toBe(false);
122
- });
123
- });
124
- describe('color properties', () => {
125
- it('handles ACI color', () => {
126
- ctx.aci = 1;
127
- expect(ctx.aci).toBe(1);
128
- expect(ctx.rgb).toBeNull();
129
- expect(() => (ctx.aci = 257)).toThrow('ACI not in range [0, 256]');
130
- });
131
- it('handles RGB color', () => {
132
- ctx.rgb = [255, 0, 0];
133
- expect(ctx.rgb).toEqual([255, 0, 0]);
134
- });
135
- });
136
- describe('copy', () => {
137
- it('creates a deep copy', () => {
138
- ctx.underline = true;
139
- ctx.rgb = [255, 0, 0];
140
- const copy = ctx.copy();
141
- expect(copy).not.toBe(ctx);
142
- expect(copy.underline).toBe(ctx.underline);
143
- expect(copy.rgb).toEqual(ctx.rgb);
144
- expect(copy.fontFace).toEqual(ctx.fontFace);
145
- expect(copy.paragraph).toEqual(ctx.paragraph);
146
- });
147
- });
148
- });
149
- describe('MTextParser', () => {
150
- describe('basic parsing', () => {
151
- it('parses plain text', () => {
152
- const parser = new parser_1.MTextParser('Hello World');
153
- const tokens = Array.from(parser.parse());
154
- expect(tokens).toHaveLength(3);
155
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
156
- expect(tokens[0].data).toBe('Hello');
157
- expect(tokens[1].type).toBe(parser_1.TokenType.SPACE);
158
- expect(tokens[2].type).toBe(parser_1.TokenType.WORD);
159
- expect(tokens[2].data).toBe('World');
160
- });
161
- it('parses spaces', () => {
162
- const parser = new parser_1.MTextParser('Hello World');
163
- const tokens = Array.from(parser.parse());
164
- expect(tokens).toHaveLength(3);
165
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
166
- expect(tokens[0].data).toBe('Hello');
167
- expect(tokens[1].type).toBe(parser_1.TokenType.SPACE);
168
- expect(tokens[2].type).toBe(parser_1.TokenType.WORD);
169
- expect(tokens[2].data).toBe('World');
170
- });
171
- it('parses text starting with control characters', () => {
172
- // Test with newline
173
- let parser = new parser_1.MTextParser('\nHello World');
174
- let tokens = Array.from(parser.parse());
175
- expect(tokens).toHaveLength(4);
176
- expect(tokens[0].type).toBe(parser_1.TokenType.NEW_PARAGRAPH);
177
- expect(tokens[1].type).toBe(parser_1.TokenType.WORD);
178
- expect(tokens[1].data).toBe('Hello');
179
- expect(tokens[2].type).toBe(parser_1.TokenType.SPACE);
180
- expect(tokens[3].type).toBe(parser_1.TokenType.WORD);
181
- expect(tokens[3].data).toBe('World');
182
- // Test with tab
183
- parser = new parser_1.MTextParser('\tHello World');
184
- tokens = Array.from(parser.parse());
185
- expect(tokens).toHaveLength(4);
186
- expect(tokens[0].type).toBe(parser_1.TokenType.TABULATOR);
187
- expect(tokens[1].type).toBe(parser_1.TokenType.WORD);
188
- expect(tokens[1].data).toBe('Hello');
189
- expect(tokens[2].type).toBe(parser_1.TokenType.SPACE);
190
- expect(tokens[3].type).toBe(parser_1.TokenType.WORD);
191
- expect(tokens[3].data).toBe('World');
192
- // Test with multiple control characters
193
- parser = new parser_1.MTextParser('\n\tHello World');
194
- tokens = Array.from(parser.parse());
195
- expect(tokens).toHaveLength(5);
196
- expect(tokens[0].type).toBe(parser_1.TokenType.NEW_PARAGRAPH);
197
- expect(tokens[1].type).toBe(parser_1.TokenType.TABULATOR);
198
- expect(tokens[2].type).toBe(parser_1.TokenType.WORD);
199
- expect(tokens[2].data).toBe('Hello');
200
- expect(tokens[3].type).toBe(parser_1.TokenType.SPACE);
201
- expect(tokens[4].type).toBe(parser_1.TokenType.WORD);
202
- expect(tokens[4].data).toBe('World');
203
- });
204
- it('parses new paragraphs', () => {
205
- const parser = new parser_1.MTextParser('Line 1\\PLine 2');
206
- const tokens = Array.from(parser.parse());
207
- expect(tokens).toHaveLength(7);
208
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
209
- expect(tokens[0].data).toBe('Line');
210
- expect(tokens[1].type).toBe(parser_1.TokenType.SPACE);
211
- expect(tokens[2].type).toBe(parser_1.TokenType.WORD);
212
- expect(tokens[2].data).toBe('1');
213
- expect(tokens[3].type).toBe(parser_1.TokenType.NEW_PARAGRAPH);
214
- expect(tokens[4].type).toBe(parser_1.TokenType.WORD);
215
- expect(tokens[4].data).toBe('Line');
216
- expect(tokens[5].type).toBe(parser_1.TokenType.SPACE);
217
- expect(tokens[6].type).toBe(parser_1.TokenType.WORD);
218
- expect(tokens[6].data).toBe('2');
219
- });
220
- });
221
- describe('formatting', () => {
222
- it('parses underline', () => {
223
- const parser = new parser_1.MTextParser('\\LUnderlined\\l');
224
- const tokens = Array.from(parser.parse());
225
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
226
- expect(tokens[0].data).toBe('Underlined');
227
- expect(tokens[0].ctx.underline).toBe(true);
228
- });
229
- it('parses color', () => {
230
- const parser = new parser_1.MTextParser('\\C1Red Text');
231
- const tokens = Array.from(parser.parse());
232
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
233
- expect(tokens[0].data).toBe('Red');
234
- expect(tokens[0].ctx.aci).toBe(1);
235
- });
236
- it('parses font properties', () => {
237
- const parser = new parser_1.MTextParser('\\FArial|b1|i1;Bold Italic');
238
- const tokens = Array.from(parser.parse());
239
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
240
- expect(tokens[0].data).toBe('Bold');
241
- expect(tokens[0].ctx.fontFace).toEqual({
242
- family: 'Arial',
243
- style: 'Italic',
244
- weight: 700,
245
- });
246
- });
247
- describe('height command', () => {
248
- it('parses absolute height values', () => {
249
- const parser = new parser_1.MTextParser('\\H2.5;Text');
250
- const tokens = Array.from(parser.parse());
251
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
252
- expect(tokens[0].data).toBe('Text');
253
- expect(tokens[0].ctx.capHeight).toBe(2.5);
254
- });
255
- it('parses relative height values with x suffix', () => {
256
- const parser = new parser_1.MTextParser('\\H2.5x;Text');
257
- const tokens = Array.from(parser.parse());
258
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
259
- expect(tokens[0].data).toBe('Text');
260
- expect(tokens[0].ctx.capHeight).toBe(2.5);
261
- });
262
- it('handles optional terminator', () => {
263
- const parser = new parser_1.MTextParser('\\H2.5Text');
264
- const tokens = Array.from(parser.parse());
265
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
266
- expect(tokens[0].data).toBe('Text');
267
- expect(tokens[0].ctx.capHeight).toBe(2.5);
268
- });
269
- it('handles leading signs', () => {
270
- let parser = new parser_1.MTextParser('\\H-2.5;Text');
271
- let tokens = Array.from(parser.parse());
272
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
273
- expect(tokens[0].data).toBe('Text');
274
- expect(tokens[0].ctx.capHeight).toBe(2.5); // Negative values are ignored
275
- parser = new parser_1.MTextParser('\\H+2.5;Text');
276
- tokens = Array.from(parser.parse());
277
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
278
- expect(tokens[0].data).toBe('Text');
279
- expect(tokens[0].ctx.capHeight).toBe(2.5);
280
- });
281
- it('handles decimal values without leading zero', () => {
282
- let parser = new parser_1.MTextParser('\\H.5x;Text');
283
- let tokens = Array.from(parser.parse());
284
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
285
- expect(tokens[0].data).toBe('Text');
286
- expect(tokens[0].ctx.capHeight).toBe(0.5);
287
- parser = new parser_1.MTextParser('\\H-.5x;Text');
288
- tokens = Array.from(parser.parse());
289
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
290
- expect(tokens[0].data).toBe('Text');
291
- expect(tokens[0].ctx.capHeight).toBe(0.5); // Negative values are ignored
292
- parser = new parser_1.MTextParser('\\H+.5x;Text');
293
- tokens = Array.from(parser.parse());
294
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
295
- expect(tokens[0].data).toBe('Text');
296
- expect(tokens[0].ctx.capHeight).toBe(0.5);
297
- });
298
- it('handles exponential notation', () => {
299
- let parser = new parser_1.MTextParser('\\H1e2;Text');
300
- let tokens = Array.from(parser.parse());
301
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
302
- expect(tokens[0].data).toBe('Text');
303
- expect(tokens[0].ctx.capHeight).toBe(100);
304
- parser = new parser_1.MTextParser('\\H1e-2;Text');
305
- tokens = Array.from(parser.parse());
306
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
307
- expect(tokens[0].data).toBe('Text');
308
- expect(tokens[0].ctx.capHeight).toBe(0.01);
309
- parser = new parser_1.MTextParser('\\H.5e2;Text');
310
- tokens = Array.from(parser.parse());
311
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
312
- expect(tokens[0].data).toBe('Text');
313
- expect(tokens[0].ctx.capHeight).toBe(50);
314
- parser = new parser_1.MTextParser('\\H.5e-2;Text');
315
- tokens = Array.from(parser.parse());
316
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
317
- expect(tokens[0].data).toBe('Text');
318
- expect(tokens[0].ctx.capHeight).toBe(0.005);
319
- });
320
- it('handles invalid floating point values', () => {
321
- let parser = new parser_1.MTextParser('\\H1..5;Text');
322
- let tokens = Array.from(parser.parse());
323
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
324
- expect(tokens[0].data).toBe('.5;Text');
325
- expect(tokens[0].ctx.capHeight).toBe(1.0); // Default value
326
- parser = new parser_1.MTextParser('\\H1e;Text');
327
- tokens = Array.from(parser.parse());
328
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
329
- expect(tokens[0].data).toBe('e;Text');
330
- expect(tokens[0].ctx.capHeight).toBe(1.0); // Default value
331
- parser = new parser_1.MTextParser('\\H1e+;Text');
332
- tokens = Array.from(parser.parse());
333
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
334
- expect(tokens[0].data).toBe('e+;Text');
335
- expect(tokens[0].ctx.capHeight).toBe(1.0); // Default value
336
- });
337
- it('handles complex height expressions', () => {
338
- let parser = new parser_1.MTextParser('\\H+1.5e-1x;Text');
339
- let tokens = Array.from(parser.parse());
340
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
341
- expect(tokens[0].data).toBe('Text');
342
- expect(tokens[0].ctx.capHeight).toBe(0.15);
343
- parser = new parser_1.MTextParser('\\H-.5e+2x;Text');
344
- tokens = Array.from(parser.parse());
345
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
346
- expect(tokens[0].data).toBe('Text');
347
- expect(tokens[0].ctx.capHeight).toBe(50); // Negative values are ignored
348
- });
349
- it('handles multiple height commands', () => {
350
- const parser = new parser_1.MTextParser('\\H2.5;First\\H.5x;Second');
351
- const tokens = Array.from(parser.parse());
352
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
353
- expect(tokens[0].data).toBe('First');
354
- expect(tokens[0].ctx.capHeight).toBe(2.5);
355
- expect(tokens[1].type).toBe(parser_1.TokenType.WORD);
356
- expect(tokens[1].data).toBe('Second');
357
- expect(tokens[1].ctx.capHeight).toBe(0.5);
358
- });
359
- it('handles height command with no value', () => {
360
- const parser = new parser_1.MTextParser('\\H;Text');
361
- const tokens = Array.from(parser.parse());
362
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
363
- expect(tokens[0].data).toBe('Text');
364
- expect(tokens[0].ctx.capHeight).toBe(1.0); // Default value
365
- });
366
- });
367
- describe('width command', () => {
368
- it('parses absolute width values', () => {
369
- const parser = new parser_1.MTextParser('\\W2.5;Text');
370
- const tokens = Array.from(parser.parse());
371
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
372
- expect(tokens[0].data).toBe('Text');
373
- expect(tokens[0].ctx.widthFactor).toBe(2.5);
374
- });
375
- it('parses relative width values with x suffix', () => {
376
- const parser = new parser_1.MTextParser('\\W2.5x;Text');
377
- const tokens = Array.from(parser.parse());
378
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
379
- expect(tokens[0].data).toBe('Text');
380
- expect(tokens[0].ctx.widthFactor).toBe(2.5);
381
- });
382
- it('handles optional terminator', () => {
383
- const parser = new parser_1.MTextParser('\\W2.5Text');
384
- const tokens = Array.from(parser.parse());
385
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
386
- expect(tokens[0].data).toBe('Text');
387
- expect(tokens[0].ctx.widthFactor).toBe(2.5);
388
- });
389
- it('handles leading signs', () => {
390
- let parser = new parser_1.MTextParser('\\W-2.5;Text');
391
- let tokens = Array.from(parser.parse());
392
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
393
- expect(tokens[0].data).toBe('Text');
394
- expect(tokens[0].ctx.widthFactor).toBe(2.5); // Negative values are ignored
395
- parser = new parser_1.MTextParser('\\W+2.5;Text');
396
- tokens = Array.from(parser.parse());
397
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
398
- expect(tokens[0].data).toBe('Text');
399
- expect(tokens[0].ctx.widthFactor).toBe(2.5);
400
- });
401
- it('handles decimal values without leading zero', () => {
402
- let parser = new parser_1.MTextParser('\\W.5x;Text');
403
- let tokens = Array.from(parser.parse());
404
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
405
- expect(tokens[0].data).toBe('Text');
406
- expect(tokens[0].ctx.widthFactor).toBe(0.5);
407
- parser = new parser_1.MTextParser('\\W-.5x;Text');
408
- tokens = Array.from(parser.parse());
409
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
410
- expect(tokens[0].data).toBe('Text');
411
- expect(tokens[0].ctx.widthFactor).toBe(0.5); // Negative values are ignored
412
- });
413
- it('handles multiple width commands', () => {
414
- const parser = new parser_1.MTextParser('\\W2.5;First\\W.5x;Second');
415
- const tokens = Array.from(parser.parse());
416
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
417
- expect(tokens[0].data).toBe('First');
418
- expect(tokens[0].ctx.widthFactor).toBe(2.5);
419
- expect(tokens[1].type).toBe(parser_1.TokenType.WORD);
420
- expect(tokens[1].data).toBe('Second');
421
- expect(tokens[1].ctx.widthFactor).toBe(0.5);
422
- });
423
- });
424
- describe('character tracking command', () => {
425
- it('parses absolute tracking values', () => {
426
- const parser = new parser_1.MTextParser('\\T2.5;Text');
427
- const tokens = Array.from(parser.parse());
428
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
429
- expect(tokens[0].data).toBe('Text');
430
- expect(tokens[0].ctx.charTrackingFactor).toBe(2.5);
431
- });
432
- it('parses relative tracking values with x suffix', () => {
433
- const parser = new parser_1.MTextParser('\\T2.5x;Text');
434
- const tokens = Array.from(parser.parse());
435
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
436
- expect(tokens[0].data).toBe('Text');
437
- expect(tokens[0].ctx.charTrackingFactor).toBe(2.5);
438
- });
439
- it('handles optional terminator', () => {
440
- const parser = new parser_1.MTextParser('\\T2.5Text');
441
- const tokens = Array.from(parser.parse());
442
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
443
- expect(tokens[0].data).toBe('Text');
444
- expect(tokens[0].ctx.charTrackingFactor).toBe(2.5);
445
- });
446
- it('handles leading signs', () => {
447
- let parser = new parser_1.MTextParser('\\T-2.5;Text');
448
- let tokens = Array.from(parser.parse());
449
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
450
- expect(tokens[0].data).toBe('Text');
451
- expect(tokens[0].ctx.charTrackingFactor).toBe(2.5); // Negative values are ignored
452
- parser = new parser_1.MTextParser('\\T+2.5;Text');
453
- tokens = Array.from(parser.parse());
454
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
455
- expect(tokens[0].data).toBe('Text');
456
- expect(tokens[0].ctx.charTrackingFactor).toBe(2.5);
457
- });
458
- it('handles decimal values without leading zero', () => {
459
- let parser = new parser_1.MTextParser('\\T.5x;Text');
460
- let tokens = Array.from(parser.parse());
461
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
462
- expect(tokens[0].data).toBe('Text');
463
- expect(tokens[0].ctx.charTrackingFactor).toBe(0.5);
464
- parser = new parser_1.MTextParser('\\T-.5x;Text');
465
- tokens = Array.from(parser.parse());
466
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
467
- expect(tokens[0].data).toBe('Text');
468
- expect(tokens[0].ctx.charTrackingFactor).toBe(0.5); // Negative values are ignored
469
- });
470
- it('handles multiple tracking commands', () => {
471
- const parser = new parser_1.MTextParser('\\T2.5;First\\T.5x;Second');
472
- const tokens = Array.from(parser.parse());
473
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
474
- expect(tokens[0].data).toBe('First');
475
- expect(tokens[0].ctx.charTrackingFactor).toBe(2.5);
476
- expect(tokens[1].type).toBe(parser_1.TokenType.WORD);
477
- expect(tokens[1].data).toBe('Second');
478
- expect(tokens[1].ctx.charTrackingFactor).toBe(0.5);
479
- });
480
- });
481
- describe('oblique command', () => {
482
- it('parses positive oblique angle', () => {
483
- const parser = new parser_1.MTextParser('\\Q15;Text');
484
- const tokens = Array.from(parser.parse());
485
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
486
- expect(tokens[0].data).toBe('Text');
487
- expect(tokens[0].ctx.oblique).toBe(15);
488
- });
489
- it('parses negative oblique angle', () => {
490
- const parser = new parser_1.MTextParser('\\Q-15;Text');
491
- const tokens = Array.from(parser.parse());
492
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
493
- expect(tokens[0].data).toBe('Text');
494
- expect(tokens[0].ctx.oblique).toBe(-15);
495
- });
496
- it('handles optional terminator', () => {
497
- const parser = new parser_1.MTextParser('\\Q15Text');
498
- const tokens = Array.from(parser.parse());
499
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
500
- expect(tokens[0].data).toBe('Text');
501
- expect(tokens[0].ctx.oblique).toBe(15);
502
- });
503
- it('handles decimal values', () => {
504
- const parser = new parser_1.MTextParser('\\Q15.5;Text');
505
- const tokens = Array.from(parser.parse());
506
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
507
- expect(tokens[0].data).toBe('Text');
508
- expect(tokens[0].ctx.oblique).toBe(15.5);
509
- });
510
- it('handles multiple oblique commands', () => {
511
- const parser = new parser_1.MTextParser('\\Q15;First\\Q-30;Second');
512
- const tokens = Array.from(parser.parse());
513
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
514
- expect(tokens[0].data).toBe('First');
515
- expect(tokens[0].ctx.oblique).toBe(15);
516
- expect(tokens[1].type).toBe(parser_1.TokenType.WORD);
517
- expect(tokens[1].data).toBe('Second');
518
- expect(tokens[1].ctx.oblique).toBe(-30);
519
- });
520
- });
521
- describe('special encoded characters', () => {
522
- it('renders diameter symbol (%%c)', () => {
523
- let parser = new parser_1.MTextParser('%%cText');
524
- let tokens = Array.from(parser.parse());
525
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
526
- expect(tokens[0].data).toBe('ØText');
527
- parser = new parser_1.MTextParser('%%CText');
528
- tokens = Array.from(parser.parse());
529
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
530
- expect(tokens[0].data).toBe('ØText');
531
- });
532
- it('renders degree symbol (%%d)', () => {
533
- let parser = new parser_1.MTextParser('%%dText');
534
- let tokens = Array.from(parser.parse());
535
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
536
- expect(tokens[0].data).toBe('°Text');
537
- parser = new parser_1.MTextParser('%%DText');
538
- tokens = Array.from(parser.parse());
539
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
540
- expect(tokens[0].data).toBe('°Text');
541
- });
542
- it('renders plus-minus symbol (%%p)', () => {
543
- let parser = new parser_1.MTextParser('%%pText');
544
- let tokens = Array.from(parser.parse());
545
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
546
- expect(tokens[0].data).toBe('±Text');
547
- parser = new parser_1.MTextParser('%%PText');
548
- tokens = Array.from(parser.parse());
549
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
550
- expect(tokens[0].data).toBe('±Text');
551
- });
552
- it('handles multiple special characters in sequence', () => {
553
- const parser = new parser_1.MTextParser('%%c%%d%%pText');
554
- const tokens = Array.from(parser.parse());
555
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
556
- expect(tokens[0].data).toBe('ذ±Text');
557
- });
558
- it('handles special characters with spaces', () => {
559
- const parser = new parser_1.MTextParser('%%c %%d %%p Text');
560
- const tokens = Array.from(parser.parse());
561
- expect(tokens).toHaveLength(7);
562
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
563
- expect(tokens[0].data).toBe('Ø');
564
- expect(tokens[1].type).toBe(parser_1.TokenType.SPACE);
565
- expect(tokens[2].type).toBe(parser_1.TokenType.WORD);
566
- expect(tokens[2].data).toBe('°');
567
- expect(tokens[3].type).toBe(parser_1.TokenType.SPACE);
568
- expect(tokens[4].type).toBe(parser_1.TokenType.WORD);
569
- expect(tokens[4].data).toBe('±');
570
- expect(tokens[5].type).toBe(parser_1.TokenType.SPACE);
571
- expect(tokens[6].type).toBe(parser_1.TokenType.WORD);
572
- expect(tokens[6].data).toBe('Text');
573
- });
574
- it('handles special characters with formatting', () => {
575
- const parser = new parser_1.MTextParser('\\H2.5;%%c\\H.5x;%%d%%p');
576
- const tokens = Array.from(parser.parse());
577
- expect(tokens).toHaveLength(2);
578
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
579
- expect(tokens[0].data).toBe('Ø');
580
- expect(tokens[0].ctx.capHeight).toBe(2.5);
581
- expect(tokens[1].type).toBe(parser_1.TokenType.WORD);
582
- expect(tokens[1].data).toBe('°±');
583
- expect(tokens[1].ctx.capHeight).toBe(0.5);
584
- });
585
- it('handles invalid special character codes', () => {
586
- const parser = new parser_1.MTextParser('%%x%%y%%zText');
587
- const tokens = Array.from(parser.parse());
588
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
589
- expect(tokens[0].data).toBe('Text');
590
- });
591
- });
592
- });
593
- describe('stacking', () => {
594
- it('parses basic fractions with different dividers', () => {
595
- let parser = new parser_1.MTextParser('\\S1/2;');
596
- let tokens = Array.from(parser.parse());
597
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
598
- expect(tokens[0].data).toEqual(['1', '2', '/']);
599
- parser = new parser_1.MTextParser('\\S1^ 2;');
600
- tokens = Array.from(parser.parse());
601
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
602
- expect(tokens[0].data).toEqual(['1', '2', '^']);
603
- parser = new parser_1.MTextParser('\\S1#2;');
604
- tokens = Array.from(parser.parse());
605
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
606
- expect(tokens[0].data).toEqual(['1', '2', '#']);
607
- });
608
- it('handles spaces in numerator and denominator', () => {
609
- const parser = new parser_1.MTextParser('\\S1 2/3 4;');
610
- const tokens = Array.from(parser.parse());
611
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
612
- expect(tokens[0].data).toEqual(['1 2', '3 4', '/']);
613
- });
614
- it('handles spaces after / and # dividers', () => {
615
- let parser = new parser_1.MTextParser('\\S1/ 2;');
616
- let tokens = Array.from(parser.parse());
617
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
618
- expect(tokens[0].data).toEqual(['1', ' 2', '/']);
619
- parser = new parser_1.MTextParser('\\S1# 2;');
620
- tokens = Array.from(parser.parse());
621
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
622
- expect(tokens[0].data).toEqual(['1', ' 2', '#']);
623
- });
624
- it('handles escaped terminator', () => {
625
- const parser = new parser_1.MTextParser('\\S1/2\\;;');
626
- const tokens = Array.from(parser.parse());
627
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
628
- expect(tokens[0].data).toEqual(['1', '2;', '/']);
629
- });
630
- it('ignores backslashes except for escaped terminator', () => {
631
- const parser = new parser_1.MTextParser('\\S\\N^ \\P;');
632
- const tokens = Array.from(parser.parse());
633
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
634
- expect(tokens[0].data).toEqual(['N', 'P', '^']);
635
- });
636
- it('renders grouping chars as simple braces', () => {
637
- const parser = new parser_1.MTextParser('\\S{1}/2;');
638
- const tokens = Array.from(parser.parse());
639
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
640
- expect(tokens[0].data).toEqual(['{1}', '2', '/']);
641
- });
642
- it('decodes caret encoded chars', () => {
643
- let parser = new parser_1.MTextParser('\\S^I/^J;');
644
- let tokens = Array.from(parser.parse());
645
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
646
- expect(tokens[0].data).toEqual([' ', ' ', '/']);
647
- parser = new parser_1.MTextParser('\\S^!/^?;');
648
- tokens = Array.from(parser.parse());
649
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
650
- expect(tokens[0].data).toEqual(['▯', '▯', '/']);
651
- });
652
- it('handles multiple divider chars', () => {
653
- const parser = new parser_1.MTextParser('\\S1/2/3;');
654
- const tokens = Array.from(parser.parse());
655
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
656
- expect(tokens[0].data).toEqual(['1', '2/3', '/']);
657
- });
658
- it('requires terminator for command end', () => {
659
- const parser = new parser_1.MTextParser('\\S1/2');
660
- const tokens = Array.from(parser.parse());
661
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
662
- expect(tokens[0].data).toEqual(['1', '2', '/']);
663
- });
664
- it('handles complex fractions', () => {
665
- const parser = new parser_1.MTextParser('\\S1 2/3 4^ 5 6;');
666
- const tokens = Array.from(parser.parse());
667
- expect(tokens[0].type).toBe(parser_1.TokenType.STACK);
668
- expect(tokens[0].data).toEqual(['1 2', '3 4^5 6', '/']);
669
- });
670
- });
671
- describe('paragraph properties', () => {
672
- it('parses indentation', () => {
673
- const parser = new parser_1.MTextParser('\\pi2;Indented');
674
- const tokens = Array.from(parser.parse());
675
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
676
- expect(tokens[0].data).toBe('Indented');
677
- expect(tokens[0].ctx.paragraph.indent).toBe(2);
678
- });
679
- it('parses alignment', () => {
680
- const parser = new parser_1.MTextParser('\\pqc;Centered');
681
- const tokens = Array.from(parser.parse());
682
- expect(tokens[0].type).toBe(parser_1.TokenType.WORD);
683
- expect(tokens[0].data).toBe('Centered');
684
- expect(tokens[0].ctx.paragraph.align).toBe(parser_1.MTextParagraphAlignment.CENTER);
685
- });
686
- });
687
- });
688
- describe('TextScanner', () => {
689
- let scanner;
690
- beforeEach(() => {
691
- scanner = new parser_1.TextScanner('Hello World');
692
- });
693
- it('initializes with correct state', () => {
694
- expect(scanner.currentIndex).toBe(0);
695
- expect(scanner.isEmpty).toBe(false);
696
- expect(scanner.hasData).toBe(true);
697
- });
698
- it('consumes characters', () => {
699
- expect(scanner.get()).toBe('H');
700
- expect(scanner.currentIndex).toBe(1);
701
- expect(scanner.get()).toBe('e');
702
- expect(scanner.currentIndex).toBe(2);
703
- });
704
- it('peeks characters', () => {
705
- expect(scanner.peek()).toBe('H');
706
- expect(scanner.peek(1)).toBe('e');
707
- expect(scanner.currentIndex).toBe(0);
708
- });
709
- it('consumes multiple characters', () => {
710
- scanner.consume(5);
711
- expect(scanner.currentIndex).toBe(5);
712
- expect(scanner.get()).toBe(' ');
713
- });
714
- it('finds characters', () => {
715
- expect(scanner.find('W')).toBe(6);
716
- expect(scanner.find('X')).toBe(-1);
717
- });
718
- it('handles escaped characters in find', () => {
719
- scanner = new parser_1.TextScanner('Hello\\;World');
720
- expect(scanner.find(';', true)).toBe(6);
721
- });
722
- it('gets remaining text', () => {
723
- scanner.consume(6);
724
- expect(scanner.tail).toBe('World');
725
- });
726
- it('handles end of text', () => {
727
- scanner.consume(11);
728
- expect(scanner.isEmpty).toBe(true);
729
- expect(scanner.hasData).toBe(false);
730
- expect(scanner.get()).toBe('');
731
- expect(scanner.peek()).toBe('');
732
- });
733
- });