mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2025-01-03 05:10:13 -05:00
Implement rotation for vector lines (#160)
* Implement rotation for vector lines * wip * Improve rotations for lines * add changelog * add layout attributes to line
This commit is contained in:
parent
b85a4f7279
commit
af81fc7e92
14 changed files with 235 additions and 138 deletions
5
.changeset/sour-radios-know.md
Normal file
5
.changeset/sour-radios-know.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"penpot-exporter": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Apply rotations to lines
|
|
@ -5,6 +5,7 @@ export * from './transformEllipseNode';
|
||||||
export * from './transformFrameNode';
|
export * from './transformFrameNode';
|
||||||
export * from './transformGroupNode';
|
export * from './transformGroupNode';
|
||||||
export * from './transformInstanceNode';
|
export * from './transformInstanceNode';
|
||||||
|
export * from './transformLineNode';
|
||||||
export * from './transformPageNode';
|
export * from './transformPageNode';
|
||||||
export * from './transformPathNode';
|
export * from './transformPathNode';
|
||||||
export * from './transformRectangleNode';
|
export * from './transformRectangleNode';
|
||||||
|
|
|
@ -11,6 +11,17 @@ export const transformDimension = (
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const transformPosition = (
|
||||||
|
node: DimensionAndPositionMixin,
|
||||||
|
baseX: number,
|
||||||
|
baseY: number
|
||||||
|
): Pick<ShapeGeomAttributes, 'x' | 'y'> => {
|
||||||
|
return {
|
||||||
|
x: node.x + baseX,
|
||||||
|
y: node.y + baseY
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const transformDimensionAndPosition = (
|
export const transformDimensionAndPosition = (
|
||||||
node: DimensionAndPositionMixin,
|
node: DimensionAndPositionMixin,
|
||||||
baseX: number,
|
baseX: number,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
|
import { applyInverseRotation, hasRotation } from '@plugin/utils';
|
||||||
|
|
||||||
import { ShapeBaseAttributes, ShapeGeomAttributes } from '@ui/lib/types/shapes/shape';
|
import { ShapeBaseAttributes, ShapeGeomAttributes } from '@ui/lib/types/shapes/shape';
|
||||||
import { Point } from '@ui/lib/types/utils/point';
|
|
||||||
|
|
||||||
export const transformRotationAndPosition = (
|
export const transformRotationAndPosition = (
|
||||||
node: LayoutMixin,
|
node: LayoutMixin,
|
||||||
|
@ -11,7 +12,7 @@ export const transformRotationAndPosition = (
|
||||||
const x = node.x + baseX;
|
const x = node.x + baseX;
|
||||||
const y = node.y + baseY;
|
const y = node.y + baseY;
|
||||||
|
|
||||||
if (rotation === 0 || !node.absoluteBoundingBox) {
|
if (!hasRotation(rotation) || !node.absoluteBoundingBox) {
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
|
@ -21,10 +22,14 @@ export const transformRotationAndPosition = (
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const point = getRotatedPoint({ x, y }, node.absoluteTransform, node.absoluteBoundingBox);
|
const referencePoint = applyInverseRotation(
|
||||||
|
{ x, y },
|
||||||
|
node.absoluteTransform,
|
||||||
|
node.absoluteBoundingBox
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...point,
|
...referencePoint,
|
||||||
rotation: -rotation < 0 ? -rotation + 360 : -rotation,
|
rotation: -rotation < 0 ? -rotation + 360 : -rotation,
|
||||||
transform: {
|
transform: {
|
||||||
a: node.absoluteTransform[0][0],
|
a: node.absoluteTransform[0][0],
|
||||||
|
@ -44,25 +49,3 @@ export const transformRotationAndPosition = (
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getRotatedPoint = (point: Point, transform: Transform, boundingBox: Rect): Point => {
|
|
||||||
const centerPoint = {
|
|
||||||
x: boundingBox.x + boundingBox.width / 2,
|
|
||||||
y: boundingBox.y + boundingBox.height / 2
|
|
||||||
};
|
|
||||||
|
|
||||||
const relativePoint = {
|
|
||||||
x: point.x - centerPoint.x,
|
|
||||||
y: point.y - centerPoint.y
|
|
||||||
};
|
|
||||||
|
|
||||||
const rotatedPoint = {
|
|
||||||
x: relativePoint.x * transform[0][0] + relativePoint.y * transform[1][0],
|
|
||||||
y: relativePoint.x * transform[0][1] + relativePoint.y * transform[1][1]
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: centerPoint.x + rotatedPoint.x,
|
|
||||||
y: centerPoint.y + rotatedPoint.y
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
|
@ -10,26 +10,9 @@ import {
|
||||||
transformStrokesFromVector
|
transformStrokesFromVector
|
||||||
} from '@plugin/transformers/partials';
|
} from '@plugin/transformers/partials';
|
||||||
import { translateFills } from '@plugin/translators/fills';
|
import { translateFills } from '@plugin/translators/fills';
|
||||||
import {
|
import { translateCommandsToSegments, translateWindingRule } from '@plugin/translators/vectors';
|
||||||
translateCommandsToSegments,
|
|
||||||
translateLineNode,
|
|
||||||
translateVectorPaths,
|
|
||||||
translateWindingRule
|
|
||||||
} from '@plugin/translators/vectors';
|
|
||||||
|
|
||||||
import { PathAttributes, PathShape } from '@ui/lib/types/shapes/pathShape';
|
import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
||||||
|
|
||||||
export const transformVectorPathsAsContent = (
|
|
||||||
node: StarNode | LineNode | PolygonNode,
|
|
||||||
baseX: number,
|
|
||||||
baseY: number
|
|
||||||
): PathAttributes => {
|
|
||||||
const vectorPaths = getVectorPaths(node);
|
|
||||||
|
|
||||||
return {
|
|
||||||
content: translateVectorPaths(vectorPaths, baseX + node.x, baseY + node.y)
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const transformVectorPaths = (
|
export const transformVectorPaths = (
|
||||||
node: VectorNode,
|
node: VectorNode,
|
||||||
|
@ -59,21 +42,12 @@ export const transformVectorPaths = (
|
||||||
return [...geometryShapes, ...pathShapes];
|
return [...geometryShapes, ...pathShapes];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getVectorPaths = (node: StarNode | LineNode | PolygonNode): VectorPaths => {
|
|
||||||
switch (node.type) {
|
|
||||||
case 'STAR':
|
|
||||||
case 'POLYGON':
|
|
||||||
return node.fillGeometry;
|
|
||||||
case 'LINE':
|
|
||||||
return translateLineNode(node);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizePath = (path: string): string => {
|
const normalizePath = (path: string): string => {
|
||||||
// Round to 2 decimal places all numbers
|
// Round to 2 decimal places all numbers
|
||||||
const str = path.replace(/(\d+\.\d+|\d+)/g, (match: string) => {
|
const str = path.replace(/(\d+\.\d+|\d+)/g, (match: string) => {
|
||||||
return parseFloat(match).toFixed(2);
|
return parseFloat(match).toFixed(2);
|
||||||
});
|
});
|
||||||
|
|
||||||
// remove spaces
|
// remove spaces
|
||||||
return str.replace(/\s/g, '');
|
return str.replace(/\s/g, '');
|
||||||
};
|
};
|
||||||
|
|
37
plugin-src/transformers/transformLineNode.ts
Normal file
37
plugin-src/transformers/transformLineNode.ts
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import {
|
||||||
|
transformBlend,
|
||||||
|
transformConstraints,
|
||||||
|
transformEffects,
|
||||||
|
transformFigmaIds,
|
||||||
|
transformLayoutAttributes,
|
||||||
|
transformPosition,
|
||||||
|
transformProportion,
|
||||||
|
transformSceneNode,
|
||||||
|
transformStrokes
|
||||||
|
} from '@plugin/transformers/partials';
|
||||||
|
import { translateLineNode } from '@plugin/translators/vectors';
|
||||||
|
|
||||||
|
import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In order to match the normal representation of a line in Penpot, we will assume that
|
||||||
|
* the line is never rotated, so we calculate its normal position.
|
||||||
|
*
|
||||||
|
* To represent the line rotated we do take into account the rotation of the line, but only in its content.
|
||||||
|
*/
|
||||||
|
export const transformLineNode = (node: LineNode, baseX: number, baseY: number): PathShape => {
|
||||||
|
return {
|
||||||
|
type: 'path',
|
||||||
|
name: node.name,
|
||||||
|
content: translateLineNode(node, baseX, baseY),
|
||||||
|
...transformFigmaIds(node),
|
||||||
|
...transformStrokes(node),
|
||||||
|
...transformEffects(node),
|
||||||
|
...transformPosition(node, baseX, baseY),
|
||||||
|
...transformSceneNode(node),
|
||||||
|
...transformBlend(node),
|
||||||
|
...transformProportion(node),
|
||||||
|
...transformLayoutAttributes(node),
|
||||||
|
...transformConstraints(node)
|
||||||
|
};
|
||||||
|
};
|
|
@ -8,29 +8,25 @@ import {
|
||||||
transformLayoutAttributes,
|
transformLayoutAttributes,
|
||||||
transformProportion,
|
transformProportion,
|
||||||
transformSceneNode,
|
transformSceneNode,
|
||||||
transformStrokes,
|
transformStrokes
|
||||||
transformVectorPathsAsContent
|
|
||||||
} from '@plugin/transformers/partials';
|
} from '@plugin/transformers/partials';
|
||||||
|
import { translateVectorPaths } from '@plugin/translators/vectors';
|
||||||
|
|
||||||
import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
||||||
|
|
||||||
const hasFillGeometry = (node: StarNode | LineNode | PolygonNode): boolean => {
|
|
||||||
return 'fillGeometry' in node && node.fillGeometry.length > 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const transformPathNode = (
|
export const transformPathNode = (
|
||||||
node: StarNode | LineNode | PolygonNode,
|
node: StarNode | PolygonNode,
|
||||||
baseX: number,
|
baseX: number,
|
||||||
baseY: number
|
baseY: number
|
||||||
): PathShape => {
|
): PathShape => {
|
||||||
return {
|
return {
|
||||||
type: 'path',
|
type: 'path',
|
||||||
name: node.name,
|
name: node.name,
|
||||||
|
content: translateVectorPaths(node.fillGeometry, baseX + node.x, baseY + node.y),
|
||||||
...transformFigmaIds(node),
|
...transformFigmaIds(node),
|
||||||
...(hasFillGeometry(node) ? transformFills(node) : []),
|
...transformFills(node),
|
||||||
...transformStrokes(node),
|
...transformStrokes(node),
|
||||||
...transformEffects(node),
|
...transformEffects(node),
|
||||||
...transformVectorPathsAsContent(node, baseX, baseY),
|
|
||||||
...transformDimensionAndPosition(node, baseX, baseY),
|
...transformDimensionAndPosition(node, baseX, baseY),
|
||||||
...transformSceneNode(node),
|
...transformSceneNode(node),
|
||||||
...transformBlend(node),
|
...transformBlend(node),
|
||||||
|
|
|
@ -7,6 +7,7 @@ import {
|
||||||
transformFrameNode,
|
transformFrameNode,
|
||||||
transformGroupNode,
|
transformGroupNode,
|
||||||
transformInstanceNode,
|
transformInstanceNode,
|
||||||
|
transformLineNode,
|
||||||
transformPathNode,
|
transformPathNode,
|
||||||
transformRectangleNode,
|
transformRectangleNode,
|
||||||
transformTextNode,
|
transformTextNode,
|
||||||
|
@ -46,9 +47,11 @@ export const transformSceneNode = async (
|
||||||
case 'VECTOR':
|
case 'VECTOR':
|
||||||
penpotNode = transformVectorNode(node, baseX, baseY);
|
penpotNode = transformVectorNode(node, baseX, baseY);
|
||||||
break;
|
break;
|
||||||
|
case 'LINE':
|
||||||
|
penpotNode = transformLineNode(node, baseX, baseY);
|
||||||
|
break;
|
||||||
case 'STAR':
|
case 'STAR':
|
||||||
case 'POLYGON':
|
case 'POLYGON':
|
||||||
case 'LINE':
|
|
||||||
penpotNode = transformPathNode(node, baseX, baseY);
|
penpotNode = transformPathNode(node, baseX, baseY);
|
||||||
break;
|
break;
|
||||||
case 'BOOLEAN_OPERATION':
|
case 'BOOLEAN_OPERATION':
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
export * from './translateCommandsToSegments';
|
||||||
export * from './translateLineNode';
|
export * from './translateLineNode';
|
||||||
export * from './translateVectorPaths';
|
export * from './translateVectorPaths';
|
||||||
export * from './translateWindingRule';
|
export * from './translateWindingRule';
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
import { Command, CurveToCommand, LineToCommand, MoveToCommand } from 'svg-path-parser';
|
||||||
|
|
||||||
|
import { Segment } from '@ui/lib/types/shapes/pathShape';
|
||||||
|
|
||||||
|
export const translateCommandsToSegments = (
|
||||||
|
commands: Command[],
|
||||||
|
baseX: number,
|
||||||
|
baseY: number
|
||||||
|
): Segment[] => {
|
||||||
|
return commands.map(command => {
|
||||||
|
switch (command.command) {
|
||||||
|
case 'moveto':
|
||||||
|
return translateMoveToCommand(command, baseX, baseY);
|
||||||
|
case 'lineto':
|
||||||
|
return translateLineToCommand(command, baseX, baseY);
|
||||||
|
case 'curveto':
|
||||||
|
return translateCurveToCommand(command, baseX, baseY);
|
||||||
|
case 'closepath':
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
command: 'close-path'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateMoveToCommand = (command: MoveToCommand, baseX: number, baseY: number): Segment => {
|
||||||
|
return {
|
||||||
|
command: 'move-to',
|
||||||
|
params: { x: command.x + baseX, y: command.y + baseY }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateLineToCommand = (command: LineToCommand, baseX: number, baseY: number): Segment => {
|
||||||
|
return {
|
||||||
|
command: 'line-to',
|
||||||
|
params: { x: command.x + baseX, y: command.y + baseY }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateCurveToCommand = (
|
||||||
|
command: CurveToCommand,
|
||||||
|
baseX: number,
|
||||||
|
baseY: number
|
||||||
|
): Segment => {
|
||||||
|
return {
|
||||||
|
command: 'curve-to',
|
||||||
|
params: {
|
||||||
|
c1x: command.x1 + baseX,
|
||||||
|
c1y: command.y1 + baseY,
|
||||||
|
c2x: command.x2 + baseX,
|
||||||
|
c2y: command.y2 + baseY,
|
||||||
|
x: command.x + baseX,
|
||||||
|
y: command.y + baseY
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,23 +1,65 @@
|
||||||
export const translateLineNode = (node: LineNode): VectorPaths => {
|
import { Command } from 'svg-path-parser';
|
||||||
const commands = [
|
|
||||||
|
import { applyInverseRotation, applyRotation, hasRotation } from '@plugin/utils';
|
||||||
|
|
||||||
|
import { Segment } from '@ui/lib/types/shapes/pathShape';
|
||||||
|
|
||||||
|
import { translateCommandsToSegments } from '.';
|
||||||
|
|
||||||
|
export const translateLineNode = (node: LineNode, baseX: number, baseY: number): Segment[] => {
|
||||||
|
if (!hasRotation(node.rotation) || !node.absoluteBoundingBox) {
|
||||||
|
return translateCommandsToSegments(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
command: 'moveto',
|
||||||
|
code: 'M'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
x: node.width,
|
||||||
|
y: 0,
|
||||||
|
command: 'lineto',
|
||||||
|
code: 'L'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
baseX + node.x,
|
||||||
|
baseY + node.y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const startPoint = applyRotation(
|
||||||
|
{ x: 0, y: 0 },
|
||||||
|
node.absoluteTransform,
|
||||||
|
node.absoluteBoundingBox
|
||||||
|
);
|
||||||
|
|
||||||
|
const endPoint = applyRotation(
|
||||||
|
{ x: node.width, y: 0 },
|
||||||
|
node.absoluteTransform,
|
||||||
|
node.absoluteBoundingBox
|
||||||
|
);
|
||||||
|
|
||||||
|
const commands: Command[] = [
|
||||||
{
|
{
|
||||||
|
x: startPoint.x,
|
||||||
|
y: startPoint.y,
|
||||||
command: 'moveto',
|
command: 'moveto',
|
||||||
code: 'M',
|
code: 'M'
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
x: endPoint.x,
|
||||||
|
y: endPoint.y,
|
||||||
command: 'lineto',
|
command: 'lineto',
|
||||||
code: 'L',
|
code: 'L'
|
||||||
x: node.width,
|
|
||||||
y: node.height
|
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
return [
|
const referencePoint = applyInverseRotation(
|
||||||
{
|
{ x: node.x, y: node.y },
|
||||||
windingRule: 'NONE',
|
node.absoluteTransform,
|
||||||
data: commands.reduce((acc, { code, x, y }) => acc + `${code} ${x} ${y}`, '')
|
node.absoluteBoundingBox
|
||||||
}
|
);
|
||||||
];
|
|
||||||
|
return translateCommandsToSegments(commands, baseX + referencePoint.x, baseY + referencePoint.y);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,73 +1,19 @@
|
||||||
import { Command, CurveToCommand, LineToCommand, MoveToCommand, parseSVG } from 'svg-path-parser';
|
import { parseSVG } from 'svg-path-parser';
|
||||||
|
|
||||||
import { Segment } from '@ui/lib/types/shapes/pathShape';
|
import { Segment } from '@ui/lib/types/shapes/pathShape';
|
||||||
|
|
||||||
|
import { translateCommandsToSegments } from '.';
|
||||||
|
|
||||||
export const translateVectorPaths = (
|
export const translateVectorPaths = (
|
||||||
paths: VectorPaths,
|
paths: VectorPaths,
|
||||||
baseX: number,
|
baseX: number,
|
||||||
baseY: number
|
baseY: number
|
||||||
): Segment[] => {
|
): Segment[] => {
|
||||||
let segments: Segment[] = [];
|
const segments: Segment[] = [];
|
||||||
|
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
const normalizedPaths = parseSVG(path.data);
|
segments.push(...translateCommandsToSegments(parseSVG(path.data), baseX, baseY));
|
||||||
|
|
||||||
segments = [...segments, ...translateCommandsToSegments(normalizedPaths, baseX, baseY)];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return segments;
|
return segments;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const translateCommandsToSegments = (
|
|
||||||
commands: Command[],
|
|
||||||
baseX: number,
|
|
||||||
baseY: number
|
|
||||||
): Segment[] => {
|
|
||||||
return commands.map(command => {
|
|
||||||
switch (command.command) {
|
|
||||||
case 'moveto':
|
|
||||||
return translateMoveToCommand(command, baseX, baseY);
|
|
||||||
case 'lineto':
|
|
||||||
return translateLineToCommand(command, baseX, baseY);
|
|
||||||
case 'curveto':
|
|
||||||
return translateCurveToCommand(command, baseX, baseY);
|
|
||||||
case 'closepath':
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
command: 'close-path'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const translateMoveToCommand = (command: MoveToCommand, baseX: number, baseY: number): Segment => {
|
|
||||||
return {
|
|
||||||
command: 'move-to',
|
|
||||||
params: { x: command.x + baseX, y: command.y + baseY }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const translateLineToCommand = (command: LineToCommand, baseX: number, baseY: number): Segment => {
|
|
||||||
return {
|
|
||||||
command: 'line-to',
|
|
||||||
params: { x: command.x + baseX, y: command.y + baseY }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const translateCurveToCommand = (
|
|
||||||
command: CurveToCommand,
|
|
||||||
baseX: number,
|
|
||||||
baseY: number
|
|
||||||
): Segment => {
|
|
||||||
return {
|
|
||||||
command: 'curve-to',
|
|
||||||
params: {
|
|
||||||
c1x: command.x1 + baseX,
|
|
||||||
c1y: command.y1 + baseY,
|
|
||||||
c2x: command.x2 + baseX,
|
|
||||||
c2y: command.y2 + baseY,
|
|
||||||
x: command.x + baseX,
|
|
||||||
y: command.y + baseY
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
40
plugin-src/utils/applyRotation.ts
Normal file
40
plugin-src/utils/applyRotation.ts
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
import { Point } from '@ui/lib/types/utils/point';
|
||||||
|
|
||||||
|
const ROTATION_TOLERANCE = 0.000001;
|
||||||
|
|
||||||
|
export const applyRotation = (point: Point, transform: Transform, boundingBox: Rect): Point => {
|
||||||
|
const centerPoint = calculateCenter(boundingBox);
|
||||||
|
|
||||||
|
const rotatedPoint = applyMatrix(transform, {
|
||||||
|
x: point.x - centerPoint.x,
|
||||||
|
y: point.y - centerPoint.y
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: centerPoint.x + rotatedPoint.x,
|
||||||
|
y: centerPoint.y + rotatedPoint.y
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const applyInverseRotation = (
|
||||||
|
point: Point,
|
||||||
|
transform: Transform,
|
||||||
|
boundingBox: Rect
|
||||||
|
): Point => applyRotation(point, inverseMatrix(transform), boundingBox);
|
||||||
|
|
||||||
|
export const hasRotation = (rotation: number): boolean => Math.abs(rotation) > ROTATION_TOLERANCE;
|
||||||
|
|
||||||
|
const inverseMatrix = (matrix: Transform): Transform => [
|
||||||
|
[matrix[0][0], matrix[1][0], matrix[0][2]],
|
||||||
|
[matrix[0][1], matrix[1][1], matrix[1][2]]
|
||||||
|
];
|
||||||
|
|
||||||
|
const applyMatrix = (matrix: Transform, point: Point): Point => ({
|
||||||
|
x: point.x * matrix[0][0] + point.y * matrix[0][1],
|
||||||
|
y: point.x * matrix[1][0] + point.y * matrix[1][1]
|
||||||
|
});
|
||||||
|
|
||||||
|
const calculateCenter = (boundingBox: Rect): Point => ({
|
||||||
|
x: boundingBox.x + boundingBox.width / 2,
|
||||||
|
y: boundingBox.y + boundingBox.height / 2
|
||||||
|
});
|
|
@ -1,4 +1,5 @@
|
||||||
export * from './applyMatrixToPoint';
|
export * from './applyMatrixToPoint';
|
||||||
|
export * from './applyRotation';
|
||||||
export * from './calculateAdjustment';
|
export * from './calculateAdjustment';
|
||||||
export * from './calculateLinearGradient';
|
export * from './calculateLinearGradient';
|
||||||
export * from './calculateRadialGradient';
|
export * from './calculateRadialGradient';
|
||||||
|
|
Loading…
Reference in a new issue