UNPKG

html2canvas-pro

Version:

Screenshots with JavaScript. Next generation!

243 lines 11.4 kB
"use strict"; 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