You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

InDesign脚本Error 27 'Stack overrun'问题解决及优化咨询

InDesign脚本栈溢出问题解决及优化建议

问题背景

我需要自动化InDesign工作流程:将含5-8位订单号的图片批量置入文档,排版后生成PDF用于定制设备打印。每张图片对应带订单号的纸质单据及条码,图片文件名包含订单号,原本需手动置入。

编写脚本通过条码扫描器输入条码(提取订单号),自动匹配文件夹内图片并置入。原脚本用app.doScript循环重启以连续插入图片,但插入39张后触发**Error 27 'Stack overrun'(栈溢出)**错误,增大memCache无效,后改用palette窗口更新了脚本,现咨询:

  1. 如何解决原脚本的栈溢出错误?
  2. 重写脚本有哪些优化建议?

原脚本(栈溢出版本)

#target "InDesign"
#targetengine "session"
if (app.documents.length == 0) {
    alert ("Open a document first");
    exit ();
}

// 尝试增大memCache但无效
$.memCache = 9999999999;
app.scriptPreferences.enableRedraw = false;
var MyDoc = app.activeDocument;
MyDoc.viewPreferences.horizontalMeasurementUnits = MeasurementUnits.millimeters;
MyDoc.viewPreferences.verticalMeasurementUnits = MeasurementUnits.millimeters;
app.viewPreferences.rulerOrigin = RulerOrigin.PAGE_ORIGIN;
MyDoc.viewPreferences.rulerOrigin = RulerOrigin.PAGE_ORIGIN;
MyDoc.zeroPoint = [0,0];
var MyFolder, MyImages, ImgNumber;

// 控制脚本是否重启
var RestartScript = 0;
var MyPanel = new Window ("dialog", "Enter the barcode");
var MyText = MyPanel.add ("edittext");
MyText.preferredSize.width = 150;
MyText.onChanging = function () {
    MyText.text = MyText.text.replace (/[^0-9]/g, "");

    // 条码长度达15位时自动处理
    if (MyText.text.length >= 15) {
        BarcodeConversion ();
        MyPanel.close ();
    }
}

// 手动输入订单号按回车确认
MyText.addEventListener ("keydown", function (KeyEvent) {
    if (KeyEvent.keyName == "Enter") {
        if (MyText.text != "") {
            ImgNumber = MyText.text;
            MyPanel.close ();
        }
    }
});

MyText.active = true;
var Browse = MyPanel.add ("button", undefined, "Browse");
Browse.onClick = function () {
    MyFolder = Folder.selectDialog ("Select a folder", " ");
    MyImages = MyFolder.getFiles (/.+\.(?:gif|jpe?g|eps|tiff?|psd|pdf|bmp|png)$/i);
    if (MyImages.length <= 0) {
        alert ("There are no images in this folder");
        return;
    }
    MyText.active = true;
}
var Cancel = MyPanel.add ("button", undefined, "Cancel");
Cancel.onClick = function () {
    MyPanel.close ();
}
MyPanel.show ();
if (ImgNumber != undefined) {
    Search ();
}

// 递归重启脚本导致栈溢出
if (RestartScript == 1) {
    app.doScript (new File (app.activeScript.parent.fsName + "\\" + app.activeScript.name) ,ScriptLanguage.JAVASCRIPT);
}

function BarcodeConversion () {
    if (MyFolder == undefined) {
        alert ("Folder not selected");
        MyText.text = "";
        return;
    }
    // 从条码提取订单号
    ImgNumber = MyText.text.slice (4, 12);
}

function Search () {
    var ImgFound = [];
    for (var a = 0; a < MyImages.length; a++) {
        if (MyImages[a].name.toLowerCase ().indexOf (ImgNumber.toString ()) != -1) {
            ImgFound.push (MyImages[a]);
            // 匹配到多张图片时手动选择
            if (ImgFound.length > 1) {
                beep ();
                alert ("There are multiple images with the number " + ImgNumber);
                var ManualSelection = (File (MyFolder + "/*" + ImgNumber + "*")).openDlg ("Place", undefined, true);
                if (ManualSelection == null) {
                    Reset ();
                    return;
                }
                ImgFound = [];
                for (var b = 0; b < ManualSelection.length; b++) {
                    ImgFound.push (ManualSelection[b]);
                }
                break;
            }
        }
    }
    // 置入图片并居中
    for (var c = 0; c < ImgFound.length; c++) {
        app.activeWindow.activePage.place (ImgFound[c]);
        MyDoc.align (app.activeWindow.activePage.rectangles[0], AlignOptions.VERTICAL_CENTERS, AlignDistributeBounds.PAGE_BOUNDS);
        MyDoc.align (app.activeWindow.activePage.rectangles[0], AlignOptions.HORIZONTAL_CENTERS, AlignDistributeBounds.PAGE_BOUNDS);
    }
    if (ImgFound == 0) {
        alert ("Image " + ImgNumber + " not present");
    }
    Reset ();
}

function Reset () {
    ImgNumber = undefined;
    ImgFound = 0;
    RestartScript = 1;
}

更新后的脚本(Palette窗口版本)

#target "InDesign"
#targetengine "session"
if (app.documents.length == 0) {
    beep ();
    alert ("Open a document first", " ");
    exit ();
}
app.scriptPreferences.enableRedraw = false;
var MyDoc = app.activeDocument;
MyDoc.viewPreferences.horizontalMeasurementUnits = MeasurementUnits.millimeters;
MyDoc.viewPreferences.verticalMeasurementUnits = MeasurementUnits.millimeters;
app.viewPreferences.rulerOrigin = RulerOrigin.PAGE_ORIGIN;
MyDoc.viewPreferences.rulerOrigin = RulerOrigin.PAGE_ORIGIN;
MyDoc.zeroPoint = [0,0];
var MyFolder, MyImages, ImgNumber;
var MyPanel = new Window ("palette", "Enter the barcode");
MyPanel.orientation = "column";
var Group1 = MyPanel.add ("group");
Group1.orientation = "row";
var MyPath = Group1.add ("statictext");
MyPath.preferredSize.width = 150;
if (MyFolder != undefined) {
    MyPath.text = decodeURI (MyFolder);
}
var BrowseButton = Group1.add ("button", undefined, "Browse");
BrowseButton.onClick = function () {
    MyFolder = Folder.selectDialog ("Select a folder");
    if (MyFolder == null) {
        MyFolder = null;
        MyPath.text = "";
        return;
    }
    MyImages = MyFolder.getFiles (/.+\.(?:gif|jpe?g|eps|tiff?|psd|pdf|bmp|png)$/i);
    if (MyImages.length <= 0) {
        beep ();
        alert ("There are no images in the selected folder", " ");
        return;
    }
    MyPath.text = decodeURI (MyFolder);
}
var Group2 = MyPanel.add ("group");
Group2.orientation = "row";
var MyText = Group2.add ("edittext");
MyText.preferredSize.width = 150;
MyText.onChanging = function () {
    MyText.text = MyText.text.replace (/[^0-9]/g, "");
    if (MyText.text.length >= 15) {
        BarcodeConversion ();
    }
}
MyText.addEventListener ("keydown", EnterEvent);
var CancelButton = Group2.add ("button", undefined, "Cancel");
CancelButton.onClick = function () {
    MyPanel.close ();
}
MyText.active = true;
MyPanel.addEventListener ("keydown", EscapeEvent);
MyPanel.onClose = function () {
    ImgNumber = undefined;
    MyText.removeEventListener ("keydown", EnterEvent);
    MyPanel.removeEventListener ("keydown", EscapeEvent);
}
MyPanel.show ();
function Warning () {
    if (MyFolder == undefined) {
        alert ("No folder selected", " ");
        MyText.text = "";
        return;
    }
}
function EnterEvent (EventA) {
    Warning ();
    if (EventA.keyName === "Enter" && MyText.text != "") {
        ImgNumber = MyText.text;
        MyText.text = "";
        Search ();
    }
}
function EscapeEvent (EventB) {
    if (EventB.keyName == "Escape") {
        MyPanel.close ();
    }
}
function BarcodeConversion () {
    Warning ();
    ImgNumber = MyText.text.slice (4, 12);
    while (ImgNumber.match (new RegExp (/^\d/)) == 0) {
        ImgNumber = ImgNumber.slice (1);
    }
    MyText.text = "";
    if (ImgNumber != undefined) {
        Search ();
    }
}
function Search () {
    var ImgFound = [];
    for (var a = 0; a < MyImages.length; a++) {
        if (MyImages[a].name.toLowerCase ().indexOf (ImgNumber.toString ()) != -1) {
            ImgFound.push (MyImages[a]);
            if (ImgFound.length > 1) {
                beep ();
                alert ("There are multiple images with the number " + ImgNumber, " ");
                var ManualSelection = (File (MyFolder + "/*" + ImgNumber + "*")).openDlg ("Inserisci", undefined, true);
                if (ManualSelection == null) {
                    ImgNumber = undefined;
                    return;
                }
                ImgFound = [];
                for (var b = 0; b < ManualSelection.length; b++) {
                    ImgFound.push (ManualSelection[b]);
                }
                ManualSelection = undefined;
                break;
            }
        }
    }
    if (ImgFound == 0) {
        beep ();
        alert ("Image " + ImgNumber + " not present", " ");
    }
    for (var c = 0; c < ImgFound.length; c++) {
        app.activeWindow.activePage.place (ImgFound[c]);
        MyDoc.align (app.activeWindow.activePage.rectangles[0], AlignOptions.VERTICAL_CENTERS, AlignDistributeBounds.PAGE_BOUNDS);
        MyDoc.align (app.activeWindow.activePage.rectangles[0], AlignOptions.HORIZONTAL_CENTERS, AlignDistributeBounds.PAGE_BOUNDS);
    }
    ImgFound = [];
    ImgNumber = undefined;
}

问题解答

1. 原脚本栈溢出错误的原因及解决方法

原因

原脚本通过app.doScript递归调用自身实现连续输入,每次调用都会将当前脚本的执行上下文压入调用栈。当调用次数达到39次时,栈内存被耗尽,触发栈溢出错误。$.memCache控制的是脚本引擎的内存缓存,和调用栈容量无关,因此调整它无法解决问题。

解决方法

放弃递归重启脚本的方式,改用以下两种方案:

  • 循环等待Dialog输入:将脚本核心逻辑放在循环内,处理完图片后重新显示Dialog,而非重启整个脚本:
    // 替换原脚本中app.doScript的部分
    while (true) {
        MyPanel.show();
        if (ImgNumber != undefined) Search();
        // 点击Cancel时退出循环
        if (RestartScript != 1) break;
        Reset();
    }
    
  • 使用Palette窗口:也就是你现在采用的方案,它是常驻窗口,脚本不会因窗口关闭而终止,通过事件监听响应输入,不会产生调用栈累积,从根源避免了栈溢出。

2. 脚本优化建议

(1)图片搜索效率优化

每次搜索都遍历整个文件夹的文件,图片数量多时速度慢。可在选择文件夹后,提前建立订单号与图片的映射:

// 选择文件夹后执行
var orderImageMap = {};
for (var i = 0; i < MyImages.length; i++) {
    var fileName = MyImages[i].name.toLowerCase();
    // 根据你的命名规则提取订单号(此处匹配5-8位数字)
    var match = fileName.match(/(\d{5,8})/);
    if (match) {
        var orderNum = match[1];
        if (!orderImageMap[orderNum]) orderImageMap[orderNum] = [];
        orderImageMap[orderNum].push(MyImages[i]);
    }
}

// 搜索时直接从映射获取
function Search() {
    var ImgFound = orderImageMap[ImgNumber] || [];
    // 后续逻辑不变...
}

(2)图片置入对齐的准确性优化

原脚本用activePage.rectangles[0]获取刚置入的图片,若页面已有其他矩形会出错。应直接获取place方法返回的置入对象:

for (var c = 0; c < ImgFound.length; c++) {
    // 获取刚置入的图片对象
    var placedGraphic = app.activeWindow.activePage.place(ImgFound[c])[0];
    MyDoc.align(placedGraphic, AlignOptions.VERTICAL_CENTERS, AlignDistributeBounds.PAGE_BOUNDS);
    MyDoc.align(placedGraphic, AlignOptions.HORIZONTAL_CENTERS, AlignDistributeBounds.PAGE_BOUNDS);
}

(3)Windows系统提示音优化

替换beep()为系统默认通知音,音量更大更清晰:

function playAlert() {
    // 调用Windows默认通知音
    app.doScript('powershell -c (New-Object Media.SoundPlayer "C:\\Windows\\Media\\notify.wav").PlaySync()', ScriptLanguage.APPLESCRIPT_LANGUAGE);
}
// 使用时替换beep()为playAlert()

(4)变量作用域与内存优化

  • 缩小变量作用域,避免全局变量过多导致冲突,比如将ImgFound等变量放在函数内部而非全局。
  • 及时清理不再使用的变量(如ManualSelection = undefined),避免内存泄漏。

(5)订单号匹配精度优化

原脚本用indexOf匹配订单号,可能出现误匹配(比如文件名包含类似数字的片段)。改用正则匹配独立的订单号:

// 搜索时用正则匹配订单号作为独立部分
var orderRegex = new RegExp('(^|\\D)' + ImgNumber + '(\\D|$)', 'i');
if (orderRegex.test(MyImages[a].name)) {
    ImgFound.push(MyImages[a]);
}

(6)性能优化

  • 在批量置入图片时保持app.scriptPreferences.enableRedraw = false,完成后再开启,减少界面重绘消耗:
    function Search() {
        app.scriptPreferences.enableRedraw = false;
        // 图片搜索、置入逻辑...
        app.scriptPreferences.enableRedraw = true;
    }
    
  • 文档属性(如度量单位)仅在脚本启动时设置一次,避免重复操作。

内容的提问来源于stack exchange,提问作者user18714172

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.18 19:27:02