mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2025-04-01 17:41:56 -05:00
Implement rotation for frames and sections (#163)
* Implement rotation for frames and sections * wip * finish implementation * add changelog
This commit is contained in:
parent
af81fc7e92
commit
e5f2943532
25 changed files with 172 additions and 199 deletions
5
.changeset/rare-pianos-complain.md
Normal file
5
.changeset/rare-pianos-complain.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
"penpot-exporter": minor
|
||||
---
|
||||
|
||||
Implement rotation for the missing figures
|
|
@ -8,15 +8,14 @@ const nodeActsAsMask = (node: SceneNode): boolean => {
|
|||
|
||||
export const transformChildren = async (
|
||||
node: ChildrenMixin,
|
||||
baseX: number = 0,
|
||||
baseY: number = 0
|
||||
baseRotation: number = 0
|
||||
): Promise<Children> => {
|
||||
const maskIndex = node.children.findIndex(nodeActsAsMask);
|
||||
const containsMask = maskIndex !== -1;
|
||||
|
||||
return {
|
||||
children: containsMask
|
||||
? await translateMaskChildren(node.children, maskIndex, baseX, baseY)
|
||||
: await translateChildren(node.children, baseX, baseY)
|
||||
? await translateMaskChildren(node.children, maskIndex, baseRotation)
|
||||
: await translateChildren(node.children, baseRotation)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { getBoundingBox } from '@plugin/utils';
|
||||
|
||||
import { ShapeGeomAttributes } from '@ui/lib/types/shapes/shape';
|
||||
|
||||
export const transformDimension = (
|
||||
|
@ -11,41 +9,13 @@ 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 = (
|
||||
node: DimensionAndPositionMixin,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
node: DimensionAndPositionMixin
|
||||
): ShapeGeomAttributes => {
|
||||
return {
|
||||
x: node.x + baseX,
|
||||
y: node.y + baseY,
|
||||
x: node.absoluteTransform[0][2],
|
||||
y: node.absoluteTransform[1][2],
|
||||
width: node.width,
|
||||
height: node.height
|
||||
};
|
||||
};
|
||||
|
||||
export const transformDimensionAndPositionFromVectorPath = (
|
||||
vectorPath: VectorPath,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
): ShapeGeomAttributes => {
|
||||
const boundingBox = getBoundingBox(vectorPath);
|
||||
|
||||
return {
|
||||
x: boundingBox.x1 + baseX,
|
||||
y: boundingBox.y1 + baseY,
|
||||
width: boundingBox.x2 - boundingBox.x1,
|
||||
height: boundingBox.y2 - boundingBox.y1
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,13 +4,12 @@ import { ShapeBaseAttributes, ShapeGeomAttributes } from '@ui/lib/types/shapes/s
|
|||
|
||||
export const transformRotationAndPosition = (
|
||||
node: LayoutMixin,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
baseRotation: number
|
||||
): Pick<ShapeBaseAttributes, 'transform' | 'transformInverse' | 'rotation'> &
|
||||
Pick<ShapeGeomAttributes, 'x' | 'y'> => {
|
||||
const rotation = node.rotation;
|
||||
const x = node.x + baseX;
|
||||
const y = node.y + baseY;
|
||||
const rotation = node.rotation + baseRotation;
|
||||
const x = node.absoluteTransform[0][2];
|
||||
const y = node.absoluteTransform[1][2];
|
||||
|
||||
if (!hasRotation(rotation) || !node.absoluteBoundingBox) {
|
||||
return {
|
||||
|
|
|
@ -2,7 +2,6 @@ import { parseSVG } from 'svg-path-parser';
|
|||
|
||||
import {
|
||||
transformBlend,
|
||||
transformDimensionAndPositionFromVectorPath,
|
||||
transformEffects,
|
||||
transformLayoutAttributes,
|
||||
transformProportion,
|
||||
|
@ -14,11 +13,7 @@ import { translateCommandsToSegments, translateWindingRule } from '@plugin/trans
|
|||
|
||||
import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
||||
|
||||
export const transformVectorPaths = (
|
||||
node: VectorNode,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
): PathShape[] => {
|
||||
export const transformVectorPaths = (node: VectorNode): PathShape[] => {
|
||||
const pathShapes = node.vectorPaths
|
||||
.filter((vectorPath, index) => {
|
||||
return (
|
||||
|
@ -27,7 +22,7 @@ export const transformVectorPaths = (
|
|||
);
|
||||
})
|
||||
.map((vectorPath, index) =>
|
||||
transformVectorPath(node, vectorPath, (node.vectorNetwork.regions ?? [])[index], baseX, baseY)
|
||||
transformVectorPath(node, vectorPath, (node.vectorNetwork.regions ?? [])[index])
|
||||
);
|
||||
|
||||
const geometryShapes = node.fillGeometry
|
||||
|
@ -37,7 +32,7 @@ export const transformVectorPaths = (
|
|||
vectorPath => normalizePath(vectorPath.data) === normalizePath(geometry.data)
|
||||
)
|
||||
)
|
||||
.map(geometry => transformVectorPath(node, geometry, undefined, baseX, baseY));
|
||||
.map(geometry => transformVectorPath(node, geometry, undefined));
|
||||
|
||||
return [...geometryShapes, ...pathShapes];
|
||||
};
|
||||
|
@ -63,16 +58,18 @@ const nodeHasFills = (
|
|||
const transformVectorPath = (
|
||||
node: VectorNode,
|
||||
vectorPath: VectorPath,
|
||||
vectorRegion: VectorRegion | undefined,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
vectorRegion: VectorRegion | undefined
|
||||
): PathShape => {
|
||||
const normalizedPaths = parseSVG(vectorPath.data);
|
||||
|
||||
return {
|
||||
type: 'path',
|
||||
name: 'svg-path',
|
||||
content: translateCommandsToSegments(normalizedPaths, baseX + node.x, baseY + node.y),
|
||||
content: translateCommandsToSegments(
|
||||
normalizedPaths,
|
||||
node.absoluteTransform[0][2],
|
||||
node.absoluteTransform[1][2]
|
||||
),
|
||||
fills:
|
||||
vectorPath.windingRule === 'NONE' ? [] : translateFills(vectorRegion?.fills ?? node.fills),
|
||||
svgAttrs: {
|
||||
|
@ -82,7 +79,6 @@ const transformVectorPath = (
|
|||
constraintsV: 'scale',
|
||||
...transformStrokesFromVector(node, normalizedPaths, vectorRegion),
|
||||
...transformEffects(node),
|
||||
...transformDimensionAndPositionFromVectorPath(vectorPath, baseX, baseY),
|
||||
...transformSceneNode(node),
|
||||
...transformBlend(node),
|
||||
...transformProportion(node),
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
import {
|
||||
transformBlend,
|
||||
transformChildren,
|
||||
transformDimensionAndPosition,
|
||||
transformDimension,
|
||||
transformEffects,
|
||||
transformFigmaIds,
|
||||
transformFills,
|
||||
transformLayoutAttributes,
|
||||
transformProportion,
|
||||
transformRotationAndPosition,
|
||||
transformSceneNode,
|
||||
transformStrokes
|
||||
} from '@plugin/transformers/partials';
|
||||
|
@ -16,19 +17,19 @@ import { BoolShape } from '@ui/lib/types/shapes/boolShape';
|
|||
|
||||
export const transformBooleanNode = async (
|
||||
node: BooleanOperationNode,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
baseRotation: number
|
||||
): Promise<BoolShape> => {
|
||||
return {
|
||||
type: 'bool',
|
||||
name: node.name,
|
||||
boolType: translateBoolType(node.booleanOperation),
|
||||
...transformFigmaIds(node),
|
||||
...(await transformChildren(node, baseX, baseY)),
|
||||
...(await transformChildren(node, baseRotation)),
|
||||
...transformFills(node),
|
||||
...transformEffects(node),
|
||||
...transformStrokes(node),
|
||||
...transformDimensionAndPosition(node, baseX, baseY),
|
||||
...transformDimension(node),
|
||||
...transformRotationAndPosition(node, baseRotation),
|
||||
...transformSceneNode(node),
|
||||
...transformBlend(node),
|
||||
...transformProportion(node),
|
||||
|
|
|
@ -5,12 +5,13 @@ import {
|
|||
transformChildren,
|
||||
transformConstraints,
|
||||
transformCornerRadius,
|
||||
transformDimensionAndPosition,
|
||||
transformDimension,
|
||||
transformEffects,
|
||||
transformFigmaIds,
|
||||
transformFills,
|
||||
transformLayoutAttributes,
|
||||
transformProportion,
|
||||
transformRotationAndPosition,
|
||||
transformSceneNode,
|
||||
transformStrokes
|
||||
} from '@plugin/transformers/partials';
|
||||
|
@ -19,8 +20,7 @@ import { ComponentRoot } from '@ui/types';
|
|||
|
||||
export const transformComponentNode = async (
|
||||
node: ComponentNode,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
baseRotation: number
|
||||
): Promise<ComponentRoot> => {
|
||||
componentsLibrary.register(node.id, {
|
||||
type: 'component',
|
||||
|
@ -36,8 +36,9 @@ export const transformComponentNode = async (
|
|||
...transformProportion(node),
|
||||
...transformLayoutAttributes(node),
|
||||
...transformCornerRadius(node),
|
||||
...(await transformChildren(node, baseX + node.x, baseY + node.y)),
|
||||
...transformDimensionAndPosition(node, baseX, baseY),
|
||||
...(await transformChildren(node, node.rotation + baseRotation)),
|
||||
...transformDimension(node),
|
||||
...transformRotationAndPosition(node, baseRotation),
|
||||
...transformConstraints(node),
|
||||
...transformAutoLayout(node)
|
||||
});
|
||||
|
|
|
@ -14,11 +14,7 @@ import {
|
|||
|
||||
import { CircleShape } from '@ui/lib/types/shapes/circleShape';
|
||||
|
||||
export const transformEllipseNode = (
|
||||
node: EllipseNode,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
): CircleShape => {
|
||||
export const transformEllipseNode = (node: EllipseNode, baseRotation: number): CircleShape => {
|
||||
return {
|
||||
type: 'circle',
|
||||
name: node.name,
|
||||
|
@ -27,7 +23,7 @@ export const transformEllipseNode = (
|
|||
...transformEffects(node),
|
||||
...transformStrokes(node),
|
||||
...transformDimension(node),
|
||||
...transformRotationAndPosition(node, baseX, baseY),
|
||||
...transformRotationAndPosition(node, baseRotation),
|
||||
...transformSceneNode(node),
|
||||
...transformBlend(node),
|
||||
...transformProportion(node),
|
||||
|
|
|
@ -4,17 +4,19 @@ import {
|
|||
transformChildren,
|
||||
transformConstraints,
|
||||
transformCornerRadius,
|
||||
transformDimensionAndPosition,
|
||||
transformDimension,
|
||||
transformEffects,
|
||||
transformFigmaIds,
|
||||
transformFills,
|
||||
transformLayoutAttributes,
|
||||
transformProportion,
|
||||
transformRotationAndPosition,
|
||||
transformSceneNode,
|
||||
transformStrokes
|
||||
} from '@plugin/transformers/partials';
|
||||
|
||||
import { FrameShape } from '@ui/lib/types/shapes/frameShape';
|
||||
import { Point } from '@ui/lib/types/utils/point';
|
||||
|
||||
const isSectionNode = (node: FrameNode | SectionNode | ComponentSetNode): node is SectionNode => {
|
||||
return node.type === 'SECTION';
|
||||
|
@ -22,12 +24,18 @@ const isSectionNode = (node: FrameNode | SectionNode | ComponentSetNode): node i
|
|||
|
||||
export const transformFrameNode = async (
|
||||
node: FrameNode | SectionNode | ComponentSetNode,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
baseRotation: number
|
||||
): Promise<FrameShape> => {
|
||||
let frameSpecificAttributes: Partial<FrameShape> = {};
|
||||
let referencePoint: Point = { x: node.absoluteTransform[0][2], y: node.absoluteTransform[1][2] };
|
||||
let rotation = baseRotation;
|
||||
|
||||
if (!isSectionNode(node)) {
|
||||
const { x, y, ...transformAndRotation } = transformRotationAndPosition(node, baseRotation);
|
||||
|
||||
referencePoint = { x, y };
|
||||
rotation += node.rotation;
|
||||
|
||||
// Figma API does not expose strokes, blend modes, corner radius, or constraint proportions for sections,
|
||||
// they plan to add it in the future. Refactor this when available.
|
||||
frameSpecificAttributes = {
|
||||
|
@ -40,7 +48,8 @@ export const transformFrameNode = async (
|
|||
...transformCornerRadius(node),
|
||||
...transformEffects(node),
|
||||
...transformConstraints(node),
|
||||
...transformAutoLayout(node)
|
||||
...transformAutoLayout(node),
|
||||
...transformAndRotation
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -50,9 +59,10 @@ export const transformFrameNode = async (
|
|||
showContent: isSectionNode(node) ? true : !node.clipsContent,
|
||||
...transformFigmaIds(node),
|
||||
...transformFills(node),
|
||||
...referencePoint,
|
||||
...frameSpecificAttributes,
|
||||
...(await transformChildren(node, baseX + node.x, baseY + node.y)),
|
||||
...transformDimensionAndPosition(node, baseX, baseY),
|
||||
...transformDimension(node),
|
||||
...(await transformChildren(node, rotation)),
|
||||
...transformSceneNode(node)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import {
|
||||
transformBlend,
|
||||
transformDimensionAndPosition,
|
||||
transformDimension,
|
||||
transformEffects,
|
||||
transformFigmaIds,
|
||||
transformRotationAndPosition,
|
||||
transformSceneNode
|
||||
} from '@plugin/transformers/partials';
|
||||
import { transformChildren } from '@plugin/transformers/partials';
|
||||
|
@ -11,27 +12,26 @@ import { GroupShape } from '@ui/lib/types/shapes/groupShape';
|
|||
|
||||
export const transformGroupNode = async (
|
||||
node: GroupNode,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
baseRotation: number
|
||||
): Promise<GroupShape> => {
|
||||
return {
|
||||
...transformFigmaIds(node),
|
||||
...transformGroupNodeLike(node, baseX, baseY),
|
||||
...transformGroupNodeLike(node, baseRotation),
|
||||
...transformEffects(node),
|
||||
...transformBlend(node),
|
||||
...(await transformChildren(node, baseX, baseY))
|
||||
...(await transformChildren(node, baseRotation))
|
||||
};
|
||||
};
|
||||
|
||||
export const transformGroupNodeLike = (
|
||||
node: BaseNodeMixin & DimensionAndPositionMixin & SceneNodeMixin,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
node: BaseNodeMixin & LayoutMixin & SceneNodeMixin,
|
||||
baseRotation: number
|
||||
): GroupShape => {
|
||||
return {
|
||||
type: 'group',
|
||||
name: node.name,
|
||||
...transformDimensionAndPosition(node, baseX, baseY),
|
||||
...transformDimension(node),
|
||||
...transformRotationAndPosition(node, baseRotation),
|
||||
...transformSceneNode(node)
|
||||
};
|
||||
};
|
||||
|
|
|
@ -5,12 +5,13 @@ import {
|
|||
transformChildren,
|
||||
transformConstraints,
|
||||
transformCornerRadius,
|
||||
transformDimensionAndPosition,
|
||||
transformDimension,
|
||||
transformEffects,
|
||||
transformFigmaIds,
|
||||
transformFills,
|
||||
transformLayoutAttributes,
|
||||
transformProportion,
|
||||
transformRotationAndPosition,
|
||||
transformSceneNode,
|
||||
transformStrokes
|
||||
} from '@plugin/transformers/partials';
|
||||
|
@ -19,8 +20,7 @@ import { ComponentInstance } from '@ui/types';
|
|||
|
||||
export const transformInstanceNode = async (
|
||||
node: InstanceNode,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
baseRotation: number
|
||||
): Promise<ComponentInstance | undefined> => {
|
||||
const mainComponent = await node.getMainComponentAsync();
|
||||
|
||||
|
@ -47,10 +47,11 @@ export const transformInstanceNode = async (
|
|||
...transformProportion(node),
|
||||
...transformLayoutAttributes(node),
|
||||
...transformCornerRadius(node),
|
||||
...transformDimensionAndPosition(node, baseX, baseY),
|
||||
...transformDimension(node),
|
||||
...transformRotationAndPosition(node, baseRotation),
|
||||
...transformConstraints(node),
|
||||
...transformAutoLayout(node),
|
||||
...(await transformChildren(node, baseX + node.x, baseY + node.y))
|
||||
...(await transformChildren(node, node.rotation + baseRotation))
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
transformEffects,
|
||||
transformFigmaIds,
|
||||
transformLayoutAttributes,
|
||||
transformPosition,
|
||||
transformProportion,
|
||||
transformSceneNode,
|
||||
transformStrokes
|
||||
|
@ -19,15 +18,14 @@ import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
|||
*
|
||||
* 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 => {
|
||||
export const transformLineNode = (node: LineNode, baseRotation: number): PathShape => {
|
||||
return {
|
||||
type: 'path',
|
||||
name: node.name,
|
||||
content: translateLineNode(node, baseX, baseY),
|
||||
content: translateLineNode(node, baseRotation),
|
||||
...transformFigmaIds(node),
|
||||
...transformStrokes(node),
|
||||
...transformEffects(node),
|
||||
...transformPosition(node, baseX, baseY),
|
||||
...transformSceneNode(node),
|
||||
...transformBlend(node),
|
||||
...transformProportion(node),
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
transformBlend,
|
||||
transformConstraints,
|
||||
transformDimensionAndPosition,
|
||||
transformEffects,
|
||||
transformFigmaIds,
|
||||
transformFills,
|
||||
|
@ -10,24 +9,22 @@ import {
|
|||
transformSceneNode,
|
||||
transformStrokes
|
||||
} from '@plugin/transformers/partials';
|
||||
import { translateVectorPaths } from '@plugin/translators/vectors';
|
||||
import { translatePathNode } from '@plugin/translators/vectors';
|
||||
|
||||
import { PathShape } from '@ui/lib/types/shapes/pathShape';
|
||||
|
||||
export const transformPathNode = (
|
||||
node: StarNode | PolygonNode,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
baseRotation: number
|
||||
): PathShape => {
|
||||
return {
|
||||
type: 'path',
|
||||
name: node.name,
|
||||
content: translateVectorPaths(node.fillGeometry, baseX + node.x, baseY + node.y),
|
||||
content: translatePathNode(node, baseRotation),
|
||||
...transformFigmaIds(node),
|
||||
...transformFills(node),
|
||||
...transformStrokes(node),
|
||||
...transformEffects(node),
|
||||
...transformDimensionAndPosition(node, baseX, baseY),
|
||||
...transformSceneNode(node),
|
||||
...transformBlend(node),
|
||||
...transformProportion(node),
|
||||
|
|
|
@ -15,11 +15,7 @@ import {
|
|||
|
||||
import { RectShape } from '@ui/lib/types/shapes/rectShape';
|
||||
|
||||
export const transformRectangleNode = (
|
||||
node: RectangleNode,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
): RectShape => {
|
||||
export const transformRectangleNode = (node: RectangleNode, baseRotation: number): RectShape => {
|
||||
return {
|
||||
type: 'rect',
|
||||
name: node.name,
|
||||
|
@ -28,7 +24,7 @@ export const transformRectangleNode = (
|
|||
...transformEffects(node),
|
||||
...transformStrokes(node),
|
||||
...transformDimension(node),
|
||||
...transformRotationAndPosition(node, baseX, baseY),
|
||||
...transformRotationAndPosition(node, baseRotation),
|
||||
...transformSceneNode(node),
|
||||
...transformBlend(node),
|
||||
...transformProportion(node),
|
||||
|
|
|
@ -16,8 +16,7 @@ import {
|
|||
|
||||
export const transformSceneNode = async (
|
||||
node: SceneNode,
|
||||
baseX: number = 0,
|
||||
baseY: number = 0
|
||||
baseRotation: number = 0
|
||||
): Promise<PenpotNode | undefined> => {
|
||||
let penpotNode: PenpotNode | undefined;
|
||||
|
||||
|
@ -28,40 +27,40 @@ export const transformSceneNode = async (
|
|||
|
||||
switch (node.type) {
|
||||
case 'RECTANGLE':
|
||||
penpotNode = transformRectangleNode(node, baseX, baseY);
|
||||
penpotNode = transformRectangleNode(node, baseRotation);
|
||||
break;
|
||||
case 'ELLIPSE':
|
||||
penpotNode = transformEllipseNode(node, baseX, baseY);
|
||||
penpotNode = transformEllipseNode(node, baseRotation);
|
||||
break;
|
||||
case 'SECTION':
|
||||
case 'FRAME':
|
||||
case 'COMPONENT_SET':
|
||||
penpotNode = await transformFrameNode(node, baseX, baseY);
|
||||
penpotNode = await transformFrameNode(node, baseRotation);
|
||||
break;
|
||||
case 'GROUP':
|
||||
penpotNode = await transformGroupNode(node, baseX, baseY);
|
||||
penpotNode = await transformGroupNode(node, baseRotation);
|
||||
break;
|
||||
case 'TEXT':
|
||||
penpotNode = transformTextNode(node, baseX, baseY);
|
||||
penpotNode = transformTextNode(node, baseRotation);
|
||||
break;
|
||||
case 'VECTOR':
|
||||
penpotNode = transformVectorNode(node, baseX, baseY);
|
||||
penpotNode = transformVectorNode(node, baseRotation);
|
||||
break;
|
||||
case 'LINE':
|
||||
penpotNode = transformLineNode(node, baseX, baseY);
|
||||
penpotNode = transformLineNode(node, baseRotation);
|
||||
break;
|
||||
case 'STAR':
|
||||
case 'POLYGON':
|
||||
penpotNode = transformPathNode(node, baseX, baseY);
|
||||
penpotNode = transformPathNode(node, baseRotation);
|
||||
break;
|
||||
case 'BOOLEAN_OPERATION':
|
||||
penpotNode = await transformBooleanNode(node, baseX, baseY);
|
||||
penpotNode = await transformBooleanNode(node, baseRotation);
|
||||
break;
|
||||
case 'COMPONENT':
|
||||
penpotNode = await transformComponentNode(node, baseX, baseY);
|
||||
penpotNode = await transformComponentNode(node, baseRotation);
|
||||
break;
|
||||
case 'INSTANCE':
|
||||
penpotNode = await transformInstanceNode(node, baseX, baseY);
|
||||
penpotNode = await transformInstanceNode(node, baseRotation);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import {
|
||||
transformBlend,
|
||||
transformConstraints,
|
||||
transformDimensionAndPosition,
|
||||
transformDimension,
|
||||
transformEffects,
|
||||
transformFigmaIds,
|
||||
transformLayoutAttributes,
|
||||
transformProportion,
|
||||
transformRotationAndPosition,
|
||||
transformSceneNode,
|
||||
transformStrokes,
|
||||
transformText
|
||||
|
@ -13,13 +14,14 @@ import {
|
|||
|
||||
import { TextShape } from '@ui/lib/types/shapes/textShape';
|
||||
|
||||
export const transformTextNode = (node: TextNode, baseX: number, baseY: number): TextShape => {
|
||||
export const transformTextNode = (node: TextNode, baseRotation: number): TextShape => {
|
||||
return {
|
||||
type: 'text',
|
||||
name: node.name,
|
||||
...transformFigmaIds(node),
|
||||
...transformText(node),
|
||||
...transformDimensionAndPosition(node, baseX, baseY),
|
||||
...transformDimension(node),
|
||||
...transformRotationAndPosition(node, baseRotation),
|
||||
...transformEffects(node),
|
||||
...transformSceneNode(node),
|
||||
...transformBlend(node),
|
||||
|
|
|
@ -17,10 +17,9 @@ import { transformGroupNodeLike } from '.';
|
|||
*/
|
||||
export const transformVectorNode = (
|
||||
node: VectorNode,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
baseRotation: number
|
||||
): GroupShape | PathShape => {
|
||||
const children = transformVectorPaths(node, baseX, baseY);
|
||||
const children = transformVectorPaths(node);
|
||||
|
||||
if (children.length === 1) {
|
||||
return {
|
||||
|
@ -32,7 +31,7 @@ export const transformVectorNode = (
|
|||
}
|
||||
|
||||
return {
|
||||
...transformGroupNodeLike(node, baseX, baseY),
|
||||
...transformGroupNodeLike(node, baseRotation),
|
||||
...transformFigmaIds(node),
|
||||
...transformConstraints(node),
|
||||
children
|
||||
|
|
|
@ -17,16 +17,31 @@ import { PenpotNode } from '@ui/types';
|
|||
export const translateMaskChildren = async (
|
||||
children: readonly SceneNode[],
|
||||
maskIndex: number,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
baseRotation: number
|
||||
): Promise<PenpotNode[]> => {
|
||||
const maskChild = children[maskIndex];
|
||||
const unmaskedChildren = await translateChildren(children.slice(0, maskIndex), baseX, baseY);
|
||||
const maskedChildren = await translateChildren(children.slice(maskIndex), baseX, baseY);
|
||||
|
||||
const unmaskedChildren = await translateChildren(children.slice(0, maskIndex), baseRotation);
|
||||
const maskedChildren = await translateChildren(children.slice(maskIndex), baseRotation);
|
||||
|
||||
if (
|
||||
maskChild.type === 'STICKY' ||
|
||||
maskChild.type === 'CONNECTOR' ||
|
||||
maskChild.type === 'CODE_BLOCK' ||
|
||||
maskChild.type === 'WIDGET' ||
|
||||
maskChild.type === 'EMBED' ||
|
||||
maskChild.type === 'LINK_UNFURL' ||
|
||||
maskChild.type === 'MEDIA' ||
|
||||
maskChild.type === 'SECTION' ||
|
||||
maskChild.type === 'TABLE' ||
|
||||
maskChild.type === 'SHAPE_WITH_TEXT'
|
||||
) {
|
||||
return [...unmaskedChildren, ...maskedChildren];
|
||||
}
|
||||
|
||||
const maskGroup = {
|
||||
...transformMaskFigmaIds(maskChild),
|
||||
...transformGroupNodeLike(maskChild, baseX, baseY),
|
||||
...transformGroupNodeLike(maskChild, baseRotation),
|
||||
children: maskedChildren,
|
||||
maskedGroup: true
|
||||
};
|
||||
|
@ -36,13 +51,12 @@ export const translateMaskChildren = async (
|
|||
|
||||
export const translateChildren = async (
|
||||
children: readonly SceneNode[],
|
||||
baseX: number = 0,
|
||||
baseY: number = 0
|
||||
baseRotation: number = 0
|
||||
): Promise<PenpotNode[]> => {
|
||||
const transformedChildren: PenpotNode[] = [];
|
||||
|
||||
for (const child of children) {
|
||||
const penpotNode = await transformSceneNode(child, baseX, baseY);
|
||||
const penpotNode = await transformSceneNode(child, baseRotation);
|
||||
|
||||
if (penpotNode) transformedChildren.push(penpotNode);
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
export * from './translateCommandsToSegments';
|
||||
export * from './translateLineNode';
|
||||
export * from './translateVectorPaths';
|
||||
export * from './translatePathNode';
|
||||
export * from './translateWindingRule';
|
||||
|
|
|
@ -4,8 +4,8 @@ import { Segment } from '@ui/lib/types/shapes/pathShape';
|
|||
|
||||
export const translateCommandsToSegments = (
|
||||
commands: Command[],
|
||||
baseX: number,
|
||||
baseY: number
|
||||
baseX: number = 0,
|
||||
baseY: number = 0
|
||||
): Segment[] => {
|
||||
return commands.map(command => {
|
||||
switch (command.command) {
|
||||
|
|
|
@ -6,8 +6,12 @@ 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) {
|
||||
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(
|
||||
[
|
||||
{
|
||||
|
@ -23,27 +27,27 @@ export const translateLineNode = (node: LineNode, baseX: number, baseY: number):
|
|||
code: 'L'
|
||||
}
|
||||
],
|
||||
baseX + node.x,
|
||||
baseY + node.y
|
||||
x,
|
||||
y
|
||||
);
|
||||
}
|
||||
|
||||
const startPoint = applyRotation(
|
||||
{ x: 0, y: 0 },
|
||||
const referencePoint = applyInverseRotation(
|
||||
{ x, y },
|
||||
node.absoluteTransform,
|
||||
node.absoluteBoundingBox
|
||||
);
|
||||
|
||||
const endPoint = applyRotation(
|
||||
{ x: node.width, y: 0 },
|
||||
{ x: referencePoint.x + node.width, y: referencePoint.y },
|
||||
node.absoluteTransform,
|
||||
node.absoluteBoundingBox
|
||||
);
|
||||
|
||||
const commands: Command[] = [
|
||||
{
|
||||
x: startPoint.x,
|
||||
y: startPoint.y,
|
||||
x,
|
||||
y,
|
||||
command: 'moveto',
|
||||
code: 'M'
|
||||
},
|
||||
|
@ -55,11 +59,5 @@ export const translateLineNode = (node: LineNode, baseX: number, baseY: number):
|
|||
}
|
||||
];
|
||||
|
||||
const referencePoint = applyInverseRotation(
|
||||
{ x: node.x, y: node.y },
|
||||
node.absoluteTransform,
|
||||
node.absoluteBoundingBox
|
||||
);
|
||||
|
||||
return translateCommandsToSegments(commands, baseX + referencePoint.x, baseY + referencePoint.y);
|
||||
return translateCommandsToSegments(commands);
|
||||
};
|
||||
|
|
37
plugin-src/translators/vectors/translatePathNode.ts
Normal file
37
plugin-src/translators/vectors/translatePathNode.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
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);
|
|
@ -1,19 +0,0 @@
|
|||
import { parseSVG } from 'svg-path-parser';
|
||||
|
||||
import { Segment } from '@ui/lib/types/shapes/pathShape';
|
||||
|
||||
import { translateCommandsToSegments } from '.';
|
||||
|
||||
export const translateVectorPaths = (
|
||||
paths: VectorPaths,
|
||||
baseX: number,
|
||||
baseY: number
|
||||
): Segment[] => {
|
||||
const segments: Segment[] = [];
|
||||
|
||||
for (const path of paths) {
|
||||
segments.push(...translateCommandsToSegments(parseSVG(path.data), baseX, baseY));
|
||||
}
|
||||
|
||||
return segments;
|
||||
};
|
|
@ -1,25 +0,0 @@
|
|||
import { parseSVG } from 'svg-path-parser';
|
||||
|
||||
type BoundingBox = { x1: number; y1: number; x2: number; y2: number };
|
||||
|
||||
export const getBoundingBox = (vectorPath: VectorPath): BoundingBox => {
|
||||
const path = parseSVG(vectorPath.data);
|
||||
|
||||
if (!path.length) return { x1: 0, y1: 0, x2: 0, y2: 0 };
|
||||
|
||||
const bounds = { x1: Infinity, y1: Infinity, x2: -Infinity, y2: -Infinity };
|
||||
|
||||
for (const points of path) {
|
||||
switch (points.code) {
|
||||
case 'M':
|
||||
case 'L':
|
||||
case 'C':
|
||||
bounds.x1 = Math.min(bounds.x1, points.x);
|
||||
bounds.y1 = Math.min(bounds.y1, points.y);
|
||||
bounds.x2 = Math.max(bounds.x2, points.x);
|
||||
bounds.y2 = Math.max(bounds.y2, points.y);
|
||||
}
|
||||
}
|
||||
|
||||
return bounds;
|
||||
};
|
|
@ -3,7 +3,6 @@ export * from './applyRotation';
|
|||
export * from './calculateAdjustment';
|
||||
export * from './calculateLinearGradient';
|
||||
export * from './calculateRadialGradient';
|
||||
export * from './getBoundingBox';
|
||||
export * from './matrixInvert';
|
||||
export * from './rgbToHex';
|
||||
export * from './sleep';
|
||||
|
|
Loading…
Add table
Reference in a new issue