0
Fork 0
mirror of https://github.com/penpot/penpot-exporter-figma-plugin.git synced 2024-12-22 05:33:02 -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:
Jordi Sala Morales 2024-05-29 10:30:56 +02:00 committed by GitHub
parent fa1d25aeab
commit 5d7263bdbf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 109 additions and 21 deletions

View file

@ -0,0 +1,5 @@
---
"penpot-exporter": minor
---
Translate rotations for ellipses and rectangles

View file

@ -5,6 +5,7 @@ export * from './transformDimensionAndPosition';
export * from './transformEffects';
export * from './transformFills';
export * from './transformProportion';
export * from './transformRotationAndPosition';
export * from './transformSceneNode';
export * from './transformStrokes';
export * from './transformText';

View file

@ -4,7 +4,7 @@ import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
export const transformBlend = (
node: SceneNodeMixin & MinimalBlendMixin
): Partial<ShapeAttributes> => {
): Pick<ShapeAttributes, 'blendMode' | 'opacity'> => {
return {
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

View file

@ -8,7 +8,7 @@ const isRectangleCornerMixin = (
export const transformCornerRadius = (
node: CornerMixin | (CornerMixin & RectangleCornerMixin)
): Partial<ShapeAttributes> | undefined => {
): Pick<ShapeAttributes, 'r1' | 'r2' | 'r3' | 'r4'> | Pick<ShapeAttributes, 'rx'> | undefined => {
if (isRectangleCornerMixin(node)) {
return {
r1: node.topLeftRadius,

View file

@ -2,6 +2,15 @@ import { getBoundingBox } from '@plugin/utils';
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 = (
node: DimensionAndPositionMixin,
baseX: number,

View file

@ -2,7 +2,7 @@ import { translateBlurEffects, translateShadowEffects } from '@plugin/translator
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 {
shadow: translateShadowEffects(node.effects),
blur: translateBlurEffects(node.effects)

View file

@ -4,7 +4,7 @@ import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
export const transformFills = async (
node: MinimalFillsMixin & DimensionAndPositionMixin
): Promise<Partial<ShapeAttributes>> => {
): Promise<Pick<ShapeAttributes, 'fills'>> => {
return {
fills: await translateFills(node.fills)
};

View file

@ -1,6 +1,6 @@
import { ShapeAttributes } from '@ui/lib/types/shapes/shape';
export const transformProportion = (node: LayoutMixin): Partial<ShapeAttributes> => {
export const transformProportion = (node: LayoutMixin): Pick<ShapeAttributes, 'proportionLock'> => {
return {
proportionLock: node.constrainProportions
};

View file

@ -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
};
};

View file

@ -1,6 +1,8 @@
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 {
blocked: node.locked,
hidden: false // @TODO: check this. it won't export if we hide it

View file

@ -15,7 +15,7 @@ const hasFillGeometry = (node: GeometryMixin): boolean => {
export const transformStrokes = async (
node: GeometryMixin | (GeometryMixin & IndividualStrokesMixin)
): Promise<Partial<ShapeAttributes>> => {
): Promise<Pick<ShapeAttributes, 'strokes'>> => {
const vectorNetwork = isVectorLike(node) ? node.vectorNetwork : undefined;
const strokeCaps = (stroke: Stroke) => {
@ -38,7 +38,7 @@ export const transformStrokesFromVector = async (
node: VectorNode,
vector: Command[],
vectorRegion: VectorRegion | undefined
): Promise<Partial<ShapeAttributes>> => {
): Promise<Pick<ShapeAttributes, 'strokes'>> => {
const strokeCaps = (stroke: Stroke) => {
if (vectorRegion !== undefined) return stroke;

View file

@ -2,9 +2,11 @@ import { transformFills } from '@plugin/transformers/partials';
import { transformTextStyle, translateStyleTextSegments } from '@plugin/translators/text';
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([
'fontName',
'fontSize',

View file

@ -1,9 +1,10 @@
import {
transformBlend,
transformDimensionAndPosition,
transformDimension,
transformEffects,
transformFills,
transformProportion,
transformRotationAndPosition,
transformSceneNode,
transformStrokes
} from '@plugin/transformers/partials';
@ -21,7 +22,8 @@ export const transformEllipseNode = async (
...(await transformFills(node)),
...transformEffects(node),
...(await transformStrokes(node)),
...transformDimensionAndPosition(node, baseX, baseY),
...transformDimension(node),
...transformRotationAndPosition(node, baseX, baseY),
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node)

View file

@ -1,10 +1,11 @@
import {
transformBlend,
transformCornerRadius,
transformDimensionAndPosition,
transformDimension,
transformEffects,
transformFills,
transformProportion,
transformRotationAndPosition,
transformSceneNode,
transformStrokes
} from '@plugin/transformers/partials';
@ -22,7 +23,8 @@ export const transformRectangleNode = async (
...(await transformFills(node)),
...transformEffects(node),
...(await transformStrokes(node)),
...transformDimensionAndPosition(node, baseX, baseY),
...transformDimension(node),
...transformRotationAndPosition(node, baseX, baseY),
...transformSceneNode(node),
...transformBlend(node),
...transformProportion(node),

View file

@ -26,10 +26,7 @@ export const translateStyleTextSegments = async (
return translateParagraphProperties(node, partials);
};
export const transformTextStyle = (
node: TextNode,
segment: StyleTextSegment
): Partial<TextStyle> => {
export const transformTextStyle = (node: TextNode, segment: StyleTextSegment): TextStyle => {
return {
...translateFontId(segment.fontName, segment.fontWeight),
fontFamily: segment.fontName.family,

View file

@ -6,7 +6,7 @@ export const translateStrokes = async (
node: MinimalStrokesMixin | (MinimalStrokesMixin & IndividualStrokesMixin),
strokeCaps: (stroke: Stroke) => Stroke = stroke => stroke
): Promise<Stroke[]> => {
const sharedStrokeProperties: Partial<Stroke> = {
const sharedStrokeProperties: Stroke = {
strokeWidth: translateStrokeWeight(node),
strokeAlignment: translateStrokeAlignment(node.strokeAlign),
strokeStyle: node.dashPattern.length ? 'dashed' : 'solid'
@ -22,7 +22,7 @@ export const translateStrokes = async (
export const translateStroke = async (
paint: Paint,
sharedStrokeProperties: Partial<Stroke>,
sharedStrokeProperties: Stroke,
strokeCaps: (stroke: Stroke) => Stroke,
firstStroke: boolean
): Promise<Stroke> => {

View file

@ -12,7 +12,7 @@ export type TextShape = ShapeBaseAttributes &
TextAttributes &
LayoutChildAttributes;
type TextAttributes = {
export type TextAttributes = {
type?: 'text';
content?: TextContent;
};