|
PhotoShop
Scripting
by
mlk : Hot Summer Month of 2004
IV.
Using a Text File as a Database
Opening and using a
text file, using filters, using history states, copy/pasting
|
 |
Download .js code file
PS7
/ CS
(right click and 'save as') |
|
.jpg)
[ 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)
[ click to see
larger image ]
|
|
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.
** 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:
-
Photoshop checks if
our original business card is open
-
It creates a new document
which will gather all the different business cards
-
Goes back to the original
business card
-
Creates a new array
(made of 20 smaller arrays) to receive the textfile string variables
-
Opens the database
text file
-
Fills in the array
with info
-
Loops 20 times
:
-
fills up the template
business card with data from the array
-
flattens document,
uses a noise filter, copies layer to our compilation of business cards
document
-
goes back to the
original business card and sets it to its original state.
|
|
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 of
11 |
 |
|