IV. Using a Text File as a Database
Opening and using a text file, using filters, using history states, copy/pasting
_businessCards.js

Download .js code file PS7 / CS
(right click and 'save as')


[ By the end of the script you'll have created 20 business cards]

This script will show you that we can use a database in Photoshop to create dynamically driven graphics. With this scripts we will be doing a set of 20 business cards with each individual’s info taken from a text file. (now imagine you have a huge company of 2000 people – that would be appreciated….).

I’ve decided that out of these 20 people there will be 3 different groups (we’ll call them Engineering, Service, Marketing). Notice here that I’ve typed the whole database by hand (well; actually the names were randomly generated with the help of this site, and phone/fax numbers were made by Javascript) – usually (and what I’ve done in the past) is take and Excel file containing all the data, parse the file in MS Word with ‘mailmerge’ to create my simple text file. I’ll provide help if anyone ever needs too – but this isn’t Photoshop scripting so I’ll stay out of it for now.

Note

In this example script, I made the business card template beforehand, then used scripting to fill in the info. Just because Scripting uses functions, loops and all that does not mean you are limited to do that. In this particular example, scripting is no more than an assistant; you do the design and PS does the repetitive task.
> The design PSD file is available here (PS 7)

Now first of all here is our database (as I said previously it could/should be dynamically filled, first because it saves time and secondly because there is a format to respect - see 'tip' below to see why). The text below is an excerpt from the database, take the one from the text file to make the script work.

Download the full text database
(right click and 'save as')

** This is the business card simple text file (and database)
** the beginning of the info MUST be on the 5th line otherwise the script will read the 4 first lines and screw up everything)
** 1 = engineering, 2 = services, 3 = marketing

Neil Shuford
1
377 624 5917
656 970 1459

Erik Rachel
3
492 313 1236
912 161 3442...

 

...Kelly Roane
3
835 407 9419
712 703 6994

 

Tip

If you take a look at the text file, you’ll notice that similar information are on the similar line (i.e. ‘pattern’ – you must make sure that your file is in order.)
In our example database here's the pattern:

Name (first name, blank, last name)
Specialty (1, 2 or 3 corresponding to engineer, services or marketing)
Phone number
Fax number
Blank Line (we'll have to tell the script to 'skip' that line)

Which means that if I look at a name, I should see another name 5 lines below, and so on. However you can make you script recognize the info, but that takes more coding. You could for example use that Javascript methods 'indexOf()','slice()','split()' and so on. Check this site for more info on string methods.


i. Code (explained below)

if(documents.length==0){
   alert("You need to have your template business card open (-businessCardTemplate.psd-)")
}else{

   function capitalizeLastName(inputName){
      var newNameArray = inputName.split(" ");
      var outPutName = newNameArray[0]+" "+newNameArray[1].toUpperCase();
      return outPutName;
   }

   function makeEmail(inputName){
      inputName = inputName.toLowerCase();
      var outPutName = inputName.replace(" ",".")+"@kfagency.com";
      return outPutName;
   }

   var defaultRulerUnits = preferences.rulerUnits;
   preferences.rulerUnits = Units.INCHES;

   var bizTemplate = activeDocument;
   var newDoc = documents.add(3.5, 2, 150.0, "BusCars Compilation",DocumentMode.RGB, DocumentFill.WHITE);

   activeDocument = bizTemplate;

   var dbText = new File("/G/db.txt");
   dbText.open ('r');

   var jobPositionArray = ["engineering","services","marketing"];
   var globalArray = new Array(20);

   for(a=0;a<=19;a++){
      globalArray[a] = new Array(4);
   }

      for(a=1;a<=4;a++){
         dbText.readln();
   }
   for(a=1;a<=20;a++){
      for(b=1;b<=4;b++){
         globalArray[a-1][b-1] = dbText.readln();
       }
      dbText.readln();
   }

   for(a=1;a<=20;a++){
   var tempName = globalArray[a-1][0];
   var emailAddress = makeEmail(tempName);
   tempName = capitalizeLastName(tempName);

   if(tempName.length >= 15){
   bizTemplate.layers[0].textItem.size = 7.5
   
    }

   bizTemplate.layers[0].textItem.contents = tempName;
   bizTemplate.layers[1].textItem.contents = "KFA "+jobPositionArray[globalArray[a-1][1]-1];
   bizTemplate.layers[2].textItem.contents = "Tel: "+globalArray[a-1][2]+"\u000DFax: "+globalArray[a-1][3];
   bizTemplate.layers[3].textItem.contents = emailAddress;
   bizTemplate.layers[4].textItem.contents = bizTemplate.layers[4].textItem.contents + " " + a;

   bizTemplate.flatten();
   bizTemplate.layers[0].applyAddNoise(1.00,NoiseDistribution.UNIFORM,0);
   bizTemplate.selection.selectAll();
   bizTemplate.selection.copy();

   activeDocument = newDoc;
   newDoc.paste();
   newDoc.layers[0].name = tempName;

   activeDocument = bizTemplate
   bizTemplate.activeHistoryState = bizTemplate.historyStates[0]
   }

}

preferences.rulerUnits = defaultRulerUnits;

 

ii. Code explanation and syntax details

Here is the outline of what happens:

 

Notes

#1 - The reason I do not have individual save steps for each business card made but rather one big PSD with all the business cards is a) because it's more organized b) it's less coding. However you can easily change the code to make it save it the business card each time. You can alternatively use the sample code (which you will see later) of section VI.ii - saving each layer as an individual document...

#2 - The only reason I added a 'noise' filter in my steps is to show how you can use the filters, which is very simple. Check the Adobe reference documentation to see the proper syntax for calling the Photoshop filter (such ass 'applyAddNoise' or 'applyGaussianBlur' etc..) or use the Script Listener (covered in section V - Non-Documented Functions: Using the Script Listener )

#3 - I have not written this script right from scratch to end without testing it, modifying it, using the orignal business card file. Moreover, the script was written only for that specific file; for example the document I create in the script is 3.5 * 2 inches at 150 dpi, same as my original business card. Change that and everything else to your needs (if you need to print for example I recommend using 300 dpi !). And remember, the more time you take to create a smooth running script, the less time it will take when you'll have to automate it.

And now, onto the code.

The first thing the script does is check if a document is open (namely it should be our business card) with 'if(documents.length==0)' . It doesn't check however if you have opened the proper document (ie with the same number of text layers and with the proper size). This is just intended for those who use the script and forget about opening up the original business card file.

The next steps are functions. In Javascript you can create functions to save coding for later on. Here the two functions declared ('capitalizeLastName(var)' and 'makeEmail(var)') are specific to our script; the first one will capitalize the last name ("John Doe" becomes "John DOE") the second one will make the email adress of our fake business agency ("John Doe" becomes "john.does@kfagency.com").

I'll explain how the first function works, and you'll guess the second. If you look at the rest code (the line where we call the first function) you'll see 'tempName = capitalizeLastName(tempName);'. Here we have a string variable 'tempName' (its value will be "John Doe", which is the type of name data we would find in our database) we assign to 'tempName' its a new value, which is the one it will have once its been through our first function. Notice our function is 'capitalizeLastName(inputName)' the variable we passed was 'tempName'; this is why 'tempName' is now 'inputName' (and 'inputName' has for value "John Doe").

'var newNameArray = inputName.split(" ");' creates an array of words, it splits our string according to a character (here the space " " character). It means that at this moment 'newNameArray' has for value ["John","Doe"].

'var outPutName = newNameArray[0]+" "+newNameArray[1].toUpperCase();'. Here we create a new variable which is made of the first item of the array ("John"), a space character, and the uppercase version of the second item of the array ("DOE"). "John Doe" has become "John DOE".
But the function is limited. If for instance the variable was "James Patrick Burbank", the function would return "James PATRICK" and not "James Patrick BURBANK". You could solve this problem with a few more lines of code, but since our database does not contain such name, we don't need to.

'return outPutName' is necessary to 'return' a value from the function. Since we used earlier 'tempName = capitalizeLastName(tempName)' well actually here 'tempName' becomes 'outputName' (and if you remember correctly we fetched 'outputName' initially from the first value of 'tempName' that was turned into a capitalized last name).
The second function to turn the name into an email works a bit the same way, except that we turn the spaces (" ") into dots ("."), lowercase the whole name and add "@kfagency.com".. That way "John Doe" becomes "john.doe@kfagency.com".

The steps that follow you should know about, we create a new document (note that it's the same size as our orignal business card template, change that according to your needs).
We then turn the business card template into the activeDocument, because it's the one we're going to need to work with.

The next steps are the central piece of the script; opening up the text file: 'var dbText = new File("/G/db.txt")' and 'dbText.open ('r')' as you see we will now refer to our text file as 'dbText'. We opened the file in read ('r') mode, but you can open it up in write mode ('w') and actually create your own text files. It could become quite handy ! Note here that even though we declared the new file, we still needed to open it with 'dbText.open ('r')'. (Mac users might also have issues with the file location, here the file is G:\db.txt for PCs and I guess desktop>g>db.txt for Macs).

The next step set up our database so we can fill it up with info. Now we have 20 employee, each with four info: Name, Job Position, Fax & Telephone; hence the creation of a first 'big' array of 20 items, and then the loop which fills each item with a new array of 4 items... This means that name will always be located on position 'globalArray[x][0]', job positions will always be located in positions 'globalArray[x][1]' and so on (remember once again that arrays start on position 0, not 1).

Note

The next steps fetch the lines from the database and put them into our array. The only way I know to do this is by using 'textFile.readln()'. However I have not managed to find a way to read a specific line (ie read only line ten or so). And whenever you call the 'readln()' function, it automatically goes on to the next line.
Therefore, since we have the first four lines containing trivial info in our database, we'll have to call the 'readln()' four times before fetching the real data. What's more, since our database has blank lines which we do not need, we'll have to make 'readln()' appear for nothing at least once in the loop. Look at the code...

The note explains the following code:

for(a=1;a<=20;a++){
  for(b=1;b<=4;b++){
    globalArray[a-1][b-1] = dbText.readln();
  }
  dbText.readln();
}

The first loop go from 1 to 20, because we have 20 cards to do; then the second loop reads four lines of the text database and fill the array accordingly. We need to add and extra 'dbText.readln()' for nothing because after the four lines there is a blank line...

The array is now filled with all the data, we're going to do the filling work (which you should know by now...).

The new loop goes from 1 to 20, to create our 20 business cards. 'var tempName = globalArray[a-1][0]' fetches the name of the employee because we need it to do the capitalization work 'tempName' and the email adress 'emailAddress' (using the functions at the beginning of the script).
After having run the script once I noticed that some names were larger than the size of the business card, which is why the following code is used (reduces the font size of the 'name' text layer of the business cards when names are over 15 characters):

if(tempName.length >= 15){
  bizTemplate.layers[0].textItem.size = 7.5
}

The rest of the filling code then ('bizTemplate.layers[0].textItem.contents = tempName', etc...) is pretty self explanatory.

Note

If you have looked at the business card template closely you'll notice that I only have 1 text layer for both the phone and fax (because they use the same font).. Which means I need a code which tells Photoshop to write on a new line; the code is '\u000D'

If I write for example "Fax: xxx xxx xxxx\u000DPhone: xxx xxx xxxx" it will output:
  Fax: xxx xxx xxxx
  Phone: xxx xxx xxxx
Notice I didnt put any spaces before or after \u000D
(Note: this might not work on a Mac)

There is however in the filling process a step you might have not understood right away:

bizTemplate.layers[1].textItem.contents = "KFA "+jobPositionArray[globalArray[a-1][1]-1];

Well if you look at the beginning of the script we filled an array called 'jobPositionArray' with "Engineering","Services", and "Marketing". And if you look at the database, you'll see that for each employee corresponds a number (1, 2 or 3). Well this line of script takes the number from the globalArray for each employee, and looks for the corresponding string data in the 'jobPositionArray' (and remember the '-1' offsets are used because arrays start a item 0; 'jobPositionArray[1]' corresponds to "Services", not "Engineering"...)

We then flatten the document ('bizTemplate.flatten()') and the next step is to add noise to our layer. This step has no other purpose than to show you how to typically use filters. It's simply using 'applyAddNoise(1.00,NoiseDistribution.UNIFORM,0)' on layer 'bizTemplate.layers[0]'. The noise filter has three options: a) The amount of noise b) the noise distribution type ('UNIFORM' or 'GAUSSIAN') and c) whether it is monochromatic noise or not (boolean value ie 1 or 0). Now most filters work this way, and to find which options belong to what filter, use the Adobe documentation (the 'Javascript Reference'). If you can find the filter (for example it's not one made by Adobe), you'll learn how to use the script listener in the next step to find out about undocumented functions.

The next step is then to copy the flattened layer from our 'bizTemplate' document to the other document ('newDoc'), the archiving document.

Note: Copying Layers From/To documents

Copying/Pasting layers in scripting happen this way:

  • Select the document you want to copy from
  • Set a selection (usually selecting the whole canvas)
  • Copy
  • Make the target document the active document
  • Paste

So typically a copy paste code would look somhow like this:

activeDocument = sourceDocument;
sourceDocument.selection.selectAll();
sourceDocument.selection.copy();

activeDocument = targetDocument;
targetDocument.paste();

However that method always copies the currently active layer of 'sourceDocument'. So to copy the layer you want, you'll need to select your activeLayer first (with 'sourceDocument.activeLayer = sourceDocument.layers[x]').

If you use 'copy(true)' instead of 'copy()', Photoshop will copy the whole document as if it were flattened (it merges the visible elements). We could have used that code to omit the layers flattening code in our script.
Note: You cannot copy a text layer from/to a document, you'll need to flatten it first. (the workaround is to copy everything from the textItem object and recreate your text layer on the new document.

Since our document only has one layer left (the background layer) when we do the copying, we don't need to set an active layer. You should be able to understing the copy/paste code of the script by looking at the note above.
'newDoc.layers[0].name = tempName'
sets the name of the newly created layer (pasted layer) to that of the employee name (that we had used previously to fill in the text layers)

activeDocument = bizTemplate;
bizTemplate.activeHistoryState = bizTemplate.historyStates[0];

These last steps return to our original (bizTemplate) document and set it to how it was originally. Notice here we used the history states of your document, where 'historyStates[0]' is the state of the document when you opened it. You can also set a variable to be specific history state and come back to it later ! Use the code 'var myHistoryState = myDoc.activeHistoryState' and go to it later with 'myDoc.activeHistoryState = myHistoryState'

The last step of the script as you know now set back the original working units to whatever they were... hope this section covered things you had no idea about !

Onto the PS jibberish: Non-documented functions: using the script listener


 page 4 / 11