Fix text scale #1008 (#1010)

* update dependencies

* fix #1008 by adjusting font-size

(allowing to keep the text keep his proportions to the font)

* refactor math.js
This commit is contained in:
JFH
2024-12-27 21:37:05 +01:00
committed by GitHub
parent f0549e4629
commit 403237c182
18 changed files with 810 additions and 521 deletions

View File

@@ -3,117 +3,111 @@
* @module math
* @license MIT
*
* @copyright 2010 Alexis Deveria, 2010 Jeff Schiller
* ©2010 Alexis Deveria, ©2010 Jeff Schiller
*/
/**
* @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 {Object} AngleCoord45
* @property {number} x - The angle-snapped x value
* @property {number} y - The angle-snapped y value
* @property {number} a - The angle (in radians) at which to snap
*/
/**
* @typedef {PlainObject} module:math.XYObject
* @property {Float} x
* @property {Float} y
* @typedef {Object} XYObject
* @property {number} x
* @property {number} y
*/
import { NS } from './namespaces.js'
// Constants
const NEAR_ZERO = 1e-14
const NEAR_ZERO = 1e-10
// Throw away SVGSVGElement used for creating matrices/transforms.
// Create a throwaway SVG element for matrix operations
const svg = document.createElementNS(NS.SVG, 'svg')
/**
* A (hopefully) quicker function to transform a point by a matrix
* (this function avoids any DOM calls and just does the math).
* @function module:math.transformPoint
* @param {Float} x - Float representing the x coordinate
* @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
* Transforms a point by a given matrix without DOM calls.
* @function transformPoint
* @param {number} x - The x coordinate
* @param {number} y - The y coordinate
* @param {SVGMatrix} m - The transformation matrix
* @returns {XYObject} 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 transformPoint = (x, y, m) => ({
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
/**
* Gets the transform list (baseVal) from an element if it exists.
* @function getTransformList
* @param {Element} elem - An SVG element or element with a transform list
* @returns {SVGTransformList|undefined} The transform list, if any
*/
export const getTransformList = elem => {
if (elem.transform?.baseVal) {
return elem.transform.baseVal
}
if (elem.gradientTransform) {
return elem.gradientTransform?.baseVal
if (elem.gradientTransform?.baseVal) {
return elem.gradientTransform.baseVal
}
if (elem.patternTransform) {
return elem.patternTransform?.baseVal
if (elem.patternTransform?.baseVal) {
return elem.patternTransform.baseVal
}
console.warn('no transform list found - check browser version', elem)
console.warn('No transform list found. Check browser compatibility.', 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
* Checks if a matrix is the identity matrix.
* @function isIdentity
* @param {SVGMatrix} m - The matrix to check
* @returns {boolean} True if it's an identity matrix (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
)
}
export const isIdentity = m =>
m.a === 1 && m.b === 0 && m.c === 0 && m.d === 1 && m.e === 0 && m.f === 0
/**
* This function tries to return a `SVGMatrix` that is the multiplication `m1 * m2`.
* We also round to zero when it's near zero.
* @function module:math.matrixMultiply
* @param {...SVGMatrix} args - Matrix objects to multiply
* @returns {SVGMatrix} The matrix object resulting from the calculation
* Multiplies multiple matrices together (m1 * m2 * ...).
* Near-zero values are rounded to zero.
* @function matrixMultiply
* @param {...SVGMatrix} args - The matrices to multiply
* @returns {SVGMatrix} The resulting matrix
*/
export const matrixMultiply = function (...args) {
const m = args.reduceRight((prev, m1) => {
return m1.multiply(prev)
})
export const matrixMultiply = (...args) => {
// If no matrices are given, return an identity matrix
if (args.length === 0) {
return svg.createSVGMatrix()
}
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
}
const m = args.reduceRight((prev, curr) => curr.multiply(prev))
// Round near-zero values to zero
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
}
/**
* See if the given transformlist includes a non-indentity matrix transform.
* @function module:math.hasMatrixTransform
* @param {SVGTransformList} [tlist] - The transformlist to check
* @returns {boolean} Whether or not a matrix transform was found
* Checks if a transform list includes a non-identity matrix transform.
* @function hasMatrixTransform
* @param {SVGTransformList} [tlist] - The transform list to check
* @returns {boolean} True if a matrix transform is found
*/
export const hasMatrixTransform = function (tlist) {
if (!tlist) {
return false
}
let num = tlist.numberOfItems
while (num--) {
const xform = tlist.getItem(num)
if (xform.type === 1 && !isIdentity(xform.matrix)) {
export const hasMatrixTransform = tlist => {
if (!tlist) return false
for (let i = 0; i < tlist.numberOfItems; i++) {
const xform = tlist.getItem(i)
if (
xform.type === SVGTransform.SVG_TRANSFORM_MATRIX &&
!isIdentity(xform.matrix)
) {
return true
}
}
@@ -121,29 +115,29 @@ export const hasMatrixTransform = function (tlist) {
}
/**
* @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 {Object} TransformedBox
* @property {XYObject} tl - Top-left coordinate
* @property {XYObject} tr - Top-right coordinate
* @property {XYObject} bl - Bottom-left coordinate
* @property {XYObject} br - Bottom-right coordinate
* @property {Object} aabox
* @property {number} aabox.x - Axis-aligned x
* @property {number} aabox.y - Axis-aligned y
* @property {number} aabox.width - Axis-aligned width
* @property {number} aabox.height - Axis-aligned height
*/
/**
* Transforms a rectangle based on the given matrix.
* @function module:math.transformBox
* @param {Float} l - Float with the box's left coordinate
* @param {Float} t - Float with the box's top coordinate
* @param {Float} w - Float with the box width
* @param {Float} h - Float with the box height
* @param {SVGMatrix} m - Matrix object to transform the box by
* @returns {module:math.TransformedBox}
* Transforms a rectangular box using a given matrix.
* @function transformBox
* @param {number} l - Left coordinate
* @param {number} t - Top coordinate
* @param {number} w - Width
* @param {number} h - Height
* @param {SVGMatrix} m - Transformation matrix
* @returns {TransformedBox} The transformed box information
*/
export const transformBox = function (l, t, w, h, m) {
export const transformBox = (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)
@@ -169,91 +163,81 @@ export const transformBox = function (l, t, w, h, m) {
}
/**
* This returns a single matrix Transform for a given Transform List
* (this is the equivalent of `SVGTransformList.consolidate()` but unlike
* that method, this one does not modify the actual `SVGTransformList`).
* This function is very liberal with its `min`, `max` arguments.
* @function module:math.transformListToTransform
* @param {SVGTransformList} tlist - The transformlist object
* @param {Integer} [min=0] - Optional integer indicating start transform position
* @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
* Consolidates a transform list into a single matrix transform without modifying the original list.
* @function transformListToTransform
* @param {SVGTransformList} tlist - The transform list
* @param {number} [min=0] - Optional start index
* @param {number} [max] - Optional end index, defaults to tlist length-1
* @returns {SVGTransform} A single transform from the combined matrices
*/
export const transformListToTransform = function (tlist, min, max) {
export const transformListToTransform = (tlist, min = 0, max = null) => {
if (!tlist) {
// Or should tlist = null have been prevented before this?
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix())
}
min = min || 0
max = max || tlist.numberOfItems - 1
min = Number.parseInt(min)
max = Number.parseInt(max)
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 =
const start = Number.parseInt(min, 10)
const end = Number.parseInt(max ?? tlist.numberOfItems - 1, 10)
const low = Math.min(start, end)
const high = Math.max(start, end)
let combinedMatrix = svg.createSVGMatrix()
for (let i = low; i <= high; i++) {
// If out of range, use identity
const currentMatrix =
i >= 0 && i < tlist.numberOfItems
? tlist.getItem(i).matrix
: svg.createSVGMatrix()
m = matrixMultiply(m, mtom)
combinedMatrix = matrixMultiply(combinedMatrix, currentMatrix)
}
return svg.createSVGTransformFromMatrix(m)
return svg.createSVGTransformFromMatrix(combinedMatrix)
}
/**
* Get the matrix object for a given element.
* @function module:math.getMatrix
* @param {Element} elem - The DOM element to check
* @returns {SVGMatrix} The matrix object associated with the element's transformlist
* Gets the matrix of a given element's transform list.
* @function getMatrix
* @param {Element} elem - The element to check
* @returns {SVGMatrix} The transformation matrix
*/
export const getMatrix = (elem) => {
export const getMatrix = elem => {
const tlist = getTransformList(elem)
return transformListToTransform(tlist).matrix
}
/**
* Returns a 45 degree angle coordinate associated with the two given
* coordinates.
* @function module:math.snapToAngle
* @param {Integer} x1 - First coordinate's x value
* @param {Integer} y1 - First coordinate's y value
* @param {Integer} x2 - Second coordinate's x value
* @param {Integer} y2 - Second coordinate's y value
* @returns {module:math.AngleCoord45}
* Returns a coordinate snapped to the nearest 45-degree angle.
* @function snapToAngle
* @param {number} x1 - First point's x
* @param {number} y1 - First point's y
* @param {number} x2 - Second point's x
* @param {number} y2 - Second point's y
* @returns {AngleCoord45} The angle-snapped coordinates and angle
*/
export const snapToAngle = (x1, y1, x2, y2) => {
const snap = Math.PI / 4 // 45 degrees
const dx = x2 - x1
const dy = y2 - y1
const angle = Math.atan2(dy, dx)
const dist = Math.sqrt(dx * dx + dy * dy)
const snapangle = Math.round(angle / snap) * snap
const dist = Math.hypot(dx, dy)
const snapAngle = Math.round(angle / snap) * snap
return {
x: x1 + dist * Math.cos(snapangle),
y: y1 + dist * Math.sin(snapangle),
a: snapangle
x: x1 + dist * Math.cos(snapAngle),
y: y1 + dist * Math.sin(snapAngle),
a: snapAngle
}
}
/**
* Check if two rectangles (BBoxes objects) intersect each other.
* @function module:math.rectsIntersect
* @param {SVGRect} r1 - The first BBox-like object
* @param {SVGRect} r2 - The second BBox-like object
* @returns {boolean} True if rectangles intersect
* Checks if two rectangles intersect.
* Both r1 and r2 are expected to have {x, y, width, height}.
* @function rectsIntersect
* @param {{x:number,y:number,width:number,height:number}} r1 - First rectangle
* @param {{x:number,y:number,width:number,height:number}} r2 - Second rectangle
* @returns {boolean} True if the 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
)
}
export const rectsIntersect = (r1, r2) =>
r2.x < r1.x + r1.width &&
r2.x + r2.width > r1.x &&
r2.y < r1.y + r1.height &&
r2.y + r2.height > r1.y