﻿#targetengine "session"
/*
 * image-export.jsx
 *
 *
 * Export images from an InDesign document to web-friendly formats.
 *
 *
 * Note: this script requires at least InDesign Version 8.0 (CS6). For 
 * versions prior to 8.0, please try image-export_pre-cs6.jsx which 
 * comes with a limited feature set.
 *
 * Authors: Gregor Fellenz (twitter: @grefel), Martin Kraetke (@mkraetke)
 *
 */
jsExtensions();
var version = "v1.7.0";
var doc = app.documents[0];
/*
 * set language
 */
var lang = {
  pre: app.locale == 1279477613 ? 1 : 0 // en = 0, de = 1
}
/*
 * image options object
 */
image = {
  minExportDPI:1,
  maxExportDPI:2400,
  exportDPI:parseInt(getConfigValue("letex:exportDPI", "144")[0], 10),
  baseDPI:72,
  maxResolution:parseInt(getConfigValue("letex:maxResolution", "4000000")[0], 10),
  pngTransparency:getConfigValue("letex:pngTransparency", "true")[0] == "true",
  objectExportOptions:getConfigValue("letex:objectExportOptions", "true")[0] == "true",
  objectExportDensityFactorValues:[1, 2, 3, 4],
  objectExportDensityFactor:parseInt(getConfigValue("letex:objectExportDensityFactor", "1")[0], 10) - 1,
  overrideExportFilenames:getConfigValue("letex:overrideExportFilenames", "false")[0] == "true",
  exportFilenameSchema:getConfigValue("letex:exportFilenameSchema", "img_###")[0],
  exportFilenameGroupSuffix:getConfigValue("letex:exportFilenameGroupSuffix", "_group")[0],
  renameExportFilenames:getConfigValue("letex:renameExportFilenames", "false")[0] == "true",
  exportFromHiddenLayers:getConfigValue("letex:exportFromHiddenLayers", "false")[0] == "true",
  exportFromMasterPages:getConfigValue("letex:exportFromMasterPages", "false")[0] == "true",
  exportFromPasteboard:getConfigValue("letex:exportFromPasteboard", "false")[0] == "true",
  relinkToExportPaths:getConfigValue("letex:relinkToExportPaths", "false")[0] == "true",
  exportGroupsAsSingleImage:getConfigValue("letex:exportGroupsAsSingleImage", "false")[0] == "true",
  cropImageToPage:getConfigValue("letex:cropImageToPage", "true")[0] == "true",
  removeRectangleStroke:getConfigValue("letex:removeRectangleStroke", "false")[0] == "true",
  insertAltTextsfromXMP:getConfigValue("letex:insertAltTextsfromXMP", "false")[0] == "true",
  exportDir:"export",
  exportQuality:parseInt(getConfigValue("letex:exportQuality", "2")[0], 10),
  exportFormat:["JPG", "PNG"].indexOf(getConfigValue("letex:exportFormat", "JPG")[0]), // 0 = JPG | 1 = PNG
  pageItemLabel:"letex:fileName",
  pageItemAltText:"letex:altText",
  XMPAltText:"letex:altTextFromXMP",
  logFilename:"export.log"
}
function getConfigValue(label, defaultValue) {
  var value = [doc.extractLabel(label), defaultValue].filter( function(text) {
    return text != ""
  })
  return value;
}
/*
 * image options object
 */
imageInfo = {
  filename:null,
  format:null,
  width:null,
  height:null,
  anchorPos:null,
}
/*
 * set panel preferences
 */
panel = {
  title:["le-tex – Export Images", "le-tex – Bilder exportieren"][lang.pre],
  tabGeneralTitle:["General", "Allgemein"][lang.pre],
  tabAdvancedTitle:["Advanced", "Erweitert"][lang.pre],
  tabInfoTitle:["Info", "Informationen"][lang.pre],
  densityTitle:["Density (ppi)", "Auflösung (ppi)"][lang.pre],
  qualityTitle:["Quality", "Qualität"][lang.pre],
  qualityValues:[["max", "high", "medium", "low"], ["Maximum", "Hoch", "Mittel", "Niedrig"]][lang.pre],
  formatTitle:"Format",
  formatValues:["JPG", "PNG"],
  formatDescriptionPNG:["for line art and text", "Für Strichzeichnungen und Text"][lang.pre],
  formatDescriptionJPEG:["for photographs and gradients", "für Fotos und Verläufe"][lang.pre],
  objectExportOptionsTitle:["Object export options", "Objektexportoptionen"][lang.pre],
  objectExportDensityFactorTitle:["Density Multiplier", "Multiplikator Auflösung"][lang.pre],
  overrideExportFilenamesTitle:["Override embedded export filenames", "Eingebettete Export-Dateinamen überschreiben"][lang.pre],
  renameExportFilenamesTitle:["Rename Export Filenames (# for numbering)", "Exportierte Dateien umbenennen (# für Nummer)"][lang.pre],
  exportFilenameSchemaTitle:["Filename", "Dateiname"][lang.pre],
  pngTransparencyTitle:["PNG Transparency", "PNG Transparenz"][lang.pre],
  exportFromHiddenLayersTitle:["Export images from hidden layers", "Bilder von versteckten Ebenen exportieren"][lang.pre],
  exportFromMasterPagesTitle:["Export images from master pages", "Bilder von Musterseiten exportieren"][lang.pre],
  exportFromPasteboardTitle:["Export images from pasteboard", "Bilder von Montagefläche exportieren"][lang.pre],
  relinkToExportPathsTitle:["Relink to export path", "Verknüpfung zu Exportpfad ändern"][lang.pre],
  exportGroupsAsSingleImageTitle:["Export grouped images as single image", "Gruppierte Bilder als einzelnes Bild exportieren"][lang.pre],
  insertAltTextsfromXMPTitle:["Insert alt texts from image metadata", "Alternativtexte aus Bildmetadaten einfügen"][lang.pre],
  relinkToExportPathsWarning:["Warning! Each link will be replaced with its export path.", "Warnung! Jede Verknüpfung wird durch ihren Exportpfad ersetzt."][lang.pre],
  removeRectangleStrokeTitle:["Remove Stroke", "Rahmen entfernen"][lang.pre],
  maxResolutionTitle:["Max Resolution (px)", "Maximale Auflösung (px)"][lang.pre],
  selectDirButtonTitle:["Choose", "Auswählen"][lang.pre],
  selectDirMenuTitle:["Choose a directory", "Verzeichnis auswählen"][lang.pre],
  panelFilenameOptionsTitle:["Filenames", "Dateinamen"][lang.pre],
  miscellaneousOptionsTitle:["Miscellaneous Options", "Sonstige Optionen"][lang.pre],
  infoCharacters:[40],
  infoFilename:["Filename", "Dateiname"][lang.pre],
  infoWidth:["width", "Breite"][lang.pre],
  infoHeight:["height", "Höhe"][lang.pre],
  cropImageToPageTitle:["Crop image to page margins", "Bild auf Seite beschneiden"][lang.pre],
  infoAnchorPosTitle:["Anchor Position", "Verankerungsposition"][lang.pre],
  infoAnchorPos:[["above line", "anchored", "inline position"], ["Über Zeile", "Benutzerdefiniert", "Eingebunden"]][lang.pre],
  infoNoImage:["No image selected.", "Kein Bild ausgewählt."][lang.pre],
  progressBarTitle:["export Images", "Bilder exportieren"][lang.pre],
  noValidLinks:["No valid links found.", "Keine Bild-Verknüpfungen gefunden"][lang.pre],
  finishedMessage:["images exported.", "Bilder exportiert."][lang.pre],
  buttonOK:"OK",
  buttonCancel:["Cancel", "Abbrechen"][lang.pre],
  buttonSaveSettings:["Save Config", "Konfiguration speichern"][lang.pre],
  errorMissingImage:["Warning! Image cannot be found: ", "Warnung! Bild konnte nicht gefunden werden: "][lang.pre],
  errorEmbeddedImage:["Warning! Embedded Image cannot be exported: ", "Warnung! Eingebettetes Bild kann nicht exportiert werden: "][lang.pre],
  promptMissingImages:["images cannot be exported. Proceed?","Bilder können nicht exportiert werden. Fortfahren?"][lang.pre],
  lockedLayerWarning:["All layers with images must be unlocked.","Alle Ebenen mit Bildern müssen entsperrt sein."][lang.pre]
}
/*
 * start
 */
main();
/*
 * main pipeline
 */
function main(){
  var userLevel = app.scriptPreferences.userInteractionLevel;
  app.scriptPreferences.userInteractionLevel = UserInteractionLevels.INTERACT_WITH_ALL;    
  // open file dialog and load a document
  if (app.layoutWindows.length == 0) {
    var file = File.openDialog ("Select a file", "InDesign:*.indd;*.indb;*.idml, InDesign Document:*.indd, InDesign Book:*.indb, InDesign Markup:*.idml", true)
    try {
      app.open(File(file));
    } catch (e) {
      alert(e);
      return;
    };
  }
  var doc = app.documents[0];
  // check if document is saved
  if ((!doc.saved || doc.modified)) {
    if ( confirm ("The document needs to be saved.", undefined, "Document not saved.")) {
      try {
        doc = doc.save();
      } catch (e) {
        alert ("The document couldn't be saved.\n" + e);
        return;
      }
    } else {
      return;
    }
  }
  // show window
  try {
    exportImages(doc);
  } catch (e) {
    alert ("Error:\n" + e);
  }
  app.scriptPreferences.userInteractionLevel = userLevel;
}
/*
 * add JavaScript extensions
 */
function jsExtensions(){
  // indexOf
  if (!Array.prototype.indexOf) {
    Array.prototype.indexOf = function(elt /*, from*/)
    {
      var len = this.length;

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

      if (from < 0)
        from += len;

      for (; from < len; from++) {
        if (from in this &&
            this[from] === elt)
          return from;
      }
      return -1;
    };
  }
  // find
  if (!Array.prototype.find) {
    Array.prototype.find = function(predicate) {
      'use strict';
      if (this == null) {
        throw new TypeError('Array.prototype.find called on null or undefined');
      }
      if (typeof predicate !== 'function') {
        throw new TypeError('predicate must be a function');
      }
      var list = Object(this);
      var length = list.length >>> 0;
      var thisArg = arguments[1];
      var value;

      for (var i = 0; i < length; i++) {
        value = list[i];
        if (predicate.call(thisArg, value, i, list)) {
          return value;
        }
      }
      return undefined;
    };
  }
  // filter
  if (!Array.prototype.filter) {
    Array.prototype.filter = function(func, thisArg) {
      'use strict';
      if ( ! ((typeof func === 'Function' || typeof func === 'function') && this) )
        throw new TypeError();
      
      var len = this.length >>> 0,
          res = new Array(len), // preallocate array
          t = this, c = 0, i = -1;
      if (thisArg === undefined) {
        while (++i !== len){
          // checks to see if the key was set
          if (i in this){
            if (func(t[i], i, t)){
              res[c++] = t[i];
            }
          }
        }
      }
      else{
        while (++i !== len){
          // checks to see if the key was set
          if (i in this){
            if (func.call(thisArg, t[i], i, t)){
              res[c++] = t[i];
            }
          }
        }
      }
      
      res.length = c; // shrink down array to proper size
      return res;
    }
  }
}
function exportImages(doc){
  try{
    drawWindow() == 1
  } catch(e){
    alert(e);
  }
}
/*
 * User Interface
 */
function drawWindow() {
  //var myWindow = new Window("dialog", panel.title, undefined, {resizable:true});
  var myWindow = new Window("palette", panel.title + " " + version, undefined);
  myWindow.orientation = "column";
  myWindow.alignChildren ="fill";
  var tpanel = myWindow.add("tabbedpanel");
  tpanel.alignChildren = "fill";
  /*
   * General Tab
   */
  var tabGeneral = tpanel.add ("tab", undefined, panel.tabGeneralTitle);
  tabGeneral.alignChildren = "fill";
  var panelSelectDir = tabGeneral.add("panel", undefined, panel.selectDirMenuTitle);
  panelSelectDir.alignChildren = "left";
  var panelSelectDirInputPath = panelSelectDir.add("edittext");
  panelSelectDirInputPath.preferredSize.width = 255;
  panelSelectDirInputPath.text = getDefaultExportPath();
  var panelSelectDirButton = panelSelectDir.add("button", undefined, panel.selectDirButtonTitle);  
  var panelQuality = tabGeneral.add("panel", undefined, panel.qualityTitle);
  panelQuality.orientation = "column";
  panelQuality.alignChildren = "left";
  var panelQualityRadiobuttons = panelQuality.add("group");
  panelQualityRadiobuttons.orientation = "row";
  for (i = 0; i < panel.qualityValues.length; i++) {
    panelQualityRadiobuttons.add ("radiobutton", undefined, (panel.qualityValues[i]));
  }
  panelQualityRadiobuttons.children[image.exportQuality].value =true;
  var densityGroup = panelQuality.add("group");  
  var densityInputDPI = densityGroup.add ("edittext", undefined, image.exportDPI);
  densityInputDPI.characters = 4;
  var densitySliderDPI = densityGroup.add("slider", undefined, image.exportDPI, image.minExportDPI, image.maxExportDPI);
  densityGroup.add ("statictext", undefined, panel.densityTitle);
  // synchronize slider and input field
  var outDPI
  densitySliderDPI.onChanging = function () { densityInputDPI.text   = densitySliderDPI.value; };
  densityInputDPI.onChanging  = function () { densitySliderDPI.value = densityInputDPI.text };
  var panelMaxResolutionGroup = panelQuality.add("group");
  panelMaxResolutionGroup.add("statictext", undefined, panel.maxResolutionTitle);  
  var inputMaxRes = panelMaxResolutionGroup.add("edittext", undefined, image.maxResolution);
  var panelFormat = tabGeneral.add("panel", undefined, panel.formatTitle);
  var panelFormatGroup = panelFormat.add("group");
  panelFormat.alignChildren = "left";
  var formatDropdown = panelFormatGroup.add("dropdownlist", undefined, panel.formatValues);
  formatDropdown.selection = image.exportFormat;
  var formatDropdownDescription = panelFormatGroup.add("statictext", undefined, undefined);
  formatDropdownDescription.text = formatDropdown.selection.text == "PNG" ? panel.formatDescriptionPNG : panel.formatDescriptionJPEG;
  /*
   * Advanced Tab
   */
  var tabAdvanced = tpanel.add ("tab", undefined, panel.tabAdvancedTitle);
  tabAdvanced.alignChildren = "fill";
  var panelObjectExportOptions = tabAdvanced.add("panel", undefined, panel.objectExportOptionsTitle);
  panelObjectExportOptions.alignChildren = "left";
  var objectExportOptions = panelObjectExportOptions.add("group");
  var objectExportOptionsCheckbox = objectExportOptions.add("checkbox", undefined, panel.objectExportOptionsTitle);
  objectExportOptionsCheckbox.value = image.objectExportOptions;
  var objectExportOptionsDensity = panelObjectExportOptions.add("group");
  var objectExportOptionsDensityDropdown = objectExportOptionsDensity.add("dropdownlist", undefined, image.objectExportDensityFactorValues);
  objectExportOptionsDensityDropdown.selection = image.objectExportDensityFactor;
  var panelFilenameOptions = tabAdvanced.add("panel", undefined, panel.panelFilenameOptionsTitle);
  panelFilenameOptions.alignChildren = "left";
  var overrideExportFilenames = panelFilenameOptions.add("group");
  var overrideExportFilenamesCheckbox = overrideExportFilenames.add("checkbox", undefined, panel.overrideExportFilenamesTitle);
  overrideExportFilenamesCheckbox.value = image.overrideExportFilenames;
  var renameExportFilenamesCheckbox = panelFilenameOptions.add("checkbox", undefined, panel.renameExportFilenamesTitle);
  renameExportFilenamesCheckbox.value = image.renameExportFilenames;
  var exportFilenameSchemaInput = panelFilenameOptions.add("edittext");
  exportFilenameSchemaInput.enabled = image.renameExportFilenames;
  exportFilenameSchemaInput.preferredSize.width = 255;
  exportFilenameSchemaInput.text = image.exportFilenameSchema;
  var exportFilenameGroupSuffixInput = panelFilenameOptions.add("edittext");
  panelFilenameOptions.alignment = "left";
  exportFilenameGroupSuffixInput.enabled = image.renameExportFilenames;
  exportFilenameGroupSuffixInput.preferredSize.width = 255;
  exportFilenameGroupSuffixInput.text = image.exportFilenameGroupSuffix;
  renameExportFilenamesCheckbox.onClick = function(){
    exportFilenameSchemaInputActive = renameExportFilenamesCheckbox.value == true;
    exportFilenameSchemaInput.enabled = overrideExportFilenamesCheckbox.value = exportFilenameSchemaInputActive;
    exportFilenameGroupSuffixInput.enabled = overrideExportFilenamesCheckbox.value = exportFilenameSchemaInputActive;
  };
  var panelMiscellaneousOptions = tabAdvanced.add("panel", undefined, panel.miscellaneousOptionsTitle)
  panelMiscellaneousOptions.alignChildren = "left";
  var pngTransparencyGroupCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.pngTransparencyTitle);
  pngTransparencyGroupCheckbox.value = image.pngTransparency;
  var pngFormatActive = formatDropdown.selection.text == "PNG" || isFormatOverrideActive("PNG");
  pngTransparencyGroupCheckbox.enabled = pngFormatActive;
  // disable checkbox if selected format is not png
  formatDropdown.onChange = function(){
    pngFormatActive = formatDropdown.selection.text == "PNG" || isFormatOverrideActive("PNG");
    pngTransparencyGroupCheckbox.enabled = pngFormatActive;
    formatDropdownDescription.text = pngFormatActive ? panel.formatDescriptionPNG : panel.formatDescriptionJPEG;
  };
  var cropImageToPageCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.cropImageToPageTitle);
  cropImageToPageCheckbox.value = image.cropImageToPage;
  var removeRectangleStrokeCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.removeRectangleStrokeTitle);
  removeRectangleStrokeCheckbox.value = image.removeRectangleStroke;
  var exportFromHiddenLayersCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.exportFromHiddenLayersTitle);
  exportFromHiddenLayersCheckbox.value = image.exportFromHiddenLayers;
  var exportFromMasterPagesCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.exportFromMasterPagesTitle);
  exportFromMasterPagesCheckbox.value = image.exportFromMasterPages;
  var exportFromPasteboardCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.exportFromPasteboardTitle);
  exportFromPasteboardCheckbox.value = image.exportFromPasteboard;
  var relinkToExportPathsCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.relinkToExportPathsTitle);
  relinkToExportPathsCheckbox.value = image.relinkToExportPaths;
  relinkToExportPathsCheckbox.onClick = function(){
    if(relinkToExportPathsCheckbox.value == true) {
      alert(panel.relinkToExportPathsWarning)
    }
  }
  var exportGroupsAsSingleImageCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.exportGroupsAsSingleImageTitle);
  exportGroupsAsSingleImageCheckbox.value = image.exportGroupsAsSingleImage;
  var insertAltTextsfromXMPCheckbox = panelMiscellaneousOptions.add("checkbox", undefined, panel.insertAltTextsfromXMPTitle);
  insertAltTextsfromXMPCheckbox.value = image.insertAltTextsfromXMP;
  /*
   * Info Tab
   */
  var tabInfo = tpanel.add ("tab", undefined, panel.tabInfoTitle);
  tabInfo.alignChildren = "left";
  tabInfo.iFilename = tabInfo.add("statictext", undefined, panel.infoNoImage + "\n\n", {multiline:true});
  tabInfo.iPosX = tabInfo.add("statictext", undefined, "");
  tabInfo.iPosY = tabInfo.add("statictext", undefined, "");
  tabInfo.iWidth = tabInfo.add("statictext", undefined, "");
  tabInfo.iHeight = tabInfo.add("statictext", undefined, "");
  tabInfo.iPosX.characters = tabInfo.iPosY.characters = tabInfo.iWidth.characters = tabInfo.iHeight.characters = tabInfo.iFilename.characters = panel.infoCharacters;
  // listen to each selection change and update info tab
  var afterSelectChanged = app.addEventListener(Event.AFTER_SELECTION_CHANGED, function(){
    if(app.selection[0] != undefined
       && (["Rectangle", "Group"].indexOf(app.selection[0].constructor.name) >= 0)){
      var rectangle = app.selection[0];
      outDensity = getMaxDensity(densitySliderDPI.value, rectangle, image.maxResolution, image.baseDPI)
      width = Math.round((rectangle.geometricBounds[3] - rectangle.geometricBounds[1])  * 100 / 100 * outDensity / image.baseDPI);
      height = Math.round((rectangle.geometricBounds[2] - rectangle.geometricBounds[0]) * 100 / 100 * outDensity / image.baseDPI);
      posX = Math.round(rectangle.geometricBounds[1] * 100) / 100;
      posY = Math.round(rectangle.geometricBounds[0] * 100) / 100;
      var fileNameString = splitStringToArray(rectangle.extractLabel(image.pageItemLabel), panel.infoCharacters).join("\n");
      tabInfo.iFilename.text = panel.infoFilename + ":\n" + fileNameString;
      tabInfo.iPosX.text = "x: " + posX + "px";
      tabInfo.iPosY.text = "y: " + posY + "px";
      tabInfo.iWidth.text = panel.infoWidth + ": " + width + "px";
      tabInfo.iHeight.text = panel.infoHeight + ": " + height + "px";
    } else {
      tabInfo.iFilename.text = panel.infoNoImage;
      tabInfo.iPosX.text = "";
      tabInfo.iPosY.text = "";
      tabInfo.iHeight.text = "";
      tabInfo.iWidth.text = "";
    }
  }, false);
  // buttons OK/Cancel
  var panelButtonGroup = myWindow.add("group");
  panelButtonGroup.orientation = "row";
  var buttonOK = panelButtonGroup.add("button", undefined, panel.buttonOK, {name: "ok"});
  var buttonCancel = panelButtonGroup.add("button", undefined, panel.buttonCancel, {name: "cancel"} );
  var buttonSaveSettings = panelButtonGroup.add("button", undefined, panel.buttonSaveSettings );
  // change text to selected file path
  panelSelectDirButton.onClick  = function() {
    var result = Folder.selectDialog ();
    if (result) {
      panelSelectDirInputPath.text = result;
    }
  }
  buttonOK.onClick = function (){
    //overwrite values with form input
    image.exportDir = Folder(panelSelectDirInputPath.text);
    image.exportDPI = Number(densityInputDPI.text);
    image.exportQuality = selectedRadiobutton(panelQualityRadiobuttons);
    image.exportFormat = formatDropdown.selection.text;
    image.maxResolution = Number(inputMaxRes.text);
    image.objectExportOptions = objectExportOptionsCheckbox.value;
    image.objectExportDensityFactor = objectExportOptionsDensityDropdown.selection.text;
    image.overrideExportFilenames = overrideExportFilenamesCheckbox.value;
    image.renameExportFilenames = renameExportFilenamesCheckbox.value;
    image.exportFilenameSchema = exportFilenameSchemaInput.text
    image.exportFilenameGroupSuffix = exportFilenameGroupSuffixInput.text;
    image.pngTransparency = pngTransparencyGroupCheckbox.value;
    image.cropImageToPage = cropImageToPageCheckbox.value;
    image.exportFromHiddenLayers = exportFromHiddenLayersCheckbox.value;
    image.exportFromMasterPages = exportFromMasterPagesCheckbox.value;
    image.exportFromPasteboard = exportFromPasteboardCheckbox.value;
    image.relinkToExportPaths = relinkToExportPathsCheckbox.value;
    image.exportGroupsAsSingleImage = exportGroupsAsSingleImageCheckbox.value;
    image.removeRectangleStroke = removeRectangleStrokeCheckbox.value;
    image.insertAltTextsfromXMP = insertAltTextsfromXMPCheckbox.value;
    myWindow.close(1);
    afterSelectChanged.remove();
    getFilelinks(app.documents[0]);
  }
  buttonCancel.onClick = function() {
    afterSelectChanged.remove();
    myWindow.close();
  }
  buttonSaveSettings.onClick = function() {
    var doc = app.documents[0];
    doc.insertLabel("letex:exportDPI", densityInputDPI.text);
    doc.insertLabel("letex:exportQuality", String(selectedRadiobutton(panelQualityRadiobuttons)));
    doc.insertLabel("letex:exportFormat", formatDropdown.selection.text);
    doc.insertLabel("letex:maxResolution", inputMaxRes.text);
    doc.insertLabel("letex:objectExportOptions", String(objectExportOptionsCheckbox.value));
    doc.insertLabel("letex:objectExportDensityFactor", objectExportOptionsDensityDropdown.selection.text);
    doc.insertLabel("letex:overrideExportFilenames", String(overrideExportFilenamesCheckbox.value));
    doc.insertLabel("letex:renameExportFilenames", String(renameExportFilenamesCheckbox.value));
    doc.insertLabel("letex:exportFilenameSchema", String(exportFilenameSchemaInput.text));
    doc.insertLabel("letex:exportFilenameGroupSuffix", String(exportFilenameGroupSuffixInput.text));
    doc.insertLabel("letex:pngTransparency", String(pngTransparencyGroupCheckbox.value));
    doc.insertLabel("letex:cropImageToPage", String(cropImageToPageCheckbox.value));
    doc.insertLabel("letex:exportFromHiddenLayers", String(exportFromHiddenLayersCheckbox.value));
    doc.insertLabel("letex:exportFromMasterPages", String(exportFromMasterPagesCheckbox.value));
    doc.insertLabel("letex:exportFromPasteboard", String(exportFromPasteboardCheckbox.value));
    doc.insertLabel("letex:relinkToExportPaths", String(relinkToExportPathsCheckbox.value));
    doc.insertLabel("letex:exportGroupsAsSingleImage", String(exportGroupsAsSingleImageCheckbox.value));
    doc.insertLabel("letex:removeRectangleStroke", String(removeRectangleStrokeCheckbox.value));
    doc.insertLabel("letex:insertAltTextsfromXMP", String(insertAltTextsfromXMPCheckbox.value));
    afterSelectChanged.remove();
    myWindow.close();
  }
  return myWindow.show();
}
function selectedRadiobutton (rbuttons){
  for(var i = 0; i < rbuttons.children.length; i++) {
    if(rbuttons.children[i].value == true) {
      return i;
    }
  }
}
function getFilelinks(doc) {
  doc.insertLabel('letex:image-export-script-version', version);
  var rulerOrigin = doc.viewPreferences.rulerOrigin;
  var measurementUnit = app.scriptPreferences.measurementUnit;
  var docLinks = linksToSortedArray(doc.links);
  var uniqueBasenames = [];
  var exportLinks = [];
  var missingLinks = [];
  var imageGroupIds = [];
  var imageGroupIterator = 0;
  app.scriptPreferences.measurementUnit = MeasurementUnits.PIXELS;
  doc.viewPreferences.rulerOrigin = RulerOrigin.SPREAD_ORIGIN;
  // delete filename labels, if option is set
  if(image.overrideExportFilenames == true || image.exportGroupsAsSingleImage == true){
    deleteLabel(doc, image.overrideExportFilenames, image.exportGroupsAsSingleImage);
  }
  // clear log
  clearLog(image.exportDir, image.logFilename);
  var currentDate = new Date();
  var dateTime = currentDate.getDate() + '/'
      + (currentDate.getMonth()+1)  + '/' 
      + currentDate.getFullYear() + ' '  
      + currentDate.getHours() + ':'
      + currentDate.getMinutes() + ':' 
      + currentDate.getSeconds();
  writeLog("le-tex image-export " + version + "\nstarted at " + dateTime + "\n", image.exportDir, image.logFilename);
  // iterate over file links
  var counter = 0;
  for (var i = 0; i < docLinks.length; i++) {
    var link = docLinks[i];
    var metadata = (link.linkXmp.isValid && link.linkXmp !== undefined) ? link.linkXmp.properties : null;
    var extension = link.name.split(".").pop().toLowerCase();
    counter = counter + 1;
    writeLog("(" + i + ") ------------------------------------------------------------------------------------------\n"
             + link.name
             + "\n"
             + link.filePath, image.exportDir, image.logFilename);
    if(isValidLink(link)){
      var rectangle = link.parent.parent;
      var linkname = link.name;
      var altText = "";
      var altTextFromXMP = "";
      var toBeExported = true;
      // probably ID bug, ID crashes while accessing link.linkXmp.description
      if(image.insertAltTextsfromXMP
         && metadata.hasOwnProperty("description")
         && extension !== "wmf"
         && rectangle.objectExportOptions.customAltText.length == 0){
         altTextFromXMP = metadata.description;
      } 
      if (rectangle.objectExportOptions.altText().length > 0){
        altText = rectangle.objectExportOptions.customAltText;
      }
      // if a group should be exported as single image, replace rectangle with group object
      if(rectangle.parent.constructor.name == "Group" && image.exportGroupsAsSingleImage){
        rectangle = getTopmostGroup(rectangle);
        linkname = getLinkNameForGroup(rectangle);
        if(linkname != link.name) {
          counter = counter - 1;
          toBeExported = false;
        }
      }
      if(toBeExported){
        rectangle = disableLocks(rectangle);
        var parentPage = (rectangle.parentPage != null) ? rectangle.parentPage : "null";
        // restore the frame of anchored objects which overlaps the page
        // after running cropRectangleToPage()
        var originalBounds = rectangle.geometricBounds;
        var rotationAngle = rectangle.rotationAngle;
        var shearAngle = rectangle.absoluteShearAngle;
        var anchored = (rectangle.parent.constructor.name == "Group") ? rectangle.parent.parent.constructor.name == "Character" : rectangle.parent.constructor.name == "Character";
        var anchoredObjectSettings = (rectangle.parent.constructor.name == "Group" && anchored) ? rectangle.parent.anchoredObjectSettings : rectangle.anchoredObjectSettings;
        var anchorXoffset = (anchored) ? anchoredObjectSettings.anchorXoffset : null;
        var anchorYoffset = (anchored) ? anchoredObjectSettings.anchorYoffset : null;
        var textWrapMode = rectangle.textWrapPreferences.textWrapMode;
        var rectangleBounds = rectangle.geometricBounds;
        var exportFromHiddenLayers = rectangle.itemLayer.visible ? true : image.exportFromHiddenLayers;
        // if an image goes over the page spread, we can't use parentPage because InDesign assumes parentPage is where the larger part of the image is 
        var leftPage;
        // we need to get a left page and rule out documents without double-page spreads
        if(doc.pages[0].side == PageSideOptions.LEFT_HAND){
          leftPage = doc.pages[0];
        } else if(doc.pages.length > 1 && doc.pages[1].side == PageSideOptions.LEFT_HAND){
          leftPage = doc.pages[1];
        } else {
          leftPage = null;
        }
        var originOnLeftPage = (leftPage != null) ? rectangleBounds[1] < leftPage.bounds[3] : null;
        var width = rectangleBounds[3] - rectangleBounds[1];
        var height = rectangleBounds[2] - rectangleBounds[0];
        // offsets y1, x1, y2, x2 (top, left, bottom, right)
        var exceedsPage;
        writeLog(  "page: " + parentPage.name
                + "\nshear angle: "    + shearAngle
                + "\nrotation angle: " + rotationAngle
                + "\ndimensions page / rectangle"
                + "\n width x height: " + width + " x " + height
                + "\n  y1: " + rectangleBounds[0]
                + "\n  x1: " + rectangleBounds[1]
                + "\n  y2: " + rectangleBounds[2]
                + "\n  x2: " + rectangleBounds[3]
                + "\nanchor offsets: " + "x: " + anchorXoffset + ", y: " + anchorYoffset
                + "\nleft page: " + originOnLeftPage
                + "\ntext wrap mode: " + textWrapMode,
                image.exportDir, image.logFilename);
        if(rectangle.hasOwnProperty("frameFittingOptions")){
          writeLog(  "\ncrop top: " + rectangle.frameFittingOptions.topCrop
                  + "\ncrop left: " + rectangle.frameFittingOptions.leftCrop
                  + "\ncrop bottom: " + rectangle.frameFittingOptions.bottomCrop
                  + "\ncrop right: " + rectangle.frameFittingOptions.rightCrop, image.exportDir, image.logFilename);
        }    
        if(image.exportFromPasteboard == true && rectangle.parentPage == null){
          exceedsPage = false;
        } else {
          if(originOnLeftPage == true && leftPage != null) {
            exceedsPage = rectangleBounds[0] < 0
                      || rectangleBounds[1] < 0
                      || rectangleBounds[2] > parentPage.bounds[2]
                      || rectangleBounds[3] > (leftPage.bounds[3] * 2);
            writeLog("exceeds page:"
                    + "\n  top:     " + (rectangleBounds[0] < parentPage.bounds[0])
                    + "\n  left:    " + (rectangleBounds[1] < leftPage.bounds[1])
                    + "\n  bottom:  " + (rectangleBounds[2] > parentPage.bounds[2])
                    + "\n  right:   " + (rectangleBounds[3] > (leftPage.bounds[3] * 2)),
                    image.exportDir, image.logFilename);
          } else {
            exceedsPage = rectangleBounds[0] < 0
                      || rectangleBounds[1] < parentPage.bounds[1]
                      || rectangleBounds[2] > parentPage.bounds[2]
                      || rectangleBounds[3] > parentPage.bounds[3];
          
            writeLog("exceeds page:"
                    + "\n  top:     " + (rectangleBounds[0] < parentPage.bounds[0])
                    + "\n  left:    " + (rectangleBounds[1] < parentPage.bounds[1])
                    + "\n  bottom:  " + (rectangleBounds[2] > parentPage.bounds[2])
                    + "\n  right:   " + (rectangleBounds[3] > parentPage.bounds[3]),                   
                    image.exportDir, image.logFilename);
          }
        }
        writeLog("exportFromHiddenLayers: " + exportFromHiddenLayers, image.exportDir, image.logFilename);
        // ignore images in overset text and rectangles with zero width or height
        if(exportFromHiddenLayers
          && originalBounds[0] - originalBounds[2] != 0
          && originalBounds[1] - originalBounds[3] != 0
          ){
          if(rectangle.itemLayer.locked == true) alert(panel.lockedLayerWarning);
          /* since cropping works not well with anchored images, we
          * create a duplicate of the rectangle where the cropping is applied
          */ 
          var rectangleCopy = null;
          if((image.cropImageToPage && exceedsPage)
            || (image.removeRectangleStroke && (rectangle.strokeWeight > 0))
            ){
            // create rectangle duplicate
            rectangleCopy = rectangle.duplicate([originalBounds[1], originalBounds[0]], [0, 0]);
            // disable text wrap temporarily, otherwise duplicate will be suppressed
            rectangle.textWrapPreferences.textWrapMode = 1852796517 // NONE
            // copy rotation angle
            rectangleCopy.rotationAngle = rectangle.rotationAngle;
            if(image.cropImageToPage && exceedsPage){
              rectangleCopy = cropRectangleToPage(rectangleCopy, originOnLeftPage, leftPage);
            }
            rectangle.textWrapPreferences.textWrapMode = textWrapMode;
            if(image.removeRectangleStroke && (rectangle.strokeWeight > 0)){
              // setting strokeTint to 0 is more robust. Setting strokeWeight to 0 adds sometimes a stroke
              rectangleCopy.strokeTint = image.removeRectangleStroke ? 0 : rectangle.strokeTint;
            }
          }
          var objectExportOptions = rectangle.objectExportOptions;
          // use format override in objectExportOptions if active. Check InDesign version because the property changed.
          var customImageConversion = isObjectExportOptionActive(objectExportOptions);
          var overrideBool = image.objectExportOptions && customImageConversion;
          var localFormat = overrideBool ? objectExportOptions.imageConversionType.toString().replace(/^JPEG/g, "JPG") : image.exportFormat;
          var localDensity = overrideBool ? Number(objectExportOptions.imageExportResolution.toString().replace(/^PPI_/g, "")) * image.objectExportDensityFactor : image.exportDPI;
          var normalizedDensity = getMaxDensity(localDensity, rectangle, image.maxResolution, image.baseDPI);
          var objectExportQualityInt = ["MAXIMUM", "HIGH", "MEDIUM", "LOW" ].indexOf(objectExportOptions.jpegOptionsQuality.toString());
          var localQuality = overrideBool && localFormat != "PNG" ? objectExportQualityInt : image.exportQuality;
          /*
          * set export filename
          */
          var filenameLabel = rectangle.extractLabel(image.pageItemLabel);
          var basename;
          var toBeExported = true;
          if (image.renameExportFilenames == true) {
            var imgNumber = pad( counter, image.exportFilenameSchema.split("#").length - 1);
            basename = image.exportFilenameSchema.replace("#", imgNumber).replace(/#/g, "");
          }
          else if (filenameLabel.length > 0 && image.overrideExportFilenames == false) {
            basename = getBasename(filenameLabel);
          } else {
            basename = getBasename(linkname);
          }
          var newFilename;
          var duplicates = hasDuplicates(link, docLinks, i);
          if( rectangle.constructor.name == "Group" ){
            newFilename = renameFile(basename + image.exportFilenameGroupSuffix, localFormat, false);
        } else if(inArray(basename, uniqueBasenames) && (!duplicates)){
            newFilename = renameFile(basename, localFormat, true);
          } else {
            newFilename = renameFile(basename, localFormat, false);
          }
          uniqueBasenames.push(getBasename(newFilename));
          /*
          * construct link object
          */
          linkObject = {
            link:link,
            pageItem:rectangle,
            format:localFormat,
            quality:localQuality,
            density:normalizedDensity,
            newFilename:newFilename,
            newFilepath:File(image.exportDir + "/" + newFilename),
            objectExportOptions:objectExportOptions,
            anchored:anchored,
            rectangleCopy:rectangleCopy,
            exceedsPage:exceedsPage,
            originalBounds:originalBounds,
            group:rectangle.constructor.name == "Group",
            id:rectangle.id,
            altText:altText,
            altTextFromXMP:altTextFromXMP
          }
          if(toBeExported){
            exportLinks.push(linkObject);
            writeLog("=> stored to: " + linkObject.newFilepath, image.exportDir, image.logFilename);
          } else {
            writeLog("=> skipped (reason: part of a group)", image.exportDir, image.logFilename);
          }
        } else {
          missingLinks.push(linkname);
        }
      }
    }
  }
  if (missingLinks.length > 0) {
    var result = confirm (missingLinks.length + " " + panel.promptMissingImages + "\n\n" + missingLinks.toString());
    if (!result) return;
  }
  // create directory
  createDir(image.exportDir);

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

    // iterate over files and store to specific location
    for (i = 0; i  < exportLinks.length; i++) {
      var exportFormat = exportLinks[i].format == "PNG" ? ExportFormat.PNG_FORMAT : ExportFormat.JPG;
      var exportResolution = exportLinks[i].density;
      var exportQuality = exportLinks[i].quality;
      // JPEG export options
      app.jpegExportPreferences.antiAlias = false;
      app.jpegExportPreferences.embedColorProfile = false;
      app.jpegExportPreferences.exportResolution = exportResolution;
      app.jpegExportPreferences.jpegColorSpace = JpegColorSpaceEnum.RGB;
      jpegExportQualityValues = [JPEGOptionsQuality.MAXIMUM ,JPEGOptionsQuality.HIGH, JPEGOptionsQuality.MEDIUM, JPEGOptionsQuality.LOW];
      app.jpegExportPreferences.jpegQuality = jpegExportQualityValues[exportQuality];
      app.jpegExportPreferences.jpegRenderingStyle = JPEGOptionsFormat.BASELINE_ENCODING;
      app.jpegExportPreferences.simulateOverprint = true;
      app.jpegExportPreferences.useDocumentBleeds = true;
      // PNG export options
      app.pngExportPreferences.antiAlias = false;
      app.pngExportPreferences.exportResolution = exportResolution;
      app.pngExportPreferences.pngColorSpace = PNGColorSpaceEnum.RGB;
      pngExportQualityValues = [PNGQualityEnum.MAXIMUM, PNGQualityEnum.HIGH, PNGQualityEnum.MEDIUM, PNGQualityEnum.LOW];
      app.pngExportPreferences.pngQuality = pngExportQualityValues[exportQuality];
      app.pngExportPreferences.transparentBackground = image.pngTransparency;
      app.pngExportPreferences.simulateOverprint = true;
      app.pngExportPreferences.useDocumentBleeds = true;
      progressBar.hit("export " + exportLinks[i].newFilename, i);
      if(exportLinks[i].rectangleCopy != null){
        exportLinks[i].rectangleCopy.exportFile(exportFormat, exportLinks[i].newFilepath);
        exportLinks[i].rectangleCopy.remove();
      } else {
        exportLinks[i].pageItem.exportFile(exportFormat, exportLinks[i].newFilepath);
      }
      // insert label with new file link for postprocessing
      exportLinks[i].pageItem.insertLabel(image.pageItemLabel, exportLinks[i].newFilename);
      if(exportLinks[i].altText.length > 0){
        exportLinks[i].pageItem.insertLabel(image.pageItemAltText, exportLinks[i].altText);
      }
      // separate label for XMP alt texts
      if(exportLinks[i].altTextFromXMP.length > 0){
        exportLinks[i].pageItem.insertLabel(image.XMPAltText, exportLinks[i].altTextFromXMP)
      }
    }
    progressBar.close();
    /*
     * danger zone: relink all images to their respective export paths
     */
    if(image.relinkToExportPaths == true) {
      writeLog("\nRelinking images with export paths...\n", image.exportDir, image.logFilename);
      relinkToExportPaths(doc, exportLinks);
    }
    alert (exportLinks.length  + " " + panel.finishedMessage);
    writeLog("\nFinished! Exported " + exportLinks.length + " of " + docLinks.length + " images.\nPlease check messages above for more details.", image.exportDir, image.logFilename);
    doc.save();
  }
  else {
    alert (panel.noValidLinks);
  }
  app.scriptPreferences.measurementUnit = measurementUnit;
  doc.viewPreferences.rulerOrigin = rulerOrigin;
}
// draw simple progress bar
function getProgressBar (title){
  var progressBarWindow = new Window("palette", title, {x:0, y:0, width:400, height:50});
  var pbar = progressBarWindow.add("progressbar", {x:10, y:10, width:380, height:6}, 0, 100);
  var stext = progressBarWindow.add("statictext", {x:10, y:26, width:380, height:20}, '');
  progressBarWindow.center();
  progressBarWindow.reset = function (maxValue) {
    stext.text = "export export export export export export export export ";
    pbar.value = 0;
    pbar.maxvalue = maxValue||0;
    pbar.visible = !!maxValue;
    this.show();
  }
  progressBarWindow.hit = function(msg, value) {
    ++pbar.value;
    stext.text = msg;
  }
  return progressBarWindow;
}
// create directory
function createDir (folder) {
  try {
    folder.create();
    return;
  } catch (e) {
    alert (e);
  }
}
// check if image is missing or embedded
function isValidLink (link) {
  var rectangle = link.parent.parent;
  try {
    // script would crash when geometricBounds not available, e.g. image is placed on overset text
    var bounds = rectangle.geometricBounds;
    if(rectangle.hasOwnProperty("parentPage") && rectangle.parentPage == null && image.exportFromPasteboard == false){
      writeLog('=> FAILED: image is on pasteboard', image.exportDir, image.logFilename);
      return false;
    } else if(link.parent.constructor.name == 'Story'){
      writeLog('=> WARNING: text-only link found: ' + link.name, image.exportDir, image.logFilename);
      return false;
    } else if(image.exportFromMasterPages == false && rectangle.parent.constructor.name == 'MasterSpread') {
      writeLog('=> INFO: image is on master page and not exported.', image.exportDir, image.logFilename);
      return false;
    } else if(rectangle.parent.constructor.name == "Group" && image.overrideExportFilenames == true) {
      writeLog('=> INFO: part of image group.', image.exportDir, image.logFilename);
      return true;
    } else {
      switch (link.status) {
      case LinkStatus.LINK_MISSING:
        writeLog('=> FAILED: image file is missing.', image.exportDir, image.logFilename);
        return false; break;
      case LinkStatus.LINK_EMBEDDED:
        writeLog('=> FAILED: embedded image.', image.exportDir, image.logFilename);
        return false; break;
      default:
        if(link != null) {
          return true
        } else {
          return false
        }
      }
    }
  } catch (e) {
    writeLog('=> FAILED: image is placed in overset text', image.exportDir, image.logFilename);
    return false;
  }
}
// return filename with new extension and conditionally attach random string
function renameFile(basename, extension, rename) {
  var normalizedBasename = basename.replace(/[%\x00-\x1f\x80-\x9f\s\/\?<>\\:\*\|":]/g, '_');
  if(rename) {
    hash = ((1 + Math.random())*0x1000).toString(36).slice(1, 6);
    var renameFile = normalizedBasename + '-' + hash + '.' + extension.toLowerCase();
    return renameFile
  } else {
    var renameFile = normalizedBasename + '.' + extension.toLowerCase();
    return renameFile
  }
}
// get file basename
function getBasename(filename) {
  var basename = filename.match( /^(.*?)\.[a-z]{2,4}$/i);
  if(basename != null){
    return basename[1];
  } else {
    // no file extension
    return filename;
  }
}
// check if string exists in array
function inArray(string, array) {
  var length = array.length;
  for(var i = 0; i < length; i++) {
    if(array[i] == string)
      return true;
  }
  return false;
}
function pad(num, size) {
    var string = num+"";
    while (string.length < size) string = "0" + string;
    return string;
}
// get density limit according to maximal resolution value
function getMaxDensity(density, rectangle, maxResolution, baseDensity) {
  var bounds = rectangle.geometricBounds;
  var densityFactor = density / baseDensity;
  var width =  (bounds[3] - bounds[1]);
  var height = (bounds[2] - bounds[0]);
  var resolution = Math.round(width * height * Math.pow(densityFactor, 2));
  if(resolution > maxResolution) {
    var maxDensity =  Math.floor(Math.sqrt(maxResolution * Math.pow(densityFactor, 2) / resolution) * baseDensity);
    return maxDensity;
  } else {
    return density;
  }
}
// crop a rectangle to page bleeds
function cropRectangleToPage (rectangle, originOnLeftPage, leftPage){
  var bounds = rectangle.geometricBounds;   // bounds: [y1, x1, y2, x2], e.g. top left / bottom right
  var page = rectangle.parentPage;
  var pageSide = page.side.toString();
  // release anchors to avoid displaced images. we need to restore the anchor later
  if(rectangle.parent.constructor.name == "Character"){
    rectangle.anchoredObjectSettings.releaseAnchoredObject();
  }
  // page is null if the object is on the pasteboard
  if(page != null){
    // rectangle.geometricBounds = [bounds[0], bounds[1], bounds[2], bounds[3]];
    // iterate over each corner and fit them into page
    var newBounds = [];
    for(var i = 0; i <= 3; i++) {
      // y1, x1, y2, x2 (top, left, bottom, right)
      // top
      if(       i == 0 && bounds[i] < page.bounds[i]){
        newBounds[i] = page.bounds[i];
      // left, left Page  
      } else if(originOnLeftPage == true && i == 1 && bounds[i] < leftPage.bounds[i]){
        newBounds[i] = leftPage.bounds[i];
      // left, single page or right page
      } else if(originOnLeftPage == false && i == 1 && bounds[i] < page.bounds[i]){
        newBounds[i] = page.bounds[i];
      // bottom
      } else if(i == 2 && bounds[i] > page.bounds[i]){
        newBounds[i] = page.bounds[i];
      // right, left page
      } else if(originOnLeftPage == true && i == 3 && bounds[i] > (leftPage.bounds[i]) * 2){
        newBounds[i] = leftPage.bounds[i] * 2;
      // right, single page or right page 
      } else if(originOnLeftPage == false && i == 3 && bounds[i] > page.bounds[i]){
        newBounds[i] = page.bounds[i];
      } else {
        newBounds[i] = bounds[i];        
      }
    }
    writeLog( "cropped bounds:" + newBounds, image.exportDir, image.logFilename);
    // assign new bounds
    rectangle.geometricBounds = newBounds;
  }
  return rectangle;
}
// get anchor position, needed for cropRectangleToPage()
function getAnchoredPosition(rectangle){
  // 1095716961: AnchorPosition.ABOVE_LINE
  // 1097814113: AnchorPosition.ANCHORED
  // 1095716969: AnchorPosition.INLINE_POSITION
  anchoredPosIndex = [1095716961, 1097814113, 1095716969].indexOf(rectangle.anchoredObjectSettings.anchoredPosition);
  return anchoredPosIndex;
}

// get path relative to indesign file location
function getDefaultExportPath() {
  var exportPath = String(app.activeDocument.fullName);
  exportPath = exportPath.substring(0, exportPath.lastIndexOf('/')) + '/' + image.exportDir;
  return exportPath
}
// delete all image file labels 
function deleteLabel(doc, deleteAllLabels, deleteOnlyGroupLabels){
  var allPageItems = doc.allPageItems;
  var labelNames = [image.pageItemLabel, image.pageItemAltText];
  for (var i = 0; i < doc.allPageItems.length; i++) {
    var obj = doc.allPageItems[i];
    if(deleteOnlyGroupLabels == true && obj.constructor.name == 'Group'){
      for (var j = 0; j < labelNames.length ; ++j) {
        obj.insertLabel(labelNames[j], '');
      }
    } else if (deleteAllLabels == true && (obj.constructor.name == 'Group' || obj.constructor.name == 'Rectangle')) {
      for (var j = 0; j < labelNames.length ; ++j) {
        obj.insertLabel(labelNames[j], '');
      }
    }
  }  
}
// simple logging
function writeLog(message, dir, filename){
  var path = dir + '/' + filename;
  createDir(dir);
  var write_file = File(path);
  if (!write_file.exists) {
    write_file = new File(path);
  }
  d = new Date();
  var timestr = "[" + d.getHours() + ":" + d.getMinutes() + ":" + d.getSeconds() + "] "
  write_file.open('a', undefined, undefined);
  write_file.encoding = "UTF-8";
  write_file.lineFeed = "Unix";
  write_file.writeln(timestr + message);  
  write_file.close();
}
function clearLog(dir, filename){
  var path = dir + '/' + filename;
  del_file = File(path);
  if (del_file.exists) {
    del_file.remove();
  }
}
// create array from links object
function linksToSortedArray(links){
  arr = [];
  // put links in an array
  for (var i = 0; i < links.length; i++) {
    arr.push(links[i]);
  }
  // sort the array by name
  arr.sort(function(a, b){
    var x = a.name.toLowerCase();
    var y = b.name.toLowerCase();
    return x.localeCompare(y);
  });
  return arr;
}
function isObjectExportOptionActive(objectExportOptions){
  var active = (parseFloat(app.version) <= 10) ? objectExportOptions.customImageConversion :
      objectExportOptions.preserveAppearanceFromLayout == PreserveAppearanceFromLayoutEnum.PRESERVE_APPEARANCE_RASTERIZE_CONTENT ||
      objectExportOptions.preserveAppearanceFromLayout == PreserveAppearanceFromLayoutEnum.PRESERVE_APPEARANCE_RASTERIZE_CONTAINER;
  return active;
}
function isFormatOverrideActive(searchFormat){
  var result = false;
  var links = app.documents[0].links;
  for(var i = 0; i < links.length; i++){
    var rectangle = links[i].parent.parent;
    if(rectangle.hasOwnProperty("objectExportOptions")
       && isObjectExportOptionActive(rectangle.objectExportOptions)
       && rectangle.objectExportOptions.imageConversionType.toString() == searchFormat
      ){
      result = true;
    }
  }
  return result;
}
// compare two links if they have equal dimensions
function hasDuplicates(link, docLinks, index) {
  var rectangle = link.parent.parent;
  var nextLink;
  var result = [];
  var i = 0;
  do{
    nextLink = docLinks[i];
    // check whether link names match. The index var is used to prevent that an image is compared with itself.
    if( link.name == nextLink.name && i != index && isValidLink(nextLink)) {
      var nextRectangle = nextLink.parent.parent;
      // InDesign calculates widths and heights not precisely, so we have to round them
      var rectangleWidth = Math.round((rectangle.geometricBounds[3] - rectangle.geometricBounds[1]) * 100) / 100;
      var rectangleHeight = Math.round((rectangle.geometricBounds[2] - rectangle.geometricBounds[0]) * 100) / 100;
      var nextRectangleWidth = Math.round((nextRectangle.geometricBounds[3] - nextRectangle.geometricBounds[1]) * 100) / 100;
      var nextRectangleHeight = Math.round((nextRectangle.geometricBounds[2] - nextRectangle.geometricBounds[0]) * 100) / 100;
      var equalWidth  = rectangleWidth == nextRectangleWidth;
      var equalHeight = rectangleHeight == nextRectangleHeight;
      var equalFlip = rectangle.absoluteFlip == nextRectangle.absoluteFlip;
      var equalRotationAngle = rectangle.absoluteRotationAngle == nextRectangle.absoluteRotationAngle;
      var equalShearAngle = rectangle.absoluteShearAngle == nextRectangle.absoluteShearAngle;
      var equalHorizontalScale = rectangle.absoluteHorizontalScale == nextRectangle.absoluteHorizontalScale;
      var equalVerticalScale = rectangle.absoluteVerticalScale == nextRectangle.absoluteVerticalScale;
      var inGroup = (rectangle.parent.constructor.name == "Group") == (nextRectangle.parent.constructor.name == "Group");
      var topCropEqual = rectangle.frameFittingOptions.topCrop == nextRectangle.frameFittingOptions.topCrop;
      var leftCropEqual = rectangle.frameFittingOptions.leftCrop == nextRectangle.frameFittingOptions.leftCrop;
      var bottomCropEqual = rectangle.frameFittingOptions.bottomCrop == nextRectangle.frameFittingOptions.bottomCrop;
      var rightCropEqual = rectangle.frameFittingOptions.rightCrop == nextRectangle.frameFittingOptions.rightCrop;
      // note: either objectExportOptions are not active, then we safely ignore them or we
      // check if they are active for the two images
      var objectExportOptionsActive = !image.objectExportOptions || isObjectExportOptionActive(rectangle.objectExportOptions) == isObjectExportOptionActive(nextRectangle.objectExportOptions);
      result.push(   equalFlip 
                  && equalRotationAngle 
                  && equalWidth 
                  && equalHeight
                  && equalShearAngle 
                  && equalHorizontalScale 
                  && equalVerticalScale 
                  && inGroup 
                  && objectExportOptionsActive
                  && topCropEqual
                  && leftCropEqual
                  && bottomCropEqual
                  && rightCropEqual
                  );
    }
    i++;
  }
  while(i < docLinks.length);
  if(inArray(true, result)){
    writeLog("Found duplicate: " + link.name, image.exportDir, image.logFilename);
  }
  return inArray(true, result);
}
function relinkToExportPaths (doc, exportLinks) {
  for(var i = 0; i < exportLinks.length; i++) {
    var linkId = exportLinks[i].link.id;
    var exportPath = exportLinks[i].newFilepath;
    var link = doc.links.itemByID(linkId);
    var rectangle = link.parent.parent;
    writeLog('"' + exportLinks[i].link.name + ' (' + rectangle.constructor.name + ') " => "' + exportLinks[i].newFilename + '"', image.exportDir, image.logFilename);
    if(exportLinks[i].group){
      var group =  doc.groups.itemByID(exportLinks[i].id);
      writeLog(' GroupSRT!!! ' + group.constructor.name + '"', image.exportDir, image.logFilename);
      var spread = group.parentPage.parent;
      if(group.parent.constructor.name == "Character"){
        writeLog(' Group is anchored', image.exportDir, image.logFilename);
        var character = group.parent;
        var newAnchoredRectangle = character.insertionPoints[-1].rectangles.add();
        newAnchoredRectangle.place(new File(exportPath));
        newAnchoredRectangle.properties = group.properties;
        newAnchoredRectangle.anchoredObjectSettings.properties = group.anchoredObjectSettings.properties;
      } else {
        writeLog(' Group is not anchored', image.exportDir, image.logFilename);
        var x = exportLinks[i].pageItem.geometricBounds[1];
        var y = exportLinks[i].pageItem.geometricBounds[0];
        spread.place(new File(exportPath), [x,y], doc.layers[0]);
        var unAnchoredRectangle = document.rectangles[document.rectangles.length];
        unAnchoredRectangle.properties = group.properties;
      }
      rectangle.remove();
    } else {
      // relink to export path
      link.relink(exportPath);
      // fit content to frame, necessary because export crops, flips, etc
      if(rectangle.constructor.name != "Polygon"){
        rectangle.fit(FitOptions.CONTENT_TO_FRAME);
      }
    }
  }
}
function splitStringToArray (string, index) {
  var tokens = [];
  for(var i = 0; i < string.length; i++){
    tokens.push(string.substring(i,  i + index - 1));
    i = i + index - 2;
  }
  return tokens;
}
function getTopmostGroup(rectangle){
  var p = rectangle.parent;
  while(p.parent.constructor.name == "Group"){
    p = p.parent;
  }
  return p;
}
function getLinkNameForGroup(group) {
  var graphics = group.allGraphics;
  var link;
  for (var i = 0; i < graphics.length; i++) {
    if (graphics[i].isValid) {
      link = graphics[i].itemLink.name;
    }
  }
  return link;
}
// disable lock since this prevents images to be exported
// note that just the group itself has a lock state, not their children
function disableLocks(rectangle){
  if(rectangle.parent.constructor.name == "Group"
     && rectangle.parent.hasOwnProperty("locked")
     && rectangle.parent.locked != false){
    rectangle.parent.locked = false;
  }
  if(rectangle.itemLayer.hasOwnProperty("locked")
     && rectangle.itemLayer.locked != false){
    rectangle.itemLayer.locked = false;
  }
  if(rectangle.hasOwnProperty("locked")
     && rectangle.locked != false){
    rectangle.locked = false;
  }
  return rectangle;
}
