UNPKG

@antv/x6

Version:

JavaScript diagramming library that uses SVG and HTML for rendering

211 lines (178 loc) 5.49 kB
function parseViewBox(svg: string) { const matches = svg.match(/<svg[^>]*viewBox\s*=\s*(["']?)(.+?)\1[^>]*>/i) if (matches && matches[2]) { return matches[2].replace(/\s+/, ' ').split(' ') } return null } function getNumber(str: string) { const ret = parseFloat(str) return Number.isNaN(ret) ? null : ret } export function isDataUrl(url: string) { const prefix = 'data:' return url.substr(0, prefix.length) === prefix } /** * Converts an image at `url` to base64-encoded data uri. * The mime type of the image is inferred from the `url` file extension. */ export function imageToDataUri( url: string, callback: (err: Error | null, dataUri?: string) => any, ) { // No need to convert to data uri if it is already in data uri. if (!url || isDataUrl(url)) { // Keep the async nature of the function. setTimeout(() => callback(null, url)) return } const onError = () => { callback(new Error(`Failed to load image: ${url}`)) } const onLoad = window.FileReader ? // chrome, IE10+ (xhr: XMLHttpRequest) => { if (xhr.status === 200) { const reader = new FileReader() reader.onload = (evt) => { const dataUri = evt.target!.result as string callback(null, dataUri) } reader.onerror = onError reader.readAsDataURL(xhr.response) } else { onError() } } : (xhr: XMLHttpRequest) => { const toString = (u8a: Uint8Array) => { const CHUNK_SZ = 0x8000 const c = [] for (let i = 0; i < u8a.length; i += CHUNK_SZ) { c.push( String.fromCharCode.apply(null, u8a.subarray(i, i + CHUNK_SZ)), ) } return c.join('') } if (xhr.status === 200) { let suffix = url.split('.').pop() || 'png' if (suffix === 'svg') { suffix = 'svg+xml' } const meta = `data:image/${suffix};base64,` const bytes = new Uint8Array(xhr.response) const base64 = meta + btoa(toString(bytes)) callback(null, base64) } else { onError() } } const xhr = new XMLHttpRequest() xhr.responseType = window.FileReader ? 'blob' : 'arraybuffer' xhr.open('GET', url, true) xhr.addEventListener('error', onError) xhr.addEventListener('load', () => onLoad(xhr)) xhr.send() } export function dataUriToBlob(dataUrl: string) { let uri = dataUrl.replace(/\s/g, '') uri = decodeURIComponent(uri) const index = uri.indexOf(',') const dataType = uri.slice(0, index) // e.g. 'data:image/jpeg;base64' const mime = dataType.split(':')[1].split(';')[0] // e.g. 'image/jpeg' const data = uri.slice(index + 1) let decodedString: string if (dataType.indexOf('base64') >= 0) { // data may be encoded in base64 decodedString = atob(data) } else { // convert the decoded string to UTF-8 decodedString = unescape(encodeURIComponent(data)) } // write the bytes of the string to a typed array const ia = new Uint8Array(decodedString.length) for (let i = 0; i < decodedString.length; i += 1) { ia[i] = decodedString.charCodeAt(i) } return new Blob([ia], { type: mime }) } export function downloadBlob(blob: Blob, fileName: string) { const msSaveBlob = (window.navigator as any).msSaveBlob if (msSaveBlob) { // requires IE 10+ // pulls up a save dialog msSaveBlob(blob, fileName) } else { // other browsers // downloads directly in Chrome and Safari // presents a save/open dialog in Firefox // Firefox bug: `from` field in save dialog always shows `from:blob:` // https://bugzilla.mozilla.org/show_bug.cgi?id=1053327 const url = window.URL.createObjectURL(blob) const link = document.createElement('a') link.href = url link.download = fileName document.body.appendChild(link) link.click() document.body.removeChild(link) // mark the url for garbage collection window.URL.revokeObjectURL(url) } } export function downloadDataUri(dataUrl: string, fileName: string) { const blob = dataUriToBlob(dataUrl) downloadBlob(blob, fileName) } export function svgToDataUrl( svg: string, options: { width?: number | null height?: number | null } = {}, ) { let viewBox: string[] | null = null const getNumberFromViewBox = (index: number) => { if (viewBox == null) { viewBox = parseViewBox(svg) } if (viewBox != null) { return getNumber(viewBox[index]) } return null } const getNumberFromMatches = (reg: RegExp) => { const matches = svg.match(reg) if (matches && matches[2]) { return getNumber(matches[2]) } return null } let w = options.width if (w == null) { w = getNumberFromMatches(/<svg[^>]*width\s*=\s*(["']?)(.+?)\1[^>]*>/i) } if (w == null) { w = getNumberFromViewBox(2) } if (w == null) { throw new Error('Can not parse width from svg string') } let h = options.height if (h == null) { h = getNumberFromMatches(/<svg[^>]*height\s*=\s*(["']?)(.+?)\1[^>]*>/i) } if (h == null) { h = getNumberFromViewBox(3) } if (h == null) { throw new Error('Can not parse height from svg string') } const decoded = encodeURIComponent(svg) .replace(/'/g, '%27') .replace(/"/g, '%22') const header = 'data:image/svg+xml' const dataUrl = `${header},${decoded}` return dataUrl }