0
Fork 0
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:
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 './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';

View file

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

View file

@ -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,

View file

@ -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,

View file

@ -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)

View file

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

View file

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

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'; 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

View file

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

View file

@ -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',

View file

@ -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)

View file

@ -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),

View file

@ -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,

View file

@ -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> => {

View file

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