diff --git a/__tests__/demos/perf/rect.ts b/__tests__/demos/perf/rect.ts index 60624c3dd0..edb455fbfb 100644 --- a/__tests__/demos/perf/rect.ts +++ b/__tests__/demos/perf/rect.ts @@ -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]], }, }); diff --git a/packages/g-lite/src/css/parser/transform.ts b/packages/g-lite/src/css/parser/transform.ts index 9f4cf6f4de..93bad6e151 100644 --- a/packages/g-lite/src/css/parser/transform.ts +++ b/packages/g-lite/src/css/parser/transform.ts @@ -15,6 +15,7 @@ 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 { @@ -22,30 +23,13 @@ export interface ParsedTransform { 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; @@ -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 []; } @@ -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 []; } @@ -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; } } @@ -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, @@ -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), @@ -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), @@ -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 [ @@ -405,10 +425,10 @@ 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': @@ -416,13 +436,13 @@ export function convertItemToMatrix(item: ParsedTransform): number[] { 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': @@ -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) { diff --git a/packages/g-lite/src/types.ts b/packages/g-lite/src/types.ts index b10c366633..b6635c237b 100644 --- a/packages/g-lite/src/types.ts +++ b/packages/g-lite/src/types.ts @@ -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'; @@ -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