UNPKG

svgdom

Version:

Straightforward DOM implementation for SVG, HTML and XML

131 lines (102 loc) 4.46 kB
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') } }