-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCircularLayout.jsx
More file actions
executable file
·292 lines (247 loc) · 11.2 KB
/
CircularLayout.jsx
File metadata and controls
executable file
·292 lines (247 loc) · 11.2 KB
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
//DESCRIPTION: Advanced Circular Layout for InDesign
/*
About Script
Arranges selected objects along a circular path with precise control over
angles, distribution, and alignment. Ideal for creating circular text labels,
infographics, or decorative layouts.
Includes an option to draw radial lines (spokes) from the circle center
to each object, automatically organized into separate layers for easy management.
To Use:
1. Select one or more objects (text frames, rectangles, etc.) in your document.
2. Run the script.
3. Set the circle center (X, Y) and radius in millimeters.
4. Choose a layout mode:
- Custom Range: Evenly distributes objects between Start and End angles.
- Fixed Interval: Places objects starting from Start Angle with a fixed step.
5. Select alignment mode (Bottom Center, Left Bottom, or Right Bottom) to
determine how objects sit on the circular path.
6. Optional: Toggle "Draw Radial Lines" to visualize the structure.
7. Click OK.
Notes:
- The script automatically creates "Circular Text" and "Radial Lines" layers.
- Alignment modes adjust both the position and rotation of objects relative
to the center point.
- The operation is fully undoable (Cmd+Z).
Copyright (c) by Gu Wenhao, 2026
Version 1.1.0
*/
function main() {
// 1. 检查文档和选中项
if (app.documents.length == 0) {
alert("请先打开一个 InDesign 文档。");
return;
}
var selection = app.selection;
if (selection.length == 0) {
alert("请先选中需要排版的对象。");
return;
}
var doc = app.activeDocument;
var page = app.activeWindow.activePage;
// 保存原始标尺设置,以便最后恢复
var oldRulerOrigin = doc.viewPreferences.rulerOrigin;
var oldZeroPoint = doc.zeroPoint;
// 将标尺原点临时设置为当前页面左上角 [0, 0]
doc.viewPreferences.rulerOrigin = RulerOrigin.PAGE_ORIGIN;
doc.zeroPoint = [0, 0];
// 页面尺寸单位:毫米(InDesign 页面坐标系默认单位)
// 注意:bounds 返回的是 [y1, x1, y2, x2],单位 mm
// 获取页面尺寸
var pageBounds = page.bounds; // [y1, x1, y2, x2]
var pageW = pageBounds[3] - pageBounds[1];
var pageH = pageBounds[2] - pageBounds[0];
// --- 读取缓存设置 ---
var c_x = (pageW / 2).toFixed(2);
var c_y = (pageH / 2).toFixed(2);
var c_r = "100";
var c_mode = 0;
var c_sa = "0";
var c_ea = "360";
var c_fs = "30";
var c_draw = true;
var c_align = 0;
var cache = app.extractLabel("CircularLayout_Cache");
if (cache) {
var arr = cache.split(",");
if (arr.length == 7) {
c_r = arr[0];
c_mode = parseInt(arr[1], 10);
c_sa = arr[2];
c_ea = arr[3];
c_fs = arr[4];
c_draw = (arr[5] === "1");
c_align = parseInt(arr[6], 10);
}
}
// 2. 创建用户界面 (使用 ScriptUI 以支持动态交互)
var w = new Window("dialog", "高级圆周排版 (含放射线)");
w.alignChildren = ["fill", "top"];
var p1 = w.add("panel", undefined, "基本参数");
p1.alignChildren = ["left", "top"];
p1.add("statictext", undefined, "选中对象总数: " + selection.length);
var gX = p1.add("group");
gX.add("statictext", undefined, "圆心 X (mm):").preferredSize.width = 90;
var centerXField = gX.add("edittext", undefined, c_x);
centerXField.characters = 8;
var gY = p1.add("group");
gY.add("statictext", undefined, "圆心 Y (mm):").preferredSize.width = 90;
var centerYField = gY.add("edittext", undefined, c_y);
centerYField.characters = 8;
var gR = p1.add("group");
gR.add("statictext", undefined, "半径 (mm):").preferredSize.width = 90;
var radiusField = gR.add("edittext", undefined, c_r);
radiusField.characters = 8;
var p2 = w.add("panel", undefined, "排列模式");
p2.alignChildren = ["left", "top"];
var gMode = p2.add("group");
gMode.add("statictext", undefined, "模式选择:").preferredSize.width = 90;
var layoutModeDropdown = gMode.add("dropdownlist", undefined, ["自定义范围 (两点间均分)", "固定间隔 (起始角+步长)"]);
layoutModeDropdown.selection = c_mode;
var gSA = p2.add("group");
gSA.add("statictext", undefined, "起始角度 (度):").preferredSize.width = 90;
var startAngleField = gSA.add("edittext", undefined, c_sa);
startAngleField.characters = 8;
var gEA = p2.add("group");
gEA.add("statictext", undefined, "终止角度 (度):").preferredSize.width = 90;
var endAngleField = gEA.add("edittext", undefined, c_ea);
endAngleField.characters = 8;
var gFS = p2.add("group");
gFS.add("statictext", undefined, "固定间隔 (度):").preferredSize.width = 90;
var fixedStepField = gFS.add("edittext", undefined, c_fs);
fixedStepField.characters = 8;
var p3 = w.add("panel", undefined, "高级选项");
p3.alignChildren = ["left", "top"];
var drawLineCheckbox = p3.add("checkbox", undefined, "绘制放射线 (中心点到文本框连线)");
drawLineCheckbox.value = c_draw;
var gAlign = p3.add("group");
gAlign.add("statictext", undefined, "对齐方式:");
var alignDropdown = gAlign.add("dropdownlist", undefined, ["底部居中对齐", "左下角对齐 (沿线向外)", "右下角对齐 (沿线向内)"]);
alignDropdown.selection = c_align;
var buttons = w.add("group");
buttons.alignment = "center";
buttons.add("button", undefined, "确定", { name: "ok" });
buttons.add("button", undefined, "取消", { name: "cancel" });
// 动态交互逻辑:切换模式时自动灰显(禁用)不需要的输入框
layoutModeDropdown.onChange = function () {
if (layoutModeDropdown.selection.index == 0) {
gEA.enabled = true;
gFS.enabled = false;
} else {
gEA.enabled = false;
gFS.enabled = true;
}
}
layoutModeDropdown.onChange(); // 初始化界面状态
// 3. 执行逻辑
if (w.show() == 1) {
// --- 保存缓存设置 ---
app.insertLabel("CircularLayout_Cache",
radiusField.text + "," +
layoutModeDropdown.selection.index + "," +
startAngleField.text + "," +
endAngleField.text + "," +
fixedStepField.text + "," +
(drawLineCheckbox.value ? "1" : "0") + "," +
alignDropdown.selection.index
);
var centerX = parseFloat(centerXField.text) || 0;
var centerY = parseFloat(centerYField.text) || 0;
var radius = parseFloat(radiusField.text) || 0;
var startAngle = parseFloat(startAngleField.text) || 0;
var endAngle = parseFloat(endAngleField.text) || 0;
var fixedStep = parseFloat(fixedStepField.text) || 0;
var drawLine = drawLineCheckbox.value;
var alignMode = alignDropdown.selection.index;
var layoutMode = layoutModeDropdown.selection.index;
var count = selection.length;
// 准备独立图层
var textLayer = doc.layers.itemByName("圆周文本");
if (!textLayer.isValid) {
textLayer = doc.layers.add({ name: "圆周文本" });
}
var lineLayer = doc.layers.itemByName("放射线");
if (!lineLayer.isValid) {
lineLayer = doc.layers.add({ name: "放射线" });
// 将放射线图层置于文本图层下方,防止线遮挡文字
try {
lineLayer.move(LocationOptions.AFTER, textLayer);
} catch (e) { }
}
// 根据模式计算角度步长
var angleStep;
if (layoutMode === 1) {
// 固定间隔模式:直接使用用户指定的固定步长
angleStep = fixedStep;
} else {
// 自定义范围:在 [startAngle, endAngle] 内均分
if (count > 1) {
angleStep = (endAngle - startAngle) / (count - 1);
// 如果范围 >= 360° 则不重叠首尾
if (Math.abs(endAngle - startAngle) >= 360) {
angleStep = (endAngle - startAngle) / count;
}
} else {
angleStep = 0;
}
}
// 循环处理选中的对象
for (var i = 0; i < count; i++) {
var item = selection[i];
var currentAngle = startAngle + (i * angleStep);
// InDesign 的 Y 轴向下,正常数学角度(顺时针增加):
var radians = currentAngle * Math.PI / 180;
// 计算放射线终点 P
var targetX = centerX + radius * Math.cos(radians);
var targetY = centerY + radius * Math.sin(radians);
// 绘制放射线
if (drawLine) {
var line = page.graphicLines.add();
line.itemLayer = lineLayer; // 分配到放射线图层
line.paths.item(0).pathPoints.item(0).anchor = [centerX, centerY];
line.paths.item(0).pathPoints.item(1).anchor = [targetX, targetY];
try {
line.strokeWeight = 1;
line.strokeColor = doc.swatches.item("Black");
} catch (e) { }
}
// 将文本框分配到文本图层
item.itemLayer = textLayer;
// 1. 先重置旋转角度为 0,以准确计算原始尺寸
item.rotationAngle = 0;
// 2. 获取对象尺寸
var gb = item.geometricBounds;
var itemW = gb[3] - gb[1];
var itemH = gb[2] - gb[0];
// 3. 计算文本框应处于的中心点坐标 (C_X, C_Y)
// 使得在旋转后,特定的锚点(底部居中/左下/右下)刚好落在终点 P 上
var cx = 0;
var cy = 0;
if (alignMode === 0) {
// 底部居中:使得文本框的底边中心正好卡在放射线终点
cx = targetX + (itemH / 2) * Math.sin(radians);
cy = targetY - (itemH / 2) * Math.cos(radians);
} else if (alignMode === 1) {
// 左下角:使得文本框左下角在终点,文本框沿着放射线向外延伸
cx = targetX + (itemW / 2) * Math.cos(radians) + (itemH / 2) * Math.sin(radians);
cy = targetY + (itemW / 2) * Math.sin(radians) - (itemH / 2) * Math.cos(radians);
} else if (alignMode === 2) {
// 右下角:使得文本框右下角在终点,文本框沿着放射线向内延伸
cx = targetX - (itemW / 2) * Math.cos(radians) + (itemH / 2) * Math.sin(radians);
cy = targetY - (itemW / 2) * Math.sin(radians) - (itemH / 2) * Math.cos(radians);
}
// 4. 将对象中心移动到计算出的 (cx, cy)
item.geometricBounds = [
cy - (itemH / 2),
cx - (itemW / 2),
cy + (itemH / 2),
cx + (itemW / 2)
];
// 5. 应用旋转 (InDesign 默认正值为逆时针,由于我们希望文本框底边顺时针贴合放射线,所以取负值)
item.rotationAngle = -currentAngle;
}
}
// 恢复原始标尺设置
doc.viewPreferences.rulerOrigin = oldRulerOrigin;
doc.zeroPoint = oldZeroPoint;
}
main();