InDesign脚本Error 27 'Stack overrun'问题解决及优化咨询
InDesign脚本栈溢出问题解决及优化建议
问题背景
我需要自动化InDesign工作流程:将含5-8位订单号的图片批量置入文档,排版后生成PDF用于定制设备打印。每张图片对应带订单号的纸质单据及条码,图片文件名包含订单号,原本需手动置入。
编写脚本通过条码扫描器输入条码(提取订单号),自动匹配文件夹内图片并置入。原脚本用app.doScript循环重启以连续插入图片,但插入39张后触发**Error 27 'Stack overrun'(栈溢出)**错误,增大memCache无效,后改用palette窗口更新了脚本,现咨询:
- 如何解决原脚本的栈溢出错误?
- 重写脚本有哪些优化建议?
原脚本(栈溢出版本)
#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
相关产品推荐
相关产品推荐

