0
Fork 0
mirror of https://github.com/penpot/penpot-exporter-figma-plugin.git synced 2024-12-22 21:53:27 -05:00

Merge pull request #11 from ryanbreen/nested-images-fix

🐛 Fix images nested in the node tree
This commit is contained in:
Alejandro 2022-12-30 15:04:32 +01:00 committed by GitHub
commit 9697a4abb4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 82 additions and 33 deletions

View file

@ -53,20 +53,22 @@ function traverse(node): NodeData {
} }
if (node.fills && Array.isArray(node.fills)){ if (node.fills && Array.isArray(node.fills)){
for (const paint of node.fills) {
if (paint.type === 'IMAGE') { // Find any fill of type image
// Get the (encoded) bytes for this image. const imageFill = node.fills.find(fill => fill.type === "IMAGE");
const image = figma.getImageByHash(paint.imageHash); if (imageFill) {
image.getBytesAsync().then((value) => { // An "image" in Figma is a shape with one or more image fills, potentially blended with other fill
// types. Given the complexity of mirroring this exactly in Penpot, which treats images as first-class
// objects, we're going to simplify this by exporting this shape as a PNG image.
node.exportAsync({format: "PNG"}).then((value) => {
const b64 = figma.base64Encode(value); const b64 = figma.base64Encode(value);
figma.ui.postMessage({type: "IMAGE", data: { figma.ui.postMessage({type: "IMAGE", data: {
imageHash: paint.imageHash, id: node.id,
value: "data:" + detectMimeType(b64) + ";base64," + b64 value: "data:" + detectMimeType(b64) + ";base64," + b64
}}); }});
}); });
} }
} }
}
if (node.type == "TEXT") { if (node.type == "TEXT") {
const styledTextSegments = node.getStyledTextSegments(["fontName", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "fills"]); const styledTextSegments = node.getStyledTextSegments(["fontName", "fontSize", "fontWeight", "lineHeight", "letterSpacing", "fills"]);

View file

@ -12,12 +12,18 @@ declare function require(path: string): any;
type PenpotExporterProps = { type PenpotExporterProps = {
} }
type FigmaImageData = {
value: string,
width: number,
height: number
}
type PenpotExporterState = { type PenpotExporterState = {
isDebug: boolean, isDebug: boolean,
penpotFileData: string penpotFileData: string
figmaFileData: string figmaFileData: string
figmaRootNode: NodeData figmaRootNode: NodeData
images: { [id: string] : string; }; images: { [id: string] : FigmaImageData; };
} }
export default class PenpotExporter extends React.Component<PenpotExporterProps, PenpotExporterState> { export default class PenpotExporter extends React.Component<PenpotExporterProps, PenpotExporterState> {
@ -77,6 +83,7 @@ export default class PenpotExporter extends React.Component<PenpotExporterProps,
} }
translateFill(fill, width, height){ translateFill(fill, width, height){
if (fill.type === "SOLID"){ if (fill.type === "SOLID"){
return this.translateSolidFill(fill); return this.translateSolidFill(fill);
} else if (fill.type === "GRADIENT_LINEAR"){ } else if (fill.type === "GRADIENT_LINEAR"){
@ -103,21 +110,14 @@ export default class PenpotExporter extends React.Component<PenpotExporterProps,
file.addPage(node.name); file.addPage(node.name);
for (var child of node.children){ for (var child of node.children){
this.createPenpotItem(file, child, 0, 0); this.createPenpotItem(file, child, 0, 0);
if (child.fills) {
for (var fill of child.fills ){
if (fill.type === "IMAGE"){
this.createPenpotImage(file, child, 0, 0, this.state.images[fill.imageHash]);
}
}
}
} }
file.closePage(); file.closePage();
} }
createPenpotBoard(file, node, baseX, baseY){ createPenpotBoard(file, node, baseX, baseY){
file.addArtboard({ name: node.name, x: node.x - baseX, y: node.y - baseY, width: node.width, height: node.height }); file.addArtboard({ name: node.name, x: node.x + baseX, y: node.y + baseY, width: node.width, height: node.height });
for (var child of node.children){ for (var child of node.children){
this.createPenpotItem(file, child, node.x - baseX, node.y - baseY); this.createPenpotItem(file, child, node.x + baseX, node.y + baseY);
} }
file.closeArtboard(); file.closeArtboard();
} }
@ -218,17 +218,51 @@ export default class PenpotExporter extends React.Component<PenpotExporterProps,
} }
createPenpotImage(file, node, baseX, baseY, image){ createPenpotImage(file, node, baseX, baseY, image){
file.createImage({ name: node.name, x: node.x + baseX, y: node.y + baseY, width: node.width, height: node.height, file.createImage({ name: node.name, x: node.x + baseX, y: node.y + baseY, width: image.width, height: image.height,
metadata: { metadata: {
width: node.width, width: image.width,
height: node.height height: image.height
}, },
dataUri: image dataUri: image.value
}); });
} }
calculateAdjustment(node){
// For each child, check whether the X or Y position is less than 0 and less than the
// current adjustment.
let adjustedX = 0;
let adjustedY = 0;
for (var child of node.children){
if (child.x < adjustedX){
adjustedX = child.x;
}
if (child.y < adjustedY){
adjustedY = child.y;
}
}
return [adjustedX, adjustedY];
}
createPenpotItem(file, node, baseX, baseY){ createPenpotItem(file, node, baseX, baseY){
if (node.type == "PAGE"){
// We special-case images because an image in figma is a shape with one or many
// image fills. Given that handling images in Penpot is a bit different, we
// rasterize a figma shape with any image fills to a PNG and then add it as a single
// Penpot image. Implication is that any node that has an image fill will only be
// treated as an image, so we skip node type checks.
const hasImageFill = node.fills?.some(fill => fill.type === "IMAGE");
if (hasImageFill){
// If the nested frames extended the bounds of the rasterized image, we need to
// account for this both in position on the canvas and the calculated width and
// height of the image.
const [adjustedX, adjustedY] = this.calculateAdjustment(node);
const width = node.width + Math.abs(adjustedX);
const height = node.height + Math.abs(adjustedY);
this.createPenpotImage(file, node, baseX + adjustedX, baseY + adjustedY, this.state.images[node.id]);
}
else if (node.type == "PAGE"){
this.createPenpotPage(file, node); this.createPenpotPage(file, node);
} }
else if (node.type == "FRAME"){ else if (node.type == "FRAME"){
@ -275,13 +309,26 @@ export default class PenpotExporter extends React.Component<PenpotExporterProps,
figmaRootNode: event.data.pluginMessage.data})); figmaRootNode: event.data.pluginMessage.data}));
} }
else if (event.data.pluginMessage.type == "IMAGE") { else if (event.data.pluginMessage.type == "IMAGE") {
const data = event.data.pluginMessage.data; const data = event.data.pluginMessage.data;
this.setState(state => const image = document.createElement('img');
const thisObj = this;
image.addEventListener('load', function() {
// Get byte array from response
thisObj.setState(state =>
{ {
state.images[data.imageHash] = data.value; state.images[data.id] = {
value: data.value,
width: image.naturalWidth,
height: image.naturalHeight
};
return state; return state;
} }
); );
});
image.src = data.value;
} }
} }