-
1
-
2
-
3
-
4
-
5
-
6
-
7
-
8
-
9
-
10
-
11
-
12
-
13
-
14
-
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
-
27
-
28
-
29
-
30
-
31
-
32
-
33
-
34
-
35
-
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
-
48
-
49
-
50
-
51
-
52
-
53
-
54
-
55
-
56
-
57
-
58
-
59
-
60
-
61
-
62
-
63
-
64
-
65
-
66
-
67
-
68
-
69
-
70
-
71
-
72
-
73
-
74
-
75
-
76
-
77
-
78
-
79
-
80
-
81
-
82
-
83
-
84
-
85
-
86
-
87
-
88
-
89
-
90
-
91
-
92
-
93
-
94
-
95
-
96
-
97
-
98
-
99
-
100
-
101
-
102
-
103
-
104
-
105
-
106
-
107
-
108
import * as figma from "../figma.js";
// Intermediate value, context object.
interface MinMaxXY {
minX: number;
maxX: number;
minY: number;
maxY: number;
}
/**
* Calculate the size and position of where to put an API rendered image.
* Ealier API did not return `absoluteRenderBounds`, so in order to place an API rendered image
* to correct position, client need to measure the effect radius of LAYER_BLUR and DROP_SHADOW.
*/
export function getRenderBoundingBox(
node: figma.Node & figma.HasBoundingBox,
): figma.Rectangle {
if (node.absoluteRenderBounds) {
return node.absoluteRenderBounds;
}
let current: MinMaxXY | null = null;
for (const target of figma.walk(node)) {
if (target.visible === false || !figma.hasBoundingBox(target)) {
continue;
}
const minmax = calculateRenderingBoundingBox(target);
if (!current) {
current = minmax;
continue;
}
current.minX = Math.min(current.minX, minmax.minX);
current.minY = Math.min(current.minY, minmax.minY);
current.maxX = Math.max(current.maxX, minmax.maxX);
current.maxY = Math.max(current.maxY, minmax.maxY);
}
return current
? {
x: current.minX,
y: current.minY,
width: current.maxX - current.minX,
height: current.maxY - current.minY,
}
: node.absoluteBoundingBox;
}
function calculateRenderingBoundingBox(
node: figma.Node & figma.HasBoundingBox,
): MinMaxXY {
if (!figma.hasEffects(node)) {
return {
minX: node.absoluteBoundingBox.x,
maxX: node.absoluteBoundingBox.x + node.absoluteBoundingBox.width,
minY: node.absoluteBoundingBox.y,
maxY: node.absoluteBoundingBox.y + node.absoluteBoundingBox.height,
};
}
// If the frame has effects, the size of rendered image is larger than frame's size
// because of rendered effects.
const margins = { top: 0, right: 0, bottom: 0, left: 0 };
for (const effect of node.effects) {
if (effect.visible === false) {
continue;
}
if (figma.isShadowEffect(effect) && effect.type === "DROP_SHADOW") {
margins.left = Math.max(margins.left, effect.radius - effect.offset.x);
margins.top = Math.max(margins.top, effect.radius - effect.offset.y);
margins.right = Math.max(margins.right, effect.radius + effect.offset.x);
margins.bottom = Math.max(
margins.bottom,
effect.radius + effect.offset.y,
);
continue;
}
if (effect.type === "LAYER_BLUR") {
margins.top = Math.max(margins.top, effect.radius);
margins.right = Math.max(margins.right, effect.radius);
margins.bottom = Math.max(margins.bottom, effect.radius);
margins.left = Math.max(margins.left, effect.radius);
continue;
}
// Other effects does not changes a size of rendered image
}
return {
minX: node.absoluteBoundingBox.x - margins.left,
maxX:
node.absoluteBoundingBox.x +
node.absoluteBoundingBox.width +
margins.right,
minY: node.absoluteBoundingBox.y - margins.top,
maxY:
node.absoluteBoundingBox.y +
node.absoluteBoundingBox.height +
margins.bottom,
};
}