UNPKG

@absulit/points

Version:

A Generative Art library made in WebGPU

166 lines (146 loc) 5 kB
/** * Utility methods to for the {@link Points#setTextureElement | setTextureElement()} * https://web.archive.org/web/20181006205840/https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Drawing_DOM_objects_into_a_canvas * @module texture-element * @ignore */ const cache = new Map(); /** * Get the CSS associated with a specific `HTMLElement`. * @param {HTMLElement} el * @returns {String} All the CSS associated to the `el` `HTMLElement`. */ export function getCSS(el) { const sheets = document.styleSheets; const matchedRules = []; for (const sheet of sheets) { try { const rules = sheet.cssRules || sheet.rules; for (const rule of rules) { if (el.matches(rule.selectorText)) { matchedRules.push(rule); } } } catch (e) { console.warn('Could not read stylesheet: ' + sheet.href); } } return matchedRules; } /** * Gets the url of the font requested from the loaded CSS. * @param {String} familyName * @returns {{url:String, fontFace:String}|null} */ function getFontSource(familyName) { let source = null; for (let sheet of document.styleSheets) { try { for (let rule of sheet.cssRules) { if (rule instanceof CSSFontFaceRule) { if (rule.style.fontFamily === familyName) { const regex = /url\(['"]?([^'"]+)['"]?\)/; const match = rule.style.src.match(regex); if (match) { const url = match[1]; source = { url, fontFace: rule.cssText } } } } } } catch (e) { console.warn('Can\'t read stylesheet (CORS?): ', e); } } return source; } /** * From the css in the HTMLElement, get the font-family attribute value to be * used later to get the source. * @param {String} cssString * @returns {Strng|null} */ function getFontFamily(cssString) { let fontFamily = null; const regex = /font-family:\s*([^;]+)/; const match = cssString.match(regex); if (match) { fontFamily = match[1].trim(); } return fontFamily; } /** * Converts a font to b64 to embed in the foreingObject * @param {String} url path to font file * @returns {Promise<String>} */ async function fontToB64(url) { const response = await fetch(url); const blob = await response.blob(); return new Promise((resolve, reject) => { const reader = new FileReader(); reader.onloadend = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(blob); }); } /** * Renders a `HTMLElement` as image along with some CSS. * @param {HTMLElement} element Element to render. * @param {String} styles CSS styles to render the element with. * @returns {Promise<Image>} */ export async function elToImage(element, styles) { const { offsetWidth: width, offsetHeight: height } = element; styles ??= ''; const fontFamily = getFontFamily(styles); let fontFace = cache.get(fontFamily) || null; if (!fontFace && fontFamily) { const fontSource = getFontSource(fontFamily); if (fontSource) { const b64 = await fontToB64(fontSource.url); const regex = /url\((['"]?)[^'"]+\1\)/; fontFace = fontSource.fontFace.replace(regex, `url($1${b64}$1)`); cache.set(fontFamily, fontFace); } } fontFace ??= ''; const htmlContent = new XMLSerializer().serializeToString(element); const svgData = ` <svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%"> <defs><style type="text/css">${fontFace}${styles}</style></defs> <foreignObject width="100%" height="100%"> <div xmlns="http://www.w3.org/1999/xhtml">${htmlContent}</div> </foreignObject> </svg> `; const encodedData = btoa(decodeURIComponent(encodeURIComponent(svgData))); const url = `data:image/svg+xml;base64,${encodedData}`; return new Promise((resolve, reject) => { const img = new Image(); img.onload = () => { const canvas = document.createElement('canvas'); canvas.width = width; canvas.height = height; const ctx = canvas.getContext('2d'); ctx.drawImage(img, 0, 0); canvas.toBlob(blob => { const url = URL.createObjectURL(blob); resolve(url); }); }; img.onerror = () => { reject(new Error('Failed to decode SVG.')); }; img.src = url; }); } /** * Utilitary method to clear the cache without exposing it. */ export function clearCache() { cache.clear(); }