text-to-canvas 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,569 @@
1
+ const DEFAULT_FONT_FAMILY = "Arial";
2
+ const DEFAULT_FONT_SIZE = 14;
3
+ const DEFAULT_FONT_COLOR = "black";
4
+ const getTextFormat = (format, baseFormat) => {
5
+ return Object.assign(
6
+ {},
7
+ {
8
+ fontFamily: DEFAULT_FONT_FAMILY,
9
+ fontSize: DEFAULT_FONT_SIZE,
10
+ fontWeight: "400",
11
+ fontStyle: "",
12
+ fontVariant: "",
13
+ fontColor: DEFAULT_FONT_COLOR
14
+ },
15
+ baseFormat,
16
+ format
17
+ );
18
+ };
19
+ const getTextStyle = ({
20
+ fontFamily,
21
+ fontSize,
22
+ fontStyle,
23
+ fontVariant,
24
+ fontWeight
25
+ }) => {
26
+ return `${fontStyle || ""} ${fontVariant || ""} ${fontWeight || ""} ${fontSize ?? DEFAULT_FONT_SIZE}px ${fontFamily || DEFAULT_FONT_FAMILY}`.trim();
27
+ };
28
+ const isWhitespace = (text) => {
29
+ return !!text.match(/^\s+$/);
30
+ };
31
+ const _extractWords = (line) => {
32
+ return line.filter((word) => !isWhitespace(word.text));
33
+ };
34
+ const _cloneWord = (word) => {
35
+ const clone = { ...word };
36
+ if (word.format) {
37
+ clone.format = { ...word.format };
38
+ }
39
+ return clone;
40
+ };
41
+ const _joinWords = (words, joiner) => {
42
+ if (words.length <= 1 || joiner.length < 1) {
43
+ return [...words];
44
+ }
45
+ const phrase = [];
46
+ words.forEach((word, wordIdx) => {
47
+ phrase.push(word);
48
+ if (wordIdx < words.length - 1) {
49
+ joiner.forEach((jw) => phrase.push(_cloneWord(jw)));
50
+ }
51
+ });
52
+ return phrase;
53
+ };
54
+ const justifyLine = ({
55
+ line,
56
+ spaceWidth,
57
+ spaceChar,
58
+ boxWidth
59
+ }) => {
60
+ const words = _extractWords(line);
61
+ if (words.length <= 1) {
62
+ return line.concat();
63
+ }
64
+ const wordsWidth = words.reduce(
65
+ (width, word) => width + (word.metrics?.width ?? 0),
66
+ 0
67
+ );
68
+ const noOfSpacesToInsert = (boxWidth - wordsWidth) / spaceWidth;
69
+ if (words.length > 2) {
70
+ const spacesPerWord = Math.ceil(noOfSpacesToInsert / (words.length - 1));
71
+ const spaces2 = Array.from({ length: spacesPerWord }, () => ({
72
+ text: spaceChar
73
+ }));
74
+ const firstWords = words.slice(0, words.length - 1);
75
+ const firstPart = _joinWords(firstWords, spaces2);
76
+ const remainingSpaces = spaces2.slice(
77
+ 0,
78
+ Math.floor(noOfSpacesToInsert) - (firstWords.length - 1) * spaces2.length
79
+ );
80
+ const lastWord = words[words.length - 1];
81
+ return [...firstPart, ...remainingSpaces, lastWord];
82
+ }
83
+ const spaces = Array.from(
84
+ { length: Math.floor(noOfSpacesToInsert) },
85
+ () => ({ text: spaceChar })
86
+ );
87
+ return _joinWords(words, spaces);
88
+ };
89
+ const trimLine = (line, side = "both") => {
90
+ let leftTrim = 0;
91
+ if (side === "left" || side === "both") {
92
+ for (; leftTrim < line.length; leftTrim++) {
93
+ if (!isWhitespace(line[leftTrim].text)) {
94
+ break;
95
+ }
96
+ }
97
+ if (leftTrim >= line.length) {
98
+ return {
99
+ trimmedLeft: line.concat(),
100
+ trimmedRight: [],
101
+ trimmedLine: []
102
+ };
103
+ }
104
+ }
105
+ let rightTrim = line.length;
106
+ if (side === "right" || side === "both") {
107
+ rightTrim--;
108
+ for (; rightTrim >= 0; rightTrim--) {
109
+ if (!isWhitespace(line[rightTrim].text)) {
110
+ break;
111
+ }
112
+ }
113
+ rightTrim++;
114
+ if (rightTrim <= 0) {
115
+ return {
116
+ trimmedLeft: [],
117
+ trimmedRight: line.concat(),
118
+ trimmedLine: []
119
+ };
120
+ }
121
+ }
122
+ return {
123
+ trimmedLeft: line.slice(0, leftTrim),
124
+ trimmedRight: line.slice(rightTrim),
125
+ trimmedLine: line.slice(leftTrim, rightTrim)
126
+ };
127
+ };
128
+ const HAIR = " ";
129
+ const SPACE = " ";
130
+ let fontBoundingBoxSupported;
131
+ const _getWordHash = (word) => {
132
+ return `${word.text}${word.format ? JSON.stringify(word.format) : ""}`;
133
+ };
134
+ const _splitIntoLines = (words, inferWhitespace = true) => {
135
+ const lines = [[]];
136
+ let wasWhitespace = false;
137
+ words.forEach((word, wordIdx) => {
138
+ if (word.text.match(/^\n+$/)) {
139
+ for (let i = 0; i < word.text.length; i++) {
140
+ lines.push([]);
141
+ }
142
+ wasWhitespace = true;
143
+ return;
144
+ }
145
+ if (isWhitespace(word.text)) {
146
+ lines.at(-1)?.push(word);
147
+ wasWhitespace = true;
148
+ return;
149
+ }
150
+ if (word.text === "") {
151
+ return;
152
+ }
153
+ if (inferWhitespace && !wasWhitespace && wordIdx > 0) {
154
+ lines.at(-1)?.push({ text: SPACE });
155
+ }
156
+ lines.at(-1)?.push(word);
157
+ wasWhitespace = false;
158
+ });
159
+ return lines;
160
+ };
161
+ const _generateSpec = ({
162
+ wrappedLines,
163
+ wordMap,
164
+ positioning: {
165
+ width: boxWidth,
166
+ height: boxHeight,
167
+ x: boxX = 0,
168
+ y: boxY = 0,
169
+ align,
170
+ vAlign
171
+ }
172
+ }) => {
173
+ const xEnd = boxX + boxWidth;
174
+ const yEnd = boxY + boxHeight;
175
+ const getHeight = (word) => (
176
+ // NOTE: `metrics` must exist as every `word` MUST have been measured at this point
177
+ word.metrics.fontBoundingBoxAscent + word.metrics.fontBoundingBoxDescent
178
+ );
179
+ const lineHeights = wrappedLines.map(
180
+ (line) => line.reduce((acc, word) => {
181
+ return Math.max(acc, getHeight(word));
182
+ }, 0)
183
+ );
184
+ const totalHeight = lineHeights.reduce((acc, h) => acc + h, 0);
185
+ let lineY;
186
+ let textBaseline;
187
+ if (vAlign === "top") {
188
+ textBaseline = "top";
189
+ lineY = boxY;
190
+ } else if (vAlign === "bottom") {
191
+ textBaseline = "bottom";
192
+ lineY = yEnd - totalHeight;
193
+ } else {
194
+ textBaseline = "top";
195
+ lineY = boxY + boxHeight / 2 - totalHeight / 2;
196
+ }
197
+ const lines = wrappedLines.map((line, lineIdx) => {
198
+ const lineWidth = line.reduce(
199
+ // NOTE: `metrics` must exist as every `word` MUST have been measured at this point
200
+ (acc, word) => acc + word.metrics.width,
201
+ 0
202
+ );
203
+ const lineHeight = lineHeights[lineIdx];
204
+ let lineX;
205
+ if (align === "right") {
206
+ lineX = xEnd - lineWidth;
207
+ } else if (align === "left") {
208
+ lineX = boxX;
209
+ } else {
210
+ lineX = boxX + boxWidth / 2 - lineWidth / 2;
211
+ }
212
+ let wordX = lineX;
213
+ const posWords = line.map((word) => {
214
+ const hash = _getWordHash(word);
215
+ const { format } = wordMap.get(hash);
216
+ const x = wordX;
217
+ const height = getHeight(word);
218
+ let y;
219
+ if (vAlign === "top") {
220
+ y = lineY;
221
+ } else if (vAlign === "bottom") {
222
+ y = lineY + lineHeight;
223
+ } else {
224
+ y = lineY + (lineHeight - height) / 2;
225
+ }
226
+ wordX += word.metrics.width;
227
+ return {
228
+ word,
229
+ format,
230
+ // undefined IF base formatting should be used when rendering (i.e. `word.format` is undefined)
231
+ x,
232
+ y,
233
+ width: word.metrics.width,
234
+ height,
235
+ isWhitespace: isWhitespace(word.text)
236
+ };
237
+ });
238
+ lineY += lineHeight;
239
+ return posWords;
240
+ });
241
+ return {
242
+ lines,
243
+ textBaseline,
244
+ textAlign: "left",
245
+ // always per current algorithm
246
+ width: boxWidth,
247
+ height: totalHeight
248
+ };
249
+ };
250
+ const _jsonReplacer = function(key, value) {
251
+ if (key === "metrics" && value && typeof value === "object") {
252
+ const metrics = value;
253
+ return {
254
+ width: metrics.width,
255
+ fontBoundingBoxAscent: metrics.fontBoundingBoxAscent,
256
+ fontBoundingBoxDescent: metrics.fontBoundingBoxDescent
257
+ };
258
+ }
259
+ return value;
260
+ };
261
+ const specToJson = (specs) => {
262
+ return JSON.stringify(specs, _jsonReplacer);
263
+ };
264
+ const wordsToJson = (words) => {
265
+ return JSON.stringify(words, _jsonReplacer);
266
+ };
267
+ const _measureWord = ({
268
+ ctx,
269
+ word,
270
+ wordMap,
271
+ baseTextFormat
272
+ }) => {
273
+ const hash = _getWordHash(word);
274
+ if (word.metrics) {
275
+ if (!wordMap.has(hash)) {
276
+ let format2 = void 0;
277
+ if (word.format) {
278
+ format2 = getTextFormat(word.format, baseTextFormat);
279
+ }
280
+ wordMap.set(hash, { metrics: word.metrics, format: format2 });
281
+ }
282
+ return word.metrics.width;
283
+ }
284
+ if (wordMap.has(hash)) {
285
+ const { metrics: metrics2 } = wordMap.get(hash);
286
+ word.metrics = metrics2;
287
+ return metrics2.width;
288
+ }
289
+ let ctxSaved = false;
290
+ let format = void 0;
291
+ if (word.format) {
292
+ ctx.save();
293
+ ctxSaved = true;
294
+ format = getTextFormat(word.format, baseTextFormat);
295
+ ctx.font = getTextStyle(format);
296
+ }
297
+ if (!fontBoundingBoxSupported) {
298
+ if (!ctxSaved) {
299
+ ctx.save();
300
+ ctxSaved = true;
301
+ }
302
+ ctx.textBaseline = "bottom";
303
+ }
304
+ const metrics = ctx.measureText(word.text);
305
+ if (typeof metrics.fontBoundingBoxAscent === "number") {
306
+ fontBoundingBoxSupported = true;
307
+ } else {
308
+ fontBoundingBoxSupported = false;
309
+ metrics.fontBoundingBoxAscent = metrics.actualBoundingBoxAscent;
310
+ metrics.fontBoundingBoxDescent = 0;
311
+ }
312
+ word.metrics = metrics;
313
+ wordMap.set(hash, { metrics, format });
314
+ if (ctxSaved) {
315
+ ctx.restore();
316
+ }
317
+ return metrics.width;
318
+ };
319
+ const splitWords = ({
320
+ ctx,
321
+ words,
322
+ justify,
323
+ format: baseFormat,
324
+ inferWhitespace = true,
325
+ ...positioning
326
+ // rest of params are related to positioning
327
+ }) => {
328
+ const wordMap = /* @__PURE__ */ new Map();
329
+ const baseTextFormat = getTextFormat(baseFormat);
330
+ const { width: boxWidth } = positioning;
331
+ const measureLine = (lineWords, force = false) => {
332
+ let lineWidth = 0;
333
+ let splitPoint = 0;
334
+ lineWords.every((word, idx) => {
335
+ const wordWidth = _measureWord({ ctx, word, wordMap, baseTextFormat });
336
+ if (!force && lineWidth + wordWidth > boxWidth) {
337
+ if (idx === 0) {
338
+ splitPoint = 1;
339
+ lineWidth = wordWidth;
340
+ }
341
+ return false;
342
+ }
343
+ splitPoint++;
344
+ lineWidth += wordWidth;
345
+ return true;
346
+ });
347
+ return { lineWidth, splitPoint };
348
+ };
349
+ ctx.save();
350
+ const hardLines = _splitIntoLines(
351
+ trimLine(words).trimmedLine,
352
+ inferWhitespace
353
+ );
354
+ if (hardLines.length <= 0 || boxWidth <= 0 || positioning.height <= 0 || baseFormat && typeof baseFormat.fontSize === "number" && baseFormat.fontSize <= 0) {
355
+ return {
356
+ lines: [],
357
+ textAlign: "center",
358
+ textBaseline: "middle",
359
+ width: positioning.width,
360
+ height: 0
361
+ };
362
+ }
363
+ ctx.font = getTextStyle(baseTextFormat);
364
+ const hairWidth = justify ? _measureWord({ ctx, word: { text: HAIR }, wordMap, baseTextFormat }) : 0;
365
+ const wrappedLines = [];
366
+ for (const hardLine of hardLines) {
367
+ let { splitPoint } = measureLine(hardLine);
368
+ if (splitPoint >= hardLine.length) {
369
+ wrappedLines.push(hardLine);
370
+ } else {
371
+ let softLine = hardLine.concat();
372
+ while (splitPoint < softLine.length) {
373
+ const splitLine = trimLine(
374
+ softLine.slice(0, splitPoint),
375
+ "right"
376
+ ).trimmedLine;
377
+ wrappedLines.push(splitLine);
378
+ softLine = trimLine(softLine.slice(splitPoint), "left").trimmedLine;
379
+ ({ splitPoint } = measureLine(softLine));
380
+ }
381
+ wrappedLines.push(softLine);
382
+ }
383
+ }
384
+ if (justify && wrappedLines.length > 1) {
385
+ wrappedLines.forEach((wrappedLine, idx) => {
386
+ if (idx < wrappedLines.length - 1) {
387
+ const justifiedLine = justifyLine({
388
+ line: wrappedLine,
389
+ spaceWidth: hairWidth,
390
+ spaceChar: HAIR,
391
+ boxWidth
392
+ });
393
+ measureLine(justifiedLine, true);
394
+ wrappedLines[idx] = justifiedLine;
395
+ }
396
+ });
397
+ }
398
+ const spec = _generateSpec({
399
+ wrappedLines,
400
+ wordMap,
401
+ positioning
402
+ });
403
+ ctx.restore();
404
+ return spec;
405
+ };
406
+ const textToWords = (text) => {
407
+ const words = [];
408
+ let word = void 0;
409
+ let wasWhitespace = false;
410
+ Array.from(text.trim()).forEach((c) => {
411
+ const charIsWhitespace = isWhitespace(c);
412
+ if (charIsWhitespace && !wasWhitespace || !charIsWhitespace && wasWhitespace) {
413
+ wasWhitespace = charIsWhitespace;
414
+ if (word) {
415
+ words.push(word);
416
+ }
417
+ word = { text: c };
418
+ } else {
419
+ if (!word) {
420
+ word = { text: "" };
421
+ }
422
+ word.text += c;
423
+ }
424
+ });
425
+ if (word) {
426
+ words.push(word);
427
+ }
428
+ return words;
429
+ };
430
+ const splitText = ({ text, ...params }) => {
431
+ const words = textToWords(text);
432
+ const results = splitWords({
433
+ ...params,
434
+ words,
435
+ inferWhitespace: false
436
+ });
437
+ return results.lines.map(
438
+ (line) => line.map(({ word: { text: t } }) => t).join("")
439
+ );
440
+ };
441
+ const _getHeight = (ctx, text, style) => {
442
+ const previousTextBaseline = ctx.textBaseline;
443
+ const previousFont = ctx.font;
444
+ ctx.textBaseline = "bottom";
445
+ if (style) {
446
+ ctx.font = style;
447
+ }
448
+ const { actualBoundingBoxAscent: height } = ctx.measureText(text);
449
+ ctx.textBaseline = previousTextBaseline;
450
+ if (style) {
451
+ ctx.font = previousFont;
452
+ }
453
+ return height;
454
+ };
455
+ const getWordHeight = ({
456
+ ctx,
457
+ word
458
+ }) => {
459
+ return _getHeight(ctx, word.text, word.format && getTextStyle(word.format));
460
+ };
461
+ const getTextHeight = ({
462
+ ctx,
463
+ text,
464
+ style
465
+ }) => {
466
+ return _getHeight(ctx, text, style);
467
+ };
468
+ const drawText = (ctx, text, config) => {
469
+ const baseFormat = getTextFormat({
470
+ fontFamily: config.fontFamily,
471
+ fontSize: config.fontSize,
472
+ fontStyle: config.fontStyle,
473
+ fontVariant: config.fontVariant,
474
+ fontWeight: config.fontWeight
475
+ });
476
+ const {
477
+ lines: richLines,
478
+ height: totalHeight,
479
+ textBaseline,
480
+ textAlign
481
+ } = splitWords({
482
+ ctx,
483
+ words: Array.isArray(text) ? text : textToWords(text),
484
+ inferWhitespace: Array.isArray(text) ? config.inferWhitespace === void 0 || config.inferWhitespace : void 0,
485
+ // ignore since `text` is a string; we assume it already has all the whitespace it needs
486
+ x: config.x || 0,
487
+ y: config.y || 0,
488
+ width: config.width,
489
+ height: config.height,
490
+ align: config.align,
491
+ vAlign: config.vAlign,
492
+ justify: config.justify,
493
+ format: baseFormat
494
+ });
495
+ ctx.save();
496
+ ctx.textAlign = textAlign;
497
+ ctx.textBaseline = textBaseline;
498
+ ctx.font = getTextStyle(baseFormat);
499
+ ctx.fillStyle = baseFormat.fontColor || DEFAULT_FONT_COLOR;
500
+ richLines.forEach((line) => {
501
+ line.forEach((pw) => {
502
+ if (!pw.isWhitespace) {
503
+ if (pw.format) {
504
+ ctx.save();
505
+ ctx.font = getTextStyle(pw.format);
506
+ if (pw.format.fontColor) {
507
+ ctx.fillStyle = pw.format.fontColor;
508
+ }
509
+ }
510
+ ctx.fillText(pw.word.text, pw.x, pw.y);
511
+ if (pw.format) {
512
+ ctx.restore();
513
+ }
514
+ }
515
+ });
516
+ });
517
+ if (config.debug) {
518
+ const { width, height, x = 0, y = 0 } = config;
519
+ const xEnd = x + width;
520
+ const yEnd = y + height;
521
+ let textAnchor;
522
+ if (config.align === "right") {
523
+ textAnchor = xEnd;
524
+ } else if (config.align === "left") {
525
+ textAnchor = x;
526
+ } else {
527
+ textAnchor = x + width / 2;
528
+ }
529
+ let debugY = y;
530
+ if (config.vAlign === "bottom") {
531
+ debugY = yEnd;
532
+ } else if (config.vAlign === "middle") {
533
+ debugY = y + height / 2;
534
+ }
535
+ const debugColor = "#0C8CE9";
536
+ ctx.lineWidth = 1;
537
+ ctx.strokeStyle = debugColor;
538
+ ctx.strokeRect(x, y, width, height);
539
+ ctx.lineWidth = 1;
540
+ if (!config.align || config.align === "center") {
541
+ ctx.strokeStyle = debugColor;
542
+ ctx.beginPath();
543
+ ctx.moveTo(textAnchor, y);
544
+ ctx.lineTo(textAnchor, yEnd);
545
+ ctx.stroke();
546
+ }
547
+ if (!config.vAlign || config.vAlign === "middle") {
548
+ ctx.strokeStyle = debugColor;
549
+ ctx.beginPath();
550
+ ctx.moveTo(x, debugY);
551
+ ctx.lineTo(xEnd, debugY);
552
+ ctx.stroke();
553
+ }
554
+ }
555
+ ctx.restore();
556
+ return { height: totalHeight };
557
+ };
558
+ export {
559
+ drawText,
560
+ getTextFormat,
561
+ getTextHeight,
562
+ getTextStyle,
563
+ getWordHeight,
564
+ specToJson,
565
+ splitText,
566
+ splitWords,
567
+ textToWords,
568
+ wordsToJson
569
+ };
@@ -0,0 +1,2 @@
1
+ (function(g,A){typeof exports=="object"&&typeof module!="undefined"?A(exports):typeof define=="function"&&define.amd?define(["exports"],A):(g=typeof globalThis!="undefined"?globalThis:g||self,A(g.textToCanvas={}))})(this,function(g){"use strict";const A="Arial",j="black",_=(t,n)=>Object.assign({},{fontFamily:A,fontSize:14,fontWeight:"400",fontStyle:"",fontVariant:"",fontColor:j},n,t),L=({fontFamily:t,fontSize:n,fontStyle:e,fontVariant:s,fontWeight:i})=>`${e||""} ${s||""} ${i||""} ${n!=null?n:14}px ${t||A}`.trim(),S=t=>!!t.match(/^\s+$/),U=t=>t.filter(n=>!S(n.text)),M=t=>{const n={...t};return t.format&&(n.format={...t.format}),n},k=(t,n)=>{if(t.length<=1||n.length<1)return[...t];const e=[];return t.forEach((s,i)=>{e.push(s),i<t.length-1&&n.forEach(r=>e.push(M(r)))}),e},V=({line:t,spaceWidth:n,spaceChar:e,boxWidth:s})=>{const i=U(t);if(i.length<=1)return t.concat();const r=i.reduce((l,o)=>{var m,p;return l+((p=(m=o.metrics)==null?void 0:m.width)!=null?p:0)},0),a=(s-r)/n;if(i.length>2){const l=Math.ceil(a/(i.length-1)),o=Array.from({length:l},()=>({text:e})),m=i.slice(0,i.length-1),p=k(m,o),u=o.slice(0,Math.floor(a)-(m.length-1)*o.length),T=i[i.length-1];return[...p,...u,T]}const h=Array.from({length:Math.floor(a)},()=>({text:e}));return k(i,h)},b=(t,n="both")=>{let e=0;if(n==="left"||n==="both"){for(;e<t.length&&S(t[e].text);e++);if(e>=t.length)return{trimmedLeft:t.concat(),trimmedRight:[],trimmedLine:[]}}let s=t.length;if(n==="right"||n==="both"){for(s--;s>=0&&S(t[s].text);s--);if(s++,s<=0)return{trimmedLeft:[],trimmedRight:t.concat(),trimmedLine:[]}}return{trimmedLeft:t.slice(0,e),trimmedRight:t.slice(s),trimmedLine:t.slice(e,s)}},D=" ",Z=" ";let F;const $=t=>`${t.text}${t.format?JSON.stringify(t.format):""}`,z=(t,n=!0)=>{const e=[[]];let s=!1;return t.forEach((i,r)=>{var a,h,l;if(i.text.match(/^\n+$/)){for(let o=0;o<i.text.length;o++)e.push([]);s=!0;return}if(S(i.text)){(a=e.at(-1))==null||a.push(i),s=!0;return}i.text!==""&&(n&&!s&&r>0&&((h=e.at(-1))==null||h.push({text:Z})),(l=e.at(-1))==null||l.push(i),s=!1)}),e},Y=({wrappedLines:t,wordMap:n,positioning:{width:e,height:s,x:i=0,y:r=0,align:a,vAlign:h}})=>{const l=i+e,o=r+s,m=f=>f.metrics.fontBoundingBoxAscent+f.metrics.fontBoundingBoxDescent,p=t.map(f=>f.reduce((y,E)=>Math.max(y,m(E)),0)),u=p.reduce((f,y)=>f+y,0);let T,c;return h==="top"?(c="top",T=r):h==="bottom"?(c="bottom",T=o-u):(c="top",T=r+s/2-u/2),{lines:t.map((f,y)=>{const E=f.reduce((B,C)=>B+C.metrics.width,0),v=p[y];let W;a==="right"?W=l-E:a==="left"?W=i:W=i+e/2-E/2;let J=W;const tt=f.map(B=>{const C=$(B),{format:et}=n.get(C),nt=J,R=m(B);let x;return h==="top"?x=T:h==="bottom"?x=T+v:x=T+(v-R)/2,J+=B.metrics.width,{word:B,format:et,x:nt,y:x,width:B.metrics.width,height:R,isWhitespace:S(B.text)}});return T+=v,tt}),textBaseline:c,textAlign:"left",width:e,height:u}},I=function(t,n){if(t==="metrics"&&n&&typeof n=="object"){const e=n;return{width:e.width,fontBoundingBoxAscent:e.fontBoundingBoxAscent,fontBoundingBoxDescent:e.fontBoundingBoxDescent}}return n},q=t=>JSON.stringify(t,I),G=t=>JSON.stringify(t,I),N=({ctx:t,word:n,wordMap:e,baseTextFormat:s})=>{const i=$(n);if(n.metrics){if(!e.has(i)){let l;n.format&&(l=_(n.format,s)),e.set(i,{metrics:n.metrics,format:l})}return n.metrics.width}if(e.has(i)){const{metrics:l}=e.get(i);return n.metrics=l,l.width}let r=!1,a;n.format&&(t.save(),r=!0,a=_(n.format,s),t.font=L(a)),F||(r||(t.save(),r=!0),t.textBaseline="bottom");const h=t.measureText(n.text);return typeof h.fontBoundingBoxAscent=="number"?F=!0:(F=!1,h.fontBoundingBoxAscent=h.actualBoundingBoxAscent,h.fontBoundingBoxDescent=0),n.metrics=h,e.set(i,{metrics:h,format:a}),r&&t.restore(),h.width},O=({ctx:t,words:n,justify:e,format:s,inferWhitespace:i=!0,...r})=>{const a=new Map,h=_(s),{width:l}=r,o=(c,d=!1)=>{let f=0,y=0;return c.every((E,v)=>{const W=N({ctx:t,word:E,wordMap:a,baseTextFormat:h});return!d&&f+W>l?(v===0&&(y=1,f=W),!1):(y++,f+=W,!0)}),{lineWidth:f,splitPoint:y}};t.save();const m=z(b(n).trimmedLine,i);if(m.length<=0||l<=0||r.height<=0||s&&typeof s.fontSize=="number"&&s.fontSize<=0)return{lines:[],textAlign:"center",textBaseline:"middle",width:r.width,height:0};t.font=L(h);const p=e?N({ctx:t,word:{text:D},wordMap:a,baseTextFormat:h}):0,u=[];for(const c of m){let{splitPoint:d}=o(c);if(d>=c.length)u.push(c);else{let f=c.concat();for(;d<f.length;){const y=b(f.slice(0,d),"right").trimmedLine;u.push(y),f=b(f.slice(d),"left").trimmedLine,{splitPoint:d}=o(f)}u.push(f)}}e&&u.length>1&&u.forEach((c,d)=>{if(d<u.length-1){const f=V({line:c,spaceWidth:p,spaceChar:D,boxWidth:l});o(f,!0),u[d]=f}});const T=Y({wrappedLines:u,wordMap:a,positioning:r});return t.restore(),T},H=t=>{const n=[];let e,s=!1;return Array.from(t.trim()).forEach(i=>{const r=S(i);r&&!s||!r&&s?(s=r,e&&n.push(e),e={text:i}):(e||(e={text:""}),e.text+=i)}),e&&n.push(e),n},K=({text:t,...n})=>{const e=H(t);return O({...n,words:e,inferWhitespace:!1}).lines.map(i=>i.map(({word:{text:r}})=>r).join(""))},P=(t,n,e)=>{const s=t.textBaseline,i=t.font;t.textBaseline="bottom",e&&(t.font=e);const{actualBoundingBoxAscent:r}=t.measureText(n);return t.textBaseline=s,e&&(t.font=i),r},Q=({ctx:t,word:n})=>P(t,n.text,n.format&&L(n.format)),X=({ctx:t,text:n,style:e})=>P(t,n,e),w=(t,n,e)=>{const s=_({fontFamily:e.fontFamily,fontSize:e.fontSize,fontStyle:e.fontStyle,fontVariant:e.fontVariant,fontWeight:e.fontWeight}),{lines:i,height:r,textBaseline:a,textAlign:h}=O({ctx:t,words:Array.isArray(n)?n:H(n),inferWhitespace:Array.isArray(n)?e.inferWhitespace===void 0||e.inferWhitespace:void 0,x:e.x||0,y:e.y||0,width:e.width,height:e.height,align:e.align,vAlign:e.vAlign,justify:e.justify,format:s});if(t.save(),t.textAlign=h,t.textBaseline=a,t.font=L(s),t.fillStyle=s.fontColor||j,i.forEach(l=>{l.forEach(o=>{o.isWhitespace||(o.format&&(t.save(),t.font=L(o.format),o.format.fontColor&&(t.fillStyle=o.format.fontColor)),t.fillText(o.word.text,o.x,o.y),o.format&&t.restore())})}),e.debug){const{width:l,height:o,x:m=0,y:p=0}=e,u=m+l,T=p+o;let c;e.align==="right"?c=u:e.align==="left"?c=m:c=m+l/2;let d=p;e.vAlign==="bottom"?d=T:e.vAlign==="middle"&&(d=p+o/2);const f="#0C8CE9";t.lineWidth=1,t.strokeStyle=f,t.strokeRect(m,p,l,o),t.lineWidth=1,(!e.align||e.align==="center")&&(t.strokeStyle=f,t.beginPath(),t.moveTo(c,p),t.lineTo(c,T),t.stroke()),(!e.vAlign||e.vAlign==="middle")&&(t.strokeStyle=f,t.beginPath(),t.moveTo(m,d),t.lineTo(u,d),t.stroke())}return t.restore(),{height:r}};g.drawText=w,g.getTextFormat=_,g.getTextHeight=X,g.getTextStyle=L,g.getWordHeight=Q,g.specToJson=q,g.splitText=K,g.splitWords=O,g.textToWords=H,g.wordsToJson=G,Object.defineProperty(g,Symbol.toStringTag,{value:"Module"})});
2
+ //# sourceMappingURL=text-to-canvas.umd.min.js.map