Skip to content

Commit

Permalink
refactor(): Align shadow with class registry, part of #9144 (#9626)
Browse files Browse the repository at this point in the history
  • Loading branch information
asturur committed Jan 28, 2024
1 parent 92103ff commit bfb8ccf
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 26 deletions.
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(): Align shadow with class registry, part of #9144 [#9626](https://github.com/fabricjs/fabric.js/pull/9626)
- cd() Surface the minified build as standard when importing. [#9624](https://github.com/fabricjs/fabric.js/pull/9624)
- chore(): removed unused code from Path render function [#9619](https://github.com/fabricjs/fabric.js/pull/9619)

Expand Down
59 changes: 47 additions & 12 deletions src/Shadow.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { classRegistry } from './ClassRegistry';
import { Color } from './color/Color';
import { config } from './config';
import { reNum } from './parser/constants';
import { Point } from './Point';
import type { FabricObject } from './shapes/Object/FabricObject';
import type { TClassProperties } from './typedefs';
Expand All @@ -9,6 +11,36 @@ import { degreesToRadians } from './util/misc/radiansDegreesConversion';
import { toFixed } from './util/misc/toFixed';
import { rotateVector } from './util/misc/vectors';

/**
* Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px")
* - (?:\s|^): This part captures either a whitespace character (\s) or the beginning of a line (^). It's non-capturing (due to (?:...)), meaning it doesn't create a capturing group.
* - (-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?: This captures the first component of the shadow, which is the horizontal offset. Breaking it down:
* - (-?\d+): Captures an optional minus sign followed by one or more digits (integer part of the number).
* - (?:\.\d*)?: Optionally captures a decimal point followed by zero or more digits (decimal part of the number).
* - (?:px)?: Optionally captures the "px" unit.
* - (?:\s?|$): Captures either an optional whitespace or the end of the line. This whole part is wrapped in a non-capturing group and marked as optional with ?.
* - (-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?: Similar to the previous step, this captures the vertical offset.
(\d+(?:\.\d*)?(?:px)?)?: This captures the blur radius. It's similar to the horizontal offset but without the optional minus sign.
(?:\s+(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?){0,1}: This captures an optional part for the color. It allows for whitespace followed by a component with an optional minus sign, digits, decimal point, and "px" unit.
(?:$|\s): This captures either the end of the line or a whitespace character. It ensures that the match ends either at the end of the string or with a whitespace character.
*/
// eslint-disable-next-line max-len

const shadowOffsetRegex = '(-?\\d+(?:\\.\\d*)?(?:px)?(?:\\s?|$))?';

const reOffsetsAndBlur = () =>
new RegExp(
'(?:\\s|^)' +
shadowOffsetRegex +
shadowOffsetRegex +
'(' +
reNum +
'?(?:px)?)?(?:\\s?|$)(?:$|\\s)'
);

export const shadowDefaultValues: Partial<TClassProperties<Shadow>> = {
color: 'rgb(0,0,0)',
blur: 0,
Expand All @@ -26,6 +58,7 @@ export type SerializedShadowOptions = {
offsetY: number;
affectStroke: boolean;
nonScaling: boolean;
type: string;
};

export class Shadow {
Expand Down Expand Up @@ -82,6 +115,9 @@ export class Shadow {
declare id: number;

static ownDefaults = shadowDefaultValues;

static type = 'shadow';

/**
* @see {@link http://fabricjs.com/shadows|Shadow demo}
* @param {Object|String} [options] Options object with any of color, blur, offsetX, offsetY properties or string (e.g. "rgba(0,0,0,0.2) 2px 2px 10px")
Expand All @@ -106,12 +142,11 @@ export class Shadow {
*/
static parseShadow(value: string) {
const shadowStr = value.trim(),
[__, offsetX = 0, offsetY = 0, blur = 0] = (
Shadow.reOffsetsAndBlur.exec(shadowStr) || []
regex = reOffsetsAndBlur(),
[, offsetX = 0, offsetY = 0, blur = 0] = (
regex.exec(shadowStr) || []
).map((value) => parseFloat(value) || 0),
color = (
shadowStr.replace(Shadow.reOffsetsAndBlur, '') || 'rgb(0,0,0)'
).trim();
color = (shadowStr.replace(regex, '') || 'rgb(0,0,0)').trim();

return {
color,
Expand Down Expand Up @@ -198,17 +233,17 @@ export class Shadow {
offsetY: this.offsetY,
affectStroke: this.affectStroke,
nonScaling: this.nonScaling,
type: (this.constructor as typeof Shadow).type,
};
const defaults = Shadow.ownDefaults;
const defaults = Shadow.ownDefaults as SerializedShadowOptions;
return !this.includeDefaultValues
? pickBy(data, (value, key) => value !== defaults[key])
: data;
}

/**
* Regex matching shadow offsetX, offsetY and blur (ex: "2px 2px 10px rgba(0,0,0,0.2)", "rgb(0,255,0) 2px 2px")
*/
// eslint-disable-next-line max-len
static reOffsetsAndBlur =
/(?:\s|^)(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(-?\d+(?:\.\d*)?(?:px)?(?:\s?|$))?(\d+(?:\.\d*)?(?:px)?)?(?:\s?|$)(?:$|\s)/;
static fromObject(options: Partial<TClassProperties<Shadow>>) {
return new this(options);
}
}

classRegistry.setClass(Shadow, 'shadow');
8 changes: 3 additions & 5 deletions src/parser/selectorMatches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@ export function selectorMatches(element: HTMLElement, selector: string) {
const nodeName = element.nodeName;
const classNames = element.getAttribute('class');
const id = element.getAttribute('id');
const azAz = '(?![a-zA-Z\\-]+)';
let matcher;
// i check if a selector matches slicing away part from it.
// if i get empty string i should match
matcher = new RegExp('^' + nodeName, 'i');
selector = selector.replace(matcher, '');
if (id && selector.length) {
matcher = new RegExp('#' + id + '(?![a-zA-Z\\-]+)', 'i');
matcher = new RegExp('#' + id + azAz, 'i');
selector = selector.replace(matcher, '');
}
if (classNames && selector.length) {
const splitClassNames = classNames.split(' ');
for (let i = splitClassNames.length; i--; ) {
matcher = new RegExp(
'\\.' + splitClassNames[i] + '(?![a-zA-Z\\-]+)',
'i'
);
matcher = new RegExp('\\.' + splitClassNames[i] + azAz, 'i');
selector = selector.replace(matcher, '');
}
}
Expand Down
11 changes: 6 additions & 5 deletions src/util/misc/objectEnlive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type { BaseFilter } from '../../filters/BaseFilter';
import type { FabricObject as BaseFabricObject } from '../../shapes/Object/Object';
import { FabricError, SignalAbortedError } from '../internals/console';
import type { Gradient } from '../../gradient';
import type { Shadow } from '../../Shadow';

export type LoadImageOptions = Abortable & {
/**
Expand Down Expand Up @@ -67,7 +68,7 @@ export type EnlivenObjectOptions = Abortable & {
* Method for further parsing of object elements,
* called after each fabric object created.
*/
reviver?: <T extends BaseFabricObject | FabricObject | BaseFilter>(
reviver?: <T extends BaseFabricObject | FabricObject | BaseFilter | Shadow>(
serializedObj: Record<string, any>,
instance: T
) => void;
Expand All @@ -83,7 +84,7 @@ export type EnlivenObjectOptions = Abortable & {
* @returns {Promise<FabricObject[]>}
*/
export const enlivenObjects = <
T extends BaseFabricObject | FabricObject | BaseFilter
T extends BaseFabricObject | FabricObject | BaseFilter | Shadow
>(
objects: any[],
{ signal, reviver = noop }: EnlivenObjectOptions = {}
Expand Down Expand Up @@ -135,7 +136,7 @@ export const enlivenObjectEnlivables = <
{ signal }: Abortable = {}
) =>
new Promise<R>((resolve, reject) => {
const instances: (FabricObject | TFiller)[] = [];
const instances: (FabricObject | TFiller | Shadow)[] = [];
signal && signal.addEventListener('abort', reject, { once: true });
// enlive every possible property
const promises = Object.values(serializedObject).map((value: any) => {
Expand All @@ -146,9 +147,9 @@ export const enlivenObjectEnlivables = <
if (value.colorStops) {
return new (classRegistry.getClass<typeof Gradient>('gradient'))(value);
}
// clipPath
// clipPath or shadow
if (value.type) {
return enlivenObjects<FabricObject>([value], { signal }).then(
return enlivenObjects<FabricObject | Shadow>([value], { signal }).then(
([enlived]) => {
instances.push(enlived);
return enlived;
Expand Down
13 changes: 9 additions & 4 deletions test/unit/shadow.js
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
assert.ok(typeof shadow.toObject === 'function');

var object = shadow.toObject();
assert.equal(JSON.stringify(object), '{"color":"rgb(0,0,0)","blur":0,"offsetX":0,"offsetY":0,"affectStroke":false,"nonScaling":false}');
assert.equal(JSON.stringify(object), '{"color":"rgb(0,0,0)","blur":0,"offsetX":0,"offsetY":0,"affectStroke":false,"nonScaling":false,"type":"shadow"}');
});

QUnit.test('clone with affectStroke', function(assert) {
Expand All @@ -177,13 +177,18 @@
var shadow = new fabric.Shadow();
shadow.includeDefaultValues = false;

assert.equal(JSON.stringify(shadow.toObject()), '{}');
assert.equal(JSON.stringify(shadow.toObject()), '{"type":"shadow"}');

shadow.color = 'red';
assert.equal(JSON.stringify(shadow.toObject()), '{"color":"red"}');
assert.equal(JSON.stringify(shadow.toObject()), '{"color":"red","type":"shadow"}');

shadow.offsetX = 15;
assert.equal(JSON.stringify(shadow.toObject()), '{"color":"red","offsetX":15}');
assert.equal(JSON.stringify(shadow.toObject()), '{"color":"red","offsetX":15,"type":"shadow"}');
});

QUnit.test('fromObject', assert => {
const shadow = fabric.Shadow.fromObject({ color: 'red', offsetX: 15 });
assert.ok(shadow instanceof fabric.Shadow);
});

QUnit.test('toSVG', function(assert) {
Expand Down

0 comments on commit bfb8ccf

Please sign in to comment.