Illustrator variables - dynamically line up two text strings next to each other when autogenerating - automation

I am automating the generation of several thousand labels in Adobe Illustrator. The use of the VariableImporter script has made easy work of it so far, but now I have reached an issue where I am stumped. The original plan worked great, until the powers that be requested that one line of text have a bold text string, followed by a normal weight text string. Before, when the font weights were the same I could have connected the two strings of text in the CSV file prior to loading them into the drawing, and they would have came out lying right next to each other. This is now no longer possible and I can't think of a solution that is not incredibly fussy.
I don't know illustrator very well, so I am thinking I could just be unaware of some setting that would stick an object next to another one even as the other one moves.

Okay here is the way I figured out how to do this with help from Adobe forums and from Vasily.
First of all, use InDesign if possible. It is better at performing a Data Merge and can do this without your scripting.
Write out <variable1> <variable2> which is formatted as needed on the same line of text.
You will need to have the variables that you are putting in there somewhere in the illustration. Recommended to put it in a hidden layer behind everything.
replace variable1 and variable2 with the names of your variables where the functions getVariableContents() are called in this script
var idoc = app.activeDocument;
var vars = idoc.variables;
var replace1 = /<variable1>/g;
var replace2 = /<variable2>/g;
// author CarlosCanto on adobe forums
function getVariableContents(variableName) {
var idoc = app.activeDocument;
var ivar = idoc.variables.getByName(variableName);
return ivar.pageItems[0].contents;
}
var replaceWith1 = getVariableContents('variable1'), result;
var replaceWith2 = getVariableContents('variable2'), result;
// regex_changeContentsOfWordOrString_RemainFormatting.jsx
// regards pixxxel schubser
function exchangeWords(s, replacer) {
var s = s;
var replacer = replacer;
var atfs = activeDocument.textFrames;
for (var i = atfs.length - 1; i >= 0; i--) {
atf = atfs[i];
while (result = s.exec(atf.contents)) {
try {
aCon = atf.characters[result.index];
aCon.length = result[0].length;
aCon.contents = aCon.contents.replace(s, replacer);
} catch (e) {};
}
}
}
exchangeWords(replace1,replaceWith1);
exchangeWords(replace2,replaceWith2);
run the script

There is a way to accomplish this by having a script do some processing during the course of your batch output, and an organizational system which adds some overhead to your file, in terms of adding more text boxes and possibly an extra layer to your document. But - here's what you can have: a hidden layer with all your variables there in separate single point-text objects, and a layer with your regular template objects such as any point text or area-text objects. Your art text objects will need to be re-worked to contain a string with multiple variable placeholders like this: "Hello, <FirstName> <LastName>". The placeholders can be styled, and a processing script would then need to replace the <placeholder> words with your real variable values. Where are the varible values? They are going to be populating into your hidden layer which has your separate text objects and the script would need to read the contents of each of those to put into the <placeholders>. ~~Those same text fields can be styled as you wish, and the script could apply the same styles to your text when it is replaced in the main text body.~~ -actually this won't be necessary of your routine backs up the original text frame with the placeholder in it, therefore preserving the styling, but it may be necessary if you are going to instead use an external text file to keep your original text in. And of course, it will need to make a backup of the original text with all the <placeholders> so that it will reset the text for every new dataset during your batch process.
However, this is much easier done in Indesign, can you not use ID for your task?

I modified script from #tucker-david-grebitus's answer. So now it gets all textual variables and replaces all their names edged by percent symbol
for (var i = activeDocument.variables.length - 1; i >= 0; i -= 1) {
var variable = activeDocument.variables[i];
if (variable.kind !== VariableKind.TEXTUAL || !variable.pageItems.length) {
continue;
}
var search = new RegExp('%' + variable.name + '%', 'g');
var value = variable.pageItems[0].contents;
for (var j = activeDocument.textFrames.length - 1; j >= 0; j -= 1) {
var textFrame = activeDocument.textFrames[j];
textFrame.contents = textFrame.contents.replace(search, value);
}
}

Related

How to clean existing properties and replace with metadata template on Photoshop (scripting)?

While creating a script that would automate all the different tasks I do when I start working on a new picture on Photoshop, I encountered the following problem.
Manually, I would Ctrl + Alt + Shift + I, click on the template I want and choose the option "Clear existing properties and replace with template properties".
I can't find the way to do precisely this. The best thing I managed to find is something like this :
app.activeDocument.info.author = "test";
app.activeDocument.info.caption = "";
app.activeDocument.info.captionWriter = "";
app.activeDocument.info.headline = "";
app.activeDocument.info.instructions = "";
app.activeDocument.info.keywords = "";
app.activeDocument.info.authorPosition = "";
app.activeDocument.info.credit = "";
app.activeDocument.info.source = "";
app.activeDocument.info.category = "";
app.activeDocument.info.supplementalCategories = "";
app.activeDocument.info.title = "";
// etc.
And it actually doesn't really work like the "Clear existing properties and replace with template properties".
I didn't find anything on the Photoshop scripting guide, nor on the internet. Any help would be greatly appreciated !
What I think is the problem is Photoshop separates file-metadata from its activeDocument-metadata. What you see in "File info..." (via Ctrl+Alt+Shift+I) is supposed to represent the file in the filesystem, which metadata is embedded in.
There are several scripting guides to Photoshop scripting. I think the one relevant for you would be "Javascript Tools Guide", specifically the chapter 10 "Scripting Access to XMP Metadata".
Is it important for you to set up the metadata already when creating a new picture? If not, you may want to look at a solution using a customized export script.
It customizes XMP-metadata upon exporting like
Create a basic metadata object:
var meta = new XMPMeta();
Provide a namespaceURI (see XMP specs) known to photoshop along with tag name, and value:
meta.setProperty(XMPConst.NS_XMP, "CreatorTool", app.version);
Save the image temporarily (using other script):
var imgFile = new File(fileName);
saveImage(fileName);
Finish saving by adding the metadata-object:
var metaFile = new XMPFile(imgFile.fsName, XMPConst.FILE_UNKNOWN, XMPConst.OPEN_FOR_UPDATE);
if (metaFile.canPutXMP(meta)) { metaFile.putXMP(meta); }
metaFile.closeFile(XMPConst.CLOSE_UPDATE_SAFELY);
Doing it this way also erases any existing or default metadata.

How to check multiple PDF files for annotations/comments?

Problem: I routinely receive PDF reports and annotate (highlight etc.) some of them. I had the bad habit of saving the annotated PDFs together with the non-annotated PDFs. I now have hundreds of PDF files in the same folder, some annotated and some not. Is there a way to check every PDF file for annotations and copy only the annotated ones to a new folder?
Thanks a lot!
I'm on Win 7 64bit, I have Adobe Acrobat XI installed and I'm able to do some beginner coding in Python and Javascript
Please ignore the following suggestion, since the answers already solved the problem.
EDIT: Following Mr. Wyss' suggestion, I created the following code for Acrobat's Javascript console to be run only once at the beginning:
counter = 1;
// Open a new report
var rep = new Report();
rep.size = 1.2;
rep.color = color.blue;
rep.writeText("Files WITH Annotations");
Then this code should be applied to all PDFs:
this.syncAnnotScan();
annots = this.getAnnots();
path = this.path;
if (annots) {
rep.color = color.black;
rep.writeText(" ");
rep.writeText(counter.toString()+"- "+path);
rep.writeText(" ");
if (counter% 20 == 0) {
rep.breakPage();
}
counter++;
}
And, at last, one code to be run only once at the end:
//Now open the report
var docRep = rep.open("files_with_annots.pdf");
There are two problems with this solution:
1. The "Action Wizard" seems to always apply the same code afresh to each PDF (that means that the "counter" variable, for instance, is meaningless; it will always be = 1. But more importantly, var "rep" will be unassigned when the middle code is run on different PDFs).
2. How can I make the codes that should be run only once run only at the beginning or at the end, instead of running everytime for every single PDF (like it does by default)?
Thank you very much again for your help!
This would be possible using the Action Wizard to put together an action.
The function to determine whether there are annotations in the document would be done in Acrobat JavaScript. Roughly, the core function would look like this:
this.syncAnnotScan() ; // updates all annots
var myAnnots = this.getAnnots() ;
if (myAnnots != null) {
// do something if there are annots
} else {
// do something if there are no annots
}
And that should get you there.
I am not completely positive, but I think there is also a Preflight check which tells you whether there are annotations in the document. If so, you would create a Preflight droplet, which would sort out the annotated and not annotated documents.
Mr. Wyss is right, here's a step-by-step guide:
In Acrobat XI Pro, go to the 'Tools' panel on the right side
Click on the 'Action Wizard' tab (you must first make it visible, though)
Click on 'Create New Action...', choose 'More tools' > 'Execute Javascript' and add it to right-hand pane > click on 'Execute Javascript' > 'Specify Settings' (uncheck 'prompt user' if you want) > paste this code:
.
this.syncAnnotScan();
var annots = this.getAnnots();
var fname = this.documentFileName;
fname = fname.replace(",", ";");
var errormsg = "";
if (annots) {
try {
this.saveAs({
cPath: "/c/folder/"+fname,
bPromptToOverwrite: false //make this 'true' if you want to be prompted on overwrites
});
} catch(e) {
for (var i in e)
{errormsg+= (i + ": " + e[i]+ " / ");}
app.alert({
cMsg: "Error! Unable to save the file under this name ('"+fname+"'- possibly an unicode string?) See this: "+errormsg,
cTitle: "Damn you Acrobat"
});
}
;}
annots = 0;
Save and run it! All your annotated PDFs will be saved to 'c:\folder' (but only if this folder already exists!)
Be sure to enable first Javascript in 'Edit' > 'Preferences...' > 'Javascript' > 'Enable Acrobat Javascript'.
VERY IMPORTANT: Acrobat's JS has a bug that doesn't allow Docs to be saved with commas (",") in their names (e.g., "Meeting with suppliers, May 11th.pdf" - this will get an error). Therefore, I substitute in the code above all "," for ";".

Indesign script: how to pasteboard items

I have this script that finds a paragraph style, puts an item from a library at the very end and applies object style:
myDoc = app.documents[0];
myLib = app.libraries[0];
myObjectStyle = myDoc.objectStyles.item ("marker");
app.findTextPreferences = app.changeTextPreferences = null;
app.findTextPreferences.appliedParagraphStyle = "Custom"
var myFound = app.activeDocument.findText(true);
alert (myFound.length);
try {
for (i = 0; i < myFound.length; i++) {
myIcon = myLib.assets.itemByName("winieta_tr").placeAsset (myFound[i].insertionPoints[-2])[0];
myIcon.appliedObjectStyle = myObjectStyle;
// myFound[i].remove ();
}
}
catch (e) {alert (e.message)}
I don't know how to alter it, so the items are obtained not from library but form pasteboard - any help would be appreciated.
Is it possible to find elements that are in the document by name, as it is with library elements?
Yes, you can find an object by name (you would assign that name in the layers panel) simply by using
myDoc.pageItems.itemByName("myItemName");
If you are looking for the same thing on a specific spread (for example if several items on several spreads have the same name), you can use
myDoc.spreads[0].pageItems.itemByName("myItemName");
Or if you just want to use the currently active spread
app.activeWindow.activeSpread.pageItems.itemByName("myItemName");
Just make sure not to use the page to address a page item on the pasteboard as the pasteboard does not belong to any page.
Is it possible to find elements that are in the document by name, as it is with library elements?
You can apply a script labels to the frame on the pasteboard to give it a name.

data loading to qlikview extension this.Data

I'm trying to use extension for Qlikview 11SR2.
I've tried to access data with this.Data.Rows, but this object is empty even though the data is not empty and I can display the data in a table.
The code I have used is:
var obj = this.Data;
for(var prop in obj) {
var div = document.createElement("div");
div.innerHTML = "" + prop + "" + obj[prop];
this.Element.appendChild(div);
}
I have no access to the internet - I work offline.
How can i make this.Data.Rows contain the data (it is undefined)?
Based on your feedback, I would say it might be worth trying a slightly different way of accessing your data as the this.Data.Rows object contains a collection of row objects which in turn contain one or more objects, which relate to the dimensions and measures passed to the script (and are defined in definition.xml).
For example, say that I have an extension that features a dimension and a measure (in that order). I can access each value of these in a loop as follows:
for (var rowIx = 0; rowIx < this.Data.Rows.length; rowIx++) {
var row = this.Data.Rows[rowIx];
myDimensionValue = row[0].text;
myMeasureValue = row[1].text;
}
I can then add code to output each value of myDimension and myMeasureValue to the loop, such as in your case adding a new div for each set of values.
I found the "QVConsole" extension invaluable when writing extensions, as it allows you to have a JavaScript console in your QlikView document, so you may then use statements like console.log(myDimensionValue) etc. to help you debug your extension. You can download it from https://github.com/thomasfriebel/QvConsole.
In you extension object in the webview, set the dimension and a valid expression. Let me know whether it works!

JSFL: selecting items returned by fl.findObjectInDocByType()

I can't seem to use the info returned by fl.findObjectInDocByType() with fl.getDocumentDOM().selection.
I want to use document.setTextRectangle to re-size some text fields from an array generated using fl.findObjectInDocByType().
I can easily access all the textObject properties but since document.setTextRectangle requires a current selection, I am at a loss.
The example in the documentaion for setting selection is:
fl.getDocumentDOM().selection = fl.getDocumentDOM().getTimeline().layers[0].frames[0].elements[0];
fl.findObjectInDocByType() returns an array of objects with the attributes: (object.timeline, object.layer, object.frame, object.parent)
But these are objects, and don't have a property for array index numbers required by fl.getDocumentDOM().selection=...
var doc = fl.getDocumentDOM();
var textFieldArray = fl.findObjectInDocByType("text", doc);
for (var i=0; i < textFieldArray.length; i ++){
fnResizeTheTextField(textFieldArray[i]);
}
function fnResizeTheTextField(theTextField){
//force current selection to be theTextField
//doc.selection MUST be an array, so assign theTextField to an array...
var selectArray = new Array();
selectArray[0] = theTextField.obj;
var theTimeline =theTextField.timeline;
var theLayer =theTextField.layer;
var theFrame =theTextField.frame;
doc.currentTimeline =theTextField.timeline;
doc.selection = doc.getTimeline().theLayer.theFrame.selectArray;//error
//resize the text rectangle
doc.setTextRectangle({left:0, top:0, right:1000, bottom:1000});
}
}
Result: Error:doc.getTimeline().theLayer has no properties
It turns out, the ObjectFindAndSelect.jsfl script already contains a function specifically for this: fl.selectElement(). Much more elegant:
var doc = fl.getDocumentDOM();
// generate an array of elements of type "text"
var textFieldArray = fl.findObjectInDocByType("text", doc);
for (var i=0; i < textFieldArray.length; i ++){
fnResizeTheTextField(textFieldArray[i]);
}
function fnResizeTheTextField(theTextField){
//force current selection to be theTextField
fl.selectElement(theTextField,false);//enter 'edit mode' =false...
//resize the text rectangle
doc.setTextRectangle({left:0, top:0, right:1000, bottom:1000});
}
}
I found the answer. In order to select anything for a document level operation, you have to also make flash focus on the keyframe of that object.
so, if I loop through an array of objects created by fl.findObjectInDocByType(), I use this code to make flash focus on the object correctly:
function fnMakeFlashLookAt(theObject){
doc.currentTimeline =theObject.timeline;
doc.getTimeline().currentLayer =theObject.layer;
doc.getTimeline().currentFrame =theObject.frame;
}
this may not work on objects nested inside a symbol however.
I had a similar issue recently, and apparently all google results about setTextRectangle() direct us here. It's unbelievable how poorly documented jsfl is :)
If you need to use setTextRectangle() inside an library item that is not on stage, you need to open for edit the item first.
Here's the code that solved my problem:
library.selectItem(libraryItemName);
doc.selection = [tf];//where tf is the reference to textfield we need to edit
doc.library.editItem(libraryItemName);
doc.setTextRectangle({left:l, top:t, right:r, bottom:b});
doc.selectNone();
If you have a better working solution, please post. I hope it saves somebody's time. Good luck!