Projekt

Allgemein

Profil

Dateien » image-export.jsx

Skript zum Bildexport - Martin Kraetke, 30.03.2017 14:58

 
1
#targetengine "session"
2
/*
3
 * image-export.jsx
4
 *
5
 *
6
 * Export images from an InDesign document to web-friendly formats.
7
 *
8
 *
9
 * Note: this script requires at least InDesign Version 8.0 (CS6).
10
 *
11
 *
12
 * Authors: Gregor Fellenz (twitter: @grefel), Martin Kraetke (@mkraetke)
13
 *
14
 *
15
 * LICENSE
16
 *
17
 * Copyright (c) 2015, Gregor Fellenz and le-tex publishing services GmbH
18
 * All rights reserved.
19
 *
20
 * Redistribution and use in source and binary forms, with or without
21
 * modification, are permitted provided that the following conditions are met:
22
 *
23
 * 1. Redistributions of source code must retain the above copyright notice,
24
 * this list of conditions and the following disclaimer.
25
 *
26
 * 2. Redistributions in binary form must reproduce the above copyright notice,
27
 * this list of conditions and the following disclaimer in the documentation
28
 * and/or other materials provided with the distribution.
29
 *
30
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
31
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
32
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
33
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
34
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
35
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
36
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
37
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
39
 * POSSIBILITY OF SUCH DAMAGE.
40
 *
41
 */
42

    
43
/*
44
 * set language
45
 */
46
lang = {
47
  pre: 1 // en = 0, de = 1
48
}
49
/*
50
 * image options object
51
 */
52
image = {
53
    minExportDPI:1,
54
    maxExportDPI:2400,
55
    exportDPI:144,
56
    maxResolution:4000000,
57
    pngTransparency:true,
58
    objectExportOptions:true,
59
    objectExportDensityFactor:0,
60
    overrideExportFilenames:false,
61
    exportFromHiddenLayers:false,
62
    relinkToExportPaths:false,
63
    exportDir:"export",
64
    exportQuality:2,
65
    exportFormat:0, // 0 = PNG | 1 = JPG
66
    pageItemLabel:"letex:fileName",
67
    logFilename:"export.log"
68
}
69
/*
70
 * image options object
71
 */
72
imageInfo = {
73
    filename:null,
74
    format:null,
75
    width:null,
76
    height:null,
77
}
78
/*
79
 * set panel preferences
80
 */
81
panel = {
82
    title:["le-tex – Export Images", "le-tex – Bilder exportieren"][lang.pre],
83
    tabGeneralTitle:["General", "Allgemein"][lang.pre],
84
    tabAdvancedTitle:["Advanced", "Erweitert"][lang.pre],
85
    tabInfoTitle:["Info", "Informationen"][lang.pre],
86
    densityTitle:["Density (ppi)", "Auflösung (ppi)"][lang.pre],
87
    qualityTitle:["Quality", "Qualität"][lang.pre],
88
    qualityValues:[["max", "high", "medium", "low"], ["Maximum", "Hoch", "Mittel", "Niedrig"]][lang.pre],
89
    formatTitle:"Format",
90
    formatValues:["JPG", "PNG"],
91
    formatDescriptionPNG:["for line art and text", "Für Strichzeichnungen und Text"][lang.pre],
92
    formatDescriptionJPEG:["for photographs and gradients", "für Fotos und Verläufe"][lang.pre],
93
    objectExportOptionsTitle:["Object export options", "Objektexportoptionen"][lang.pre],
94
    objectExportDensityFactorTitle:["Density Multiplier", "Multiplikator Auflösung"][lang.pre],
95
    objectExportDensityFactorValues:[1, 2, 3, 4],
96
    overrideExportFilenamesTitle:["Override embedded export filenames", "Eingebettete Export-Dateinamen überschreiben"][lang.pre],
97
    pngTransparencyTitle:["PNG Transparency", "PNG Transparenz"][lang.pre],
98
    exportFromHiddenLayersTitle:["Export images from hidden layers", "Bilder von versteckten Ebenen exportieren"][lang.pre],
99
    relinkToExportPathsTitle:["Relink to export path", "Verknüpfung zu Exportpfad ändern"][lang.pre],
100
    relinkToExportPathsWarning:["Warning! Each link will be replaced with its export path.", "Warnung! Jede Verknüpfung wird durch ihren Exportpfad ersetzt."][lang.pre],
101
    maxResolutionTitle:["Max Resolution (px)", "Maximale Auflösung (px)"][lang.pre],
102
    selectDirButtonTitle:["Choose", "Auswählen"][lang.pre],
103
    selectDirMenuTitle:["Choose a directory", "Verzeichnis auswählen"][lang.pre],
104
    panelFilenameOptionsTitle:["Filenames", "Dateinamen"][lang.pre],
105
    miscellaneousOptionsTitle:["Miscellaneous Options", "Sonstige Optionen"][lang.pre],
106
    infoFilename:["Filename", "Dateiname"][lang.pre],
107
    infoWidth:["Width", "Breite"][lang.pre],
108
    infoHeight:["Height", "Höhe"][lang.pre],
109
    infoNoImage:["No image selected.", "Kein Bild ausgewählt."][lang.pre],
110
    progressBarTitle:["export Images", "Bilder exportieren"][lang.pre],
111
    noValidLinks:["No valid links found.", "Keine Bild-Verknüpfungen gefunden"][lang.pre],
112
    finishedMessage:["images exported.", "Bilder exportiert."][lang.pre],
113
    buttonOK:"OK",
114
    buttonCancel:["Cancel", "Abbrechen"][lang.pre],
115
    errorMissingImage:["Warning! Image cannot be found: ", "Warnung! Bild konnte nicht gefunden werden: "][lang.pre],
116
    errorEmbeddedImage:["Warning! Embedded Image cannot be exported: ", "Warnung! Eingebettetes Bild kann nicht exportiert werden: "][lang.pre],
117
    promptMissingImages:["images cannot be exported. Proceed?","Bilder können nicht exportiert werden. Fortfahren?"][lang.pre],
118
    lockedLayerWarning:["All layers with images must be unlocked.","Alle Ebenen mit Bildern müssen entsperrt sein."][lang.pre]
119
}
120
/*
121
 * start
122
 */
123
main();
124
/*
125
 * main pipeline
126
 */
127
function main(){
128
    // open file dialog and load a document
129
    if (app.layoutWindows.length == 0) {
130
        var file = File.openDialog ("Select a file", "InDesign:*.indd;*.indb;*.idml, InDesign Document:*.indd, InDesign Book:*.indb, InDesign Markup:*.idml", true)
131
        try {
132
            app.open(File(file));
133
        } catch (e) {
134
            alert(e);
135
            return;
136
        };
137
    }
138
    var doc = app.documents[0];
139
    // check if document is saved
140
    if ((!doc.saved || doc.modified)) {
141
        if ( confirm ("The document needs to be saved.", undefined, "Document not saved.")) {
142
            try {
143
                var userLevel = app.scriptPreferences.userInteractionLevel;
144
                app.scriptPreferences.userInteractionLevel = UserInteractionLevels.INTERACT_WITH_ALL;
145
                doc = doc.save();
146
                app.scriptPreferences.userInteractionLevel = userLevel;
147
            } catch (e) {
148
                alert ("The document couldn't be saved.\n" + e);
149
                return;
150
            }
151
        } else {
152
            return;
153
        }
154
    }
155
    // show window
156
    try {
157
        // register event listener
158
        jsExtensions();
159
        exportImages(doc);
160
    } catch (e) {
161
        alert ("Error:\n" + e);
162
    }
163
}
164
/*
165
 * add JavaScript extensions
166
 */
167
function jsExtensions(){
168
    // indexOf
169
    if (!Array.prototype.indexOf) {
170
        Array.prototype.indexOf = function(elt /*, from*/)
171
        {
172
            var len = this.length;
173

    
174
            var from = Number(arguments[1]) || 0;
175
            from = (from < 0)
176
                ? Math.ceil(from)
177
                : Math.floor(from);
178

    
179
            if (from < 0)
180
                from += len;
181

    
182
            for (; from < len; from++) {
183
                if (from in this &&
184
                    this[from] === elt)
185
                    return from;
186
            }
187
            return -1;
188
        };
189
    }
190
    // find
191
    if (!Array.prototype.find) {
192
        Array.prototype.find = function(predicate) {
193
            'use strict';
194
            if (this == null) {
195
                throw new TypeError('Array.prototype.find called on null or undefined');
196
            }
197
            if (typeof predicate !== 'function') {
198
                throw new TypeError('predicate must be a function');
199
            }
200
            var list = Object(this);
201
            var length = list.length >>> 0;
202
            var thisArg = arguments[1];
203
            var value;
204

    
205
            for (var i = 0; i < length; i++) {
206
                value = list[i];
207
                if (predicate.call(thisArg, value, i, list)) {
208
                    return value;
209
                }
210
            }
211
            return undefined;
212
        };
213
    }
214
}
215
function exportImages(doc){
216
    if(drawWindow() == 1){
217
        // overwrite internal Measurement Units to pixels
218
        app.scriptPreferences.measurementUnit = MeasurementUnits.PIXELS;
219
    } else {
220
        return;
221
    }
222
}
223
/*
224
 * User Interface
225
 */
226
function drawWindow() {
227
    //var myWindow = new Window("dialog", panel.title, undefined, {resizable:true});
228
    var myWindow = new Window("palette", panel.title, undefined);
229
    myWindow.orientation = "column";
230
    myWindow.alignChildren ="fill";
231
    var tpanel = myWindow.add("tabbedpanel");
232
    tpanel.alignChildren = "fill";
233
    /*
234
     * General Tab
235
     */
236
    var tabGeneral = tpanel.add ("tab", undefined, panel.tabGeneralTitle);
237
    tabGeneral.alignChildren = "fill";
238
    var panelSelectDir = tabGeneral.add("panel", undefined, panel.selectDirMenuTitle);
239
    panelSelectDir.alignChildren = "left";
240
    var panelSelectDirInputPath = panelSelectDir.add("edittext");
241
    panelSelectDirInputPath.preferredSize.width = 255;
242
    panelSelectDirInputPath.text = getDefaultExportPath();
243
    var panelSelectDirButton = panelSelectDir.add("button", undefined, panel.selectDirButtonTitle);  
244
    var panelQuality = tabGeneral.add("panel", undefined, panel.qualityTitle);
245
    panelQuality.orientation = "column";
246
    panelQuality.alignChildren = "left";
247
    var panelQualityRadiobuttons = panelQuality.add("group");
248
    panelQualityRadiobuttons.orientation = "row";
249
    for (i = 0; i < panel.qualityValues.length; i++) {
250
        panelQualityRadiobuttons.add ("radiobutton", undefined, (panel.qualityValues[i]));
251
    }
252
    panelQualityRadiobuttons.children[image.exportQuality].value =true;
253
    var densityGroup = panelQuality.add("group");  
254
    var densityInputDPI = densityGroup.add ("edittext", undefined, image.exportDPI);
255
    densityInputDPI.characters = 4;
256
    var densitySliderDPI = densityGroup.add("slider", undefined, image.exportDPI, image.minExportDPI, image.maxExportDPI);
257
    densityGroup.add ("statictext", undefined, panel.densityTitle);
258
    // synchronize slider and input field
259
    densitySliderDPI.onChanging = function () {densityInputDPI.text = densitySliderDPI.value};
260
    densityInputDPI.onChanging = function () {densitySliderDPI.value = densityInputDPI.text};
261
    var panelMaxResolutionGroup = panelQuality.add("group");
262
    panelMaxResolutionGroup.add("statictext", undefined, panel.maxResolutionTitle);  
263
    var inputMaxRes = panelMaxResolutionGroup.add("edittext", undefined, image.maxResolution);
264
    var panelFormat = tabGeneral.add("panel", undefined, panel.formatTitle);
265
    var panelFormatGroup = panelFormat.add("group");
266
    panelFormat.alignChildren = "left";
267
    var formatDropdown = panelFormatGroup.add("dropdownlist", undefined, panel.formatValues);
268
    formatDropdown.selection = image.exportFormat;
269
    var formatDropdownDescription = panelFormatGroup.add("statictext", undefined, undefined);
270
    formatDropdownDescription.text = formatDropdown.selection.text == "PNG" ? panel.formatDescriptionPNG : panel.formatDescriptionJPEG;
271
    /*
272
     * Advanced Tab
273
     */
274
    var tabAdvanced = tpanel.add ("tab", undefined, panel.tabAdvancedTitle);
275
    tabAdvanced.alignChildren = "fill";
276
    var panelObjectExportOptions = tabAdvanced.add("panel", undefined, panel.objectExportOptionsTitle);
277
    panelObjectExportOptions.alignChildren = "left";
278
    var objectExportOptions = panelObjectExportOptions.add("group");
279
    var objectExportOptionsCheckbox = objectExportOptions.add("checkbox", undefined, panel.objectExportOptionsTitle);
280
    objectExportOptionsCheckbox.value = image.objectExportOptions;
281
    var objectExportOptionsDensity = panelObjectExportOptions.add("group");
282
    var objectExportOptionsDensityDropdown = objectExportOptionsDensity.add("dropdownlist", undefined, panel.objectExportDensityFactorValues);
283
    objectExportOptionsDensityDropdown.selection = image.objectExportDensityFactor;
284
    var resolutionFactor = objectExportOptionsDensity.add("statictext", undefined, panel.objectExportDensityFactorTitle);
285
    var panelFilenameOptions = tabAdvanced.add("panel", undefined, panel.panelFilenameOptionsTitle);
286
    panelFilenameOptions.alignChildren = "left";
287
    var overrideExportFilenames = panelFilenameOptions.add("group");
288
    var overrideExportFilenamesCheckbox = overrideExportFilenames.add("checkbox", undefined, panel.overrideExportFilenamesTitle);
289
    overrideExportFilenamesCheckbox.value = image.overrideExportFilenames;
290
    var panelMiscellaneousOptions = tabAdvanced.add("panel", undefined, panel.miscellaneousOptionsTitle)
291
    panelMiscellaneousOptions.alignChildren = "left";
292
    var pngTransparencyGroupCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.pngTransparencyTitle);
293
    pngTransparencyGroupCheckbox.value = image.pngTransparency;
294
    var pngFormatActive = formatDropdown.selection.text == "PNG" || isFormatOverrideActive("PNG");
295
    pngTransparencyGroupCheckbox.enabled = pngFormatActive;
296
    // disable checkbox if selected format is not png
297
    formatDropdown.onChange = function(){
298
        pngFormatActive = formatDropdown.selection.text == "PNG" || isFormatOverrideActive("PNG");
299
        pngTransparencyGroupCheckbox.enabled = pngFormatActive;
300
        formatDropdownDescription.text = pngFormatActive ? panel.formatDescriptionPNG : panel.formatDescriptionJPEG;
301
    };
302
    var exportFromHiddenLayersCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.exportFromHiddenLayersTitle);
303
    exportFromHiddenLayersCheckbox.value = image.exportFromHiddenLayers;
304
    var relinkToExportPathsCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.relinkToExportPathsTitle);
305
    relinkToExportPathsCheckbox.value = image.relinkToExportPaths;
306
    relinkToExportPathsCheckbox.onClick = function(){
307
        if(relinkToExportPathsCheckbox.value == true) {
308
            alert(panel.relinkToExportPathsWarning)
309
        }
310
    }
311
    /*
312
     * Info Tab
313
     */
314
    var tabInfo = tpanel.add ("tab", undefined, panel.tabInfoTitle);
315
    tabInfo.alignChildren = "left";
316
    tabInfo.iFilename = tabInfo.add("statictext", undefined, panel.infoNoImage);
317
    tabInfo.iPosX = tabInfo.add("statictext", undefined, "");
318
    tabInfo.iPosY = tabInfo.add("statictext", undefined, "");
319
    tabInfo.iWidth = tabInfo.add("statictext", undefined, "");
320
    tabInfo.iHeight = tabInfo.add("statictext", undefined, "");
321
    tabInfo.iPosX.characters = tabInfo.iPosY.characters = tabInfo.iWidth.characters = tabInfo.iHeight.characters = tabInfo.iFilename.characters = 40;
322
    // listen to each selection change and update info tab
323
    var afterSelectChanged = app.addEventListener(Event.AFTER_SELECTION_CHANGED, function(){
324
        if(app.selection[0] != undefined && app.selection[0].constructor.name == "Rectangle"){
325
            var rectangle = app.selection[0];
326
            width = Math.round((rectangle.geometricBounds[3] - rectangle.geometricBounds[1])  * 100) / 100;
327
            height = Math.round((rectangle.geometricBounds[2] - rectangle.geometricBounds[0]) * 100) / 100;
328
            posX = Math.round(rectangle.geometricBounds[1] * 100) / 100;
329
            posY = Math.round(rectangle.geometricBounds[0] * 100) / 100;
330
            tabInfo.iFilename.text = panel.infoFilename + ": " + rectangle.extractLabel(image.pageItemLabel);
331
            tabInfo.iPosX.text = "x: " + posX;
332
            tabInfo.iPosY.text = "y: " + posY;
333
            tabInfo.iWidth.text = panel.infoWidth + ": " + width;
334
            tabInfo.iHeight.text = panel.infoHeight + ": " + height;
335
        } else {
336
            tabInfo.iFilename.text = panel.infoNoImage;
337
            tabInfo.iPosX.text = "";
338
            tabInfo.iPosY.text = "";
339
            tabInfo.iHeight.text = "";
340
            tabInfo.iWidth.text = "";
341
        }
342
    }, false);
343
    // buttons OK/Cancel
344
    var panelButtonGroup = myWindow.add("group");
345
    panelButtonGroup.orientation = "row";
346
    var buttonOK = panelButtonGroup.add("button", undefined, panel.buttonOK, {name: "ok"});
347
    var buttonCancel = panelButtonGroup.add("button", undefined, panel.buttonCancel, {name: "cancel"} );
348
    // change text to selected file path
349
    panelSelectDirButton.onClick  = function() {
350
        var result = Folder.selectDialog ();
351
        if (result) {
352
            panelSelectDirInputPath.text = result;
353
        }
354
    }
355
    buttonOK.onClick = function (){
356
        //overwrite values with form input
357
        image.exportDir = Folder(panelSelectDirInputPath.text);
358
        image.exportDPI = Number(densityInputDPI.text);
359
        image.exportQuality = selectedRadiobutton(panelQualityRadiobuttons);
360
        image.exportFormat = formatDropdown.selection.text;
361
        image.maxResolution = Number(inputMaxRes.text);
362
        image.objectExportOptions = objectExportOptionsCheckbox.value;
363
        image.objectExportDensityFactor = objectExportOptionsDensityDropdown.selection.text;
364
        image.overrideExportFilenames = overrideExportFilenamesCheckbox.value;
365
        image.pngTransparency = pngTransparencyGroupCheckbox.value;
366
        image.exportFromHiddenLayers = exportFromHiddenLayersCheckbox.value;
367
        image.relinkToExportPaths = relinkToExportPathsCheckbox.value;
368
        myWindow.close(1);
369

    
370
        function selectedRadiobutton (rbuttons){
371
            for(var i = 0; i < rbuttons.children.length; i++) {
372
                if(rbuttons.children[i].value == true) {
373
                    return i;
374
                }
375
            }
376
        }
377
        afterSelectChanged.remove();
378
        getFilelinks(app.documents[0]);
379
    }
380
    buttonCancel.onClick = function(){
381
        afterSelectChanged.remove();
382
        myWindow.close();
383
    }
384
    return myWindow.show();
385
}
386
function getFilelinks(doc) {
387
    var docLinks = linksToSortedArray(doc.links);
388
    var uniqueBasenames = [];
389
    var exportLinks = [];
390
    var missingLinks = [];
391
    // delete filename labels, if option is set
392
    if(image.overrideExportFilenames == true){
393
        deleteLabel(docLinks);
394
    }
395
    
396
    // clear log
397
    clearLog(image.exportDir, image.logFilename);
398
    var currentDate = new Date();
399
    var dateTime = currentDate.getDate() + '/'
400
        + (currentDate.getMonth()+1)  + '/' 
401
        + currentDate.getFullYear() + ' '  
402
        + currentDate.getHours() + ':'
403
        + currentDate.getMinutes() + ':' 
404
        + currentDate.getSeconds();
405
    writeLog("Image export started at " + dateTime + "\n", image.exportDir, image.logFilename);
406
    
407
    // iterate over file links
408
    for (var i = 0; i < docLinks.length; i++) {
409
        var link = docLinks[i];
410

    
411
        if(isValidLink(link) == true){
412
            writeLog(link.name + "\n" + link.filePath, image.exportDir, image.logFilename);
413
            
414
            var rectangle = link.parent.parent;
415
            // disable lock since this prevents images to be exported
416
            // note that just the group itself has a lock state, not their childs
417
            if(rectangle.parent.constructor.name == 'Group'){
418
                if(rectangle.parent.locked != false){
419
                    rectangle.parent.locked = false; 
420
                }
421
            } else {
422
                if(rectangle.locked != false){
423
                    rectangle.locked = false;
424
                }
425
            }
426
            var exportFromHiddenLayers = rectangle.itemLayer.visible ? true : image.exportFromHiddenLayers;
427
            var originalBounds = rectangle.geometricBounds;
428
            // ignore images in overset text and rectangles with zero width or height 
429
            if(exportFromHiddenLayers
430
               && originalBounds[0] - originalBounds[2] != 0
431
               && originalBounds[1] - originalBounds[3] != 0
432
              ){
433
                if(rectangle.itemLayer.locked == true) alert(panel.lockedLayerWarning);
434
                // this is necessary to avoid moving of anchored objects with Y-Offset
435
                var imageExceedsPageY = rectangle.parentPage.bounds[0] > originalBounds[0];
436
                if(imageExceedsPageY == true){
437
                    originalBounds[0] = originalBounds[0] + getAnchoredObjectOffset(rectangle)[0]; //y1
438
                    originalBounds[2] = originalBounds[2] + getAnchoredObjectOffset(rectangle)[0]; //y2
439
                }
440
                rectangle = cropRectangleToBleeds(rectangle);
441
                var objectExportOptions = rectangle.objectExportOptions;
442
                // use format override in objectExportOptions if active. Check InDesign version because the property changed.
443
                var customImageConversion = isObjectExportOptionActive(objectExportOptions);
444
                var overrideBool = image.objectExportOptions && customImageConversion;
445
                var localFormat = overrideBool ? objectExportOptions.imageConversionType.toString().replace(/^JPEG/g, "JPG") : image.exportFormat;
446
                var localDensity = overrideBool ? Number(objectExportOptions.imageExportResolution.toString().replace(/^PPI_/g, "")) * image.objectExportDensityFactor : image.exportDPI;
447
                var normalizedDensity = getMaxDensity(localDensity, rectangle, image.maxResolution);
448
                var objectExportQualityInt = ["MAXIMUM", "HIGH", "MEDIUM", "LOW" ].indexOf(objectExportOptions.jpegOptionsQuality.toString());
449
                var localQuality = overrideBool && localFormat != "PNG" ? objectExportQualityInt : image.exportQuality;
450

    
451
                /*
452
                 * set export filename
453
                 */
454
                var filenameLabel = rectangle.extractLabel(image.pageItemLabel);
455
                var basename = (filenameLabel.length > 0 && image.overrideExportFilenames == false) ? getBasename(filenameLabel) : getBasename(link.name);
456
                var newFilename;
457
                // just adjacent images are compared.
458
                var duplicates = hasDuplicates(link, docLinks, i);
459
                if(inArray(basename, uniqueBasenames) && (!duplicates)){
460
                    newFilename = renameFile(basename, localFormat, true);
461
                } else {
462
                    newFilename = renameFile(basename, localFormat, false);
463
                }
464
                uniqueBasenames.push(getBasename(newFilename));
465
                /*
466
                 * construct link object
467
                 */ 
468
                linkObject = {
469
                    link:link,
470
                    pageItem:rectangle,
471
                    format:localFormat,
472
                    quality:localQuality,
473
                    density:normalizedDensity,
474
                    newFilename:newFilename,
475
                    newFilepath:File(image.exportDir + "/" + newFilename),
476
                    objectExportOptions:objectExportOptions,
477
                    originalBounds:originalBounds
478
                }
479
                exportLinks.push(linkObject);
480
                writeLog("=> stored to: " + linkObject.newFilepath, image.exportDir, image.logFilename);
481
            } else {
482
                missingLinks.push(link.name);
483
            }
484
        } else {
485
            writeLog("=> FAILED: image is not placed on a page.", image.exportDir, image.logFilename);
486
        }
487
    }
488
    if (missingLinks.length > 0) {
489
        var result = confirm (missingLinks.length + " " + panel.promptMissingImages + "\n\n" + missingLinks.toString());
490
        if (!result) return;
491
    }
492
    // create directory
493
    createDir(image.exportDir);
494

    
495
    if (exportLinks.length  > 0) {
496
        //var progressBar = getProgressBar(exportLinks.length);
497
        var progressBar = getProgressBar("export " + exportLinks.length + " images");
498
        progressBar.reset(exportLinks.length);
499

    
500
        // iterate over files and store to specific location
501
        for (i = 0; i  < exportLinks.length; i++) {
502
            var exportFormat = exportLinks[i].format == "PNG" ? ExportFormat.PNG_FORMAT : ExportFormat.JPG;
503
            var exportResolution = exportLinks[i].density;
504
            var exportQuality = exportLinks[i].quality;
505
            // JPEG export options
506
            app.jpegExportPreferences.antiAlias = false;
507
            app.jpegExportPreferences.embedColorProfile = false;
508
            app.jpegExportPreferences.exportResolution = exportResolution;
509
            app.jpegExportPreferences.jpegColorSpace = JpegColorSpaceEnum.RGB;
510
            jpegExportQualityValues = [JPEGOptionsQuality.MAXIMUM ,JPEGOptionsQuality.HIGH, JPEGOptionsQuality.MEDIUM, JPEGOptionsQuality.LOW];
511
            app.jpegExportPreferences.jpegQuality = jpegExportQualityValues[exportQuality];
512
            app.jpegExportPreferences.jpegRenderingStyle = JPEGOptionsFormat.BASELINE_ENCODING;
513
            app.jpegExportPreferences.simulateOverprint = true;
514
            app.jpegExportPreferences.useDocumentBleeds = true;
515
            // PNG export options
516
            app.pngExportPreferences.antiAlias = false;
517
            app.pngExportPreferences.exportResolution = exportResolution;
518
            app.pngExportPreferences.pngColorSpace = PNGColorSpaceEnum.RGB;
519
            pngExportQualityValues = [PNGQualityEnum.MAXIMUM, PNGQualityEnum.HIGH, PNGQualityEnum.MEDIUM, PNGQualityEnum.LOW];
520
            app.pngExportPreferences.pngQuality = pngExportQualityValues[exportQuality];
521
            app.pngExportPreferences.transparentBackground = image.pngTransparency;
522
            app.pngExportPreferences.simulateOverprint = true;
523
            app.pngExportPreferences.useDocumentBleeds = true;
524

    
525
            progressBar.hit("export " + exportLinks[i].newFilename, i);
526

    
527
            exportLinks[i].pageItem.exportFile(exportFormat, exportLinks[i].newFilepath);
528
            // insert label with new file link for postprocessing
529
            exportLinks[i].pageItem.insertLabel(image.pageItemLabel, exportLinks[i].newFilename);
530
            // restore original bounds
531
            exportLinks[i].pageItem.geometricBounds = exportLinks[i].originalBounds;
532
        }
533
        progressBar.close();
534

    
535
        /*
536
         * danger zone: relink all images to their respective export paths
537
         */
538
        if(image.relinkToExportPaths == true) {
539
            relinkToExportPaths(doc, exportLinks);
540
        }
541
        alert (exportLinks.length  + " " + panel.finishedMessage);
542
        writeLog("\nFinished! Exported " + exportLinks.length + " of " + docLinks.length + " images.\nPlease check messages above for further details.", image.exportDir, image.logFilename);
543
        doc.save();
544
    }
545
    else {
546
        alert (panel.noValidLinks);
547
    }
548
}
549
// draw simple progress bar
550
function getProgressBar (title){
551
    var progressBarWindow = new Window("palette", title, {x:0, y:0, width:400, height:50});
552
    var pbar = progressBarWindow.add("progressbar", {x:10, y:10, width:380, height:6}, 0, 100);
553
    var stext = progressBarWindow.add("statictext", {x:10, y:26, width:380, height:20}, '');
554
    progressBarWindow.center();
555
    progressBarWindow.reset = function (maxValue) {
556
        stext.text = "export export export export export export export export ";
557
        pbar.value = 0;
558
        pbar.maxvalue = maxValue||0;
559
        pbar.visible = !!maxValue;
560
        this.show();
561
    }
562
    progressBarWindow.hit = function(msg, value) {
563
        ++pbar.value;
564
        stext.text = msg;
565
    }
566
    return progressBarWindow;
567
}
568
// create directory
569
function createDir (folder) {
570
    try {
571
        folder.create();
572
        return;
573
    } catch (e) {
574
        alert (e);
575
    }
576
}
577
// check if image is missing or embedded
578
function isValidLink (link) {
579
    if(link.parent.parent.parentPage == null) {
580
        writeLog('=> FAILED: image is on pasteboard or overset text.', image.exportDir, image.logFilename);
581
        return false;
582
    } else {
583
        switch (link.status) {
584
        case LinkStatus.LINK_MISSING:
585
            writeLog('=> FAILED: image file is missing.', image.exportDir, image.logFilename);
586
            return false; break;
587
        case LinkStatus.LINK_EMBEDDED:
588
            writeLog('=> FAILED: embedded image.', image.exportDir, image.logFilename);
589
            return false; break;
590
        default:
591
            if(link != null) return true else return false;
592
        }
593
    }
594
}
595
// return filename with new extension and conditionally attach random string
596
function renameFile(basename, extension, rename) {
597
    var cleanBasename = cleanString(basename, '_');
598
    if(rename) {
599
        hash = ((1 + Math.random())*0x1000).toString(36).slice(1, 6);
600
        var renameFile = cleanBasename + '-' + hash + '.' + extension.toLowerCase();
601
        return renameFile
602
    } else {
603
        var renameFile = cleanBasename + '.' + extension.toLowerCase();
604
        return renameFile
605
    }
606
}
607
// clean string from illegal characters
608
function cleanString(string, replacement){
609
    var illegalCharsRegex = /[\x00-\x1f\x80-\x9f\s\/\?<>\\:\*\|":]/g;
610
    var replace = string.replace(/[\x00-\x1f\x80-\x9f\s\/\?<>\\:\*\|":]/g, replacement);
611
    return replace;
612
}
613
// get file basename
614
function getBasename(filename) {
615
    var basename = filename.match( /^(.*?)\.[a-z]{2,4}$/i);
616
    if(basename != null){
617
        return basename[1];
618
    }else
619
        // no file extension
620
        return filename;
621
    
622
}
623
// check if string exists in array
624
function inArray(string, array) {
625
    var length = array.length;
626
    for(var i = 0; i < length; i++) {
627
        if(array[i] == string)
628
            return true;
629
    }
630
    return false;
631
}
632
// get density limit according to maximal resolution value
633
function getMaxDensity(density, rectangle, maxResolution) {
634
    var bounds = rectangle.geometricBounds;
635
    var baseMultiplier = 72;
636
    var densityFactor = density / baseMultiplier;
637
    var width =  (bounds[3] - bounds[1]);
638
    var height = (bounds[2] - bounds[0]);
639
    var resolution = width * height * Math.pow(densityFactor, 2);
640
    if(resolution > maxResolution) {
641
        var maxDensity =  Math.floor(Math.sqrt(maxResolution * Math.pow(densityFactor, 2) / resolution) * baseMultiplier);
642
        return maxDensity;
643
    } else {
644
        return density;
645
    }
646
}
647
// crop rectangle to page bleeds
648
function cropRectangleToBleeds (rectangle){
649
    document.viewPreferences.rulerOrigin = RulerOrigin.SPREAD_ORIGIN;
650
    var rect = rectangle;
651
    var bounds = rect.geometricBounds;   // bounds: [y1, x1, y2, x2], e.g. top left / bottom right
652
    var page = rect.parentPage;
653
    // page is null if the object is on the pasteboard
654
    var rulerOrigin = document.viewPreferences.rulerOrigin;
655
    if(page != null){
656
        // iterate over corners and fit them into page
657
        var newBounds = [];
658
        for(var i = 0; i <= 3; i++) {
659
            // y1
660
            if(i == 0 && bounds[i] < page.bounds[i]){
661
                newBounds[i] = page.bounds[i];
662
                // x1
663
            } else if(i == 2 && bounds[i] > page.bounds[i]){
664
                newBounds[i] = page.bounds[i];
665
                // left edge, do not crop images which touch the spine
666
            } else if(i == 1 && bounds[i] < page.bounds[i] && page.side.toString() != "RIGHT_HAND"){
667
                newBounds[i] = page.bounds[i];
668
                // right edge
669
            } else if(i == 3 && bounds[i] > page.bounds[i] && page.side.toString() != "LEFT_HAND"){
670
                newBounds[i] = page.bounds[i];
671
            } else {
672
                newBounds[i] = bounds[i];
673
            }
674
        }
675
        // restore old bounds
676
        rect.geometricBounds = newBounds;
677
    }
678
    document.viewPreferences.rulerOrigin =  rulerOrigin;
679
    return rect;
680
}
681
function getAnchoredObjectOffset (obj){
682
    if(obj.parent.constructor === Character){
683
        anchorYoffset = obj.anchoredObjectSettings.anchorYoffset;
684
        anchorXoffset = obj.anchoredObjectSettings.anchorXoffset;
685
        return [anchorYoffset, anchorXoffset];
686
    } else {
687
        return [0, 0];
688
    }
689
}
690
// get path relative to indesign file location
691
function getDefaultExportPath() {
692
    var exportPath = String(app.activeDocument.fullName);
693
    exportPath = exportPath.substring(0, exportPath.lastIndexOf('/')) + '/' + image.exportDir;
694
    return exportPath
695
}
696
// delete all image file labels 
697
function deleteLabel(docLinks){
698
    for (var i = 0; i < docLinks.length; i++) {
699
        var link = docLinks[i];
700
        var rectangle = link.parent.parent;
701
        rectangle.insertLabel(image.pageItemLabel, '');
702
    }
703
}
704
// simple logging
705
function writeLog(message, dir, filename){
706
    var path = dir + '/' + filename;
707
    createDir(dir);
708
    var write_file = File(path);
709
    if (!write_file.exists) {
710
        write_file = new File(path);
711
    }
712
    write_file.open('a', undefined, undefined);
713
    write_file.encoding = "UTF-8";
714
    write_file.lineFeed = "Unix";
715
    write_file.writeln(message);
716
    write_file.close();
717
}
718
function clearLog(dir, filename){
719
    var path = dir + '/' + filename;
720
    del_file = File(path);
721
    if (del_file.exists) {
722
        del_file.remove();
723
    }
724
}
725
// create array from links object
726
function linksToSortedArray(links){
727
    arr = [];
728
    // put links in an array
729
    for (var i = 0; i < links.length; i++) {
730
        arr.push(links[i]);
731
    }
732
    // sort the array by name
733
    arr.sort(function(a, b){
734
        var x = a.name.toLowerCase();
735
        var y = b.name.toLowerCase();
736
        return x.localeCompare(y);
737
    });
738
    return arr;
739
}
740
function isObjectExportOptionActive(objectExportOptions){
741
    var active = (parseFloat(app.version) < 10) ? objectExportOptions.customImageConversion :
742
        objectExportOptions.preserveAppearanceFromLayout == PreserveAppearanceFromLayoutEnum.PRESERVE_APPEARANCE_RASTERIZE_CONTENT ||
743
        objectExportOptions.preserveAppearanceFromLayout == PreserveAppearanceFromLayoutEnum.PRESERVE_APPEARANCE_RASTERIZE_CONTAINER;
744
    return active;
745
}
746
function isFormatOverrideActive(searchFormat){
747
    var result = false;
748
    var links = app.documents[0].links;
749
    for(var i = 0; i < links.length; i++){
750
        var rectangle = links[i].parent.parent;
751
        var objectExportOptions = rectangle.objectExportOptions;
752
        if(isObjectExportOptionActive(objectExportOptions)){
753
            var localFormat = objectExportOptions.imageConversionType.toString();
754
            if(localFormat == searchFormat){
755
                result = true;
756
            }
757
        }
758
    }
759
    return result;
760
}
761

    
762

    
763
// compare two links if they have equal dimensions
764
function hasDuplicates(link, docLinks, index) {
765
    var rectangle = link.parent.parent;
766
    var nextLink;
767
    var result = [];
768
    var i = 0;
769
    do{
770
        nextLink = docLinks[i];
771
        // check whether link names match. The index var is used to prevent that an image is compared with itself.
772
        if(link.name == nextLink.name && i != index) {
773
            var nextRectangle = nextLink.parent.parent;
774
            // InDesign calculates widths and heights not precisely, so we have to round them
775
            var rectangleWidth = Math.round((rectangle.geometricBounds[3] - rectangle.geometricBounds[1]) * 100) / 100;
776
            var rectangleHeight = Math.round((rectangle.geometricBounds[2] - rectangle.geometricBounds[0]) * 100) / 100;
777
            var nextRectangleWidth = Math.round((nextRectangle.geometricBounds[3] - nextRectangle.geometricBounds[1]) * 100) / 100;
778
            var nextRectangleHeight = Math.round((nextRectangle.geometricBounds[2] - nextRectangle.geometricBounds[0]) * 100) / 100;        
779
            var equalWidth  = rectangleWidth == nextRectangleWidth;
780
            var equalHeight = rectangleHeight == nextRectangleHeight;
781
            var equalFlip = rectangle.absoluteFlip == nextRectangle.absoluteFlip;
782
            var equalRotationAngle = rectangle.absoluteRotationAngle == nextRectangle.absoluteRotationAngle;
783
            var equalShearAngle = rectangle.absoluteShearAngle == nextRectangle.absoluteShearAngle;
784
            var equalHorizontalScale = rectangle.absoluteHorizontalScale == nextRectangle.absoluteHorizontalScale;
785
            var equalVerticalScale = rectangle.absoluteVerticalScale == nextRectangle.absoluteVerticalScale;
786
            // note: either objectExportOptions are not active, then we safely ignore them or we
787
            // check if they are active for the two images
788
            var objectExportOptionsActive = !image.objectExportOptions || isObjectExportOptionActive(rectangle.objectExportOptions) == isObjectExportOptionActive(nextRectangle.objectExportOptions);
789
            result.push(equalFlip && equalRotationAngle && equalWidth && equalHeight && equalShearAngle && equalHorizontalScale && equalVerticalScale && objectExportOptionsActive);
790
        }
791
        i++;
792
    }
793
    while(i < docLinks.length);
794
    if(inArray(true, result)){
795
        writeLog("Found duplicate: " + link.name, image.exportDir, image.logFilename);
796
    }
797
    return inArray(true, result);
798
}
799
function relinkToExportPaths (doc, exportLinks) {
800
    for(var i = 0; i < exportLinks.length; i++) {
801
        var linkId = exportLinks[i].link.id;
802
        var exportPath = exportLinks[i].newFilepath;
803
        var link = doc.links.itemByID(linkId);
804
        var rectangle = link.parent.parent;        
805
        // relink to export path
806
        link.relink(exportPath);
807
        // fit content to frame, necessary because export crops, flips, etc
808
        rectangle.fit(FitOptions.CONTENT_TO_FRAME);
809
    }
810
}
(2-2/2)