diff --git a/modules/modern-screenshot/4.6.7/dist/index.d.ts b/modules/modern-screenshot/4.6.7/dist/index.d.ts deleted file mode 100644 index b50a7fd8e..000000000 --- a/modules/modern-screenshot/4.6.7/dist/index.d.ts +++ /dev/null @@ -1,353 +0,0 @@ -interface Options { - /** - * Width in pixels to be applied to node before rendering. - */ - width?: number; - /** - * Height in pixels to be applied to node before rendering. - */ - height?: number; - /** - * A number between `0` and `1` indicating image quality (e.g. 0.92 => 92%) of the JPEG image. - */ - quality?: number; - /** - * A string indicating the image format. The default type is image/png; that type is also used if the given type isn't supported. - */ - type?: string; - /** - * The pixel ratio of captured image. - * - * DPI = 96 * scale - * - * default: 1 - */ - scale?: number; - /** - * A string value for the background color, any valid CSS color value. - */ - backgroundColor?: string | null; - /** - * An object whose properties to be copied to node's style before rendering. - */ - style?: Partial | null; - /** - * A function taking DOM node as argument. Should return `true` if passed - * node should be included in the output. Excluding node means excluding - * it's children as well. - */ - filter?: ((el: Node) => boolean) | null; - /** - * Maximum canvas size (pixels). - * - * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas#maximum_canvas_size - */ - maximumCanvasSize?: number; - /** - * Load media timeout and fetch remote asset timeout (millisecond). - * - * default: 30000 - */ - timeout?: number; - /** - * Embed assets progress. - */ - progress?: ((current: number, total: number) => void) | null; - /** - * Enable debug mode to view the execution time log. - */ - debug?: boolean; - /** - * Custom implementation to get image data for a custom URL. - * This can be helpful for Capacitor or Cordova when using - * native fetch to bypass CORS issues. - * - * If returns a string, will completely bypass any `Options.fetch` - * settings with your custom implementation. - * - * If returns false, will fall back to normal fetch implementation - * - * @param url - * @returns A data URL for the image - */ - fetchFn?: ((url: string) => Promise) | null; - /** - * The options of fetch resources. - */ - fetch?: { - /** - * The second parameter of `window.fetch` RequestInit - * - * default: { - * cache: 'force-cache', - * } - */ - requestInit?: RequestInit; - /** - * Set to `true` to append the current time as a query string to URL - * requests to enable cache busting. - * - * default: false - */ - bypassingCache?: boolean | RegExp; - /** - * A data URL for a placeholder image that will be used when fetching - * an image fails. Defaults to an empty string and will render empty - * areas for failed images. - * - * default: data:image/png;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7 - */ - placeholderImage?: string | ((cloned: HTMLImageElement | SVGImageElement) => string | Promise); - }; - /** - * The options of fonts download and embed. - */ - font?: false | { - /** - * Font minify - */ - minify?: (font: ArrayBuffer, subset: string) => ArrayBuffer; - /** - * The preferred font format. If specified all other font formats are ignored. - */ - preferredFormat?: 'woff' | 'woff2' | 'truetype' | 'opentype' | 'embedded-opentype' | 'svg' | string; - /** - * A CSS string to specify for font embeds. If specified only this CSS will - * be present in the resulting image. - */ - cssText?: string; - }; - /** - * All enabled features - * - * default: true - */ - features?: boolean | { - /** - * Copy scrollbar css styles - * - * default: true - */ - copyScrollbar?: boolean; - /** - * Remove abnormal attributes to cloned node (for normalize XML) - * - * default: true - */ - removeAbnormalAttributes?: boolean; - /** - * Remove control characters (for normalize XML) - * - * default: true - */ - removeControlCharacter?: boolean; - /** - * Fix svg+xml image decode (for Safari、Firefox) - * - * default: true - */ - fixSvgXmlDecode?: boolean; - /** - * Render scrolled children with scrolled content - * - * default: false - */ - restoreScrollPosition?: boolean; - }; - /** - * Canvas `drawImage` interval - * is used to fix errors in decoding images in Safari、Firefox - * - * default: 100 - */ - drawImageInterval?: number; - /** - * Web Worker script url - */ - workerUrl?: string | null; - /** - * Web Worker number - */ - workerNumber?: number; - /** - * Triggered after each node is cloned - */ - onCloneEachNode?: ((cloned: Node) => void | Promise) | null; - /** - * Triggered after a node is cloned - */ - onCloneNode?: ((cloned: Node) => void | Promise) | null; - /** - * Triggered after a node is embed - */ - onEmbedNode?: ((cloned: Node) => void | Promise) | null; - /** - * Triggered after a ForeignObjectSvg is created - */ - onCreateForeignObjectSvg?: ((svg: SVGSVGElement) => void | Promise) | null; - /** - * An array of style property names. - * Can be used to manually specify which style properties are - * included when cloning nodes. - * This can be useful for performance-critical scenarios. - */ - includeStyleProperties?: string[] | null; -} - -interface Request { - type: 'image' | 'text'; - resolve?: (response: string) => void; - reject?: (error: Error) => void; - response: Promise; -} -interface InternalContext { - /** - * FLAG - */ - __CONTEXT__: true; - /** - * Logger - */ - log: { - time: (label: string) => void; - timeEnd: (label: string) => void; - warn: (...args: any[]) => void; - }; - /** - * Node - */ - node: T; - /** - * Owner document - */ - ownerDocument?: Document; - /** - * Owner window - */ - ownerWindow?: Window; - /** - * DPI - * - * scale === 1 ? null : 96 * scale - */ - dpi: number | null; - /** - * The `style` element under the root `svg` element - */ - svgStyleElement?: HTMLStyleElement; - /** - * The `defs` element under the root `svg` element - */ - svgDefsElement?: SVGDefsElement; - /** - * The `svgStyleElement` class styles - * - * Map - */ - svgStyles: Map; - /** - * The map of default `getComputedStyle` for all tagnames - */ - defaultComputedStyles: Map>; - /** - * The IFrame sandbox used to get the `defaultComputedStyles` - */ - sandbox?: HTMLIFrameElement; - /** - * Web Workers - */ - workers: Worker[]; - /** - * The map of `font-family` values for all cloend elements - */ - fontFamilies: Map>; - /** - * Map - */ - fontCssTexts: Map; - /** - * `headers.accept` to use when `window.fetch` fetches images - */ - acceptOfImage: string; - /** - * All requests for `fetch` - */ - requests: Map; - /** - * Canvas multiple draw image fix svg+xml image decoding in Safari and Firefox - */ - drawImageCount: number; - /** - * Wait for all tasks embedded in - */ - tasks: Promise[]; - /** - * Automatically destroy context - */ - autoDestruct: boolean; - /** - * Is enable - * - * @param key - */ - isEnable: (key: string) => boolean; - /** - * [cloning phase] To get the node style set by the user - */ - currentNodeStyle?: Map; - currentParentNodeStyle?: Map; - /** - * [cloning phase] shadowDOM root list - */ - shadowRoots: ShadowRoot[]; -} -type Context = InternalContext & Required; - -declare function domToBlob(node: T, options?: Options): Promise; -declare function domToBlob(context: Context): Promise; - -declare function domToCanvas(node: T, options?: Options): Promise; -declare function domToCanvas(context: Context): Promise; - -declare function domToDataUrl(node: T, options?: Options): Promise; -declare function domToDataUrl(context: Context): Promise; - -declare function domToForeignObjectSvg(node: T, options?: Options): Promise; -declare function domToForeignObjectSvg(context: Context): Promise; - -declare function domToImage(node: T, options?: Options): Promise; -declare function domToImage(context: Context): Promise; - -declare function domToJpeg(node: T, options?: Options): Promise; -declare function domToJpeg(context: Context): Promise; - -declare function domToPixel(node: T, options?: Options): Promise; -declare function domToPixel(context: Context): Promise; - -declare function domToPng(node: T, options?: Options): Promise; -declare function domToPng(context: Context): Promise; - -declare function domToSvg(node: T, options?: Options): Promise; -declare function domToSvg(context: Context): Promise; - -declare function domToWebp(node: T, options?: Options): Promise; -declare function domToWebp(context: Context): Promise; - -declare function createContext(node: T, options?: Options & { - autoDestruct?: boolean; -}): Promise>; - -declare function destroyContext(context: Context): void; - -type Media = HTMLVideoElement | HTMLImageElement | SVGImageElement; -interface LoadMediaOptions { - ownerDocument?: Document; - timeout?: number; - onError?: (error: Error) => void; - onWarn?: (...args: any[]) => void; -} -declare function loadMedia(media: T, options?: LoadMediaOptions): Promise; -declare function loadMedia(media: string, options?: LoadMediaOptions): Promise; -declare function waitUntilLoad(node: Node, options?: LoadMediaOptions): Promise; - -export { type Context, type Options, createContext, destroyContext, domToBlob, domToCanvas, domToDataUrl, domToForeignObjectSvg, domToImage, domToJpeg, domToPixel, domToPng, domToSvg, domToWebp, loadMedia, waitUntilLoad }; diff --git a/modules/modern-screenshot/4.6.7/dist/index.mjs b/modules/modern-screenshot/4.6.7/dist/index.mjs deleted file mode 100644 index 012e83d05..000000000 --- a/modules/modern-screenshot/4.6.7/dist/index.mjs +++ /dev/null @@ -1,1653 +0,0 @@ -// @ts-nocheck -function changeJpegDpi(uint8Array, dpi) { - uint8Array[13] = 1; - uint8Array[14] = dpi >> 8; - uint8Array[15] = dpi & 255; - uint8Array[16] = dpi >> 8; - uint8Array[17] = dpi & 255; - return uint8Array; -} - -const _P = "p".charCodeAt(0); -const _H = "H".charCodeAt(0); -const _Y = "Y".charCodeAt(0); -const _S = "s".charCodeAt(0); -let pngDataTable; -function createPngDataTable() { - const crcTable = new Int32Array(256); - for (let n = 0; n < 256; n++) { - let c = n; - for (let k = 0; k < 8; k++) { - c = c & 1 ? 3988292384 ^ c >>> 1 : c >>> 1; - } - crcTable[n] = c; - } - return crcTable; -} -function calcCrc(uint8Array) { - let c = -1; - if (!pngDataTable) - pngDataTable = createPngDataTable(); - for (let n = 0; n < uint8Array.length; n++) { - c = pngDataTable[(c ^ uint8Array[n]) & 255] ^ c >>> 8; - } - return c ^ -1; -} -function searchStartOfPhys(uint8Array) { - const length = uint8Array.length - 1; - for (let i = length; i >= 4; i--) { - if (uint8Array[i - 4] === 9 && uint8Array[i - 3] === _P && uint8Array[i - 2] === _H && uint8Array[i - 1] === _Y && uint8Array[i] === _S) { - return i - 3; - } - } - return 0; -} -function changePngDpi(uint8Array, dpi, overwritepHYs = false) { - const physChunk = new Uint8Array(13); - dpi *= 39.3701; - physChunk[0] = _P; - physChunk[1] = _H; - physChunk[2] = _Y; - physChunk[3] = _S; - physChunk[4] = dpi >>> 24; - physChunk[5] = dpi >>> 16; - physChunk[6] = dpi >>> 8; - physChunk[7] = dpi & 255; - physChunk[8] = physChunk[4]; - physChunk[9] = physChunk[5]; - physChunk[10] = physChunk[6]; - physChunk[11] = physChunk[7]; - physChunk[12] = 1; - const crc = calcCrc(physChunk); - const crcChunk = new Uint8Array(4); - crcChunk[0] = crc >>> 24; - crcChunk[1] = crc >>> 16; - crcChunk[2] = crc >>> 8; - crcChunk[3] = crc & 255; - if (overwritepHYs) { - const startingIndex = searchStartOfPhys(uint8Array); - uint8Array.set(physChunk, startingIndex); - uint8Array.set(crcChunk, startingIndex + 13); - return uint8Array; - } else { - const chunkLength = new Uint8Array(4); - chunkLength[0] = 0; - chunkLength[1] = 0; - chunkLength[2] = 0; - chunkLength[3] = 9; - const finalHeader = new Uint8Array(54); - finalHeader.set(uint8Array, 0); - finalHeader.set(chunkLength, 33); - finalHeader.set(physChunk, 37); - finalHeader.set(crcChunk, 50); - return finalHeader; - } -} -const b64PhysSignature1 = "AAlwSFlz"; -const b64PhysSignature2 = "AAAJcEhZ"; -const b64PhysSignature3 = "AAAACXBI"; -function detectPhysChunkFromDataUrl(dataUrl) { - let b64index = dataUrl.indexOf(b64PhysSignature1); - if (b64index === -1) { - b64index = dataUrl.indexOf(b64PhysSignature2); - } - if (b64index === -1) { - b64index = dataUrl.indexOf(b64PhysSignature3); - } - return b64index; -} - -const PREFIX = "[modern-screenshot]"; -const IN_BROWSER = typeof window !== "undefined"; -const SUPPORT_WEB_WORKER = IN_BROWSER && "Worker" in window; -const SUPPORT_ATOB = IN_BROWSER && "atob" in window; -const SUPPORT_BTOA = IN_BROWSER && "btoa" in window; -const USER_AGENT = IN_BROWSER ? window.navigator?.userAgent : ""; -const IN_CHROME = USER_AGENT.includes("Chrome"); -const IN_SAFARI = USER_AGENT.includes("AppleWebKit") && !IN_CHROME; -const IN_FIREFOX = USER_AGENT.includes("Firefox"); -const isContext = (value) => value && "__CONTEXT__" in value; -const isCssFontFaceRule = (rule) => rule.constructor.name === "CSSFontFaceRule"; -const isCSSImportRule = (rule) => rule.constructor.name === "CSSImportRule"; -const isElementNode = (node) => node.nodeType === 1; -const isSVGElementNode = (node) => typeof node.className === "object"; -const isSVGImageElementNode = (node) => node.tagName === "image"; -const isSVGUseElementNode = (node) => node.tagName === "use"; -const isHTMLElementNode = (node) => isElementNode(node) && typeof node.style !== "undefined" && !isSVGElementNode(node); -const isCommentNode = (node) => node.nodeType === 8; -const isTextNode = (node) => node.nodeType === 3; -const isImageElement = (node) => node.tagName === "IMG"; -const isVideoElement = (node) => node.tagName === "VIDEO"; -const isCanvasElement = (node) => node.tagName === "CANVAS"; -const isTextareaElement = (node) => node.tagName === "TEXTAREA"; -const isInputElement = (node) => node.tagName === "INPUT"; -const isStyleElement = (node) => node.tagName === "STYLE"; -const isScriptElement = (node) => node.tagName === "SCRIPT"; -const isSelectElement = (node) => node.tagName === "SELECT"; -const isSlotElement = (node) => node.tagName === "SLOT"; -const isIFrameElement = (node) => node.tagName === "IFRAME"; -const consoleWarn = (...args) => console.warn(PREFIX, ...args); -function supportWebp(ownerDocument) { - const canvas = ownerDocument?.createElement?.("canvas"); - if (canvas) { - canvas.height = canvas.width = 1; - } - return Boolean(canvas) && "toDataURL" in canvas && Boolean(canvas.toDataURL("image/webp").includes("image/webp")); -} -const isDataUrl = (url) => url.startsWith("data:"); -function resolveUrl(url, baseUrl) { - if (url.match(/^[a-z]+:\/\//i)) - return url; - if (IN_BROWSER && url.match(/^\/\//)) - return window.location.protocol + url; - if (url.match(/^[a-z]+:/i)) - return url; - if (!IN_BROWSER) - return url; - const doc = getDocument().implementation.createHTMLDocument(); - const base = doc.createElement("base"); - const a = doc.createElement("a"); - doc.head.appendChild(base); - doc.body.appendChild(a); - if (baseUrl) - base.href = baseUrl; - a.href = url; - return a.href; -} -function getDocument(target) { - return (target && isElementNode(target) ? target?.ownerDocument : target) ?? window.document; -} -const XMLNS = "http://www.w3.org/2000/svg"; -function createSvg(width, height, ownerDocument) { - const svg = getDocument(ownerDocument).createElementNS(XMLNS, "svg"); - svg.setAttributeNS(null, "width", width.toString()); - svg.setAttributeNS(null, "height", height.toString()); - svg.setAttributeNS(null, "viewBox", `0 0 ${width} ${height}`); - return svg; -} -function svgToDataUrl(svg, removeControlCharacter) { - let xhtml = new XMLSerializer().serializeToString(svg); - if (removeControlCharacter) { - xhtml = xhtml.replace(/[\u0000-\u0008\v\f\u000E-\u001F\uD800-\uDFFF\uFFFE\uFFFF]/gu, ""); - } - return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(xhtml)}`; -} -async function canvasToBlob(canvas, type = "image/png", quality = 1) { - try { - return await new Promise((resolve, reject) => { - canvas.toBlob((blob) => { - if (blob) { - resolve(blob); - } else { - reject(new Error("Blob is null")); - } - }, type, quality); - }); - } catch (error) { - if (SUPPORT_ATOB) { - return dataUrlToBlob(canvas.toDataURL(type, quality)); - } - throw error; - } -} -function dataUrlToBlob(dataUrl) { - const [header, base64] = dataUrl.split(","); - const type = header.match(/data:(.+);/)?.[1] ?? void 0; - const decoded = window.atob(base64); - const length = decoded.length; - const buffer = new Uint8Array(length); - for (let i = 0; i < length; i += 1) { - buffer[i] = decoded.charCodeAt(i); - } - return new Blob([buffer], { type }); -} -function readBlob(blob, type) { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.onload = () => resolve(reader.result); - reader.onerror = () => reject(reader.error); - reader.onabort = () => reject(new Error(`Failed read blob to ${type}`)); - if (type === "dataUrl") { - reader.readAsDataURL(blob); - } else if (type === "arrayBuffer") { - reader.readAsArrayBuffer(blob); - } - }); -} -const blobToDataUrl = (blob) => readBlob(blob, "dataUrl"); -const blobToArrayBuffer = (blob) => readBlob(blob, "arrayBuffer"); -function createImage(url, ownerDocument) { - const img = getDocument(ownerDocument).createElement("img"); - img.decoding = "sync"; - img.loading = "eager"; - img.src = url; - return img; -} -function loadMedia(media, options) { - return new Promise((resolve) => { - const { timeout, ownerDocument, onError: userOnError, onWarn } = options ?? {}; - const node = typeof media === "string" ? createImage(media, getDocument(ownerDocument)) : media; - let timer = null; - let removeEventListeners = null; - function onResolve() { - resolve(node); - timer && clearTimeout(timer); - removeEventListeners?.(); - } - if (timeout) { - timer = setTimeout(onResolve, timeout); - } - if (isVideoElement(node)) { - const currentSrc = node.currentSrc || node.src; - if (!currentSrc) { - if (node.poster) { - return loadMedia(node.poster, options).then(resolve); - } - return onResolve(); - } - if (node.readyState >= 2) { - return onResolve(); - } - const onLoadeddata = onResolve; - const onError = (error) => { - onWarn?.( - "Failed video load", - currentSrc, - error - ); - userOnError?.(error); - onResolve(); - }; - removeEventListeners = () => { - node.removeEventListener("loadeddata", onLoadeddata); - node.removeEventListener("error", onError); - }; - node.addEventListener("loadeddata", onLoadeddata, { once: true }); - node.addEventListener("error", onError, { once: true }); - } else { - const currentSrc = isSVGImageElementNode(node) ? node.href.baseVal : node.currentSrc || node.src; - if (!currentSrc) { - return onResolve(); - } - const onLoad = async () => { - if (isImageElement(node) && "decode" in node) { - try { - await node.decode(); - } catch (error) { - onWarn?.( - "Failed to decode image, trying to render anyway", - node.dataset.originalSrc || currentSrc, - error - ); - } - } - onResolve(); - }; - const onError = (error) => { - onWarn?.( - "Failed image load", - node.dataset.originalSrc || currentSrc, - error - ); - onResolve(); - }; - if (isImageElement(node) && node.complete) { - return onLoad(); - } - removeEventListeners = () => { - node.removeEventListener("load", onLoad); - node.removeEventListener("error", onError); - }; - node.addEventListener("load", onLoad, { once: true }); - node.addEventListener("error", onError, { once: true }); - } - }); -} -async function waitUntilLoad(node, options) { - if (isHTMLElementNode(node)) { - if (isImageElement(node) || isVideoElement(node)) { - await loadMedia(node, options); - } else { - await Promise.all( - ["img", "video"].flatMap((selectors) => { - return Array.from(node.querySelectorAll(selectors)).map((el) => loadMedia(el, options)); - }) - ); - } - } -} -const uuid = /* @__PURE__ */ function uuid2() { - let counter = 0; - const random = () => `0000${(Math.random() * 36 ** 4 << 0).toString(36)}`.slice(-4); - return () => { - counter += 1; - return `u${random()}${counter}`; - }; -}(); -function splitFontFamily(fontFamily) { - return fontFamily?.split(",").map((val) => val.trim().replace(/"|'/g, "").toLowerCase()).filter(Boolean); -} - -let uid = 0; -function createLogger(debug) { - const prefix = `${PREFIX}[#${uid}]`; - uid++; - return { - // eslint-disable-next-line no-console - time: (label) => debug && console.time(`${prefix} ${label}`), - // eslint-disable-next-line no-console - timeEnd: (label) => debug && console.timeEnd(`${prefix} ${label}`), - warn: (...args) => debug && consoleWarn(...args) - }; -} - -function getDefaultRequestInit(bypassingCache) { - return { - cache: bypassingCache ? "no-cache" : "force-cache" - }; -} - -async function orCreateContext(node, options) { - return isContext(node) ? node : createContext(node, { ...options, autoDestruct: true }); -} -async function createContext(node, options) { - const { scale = 1, workerUrl, workerNumber = 1 } = options || {}; - const debug = Boolean(options?.debug); - const features = options?.features ?? true; - const ownerDocument = node.ownerDocument ?? (IN_BROWSER ? window.document : void 0); - const ownerWindow = node.ownerDocument?.defaultView ?? (IN_BROWSER ? window : void 0); - const requests = /* @__PURE__ */ new Map(); - const context = { - // Options - width: 0, - height: 0, - quality: 1, - type: "image/png", - scale, - backgroundColor: null, - style: null, - filter: null, - maximumCanvasSize: 0, - timeout: 3e4, - progress: null, - debug, - fetch: { - requestInit: getDefaultRequestInit(options?.fetch?.bypassingCache), - placeholderImage: "data:image/png;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7", - bypassingCache: false, - ...options?.fetch - }, - fetchFn: null, - font: {}, - drawImageInterval: 100, - workerUrl: null, - workerNumber, - onCloneEachNode: null, - onCloneNode: null, - onEmbedNode: null, - onCreateForeignObjectSvg: null, - includeStyleProperties: null, - autoDestruct: false, - ...options, - // InternalContext - __CONTEXT__: true, - log: createLogger(debug), - node, - ownerDocument, - ownerWindow, - dpi: scale === 1 ? null : 96 * scale, - svgStyleElement: createStyleElement(ownerDocument), - svgDefsElement: ownerDocument?.createElementNS(XMLNS, "defs"), - svgStyles: /* @__PURE__ */ new Map(), - defaultComputedStyles: /* @__PURE__ */ new Map(), - workers: [ - ...Array.from({ - length: SUPPORT_WEB_WORKER && workerUrl && workerNumber ? workerNumber : 0 - }) - ].map(() => { - try { - const worker = new Worker(workerUrl); - worker.onmessage = async (event) => { - const { url, result } = event.data; - if (result) { - requests.get(url)?.resolve?.(result); - } else { - requests.get(url)?.reject?.(new Error(`Error receiving message from worker: ${url}`)); - } - }; - worker.onmessageerror = (event) => { - const { url } = event.data; - requests.get(url)?.reject?.(new Error(`Error receiving message from worker: ${url}`)); - }; - return worker; - } catch (error) { - context.log.warn("Failed to new Worker", error); - return null; - } - }).filter(Boolean), - fontFamilies: /* @__PURE__ */ new Map(), - fontCssTexts: /* @__PURE__ */ new Map(), - acceptOfImage: `${[ - supportWebp(ownerDocument) && "image/webp", - "image/svg+xml", - "image/*", - "*/*" - ].filter(Boolean).join(",")};q=0.8`, - requests, - drawImageCount: 0, - tasks: [], - features, - isEnable: (key) => { - if (key === "restoreScrollPosition") { - return typeof features === "boolean" ? false : features[key] ?? false; - } - if (typeof features === "boolean") { - return features; - } - return features[key] ?? true; - }, - shadowRoots: [] - }; - context.log.time("wait until load"); - await waitUntilLoad(node, { timeout: context.timeout, onWarn: context.log.warn }); - context.log.timeEnd("wait until load"); - const { width, height } = resolveBoundingBox(node, context); - context.width = width; - context.height = height; - return context; -} -function createStyleElement(ownerDocument) { - if (!ownerDocument) - return void 0; - const style = ownerDocument.createElement("style"); - const cssText = style.ownerDocument.createTextNode(` -.______background-clip--text { - background-clip: text; - -webkit-background-clip: text; -} -`); - style.appendChild(cssText); - return style; -} -function resolveBoundingBox(node, context) { - let { width, height } = context; - if (isElementNode(node) && (!width || !height)) { - const box = node.getBoundingClientRect(); - width = width || box.width || Number(node.getAttribute("width")) || 0; - height = height || box.height || Number(node.getAttribute("height")) || 0; - } - return { width, height }; -} - -async function imageToCanvas(image, context) { - const { - log, - timeout, - drawImageCount, - drawImageInterval - } = context; - log.time("image to canvas"); - const loaded = await loadMedia(image, { timeout, onWarn: context.log.warn }); - const { canvas, context2d } = createCanvas(image.ownerDocument, context); - const drawImage = () => { - try { - context2d?.drawImage(loaded, 0, 0, canvas.width, canvas.height); - } catch (error) { - context.log.warn("Failed to drawImage", error); - } - }; - drawImage(); - if (context.isEnable("fixSvgXmlDecode")) { - for (let i = 0; i < drawImageCount; i++) { - await new Promise((resolve) => { - setTimeout(() => { - context2d?.clearRect(0, 0, canvas.width, canvas.height); - drawImage(); - resolve(); - }, i + drawImageInterval); - }); - } - } - context.drawImageCount = 0; - log.timeEnd("image to canvas"); - return canvas; -} -function createCanvas(ownerDocument, context) { - const { width, height, scale, backgroundColor, maximumCanvasSize: max } = context; - const canvas = ownerDocument.createElement("canvas"); - canvas.width = Math.floor(width * scale); - canvas.height = Math.floor(height * scale); - canvas.style.width = `${width}px`; - canvas.style.height = `${height}px`; - if (max) { - if (canvas.width > max || canvas.height > max) { - if (canvas.width > max && canvas.height > max) { - if (canvas.width > canvas.height) { - canvas.height *= max / canvas.width; - canvas.width = max; - } else { - canvas.width *= max / canvas.height; - canvas.height = max; - } - } else if (canvas.width > max) { - canvas.height *= max / canvas.width; - canvas.width = max; - } else { - canvas.width *= max / canvas.height; - canvas.height = max; - } - } - } - const context2d = canvas.getContext("2d"); - if (context2d && backgroundColor) { - context2d.fillStyle = backgroundColor; - context2d.fillRect(0, 0, canvas.width, canvas.height); - } - return { canvas, context2d }; -} - -function cloneCanvas(canvas, context) { - if (canvas.ownerDocument) { - try { - const dataURL = canvas.toDataURL(); - if (dataURL !== "data:,") { - return createImage(dataURL, canvas.ownerDocument); - } - } catch (error) { - context.log.warn("Failed to clone canvas", error); - } - } - const cloned = canvas.cloneNode(false); - const ctx = canvas.getContext("2d"); - const clonedCtx = cloned.getContext("2d"); - try { - if (ctx && clonedCtx) { - clonedCtx.putImageData( - ctx.getImageData(0, 0, canvas.width, canvas.height), - 0, - 0 - ); - } - return cloned; - } catch (error) { - context.log.warn("Failed to clone canvas", error); - } - return cloned; -} - -function cloneIframe(iframe, context) { - try { - if (iframe?.contentDocument?.body) { - return cloneNode(iframe.contentDocument.body, context); - } - } catch (error) { - context.log.warn("Failed to clone iframe", error); - } - return iframe.cloneNode(false); -} - -function cloneImage(image) { - const cloned = image.cloneNode(false); - if (image.currentSrc && image.currentSrc !== image.src) { - cloned.src = image.currentSrc; - cloned.srcset = ""; - } - if (cloned.loading === "lazy") { - cloned.loading = "eager"; - } - return cloned; -} - -async function cloneVideo(video, context) { - if (video.ownerDocument && !video.currentSrc && video.poster) { - return createImage(video.poster, video.ownerDocument); - } - const cloned = video.cloneNode(false); - cloned.crossOrigin = "anonymous"; - if (video.currentSrc && video.currentSrc !== video.src) { - cloned.src = video.currentSrc; - } - const ownerDocument = cloned.ownerDocument; - if (ownerDocument) { - let canPlay = true; - await loadMedia(cloned, { onError: () => canPlay = false, onWarn: context.log.warn }); - if (!canPlay) { - if (video.poster) { - return createImage(video.poster, video.ownerDocument); - } - return cloned; - } - cloned.currentTime = video.currentTime; - await new Promise((resolve) => { - cloned.addEventListener("seeked", resolve, { once: true }); - }); - const canvas = ownerDocument.createElement("canvas"); - canvas.width = video.offsetWidth; - canvas.height = video.offsetHeight; - try { - const ctx = canvas.getContext("2d"); - if (ctx) - ctx.drawImage(cloned, 0, 0, canvas.width, canvas.height); - } catch (error) { - context.log.warn("Failed to clone video", error); - if (video.poster) { - return createImage(video.poster, video.ownerDocument); - } - return cloned; - } - return cloneCanvas(canvas, context); - } - return cloned; -} - -function cloneElement(node, context) { - if (isCanvasElement(node)) { - return cloneCanvas(node, context); - } - if (isIFrameElement(node)) { - return cloneIframe(node, context); - } - if (isImageElement(node)) { - return cloneImage(node); - } - if (isVideoElement(node)) { - return cloneVideo(node, context); - } - return node.cloneNode(false); -} - -function getSandBox(context) { - let sandbox = context.sandbox; - if (!sandbox) { - const { ownerDocument } = context; - try { - if (ownerDocument) { - sandbox = ownerDocument.createElement("iframe"); - sandbox.id = `__SANDBOX__${uuid()}`; - sandbox.width = "0"; - sandbox.height = "0"; - sandbox.style.visibility = "hidden"; - sandbox.style.position = "fixed"; - ownerDocument.body.appendChild(sandbox); - sandbox.srcdoc = ''; - context.sandbox = sandbox; - } - } catch (error) { - context.log.warn("Failed to getSandBox", error); - } - } - return sandbox; -} - -const ignoredStyles = [ - "width", - "height", - "-webkit-text-fill-color" -]; -const includedAttributes = [ - "stroke", - "fill" -]; -function getDefaultStyle(node, pseudoElement, context) { - const { defaultComputedStyles } = context; - const nodeName = node.nodeName.toLowerCase(); - const isSvgNode = isSVGElementNode(node) && nodeName !== "svg"; - const attributes = isSvgNode ? includedAttributes.map((name) => [name, node.getAttribute(name)]).filter(([, value]) => value !== null) : []; - const key = [ - isSvgNode && "svg", - nodeName, - attributes.map((name, value) => `${name}=${value}`).join(","), - pseudoElement - ].filter(Boolean).join(":"); - if (defaultComputedStyles.has(key)) - return defaultComputedStyles.get(key); - const sandbox = getSandBox(context); - const sandboxWindow = sandbox?.contentWindow; - if (!sandboxWindow) - return /* @__PURE__ */ new Map(); - const sandboxDocument = sandboxWindow?.document; - let root; - let el; - if (isSvgNode) { - root = sandboxDocument.createElementNS(XMLNS, "svg"); - el = root.ownerDocument.createElementNS(root.namespaceURI, nodeName); - attributes.forEach(([name, value]) => { - el.setAttributeNS(null, name, value); - }); - root.appendChild(el); - } else { - root = el = sandboxDocument.createElement(nodeName); - } - el.textContent = " "; - sandboxDocument.body.appendChild(root); - const computedStyle = sandboxWindow.getComputedStyle(el, pseudoElement); - const styles = /* @__PURE__ */ new Map(); - for (let len = computedStyle.length, i = 0; i < len; i++) { - const name = computedStyle.item(i); - if (ignoredStyles.includes(name)) - continue; - styles.set(name, computedStyle.getPropertyValue(name)); - } - sandboxDocument.body.removeChild(root); - defaultComputedStyles.set(key, styles); - return styles; -} - -function getDiffStyle(style, defaultStyle, includeStyleProperties) { - const diffStyle = /* @__PURE__ */ new Map(); - const prefixs = []; - const prefixTree = /* @__PURE__ */ new Map(); - if (includeStyleProperties) { - for (const name of includeStyleProperties) { - applyTo(name); - } - } else { - for (let len = style.length, i = 0; i < len; i++) { - const name = style.item(i); - applyTo(name); - } - } - for (let len = prefixs.length, i = 0; i < len; i++) { - prefixTree.get(prefixs[i])?.forEach((value, name) => diffStyle.set(name, value)); - } - function applyTo(name) { - const value = style.getPropertyValue(name); - const priority = style.getPropertyPriority(name); - const subIndex = name.lastIndexOf("-"); - const prefix = subIndex > -1 ? name.substring(0, subIndex) : void 0; - if (prefix) { - let map = prefixTree.get(prefix); - if (!map) { - map = /* @__PURE__ */ new Map(); - prefixTree.set(prefix, map); - } - map.set(name, [value, priority]); - } - if (defaultStyle.get(name) === value && !priority) - return; - if (prefix) { - prefixs.push(prefix); - } else { - diffStyle.set(name, [value, priority]); - } - } - return diffStyle; -} - -function copyCssStyles(node, cloned, isRoot, context) { - const { ownerWindow, includeStyleProperties, currentParentNodeStyle } = context; - const clonedStyle = cloned.style; - const computedStyle = ownerWindow.getComputedStyle(node); - const defaultStyle = getDefaultStyle(node, null, context); - currentParentNodeStyle?.forEach((_, key) => { - defaultStyle.delete(key); - }); - const style = getDiffStyle(computedStyle, defaultStyle, includeStyleProperties); - style.delete("transition-property"); - style.delete("all"); - style.delete("d"); - style.delete("content"); - if (isRoot) { - style.delete("margin-top"); - style.delete("margin-right"); - style.delete("margin-bottom"); - style.delete("margin-left"); - style.delete("margin-block-start"); - style.delete("margin-block-end"); - style.delete("margin-inline-start"); - style.delete("margin-inline-end"); - style.set("box-sizing", ["border-box", ""]); - } - if (style.get("background-clip")?.[0] === "text") { - cloned.classList.add("______background-clip--text"); - } - if (IN_CHROME) { - if (!style.has("font-kerning")) - style.set("font-kerning", ["normal", ""]); - if ((style.get("overflow-x")?.[0] === "hidden" || style.get("overflow-y")?.[0] === "hidden") && style.get("text-overflow")?.[0] === "ellipsis" && node.scrollWidth === node.clientWidth) { - style.set("text-overflow", ["clip", ""]); - } - } - for (let len = clonedStyle.length, i = 0; i < len; i++) { - clonedStyle.removeProperty(clonedStyle.item(i)); - } - style.forEach(([value, priority], name) => { - clonedStyle.setProperty(name, value, priority); - }); - return style; -} - -function copyInputValue(node, cloned) { - if (isTextareaElement(node) || isInputElement(node) || isSelectElement(node)) { - cloned.setAttribute("value", node.value); - } -} - -const pseudoClasses = [ - "::before", - "::after" - // '::placeholder', TODO -]; -const scrollbarPseudoClasses = [ - "::-webkit-scrollbar", - "::-webkit-scrollbar-button", - // '::-webkit-scrollbar:horizontal', TODO - "::-webkit-scrollbar-thumb", - "::-webkit-scrollbar-track", - "::-webkit-scrollbar-track-piece", - // '::-webkit-scrollbar:vertical', TODO - "::-webkit-scrollbar-corner", - "::-webkit-resizer" -]; -function copyPseudoClass(node, cloned, copyScrollbar, context, addWordToFontFamilies) { - const { ownerWindow, svgStyleElement, svgStyles, currentNodeStyle } = context; - if (!svgStyleElement || !ownerWindow) - return; - function copyBy(pseudoClass) { - const computedStyle = ownerWindow.getComputedStyle(node, pseudoClass); - let content = computedStyle.getPropertyValue("content"); - if (!content || content === "none") - return; - addWordToFontFamilies?.(content); - content = content.replace(/(')|(")|(counter\(.+\))/g, ""); - const klasses = [uuid()]; - const defaultStyle = getDefaultStyle(node, pseudoClass, context); - currentNodeStyle?.forEach((_, key) => { - defaultStyle.delete(key); - }); - const style = getDiffStyle(computedStyle, defaultStyle, context.includeStyleProperties); - style.delete("content"); - style.delete("-webkit-locale"); - if (style.get("background-clip")?.[0] === "text") { - cloned.classList.add("______background-clip--text"); - } - const cloneStyle = [ - `content: '${content}';` - ]; - style.forEach(([value, priority], name) => { - cloneStyle.push(`${name}: ${value}${priority ? " !important" : ""};`); - }); - if (cloneStyle.length === 1) - return; - try { - cloned.className = [cloned.className, ...klasses].join(" "); - } catch (err) { - context.log.warn("Failed to copyPseudoClass", err); - return; - } - const cssText = cloneStyle.join("\n "); - let allClasses = svgStyles.get(cssText); - if (!allClasses) { - allClasses = []; - svgStyles.set(cssText, allClasses); - } - allClasses.push(`.${klasses[0]}${pseudoClass}`); - } - pseudoClasses.forEach(copyBy); - if (copyScrollbar) - scrollbarPseudoClasses.forEach(copyBy); -} - -const excludeParentNodes = /* @__PURE__ */ new Set([ - "symbol" - // test/fixtures/svg.symbol.html -]); -async function appendChildNode(node, cloned, child, context, addWordToFontFamilies) { - if (isElementNode(child) && (isStyleElement(child) || isScriptElement(child))) - return; - if (context.filter && !context.filter(child)) - return; - if (excludeParentNodes.has(cloned.nodeName) || excludeParentNodes.has(child.nodeName)) { - context.currentParentNodeStyle = void 0; - } else { - context.currentParentNodeStyle = context.currentNodeStyle; - } - const childCloned = await cloneNode(child, context, false, addWordToFontFamilies); - if (context.isEnable("restoreScrollPosition")) { - restoreScrollPosition(node, childCloned); - } - cloned.appendChild(childCloned); -} -async function cloneChildNodes(node, cloned, context, addWordToFontFamilies) { - let firstChild = node.firstChild; - if (isElementNode(node)) { - if (node.shadowRoot) { - firstChild = node.shadowRoot?.firstChild; - context.shadowRoots.push(node.shadowRoot); - } - } - for (let child = firstChild; child; child = child.nextSibling) { - if (isCommentNode(child)) - continue; - if (isElementNode(child) && isSlotElement(child) && typeof child.assignedNodes === "function") { - const nodes = child.assignedNodes(); - for (let i = 0; i < nodes.length; i++) { - await appendChildNode(node, cloned, nodes[i], context, addWordToFontFamilies); - } - } else { - await appendChildNode(node, cloned, child, context, addWordToFontFamilies); - } - } -} -function restoreScrollPosition(node, chlidCloned) { - if (!isHTMLElementNode(node) || !isHTMLElementNode(chlidCloned)) - return; - const { scrollTop, scrollLeft } = node; - if (!scrollTop && !scrollLeft) { - return; - } - const { transform } = chlidCloned.style; - const matrix = new DOMMatrix(transform); - const { a, b, c, d } = matrix; - matrix.a = 1; - matrix.b = 0; - matrix.c = 0; - matrix.d = 1; - matrix.translateSelf(-scrollLeft, -scrollTop); - matrix.a = a; - matrix.b = b; - matrix.c = c; - matrix.d = d; - chlidCloned.style.transform = matrix.toString(); -} -function applyCssStyleWithOptions(cloned, context) { - const { backgroundColor, width, height, style: styles } = context; - const clonedStyle = cloned.style; - if (backgroundColor) - clonedStyle.setProperty("background-color", backgroundColor, "important"); - if (width) - clonedStyle.setProperty("width", `${width}px`, "important"); - if (height) - clonedStyle.setProperty("height", `${height}px`, "important"); - if (styles) { - for (const name in styles) clonedStyle[name] = styles[name]; - } -} -const NORMAL_ATTRIBUTE_RE = /^[\w-:]+$/; -async function cloneNode(node, context, isRoot = false, addWordToFontFamilies) { - const { ownerDocument, ownerWindow, fontFamilies, onCloneEachNode } = context; - if (ownerDocument && isTextNode(node)) { - if (addWordToFontFamilies && /\S/.test(node.data)) { - addWordToFontFamilies(node.data); - } - return ownerDocument.createTextNode(node.data); - } - if (ownerDocument && ownerWindow && isElementNode(node) && (isHTMLElementNode(node) || isSVGElementNode(node))) { - const cloned2 = await cloneElement(node, context); - if (context.isEnable("removeAbnormalAttributes")) { - const names = cloned2.getAttributeNames(); - for (let len = names.length, i = 0; i < len; i++) { - const name = names[i]; - if (!NORMAL_ATTRIBUTE_RE.test(name)) { - cloned2.removeAttribute(name); - } - } - } - const style = context.currentNodeStyle = copyCssStyles(node, cloned2, isRoot, context); - if (isRoot) - applyCssStyleWithOptions(cloned2, context); - let copyScrollbar = false; - if (context.isEnable("copyScrollbar")) { - const overflow = [ - style.get("overflow-x")?.[0], - style.get("overflow-y")?.[0] - ]; - copyScrollbar = overflow.includes("scroll") || (overflow.includes("auto") || overflow.includes("overlay")) && (node.scrollHeight > node.clientHeight || node.scrollWidth > node.clientWidth); - } - const textTransform = style.get("text-transform")?.[0]; - const families = splitFontFamily(style.get("font-family")?.[0]); - const addWordToFontFamilies2 = families ? (word) => { - if (textTransform === "uppercase") { - word = word.toUpperCase(); - } else if (textTransform === "lowercase") { - word = word.toLowerCase(); - } else if (textTransform === "capitalize") { - word = word[0].toUpperCase() + word.substring(1); - } - families.forEach((family) => { - let fontFamily = fontFamilies.get(family); - if (!fontFamily) { - fontFamilies.set(family, fontFamily = /* @__PURE__ */ new Set()); - } - word.split("").forEach((text) => fontFamily.add(text)); - }); - } : void 0; - copyPseudoClass( - node, - cloned2, - copyScrollbar, - context, - addWordToFontFamilies2 - ); - copyInputValue(node, cloned2); - if (!isVideoElement(node)) { - await cloneChildNodes( - node, - cloned2, - context, - addWordToFontFamilies2 - ); - } - await onCloneEachNode?.(cloned2); - return cloned2; - } - const cloned = node.cloneNode(false); - await cloneChildNodes(node, cloned, context); - await onCloneEachNode?.(cloned); - return cloned; -} - -function destroyContext(context) { - context.ownerDocument = void 0; - context.ownerWindow = void 0; - context.svgStyleElement = void 0; - context.svgDefsElement = void 0; - context.svgStyles.clear(); - context.defaultComputedStyles.clear(); - if (context.sandbox) { - try { - context.sandbox.remove(); - } catch (err) { - context.log.warn("Failed to destroyContext", err); - } - context.sandbox = void 0; - } - context.workers = []; - context.fontFamilies.clear(); - context.fontCssTexts.clear(); - context.requests.clear(); - context.tasks = []; - context.shadowRoots = []; -} - -function baseFetch(options) { - const { url, timeout, responseType, ...requestInit } = options; - const controller = new AbortController(); - const timer = timeout ? setTimeout(() => controller.abort(), timeout) : void 0; - return fetch(url, { signal: controller.signal, ...requestInit }).then((response) => { - if (!response.ok) { - throw new Error("Failed fetch, not 2xx response", { cause: response }); - } - switch (responseType) { - case "arrayBuffer": - return response.arrayBuffer(); - case "dataUrl": - return response.blob().then(blobToDataUrl); - case "text": - default: - return response.text(); - } - }).finally(() => clearTimeout(timer)); -} -function contextFetch(context, options) { - const { url: rawUrl, requestType = "text", responseType = "text", imageDom } = options; - let url = rawUrl; - const { - timeout, - acceptOfImage, - requests, - fetchFn, - fetch: { - requestInit, - bypassingCache, - placeholderImage - }, - font, - workers, - fontFamilies - } = context; - if (requestType === "image" && (IN_SAFARI || IN_FIREFOX)) { - context.drawImageCount++; - } - let request = requests.get(rawUrl); - if (!request) { - if (bypassingCache) { - if (bypassingCache instanceof RegExp && bypassingCache.test(url)) { - url += (/\?/.test(url) ? "&" : "?") + (/* @__PURE__ */ new Date()).getTime(); - } - } - const canFontMinify = requestType.startsWith("font") && font && font.minify; - const fontTexts = /* @__PURE__ */ new Set(); - if (canFontMinify) { - const families = requestType.split(";")[1].split(","); - families.forEach((family) => { - if (!fontFamilies.has(family)) - return; - fontFamilies.get(family).forEach((text) => fontTexts.add(text)); - }); - } - const needFontMinify = canFontMinify && fontTexts.size; - const baseFetchOptions = { - url, - timeout, - responseType: needFontMinify ? "arrayBuffer" : responseType, - headers: requestType === "image" ? { accept: acceptOfImage } : void 0, - ...requestInit - }; - request = { - type: requestType, - resolve: void 0, - reject: void 0, - response: null - }; - request.response = (async () => { - if (fetchFn && requestType === "image") { - const result = await fetchFn(rawUrl); - if (result) - return result; - } - if (!IN_SAFARI && rawUrl.startsWith("http") && workers.length) { - return new Promise((resolve, reject) => { - const worker = workers[requests.size & workers.length - 1]; - worker.postMessage({ rawUrl, ...baseFetchOptions }); - request.resolve = resolve; - request.reject = reject; - }); - } - return baseFetch(baseFetchOptions); - })().catch((error) => { - requests.delete(rawUrl); - if (requestType === "image" && placeholderImage) { - context.log.warn("Failed to fetch image base64, trying to use placeholder image", url); - return typeof placeholderImage === "string" ? placeholderImage : placeholderImage(imageDom); - } - throw error; - }); - requests.set(rawUrl, request); - } - return request.response; -} - -async function replaceCssUrlToDataUrl(cssText, baseUrl, context, isImage) { - if (!hasCssUrl(cssText)) - return cssText; - for (const [rawUrl, url] of parseCssUrls(cssText, baseUrl)) { - try { - const dataUrl = await contextFetch( - context, - { - url, - requestType: isImage ? "image" : "text", - responseType: "dataUrl" - } - ); - cssText = cssText.replace(toRE(rawUrl), `$1${dataUrl}$3`); - } catch (error) { - context.log.warn("Failed to fetch css data url", rawUrl, error); - } - } - return cssText; -} -function hasCssUrl(cssText) { - return /url\((['"]?)([^'"]+?)\1\)/.test(cssText); -} -const URL_RE = /url\((['"]?)([^'"]+?)\1\)/g; -function parseCssUrls(cssText, baseUrl) { - const result = []; - cssText.replace(URL_RE, (raw, quotation, url) => { - result.push([url, resolveUrl(url, baseUrl)]); - return raw; - }); - return result.filter(([url]) => !isDataUrl(url)); -} -function toRE(url) { - const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, "\\$1"); - return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, "g"); -} - -const properties = [ - "background-image", - "border-image-source", - "-webkit-border-image", - "-webkit-mask-image", - "list-style-image" -]; -function embedCssStyleImage(style, context) { - return properties.map((property) => { - const value = style.getPropertyValue(property); - if (!value || value === "none") { - return null; - } - if (IN_SAFARI || IN_FIREFOX) { - context.drawImageCount++; - } - return replaceCssUrlToDataUrl(value, null, context, true).then((newValue) => { - if (!newValue || value === newValue) - return; - style.setProperty( - property, - newValue, - style.getPropertyPriority(property) - ); - }); - }).filter(Boolean); -} - -function embedImageElement(cloned, context) { - if (isImageElement(cloned)) { - const originalSrc = cloned.currentSrc || cloned.src; - if (!isDataUrl(originalSrc)) { - return [ - contextFetch(context, { - url: originalSrc, - imageDom: cloned, - requestType: "image", - responseType: "dataUrl" - }).then((url) => { - if (!url) - return; - cloned.srcset = ""; - cloned.dataset.originalSrc = originalSrc; - cloned.src = url || ""; - }) - ]; - } - if (IN_SAFARI || IN_FIREFOX) { - context.drawImageCount++; - } - } else if (isSVGElementNode(cloned) && !isDataUrl(cloned.href.baseVal)) { - const originalSrc = cloned.href.baseVal; - return [ - contextFetch(context, { - url: originalSrc, - imageDom: cloned, - requestType: "image", - responseType: "dataUrl" - }).then((url) => { - if (!url) - return; - cloned.dataset.originalSrc = originalSrc; - cloned.href.baseVal = url || ""; - }) - ]; - } - return []; -} - -function embedSvgUse(cloned, context) { - const { ownerDocument, svgDefsElement } = context; - const href = cloned.getAttribute("href") ?? cloned.getAttribute("xlink:href"); - if (!href) - return []; - const [svgUrl, id] = href.split("#"); - if (id) { - const query = `#${id}`; - const definition = context.shadowRoots.reduce( - (res, root) => { - return res ?? root.querySelector(`svg ${query}`); - }, - ownerDocument?.querySelector(`svg ${query}`) - ); - if (svgUrl) { - cloned.setAttribute("href", query); - } - if (svgDefsElement?.querySelector(query)) - return []; - if (definition) { - svgDefsElement?.appendChild(definition.cloneNode(true)); - return []; - } else if (svgUrl) { - return [ - contextFetch(context, { - url: svgUrl, - responseType: "text" - }).then((svgData) => { - svgDefsElement?.insertAdjacentHTML("beforeend", svgData); - }) - ]; - } - } - return []; -} - -function embedNode(cloned, context) { - const { tasks } = context; - if (isElementNode(cloned)) { - if (isImageElement(cloned) || isSVGImageElementNode(cloned)) { - tasks.push(...embedImageElement(cloned, context)); - } - if (isSVGUseElementNode(cloned)) { - tasks.push(...embedSvgUse(cloned, context)); - } - } - if (isHTMLElementNode(cloned)) { - tasks.push(...embedCssStyleImage(cloned.style, context)); - } - cloned.childNodes.forEach((child) => { - embedNode(child, context); - }); -} - -async function embedWebFont(clone, context) { - const { - ownerDocument, - svgStyleElement, - fontFamilies, - fontCssTexts, - tasks, - font - } = context; - if (!ownerDocument || !svgStyleElement || !fontFamilies.size) { - return; - } - if (font && font.cssText) { - const cssText = filterPreferredFormat(font.cssText, context); - svgStyleElement.appendChild(ownerDocument.createTextNode(`${cssText} -`)); - } else { - const styleSheets = Array.from(ownerDocument.styleSheets).filter((styleSheet) => { - try { - return "cssRules" in styleSheet && Boolean(styleSheet.cssRules.length); - } catch (error) { - context.log.warn(`Error while reading CSS rules from ${styleSheet.href}`, error); - return false; - } - }); - await Promise.all( - styleSheets.flatMap((styleSheet) => { - return Array.from(styleSheet.cssRules).map(async (cssRule, index) => { - if (isCSSImportRule(cssRule)) { - let importIndex = index + 1; - const baseUrl = cssRule.href; - let cssText = ""; - try { - cssText = await contextFetch(context, { - url: baseUrl, - requestType: "text", - responseType: "text" - }); - } catch (error) { - context.log.warn(`Error fetch remote css import from ${baseUrl}`, error); - } - const replacedCssText = cssText.replace( - URL_RE, - (raw, quotation, url) => raw.replace(url, resolveUrl(url, baseUrl)) - ); - for (const rule of parseCss(replacedCssText)) { - try { - styleSheet.insertRule( - rule, - rule.startsWith("@import") ? importIndex += 1 : styleSheet.cssRules.length - ); - } catch (error) { - context.log.warn("Error inserting rule from remote css import", { rule, error }); - } - } - } - }); - }) - ); - const cssRules = styleSheets.flatMap((styleSheet) => Array.from(styleSheet.cssRules)); - cssRules.filter((cssRule) => isCssFontFaceRule(cssRule) && hasCssUrl(cssRule.style.getPropertyValue("src")) && splitFontFamily(cssRule.style.getPropertyValue("font-family"))?.some((val) => fontFamilies.has(val))).forEach((value) => { - const rule = value; - const cssText = fontCssTexts.get(rule.cssText); - if (cssText) { - svgStyleElement.appendChild(ownerDocument.createTextNode(`${cssText} -`)); - } else { - tasks.push( - replaceCssUrlToDataUrl( - rule.cssText, - rule.parentStyleSheet ? rule.parentStyleSheet.href : null, - context - ).then((cssText2) => { - cssText2 = filterPreferredFormat(cssText2, context); - fontCssTexts.set(rule.cssText, cssText2); - svgStyleElement.appendChild(ownerDocument.createTextNode(`${cssText2} -`)); - }) - ); - } - }); - } -} -const COMMENTS_RE = /(\/\*[\s\S]*?\*\/)/g; -const KEYFRAMES_RE = /((@.*?keyframes [\s\S]*?){([\s\S]*?}\s*?)})/gi; -function parseCss(source) { - if (source == null) - return []; - const result = []; - let cssText = source.replace(COMMENTS_RE, ""); - while (true) { - const matches = KEYFRAMES_RE.exec(cssText); - if (!matches) - break; - result.push(matches[0]); - } - cssText = cssText.replace(KEYFRAMES_RE, ""); - const IMPORT_RE = /@import[\s\S]*?url\([^)]*\)[\s\S]*?;/gi; - const UNIFIED_RE = new RegExp( - // eslint-disable-next-line - "((\\s*?(?:\\/\\*[\\s\\S]*?\\*\\/)?\\s*?@media[\\s\\S]*?){([\\s\\S]*?)}\\s*?})|(([\\s\\S]*?){([\\s\\S]*?)})", - "gi" - ); - while (true) { - let matches = IMPORT_RE.exec(cssText); - if (!matches) { - matches = UNIFIED_RE.exec(cssText); - if (!matches) { - break; - } else { - IMPORT_RE.lastIndex = UNIFIED_RE.lastIndex; - } - } else { - UNIFIED_RE.lastIndex = IMPORT_RE.lastIndex; - } - result.push(matches[0]); - } - return result; -} -const URL_WITH_FORMAT_RE = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g; -const FONT_SRC_RE = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g; -function filterPreferredFormat(str, context) { - const { font } = context; - const preferredFormat = font ? font?.preferredFormat : void 0; - return preferredFormat ? str.replace(FONT_SRC_RE, (match) => { - while (true) { - const [src, , format] = URL_WITH_FORMAT_RE.exec(match) || []; - if (!format) - return ""; - if (format === preferredFormat) - return `src: ${src};`; - } - }) : str; -} - -async function domToForeignObjectSvg(node, options) { - const context = await orCreateContext(node, options); - if (isElementNode(context.node) && isSVGElementNode(context.node)) - return context.node; - const { - ownerDocument, - log, - tasks, - svgStyleElement, - svgDefsElement, - svgStyles, - font, - progress, - autoDestruct, - onCloneNode, - onEmbedNode, - onCreateForeignObjectSvg - } = context; - log.time("clone node"); - const clone = await cloneNode(context.node, context, true); - if (svgStyleElement && ownerDocument) { - let allCssText = ""; - svgStyles.forEach((klasses, cssText) => { - allCssText += `${klasses.join(",\n")} { - ${cssText} -} -`; - }); - svgStyleElement.appendChild(ownerDocument.createTextNode(allCssText)); - } - log.timeEnd("clone node"); - await onCloneNode?.(clone); - if (font !== false && isElementNode(clone)) { - log.time("embed web font"); - await embedWebFont(clone, context); - log.timeEnd("embed web font"); - } - log.time("embed node"); - embedNode(clone, context); - const count = tasks.length; - let current = 0; - const runTask = async () => { - while (true) { - const task = tasks.pop(); - if (!task) - break; - try { - await task; - } catch (error) { - context.log.warn("Failed to run task", error); - } - progress?.(++current, count); - } - }; - progress?.(current, count); - await Promise.all([...Array.from({ length: 4 })].map(runTask)); - log.timeEnd("embed node"); - await onEmbedNode?.(clone); - const svg = createForeignObjectSvg(clone, context); - svgDefsElement && svg.insertBefore(svgDefsElement, svg.children[0]); - svgStyleElement && svg.insertBefore(svgStyleElement, svg.children[0]); - autoDestruct && destroyContext(context); - await onCreateForeignObjectSvg?.(svg); - return svg; -} -function createForeignObjectSvg(clone, context) { - const { width, height } = context; - const svg = createSvg(width, height, clone.ownerDocument); - const foreignObject = svg.ownerDocument.createElementNS(svg.namespaceURI, "foreignObject"); - foreignObject.setAttributeNS(null, "x", "0%"); - foreignObject.setAttributeNS(null, "y", "0%"); - foreignObject.setAttributeNS(null, "width", "100%"); - foreignObject.setAttributeNS(null, "height", "100%"); - foreignObject.append(clone); - svg.appendChild(foreignObject); - return svg; -} - -async function domToCanvas(node, options) { - const context = await orCreateContext(node, options); - const svg = await domToForeignObjectSvg(context); - const dataUrl = svgToDataUrl(svg, context.isEnable("removeControlCharacter")); - if (!context.autoDestruct) { - context.svgStyleElement = createStyleElement(context.ownerDocument); - context.svgDefsElement = context.ownerDocument?.createElementNS(XMLNS, "defs"); - context.svgStyles.clear(); - } - const image = createImage(dataUrl, svg.ownerDocument); - return await imageToCanvas(image, context); -} - -async function domToBlob(node, options) { - const context = await orCreateContext(node, options); - const { log, type, quality, dpi } = context; - const canvas = await domToCanvas(context); - log.time("canvas to blob"); - const blob = await canvasToBlob(canvas, type, quality); - if (["image/png", "image/jpeg"].includes(type) && dpi) { - const arrayBuffer = await blobToArrayBuffer(blob.slice(0, 33)); - let uint8Array = new Uint8Array(arrayBuffer); - if (type === "image/png") { - uint8Array = changePngDpi(uint8Array, dpi); - } else if (type === "image/jpeg") { - uint8Array = changeJpegDpi(uint8Array, dpi); - } - log.timeEnd("canvas to blob"); - return new Blob([uint8Array, blob.slice(33)], { type }); - } - log.timeEnd("canvas to blob"); - return blob; -} - -async function domToDataUrl(node, options) { - const context = await orCreateContext(node, options); - const { log, quality, type, dpi } = context; - const canvas = await domToCanvas(context); - log.time("canvas to data url"); - let dataUrl = canvas.toDataURL(type, quality); - if (["image/png", "image/jpeg"].includes(type) && dpi && SUPPORT_ATOB && SUPPORT_BTOA) { - const [format, body] = dataUrl.split(","); - let headerLength = 0; - let overwritepHYs = false; - if (type === "image/png") { - const b64Index = detectPhysChunkFromDataUrl(body); - if (b64Index >= 0) { - headerLength = Math.ceil((b64Index + 28) / 3) * 4; - overwritepHYs = true; - } else { - headerLength = 33 / 3 * 4; - } - } else if (type === "image/jpeg") { - headerLength = 18 / 3 * 4; - } - const stringHeader = body.substring(0, headerLength); - const restOfData = body.substring(headerLength); - const headerBytes = window.atob(stringHeader); - const uint8Array = new Uint8Array(headerBytes.length); - for (let i = 0; i < uint8Array.length; i++) { - uint8Array[i] = headerBytes.charCodeAt(i); - } - const finalArray = type === "image/png" ? changePngDpi(uint8Array, dpi, overwritepHYs) : changeJpegDpi(uint8Array, dpi); - const base64Header = window.btoa(String.fromCharCode(...finalArray)); - dataUrl = [format, ",", base64Header, restOfData].join(""); - } - log.timeEnd("canvas to data url"); - return dataUrl; -} - -async function domToSvg(node, options) { - const context = await orCreateContext(node, options); - const { width, height, ownerDocument } = context; - const dataUrl = await domToDataUrl(context); - const svg = createSvg(width, height, ownerDocument); - const svgImage = svg.ownerDocument.createElementNS(svg.namespaceURI, "image"); - svgImage.setAttributeNS(null, "href", dataUrl); - svgImage.setAttributeNS(null, "height", "100%"); - svgImage.setAttributeNS(null, "width", "100%"); - svg.appendChild(svgImage); - return svgToDataUrl(svg, context.isEnable("removeControlCharacter")); -} - -async function domToImage(node, options) { - const context = await orCreateContext(node, options); - const { ownerDocument, width, height, scale, type } = context; - const url = type === "image/svg+xml" ? await domToSvg(context) : await domToDataUrl(context); - const image = createImage(url, ownerDocument); - image.width = Math.floor(width * scale); - image.height = Math.floor(height * scale); - image.style.width = `${width}px`; - image.style.height = `${height}px`; - return image; -} - -async function domToJpeg(node, options) { - return domToDataUrl( - await orCreateContext(node, { ...options, type: "image/jpeg" }) - ); -} - -async function domToPixel(node, options) { - const context = await orCreateContext(node, options); - const canvas = await domToCanvas(context); - return canvas.getContext("2d").getImageData(0, 0, canvas.width, canvas.height).data; -} - -async function domToPng(node, options) { - return domToDataUrl( - await orCreateContext(node, { ...options, type: "image/png" }) - ); -} - -async function domToWebp(node, options) { - return domToDataUrl( - await orCreateContext(node, { ...options, type: "image/webp" }) - ); -} - -export { createContext, destroyContext, domToBlob, domToCanvas, domToDataUrl, domToForeignObjectSvg, domToImage, domToJpeg, domToPixel, domToPng, domToSvg, domToWebp, loadMedia, waitUntilLoad }; diff --git a/modules/unpkg.sh b/modules/unpkg.sh index 3670ccf49..85d17dd6a 100755 --- a/modules/unpkg.sh +++ b/modules/unpkg.sh @@ -409,4 +409,3 @@ main "@solidjs/signals" main "@leeoniya/ufuzzy" main "lean-qr" main "lightweight-charts" -main "modern-screenshot" diff --git a/website/index.html b/website/index.html index 43e3e5554..163948781 100644 --- a/website/index.html +++ b/website/index.html @@ -574,10 +574,6 @@ color: var(--color); font-style: normal; } - - [data-screenshot="true"] &:has(input:not(:checked)) { - display: none; - } } main { @@ -603,8 +599,7 @@ right: 0; left: 0; text-transform: uppercase; - margin: var(--main-padding); - margin-bottom: var(--main-padding); + margin: var(--main-padding) 0; z-index: 100; pointer-events: none; justify-content: center; @@ -616,13 +611,18 @@ } > fieldset { - width: 100%; display: flex; gap: 1.25rem; + overflow-x: auto; + scrollbar-width: thin; + min-width: 0; + margin: -0.5rem 0; + padding: 0.5rem var(--main-padding); + pointer-events: auto; > label, > button { - pointer-events: auto; + flex-shrink: 0; } > button { @@ -1018,19 +1018,6 @@ margin-top: 2rem; margin-bottom: 1rem; - button#screenshot { - width: 100%; - text-align: left; - padding-left: 1rem; - height: 100%; - font-size: var(--font-size-sm); - line-height: var(--line-height-sm); - - [data-screenshot="true"] & { - display: none; - } - } - > legend { gap: 1.5rem; @@ -1039,10 +1026,6 @@ } > div { - [data-screenshot="true"] &:has(input:not(:checked)) { - display: none; - } - flex: 0; height: 100%; display: flex; @@ -1083,11 +1066,6 @@ padding-right: 0.375rem; margin-left: -0.375rem; margin-right: -0.375rem; - - [data-screenshot="true"] & { - width: 0; - opacity: 0; - } } } } @@ -1129,6 +1107,16 @@ gap: 0; } + + button.capture { + position: absolute; + top: 0.5rem; + right: 0.5rem; + z-index: 10; + font-size: var(--font-size-xs); + line-height: var(--line-height-xs); + color: var(--off-color); + } } > .panes { @@ -1206,17 +1194,6 @@ flex: 1; } - #interval { - > span { - display: none; - - [data-screenshot="true"] & { - color: var(--gray); - display: inline-block; - } - } - } - > .chart > legend, > fieldset { z-index: 20; @@ -1534,8 +1511,12 @@ ); let storedTheme; - try { storedTheme = localStorage.getItem("theme"); } catch (_) {} - const isDark = storedTheme ? storedTheme === "dark" : preferredColorSchemeMatchMedia.matches; + try { + storedTheme = localStorage.getItem("theme"); + } catch (_) {} + const isDark = storedTheme + ? storedTheme === "dark" + : preferredColorSchemeMatchMedia.matches; document.documentElement.style.colorScheme = isDark ? "dark" : "light"; const themeColor = window.document.createElement("meta"); @@ -1801,6 +1782,8 @@