Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(): Align shadow with class registry, part of #9144 #9626

Merged
merged 7 commits into from
Jan 28, 2024
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.
Jump to
Jump to file
Failed to load files.
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(): 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);
Copy link
Contributor

Choose a reason for hiding this comment

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

is fromObject receiving options without type?

}
}

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