コモノExtendScript100本ノック

超初心者のDTPオペレーターが週にひとつスクリプトを書くブログ

062.【Id】レイヤーを切り替えて複数回PDF書き出し

挙動

  1. 起動するとダイアログが立ち上がるので、書き出しプリセットを選択する
  2. ダイアログに表示されたレイヤー名を元に、以下のようなテキストデータを作成する
    ページ範囲 [タブ] PDFファイル名1 [タブ] レイヤー1,レイヤー2
    ページ範囲 [タブ] PDFファイル名2 [タブ] レイヤー1,レイヤー3,レイヤー4
    ページ範囲 [タブ] PDFファイル名3 [タブ] レイヤー5
  3. 実行ボタンを押すと、指定したレイヤーのみが表示された状態でPDFが書き出される
  4. スクリプト実行前の状態に戻る

    コード

//@targetengine "filterLayers"
/* 
あらかじめ以下のようなテキストデータを作成する。
ページ範囲 [タブ] PDFファイル名1 [タブ] レイヤー1,レイヤー2
ページ範囲 [タブ] PDFファイル名2 [タブ] レイヤー1,レイヤー3,レイヤー4
*/
var main = function() {
    app.scriptPreferences.enableRedraw = false;

    var doc = app.activeDocument;
    var ls = [];
    for (var i = 0, l = doc.layers; i < l.length; i++) {
        ls.push(l[i].name);
    }
    if (app.documents.length === 0) {
        alert("開いているドキュメントがないため中断します");
        exitFlag = true;
        exit();
    }
    // ウィンドウを作成
    var dialog = new Window("dialog");
    dialog.text = "レイヤー切り替えPDF書き出し";
    dialog.orientation = "column";
    dialog.alignChildren = ["center", "top"];

    var group1 = dialog.add("group");
    group1.orientation = "column";
    group1.alignChildren = ["left", "top"];

    var group2 = group1.add("group");
    group2.orientation = "column";
    group2.alignChildren = ["left", "center"];

    var dropdown1 = dialog.add("dropdownlist", undefined, app.pdfExportPresets.everyItem().name);
    dropdown1.preferredSize.width = 500;

    var group3 = group1.add("group");
    group3.orientation = "column";
    group3.alignChildren = ["left", "center"];

    var statictext2 = group3.add("statictext");
    statictext2.text = "レイヤー一覧";

    var edittext2 = group3.add('edittext {properties: {multiline: true, scrollable: true}}');
    edittext2.text = ls.join(",");
    edittext2.preferredSize.width = 500;
    edittext2.preferredSize.height = 100;
    edittext2.multiline = true;

    var button1 = dialog.add("button");
    button1.text = "実行";

    button1.onClick = function() {
        dialog.close(1);
    }
    var dlgResult = dialog.show();
    if (dlgResult !== 1) {
        alert("中断しました");
        exitFlag = true;
        exit();
    }

    var myList = fileReadInAppend("prange", "pname", "visibleLayers");
    var cnt = myList.length;

    // ファイル名の重複チェック
    if (!chkDuplicates(myList, "pname")) {
        alert("ファイル名が重複しているため中断しました");
        exitFlag = true;
        exit();
    }

    if (!dropdown1.selection) {
        alert("書き出しプリセットが選択されていないため中断しました");
        exitFlag = true;
        exit();
    }
    var p = app.pdfExportPresets.itemByName(dropdown1.selection.text);

    for (var i = 0; i < cnt; i++) {
        doc.layers.everyItem().visible = false;
        for (var j = 0, vlcnt = myList[i].visibleLayers.length; j < vlcnt; j++) {
            try {
                doc.layers.itemByName(myList[i].visibleLayers[j]).visible = true;
            } catch (e) {
                alert("指定のレイヤーが存在しないため中断しました");
                exitFlag = true;
                exit();
            }
        }
        // ファイル名に使用できない文字チェック
        if (/[\*\?"<>\|]/g.test(myList[i].pname)) {
            alert("ファイル名に使用できない文字が含まれているため中断しました");
            exitFlag = true;
            exit();
        }
        var fn = doc.filePath + "/" + myList[i].pname + ".pdf".replace(".pdf.pdf", ".pdf").replace(".PDF", "");
        app.pdfExportPreferences.pageRange = myList[i].prange;
        try {
            doc.exportFile(ExportFormat.pdfType, File(fn), false, p);
        } catch (e) {
            alert("存在しないページが指定されている等なんらかのエラーが発生しました。\n中断します。");
            exitFlag = true;
            exit();
        }
    }


    function fileReadInAppend(value1, value2, value3 /*string*/ ) {
        var tgtFile = File.openDialog("ファイルを選んでください", "*.txt");
        if (!tgtFile || !tgtFile.exists) {
            alert("中断しました");
            exitFlag = true;
            exit();
        }
        var res = [];
        var fileReadFlag;
        tgtFile.open("r");
        tgtFile.encoding = "UTF-8";

        try {
            while (!tgtFile.eof) {
                var ln = tgtFile.readln().split(/\t/);
                if (ln.length !== arguments.length) {
                    alert("読み込みテキストに不備があります。\n中断します。");
                    exit();
                    exitFlag = true;
                }
                var item = {};
                item[value1] = ln[0];
                item[value2] = ln[1];
                item[value3] = ln[2].split(",");
                res.push(item);
            }
            fileReadFlag = true;
        } catch (e) {
            alert(e);
            fileReadFlag = false;
        } finally {
            tgtFile.close();
        }
        if (!fileReadFlag || res.length === 0) {
            alert("ファイルを読み込めませんでした");
            exit();
            exitFlag = true;
        }

        return res;
    }

    // 配列内で同じプロパティを持つオブジェクトを抽出
    function chkDuplicates(objArr, prop) {
        var exist = {};
        var result = [];
        for (var i = 0, l = objArr.length; i < l; i++) {
            if (typeof objArr[i] !== "object") {
                continue;
            }
            var tmp = objArr[i][prop];
            if (!exist[tmp]) {
                exist[tmp] = 1;
            } else if (exist[tmp] === 1) {
                exist[tmp]++;
                result.push(arr[i]);
            } else {
                exist[tmp]++;
            }
        }
        return result;
    }
}

var exitFlag = false; // main内で中断した場合に完了アラートを出さないようにするため
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.FAST_ENTIRE_SCRIPT, "filterLayers.jsx");
app.undo();
if (!exitFlag) {
    alert("完了しました");
}

メモ

分かったこと

doScriptでmain関数を呼ぶような書き方をしている場合、main関数内でexit()した後はdoScript以降の処理に移行する(ちょっと考えれば当たり前なんだけど…)。

積み残し

  • ページ範囲指定
    色んなページの指定の仕方があるのと、数値っぽい文字列を扱わなくちゃならないあたり、こんな単純な書き方で大丈夫かどうかちょっと不安。
    もう少し考えておきたい。
  • fileReadIn関数の引数
    いくつ項目があっても対応できるようにしておきたい。
    arguments.length分itemにプロパティを追加するかたちにするか、あるいは引数を配列でわたすか、かなあ。

参考