* create and use getTransformList
* update dependencies
This commit is contained in:
JFH
2023-03-31 01:57:06 +02:00
committed by GitHub
parent 8619a17333
commit 13cdaedc63
20 changed files with 5166 additions and 3512 deletions

View File

@@ -8,7 +8,7 @@ import {
snapToGrid, assignAttributes, getBBox, getRefElem, findDefs
} from './utilities.js'
import {
transformPoint, transformListToTransform, matrixMultiply, transformBox
transformPoint, transformListToTransform, matrixMultiply, transformBox, getTransformList
} from './math.js'
// this is how we map paths to our preferred relative segment types
@@ -98,7 +98,7 @@ export const remapElement = (selected, changes, m) => {
changes.y = Number.parseFloat(changes.y) + tNew.f
} else {
// we just absorb all matrices into the element and don't do any remapping
const chlist = selected.transform.baseVal
const chlist = getTransformList(selected)
const mt = svgCanvas.getSvgRoot().createSVGTransform()
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m))
chlist.clear()
@@ -115,7 +115,7 @@ export const remapElement = (selected, changes, m) => {
// Allow images to be inverted (give them matrix when flipped)
if (elName === 'image' && (m.a < 0 || m.d < 0)) {
// Convert to matrix
const chlist = selected.transform.baseVal
const chlist = getTransformList(selected)
const mt = svgCanvas.getSvgRoot().createSVGTransform()
mt.setMatrix(matrixMultiply(transformListToTransform(chlist).matrix, m))
chlist.clear()

View File

@@ -12,7 +12,7 @@ import {
convertAttrs
} from './units.js'
import {
transformPoint, hasMatrixTransform, getMatrix, snapToAngle
transformPoint, hasMatrixTransform, getMatrix, snapToAngle, getTransformList
} from './math.js'
import * as draw from './draw.js'
import * as pathModule from './path.js'
@@ -83,7 +83,7 @@ const getBsplinePoint = (t) => {
const updateTransformList = (svgRoot, element, dx, dy) => {
const xform = svgRoot.createSVGTransform()
xform.setTranslate(dx, dy)
const tlist = element.transform?.baseVal
const tlist = getTransformList(element)
if (tlist.numberOfItems) {
const firstItem = tlist.getItem(0)
if (firstItem.type === 2) { // SVG_TRANSFORM_TRANSLATE = 2
@@ -221,7 +221,7 @@ const mouseMoveEvent = (evt) => {
// we track the resize bounding box and translate/scale the selected element
// while the mouse is down, when mouse goes up, we use this to recalculate
// the shape's coordinates
tlist = selected.transform.baseVal
tlist = getTransformList(selected)
const hasMatrix = hasMatrixTransform(tlist)
box = hasMatrix ? svgCanvas.getInitBbox() : getBBox(selected)
let left = box.x
@@ -1026,7 +1026,7 @@ const mouseDownEvent = (evt) => {
svgCanvas.setStartTransform(mouseTarget.getAttribute('transform'))
const tlist = mouseTarget.transform.baseVal
const tlist = getTransformList(mouseTarget)
// consolidate transforms using standard SVG but keep the transformation used for the move/scale
if (tlist.numberOfItems > 1) {
const firstTransform = tlist.getItem(0)
@@ -1060,7 +1060,7 @@ const mouseDownEvent = (evt) => {
// a transform to use for its translate
for (const selectedElement of selectedElements) {
if (!selectedElement) { continue }
const slist = selectedElement.transform?.baseVal
const slist = getTransformList(selectedElement)
if (slist.numberOfItems) {
slist.insertItemBefore(svgRoot.createSVGTransform(), 0)
} else {

View File

@@ -7,17 +7,17 @@
*/
/**
* @typedef {PlainObject} module:math.AngleCoord45
* @property {Float} x - The angle-snapped x value
* @property {Float} y - The angle-snapped y value
* @property {Integer} a - The angle at which to snap
*/
* @typedef {PlainObject} module:math.AngleCoord45
* @property {Float} x - The angle-snapped x value
* @property {Float} y - The angle-snapped y value
* @property {Integer} a - The angle at which to snap
*/
/**
* @typedef {PlainObject} module:math.XYObject
* @property {Float} x
* @property {Float} y
*/
* @typedef {PlainObject} module:math.XYObject
* @property {Float} x
* @property {Float} y
*/
import { NS } from './namespaces.js'
@@ -35,20 +35,35 @@ const svg = document.createElementNS(NS.SVG, 'svg')
* @param {Float} y - Float representing the y coordinate
* @param {SVGMatrix} m - Matrix object to transform the point with
* @returns {module:math.XYObject} An x, y object representing the transformed point
*/
*/
export const transformPoint = function (x, y, m) {
return { x: m.a * x + m.c * y + m.e, y: m.b * x + m.d * y + m.f }
}
export const getTransformList = (elem) => {
if (elem.transform) {
return elem.transform?.baseVal
}
if (elem.gradientTransform) {
return elem.gradientTransform?.baseVal
}
if (elem.patternTransform) {
return elem.patternTransform?.baseVal
}
console.warn('no transform list found - check browser version', elem)
}
/**
* Helper function to check if the matrix performs no actual transform
* (i.e. exists for identity purposes).
* @function module:math.isIdentity
* @param {SVGMatrix} m - The matrix object to check
* @returns {boolean} Indicates whether or not the matrix is 1,0,0,1,0,0
*/
*/
export const isIdentity = function (m) {
return (m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0)
return (
m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0
)
}
/**
@@ -57,18 +72,30 @@ export const isIdentity = function (m) {
* @function module:math.matrixMultiply
* @param {...SVGMatrix} args - Matrix objects to multiply
* @returns {SVGMatrix} The matrix object resulting from the calculation
*/
*/
export const matrixMultiply = function (...args) {
const m = args.reduceRight((prev, m1) => {
return m1.multiply(prev)
})
if (Math.abs(m.a) < NEAR_ZERO) { m.a = 0 }
if (Math.abs(m.b) < NEAR_ZERO) { m.b = 0 }
if (Math.abs(m.c) < NEAR_ZERO) { m.c = 0 }
if (Math.abs(m.d) < NEAR_ZERO) { m.d = 0 }
if (Math.abs(m.e) < NEAR_ZERO) { m.e = 0 }
if (Math.abs(m.f) < NEAR_ZERO) { m.f = 0 }
if (Math.abs(m.a) < NEAR_ZERO) {
m.a = 0
}
if (Math.abs(m.b) < NEAR_ZERO) {
m.b = 0
}
if (Math.abs(m.c) < NEAR_ZERO) {
m.c = 0
}
if (Math.abs(m.d) < NEAR_ZERO) {
m.d = 0
}
if (Math.abs(m.e) < NEAR_ZERO) {
m.e = 0
}
if (Math.abs(m.f) < NEAR_ZERO) {
m.f = 0
}
return m
}
@@ -78,29 +105,33 @@ export const matrixMultiply = function (...args) {
* @function module:math.hasMatrixTransform
* @param {SVGTransformList} [tlist] - The transformlist to check
* @returns {boolean} Whether or not a matrix transform was found
*/
*/
export const hasMatrixTransform = function (tlist) {
if (!tlist) { return false }
if (!tlist) {
return false
}
let num = tlist.numberOfItems
while (num--) {
const xform = tlist.getItem(num)
if (xform.type === 1 && !isIdentity(xform.matrix)) { return true }
if (xform.type === 1 && !isIdentity(xform.matrix)) {
return true
}
}
return false
}
/**
* @typedef {PlainObject} module:math.TransformedBox An object with the following values
* @property {module:math.XYObject} tl - The top left coordinate
* @property {module:math.XYObject} tr - The top right coordinate
* @property {module:math.XYObject} bl - The bottom left coordinate
* @property {module:math.XYObject} br - The bottom right coordinate
* @property {PlainObject} aabox - Object with the following values:
* @property {Float} aabox.x - Float with the axis-aligned x coordinate
* @property {Float} aabox.y - Float with the axis-aligned y coordinate
* @property {Float} aabox.width - Float with the axis-aligned width coordinate
* @property {Float} aabox.height - Float with the axis-aligned height coordinate
*/
* @typedef {PlainObject} module:math.TransformedBox An object with the following values
* @property {module:math.XYObject} tl - The top left coordinate
* @property {module:math.XYObject} tr - The top right coordinate
* @property {module:math.XYObject} bl - The bottom left coordinate
* @property {module:math.XYObject} br - The bottom right coordinate
* @property {PlainObject} aabox - Object with the following values:
* @property {Float} aabox.x - Float with the axis-aligned x coordinate
* @property {Float} aabox.y - Float with the axis-aligned y coordinate
* @property {Float} aabox.width - Float with the axis-aligned width coordinate
* @property {Float} aabox.height - Float with the axis-aligned height coordinate
*/
/**
* Transforms a rectangle based on the given matrix.
@@ -111,12 +142,12 @@ export const hasMatrixTransform = function (tlist) {
* @param {Float} h - Float with the box height
* @param {SVGMatrix} m - Matrix object to transform the box by
* @returns {module:math.TransformedBox}
*/
*/
export const transformBox = function (l, t, w, h, m) {
const tl = transformPoint(l, t, m)
const tr = transformPoint((l + w), t, m)
const bl = transformPoint(l, (t + h), m)
const br = transformPoint((l + w), (t + h), m)
const tr = transformPoint(l + w, t, m)
const bl = transformPoint(l, t + h, m)
const br = transformPoint(l + w, t + h, m)
const minx = Math.min(tl.x, tr.x, bl.x, br.x)
const maxx = Math.max(tl.x, tr.x, bl.x, br.x)
@@ -131,8 +162,8 @@ export const transformBox = function (l, t, w, h, m) {
aabox: {
x: minx,
y: miny,
width: (maxx - minx),
height: (maxy - miny)
width: maxx - minx,
height: maxy - miny
}
}
}
@@ -148,23 +179,28 @@ export const transformBox = function (l, t, w, h, m) {
* @param {Integer} [max] - Optional integer indicating end transform position;
* defaults to one less than the tlist's `numberOfItems`
* @returns {SVGTransform} A single matrix transform object
*/
*/
export const transformListToTransform = function (tlist, min, max) {
if (!tlist) {
// Or should tlist = null have been prevented before this?
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix())
}
min = min || 0
max = max || (tlist.numberOfItems - 1)
max = max || tlist.numberOfItems - 1
min = Number.parseInt(min)
max = Number.parseInt(max)
if (min > max) { const temp = max; max = min; min = temp }
if (min > max) {
const temp = max
max = min
min = temp
}
let m = svg.createSVGMatrix()
for (let i = min; i <= max; ++i) {
// if our indices are out of range, just use a harmless identity matrix
const mtom = (i >= 0 && i < tlist.numberOfItems
? tlist.getItem(i).matrix
: svg.createSVGMatrix())
const mtom =
i >= 0 && i < tlist.numberOfItems
? tlist.getItem(i).matrix
: svg.createSVGMatrix()
m = matrixMultiply(m, mtom)
}
return svg.createSVGTransformFromMatrix(m)
@@ -175,9 +211,9 @@ export const transformListToTransform = function (tlist, min, max) {
* @function module:math.getMatrix
* @param {Element} elem - The DOM element to check
* @returns {SVGMatrix} The matrix object associated with the element's transformlist
*/
*/
export const getMatrix = (elem) => {
const tlist = elem.transform.baseVal
const tlist = getTransformList(elem)
return transformListToTransform(tlist).matrix
}
@@ -190,7 +226,7 @@ export const getMatrix = (elem) => {
* @param {Integer} x2 - Second coordinate's x value
* @param {Integer} y2 - Second coordinate's y value
* @returns {module:math.AngleCoord45}
*/
*/
export const snapToAngle = (x1, y1, x2, y2) => {
const snap = Math.PI / 4 // 45 degrees
const dx = x2 - x1
@@ -214,8 +250,10 @@ export const snapToAngle = (x1, y1, x2, y2) => {
* @returns {boolean} True if rectangles intersect
*/
export const rectsIntersect = (r1, r2) => {
return r2.x < (r1.x + r1.width) &&
(r2.x + r2.width) > r1.x &&
r2.y < (r1.y + r1.height) &&
(r2.y + r2.height) > r1.y
return (
r2.x < r1.x + r1.width &&
r2.x + r2.width > r1.x &&
r2.y < r1.y + r1.height &&
r2.y + r2.height > r1.y
)
}

View File

@@ -11,7 +11,7 @@ import { shortFloat } from './units.js'
import { ChangeElementCommand, BatchCommand } from './history.js'
import {
transformPoint, snapToAngle, rectsIntersect,
transformListToTransform
transformListToTransform, getTransformList
} from './math.js'
import {
assignAttributes, getElement, getRotationAngle, snapToGrid,
@@ -876,7 +876,7 @@ export const pathActionsMethod = (function () {
*/
resetOrientation (pth) {
if (pth?.nodeName !== 'path') { return false }
const tlist = pth.transform.baseVal
const tlist = getTransformList(pth)
const m = transformListToTransform(tlist).matrix
tlist.clear()
pth.removeAttribute('transform')

View File

@@ -7,7 +7,7 @@
*/
import { shortFloat } from './units.js'
import { transformPoint } from './math.js'
import { transformPoint, getTransformList } from './math.js'
import {
getRotationAngle, getBBox,
getRefElem, findDefs,
@@ -511,7 +511,7 @@ export const recalcRotatedPath = () => {
// now we must set the new transform to be rotated around the new center
const Rnc = svgCanvas.getSvgRoot().createSVGTransform()
const tlist = currentPath.transform.baseVal
const tlist = getTransformList(currentPath)
Rnc.setRotate((angle * 180.0 / Math.PI), newcx, newcy)
tlist.replaceItem(Rnc, 0)
}

View File

@@ -11,7 +11,7 @@ import { BatchCommand, ChangeElementCommand } from './history.js'
import { remapElement } from './coords.js'
import {
isIdentity, matrixMultiply, transformPoint, transformListToTransform,
hasMatrixTransform
hasMatrixTransform, getTransformList
} from './math.js'
import {
mergeDeep
@@ -55,7 +55,7 @@ export const init = (canvas) => {
*/
export const updateClipPath = (attr, tx, ty) => {
const path = getRefElem(attr).firstChild
const cpXform = path.transform.baseVal
const cpXform = getTransformList(path)
const newxlate = svgCanvas.getSvgRoot().createSVGTransform()
newxlate.setTranslate(tx, ty)
@@ -75,7 +75,7 @@ export const recalculateDimensions = (selected) => {
if (!selected) return null
const svgroot = svgCanvas.getSvgRoot()
const dataStorage = svgCanvas.getDataStorage()
const tlist = selected.transform?.baseVal
const tlist = getTransformList(selected)
// remove any unnecessary transforms
if (tlist?.numberOfItems > 0) {
let k = tlist.numberOfItems
@@ -113,7 +113,6 @@ export const recalculateDimensions = (selected) => {
selected.setAttribute('transform', '')
// However, this still next line currently doesn't work at all in Chrome
selected.removeAttribute('transform')
// selected.transform.baseVal.clear(); // Didn't help for Chrome bug
return null
}
@@ -296,7 +295,7 @@ export const recalculateDimensions = (selected) => {
tx = 0
ty = 0
if (child.nodeType === 1) {
const childTlist = child.transform.baseVal
const childTlist = getTransformList(child)
// some children might not have a transform (<metadata>, <defs>, etc)
if (!childTlist) { continue }
@@ -413,7 +412,7 @@ export const recalculateDimensions = (selected) => {
oldStartTransform = svgCanvas.getStartTransform()
svgCanvas.setStartTransform(child.getAttribute('transform'))
const childTlist = child.transform?.baseVal
const childTlist = getTransformList(child)
// some children might not have a transform (<metadata>, <defs>, etc)
if (childTlist) {
const newxlate = svgroot.createSVGTransform()
@@ -460,7 +459,7 @@ export const recalculateDimensions = (selected) => {
if (child.nodeType === 1) {
oldStartTransform = svgCanvas.getStartTransform()
svgCanvas.setStartTransform(child.getAttribute('transform'))
const childTlist = child.transform?.baseVal
const childTlist = getTransformList(child)
if (!childTlist) { continue }
@@ -544,7 +543,7 @@ export const recalculateDimensions = (selected) => {
if (child.nodeType === 1) {
oldStartTransform = svgCanvas.getStartTransform()
svgCanvas.setStartTransform(child.getAttribute('transform'))
const childTlist = child.transform?.baseVal
const childTlist = getTransformList(child)
const newxlate = svgroot.createSVGTransform()
newxlate.setTranslate(tx, ty)
if (childTlist.numberOfItems) {
@@ -629,7 +628,7 @@ export const recalculateDimensions = (selected) => {
if (attrVal === 'userSpaceOnUse') {
// Update the userSpaceOnUse element
m = transformListToTransform(tlist).matrix
const gtlist = paint.transform.baseVal
const gtlist = getTransformList(paint)
const gmatrix = transformListToTransform(gtlist).matrix
m = matrixMultiply(m, gmatrix)
const mStr = 'matrix(' + [m.a, m.b, m.c, m.d, m.e, m.f].join(',') + ')'

View File

@@ -8,7 +8,7 @@
import { isWebkit } from '../common/browser.js'
import { getRotationAngle, getBBox, getStrokedBBox } from './utilities.js'
import { transformListToTransform, transformBox, transformPoint, matrixMultiply } from './math.js'
import { transformListToTransform, transformBox, transformPoint, matrixMultiply, getTransformList } from './math.js'
import { NS } from './namespaces'
let svgCanvas
@@ -130,14 +130,14 @@ export class Selector {
while (currentElt.parentNode) {
if (currentElt.parentNode && currentElt.parentNode.tagName === 'g' && currentElt.parentNode.transform) {
if (currentElt.parentNode.transform.baseVal.numberOfItems) {
parentTransformationMatrix = matrixMultiply(transformListToTransform(selected.parentNode.transform.baseVal).matrix, parentTransformationMatrix)
parentTransformationMatrix = matrixMultiply(transformListToTransform(getTransformList(selected.parentNode)).matrix, parentTransformationMatrix)
}
}
currentElt = currentElt.parentNode
}
// loop and transform our bounding box until we reach our first rotation
const tlist = selected.transform.baseVal
const tlist = getTransformList(selected)
// combines the parent transformation with that of the selected element if necessary
const m = parentTransformationMatrix ? matrixMultiply(parentTransformationMatrix, transformListToTransform(tlist).matrix) : transformListToTransform(tlist).matrix

View File

@@ -26,7 +26,8 @@ import {
import {
transformPoint,
matrixMultiply,
transformListToTransform
transformListToTransform,
getTransformList
} from './math.js'
import { recalculateDimensions } from './recalculate.js'
import { isGecko } from '../common/browser.js'
@@ -207,7 +208,7 @@ const moveSelectedElements = (dx, dy, undoable = true) => {
selectedElements.forEach((selected, i) => {
if (selected) {
const xform = svgCanvas.getSvgRoot().createSVGTransform()
const tlist = selected.transform?.baseVal
const tlist = getTransformList(selected)
// dx and dy could be arrays
if (Array.isArray(dx)) {
@@ -724,7 +725,7 @@ const pushGroupProperty = (g, undoable) => {
const len = children.length
const xform = g.getAttribute('transform')
const glist = g.transform.baseVal
const glist = getTransformList(g)
const m = transformListToTransform(glist).matrix
const batchCmd = new BatchCommand('Push group properties')
@@ -810,7 +811,7 @@ const pushGroupProperty = (g, undoable) => {
}
}
let chtlist = elem.transform?.baseVal
let chtlist = getTransformList(elem)
// Don't process gradient transforms
if (elem.tagName.includes('Gradient')) {
@@ -963,7 +964,7 @@ const convertToGroup = elem => {
}
dataStorage.remove(elem, 'gsvg')
const tlist = elem.transform.baseVal
const tlist = getTransformList(elem)
const xform = svgCanvas.getSvgRoot().createSVGTransform()
xform.setTranslate(pt.x, pt.y)
tlist.appendItem(xform)

View File

@@ -13,7 +13,8 @@ import {
import {
transformPoint,
transformListToTransform,
rectsIntersect
rectsIntersect,
getTransformList
} from './math.js'
import * as hstry from './history.js'
import { getClosest } from '../common/util.js'
@@ -392,7 +393,7 @@ const setRotationAngle = (val, preventUndo) => {
const bbox = getBBox(elem)
const cx = bbox.x + bbox.width / 2
const cy = bbox.y + bbox.height / 2
const tlist = elem.transform.baseVal
const tlist = getTransformList(elem)
// only remove the real rotational transform if present (i.e. at index=0)
if (tlist.numberOfItems > 0) {

View File

@@ -23,7 +23,7 @@ import {
getBBox as utilsGetBBox,
hashCode
} from './utilities.js'
import { transformPoint, transformListToTransform } from './math.js'
import { transformPoint, transformListToTransform, getTransformList } from './math.js'
import { convertUnit, shortFloat, convertToNum } from './units.js'
import { isGecko, isChrome, isWebkit } from '../common/browser.js'
import * as pathModule from './path.js'
@@ -1269,7 +1269,7 @@ const convertGradientsMethod = (elem) => {
}
// If has transform, convert
const tlist = grad.gradientTransform.baseVal
const tlist = getTransformList(grad)
if (tlist?.numberOfItems > 0) {
const m = transformListToTransform(tlist).matrix
const pt1 = transformPoint(gCoords.x1, gCoords.y1, m)

View File

@@ -13,7 +13,7 @@ import {
isGecko
} from '../common/browser.js'
import {
transformPoint, transformListToTransform
transformPoint, transformListToTransform, getTransformList
} from './math.js'
const {
@@ -228,7 +228,7 @@ export const changeSelectedAttributeNoUndoMethod = (attr, newValue, elems) => {
// we need to update the rotational transform attribute
const angle = getRotationAngle(elem)
if (angle !== 0 && attr !== 'transform') {
const tlist = elem.transform?.baseVal
const tlist = getTransformList(elem)
let n = tlist.numberOfItems
while (n--) {
const xform = tlist.getItem(n)

View File

@@ -9,7 +9,7 @@
import { NS } from './namespaces.js'
import { setUnitAttr, getTypeMap } from './units.js'
import {
hasMatrixTransform, transformListToTransform, transformBox
hasMatrixTransform, transformListToTransform, transformBox, getTransformList
} from './math.js'
import { getClosest, mergeDeep } from '../common/util.js'
@@ -770,7 +770,7 @@ export const convertToPath = (elem, attrs, svgCanvas) => {
// Reorient if it has a matrix
if (eltrans) {
const tlist = path.transform.baseVal
const tlist = getTransformList(path)
if (hasMatrixTransform(tlist)) {
svgCanvas.pathActions.resetOrientation(path)
}
@@ -841,7 +841,7 @@ export const getBBoxWithTransform = function (elem, addSVGElementsFromJson, path
return null
}
const tlist = elem.transform.baseVal
const tlist = getTransformList(elem)
const angle = getRotationAngleFromTransformList(tlist)
const hasMatrixXForm = hasMatrixTransform(tlist)
@@ -1029,7 +1029,7 @@ export const getRotationAngleFromTransformList = (tlist, toRad) => {
export let getRotationAngle = (elem, toRad) => {
const selected = elem || svgCanvas.getSelectedElements()[0]
// find the rotation transform (if any) and set it
const tlist = selected.transform?.baseVal
const tlist = getTransformList(selected)
return getRotationAngleFromTransformList(tlist, toRad)
}

View File

@@ -10,7 +10,8 @@ import commonjs from '@rollup/plugin-commonjs'
import filesize from 'rollup-plugin-filesize'
// remove existing distribution
rimraf('./dist', () => console.info('recreating dist'))
await rimraf('./dist')
console.info('recreating dist')
// config for svgedit core module
const config = [{