mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2024-12-22 13:43:03 -05:00
Apply rotations to any figure (#168)
* Apply rotations to any figure * add changelog * apply rotations to curves too
This commit is contained in:
parent
4591369e3c
commit
202e7f4fda
17 changed files with 246 additions and 196 deletions
5
.changeset/wise-olives-teach.md
Normal file
5
.changeset/wise-olives-teach.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"penpot-exporter": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Implement rotation for any arbitrary figure
|
|
@ -1,7 +1,21 @@
|
||||||
|
import { translateRotation, translateZeroRotation } from '@plugin/translators';
|
||||||
import { applyInverseRotation, hasRotation } from '@plugin/utils';
|
import { applyInverseRotation, hasRotation } from '@plugin/utils';
|
||||||
|
|
||||||
import { ShapeBaseAttributes, ShapeGeomAttributes } from '@ui/lib/types/shapes/shape';
|
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 = (
|
export const transformRotationAndPosition = (
|
||||||
node: LayoutMixin,
|
node: LayoutMixin,
|
||||||
baseRotation: number
|
baseRotation: number
|
||||||
|
@ -15,9 +29,7 @@ export const transformRotationAndPosition = (
|
||||||
return {
|
return {
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
rotation,
|
...translateZeroRotation()
|
||||||
transform: undefined,
|
|
||||||
transformInverse: undefined
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,22 +41,6 @@ export const transformRotationAndPosition = (
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...referencePoint,
|
...referencePoint,
|
||||||
rotation: -rotation < 0 ? -rotation + 360 : -rotation,
|
...translateRotation(node.absoluteTransform, 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
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,11 +9,11 @@ import {
|
||||||
transformStrokesFromVector
|
transformStrokesFromVector
|
||||||
} from '@plugin/transformers/partials';
|
} from '@plugin/transformers/partials';
|
||||||
import { translateFills } from '@plugin/translators/fills';
|
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';
|
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
|
const pathShapes = node.vectorPaths
|
||||||
.filter((vectorPath, index) => {
|
.filter((vectorPath, index) => {
|
||||||
return (
|
return (
|
||||||
|
@ -22,7 +22,7 @@ export const transformVectorPaths = (node: VectorNode): PathShape[] => {
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
.map((vectorPath, index) =>
|
.map((vectorPath, index) =>
|
||||||
transformVectorPath(node, vectorPath, (node.vectorNetwork.regions ?? [])[index])
|
transformVectorPath(node, vectorPath, (node.vectorNetwork.regions ?? [])[index], baseRotation)
|
||||||
);
|
);
|
||||||
|
|
||||||
const geometryShapes = node.fillGeometry
|
const geometryShapes = node.fillGeometry
|
||||||
|
@ -32,7 +32,7 @@ export const transformVectorPaths = (node: VectorNode): PathShape[] => {
|
||||||
vectorPath => normalizePath(vectorPath.data) === normalizePath(geometry.data)
|
vectorPath => normalizePath(vectorPath.data) === normalizePath(geometry.data)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.map(geometry => transformVectorPath(node, geometry, undefined));
|
.map(geometry => transformVectorPath(node, geometry, undefined, baseRotation));
|
||||||
|
|
||||||
return [...geometryShapes, ...pathShapes];
|
return [...geometryShapes, ...pathShapes];
|
||||||
};
|
};
|
||||||
|
@ -58,18 +58,15 @@ const nodeHasFills = (
|
||||||
const transformVectorPath = (
|
const transformVectorPath = (
|
||||||
node: VectorNode,
|
node: VectorNode,
|
||||||
vectorPath: VectorPath,
|
vectorPath: VectorPath,
|
||||||
vectorRegion: VectorRegion | undefined
|
vectorRegion: VectorRegion | undefined,
|
||||||
|
baseRotation: number
|
||||||
): PathShape => {
|
): PathShape => {
|
||||||
const normalizedPaths = parseSVG(vectorPath.data);
|
const normalizedPaths = parseSVG(vectorPath.data);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'path',
|
type: 'path',
|
||||||
name: 'svg-path',
|
name: 'svg-path',
|
||||||
content: translateCommandsToSegments(
|
content: translateCommands(node, normalizedPaths, baseRotation),
|
||||||
normalizedPaths,
|
|
||||||
node.absoluteTransform[0][2],
|
|
||||||
node.absoluteTransform[1][2]
|
|
||||||
),
|
|
||||||
fills:
|
fills:
|
||||||
vectorPath.windingRule === 'NONE' ? [] : translateFills(vectorRegion?.fills ?? node.fills),
|
vectorPath.windingRule === 'NONE' ? [] : translateFills(vectorRegion?.fills ?? node.fills),
|
||||||
svgAttrs: {
|
svgAttrs: {
|
||||||
|
|
|
@ -9,9 +9,10 @@ import {
|
||||||
transformSceneNode,
|
transformSceneNode,
|
||||||
transformStrokes
|
transformStrokes
|
||||||
} from '@plugin/transformers/partials';
|
} 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 { 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
|
* In order to match the normal representation of a line in Penpot, we will assume that
|
||||||
|
@ -35,3 +36,24 @@ export const transformLineNode = (node: LineNode, baseRotation: number): PathSha
|
||||||
...transformOverrides(node)
|
...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
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import { parseSVG } from 'svg-path-parser';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
transformBlend,
|
transformBlend,
|
||||||
transformConstraints,
|
transformConstraints,
|
||||||
|
@ -7,12 +9,13 @@ import {
|
||||||
transformLayoutAttributes,
|
transformLayoutAttributes,
|
||||||
transformOverrides,
|
transformOverrides,
|
||||||
transformProportion,
|
transformProportion,
|
||||||
|
transformRotation,
|
||||||
transformSceneNode,
|
transformSceneNode,
|
||||||
transformStrokes
|
transformStrokes
|
||||||
} from '@plugin/transformers/partials';
|
} 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 = (
|
export const transformPathNode = (
|
||||||
node: StarNode | PolygonNode,
|
node: StarNode | PolygonNode,
|
||||||
|
@ -29,8 +32,12 @@ export const transformPathNode = (
|
||||||
...transformSceneNode(node),
|
...transformSceneNode(node),
|
||||||
...transformBlend(node),
|
...transformBlend(node),
|
||||||
...transformProportion(node),
|
...transformProportion(node),
|
||||||
|
...transformRotation(node, baseRotation),
|
||||||
...transformLayoutAttributes(node),
|
...transformLayoutAttributes(node),
|
||||||
...transformConstraints(node),
|
...transformConstraints(node),
|
||||||
...transformOverrides(node)
|
...transformOverrides(node)
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const translatePathNode = (node: StarNode | PolygonNode, baseRotation: number): Segment[] =>
|
||||||
|
translateCommands(node, parseSVG(node.fillGeometry[0].data), baseRotation);
|
||||||
|
|
|
@ -20,7 +20,7 @@ export const transformVectorNode = (
|
||||||
node: VectorNode,
|
node: VectorNode,
|
||||||
baseRotation: number
|
baseRotation: number
|
||||||
): GroupShape | PathShape => {
|
): GroupShape | PathShape => {
|
||||||
const children = transformVectorPaths(node);
|
const children = transformVectorPaths(node, baseRotation);
|
||||||
|
|
||||||
if (children.length === 1) {
|
if (children.length === 1) {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -4,5 +4,6 @@ export * from './translateBoolType';
|
||||||
export * from './translateChildren';
|
export * from './translateChildren';
|
||||||
export * from './translateConstraints';
|
export * from './translateConstraints';
|
||||||
export * from './translateLayout';
|
export * from './translateLayout';
|
||||||
|
export * from './translateRotation';
|
||||||
export * from './translateShadowEffects';
|
export * from './translateShadowEffects';
|
||||||
export * from './translateStrokes';
|
export * from './translateStrokes';
|
||||||
|
|
33
plugin-src/translators/translateRotation.ts
Normal file
33
plugin-src/translators/translateRotation.ts
Normal file
|
@ -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
|
||||||
|
});
|
|
@ -1,4 +1,4 @@
|
||||||
export * from './translateCommandsToSegments';
|
export * from './translateCommands';
|
||||||
export * from './translateLineNode';
|
export * from './translateNonRotatedCommands';
|
||||||
export * from './translatePathNode';
|
export * from './translateRotatedCommands';
|
||||||
export * from './translateWindingRule';
|
export * from './translateWindingRule';
|
||||||
|
|
18
plugin-src/translators/vectors/translateCommands.ts
Normal file
18
plugin-src/translators/vectors/translateCommands.ts
Normal file
|
@ -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]
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,57 +0,0 @@
|
||||||
import { Command, CurveToCommand, LineToCommand, MoveToCommand } from 'svg-path-parser';
|
|
||||||
|
|
||||||
import { Segment } from '@ui/lib/types/shapes/pathShape';
|
|
||||||
|
|
||||||
export const translateCommandsToSegments = (
|
|
||||||
commands: Command[],
|
|
||||||
baseX: number = 0,
|
|
||||||
baseY: number = 0
|
|
||||||
): 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,63 +0,0 @@
|
||||||
import { Command } from 'svg-path-parser';
|
|
||||||
|
|
||||||
import { applyInverseRotation, applyRotation, hasRotation } from '@plugin/utils';
|
|
||||||
|
|
||||||
import { Segment } from '@ui/lib/types/shapes/pathShape';
|
|
||||||
|
|
||||||
import { translateCommandsToSegments } from '.';
|
|
||||||
|
|
||||||
export const translateLineNode = (node: LineNode, baseRotation: number): Segment[] => {
|
|
||||||
const rotation = node.rotation + baseRotation;
|
|
||||||
const x = node.absoluteTransform[0][2];
|
|
||||||
const y = node.absoluteTransform[1][2];
|
|
||||||
|
|
||||||
if (!hasRotation(rotation) || !node.absoluteBoundingBox) {
|
|
||||||
return translateCommandsToSegments(
|
|
||||||
[
|
|
||||||
{
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
command: 'moveto',
|
|
||||||
code: 'M'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: node.width,
|
|
||||||
y: 0,
|
|
||||||
command: 'lineto',
|
|
||||||
code: 'L'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
x,
|
|
||||||
y
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const referencePoint = applyInverseRotation(
|
|
||||||
{ x, y },
|
|
||||||
node.absoluteTransform,
|
|
||||||
node.absoluteBoundingBox
|
|
||||||
);
|
|
||||||
|
|
||||||
const endPoint = applyRotation(
|
|
||||||
{ x: referencePoint.x + node.width, y: referencePoint.y },
|
|
||||||
node.absoluteTransform,
|
|
||||||
node.absoluteBoundingBox
|
|
||||||
);
|
|
||||||
|
|
||||||
const commands: Command[] = [
|
|
||||||
{
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
command: 'moveto',
|
|
||||||
code: 'M'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
x: endPoint.x,
|
|
||||||
y: endPoint.y,
|
|
||||||
command: 'lineto',
|
|
||||||
code: 'L'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
return translateCommandsToSegments(commands);
|
|
||||||
};
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
import { Command, CurveToCommand, LineToCommand, MoveToCommand } from 'svg-path-parser';
|
||||||
|
|
||||||
|
import { Segment } from '@ui/lib/types/shapes/pathShape';
|
||||||
|
|
||||||
|
export const translateNonRotatedCommands = (
|
||||||
|
commands: Command[],
|
||||||
|
baseX: number = 0,
|
||||||
|
baseY: number = 0
|
||||||
|
): Segment[] => {
|
||||||
|
return commands.map(command => translateNonRotatedCommand(command, baseX, baseY));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const translateNonRotatedCommand = (
|
||||||
|
command: Command,
|
||||||
|
baseX: number,
|
||||||
|
baseY: number
|
||||||
|
): Segment => {
|
||||||
|
switch (command.command) {
|
||||||
|
case 'moveto':
|
||||||
|
return translateMoveTo(command, baseX, baseY);
|
||||||
|
case 'lineto':
|
||||||
|
return translateLineTo(command, baseX, baseY);
|
||||||
|
case 'curveto':
|
||||||
|
return translateCurveTo(command, baseX, baseY);
|
||||||
|
case 'closepath':
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
command: 'close-path'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateMoveTo = (command: MoveToCommand, baseX: number, baseY: number): Segment => {
|
||||||
|
return {
|
||||||
|
command: 'move-to',
|
||||||
|
params: { x: command.x + baseX, y: command.y + baseY }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateLineTo = (command: LineToCommand, baseX: number, baseY: number): Segment => {
|
||||||
|
return {
|
||||||
|
command: 'line-to',
|
||||||
|
params: { x: command.x + baseX, y: command.y + baseY }
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const translateCurveTo = (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,37 +0,0 @@
|
||||||
import { parseSVG } from 'svg-path-parser';
|
|
||||||
|
|
||||||
import { applyInverseRotation, hasRotation } from '@plugin/utils';
|
|
||||||
|
|
||||||
import { Segment } from '@ui/lib/types/shapes/pathShape';
|
|
||||||
import { Point } from '@ui/lib/types/utils/point';
|
|
||||||
|
|
||||||
import { translateCommandsToSegments } from '.';
|
|
||||||
|
|
||||||
export const translatePathNode = (
|
|
||||||
node: StarNode | PolygonNode,
|
|
||||||
baseRotation: number
|
|
||||||
): Segment[] => {
|
|
||||||
let referencePoint: Point = {
|
|
||||||
x: node.absoluteTransform[0][2],
|
|
||||||
y: node.absoluteTransform[1][2]
|
|
||||||
};
|
|
||||||
|
|
||||||
if (hasRotation(node.rotation + baseRotation) && node.absoluteBoundingBox) {
|
|
||||||
referencePoint = applyInverseRotation(
|
|
||||||
{ x: referencePoint.x, y: referencePoint.y },
|
|
||||||
node.absoluteTransform,
|
|
||||||
node.absoluteBoundingBox
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const segments: Segment[] = [];
|
|
||||||
|
|
||||||
for (const path of node.fillGeometry) {
|
|
||||||
segments.push(...translateVectorPath(path, referencePoint.x, referencePoint.y));
|
|
||||||
}
|
|
||||||
|
|
||||||
return segments;
|
|
||||||
};
|
|
||||||
|
|
||||||
const translateVectorPath = (path: VectorPath, baseX: number, baseY: number): Segment[] =>
|
|
||||||
translateCommandsToSegments(parseSVG(path.data), baseX, baseY);
|
|
31
plugin-src/translators/vectors/translateRotatedCommands.ts
Normal file
31
plugin-src/translators/vectors/translateRotatedCommands.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
import { Command } from 'svg-path-parser';
|
||||||
|
|
||||||
|
import { applyInverseRotation, applyRotationToSegment } from '@plugin/utils';
|
||||||
|
|
||||||
|
import { ClosePath, Segment } from '@ui/lib/types/shapes/pathShape';
|
||||||
|
|
||||||
|
import { translateNonRotatedCommand } from '.';
|
||||||
|
|
||||||
|
const isClosePath = (segment: Segment): segment is ClosePath => segment.command === 'close-path';
|
||||||
|
|
||||||
|
export const translateRotatedCommands = (
|
||||||
|
commands: Command[],
|
||||||
|
transform: Transform,
|
||||||
|
boundingBox: Rect
|
||||||
|
): Segment[] => {
|
||||||
|
const referencePoint = applyInverseRotation(
|
||||||
|
{ x: transform[0][2], y: transform[1][2] },
|
||||||
|
transform,
|
||||||
|
boundingBox
|
||||||
|
);
|
||||||
|
|
||||||
|
return commands.map(command => {
|
||||||
|
const segment = translateNonRotatedCommand(command, referencePoint.x, referencePoint.y);
|
||||||
|
|
||||||
|
if (isClosePath(segment)) {
|
||||||
|
return segment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return applyRotationToSegment(segment, transform, boundingBox);
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { ClosePath, CurveTo, Segment } from '@ui/lib/types/shapes/pathShape';
|
||||||
import { Point } from '@ui/lib/types/utils/point';
|
import { Point } from '@ui/lib/types/utils/point';
|
||||||
|
|
||||||
const ROTATION_TOLERANCE = 0.000001;
|
const ROTATION_TOLERANCE = 0.000001;
|
||||||
|
@ -16,6 +17,41 @@ export const applyRotation = (point: Point, transform: Transform, boundingBox: R
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const applyRotationToSegment = (
|
||||||
|
segment: Exclude<Segment, ClosePath>,
|
||||||
|
transform: Transform,
|
||||||
|
boundingBox: Rect
|
||||||
|
): Segment => {
|
||||||
|
const rotated = applyRotation(
|
||||||
|
{ x: segment.params.x, y: segment.params.y },
|
||||||
|
transform,
|
||||||
|
boundingBox
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isCurveTo(segment)) {
|
||||||
|
const curve1 = applyRotation(
|
||||||
|
{ x: segment.params.c1x, y: segment.params.c1y },
|
||||||
|
transform,
|
||||||
|
boundingBox
|
||||||
|
);
|
||||||
|
const curve2 = applyRotation(
|
||||||
|
{ x: segment.params.c2x, y: segment.params.c2y },
|
||||||
|
transform,
|
||||||
|
boundingBox
|
||||||
|
);
|
||||||
|
|
||||||
|
segment.params.c1x = curve1.x;
|
||||||
|
segment.params.c1y = curve1.y;
|
||||||
|
segment.params.c2x = curve2.x;
|
||||||
|
segment.params.c2y = curve2.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
segment.params.x = rotated.x;
|
||||||
|
segment.params.y = rotated.y;
|
||||||
|
|
||||||
|
return segment;
|
||||||
|
};
|
||||||
|
|
||||||
export const applyInverseRotation = (
|
export const applyInverseRotation = (
|
||||||
point: Point,
|
point: Point,
|
||||||
transform: Transform,
|
transform: Transform,
|
||||||
|
@ -38,3 +74,5 @@ const calculateCenter = (boundingBox: Rect): Point => ({
|
||||||
x: boundingBox.x + boundingBox.width / 2,
|
x: boundingBox.x + boundingBox.width / 2,
|
||||||
y: boundingBox.y + boundingBox.height / 2
|
y: boundingBox.y + boundingBox.height / 2
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const isCurveTo = (segment: Segment): segment is CurveTo => segment.command === 'curve-to';
|
||||||
|
|
|
@ -41,7 +41,7 @@ type LineTo = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type ClosePath = {
|
export type ClosePath = {
|
||||||
command: 'close-path' | typeof VECTOR_CLOSE_PATH;
|
command: 'close-path' | typeof VECTOR_CLOSE_PATH;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ type MoveTo = {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type CurveTo = {
|
export type CurveTo = {
|
||||||
command: 'curve-to' | typeof VECTOR_CURVE_TO;
|
command: 'curve-to' | typeof VECTOR_CURVE_TO;
|
||||||
params: {
|
params: {
|
||||||
x: number;
|
x: number;
|
||||||
|
|
Loading…
Reference in a new issue