Clean up getCarbonImage (#1126)

* clean up getCarbonImage

* remove createObjectURL change

* bug fixes

* refactor getCarbonImage to use blobs/dataURLs only

* add console.error for copy to clipboard failures

* use blob.type for ClipboardItem

* add TODO
main
Michael Fix 4 years ago committed by GitHub
parent b048dae54b
commit cd7c6eb5c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -33,7 +33,7 @@ import {
FONTS, FONTS,
} from '../lib/constants' } from '../lib/constants'
import { serializeState, getRouteState } from '../lib/routing' import { serializeState, getRouteState } from '../lib/routing'
import { getSettings, unescapeHtml, formatCode, omit } from '../lib/util' import { getSettings, unescapeHtml, formatCode, omit, dataURLtoBlob } from '../lib/util'
import domtoimage from '../lib/dom-to-image' import domtoimage from '../lib/dom-to-image'
const languageIcon = <LanguageIcon /> const languageIcon = <LanguageIcon />
@ -110,32 +110,8 @@ class Editor extends React.Component {
exportSize = (EXPORT_SIZES_HASH[this.state.exportSize] || DEFAULT_EXPORT_SIZE).value, exportSize = (EXPORT_SIZES_HASH[this.state.exportSize] || DEFAULT_EXPORT_SIZE).value,
} = { format: 'png' } } = { format: 'png' }
) => { ) => {
// if safari, get image from api
const isPNG = format !== 'svg'
if (this.context.image && this.isSafari && isPNG) {
const themeConfig = this.getTheme()
// pull from custom theme highlights, or state highlights
const encodedState = serializeState({
...this.state,
highlights: { ...themeConfig.highlights, ...this.state.highlights },
})
return this.context.image(encodedState)
}
const node = this.carbonNode.current const node = this.carbonNode.current
// const map = new Map()
// if (isPNG) {
// node.querySelectorAll('span[role="presentation"]').forEach(node => {
// if (node.innerText && node.innerText.match(/%[A-Fa-f0-9]{2}/)) {
// map.set(node, node.innerHTML)
// node.innerText.match(/%[A-Fa-f0-9]{2}/g).forEach(t => {
// node.innerHTML = node.innerHTML.replace(t, encodeURIComponent(t))
// })
// }
// })
// }
const width = node.offsetWidth * exportSize const width = node.offsetWidth * exportSize
const height = squared ? node.offsetWidth * exportSize : node.offsetHeight * exportSize const height = squared ? node.offsetWidth * exportSize : node.offsetHeight * exportSize
@ -163,43 +139,49 @@ class Editor extends React.Component {
// current font-family used // current font-family used
const fontFamily = this.state.fontFamily const fontFamily = this.state.fontFamily
try {
// TODO consolidate type/format to only use one param
if (type === 'objectURL') {
if (format === 'svg') {
return domtoimage
.toSvg(node, config)
.then(dataUrl =>
dataUrl
.replace(/&nbsp;/g, '&#160;')
// https://github.com/tsayen/dom-to-image/blob/fae625bce0970b3a039671ea7f338d05ecb3d0e8/src/dom-to-image.js#L551
.replace(/%23/g, '#')
.replace(/%0A/g, '\n')
// https://stackoverflow.com/questions/7604436/xmlparseentityref-no-name-warnings-while-loading-xml-into-a-php-file
.replace(/&(?!#?[a-z0-9]+;)/g, '&amp;')
// remove other fonts which are not used
.replace(
new RegExp('@font-face\\s+{\\s+font-family: (?!"*' + fontFamily + ').*?}', 'g'),
''
)
)
.then(uri => uri.slice(uri.indexOf(',') + 1))
.then(data => new Blob([data], { type: 'image/svg+xml' }))
.then(data => window.URL.createObjectURL(data))
}
return await domtoimage.toBlob(node, config).then(blob => window.URL.createObjectURL(blob)) // TODO consolidate type/format to only use one param
} if (format === 'svg') {
return domtoimage
.toSvg(node, config)
.then(dataURL =>
dataURL
.replace(/&nbsp;/g, '&#160;')
// https://github.com/tsayen/dom-to-image/blob/fae625bce0970b3a039671ea7f338d05ecb3d0e8/src/dom-to-image.js#L551
.replace(/%23/g, '#')
.replace(/%0A/g, '\n')
// https://stackoverflow.com/questions/7604436/xmlparseentityref-no-name-warnings-while-loading-xml-into-a-php-file
.replace(/&(?!#?[a-z0-9]+;)/g, '&amp;')
// remove other fonts which are not used
.replace(
new RegExp('@font-face\\s+{\\s+font-family: (?!"*' + fontFamily + ').*?}', 'g'),
''
)
)
.then(uri => uri.slice(uri.indexOf(',') + 1))
.then(data => new Blob([data], { type: 'image/svg+xml' }))
}
if (type === 'blob') { // if safari, get image from api
return await domtoimage.toBlob(node, config) if (this.context.image && this.isSafari) {
} const themeConfig = this.getTheme()
// pull from custom theme highlights, or state highlights
const encodedState = serializeState({
...this.state,
highlights: { ...themeConfig.highlights, ...this.state.highlights },
})
// TODO consider returning blob responseType from axios
return this.context
.image(encodedState)
.then(dataURL => (type === 'blob' ? dataURLtoBlob(dataURL) : dataURL))
}
// Twitter needs regular dataurls if (type === 'blob') {
return await domtoimage.toPng(node, config) return domtoimage.toBlob(node, config)
} finally {
// map.forEach((value, node) => (node.innerHTML = value))
} }
// Twitter needs regular dataURLs
return domtoimage.toPng(node, config)
} }
tweet = () => { tweet = () => {
@ -213,28 +195,32 @@ class Editor extends React.Component {
const prefix = options.filename || this.state.name || 'carbon' const prefix = options.filename || this.state.name || 'carbon'
return this.getCarbonImage({ format, type: 'objectURL' }).then(url => { return this.getCarbonImage({ format, type: 'blob' })
if (format !== 'open') { .then(blob => window.URL.createObjectURL(blob))
link.download = `${prefix}.${format}` .then(url => {
} if (format !== 'open') {
if (this.isFirefox) { link.download = `${prefix}.${format}`
link.target = '_blank' }
} if (this.isFirefox) {
link.href = url link.target = '_blank'
document.body.appendChild(link) }
link.click() link.href = url
link.remove() document.body.appendChild(link)
}) link.click()
link.remove()
})
} }
copyImage = () => copyImage = () =>
this.getCarbonImage({ format: 'png', type: 'blob' }).then(blob => this.getCarbonImage({ format: 'png', type: 'blob' })
navigator.clipboard.write([ .then(blob =>
new window.ClipboardItem({ navigator.clipboard.write([
'image/png': blob, new window.ClipboardItem({
}), [blob.type]: blob,
]) }),
) ])
)
.catch(console.error)
updateSetting = (key, value) => { updateSetting = (key, value) => {
this.updateState({ [key]: value }) this.updateState({ [key]: value })

@ -94,3 +94,15 @@ export const formatCode = async code => {
export const stringifyRGBA = obj => `rgba(${obj.r},${obj.g},${obj.b},${obj.a})` export const stringifyRGBA = obj => `rgba(${obj.r},${obj.g},${obj.b},${obj.a})`
export const generateId = () => Math.random().toString(36).slice(2) export const generateId = () => Math.random().toString(36).slice(2)
export function dataURLtoBlob(dataurl) {
const [first, second] = dataurl.split(',')
const mime = first.match(/:(.*?);/)[1]
const bstr = atob(second)
let n = bstr.length
const u8arr = new Uint8Array(n)
while (n--) {
u8arr[n] = bstr.charCodeAt(n)
}
return new Blob([u8arr], { type: mime })
}

Loading…
Cancel
Save