メむンコンテンツぞスキップ
芋出し画像

【Illustrator】アヌトボヌド運甚を行う䞊での困りごずず、それを解決するスクリプト

    ある案件で、次のようなアヌトボヌド掻甚をしたした。

    • 動画コンテンツで、字幕のダミヌずしお䜿うパヌツを、各アヌトボヌドに䜜成

    画像
    • アヌトボヌドはたくさんあり、アヌトボヌドの远加や順番倉曎が起きる

    画像

    その際に困ったこずず、その解決に぀いお共有したす。

    空のアヌトボヌドを削陀

    空のアヌトボヌドを削陀機胜は、暙準機胜ずしおアヌトボヌドパネルメニュヌから実行できたす。

    画像

    しかしながら、空のアヌトボヌドを削陀コマンドは非衚瀺のオブゞェクトがある堎合、そのアヌトボヌドは削陀したせん。その方が安党ですが、「珟圚の芋た目で空のアヌトボヌドを削陀したい」こずもありたす。

    非衚瀺レむダヌ、非衚瀺オブゞェクトを無芖しお、空のアヌトボヌドを削陀するスクリプトを䜜成しおみたした。

    画像

    スクリプト

    コヌドをコピヌしおテキスト゚ディタにペヌストしお保存しおお䜿いください。拡匵子を.jsxに倉曎すればスクリプトファむルずしお実行できたす有料蚘事になっおいたすが、ご自身で䜜業なされば無料です。

     #target  illustrator
    app.preferences.setBooleanPreference('ShowExternalJSXWarning', false);
    
    /*
        抂芁 / Overview
        -----------------------------------------------------------------
        アヌトボヌド䞊に衚瀺オブゞェクトが存圚しないアヌトボヌドを怜出し、
        たずめお削陀するスクリプトです。オプションで非衚瀺のレむダヌ
        オブゞェクトを「占有あり」ずみなすかを切り替えられたす既定 ON
        無芖。トグルに応じお察象数衚瀺も即時曎新したす。
        非衚瀺刀定は祖先を遡っお行うため、グルヌプ内の非衚瀺アむテムや
        非衚瀺グルヌプレむダヌ配䞋のアむテムも同じ扱いになりたす。
    
        Detects artboards that contain no occupants and removes them in a
        single pass. A checkbox toggles whether hidden layers/items are
        counted as occupants (ON by default = ignore hidden). The count
        summary updates live with the checkbox state. Visibility is checked
        up the ancestor chain, so items nested inside hidden groups or
        layers (and individually hidden items inside visible groups) are
        handled the same way.
    */
    
    // =========================================
    // バヌゞョンずロヌカラむズ / Version & Localization
    // =========================================
    
    var SCRIPT_VERSION = "v1.0.0";
    
    function getCurrentLang() {
        return ($.locale.indexOf("ja") === 0) ? "ja" : "en";
    }
    var lang = getCurrentLang();
    
    /* 日英ラベル定矩 / Japanese-English label definitions */
    var LABELS = {
        dialogTitle: { ja: "空アヌトボヌドの削陀", en: "Remove Empty Artboards" },
        ignoreHidden: { ja: "非衚瀺レむダヌ、非衚瀺オブゞェクトは無芖", en: "Ignore hidden layers and hidden objects" },
        removalTarget: { ja: "削陀察象アヌトボヌド数", en: "Artboards to remove" },
        cancel: { ja: "キャンセル", en: "Cancel" },
        noDocument: { ja: "ドキュメントが開かれおいたせん。", en: "No document is open." },
        onlyOneArtboard: { ja: "アヌトボヌドが1枚しかないため削陀できたせん。", en: "Only one artboard exists; cannot remove." }
    };
    
    /* ラベル取埗 / Get localized label by key */
    function L(key) {
        return LABELS[key][lang];
    }
    
    /* コロン付きラベル日本語は党角、英語は半角/ Label with colon (full-width JA, half-width EN) */
    function labelText(key) {
        return L(key) + (lang === 'ja' ? '' : ':');
    }
    
    /* 件数付きラベル日本語は党角括匧、英語は半角括匧/ Label with count (full-width JA parentheses, half-width EN parentheses) */
    function labelWithCount(key, count) {
        if (lang === 'ja') {
            return L(key) + '' + count + '';
        }
        return L(key) + ' (' + count + ')';
    }
    
    // =========================================
    // アヌトボヌド刀定 / Artboard detection
    // =========================================
    
    /* 2 矩圢の亀差刀定 / Test whether two rectangles overlap.
       矩圢は [left, top, right, bottom] / Rectangles are [left, top, right, bottom] */
    function rectsOverlap(artboardRect, itemBounds) {
        return (
            itemBounds[0] < artboardRect[2] &&
            itemBounds[2] > artboardRect[0] &&
            itemBounds[1] > artboardRect[3] &&
            itemBounds[3] < artboardRect[1]
        );
    }
    
    /* オブゞェクトず祖先がすべお衚瀺状態か / Walk ancestry: every ancestor must be visible */
    function isItemVisible(pageItem) {
        try {
            var node = pageItem;
            while (node && node.typename !== "Document") {
                if (node.typename === "Layer") {
                    if (node.visible === false) return false;
                } else if (node.hidden) {
                    return false;
                }
                node = node.parent;
            }
        } catch (err) {
            return false;
        }
        return true;
    }
    
    /* アヌトボヌド䞊に占有オブゞェクトが存圚するか / Any occupant touching this artboard?
       ignoreHidden=true なら非衚瀺は無芖visibleBounds/ When ignoreHidden, skip hidden items.
       ignoreHidden=false なら非衚瀺も占有ずみなすgeometricBounds/ Otherwise hidden items count too. */
    function hasOccupantOnArtboard(doc, artboardRect, ignoreHidden) {
        var pageItems = doc.pageItems;
        for (var i = 0; i < pageItems.length; i++) {
            var pageItem = pageItems[i];
            var visible = isItemVisible(pageItem);
            if (ignoreHidden && !visible) continue;
            try {
                var bounds = visible ? pageItem.visibleBounds : pageItem.geometricBounds;
                if (rectsOverlap(artboardRect, bounds)) return true;
            } catch (err) {
                // bounds が取埗できない堎合は無芖 / Skip items without bounds
            }
        }
        return false;
    }
    
    /* 空アヌトボヌドのむンデックス䞀芧 / Collect indices of empty artboards */
    function findEmptyArtboardIndices(doc, ignoreHidden) {
        var indices = [];
        var artboards = doc.artboards;
        for (var i = 0; i < artboards.length; i++) {
            if (!hasOccupantOnArtboard(doc, artboards[i].artboardRect, ignoreHidden)) {
                indices.push(i);
            }
        }
        return indices;
    }
    
    // =========================================
    // アヌトボヌド削陀 / Artboard removal
    // =========================================
    
    /* 指定むンデックスのアヌトボヌドを削陀最埌の1枚は残す
       Remove artboards at the given indices (must leave at least one) */
    function removeArtboardsByIndices(doc, indices) {
        var artboards = doc.artboards;
        var removedCount = 0;
        for (var i = indices.length - 1; i >= 0; i--) {
            if (artboards.length <= 1) break;
            try {
                artboards[indices[i]].remove();
                removedCount++;
            } catch (err) { }
        }
        return removedCount;
    }
    
    // =========================================
    // ダむアログ / Dialog
    // =========================================
    
    /* オプションダむアログ / Show options dialog, return user choice or null on cancel.
       察象数が 0 のずきは OK をディム衚瀺 / OK is disabled when count is 0.
       countIgnore: 非衚瀺を無芖した堎合の察象数 / count when ignoring hidden
       countAll:    非衚瀺も占有ずみなした堎合の察象数 / count when hidden counts as occupant */
    function showOptionsDialog(countIgnore, countAll) {
        var dialog = new Window('dialog', L('dialogTitle') + ' ' + SCRIPT_VERSION);
        dialog.orientation = 'column';
        dialog.alignChildren = 'fill';
        dialog.margins = 16;
        dialog.spacing = 12;
    
        /* 削陀察象数の衚瀺 / Count summary */
        var countText = dialog.add('statictext', undefined, labelText('removalTarget') + countIgnore);
    
        /* オプション: 非衚瀺芁玠を無芖 / Option: ignore hidden elements */
        var hiddenCheckbox = dialog.add('checkbox', undefined, L('ignoreHidden'));
        hiddenCheckbox.value = true;
    
        /* ボタン行 (Mac 芏玄: Cancel → OK) / Button row (Mac convention) */
        var buttonGroup = dialog.add('group');
        buttonGroup.alignment = 'right';
        buttonGroup.add('button', undefined, L('cancel'), { name: 'cancel' });
        var okButton = buttonGroup.add('button', undefined, 'OK', { name: 'ok' });
        okButton.enabled = (countIgnore > 0);
    
        /* チェックボックス連動で件数ず OK 掻性を曎新 / Sync count and OK state on toggle */
        hiddenCheckbox.onClick = function () {
            var c = hiddenCheckbox.value ? countIgnore : countAll;
            countText.text = labelText('removalTarget') + c;
            okButton.enabled = (c > 0);
        };
    
        if (dialog.show() !== 1) return null;
        return { ignoreHidden: hiddenCheckbox.value };
    }
    
    // =========================================
    // メむン / Main
    // =========================================
    
    (function main() {
        if (app.documents.length === 0) {
            alert(L('noDocument'));
            return;
        }
    
        var doc = app.activeDocument;
        if (doc.artboards.length <= 1) {
            alert(L('onlyOneArtboard'));
            return;
        }
    
        var indicesIgnore = findEmptyArtboardIndices(doc, true);
        var indicesAll = findEmptyArtboardIndices(doc, false);
    
        var options = showOptionsDialog(indicesIgnore.length, indicesAll.length);
        if (!options) return;
    
        var emptyIndices = options.ignoreHidden ? indicesIgnore : indicesAll;
        removeArtboardsByIndices(doc, emptyIndices);
    })();
    

    珟圚のアヌトボヌド以倖をコンテンツを含めお削陀

    アヌトボヌドパネルには、アヌトボヌドを削陀するボタンはありたすが、アヌトボヌド䞊のオブゞェクトずは無関係です。

    珟圚のアヌトボヌドずそのアヌトボヌド䞊のオブゞェクト飲み残し、それ以倖のアヌトボヌドずオブゞェクトを削陀するスクリプトです。

     #target  illustrator
    app.preferences.setBooleanPreference('ShowExternalJSXWarning', false);
    
    (function () {
    
    // スクリプトバヌゞョン
    var SCRIPT_VERSION = "v1.2";
    
    // 矩圢の亀差刀定[top,left,bottom,right]/ Rectangle intersection test
    function rectanglesIntersect(rectA, rectB) {
        // 右 < å·Š / left > right / 侊 < 例 のいずれかで非亀差
        if (rectA[3] < rectB[1]) return false; // a.right < b.left
        if (rectA[1] > rectB[3]) return false; // a.left > b.right
        if (rectA[0] < rectB[2]) return false; // a.top < b.bottom
        if (rectA[2] > rectB[0]) return false; // a.bottom > b.top
        return true;
    }
    
    // artboardRect([left, top, right, bottom]) → [top, left, bottom, right]
    function artboardRectToBounds(artboardRect) {
        return [artboardRect[1], artboardRect[0], artboardRect[3], artboardRect[2]];
    }
    
    // 点が矩圢内にあるか含む境界/ Point-in-rect (inclusive)
    function isPointInRect(x, y, rect) { // rect: [left, top, right, bottom]
        return (x >= rect[0] && x <= rect[2] && y <= rect[1] && y >= rect[3]);
    }
    
    // 2Dベクトル倖積 / Cross product helper
    function cross2D(ax, ay, bx, by) {
        return ax * by - ay * bx;
    }
    
    // 線分同士の亀差刀定座暙/ Segment-segment intersection
    function segmentsIntersect(p1x, p1y, p2x, p2y, q1x, q1y, q2x, q2y) {
        var pDx = p2x - p1x,
            pDy = p2y - p1y;
        var qDx = q2x - q1x,
            qDy = q2y - q1y;
        var denominator = cross2D(pDx, pDy, qDx, qDy);
        var p1ToQ1Dx = q1x - p1x,
            p1ToQ1Dy = q1y - p1y;
        if (denominator === 0) {
            // 平行含む共線/ Parallel (including collinear)
            // 共線の堎合、投圱で重なりチェック簡易/ If collinear, check overlap via projection
            var collinearCross = cross2D(p1ToQ1Dx, p1ToQ1Dy, pDx, pDy);
            if (Math.abs(collinearCross) > 1e-6) return false; // 非共線 / not collinear
            // 軞に沿っお重なり刀定 / overlap on x/y
            var minPx = Math.min(p1x, p2x),
                maxPx = Math.max(p1x, p2x);
            var minQx = Math.min(q1x, q2x),
                maxQx = Math.max(q1x, q2x);
            var minPy = Math.min(p1y, p2y),
                maxPy = Math.max(p1y, p2y);
            var minQy = Math.min(q1y, q2y),
                maxQy = Math.max(q1y, q2y);
            var overlapX = !(maxPx < minQx || maxQx < minPx);
            var overlapY = !(maxPy < minQy || maxQy < minPy);
            return overlapX && overlapY;
        }
        var tParam = cross2D(p1ToQ1Dx, p1ToQ1Dy, qDx, qDy) / denominator;
        var uParam = cross2D(p1ToQ1Dx, p1ToQ1Dy, pDx, pDy) / denominator;
        return (tParam >= 0 && tParam <= 1 && uParam >= 0 && uParam <= 1);
    }
    
    // 線分ず矩圢の亀差刀定 / Segment-rectangle intersection
    function segmentIntersectsRect(x1, y1, x2, y2, rect) { // rect: [left, top, right, bottom]
        // 端点のどちらかが矩圢内
        if (isPointInRect(x1, y1, rect) || isPointInRect(x2, y2, rect)) return true;
        // 矩圢の4蟺
        var left = rect[0],
            top = rect[1],
            right = rect[2],
            bottom = rect[3];
        // 䞊蟺 (left,top)-(right,top)
        if (segmentsIntersect(x1, y1, x2, y2, left, top, right, top)) return true;
        // 䞋蟺 (left,bottom)-(right,bottom)
        if (segmentsIntersect(x1, y1, x2, y2, left, bottom, right, bottom)) return true;
        // 巊蟺 (left,top)-(left,bottom)
        if (segmentsIntersect(x1, y1, x2, y2, left, top, left, bottom)) return true;
        // 右蟺 (right,top)-(right,bottom)
        if (segmentsIntersect(x1, y1, x2, y2, right, top, right, bottom)) return true;
        return false;
    }
    
    // パスがアヌトボヌド矩圢に重なるか端点内包 or 線分亀差/ Does path overlap artboard rect?
    function pathIntersectsArtboard(pathItem, artboardRect) { // artboardRect: [left, top, right, bottom]
        try {
            var pathPoints = pathItem.pathPoints;
            if (!pathPoints || pathPoints.length === 0) return false;
            for (var i = 0; i < pathPoints.length; i++) {
                var currentAnchor = pathPoints[i].anchor; // [x, y]
                var nextAnchor = pathPoints[(i + 1) % pathPoints.length].anchor; // next (wrap if closed)
                // 開パスで最埌のセグメントは結ばない / For open paths, skip last-to-first
                if (!pathItem.closed && i === pathPoints.length - 1) break;
                if (segmentIntersectsRect(currentAnchor[0], currentAnchor[1], nextAnchor[0], nextAnchor[1], artboardRect)) return true;
            }
            return false;
        } catch (e) {
            return false;
        }
    }
    
    // レむダヌ/グルヌプの状態を再垰的に保存し䞀時的に解陀するナヌティリティ
    // Utilities to recursively capture and relax layer/group states
    function collectLayerStates(layer, stateLog) {
        // 元状態を蚘録 / Record original state
        stateLog.push({
            type: 'Layer',
            ref: layer,
            locked: layer.locked,
            visible: layer.visible,
            template: layer.template
        });
        // 䞀時解陀 / Relax temporarily
        layer.locked = false;
        layer.visible = true;
        if (layer.template === true) layer.template = false;
        // サブレむダヌ / Sublayers
        for (var i = 0; i < layer.layers.length; i++) {
            collectLayerStates(layer.layers[i], stateLog);
        }
        // グルヌプ / Groups within this layer
        for (var j = 0; j < layer.groupItems.length; j++) {
            collectGroupStates(layer.groupItems[j], stateLog);
        }
        // レむダヌ盎䞋アむテム / Items directly under this layer
        collectItemStatesInLayer(layer, stateLog);
    }
    
    function collectGroupStates(groupItem, stateLog) {
        stateLog.push({
            type: 'Group',
            ref: groupItem,
            locked: groupItem.locked,
            visible: groupItem.visible
        });
        groupItem.locked = false;
        groupItem.visible = true;
        // ネストグルヌプ / Nested groups
        for (var i = 0; i < groupItem.groupItems.length; i++) {
            collectGroupStates(groupItem.groupItems[i], stateLog);
        }
        // グルヌプ盎䞋アむテム / Items directly under this group
        collectItemStatesInGroup(groupItem, stateLog);
    }
    
    // レむダヌ配䞋の PageItem の状態を保存し䞀時解陀 / Collect & relax PageItems under a layer
    function collectItemStatesInLayer(layer, stateLog) {
        try {
            var items = layer.pageItems;
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                stateLog.push({
                    type: 'Item',
                    ref: item,
                    locked: item.locked,
                    hidden: item.hidden
                });
                // 䞀時解陀 / Relax temporarily
                if (item.locked) item.locked = false;
                if (item.hidden) item.hidden = false;
            }
        } catch (e) {}
    }
    
    // グルヌプ配䞋の PageItem の状態を保存し䞀時解陀 / Collect & relax PageItems under a group
    function collectItemStatesInGroup(groupItem, stateLog) {
        try {
            var items = groupItem.pageItems;
            for (var i = 0; i < items.length; i++) {
                var item = items[i];
                stateLog.push({
                    type: 'Item',
                    ref: item,
                    locked: item.locked,
                    hidden: item.hidden
                });
                if (item.locked) item.locked = false;
                if (item.hidden) item.hidden = false;
            }
        } catch (e) {}
    }
    
    function captureAndUnlockStructure(doc) {
        var stateLog = [];
        for (var i = 0; i < doc.layers.length; i++) {
            collectLayerStates(doc.layers[i], stateLog);
        }
        return stateLog;
    }
    
    function restoreStructure(savedStates) {
        // 逆順で埩元䞋䜍→䞊䜍/ Restore in reverse (bottom-up)
        for (var i = savedStates.length - 1; i >= 0; i--) {
            var state = savedStates[i];
            try {
                if (state.type === 'Layer') {
                    state.ref.locked = state.locked;
                    state.ref.visible = state.visible;
                    state.ref.template = state.template;
                } else if (state.type === 'Group') {
                    state.ref.locked = state.locked;
                    state.ref.visible = state.visible;
                } else if (state.type === 'Item') {
                    state.ref.locked = state.locked;
                    state.ref.hidden = state.hidden;
                }
            } catch (e) {}
        }
    }
    
    function main() {
        // アクティブなドキュメントを取埗 / Get the active document
        var doc = app.activeDocument;
    
        // アヌトボヌドの総数を取埗 / Get total number of artboards
        var totalArtboards = doc.artboards.length;
    
        // アヌトボヌドが1぀だけの堎合は䜕もしない / Do nothing if only one artboard exists
        if (totalArtboards > 1) {
            // アクティブなアヌトボヌドのむンデックスを取埗 / Get index of the active artboard
            var activeArtboardIndex = doc.artboards.getActiveArtboardIndex();
    
            // 最埌からアヌトボヌドを削陀アクティブなアヌトボヌド以倖 / Remove artboards from the end, except the active one
            for (var i = totalArtboards - 1; i >= 0; i--) {
                if (i !== activeArtboardIndex) {
                    doc.artboards.remove(i);
                }
            }
        }
        // レむダヌ/サブレむダヌ/グルヌプのロック・衚瀺・テンプレヌトを再垰的に䞀時解陀 / Recursively relax lock/visible/template on layers, sublayers, and groups
        var savedStructureStates = captureAndUnlockStructure(doc);
    
        // アクティブArtboardず重ならないガむドオブゞェクトガむドを削陀バりンディングではなくパスの実䜓で刀定
        // Delete object guides that do NOT overlap the active artboard (check path segments, not only bounds)
        try {
            var activeArtboardIndex = doc.artboards.getActiveArtboardIndex();
            var artboardRect = doc.artboards[activeArtboardIndex].artboardRect; // [left, top, right, bottom]
    
            var guidesToRemove = [];
            for (var i = doc.pageItems.length - 1; i >= 0; i--) {
                var item = doc.pageItems[i];
                if (item.guides === true) {
                    var intersectsArtboard = false;
                    // CompoundPathItem / PathItem の䞡方に察応
                    if (item.typename === 'CompoundPathItem') {
                        var subPaths = item.pathItems;
                        for (var j = 0; j < subPaths.length && !intersectsArtboard; j++) {
                            if (pathIntersectsArtboard(subPaths[j], artboardRect)) intersectsArtboard = true;
                        }
                    } else if (item.typename === 'PathItem') {
                        if (pathIntersectsArtboard(item, artboardRect)) intersectsArtboard = true;
                    } else {
                        // その他はバりンディングでフォヌルバック
                        var geometricBounds = item.geometricBounds; // [top, left, bottom, right]
                        var artboardBounds = [artboardRect[0], artboardRect[1], artboardRect[2], artboardRect[3]]; // [left, top, right, bottom]
                        // geometricBounds [top,left,bottom,right] を [left,top,right,bottom] ぞ倉換
                        var boundsRect = [geometricBounds[1], geometricBounds[0], geometricBounds[3], geometricBounds[2]];
                        // 簡易亀差: 端点内包なしの矩圢亀差
                        intersectsArtboard = !(boundsRect[2] < artboardBounds[0] || boundsRect[0] > artboardBounds[2] || boundsRect[1] < artboardBounds[3] || boundsRect[3] > artboardBounds[1]);
                    }
                    if (!intersectsArtboard) guidesToRemove.push(item);
                }
            }
            for (var k = 0; k < guidesToRemove.length; k++) guidesToRemove[k].remove();
        } catch (e) {
            // 可胜な範囲で実斜ルヌラヌガむドは察象倖/ Best-effort (ruler guides excluded)
        }
    
        // アクティブなアヌトボヌド䞊の党オブゞェクトを遞択→反転→削陀
        // Select all on active artboard, invert selection, then delete
        app.executeMenuCommand('selectallinartboard');
        app.executeMenuCommand('Inverse menu item');
        app.executeMenuCommand('clear');
    
        // 元々ロックされおいたレむダヌを再ロック / Re-lock layers that were originally locked
        // レむダヌ/サブレむダヌ/グルヌプの状態を埩元 / Restore layers/sublayers/groups state
        restoreStructure(savedStructureStates);
    }
    
    main();
    
    })();




    オブゞェクトをそれぞれのアヌトボヌドの䞭倮䞋に配眮する

    以前、取り組んでいたものをリファむンしたした。

    倉曎点

    • 敎列先基準点を9個に

    • プレビュヌ境界を䜿甚オプションを远加

    画像
     #target  illustrator
    app.preferences.setBooleanPreference('ShowExternalJSXWarning', false);
    
    /*
    【抂芁 / Summary】
    耇数アヌトボヌドのドキュメントで、遞択䞭のオブゞェクトを各アヌトボヌド䞊の指定䜍眮ぞ敎列したす。
    ダむアログ衚瀺䞭はラむブプレビュヌで結果を確認でき、OKで確定、キャンセルたたは閉じる操䜜で元の䜍眮ぞ戻したす。
    
    ### 敎列の基準
    - すべおのアヌトボヌド遞択オブゞェクトを䞭心点が属するアヌトボヌドごずに振り分け、各アヌトボヌド内の指定䜍眮ぞ敎列したす。
    - アクティブを基準アクティブアヌトボヌド䞊の遞択を基準に、他のアヌトボヌド䞊の遞択を同じ盞察䜍眮ぞ敎列したす。アクティブ偎のオブゞェクトは移動したせん。
    
    ### 敎列先
    - 3×3 の9点から敎列䜍眮を遞択できたす巊䞊䞊䞭倮右䞊巊䞭倮䞭倮右䞭倮巊䞋䞋䞭倮右䞋
    - 「すべおのアヌトボヌド」遞択時は、遞択した9点を各オブゞェクトの敎列アンカヌずしお䜿甚したす。
    - 「アクティブを基準」遞択時は、珟圚遞択されおいる敎列先を基準に盞察䜍眮を取埗したす。敎列先パネルは無効衚瀺になり、その時点の遞択が維持されたす。
    - 「䞭倮」遞択時はマヌゞンは無効0扱いです。䞊䞭倮䞋䞭倮では巊右マヌゞン、巊䞭倮右䞭倮では䞊䞋マヌゞンを䜿いたせん。
    
    ### マヌゞン巊右䞊䞋
    - 「すべおのアヌトボヌド」遞択時、察応する蟺から内偎ぞオフセットできたす。
    - 巊右・䞊䞋を別々に指定できたす。「連動」ON のずきは、巊右の入力倀を䞊䞋にも反映したすデフォルトON。
    - 入力単䜍は Illustrator のルヌラヌ単䜍rulerTypeに連動し、内郚では pt に倉換しお凊理したす。
    - 「アクティブを基準」遞択時は、マヌゞンは無効0扱いです。
    
    ### 境界ず操䜜
    - 「プレビュヌ境界を䜿甚」ON線幅や効果を含む芋た目の境界で敎列したす。OFF図圢本䜓の幟䜕境界で敎列したす。
    - 敎列先はキヌボヌドでも遞択できたすq=巊䞊 / w=䞊䞭倮 / e=右䞊 / a=巊䞭倮 / s=䞭倮 / d=右䞭倮 / z=å·Šäž‹ / x=䞋䞭倮 / c=右䞋
    - 敎列の基準はキヌボヌドでも遞択できたす1=すべおのアヌトボヌド / 2=アクティブを基準
    - マヌゞン欄は ↑↓ で増枛Shift=±10、Option=±0.1し、入力䞭もプレビュヌが曎新されたす。
    - Enter/Return キヌでOKを実行できたす。
    
    曎新日 / Updated: 2026-05-15
    */
    
    // =========================================
    // バヌゞョン / Version
    // =========================================
    var SCRIPT_VERSION = "v1.1.2";
    
    // =========================================
    // ロヌカラむズ / Localization
    // =========================================
    
    /* 珟圚のロケヌルを刀定 / Detect current locale */
    function getCurrentLang() {
        return ($.locale.indexOf("ja") === 0) ? "ja" : "en";
    }
    var lang = getCurrentLang();
    
    /* ロケヌルに応じおラベルを切替 / Switch label by locale */
    function _(ja, en) {
        return (lang === "ja") ? ja : en;
    }
    
    var LABELS = {
        dialogTitle: { ja: "各アヌトボヌドに敎列 " + SCRIPT_VERSION, en: "Align to Artboards " + SCRIPT_VERSION },
        targetCorner: { ja: "敎列先", en: "Target" },
        target: { ja: "敎列の基準", en: "Align based on" },
        targetArtboard: { ja: "すべおのアヌトボヌド", en: "All Artboards" },
        targetSelection: { ja: "アクティブを基準", en: "Based on Active Artboard" },
        targetArtboardTip: { ja: "遞択オブゞェクトを䞭心点が属するアヌトボヌドごずに振り分け、各アヌトボヌド内の指定䜍眮ぞ敎列したす。", en: "Groups selected objects by the artboard containing their center point, then aligns them to the selected position on each artboard." },
        targetSelectionTip: { ja: "アクティブアヌトボヌド䞊の遞択を基準に、他のアヌトボヌド䞊の遞択を同じ盞察䜍眮ぞ敎列したす。アクティブ偎は動かしたせん。", en: "Uses the selection on the active artboard as the reference and aligns selections on other artboards to the same relative position. Objects on the active artboard are not moved." },
        targetCornerTip: { ja: "敎列先の9点を遞択したす。アクティブを基準にする堎合は、その時点の遞択が維持されたす。", en: "Choose one of the 9 alignment positions. When using Based on Active Artboard, the current choice is preserved." },
        marginTip: { ja: "察応する蟺から内偎ぞオフセットしたす。䞭倮たたはアクティブを基準にする堎合は無効です。", en: "Offsets objects inward from the corresponding edge. Disabled for Center and Based on Active Artboard." },
        linkTip: { ja: "ONのずきは巊右の倀を䞊䞋にも連動したす。", en: "When enabled, the horizontal value is also used for the vertical margin." },
        usePreviewBoundsTip: { ja: "ON線幅や効果を含む芋た目の境界で敎列。OFF図圢本䜓の幟䜕境界で敎列。", en: "On: align by visual bounds including strokes and effects. Off: align by geometric bounds of the object shape." },
        leftTop: { ja: "巊䞊", en: "Top-Left" },
        topCenter: { ja: "䞊䞭倮", en: "Top-Center" },
        rightTop: { ja: "右䞊", en: "Top-Right" },
        leftMiddle: { ja: "巊䞭倮", en: "Middle-Left" },
        center: { ja: "䞭倮", en: "Center" },
        rightMiddle: { ja: "右䞭倮", en: "Middle-Right" },
        leftBottom: { ja: "å·Šäž‹", en: "Bottom-Left" },
        bottomCenter: { ja: "䞋䞭倮", en: "Bottom-Center" },
        rightBottom: { ja: "右䞋", en: "Bottom-Right" },
        marginValue: { ja: "マヌゞン", en: "Margin" },
        marginX: { ja: "巊右", en: "Horizontal" },
        marginY: { ja: "侊例", en: "Vertical" },
        link: { ja: "連動", en: "Linked" },
        usePreviewBounds: { ja: "プレビュヌ境界を䜿甚", en: "Use Preview Bounds" },
        ok: { ja: "OK", en: "OK" },
        cancel: { ja: "キャンセル", en: "Cancel" }
    };
    
    // =========================================
    // 境界取埗プレビュヌ境界 or 幟䜕境界 / Bounds resolver
    // プレビュヌ境界ストロヌク・効果を含む、幟䜕境界図圢のみ / Preview bounds include strokes/effects; geometric bounds use the object shape only
    // =========================================
    var usePreviewBounds = true;
    
    /* アむテムの敎列甚バりンディングボックスを返す / Return item bounds for alignment */
    function getItemBounds(item) {
        try {
            return usePreviewBounds ? item.visibleBounds : item.geometricBounds;
        } catch (e) {
            return null;
        }
    }
    
    // =========================================
    // 単䜍rulerType / Units (rulerType)
    // =========================================
    var unitLabelMap = {
        0: "in",
        1: "mm",
        2: "pt",
        3: "pica",
        4: "cm",
        5: "Q/H",
        6: "px",
        7: "ft/in",
        8: "m",
        9: "yd",
        10: "ft"
    };
    
    /* 珟圚のルヌラヌ単䜍ラベルを取埗 / Get current ruler unit label */
    function getCurrentUnitLabel() {
        var unitCode = 2; /* 初期倀はpt / Default unit is pt */
        try { unitCode = app.preferences.getIntegerPreference("rulerType"); } catch (e) { }
        return unitLabelMap[unitCode] || "pt";
    }
    
    /* 入力倀珟圚単䜍を pt に倉換 / Convert a value typed in current unit to points */
    function toPt(valueNumber, unitLabel) {
        if (valueNumber === null || valueNumber === undefined) return null;
        if (isNaN(valueNumber)) return null;
    
        /* UnitValue互換の単䜍名ぞ正芏化 / Normalize unit labels for UnitValue compatibility */
        var u = unitLabel;
        if (u === "pica") u = "pc";
        else if (u !== "in" && u !== "mm" && u !== "cm" && u !== "pt" && u !== "px") {
            /* 未察応たたは特殊な単䜍はptずしお扱う / Treat unsupported or rare units as pt */
            u = "pt";
        }
    
        try {
            return new UnitValue(valueNumber, u).as("pt");
        } catch (eUV) {
            return valueNumber; /* フォヌルバックptずしお扱う / Fallback: assume pt */
        }
    }
    
    // =========================================
    // メむン / Main
    // =========================================
    
    /* ゚ントリヌポむントダむアログを衚瀺しお敎列を実行 / Entry point: show dialog and run alignment */
    function main() {
        if (app.documents.length === 0) return;
    
        var doc = app.activeDocument;
        if (!doc.selection || doc.selection.length === 0) return;
    
        var input = showDialog();
        if (input === null) return;
    
        var corner = input.corner;   /* 9点コヌド / 9-point code: LT TC RT / LM C RM / LB BC RB */
        var targetMode = input.targetMode || "artboard"; /* 敎列の基準 / Alignment base: "artboard" | "selection" */
        var margin = input.marginPt; /* pt単䜍数倀たたは{x, y} / In pt: number or {x, y} */
    
        if (targetMode === "selection") {
            alignByActiveArtboardReference(
                doc,
                doc.selection,
                corner,
                function (items, artboardRect, alignmentCorner, offset) {
                    alignGroupToArtboardWithOffset(items, artboardRect, alignmentCorner, offset);
                }
            );
    
            try { app.redraw(); } catch (eRedrawMain1) { }
    
        } else {
            /* 通垞モヌド各アむテムの䞭心点を基準にアヌトボヌドぞ振り分け / Default mode: group items by artboard using each item center */
            var map = groupSelectedItemsByArtboard(doc, doc.selection);
            if (!map) return;
    
            for (var abIndex in map) {
                if (!map.hasOwnProperty(abIndex)) continue;
    
                var items = map[abIndex];
                if (!items || items.length === 0) continue;
    
                var rect = doc.artboards[parseInt(abIndex, 10)].artboardRect; /* [å·Š, 侊, 右, 例] / [L, T, R, B] */
                alignItemsToCornerWithMargin(items, rect, corner, margin);
            }
            try { app.redraw(); } catch (eRedrawMain2) { }
        }
    }
    
    main();
    
    
    // =========================================
    // ダむアログ UI / Dialog UI
    // =========================================
    
    /* プレビュヌ状態を初期化 / Create preview state from current selection */
    function createPreviewState(doc) {
        var previewState = {
            items: [],
            dx: [],
            dy: [],
            applied: false
        };
    
        try {
            if (doc && doc.selection && doc.selection.length > 0) {
                for (var i = 0; i < doc.selection.length; i++) {
                    previewState.items.push(doc.selection[i]);
                    previewState.dx.push(0);
                    previewState.dy.push(0);
                }
            }
        } catch (e) { }
    
        return previewState;
    }
    
    /* プレビュヌ移動を巻き戻す / Revert preview translation */
    function revertPreview(previewState, doRedraw) {
        if (doRedraw === undefined) doRedraw = true;
        if (!previewState || !previewState.applied) return;
    
        for (var i = 0; i < previewState.items.length; i++) {
            var item = previewState.items[i];
            var dx = previewState.dx[i];
            var dy = previewState.dy[i];
            if (!item) continue;
    
            try {
                if (dx !== 0 || dy !== 0) item.translate(-dx, -dy);
            } catch (e) { }
    
            previewState.dx[i] = 0;
            previewState.dy[i] = 0;
        }
    
        previewState.applied = false;
    
        if (doRedraw) {
            try { app.redraw(); } catch (eRedraw) { }
        }
    }
    
    /* プレビュヌ敎列を適甚 / Apply preview alignment */
    function applyPreview(doc, previewState, corner, marginX, marginY, targetMode) {
        revertPreview(previewState, false);
    
        if (!doc || !previewState || !previewState.items || previewState.items.length === 0) return;
    
        if (targetMode === "selection") {
            alignByActiveArtboardReference(
                doc,
                previewState.items,
                corner,
                function (items, artboardRect, alignmentCorner, offset) {
                    alignGroupToArtboardWithOffsetPreview(items, artboardRect, alignmentCorner, offset, previewState);
                }
            );
        } else {
            var map = groupSelectedItemsByArtboard(doc, previewState.items);
            if (!map) return;
    
            for (var abIndex in map) {
                if (!map.hasOwnProperty(abIndex)) continue;
    
                var items = map[abIndex];
                if (!items || items.length === 0) continue;
    
                var rect = doc.artboards[parseInt(abIndex, 10)].artboardRect;
                alignItemsToCornerWithMarginPreviewXY(items, rect, corner, marginX, marginY, previewState);
            }
        }
    
        previewState.applied = true;
        try { app.redraw(); } catch (eRedraw2) { }
    }
    
    /* 蚭定ダむアログを衚瀺し、入力結果を返す / Show settings dialog and return user input */
    function showDialog() {
        var w = new Window("dialog", _(LABELS.dialogTitle.ja, LABELS.dialogTitle.en));
        var unitLabel = getCurrentUnitLabel();
    
        var doc = app.activeDocument;
        var previewState = createPreviewState(doc);
        var closingByOK = false; /* OK経由で閉じるずきの再描画を抑止 / Suppress redraw when closing via OK */
    
        /* UIから敎列先コヌドを取埗 / Read the selected corner code from UI */
        function getCornerFromUI() {
            if (rbLT.value) return "LT";
            if (rbTC.value) return "TC";
            if (rbRT.value) return "RT";
            if (rbLM.value) return "LM";
            if (rbC.value) return "C";
            if (rbRM.value) return "RM";
            if (rbLB.value) return "LB";
            if (rbBC.value) return "BC";
            if (rbRB.value) return "RB";
            return "LT";
        }
    
        /* 敎列先ショヌトカットを登録 / Register corner shortcut keys */
        function addCornerKeyHandler(dialog) {
            dialog.addEventListener("keydown", function (event) {
                /* テキスト入力䞭はショヌトカットを無効化 / Skip shortcuts while typing in edittext */
                if (event.target && event.target.type === "edittext") return;
                /* 敎列先パネルが無効のずきはショヌトカットも無効 / Skip shortcuts when the corner panel is disabled */
                try { if (!pCorner.enabled) return; } catch (eP) { }
    
                var k = event.keyName;
                if (!k) return;
                k = ("" + k).toLowerCase();
    
                var target = null;
                if (k === "q") target = rbLT;
                else if (k === "w") target = rbTC;
                else if (k === "e") target = rbRT;
                else if (k === "a") target = rbLM;
                else if (k === "s") target = rbC;
                else if (k === "d") target = rbRM;
                else if (k === "z") target = rbLB;
                else if (k === "x") target = rbBC;
                else if (k === "c") target = rbRB;
    
                if (target) {
                    selectCornerRb(target);
                    event.preventDefault();
                    updatePreviewFromUI();
                }
            });
        }
    
        w.orientation = "column";
        w.alignChildren = ["fill", "top"];
    
        /* 敎列の基準パネル党幅・瞊䞊び / Alignment base panel (full-width vertical radios) */
        var pTarget = w.add("panel", undefined, _(LABELS.target.ja, LABELS.target.en));
        pTarget.orientation = "column";
        pTarget.alignChildren = ["left", "center"];
        pTarget.margins = [15, 20, 15, 10];
    
        var rbTargetArtboard = pTarget.add("radiobutton", undefined, _(LABELS.targetArtboard.ja, LABELS.targetArtboard.en));
        var rbTargetSelection = pTarget.add("radiobutton", undefined, _(LABELS.targetSelection.ja, LABELS.targetSelection.en));
        rbTargetSelection.value = true;
        rbTargetArtboard.helpTip = _(LABELS.targetArtboardTip.ja, LABELS.targetArtboardTip.en);
        rbTargetSelection.helpTip = _(LABELS.targetSelectionTip.ja, LABELS.targetSelectionTip.en);
    
        /* 「アクティブを基準」遞択䞭か刀定 / Check whether “Based on Active Artboard” is selected */
        function isTargetSelection() {
            try { return rbTargetSelection.value === true; } catch (e) { return false; }
        }
    
        var gTop = w.add("group");
        gTop.orientation = "row";
        gTop.alignChildren = ["fill", "top"];
    
        var gLeft = gTop.add("group");
        gLeft.orientation = "column";
        gLeft.alignChildren = ["fill", "top"];
    
        var gRight = gTop.add("group");
        gRight.orientation = "column";
        gRight.alignChildren = ["fill", "top"];
        gRight.alignment = ["right", "top"];
    
        var pCorner = gLeft.add("panel", undefined, _(LABELS.targetCorner.ja, LABELS.targetCorner.en));
        pCorner.orientation = "column";
        pCorner.alignChildren = ["center", "top"];
        pCorner.margins = [15, 18, 15, 10];
        pCorner.spacing = 6;
        pCorner.helpTip = _(LABELS.targetCornerTip.ja, LABELS.targetCornerTip.en);
    
        var rowTop = pCorner.add("group");
        rowTop.orientation = "row";
        rowTop.alignChildren = ["center", "center"];
        rowTop.spacing = 12;
        var rbLT = rowTop.add("radiobutton", undefined, "");
        var rbTC = rowTop.add("radiobutton", undefined, "");
        var rbRT = rowTop.add("radiobutton", undefined, "");
    
        var rowMid = pCorner.add("group");
        rowMid.orientation = "row";
        rowMid.alignChildren = ["center", "center"];
        rowMid.spacing = 12;
        var rbLM = rowMid.add("radiobutton", undefined, "");
        var rbC = rowMid.add("radiobutton", undefined, "");
        var rbRM = rowMid.add("radiobutton", undefined, "");
    
        var rowBot = pCorner.add("group");
        rowBot.orientation = "row";
        rowBot.alignChildren = ["center", "center"];
        rowBot.spacing = 12;
        var rbLB = rowBot.add("radiobutton", undefined, "");
        var rbBC = rowBot.add("radiobutton", undefined, "");
        var rbRB = rowBot.add("radiobutton", undefined, "");
    
        rbLT.helpTip = _(LABELS.leftTop.ja, LABELS.leftTop.en) + " (q)";
        rbTC.helpTip = _(LABELS.topCenter.ja, LABELS.topCenter.en) + " (w)";
        rbRT.helpTip = _(LABELS.rightTop.ja, LABELS.rightTop.en) + " (e)";
        rbLM.helpTip = _(LABELS.leftMiddle.ja, LABELS.leftMiddle.en) + " (a)";
        rbC.helpTip = _(LABELS.center.ja, LABELS.center.en) + " (s)";
        rbRM.helpTip = _(LABELS.rightMiddle.ja, LABELS.rightMiddle.en) + " (d)";
        rbLB.helpTip = _(LABELS.leftBottom.ja, LABELS.leftBottom.en) + " (z)";
        rbBC.helpTip = _(LABELS.bottomCenter.ja, LABELS.bottomCenter.en) + " (x)";
        rbRB.helpTip = _(LABELS.rightBottom.ja, LABELS.rightBottom.en) + " (c)";
    
        var allCornerRadios = [rbLT, rbTC, rbRT, rbLM, rbC, rbRM, rbLB, rbBC, rbRB];
    
        /* コヌナヌラゞオを排他遞択 / Select exactly one corner radio */
        function selectCornerRb(target) {
            for (var i = 0; i < allCornerRadios.length; i++) {
                allCornerRadios[i].value = (allCornerRadios[i] === target);
            }
        }
    
        selectCornerRb(rbLT);
        addCornerKeyHandler(w);
        /* 敎列の基準ショヌトカット1=すべおのアヌトボヌド、2=アクティブを基準 / Target shortcuts: 1=All Artboards, 2=Based on Active Artboard */
        w.addEventListener("keydown", function (event) {
            /* テキスト入力䞭は敎列の基準ショヌトカットを無効化 / Skip alignment-base shortcuts while typing in edittext */
            if (event.target && event.target.type === "edittext") return;
            var k = event.keyName;
            if (!k) return;
            k = ("" + k).toLowerCase();
    
            if (k === "1") {
                rbTargetArtboard.value = true;
                rbTargetSelection.value = false;
                event.preventDefault();
                updatePreviewFromUI();
            } else if (k === "2") {
                rbTargetSelection.value = true;
                rbTargetArtboard.value = false;
                event.preventDefault();
                updatePreviewFromUI();
            }
        });
    
        /* コヌナヌラゞオ甚クリックハンドラを䜜成3行分を明瀺的に排他化
           Create a corner radio click handler and enforce mutual exclusion across the 3 rows */
        function makeCornerClickHandler(rb) {
            return function () {
                selectCornerRb(rb);
                updatePreviewFromUI();
            };
        }
        for (var i = 0; i < allCornerRadios.length; i++) {
            allCornerRadios[i].onClick = makeCornerClickHandler(allCornerRadios[i]);
        }
        rbTargetArtboard.onClick = updatePreviewFromUI;
        rbTargetSelection.onClick = updatePreviewFromUI;
    
        var pMargin = gRight.add("panel", undefined, _(LABELS.marginValue.ja, LABELS.marginValue.en) + " (" + unitLabel + ")");
        pMargin.orientation = "row";
        pMargin.alignChildren = ["fill", "center"];
        pMargin.margins = [15, 20, 15, 10];
        pMargin.helpTip = _(LABELS.marginTip.ja, LABELS.marginTip.en);
    
        var leftColumn = pMargin.add("group");
        leftColumn.orientation = "column";
        leftColumn.alignChildren = ["left", "center"];
        var rightColumn = pMargin.add("group");
        rightColumn.orientation = "column";
        rightColumn.alignChildren = ["left", "center"];
    
        var gMX = leftColumn.add("group");
        gMX.orientation = "row";
        gMX.alignChildren = ["left", "center"];
        gMX.add("statictext", undefined, _(LABELS.marginX.ja, LABELS.marginX.en));
        var etMarginX = gMX.add("edittext", undefined, "0");
        etMarginX.characters = 4;
    
        var gMY = leftColumn.add("group");
        gMY.orientation = "row";
        gMY.alignChildren = ["left", "center"];
        gMY.add("statictext", undefined, _(LABELS.marginY.ja, LABELS.marginY.en));
        var etMarginY = gMY.add("edittext", undefined, "0");
        etMarginY.characters = 4;
    
        var cbLink = rightColumn.add("checkbox", undefined, _(LABELS.link.ja, LABELS.link.en));
        cbLink.value = true;
        cbLink.helpTip = _(LABELS.linkTip.ja, LABELS.linkTip.en);
    
        /* 巊右マヌゞン倉曎時の凊理 / Handle horizontal margin edits */
        function onMarginXEdit() {
            if (cbLink.value) etMarginY.text = etMarginX.text;
            updatePreviewFromUI();
        }
        /* 䞊䞋マヌゞン倉曎時の凊理 / Handle vertical margin edits */
        function onMarginYEdit() {
            if (!cbLink.value) updatePreviewFromUI();
        }
        etMarginX.onChange = etMarginX.onChanging = onMarginXEdit;
        etMarginY.onChange = etMarginY.onChanging = onMarginYEdit;
    
        changeValueByArrowKey(etMarginX, true, onMarginXEdit);
        changeValueByArrowKey(etMarginY, true, onMarginYEdit);
    
        /* Enter/ReturnでOK実行 / Execute OK on Enter or Return */
        function bindEnterToOK(editText) {
            editText.addEventListener("keydown", function (ev) {
                if (ev.keyName === "Enter" || ev.keyName === "Return") {
                    try { btnOK.notify(); } catch (eNotify) { }
                    ev.preventDefault();
                }
            });
        }
        /* プレビュヌ境界オプション / Preview bounds option */
        var gOptions = w.add("group");
        gOptions.orientation = "row";
        gOptions.alignment = ["left", "top"];
        gOptions.margins = [4, 4, 4, 4];
    
        var cbUsePreviewBounds = gOptions.add("checkbox", undefined, _(LABELS.usePreviewBounds.ja, LABELS.usePreviewBounds.en));
        cbUsePreviewBounds.value = usePreviewBounds;
        cbUsePreviewBounds.helpTip = _(LABELS.usePreviewBoundsTip.ja, LABELS.usePreviewBoundsTip.en);
        cbUsePreviewBounds.onClick = function () {
            usePreviewBounds = cbUsePreviewBounds.value;
            updatePreviewFromUI();
        };
    
        var gButtons = w.add("group");
        gButtons.orientation = "row";
        gButtons.alignChildren = ["center", "center"];
        gButtons.alignment = ["center", "bottom"];
    
        gButtons.add("button", undefined, _(LABELS.cancel.ja, LABELS.cancel.en), { name: "cancel" });
        var btnOK = gButtons.add("button", undefined, _(LABELS.ok.ja, LABELS.ok.en), { name: "ok" });
    
        try { w.defaultElement = btnOK; } catch (eDef) { }
    
        bindEnterToOK(etMarginX);
        bindEnterToOK(etMarginY);
    
        /* 連動ON/OFF時のUI曎新 / Update UI when link mode changes */
        function updateLinkStateUI() {
            if (cbLink.value) {
                /* 連動ON巊右を操䜜元に、䞊䞋入力を無効化 / Linked ON: use horizontal as source and disable vertical input */
                etMarginX.enabled = true;
                etMarginY.enabled = false;
                etMarginY.text = etMarginX.text;
            } else {
                /* 連動OFF巊右・䞊䞋を個別に操䜜 / Linked OFF: edit horizontal and vertical independently */
                etMarginX.enabled = true;
                etMarginY.enabled = true;
            }
            updatePreviewFromUI();
        }
        cbLink.onClick = updateLinkStateUI;
    
        /* モヌドに応じお敎列先パネルマヌゞン欄の有効状態を同期
           Sync enabled state of the corner panel, margin panel, and link checkbox */
        function syncDialogEnabledState(targetIsSelection, isCenter) {
            try { pCorner.enabled = !targetIsSelection; } catch (eDimCorner) { }
            var enableMargin = (!isCenter && !targetIsSelection);
            try { pMargin.enabled = enableMargin; } catch (eDim) { }
            try { cbLink.enabled = enableMargin; } catch (eDim2) { }
        }
    
        /* 入力欄からマヌゞンをpt単䜍で取埗䞭倮・遞択モヌド時は0
           Read margin input as pt; use 0 in Center or Selection mode */
        function readMarginPt(isCenter, targetIsSelection) {
            if (isCenter || targetIsSelection) return { x: 0, y: 0 };
            var mxInput = safeToNumber(etMarginX.text, 0);
            var myInput = safeToNumber(etMarginY.text, 0);
            var sourceY = cbLink.value ? mxInput : myInput;
            var mxPt = toPt(mxInput, unitLabel);
            var myPt = toPt(sourceY, unitLabel);
            return { x: (mxPt === null ? 0 : mxPt), y: (myPt === null ? 0 : myPt) };
        }
    
        /* UI状態を読み取り、有効状態の同期ずプレビュヌ反映を実行
           Read UI state, sync enabled states, and update the preview */
        function updatePreviewFromUI() {
            var targetIsSelection = isTargetSelection();
            var corner = getCornerFromUI();
            var isCenter = (corner === "C");
    
            syncDialogEnabledState(targetIsSelection, isCenter);
    
            var margin = readMarginPt(isCenter, targetIsSelection);
            applyPreview(doc, previewState, corner, margin.x, margin.y, targetIsSelection ? "selection" : "artboard");
        }
    
        /* ダむアログ終了時はプレビュヌを巻き戻すOK埌はmainで確定実行 / Revert preview on dialog close; OK is applied later in main() */
        w.onClose = function () {
            revertPreview(previewState, closingByOK ? false : true);
            return true;
        };
    
        /* 衚瀺時に巊右マヌゞン欄ぞフォヌカス / Focus and select the horizontal margin field on show */
        w.onShow = function () {
            try {
                etMarginX.active = true;
                etMarginX.selection = [0, (etMarginX.text || "").length];
            } catch (eFocus) { }
        };
    
        /* 初期衚瀺でプレビュヌ適甚遞択がある堎合 / Apply initial preview when selection exists */
        updateLinkStateUI();
    
        btnOK.onClick = function () {
            closingByOK = true;
            revertPreview(previewState, false);
            w.close(1);
        };
    
        if (w.show() !== 1) return null;
    
        var corner = getCornerFromUI();
        var isCenter = (corner === "C");
        var targetIsSelection = isTargetSelection();
        var marginPt = readMarginPt(isCenter, targetIsSelection);
        return {
            corner: corner,
            targetMode: targetIsSelection ? "selection" : "artboard",
            /* 「アクティブを基準」たたは「䞭倮」のずきは0、それ以倖は{x, y} / 0 when selection mode or center; otherwise {x, y} */
            marginPt: (isCenter || targetIsSelection) ? 0 : marginPt
        };
    }
    
    /* 文字列を安党に数倀化、䞍正倀は fallback / Safe string-to-number with fallback (default: null) */
    function safeToNumber(s, fallback) {
        if (fallback === undefined) fallback = null;
        if (s === null || s === undefined) return fallback;
        s = ("" + s).replace(/^\s+|\s+$/g, "");
        if (s === "") return fallback;
        var n = Number(s);
        if (isNaN(n)) return fallback;
        return n;
    }
    
    /* 数倀入力欄を矢印キヌで増枛Shift=10、Option=0.1 / Arrow-key increment for number fields */
    function changeValueByArrowKey(editText, allowNegative, onValueChanged) {
        if (allowNegative === undefined) allowNegative = false;
    
        editText.addEventListener("keydown", function (event) {
            /* Up / Down以倖は玠通り数倀入力を劚げない / Pass through non-arrow keys */
            if (event.keyName !== "Up" && event.keyName !== "Down") return;
    
            var value = Number(editText.text);
            if (isNaN(value)) value = 0;
    
            var keyboard = ScriptUI.environment.keyboardState;
            var direction = (event.keyName === "Up") ? 1 : -1;
    
            if (keyboard.shiftKey) {
                /* Shift10の倍数にスナップ / Snap to multiples of 10 */
                var step = 10;
                value = (direction > 0)
                    ? Math.ceil((value + 1) / step) * step
                    : Math.floor((value - 1) / step) * step;
            } else if (keyboard.altKey) {
                /* Option0.1単䜍 / Increment by 0.1 */
                value = Math.round((value + direction * 0.1) * 10) / 10;
            } else {
                /* 通垞1単䜍、敎数䞞め / Default: by 1, integer */
                value = Math.round(value + direction);
            }
    
            if (!allowNegative && value < 0) value = 0;
            editText.text = value;
    
            if (typeof onValueChanged === "function") {
                try { onValueChanged(); } catch (eCb) { }
            }
    
            event.preventDefault();
        });
    }
    
    // =========================================
    // アヌトボヌド振り分けず刀定 / Artboard mapping & predicates
    // =========================================
    
    /* 遞択アむテムを䞭心点が属するアヌトボヌドごずに振り分け / Group selection by containing artboard (by center) */
    function groupSelectedItemsByArtboard(doc, selection) {
        var result = {};
    
        for (var i = 0; i < selection.length; i++) {
            var item = selection[i];
            if (isLockedOrHidden(item)) continue;
    
            var itemBounds = getItemBounds(item); /* [å·Š, 侊, 右, 例] / [L, T, R, B] */
            if (!itemBounds) continue;
    
            var cx = (itemBounds[0] + itemBounds[2]) / 2;
            var cy = (itemBounds[1] + itemBounds[3]) / 2;
    
            var abIndex = findArtboardIndexByPoint(doc, cx, cy);
            if (abIndex === -1) continue;
    
            if (!result[abIndex]) result[abIndex] = [];
            result[abIndex].push(item);
        }
    
        return result;
    }
    
    /* 座暙(x,y)を含むアヌトボヌドのむンデックスを返す / Return index of artboard containing (x, y) */
    function findArtboardIndexByPoint(doc, x, y) {
        var n = doc.artboards.length;
        for (var i = 0; i < n; i++) {
            var r = doc.artboards[i].artboardRect; /* [å·Š, 侊, 右, 例] / [L, T, R, B] */
            if (x >= r[0] && x <= r[2] && y <= r[1] && y >= r[3]) return i;
        }
        return -1;
    }
    
    /* アむテムたたは芪階局がロック/非衚瀺か刀定 / Check if item or any ancestor is locked/hidden */
    function isLockedOrHidden(item) {
        try { if (item.locked) return true; } catch (e) { }
        try { if (item.hidden) return true; } catch (e2) { }
    
        var p = item.parent;
        while (p) {
            try { if (p.locked) return true; } catch (e3) { }
            try { if (p.hidden) return true; } catch (e4) { }
            if (p.typename === "Document") break;
            p = p.parent;
        }
        return false;
    }
    
    /* 耇数アむテムを包含する最小バりンディングを返す / Return union bounds of multiple items */
    function getUnionBounds(items) {
        if (!items || items.length === 0) return null;
    
        var l = null, t = null, r = null, b = null;
        for (var i = 0; i < items.length; i++) {
            var item = items[i];
            if (!item) continue;
    
            var itemBounds = getItemBounds(item);
            if (!itemBounds) continue;
    
            if (l === null) {
                l = itemBounds[0]; t = itemBounds[1]; r = itemBounds[2]; b = itemBounds[3];
            } else {
                if (itemBounds[0] < l) l = itemBounds[0];
                if (itemBounds[1] > t) t = itemBounds[1];
                if (itemBounds[2] > r) r = itemBounds[2];
                if (itemBounds[3] < b) b = itemBounds[3];
            }
        }
    
        if (l === null) return null;
        return [l, t, r, b];
    }
    
    // =========================================
    // 敎列凊理 / Alignment
    // =========================================
    
    /* 敎列先アンカヌのアヌトボヌド䞊の座暙を蚈算 / Compute target anchor point on the artboard */
    function computeCornerTargetPoint(artboardRect, corner, marginX, marginY) {
        var abL = artboardRect[0];
        var abT = artboardRect[1];
        var abR = artboardRect[2];
        var abB = artboardRect[3];
        var abCx = (abL + abR) / 2;
        var abCy = (abT + abB) / 2;
    
        if (corner === "LT") return [abL + marginX, abT - marginY];
        if (corner === "TC") return [abCx, abT - marginY];
        if (corner === "RT") return [abR - marginX, abT - marginY];
        if (corner === "LM") return [abL + marginX, abCy];
        if (corner === "C") return [abCx, abCy];
        if (corner === "RM") return [abR - marginX, abCy];
        if (corner === "LB") return [abL + marginX, abB + marginY];
        if (corner === "BC") return [abCx, abB + marginY];
        return [abR - marginX, abB + marginY]; /* 右䞋 / RB */
    }
    
    /* 各アむテムをアヌトボヌド基準のアンカヌぞ敎列マヌゞン付き / Align each item to artboard anchor with margin */
    function alignItemsToCornerWithMargin(items, artboardRect, corner, margin) {
        var marginXY = normalizeMarginXY(margin);
    
        alignItemsToCornerWithMarginCore(
            items,
            artboardRect,
            corner,
            marginXY.x,
            marginXY.y,
            translateItemDirect
        );
    }
    
    /* マヌゞン指定を {x, y} 圢匏ぞ正芏化 / Normalize margin value to {x, y} */
    function normalizeMarginXY(margin) {
        if (typeof margin === "object" && margin !== null) {
            return {
                x: margin.x || 0,
                y: margin.y || 0
            };
        }
    
        return {
            x: margin || 0,
            y: margin || 0
        };
    }
    
    /* 敎列凊理の共通本䜓 / Shared item alignment core */
    function alignItemsToCornerWithMarginCore(items, artboardRect, corner, marginX, marginY, translateItemForAlignment) {
        var target = computeCornerTargetPoint(artboardRect, corner, marginX, marginY);
        var targetX = target[0];
        var targetY = target[1];
    
        for (var i = 0; i < items.length; i++) {
            var item = items[i];
            if (!item) continue;
    
            var itemBounds = getItemBounds(item);
            if (!itemBounds) continue;
    
            var anchor = getBoundsAnchorPoint(itemBounds, corner);
            var dx = targetX - anchor[0];
            var dy = targetY - anchor[1];
    
            translateItemForAlignment(item, dx, dy);
        }
    }
    
    /* アむテムを盎接移動 / Translate item directly */
    function translateItemDirect(item, dx, dy) {
        try { item.translate(dx, dy); } catch (e) { }
    }
    
    /* プレビュヌ甚移動量を蚘録しながら敎列埌で巻き戻し可胜 / Preview alignment with recorded deltas for later revert */
    function alignItemsToCornerWithMarginPreviewXY(items, artboardRect, corner, marginX, marginY, previewState) {
        alignItemsToCornerWithMarginCore(
            items,
            artboardRect,
            corner,
            marginX,
            marginY,
            function (item, dx, dy) {
                translateItemForPreview(item, dx, dy, previewState);
            }
        );
    }
    
    /* プレビュヌ甚アむテムを移動しお移動量を蚘録 / Translate an item and record its preview delta */
    function translateItemForPreview(item, dx, dy, previewState) {
        var itemIndex = findPreviewItemIndex(previewState, item);
    
        try { item.translate(dx, dy); } catch (e) { }
    
        if (itemIndex >= 0) {
            previewState.dx[itemIndex] += dx;
            previewState.dy[itemIndex] += dy;
        }
    }
    
    /* プレビュヌ状態内のアむテム䜍眮を怜玢 / Find the item index in preview state */
    function findPreviewItemIndex(previewState, targetItem) {
        if (!previewState || !previewState.items) return -1;
    
        for (var i = 0; i < previewState.items.length; i++) {
            if (previewState.items[i] === targetItem) return i;
        }
    
        return -1;
    }
    
    /* アヌトボヌド矩圢䞊の9点アンカヌ座暙を返すマヌゞンなし / Return one of the 9 artboard anchor points without margin */
    function getArtboardAnchorPoint(artboardRect, corner) {
        return computeCornerTargetPoint(artboardRect, corner, 0, 0);
    }
    
    /* バりンディングボックス䞊の9点アンカヌ座暙を返す / Return one of the 9 bounds anchor points */
    function getBoundsAnchorPoint(bounds, corner) {
        /* bounds: [å·Š, 侊, 右, 例] / [L, T, R, B] */
        var l = bounds[0], t = bounds[1], r = bounds[2], b = bounds[3];
        var cx = (l + r) / 2;
        var cy = (t + b) / 2;
    
        if (corner === "C") return [cx, cy];
        if (corner === "LT") return [l, t];
        if (corner === "TC") return [cx, t];
        if (corner === "RT") return [r, t];
        if (corner === "LM") return [l, cy];
        if (corner === "RM") return [r, cy];
        if (corner === "LB") return [l, b];
        if (corner === "BC") return [cx, b];
        return [r, b]; /* RB */
    }
    
    /* アクティブアヌトボヌド䞊の遞択を基準に他アヌトボヌドぞ敎列 / Align other artboards by active-artboard reference */
    function alignByActiveArtboardReference(doc, sourceItems, corner, moveGroupWithOffset) {
        var activeArtboardIndex = -1;
        try { activeArtboardIndex = doc.artboards.getActiveArtboardIndex(); } catch (e) { activeArtboardIndex = -1; }
        if (activeArtboardIndex < 0) return;
    
        var itemsByArtboard = groupSelectedItemsByArtboard(doc, sourceItems);
        if (!itemsByArtboard) return;
    
        var referenceItems = itemsByArtboard[activeArtboardIndex];
        if (!referenceItems || referenceItems.length === 0) return;
    
        var referenceRect = doc.artboards[activeArtboardIndex].artboardRect;
        var offset = computeGroupOffsetFromArtboard(referenceItems, referenceRect, corner);
        if (!offset) return;
    
        for (var artboardIndex in itemsByArtboard) {
            if (!itemsByArtboard.hasOwnProperty(artboardIndex)) continue;
    
            var targetArtboardIndex = parseInt(artboardIndex, 10);
            if (targetArtboardIndex === activeArtboardIndex) continue;
    
            var targetItems = itemsByArtboard[artboardIndex];
            if (!targetItems || targetItems.length === 0) continue;
    
            var targetRect = doc.artboards[targetArtboardIndex].artboardRect;
            moveGroupWithOffset(targetItems, targetRect, corner, offset);
        }
    }
    
    /* テンプレ矀ずアヌトボヌドアンカヌの盞察オフセットを蚈算 / Compute relative offset of template group from artboard anchor */
    function computeGroupOffsetFromArtboard(items, artboardRect, corner) {
        /* 戻り倀{x, y} = groupAnchor - artboardAnchor / Returns {x, y} offset */
        var groupBounds = getUnionBounds(items);
        if (!groupBounds) return null;
    
        var artboardAnchor = getArtboardAnchorPoint(artboardRect, corner);
        var groupAnchor = getBoundsAnchorPoint(groupBounds, corner);
    
        return { x: (groupAnchor[0] - artboardAnchor[0]), y: (groupAnchor[1] - artboardAnchor[1]) };
    }
    
    /* アむテム矀をオフセット付きでアヌトボヌドアンカヌに敎列 / Align a group to the artboard anchor with an offset */
    function alignGroupToArtboardWithOffset(items, artboardRect, corner, offset) {
        var groupBounds = getUnionBounds(items);
        if (!groupBounds) return;
    
        var artboardAnchor = getArtboardAnchorPoint(artboardRect, corner);
        var groupAnchor = getBoundsAnchorPoint(groupBounds, corner);
        var dx = artboardAnchor[0] + (offset.x || 0) - groupAnchor[0];
        var dy = artboardAnchor[1] + (offset.y || 0) - groupAnchor[1];
    
        for (var i = 0; i < items.length; i++) {
            try { items[i].translate(dx, dy); } catch (e) { }
        }
    }
    
    /* プレビュヌ甚アむテム矀を敎列し、移動量を蚘録 / Preview group alignment with recorded deltas for later revert */
    function alignGroupToArtboardWithOffsetPreview(items, artboardRect, corner, offset, previewState) {
        var groupBounds = getUnionBounds(items);
        if (!groupBounds) return;
    
        var artboardAnchor = getArtboardAnchorPoint(artboardRect, corner);
        var groupAnchor = getBoundsAnchorPoint(groupBounds, corner);
        var dx = artboardAnchor[0] + (offset.x || 0) - groupAnchor[0];
        var dy = artboardAnchor[1] + (offset.y || 0) - groupAnchor[1];
    
        for (var i = 0; i < items.length; i++) {
            var item = items[i];
            if (!item) continue;
    
            translateItemForPreview(item, dx, dy, previewState);
        }
    }

    アヌトボヌド単䜍でレコヌドを流し蟌むスクリプト



     #target  illustrator
    app.preferences.setBooleanPreference('ShowExternalJSXWarning', false);
    
    /*
    VariableDataImport.jsx
    
    抂芁 / Overview:
    CSV / タブ区切りテキスト のデヌタファむルを Illustrator のテンプレヌトに流し蟌むデヌタ結合スクリプト。
    テキストフレヌム内の <ヘッダヌ名> 圢匏のタグをデヌタ倀に眮換し、
    デヌタ行数ぶんのアヌトボヌドずバリ゚ヌションを生成する。
    A data-merge script for Illustrator. It imports a CSV / TSV data file,
    replaces <header> placeholder tags inside text frames with row values,
    and generates one artboard variation per data row.
    
    䞻な動䜜 / Behavior:
    - デヌタファむルは、開いおいるドキュメントず同じフォルダヌの .csv / .txt から遞ぶ。
    - 実行時に元ドキュメントを別名保存で耇補し、耇補偎に流し蟌む元ファむルは無倉曎。
    - アヌトボヌド0を雛圢ずし、デヌタ件数ぶんアヌトボヌドを耇補する。
      正方圢に近いグリッドを蚈算し、カンバスの倩地・巊右䞭倮に配眮する。
    - アヌトボヌド名は、遞択したデヌタ列の倀から蚭定する。
    - 「プレビュヌ」をオンにするず、元ファむルを倉曎せず耇補ファむルで結果を確認できる。
      蚭定倉曎時は元ドキュメントからプレビュヌ甚ファむルを䜜り盎しお反映する。
      「プレビュヌ」をオフにするず、開いおいるプレビュヌ甚ドキュメントを保存せず閉じる。
    - 1件目のデヌタを最埌に凊理し、雛圢のタグ消倱を回避する。
    */
    
    // =========================================
    // バヌゞョンずロヌカラむズ / Version & Localization
    // =========================================
    
    var SCRIPT_VERSION = "v1.4";
    
    /* 実行環境のロケヌルから衚瀺蚀語を刀定 / Detect the display language from the runtime locale */
    function getCurrentLang() {
        return ($.locale.indexOf("ja") === 0) ? "ja" : "en";
    }
    var lang = getCurrentLang();
    
    /* 日英ラベル定矩 / Japanese-English label definitions */
    var LABELS = {
        dialogTitle: { ja: "デヌタ流し蟌み", en: "Data Import" },
        infoTitle: { ja: "デヌタファむル", en: "Data File" },
        fileLabel: { ja: "ファむル", en: "File" },
        settingsTitle: { ja: "流し蟌み蚭定", en: "Import Settings" },
        abNameLabel: { ja: "アヌトボヌド名の参照列", en: "Artboard name column" },
        preview: { ja: "プレビュヌ", en: "Preview" },
        cancel: { ja: "キャンセル", en: "Cancel" },
        run: { ja: "実行", en: "Run" },
        processing: { ja: "凊理䞭...", en: "Processing..." },
        done: { ja: "完了\n#count# 件凊理したした。", en: "Done!\nProcessed #count # rows." },
        noDoc: { ja: "ドキュメントが開かれおいたせん。", en: "No document is open." },
        needSave: { ja: "ドキュメントを保存しおから実行しおください。", en: "Please save the document before running." },
        noDataFiles: { ja: "同じ階局に.txtたたは.csvファむルが芋぀かりたせんでした。", en: "No .txt or .csv files found in the same folder." },
        noTemplate: { ja: "雛圢にオブゞェクトがありたせん。", en: "No template objects found." },
        dupFailed: { ja: "ドキュメントの耇補別名保存に倱敗したした。\n\n#detail#", en: "Failed to duplicate (Save As) the document.\n\n#detail#" },
        tooManyData: { ja: "デヌタ件数#count# 件がカンバスに収たりたせん。\n珟圚の蚭定では最倧 #max # 件たで配眮できたす。\n間隔を小さくしおください。", en: "The number of rows (#count#) does not fit on the canvas.\nUp to #max # artboards can be placed with the current settings.\nReduce the gap." },
        gapLabel: { ja: "アヌトボヌド間隔", en: "Artboard gap" },
        fileHelp: { ja: "開いおいるドキュメントず同じフォルダヌにある CSV / TSV ファむルを遞びたす。", en: "Choose a CSV / TSV file in the same folder as the open document." },
        artboardNameHelp: { ja: "各アヌトボヌド名に䜿うデヌタ列を遞びたす。", en: "Choose the data column used for each artboard name." },
        gapHelp: { ja: "耇補するアヌトボヌド同士の間隔です。入力倀は10pt単䜍に䞞められたす。", en: "The gap between duplicated artboards. Values are rounded to 10 pt increments." },
        previewHelp: { ja: "元ファむルを倉曎せず、耇補ファむルで流し蟌み結果を確認したす。", en: "Preview the import result in a duplicate file without changing the original." },
        runHelp: { ja: "元ドキュメントを別名保存で耇補し、耇補偎にデヌタを流し蟌みたす。", en: "Duplicate the original document with Save As and import the data into the copy." },
        emptyFile: { ja: "遞択したファむルにヘッダヌ行がありたせん。", en: "The selected file has no header row." },
        fileOpenFailed: { ja: "遞択したファむルを開けたせんでした。", en: "Could not open the selected file." }
    };
    
    /* キヌから珟圚の蚀語のラベルを取埗無ければキヌ名をそのたた返す/ Get the localized label for a key (falls back to the key itself) */
    function L(key) {
        try {
            if (LABELS[key] && LABELS[key][lang]) return LABELS[key][lang];
        } catch (e) { }
        return key;
    }
    
    /* コロン付きラベル日本語は党角、英語は半角/ Label with a colon (full-width for JA, half-width for EN) */
    function labelText(key) {
        return L(key) + (lang === "ja" ? "" : ":");
    }
    
    /* 文字列先頭のBOMU+FEFF / 65279ず前埌の空癜を陀去 / Strip a leading BOM (U+FEFF / 65279) and surrounding whitespace */
    function trimAndStripBom(text) {
        text = String(text);
        if (text.length && text.charCodeAt(0) === 65279) text = text.substring(1);
        return text.replace(/^\s+|\s+$/g, "");
    }
    
    (function () {
    
        var dataRows = [];      // 読み蟌んだデヌタ行 / Loaded data rows
        var headers = [];       // ヘッダヌ列名/ Header column names
        var previewFile = null; // プレビュヌ甚に開く耇補ファむル / Duplicate file opened for preview
    
        // =========================================
        // 初期チェックずデヌタファむル収集 / Initial checks & data file discovery
        // =========================================
    
        if (app.documents.length === 0) {
            alert(L("noDoc"));
            return;
        }
    
        var doc = app.activeDocument;
        var docPath;
        try {
            docPath = doc.path;
        } catch (e) {
            alert(L("needSave"));
            return;
        }
    
        var documentFolder = new Folder(docPath);
        var dataFiles = documentFolder.getFiles(/\.(txt|csv)$/i);
        if (dataFiles.length === 0) {
            alert(L("noDataFiles"));
            return;
        }
    
        /* カンバス範囲ず雛圢アヌトボヌドのサむズを取埗 / Canvas bounds & template artboard size */
        var MAX_ARTBOARDS = 1000;
        var canvasBounds = getCanvasBounds();
        var templateRect = doc.artboards[0].artboardRect;
        var templateWidth = templateRect[2] - templateRect[0];
        var templateHeight = templateRect[1] - templateRect[3];
    
        // =========================================
        // ダむアログUIの構築 / Build the dialog UI
        // =========================================
    
        var LABEL_WIDTH = (lang === "ja") ? 165 : 195; // 流し蟌み蚭定パネルのラベル幅 / Label width in the import-settings panel
    
        /* 蚭定パネル甚䞀定幅のコロン付きラベルを远加し、項目を瞊に揃える / Add a fixed-width colon label so settings rows line up */
        function addFieldLabel(parentGroup, key) {
            var labelControl = parentGroup.add("statictext", undefined, labelText(key));
            labelControl.preferredSize.width = LABEL_WIDTH;
            return labelControl;
        }
    
        var mainDialog = new Window("dialog", L("dialogTitle") + " " + SCRIPT_VERSION);
        mainDialog.orientation = "column";
        mainDialog.alignChildren = ["fill", "top"];
        mainDialog.spacing = 10;
        mainDialog.margins = 15;
    
        /* デヌタファむルパネルファむル遞択ずデヌタプレビュヌ / Data-file panel: file selector & data preview */
        var infoPanel = mainDialog.add("panel", undefined, L("infoTitle"));
        infoPanel.orientation = "column";
        infoPanel.alignChildren = ["fill", "top"];
        infoPanel.margins = 15;
        infoPanel.spacing = 10;
    
        var fileSelectGroup = infoPanel.add("group");
        fileSelectGroup.add("statictext", undefined, labelText("fileLabel"));
        var fileDropdown = fileSelectGroup.add("dropdownlist", undefined, []);
        fileDropdown.size = [350, 25];
        fileDropdown.helpTip = L("fileHelp");
        for (var i = 0; i < dataFiles.length; i++) fileDropdown.add("item", decodeURI(dataFiles[i].name));
    
        var listContainer = infoPanel.add("group");
        listContainer.alignment = ["fill", "fill"];
        var dataListBox = null;
    
        var settingsPanel = mainDialog.add("panel", undefined, L("settingsTitle"));
        settingsPanel.orientation = "column";
        settingsPanel.alignChildren = ["left", "top"];
        settingsPanel.margins = 15;
    
        var artboardNameGroup = settingsPanel.add("group");
        addFieldLabel(artboardNameGroup, "abNameLabel");
        var artboardNameDropdown = artboardNameGroup.add("dropdownlist", undefined, []);
        artboardNameDropdown.size = [200, 25];
        artboardNameDropdown.helpTip = L("artboardNameHelp");
    
        /* グリッド配眮蚭定アヌトボヌド間隔/ Grid layout settings (artboard gap) */
        var defaultGap = Math.round(templateWidth / 5 / 10) * 10;
    
        var rowGap = settingsPanel.add("group");
        addFieldLabel(rowGap, "gapLabel");
        var gapInput = rowGap.add("edittext", undefined, String(defaultGap));
        gapInput.size = [60, 25];
        gapInput.helpTip = L("gapHelp");
        rowGap.add("statictext", undefined, "pt");
    
        // =========================================
        // グリッド配眮の蚈算 / Grid layout calculations
        // =========================================
    
        /* 暪方向に䞊べられる最倧数カンバス幅から算出 / Max columns by canvas width */
        function calcMaxColumns(gapSize) {
            var availableWidth = canvasBounds[2] - canvasBounds[0];
            var maxCount = Math.floor((availableWidth - templateWidth) / (gapSize + templateWidth)) + 1;
            return (maxCount < 1) ? 1 : maxCount;
        }
    
        /* 瞊方向に䞊べられる最倧数カンバス高さから算出 / Max rows by canvas height */
        function calcMaxRows(gapSize) {
            var availableHeight = canvasBounds[1] - canvasBounds[3];
            var maxCount = Math.floor((availableHeight - templateHeight) / (gapSize + templateHeight)) + 1;
            return (maxCount < 1) ? 1 : maxCount;
        }
    
        /* アヌトボヌド間隔の入力倀を取埗負倀は0に補正し、10の倍数ぞ四捚五入/ Read the gap input (clamp negatives to 0, round to the nearest multiple of 10) */
        function getGapValue() {
            var gap = parseFloat(gapInput.text);
            if (isNaN(gap) || gap < 0) gap = 0;
            return Math.round(gap / 10) * 10;
        }
    
        /*
           アヌトボヌドのグリッドを算出 / Resolve the artboard grid.
           グリッド党䜓の幅ず高さがなるべく等しく正方圢に近くなる列数を遞び、
           カンバスの倩地・巊右䞭倮に配眮する。
        */
        function computeArtboardLayout() {
            var gap = getGapValue();
            var maxFitColumns = calcMaxColumns(gap); // 暪に収たる最倧列数 / max columns that fit
            var maxFitRows = calcMaxRows(gap);       // 瞊に収たる最倧行数 / max rows that fit
            var dataCount = dataRows ? dataRows.length : 0;
            var layout = { cols: maxFitColumns, rows: 0, gap: gap, fits: false };
            if (dataCount < 1) return layout; // 未読蟌衚瀺甚に最倧列数を返す
    
            var bestColumns = 0, smallestDiff = -1;
            for (var columns = 1; columns <= maxFitColumns; columns++) {
                var rows = Math.ceil(dataCount / columns);
                if (rows > maxFitRows) continue;                       // 瞊に収たらない
                if (columns * rows > MAX_ARTBOARDS) continue;          // アヌトボヌド数の䞊限
                var occupiedColumns = (columns < dataCount) ? columns : dataCount; // 実際に䜿う列数
                var gridWidth = occupiedColumns * templateWidth + (occupiedColumns - 1) * gap;
                var gridHeight = rows * templateHeight + (rows - 1) * gap;
                var widthHeightDiff = Math.abs(gridWidth - gridHeight);
                if (smallestDiff < 0 || widthHeightDiff < smallestDiff) {
                    smallestDiff = widthHeightDiff;
                    bestColumns = columns;
                }
            }
            if (bestColumns < 1) return layout; // どの列数でも収たらないfits:false
    
            layout.cols = bestColumns;
            layout.rows = Math.ceil(dataCount / bestColumns);
            layout.fits = true;
            return layout;
        }
    
        /* 入力確定時に間隔を10の倍数ぞ四捚五入し、入力欄ぞ反映 / On commit, round the gap to the nearest 10 and write it back to the field */
        function roundGapInput() {
            gapInput.text = String(getGapValue());
        }
    
        gapInput.onChange = function () {
            roundGapInput();
            if (previewCheckbox.value) runPreview(); // プレビュヌ䞭なら耇補ファむルを開き盎しお曎新 / refresh the preview while active
        };
    
        // ボタン゚リア3カラム巊プレビュヌ / 䞭倮スペヌサヌ / 右キャンセル・実行
        var buttonArea = mainDialog.add("group");
        buttonArea.orientation = "row";
        buttonArea.alignment = ["fill", "top"];
    
        var buttonAreaLeft = buttonArea.add("group");
        buttonAreaLeft.alignment = ["left", "center"];
        var previewCheckbox = buttonAreaLeft.add("checkbox", undefined, L("preview"));
        previewCheckbox.helpTip = L("previewHelp");
    
        var buttonAreaCenter = buttonArea.add("group"); // 䞭倮スペヌサヌ䌞瞮/ flexible spacer
        buttonAreaCenter.alignment = ["fill", "center"];
    
        var buttonAreaRight = buttonArea.add("group");
        buttonAreaRight.alignment = ["right", "center"];
        var cancelButton = buttonAreaRight.add("button", undefined, L("cancel"), { name: "cancel" });
        var runButton = buttonAreaRight.add("button", undefined, L("run"), { name: "ok" });
        runButton.helpTip = L("runHelp");
    
        // =========================================
        // ドキュメントの耇補別名保存/ Document duplication (Save As)
        // =========================================
    
        /* 耇補ファむルのパスを生成元名_タグ_日時.拡匵子/ Build the duplicate file path (origName_tag_timestamp.ext) */
        function buildDuplicateFilePath(originalFile, tag) {
            var now = new Date();
            function padTwoDigits(num) { return (num < 10 ? "0" : "") + String(num); }
            var timestamp = String(now.getFullYear()) + padTwoDigits(now.getMonth() + 1) + padTwoDigits(now.getDate()) + "_" + padTwoDigits(now.getHours()) + padTwoDigits(now.getMinutes()) + padTwoDigits(now.getSeconds());
    
            var parentFolder = originalFile.parent;
            var originalName = originalFile.name;
            var dotIndex = originalName.lastIndexOf(".");
            var baseName = (dotIndex >= 0) ? originalName.substring(0, dotIndex) : originalName;
            var extension = (dotIndex >= 0) ? originalName.substring(dotIndex) : ".ai";
    
            var duplicateName = baseName + "_" + (tag || "import") + "_" + timestamp + extension;
            return new File(parentFolder.fsName + "/" + duplicateName);
        }
    
        /* 指定ドキュメントを別名保存し、操䜜察象ずしお耇補埌ドキュメントを返す / Save the document under a new name and return the duplicated document */
        function duplicateDocumentBySaveAs(sourceDocument) {
            var originalFile = sourceDocument.fullName; // File
            var duplicateFile = buildDuplicateFilePath(originalFile);
            sourceDocument.saveAs(duplicateFile); // Illustrator switches this document to the duplicated file
            return sourceDocument;
        }
    
        // =========================================
        // CSV / TSV の解析ず読み蟌み / CSV / TSV parsing & loading
        // =========================================
    
        /* 1行を区切り文字で分割CSVは匕甚笊・゚スケヌプも解釈/ Split one line by delimiter (CSV also honors quotes and escapes) */
        function parseLine(line, delimiter) {
            if (delimiter === "\t") return line.split("\t");
            var fields = [], pattern = new RegExp("(\\" + delimiter + "|\\r?\\n|\\r|^)(?:\"([^\"]*(?:\"\"[^\"]*)*)\"|([^\"\\" + delimiter + "\\r\\n]*))", "gi");
            var matches = null;
            while (matches = pattern.exec(line)) {
                fields.push(matches[2] ? matches[2].replace(/\"\"/g, "\"") : matches[3]);
            }
            return fields;
        }
    
        /* Illustrator の最倧カンバス範囲を取埗䞀時レむダヌで原点を枬定/ Get Illustrator's max canvas bounds (measures the origin via a temp layer) */
        function getCanvasBounds() {
            var CANVAS_MAX_SIZE = 16383;
            var targetDoc = app.activeDocument;
            var wasModified = targetDoc.modified; // 蚈枬前の倉曎フラグを退避 / remember the modified flag before measuring
            var tempLayer = targetDoc.layers.add();
            var tempTextFrame = tempLayer.textFrames.add();
            var left = tempTextFrame.matrix.mValueTX;
            var top = tempTextFrame.matrix.mValueTY;
            tempLayer.remove();
            targetDoc.modified = wasModified; // 䞀時レむダヌ远加で立った倉曎フラグを元に戻す / restore the modified flag
            // [left, top, right, bottom]
            return [left, top, left + CANVAS_MAX_SIZE, top - CANVAS_MAX_SIZE];
        }
    
        /* 遞択ファむルを読み蟌み、ヘッダヌ・デヌタ・デヌタリストを再構築 / Load the selected file and rebuild headers, rows and the data list */
        function loadDataFile(dataFile) {
            if (dataListBox !== null) { listContainer.remove(dataListBox); dataListBox = null; }
            dataRows = [];
            dataFile.encoding = "UTF-8";
            if (!dataFile.open("r")) {
                headers = [];
                artboardNameDropdown.removeAll();
                mainDialog.layout.layout(true);
                alert(L("fileOpenFailed"));
                return;
            }
            var fileContent = dataFile.read();
            dataFile.close();
    
            var lines = fileContent.split(/[\r\n]+/);
            if (lines.length === 0) return;
    
            var delimiter = dataFile.name.toLowerCase().match(/\.csv$/) ? "," : "\t";
            headers = parseLine(lines[0], delimiter);
    
            // ヘッダヌの正芏化BOM/前埌空癜を陀去/ Normalize headers (strip BOM & surrounding spaces)
            for (var headerIndex = 0; headerIndex < headers.length; headerIndex++) {
                headers[headerIndex] = trimAndStripBom(headers[headerIndex]);
            }
    
            // ヘッダヌが実質空空ファむル等なら䞭断 / Abort when there is effectively no header (e.g. empty file)
            var hasHeader = false;
            for (var headerCheckIndex = 0; headerCheckIndex < headers.length; headerCheckIndex++) {
                if (headers[headerCheckIndex] !== "") { hasHeader = true; break; }
            }
            if (!hasHeader) {
                headers = [];
                artboardNameDropdown.removeAll();
                mainDialog.layout.layout(true);
                alert(L("emptyFile"));
                return;
            }
    
            artboardNameDropdown.removeAll();
            for (var dropdownHeaderIndex = 0; dropdownHeaderIndex < headers.length; dropdownHeaderIndex++) {
                artboardNameDropdown.add("item", headers[dropdownHeaderIndex]);
            }
            artboardNameDropdown.selection = 0;
    
            dataListBox = listContainer.add("listbox", [0, 0, 550, 180], [], {
                numberOfColumns: headers.length, showHeaders: true, columnTitles: headers
            });
    
            for (var lineIndex = 1; lineIndex < lines.length; lineIndex++) {
                if (lines[lineIndex] === "") continue;
                var cells = parseLine(lines[lineIndex], delimiter);
    
                // 倀の正芏化前埌空癜を陀去/ Normalize cell values (strip surrounding spaces)
                for (var cellIndex = 0; cellIndex < cells.length; cellIndex++) {
                    cells[cellIndex] = String(cells[cellIndex]).replace(/^\s+|\s+$/g, "");
                }
    
                dataRows.push(cells);
                var listItem = dataListBox.add("item", cells[0] || "");
                for (var listCellIndex = 1; listCellIndex < cells.length; listCellIndex++) {
                    if (listCellIndex < headers.length) {
                        listItem.subItems[listCellIndex - 1].text = cells[listCellIndex] || "";
                    }
                }
            }
    
            mainDialog.layout.layout(true);
        }
    
        fileDropdown.onChange = function () {
            if (fileDropdown.selection) loadDataFile(dataFiles[fileDropdown.selection.index]);
        };
        fileDropdown.selection = 0;
    
        // =========================================
        // 実行凊理流し蟌み/ Run handler (data import)
        // =========================================
    
        /* 配眮レむアりトを確定収たらなければ譊告しお null を返す/ Resolve the layout (alerts and returns null when it does not fit) */
        function prepareLayout() {
            var nameColumnIndex = artboardNameDropdown.selection ? artboardNameDropdown.selection.index : 0;
            var gridLayout = computeArtboardLayout();
            if (!gridLayout.fits) {
                var maxFitColumns = calcMaxColumns(gridLayout.gap);
                var maxCapacity = Math.min(maxFitColumns * calcMaxRows(gridLayout.gap), MAX_ARTBOARDS);
                alert(L("tooManyData").replace("#count#", String(dataRows.length)).replace("#max#", String(maxCapacity)));
                return null;
            }
            return {
                cols: gridLayout.cols,
                rows: gridLayout.rows,
                gap: gridLayout.gap,
                nameColumnIndex: nameColumnIndex
            };
        }
    
        /* 雛圢を耇補しおデヌタを流し蟌むtargetDoc に察しお実行/ Duplicate the template and merge data into targetDoc */
        function performImport(targetDoc, layout, isPreview) {
            targetDoc.activate();
            var nameColumnIndex = layout.nameColumnIndex;
            var columnCount = layout.cols;
            var gap = layout.gap;
    
            var progressWindow = new Window("palette", L("processing"), undefined);
            var progressBar = progressWindow.add("progressbar", undefined, 0, dataRows.length);
            progressBar.preferredSize.width = 300;
            progressWindow.show();
    
            // 雛圢アヌトボヌド0ず、その䞊に乗っおいるオブゞェクト
            var templateArtboard = targetDoc.artboards[0];
            var placementRect = templateArtboard.artboardRect;
            targetDoc.artboards.setActiveArtboardIndex(0);
            targetDoc.selectObjectsOnActiveArtboard();
            var templateItems = targetDoc.selection;
            if (!templateItems || templateItems.length === 0) {
                progressWindow.close();
                alert(L("noTemplate"));
                return;
            }
    
            // 雛圢1セルのサむズアヌトボヌド0のサむズ
            var cellWidth = placementRect[2] - placementRect[0];
            var cellHeight = placementRect[1] - placementRect[3];
    
            // グリッド党䜓の幅・高さを求め、カンバスの倩地・巊右䞭倮ぞ配眮
            var dataCount = dataRows.length;
            var gridRows = Math.ceil(dataCount / columnCount);
            var occupiedColumns = (columnCount < dataCount) ? columnCount : dataCount;
            var gridWidth = occupiedColumns * cellWidth + (occupiedColumns - 1) * gap;
            var gridHeight = gridRows * cellHeight + (gridRows - 1) * gap;
            var canvasWidth = canvasBounds[2] - canvasBounds[0];
            var canvasHeight = canvasBounds[1] - canvasBounds[3];
            var leftMargin = Math.round((canvasWidth - gridWidth) / 2);
            var topMargin = Math.round((canvasHeight - gridHeight) / 2);
            var gridLeft = canvasBounds[0] + leftMargin;
            var gridTop = canvasBounds[1] - topMargin;
            var deltaX = gridLeft - placementRect[0];
            var deltaY = gridTop - placementRect[1];
            templateArtboard.artboardRect = [gridLeft, gridTop, gridLeft + cellWidth, gridTop - cellHeight];
            for (var i = 0; i < templateItems.length; i++) {
                templateItems[i].translate(deltaX, deltaY);
            }
            placementRect = templateArtboard.artboardRect; // 移動埌の䜍眮をグリッド配眮の基準にする
    
            // 重芁2件目(i=1)から先に凊理するタグを維持するため
            for (var i = 1; i < dataRows.length; i++) {
                progressBar.value = i;
                progressWindow.update();
    
                var col = i % columnCount;
                var row = Math.floor(i / columnCount);
                var offsetX = (cellWidth + gap) * col;
                var offsetY = -(cellHeight + gap) * row;
    
                var newArtboard = targetDoc.artboards.add([
                    placementRect[0] + offsetX, placementRect[1] + offsetY,
                    placementRect[2] + offsetX, placementRect[3] + offsetY
                ]);
                newArtboard.name = (dataRows[i][nameColumnIndex] || "Data_" + (i + 1)).toString();
    
                // 雛圢オブゞェクトを耇補し、察応するアヌトボヌドぞ移動しお流し蟌み
                for (var j = 0; j < templateItems.length; j++) {
                    var duplicatedItem = templateItems[j].duplicate();
                    duplicatedItem.translate(offsetX, offsetY);
                    replaceTextRecursive(duplicatedItem, headers, dataRows[i]);
                }
            }
    
            // 最埌に1件目(i=0)を曞き換える
            progressBar.value = dataRows.length;
            progressWindow.update();
            templateArtboard.name = (dataRows[0][nameColumnIndex] || "Data_1").toString();
            for (var k = 0; k < templateItems.length; k++) {
                replaceTextRecursive(templateItems[k], headers, dataRows[0]);
            }
    
            targetDoc.selection = null;
            progressWindow.close();
            app.redraw();
            if (!isPreview) {
                alert(L("done").replace("#count#", String(dataRows.length)));
            }
        }
    
        /* 「実行」ドキュメントを別名保存で耇補し、流し蟌んでダむアログを閉じる / Run: duplicate via Save As, merge, then close the dialog */
        runButton.onClick = function () {
            if (dataRows.length === 0) return;
            var layout = prepareLayout();
            if (!layout) return;
    
            var importDocument;
            try {
                importDocument = duplicateDocumentBySaveAs(doc);
            } catch (e) {
                alert(L("dupFailed").replace("#detail#", String(e)));
                return;
            }
            mainDialog.close();
            performImport(importDocument, layout, false);
        };
    
        /* 既に開いおいるプレビュヌ甚ドキュメントを探す無ければ null/ Find the already-open preview document (null if none) */
        function getOpenPreviewDocument() {
            if (!previewFile) return null;
            for (var i = 0; i < app.documents.length; i++) {
                try {
                    if (app.documents[i].fullName.fsName === previewFile.fsName) return app.documents[i];
                } catch (e) { }
            }
            return null;
        }
    
        /* プレビュヌ甚ドキュメントを閉じ、プレビュヌ甚ファむルを削陀 / Close the preview document and remove the preview file */
        function cleanupPreviewFile() {
            var openedPreview = getOpenPreviewDocument();
            if (openedPreview) {
                try { openedPreview.close(SaveOptions.DONOTSAVECHANGES); } catch (e) { }
            }
            if (previewFile && previewFile.exists) {
                try { previewFile.remove(); } catch (e) { }
            }
            previewFile = null;
        }
    
        /*
           プレビュヌを生成・曎新 / Build or refresh the preview.
           既存のプレビュヌドキュメントがあれば䞀旊閉じ、元ドキュメントから耇補ファむルを䜜り盎しお開く。
           流し蟌み結果は保存しないため、毎回クリヌンな雛圢状態から始たる。
        */
        function runPreview() {
            if (dataRows.length === 0) return false;
            var layout = prepareLayout();
            if (!layout) return false;
    
            // 既存のプレビュヌドキュメントがあれば閉じる流し蟌み結果は砎棄
            var openedPreview = getOpenPreviewDocument();
            if (openedPreview) {
                try { openedPreview.close(SaveOptions.DONOTSAVECHANGES); } catch (e) { }
            }
    
            // 元ドキュメントからプレビュヌ甚の耇補ファむルを毎回䜜り盎す
            var previewDocument;
            try {
                var sourceFile = doc.fullName;
                if (!previewFile) {
                    previewFile = buildDuplicateFilePath(sourceFile, "preview");
                }
                if (previewFile.exists) {
                    previewFile.remove();
                }
                if (!sourceFile.copy(previewFile)) throw new Error("File copy failed");
                previewDocument = app.open(previewFile);
            } catch (e) {
                alert(L("dupFailed").replace("#detail#", String(e)));
                previewFile = null;
                return false;
            }
    
            performImport(previewDocument, layout, true);
            return true;
        }
    
        /* 「プレビュヌ」オンで衚瀺、オフでプレビュヌ甚ドキュメントを閉じる / Preview checkbox: show on, close the preview document off */
        previewCheckbox.onClick = function () {
            if (!previewCheckbox.value) {
                cleanupPreviewFile();
                return;
            }
            if (!runPreview()) previewCheckbox.value = false;
        };
    
        /* 「キャンセル」プレビュヌ甚ファむルを削陀しお閉じる / Cancel: remove the preview file and close the dialog */
        cancelButton.onClick = function () {
            cleanupPreviewFile();
            mainDialog.close();
        };
    
        /* アヌトボヌド名の参照項目を倉曎したら、プレビュヌ䞭は耇補ファむルを開き盎しお曎新 / Refresh the preview when the artboard-name field changes */
        artboardNameDropdown.onChange = function () {
            if (previewCheckbox.value) runPreview();
        };
    
        // =========================================
        // テキストの眮換 / Text replacement
        // =========================================
    
        /* オブゞェクトを再垰的に走査し、テキスト内の <タグ> をデヌタ倀ぞ眮換 / Walk objects recursively and replace <tag> placeholders in text with data values */
        function replaceTextRecursive(pageItem, headerKeys, rowValues) {
            if (!pageItem) return;
    
            if (pageItem.typename === "TextFrame") {
                var textContent = pageItem.contents;
                if (textContent == null) return;
    
                for (var k = 0; k < headerKeys.length; k++) {
                    var headerKey = trimAndStripBom(headerKeys[k]);
                    if (headerKey === "") continue;
    
                    var placeholderTag = "<" + headerKey + ">";
                    if (textContent.indexOf(placeholderTag) !== -1) {
                        var value = (rowValues && rowValues.length > k && rowValues[k] != null) ? String(rowValues[k]) : "";
                        // 党出珟を眮換ExtendScript安党
                        textContent = textContent.split(placeholderTag).join(value);
                    }
                }
    
                pageItem.contents = textContent;
            } else if (pageItem.typename === "GroupItem") {
                for (var i = 0; i < pageItem.pageItems.length; i++) {
                    replaceTextRecursive(pageItem.pageItems[i], headerKeys, rowValues);
                }
            }
        }
    
        mainDialog.show();
    })();
    


    ダりンロヌド

     
     
    「知っおいたら早く終わるキレむに仕䞊がる」など、芋萜ずしがちなポむントをおさえお効率的なデザむン制䜜を支揎 ➡ IllustratorPhotoshopInDesignAcrobatPDFグラフィック゚ディトリアルフォント執筆線集組版

    あなたぞのおすすめ