creold/illustrator-scripts

FitSelectionToArtboards-Lite and FitSelectionToArtboards are not working anymore after resetting my Illustrator Preferences.

futuremotiondev opened this issue · 5 comments

I can't tell if it's the fact that I reset my preferences, or if a new update to Illustrator caused these scripts to break.

FitSelectionToArtboards-Lite is correctly resizing the selected item, but incorrectly placing it directly above the artboard instead of centered in the middle.

See this video:

Illustrator_50Vkk0OpXV.mp4

(Also uploaded here because GitHub seems to be misbehaving: https://streamable.com/8nmpsn)

I think the fitToArtboard function is working as expected, but the centerToArtboard function is broken.

It also no longer handles multiple selections. I'm pretty sure that previously I could select multiple items, run the script, and all of my selections would be fit to multiple empty artboards. Similar to the FitSelectionToArtboards script but with no dialog.

Setting that aside, the FitSelectionToArtboards is also misbehaving in the same way. It correctly resizes the artwork but again places each object directly above the artboard instead of centered in the middle.

See this video:

Illustrator_uuTGZXz1mU.mp4

(Also uploaded here because GitHub seems to be misbehaving: https://streamable.com/qgx6h8)

I am terrible with extendscript, but I managed to get FitSelectionToArtboards-Lite to work by modifying line 283 from:

centerY = top + (itemSize.h + (isFlipY ? -1 : 1) * abHeight) / 2;

To:

centerY = (top + (itemSize.h + (isFlipY ? -1 : 1) * abHeight) / 2) - itemSize.h;

And the same modification to FitSelectionToArtboards also seemeed the fix the problem.

However, I'm positive this isn't the correct approach, but just a hack. I'm hoping you can find some time to update the scripts and are able to reproduce the issue.

Maybe there is an important setting that was reverted when I reset my preferences that broke the script?

Anyway, thanks for all your hard work on these scripts. Hoping there is a solution.

Jay

@futuremotiondev Yes, it has to do with the Adobe Illustrator Prefs file, the value of the rulers in it, and the artboard rulers in the file if the file was saved with previous Adobe Illustrator Prefs settings.

You can test this small fix for both scripts in the zip archive.
FitSelectionToArtboards.zip

That did the trick! Thank you so much for making the modification and also for the lightning fast turnaround. You are an Illustrator scripting god!

Best,
Jay

One more thing - do you happen to have a one-click script like FitSelectionToArtboards that doesn't have a dialog box and always defaults to "Visible" for Item Bounds?

Illustrator_ufujhSIDIK

Illustrator_ZsuffrPLQ0.mp4

Ideally I'd just like to be able to select a bunch of artwork, run the script, and then just have it go, without any dialog popup.

I can try to hack this together, but if it's an easy change for you that would be awesome.

Thanks again!

Jay

Edit: I edited your script (Changed the name to FitSelectionToArtboards-NoDialog.jsx) and just modified the CFG object to my desired defaults. Then ran process(CFG); instead of invoking the dialog.

Seems to work as expected, but the execution is a bit slower than the original script. Not exactly sure why. But I'm happy because it works. Here's the full code:

//@target illustrator
preferences.setBooleanPreference('ShowExternalJSXWarning', false); // Fix drag and drop a .jsx file

function main() {
  var SCRIPT = {
        name: 'Fit Selection To Artboards',
        version: 'v.0.3.4'
      },

      CFG = {
        pads: 0,
        isAll: true,
        isVisBnds: true,
        isFit: true,
        isRename: false,
        aiVers: parseInt(app.version),
        isScaleStroke: preferences.getBooleanPreference('scaleLineWeight'),
        units: getUnits(), // Active document units
        showUI: false,    // Silent mode or dialog
      };

  if (CFG.aiVers < 16) {
    alert('Error\nSorry, script only works in Illustrator CS6 and later', 'Script error');
    return;
  }

  if (!documents.length) {
    alert('Error\nOpen a document and try again', 'Script error');
    return;
  }

  if (!selection.length || selection.typename == 'TextRange') {
    alert('Error\nPlease, select one or more items', 'Script error');
    return;
  }

  // Scale factor for Large Canvas mode
  CFG.sf = activeDocument.scaleFactor ? activeDocument.scaleFactor : 1;

  process(CFG);

}

// Run processing
function process(cfg) {
  var doc = app.activeDocument,
      docAbs = doc.artboards,
      abIdx = docAbs.getActiveArtboardIndex(),
      abBnds = docAbs[abIdx].artboardRect,
      docSel = selection,
      item = docSel[0],
      coord = app.coordinateSystem,
      ruler = docAbs[abIdx].rulerOrigin;

  app.coordinateSystem = CoordinateSystem.ARTBOARDCOORDINATESYSTEM;

  if (!cfg.isAll) {
    if (cfg.isFit) {
      fitToArtboard(item, abBnds, cfg.isVisBnds, cfg.isScaleStroke, cfg.pads);
    }
    docAbs[abIdx].rulerOrigin = [0, 0];
    centerToArtboard(item, abBnds);
    docAbs[abIdx].rulerOrigin = ruler;
    if (cfg.isRename) {
      renameArtboard(item, docAbs[abIdx]);
    }
  } else {
    var emptyAbs = getEmptyArtboards(doc),
        len = Math.min(emptyAbs.length, docSel.length);

    for (var i = len - 1; i >= 0; i--) {
      item = docSel[i];
      abIdx = emptyAbs[i];
      abBnds = docAbs[abIdx].artboardRect;
      docAbs.setActiveArtboardIndex(abIdx);
      if (cfg.isFit) {
        fitToArtboard(item, abBnds, cfg.isVisBnds, cfg.isScaleStroke, cfg.pads);
      }
      ruler = docAbs[abIdx].rulerOrigin;
      docAbs[abIdx].rulerOrigin = [0, 0];
      centerToArtboard(item, abBnds);
      docAbs[abIdx].rulerOrigin = ruler;
      if (cfg.isRename) {
        renameArtboard(item, docAbs[abIdx]);
      }
    }
  }

  app.coordinateSystem = coord;
  selection = docSel;
}

// Get the ruler units of the active document
function getUnits() {
  if (!documents.length) return '';
  var key = activeDocument.rulerUnits.toString().replace('RulerUnits.', '');
  switch (key) {
    case 'Pixels': return 'px';
    case 'Points': return 'pt';
    case 'Picas': return 'pc';
    case 'Inches': return 'in';
    case 'Millimeters': return 'mm';
    case 'Centimeters': return 'cm';
    // Added in CC 2023 v27.1.1
    case 'Meters': return 'm';
    case 'Feet': return 'ft';
    case 'FeetInches': return 'ft';
    case 'Yards': return 'yd';
    // Parse new units in CC 2020-2023 if a document is saved
    case 'Unknown':
      var xmp = activeDocument.XMPString;
      if (/stDim:unit/i.test(xmp)) {
        var units = /<stDim:unit>(.*?)<\/stDim:unit>/g.exec(xmp)[1];
        if (units == 'Meters') return 'm';
        if (units == 'Feet') return 'ft';
        if (units == 'FeetInches') return 'ft';
        if (units == 'Yards') return 'yd';
        return 'px';
      }
      break;
    default: return 'px';
  }
}

// Units conversion
function convertUnits(value, currUnits, newUnits) {
  return UnitValue(value, currUnits).as(newUnits);
}

// Convert string to absolute number
function strToAbsNum(str, def) {
  if (arguments.length == 1 || def == undefined) def = 1;
  str = str.replace(/,/g, '.').replace(/[^\d.]/g, '');
  str = str.split('.');
  str = str[0] ? str[0] + '.' + str.slice(1).join('') : '';
  if (isNaN(str) || !str.length) return parseFloat(def);
  else return parseFloat(str);
}

// Fit the item to the size of the artboard
function fitToArtboard(item, abBnds, isVisBnds, isStroke, pads) {
  var type = isVisBnds ? 'visibleBounds' : 'geometricBounds';
  var bnds = [];

  if (isType(item, 'group|text')) {
    var dup = item.duplicate();
    app.executeMenuCommand('deselectall');
    selection = dup;
    outlineText(dup.pageItems ? dup.pageItems : [dup]);
    dup = selection[0];
    bnds = getVisibleBounds(dup, type);
    app.executeMenuCommand('deselectall');
    dup.remove();
  } else {
    bnds = getVisibleBounds(item, type);
  }

  var itemW = Math.abs(bnds[2] - bnds[0]),
      itemH = Math.abs(bnds[1] - bnds[3]),
      abWidth = Math.abs(abBnds[2] - abBnds[0]),
      abHeight = Math.abs(abBnds[1] - abBnds[3]);

  var ratioW = 100 * (abWidth - 2 * pads) / itemW,
      ratioH = 100 * (abHeight - 2 * pads) / itemH,
      ratio = Math.min(ratioW, ratioH);

  // X, Y, Positions, FillPatterns, FillGradients, StrokePattern, LineWidths
  item.resize(ratio, ratio, true, true, true, true, (isVisBnds || isStroke) ? ratio : 100);
}

// Create outlines
function outlineText(coll) {
  for (var i = coll.length - 1; i >= 0; i--) {
    var item = coll[i];
    if (isType(item, 'text')) {
      item.createOutline();
    } else if (isType(item, 'group')) {
      outlineText(item.pageItems);
    }
  }
}

// Get the actual "visible" bounds
// https://github.com/joshbduncan/adobe-scripts/blob/main/DrawVisibleBounds.jsx
function getVisibleBounds(obj, type) {
  if (arguments.length == 1 || type == undefined) type = 'geometricBounds';
  var doc = app.activeDocument;
  var bnds, clippedItem, tmpItem, tmpLayer;
  var curItem;
  if (obj.typename === 'GroupItem') {
    if (obj.clipped) {
      // Check all sub objects to find the clipping path
      for (var i = 0; i < obj.pageItems.length; i++) {
        curItem = obj.pageItems[i];
        if (curItem.clipping) {
          clippedItem = curItem;
          break;
        } else if (curItem.typename === 'CompoundPathItem') {
          if (!curItem.pathItems.length) {
            // Catch compound path items with no pathItems
            // via William Dowling @ github.com/wdjsdev
            tmpLayer = doc.layers.add();
            tmpItem = curItem.duplicate(tmpLayer);
            app.executeMenuCommand('deselectall');
            tmpItem.selected = true;
            app.executeMenuCommand('noCompoundPath');
            tmpLayer.hasSelectedArtwork = true;
            app.executeMenuCommand('group');
            clippedItem = selection[0];
            break;
          } else if (curItem.pathItems[0].clipping) {
            clippedItem = curItem;
            break;
          }
        }
      }
      if (!clippedItem) clippedItem = obj.pageItems[0];
      bnds = clippedItem[type];
      if (tmpLayer) {
        tmpLayer.remove();
        tmpLayer = undefined;
      }
    } else {
      // If the object is not clipped
      var subObjBnds;
      var allBoundPoints = [[], [], [], []];
      // Get the bounds of every object in the group
      for (var i = 0; i < obj.pageItems.length; i++) {
        curItem = obj.pageItems[i];
        subObjBnds = getVisibleBounds(curItem, type);
        allBoundPoints[0].push(subObjBnds[0]);
        allBoundPoints[1].push(subObjBnds[1]);
        allBoundPoints[2].push(subObjBnds[2]);
        allBoundPoints[3].push(subObjBnds[3]);
      }
      // Determine the groups bounds from it sub object bound points
      bnds = [
        Math.min.apply(Math, allBoundPoints[0]),
        Math.max.apply(Math, allBoundPoints[1]),
        Math.max.apply(Math, allBoundPoints[2]),
        Math.min.apply(Math, allBoundPoints[3]),
      ];
    }
  } else {
    bnds = obj[type];
  }
  return bnds;
}

// Place the item in the center of the artboard
function centerToArtboard(item, abBnds) {
  var bnds = item.geometricBounds,
      itemSize = {
        left: bnds[0],
        top: bnds[1],
        inLeft: bnds[0],
        inTop: bnds[1],
        inRight: bnds[2],
        inBottom: bnds[3],
        h: 0,
        w: 0
      };

  if (isType(item, 'group') && item.clipped) {
    bnds = getVisibleBounds(item, 'geometricBounds');
    itemSize.inLeft = bnds[0];
    itemSize.inTop = bnds[1];
    itemSize.inRight = bnds[2];
    itemSize.inBottom = bnds[3];
  } else if (isType(item, 'group|text')) {
    var dup = item.duplicate();
    app.executeMenuCommand('deselectall');
    selection = dup;
    outlineText(dup.pageItems ? dup.pageItems : [dup]);
    dup = selection[0];
    bnds = getVisibleBounds(dup, 'geometricBounds');
    app.executeMenuCommand('deselectall');
    itemSize.inLeft = bnds[0];
    itemSize.inTop = bnds[1];
    itemSize.inRight = bnds[2];
    itemSize.inBottom = bnds[3];
    dup.remove();
  }

  abWidth = Math.abs(abBnds[2] - abBnds[0]);
  abHeight = Math.abs(abBnds[1] - abBnds[3]);
  itemSize.h = Math.abs(itemSize.inTop - itemSize.inBottom);
  itemSize.w = Math.abs(itemSize.inRight - itemSize.inLeft);

  var left = itemSize.left - itemSize.inLeft,
      top = itemSize.top - itemSize.inTop,
      centerX = left + (abWidth - itemSize.w) / 2,
      centerY = top + (itemSize.h - abHeight) / 2;

  item.position = [centerX, centerY];
}

// Rename the artboard as an item
function renameArtboard(item, ab) {
  var name = '';

  if (isType(item, 'text') && isEmpty(item.name) && !isEmpty(item.contents)) {
    name = item.contents.slice(0, 100);
  } else if (isType(item, 'symbol') && isEmpty(item.name)) {
    name = item.symbol.name;
  } else {
    name = item.name;
  }

  if (!isEmpty(name) && ab.name !== name) ab.name = name;
}

// Get empty artboards of the document
function getEmptyArtboards(doc) {
  var out = [];
  for (var i = 0, len = doc.artboards.length; i < len; i++) {
    selection = null;
    doc.artboards.setActiveArtboardIndex(i);
    doc.selectObjectsOnActiveArtboard();
    if (!selection.length) out.push(i);
  }
  return out;
}

// Check an empty string
function isEmpty(str) {
  return str.replace(/\s/g, '').length == 0;
}

// Check the item typename by short name
function isType(item, type) {
  var regexp = new RegExp(type, 'i');
  return regexp.test(item.typename);
}

// Open link in browser
function openURL(url) {
  var html = new File(Folder.temp.absoluteURI + '/aisLink.html');
  html.open('w');
  var htmlBody = '<html><head><META HTTP-EQUIV=Refresh CONTENT="0; URL=' + url + '"></head><body> <p></body></html>';
  html.write(htmlBody);
  html.close();
  html.execute();
}

try {
  main();
} catch (e) {}

Is this change the most optimal way to do what I'm looking for? Or is there a better (or more fast) way to do this?

Thanks again,
Jay

@futuremotiondev the speed of the script is affected by the number of artboards in the document. The script first checks which artboards are empty. The UI and No UI versions do not differ in the algorithm.