0
Fork 0
mirror of https://github.com/penpot/penpot-exporter-figma-plugin.git synced 2024-12-22 13:43:03 -05:00

Vectors - Line & Arrow (#37)

* lines & arrows draft

* simplify vectors

* try to refactor

* remove comments

* add more clarity to code

* fix translate strokes

* minor style fix

* fix for vectors without geometry

* reduce code

---------

Co-authored-by: Alex Sánchez <alejandro@runroom.com>
Co-authored-by: Jordi Sala Morales <jordism91@gmail.com>
This commit is contained in:
Alex Sánchez 2024-04-17 10:53:38 +02:00 committed by GitHub
parent 73ccf83657
commit c9f8a0dcd2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 126 additions and 53 deletions

View file

@ -4,8 +4,7 @@ export * from './transformFrameNode';
export * from './transformGroupNode';
export * from './transformImageNode';
export * from './transformPageNode';
export * from './transformPolygonNode';
export * from './transformPathNode';
export * from './transformRectangleNode';
export * from './transformSceneNode';
export * from './transformTextNode';
export * from './transformVectorNode';

View file

@ -2,3 +2,5 @@ export * from './transformBlend';
export * from './transformChildren';
export * from './transformDimensionAndPosition';
export * from './transformSceneNode';
export * from './transformStrokes';
export * from './transformVectorPaths';

View file

@ -0,0 +1,24 @@
import { translateStrokes } from '@plugin/translators';
import { ShapeAttributes } from '@ui/lib/types/shape/shapeAttributes';
const isVectorLike = (node: GeometryMixin | VectorLikeMixin): node is VectorLikeMixin => {
return 'vectorNetwork' in node;
};
const hasFillGeometry = (node: GeometryMixin | (GeometryMixin & VectorLikeMixin)): boolean => {
return node.fillGeometry.length > 0;
};
export const transformStrokes = (
node: GeometryMixin | (GeometryMixin & VectorLikeMixin)
): Partial<ShapeAttributes> => {
return {
strokes: translateStrokes(
node.strokes,
node.strokeWeight,
hasFillGeometry(node),
isVectorLike(node) ? node.vectorNetwork : undefined
)
};
};

View file

@ -0,0 +1,28 @@
import { translateVectorPaths } from '@plugin/translators';
import { PathAttributes } from '@ui/lib/types/path/pathAttributes';
const getVectorPaths = (node: VectorNode | StarNode | LineNode | PolygonNode): VectorPaths => {
switch (node.type) {
case 'STAR':
case 'POLYGON':
return node.fillGeometry;
case 'VECTOR':
return node.vectorPaths;
case 'LINE':
return node.strokeGeometry;
}
};
export const transformVectorPaths = (
node: VectorNode | StarNode | LineNode | PolygonNode,
baseX: number,
baseY: number
): PathAttributes => {
const vectorPaths = getVectorPaths(node);
return {
type: 'path',
content: translateVectorPaths(vectorPaths, baseX + node.x, baseY + node.y)
};
};

View file

@ -1,9 +1,10 @@
import {
transformBlend,
transformDimensionAndPosition,
transformSceneNode
transformSceneNode,
transformStrokes
} from '@plugin/transformers/partials';
import { translateFills, translateStrokes } from '@plugin/translators';
import { translateFills } from '@plugin/translators';
import { CircleShape } from '@ui/lib/types/circle/circleShape';
@ -16,7 +17,7 @@ export const transformEllipseNode = (
type: 'circle',
name: node.name,
fills: translateFills(node.fills, node.width, node.height),
strokes: translateStrokes(node),
...transformStrokes(node),
...transformDimensionAndPosition(node, baseX, baseY),
...transformSceneNode(node),
...transformBlend(node)

View file

@ -1,6 +1,10 @@
import { transformDimensionAndPosition, transformSceneNode } from '@plugin/transformers/partials';
import {
transformDimensionAndPosition,
transformSceneNode,
transformStrokes
} from '@plugin/transformers/partials';
import { transformChildren } from '@plugin/transformers/partials';
import { translateFills, translateStrokes } from '@plugin/translators';
import { translateFills } from '@plugin/translators';
import { FrameShape } from '@ui/lib/types/frame/frameShape';
@ -13,7 +17,7 @@ export const transformFrameNode = async (
type: 'frame',
name: node.name,
fills: translateFills(node.fills, node.width, node.height),
strokes: translateStrokes(node),
...transformStrokes(node),
...(await transformChildren(node, baseX, baseY)),
...transformDimensionAndPosition(node, baseX, baseY),
...transformSceneNode(node)

View file

@ -1,19 +1,24 @@
import {
transformBlend,
transformDimensionAndPosition,
transformSceneNode
transformSceneNode,
transformStrokes,
transformVectorPaths
} from '@plugin/transformers/partials';
import { translateFills, translateStrokes, translateVectorPaths } from '@plugin/translators';
import { translateFills } from '@plugin/translators';
import { PathShape } from '@ui/lib/types/path/pathShape';
export const transformVectorNode = (node: VectorNode, baseX: number, baseY: number): PathShape => {
export const transformPathNode = (
node: VectorNode | StarNode | LineNode | PolygonNode,
baseX: number,
baseY: number
): PathShape => {
return {
type: 'path',
name: node.name,
fills: node.fillGeometry.length ? translateFills(node.fills, node.width, node.height) : [],
content: translateVectorPaths(node.vectorPaths, baseX + node.x, baseY + node.y),
strokes: translateStrokes(node),
...transformStrokes(node),
...transformVectorPaths(node, baseX, baseY),
...transformDimensionAndPosition(node, baseX, baseY),
...transformSceneNode(node),
...transformBlend(node)

View file

@ -1,25 +0,0 @@
import {
transformBlend,
transformDimensionAndPosition,
transformSceneNode
} from '@plugin/transformers/partials';
import { translateFills, translateStrokes, translateVectorPaths } from '@plugin/translators';
import { PathShape } from '@ui/lib/types/path/pathShape';
export const transformPolygonNode = (
node: DefaultShapeMixin,
baseX: number,
baseY: number
): PathShape => {
return {
type: 'path',
name: node.name,
content: translateVectorPaths(node.fillGeometry, baseX + node.x, baseY + node.y),
strokes: translateStrokes(node),
fills: translateFills(node.fills, node.width, node.height),
...transformDimensionAndPosition(node, baseX, baseY),
...transformSceneNode(node),
...transformBlend(node)
};
};

View file

@ -1,9 +1,10 @@
import {
transformBlend,
transformDimensionAndPosition,
transformSceneNode
transformSceneNode,
transformStrokes
} from '@plugin/transformers/partials';
import { translateFills, translateStrokes } from '@plugin/translators';
import { translateFills } from '@plugin/translators';
import { RectShape } from '@ui/lib/types/rect/rectShape';
@ -16,7 +17,7 @@ export const transformRectangleNode = (
type: 'rect',
name: node.name,
fills: translateFills(node.fills, node.width, node.height),
strokes: translateStrokes(node),
...transformStrokes(node),
...transformDimensionAndPosition(node, baseX, baseY),
...transformSceneNode(node),
...transformBlend(node)

View file

@ -7,10 +7,9 @@ import {
transformFrameNode,
transformGroupNode,
transformImageNode,
transformPolygonNode,
transformPathNode,
transformRectangleNode,
transformTextNode,
transformVectorNode
transformTextNode
} from '.';
export const transformSceneNode = async (
@ -44,9 +43,9 @@ export const transformSceneNode = async (
return transformTextNode(node, baseX, baseY);
case 'STAR':
case 'POLYGON':
return transformPolygonNode(node, baseX, baseY);
case 'VECTOR':
return transformVectorNode(node, baseX, baseY);
case 'LINE':
return transformPathNode(node, baseX, baseY);
}
throw new Error(`Unsupported node type: ${node.type}`);

View file

@ -1,14 +1,49 @@
import { translateFill } from '@plugin/translators/translateFills';
import { Stroke } from '@ui/lib/types/utils/stroke';
import { Stroke, StrokeCaps } from '@ui/lib/types/utils/stroke';
export const translateStrokes = (node: MinimalStrokesMixin): Stroke[] => {
return node.strokes.map(stroke => {
const fill = translateFill(stroke, 0, 0);
return {
export const translateStrokes = (
paints: readonly Paint[],
strokeWeight: number | typeof figma.mixed,
hasFillGeometry?: boolean,
vectorNetwork?: VectorNetwork
): Stroke[] => {
return paints.map((paint, index) => {
const fill = translateFill(paint, 0, 0);
const stroke: Stroke = {
strokeColor: fill?.fillColor,
strokeOpacity: fill?.fillOpacity,
strokeWidth: node.strokeWeight === figma.mixed ? 1 : node.strokeWeight
strokeWidth: strokeWeight === figma.mixed ? 1 : strokeWeight
};
if (!hasFillGeometry && index === 0 && vectorNetwork && vectorNetwork.vertices.length) {
stroke.strokeCapStart = translateStrokeCap(vectorNetwork.vertices[0]);
stroke.strokeCapEnd = translateStrokeCap(
vectorNetwork.vertices[vectorNetwork.vertices.length - 1]
);
}
return stroke;
});
};
const translateStrokeCap = (vertex: VectorVertex): StrokeCaps | undefined => {
switch (vertex.strokeCap as StrokeCap | ConnectorStrokeCap) {
case 'NONE':
return;
case 'ROUND':
return 'round';
case 'ARROW_EQUILATERAL':
case 'TRIANGLE_FILLED':
return 'triangle-arrow';
case 'SQUARE':
return 'square';
case 'CIRCLE_FILLED':
return 'circle-marker';
case 'DIAMOND_FILLED':
return 'diamond-marker';
case 'ARROW_LINES':
default:
return 'line-arrow';
}
};