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:
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 './transformFills';
|
||||
export * from './transformProportion';
|
||||
export * from './transformRotationAndPosition';
|
||||
export * from './transformSceneNode';
|
||||
export * from './transformStrokes';
|
||||
export * from './transformText';
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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';
|
||||
|
||||
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
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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> => {
|
||||
|
|
|
@ -12,7 +12,7 @@ export type TextShape = ShapeBaseAttributes &
|
|||
TextAttributes &
|
||||
LayoutChildAttributes;
|
||||
|
||||
type TextAttributes = {
|
||||
export type TextAttributes = {
|
||||
type?: 'text';
|
||||
content?: TextContent;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue