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