Copy link to clipboard
Copied
For an advertisement newspaper, I've constructed a script that reads a CSV file with information about the ads, their size and desired location.
The ads are imported and placed correctly, until a new page is created. The placed files keep on being added on the first page.
Is there a way to activated the newly added page, or am I doing something fundamentally wrong?
Disclaimer: I'm no developer, so the code might not be ideal. I hope the solution is near!
Thanks for taking time.
CSV
Page;AdName;Width(mm);Height(mm);X-value;Y-value
1;G26-3548-2x1.pdf;83.666;27;86.666;0
1;G26-0006-A.pdf;83.666;27;86.333;30
1;F40-0004.pdf;83.666;57;173.333;0
2;G03-1459.pdf;40.333;57;0;0
3;G25-3264.pdf;127;57;40.333;0
JS
function main() {
// Create a filter for CSV files
var csvFilter = function(f){return/\.csv$/i.test ( f.name ); },
// Open a dialog to import the CSV file
f = File.openDialog ("Please select the CSV File…", csvFilter, false),
docsData = [], csvSep = ";", csvContent, csvHeaders;
// If we have a CSV file
if ( !f ) return;
// Open it
f.open('r');
// And start reading
csvHeaders = f.readln().split(";");
// if there is no doc create one
if(app.documents.length < 1){
doc = app.documents.add();
// if there is one - use it
} else {
doc = app.activeDocument;
}
// Loop through the data
while (!f.eof){
// Read the line and split it
s = f.readln().split(";");
// Loop through the six columns
if ( s.length>=6 ) {
// What's in the CSV
var pageNumber = s[0];
var fileName = s[1];
var width = s[2];
var height = s[3];
var topLeftX = s[4];
var topLeftY = s[5];
// If the desired pagenumber is not the same as the documentpagenumber
if(app.activeWindow.activePage.name != pageNumber){
// Add a new page
np = doc.pages.add(LocationOptions.AT_END);
//np.activate();
}
// Draw rectangle in the right place
var rect = doc.pages.item(0).rectangles.add();
var gb = [topLeftY, topLeftX, (Number(topLeftY) + Number(height)), (Number(topLeftX) + Number(width))];
rect.geometricBounds = gb;
// Import the PDF and place it inside
var mydoc = app.activeDocument;
var myframe = mydoc.rectangles[0];
var img = myframe.place(new File("location/to/the/PDF/"+fileName));
}
}
};
main();
You shoud not be "happy when it works".
The problem when copying and pasting snippets of code without understanding them is that it usually does not work, and when it does work, it does not work reliably. You "write" the script, it works for the initial tests (which usually involve the same data you used to develop the script), you put it into production, and somewhere, somehow, in page 230 in a 600 pages document something goes wrong and everyting is messed up. Someone goes through the file, see
...Copy link to clipboard
Copied
Quite a few issues with your code there, but the big one that is causing you to have wrong results is in lines 60 to 63.
You are placing the pdf in the first rectangle of the active document (mydoc.rectangles[0]) instead of into the rectangle you added at line 55!
How many different scripts have you frankensteined in this? I count at least 3.
Copy link to clipboard
Copied
Caught! Indeed, it's all duct-taped together. Allthough I like things neat and performant on other fields, these are my first steps into javascript, so I'm happy when it works.
The rectangles that should appear on page 2 are also created on the first page, so I think it starts to go wrong on line 55. It makes sense what you say, but I've run out of google searches and I have no idea where to take from here.
Am I persuing the right way like this:
Line 50: I tried to activate the newly added page (Can't find anything that works)
Line 55: Should be: Draw the rectangle on the active page (Also no luck in finding a solution)
Thanks again, Vamitul, for your time.
Copy link to clipboard
Copied
You shoud not be "happy when it works".
The problem when copying and pasting snippets of code without understanding them is that it usually does not work, and when it does work, it does not work reliably. You "write" the script, it works for the initial tests (which usually involve the same data you used to develop the script), you put it into production, and somewhere, somehow, in page 230 in a 600 pages document something goes wrong and everyting is messed up. Someone goes through the file, sees that the first few oages are ok, then the next 100 pages are ok so ... ok... and the file gets to the printer then to market and you are suddenly in a big hole! It is exactly what happend to me when I had just started with scripting and "wrote" (as in copy-pasted a bunch of stuff I had no idea how it worked) a script that did some extensive text manipulations based mostly on GREP patterns. One of the things it was supposed to do was to remove all spaces inside specially marked anchored text frames. But, in some 1%-chance of happening circumstances bug, it also removed all the spaces in the parent story of the anchored frame. The company I worked for back then had to pay damages to the customer of over 45000 euros because documents diseminated and printed in about 20 different countries around the globe had 20-30 pages of text in them whithout any spaces in.
Now that the rant is over I'm gonna do something I very rarely do these days and give you the whole solution In the hope that you will actually learn how it works and use it to go forward.
First step is to read and understand the requirements: you need to read data from a CSV file and use that data to create and paginate a new document. You should already see two distinctive parts (READ and PAGINATE) connected by a common thread (DATA).
/*
First step is to read and understand the requirements: you need to read data from a CSV file and use that data to create and paginate a new document. You should already see two distinctive parts (READ and PAGINATE) connected by a common thread (DATA). So let's start with the data. There are many many ways to structure your data (some better than others), but for this a simple array of objects somewhat mimicking the csv structure should suffice:
data=[
{
page:Number,
adFile:String,
geometricBounds:[Number,Number,Number,Number]
}]
Next up is reading data:
*/
(function() { //IIFE is always better than "main" crap: https://stackoverflow.com/questions/8228281/what-is-the-function-construct-in-javascript
function readCSV() {
// Open a dialog to import the CSV file
var f = File.openDialog("Please select the CSV File…", function(f) {
return /\.csv$/i.test(f.name);
}); //do you understand how that inline function works?
if (!f) {
return; //the user hit cancel on the dialog. Nothing to do!
}
// we need some more variables and it's best to declare them early.
var line, //the current csv line entry
ret = [], //the return array that will contain all of the data
lineIndex = 1, //the current csv line number, used for error reporting. We start from "1" because that is how non-programmers (and some Pascal and older BASIC programmers) count.
gb, //the geometric bounds (as used by InDesign of the csv entry)
obj; //the full entry object. Just to make code more readable.
f.open('r');
//first line is the headers, we ignore them.
line = f.readln();
while (!f.eof) { //this means while we havenot reached the End Of File
line = f.readln().split(';'); //walk and chew gum at the same time: read a line of text from the file and convert it to an array
lineIndex++; //increment the line index, this can be read as lineIndex=lineIndex+1;
if (line.length !== 6) {
// this entry in the csv is wrong. you can either ingore it or quit the whole thing. your choice, comment and uncomment as needed.
throw 'Invalid csv data on line ' + lineIndex + ' (' + line.join(';') + ')'; //can you explain what this does?
//other choice:
//continue;
}
//csv entry is: Page;AdName;Width(mm);Height(mm);X-value;Y-value
//canonic format of geometric bounds is [y0,x0,y1,x1]:
gb = [
parseFloat(line[5]), //Y-value
parseFloat(line[4]), //X-value
parseFloat(line[5]) + parseFloat(line[3]), //y+height
parseFloat(line[4]) + parseFloat(line[2]), //x+width
];
obj = {
page: parseInt(line[0]) - 1, //remember that programmers count from 0?
adFile: File("/Users/vlad/Downloads/" + line[1]),
geometricBounds: gb
};
//add the object to the return array
ret.push(obj);
}
//at this point we have all our data. But because the program works on a page by page basis it is good to sort it as such. See https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
return ret.sort(function(a, b) {
return a.page - b.page;
});
r }
/* we now need a function that will paginate the data*/
function paginate(data) {
//always declare your variables!
var doc, page, frame;
doc = app.documents.length ? app.documents[0] : app.documents.add();
/*see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Conditional_Operator for the above statement. It is a shorthandend "if": if there are documents (documents.length>=0, use the front-most one (documents[0]), otherwise create a new one */
//because the data is in mm let's make sure the script will use mm as a measurement (but i strongly recommend you start dealing with point asap!)
app.scriptPreferences.measurementUnit=MeasurementUnits.MILLIMETERS;
//first let's make sure our document has enough pages. Since we have the data storted knowing how many pages we need is easy. Just look at the last data entry. But we do need to make sure we don't acidentally remove pages!!
doc.documentPreferences.pagesPerDocument = Math.max(doc.pages.length, data[data.length-1].page + 1);
/*things to note in the above statement: by using Math.max we are getting the bigger number between the number of currently existing nr of pages in the document and the biggest page number in our data (+1 because .. see above about counting)*/
//now let's go through out data. To keep code simpler we'll use another function "paginateData" (see below)
for (var i = 0; i < data.length; i++) {
page = doc.pages[data.page];
paginateData(page, data);
}
}
//this function will paginate one entry on a set page
function paginateData(page, entry) {
var rect = page.rectangles.add();
rect.geometricBounds = entry.geometricBounds;
//what if the ad file is not available? Let's convert it to text and add a message!
if (!entry.adFile.exists) {
rect.contentType = ContentType.TEXT_TYPE; // force to text frame
rect = page.textFrames.itemByID(rect.id);
rect.contents = 'File not found: ' + entry.adFile;
return;
}
rect.place(entry.adFile);
}
/*now let's tie everything up togheter. Not that much to do */
var data = readCSV();
if (!data) { //user canceled
return;
}
paginate(data);
alert('All done!\nYou can go outside and play now!');
}());
Copy link to clipboard
Copied
Hi Vamitul,
I hope, that this time my post will make it to the forums. it's my third try 😞
For a couple of days I see errors like:
System Error
We're sorry but a serious error has occurred in the system.
when trying to access posts or to log in. That's a Forum-Software-Jive bug not resolved yet.
Ok. Here my comment on your code:
Rather than changing app.scriptPreferences.measurementUnit I would change the document's viewPreferences :
doc.viewPreferences.properties =
{
horizontalMeasurementUnits : MeasurementUnits.MILLIMETERS ,
verticalMeasurementUnits : MeasurementUnits.MILLIMETERS ,
rulerOrigin : RulerOrigin.PAGE_ORIGIN
};
For predictable results also with facing pages documents I would reset rulerOrigin always to RulerOrigin.PAGE_ORIGIN when working with geometric bounds that are meant for individual pages.
And additionally to that one should also reset the zero point.
doc.zeroPoint = [ 0,0 ] ;
Regards,
Uwe
Copy link to clipboard
Copied
Hi Uwe,
The forum software is crapping on me also. Wiping all adobe related cookies seems to have helped.
So, about the measurements: app.scriptPreferences.measurementUnit will trump the document's measurement units unless it's set to AUTO. And, on of my systems it is set to AUTO (because of consistency and other stuff), so I feel it's very important to set it up front. And once you do, you don't need to worry about changing the document's settings and the switching them back. I completely agree with the rulers and zero point, but that's a bit advanced for the OP.
Copy link to clipboard
Copied
Hm, yes…
I always hesitate to set app.scriptPreferences.measurementUnit other than to points ( and working on with points doing conversions with input units using UnitValue ). But that's a discussion on its own, I think.
About your comment, that setting rulers and zero point:
It might be a bit advanced for the OP, but it is absolutely necessary that he understands why this is needed.
See his comment:
… A thing I noticed when testing is that pages 3, 5, 7, ... stayed empty, but I've learned that the zero point is on the page before it. I can easily fix that by updating the coordinates for the ads on those pages. …
@Yannick, do not update the coordinates in your CSV file!
No change of the coordinates is needed, if rulerOrigin will be changed to RulerOrigin.PAGE_ORIGIN.
( Here we have that case: A facing pages document. )
Just add two lines before adding the rectangles to get consistent results:
doc.viewPreferences.rulerOrigin = RulerOrigin.PAGE_ORIGIN;
doc.zeroPoint = [ 0,0 ];
Regards,
Uwe
Copy link to clipboard
Copied
Hey Uwe,
Thanks for the addition. It works like a charm now.
Thanks a lot guys, this script will save us hours each week, as we handplace all ads for now.
Copy link to clipboard
Copied
Wow Vamitul,
I've tested and read the script and it works perfectly. Thanks a million times for going the extra mile! I'm overwhelmed!
Reading the last line to get the number of pages, is clever. Also the line where you compare the number of current pages with the desired pages is also very nice. Thinking ahead.
I've learned a whole bunch, but I'm pretty sure I couldn't come up with this by my own. (I've never done anything in javascript before, but I know a little(!) bit of PHP so I knew how to make a loop etc.)
About line 34: I'd think it would throw an error when the line in the CSV isn't 6 columns. I've tested this, but I got a JS errormessage instead.
A thing I noticed when testing is that pages 3, 5, 7, ... stayed empty, but I've learned that the zero point is on the page before it. I can easily fix that by updating the coordinates for the ads on those pages.
I also love the 'errorhandling'. I will try to add these 'not found' errormessages to the alert in the end so I get a list of them when it finished.
Thanks again, this is amazing.
Yannick