From 30b1f3d5b100222614eb114e9e45a4365919c133 Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Sun, 11 Dec 2022 12:15:41 -0500 Subject: [PATCH 1/3] :bug: Fix images nested in the node tree This fixes a bug where only top-level images in the node tree were being added to the penpot export as images. They were instead treated as empty shapes. Closes #10. Signed-off-by: Ryan Breen --- src/ui.tsx | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/ui.tsx b/src/ui.tsx index 23c06b5..07da7b2 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -103,21 +103,14 @@ export default class PenpotExporter extends React.Component fill.type === "IMAGE"); + if (imageFill){ + this.createPenpotImage(file, node, baseX, baseY, this.state.images[imageFill.imageHash]); + } else { + this.createPenpotRectangle(file, node, baseX, baseY); + } } else if (node.type == "ELLIPSE"){ this.createPenpotCircle(file, node, baseX, baseY); From cac49c2e9eb9e8b0594f8e725af12509474e2f29 Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Thu, 29 Dec 2022 14:05:42 -0500 Subject: [PATCH 2/3] Convert Figma nodes with image fills into Penpot images by exporting the node to png Signed-off-by: Ryan Breen --- package.json | 1 + src/code.ts | 26 ++++++++-------- src/ui.tsx | 86 ++++++++++++++++++++++++++++++++++++++++------------ 3 files changed, 82 insertions(+), 31 deletions(-) diff --git a/package.json b/package.json index fc6af9e..b46d836 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "@types/react-dom": "^17.0.9", "crypto": "^1.0.1", "crypto-browserify": "^3.12.0", + "pngjs": "^6.0.0", "react": "^17.0.2", "react-dev-utils": "^11.0.4", "react-dom": "^17.0.2", diff --git a/src/code.ts b/src/code.ts index 8db0093..273ddef 100644 --- a/src/code.ts +++ b/src/code.ts @@ -53,18 +53,20 @@ function traverse(node): NodeData { } if (node.fills && Array.isArray(node.fills)){ - for (const paint of node.fills) { - if (paint.type === 'IMAGE') { - // Get the (encoded) bytes for this image. - const image = figma.getImageByHash(paint.imageHash); - image.getBytesAsync().then((value) => { - const b64 = figma.base64Encode(value); - figma.ui.postMessage({type: "IMAGE", data: { - imageHash: paint.imageHash, - value: "data:" + detectMimeType(b64) + ";base64," + b64 - }}); - }); - } + + // Find any fill of type image + const imageFill = node.fills.find(fill => fill.type === "IMAGE"); + if (imageFill) { + // 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); + figma.ui.postMessage({type: "IMAGE", data: { + id: node.id, + value: "data:" + detectMimeType(b64) + ";base64," + b64 + }}); + }); } } diff --git a/src/ui.tsx b/src/ui.tsx index 07da7b2..2fdaf7c 100644 --- a/src/ui.tsx +++ b/src/ui.tsx @@ -12,12 +12,18 @@ declare function require(path: string): any; type PenpotExporterProps = { } +type FigmaImageData = { + value: string, + width: number, + height: number +} + type PenpotExporterState = { isDebug: boolean, penpotFileData: string figmaFileData: string figmaRootNode: NodeData - images: { [id: string] : string; }; + images: { [id: string] : FigmaImageData; }; } export default class PenpotExporter extends React.Component { @@ -77,6 +83,7 @@ export default class PenpotExporter extends React.Component 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); } else if (node.type == "FRAME"){ @@ -231,13 +272,7 @@ export default class PenpotExporter extends React.Component fill.type === "IMAGE"); - if (imageFill){ - this.createPenpotImage(file, node, baseX, baseY, this.state.images[imageFill.imageHash]); - } else { - this.createPenpotRectangle(file, node, baseX, baseY); - } + this.createPenpotRectangle(file, node, baseX, baseY); } else if (node.type == "ELLIPSE"){ this.createPenpotCircle(file, node, baseX, baseY); @@ -274,13 +309,26 @@ export default class PenpotExporter extends React.Component - { - state.images[data.imageHash] = data.value; - return state; - } - ) ; + const image = document.createElement('img'); + const thisObj = this; + + image.addEventListener('load', function() { + // Get byte array from response + thisObj.setState(state => + { + state.images[data.id] = { + value: data.value, + width: image.naturalWidth, + height: image.naturalHeight + }; + return state; + } + ); + }); + image.src = data.value; + } } From 78dbc18180b8b8f240101c5a9815df9509fa3ae4 Mon Sep 17 00:00:00 2001 From: Ryan Breen Date: Thu, 29 Dec 2022 14:15:57 -0500 Subject: [PATCH 3/3] Clean up a stray dependency. Signed-off-by: Ryan Breen --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index b46d836..fc6af9e 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,6 @@ "@types/react-dom": "^17.0.9", "crypto": "^1.0.1", "crypto-browserify": "^3.12.0", - "pngjs": "^6.0.0", "react": "^17.0.2", "react-dev-utils": "^11.0.4", "react-dom": "^17.0.2",