mirror of
https://github.com/penpot/penpot-exporter-figma-plugin.git
synced 2025-01-18 05:22:28 -05:00
Allow rotation for Ellipses and Rectangles (#122)
* Allow rotation for Ellipses, Rectangles and Texts * undo rotations for texts * Translate rotations for ellipses and rectangles
This commit is contained in:
parent
fa1d25aeab
commit
5d7263bdbf
17 changed files with 109 additions and 21 deletions
5
.changeset/polite-fishes-poke.md
Normal file
5
.changeset/polite-fishes-poke.md
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
"penpot-exporter": minor
|
||||||
|
---
|
||||||
|
|
||||||
|
Translate rotations for ellipses and rectangles
|
|
@ -5,6 +5,7 @@ export * from './transformDimensionAndPosition';
|
||||||
export * from './transformEffects';
|
export * from './transformEffects';
|
||||||
export * from './transformFills';
|
export * from './transformFills';
|
||||||
export * from './transformProportion';
|
export * from './transformProportion';
|
||||||
|
export * from './transformRotationAndPosition';
|
||||||
export * from './transformSceneNode';
|
export * from './transformSceneNode';
|
||||||
export * from './transformStrokes';
|
export * from './transformStrokes';
|
||||||
export * from './transformText';
|
export * from './transformText';
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
|
||||||
|
|
||||||
export const transformBlend = (
|
export const transformBlend = (
|
||||||
node: SceneNodeMixin & MinimalBlendMixin
|
node: SceneNodeMixin & MinimalBlendMixin
|
||||||
): Partial<ShapeAttributes> => {
|
): Pick<ShapeAttributes, 'blendMode' | 'opacity'> => {
|
||||||
return {
|
return {
|
||||||
blendMode: translateBlendMode(node.blendMode),
|
blendMode: translateBlendMode(node.blendMode),
|
||||||
opacity: !node.visible ? 0 : node.opacity // @TODO: check this. If we use the property hidden and it's hidden, it won't export
|
opacity: !node.visible ? 0 : node.opacity // @TODO: check this. If we use the property hidden and it's hidden, it won't export
|
||||||
|
|
|
@ -8,7 +8,7 @@ const isRectangleCornerMixin = (
|
||||||
|
|
||||||
export const transformCornerRadius = (
|
export const transformCornerRadius = (
|
||||||
node: CornerMixin | (CornerMixin & RectangleCornerMixin)
|
node: CornerMixin | (CornerMixin & RectangleCornerMixin)
|
||||||
): Partial<ShapeAttributes> | undefined => {
|
): Pick<ShapeAttributes, 'r1' | 'r2' | 'r3' | 'r4'> | Pick<ShapeAttributes, 'rx'> | undefined => {
|
||||||
if (isRectangleCornerMixin(node)) {
|
if (isRectangleCornerMixin(node)) {
|
||||||
return {
|
return {
|
||||||
r1: node.topLeftRadius,
|
r1: node.topLeftRadius,
|
||||||
|
|
|
@ -2,6 +2,15 @@ import { getBoundingBox } from '@plugin/utils';
|
||||||
|
|
||||||
import { ShapeGeomAttributes } from '@ui/lib/types/shapes/shape';
|
import { ShapeGeomAttributes } from '@ui/lib/types/shapes/shape';
|
||||||
|
|
||||||
|
export const transformDimension = (
|
||||||
|
node: DimensionAndPositionMixin
|
||||||
|
): Pick<ShapeGeomAttributes, 'width' | 'height'> => {
|
||||||
|
return {
|
||||||
|
width: node.width,
|
||||||
|
height: node.height
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const transformDimensionAndPosition = (
|
export const transformDimensionAndPosition = (
|
||||||
node: DimensionAndPositionMixin,
|
node: DimensionAndPositionMixin,
|
||||||
baseX: number,
|
baseX: number,
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { translateBlurEffects, translateShadowEffects } from '@plugin/translator
|
||||||
|
|
||||||
import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
|
import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
|
||||||
|
|
||||||
export const transformEffects = (node: BlendMixin): Partial<ShapeAttributes> => {
|
export const transformEffects = (node: BlendMixin): Pick<ShapeAttributes, 'shadow' | 'blur'> => {
|
||||||
return {
|
return {
|
||||||
shadow: translateShadowEffects(node.effects),
|
shadow: translateShadowEffects(node.effects),
|
||||||
blur: translateBlurEffects(node.effects)
|
blur: translateBlurEffects(node.effects)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
|
||||||
|
|
||||||
export const transformFills = async (
|
export const transformFills = async (
|
||||||
node: MinimalFillsMixin & DimensionAndPositionMixin
|
node: MinimalFillsMixin & DimensionAndPositionMixin
|
||||||
): Promise<Partial<ShapeAttributes>> => {
|
): Promise<Pick<ShapeAttributes, 'fills'>> => {
|
||||||
return {
|
return {
|
||||||
fills: await translateFills(node.fills)
|
fills: await translateFills(node.fills)
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
|
import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
|
||||||
|
|
||||||
export const transformProportion = (node: LayoutMixin): Partial<ShapeAttributes> => {
|
export const transformProportion = (node: LayoutMixin): Pick<ShapeAttributes, 'proportionLock'> => {
|
||||||
return {
|
return {
|
||||||
proportionLock: node.constrainProportions
|
proportionLock: node.constrainProportions
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,68 @@
|
||||||
|
import { ShapeBaseAttributes, ShapeGeomAttributes } from '@ui/lib/types/shapes/shape';
|
||||||
|
import { Point } from '@ui/lib/types/utils/point';
|
||||||
|
|
||||||
|
export const transformRotationAndPosition = (
|
||||||
|
node: LayoutMixin,
|
||||||
|
baseX: number,
|
||||||
|
baseY: number
|
||||||
|
): Pick<ShapeBaseAttributes, 'transform' | 'transformInverse' | 'rotation'> &
|
||||||
|
Pick<ShapeGeomAttributes, 'x' | 'y'> => {
|
||||||
|
const rotation = node.rotation;
|
||||||
|
const x = node.x + baseX;
|
||||||
|
const y = node.y + baseY;
|
||||||
|
|
||||||
|
if (rotation === 0 || !node.absoluteBoundingBox) {
|
||||||
|
return {
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
rotation,
|
||||||
|
transform: undefined,
|
||||||
|
transformInverse: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const point = getRotatedPoint({ x, y }, node.absoluteTransform, node.absoluteBoundingBox);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...point,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRotatedPoint = (point: Point, transform: Transform, boundingBox: Rect): Point => {
|
||||||
|
const centerPoint = {
|
||||||
|
x: boundingBox.x + boundingBox.width / 2,
|
||||||
|
y: boundingBox.y + boundingBox.height / 2
|
||||||
|
};
|
||||||
|
|
||||||
|
const relativePoint = {
|
||||||
|
x: point.x - centerPoint.x,
|
||||||
|
y: point.y - centerPoint.y
|
||||||
|
};
|
||||||
|
|
||||||
|
const rotatedPoint = {
|
||||||
|
x: relativePoint.x * transform[0][0] + relativePoint.y * transform[1][0],
|
||||||
|
y: relativePoint.x * transform[0][1] + relativePoint.y * transform[1][1]
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
x: centerPoint.x + rotatedPoint.x,
|
||||||
|
y: centerPoint.y + rotatedPoint.y
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,6 +1,8 @@
|
||||||
import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
|
import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
|
||||||
|
|
||||||
export const transformSceneNode = (node: SceneNodeMixin): Partial<ShapeAttributes> => {
|
export const transformSceneNode = (
|
||||||
|
node: SceneNodeMixin
|
||||||
|
): Pick<ShapeAttributes, 'blocked' | 'hidden'> => {
|
||||||
return {
|
return {
|
||||||
blocked: node.locked,
|
blocked: node.locked,
|
||||||
hidden: false // @TODO: check this. it won't export if we hide it
|
hidden: false // @TODO: check this. it won't export if we hide it
|
||||||
|
|
|
@ -15,7 +15,7 @@ const hasFillGeometry = (node: GeometryMixin): boolean => {
|
||||||
|
|
||||||
export const transformStrokes = async (
|
export const transformStrokes = async (
|
||||||
node: GeometryMixin | (GeometryMixin & IndividualStrokesMixin)
|
node: GeometryMixin | (GeometryMixin & IndividualStrokesMixin)
|
||||||
): Promise<Partial<ShapeAttributes>> => {
|
): Promise<Pick<ShapeAttributes, 'strokes'>> => {
|
||||||
const vectorNetwork = isVectorLike(node) ? node.vectorNetwork : undefined;
|
const vectorNetwork = isVectorLike(node) ? node.vectorNetwork : undefined;
|
||||||
|
|
||||||
const strokeCaps = (stroke: Stroke) => {
|
const strokeCaps = (stroke: Stroke) => {
|
||||||
|
@ -38,7 +38,7 @@ export const transformStrokesFromVector = async (
|
||||||
node: VectorNode,
|
node: VectorNode,
|
||||||
vector: Command[],
|
vector: Command[],
|
||||||
vectorRegion: VectorRegion | undefined
|
vectorRegion: VectorRegion | undefined
|
||||||
): Promise<Partial<ShapeAttributes>> => {
|
): Promise<Pick<ShapeAttributes, 'strokes'>> => {
|
||||||
const strokeCaps = (stroke: Stroke) => {
|
const strokeCaps = (stroke: Stroke) => {
|
||||||
if (vectorRegion !== undefined) return stroke;
|
if (vectorRegion !== undefined) return stroke;
|
||||||
|
|
||||||
|
|
|
@ -2,9 +2,11 @@ import { transformFills } from '@plugin/transformers/partials';
|
||||||
import { transformTextStyle, translateStyleTextSegments } from '@plugin/translators/text';
|
import { transformTextStyle, translateStyleTextSegments } from '@plugin/translators/text';
|
||||||
import { translateGrowType, translateVerticalAlign } from '@plugin/translators/text/properties';
|
import { translateGrowType, translateVerticalAlign } from '@plugin/translators/text/properties';
|
||||||
|
|
||||||
import { TextShape } from '@ui/lib/types/shapes/textShape';
|
import { TextAttributes, TextShape } from '@ui/lib/types/shapes/textShape';
|
||||||
|
|
||||||
export const transformText = async (node: TextNode): Promise<Partial<TextShape>> => {
|
export const transformText = async (
|
||||||
|
node: TextNode
|
||||||
|
): Promise<TextAttributes & Pick<TextShape, 'growType'>> => {
|
||||||
const styledTextSegments = node.getStyledTextSegments([
|
const styledTextSegments = node.getStyledTextSegments([
|
||||||
'fontName',
|
'fontName',
|
||||||
'fontSize',
|
'fontSize',
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
import {
|
import {
|
||||||
transformBlend,
|
transformBlend,
|
||||||
transformDimensionAndPosition,
|
transformDimension,
|
||||||
transformEffects,
|
transformEffects,
|
||||||
transformFills,
|
transformFills,
|
||||||
transformProportion,
|
transformProportion,
|
||||||
|
transformRotationAndPosition,
|
||||||
transformSceneNode,
|
transformSceneNode,
|
||||||
transformStrokes
|
transformStrokes
|
||||||
} from '@plugin/transformers/partials';
|
} from '@plugin/transformers/partials';
|
||||||
|
@ -21,7 +22,8 @@ export const transformEllipseNode = async (
|
||||||
...(await transformFills(node)),
|
...(await transformFills(node)),
|
||||||
...transformEffects(node),
|
...transformEffects(node),
|
||||||
...(await transformStrokes(node)),
|
...(await transformStrokes(node)),
|
||||||
...transformDimensionAndPosition(node, baseX, baseY),
|
...transformDimension(node),
|
||||||
|
...transformRotationAndPosition(node, baseX, baseY),
|
||||||
...transformSceneNode(node),
|
...transformSceneNode(node),
|
||||||
...transformBlend(node),
|
...transformBlend(node),
|
||||||
...transformProportion(node)
|
...transformProportion(node)
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import {
|
import {
|
||||||
transformBlend,
|
transformBlend,
|
||||||
transformCornerRadius,
|
transformCornerRadius,
|
||||||
transformDimensionAndPosition,
|
transformDimension,
|
||||||
transformEffects,
|
transformEffects,
|
||||||
transformFills,
|
transformFills,
|
||||||
transformProportion,
|
transformProportion,
|
||||||
|
transformRotationAndPosition,
|
||||||
transformSceneNode,
|
transformSceneNode,
|
||||||
transformStrokes
|
transformStrokes
|
||||||
} from '@plugin/transformers/partials';
|
} from '@plugin/transformers/partials';
|
||||||
|
@ -22,7 +23,8 @@ export const transformRectangleNode = async (
|
||||||
...(await transformFills(node)),
|
...(await transformFills(node)),
|
||||||
...transformEffects(node),
|
...transformEffects(node),
|
||||||
...(await transformStrokes(node)),
|
...(await transformStrokes(node)),
|
||||||
...transformDimensionAndPosition(node, baseX, baseY),
|
...transformDimension(node),
|
||||||
|
...transformRotationAndPosition(node, baseX, baseY),
|
||||||
...transformSceneNode(node),
|
...transformSceneNode(node),
|
||||||
...transformBlend(node),
|
...transformBlend(node),
|
||||||
...transformProportion(node),
|
...transformProportion(node),
|
||||||
|
|
|
@ -26,10 +26,7 @@ export const translateStyleTextSegments = async (
|
||||||
return translateParagraphProperties(node, partials);
|
return translateParagraphProperties(node, partials);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const transformTextStyle = (
|
export const transformTextStyle = (node: TextNode, segment: StyleTextSegment): TextStyle => {
|
||||||
node: TextNode,
|
|
||||||
segment: StyleTextSegment
|
|
||||||
): Partial<TextStyle> => {
|
|
||||||
return {
|
return {
|
||||||
...translateFontId(segment.fontName, segment.fontWeight),
|
...translateFontId(segment.fontName, segment.fontWeight),
|
||||||
fontFamily: segment.fontName.family,
|
fontFamily: segment.fontName.family,
|
||||||
|
|
|
@ -6,7 +6,7 @@ export const translateStrokes = async (
|
||||||
node: MinimalStrokesMixin | (MinimalStrokesMixin & IndividualStrokesMixin),
|
node: MinimalStrokesMixin | (MinimalStrokesMixin & IndividualStrokesMixin),
|
||||||
strokeCaps: (stroke: Stroke) => Stroke = stroke => stroke
|
strokeCaps: (stroke: Stroke) => Stroke = stroke => stroke
|
||||||
): Promise<Stroke[]> => {
|
): Promise<Stroke[]> => {
|
||||||
const sharedStrokeProperties: Partial<Stroke> = {
|
const sharedStrokeProperties: Stroke = {
|
||||||
strokeWidth: translateStrokeWeight(node),
|
strokeWidth: translateStrokeWeight(node),
|
||||||
strokeAlignment: translateStrokeAlignment(node.strokeAlign),
|
strokeAlignment: translateStrokeAlignment(node.strokeAlign),
|
||||||
strokeStyle: node.dashPattern.length ? 'dashed' : 'solid'
|
strokeStyle: node.dashPattern.length ? 'dashed' : 'solid'
|
||||||
|
@ -22,7 +22,7 @@ export const translateStrokes = async (
|
||||||
|
|
||||||
export const translateStroke = async (
|
export const translateStroke = async (
|
||||||
paint: Paint,
|
paint: Paint,
|
||||||
sharedStrokeProperties: Partial<Stroke>,
|
sharedStrokeProperties: Stroke,
|
||||||
strokeCaps: (stroke: Stroke) => Stroke,
|
strokeCaps: (stroke: Stroke) => Stroke,
|
||||||
firstStroke: boolean
|
firstStroke: boolean
|
||||||
): Promise<Stroke> => {
|
): Promise<Stroke> => {
|
||||||
|
|
|
@ -12,7 +12,7 @@ export type TextShape = ShapeBaseAttributes &
|
||||||
TextAttributes &
|
TextAttributes &
|
||||||
LayoutChildAttributes;
|
LayoutChildAttributes;
|
||||||
|
|
||||||
type TextAttributes = {
|
export type TextAttributes = {
|
||||||
type?: 'text';
|
type?: 'text';
|
||||||
content?: TextContent;
|
content?: TextContent;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue