Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save monokano/fbe7e17d870f710f52f9cd1b8d523688 to your computer and use it in GitHub Desktop.

Select an option

Save monokano/fbe7e17d870f710f52f9cd1b8d523688 to your computer and use it in GitHub Desktop.
選択オブジェクトを個別に拡大縮小して間隔を維持するInDesignスクリプト
// Adobe InDesign ExtendScript - 選択オブジェクト拡大縮小
(function() {
// メイン関数
function main() {
// ドキュメントが開いているかチェック
if (app.documents.length == 0) {
alert("ドキュメントが開かれていません。");
return;
}
// 選択オブジェクトをチェック
if (app.selection.length == 0) {
alert("オブジェクトが選択されていません。");
return;
}
// ダイアログを表示
var dialog = createDialog();
var result = dialog.show();
if (result == 1) { // OKボタンが押された場合
var horizontalPercent = parseFloat(dialog.scaleGroup.horizontalInput.text);
var verticalPercent = parseFloat(dialog.scaleGroup.verticalInput.text);
var anchorPoint = dialog.anchorGroup.anchorDropdown.selection.index;
if (isNaN(horizontalPercent) || horizontalPercent <= 0 ||
isNaN(verticalPercent) || verticalPercent <= 0) {
alert("有効な数値を入力してください。");
return;
}
// 100%/100%の場合は処理をスキップ
if (horizontalPercent == 100 && verticalPercent == 100) {
alert("100%/100%では変更されません。");
return;
}
// Undo履歴を残さずに実行
app.doScript(function() {
scaleSelectedObjects(horizontalPercent, verticalPercent, anchorPoint);
}, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT);
}
}
// 選択オブジェクトを展開(グループを含む)
function expandSelection(selection) {
var expandedObjects = [];
for (var i = 0; i < selection.length; i++) {
var obj = selection[i];
expandObjectsRecursively(obj, expandedObjects);
}
return expandedObjects;
}
// オブジェクトを再帰的に展開
function expandObjectsRecursively(obj, result) {
// グループの場合
if (obj.constructor.name == "Group") {
// グループ内のオブジェクトを再帰的に展開
for (var i = 0; i < obj.allPageItems.length; i++) {
expandObjectsRecursively(obj.allPageItems[i], result);
}
} else {
// グループではない場合、結果に追加
result.push(obj);
}
}
// ダイアログUI作成
function createDialog() {
var dialog = new Window("dialog", "オブジェクト拡大縮小");
dialog.orientation = "column";
dialog.alignChildren = "fill";
dialog.spacing = 10;
dialog.margins = 16;
// 拡大縮小率入力
dialog.scaleGroup = dialog.add("group");
dialog.scaleGroup.orientation = "column";
dialog.scaleGroup.alignChildren = "fill";
// 横幅
dialog.scaleGroup.horizontalGroup = dialog.scaleGroup.add("group");
dialog.scaleGroup.horizontalGroup.orientation = "row";
dialog.scaleGroup.horizontalGroup.alignChildren = "center";
dialog.scaleGroup.horizontalGroup.add("statictext", undefined, "横幅:");
dialog.scaleGroup.horizontalInput = dialog.scaleGroup.horizontalGroup.add("edittext", undefined, "100");
dialog.scaleGroup.horizontalInput.characters = 8;
dialog.scaleGroup.horizontalGroup.add("statictext", undefined, "%");
// 縦幅
dialog.scaleGroup.verticalGroup = dialog.scaleGroup.add("group");
dialog.scaleGroup.verticalGroup.orientation = "row";
dialog.scaleGroup.verticalGroup.alignChildren = "center";
dialog.scaleGroup.verticalGroup.add("statictext", undefined, "縦幅:");
dialog.scaleGroup.verticalInput = dialog.scaleGroup.verticalGroup.add("edittext", undefined, "100");
dialog.scaleGroup.verticalInput.characters = 8;
dialog.scaleGroup.verticalGroup.add("statictext", undefined, "%");
// 比率固定チェックボックス
dialog.scaleGroup.lockGroup = dialog.scaleGroup.add("group");
dialog.scaleGroup.lockGroup.orientation = "row";
dialog.scaleGroup.lockGroup.alignChildren = "left";
dialog.scaleGroup.lockRatio = dialog.scaleGroup.lockGroup.add("checkbox", undefined, "縦横比を固定");
dialog.scaleGroup.lockRatio.value = true; // デフォルトでON
// 基準点選択
dialog.anchorGroup = dialog.add("group");
dialog.anchorGroup.orientation = "row";
dialog.anchorGroup.alignChildren = "center";
dialog.anchorGroup.add("statictext", undefined, "基準点:");
dialog.anchorGroup.anchorDropdown = dialog.anchorGroup.add("dropdownlist", undefined, [
"左上", "上中央", "右上",
"左中央", "中央", "右中央",
"左下", "下中央", "右下"
]);
dialog.anchorGroup.anchorDropdown.selection = 4; // デフォルトは中央
// 比率固定の動作
dialog.scaleGroup.horizontalInput.onChanging = function() {
if (dialog.scaleGroup.lockRatio.value) {
dialog.scaleGroup.verticalInput.text = dialog.scaleGroup.horizontalInput.text;
}
updateButtonState();
};
dialog.scaleGroup.verticalInput.onChanging = function() {
if (dialog.scaleGroup.lockRatio.value) {
dialog.scaleGroup.horizontalInput.text = dialog.scaleGroup.verticalInput.text;
}
updateButtonState();
};
// 縦横比固定チェックボックスの動作
dialog.scaleGroup.lockRatio.onClick = function() {
if (dialog.scaleGroup.lockRatio.value) {
// チェックをONにした時:横の値に縦を合わせる
dialog.scaleGroup.verticalInput.text = dialog.scaleGroup.horizontalInput.text;
}
updateButtonState();
};
// ボタンの有効/無効を制御
function updateButtonState() {
var horizontalValue = parseFloat(dialog.scaleGroup.horizontalInput.text);
var verticalValue = parseFloat(dialog.scaleGroup.verticalInput.text);
// 横100かつ縦100の場合はボタンを無効にする
if (horizontalValue == 100 && verticalValue == 100) {
buttonGroup.okButton.enabled = false;
} else {
buttonGroup.okButton.enabled = true;
}
}
// 説明テキスト
var infoPanel = dialog.add("panel", undefined, "処理内容");
infoPanel.orientation = "column";
infoPanel.alignChildren = "fill";
infoPanel.add("statictext", undefined, "• グラフィックフレーム: 縦横それぞれ拡大縮小");
infoPanel.add("statictext", undefined, "• テキストフレーム: 横幅のみ拡大縮小(縦幅は無視)");
infoPanel.add("statictext", undefined, "• オブジェクトの相対位置関係は維持されます");
infoPanel.add("statictext", undefined, "• グループ化されたオブジェクトにも対応");
infoPanel.add("statictext", undefined, "• 横100%かつ縦100%の場合は実行できません");
// ボタン
var buttonGroup = dialog.add("group");
buttonGroup.orientation = "row";
buttonGroup.alignment = "center";
buttonGroup.add("button", undefined, "キャンセル", {name: "cancel"});
buttonGroup.add("button", undefined, "実行", {name: "ok"});
return dialog;
}
// 基準点のAnchorPointを取得
function getAnchorPoint(index) {
var anchorPoints = [
AnchorPoint.TOP_LEFT_ANCHOR,
AnchorPoint.TOP_CENTER_ANCHOR,
AnchorPoint.TOP_RIGHT_ANCHOR,
AnchorPoint.LEFT_CENTER_ANCHOR,
AnchorPoint.CENTER_ANCHOR,
AnchorPoint.RIGHT_CENTER_ANCHOR,
AnchorPoint.BOTTOM_LEFT_ANCHOR,
AnchorPoint.BOTTOM_CENTER_ANCHOR,
AnchorPoint.BOTTOM_RIGHT_ANCHOR
];
return anchorPoints[index];
}
// 選択オブジェクトを拡大縮小(間隔維持)
function scaleSelectedObjects(horizontalPercent, verticalPercent, anchorIndex) {
var doc = app.activeDocument;
var horizontalRatio = horizontalPercent / 100;
var verticalRatio = verticalPercent / 100;
// 選択オブジェクトを展開(グループを含む)
var allObjects = expandSelection(app.selection);
// 元の全体境界を取得
var originalBounds = getSelectionBounds(allObjects);
// 基準点の座標を計算
var anchorX = calculateAnchorX(originalBounds, anchorIndex);
var anchorY = calculateAnchorY(originalBounds, anchorIndex);
// 各オブジェクトの元情報を記録
var objectData = [];
for (var i = 0; i < allObjects.length; i++) {
var obj = allObjects[i];
var bounds = obj.geometricBounds;
objectData.push({
object: obj,
originalBounds: [bounds[0], bounds[1], bounds[2], bounds[3]],
originalWidth: bounds[3] - bounds[1],
originalHeight: bounds[2] - bounds[0],
isGraphic: isGraphicFrame(obj),
isText: isTextFrame(obj)
});
}
// Y座標でグループ化(行の検出)
var rows = groupByRows(objectData);
// X座標でグループ化(列の検出)
var columns = groupByColumns(objectData);
// 行間と列間の間隔を計算
var rowGaps = calculateRowGaps(rows);
var columnGaps = calculateColumnGaps(columns);
// 各オブジェクトのサイズを変更
for (var i = 0; i < objectData.length; i++) {
var data = objectData[i];
var obj = data.object;
var newWidth, newHeight;
if (data.isText) {
// テキストフレーム:横幅のみ拡大縮小
newWidth = data.originalWidth * horizontalRatio;
newHeight = data.originalHeight; // 高さは変更しない
} else {
// グラフィックフレームやその他:縦横それぞれ拡大縮小
newWidth = data.originalWidth * horizontalRatio;
newHeight = data.originalHeight * verticalRatio;
}
// 左上を基準にサイズ変更
var bounds = data.originalBounds;
obj.geometricBounds = [
bounds[0], // top (変更なし)
bounds[1], // left (変更なし)
bounds[0] + newHeight, // bottom
bounds[1] + newWidth // right
];
// 内容の拡大縮小(グラフィックフレームの場合)
if (data.isGraphic && obj.allGraphics.length > 0) {
obj.allGraphics[0].horizontalScale *= horizontalRatio;
if (!data.isText) {
obj.allGraphics[0].verticalScale *= verticalRatio;
}
}
}
// 列間隔を調整
adjustColumnSpacing(columns, columnGaps);
// 行間隔を調整
adjustRowSpacing(rows, rowGaps);
// 基準点に合わせて全体を移動
var newBounds = getSelectionBounds(allObjects);
var newAnchorX = calculateAnchorX(newBounds, anchorIndex);
var newAnchorY = calculateAnchorY(newBounds, anchorIndex);
var offsetX = anchorX - newAnchorX;
var offsetY = anchorY - newAnchorY;
// 全オブジェクトを移動
for (var i = 0; i < allObjects.length; i++) {
var obj = allObjects[i];
var bounds = obj.geometricBounds;
obj.geometricBounds = [
bounds[0] + offsetY,
bounds[1] + offsetX,
bounds[2] + offsetY,
bounds[3] + offsetX
];
}
}
// Y座標で行にグループ化
function groupByRows(objectData) {
var rows = [];
var tolerance = 1; // 1ポイントの誤差を許容
for (var i = 0; i < objectData.length; i++) {
var obj = objectData[i];
var top = obj.originalBounds[0];
// 既存の行を検索
var foundRow = false;
for (var r = 0; r < rows.length; r++) {
var rowTop = rows[r][0].originalBounds[0];
if (Math.abs(top - rowTop) <= tolerance) {
rows[r].push(obj);
foundRow = true;
break;
}
}
// 新しい行を作成
if (!foundRow) {
rows.push([obj]);
}
}
// 各行を上から下の順にソート
rows.sort(function(a, b) {
return a[0].originalBounds[0] - b[0].originalBounds[0];
});
// 各行内を左から右の順にソート
for (var r = 0; r < rows.length; r++) {
rows[r].sort(function(a, b) {
return a.originalBounds[1] - b.originalBounds[1];
});
}
return rows;
}
// X座標で列にグループ化
function groupByColumns(objectData) {
var columns = [];
var tolerance = 1; // 1ポイントの誤差を許容
for (var i = 0; i < objectData.length; i++) {
var obj = objectData[i];
var left = obj.originalBounds[1];
// 既存の列を検索
var foundColumn = false;
for (var c = 0; c < columns.length; c++) {
var columnLeft = columns[c][0].originalBounds[1];
if (Math.abs(left - columnLeft) <= tolerance) {
columns[c].push(obj);
foundColumn = true;
break;
}
}
// 新しい列を作成
if (!foundColumn) {
columns.push([obj]);
}
}
// 各列を左から右の順にソート
columns.sort(function(a, b) {
return a[0].originalBounds[1] - b[0].originalBounds[1];
});
// 各列内を上から下の順にソート
for (var c = 0; c < columns.length; c++) {
columns[c].sort(function(a, b) {
return a.originalBounds[0] - b.originalBounds[0];
});
}
return columns;
}
// 行間の間隔を計算
function calculateRowGaps(rows) {
var gaps = [];
for (var i = 0; i < rows.length - 1; i++) {
// 現在の行の最大bottom
var currentBottom = rows[i][0].originalBounds[2];
for (var j = 1; j < rows[i].length; j++) {
if (rows[i][j].originalBounds[2] > currentBottom) {
currentBottom = rows[i][j].originalBounds[2];
}
}
// 次の行の最小top
var nextTop = rows[i + 1][0].originalBounds[0];
for (var j = 1; j < rows[i + 1].length; j++) {
if (rows[i + 1][j].originalBounds[0] < nextTop) {
nextTop = rows[i + 1][j].originalBounds[0];
}
}
gaps.push(nextTop - currentBottom);
}
return gaps;
}
// 列間の間隔を計算
function calculateColumnGaps(columns) {
var gaps = [];
for (var i = 0; i < columns.length - 1; i++) {
// 現在の列の最大right
var currentRight = columns[i][0].originalBounds[3];
for (var j = 1; j < columns[i].length; j++) {
if (columns[i][j].originalBounds[3] > currentRight) {
currentRight = columns[i][j].originalBounds[3];
}
}
// 次の列の最小left
var nextLeft = columns[i + 1][0].originalBounds[1];
for (var j = 1; j < columns[i + 1].length; j++) {
if (columns[i + 1][j].originalBounds[1] < nextLeft) {
nextLeft = columns[i + 1][j].originalBounds[1];
}
}
gaps.push(nextLeft - currentRight);
}
return gaps;
}
// 列間隔を調整
function adjustColumnSpacing(columns, columnGaps) {
for (var c = 1; c < columns.length; c++) {
// 前の列の現在の最大right
var prevColumnRight = columns[c - 1][0].object.geometricBounds[3];
for (var j = 1; j < columns[c - 1].length; j++) {
if (columns[c - 1][j].object.geometricBounds[3] > prevColumnRight) {
prevColumnRight = columns[c - 1][j].object.geometricBounds[3];
}
}
// 目標left位置
var targetLeft = prevColumnRight + columnGaps[c - 1];
// 現在の列の現在のleft
var currentLeft = columns[c][0].object.geometricBounds[1];
for (var j = 1; j < columns[c].length; j++) {
if (columns[c][j].object.geometricBounds[1] < currentLeft) {
currentLeft = columns[c][j].object.geometricBounds[1];
}
}
// 移動量
var offsetX = targetLeft - currentLeft;
// 列内の全オブジェクトを移動
for (var i = 0; i < columns[c].length; i++) {
var obj = columns[c][i].object;
var bounds = obj.geometricBounds;
obj.geometricBounds = [
bounds[0],
bounds[1] + offsetX,
bounds[2],
bounds[3] + offsetX
];
}
}
}
// 行間隔を調整
function adjustRowSpacing(rows, rowGaps) {
for (var r = 1; r < rows.length; r++) {
// 前の行の現在の最大bottom
var prevRowBottom = rows[r - 1][0].object.geometricBounds[2];
for (var j = 1; j < rows[r - 1].length; j++) {
if (rows[r - 1][j].object.geometricBounds[2] > prevRowBottom) {
prevRowBottom = rows[r - 1][j].object.geometricBounds[2];
}
}
// 目標top位置
var targetTop = prevRowBottom + rowGaps[r - 1];
// 現在の行の現在のtop
var currentTop = rows[r][0].object.geometricBounds[0];
for (var j = 1; j < rows[r].length; j++) {
if (rows[r][j].object.geometricBounds[0] < currentTop) {
currentTop = rows[r][j].object.geometricBounds[0];
}
}
// 移動量
var offsetY = targetTop - currentTop;
// 行内の全オブジェクトを移動
for (var i = 0; i < rows[r].length; i++) {
var obj = rows[r][i].object;
var bounds = obj.geometricBounds;
obj.geometricBounds = [
bounds[0] + offsetY,
bounds[1],
bounds[2] + offsetY,
bounds[3]
];
}
}
}
// 基準点のX座標を計算
function calculateAnchorX(bounds, anchorIndex) {
var width = bounds[3] - bounds[1];
switch (anchorIndex) {
case 0: case 3: case 6: // 左
return bounds[1];
case 1: case 4: case 7: // 中央
return bounds[1] + width / 2;
case 2: case 5: case 8: // 右
return bounds[3];
}
}
// 基準点のY座標を計算
function calculateAnchorY(bounds, anchorIndex) {
var height = bounds[2] - bounds[0];
switch (anchorIndex) {
case 0: case 1: case 2: // 上
return bounds[0];
case 3: case 4: case 5: // 中央
return bounds[0] + height / 2;
case 6: case 7: case 8: // 下
return bounds[2];
}
}
// 選択範囲全体の境界を取得
function getSelectionBounds(objects) {
var minTop = Number.MAX_VALUE;
var minLeft = Number.MAX_VALUE;
var maxBottom = -Number.MAX_VALUE;
var maxRight = -Number.MAX_VALUE;
for (var i = 0; i < objects.length; i++) {
var bounds = objects[i].geometricBounds;
if (bounds[0] < minTop) minTop = bounds[0];
if (bounds[1] < minLeft) minLeft = bounds[1];
if (bounds[2] > maxBottom) maxBottom = bounds[2];
if (bounds[3] > maxRight) maxRight = bounds[3];
}
return [minTop, minLeft, maxBottom, maxRight];
}
// グラフィックフレーム判定
function isGraphicFrame(obj) {
return (obj.constructor.name == "Rectangle" ||
obj.constructor.name == "Oval" ||
obj.constructor.name == "Polygon") &&
obj.contentType == ContentType.GRAPHIC_TYPE;
}
// テキストフレーム判定
function isTextFrame(obj) {
return obj.constructor.name == "TextFrame" ||
(obj.constructor.name == "Rectangle" && obj.contentType == ContentType.TEXT_TYPE);
}
// メイン実行
try {
main();
} catch (e) {
alert("エラーが発生しました: " + e.message);
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment