Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## [next]

- refactor(svImport): remove the css/gradient/clipPath global definitions [#9030](https://github.com/fabricjs/fabric.js/pull/9030)
- fix(): tweaks to type getter [#9022](https://github.com/fabricjs/fabric.js/pull/9022)
- ci() Refactor GHA actions for caching and reuse [#9029](https://github.com/fabricjs/fabric.js/pull/9029)
- ci(): install dev deps types [#9039](https://github.com/fabricjs/fabric.js/pull/9039)
Expand Down
11 changes: 0 additions & 11 deletions src/parser/constants.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
//@ts-nocheck
import { getSvgRegex } from './getSvgRegex';
import { LEFT, TOP } from '../constants';

export const cssRules = {};
export const gradientDefs = {};
export const clipPaths = {};

export const storage = {
cssRules,
gradientDefs,
clipPaths,
};

export const reNum = String.raw`(?:[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?)`;

export const svgNS = 'http://www.w3.org/2000/svg';
Expand Down
23 changes: 13 additions & 10 deletions src/parser/elements_parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import {
multiplyTransformMatrices,
qrDecompose,
} from '../util/misc/matrix';
import { storage } from './constants';
import { removeTransformMatrixForSvgParsing } from '../util/transform_matrix_removal';
import type { FabricObject } from '../shapes/Object/FabricObject';
import { Point } from '../Point';
import { CENTER } from '../constants';
import { getGradientDefs } from './getGradientDefs';
import { getCSSRules } from './getCSSRules';

const findTag = (el: HTMLElement) =>
classRegistry.getSVGClass(el.tagName.toLowerCase().replace('svg:', ''));
Expand All @@ -22,22 +23,24 @@ const ElementsParser = function (
options,
reviver,
parsingOptions,
doc
doc,
clipPaths
) {
this.elements = elements;
this.options = options;
this.reviver = reviver;
this.svgUid = (options && options.svgUid) || 0;
this.parsingOptions = parsingOptions;
this.regexUrl = /^url\(['"]?#([^'"]+)['"]?\)/g;
this.doc = doc;
this.clipPaths = clipPaths;
this.gradientDefs = getGradientDefs(doc);
this.cssRules = getCSSRules(doc);
};

(function (proto) {
proto.parse = function (): Promise<FabricObject[]> {
return Promise.all(
this.elements.map((element: HTMLElement, i) => {
element.setAttribute('svgUid', this.svgUid);
return this.createObject(element);
})
);
Expand All @@ -46,7 +49,7 @@ const ElementsParser = function (
proto.createObject = async function (el: HTMLElement): Promise<FabricObject> {
const klass = findTag(el);
if (klass) {
const obj = await klass.fromElement(el, this.options);
const obj = await klass.fromElement(el, this.options, this.cssRules);
let _options;
this.resolveGradient(obj, el, 'fill');
this.resolveGradient(obj, el, 'stroke');
Expand All @@ -61,7 +64,7 @@ const ElementsParser = function (
return null;
};

proto.extractPropertyDefinition = function (obj, property, storageType) {
proto.extractPropertyDefinition = function (obj, property, storage) {
const value = obj[property],
regex = this.regexUrl;
if (!regex.test(value)) {
Expand All @@ -71,14 +74,14 @@ const ElementsParser = function (
const id = regex.exec(value)[1];
regex.lastIndex = 0;
// @todo fix this
return storage[storageType][this.svgUid][id];
return storage[id];
};

proto.resolveGradient = function (obj, el, property) {
const gradientDef = this.extractPropertyDefinition(
obj,
property,
'gradientDefs'
this.gradientDefs
);
if (gradientDef) {
const opacityAttr = el.getAttribute(property + '-opacity');
Expand All @@ -94,7 +97,7 @@ const ElementsParser = function (
const clipPathElements = this.extractPropertyDefinition(
obj,
'clipPath',
'clipPaths'
this.clipPaths
);
if (clipPathElements) {
const objTransformInv = invertTransform(obj.calcTransformMatrix());
Expand All @@ -111,7 +114,7 @@ const ElementsParser = function (
const container = await Promise.all(
clipPathElements.map((clipPathElement) => {
return findTag(clipPathElement)
.fromElement(clipPathElement, this.options)
.fromElement(clipPathElement, this.options, this.cssRules)
.then((enlivedClippath) => {
removeTransformMatrixForSvgParsing(enlivedClippath);
enlivedClippath.fillRule = enlivedClippath.clipRule;
Expand Down
70 changes: 36 additions & 34 deletions src/parser/getCSSRules.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import type { CSSRules } from './typedefs';

/**
* Returns CSS rules for a given SVG document
* @param {SVGDocument} doc SVG document to parse
* @param {HTMLElement} doc SVG document to parse
* @return {Object} CSS rules of this document
*/
export function getCSSRules(doc: Document) {
export function getCSSRules(doc: HTMLElement) {
const styles = doc.getElementsByTagName('style');
let i;
let len;
const allRules: Record<string, Record<string, string>> = {};
let rules;
const allRules: CSSRules = {};

// very crude parsing of style contents
for (i = 0, len = styles.length; i < len; i++) {
Expand All @@ -23,39 +24,40 @@ export function getCSSRules(doc: Document) {
}
// recovers all the rule in this form `body { style code... }`
// rules = styleContents.match(/[^{]*\{[\s\S]*?\}/g);
rules = styleContents.split('}');
// remove empty rules.
rules = rules.filter(function (rule) {
return rule.trim();
});
// at this point we have hopefully an array of rules `body { style code... `
// eslint-disable-next-line no-loop-func
rules.forEach(function (rule) {
const match = rule.split('{'),
ruleObj: Record<string, string> = {},
declaration = match[1].trim(),
propertyValuePairs = declaration.split(';').filter(function (pair) {
return pair.trim();
});
styleContents
.split('}')
// remove empty rules.
.filter(function (rule) {
return rule.trim();
})
// at this point we have hopefully an array of rules `body { style code... `
// eslint-disable-next-line no-loop-func
.forEach(function (rule) {
const match = rule.split('{'),
ruleObj: Record<string, string> = {},
declaration = match[1].trim(),
propertyValuePairs = declaration.split(';').filter(function (pair) {
return pair.trim();
});

for (i = 0, len = propertyValuePairs.length; i < len; i++) {
const pair = propertyValuePairs[i].split(':'),
property = pair[0].trim(),
value = pair[1].trim();
ruleObj[property] = value;
}
rule = match[0].trim();
rule.split(',').forEach((_rule) => {
_rule = _rule.replace(/^svg/i, '').trim();
if (_rule === '') {
return;
for (i = 0, len = propertyValuePairs.length; i < len; i++) {
const pair = propertyValuePairs[i].split(':'),
property = pair[0].trim(),
value = pair[1].trim();
ruleObj[property] = value;
}
allRules[_rule] = {
...(allRules[_rule] || {}),
...ruleObj,
};
rule = match[0].trim();
rule.split(',').forEach((_rule) => {
_rule = _rule.replace(/^svg/i, '').trim();
if (_rule === '') {
return;
}
allRules[_rule] = {
...(allRules[_rule] || {}),
...ruleObj,
};
});
});
});
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks bad but i really just removed the var rules and chained 3 array methods of which we didn't need the return value

}
return allRules;
}
19 changes: 11 additions & 8 deletions src/parser/getGlobalStylesForElement.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
//@ts-nocheck
import { cssRules } from './constants';
import { elementMatchesRule } from './elementMatchesRule';
import type { CSSRules } from './typedefs';

/**
* @private
*/

export function getGlobalStylesForElement(element, svgUid) {
const styles = {};
for (const rule in cssRules[svgUid]) {
export function getGlobalStylesForElement(
element: HTMLElement,
cssRules: CSSRules = {}
) {
let styles: Record<string, string> = {};
for (const rule in cssRules) {
if (elementMatchesRule(element, rule.split(' '))) {
for (const property in cssRules[svgUid][rule]) {
styles[property] = cssRules[svgUid][rule][property];
}
styles = {
...styles,
...cssRules[rule],
};
}
}
return styles;
Expand Down
65 changes: 32 additions & 33 deletions src/parser/parseAttributes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
//@ts-nocheck
import { DEFAULT_SVG_FONT_SIZE } from '../constants';
import { parseUnit } from '../util/misc/svgParsing';
import { cPath, fSize, svgValidParentsRegEx } from './constants';
Expand All @@ -8,6 +7,7 @@ import { normalizeValue } from './normalizeValue';
import { parseFontDeclaration } from './parseFontDeclaration';
import { parseStyleAttribute } from './parseStyleAttribute';
import { setStrokeFillOpacity } from './setStrokeFillOpacity';
import type { CSSRules } from './typedefs';

/**
* Returns an object of attributes' name/value, given element and an array of attribute names;
Expand All @@ -17,59 +17,58 @@ import { setStrokeFillOpacity } from './setStrokeFillOpacity';
* @return {Object} object containing parsed attributes' names/values
*/
export function parseAttributes(
element: SVGElement | HTMLElement,
element: HTMLElement | null,
attributes: string[],
svgUid?: string
cssRules?: CSSRules
): Record<string, any> {
if (!element) {
return {};
}

let value,
parentAttributes = {},
parentAttributes: Record<string, string> = {},
fontSize,
parentFontSize;
parentFontSize = DEFAULT_SVG_FONT_SIZE;

if (typeof svgUid === 'undefined') {
svgUid = element.getAttribute('svgUid');
}
// if there's a parent container (`g` or `a` or `symbol` node), parse its attributes recursively upwards
if (
element.parentNode &&
svgValidParentsRegEx.test(element.parentNode.nodeName)
) {
parentAttributes = parseAttributes(element.parentNode, attributes, svgUid);
parentAttributes = parseAttributes(
element.parentElement,
attributes,
cssRules
);
if (parentAttributes.fontSize) {
fontSize = parentFontSize = parseUnit(parentAttributes.fontSize);
}
}

let ownAttributes = attributes.reduce(function (memo, attr) {
value = element.getAttribute(attr);
if (value) {
// eslint-disable-line
memo[attr] = value;
}
return memo;
}, {});
// add values parsed from style, which take precedence over attributes
// (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
const cssAttrs = Object.assign(
getGlobalStylesForElement(element, svgUid),
parseStyleAttribute(element)
);
ownAttributes = Object.assign(ownAttributes, cssAttrs);
if (cssAttrs[cPath]) {
element.setAttribute(cPath, cssAttrs[cPath]);
const ownAttributes: Record<string, string> = {
...attributes.reduce<Record<string, string>>((memo, attr) => {
value = element.getAttribute(attr);
if (value) {
memo[attr] = value;
}
return memo;
}, {}),
// add values parsed from style, which take precedence over attributes
// (see: http://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes)
...getGlobalStylesForElement(element, cssRules),
...parseStyleAttribute(element),
};

if (ownAttributes[cPath]) {
element.setAttribute(cPath, ownAttributes[cPath]);
}
fontSize = parentFontSize =
parentAttributes.fontSize || DEFAULT_SVG_FONT_SIZE;
if (ownAttributes[fSize]) {
// looks like the minimum should be 9px when dealing with ems. this is what looks like in browsers.
ownAttributes[fSize] = fontSize = parseUnit(
ownAttributes[fSize],
parentFontSize
);
fontSize = parseUnit(ownAttributes[fSize], parentFontSize);
ownAttributes[fSize] = `${fontSize}`;
}

const normalizedStyle = {};
const normalizedStyle: Record<string, string> = {};
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i m not sure what happened here, i will rely on tests.

for (const attr in ownAttributes) {
const normalizedAttr = normalizeAttr(attr);
const normalizedValue = normalizeValue(
Expand Down
Loading