@codehz/draw-call 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/render.cjs CHANGED
@@ -195,6 +195,174 @@ function measureImageSize(element, _ctx, _availableWidth) {
195
195
  };
196
196
  }
197
197
 
198
+ //#endregion
199
+ //#region src/layout/components/richtext.ts
200
+ /**
201
+ * 合并 span 样式和元素级别样式
202
+ * 优先级:span 样式 > 元素样式 > 默认值
203
+ * font 属性进行深度合并,允许 span 部分覆盖 element 的 font
204
+ */
205
+ function mergeSpanStyle(span, elementStyle) {
206
+ return {
207
+ font: {
208
+ ...elementStyle.font || {},
209
+ ...span.font || {}
210
+ },
211
+ color: span.color ?? elementStyle.color,
212
+ background: span.background ?? elementStyle.background,
213
+ underline: span.underline ?? elementStyle.underline ?? false,
214
+ strikethrough: span.strikethrough ?? elementStyle.strikethrough ?? false
215
+ };
216
+ }
217
+ /**
218
+ * 测量富文本元素的固有尺寸
219
+ */
220
+ function measureRichTextSize(element, ctx, availableWidth) {
221
+ const lineHeight = element.lineHeight ?? 1.2;
222
+ const elementStyle = {
223
+ font: element.font,
224
+ color: element.color,
225
+ background: element.background,
226
+ underline: element.underline,
227
+ strikethrough: element.strikethrough
228
+ };
229
+ const richLines = wrapRichText(ctx, element.spans, availableWidth, lineHeight, elementStyle);
230
+ let maxWidth = 0;
231
+ let totalHeight = 0;
232
+ for (const line of richLines) {
233
+ maxWidth = Math.max(maxWidth, line.width);
234
+ totalHeight += line.height;
235
+ }
236
+ return {
237
+ width: maxWidth,
238
+ height: totalHeight
239
+ };
240
+ }
241
+ /**
242
+ * 将富文本内容拆分为行
243
+ */
244
+ function wrapRichText(ctx, spans, maxWidth, lineHeightScale = 1.2, elementStyle = {}) {
245
+ const lines = [];
246
+ let currentSegments = [];
247
+ let currentLineWidth = 0;
248
+ const pushLine = () => {
249
+ if (currentSegments.length === 0) return;
250
+ let maxTopDist = 0;
251
+ let maxBottomDist = 0;
252
+ let maxLineHeight = 0;
253
+ for (const seg of currentSegments) {
254
+ const topDist = seg.ascent - seg.offset;
255
+ const bottomDist = seg.descent + seg.offset;
256
+ maxTopDist = Math.max(maxTopDist, topDist);
257
+ maxBottomDist = Math.max(maxBottomDist, bottomDist);
258
+ maxLineHeight = Math.max(maxLineHeight, seg.height);
259
+ }
260
+ const contentHeight = maxTopDist + maxBottomDist;
261
+ const finalHeight = Math.max(contentHeight, maxLineHeight);
262
+ const extra = (finalHeight - contentHeight) / 2;
263
+ lines.push({
264
+ segments: [...currentSegments],
265
+ width: currentLineWidth,
266
+ height: finalHeight,
267
+ baseline: maxTopDist + extra
268
+ });
269
+ currentSegments = [];
270
+ currentLineWidth = 0;
271
+ };
272
+ for (const span of spans) {
273
+ const mergedStyle = mergeSpanStyle(span, elementStyle);
274
+ const font = mergedStyle.font;
275
+ const lh = (font.size ?? 16) * lineHeightScale;
276
+ const words = span.text.split(/(\s+)/);
277
+ for (const word of words) {
278
+ if (word === "") continue;
279
+ if (/^\s+$/.test(word)) {
280
+ const metrics = ctx.measureText(word, font);
281
+ const wordWidth = metrics.width;
282
+ if (maxWidth > 0 && currentLineWidth + wordWidth > maxWidth && currentSegments.length > 0) pushLine();
283
+ currentSegments.push({
284
+ text: word,
285
+ font: mergedStyle.font,
286
+ color: mergedStyle.color,
287
+ background: mergedStyle.background,
288
+ underline: mergedStyle.underline,
289
+ strikethrough: mergedStyle.strikethrough,
290
+ width: wordWidth,
291
+ height: lh,
292
+ ascent: metrics.ascent,
293
+ descent: metrics.descent,
294
+ offset: metrics.offset
295
+ });
296
+ currentLineWidth += wordWidth;
297
+ } else {
298
+ const metrics = ctx.measureText(word, font);
299
+ const wordWidth = metrics.width;
300
+ if (maxWidth <= 0 || currentLineWidth + wordWidth <= maxWidth) {
301
+ currentSegments.push({
302
+ text: word,
303
+ font: mergedStyle.font,
304
+ color: mergedStyle.color,
305
+ background: mergedStyle.background,
306
+ underline: mergedStyle.underline,
307
+ strikethrough: mergedStyle.strikethrough,
308
+ width: wordWidth,
309
+ height: lh,
310
+ ascent: metrics.ascent,
311
+ descent: metrics.descent,
312
+ offset: metrics.offset
313
+ });
314
+ currentLineWidth += wordWidth;
315
+ } else {
316
+ if (currentSegments.length > 0) pushLine();
317
+ const remainingWidth = maxWidth;
318
+ let currentPos = 0;
319
+ while (currentPos < word.length) {
320
+ let bestLen = 0;
321
+ for (let len = word.length - currentPos; len > 0; len--) {
322
+ const substr = word.substring(currentPos, currentPos + len);
323
+ const m = ctx.measureText(substr, font);
324
+ if (currentLineWidth + m.width <= remainingWidth) {
325
+ bestLen = len;
326
+ if (len < word.length - currentPos) break;
327
+ }
328
+ }
329
+ if (bestLen === 0) {
330
+ if (currentSegments.length > 0) pushLine();
331
+ bestLen = 1;
332
+ }
333
+ const substr = word.substring(currentPos, currentPos + bestLen);
334
+ const m = ctx.measureText(substr, font);
335
+ currentSegments.push({
336
+ text: substr,
337
+ font: mergedStyle.font,
338
+ color: mergedStyle.color,
339
+ background: mergedStyle.background,
340
+ underline: mergedStyle.underline,
341
+ strikethrough: mergedStyle.strikethrough,
342
+ width: m.width,
343
+ height: lh,
344
+ ascent: m.ascent,
345
+ descent: m.descent,
346
+ offset: m.offset
347
+ });
348
+ currentLineWidth += m.width;
349
+ currentPos += bestLen;
350
+ if (currentPos < word.length && currentLineWidth >= remainingWidth) pushLine();
351
+ }
352
+ }
353
+ }
354
+ }
355
+ }
356
+ pushLine();
357
+ if (lines.length === 0) return [{
358
+ segments: [],
359
+ width: 0,
360
+ height: 0,
361
+ baseline: 0
362
+ }];
363
+ return lines;
364
+ }
365
+
198
366
  //#endregion
199
367
  //#region src/layout/components/stack.ts
200
368
  /**
@@ -272,10 +440,13 @@ function createCanvasMeasureContext(ctx) {
272
440
  ctx.textBaseline = "middle";
273
441
  const metrics = ctx.measureText(text);
274
442
  const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
443
+ const fontSize = font.size || 16;
275
444
  return {
276
445
  width: metrics.width,
277
- height: height || font.size || 16,
278
- offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2
446
+ height: height || fontSize,
447
+ offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
448
+ ascent: metrics.actualBoundingBoxAscent,
449
+ descent: metrics.actualBoundingBoxDescent
279
450
  };
280
451
  } };
281
452
  }
@@ -387,6 +558,7 @@ function measureTextSize(element, ctx, availableWidth) {
387
558
  function measureIntrinsicSize(element, ctx, availableWidth) {
388
559
  switch (element.type) {
389
560
  case "text": return measureTextSize(element, ctx, availableWidth);
561
+ case "richtext": return measureRichTextSize(element, ctx, availableWidth);
390
562
  case "box": return measureBoxSize(element, ctx, availableWidth, measureIntrinsicSize);
391
563
  case "stack": return measureStackSize(element, ctx, availableWidth, measureIntrinsicSize);
392
564
  case "image": return measureImageSize(element, ctx, availableWidth);
@@ -482,6 +654,23 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
482
654
  node.lineOffsets = [offset];
483
655
  }
484
656
  }
657
+ if (element.type === "richtext") {
658
+ const lineHeight = element.lineHeight ?? 1.2;
659
+ let lines = wrapRichText(ctx, element.spans, contentWidth, lineHeight);
660
+ if (element.maxLines && lines.length > element.maxLines) {
661
+ lines = lines.slice(0, element.maxLines);
662
+ if (element.ellipsis && lines.length > 0) {
663
+ const lastLine = lines[lines.length - 1];
664
+ if (lastLine.segments.length > 0) {
665
+ const lastSeg = lastLine.segments[lastLine.segments.length - 1];
666
+ lastSeg.text += "...";
667
+ lastSeg.width = ctx.measureText(lastSeg.text, lastSeg.font ?? {}).width;
668
+ lastLine.width = lastLine.segments.reduce((sum, s) => sum + s.width, 0);
669
+ }
670
+ }
671
+ }
672
+ node.richLines = lines;
673
+ }
485
674
  if (element.type === "box" || element.type === "stack") {
486
675
  const children = element.children ?? [];
487
676
  if (element.type === "stack") {
@@ -925,6 +1114,57 @@ function renderImage(ctx, node) {
925
1114
  if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
926
1115
  }
927
1116
 
1117
+ //#endregion
1118
+ //#region src/render/components/richtext.ts
1119
+ function renderRichText(ctx, node) {
1120
+ const element = node.element;
1121
+ const { contentX, contentY, contentWidth, contentHeight } = node.layout;
1122
+ const lines = node.richLines ?? [];
1123
+ if (lines.length === 0) return;
1124
+ const totalTextHeight = lines.reduce((sum, line) => sum + line.height, 0);
1125
+ let verticalOffset = 0;
1126
+ if (element.verticalAlign === "middle") verticalOffset = (contentHeight - totalTextHeight) / 2;
1127
+ else if (element.verticalAlign === "bottom") verticalOffset = contentHeight - totalTextHeight;
1128
+ let currentY = contentY + verticalOffset;
1129
+ for (const line of lines) {
1130
+ let lineX = contentX;
1131
+ if (element.align === "center") lineX = contentX + (contentWidth - line.width) / 2;
1132
+ else if (element.align === "right") lineX = contentX + (contentWidth - line.width);
1133
+ const baselineY = currentY + line.baseline;
1134
+ for (const seg of line.segments) {
1135
+ ctx.save();
1136
+ ctx.font = buildFontString(seg.font ?? {});
1137
+ if (seg.background) {
1138
+ ctx.fillStyle = resolveColor$1(ctx, seg.background, lineX, currentY, seg.width, line.height);
1139
+ ctx.fillRect(lineX, currentY, seg.width, line.height);
1140
+ }
1141
+ ctx.fillStyle = seg.color ? resolveColor$1(ctx, seg.color, lineX, currentY, seg.width, line.height) : "#000";
1142
+ ctx.textBaseline = "middle";
1143
+ ctx.fillText(seg.text, lineX, baselineY - seg.offset);
1144
+ if (seg.underline) {
1145
+ ctx.beginPath();
1146
+ ctx.strokeStyle = ctx.fillStyle;
1147
+ ctx.lineWidth = 1;
1148
+ ctx.moveTo(lineX, currentY + seg.height);
1149
+ ctx.lineTo(lineX + seg.width, currentY + seg.height);
1150
+ ctx.stroke();
1151
+ }
1152
+ if (seg.strikethrough) {
1153
+ ctx.beginPath();
1154
+ ctx.strokeStyle = ctx.fillStyle;
1155
+ ctx.lineWidth = 1;
1156
+ const strikeY = currentY + seg.height / 2 + seg.offset;
1157
+ ctx.moveTo(lineX, strikeY);
1158
+ ctx.lineTo(lineX + seg.width, strikeY);
1159
+ ctx.stroke();
1160
+ }
1161
+ ctx.restore();
1162
+ lineX += seg.width;
1163
+ }
1164
+ currentY += line.height;
1165
+ }
1166
+ }
1167
+
928
1168
  //#endregion
929
1169
  //#region src/compat/DOMMatrix.ts
930
1170
  const DOMMatrixCompat = (() => {
@@ -1270,6 +1510,9 @@ function renderNode(ctx, node) {
1270
1510
  case "text":
1271
1511
  renderText(ctx, node);
1272
1512
  break;
1513
+ case "richtext":
1514
+ renderRichText(ctx, node);
1515
+ break;
1273
1516
  case "image":
1274
1517
  renderImage(ctx, node);
1275
1518
  break;
package/render.mjs CHANGED
@@ -200,6 +200,174 @@ function measureImageSize(element, _ctx, _availableWidth) {
200
200
  };
201
201
  }
202
202
 
203
+ //#endregion
204
+ //#region src/layout/components/richtext.ts
205
+ /**
206
+ * 合并 span 样式和元素级别样式
207
+ * 优先级:span 样式 > 元素样式 > 默认值
208
+ * font 属性进行深度合并,允许 span 部分覆盖 element 的 font
209
+ */
210
+ function mergeSpanStyle(span, elementStyle) {
211
+ return {
212
+ font: {
213
+ ...elementStyle.font || {},
214
+ ...span.font || {}
215
+ },
216
+ color: span.color ?? elementStyle.color,
217
+ background: span.background ?? elementStyle.background,
218
+ underline: span.underline ?? elementStyle.underline ?? false,
219
+ strikethrough: span.strikethrough ?? elementStyle.strikethrough ?? false
220
+ };
221
+ }
222
+ /**
223
+ * 测量富文本元素的固有尺寸
224
+ */
225
+ function measureRichTextSize(element, ctx, availableWidth) {
226
+ const lineHeight = element.lineHeight ?? 1.2;
227
+ const elementStyle = {
228
+ font: element.font,
229
+ color: element.color,
230
+ background: element.background,
231
+ underline: element.underline,
232
+ strikethrough: element.strikethrough
233
+ };
234
+ const richLines = wrapRichText(ctx, element.spans, availableWidth, lineHeight, elementStyle);
235
+ let maxWidth = 0;
236
+ let totalHeight = 0;
237
+ for (const line of richLines) {
238
+ maxWidth = Math.max(maxWidth, line.width);
239
+ totalHeight += line.height;
240
+ }
241
+ return {
242
+ width: maxWidth,
243
+ height: totalHeight
244
+ };
245
+ }
246
+ /**
247
+ * 将富文本内容拆分为行
248
+ */
249
+ function wrapRichText(ctx, spans, maxWidth, lineHeightScale = 1.2, elementStyle = {}) {
250
+ const lines = [];
251
+ let currentSegments = [];
252
+ let currentLineWidth = 0;
253
+ const pushLine = () => {
254
+ if (currentSegments.length === 0) return;
255
+ let maxTopDist = 0;
256
+ let maxBottomDist = 0;
257
+ let maxLineHeight = 0;
258
+ for (const seg of currentSegments) {
259
+ const topDist = seg.ascent - seg.offset;
260
+ const bottomDist = seg.descent + seg.offset;
261
+ maxTopDist = Math.max(maxTopDist, topDist);
262
+ maxBottomDist = Math.max(maxBottomDist, bottomDist);
263
+ maxLineHeight = Math.max(maxLineHeight, seg.height);
264
+ }
265
+ const contentHeight = maxTopDist + maxBottomDist;
266
+ const finalHeight = Math.max(contentHeight, maxLineHeight);
267
+ const extra = (finalHeight - contentHeight) / 2;
268
+ lines.push({
269
+ segments: [...currentSegments],
270
+ width: currentLineWidth,
271
+ height: finalHeight,
272
+ baseline: maxTopDist + extra
273
+ });
274
+ currentSegments = [];
275
+ currentLineWidth = 0;
276
+ };
277
+ for (const span of spans) {
278
+ const mergedStyle = mergeSpanStyle(span, elementStyle);
279
+ const font = mergedStyle.font;
280
+ const lh = (font.size ?? 16) * lineHeightScale;
281
+ const words = span.text.split(/(\s+)/);
282
+ for (const word of words) {
283
+ if (word === "") continue;
284
+ if (/^\s+$/.test(word)) {
285
+ const metrics = ctx.measureText(word, font);
286
+ const wordWidth = metrics.width;
287
+ if (maxWidth > 0 && currentLineWidth + wordWidth > maxWidth && currentSegments.length > 0) pushLine();
288
+ currentSegments.push({
289
+ text: word,
290
+ font: mergedStyle.font,
291
+ color: mergedStyle.color,
292
+ background: mergedStyle.background,
293
+ underline: mergedStyle.underline,
294
+ strikethrough: mergedStyle.strikethrough,
295
+ width: wordWidth,
296
+ height: lh,
297
+ ascent: metrics.ascent,
298
+ descent: metrics.descent,
299
+ offset: metrics.offset
300
+ });
301
+ currentLineWidth += wordWidth;
302
+ } else {
303
+ const metrics = ctx.measureText(word, font);
304
+ const wordWidth = metrics.width;
305
+ if (maxWidth <= 0 || currentLineWidth + wordWidth <= maxWidth) {
306
+ currentSegments.push({
307
+ text: word,
308
+ font: mergedStyle.font,
309
+ color: mergedStyle.color,
310
+ background: mergedStyle.background,
311
+ underline: mergedStyle.underline,
312
+ strikethrough: mergedStyle.strikethrough,
313
+ width: wordWidth,
314
+ height: lh,
315
+ ascent: metrics.ascent,
316
+ descent: metrics.descent,
317
+ offset: metrics.offset
318
+ });
319
+ currentLineWidth += wordWidth;
320
+ } else {
321
+ if (currentSegments.length > 0) pushLine();
322
+ const remainingWidth = maxWidth;
323
+ let currentPos = 0;
324
+ while (currentPos < word.length) {
325
+ let bestLen = 0;
326
+ for (let len = word.length - currentPos; len > 0; len--) {
327
+ const substr = word.substring(currentPos, currentPos + len);
328
+ const m = ctx.measureText(substr, font);
329
+ if (currentLineWidth + m.width <= remainingWidth) {
330
+ bestLen = len;
331
+ if (len < word.length - currentPos) break;
332
+ }
333
+ }
334
+ if (bestLen === 0) {
335
+ if (currentSegments.length > 0) pushLine();
336
+ bestLen = 1;
337
+ }
338
+ const substr = word.substring(currentPos, currentPos + bestLen);
339
+ const m = ctx.measureText(substr, font);
340
+ currentSegments.push({
341
+ text: substr,
342
+ font: mergedStyle.font,
343
+ color: mergedStyle.color,
344
+ background: mergedStyle.background,
345
+ underline: mergedStyle.underline,
346
+ strikethrough: mergedStyle.strikethrough,
347
+ width: m.width,
348
+ height: lh,
349
+ ascent: m.ascent,
350
+ descent: m.descent,
351
+ offset: m.offset
352
+ });
353
+ currentLineWidth += m.width;
354
+ currentPos += bestLen;
355
+ if (currentPos < word.length && currentLineWidth >= remainingWidth) pushLine();
356
+ }
357
+ }
358
+ }
359
+ }
360
+ }
361
+ pushLine();
362
+ if (lines.length === 0) return [{
363
+ segments: [],
364
+ width: 0,
365
+ height: 0,
366
+ baseline: 0
367
+ }];
368
+ return lines;
369
+ }
370
+
203
371
  //#endregion
204
372
  //#region src/layout/components/stack.ts
205
373
  /**
@@ -277,10 +445,13 @@ function createCanvasMeasureContext(ctx) {
277
445
  ctx.textBaseline = "middle";
278
446
  const metrics = ctx.measureText(text);
279
447
  const height = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
448
+ const fontSize = font.size || 16;
280
449
  return {
281
450
  width: metrics.width,
282
- height: height || font.size || 16,
283
- offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2
451
+ height: height || fontSize,
452
+ offset: (metrics.actualBoundingBoxAscent - metrics.actualBoundingBoxDescent) / 2,
453
+ ascent: metrics.actualBoundingBoxAscent,
454
+ descent: metrics.actualBoundingBoxDescent
284
455
  };
285
456
  } };
286
457
  }
@@ -392,6 +563,7 @@ function measureTextSize(element, ctx, availableWidth) {
392
563
  function measureIntrinsicSize(element, ctx, availableWidth) {
393
564
  switch (element.type) {
394
565
  case "text": return measureTextSize(element, ctx, availableWidth);
566
+ case "richtext": return measureRichTextSize(element, ctx, availableWidth);
395
567
  case "box": return measureBoxSize(element, ctx, availableWidth, measureIntrinsicSize);
396
568
  case "stack": return measureStackSize(element, ctx, availableWidth, measureIntrinsicSize);
397
569
  case "image": return measureImageSize(element, ctx, availableWidth);
@@ -487,6 +659,23 @@ function computeLayout(element, ctx, constraints, x = 0, y = 0) {
487
659
  node.lineOffsets = [offset];
488
660
  }
489
661
  }
662
+ if (element.type === "richtext") {
663
+ const lineHeight = element.lineHeight ?? 1.2;
664
+ let lines = wrapRichText(ctx, element.spans, contentWidth, lineHeight);
665
+ if (element.maxLines && lines.length > element.maxLines) {
666
+ lines = lines.slice(0, element.maxLines);
667
+ if (element.ellipsis && lines.length > 0) {
668
+ const lastLine = lines[lines.length - 1];
669
+ if (lastLine.segments.length > 0) {
670
+ const lastSeg = lastLine.segments[lastLine.segments.length - 1];
671
+ lastSeg.text += "...";
672
+ lastSeg.width = ctx.measureText(lastSeg.text, lastSeg.font ?? {}).width;
673
+ lastLine.width = lastLine.segments.reduce((sum, s) => sum + s.width, 0);
674
+ }
675
+ }
676
+ }
677
+ node.richLines = lines;
678
+ }
490
679
  if (element.type === "box" || element.type === "stack") {
491
680
  const children = element.children ?? [];
492
681
  if (element.type === "stack") {
@@ -930,6 +1119,57 @@ function renderImage(ctx, node) {
930
1119
  if (element.opacity !== void 0 && element.opacity < 1) ctx.globalAlpha = 1;
931
1120
  }
932
1121
 
1122
+ //#endregion
1123
+ //#region src/render/components/richtext.ts
1124
+ function renderRichText(ctx, node) {
1125
+ const element = node.element;
1126
+ const { contentX, contentY, contentWidth, contentHeight } = node.layout;
1127
+ const lines = node.richLines ?? [];
1128
+ if (lines.length === 0) return;
1129
+ const totalTextHeight = lines.reduce((sum, line) => sum + line.height, 0);
1130
+ let verticalOffset = 0;
1131
+ if (element.verticalAlign === "middle") verticalOffset = (contentHeight - totalTextHeight) / 2;
1132
+ else if (element.verticalAlign === "bottom") verticalOffset = contentHeight - totalTextHeight;
1133
+ let currentY = contentY + verticalOffset;
1134
+ for (const line of lines) {
1135
+ let lineX = contentX;
1136
+ if (element.align === "center") lineX = contentX + (contentWidth - line.width) / 2;
1137
+ else if (element.align === "right") lineX = contentX + (contentWidth - line.width);
1138
+ const baselineY = currentY + line.baseline;
1139
+ for (const seg of line.segments) {
1140
+ ctx.save();
1141
+ ctx.font = buildFontString(seg.font ?? {});
1142
+ if (seg.background) {
1143
+ ctx.fillStyle = resolveColor$1(ctx, seg.background, lineX, currentY, seg.width, line.height);
1144
+ ctx.fillRect(lineX, currentY, seg.width, line.height);
1145
+ }
1146
+ ctx.fillStyle = seg.color ? resolveColor$1(ctx, seg.color, lineX, currentY, seg.width, line.height) : "#000";
1147
+ ctx.textBaseline = "middle";
1148
+ ctx.fillText(seg.text, lineX, baselineY - seg.offset);
1149
+ if (seg.underline) {
1150
+ ctx.beginPath();
1151
+ ctx.strokeStyle = ctx.fillStyle;
1152
+ ctx.lineWidth = 1;
1153
+ ctx.moveTo(lineX, currentY + seg.height);
1154
+ ctx.lineTo(lineX + seg.width, currentY + seg.height);
1155
+ ctx.stroke();
1156
+ }
1157
+ if (seg.strikethrough) {
1158
+ ctx.beginPath();
1159
+ ctx.strokeStyle = ctx.fillStyle;
1160
+ ctx.lineWidth = 1;
1161
+ const strikeY = currentY + seg.height / 2 + seg.offset;
1162
+ ctx.moveTo(lineX, strikeY);
1163
+ ctx.lineTo(lineX + seg.width, strikeY);
1164
+ ctx.stroke();
1165
+ }
1166
+ ctx.restore();
1167
+ lineX += seg.width;
1168
+ }
1169
+ currentY += line.height;
1170
+ }
1171
+ }
1172
+
933
1173
  //#endregion
934
1174
  //#region src/compat/DOMMatrix.ts
935
1175
  const DOMMatrixCompat = (() => {
@@ -1275,6 +1515,9 @@ function renderNode(ctx, node) {
1275
1515
  case "text":
1276
1516
  renderText(ctx, node);
1277
1517
  break;
1518
+ case "richtext":
1519
+ renderRichText(ctx, node);
1520
+ break;
1278
1521
  case "image":
1279
1522
  renderImage(ctx, node);
1280
1523
  break;