Skip to content

Instantly share code, notes, and snippets.

@elmimmo
Last active March 5, 2026 15:24
Show Gist options
  • Select an option

  • Save elmimmo/940d2039ebb9b8c77bbf01f45f674cc5 to your computer and use it in GitHub Desktop.

Select an option

Save elmimmo/940d2039ebb9b8c77bbf01f45f674cc5 to your computer and use it in GitHub Desktop.
InDesign script to place pages from an InDesign document into a grid.
//DESCRIPTION:Place pages from an InDesign document into a grid.
/*
Goal:
- Select one or more source INDD files.
- Calculate and preview a grid on the active page.
- Place source pages at 100% (no rotation), creating destination pages as needed.
- Draw overlay frames on the trim area of each placed page.
Main behavior:
- Uses slug area for imported page placement.
- Uses trim area to calculate and draw overlay frames.
- Lets you define gaps, usable margins, and stroke weight from the UI.
- Shows live preview in a persistent palette.
Notes:
- UI values accept comma or dot as decimal separator.
- If a number is entered without units, it is interpreted in the field's current unit.
- Includes one extra preview row and column to indicate cells outside the usable area.
by Jorge Hernández Valiñani (@Estudio Fénix) + Codex (GPT-5).
*/
#target "InDesign"
#targetengine "gridImportPreview"
(function () {
var L = {
openDocFirst: "Abre un documento antes de ejecutar el script.",
selectInddFiles: "Selecciona uno o varios archivos INDD para colocar",
firstFileNoPages: "El primer archivo no tiene paginas.",
cannotMeasureFirstSlug: "No se pudo medir el area de anotacion del primer archivo.",
cannotUseSlugCrop: "Tu version de InDesign no permite recortar INDD al area de anotacion (Slug).",
fileSlugSizeMismatch: "El archivo '{0}' tiene paginas con tamanos de area de anotacion diferentes.",
swatchNoneMissing: "No existe la muestra 'None' en el documento.",
swatchBlackMissing: "No existe la muestra 'Black' en el documento.",
previewSwatchesMissing: "No existen las muestras necesarias para la previsualizacion.",
dialogTitle: "Importar a cuadricula",
panelGrid: "Cuadricula",
panelSpacing: "Espaciado",
panelMargins: "Margenes",
labelColumns: "Columnas:",
labelRows: "Filas:",
labelBorder: "Borde:",
labelHorizontal: "Horizontal:",
labelVertical: "Vertical:",
labelSideMargins: "Laterales:",
labelVerticalMargins: "Verticales:",
labelCross: "x",
labelRemainderDefault: "(+ 0,99)",
labelCounterDefault: "99",
defaultMarginText: "0 mm",
labelRemainderPattern: "(+ {0})",
buttonCancel: "Cancelar",
buttonOk: "OK",
noFitInPage: "Con esos valores no cabe ningun elemento en la pagina.",
undoLabel: "Colocar INDD en rejilla",
overlayLayerBaseName: "Marcos recorte",
noFitForFile: "No cabe ninguna pagina de '{0}' con los parametros actuales.",
noFitInTargetPage: "No cabe ninguna pagina en la pagina destino '{0}'.",
errorPrefix: "Error: ",
doneMessage: "Colocacion completada. Paginas colocadas: {0}."
};
if (app.documents.length === 0) {
alert(L.openDocFirst);
return;
}
var targetDoc = app.activeDocument;
var targetPage = app.activeWindow.activePage;
var LOCALE_DECIMAL = getLocaleDecimalSeparator();
var DEBUG_GRID = true;
var DOC_UNIT_X = measurementUnitToUnitValueUnit(targetDoc.viewPreferences.horizontalMeasurementUnits);
var DOC_UNIT_Y = measurementUnitToUnitValueUnit(targetDoc.viewPreferences.verticalMeasurementUnits);
var DEBUG_FORCE_SETTINGS = null;
// Example for debugging without the palette:
// DEBUG_FORCE_SETTINGS = {
// horGapPt: new UnitValue(0, "mm").as("pt"),
// verGapPt: new UnitValue(0, "mm").as("pt"),
// strokePt: new UnitValue(1, "mm").as("pt"),
// horMarPt: 0,
// verMarPt: 0,
// autoHorMargin: true,
// autoVerMargin: true
// };
var files = File.openDialog(
L.selectInddFiles,
function (f) { return f instanceof Folder || /\.indd$/i.test(f.name); },
true
);
if (!files || files.length === 0) {
return;
}
var originalPageNumber = app.importedPageAttributes.pageNumber;
var originalCrop = app.importedPageAttributes.importedPageCrop;
var placementCrop = resolveImportedPageCrop();
var sourceInfoCache = {};
var firstSource = getSourceInfo(files[0], sourceInfoCache);
if (firstSource.pageCount < 1) {
alert(L.firstFileNoPages);
return;
}
var firstSlugSize = measurePlacedSlugSize(targetPage, targetDoc, files[0], placementCrop, 1, originalPageNumber, originalCrop);
if (!firstSlugSize) {
alert(L.cannotMeasureFirstSlug);
return;
}
var pageBounds = targetPage.bounds;
var pageWidth = pageBounds[3] - pageBounds[1];
var pageHeight = pageBounds[2] - pageBounds[0];
if (DEBUG_FORCE_SETTINGS) {
executePlacement(DEBUG_FORCE_SETTINGS);
return;
}
showGridDialog(targetDoc, targetPage, pageWidth, pageHeight, firstSlugSize.width, firstSlugSize.height, firstSource.pageCount, executePlacement);
function resolveImportedPageCrop() {
var slug = getImportedCropOption("CROP_SLUG");
if (slug !== null) {
return slug;
}
throw new Error(L.cannotUseSlugCrop);
}
function getImportedCropOption(name) {
try {
if (typeof ImportedPageCropOptions === "undefined") {
return null;
}
var value = ImportedPageCropOptions[name];
if (value === undefined || value === null) {
return null;
}
return value;
} catch (_) {
return null;
}
}
function getSourceInfo(file, cache) {
var key = file.fsName;
if (cache[key]) {
return cache[key];
}
var src = app.open(file, false);
var info;
try {
info = buildSourceInfo(src);
} finally {
src.close(SaveOptions.NO);
}
cache[key] = info;
return info;
}
function buildSourceInfo(srcDoc) {
var dp = srcDoc.documentPreferences;
var facing = dp.facingPages;
var slugTop = dp.slugTopOffset;
var slugBottom = dp.slugBottomOffset;
var slugInsideOrLeft = dp.slugInsideOrLeftOffset;
var slugOutsideOrRight = dp.slugRightOrOutsideOffset;
var pageCount = srcDoc.pages.length;
var metricsByPage = {};
for (var i = 0; i < pageCount; i++) {
var p = srcDoc.pages[i];
var pb = p.bounds;
var trimWidth = pb[3] - pb[1];
var trimHeight = pb[2] - pb[0];
var slugLeft = slugInsideOrLeft;
var slugRight = slugOutsideOrRight;
if (facing && p.side === PageSideOptions.LEFT_HAND) {
slugLeft = slugOutsideOrRight;
slugRight = slugInsideOrLeft;
}
metricsByPage[i + 1] = {
slugWidth: trimWidth + slugLeft + slugRight,
slugHeight: trimHeight + slugTop + slugBottom,
trimWidth: trimWidth,
trimHeight: trimHeight,
trimLeftFromSlugLeft: slugLeft,
trimTopFromSlugTop: slugTop
};
}
return {
pageCount: pageCount,
metricsByPage: metricsByPage
};
}
function validateUniformSlugSize(file, srcInfo) {
if (srcInfo.pageCount < 2) {
return;
}
var first = srcInfo.metricsByPage[1];
for (var i = 2; i <= srcInfo.pageCount; i++) {
var m = srcInfo.metricsByPage[i];
if (Math.abs(m.slugWidth - first.slugWidth) > 0.01 || Math.abs(m.slugHeight - first.slugHeight) > 0.01) {
throw new Error(lfmt(L.fileSlugSizeMismatch, file.name));
}
}
}
function buildGridPlan(page, itemWidth, itemHeight, settings) {
var pb = page.bounds;
var pageLeft = pb[1];
var pageTop = pb[0];
var pageWidth = pb[3] - pb[1];
var pageHeight = pb[2] - pb[0];
var strokeX = ptToDocX(settings.strokePt);
var strokeY = ptToDocY(settings.strokePt);
var gapX = ptToDocX(settings.horGapPt);
var gapY = ptToDocY(settings.verGapPt);
var marginX = ptToDocX(settings.horMarPt);
var marginY = ptToDocY(settings.verMarPt);
var calcX = calcAxis(pageWidth, itemWidth, gapX, strokeX, marginX, settings.autoHorMargin);
var calcY = calcAxis(pageHeight, itemHeight, gapY, strokeY, marginY, settings.autoVerMargin);
return {
pageLeft: pageLeft,
pageTop: pageTop,
itemWidth: itemWidth,
itemHeight: itemHeight,
strokeX: strokeX,
strokeY: strokeY,
gapX: gapX,
gapY: gapY,
cols: calcX.total,
rows: calcY.total,
marginX: calcX.margin,
marginY: calcY.margin,
capacity: calcX.total * calcY.total,
slotIndex: 0
};
}
function gridPlanKey(plan) {
return [
round4(plan.itemWidth),
round4(plan.itemHeight),
round4(plan.strokeX),
round4(plan.strokeY),
round4(plan.gapX),
round4(plan.gapY),
plan.cols,
plan.rows,
round4(plan.marginX),
round4(plan.marginY)
].join("|");
}
function slotFrameBounds(plan, index) {
var col = index % plan.cols;
var row = Math.floor(index / plan.cols);
var stepX = plan.itemWidth + (2 * plan.strokeX) + plan.gapX;
var stepY = plan.itemHeight + (2 * plan.strokeY) + plan.gapY;
var outerLeft = plan.pageLeft + plan.marginX + (col * stepX);
var outerTop = plan.pageTop + plan.marginY + (row * stepY);
var frameLeft = outerLeft + plan.strokeX;
var frameTop = outerTop + plan.strokeY;
return [frameTop, frameLeft, frameTop + plan.itemHeight, frameLeft + plan.itemWidth];
}
function getOverlayBoundsFromSourceMetrics(placedFrame, metrics) {
if (!metrics || metrics.slugWidth <= 0 || metrics.slugHeight <= 0) {
return placedFrame.geometricBounds;
}
var pb = placedFrame.geometricBounds;
var frameTop = pb[0];
var frameLeft = pb[1];
var frameWidth = pb[3] - pb[1];
var frameHeight = pb[2] - pb[0];
var scaleX = frameWidth / metrics.slugWidth;
var scaleY = frameHeight / metrics.slugHeight;
var left = frameLeft + (metrics.trimLeftFromSlugLeft * scaleX);
var top = frameTop + (metrics.trimTopFromSlugTop * scaleY);
var right = left + (metrics.trimWidth * scaleX);
var bottom = top + (metrics.trimHeight * scaleY);
return [top, left, bottom, right];
}
function createOverlayFrame(doc, page, layer, bounds, weight, tint) {
var noneSwatch = doc.swatches.itemByName("None");
if (!noneSwatch.isValid) {
throw new Error(L.swatchNoneMissing);
}
var blackSwatch = doc.swatches.itemByName("Black");
if (!blackSwatch.isValid) {
throw new Error(L.swatchBlackMissing);
}
var rect = page.rectangles.add({
geometricBounds: bounds,
itemLayer: layer,
fillColor: noneSwatch,
strokeColor: blackSwatch,
strokeTint: tint,
strokeWeight: weight
});
enforceOutsideOrCompensate(rect, weight);
return rect;
}
function getOrCreateOverlayLayer(doc, baseName) {
var existing = doc.layers.itemByName(baseName);
if (existing.isValid) {
if (existing.allPageItems.length === 0) {
return existing;
}
var n = 2;
while (doc.layers.itemByName(baseName + " " + n).isValid) {
n++;
}
return doc.layers.add({ name: baseName + " " + n });
}
return doc.layers.add({ name: baseName });
}
function moveLayerToTop(layer) {
try { layer.move(LocationOptions.AT_BEGINNING); } catch (_) {}
}
function showGridDialog(doc, page, pageWidth, pageHeight, itemWidth, itemHeight, previewPageCount, onAccept) {
var defaultGapPt = new UnitValue(5, "mm").as("pt");
var defaultStrokePt = 2;
var previewItems = [];
var previewLayer = null;
var blackSwatch = doc.swatches.itemByName("Black");
var blueSwatch = getOrCreatePreviewBlueSwatch(doc);
var noneSwatch = doc.swatches.itemByName("None");
var redSwatch = getOrCreatePreviewRedSwatch(doc);
if (!blackSwatch.isValid || !blueSwatch.isValid || !noneSwatch.isValid || !redSwatch.isValid) {
throw new Error(L.previewSwatchesMissing);
}
previewLayer = doc.layers.add({ name: "__preview_rejilla_tmp__" + (new Date().getTime()) });
moveLayerToTop(previewLayer);
var state = {
horGapPt: defaultGapPt,
verGapPt: defaultGapPt,
strokePt: defaultStrokePt,
horMarPt: 0,
verMarPt: 0,
horGapUnit: "mm",
verGapUnit: "mm",
strokeUnit: "pt",
horMarUnit: "mm",
verMarUnit: "mm",
autoHorMargin: true,
autoVerMargin: true,
horTot: 0,
verTot: 0
};
var dialog = new Window("palette");
dialog.text = L.dialogTitle;
dialog.orientation = "column";
dialog.alignChildren = ["left", "top"];
dialog.spacing = 10;
dialog.margins = 16;
var panel1 = dialog.add("panel", undefined, undefined, {name: "panel1"});
panel1.text = L.panelGrid;
panel1.orientation = "column";
panel1.alignChildren = ["left", "top"];
panel1.spacing = 10;
panel1.margins = 10;
panel1.alignment = ["fill", "top"];
var group1 = panel1.add("group", undefined, {name: "group1"});
group1.orientation = "row";
group1.alignChildren = ["left", "center"];
group1.spacing = 10;
group1.margins = 0;
var group2 = group1.add("group", undefined, {name: "group2"});
group2.orientation = "row";
group2.alignChildren = ["left", "center"];
group2.spacing = 10;
group2.margins = 0;
var statictext1 = group2.add("statictext", undefined, undefined, {name: "statictext1"});
statictext1.text = L.labelColumns;
var horTot = group2.add("statictext", undefined, undefined, {name: "horTot"});
horTot.text = L.labelCounterDefault;
horTot.preferredSize.width = 15;
var horTotRem = group2.add("statictext", undefined, undefined, {name: "horTotRem"});
horTotRem.text = L.labelRemainderDefault;
var group3 = group1.add("group", undefined, {name: "group3"});
group3.orientation = "column";
group3.alignChildren = ["left", "center"];
group3.spacing = 10;
group3.margins = 0;
var statictext2 = group3.add("statictext", undefined, undefined, {name: "statictext2"});
statictext2.text = L.labelCross;
var group4 = group1.add("group", undefined, {name: "group4"});
group4.orientation = "row";
group4.alignChildren = ["left", "center"];
group4.spacing = 10;
group4.margins = 0;
var statictext3 = group4.add("statictext", undefined, undefined, {name: "statictext3"});
statictext3.text = L.labelRows;
var verTot = group4.add("statictext", undefined, undefined, {name: "verTot"});
verTot.text = L.labelCounterDefault;
verTot.preferredSize.width = 15;
var verTotRem = group4.add("statictext", undefined, undefined, {name: "verTotRem"});
verTotRem.text = L.labelRemainderDefault;
var group5 = panel1.add("group", undefined, {name: "group5"});
group5.orientation = "row";
group5.alignChildren = ["left", "center"];
group5.spacing = 10;
group5.margins = 0;
var statictext4 = group5.add("statictext", undefined, undefined, {name: "statictext4"});
statictext4.text = L.labelBorder;
var stroke = group5.add('edittext {justify: "right", properties: {name: "stroke"}}');
stroke.text = formatPtAsUnit(defaultStrokePt, "pt");
stroke.preferredSize.width = 63;
var group6 = dialog.add("group", undefined, {name: "group6"});
group6.orientation = "row";
group6.alignChildren = ["left", "center"];
group6.spacing = 10;
group6.margins = 0;
var panel2 = group6.add("panel", undefined, undefined, {name: "panel2"});
panel2.text = L.panelSpacing;
panel2.orientation = "column";
panel2.alignChildren = ["left", "top"];
panel2.spacing = 10;
panel2.margins = 10;
var group7 = panel2.add("group", undefined, {name: "group7"});
group7.orientation = "row";
group7.alignChildren = ["left", "center"];
group7.spacing = 10;
group7.margins = 0;
var statictext5 = group7.add("statictext", undefined, undefined, {name: "statictext5"});
statictext5.text = L.labelHorizontal;
statictext5.preferredSize.width = 70;
var horGap = group7.add('edittext {justify: "right", properties: {name: "horGap"}}');
horGap.text = formatPtAsMm(defaultGapPt);
horGap.preferredSize.width = 63;
var group8 = panel2.add("group", undefined, {name: "group8"});
group8.orientation = "row";
group8.alignChildren = ["left", "center"];
group8.spacing = 10;
group8.margins = 0;
var statictext6 = group8.add("statictext", undefined, undefined, {name: "statictext6"});
statictext6.text = L.labelVertical;
statictext6.preferredSize.width = 70;
var verGap = group8.add('edittext {justify: "right", properties: {name: "verGap"}}');
verGap.text = formatPtAsMm(defaultGapPt);
verGap.preferredSize.width = 63;
var panel3 = group6.add("panel", undefined, undefined, {name: "panel3"});
panel3.text = L.panelMargins;
panel3.orientation = "column";
panel3.alignChildren = ["left", "center"];
panel3.spacing = 10;
panel3.margins = 10;
var group9 = panel3.add("group", undefined, {name: "group9"});
group9.orientation = "column";
group9.alignChildren = ["left", "center"];
group9.spacing = 10;
group9.margins = 0;
var group10 = group9.add("group", undefined, {name: "group10"});
group10.orientation = "row";
group10.alignChildren = ["left", "center"];
group10.spacing = 10;
group10.margins = 0;
var statictext7 = group10.add("statictext", undefined, undefined, {name: "statictext7"});
statictext7.text = L.labelSideMargins;
var horMar = group10.add('edittext {justify: "right", properties: {name: "horMar"}}');
horMar.text = L.defaultMarginText;
horMar.preferredSize.width = 63;
var group11 = panel3.add("group", undefined, {name: "group11"});
group11.orientation = "column";
group11.alignChildren = ["left", "center"];
group11.spacing = 10;
group11.margins = 0;
var group12 = group11.add("group", undefined, {name: "group12"});
group12.orientation = "row";
group12.alignChildren = ["left", "center"];
group12.spacing = 10;
group12.margins = 0;
var statictext8 = group12.add("statictext", undefined, undefined, {name: "statictext8"});
statictext8.text = L.labelVerticalMargins;
var verMar = group12.add('edittext {justify: "right", properties: {name: "verMar"}}');
verMar.text = L.defaultMarginText;
verMar.preferredSize.width = 63;
var group13 = dialog.add("group", undefined, {name: "group13"});
group13.orientation = "row";
group13.alignChildren = ["right", "center"];
group13.spacing = 10;
group13.margins = [0, 11, 0, 0];
group13.alignment = ["fill", "top"];
var button1 = group13.add("button", undefined, undefined, {name: "button1"});
button1.text = L.buttonCancel;
button1.preferredSize.width = 90;
var ok = group13.add("button", undefined, undefined, {name: "ok"});
ok.text = L.buttonOk;
ok.preferredSize.width = 90;
function recalc() {
var parsedGapX = parseMeasureToPtInCurrentUnit(horGap.text, state.horGapPt, state.horGapUnit);
var parsedGapY = parseMeasureToPtInCurrentUnit(verGap.text, state.verGapPt, state.verGapUnit);
var parsedStroke = parseMeasureToPtInCurrentUnit(stroke.text, state.strokePt, state.strokeUnit);
state.horGapPt = Math.max(0, parsedGapX.valuePt);
state.verGapPt = Math.max(0, parsedGapY.valuePt);
state.strokePt = Math.max(0, parsedStroke.valuePt);
state.horGapUnit = parsedGapX.unit;
state.verGapUnit = parsedGapY.unit;
state.strokeUnit = parsedStroke.unit;
var parsedHorMar = parseMeasureToPtInCurrentUnit(horMar.text, state.horMarPt, state.horMarUnit);
var parsedVerMar = parseMeasureToPtInCurrentUnit(verMar.text, state.verMarPt, state.verMarUnit);
state.horMarPt = Math.max(0, parsedHorMar.valuePt);
state.verMarPt = Math.max(0, parsedVerMar.valuePt);
state.horMarUnit = parsedHorMar.unit;
state.verMarUnit = parsedVerMar.unit;
var gapX = ptToDocX(state.horGapPt);
var gapY = ptToDocY(state.verGapPt);
var strokeX = ptToDocX(state.strokePt);
var strokeY = ptToDocY(state.strokePt);
var marginX = ptToDocX(state.horMarPt);
var marginY = ptToDocY(state.verMarPt);
var ax = calcAxis(pageWidth, itemWidth, gapX, strokeX, marginX, state.autoHorMargin);
var ay = calcAxis(pageHeight, itemHeight, gapY, strokeY, marginY, state.autoVerMargin);
state.horTot = ax.total;
state.verTot = ay.total;
state.horMarPt = docXToPt(ax.outerMargin);
state.verMarPt = docYToPt(ay.outerMargin);
horTot.text = String(ax.total);
verTot.text = String(ay.total);
if (state.autoHorMargin) {
horMar.text = formatPtAsUnit(state.horMarPt, state.horMarUnit);
}
if (state.autoVerMargin) {
verMar.text = formatPtAsUnit(state.verMarPt, state.verMarUnit);
}
updateRemLabel(horTotRem, ax.step, ax.remainder);
updateRemLabel(verTotRem, ay.step, ay.remainder);
refreshPreview(ax, ay);
}
function normalizeInputFields() {
horGap.text = formatPtAsUnit(state.horGapPt, state.horGapUnit);
verGap.text = formatPtAsUnit(state.verGapPt, state.verGapUnit);
stroke.text = formatPtAsUnit(state.strokePt, state.strokeUnit);
if (!state.autoHorMargin) {
horMar.text = formatPtAsUnit(state.horMarPt, state.horMarUnit);
}
if (!state.autoVerMargin) {
verMar.text = formatPtAsUnit(state.verMarPt, state.verMarUnit);
}
}
function updateRemLabel(label, step, remainder) {
if (Math.abs(remainder) < 0.0001) {
label.visible = false;
return;
}
label.visible = true;
label.text = lfmt(L.labelRemainderPattern, formatNumberLocale(remainder / step, 2));
}
horGap.onChange = function () { recalc(); normalizeInputFields(); };
verGap.onChange = function () { recalc(); normalizeInputFields(); };
stroke.onChange = function () { recalc(); normalizeInputFields(); };
horGap.onChanging = function () { recalc(); };
verGap.onChanging = function () { recalc(); };
stroke.onChanging = function () { recalc(); };
horMar.onChange = function () {
state.autoHorMargin = false;
recalc();
normalizeInputFields();
};
horMar.onChanging = function () {
state.autoHorMargin = false;
recalc();
};
verMar.onChange = function () {
state.autoVerMargin = false;
recalc();
normalizeInputFields();
};
verMar.onChanging = function () {
state.autoVerMargin = false;
recalc();
};
bindArrowStep(horGap, function () { return state.horGapPt; }, function (v) { state.horGapPt = v; }, function () { return state.horGapUnit; });
bindArrowStep(verGap, function () { return state.verGapPt; }, function (v) { state.verGapPt = v; }, function () { return state.verGapUnit; });
bindArrowStep(stroke, function () { return state.strokePt; }, function (v) { state.strokePt = v; }, function () { return state.strokeUnit; });
bindArrowStep(
horMar,
function () { return state.horMarPt; },
function (v) {
state.autoHorMargin = false;
state.horMarPt = v;
},
function () { return state.horMarUnit; }
);
bindArrowStep(
verMar,
function () { return state.verMarPt; },
function (v) {
state.autoVerMargin = false;
state.verMarPt = v;
},
function () { return state.verMarUnit; }
);
button1.onClick = function () {
cleanupPreview();
dialog.close();
};
ok.onClick = function () {
recalc();
if (state.horTot < 1 || state.verTot < 1) {
alert(L.noFitInPage);
return;
}
normalizeInputFields();
var payload = {
horGapPt: state.horGapPt,
verGapPt: state.verGapPt,
strokePt: state.strokePt,
horMarPt: state.horMarPt,
verMarPt: state.verMarPt,
autoHorMargin: state.autoHorMargin,
autoVerMargin: state.autoVerMargin
};
cleanupPreview();
dialog.close();
onAccept(payload);
};
dialog.onClose = function () {
cleanupPreview();
try { $.global.__gridImportPalette = null; } catch (_) {}
return true;
};
recalc();
try { $.global.__gridImportPalette = dialog; } catch (_) {}
dialog.show();
function refreshPreview(ax, ay) {
clearPreview();
if (ax.total < 1 || ay.total < 1) {
safeRedraw();
return;
}
var pb = page.bounds;
var pageTop = pb[0];
var pageLeft = pb[1];
var pageRight = pb[3];
var pageBottom = pb[2];
var strokeX = ptToDocX(state.strokePt);
var strokeY = ptToDocY(state.strokePt);
var gapX = ptToDocX(state.horGapPt);
var gapY = ptToDocY(state.verGapPt);
var stepX = itemWidth + (2 * strokeX) + gapX;
var stepY = itemHeight + (2 * strokeY) + gapY;
var maxPreview = 2000;
var count = 0;
var firstInRowPrevRect = null;
var debugPrinted = 0;
for (var r = 0; r <= ay.total; r++) {
for (var c = 0; c <= ax.total; c++) {
if (count >= maxPreview) {
return;
}
var outerLeft = pageLeft + ax.margin + (c * stepX);
var outerTop = pageTop + ay.margin + (r * stepY);
var frameLeft = outerLeft + strokeX;
var frameTop = outerTop + strokeY;
var frameRight = frameLeft + itemWidth;
var frameBottom = frameTop + itemHeight;
var previewRect = page.rectangles.add({
geometricBounds: [frameTop, frameLeft, frameBottom, frameRight],
itemLayer: previewLayer,
fillColor: blackSwatch,
strokeColor: blueSwatch,
strokeWeight: state.strokePt,
strokeTint: 10
});
var isExtra = (r === ay.total || c === ax.total);
if (isExtra) {
previewRect.fillTint = 10;
} else {
var idx = (r * ax.total) + c;
previewRect.fillTint = (idx < previewPageCount) ? 50 : 30;
}
enforceOutsideOrCompensate(previewRect, state.strokePt);
if (DEBUG_GRID && debugPrinted < 8) {
var gb = previewRect.geometricBounds;
var vb = previewRect.visibleBounds;
dbg("PREVIEW r=" + r + " c=" + c +
" gapH=" + round4(gapX) +
" strokeX=" + round4(strokeX) +
" outerLeft=" + round4(outerLeft) +
" frameLeft=" + round4(frameLeft) +
" gbL=" + round4(gb[1]) + " gbR=" + round4(gb[3]) +
" vbL=" + round4(vb[1]) + " vbR=" + round4(vb[3]));
if (c === 1 && firstInRowPrevRect && firstInRowPrevRect.isValid) {
var prevVb = firstInRowPrevRect.visibleBounds;
var gapReal = vb[1] - prevVb[3];
dbg("REAL_GAP_ROW r=" + r + " between c0->c1 = " + round4(gapReal) + " pt");
}
debugPrinted++;
}
if (c === 0) {
firstInRowPrevRect = previewRect;
}
previewItems.push(previewRect);
count++;
}
}
// Usable area defined by horMar/verMar (drawn above the rest of preview rectangles).
var utilRect = page.rectangles.add({
geometricBounds: [
pageTop + ay.outerMargin,
pageLeft + ax.outerMargin,
pageBottom - ay.outerMargin,
pageRight - ax.outerMargin
],
itemLayer: previewLayer,
fillColor: noneSwatch,
strokeColor: redSwatch,
strokeWeight: 0.1
});
previewItems.push(utilRect);
safeRedraw();
}
function clearPreview() {
for (var i = previewItems.length - 1; i >= 0; i--) {
if (previewItems[i] && previewItems[i].isValid) {
previewItems[i].remove();
}
}
previewItems = [];
safeRedraw();
}
function cleanupPreview() {
clearPreview();
if (previewLayer && previewLayer.isValid) {
try {
previewLayer.remove();
} catch (_) {}
}
}
function safeRedraw() {
try {
app.redraw();
} catch (_) {}
}
function bindArrowStep(field, getValueFn, setValueFn, getUnitFn) {
var stepPt = new UnitValue(1, "mm").as("pt");
field.addEventListener("keydown", function (ev) {
var keyName = "";
try { keyName = ev.keyName || ""; } catch (eKn) {}
var isUp = (keyName === "Up" || keyName === "UpArrow");
var isDown = (keyName === "Down" || keyName === "DownArrow");
if (!isUp && !isDown) {
return;
}
var current = parseMeasureToPt(field.text, getValueFn());
var next = current + (isUp ? stepPt : -stepPt);
if (next < 0) {
next = 0;
}
setValueFn(next);
field.text = formatPtAsUnit(next, getUnitFn ? getUnitFn() : "mm");
recalc();
normalizeInputFields();
try { if (ev.preventDefault) ev.preventDefault(); } catch (ePd) {}
});
}
}
function enforceOutsideOrCompensate(rect, strokeWeight) {
if (!rect || !rect.isValid || !strokeWeight || strokeWeight <= 0) {
return;
}
var outsideApplied = false;
if (typeof StrokeAlignment !== "undefined" && StrokeAlignment.OUTSIDE_ALIGNMENT !== undefined) {
try {
rect.strokeAlignment = StrokeAlignment.OUTSIDE_ALIGNMENT;
outsideApplied = (rect.strokeAlignment === StrokeAlignment.OUTSIDE_ALIGNMENT);
} catch (_) {
outsideApplied = false;
}
}
if (!outsideApplied) {
// Emulate outside stroke when only center alignment is available: expand half-line on each side.
var gb = rect.geometricBounds;
var halfX = ptToDocX(strokeWeight / 2);
var halfY = ptToDocY(strokeWeight / 2);
rect.geometricBounds = [gb[0] - halfY, gb[1] - halfX, gb[2] + halfY, gb[3] + halfX];
dbg("Outside alignment fallback aplicado. Expandido X+-" + round4(halfX) + " / Y+-" + round4(halfY));
}
}
function dbg(msg) {
if (!DEBUG_GRID) {
return;
}
try { $.writeln("[GRIDDBG] " + msg); } catch (_) {}
}
function calcAxis(pageLen, itemLen, gap, stroke, margin, autoMargin) {
var total;
var step = itemLen + (2 * stroke) + gap;
var outerMargin = Math.max(0, margin);
var effectiveMargin = outerMargin;
var availableLen;
var usedLen;
var innerOffset = 0;
if (step <= 0 || pageLen <= 0) {
return { total: 0, margin: effectiveMargin, outerMargin: outerMargin, step: step, remainder: 0 };
}
if (autoMargin) {
total = Math.floor((pageLen + gap) / step);
if (total < 0) { total = 0; }
usedLen = (total * (itemLen + (2 * stroke))) + (Math.max(0, total - 1) * gap);
outerMargin = (pageLen - usedLen) / 2;
if (outerMargin < 0) { outerMargin = 0; }
effectiveMargin = outerMargin;
availableLen = pageLen - (2 * outerMargin);
} else {
availableLen = pageLen - (2 * outerMargin);
total = Math.floor((availableLen + gap) / step);
if (total < 0) { total = 0; }
usedLen = (total * (itemLen + (2 * stroke))) + (Math.max(0, total - 1) * gap);
innerOffset = (availableLen - usedLen) / 2;
if (innerOffset < 0) { innerOffset = 0; }
effectiveMargin = outerMargin + innerOffset;
}
var remainder = availableLen - usedLen;
return {
total: total,
margin: effectiveMargin,
outerMargin: outerMargin,
step: step,
remainder: remainder
};
}
function parseMeasureToPt(text, fallbackPt) {
if (text === null || text === undefined) {
return fallbackPt;
}
var s = String(text);
s = s.replace(/\s+/g, " ").replace(/,/g, ".");
s = s.replace(/^\s+|\s+$/g, "");
if (!s) {
return fallbackPt;
}
try {
var uv = new UnitValue(s);
var pt = uv.as("pt");
if (isNaN(pt)) {
return fallbackPt;
}
return pt;
} catch (_) {
var n = Number(s);
if (isNaN(n)) {
return fallbackPt;
}
try {
return new UnitValue(n, "mm").as("pt");
} catch (__){
return fallbackPt;
}
}
}
function parseMeasureToPtInCurrentUnit(text, fallbackPt, currentUnit) {
var unit = normalizeUnitToken(currentUnit || "mm");
if (text === null || text === undefined) {
return { valuePt: fallbackPt, unit: unit };
}
var s = String(text);
s = s.replace(/\s+/g, " ").replace(/,/g, ".");
s = s.replace(/^\s+|\s+$/g, "");
if (!s) {
return { valuePt: fallbackPt, unit: unit };
}
var explicitUnit = extractExplicitUnit(s);
if (explicitUnit) {
try {
var uvExplicit = new UnitValue(s);
var ptExplicit = uvExplicit.as("pt");
if (!isNaN(ptExplicit)) {
return { valuePt: ptExplicit, unit: explicitUnit };
}
} catch (_) {}
}
var n = Number(s);
if (!isNaN(n)) {
try {
return { valuePt: new UnitValue(n, unit).as("pt"), unit: unit };
} catch (_) {
return { valuePt: fallbackPt, unit: unit };
}
}
try {
var uv = new UnitValue(s);
var pt = uv.as("pt");
if (!isNaN(pt)) {
return { valuePt: pt, unit: unit };
}
} catch (_) {}
return { valuePt: fallbackPt, unit: unit };
}
function formatPtAsMm(pt) {
return formatPtAsUnit(pt, "mm");
}
function formatPtAsUnit(pt, unit) {
var u = normalizeUnitToken(unit || "mm");
return formatNumberLocale(new UnitValue(pt, "pt").as(u), 2) + " " + u;
}
function round2(n) {
return Math.round(n * 100) / 100;
}
function formatNumberLocale(n, decimals) {
var factor = Math.pow(10, decimals);
var rounded = Math.round(n * factor) / factor;
var s = String(rounded);
if (LOCALE_DECIMAL === ",") {
s = s.replace(/\./g, ",");
}
return s;
}
function round4(n) {
return Math.round(n * 10000) / 10000;
}
function getLocaleDecimalSeparator() {
try {
var loc = String($.locale || "").toLowerCase();
if (/^(es|fr|de|it|pt|ru|nl|pl|tr|cs|da|fi|no|sv)/.test(loc)) {
return ",";
}
} catch (_) {}
return ".";
}
function extractExplicitUnit(s) {
var m = String(s || "").toLowerCase().match(/[a-z]+$/);
if (!m || !m[0]) {
return null;
}
return normalizeUnitToken(m[0]);
}
function normalizeUnitToken(u) {
var s = String(u || "").toLowerCase();
if (s === "inch" || s === "inches") { return "in"; }
if (s === "point" || s === "points") { return "pt"; }
if (s === "millimeter" || s === "millimeters") { return "mm"; }
if (s === "centimeter" || s === "centimeters") { return "cm"; }
if (s === "pica" || s === "picas") { return "pc"; }
if (s === "pixel" || s === "pixels") { return "px"; }
if (s === "mm" || s === "cm" || s === "pt" || s === "in" || s === "pc" || s === "px") { return s; }
return "mm";
}
function getOrCreatePreviewRedSwatch(doc) {
var sw = doc.swatches.itemByName("Red");
if (sw.isValid) {
return sw;
}
var c = doc.colors.itemByName("__preview_red__");
if (c.isValid) {
return c;
}
try {
return doc.colors.add({
name: "__preview_red__",
model: ColorModel.PROCESS,
space: ColorSpace.RGB,
colorValue: [255, 0, 0]
});
} catch (_) {
return doc.swatches.itemByName("Black");
}
}
function getOrCreatePreviewBlueSwatch(doc) {
var sw = doc.swatches.itemByName("Blue");
if (sw.isValid) {
return sw;
}
var c = doc.colors.itemByName("__preview_blue__");
if (c.isValid) {
return c;
}
try {
return doc.colors.add({
name: "__preview_blue__",
model: ColorModel.PROCESS,
space: ColorSpace.RGB,
colorValue: [0, 120, 255]
});
} catch (_) {
return doc.swatches.itemByName("Black");
}
}
function measurementUnitToUnitValueUnit(mu) {
try {
if (mu === MeasurementUnits.MILLIMETERS) { return "mm"; }
if (mu === MeasurementUnits.CENTIMETERS) { return "cm"; }
if (mu === MeasurementUnits.INCHES) { return "in"; }
if (mu === MeasurementUnits.PICAS) { return "pc"; }
if (mu === MeasurementUnits.POINTS) { return "pt"; }
if (mu === MeasurementUnits.PIXELS) { return "px"; }
} catch (_) {}
return "pt";
}
function convertUnit(value, fromUnit, toUnit) {
try {
return new UnitValue(value, fromUnit).as(toUnit);
} catch (_) {
return value;
}
}
function ptToDocX(v) { return convertUnit(v, "pt", DOC_UNIT_X); }
function ptToDocY(v) { return convertUnit(v, "pt", DOC_UNIT_Y); }
function docXToPt(v) { return convertUnit(v, DOC_UNIT_X, "pt"); }
function docYToPt(v) { return convertUnit(v, DOC_UNIT_Y, "pt"); }
function measurePlacedSlugSize(page, doc, file, crop, pageNumber, prevPageNumber, prevCrop) {
var noneSwatch = doc.swatches.itemByName("None");
if (!noneSwatch.isValid) {
throw new Error(L.swatchNoneMissing);
}
var frame = null;
try {
app.importedPageAttributes.pageNumber = pageNumber;
app.importedPageAttributes.importedPageCrop = crop;
frame = page.rectangles.add({
geometricBounds: [0, 0, 10, 10],
strokeWeight: 0,
fillColor: noneSwatch
});
var g = frame.place(file, false)[0];
var gb = g.geometricBounds;
return {
width: gb[3] - gb[1],
height: gb[2] - gb[0]
};
} catch (_) {
return null;
} finally {
if (frame && frame.isValid) {
frame.remove();
}
app.importedPageAttributes.pageNumber = prevPageNumber;
app.importedPageAttributes.importedPageCrop = prevCrop;
}
}
function executePlacement(ui) {
app.doScript(
function () {
executePlacementCore(ui);
},
ScriptLanguage.JAVASCRIPT,
undefined,
UndoModes.ENTIRE_SCRIPT,
L.undoLabel
);
}
function executePlacementCore(ui) {
var overlayLayer = getOrCreateOverlayLayer(targetDoc, L.overlayLayerBaseName);
moveLayerToTop(overlayLayer);
var totalPlaced = 0;
var currentPage = targetPage;
var currentPlan = null;
var currentPlanKey = "";
var failed = false;
try {
app.importedPageAttributes.importedPageCrop = placementCrop;
for (var f = 0; f < files.length; f++) {
var file = files[f];
var srcInfo = getSourceInfo(file, sourceInfoCache);
validateUniformSlugSize(file, srcInfo);
var metrics = srcInfo.metricsByPage[1];
var filePlan = buildGridPlan(currentPage, metrics.slugWidth, metrics.slugHeight, ui);
if (filePlan.capacity < 1) {
throw new Error(lfmt(L.noFitForFile, file.name));
}
var filePlanKey = gridPlanKey(filePlan);
if (currentPlan && (currentPlan.slotIndex > 0 || currentPlanKey !== filePlanKey)) {
currentPage = targetDoc.pages.add(LocationOptions.AFTER, currentPage);
currentPlan = null;
currentPlanKey = "";
}
for (var p = 1; p <= srcInfo.pageCount; p++) {
var pageMetrics = srcInfo.metricsByPage[p];
if (!currentPlan || currentPlan.slotIndex >= currentPlan.capacity || currentPlanKey !== filePlanKey) {
if (currentPlan && currentPlan.slotIndex >= currentPlan.capacity) {
currentPage = targetDoc.pages.add(LocationOptions.AFTER, currentPage);
}
currentPlan = buildGridPlan(currentPage, metrics.slugWidth, metrics.slugHeight, ui);
currentPlanKey = gridPlanKey(currentPlan);
if (currentPlan.capacity < 1) {
throw new Error(lfmt(L.noFitInTargetPage, currentPage.name));
}
}
app.importedPageAttributes.pageNumber = p;
app.importedPageAttributes.importedPageCrop = placementCrop;
var frameBounds = slotFrameBounds(currentPlan, currentPlan.slotIndex);
var placedFrame = currentPage.rectangles.add({
geometricBounds: frameBounds,
strokeWeight: 0,
fillColor: targetDoc.swatches.itemByName("None")
});
placedFrame.place(file, false);
var overlayBounds = getOverlayBoundsFromSourceMetrics(placedFrame, pageMetrics);
createOverlayFrame(targetDoc, currentPage, overlayLayer, overlayBounds, ui.strokePt, 10);
currentPlan.slotIndex++;
totalPlaced++;
}
}
} catch (e) {
failed = true;
alert(L.errorPrefix + e.message);
} finally {
app.importedPageAttributes.pageNumber = originalPageNumber;
app.importedPageAttributes.importedPageCrop = originalCrop;
}
if (!failed) {
alert(lfmt(L.doneMessage, totalPlaced));
}
}
function lfmt(template) {
var out = String(template || "");
for (var i = 1; i < arguments.length; i++) {
out = out.replace(new RegExp("\\{" + (i - 1) + "\\}", "g"), String(arguments[i]));
}
return out;
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment