svgdom
Version:
Straightforward DOM implementation for SVG, HTML and XML
131 lines (102 loc) • 4.46 kB
JavaScript
import { SVGElement } from './SVGElement.js'
import { getSegments } from '../../utils/bboxUtils.js'
import * as regex from '../../utils/regex.js'
import { SVGMatrix } from './SVGMatrix.js'
// Map matrix array to object
function arrayToMatrix (a) {
return { a: a[0], b: a[1], c: a[2], d: a[3], e: a[4], f: a[5] }
}
export class SVGGraphicsElement extends SVGElement {
// TODO: https://www.w3.org/TR/SVG2/coords.html#ComputingAViewportsTransform
generateViewBoxMatrix () {
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/viewBox
if (![ 'marker', 'symbol', 'pattern', 'svg', 'view' ].includes(this.nodeName)) {
return new SVGMatrix()
}
let view = (this.getAttribute('viewBox') || '').split(regex.delimiter).map(parseFloat).filter(el => !isNaN(el))
const width = parseFloat(this.getAttribute('width')) || 0
const height = parseFloat(this.getAttribute('height')) || 0
const x = parseFloat(this.getAttribute('x')) || 0
const y = parseFloat(this.getAttribute('y')) || 0
// TODO: If no width and height is given, width and height of the outer svg element is used
if (!width || !height) {
return new SVGMatrix().translate(x, y)
}
if (view.length !== 4) {
view = [ 0, 0, width, height ]
}
// first apply x and y if nested, then viewbox scale, then viewBox move
return new SVGMatrix().translate(x, y).scale(width / view[2], height / view[3]).translate(-view[0], -view[1])
}
getBBox () {
return getSegments(this).bbox()
}
// TODO: This method actually exists on all Elements
getBoundingClientRect () {
// The bounding client rect takes the screen ctm of the element
// and converts the bounding box with it
// however, normal bounding consists of:
// - all children transformed
// - the viewbox of the element if available
// The boundingClientRect is not affected by its own viewbox
// So we apply only our own transformations and parents screenCTM
let m = this.matrixify()
if (this.parentNode && this.parentNode.nodeName !== '#document') {
m = this.parentNode.getScreenCTM().multiply(m)
}
// let m = this.getScreenCTM()
// There are a few extra rules regarding rbox and the <svg> element
// Namely this is:
// BBox is calculated as normal for container elements
// Rbox is calculated with the width and height of the <svg>
// This could be also true for symbols so this is a:
// Todo: ...
return getSegments(this, false, true).transform(m).bbox()
}
getCTM () {
let m = this.matrixify()
let node = this
while ((node = node.parentNode)) {
if ([ 'svg', 'symbol', 'image', 'pattern', 'marker' ].indexOf(node.nodeName) > -1) break
m = m.multiply(node.matrixify())
if (node.nodeName === '#document') return this.getScreenCTM()
}
return node.generateViewBoxMatrix().multiply(m)
}
getInnerMatrix () {
let m = this.matrixify()
if ([ 'svg', 'symbol', 'image', 'pattern', 'marker' ].indexOf(this.nodeName) > -1) {
m = this.generateViewBoxMatrix().multiply(m)
}
return m
}
getScreenCTM () {
// ref: https://bugzilla.mozilla.org/show_bug.cgi?id=1344537
// We follow Chromes behavior and include the viewbox in the screenCTM
const m = this.getInnerMatrix()
// TODO: We have to loop until document, however html elements dont have getScreenCTM implemented
// they also dont have a transform attribute. Therefore we need a different way of figuring out their (css) transform
if (this.parentNode && this.parentNode instanceof SVGGraphicsElement) {
return this.parentNode.getScreenCTM().multiply(m)
}
return m
}
matrixify () {
const matrix = (this.getAttribute('transform') || '').trim()
// split transformations
.split(regex.transforms).slice(0, -1).map(function (str) {
// generate key => value pairs
const kv = str.trim().split('(')
return [ kv[0].trim(), kv[1].split(regex.delimiter).map(function (str) { return parseFloat(str.trim()) }) ]
})
// merge every transformation into one matrix
.reduce(function (matrix, transform) {
if (transform[0] === 'matrix') return matrix.multiply(arrayToMatrix(transform[1]))
return matrix[transform[0]].apply(matrix, transform[1])
}, new SVGMatrix())
return matrix
}
get transform () {
throw new Error('Not implemented')
}
}