Skip to content

Commit

Permalink
Apply rotations to any figure (#168)
Browse files Browse the repository at this point in the history
* Apply rotations to any figure

* add changelog

* apply rotations to curves too
  • Loading branch information
jordisala1991 authored Jun 17, 2024
1 parent 4591369 commit 202e7f4
Show file tree
Hide file tree
Showing 17 changed files with 246 additions and 196 deletions.
5 changes: 5 additions & 0 deletions .changeset/wise-olives-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"penpot-exporter": minor
---

Implement rotation for any arbitrary figure
36 changes: 16 additions & 20 deletions plugin-src/transformers/partials/transformRotationAndPosition.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
import { translateRotation, translateZeroRotation } from '@plugin/translators';
import { applyInverseRotation, hasRotation } from '@plugin/utils';

import { ShapeBaseAttributes, ShapeGeomAttributes } from '@ui/lib/types/shapes/shape';

export const transformRotation = (
node: LayoutMixin,
baseRotation: number
): Pick<ShapeBaseAttributes, 'transform' | 'transformInverse' | 'rotation'> => {
const rotation = node.rotation + baseRotation;

if (!hasRotation(rotation)) {
return translateZeroRotation();
}

return translateRotation(node.absoluteTransform, rotation);
};

export const transformRotationAndPosition = (
node: LayoutMixin,
baseRotation: number
Expand All @@ -15,9 +29,7 @@ export const transformRotationAndPosition = (
return {
x,
y,
rotation,
transform: undefined,
transformInverse: undefined
...translateZeroRotation()
};
}

Expand All @@ -29,22 +41,6 @@ export const transformRotationAndPosition = (

return {
...referencePoint,
rotation: -rotation < 0 ? -rotation + 360 : -rotation,
transform: {
a: node.absoluteTransform[0][0],
b: node.absoluteTransform[1][0],
c: node.absoluteTransform[0][1],
d: node.absoluteTransform[1][1],
e: 0,
f: 0
},
transformInverse: {
a: node.absoluteTransform[0][0],
b: node.absoluteTransform[0][1],
c: node.absoluteTransform[1][0],
d: node.absoluteTransform[1][1],
e: 0,
f: 0
}
...translateRotation(node.absoluteTransform, rotation)
};
};
17 changes: 7 additions & 10 deletions plugin-src/transformers/partials/transformVectorPaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ import {
transformStrokesFromVector
} from '@plugin/transformers/partials';
import { translateFills } from '@plugin/translators/fills';
import { translateCommandsToSegments, translateWindingRule } from '@plugin/translators/vectors';
import { translateCommands, translateWindingRule } from '@plugin/translators/vectors';

import { PathShape } from '@ui/lib/types/shapes/pathShape';

export const transformVectorPaths = (node: VectorNode): PathShape[] => {
export const transformVectorPaths = (node: VectorNode, baseRotation: number): PathShape[] => {
const pathShapes = node.vectorPaths
.filter((vectorPath, index) => {
return (
Expand All @@ -22,7 +22,7 @@ export const transformVectorPaths = (node: VectorNode): PathShape[] => {
);
})
.map((vectorPath, index) =>
transformVectorPath(node, vectorPath, (node.vectorNetwork.regions ?? [])[index])
transformVectorPath(node, vectorPath, (node.vectorNetwork.regions ?? [])[index], baseRotation)
);

const geometryShapes = node.fillGeometry
Expand All @@ -32,7 +32,7 @@ export const transformVectorPaths = (node: VectorNode): PathShape[] => {
vectorPath => normalizePath(vectorPath.data) === normalizePath(geometry.data)
)
)
.map(geometry => transformVectorPath(node, geometry, undefined));
.map(geometry => transformVectorPath(node, geometry, undefined, baseRotation));

return [...geometryShapes, ...pathShapes];
};
Expand All @@ -58,18 +58,15 @@ const nodeHasFills = (
const transformVectorPath = (
node: VectorNode,
vectorPath: VectorPath,
vectorRegion: VectorRegion | undefined
vectorRegion: VectorRegion | undefined,
baseRotation: number
): PathShape => {
const normalizedPaths = parseSVG(vectorPath.data);

return {
type: 'path',
name: 'svg-path',
content: translateCommandsToSegments(
normalizedPaths,
node.absoluteTransform[0][2],
node.absoluteTransform[1][2]
),
content: translateCommands(node, normalizedPaths, baseRotation),
fills:
vectorPath.windingRule === 'NONE' ? [] : translateFills(vectorRegion?.fills ?? node.fills),
svgAttrs: {
Expand Down
24 changes: 23 additions & 1 deletion plugin-src/transformers/transformLineNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import {
transformSceneNode,
transformStrokes
} from '@plugin/transformers/partials';
import { translateLineNode } from '@plugin/translators/vectors';
import { translateCommands } from '@plugin/translators/vectors';

import { PathShape } from '@ui/lib/types/shapes/pathShape';
import { Segment } from '@ui/lib/types/shapes/pathShape';

/**
* In order to match the normal representation of a line in Penpot, we will assume that
Expand All @@ -35,3 +36,24 @@ export const transformLineNode = (node: LineNode, baseRotation: number): PathSha
...transformOverrides(node)
};
};

const translateLineNode = (node: LineNode, baseRotation: number): Segment[] => {
return translateCommands(
node,
[
{
x: 0,
y: 0,
command: 'moveto',
code: 'M'
},
{
x: node.width,
y: 0,
command: 'lineto',
code: 'L'
}
],
baseRotation
);
};
11 changes: 9 additions & 2 deletions plugin-src/transformers/transformPathNode.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { parseSVG } from 'svg-path-parser';

import {
transformBlend,
transformConstraints,
Expand All @@ -7,12 +9,13 @@ import {
transformLayoutAttributes,
transformOverrides,
transformProportion,
transformRotation,
transformSceneNode,
transformStrokes
} from '@plugin/transformers/partials';
import { translatePathNode } from '@plugin/translators/vectors';
import { translateCommands } from '@plugin/translators/vectors';

import { PathShape } from '@ui/lib/types/shapes/pathShape';
import { PathShape, Segment } from '@ui/lib/types/shapes/pathShape';

export const transformPathNode = (
node: StarNode | PolygonNode,
Expand All @@ -29,8 +32,12 @@ export const transformPathNode = (
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node),
...transformRotation(node, baseRotation),
...transformLayoutAttributes(node),
...transformConstraints(node),
...transformOverrides(node)
};
};

const translatePathNode = (node: StarNode | PolygonNode, baseRotation: number): Segment[] =>
translateCommands(node, parseSVG(node.fillGeometry[0].data), baseRotation);
2 changes: 1 addition & 1 deletion plugin-src/transformers/transformVectorNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export const transformVectorNode = (
node: VectorNode,
baseRotation: number
): GroupShape | PathShape => {
const children = transformVectorPaths(node);
const children = transformVectorPaths(node, baseRotation);

if (children.length === 1) {
return {
Expand Down
1 change: 1 addition & 0 deletions plugin-src/translators/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ export * from './translateBoolType';
export * from './translateChildren';
export * from './translateConstraints';
export * from './translateLayout';
export * from './translateRotation';
export * from './translateShadowEffects';
export * from './translateStrokes';
33 changes: 33 additions & 0 deletions plugin-src/translators/translateRotation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { ShapeBaseAttributes } from '@ui/lib/types/shapes/shape';

export const translateRotation = (
transform: Transform,
rotation: number
): Pick<ShapeBaseAttributes, 'transform' | 'transformInverse' | 'rotation'> => ({
rotation: -rotation < 0 ? -rotation + 360 : -rotation,
transform: {
a: transform[0][0],
b: transform[1][0],
c: transform[0][1],
d: transform[1][1],
e: 0,
f: 0
},
transformInverse: {
a: transform[0][0],
b: transform[0][1],
c: transform[1][0],
d: transform[1][1],
e: 0,
f: 0
}
});

export const translateZeroRotation = (): Pick<
ShapeBaseAttributes,
'transform' | 'transformInverse' | 'rotation'
> => ({
rotation: 0,
transform: undefined,
transformInverse: undefined
});
6 changes: 3 additions & 3 deletions plugin-src/translators/vectors/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from './translateCommandsToSegments';
export * from './translateLineNode';
export * from './translatePathNode';
export * from './translateCommands';
export * from './translateNonRotatedCommands';
export * from './translateRotatedCommands';
export * from './translateWindingRule';
18 changes: 18 additions & 0 deletions plugin-src/translators/vectors/translateCommands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Command } from 'svg-path-parser';

import { hasRotation } from '@plugin/utils';

import { translateNonRotatedCommands } from '.';
import { translateRotatedCommands } from './translateRotatedCommands';

export const translateCommands = (node: LayoutMixin, commands: Command[], baseRotation: number) => {
if (hasRotation(node.rotation + baseRotation) && node.absoluteBoundingBox) {
return translateRotatedCommands(commands, node.absoluteTransform, node.absoluteBoundingBox);
}

return translateNonRotatedCommands(
commands,
node.absoluteTransform[0][2],
node.absoluteTransform[1][2]
);
};
57 changes: 0 additions & 57 deletions plugin-src/translators/vectors/translateCommandsToSegments.ts

This file was deleted.

63 changes: 0 additions & 63 deletions plugin-src/translators/vectors/translateLineNode.ts

This file was deleted.

Loading

0 comments on commit 202e7f4

Please sign in to comment.