svg-paper
Version:
The world's most maintainable way to create paper-printable documents 🖨💘
89 lines (68 loc) • 3.11 kB
JavaScript
import adjustText from './adjust-text'
import adjustTextarea from './adjust-textarea'
export default class SvgPaper {
constructor(selector = '.paper svg') {
if (!document.querySelector(selector)) {
throw new Error('Invalid selector')
}
this.selector = selector
this.svg = document.querySelector(selector).outerHTML.replace(/[\r|\n]+/g, "\n")
this.adjustTextQueries = []
}
replace(search, replacement) {
if (search instanceof RegExp) {
search = new RegExp(search.source, search.flags.replace(/g/g, '') + 'g')
} else {
search = search.replace(/[\r|\n]+/g, "\n")
// @see https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
search = search.replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&')
search = new RegExp(search, 'g')
}
// cast to string
replacement = replacement !== undefined && replacement !== null ? replacement + '' : ''
replacement = replacement.replace(/[\r|\n]+/g, "\n")
this.svg = this.svg.replace(search, replacement)
return this
}
adjustText(selector, textLength = null, textAnchor = 'start') {
this.adjustTextQueries.push({selector, textLength, textAnchor})
return this
}
adjustTextarea(selector, width, height, lineHeight = 1.2, paddingX = 0.5, paddingY = 0.5, nowrap = false) {
const doc = new DOMParser().parseFromString(this.svg, 'text/html')
const textElement = doc.querySelector(selector)
if (!textElement) {
return this
}
const textSvg = textElement.outerHTML
// SVGElement doesn't have innerText
// @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement
const textContent = textElement.innerHTML.replace(new RegExp('^<tspan[^>]*>([\\S|\\s]*)</tspan>$'), '$1')
const adjustedTextSvg = adjustTextarea(textSvg, textContent, width, height, lineHeight, paddingX, paddingY, nowrap)
this.replace(textSvg, adjustedTextSvg)
return this
}
apply() {
if (this.svg !== document.querySelector(this.selector).outerHTML) {
document.querySelector(this.selector).outerHTML = this.svg
}
this.adjustTextQueries.forEach(query => {
// if viewBox is specified, Element.clientWidth and Element.getBoundingClientRect() return different values
// clientWidth: ???
// getBoundingClientRect(): pure pixel value
// so this library uses getBoundingClientRect() and pre-calculated ratio to specify the width of some elements
// @see https://stackoverflow.com/questions/15335926/how-to-use-the-svg-viewbox-attribute
const $svg = document.querySelector(this.selector)
const viewBoxWidth = $svg.getAttribute('viewBox')?.split(/ +/)[2] ?? null
const paperPixelRatio = viewBoxWidth ? parseFloat(viewBoxWidth) / $svg.getBoundingClientRect().width : 1
adjustText(query.selector, {
textLength: query.textLength,
'text-anchor': query.textAnchor,
}, paperPixelRatio)
})
// initialize
this.svg = document.querySelector(this.selector).outerHTML
this.adjustTextQueries = []
}
}