@mlightcad/mtext-parser 1.0.0 → 1.0.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.
- package/README.md +6 -0
- package/dist/parser.cjs.js +4 -0
- package/dist/parser.cjs.js.map +1 -0
- package/dist/parser.es.js +707 -0
- package/dist/parser.es.js.map +1 -0
- package/dist/parser.umd.js +4 -0
- package/dist/parser.umd.js.map +1 -0
- package/dist/{parser.d.ts → types/parser.d.ts} +7 -0
- package/package.json +16 -9
- package/dist/example.js +0 -125
- package/dist/parser.js +0 -1036
- package/dist/parser.test.d.ts +0 -1
- package/dist/parser.test.js +0 -733
- /package/dist/{example.d.ts → types/example.d.ts} +0 -0
package/dist/parser.js
DELETED
|
@@ -1,1036 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MTextToken = exports.MTextContext = exports.TextScanner = exports.MTextParser = exports.MTextStroke = exports.MTextParagraphAlignment = exports.MTextLineAlignment = exports.TokenType = void 0;
|
|
4
|
-
exports.rgb2int = rgb2int;
|
|
5
|
-
exports.int2rgb = int2rgb;
|
|
6
|
-
exports.caretDecode = caretDecode;
|
|
7
|
-
exports.escapeDxfLineEndings = escapeDxfLineEndings;
|
|
8
|
-
exports.hasInlineFormattingCodes = hasInlineFormattingCodes;
|
|
9
|
-
/**
|
|
10
|
-
* Token types used in MText parsing
|
|
11
|
-
*/
|
|
12
|
-
var TokenType;
|
|
13
|
-
(function (TokenType) {
|
|
14
|
-
/** No token */
|
|
15
|
-
TokenType[TokenType["NONE"] = 0] = "NONE";
|
|
16
|
-
/** Word token with string data */
|
|
17
|
-
TokenType[TokenType["WORD"] = 1] = "WORD";
|
|
18
|
-
/** Stack token with [numerator, denominator, type] data */
|
|
19
|
-
TokenType[TokenType["STACK"] = 2] = "STACK";
|
|
20
|
-
/** Space token with no data */
|
|
21
|
-
TokenType[TokenType["SPACE"] = 3] = "SPACE";
|
|
22
|
-
/** Non-breaking space token with no data */
|
|
23
|
-
TokenType[TokenType["NBSP"] = 4] = "NBSP";
|
|
24
|
-
/** Tab token with no data */
|
|
25
|
-
TokenType[TokenType["TABULATOR"] = 5] = "TABULATOR";
|
|
26
|
-
/** New paragraph token with no data */
|
|
27
|
-
TokenType[TokenType["NEW_PARAGRAPH"] = 6] = "NEW_PARAGRAPH";
|
|
28
|
-
/** New column token with no data */
|
|
29
|
-
TokenType[TokenType["NEW_COLUMN"] = 7] = "NEW_COLUMN";
|
|
30
|
-
/** Wrap at dimension line token with no data */
|
|
31
|
-
TokenType[TokenType["WRAP_AT_DIMLINE"] = 8] = "WRAP_AT_DIMLINE";
|
|
32
|
-
/** Properties changed token with string data (full command) */
|
|
33
|
-
TokenType[TokenType["PROPERTIES_CHANGED"] = 9] = "PROPERTIES_CHANGED";
|
|
34
|
-
})(TokenType || (exports.TokenType = TokenType = {}));
|
|
35
|
-
/**
|
|
36
|
-
* Line alignment options for MText
|
|
37
|
-
*/
|
|
38
|
-
var MTextLineAlignment;
|
|
39
|
-
(function (MTextLineAlignment) {
|
|
40
|
-
/** Align text to bottom */
|
|
41
|
-
MTextLineAlignment[MTextLineAlignment["BOTTOM"] = 0] = "BOTTOM";
|
|
42
|
-
/** Align text to middle */
|
|
43
|
-
MTextLineAlignment[MTextLineAlignment["MIDDLE"] = 1] = "MIDDLE";
|
|
44
|
-
/** Align text to top */
|
|
45
|
-
MTextLineAlignment[MTextLineAlignment["TOP"] = 2] = "TOP";
|
|
46
|
-
})(MTextLineAlignment || (exports.MTextLineAlignment = MTextLineAlignment = {}));
|
|
47
|
-
/**
|
|
48
|
-
* Paragraph alignment options for MText
|
|
49
|
-
*/
|
|
50
|
-
var MTextParagraphAlignment;
|
|
51
|
-
(function (MTextParagraphAlignment) {
|
|
52
|
-
/** Default alignment */
|
|
53
|
-
MTextParagraphAlignment[MTextParagraphAlignment["DEFAULT"] = 0] = "DEFAULT";
|
|
54
|
-
/** Left alignment */
|
|
55
|
-
MTextParagraphAlignment[MTextParagraphAlignment["LEFT"] = 1] = "LEFT";
|
|
56
|
-
/** Right alignment */
|
|
57
|
-
MTextParagraphAlignment[MTextParagraphAlignment["RIGHT"] = 2] = "RIGHT";
|
|
58
|
-
/** Center alignment */
|
|
59
|
-
MTextParagraphAlignment[MTextParagraphAlignment["CENTER"] = 3] = "CENTER";
|
|
60
|
-
/** Justified alignment */
|
|
61
|
-
MTextParagraphAlignment[MTextParagraphAlignment["JUSTIFIED"] = 4] = "JUSTIFIED";
|
|
62
|
-
/** Distributed alignment */
|
|
63
|
-
MTextParagraphAlignment[MTextParagraphAlignment["DISTRIBUTED"] = 5] = "DISTRIBUTED";
|
|
64
|
-
})(MTextParagraphAlignment || (exports.MTextParagraphAlignment = MTextParagraphAlignment = {}));
|
|
65
|
-
/**
|
|
66
|
-
* Text stroke options for MText
|
|
67
|
-
*/
|
|
68
|
-
var MTextStroke;
|
|
69
|
-
(function (MTextStroke) {
|
|
70
|
-
/** No stroke */
|
|
71
|
-
MTextStroke[MTextStroke["NONE"] = 0] = "NONE";
|
|
72
|
-
/** Underline stroke */
|
|
73
|
-
MTextStroke[MTextStroke["UNDERLINE"] = 1] = "UNDERLINE";
|
|
74
|
-
/** Overline stroke */
|
|
75
|
-
MTextStroke[MTextStroke["OVERLINE"] = 2] = "OVERLINE";
|
|
76
|
-
/** Strike-through stroke */
|
|
77
|
-
MTextStroke[MTextStroke["STRIKE_THROUGH"] = 4] = "STRIKE_THROUGH";
|
|
78
|
-
})(MTextStroke || (exports.MTextStroke = MTextStroke = {}));
|
|
79
|
-
/**
|
|
80
|
-
* Special character encoding mapping
|
|
81
|
-
*/
|
|
82
|
-
const SPECIAL_CHAR_ENCODING = {
|
|
83
|
-
c: 'Ø',
|
|
84
|
-
d: '°',
|
|
85
|
-
p: '±',
|
|
86
|
-
};
|
|
87
|
-
/**
|
|
88
|
-
* Character to paragraph alignment mapping
|
|
89
|
-
*/
|
|
90
|
-
const CHAR_TO_ALIGN = {
|
|
91
|
-
l: MTextParagraphAlignment.LEFT,
|
|
92
|
-
r: MTextParagraphAlignment.RIGHT,
|
|
93
|
-
c: MTextParagraphAlignment.CENTER,
|
|
94
|
-
j: MTextParagraphAlignment.JUSTIFIED,
|
|
95
|
-
d: MTextParagraphAlignment.DISTRIBUTED,
|
|
96
|
-
};
|
|
97
|
-
/**
|
|
98
|
-
* Convert RGB tuple to integer color value
|
|
99
|
-
* @param rgb - RGB color tuple
|
|
100
|
-
* @returns Integer color value
|
|
101
|
-
*/
|
|
102
|
-
function rgb2int(rgb) {
|
|
103
|
-
const [r, g, b] = rgb;
|
|
104
|
-
return (b << 16) | (g << 8) | r;
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Convert integer color value to RGB tuple
|
|
108
|
-
* @param value - Integer color value
|
|
109
|
-
* @returns RGB color tuple
|
|
110
|
-
*/
|
|
111
|
-
function int2rgb(value) {
|
|
112
|
-
const r = value & 0xff;
|
|
113
|
-
const g = (value >> 8) & 0xff;
|
|
114
|
-
const b = (value >> 16) & 0xff;
|
|
115
|
-
return [r, g, b];
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* DXF stores some special characters using caret notation. This function
|
|
119
|
-
* decodes this notation to normalize the representation of special characters
|
|
120
|
-
* in the string.
|
|
121
|
-
* see: https://en.wikipedia.org/wiki/Caret_notation
|
|
122
|
-
* @param text - Text to decode
|
|
123
|
-
* @returns Decoded text
|
|
124
|
-
*/
|
|
125
|
-
function caretDecode(text) {
|
|
126
|
-
return text.replace(/\^(.)/g, (_, c) => {
|
|
127
|
-
const code = c.charCodeAt(0);
|
|
128
|
-
// Handle space after caret
|
|
129
|
-
if (code === 32) {
|
|
130
|
-
// Space
|
|
131
|
-
return '^';
|
|
132
|
-
}
|
|
133
|
-
// Handle control characters
|
|
134
|
-
if (code === 73) {
|
|
135
|
-
// Tab (^I)
|
|
136
|
-
return '\t';
|
|
137
|
-
}
|
|
138
|
-
if (code === 74) {
|
|
139
|
-
// Line Feed (^J)
|
|
140
|
-
return '\n';
|
|
141
|
-
}
|
|
142
|
-
if (code === 77) {
|
|
143
|
-
// Carriage Return (^M)
|
|
144
|
-
return '';
|
|
145
|
-
}
|
|
146
|
-
// Handle all other characters
|
|
147
|
-
return '▯';
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* Escape DXF line endings
|
|
152
|
-
* @param text - Text to escape
|
|
153
|
-
* @returns Escaped text
|
|
154
|
-
*/
|
|
155
|
-
function escapeDxfLineEndings(text) {
|
|
156
|
-
return text.replace(/\r\n|\r|\n/g, '\\P');
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Check if text contains inline formatting codes
|
|
160
|
-
* @param text - Text to check
|
|
161
|
-
* @returns True if text contains formatting codes
|
|
162
|
-
*/
|
|
163
|
-
function hasInlineFormattingCodes(text) {
|
|
164
|
-
return text.replace(/\\P/g, '').replace(/\\~/g, '').includes('\\');
|
|
165
|
-
}
|
|
166
|
-
/**
|
|
167
|
-
* Main parser class for MText content
|
|
168
|
-
*/
|
|
169
|
-
class MTextParser {
|
|
170
|
-
/**
|
|
171
|
-
* Creates a new MTextParser instance
|
|
172
|
-
* @param content - The MText content to parse
|
|
173
|
-
* @param ctx - Optional initial MText context
|
|
174
|
-
* @param yieldPropertyCommands - Whether to yield property change commands
|
|
175
|
-
*/
|
|
176
|
-
constructor(content, ctx, yieldPropertyCommands = false) {
|
|
177
|
-
this.ctxStack = [];
|
|
178
|
-
this.continueStroke = false;
|
|
179
|
-
this.scanner = new TextScanner(caretDecode(content));
|
|
180
|
-
this.ctx = ctx || new MTextContext();
|
|
181
|
-
this.yieldPropertyCommands = yieldPropertyCommands;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Push current context onto the stack
|
|
185
|
-
*/
|
|
186
|
-
pushCtx() {
|
|
187
|
-
this.ctxStack.push(this.ctx);
|
|
188
|
-
}
|
|
189
|
-
/**
|
|
190
|
-
* Pop context from the stack
|
|
191
|
-
*/
|
|
192
|
-
popCtx() {
|
|
193
|
-
if (this.ctxStack.length > 0) {
|
|
194
|
-
this.ctx = this.ctxStack.pop();
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
/**
|
|
198
|
-
* Parse stacking expression (numerator/denominator)
|
|
199
|
-
* @returns Tuple of [TokenType.STACK, [numerator, denominator, type]]
|
|
200
|
-
*/
|
|
201
|
-
parseStacking() {
|
|
202
|
-
const scanner = new TextScanner(this.extractExpression(true));
|
|
203
|
-
let numerator = '';
|
|
204
|
-
let denominator = '';
|
|
205
|
-
let stackingType = '';
|
|
206
|
-
const getNextChar = () => {
|
|
207
|
-
let c = scanner.peek();
|
|
208
|
-
let escape = false;
|
|
209
|
-
if (c.charCodeAt(0) < 32) {
|
|
210
|
-
c = ' ';
|
|
211
|
-
}
|
|
212
|
-
if (c === '\\') {
|
|
213
|
-
escape = true;
|
|
214
|
-
scanner.consume(1);
|
|
215
|
-
c = scanner.peek();
|
|
216
|
-
}
|
|
217
|
-
scanner.consume(1);
|
|
218
|
-
return [c, escape];
|
|
219
|
-
};
|
|
220
|
-
const parseNumerator = () => {
|
|
221
|
-
let word = '';
|
|
222
|
-
while (scanner.hasData) {
|
|
223
|
-
const [c, escape] = getNextChar();
|
|
224
|
-
if (!escape && '^/#'.includes(c)) {
|
|
225
|
-
return [word, c];
|
|
226
|
-
}
|
|
227
|
-
word += c;
|
|
228
|
-
}
|
|
229
|
-
return [word, ''];
|
|
230
|
-
};
|
|
231
|
-
const parseDenominator = () => {
|
|
232
|
-
let word = '';
|
|
233
|
-
while (scanner.hasData) {
|
|
234
|
-
const [c, escape] = getNextChar();
|
|
235
|
-
if (escape && c === ';') {
|
|
236
|
-
word += ';';
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
word += c;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
return word;
|
|
243
|
-
};
|
|
244
|
-
[numerator, stackingType] = parseNumerator();
|
|
245
|
-
if (stackingType) {
|
|
246
|
-
denominator = parseDenominator();
|
|
247
|
-
}
|
|
248
|
-
return [TokenType.STACK, [numerator, denominator, stackingType]];
|
|
249
|
-
}
|
|
250
|
-
/**
|
|
251
|
-
* Parse MText properties
|
|
252
|
-
* @param cmd - The property command to parse
|
|
253
|
-
*/
|
|
254
|
-
parseProperties(cmd) {
|
|
255
|
-
const newCtx = this.ctx.copy();
|
|
256
|
-
switch (cmd) {
|
|
257
|
-
case 'L':
|
|
258
|
-
newCtx.underline = true;
|
|
259
|
-
this.continueStroke = true;
|
|
260
|
-
break;
|
|
261
|
-
case 'l':
|
|
262
|
-
newCtx.underline = false;
|
|
263
|
-
if (!newCtx.hasAnyStroke) {
|
|
264
|
-
this.continueStroke = false;
|
|
265
|
-
}
|
|
266
|
-
break;
|
|
267
|
-
case 'O':
|
|
268
|
-
newCtx.overline = true;
|
|
269
|
-
this.continueStroke = true;
|
|
270
|
-
break;
|
|
271
|
-
case 'o':
|
|
272
|
-
newCtx.overline = false;
|
|
273
|
-
if (!newCtx.hasAnyStroke) {
|
|
274
|
-
this.continueStroke = false;
|
|
275
|
-
}
|
|
276
|
-
break;
|
|
277
|
-
case 'K':
|
|
278
|
-
newCtx.strikeThrough = true;
|
|
279
|
-
this.continueStroke = true;
|
|
280
|
-
break;
|
|
281
|
-
case 'k':
|
|
282
|
-
newCtx.strikeThrough = false;
|
|
283
|
-
if (!newCtx.hasAnyStroke) {
|
|
284
|
-
this.continueStroke = false;
|
|
285
|
-
}
|
|
286
|
-
break;
|
|
287
|
-
case 'A':
|
|
288
|
-
this.parseAlign(newCtx);
|
|
289
|
-
break;
|
|
290
|
-
case 'C':
|
|
291
|
-
this.parseAciColor(newCtx);
|
|
292
|
-
break;
|
|
293
|
-
case 'c':
|
|
294
|
-
this.parseRgbColor(newCtx);
|
|
295
|
-
break;
|
|
296
|
-
case 'H':
|
|
297
|
-
this.parseHeight(newCtx);
|
|
298
|
-
break;
|
|
299
|
-
case 'W':
|
|
300
|
-
this.parseWidth(newCtx);
|
|
301
|
-
break;
|
|
302
|
-
case 'Q':
|
|
303
|
-
this.parseOblique(newCtx);
|
|
304
|
-
break;
|
|
305
|
-
case 'T':
|
|
306
|
-
this.parseCharTracking(newCtx);
|
|
307
|
-
break;
|
|
308
|
-
case 'p':
|
|
309
|
-
this.parseParagraphProperties(newCtx);
|
|
310
|
-
break;
|
|
311
|
-
case 'f':
|
|
312
|
-
case 'F':
|
|
313
|
-
this.parseFontProperties(newCtx);
|
|
314
|
-
break;
|
|
315
|
-
default:
|
|
316
|
-
throw new Error(`Unknown command: ${cmd}`);
|
|
317
|
-
}
|
|
318
|
-
newCtx.continueStroke = this.continueStroke;
|
|
319
|
-
this.ctx = newCtx;
|
|
320
|
-
}
|
|
321
|
-
/**
|
|
322
|
-
* Parse alignment property
|
|
323
|
-
* @param ctx - The context to update
|
|
324
|
-
*/
|
|
325
|
-
parseAlign(ctx) {
|
|
326
|
-
const char = this.scanner.get();
|
|
327
|
-
if ('012'.includes(char)) {
|
|
328
|
-
ctx.align = parseInt(char);
|
|
329
|
-
}
|
|
330
|
-
else {
|
|
331
|
-
ctx.align = MTextLineAlignment.BOTTOM;
|
|
332
|
-
}
|
|
333
|
-
this.consumeOptionalTerminator();
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* Parse height property
|
|
337
|
-
* @param ctx - The context to update
|
|
338
|
-
*/
|
|
339
|
-
parseHeight(ctx) {
|
|
340
|
-
const expr = this.extractFloatExpression(true);
|
|
341
|
-
if (expr) {
|
|
342
|
-
try {
|
|
343
|
-
if (expr.endsWith('x')) {
|
|
344
|
-
// For height command, treat x suffix as absolute value
|
|
345
|
-
ctx.capHeight = Math.abs(parseFloat(expr.slice(0, -1)));
|
|
346
|
-
}
|
|
347
|
-
else {
|
|
348
|
-
ctx.capHeight = Math.abs(parseFloat(expr));
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
catch (e) {
|
|
352
|
-
// If parsing fails, treat the entire command as literal text
|
|
353
|
-
this.scanner.consume(-expr.length); // Rewind to before the expression
|
|
354
|
-
return;
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
this.consumeOptionalTerminator();
|
|
358
|
-
}
|
|
359
|
-
/**
|
|
360
|
-
* Parse width property
|
|
361
|
-
* @param ctx - The context to update
|
|
362
|
-
*/
|
|
363
|
-
parseWidth(ctx) {
|
|
364
|
-
const expr = this.extractFloatExpression(true);
|
|
365
|
-
if (expr) {
|
|
366
|
-
try {
|
|
367
|
-
if (expr.endsWith('x')) {
|
|
368
|
-
// For width command, treat x suffix as absolute value
|
|
369
|
-
ctx.widthFactor = Math.abs(parseFloat(expr.slice(0, -1)));
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
ctx.widthFactor = Math.abs(parseFloat(expr));
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
catch (e) {
|
|
376
|
-
// If parsing fails, treat the entire command as literal text
|
|
377
|
-
this.scanner.consume(-expr.length); // Rewind to before the expression
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
this.consumeOptionalTerminator();
|
|
382
|
-
}
|
|
383
|
-
/**
|
|
384
|
-
* Parse character tracking property
|
|
385
|
-
* @param ctx - The context to update
|
|
386
|
-
*/
|
|
387
|
-
parseCharTracking(ctx) {
|
|
388
|
-
const expr = this.extractFloatExpression(true);
|
|
389
|
-
if (expr) {
|
|
390
|
-
try {
|
|
391
|
-
if (expr.endsWith('x')) {
|
|
392
|
-
// For tracking command, treat x suffix as absolute value
|
|
393
|
-
ctx.charTrackingFactor = Math.abs(parseFloat(expr.slice(0, -1)));
|
|
394
|
-
}
|
|
395
|
-
else {
|
|
396
|
-
ctx.charTrackingFactor = Math.abs(parseFloat(expr));
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
catch (e) {
|
|
400
|
-
// If parsing fails, treat the entire command as literal text
|
|
401
|
-
this.scanner.consume(-expr.length); // Rewind to before the expression
|
|
402
|
-
return;
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
this.consumeOptionalTerminator();
|
|
406
|
-
}
|
|
407
|
-
/**
|
|
408
|
-
* Parse float value or factor
|
|
409
|
-
* @param value - Current value to apply factor to
|
|
410
|
-
* @returns New value
|
|
411
|
-
*/
|
|
412
|
-
parseFloatValueOrFactor(value) {
|
|
413
|
-
const expr = this.extractFloatExpression(true);
|
|
414
|
-
if (expr) {
|
|
415
|
-
if (expr.endsWith('x')) {
|
|
416
|
-
const factor = parseFloat(expr.slice(0, -1));
|
|
417
|
-
value *= Math.abs(factor);
|
|
418
|
-
}
|
|
419
|
-
else {
|
|
420
|
-
value = Math.abs(parseFloat(expr));
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
return value;
|
|
424
|
-
}
|
|
425
|
-
/**
|
|
426
|
-
* Parse oblique angle property
|
|
427
|
-
* @param ctx - The context to update
|
|
428
|
-
*/
|
|
429
|
-
parseOblique(ctx) {
|
|
430
|
-
const obliqueExpr = this.extractFloatExpression(false);
|
|
431
|
-
if (obliqueExpr) {
|
|
432
|
-
ctx.oblique = parseFloat(obliqueExpr);
|
|
433
|
-
}
|
|
434
|
-
this.consumeOptionalTerminator();
|
|
435
|
-
}
|
|
436
|
-
/**
|
|
437
|
-
* Parse ACI color property
|
|
438
|
-
* @param ctx - The context to update
|
|
439
|
-
*/
|
|
440
|
-
parseAciColor(ctx) {
|
|
441
|
-
const aciExpr = this.extractIntExpression();
|
|
442
|
-
if (aciExpr) {
|
|
443
|
-
const aci = parseInt(aciExpr);
|
|
444
|
-
if (aci < 257) {
|
|
445
|
-
ctx.aci = aci;
|
|
446
|
-
ctx.rgb = null;
|
|
447
|
-
}
|
|
448
|
-
}
|
|
449
|
-
this.consumeOptionalTerminator();
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Parse RGB color property
|
|
453
|
-
* @param ctx - The context to update
|
|
454
|
-
*/
|
|
455
|
-
parseRgbColor(ctx) {
|
|
456
|
-
const rgbExpr = this.extractIntExpression();
|
|
457
|
-
if (rgbExpr) {
|
|
458
|
-
const value = parseInt(rgbExpr) & 0xffffff;
|
|
459
|
-
const [b, g, r] = int2rgb(value);
|
|
460
|
-
ctx.rgb = [r, g, b];
|
|
461
|
-
}
|
|
462
|
-
this.consumeOptionalTerminator();
|
|
463
|
-
}
|
|
464
|
-
/**
|
|
465
|
-
* Extract float expression from scanner
|
|
466
|
-
* @param relative - Whether to allow relative values (ending in 'x')
|
|
467
|
-
* @returns Extracted expression
|
|
468
|
-
*/
|
|
469
|
-
extractFloatExpression(relative = false) {
|
|
470
|
-
const pattern = relative
|
|
471
|
-
? /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?x?/
|
|
472
|
-
: /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?/;
|
|
473
|
-
const match = this.scanner.tail.match(pattern);
|
|
474
|
-
if (match) {
|
|
475
|
-
const result = match[0];
|
|
476
|
-
this.scanner.consume(result.length);
|
|
477
|
-
return result;
|
|
478
|
-
}
|
|
479
|
-
return '';
|
|
480
|
-
}
|
|
481
|
-
/**
|
|
482
|
-
* Extract integer expression from scanner
|
|
483
|
-
* @returns Extracted expression
|
|
484
|
-
*/
|
|
485
|
-
extractIntExpression() {
|
|
486
|
-
const match = this.scanner.tail.match(/^\d+/);
|
|
487
|
-
if (match) {
|
|
488
|
-
const result = match[0];
|
|
489
|
-
this.scanner.consume(result.length);
|
|
490
|
-
return result;
|
|
491
|
-
}
|
|
492
|
-
return '';
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Extract expression until semicolon or end
|
|
496
|
-
* @param escape - Whether to handle escaped semicolons
|
|
497
|
-
* @returns Extracted expression
|
|
498
|
-
*/
|
|
499
|
-
extractExpression(escape = false) {
|
|
500
|
-
const stop = this.scanner.find(';', escape);
|
|
501
|
-
if (stop < 0) {
|
|
502
|
-
const expr = this.scanner.tail;
|
|
503
|
-
this.scanner.consume(expr.length);
|
|
504
|
-
return expr;
|
|
505
|
-
}
|
|
506
|
-
// Check if the semicolon is escaped by looking at the previous character
|
|
507
|
-
const prevChar = this.scanner.peek(stop - this.scanner.currentIndex - 1);
|
|
508
|
-
const isEscaped = prevChar === '\\';
|
|
509
|
-
const expr = this.scanner.tail.slice(0, stop - this.scanner.currentIndex + (isEscaped ? 1 : 0));
|
|
510
|
-
this.scanner.consume(expr.length + 1);
|
|
511
|
-
return expr;
|
|
512
|
-
}
|
|
513
|
-
/**
|
|
514
|
-
* Parse font properties
|
|
515
|
-
* @param ctx - The context to update
|
|
516
|
-
*/
|
|
517
|
-
parseFontProperties(ctx) {
|
|
518
|
-
const parts = this.extractExpression().split('|');
|
|
519
|
-
if (parts.length > 0 && parts[0]) {
|
|
520
|
-
const name = parts[0];
|
|
521
|
-
let style = 'Regular';
|
|
522
|
-
let weight = 400;
|
|
523
|
-
for (const part of parts.slice(1)) {
|
|
524
|
-
if (part.startsWith('b1')) {
|
|
525
|
-
weight = 700;
|
|
526
|
-
}
|
|
527
|
-
else if (part.startsWith('i1')) {
|
|
528
|
-
style = 'Italic';
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
ctx.fontFace = {
|
|
532
|
-
family: name,
|
|
533
|
-
style,
|
|
534
|
-
weight,
|
|
535
|
-
};
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
/**
|
|
539
|
-
* Parse paragraph properties from the MText content
|
|
540
|
-
* Handles properties like indentation, alignment, and tab stops
|
|
541
|
-
* @param ctx - The context to update
|
|
542
|
-
*/
|
|
543
|
-
parseParagraphProperties(ctx) {
|
|
544
|
-
const scanner = new TextScanner(this.extractExpression());
|
|
545
|
-
/** Current indentation value */
|
|
546
|
-
let indent = ctx.paragraph.indent;
|
|
547
|
-
/** Left margin value */
|
|
548
|
-
let left = ctx.paragraph.left;
|
|
549
|
-
/** Right margin value */
|
|
550
|
-
let right = ctx.paragraph.right;
|
|
551
|
-
/** Current paragraph alignment */
|
|
552
|
-
let align = ctx.paragraph.align;
|
|
553
|
-
/** Array of tab stop positions and types */
|
|
554
|
-
let tabStops = [];
|
|
555
|
-
/**
|
|
556
|
-
* Parse a floating point number from the scanner's current position
|
|
557
|
-
* Handles optional sign, decimal point, and scientific notation
|
|
558
|
-
* @returns The parsed float value, or 0 if no valid number is found
|
|
559
|
-
*/
|
|
560
|
-
const parseFloatValue = () => {
|
|
561
|
-
const match = scanner.tail.match(/^[+-]?\d+(?:\.\d*)?(?:[eE][+-]?\d+)?/);
|
|
562
|
-
if (match) {
|
|
563
|
-
const value = parseFloat(match[0]);
|
|
564
|
-
scanner.consume(match[0].length);
|
|
565
|
-
while (scanner.peek() === ',') {
|
|
566
|
-
scanner.consume(1);
|
|
567
|
-
}
|
|
568
|
-
return value;
|
|
569
|
-
}
|
|
570
|
-
return 0;
|
|
571
|
-
};
|
|
572
|
-
while (scanner.hasData) {
|
|
573
|
-
const cmd = scanner.get();
|
|
574
|
-
switch (cmd) {
|
|
575
|
-
case 'i': // Indentation
|
|
576
|
-
indent = parseFloatValue();
|
|
577
|
-
break;
|
|
578
|
-
case 'l': // Left margin
|
|
579
|
-
left = parseFloatValue();
|
|
580
|
-
break;
|
|
581
|
-
case 'r': // Right margin
|
|
582
|
-
right = parseFloatValue();
|
|
583
|
-
break;
|
|
584
|
-
case 'x': // Skip
|
|
585
|
-
break;
|
|
586
|
-
case 'q': {
|
|
587
|
-
// Alignment
|
|
588
|
-
const adjustment = scanner.get();
|
|
589
|
-
align = CHAR_TO_ALIGN[adjustment] || MTextParagraphAlignment.DEFAULT;
|
|
590
|
-
while (scanner.peek() === ',') {
|
|
591
|
-
scanner.consume(1);
|
|
592
|
-
}
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
case 't': // Tab stops
|
|
596
|
-
tabStops = [];
|
|
597
|
-
while (scanner.hasData) {
|
|
598
|
-
const type = scanner.peek();
|
|
599
|
-
if (type === 'r' || type === 'c') {
|
|
600
|
-
scanner.consume(1);
|
|
601
|
-
const value = parseFloatValue();
|
|
602
|
-
tabStops.push(type + value.toString());
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
const value = parseFloatValue();
|
|
606
|
-
if (!isNaN(value)) {
|
|
607
|
-
tabStops.push(value);
|
|
608
|
-
}
|
|
609
|
-
else {
|
|
610
|
-
scanner.consume(1);
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
}
|
|
614
|
-
break;
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
ctx.paragraph = {
|
|
618
|
-
indent,
|
|
619
|
-
left,
|
|
620
|
-
right,
|
|
621
|
-
align,
|
|
622
|
-
tab_stops: tabStops,
|
|
623
|
-
};
|
|
624
|
-
}
|
|
625
|
-
/**
|
|
626
|
-
* Consume optional terminator (semicolon)
|
|
627
|
-
*/
|
|
628
|
-
consumeOptionalTerminator() {
|
|
629
|
-
if (this.scanner.peek() === ';') {
|
|
630
|
-
this.scanner.consume(1);
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
/**
|
|
634
|
-
* Parse MText content into tokens
|
|
635
|
-
* @yields MTextToken objects
|
|
636
|
-
*/
|
|
637
|
-
*parse() {
|
|
638
|
-
const wordToken = TokenType.WORD;
|
|
639
|
-
const spaceToken = TokenType.SPACE;
|
|
640
|
-
let followupToken = null;
|
|
641
|
-
const nextToken = () => {
|
|
642
|
-
let word = '';
|
|
643
|
-
while (this.scanner.hasData) {
|
|
644
|
-
let escape = false;
|
|
645
|
-
let letter = this.scanner.peek();
|
|
646
|
-
const cmdStartIndex = this.scanner.currentIndex;
|
|
647
|
-
// Handle control characters first
|
|
648
|
-
if (letter.charCodeAt(0) < 32) {
|
|
649
|
-
this.scanner.consume(1); // Always consume the control character
|
|
650
|
-
if (letter === '\t') {
|
|
651
|
-
return [TokenType.TABULATOR, null];
|
|
652
|
-
}
|
|
653
|
-
if (letter === '\n') {
|
|
654
|
-
return [TokenType.NEW_PARAGRAPH, null];
|
|
655
|
-
}
|
|
656
|
-
letter = ' ';
|
|
657
|
-
}
|
|
658
|
-
if (letter === '\\') {
|
|
659
|
-
if ('\\{}'.includes(this.scanner.peek(1))) {
|
|
660
|
-
escape = true;
|
|
661
|
-
this.scanner.consume(1);
|
|
662
|
-
letter = this.scanner.peek();
|
|
663
|
-
}
|
|
664
|
-
else {
|
|
665
|
-
if (word) {
|
|
666
|
-
return [wordToken, word];
|
|
667
|
-
}
|
|
668
|
-
this.scanner.consume(1);
|
|
669
|
-
const cmd = this.scanner.get();
|
|
670
|
-
switch (cmd) {
|
|
671
|
-
case '~':
|
|
672
|
-
return [TokenType.NBSP, null];
|
|
673
|
-
case 'P':
|
|
674
|
-
return [TokenType.NEW_PARAGRAPH, null];
|
|
675
|
-
case 'N':
|
|
676
|
-
return [TokenType.NEW_COLUMN, null];
|
|
677
|
-
case 'X':
|
|
678
|
-
return [TokenType.WRAP_AT_DIMLINE, null];
|
|
679
|
-
case 'S':
|
|
680
|
-
return this.parseStacking();
|
|
681
|
-
default:
|
|
682
|
-
if (cmd) {
|
|
683
|
-
try {
|
|
684
|
-
this.parseProperties(cmd);
|
|
685
|
-
if (this.yieldPropertyCommands) {
|
|
686
|
-
return [
|
|
687
|
-
TokenType.PROPERTIES_CHANGED,
|
|
688
|
-
this.scanner.tail.slice(cmdStartIndex, this.scanner.currentIndex),
|
|
689
|
-
];
|
|
690
|
-
}
|
|
691
|
-
// After processing a property command, continue with normal parsing
|
|
692
|
-
continue;
|
|
693
|
-
}
|
|
694
|
-
catch (e) {
|
|
695
|
-
const commandText = this.scanner.tail.slice(cmdStartIndex, this.scanner.currentIndex);
|
|
696
|
-
word += commandText;
|
|
697
|
-
}
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
continue;
|
|
701
|
-
}
|
|
702
|
-
}
|
|
703
|
-
if (letter === '%' && this.scanner.peek(1) === '%') {
|
|
704
|
-
const code = this.scanner.peek(2).toLowerCase();
|
|
705
|
-
const specialChar = SPECIAL_CHAR_ENCODING[code];
|
|
706
|
-
if (specialChar) {
|
|
707
|
-
this.scanner.consume(3);
|
|
708
|
-
word += specialChar;
|
|
709
|
-
continue;
|
|
710
|
-
}
|
|
711
|
-
else {
|
|
712
|
-
// Skip invalid special character codes
|
|
713
|
-
this.scanner.consume(3);
|
|
714
|
-
continue;
|
|
715
|
-
}
|
|
716
|
-
}
|
|
717
|
-
if (letter === ' ') {
|
|
718
|
-
if (word) {
|
|
719
|
-
this.scanner.consume(1);
|
|
720
|
-
followupToken = spaceToken;
|
|
721
|
-
return [wordToken, word];
|
|
722
|
-
}
|
|
723
|
-
this.scanner.consume(1);
|
|
724
|
-
return [spaceToken, null];
|
|
725
|
-
}
|
|
726
|
-
if (!escape) {
|
|
727
|
-
if (letter === '{') {
|
|
728
|
-
if (word) {
|
|
729
|
-
return [wordToken, word];
|
|
730
|
-
}
|
|
731
|
-
this.scanner.consume(1);
|
|
732
|
-
this.pushCtx();
|
|
733
|
-
continue;
|
|
734
|
-
}
|
|
735
|
-
else if (letter === '}') {
|
|
736
|
-
if (word) {
|
|
737
|
-
return [wordToken, word];
|
|
738
|
-
}
|
|
739
|
-
this.scanner.consume(1);
|
|
740
|
-
this.popCtx();
|
|
741
|
-
continue;
|
|
742
|
-
}
|
|
743
|
-
}
|
|
744
|
-
this.scanner.consume(1);
|
|
745
|
-
if (letter.charCodeAt(0) >= 32) {
|
|
746
|
-
word += letter;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
if (word) {
|
|
750
|
-
return [wordToken, word];
|
|
751
|
-
}
|
|
752
|
-
return [TokenType.NONE, null];
|
|
753
|
-
};
|
|
754
|
-
while (true) {
|
|
755
|
-
const [type, data] = nextToken();
|
|
756
|
-
if (type) {
|
|
757
|
-
yield new MTextToken(type, this.ctx, data);
|
|
758
|
-
if (followupToken) {
|
|
759
|
-
yield new MTextToken(followupToken, this.ctx, null);
|
|
760
|
-
followupToken = null;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
else {
|
|
764
|
-
break;
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
exports.MTextParser = MTextParser;
|
|
770
|
-
/**
|
|
771
|
-
* Text scanner for parsing MText content
|
|
772
|
-
*/
|
|
773
|
-
class TextScanner {
|
|
774
|
-
/**
|
|
775
|
-
* Create a new text scanner
|
|
776
|
-
* @param text - The text to scan
|
|
777
|
-
*/
|
|
778
|
-
constructor(text) {
|
|
779
|
-
this.text = text;
|
|
780
|
-
this.textLen = text.length;
|
|
781
|
-
this._index = 0;
|
|
782
|
-
}
|
|
783
|
-
/**
|
|
784
|
-
* Get the current index in the text
|
|
785
|
-
*/
|
|
786
|
-
get currentIndex() {
|
|
787
|
-
return this._index;
|
|
788
|
-
}
|
|
789
|
-
/**
|
|
790
|
-
* Check if the scanner has reached the end of the text
|
|
791
|
-
*/
|
|
792
|
-
get isEmpty() {
|
|
793
|
-
return this._index >= this.textLen;
|
|
794
|
-
}
|
|
795
|
-
/**
|
|
796
|
-
* Check if there is more text to scan
|
|
797
|
-
*/
|
|
798
|
-
get hasData() {
|
|
799
|
-
return this._index < this.textLen;
|
|
800
|
-
}
|
|
801
|
-
/**
|
|
802
|
-
* Get the next character and advance the index
|
|
803
|
-
* @returns The next character, or empty string if at end
|
|
804
|
-
*/
|
|
805
|
-
get() {
|
|
806
|
-
if (this.isEmpty) {
|
|
807
|
-
return '';
|
|
808
|
-
}
|
|
809
|
-
const char = this.text[this._index];
|
|
810
|
-
this._index++;
|
|
811
|
-
return char;
|
|
812
|
-
}
|
|
813
|
-
/**
|
|
814
|
-
* Advance the index by the specified count
|
|
815
|
-
* @param count - Number of characters to advance
|
|
816
|
-
*/
|
|
817
|
-
consume(count = 1) {
|
|
818
|
-
if (count < 1)
|
|
819
|
-
throw new Error(`Invalid consume count: ${count}`);
|
|
820
|
-
this._index = Math.min(this._index + count, this.textLen);
|
|
821
|
-
}
|
|
822
|
-
/**
|
|
823
|
-
* Look at a character without advancing the index
|
|
824
|
-
* @param offset - Offset from current position
|
|
825
|
-
* @returns The character at the offset position, or empty string if out of bounds
|
|
826
|
-
*/
|
|
827
|
-
peek(offset = 0) {
|
|
828
|
-
if (offset < 0)
|
|
829
|
-
throw new Error(`Invalid peek offset: ${offset}`);
|
|
830
|
-
const index = this._index + offset;
|
|
831
|
-
if (index >= this.textLen) {
|
|
832
|
-
return '';
|
|
833
|
-
}
|
|
834
|
-
return this.text[index];
|
|
835
|
-
}
|
|
836
|
-
/**
|
|
837
|
-
* Find the next occurrence of a character
|
|
838
|
-
* @param char - The character to find
|
|
839
|
-
* @param escape - Whether to handle escaped characters
|
|
840
|
-
* @returns Index of the character, or -1 if not found
|
|
841
|
-
*/
|
|
842
|
-
find(char, escape = false) {
|
|
843
|
-
let index = this._index;
|
|
844
|
-
while (index < this.textLen) {
|
|
845
|
-
if (escape && this.text[index] === '\\') {
|
|
846
|
-
if (index + 1 < this.textLen) {
|
|
847
|
-
if (this.text[index + 1] === char) {
|
|
848
|
-
return index + 1;
|
|
849
|
-
}
|
|
850
|
-
index += 2;
|
|
851
|
-
continue;
|
|
852
|
-
}
|
|
853
|
-
index++;
|
|
854
|
-
continue;
|
|
855
|
-
}
|
|
856
|
-
if (this.text[index] === char) {
|
|
857
|
-
return index;
|
|
858
|
-
}
|
|
859
|
-
index++;
|
|
860
|
-
}
|
|
861
|
-
return -1;
|
|
862
|
-
}
|
|
863
|
-
/**
|
|
864
|
-
* Get the remaining text from the current position
|
|
865
|
-
*/
|
|
866
|
-
get tail() {
|
|
867
|
-
return this.text.slice(this._index);
|
|
868
|
-
}
|
|
869
|
-
/**
|
|
870
|
-
* Check if the next character is a space
|
|
871
|
-
*/
|
|
872
|
-
isNextSpace() {
|
|
873
|
-
return this.peek() === ' ';
|
|
874
|
-
}
|
|
875
|
-
/**
|
|
876
|
-
* Consume spaces until a non-space character is found
|
|
877
|
-
* @returns Number of spaces consumed
|
|
878
|
-
*/
|
|
879
|
-
consumeSpaces() {
|
|
880
|
-
let count = 0;
|
|
881
|
-
while (this.isNextSpace()) {
|
|
882
|
-
this.consume();
|
|
883
|
-
count++;
|
|
884
|
-
}
|
|
885
|
-
return count;
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
exports.TextScanner = TextScanner;
|
|
889
|
-
/**
|
|
890
|
-
* MText context class for managing text formatting state
|
|
891
|
-
*/
|
|
892
|
-
class MTextContext {
|
|
893
|
-
constructor() {
|
|
894
|
-
this._stroke = 0;
|
|
895
|
-
/** Whether to continue stroke formatting */
|
|
896
|
-
this.continueStroke = false;
|
|
897
|
-
this._aci = 7;
|
|
898
|
-
/** RGB color value, or null if using ACI */
|
|
899
|
-
this.rgb = null;
|
|
900
|
-
/** Line alignment */
|
|
901
|
-
this.align = MTextLineAlignment.BOTTOM;
|
|
902
|
-
/** Font face properties */
|
|
903
|
-
this.fontFace = { family: '', style: 'Regular', weight: 400 };
|
|
904
|
-
/** Capital letter height */
|
|
905
|
-
this.capHeight = 1.0;
|
|
906
|
-
/** Character width factor */
|
|
907
|
-
this.widthFactor = 1.0;
|
|
908
|
-
/** Character tracking factor */
|
|
909
|
-
this.charTrackingFactor = 1.0;
|
|
910
|
-
/** Oblique angle */
|
|
911
|
-
this.oblique = 0.0;
|
|
912
|
-
/** Paragraph properties */
|
|
913
|
-
this.paragraph = {
|
|
914
|
-
indent: 0,
|
|
915
|
-
left: 0,
|
|
916
|
-
right: 0,
|
|
917
|
-
align: MTextParagraphAlignment.DEFAULT,
|
|
918
|
-
tab_stops: [],
|
|
919
|
-
};
|
|
920
|
-
}
|
|
921
|
-
/**
|
|
922
|
-
* Get the ACI color value
|
|
923
|
-
*/
|
|
924
|
-
get aci() {
|
|
925
|
-
return this._aci;
|
|
926
|
-
}
|
|
927
|
-
/**
|
|
928
|
-
* Set the ACI color value
|
|
929
|
-
* @param value - ACI color value (0-256)
|
|
930
|
-
* @throws Error if value is out of range
|
|
931
|
-
*/
|
|
932
|
-
set aci(value) {
|
|
933
|
-
if (value >= 0 && value <= 256) {
|
|
934
|
-
this._aci = value;
|
|
935
|
-
this.rgb = null;
|
|
936
|
-
}
|
|
937
|
-
else {
|
|
938
|
-
throw new Error('ACI not in range [0, 256]');
|
|
939
|
-
}
|
|
940
|
-
}
|
|
941
|
-
/**
|
|
942
|
-
* Get whether text is underlined
|
|
943
|
-
*/
|
|
944
|
-
get underline() {
|
|
945
|
-
return Boolean(this._stroke & MTextStroke.UNDERLINE);
|
|
946
|
-
}
|
|
947
|
-
/**
|
|
948
|
-
* Set whether text is underlined
|
|
949
|
-
* @param value - Whether to underline
|
|
950
|
-
*/
|
|
951
|
-
set underline(value) {
|
|
952
|
-
this._setStrokeState(MTextStroke.UNDERLINE, value);
|
|
953
|
-
}
|
|
954
|
-
/**
|
|
955
|
-
* Get whether text has strike-through
|
|
956
|
-
*/
|
|
957
|
-
get strikeThrough() {
|
|
958
|
-
return Boolean(this._stroke & MTextStroke.STRIKE_THROUGH);
|
|
959
|
-
}
|
|
960
|
-
/**
|
|
961
|
-
* Set whether text has strike-through
|
|
962
|
-
* @param value - Whether to strike through
|
|
963
|
-
*/
|
|
964
|
-
set strikeThrough(value) {
|
|
965
|
-
this._setStrokeState(MTextStroke.STRIKE_THROUGH, value);
|
|
966
|
-
}
|
|
967
|
-
/**
|
|
968
|
-
* Get whether text has overline
|
|
969
|
-
*/
|
|
970
|
-
get overline() {
|
|
971
|
-
return Boolean(this._stroke & MTextStroke.OVERLINE);
|
|
972
|
-
}
|
|
973
|
-
/**
|
|
974
|
-
* Set whether text has overline
|
|
975
|
-
* @param value - Whether to overline
|
|
976
|
-
*/
|
|
977
|
-
set overline(value) {
|
|
978
|
-
this._setStrokeState(MTextStroke.OVERLINE, value);
|
|
979
|
-
}
|
|
980
|
-
/**
|
|
981
|
-
* Check if any stroke formatting is active
|
|
982
|
-
*/
|
|
983
|
-
get hasAnyStroke() {
|
|
984
|
-
return Boolean(this._stroke);
|
|
985
|
-
}
|
|
986
|
-
/**
|
|
987
|
-
* Set the state of a stroke type
|
|
988
|
-
* @param stroke - The stroke type to set
|
|
989
|
-
* @param state - Whether to enable or disable the stroke
|
|
990
|
-
*/
|
|
991
|
-
_setStrokeState(stroke, state = true) {
|
|
992
|
-
if (state) {
|
|
993
|
-
this._stroke |= stroke;
|
|
994
|
-
}
|
|
995
|
-
else {
|
|
996
|
-
this._stroke &= ~stroke;
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
/**
|
|
1000
|
-
* Create a copy of this context
|
|
1001
|
-
* @returns A new context with the same properties
|
|
1002
|
-
*/
|
|
1003
|
-
copy() {
|
|
1004
|
-
const ctx = new MTextContext();
|
|
1005
|
-
ctx._stroke = this._stroke;
|
|
1006
|
-
ctx.continueStroke = this.continueStroke;
|
|
1007
|
-
ctx._aci = this._aci;
|
|
1008
|
-
ctx.rgb = this.rgb;
|
|
1009
|
-
ctx.align = this.align;
|
|
1010
|
-
ctx.fontFace = { ...this.fontFace };
|
|
1011
|
-
ctx.capHeight = this.capHeight;
|
|
1012
|
-
ctx.widthFactor = this.widthFactor;
|
|
1013
|
-
ctx.charTrackingFactor = this.charTrackingFactor;
|
|
1014
|
-
ctx.oblique = this.oblique;
|
|
1015
|
-
ctx.paragraph = { ...this.paragraph };
|
|
1016
|
-
return ctx;
|
|
1017
|
-
}
|
|
1018
|
-
}
|
|
1019
|
-
exports.MTextContext = MTextContext;
|
|
1020
|
-
/**
|
|
1021
|
-
* Token class for MText parsing
|
|
1022
|
-
*/
|
|
1023
|
-
class MTextToken {
|
|
1024
|
-
/**
|
|
1025
|
-
* Create a new MText token
|
|
1026
|
-
* @param type - The token type
|
|
1027
|
-
* @param ctx - The text context at this token
|
|
1028
|
-
* @param data - Optional token data
|
|
1029
|
-
*/
|
|
1030
|
-
constructor(type, ctx, data) {
|
|
1031
|
-
this.type = type;
|
|
1032
|
-
this.ctx = ctx;
|
|
1033
|
-
this.data = data;
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
exports.MTextToken = MTextToken;
|