Document application modes (development/debug/production) Add global file drop handler, order column normalization, SPA hash fix Serve CDN assets via /_vendor/ URLs instead of merging into bundles Add production minification with license preservation Improve JSON formatting for debugging and production optimization Add CDN asset caching with CSS URL inlining for production builds Add three-mode system (development, debug, production) Update Manifest CLAUDE.md to reflect helper class architecture Refactor Manifest.php into helper classes for better organization Pre-manifest-refactor checkpoint: Add app_mode documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
377 lines
11 KiB
JavaScript
377 lines
11 KiB
JavaScript
import { path2js } from './_path.js';
|
|
import {
|
|
transform2js,
|
|
transformArc,
|
|
transformsMultiply,
|
|
} from './_transforms.js';
|
|
import { attrsGroupsDefaults, referencesProps } from './_collections.js';
|
|
import { collectStylesheet, computeStyle } from '../lib/style.js';
|
|
|
|
import { includesUrlReference, removeLeadingZero } from '../lib/svgo/tools.js';
|
|
|
|
const regNumericValues = /[-+]?(\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g;
|
|
|
|
/**
|
|
* Apply transformation(s) to the Path data.
|
|
*
|
|
* @type {import('../lib/types.js').Plugin<{
|
|
* transformPrecision: number,
|
|
* applyTransformsStroked: boolean,
|
|
* }>}
|
|
*/
|
|
export const applyTransforms = (root, params) => {
|
|
const stylesheet = collectStylesheet(root);
|
|
return {
|
|
element: {
|
|
enter: (node) => {
|
|
if (node.attributes.d == null) {
|
|
return;
|
|
}
|
|
|
|
// stroke and stroke-width can be redefined with <use>
|
|
if (node.attributes.id != null) {
|
|
return;
|
|
}
|
|
|
|
// if there are no 'stroke' attr and references to other objects such as
|
|
// gradients or clip-path which are also subjects to transform.
|
|
if (
|
|
node.attributes.transform == null ||
|
|
node.attributes.transform === '' ||
|
|
// styles are not considered when applying transform
|
|
// can be fixed properly with new style engine
|
|
node.attributes.style != null ||
|
|
Object.entries(node.attributes).some(
|
|
([name, value]) =>
|
|
referencesProps.has(name) && includesUrlReference(value),
|
|
)
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const computedStyle = computeStyle(stylesheet, node);
|
|
const transformStyle = computedStyle.transform;
|
|
|
|
// Transform overridden in <style> tag which is not considered
|
|
if (
|
|
transformStyle.type === 'static' &&
|
|
transformStyle.value !== node.attributes.transform
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const matrix = transformsMultiply(
|
|
transform2js(node.attributes.transform),
|
|
);
|
|
|
|
const stroke =
|
|
computedStyle.stroke?.type === 'static'
|
|
? computedStyle.stroke.value
|
|
: null;
|
|
|
|
const strokeWidth =
|
|
computedStyle['stroke-width']?.type === 'static'
|
|
? computedStyle['stroke-width'].value
|
|
: null;
|
|
const transformPrecision = params.transformPrecision;
|
|
|
|
if (
|
|
computedStyle.stroke?.type === 'dynamic' ||
|
|
computedStyle['stroke-width']?.type === 'dynamic'
|
|
) {
|
|
return;
|
|
}
|
|
|
|
const scale = Number(
|
|
Math.hypot(matrix.data[0], matrix.data[1]).toFixed(
|
|
transformPrecision,
|
|
),
|
|
);
|
|
|
|
if (stroke && stroke != 'none') {
|
|
if (!params.applyTransformsStroked) {
|
|
return;
|
|
}
|
|
|
|
// stroke cannot be transformed with different vertical and horizontal scale or skew
|
|
if (
|
|
(matrix.data[0] !== matrix.data[3] ||
|
|
matrix.data[1] !== -matrix.data[2]) &&
|
|
(matrix.data[0] !== -matrix.data[3] ||
|
|
matrix.data[1] !== matrix.data[2])
|
|
) {
|
|
return;
|
|
}
|
|
|
|
// apply transform to stroke-width, stroke-dashoffset and stroke-dasharray
|
|
if (scale !== 1) {
|
|
if (node.attributes['vector-effect'] !== 'non-scaling-stroke') {
|
|
node.attributes['stroke-width'] = (
|
|
strokeWidth || attrsGroupsDefaults.presentation['stroke-width']
|
|
)
|
|
.trim()
|
|
.replace(regNumericValues, (num) =>
|
|
removeLeadingZero(Number(num) * scale),
|
|
);
|
|
|
|
if (node.attributes['stroke-dashoffset'] != null) {
|
|
node.attributes['stroke-dashoffset'] = node.attributes[
|
|
'stroke-dashoffset'
|
|
]
|
|
.trim()
|
|
.replace(regNumericValues, (num) =>
|
|
removeLeadingZero(Number(num) * scale),
|
|
);
|
|
}
|
|
|
|
if (node.attributes['stroke-dasharray'] != null) {
|
|
node.attributes['stroke-dasharray'] = node.attributes[
|
|
'stroke-dasharray'
|
|
]
|
|
.trim()
|
|
.replace(regNumericValues, (num) =>
|
|
removeLeadingZero(Number(num) * scale),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const pathData = path2js(node);
|
|
applyMatrixToPathData(pathData, matrix.data);
|
|
|
|
// remove transform attr
|
|
delete node.attributes.transform;
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
/**
|
|
* @param {ReadonlyArray<number>} matrix
|
|
* @param {number} x
|
|
* @param {number} y
|
|
* @returns {[number, number]}
|
|
*/
|
|
const transformAbsolutePoint = (matrix, x, y) => {
|
|
const newX = matrix[0] * x + matrix[2] * y + matrix[4];
|
|
const newY = matrix[1] * x + matrix[3] * y + matrix[5];
|
|
return [newX, newY];
|
|
};
|
|
|
|
/**
|
|
* @param {ReadonlyArray<number>} matrix
|
|
* @param {number} x
|
|
* @param {number} y
|
|
* @returns {[number, number]}
|
|
*/
|
|
const transformRelativePoint = (matrix, x, y) => {
|
|
const newX = matrix[0] * x + matrix[2] * y;
|
|
const newY = matrix[1] * x + matrix[3] * y;
|
|
return [newX, newY];
|
|
};
|
|
|
|
/**
|
|
* @param {ReadonlyArray<import('../lib/types.js').PathDataItem>} pathData
|
|
* @param {ReadonlyArray<number>} matrix
|
|
*/
|
|
const applyMatrixToPathData = (pathData, matrix) => {
|
|
/** @type {[number, number]} */
|
|
const start = [0, 0];
|
|
/** @type {[number, number]} */
|
|
const cursor = [0, 0];
|
|
|
|
for (const pathItem of pathData) {
|
|
let { command, args } = pathItem;
|
|
|
|
// moveto (x y)
|
|
if (command === 'M') {
|
|
cursor[0] = args[0];
|
|
cursor[1] = args[1];
|
|
start[0] = cursor[0];
|
|
start[1] = cursor[1];
|
|
const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
|
args[0] = x;
|
|
args[1] = y;
|
|
}
|
|
if (command === 'm') {
|
|
cursor[0] += args[0];
|
|
cursor[1] += args[1];
|
|
start[0] = cursor[0];
|
|
start[1] = cursor[1];
|
|
const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
|
args[0] = x;
|
|
args[1] = y;
|
|
}
|
|
|
|
// horizontal lineto (x)
|
|
// convert to lineto to handle two-dimensional transforms
|
|
if (command === 'H') {
|
|
command = 'L';
|
|
args = [args[0], cursor[1]];
|
|
}
|
|
if (command === 'h') {
|
|
command = 'l';
|
|
args = [args[0], 0];
|
|
}
|
|
|
|
// vertical lineto (y)
|
|
// convert to lineto to handle two-dimensional transforms
|
|
if (command === 'V') {
|
|
command = 'L';
|
|
args = [cursor[0], args[0]];
|
|
}
|
|
if (command === 'v') {
|
|
command = 'l';
|
|
args = [0, args[0]];
|
|
}
|
|
|
|
// lineto (x y)
|
|
if (command === 'L') {
|
|
cursor[0] = args[0];
|
|
cursor[1] = args[1];
|
|
const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
|
args[0] = x;
|
|
args[1] = y;
|
|
}
|
|
if (command === 'l') {
|
|
cursor[0] += args[0];
|
|
cursor[1] += args[1];
|
|
const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
|
args[0] = x;
|
|
args[1] = y;
|
|
}
|
|
|
|
// curveto (x1 y1 x2 y2 x y)
|
|
if (command === 'C') {
|
|
cursor[0] = args[4];
|
|
cursor[1] = args[5];
|
|
const [x1, y1] = transformAbsolutePoint(matrix, args[0], args[1]);
|
|
const [x2, y2] = transformAbsolutePoint(matrix, args[2], args[3]);
|
|
const [x, y] = transformAbsolutePoint(matrix, args[4], args[5]);
|
|
args[0] = x1;
|
|
args[1] = y1;
|
|
args[2] = x2;
|
|
args[3] = y2;
|
|
args[4] = x;
|
|
args[5] = y;
|
|
}
|
|
if (command === 'c') {
|
|
cursor[0] += args[4];
|
|
cursor[1] += args[5];
|
|
const [x1, y1] = transformRelativePoint(matrix, args[0], args[1]);
|
|
const [x2, y2] = transformRelativePoint(matrix, args[2], args[3]);
|
|
const [x, y] = transformRelativePoint(matrix, args[4], args[5]);
|
|
args[0] = x1;
|
|
args[1] = y1;
|
|
args[2] = x2;
|
|
args[3] = y2;
|
|
args[4] = x;
|
|
args[5] = y;
|
|
}
|
|
|
|
// smooth curveto (x2 y2 x y)
|
|
if (command === 'S') {
|
|
cursor[0] = args[2];
|
|
cursor[1] = args[3];
|
|
const [x2, y2] = transformAbsolutePoint(matrix, args[0], args[1]);
|
|
const [x, y] = transformAbsolutePoint(matrix, args[2], args[3]);
|
|
args[0] = x2;
|
|
args[1] = y2;
|
|
args[2] = x;
|
|
args[3] = y;
|
|
}
|
|
if (command === 's') {
|
|
cursor[0] += args[2];
|
|
cursor[1] += args[3];
|
|
const [x2, y2] = transformRelativePoint(matrix, args[0], args[1]);
|
|
const [x, y] = transformRelativePoint(matrix, args[2], args[3]);
|
|
args[0] = x2;
|
|
args[1] = y2;
|
|
args[2] = x;
|
|
args[3] = y;
|
|
}
|
|
|
|
// quadratic Bézier curveto (x1 y1 x y)
|
|
if (command === 'Q') {
|
|
cursor[0] = args[2];
|
|
cursor[1] = args[3];
|
|
const [x1, y1] = transformAbsolutePoint(matrix, args[0], args[1]);
|
|
const [x, y] = transformAbsolutePoint(matrix, args[2], args[3]);
|
|
args[0] = x1;
|
|
args[1] = y1;
|
|
args[2] = x;
|
|
args[3] = y;
|
|
}
|
|
if (command === 'q') {
|
|
cursor[0] += args[2];
|
|
cursor[1] += args[3];
|
|
const [x1, y1] = transformRelativePoint(matrix, args[0], args[1]);
|
|
const [x, y] = transformRelativePoint(matrix, args[2], args[3]);
|
|
args[0] = x1;
|
|
args[1] = y1;
|
|
args[2] = x;
|
|
args[3] = y;
|
|
}
|
|
|
|
// smooth quadratic Bézier curveto (x y)
|
|
if (command === 'T') {
|
|
cursor[0] = args[0];
|
|
cursor[1] = args[1];
|
|
const [x, y] = transformAbsolutePoint(matrix, args[0], args[1]);
|
|
args[0] = x;
|
|
args[1] = y;
|
|
}
|
|
if (command === 't') {
|
|
cursor[0] += args[0];
|
|
cursor[1] += args[1];
|
|
const [x, y] = transformRelativePoint(matrix, args[0], args[1]);
|
|
args[0] = x;
|
|
args[1] = y;
|
|
}
|
|
|
|
// elliptical arc (rx ry x-axis-rotation large-arc-flag sweep-flag x y)
|
|
if (command === 'A') {
|
|
transformArc(cursor, args, matrix);
|
|
cursor[0] = args[5];
|
|
cursor[1] = args[6];
|
|
// reduce number of digits in rotation angle
|
|
if (Math.abs(args[2]) > 80) {
|
|
const a = args[0];
|
|
const rotation = args[2];
|
|
args[0] = args[1];
|
|
args[1] = a;
|
|
args[2] = rotation + (rotation > 0 ? -90 : 90);
|
|
}
|
|
const [x, y] = transformAbsolutePoint(matrix, args[5], args[6]);
|
|
args[5] = x;
|
|
args[6] = y;
|
|
}
|
|
if (command === 'a') {
|
|
transformArc([0, 0], args, matrix);
|
|
cursor[0] += args[5];
|
|
cursor[1] += args[6];
|
|
// reduce number of digits in rotation angle
|
|
if (Math.abs(args[2]) > 80) {
|
|
const a = args[0];
|
|
const rotation = args[2];
|
|
args[0] = args[1];
|
|
args[1] = a;
|
|
args[2] = rotation + (rotation > 0 ? -90 : 90);
|
|
}
|
|
const [x, y] = transformRelativePoint(matrix, args[5], args[6]);
|
|
args[5] = x;
|
|
args[6] = y;
|
|
}
|
|
|
|
// closepath
|
|
if (command === 'z' || command === 'Z') {
|
|
cursor[0] = start[0];
|
|
cursor[1] = start[1];
|
|
}
|
|
|
|
pathItem.command = command;
|
|
pathItem.args = args;
|
|
}
|
|
};
|