mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2025-01-03 05:10:13 -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:
parent
73ccf83657
commit
c9f8a0dcd2
11 changed files with 126 additions and 53 deletions
|
@ -4,8 +4,7 @@ export * from './transformFrameNode';
|
||||||
export * from './transformGroupNode';
|
export * from './transformGroupNode';
|
||||||
export * from './transformImageNode';
|
export * from './transformImageNode';
|
||||||
export * from './transformPageNode';
|
export * from './transformPageNode';
|
||||||
export * from './transformPolygonNode';
|
export * from './transformPathNode';
|
||||||
export * from './transformRectangleNode';
|
export * from './transformRectangleNode';
|
||||||
export * from './transformSceneNode';
|
export * from './transformSceneNode';
|
||||||
export * from './transformTextNode';
|
export * from './transformTextNode';
|
||||||
export * from './transformVectorNode';
|
|
||||||
|
|
|
@ -2,3 +2,5 @@ export * from './transformBlend';
|
||||||
export * from './transformChildren';
|
export * from './transformChildren';
|
||||||
export * from './transformDimensionAndPosition';
|
export * from './transformDimensionAndPosition';
|
||||||
export * from './transformSceneNode';
|
export * from './transformSceneNode';
|
||||||
|
export * from './transformStrokes';
|
||||||
|
export * from './transformVectorPaths';
|
||||||
|
|
24
plugin-src/transformers/partials/transformStrokes.ts
Normal file
24
plugin-src/transformers/partials/transformStrokes.ts
Normal 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
|
||||||
|
)
|
||||||
|
};
|
||||||
|
};
|
28
plugin-src/transformers/partials/transformVectorPaths.ts
Normal file
28
plugin-src/transformers/partials/transformVectorPaths.ts
Normal 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)
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,9 +1,10 @@
|
||||||
import {
|
import {
|
||||||
transformBlend,
|
transformBlend,
|
||||||
transformDimensionAndPosition,
|
transformDimensionAndPosition,
|
||||||
transformSceneNode
|
transformSceneNode,
|
||||||
|
transformStrokes
|
||||||
} from '@plugin/transformers/partials';
|
} from '@plugin/transformers/partials';
|
||||||
import { translateFills, translateStrokes } from '@plugin/translators';
|
import { translateFills } from '@plugin/translators';
|
||||||
|
|
||||||
import { CircleShape } from '@ui/lib/types/circle/circleShape';
|
import { CircleShape } from '@ui/lib/types/circle/circleShape';
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ export const transformEllipseNode = (
|
||||||
type: 'circle',
|
type: 'circle',
|
||||||
name: node.name,
|
name: node.name,
|
||||||
fills: translateFills(node.fills, node.width, node.height),
|
fills: translateFills(node.fills, node.width, node.height),
|
||||||
strokes: translateStrokes(node),
|
...transformStrokes(node),
|
||||||
...transformDimensionAndPosition(node, baseX, baseY),
|
...transformDimensionAndPosition(node, baseX, baseY),
|
||||||
...transformSceneNode(node),
|
...transformSceneNode(node),
|
||||||
...transformBlend(node)
|
...transformBlend(node)
|
||||||
|
|
|
@ -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 { transformChildren } from '@plugin/transformers/partials';
|
||||||
import { translateFills, translateStrokes } from '@plugin/translators';
|
import { translateFills } from '@plugin/translators';
|
||||||
|
|
||||||
import { FrameShape } from '@ui/lib/types/frame/frameShape';
|
import { FrameShape } from '@ui/lib/types/frame/frameShape';
|
||||||
|
|
||||||
|
@ -13,7 +17,7 @@ export const transformFrameNode = async (
|
||||||
type: 'frame',
|
type: 'frame',
|
||||||
name: node.name,
|
name: node.name,
|
||||||
fills: translateFills(node.fills, node.width, node.height),
|
fills: translateFills(node.fills, node.width, node.height),
|
||||||
strokes: translateStrokes(node),
|
...transformStrokes(node),
|
||||||
...(await transformChildren(node, baseX, baseY)),
|
...(await transformChildren(node, baseX, baseY)),
|
||||||
...transformDimensionAndPosition(node, baseX, baseY),
|
...transformDimensionAndPosition(node, baseX, baseY),
|
||||||
...transformSceneNode(node)
|
...transformSceneNode(node)
|
||||||
|
|
|
@ -1,19 +1,24 @@
|
||||||
import {
|
import {
|
||||||
transformBlend,
|
transformBlend,
|
||||||
transformDimensionAndPosition,
|
transformDimensionAndPosition,
|
||||||
transformSceneNode
|
transformSceneNode,
|
||||||
|
transformStrokes,
|
||||||
|
transformVectorPaths
|
||||||
} from '@plugin/transformers/partials';
|
} from '@plugin/transformers/partials';
|
||||||
import { translateFills, translateStrokes, translateVectorPaths } from '@plugin/translators';
|
import { translateFills } from '@plugin/translators';
|
||||||
|
|
||||||
import { PathShape } from '@ui/lib/types/path/pathShape';
|
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 {
|
return {
|
||||||
type: 'path',
|
|
||||||
name: node.name,
|
name: node.name,
|
||||||
fills: node.fillGeometry.length ? translateFills(node.fills, node.width, node.height) : [],
|
fills: node.fillGeometry.length ? translateFills(node.fills, node.width, node.height) : [],
|
||||||
content: translateVectorPaths(node.vectorPaths, baseX + node.x, baseY + node.y),
|
...transformStrokes(node),
|
||||||
strokes: translateStrokes(node),
|
...transformVectorPaths(node, baseX, baseY),
|
||||||
...transformDimensionAndPosition(node, baseX, baseY),
|
...transformDimensionAndPosition(node, baseX, baseY),
|
||||||
...transformSceneNode(node),
|
...transformSceneNode(node),
|
||||||
...transformBlend(node)
|
...transformBlend(node)
|
|
@ -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)
|
|
||||||
};
|
|
||||||
};
|
|
|
@ -1,9 +1,10 @@
|
||||||
import {
|
import {
|
||||||
transformBlend,
|
transformBlend,
|
||||||
transformDimensionAndPosition,
|
transformDimensionAndPosition,
|
||||||
transformSceneNode
|
transformSceneNode,
|
||||||
|
transformStrokes
|
||||||
} from '@plugin/transformers/partials';
|
} from '@plugin/transformers/partials';
|
||||||
import { translateFills, translateStrokes } from '@plugin/translators';
|
import { translateFills } from '@plugin/translators';
|
||||||
|
|
||||||
import { RectShape } from '@ui/lib/types/rect/rectShape';
|
import { RectShape } from '@ui/lib/types/rect/rectShape';
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ export const transformRectangleNode = (
|
||||||
type: 'rect',
|
type: 'rect',
|
||||||
name: node.name,
|
name: node.name,
|
||||||
fills: translateFills(node.fills, node.width, node.height),
|
fills: translateFills(node.fills, node.width, node.height),
|
||||||
strokes: translateStrokes(node),
|
...transformStrokes(node),
|
||||||
...transformDimensionAndPosition(node, baseX, baseY),
|
...transformDimensionAndPosition(node, baseX, baseY),
|
||||||
...transformSceneNode(node),
|
...transformSceneNode(node),
|
||||||
...transformBlend(node)
|
...transformBlend(node)
|
||||||
|
|
|
@ -7,10 +7,9 @@ import {
|
||||||
transformFrameNode,
|
transformFrameNode,
|
||||||
transformGroupNode,
|
transformGroupNode,
|
||||||
transformImageNode,
|
transformImageNode,
|
||||||
transformPolygonNode,
|
transformPathNode,
|
||||||
transformRectangleNode,
|
transformRectangleNode,
|
||||||
transformTextNode,
|
transformTextNode
|
||||||
transformVectorNode
|
|
||||||
} from '.';
|
} from '.';
|
||||||
|
|
||||||
export const transformSceneNode = async (
|
export const transformSceneNode = async (
|
||||||
|
@ -44,9 +43,9 @@ export const transformSceneNode = async (
|
||||||
return transformTextNode(node, baseX, baseY);
|
return transformTextNode(node, baseX, baseY);
|
||||||
case 'STAR':
|
case 'STAR':
|
||||||
case 'POLYGON':
|
case 'POLYGON':
|
||||||
return transformPolygonNode(node, baseX, baseY);
|
|
||||||
case 'VECTOR':
|
case 'VECTOR':
|
||||||
return transformVectorNode(node, baseX, baseY);
|
case 'LINE':
|
||||||
|
return transformPathNode(node, baseX, baseY);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(`Unsupported node type: ${node.type}`);
|
throw new Error(`Unsupported node type: ${node.type}`);
|
||||||
|
|
|
@ -1,14 +1,49 @@
|
||||||
import { translateFill } from '@plugin/translators/translateFills';
|
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[] => {
|
export const translateStrokes = (
|
||||||
return node.strokes.map(stroke => {
|
paints: readonly Paint[],
|
||||||
const fill = translateFill(stroke, 0, 0);
|
strokeWeight: number | typeof figma.mixed,
|
||||||
return {
|
hasFillGeometry?: boolean,
|
||||||
|
vectorNetwork?: VectorNetwork
|
||||||
|
): Stroke[] => {
|
||||||
|
return paints.map((paint, index) => {
|
||||||
|
const fill = translateFill(paint, 0, 0);
|
||||||
|
const stroke: Stroke = {
|
||||||
strokeColor: fill?.fillColor,
|
strokeColor: fill?.fillColor,
|
||||||
strokeOpacity: fill?.fillOpacity,
|
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';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Reference in a new issue