html2canvas-pro 2.3.8 → 2.4.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.
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * html2canvas-pro 2.3.8 <https://yorickshan.github.io/html2canvas-pro/>
2
+ * html2canvas-pro 2.4.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
  */
@@ -6983,19 +6983,18 @@ var DocumentCloner = class {
6983
6983
  const documentClone = cloneWindow.document;
6984
6984
  const iframeLoad = iframeLoader(iframe).then(async () => {
6985
6985
  this.scrolledElements.forEach(restoreNodeScroll);
6986
- if (cloneWindow) {
6987
- cloneWindow.scrollTo(windowSize.left, windowSize.top);
6988
- if (/AppleWebKit/g.test(navigator.userAgent) && (cloneWindow.scrollY !== windowSize.top || cloneWindow.scrollX !== windowSize.left)) {
6989
- this.context.logger.warn("Unable to restore scroll position for cloned document");
6990
- this.context.windowBounds = this.context.windowBounds.add(cloneWindow.scrollX - windowSize.left, cloneWindow.scrollY - windowSize.top, 0, 0);
6991
- }
6992
- }
6986
+ if (cloneWindow) cloneWindow.scrollTo(windowSize.left, windowSize.top);
6993
6987
  const onclone = this.options.onclone;
6994
6988
  const referenceElement = this.clonedReferenceElement;
6995
6989
  if (typeof referenceElement === "undefined") throw new Error(`Error finding the ${this.referenceElement.nodeName} in the cloned document`);
6996
6990
  if (documentClone.fonts && documentClone.fonts.ready) await documentClone.fonts.ready;
6997
6991
  if (/(AppleWebKit)/g.test(navigator.userAgent)) await imagesReady(documentClone);
6998
- if (typeof onclone === "function") return Promise.resolve().then(() => onclone(documentClone, referenceElement)).then(() => iframe);
6992
+ if (typeof onclone === "function") await Promise.resolve().then(() => onclone(documentClone, referenceElement));
6993
+ cloneWindow.scrollTo(windowSize.left, windowSize.top);
6994
+ if (/AppleWebKit/g.test(navigator.userAgent) && (cloneWindow.scrollY !== windowSize.top || cloneWindow.scrollX !== windowSize.left)) {
6995
+ this.context.logger.warn("Unable to restore scroll position for cloned document");
6996
+ this.context.windowBounds = this.context.windowBounds.add(cloneWindow.scrollX - windowSize.left, cloneWindow.scrollY - windowSize.top, 0, 0);
6997
+ }
6999
6998
  return iframe;
7000
6999
  });
7001
7000
  /**
@@ -9085,8 +9084,8 @@ var TextDecorationRenderer = class {
9085
9084
  /**
9086
9085
  * Font measurement utility
9087
9086
  *
9088
- * Provides Canvas API-based baseline measurement that works correctly with
9089
- * webfonts (which may not be loaded in the original document) and system fonts.
9087
+ * Provides Canvas API-based font measurement that works correctly with webfonts
9088
+ * (which may not be loaded in the original document) and system fonts.
9090
9089
  *
9091
9090
  * Fallback chain:
9092
9091
  * 1. fontBoundingBoxAscent — font-level metric (Chrome 99+, FF 116+, Safari 17.4+)
@@ -9094,6 +9093,18 @@ var TextDecorationRenderer = class {
9094
9093
  * 3. fallback value — coarse CSS fallback
9095
9094
  */
9096
9095
  const SAMPLE_TEXT = "Mg";
9096
+ const isValidMetric = (value) => value !== void 0 && !Number.isNaN(value);
9097
+ /**
9098
+ * Measure the baseline ascent and full font box for the currently set font.
9099
+ */
9100
+ const measureFontMetrics = (ctx, fallback) => {
9101
+ const tm = ctx.measureText(SAMPLE_TEXT);
9102
+ const fontMetrics = tm;
9103
+ return {
9104
+ baseline: isValidMetric(fontMetrics.fontBoundingBoxAscent) ? fontMetrics.fontBoundingBoxAscent : isValidMetric(tm.actualBoundingBoxAscent) ? tm.actualBoundingBoxAscent : fallback,
9105
+ height: isValidMetric(fontMetrics.fontBoundingBoxAscent) && isValidMetric(fontMetrics.fontBoundingBoxDescent) ? fontMetrics.fontBoundingBoxAscent + fontMetrics.fontBoundingBoxDescent : isValidMetric(tm.actualBoundingBoxAscent) && isValidMetric(tm.actualBoundingBoxDescent) ? tm.actualBoundingBoxAscent + tm.actualBoundingBoxDescent : fallback
9106
+ };
9107
+ };
9097
9108
  /**
9098
9109
  * Measure the baseline ascent for the currently set font.
9099
9110
  *
@@ -9102,10 +9113,7 @@ const SAMPLE_TEXT = "Mg";
9102
9113
  * @returns The distance from the text baseline to the top of the bounding box
9103
9114
  */
9104
9115
  const measureBaseline = (ctx, fallback) => {
9105
- const tm = ctx.measureText(SAMPLE_TEXT);
9106
- const fmAscent = tm.fontBoundingBoxAscent;
9107
- const ascent = fmAscent !== void 0 && !Number.isNaN(fmAscent) ? fmAscent : tm.actualBoundingBoxAscent;
9108
- return ascent !== void 0 && !Number.isNaN(ascent) ? ascent : fallback;
9116
+ return measureFontMetrics(ctx, fallback).baseline;
9109
9117
  };
9110
9118
  //#endregion
9111
9119
  //#region src/render/canvas/text-renderer.ts
@@ -10352,11 +10360,13 @@ function renderFormElements(ctx, textRenderer, pathFn, container, styles) {
10352
10360
  if (isTextInputElement(container) && container.value.length) {
10353
10361
  const [font] = textRenderer.createFontStyle(styles);
10354
10362
  ctx.font = font;
10355
- const baseline = measureBaseline(ctx, getAbsoluteValue(styles.fontSize, 0));
10363
+ const fontSizeValue = getAbsoluteValue(styles.fontSize, 0);
10364
+ const { baseline, height: fontHeight } = measureFontMetrics(ctx, fontSizeValue);
10356
10365
  ctx.fillStyle = container instanceof InputElementContainer && container.isPlaceholder ? asString(PLACEHOLDER_COLOR) : asString(styles.color);
10357
10366
  ctx.textBaseline = "alphabetic";
10358
10367
  ctx.textAlign = canvasTextAlign(container.styles.textAlign);
10359
10368
  const bounds = contentBox(container);
10369
+ const clipBounds = container instanceof InputElementContainer ? inputTextClipBounds(bounds, paddingBox(container), fontHeight) : bounds;
10360
10370
  let x = 0;
10361
10371
  switch (container.styles.textAlign) {
10362
10372
  case 1:
@@ -10367,21 +10377,17 @@ function renderFormElements(ctx, textRenderer, pathFn, container, styles) {
10367
10377
  break;
10368
10378
  }
10369
10379
  let verticalOffset = 0;
10370
- if (container instanceof InputElementContainer) {
10371
- const fontSizeValue = getAbsoluteValue(styles.fontSize, 0);
10372
- verticalOffset = (bounds.height - fontSizeValue) / 2;
10373
- }
10380
+ if (container instanceof InputElementContainer) verticalOffset = (bounds.height - fontHeight) / 2;
10374
10381
  const textBounds = bounds.add(x, verticalOffset, 0, 0);
10375
10382
  ctx.save();
10376
10383
  pathFn([
10377
- new Vector(bounds.left, bounds.top),
10378
- new Vector(bounds.left + bounds.width, bounds.top),
10379
- new Vector(bounds.left + bounds.width, bounds.top + bounds.height),
10380
- new Vector(bounds.left, bounds.top + bounds.height)
10384
+ new Vector(clipBounds.left, clipBounds.top),
10385
+ new Vector(clipBounds.left + clipBounds.width, clipBounds.top),
10386
+ new Vector(clipBounds.left + clipBounds.width, clipBounds.top + clipBounds.height),
10387
+ new Vector(clipBounds.left, clipBounds.top + clipBounds.height)
10381
10388
  ]);
10382
10389
  ctx.clip();
10383
10390
  const lines = wrapTextToWidth(ctx, container.value, bounds.width, styles.whiteSpace === "pre" || styles.whiteSpace === "nowrap");
10384
- const fontSizeValue = getAbsoluteValue(styles.fontSize, 0);
10385
10391
  const lineHeight = computeLineHeight(styles.lineHeight, fontSizeValue);
10386
10392
  lines.forEach((line, index) => {
10387
10393
  let lineX = textBounds.left;
@@ -10403,6 +10409,14 @@ function renderFormElements(ctx, textRenderer, pathFn, container, styles) {
10403
10409
  }
10404
10410
  }
10405
10411
  /**
10412
+ * Native single-line inputs may paint value text into their padding area when
10413
+ * fixed height and vertical padding leave a content box shorter than the font.
10414
+ * Preserve the content box horizontally, but avoid clipping those glyphs to the
10415
+ * collapsed vertical content box.
10416
+ * @internal exported for testing
10417
+ */
10418
+ const inputTextClipBounds = (contentBounds, inputPaddingBox, fontHeight) => contentBounds.height < fontHeight ? new Bounds(contentBounds.left, inputPaddingBox.top, contentBounds.width, inputPaddingBox.height) : contentBounds;
10419
+ /**
10406
10420
  * Split text into lines that fit the given pixel width, preserving hard line
10407
10421
  * breaks and (when wrapping is enabled) breaking at word boundaries — mirroring
10408
10422
  * how a <textarea> lays out its value.
@@ -10598,11 +10612,13 @@ var CanvasRenderer = class CanvasRenderer {
10598
10612
  }
10599
10613
  mask(paths) {
10600
10614
  this.ctx.beginPath();
10601
- this.ctx.moveTo(0, 0);
10602
- this.ctx.lineTo(this.options.width, 0);
10603
- this.ctx.lineTo(this.options.width, this.options.height);
10604
- this.ctx.lineTo(0, this.options.height);
10605
- this.ctx.lineTo(0, 0);
10615
+ const x = this.options.x;
10616
+ const y = this.options.y;
10617
+ this.ctx.moveTo(x, y);
10618
+ this.ctx.lineTo(x + this.options.width, y);
10619
+ this.ctx.lineTo(x + this.options.width, y + this.options.height);
10620
+ this.ctx.lineTo(x, y + this.options.height);
10621
+ this.ctx.lineTo(x, y);
10606
10622
  this.formatPath(paths.slice(0).reverse());
10607
10623
  this.ctx.closePath();
10608
10624
  }