html2canvas-pro 1.5.13 → 1.6.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/README.md CHANGED
@@ -47,12 +47,32 @@ import html2canvas from 'html2canvas-pro';
47
47
 
48
48
  To render an `element` with html2canvas-pro with some (optional) [options](/docs/configuration.md), simply call `html2canvas(element, options);`
49
49
 
50
+ ### Basic Example
51
+
50
52
  ```javascript
51
53
  html2canvas(document.body).then(function(canvas) {
52
54
  document.body.appendChild(canvas);
53
55
  });
54
56
  ```
55
57
 
58
+ ### Controlling Output Dimensions
59
+
60
+ ⚠️ **Important**: By default, the output canvas dimensions are affected by `devicePixelRatio`.
61
+
62
+ ```javascript
63
+ // If you need exact pixel dimensions (e.g., for a specific file size):
64
+ html2canvas(element, {
65
+ width: 1920,
66
+ height: 1080,
67
+ scale: 1 // Set scale to 1 for exact dimensions
68
+ }).then(canvas => {
69
+ // Canvas will be exactly 1920×1080 pixels
70
+ const dataURL = canvas.toDataURL('image/png');
71
+ });
72
+ ```
73
+
74
+ See the [Configuration Guide](/docs/configuration.md#canvas-dimensions) for more details.
75
+
56
76
  ## Contribution
57
77
 
58
78
  If you want to add some features, feel free to submit PR.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * html2canvas-pro 1.5.13 <https://yorickshan.github.io/html2canvas-pro/>
2
+ * html2canvas-pro 1.6.0 <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
  */
@@ -112,7 +112,19 @@ var Bounds = /** @class */ (function () {
112
112
  return new Bounds(clientRect.left + context.windowBounds.left, clientRect.top + context.windowBounds.top, clientRect.width, clientRect.height);
113
113
  };
114
114
  Bounds.fromDOMRectList = function (context, domRectList) {
115
- var domRect = Array.from(domRectList).find(function (rect) { return rect.width !== 0; });
115
+ var rects = Array.from(domRectList);
116
+ // First try to find a rect with non-zero width
117
+ var domRect = rects.find(function (rect) { return rect.width !== 0; });
118
+ // If not found, try to find a rect with non-zero height
119
+ // This handles cases like inline-flex with single child where width might be 0
120
+ if (!domRect) {
121
+ domRect = rects.find(function (rect) { return rect.height !== 0; });
122
+ }
123
+ // If still not found but rects exist, use the first rect
124
+ // Position info (left, top) might still be valid even if dimensions are 0
125
+ if (!domRect && rects.length > 0) {
126
+ domRect = rects[0];
127
+ }
116
128
  return domRect
117
129
  ? new Bounds(domRect.left + context.windowBounds.left, domRect.top + context.windowBounds.top, domRect.width, domRect.height)
118
130
  : Bounds.EMPTY;
@@ -1604,6 +1616,94 @@ var isLength = function (token) {
1604
1616
  var isLengthPercentage = function (token) {
1605
1617
  return token.type === 16 /* TokenType.PERCENTAGE_TOKEN */ || isLength(token);
1606
1618
  };
1619
+ /**
1620
+ * Check if a token is a calc() function
1621
+ */
1622
+ var isCalcFunction = function (token) {
1623
+ return token.type === 18 /* TokenType.FUNCTION */ && token.name === 'calc';
1624
+ };
1625
+ /**
1626
+ * Evaluate a calc() expression and convert to LengthPercentage token
1627
+ * Supports basic arithmetic: +, -, *, /
1628
+ * Note: Percentages in calc() are converted based on a context value
1629
+ */
1630
+ var evaluateCalcToLengthPercentage = function (calcToken, contextValue) {
1631
+ if (contextValue === void 0) { contextValue = 0; }
1632
+ // Build expression string from tokens
1633
+ var buildExpression = function (values) {
1634
+ var expression = '';
1635
+ for (var _i = 0, values_1 = values; _i < values_1.length; _i++) {
1636
+ var value = values_1[_i];
1637
+ if (value.type === 31 /* TokenType.WHITESPACE_TOKEN */) {
1638
+ continue;
1639
+ }
1640
+ if (value.type === 18 /* TokenType.FUNCTION */) {
1641
+ if (value.name === 'calc') {
1642
+ var nested = buildExpression(value.values);
1643
+ if (nested === null)
1644
+ return null;
1645
+ expression += "(".concat(nested, ")");
1646
+ }
1647
+ else {
1648
+ return null;
1649
+ }
1650
+ }
1651
+ else if (value.type === 17 /* TokenType.NUMBER_TOKEN */) {
1652
+ expression += value.number.toString();
1653
+ }
1654
+ else if (value.type === 15 /* TokenType.DIMENSION_TOKEN */) {
1655
+ // Convert units to px
1656
+ if (value.unit === 'px') {
1657
+ expression += value.number.toString();
1658
+ }
1659
+ else if (value.unit === 'rem' || value.unit === 'em') {
1660
+ expression += (value.number * 16).toString();
1661
+ }
1662
+ else {
1663
+ expression += value.number.toString();
1664
+ }
1665
+ }
1666
+ else if (value.type === 16 /* TokenType.PERCENTAGE_TOKEN */) {
1667
+ // Convert percentage to absolute value based on context
1668
+ expression += ((value.number / 100) * contextValue).toString();
1669
+ }
1670
+ else if (value.type === 6 /* TokenType.DELIM_TOKEN */) {
1671
+ var op = value.value;
1672
+ if (op === '+' || op === '-' || op === '*' || op === '/') {
1673
+ expression += " ".concat(op, " ");
1674
+ }
1675
+ else if (op === '(') {
1676
+ expression += '(';
1677
+ }
1678
+ else if (op === ')') {
1679
+ expression += ')';
1680
+ }
1681
+ }
1682
+ }
1683
+ return expression;
1684
+ };
1685
+ try {
1686
+ var expression = buildExpression(calcToken.values);
1687
+ if (expression === null || expression.trim() === '') {
1688
+ return null;
1689
+ }
1690
+ // Evaluate the expression
1691
+ // Note: Using Function constructor (similar to color.ts line 185)
1692
+ var result = new Function('return ' + expression)();
1693
+ if (typeof result === 'number' && !isNaN(result)) {
1694
+ // Return as a number token in px
1695
+ return {
1696
+ type: 17 /* TokenType.NUMBER_TOKEN */,
1697
+ number: result,
1698
+ flags: FLAG_INTEGER
1699
+ };
1700
+ }
1701
+ }
1702
+ catch (e) {
1703
+ return null;
1704
+ }
1705
+ return null;
1706
+ };
1607
1707
  var parseLengthPercentageTuple = function (tokens) {
1608
1708
  return tokens.length > 1 ? [tokens[0], tokens[1]] : [tokens[0]];
1609
1709
  };
@@ -3344,7 +3444,20 @@ var backgroundPosition = {
3344
3444
  prefix: false,
3345
3445
  parse: function (_context, tokens) {
3346
3446
  return parseFunctionArgs(tokens)
3347
- .map(function (values) { return values.filter(isLengthPercentage); })
3447
+ .map(function (values) {
3448
+ // Convert calc() to length-percentage tokens, keep other length-percentage as is
3449
+ return values
3450
+ .map(function (value) {
3451
+ if (isCalcFunction(value)) {
3452
+ // For calc() at parse time, we can't know the container size
3453
+ // So we evaluate with 0 context which will work for px-only calc()
3454
+ // Percentage-based calc() will need special handling
3455
+ return evaluateCalcToLengthPercentage(value, 0);
3456
+ }
3457
+ return isLengthPercentage(value) ? value : null;
3458
+ })
3459
+ .filter(function (v) { return v !== null; });
3460
+ })
3348
3461
  .map(parseLengthPercentageTuple);
3349
3462
  }
3350
3463
  };
@@ -5700,7 +5813,19 @@ var createsRealStackingContext = function (node, container, root) {
5700
5813
  container.styles.isTransformed() ||
5701
5814
  (isBodyElement(node) && root.styles.isTransparent()));
5702
5815
  };
5703
- var createsStackingContext = function (styles) { return styles.isPositioned() || styles.isFloating(); };
5816
+ var createsStackingContext = function (styles) {
5817
+ // Positioned and floating elements create stacking contexts
5818
+ if (styles.isPositioned() || styles.isFloating()) {
5819
+ return true;
5820
+ }
5821
+ // Fix for Issue #137: Inline-level containers (inline-flex, inline-block, etc.)
5822
+ // should create stacking contexts to prevent their children from being added
5823
+ // to the parent's stacking context, which causes rendering order issues
5824
+ return (contains(styles.display, 268435456 /* DISPLAY.INLINE_FLEX */) ||
5825
+ contains(styles.display, 33554432 /* DISPLAY.INLINE_BLOCK */) ||
5826
+ contains(styles.display, 536870912 /* DISPLAY.INLINE_GRID */) ||
5827
+ contains(styles.display, 134217728 /* DISPLAY.INLINE_TABLE */));
5828
+ };
5704
5829
  var isTextNode = function (node) { return node.nodeType === Node.TEXT_NODE; };
5705
5830
  var isElementNode = function (node) { return node.nodeType === Node.ELEMENT_NODE; };
5706
5831
  var isHTMLElementNode = function (node) {
@@ -7063,8 +7188,9 @@ var ElementPaint = /** @class */ (function () {
7063
7188
  this.effects.push(new OpacityEffect(this.container.styles.opacity));
7064
7189
  }
7065
7190
  if (this.container.styles.rotate !== null) {
7066
- var offsetX = this.container.bounds.left + this.container.styles.transformOrigin[0].number;
7067
- var offsetY = this.container.bounds.top + this.container.styles.transformOrigin[1].number;
7191
+ var origin_1 = this.container.styles.transformOrigin;
7192
+ var offsetX = this.container.bounds.left + getAbsoluteValue(origin_1[0], this.container.bounds.width);
7193
+ var offsetY = this.container.bounds.top + getAbsoluteValue(origin_1[1], this.container.bounds.height);
7068
7194
  // Apply rotate property if present
7069
7195
  var angle = this.container.styles.rotate;
7070
7196
  var rad = (angle * Math.PI) / 180;
@@ -7074,8 +7200,9 @@ var ElementPaint = /** @class */ (function () {
7074
7200
  this.effects.push(new TransformEffect(offsetX, offsetY, rotateMatrix));
7075
7201
  }
7076
7202
  if (this.container.styles.transform !== null) {
7077
- var offsetX = this.container.bounds.left + this.container.styles.transformOrigin[0].number;
7078
- var offsetY = this.container.bounds.top + this.container.styles.transformOrigin[1].number;
7203
+ var origin_2 = this.container.styles.transformOrigin;
7204
+ var offsetX = this.container.bounds.left + getAbsoluteValue(origin_2[0], this.container.bounds.width);
7205
+ var offsetY = this.container.bounds.top + getAbsoluteValue(origin_2[1], this.container.bounds.height);
7079
7206
  var matrix = this.container.styles.transform;
7080
7207
  this.effects.push(new TransformEffect(offsetX, offsetY, matrix));
7081
7208
  }
@@ -7646,15 +7773,9 @@ var CanvasRenderer = /** @class */ (function (_super) {
7646
7773
  CanvasRenderer.prototype.renderTextWithLetterSpacing = function (text, letterSpacing, baseline) {
7647
7774
  var _this = this;
7648
7775
  if (letterSpacing === 0) {
7649
- // Fixed an issue with characters moving up in non-Firefox.
7650
- // https://github.com/niklasvh/html2canvas/issues/2107#issuecomment-692462900
7651
- if (navigator.userAgent.indexOf('Firefox') === -1) {
7652
- this.ctx.textBaseline = 'ideographic';
7653
- this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
7654
- }
7655
- else {
7656
- this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + baseline);
7657
- }
7776
+ // Use alphabetic baseline for consistent text positioning across browsers
7777
+ // Issue #129: text.bounds.top + text.bounds.height causes text to render too low
7778
+ this.ctx.fillText(text.text, text.bounds.left, text.bounds.top + baseline);
7658
7779
  }
7659
7780
  else {
7660
7781
  var letters = segmentGraphemes(text.text);
@@ -7737,7 +7858,19 @@ var CanvasRenderer = /** @class */ (function (_super) {
7737
7858
  _this.ctx.lineWidth = styles.webkitTextStrokeWidth;
7738
7859
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
7739
7860
  _this.ctx.lineJoin = !!window.chrome ? 'miter' : 'round';
7740
- _this.ctx.strokeText(text.text, text.bounds.left, text.bounds.top + text.bounds.height);
7861
+ // Issue #110: Use baseline (fontSize) for consistent positioning with fill
7862
+ // Previously used text.bounds.height which caused stroke to render too low
7863
+ var baseline_1 = styles.fontSize.number;
7864
+ if (styles.letterSpacing === 0) {
7865
+ _this.ctx.strokeText(text.text, text.bounds.left, text.bounds.top + baseline_1);
7866
+ }
7867
+ else {
7868
+ var letters = segmentGraphemes(text.text);
7869
+ letters.reduce(function (left, letter) {
7870
+ _this.ctx.strokeText(letter, left, text.bounds.top + baseline_1);
7871
+ return left + _this.ctx.measureText(letter).width;
7872
+ }, text.bounds.left);
7873
+ }
7741
7874
  }
7742
7875
  _this.ctx.strokeStyle = '';
7743
7876
  _this.ctx.lineWidth = 0;