Skip to content

Commit

Permalink
feat: transform support array type
Browse files Browse the repository at this point in the history
  • Loading branch information
Aarebecca committed Oct 16, 2024
1 parent 6705389 commit 3eafc7a
Show file tree
Hide file tree
Showing 3 changed files with 155 additions and 59 deletions.
3 changes: 1 addition & 2 deletions __tests__/demos/perf/rect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ export async function rects(context: { canvas: Canvas }) {
for (let i = 0; i < 10_0000; i++) {
const group = new Group({
style: {
// transform: [['translate', Math.random() * 640, Math.random() * 640]],
transform: `translate(${Math.random() * 640}, ${Math.random() * 640})`,
transform: [['translate', Math.random() * 640, Math.random() * 640]],
},
});

Expand Down
132 changes: 76 additions & 56 deletions packages/g-lite/src/css/parser/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,37 +15,21 @@ import {
parseLengthOrPercentageUnmemoize,
} from './dimension';
import { parseNumber, parseNumberUnmemoize } from './numeric';
import type { TransformArray, TransformType } from '../../types';

// eg. { t: 'scale', d: [CSSUnitValue(1), CSSUnitValue(2)] }
export interface ParsedTransform {
t: string;
d: CSSUnitValue[];
}

type TransformType =
| 'matrix'
| 'matrix3d'
| 'rotate'
| 'rotatex'
| 'rotatey'
| 'rotatez'
| 'rotate3d'
| 'scale'
| 'scalex'
| 'scaley'
| 'scalez'
| 'scale3d'
| 'translate'
| 'translatex'
| 'translatey'
| 'translatez'
| 'translate3d';
type PatternElement = string | number | null | CSSUnitValue;
type CastFunction =
| ((string: string) => string)
| ((contents: (number | PatternElement)[]) => PatternElement[]);

const _ = null;
const TRANSFORM_REGEXP = /\s*(\w+)\(([^)]*)\)/g;
function cast(pattern: PatternElement[]) {
return function (contents: PatternElement[]) {
let i = 0;
Expand All @@ -69,49 +53,79 @@ const transformFunctions: Record<
matrix: ['NNNNNN', [_, _, 0, 0, _, _, 0, 0, 0, 0, 1, 0, _, _, 0, 1], id],
matrix3d: ['NNNNNNNNNNNNNNNN', id],
rotate: ['A'],
rotatex: ['A'],
rotatey: ['A'],
rotatez: ['A'],
rotateX: ['A'],
rotateY: ['A'],
rotateZ: ['A'],
rotate3d: ['NNNA'],
perspective: ['L'],
scale: ['Nn', cast([_, _, new CSSUnitValue(1)]), id],
scalex: [
scaleX: [
'N',
cast([_, new CSSUnitValue(1), new CSSUnitValue(1)]),
cast([_, new CSSUnitValue(1)]),
],
scaley: [
scaleY: [
'N',
cast([new CSSUnitValue(1), _, new CSSUnitValue(1)]),
cast([new CSSUnitValue(1), _]),
],
scalez: ['N', cast([new CSSUnitValue(1), new CSSUnitValue(1), _])],
scaleZ: ['N', cast([new CSSUnitValue(1), new CSSUnitValue(1), _])],
scale3d: ['NNN', id],
skew: ['Aa', null, id],
skewx: ['A', null, cast([_, Odeg])],
skewy: ['A', null, cast([Odeg, _])],
skewX: ['A', null, cast([_, Odeg])],
skewY: ['A', null, cast([Odeg, _])],
translate: ['Tt', cast([_, _, Opx]), id],
translatex: ['T', cast([_, Opx, Opx]), cast([_, Opx])],
translatey: ['T', cast([Opx, _, Opx]), cast([Opx, _])],
translatez: ['L', cast([Opx, Opx, _])],
translateX: ['T', cast([_, Opx, Opx]), cast([_, Opx])],
translateY: ['T', cast([Opx, _, Opx]), cast([Opx, _])],
translateZ: ['L', cast([Opx, Opx, _])],
translate3d: ['TTL', id],
};

function parseArrayTransform(transform: TransformArray): ParsedTransform[] {
const result: ParsedTransform[] = [];
const length = transform.length;

for (let i = 0; i < length; i++) {
const item = transform[i];
const name = item[0];
const args = item.slice(1) as number[];
// infer default value
if (name === 'translate' || name === 'skew') {
if (args.length === 1) args.push(0);
} else if (name === 'scale') {
if (args.length === 1) args.push(args[0]);
}

const functionData = transformFunctions[name];
if (!functionData) return [];
const parsedArgs = args.map((value) => getOrCreateUnitValue(value));
result.push({ t: name, d: parsedArgs });
}

return result;
}

/**
* none
* scale(1) scale(1, 2)
* scaleX(1)
*/
export function parseTransform(string: string): ParsedTransform[] {
string = (string || 'none').toLowerCase().trim();
if (string === 'none') {
export function parseTransform(
transform: string | TransformArray,
): ParsedTransform[] {
if (Array.isArray(transform)) {
return parseArrayTransform(transform);
}

transform = (transform || 'none').trim();
if (transform === 'none') {
return [];
}
const transformRegExp = /\s*(\w+)\(([^)]*)\)/g;
const result: ParsedTransform[] = [];
let match;
let prevLastIndex = 0;
while ((match = transformRegExp.exec(string))) {
TRANSFORM_REGEXP.lastIndex = 0;
while ((match = TRANSFORM_REGEXP.exec(transform))) {
if (match.index !== prevLastIndex) {
return [];
}
Expand Down Expand Up @@ -159,23 +173,29 @@ export function parseTransform(string: string): ParsedTransform[] {
}
result.push({ t: functionName, d: parsedArgs }); // { t: scale, d: [1, 2] }

if (transformRegExp.lastIndex === string.length) {
if (TRANSFORM_REGEXP.lastIndex === transform.length) {
return result;
}
}

return [];
}
export function parseTransformUnmemoize(string: string): ParsedTransform[] {
string = (string || 'none').toLowerCase().trim();
if (string === 'none') {
export function parseTransformUnmemoize(
transform: string | TransformArray,
): ParsedTransform[] {
if (Array.isArray(transform)) {
return parseArrayTransform(transform);
}

transform = (transform || 'none').trim();
if (transform === 'none') {
return [];
}
const transformRegExp = /\s*(\w+)\(([^)]*)\)/g;
const result: ParsedTransform[] = [];
let match;
let prevLastIndex = 0;
while ((match = transformRegExp.exec(string))) {
TRANSFORM_REGEXP.lastIndex = 0;
while ((match = TRANSFORM_REGEXP.exec(transform))) {
if (match.index !== prevLastIndex) {
return [];
}
Expand Down Expand Up @@ -223,7 +243,7 @@ export function parseTransformUnmemoize(string: string): ParsedTransform[] {
}
result.push({ t: functionName, d: parsedArgs }); // { t: scale, d: [1, 2] }

if (transformRegExp.lastIndex === string.length) {
if (TRANSFORM_REGEXP.lastIndex === transform.length) {
return result;
}
}
Expand All @@ -237,7 +257,7 @@ export function convertItemToMatrix(item: ParsedTransform): number[] {
let z: number;
let angle: number;
switch (item.t) {
case 'rotatex':
case 'rotateX':
angle = deg2rad(convertAngleUnit(item.d[0]));
return [
1,
Expand All @@ -257,7 +277,7 @@ export function convertItemToMatrix(item: ParsedTransform): number[] {
0,
1,
];
case 'rotatey':
case 'rotateY':
angle = deg2rad(convertAngleUnit(item.d[0]));
return [
Math.cos(angle),
Expand All @@ -278,7 +298,7 @@ export function convertItemToMatrix(item: ParsedTransform): number[] {
1,
];
case 'rotate':
case 'rotatez':
case 'rotateZ':
angle = deg2rad(convertAngleUnit(item.d[0]));
return [
Math.cos(angle),
Expand Down Expand Up @@ -359,11 +379,11 @@ export function convertItemToMatrix(item: ParsedTransform): number[] {
0,
1,
];
case 'scalex':
case 'scaleX':
return [item.d[0].value, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
case 'scaley':
case 'scaleY':
return [1, 0, 0, 0, 0, item.d[0].value, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
case 'scalez':
case 'scaleZ':
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, item.d[0].value, 0, 0, 0, 0, 1];
case 'scale3d':
return [
Expand Down Expand Up @@ -405,24 +425,24 @@ export function convertItemToMatrix(item: ParsedTransform): number[] {
0,
1,
];
case 'skewx':
case 'skewX':
angle = deg2rad(convertAngleUnit(item.d[0]));
return [1, 0, 0, 0, Math.tan(angle), 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
case 'skewy':
case 'skewY':
angle = deg2rad(convertAngleUnit(item.d[0]));
return [1, Math.tan(angle), 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
case 'translate':
// TODO: pass target
x = convertPercentUnit(item.d[0], 0, null) || 0;
y = convertPercentUnit(item.d[1], 0, null) || 0;
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, y, 0, 1];
case 'translatex':
case 'translateX':
x = convertPercentUnit(item.d[0], 0, null) || 0;
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, x, 0, 0, 1];
case 'translatey':
case 'translateY':
y = convertPercentUnit(item.d[0], 0, null) || 0;
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, y, 0, 1];
case 'translatez':
case 'translateZ':
z = convertPercentUnit(item.d[0], 0, null) || 0;
return [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, z, 1];
case 'translate3d':
Expand Down Expand Up @@ -704,14 +724,14 @@ function quat(fromQ: number[], toQ: number[], f: number): number[] {
return quat;
}

// scalex/y/z -> scale
// scaleX/Y/Z -> scale
function typeTo2D(type: string) {
return type.replace(/[xy]/, '');
return type.replace(/[XY]/, '');
}

// scalex/y/z -> scale3d
// scaleX/Y/Z -> scale3d
function typeTo3D(type: string) {
return type.replace(/(x|y|z|3d)?$/, '3d');
return type.replace(/(X|Y|Z|3d)?$/, '3d');
}

const isMatrixOrPerspective = function (lt: string, rt: string) {
Expand Down
79 changes: 78 additions & 1 deletion packages/g-lite/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,83 @@ export interface EventPosition {
y: number;
}

export type TransformType =
| 'matrix'
| 'matrix3d'
| 'rotate'
| 'rotateX'
| 'rotateY'
| 'rotateZ'
| 'rotate3d'
| 'scale'
| 'scaleX'
| 'scaleY'
| 'scaleZ'
| 'scale3d'
| 'skew'
| 'skewX'
| 'skewY'
| 'translate'
| 'translateX'
| 'translateY'
| 'translateZ'
| 'translate3d';
export type TransformScale = ['scale', number, number?];
export type TransformScaleX = ['scaleX', number];
export type TransformScaleY = ['scaleY', number];
export type TransformScaleZ = ['scaleZ', number];
export type TransformScale3d = ['scale3d', number, number, number];
export type TransformTranslate = ['translate', number, number?];
export type TransformTranslateX = ['translateX', number];
export type TransformTranslateY = ['translateY', number];
export type TransformTranslateZ = ['translateZ', number];
export type TransformTranslate3d = ['translate3d', number, number, number];
export type TransformRotate = ['rotate', number];
export type TransformRotateX = ['rotateX', number];
export type TransformRotateY = ['rotateY', number];
export type TransformRotateZ = ['rotateZ', number];
export type TransformRotate3d = ['rotate3d', number, number, number, number?];
export type TransformSkew = ['skew', number, number?];
export type TransformSkewX = ['skewX', number];
export type TransformSkewY = ['skewY', number];
// prettier-ignore
export type TransformMatrix = [
'matrix',
number, number, // a, b
number, number, // c, d
number, number, // tx, ty
];
// prettier-ignore
export type TransformMatrix3d = [
'matrix3d',
number, number, number, number,
number, number, number, number,
number, number, number, number,
number, number, number, number,
];

export type TransformArray = (
| TransformScale
| TransformScaleX
| TransformScaleY
| TransformScaleZ
| TransformScale3d
| TransformTranslate
| TransformTranslateX
| TransformTranslateY
| TransformTranslateZ
| TransformTranslate3d
| TransformRotate
| TransformRotateX
| TransformRotateY
| TransformRotateZ
| TransformRotate3d
| TransformSkew
| TransformSkewX
| TransformSkewY
| TransformMatrix
| TransformMatrix3d
)[];
export type TextTransform = 'capitalize' | 'uppercase' | 'lowercase' | 'none';
export type TextOverflow = 'clip' | 'ellipsis' | string;
export type TextDecorationLine = string | 'none';
Expand All @@ -53,7 +130,7 @@ export interface BaseStyleProps {
/**
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform
*/
transform?: string;
transform?: string | TransformArray;

/**
* @see https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin
Expand Down

0 comments on commit 3eafc7a

Please sign in to comment.