html2canvas-pro
Version:
Screenshots with JavaScript. Next generation!
243 lines • 11.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.IMAGE_RENDERING = exports.PerformanceMonitor = exports.createDefaultValidator = exports.Validator = exports.Html2CanvasConfig = exports.setCspNonce = exports.html2canvas = void 0;
const bounds_1 = require("./css/layout/bounds");
const color_1 = require("./css/types/color");
const color_utilities_1 = require("./css/types/color-utilities");
const document_cloner_1 = require("./dom/document-cloner");
const node_parser_1 = require("./dom/node-parser");
const canvas_renderer_1 = require("./render/canvas/canvas-renderer");
const foreignobject_renderer_1 = require("./render/canvas/foreignobject-renderer");
const context_1 = require("./core/context");
const config_1 = require("./config");
Object.defineProperty(exports, "Html2CanvasConfig", { enumerable: true, get: function () { return config_1.Html2CanvasConfig; } });
const validator_1 = require("./core/validator");
Object.defineProperty(exports, "createDefaultValidator", { enumerable: true, get: function () { return validator_1.createDefaultValidator; } });
Object.defineProperty(exports, "Validator", { enumerable: true, get: function () { return validator_1.Validator; } });
const performance_monitor_1 = require("./core/performance-monitor");
Object.defineProperty(exports, "PerformanceMonitor", { enumerable: true, get: function () { return performance_monitor_1.PerformanceMonitor; } });
/**
* Main html2canvas function with improved configuration management
*
* @param element - Element to render
* @param options - Rendering options
* @param config - Optional configuration (for advanced use cases)
* @returns Promise resolving to rendered canvas
*/
const html2canvas = (element, options = {}, config) => {
// Create configuration from element if not provided
const finalConfig = config ||
config_1.Html2CanvasConfig.fromElement(element, {
cspNonce: options.cspNonce,
cache: options.cache
});
return renderElement(element, options, finalConfig);
};
exports.html2canvas = html2canvas;
/**
* Set CSP nonce for inline styles
* @deprecated Use options.cspNonce instead
*/
const setCspNonce = (nonce) => {
console.warn('[html2canvas-pro] setCspNonce is deprecated. ' +
'Pass cspNonce in options instead: html2canvas(element, { cspNonce: "..." })');
// For backward compatibility, set default config
if (typeof window !== 'undefined') {
(0, config_1.setDefaultConfig)(new config_1.Html2CanvasConfig({ window, cspNonce: nonce }));
}
};
exports.setCspNonce = setCspNonce;
html2canvas.setCspNonce = setCspNonce;
exports.default = html2canvas;
var image_rendering_1 = require("./css/property-descriptors/image-rendering");
Object.defineProperty(exports, "IMAGE_RENDERING", { enumerable: true, get: function () { return image_rendering_1.IMAGE_RENDERING; } });
/**
* Coerce number-like option values for backward compatibility (e.g. string "2" from form/query).
* Mutates opts in place; callers should avoid reusing the same options object if they rely on original types.
*/
const coerceNumberOptions = (opts) => {
const numKeys = [
'scale',
'width',
'height',
'imageTimeout',
'x',
'y',
'windowWidth',
'windowHeight',
'scrollX',
'scrollY'
];
numKeys.forEach((key) => {
const v = opts[key];
if (v !== undefined && v !== null && typeof v !== 'number') {
const n = Number(v);
if (!Number.isNaN(n)) {
opts[key] = n;
}
}
});
};
const renderElement = async (element, opts, config) => {
coerceNumberOptions(opts);
// Input validation (unless explicitly skipped)
if (!opts.skipValidation) {
const validator = opts.validator || (0, validator_1.createDefaultValidator)();
// Validate element
const elementValidation = validator.validateElement(element);
if (!elementValidation.valid) {
throw new Error(elementValidation.error);
}
// Validate options
const optionsValidation = validator.validateOptions(opts);
if (!optionsValidation.valid) {
throw new Error(`Invalid options: ${optionsValidation.error}`);
}
}
if (!element || typeof element !== 'object') {
throw new Error('Invalid element provided as first argument');
}
const ownerDocument = element.ownerDocument;
if (!ownerDocument) {
throw new Error(`Element is not attached to a Document`);
}
const defaultView = ownerDocument.defaultView;
if (!defaultView) {
throw new Error(`Document is not attached to a Window`);
}
const resourceOptions = {
allowTaint: opts.allowTaint ?? false,
imageTimeout: opts.imageTimeout ?? 15000,
proxy: opts.proxy,
useCORS: opts.useCORS ?? false,
customIsSameOrigin: opts.customIsSameOrigin
};
const contextOptions = {
logging: opts.logging ?? true,
cache: opts.cache ?? config.cache,
...resourceOptions
};
// Fallbacks for minimal window (e.g. element-like mocks) so we don't get NaN
const DEFAULT_WINDOW_WIDTH = 800;
const DEFAULT_WINDOW_HEIGHT = 600;
const DEFAULT_SCROLL = 0;
const win = defaultView;
const windowOptions = {
windowWidth: opts.windowWidth ?? win.innerWidth ?? DEFAULT_WINDOW_WIDTH,
windowHeight: opts.windowHeight ?? win.innerHeight ?? DEFAULT_WINDOW_HEIGHT,
scrollX: opts.scrollX ?? win.pageXOffset ?? DEFAULT_SCROLL,
scrollY: opts.scrollY ?? win.pageYOffset ?? DEFAULT_SCROLL
};
const windowBounds = new bounds_1.Bounds(windowOptions.scrollX, windowOptions.scrollY, windowOptions.windowWidth, windowOptions.windowHeight);
const context = new context_1.Context(contextOptions, windowBounds, config);
// Initialize performance monitoring if enabled
const performanceMonitoring = opts.enablePerformanceMonitoring ?? opts.logging ?? false;
const perfMonitor = new performance_monitor_1.PerformanceMonitor(context, performanceMonitoring);
perfMonitor.start('total', {
width: windowOptions.windowWidth,
height: windowOptions.windowHeight
});
const foreignObjectRendering = opts.foreignObjectRendering ?? false;
const cloneOptions = {
allowTaint: opts.allowTaint ?? false,
onclone: opts.onclone,
ignoreElements: opts.ignoreElements,
iframeContainer: opts.iframeContainer,
inlineImages: foreignObjectRendering,
copyStyles: foreignObjectRendering,
cspNonce: opts.cspNonce ?? config.cspNonce
};
context.logger.debug(`Starting document clone with size ${windowBounds.width}x${windowBounds.height} scrolled to ${-windowBounds.left},${-windowBounds.top}`);
perfMonitor.start('clone');
const documentCloner = new document_cloner_1.DocumentCloner(context, element, cloneOptions);
const clonedElement = documentCloner.clonedReferenceElement;
if (!clonedElement) {
throw new Error('Unable to find element in cloned iframe');
}
const container = await documentCloner.toIFrame(ownerDocument, windowBounds);
perfMonitor.end('clone');
const { width, height, left, top } = (0, node_parser_1.isBodyElement)(clonedElement) || (0, node_parser_1.isHTMLElement)(clonedElement)
? (0, bounds_1.parseDocumentSize)(clonedElement.ownerDocument)
: (0, bounds_1.parseBounds)(context, clonedElement);
const backgroundColor = parseBackgroundColor(context, clonedElement, opts.backgroundColor);
const renderOptions = {
canvas: opts.canvas,
backgroundColor,
scale: opts.scale ?? defaultView.devicePixelRatio ?? 1,
x: (opts.x ?? 0) + left,
y: (opts.y ?? 0) + top,
width: opts.width ?? Math.ceil(width),
height: opts.height ?? Math.ceil(height),
imageSmoothing: opts.imageSmoothing,
imageSmoothingQuality: opts.imageSmoothingQuality
};
let canvas;
let root;
try {
if (foreignObjectRendering) {
context.logger.debug(`Document cloned, using foreign object rendering`);
perfMonitor.start('render-foreignobject');
const renderer = new foreignobject_renderer_1.ForeignObjectRenderer(context, renderOptions);
canvas = await renderer.render(clonedElement);
perfMonitor.end('render-foreignobject');
}
else {
context.logger.debug(`Document cloned, element located at ${left},${top} with size ${width}x${height} using computed rendering`);
context.logger.debug(`Starting DOM parsing`);
perfMonitor.start('parse');
root = (0, node_parser_1.parseTree)(context, clonedElement);
perfMonitor.end('parse');
if (backgroundColor === root.styles.backgroundColor) {
root.styles.backgroundColor = color_1.COLORS.TRANSPARENT;
}
context.logger.debug(`Starting renderer for element at ${renderOptions.x},${renderOptions.y} with size ${renderOptions.width}x${renderOptions.height}`);
perfMonitor.start('render');
const renderer = new canvas_renderer_1.CanvasRenderer(context, renderOptions);
canvas = await renderer.render(root);
perfMonitor.end('render');
}
perfMonitor.start('cleanup');
if (opts.removeContainer ?? true) {
if (!document_cloner_1.DocumentCloner.destroy(container)) {
context.logger.error(`Cannot detach cloned iframe as it is not in the DOM anymore`);
}
}
perfMonitor.end('cleanup');
perfMonitor.end('total');
context.logger.debug(`Finished rendering`);
// Log performance summary if monitoring is enabled
if (performanceMonitoring) {
perfMonitor.logSummary();
}
return canvas;
}
finally {
// Restore DOM modifications (animations, transforms) in cloned document
if (root) {
root.restoreTree();
}
}
};
const parseBackgroundColor = (context, element, backgroundColorOverride) => {
const ownerDocument = element.ownerDocument;
// http://www.w3.org/TR/css3-background/#special-backgrounds
const documentBackgroundColor = ownerDocument.documentElement
? (0, color_1.parseColor)(context, getComputedStyle(ownerDocument.documentElement).backgroundColor)
: color_1.COLORS.TRANSPARENT;
const bodyBackgroundColor = ownerDocument.body
? (0, color_1.parseColor)(context, getComputedStyle(ownerDocument.body).backgroundColor)
: color_1.COLORS.TRANSPARENT;
const defaultBackgroundColor = typeof backgroundColorOverride === 'string'
? (0, color_1.parseColor)(context, backgroundColorOverride)
: backgroundColorOverride === null
? color_1.COLORS.TRANSPARENT
: 0xffffffff;
return element === ownerDocument.documentElement
? (0, color_utilities_1.isTransparent)(documentBackgroundColor)
? (0, color_utilities_1.isTransparent)(bodyBackgroundColor)
? defaultBackgroundColor
: bodyBackgroundColor
: documentBackgroundColor
: defaultBackgroundColor;
};
//# sourceMappingURL=index.js.map