html2canvas-pro 2.0.2 → 2.0.4

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,5 +1,5 @@
1
1
  /*!
2
- * html2canvas-pro 2.0.2 <https://yorickshan.github.io/html2canvas-pro/>
2
+ * html2canvas-pro 2.0.4 <https://yorickshan.github.io/html2canvas-pro/>
3
3
  * Copyright (c) 2024-present yorickshan and html2canvas-pro contributors
4
4
  * Released under MIT License
5
5
  */
@@ -4239,6 +4239,30 @@ const wordBreak = {
4239
4239
  }
4240
4240
  };
4241
4241
 
4242
+ const isVerticalWritingMode = (writingMode) => writingMode !== 0 /* WRITING_MODE.HORIZONTAL_TB */;
4243
+ const isSidewaysWritingMode = (writingMode) => writingMode === 3 /* WRITING_MODE.SIDEWAYS_RL */ || writingMode === 4 /* WRITING_MODE.SIDEWAYS_LR */;
4244
+ const writingMode = {
4245
+ name: 'writing-mode',
4246
+ initialValue: 'horizontal-tb',
4247
+ prefix: false,
4248
+ type: 2 /* PropertyDescriptorParsingType.IDENT_VALUE */,
4249
+ parse: (_context, writingMode) => {
4250
+ switch (writingMode) {
4251
+ case 'vertical-rl':
4252
+ return 1 /* WRITING_MODE.VERTICAL_RL */;
4253
+ case 'vertical-lr':
4254
+ return 2 /* WRITING_MODE.VERTICAL_LR */;
4255
+ case 'sideways-rl':
4256
+ return 3 /* WRITING_MODE.SIDEWAYS_RL */;
4257
+ case 'sideways-lr':
4258
+ return 4 /* WRITING_MODE.SIDEWAYS_LR */;
4259
+ case 'horizontal-tb':
4260
+ default:
4261
+ return 0 /* WRITING_MODE.HORIZONTAL_TB */;
4262
+ }
4263
+ }
4264
+ };
4265
+
4242
4266
  const zIndex = {
4243
4267
  name: 'z-index',
4244
4268
  initialValue: 'auto',
@@ -4855,6 +4879,7 @@ class CSSParsedDeclaration {
4855
4879
  this.webkitTextStrokeWidth = parse(context, webkitTextStrokeWidth, declaration.webkitTextStrokeWidth);
4856
4880
  this.webkitLineClamp = parse(context, webkitLineClamp, declaration.webkitLineClamp);
4857
4881
  this.wordBreak = parse(context, wordBreak, declaration.wordBreak);
4882
+ this.writingMode = parse(context, writingMode, declaration.writingMode);
4858
4883
  this.zIndex = parse(context, zIndex, declaration.zIndex);
4859
4884
  this.objectFit = parse(context, objectFit, declaration.objectFit);
4860
4885
  this.imageRendering = parse(context, imageRendering, declaration.imageRendering);
@@ -5790,6 +5815,9 @@ const segmentWords = (value, styles) => {
5790
5815
  return breakWords(value, styles);
5791
5816
  };
5792
5817
  const breakText = (value, styles) => {
5818
+ if (isVerticalWritingMode(styles.writingMode)) {
5819
+ return segmentGraphemes(value);
5820
+ }
5793
5821
  return styles.letterSpacing !== 0 ? segmentGraphemes(value) : segmentWords(value, styles);
5794
5822
  };
5795
5823
  // https://drafts.csswg.org/css-text/#word-separator
@@ -5829,6 +5857,11 @@ const breakWords = (str, styles) => {
5829
5857
  class TextContainer {
5830
5858
  constructor(context, node, styles) {
5831
5859
  this.text = transform(node.data, styles.textTransform);
5860
+ // Range offsets below are based on transformed text; keep the node in sync
5861
+ // when casing changes string length, for example "ß".toUpperCase() === "SS".
5862
+ if (this.text.length !== node.data.length) {
5863
+ node.data = this.text;
5864
+ }
5832
5865
  this.textBounds = parseTextBounds(context, this.text, styles, node);
5833
5866
  }
5834
5867
  }
@@ -6586,10 +6619,11 @@ class DocumentCloner {
6586
6619
  return iframe;
6587
6620
  });
6588
6621
  /**
6589
- * The baseURI of the document will be lost after documentClone.open().
6590
- * We save it before open() to preserve the original base URI for resource resolution.
6591
- * */
6592
- const baseUri = documentClone.baseURI;
6622
+ * The base URI used for resolving relative URLs (e.g. background-image) in the clone.
6623
+ * Must come from the source document: the iframe document is about:blank, so
6624
+ * documentClone.baseURI would break getComputedStyle() for relative background URLs.
6625
+ */
6626
+ const baseUri = ownerDocument.baseURI;
6593
6627
  documentClone.open();
6594
6628
  const rawHTML = serializeDoctype(document.doctype) + '<html></html>';
6595
6629
  try {
@@ -8716,7 +8750,11 @@ class TextRenderer {
8716
8750
  * call (fillText or strokeText), allowing fill and stroke paths to share one
8717
8751
  * implementation.
8718
8752
  */
8719
- iterateLettersWithLetterSpacing(text, letterSpacing, baseline, renderFn) {
8753
+ iterateLettersWithLetterSpacing(text, letterSpacing, baseline, writingMode, renderFn) {
8754
+ if (isVerticalWritingMode(writingMode)) {
8755
+ this.iterateVerticalGlyphs(text, letterSpacing, baseline, writingMode, renderFn);
8756
+ return;
8757
+ }
8720
8758
  const letters = segmentGraphemes(text.text);
8721
8759
  const y = text.bounds.top + baseline;
8722
8760
  let left = text.bounds.left;
@@ -8733,17 +8771,39 @@ class TextRenderer {
8733
8771
  left += this.ctx.measureText(letter).width + letterSpacing;
8734
8772
  }
8735
8773
  }
8774
+ iterateVerticalGlyphs(text, letterSpacing, baseline, writingMode, renderFn) {
8775
+ const letters = segmentGraphemes(text.text);
8776
+ let top = text.bounds.top;
8777
+ for (const letter of letters) {
8778
+ if (isSidewaysWritingMode(writingMode) || (!hasCJKCharacters(letter) && letter.trim().length > 0)) {
8779
+ this.ctx.save();
8780
+ this.ctx.translate(text.bounds.left + baseline, top);
8781
+ this.ctx.rotate(writingMode === 4 /* WRITING_MODE.SIDEWAYS_LR */ ? -Math.PI / 2 : Math.PI / 2);
8782
+ renderFn(letter, 0, 0);
8783
+ this.ctx.restore();
8784
+ }
8785
+ else {
8786
+ const savedBaseline = this.ctx.textBaseline;
8787
+ if (hasCJKCharacters(letter)) {
8788
+ this.ctx.textBaseline = 'ideographic';
8789
+ }
8790
+ renderFn(letter, text.bounds.left, top + baseline);
8791
+ this.ctx.textBaseline = savedBaseline;
8792
+ }
8793
+ top += this.ctx.measureText(letter).width + letterSpacing;
8794
+ }
8795
+ }
8736
8796
  /**
8737
8797
  * Render text with letter-spacing applied (fill pass).
8738
8798
  * When letterSpacing is 0 the whole string is drawn in one call; otherwise each
8739
8799
  * grapheme is drawn individually so spacing and CJK baseline are applied correctly.
8740
8800
  */
8741
- renderTextWithLetterSpacing(text, letterSpacing, baseline) {
8742
- if (letterSpacing === 0) {
8801
+ renderTextWithLetterSpacing(text, letterSpacing, baseline, writingMode = 0 /* WRITING_MODE.HORIZONTAL_TB */) {
8802
+ if (letterSpacing === 0 && !isVerticalWritingMode(writingMode)) {
8743
8803
  this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + baseline);
8744
8804
  }
8745
8805
  else {
8746
- this.iterateLettersWithLetterSpacing(text, letterSpacing, baseline, (letter, x, y) => {
8806
+ this.iterateLettersWithLetterSpacing(text, letterSpacing, baseline, writingMode, (letter, x, y) => {
8747
8807
  this.ctx.fillText(letter, x, y);
8748
8808
  });
8749
8809
  }
@@ -8757,7 +8817,7 @@ class TextRenderer {
8757
8817
  switch (paintOrderLayer) {
8758
8818
  case 0 /* PAINT_ORDER_LAYER.FILL */:
8759
8819
  this.ctx.fillStyle = asString(styles.color);
8760
- this.renderTextWithLetterSpacing(textBound, styles.letterSpacing, styles.fontSize.number);
8820
+ this.renderTextWithLetterSpacing(textBound, styles.letterSpacing, styles.fontSize.number, styles.writingMode);
8761
8821
  break;
8762
8822
  case 1 /* PAINT_ORDER_LAYER.STROKE */:
8763
8823
  if (styles.webkitTextStrokeWidth && textBound.text.trim().length) {
@@ -8765,11 +8825,11 @@ class TextRenderer {
8765
8825
  this.ctx.lineWidth = styles.webkitTextStrokeWidth;
8766
8826
  this.ctx.lineJoin =
8767
8827
  typeof window !== 'undefined' && !!window.chrome ? 'miter' : 'round';
8768
- if (styles.letterSpacing === 0) {
8828
+ if (styles.letterSpacing === 0 && !isVerticalWritingMode(styles.writingMode)) {
8769
8829
  this.ctx.strokeText(textBound.text, textBound.bounds.left, textBound.bounds.top + styles.fontSize.number);
8770
8830
  }
8771
8831
  else {
8772
- this.iterateLettersWithLetterSpacing(textBound, styles.letterSpacing, styles.fontSize.number, (letter, x, y) => this.ctx.strokeText(letter, x, y));
8832
+ this.iterateLettersWithLetterSpacing(textBound, styles.letterSpacing, styles.fontSize.number, styles.writingMode, (letter, x, y) => this.ctx.strokeText(letter, x, y));
8773
8833
  }
8774
8834
  this.ctx.strokeStyle = '';
8775
8835
  this.ctx.lineWidth = 0;
@@ -9005,11 +9065,11 @@ class TextRenderer {
9005
9065
  switch (paintOrderLayer) {
9006
9066
  case 0 /* PAINT_ORDER_LAYER.FILL */:
9007
9067
  this.ctx.fillStyle = asString(styles.color);
9008
- if (styles.letterSpacing === 0) {
9068
+ if (styles.letterSpacing === 0 && !isVerticalWritingMode(styles.writingMode)) {
9009
9069
  this.ctx.fillText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
9010
9070
  }
9011
9071
  else {
9012
- this.iterateLettersWithLetterSpacing(truncatedBounds, styles.letterSpacing, styles.fontSize.number, (letter, x, y) => this.ctx.fillText(letter, x, y));
9072
+ this.iterateLettersWithLetterSpacing(truncatedBounds, styles.letterSpacing, styles.fontSize.number, styles.writingMode, (letter, x, y) => this.ctx.fillText(letter, x, y));
9013
9073
  }
9014
9074
  break;
9015
9075
  case 1 /* PAINT_ORDER_LAYER.STROKE */:
@@ -9018,11 +9078,11 @@ class TextRenderer {
9018
9078
  this.ctx.lineWidth = styles.webkitTextStrokeWidth;
9019
9079
  this.ctx.lineJoin =
9020
9080
  typeof window !== 'undefined' && !!window.chrome ? 'miter' : 'round';
9021
- if (styles.letterSpacing === 0) {
9081
+ if (styles.letterSpacing === 0 && !isVerticalWritingMode(styles.writingMode)) {
9022
9082
  this.ctx.strokeText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
9023
9083
  }
9024
9084
  else {
9025
- this.iterateLettersWithLetterSpacing(truncatedBounds, styles.letterSpacing, styles.fontSize.number, (letter, x, y) => this.ctx.strokeText(letter, x, y));
9085
+ this.iterateLettersWithLetterSpacing(truncatedBounds, styles.letterSpacing, styles.fontSize.number, styles.writingMode, (letter, x, y) => this.ctx.strokeText(letter, x, y));
9026
9086
  }
9027
9087
  this.ctx.strokeStyle = '';
9028
9088
  this.ctx.lineWidth = 0;
@@ -9077,11 +9137,11 @@ class TextRenderer {
9077
9137
  switch (paintOrderLayer) {
9078
9138
  case 0 /* PAINT_ORDER_LAYER.FILL */: {
9079
9139
  this.ctx.fillStyle = asString(styles.color);
9080
- if (styles.letterSpacing === 0) {
9140
+ if (styles.letterSpacing === 0 && !isVerticalWritingMode(styles.writingMode)) {
9081
9141
  this.ctx.fillText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
9082
9142
  }
9083
9143
  else {
9084
- this.iterateLettersWithLetterSpacing(truncatedBounds, styles.letterSpacing, styles.fontSize.number, (letter, x, y) => this.ctx.fillText(letter, x, y));
9144
+ this.iterateLettersWithLetterSpacing(truncatedBounds, styles.letterSpacing, styles.fontSize.number, styles.writingMode, (letter, x, y) => this.ctx.fillText(letter, x, y));
9085
9145
  }
9086
9146
  const textShadows = styles.textShadow;
9087
9147
  if (textShadows.length && truncatedText.trim().length) {
@@ -9093,11 +9153,11 @@ class TextRenderer {
9093
9153
  this.ctx.shadowOffsetX = textShadow.offsetX.number * this.options.scale;
9094
9154
  this.ctx.shadowOffsetY = textShadow.offsetY.number * this.options.scale;
9095
9155
  this.ctx.shadowBlur = textShadow.blur.number;
9096
- if (styles.letterSpacing === 0) {
9156
+ if (styles.letterSpacing === 0 && !isVerticalWritingMode(styles.writingMode)) {
9097
9157
  this.ctx.fillText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
9098
9158
  }
9099
9159
  else {
9100
- this.iterateLettersWithLetterSpacing(truncatedBounds, styles.letterSpacing, styles.fontSize.number, (letter, x, y) => this.ctx.fillText(letter, x, y));
9160
+ this.iterateLettersWithLetterSpacing(truncatedBounds, styles.letterSpacing, styles.fontSize.number, styles.writingMode, (letter, x, y) => this.ctx.fillText(letter, x, y));
9101
9161
  }
9102
9162
  });
9103
9163
  this.ctx.shadowColor = '';
@@ -9113,11 +9173,11 @@ class TextRenderer {
9113
9173
  this.ctx.lineWidth = styles.webkitTextStrokeWidth;
9114
9174
  this.ctx.lineJoin =
9115
9175
  typeof window !== 'undefined' && !!window.chrome ? 'miter' : 'round';
9116
- if (styles.letterSpacing === 0) {
9176
+ if (styles.letterSpacing === 0 && !isVerticalWritingMode(styles.writingMode)) {
9117
9177
  this.ctx.strokeText(truncatedText, firstBound.bounds.left, firstBound.bounds.top + styles.fontSize.number);
9118
9178
  }
9119
9179
  else {
9120
- this.iterateLettersWithLetterSpacing(truncatedBounds, styles.letterSpacing, styles.fontSize.number, (letter, x, y) => this.ctx.strokeText(letter, x, y));
9180
+ this.iterateLettersWithLetterSpacing(truncatedBounds, styles.letterSpacing, styles.fontSize.number, styles.writingMode, (letter, x, y) => this.ctx.strokeText(letter, x, y));
9121
9181
  }
9122
9182
  this.ctx.strokeStyle = '';
9123
9183
  this.ctx.lineWidth = 0;
@@ -9134,7 +9194,7 @@ class TextRenderer {
9134
9194
  switch (paintOrderLayer) {
9135
9195
  case 0 /* PAINT_ORDER_LAYER.FILL */: {
9136
9196
  this.ctx.fillStyle = asString(styles.color);
9137
- this.renderTextWithLetterSpacing(text, styles.letterSpacing, styles.fontSize.number);
9197
+ this.renderTextWithLetterSpacing(text, styles.letterSpacing, styles.fontSize.number, styles.writingMode);
9138
9198
  const textShadows = styles.textShadow;
9139
9199
  if (textShadows.length && text.text.trim().length) {
9140
9200
  textShadows
@@ -9145,7 +9205,7 @@ class TextRenderer {
9145
9205
  this.ctx.shadowOffsetX = textShadow.offsetX.number * this.options.scale;
9146
9206
  this.ctx.shadowOffsetY = textShadow.offsetY.number * this.options.scale;
9147
9207
  this.ctx.shadowBlur = textShadow.blur.number;
9148
- this.renderTextWithLetterSpacing(text, styles.letterSpacing, styles.fontSize.number);
9208
+ this.renderTextWithLetterSpacing(text, styles.letterSpacing, styles.fontSize.number, styles.writingMode);
9149
9209
  });
9150
9210
  this.ctx.shadowColor = '';
9151
9211
  this.ctx.shadowOffsetX = 0;
@@ -9164,11 +9224,11 @@ class TextRenderer {
9164
9224
  this.ctx.lineJoin =
9165
9225
  typeof window !== 'undefined' && !!window.chrome ? 'miter' : 'round';
9166
9226
  const baseline = styles.fontSize.number;
9167
- if (styles.letterSpacing === 0) {
9227
+ if (styles.letterSpacing === 0 && !isVerticalWritingMode(styles.writingMode)) {
9168
9228
  this.ctx.strokeText(text.text, text.bounds.left, text.bounds.top + baseline);
9169
9229
  }
9170
9230
  else {
9171
- this.iterateLettersWithLetterSpacing(text, styles.letterSpacing, baseline, (letter, x, y) => this.ctx.strokeText(letter, x, y));
9231
+ this.iterateLettersWithLetterSpacing(text, styles.letterSpacing, baseline, styles.writingMode, (letter, x, y) => this.ctx.strokeText(letter, x, y));
9172
9232
  }
9173
9233
  this.ctx.strokeStyle = '';
9174
9234
  this.ctx.lineWidth = 0;
@@ -9463,7 +9523,7 @@ class CanvasRenderer extends Renderer {
9463
9523
  new Vector(bounds.left, bounds.top + bounds.height)
9464
9524
  ]);
9465
9525
  this.ctx.clip();
9466
- this.textRenderer.renderTextWithLetterSpacing(new TextBounds(container.value, textBounds), styles.letterSpacing, baseline);
9526
+ this.textRenderer.renderTextWithLetterSpacing(new TextBounds(container.value, textBounds), styles.letterSpacing, baseline, styles.writingMode);
9467
9527
  this.ctx.restore();
9468
9528
  this.ctx.textBaseline = 'alphabetic';
9469
9529
  this.ctx.textAlign = 'left';
@@ -9490,7 +9550,7 @@ class CanvasRenderer extends Renderer {
9490
9550
  this.ctx.textBaseline = 'middle';
9491
9551
  this.ctx.textAlign = 'right';
9492
9552
  const bounds = new Bounds(container.bounds.left, container.bounds.top + getAbsoluteValue(container.styles.paddingTop, container.bounds.width), container.bounds.width, computeLineHeight(styles.lineHeight, styles.fontSize.number) / 2 + 1);
9493
- this.textRenderer.renderTextWithLetterSpacing(new TextBounds(paint.listValue, bounds), styles.letterSpacing, computeLineHeight(styles.lineHeight, styles.fontSize.number) / 2 + 2);
9553
+ this.textRenderer.renderTextWithLetterSpacing(new TextBounds(paint.listValue, bounds), styles.letterSpacing, computeLineHeight(styles.lineHeight, styles.fontSize.number) / 2 + 2, styles.writingMode);
9494
9554
  this.ctx.textBaseline = 'bottom';
9495
9555
  this.ctx.textAlign = 'left';
9496
9556
  }