<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-15837111</id><updated>2012-01-31T14:22:45.949-08:00</updated><title type='text'>JavaScripting InDesign</title><subtitle type='html'>A sporadic discussion of my scripting exploits using JavaScript to drive Adobe InDesign.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default?start-index=101&amp;max-results=100'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>137</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-15837111.post-178095382042206211</id><published>2012-01-09T09:10:00.000-08:00</published><updated>2012-01-09T09:11:55.649-08:00</updated><title type='text'>Syntax Highlighting Here</title><content type='html'>&lt;div&gt;One of the reasons I took a long hiatus at this blog was because I learned how to do syntax highlighting in my posts and it was such a laborious process that I quickly tired of it because posting became such a daunting task.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;But scripts are supposed to make daunting tasks more manageable, possibly even eliminating the daunt altogether. That's the first time I've ever used "daunt" as a noun like that. Probably not good English.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If you've ever looked at ESTK's preferences, you'll see that it offers about a million syntax highlighting controls. There's no way I want to duplicate that lot. My sights are set much lower. If I can highlight reserved words in blue, strings in red, and comments in green, I'll be happy.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The interesting thing is that I've already solved this problem in InDesign itself. When I want to showJS code in an InDesign document, I use a paragraph style with a bunch of GREP styles. I can't do that here, but perhaps I can use similar GREPs to get the right tags around the right text to achieve the desired effect.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;And with that, the thought occurs that perhaps I can write a script that pulls the GREP search values out of the paragraph style so I don't have to maintain them in more than one place. Sounds like a fun project. Let's give it a shot.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;More to come ...&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-178095382042206211?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/178095382042206211/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=178095382042206211' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/178095382042206211'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/178095382042206211'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2012/01/syntax-highlighting-here.html' title='Syntax Highlighting Here'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-8933810540755695588</id><published>2012-01-07T04:35:00.000-08:00</published><updated>2012-01-07T05:03:34.909-08:00</updated><title type='text'>PopTabUnleashed</title><content type='html'>When InDesign CS4 hit the streets, I thought that PopTabFmClip was obsolete. I said so on my web site and basically abandoned it. But it turns out that there's one thing that PopTab did that isn't available in native InDesign. If you want to copy the content of one InDesign table into another (target) table without losing the formatting of the target table, then PopTab is the answer.&lt;br /&gt;&lt;br /&gt;So, over the holidays, I decided to rework PopTab to make it available for users of CS4 and later. And I also decided to release it in source form so people are free to read its code if they wish. I'll examine the code here in this blog too to explain the techniques it uses. But the purpose of this blog entry is to announce the availability of PopTabUnleashed -- there's a link at the left of this page you can use to download it.&lt;br /&gt;&lt;br /&gt;Here's how the script describes itself in the introductory comment at the head of its code:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;This script is provided as-is. Use at your own risk.&lt;br /&gt;&lt;br /&gt;The script populates a table, or part of a table, with the text/values on the clipboard without changing the formatting/styling of the target table. The effects of running the script can be undone using Edit/Undo Populate Table.&lt;br /&gt;&lt;br /&gt;The content of the clipboard is expected to be a table or part of a table. It can be a selection from Excel or any other application that exports table data, but you hardly need this script to support external applications because since CS4, InDesign provides this functionality: just paste into an existing table.&lt;br /&gt;&lt;br /&gt;PopTab comes into its own when you're copying from one InDesign table to another. That is not natively supported because the formatting of the source table is included when you paste.&lt;br /&gt;&lt;br /&gt;The selection is required to be in a table. It can be text in a cell, a cell, a range of cells, or a complete table. Selecting text in a cell is regarded as the same as selecting the cell.&lt;/blockquote&gt;As always, just move the script into your Scripts Panel folder so it is visible in your Scripts Panel. To use it, copy appropriate content to the clipboard, make a selection in your target table and then trigger the script to move the clipboard content into the table.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-8933810540755695588?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/8933810540755695588/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=8933810540755695588' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/8933810540755695588'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/8933810540755695588'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2012/01/poptabunleashed.html' title='PopTabUnleashed'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-8874174562978428809</id><published>2008-10-18T16:10:00.000-07:00</published><updated>2008-10-18T16:20:50.607-07:00</updated><title type='text'>CS4 Change</title><content type='html'>Now that CS4 is with us, it's time to start a list of changes (as opposed to out-and-out new features) that might affect existing scripts. The first one I ran into affected my &lt;a href="http://www.pdsassoc.com/index.php?Nav=solnssub&amp;Ban=WrapNudger&amp;Info=PDSproducts/WrapNudger/index.php"&gt;WrapNudger product&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The TextWrapPreferences property previously known as textWrapType is now called textWrapMode. This change affected the function I use in WrapNudger to determine whether or not the selection has a text wrap. Here's the revised version of the function which shows how I made the script work with either CS3 (InDesign version 5) or CS4 (InDesign version 6):&lt;br /&gt;&lt;br /&gt;&lt;div align="left"&gt;&lt;strong&gt;&lt;font class="jsres"&gt;function&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; checkSelection() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;if&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (app.documents.length == 0 || &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.selection.length == 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert(&lt;/font&gt;&lt;font class="jsstr"&gt;"Please select an object with a text wrap."&lt;/font&gt;&lt;font class="jscod"&gt;); &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;return&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;false&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;if&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (Number(app.version.split(&lt;/font&gt;&lt;font class="jsstr"&gt;"."&lt;/font&gt;&lt;font class="jscod"&gt;)[0]) &amp;gt; 5) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;if&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (app.selection.length == 1 &amp;amp;&amp;amp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(!(app.selection[0].hasOwnProperty(&lt;/font&gt;&lt;font class="jsstr"&gt;"textWrapPreferences"&lt;/font&gt;&lt;font class="jscod"&gt;)) ||&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.selection[0].textWrapPreferences.textWrapMode == &lt;/font&gt;&lt;font class="jscod"&gt;TextWrapModes&lt;/font&gt;&lt;font class="jscod"&gt;.none)) {&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert(&lt;/font&gt;&lt;font class="jsstr"&gt;"Selected item must have a text wrap."&lt;/font&gt;&lt;font class="jscod"&gt;); &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;return&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;false&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;} &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;else&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;if&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (app.selection.length == 1 &amp;amp;&amp;amp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;(!(app.selection[0].hasOwnProperty(&lt;/font&gt;&lt;font class="jsstr"&gt;"textWrapPreferences"&lt;/font&gt;&lt;font class="jscod"&gt;)) ||&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.selection[0].textWrapPreferences.textWrapType == &lt;/font&gt;&lt;font class="jscod"&gt;TextWrapTypes&lt;/font&gt;&lt;font class="jscod"&gt;.none)) {&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert(&lt;/font&gt;&lt;font class="jsstr"&gt;"Selected item must have a text wrap."&lt;/font&gt;&lt;font class="jscod"&gt;); &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;return&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;false&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;return&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;true&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt;;&lt;br /&gt;}&lt;/font&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-8874174562978428809?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/8874174562978428809/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=8874174562978428809' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/8874174562978428809'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/8874174562978428809'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2008/10/cs4-change.html' title='CS4 Change'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-259218706663146604</id><published>2008-10-15T08:34:00.000-07:00</published><updated>2008-10-15T09:05:33.035-07:00</updated><title type='text'>Story to Layer</title><content type='html'>I was working on a job this morning (actually in CS4, which is released for download today) when I realized that the text frame I was looking at was on the wrong layer. Indeed, all the frames in my story, except for the first few, were on the wrong layer. This must have been the result of my leaving that wrong layer active when I saved the template. I've now fixed the main script that created this situation, but to solve the issue in the document on which I was working, I wrote this quick script:&lt;br /&gt;&lt;br /&gt;&lt;div align="left"&gt;&lt;font class="jscom"&gt;//DESCRIPTION: Move all text frames of story to same layer&lt;br /&gt;/*&lt;br /&gt; This script assumes that the first frame of the story is on the right layer&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="jscod"&gt;(&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;function&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt;() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;if&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (app.documents.length &amp;gt; 0 &amp;amp;&amp;amp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.selection.length &amp;gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; aSel = app.selection[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;if&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (!aSel.hasOwnProperty(&lt;/font&gt;&lt;font class="jsstr"&gt;"parentStory"&lt;/font&gt;&lt;font class="jscod"&gt;)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert(&lt;/font&gt;&lt;font class="jsstr"&gt;"Selection has no parent story"&lt;/font&gt;&lt;font class="jscod"&gt;); &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;return&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; aStory = aSel.parentStory;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; aLayer = aStory.textContainers[0].itemLayer;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;for&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; j = aStory.textContainers.length - 1; j &amp;gt; 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aStory.textContainers[j].itemLayer = aLayer;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}())&lt;/font&gt;&lt;/div&gt;&lt;br /&gt;The script is contained within an anonymous function which starts out by checking that there is a selection. If there is, it confirms that the selection has a parentStory property, and if so, it moves all the text frames that constitute that story (textContainers in CS3 and CS4) on to the same layer as the first frame of the story.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-259218706663146604?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/259218706663146604/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=259218706663146604' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/259218706663146604'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/259218706663146604'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2008/10/story-to-layer.html' title='Story to Layer'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-1188698837579654737</id><published>2008-07-20T06:49:00.000-07:00</published><updated>2008-07-20T07:08:53.757-07:00</updated><title type='text'>Orthogonal Graphic Lines</title><content type='html'>I was called upon the other day to write a script that required that the selection be an orthogonal graphic line. That is, one that was either perfectly horizontal or perfectly vertical.&lt;br /&gt;&lt;br /&gt;A graphic line is not necessarily straight. It appears to be any object that has exactly two points, no matter how it was drawn. For example, anything drawn with the Line Tool is a graphic line; any two-point path drawn with the Pen Tool is a graphic line; any ploygon or rectangle that has been reduced to two points by deleting points from its original shape is a graphic line.&lt;br /&gt;&lt;br /&gt;This means that finding an "orthogonal" line is not as simple a looking at the coordinates of a graphic line and checking to see if the two x-values or two y-values are the same as each other.&lt;br /&gt;&lt;br /&gt;Here's a framework:&lt;br /&gt;&lt;br /&gt;&lt;font class="jscom"&gt;//DESCRIPTION: Framework for operating on selected orthogonal lines&lt;br /&gt;&lt;/font&gt;&lt;font class="jscod"&gt;(&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;function&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt;() {&lt;br /&gt;&lt;/font&gt;&lt;font class="jscod"&gt;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;if&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (app.documents.length &amp;gt; 0) {&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;for&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; j = app.documents[0].selection.length - 1; j &amp;gt;= 0; j--) {&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;if&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (app.documents[0].selection[j] &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;instanceof&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; &lt;/font&gt;&lt;font class="jscod"&gt;GraphicLine&lt;/font&gt;&lt;font class="jscod"&gt; &amp;amp;&amp;amp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isOrthogonal(app.documents[0].selection[j])) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;processOrthogLine(app.documents[0].selection[j]);&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="jscod"&gt;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;function&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; processOrthogLine(theLine) {&lt;br /&gt;&lt;/font&gt;&lt;font class="jscod"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; delta = 0.00001;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; vert = Math.abs(theLine.paths[0].entirePath[0][1] - theLine.paths[0].entirePath[1][1]) &amp;gt; delta &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert(&lt;/font&gt;&lt;font class="jsstr"&gt;"The selected line is "&lt;/font&gt;&lt;font class="jscod"&gt; + (vert ? &lt;/font&gt;&lt;font class="jsstr"&gt;"vertical."&lt;/font&gt;&lt;font class="jscod"&gt; : &lt;/font&gt;&lt;font class="jsstr"&gt;"horizontal."&lt;/font&gt;&lt;font class="jscod"&gt;));&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="jscod"&gt;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;function&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; isOrthogonal(gl) {&lt;br /&gt;&lt;/font&gt;&lt;font class="jscom"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// By definition, a graphicLine has just one path with two pathPoints&lt;br /&gt;&lt;/font&gt;&lt;font class="jscod"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; myPath = gl.paths[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; entirePath = myPath.entirePath;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;if&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (entirePath[0].length &amp;gt; 2 || entirePath[1].length &amp;gt; 2) {&lt;br /&gt;&lt;/font&gt;&lt;font class="jscom"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// one or other of the path points has control handles&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// thus, the line is not straight&lt;br /&gt;&lt;/font&gt;&lt;font class="jscod"&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;return&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;false&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; delta = 0.00001;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;if&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; (Math.abs(entirePath[0][0] - entirePath[1][0]) &amp;lt; delta ||&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Math.abs(entirePath[0][1] - entirePath[1][1]) &amp;lt; delta) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;return&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;true&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;return&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; &lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;false&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt;;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}())&lt;br&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;It is possible that lines drawn with carefully positioned control handles could result in a straight line, but we make the assumption that the lines we're looking for have no control handles.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-1188698837579654737?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/1188698837579654737/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=1188698837579654737' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/1188698837579654737'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/1188698837579654737'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2008/07/orthogonal-graphic-lines.html' title='Orthogonal Graphic Lines'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-8078314864167465005</id><published>2008-07-19T10:04:00.000-07:00</published><updated>2008-07-20T06:34:55.917-07:00</updated><title type='text'>Align Left Edge</title><content type='html'>&lt;div align="left"&gt;&lt;font class="v1"&gt;Over at InDesignSecrets.com, the topic arose about how to get rid of the space that sometimes appears at the left of the first capital letter of a paragraph. This is much more noticeable in some fonts than others. See: &lt;a href="http://indesignsecrets.com/removing-space-along-left-edge.php"&gt;Removing Space Along the Left Edge of Text&lt;/a&gt;. The discussion gave me an idea for a script that would do the job in CS3 for paragraphs that don't have a drop-cap (for those that do, the Align Left Edge option is available in the Drop-cap dialog). That led to my posting this (slightly edited for posting here):&lt;br /&gt;&lt;br /&gt;Here’s a proof of concept script deeply annotated. It assumes that the insertion point is in some paragraph and that temporarily making the first character into a drop cap won’t push the paragraph overset.&lt;br /&gt;&lt;br /&gt;First, we need references to the paragraph and its first character:&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; myPara = app.selection[0].paragraphs[0];&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; myChar = myPara.characters[0];&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;Now we need to know the width of the character. I wrote a simple function which I called getWidth() to do this that returns the distance between the horizontalOffsets on left and right of the character.&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; firstCharWidth = getWidth(myChar);&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;But, as we know, the Align Left Edge feature of &lt;/font&gt;&lt;font class="v1"&gt;InDesign&lt;/font&gt;&lt;font class="v1"&gt; only works if you have a drop cap and that changes the size of the character to an unknown point size. But still, we can use relative values, so the next two lines make the first character into a drop cap:&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;font class="jscod"&gt;myPara.dropCapCharacters = 1;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;font class="jscod"&gt;myPara.dropCapLines = 2;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;And this cockamamie next line switches on Align Left Edge — the dropcapDetail property not only uses different capitalization from the other dropCap related properties, it works like a two-bit bitmap. The right-most bit controls Align Left Edge, the left-most controls Scale for Descenders. So, it has four values:&lt;br /&gt;&lt;br /&gt;0 means both off&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;1 means ALE on, &lt;/font&gt;&lt;font class="v1"&gt;SfD&lt;/font&gt;&lt;font class="v1"&gt; off&lt;br /&gt;2 means &lt;/font&gt;&lt;font class="v1"&gt;SfD&lt;/font&gt;&lt;font class="v1"&gt; on, ALE off&lt;br /&gt;3 means both on&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;font class="jscod"&gt;myPara.dropcapDetail = 1;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;br /&gt;&lt;font class="v1"&gt;Now we get the character width again, this time we’re getting the size of the dropcap at its unknown point size.&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; largeCharWidth = getWidth(myChar);&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;And, having align left edge switched on, we record where the first insertion point is:&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; largeAdjLeft = myChar.horizontalOffset;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;Now we switch it off and get the revised position:&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;font class="jscod"&gt;myPara.dropcapDetail = 0;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; largeBaseLeft = myChar.horizontalOffset;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;This allows us to express the width of the left side bearing as a fraction of the width of the character (remember, way back at the start we got the original width):&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; leftSideBearing = (largeBaseLeft - largeAdjLeft)/largeCharWidth;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;So we can work out how much we need to shift the original character left by multiplying this fraction by the original widith:&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; neededShift = leftSideBearing * firstCharWidth;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;We have all the information we need. So, let’s switch off the drop cap:&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;font class="jscod"&gt;myPara.dropCapLines = 1;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;We’re going to add a hair space to the text to give us something to kern back over. But we have to bear in mind that kerning is a relative value. The absolute amount of a kern varies according to the size of the type. What we know is that 1000 kerning units is a distance of 1 em which, for 12 point type, is a value of 12 points. So, we need to normalize the kerning amount to what it would be if the type were at 12 points. And then add the width of a 12-point hair space (24 to the em):&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;strong&gt;&lt;font class="jsres"&gt;var&lt;/font&gt;&lt;/strong&gt;&lt;font class="jscod"&gt; normalizedKernAmount = (neededShift * 12/myChar.pointSize) + .5;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;Now we insert the hair space:&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;font class="jscod"&gt;myPara.insertionPoints[0].contents = &lt;/font&gt;&lt;font class="jscod"&gt;SpecialCharacters&lt;/font&gt;&lt;font class="jscod"&gt;.hairSpace;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;And, finally, we set the kerning value of the insertion point between the hair space and the first character, multiplying by -1000/12 to (a) make it a negative kern and (b) to convert the value in points to a fraction of 1000:&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/font&gt;&lt;font class="jscod"&gt;myPara.insertionPoints[1].kerningValue = normalizedKernAmount*-1000/12;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;font class="v1"&gt;There you go: Align Left Edge for the first line of any paragraph.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/font&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-8078314864167465005?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/8078314864167465005/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=8078314864167465005' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/8078314864167465005'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/8078314864167465005'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2008/07/align-left-edge.html' title='Align Left Edge'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-6850769693879381734</id><published>2008-07-19T06:26:00.000-07:00</published><updated>2008-07-19T06:48:12.051-07:00</updated><title type='text'>Delete "Empty" Pages</title><content type='html'>&lt;div align="left"&gt;&lt;span class="v2"&gt;I often get into the state where a run of pages is dedicated to a particular story and because of editing the last pages of the story are now effectively empty; that is, the text frames on those pages are empty. This script seeks those pages and deletes them. So, the script must start with a framework that requires the selection to have a parentStory property:&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class="v3"&gt;//DESCRIPTION: Delete "empty" pages at end of selected story&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class="v2"&gt;(&lt;/span&gt;&lt;strong&gt;&lt;span class="v5"&gt;function&lt;/span&gt;&lt;/strong&gt;&lt;span class="v2"&gt;() {&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&lt;strong&gt;&lt;span class="v5"&gt;if&lt;/span&gt;&lt;/strong&gt;&lt;span class="v2"&gt; (app.documents.length &amp;gt; 0 &amp;amp;&amp;amp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.selection.length == 1 &amp;amp;&amp;amp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.selection[0].hasOwnProperty(&lt;/span&gt;&lt;span class="v6"&gt;"parentStory"&lt;/span&gt;&lt;span class="v2"&gt;)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;deleteEmptyPages(app.selection[0].parentStory);&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;&lt;span class="v5"&gt;function&lt;/span&gt;&lt;/strong&gt;&lt;span class="v2"&gt; deleteEmptyPages(story){&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}())&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span class="v2"&gt;This is the first time I've used an anonymous function on this blog. I learned about them only recently (thanks Harbs and Kris). They help protect the global name space. In particular, you can have one script in this structure call another that uses the same variable or function names and there's no confusion because they're inside the anonymous function. Notice that the whole thing is inside parentheses. The pair of parentheses right at the end are the call to the function. Looks odd, but it works a charm.&lt;br /&gt;&lt;br /&gt;So, now all we need do is write our function. This is a CS3 only script so we must use the textContainers property to get at the frames that constitute the story. Here's the function:&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;&lt;span class="v5"&gt;function&lt;/span&gt;&lt;/strong&gt;&lt;span class="v2"&gt; deleteEmptyPages(story){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;strong&gt;&lt;span class="v5"&gt;var&lt;/span&gt;&lt;/strong&gt;&lt;span class="v2"&gt; myFrames = story.textContainers;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;&lt;span class="v5"&gt;for&lt;/span&gt;&lt;/strong&gt;&lt;span class="v2"&gt; (&lt;/span&gt;&lt;strong&gt;&lt;span class="v5"&gt;var&lt;/span&gt;&lt;/strong&gt;&lt;span class="v2"&gt; j = myFrames.length - 1; j &amp;gt;= 1; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;strong&gt;&lt;span class="v5"&gt;if&lt;/span&gt;&lt;/strong&gt;&lt;span class="v2"&gt; (myFrames[j].insertionPoints.length == 0 &amp;amp;&amp;amp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myFrames[j].parent &lt;/span&gt;&lt;strong&gt;&lt;span class="v5"&gt;instanceof&lt;/span&gt;&lt;/strong&gt;&lt;span class="v2"&gt; Page) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myFrames[j].parent.remove();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&lt;/span&gt;&lt;span class="v2"&gt;Notice that the loop goes down to a value of 1. By doing this, it assumes that you don't want to completely delete the story even if it is empty. Notice also that it checks that the parent is indeed a page before deleting it. We don't want to delete a page where the frame, albeit empty, has been grouped with something else.&lt;/span&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-6850769693879381734?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/6850769693879381734/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=6850769693879381734' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/6850769693879381734'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/6850769693879381734'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2008/07/delete-empty-pages-i-often-get-into.html' title='Delete &quot;Empty&quot; Pages'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-7924119369064019205</id><published>2008-01-11T14:19:00.000-08:00</published><updated>2008-01-11T14:40:59.089-08:00</updated><title type='text'>Speeding up a Script</title><content type='html'>Most of the time, I pay scant attention to how long a script takes to run because most of them are so quick it hardly matters, but every now and then a simple script comes along that takes serious time to run. I just hit one as I sit here working on a catalog. I'm getting near the end of the job, making corrections and changes (mainly changes).&lt;br /&gt;&lt;br /&gt;I realized that I've done so much dickering around to get things to fit on the page in earlier passes that it's time to reset the space before/after of every paragraph. Well, that's easy:&lt;pre&gt;//DESCRIPTION: Restore vertical paragraph style spacing&lt;br /&gt;&lt;br /&gt;if (app.documents.length &gt; 0 &amp;amp;&amp;amp;&lt;br /&gt;       app.selection.length == 1 &amp;amp;&amp;amp;&lt;br /&gt;           app.selection[0].hasOwnProperty("baseline")) {&lt;br /&gt; restoreParagraphSpacing(app.selection[0]);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function restoreParagraphSpacing(myText) {&lt;br /&gt; var myParas = myText.paragraphs;&lt;br /&gt; for (var j = 0; myParas.length &gt; j; j++) {&lt;br /&gt;   myParas[j].spaceBefore = myParas[j].appliedParagraphStyle.spaceBefore;&lt;br /&gt;   myParas[j].spaceAfter = myParas[j].appliedParagraphStyle.spaceAfter;&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;If I were just running this on the odd page here and there, it wouldn't take too long, but document has 43 pages and the story has 782 paragraphs, so this loop interacts with InDesign 1564 times.&lt;br /&gt;&lt;br /&gt;Clearly, we could cut that in half by changing the loop to this:&lt;pre&gt;  for (var j = 0; myParas.length &gt; j; j++) {&lt;br /&gt;   myParas[j].properties = {&lt;br /&gt;     spaceBefore:myParas[j].appliedParagraphStyle.spaceBefore,&lt;br /&gt;     spaceAfter:myParas[j].appliedParagraphStyle.spaceAfter&lt;br /&gt;   }&lt;br /&gt; }&lt;/pre&gt;But this still requires 782 interactions with InDesign. And all of them happen even if the script does nothing because I'm running it for a second time. There's one more thing worth trying:&lt;pre&gt;//DESCRIPTION: Restore vertical paragraph style spacing&lt;br /&gt;&lt;br /&gt;if (app.documents.length &gt; 0 &amp;amp;&amp;amp;&lt;br /&gt;       app.selection.length == 1 &amp;amp;&amp;amp;&lt;br /&gt;           app.selection[0].hasOwnProperty("baseline")) {&lt;br /&gt; restoreParagraphSpacing(app.selection[0]);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function restoreParagraphSpacing(myText) {&lt;br /&gt; var myParas = myText.paragraphs;&lt;br /&gt; var mySBs = myParas.everyItem().spaceBefore;&lt;br /&gt; var mySAs = myParas.everyItem().spaceAfter;&lt;br /&gt; for (var j = 0; myParas.length &gt; j; j++) {&lt;br /&gt;   if (myParas[j].spaceBefore != mySBs[j] || myParas[j].spaceAfter != mySAs[j]) {&lt;br /&gt;     myParas[j].properties = {&lt;br /&gt;       spaceBefore:myParas[j].appliedParagraphStyle.spaceBefore,&lt;br /&gt;       spaceAfter:myParas[j].appliedParagraphStyle.spaceAfter&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;The script still does a lot of interacting, but most of it is reading. It only writes a value into the document if there is a change needed, so right now the script runs very quickly because there are no longer any changes to be made.&lt;br /&gt;&lt;br /&gt;Ah well, back to work!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-7924119369064019205?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/7924119369064019205/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=7924119369064019205' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/7924119369064019205'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/7924119369064019205'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2008/01/speeding-up-script.html' title='Speeding up a Script'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-3220221216733547726</id><published>2008-01-05T17:38:00.000-08:00</published><updated>2008-01-05T18:00:52.211-08:00</updated><title type='text'>Instructive Confusion</title><content type='html'>The other day, I went so far as to report a bug to Adobe only to discover that I was missing the obvious. It wasn't a bug I was experiencing, but it surely would make a nice feature request. I was working with a book and I had all the documents open. But one of the documents was of primary interest to me at that point in time, and I had two windows open into it, arranged to be side-by-side on my screen.&lt;br /&gt;&lt;br /&gt;When I double-clicked that document in the book window, I expected both windows to come to the front. But only one did. And, as luck would have it, it was the one on the left that I had temporarily forgotten about. I was expecting the window on the right to come to the front. When it didn't after repeated tries, I fired off a bug report to Adobe, only to feel rather silly a few minutes later when I realized that the window at left was indeed a window of the document I was trying to wake up. So, I modified my report and made it a feature request instead.&lt;br /&gt;&lt;br /&gt;Anyway, it being Saturday evening and I'm sitting here amusing myself messing around with scripts while listening to Mahler's Ninth Symphony, it occurred to me that I could write a script to bring all the windows of the active document to the front. I decided that I wanted them to retain whatever stacking order they already had, so I thought that all that meant was I should cycle through all the active document's windows and bring them to the front starting with the back one.&lt;br /&gt;&lt;br /&gt;It came as quite a blow when this script didn't work:&lt;pre&gt;//DESCRIPTION: Bring all Windows of active document to the front&lt;br /&gt;&lt;br /&gt;if (app.windows.length &gt; 0) {&lt;br /&gt;   bringToFront(app.windows[0].parent);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function bringToFront(aDoc) {&lt;br /&gt;   var myWindows = aDoc.windows;&lt;br /&gt;   for (var j = myWindows.length - 1; j &gt;= 0; j--) {&lt;br /&gt;     app.activeWindow = myWindows[j];&lt;br /&gt; }&lt;br /&gt;} // end bringToFront&lt;/pre&gt;In as much as it did bring all the active document's windows to the front, you could say it did work, but it reversed the order of the windows. That which was at the back was now at the front (I only had two windows open; with more the result would have been more muddled).&lt;br /&gt;&lt;br /&gt;Why is this? Because &lt;span style="font-style: italic;"&gt;myWindows&lt;/span&gt; is pointing at the collection of windows owned by the active document, so each time I changed the order of the windows, the order of their references in in &lt;span style="font-style: italic;"&gt;myWindows&lt;/span&gt; also changed. I've fallen into this trap so many times with objects of various sorts, you'd think I'd see it coming. The solution is to deploy getElements() to create an array that captures a snapshot of the order of the windows before we perform the loop, like this:&lt;pre&gt;//DESCRIPTION: Bring all Windows of active document to the front&lt;br /&gt;&lt;br /&gt;if (app.windows.length &gt; 0) {&lt;br /&gt;   bringToFront(app.windows[0].parent);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function bringToFront(aDoc) {&lt;br /&gt;   var myWindows = aDoc.windows.everyItem().getElements();&lt;br /&gt;   for (var j = myWindows.length - 1; j &gt;= 0; j--) {&lt;br /&gt;     app.activeWindow = myWindows[j];&lt;br /&gt; }&lt;br /&gt;} // end bringToFront&lt;/pre&gt;Now, the contents of myWindows is not affected by the changes made in the loop and so the windows end up in the order I wanted them, at the front of any other windows that might be open into other documents.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Note:&lt;/span&gt; app.windows[0].parent is equivalent to app.documents[0], but this is a script about windows, so I used the window-based construction.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-3220221216733547726?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/3220221216733547726/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=3220221216733547726' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/3220221216733547726'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/3220221216733547726'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2008/01/instructive-confusion.html' title='Instructive Confusion'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-883951013335837675</id><published>2007-09-09T08:42:00.000-07:00</published><updated>2007-09-09T09:04:15.467-07:00</updated><title type='text'>Placing Snippets Inline/Anchored</title><content type='html'>I've found InDesign's snippets feature to be a constant source of frustration because you simply can't place them as inline or anchored objects. So much of my work involves inline/anchored objects that this makes snippets just about useless. But the alternative of using library items isn't that great either. Library panels take up space on screen and they clutter up the the Open Recent menu. Also, in the past, they've been a source of frustration when crashes occur and they have to be recovered. Untitled assets seem to pop into existence in a recovered library, or worse yet, a previously named asset suddenly loses its name.&lt;br /&gt;&lt;br /&gt;Snippets do not take up space on screen, are impervious to crash/recovery issues, and they do not appear on the Open Recent menu. So, how to work around the inability to place them inline? Whats more, how to do it without using the clipboard which might or might not have important information on it at any point in time.&lt;br /&gt;&lt;br /&gt;I came up with the idea of using a temporary library. Even on a G4, creating a library, putting something into it, pulling it back out again and deleting the library is pretty swift, while on a MacIntel it positively flies. So, I wrote this function to do the work:&lt;pre&gt;function placeSnipInline( mySnipFile, text ) {&lt;br /&gt;var myDoc = app.documents.add(false);&lt;br /&gt;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.neverInteract;&lt;br /&gt;myDoc.pages[0].place(mySnipFile);&lt;br /&gt;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;&lt;br /&gt;myLib = app.libraries.add(File("~/Desktop/templib.indl"));&lt;br /&gt;myLib.store(myDoc.pageItems[0]);&lt;br /&gt;myDoc.close(SaveOptions.no);&lt;br /&gt;myLib.assets[0].placeAsset(text);&lt;br /&gt;myLib.close();&lt;br /&gt;File("~/Desktop/templib.indl").remove();&lt;br /&gt;}&lt;/pre&gt;One odd thing here might leap out at you. Why did I not take advantage of the returned object from the place() call to get a reference to the placed object rather than rely on myDoc.pageItems[0] in the store call? The reason is that I have just this morning discovered that if you place a snippet that consists of just a text frame, what's returned by place() is the story, not the text frame.&lt;br /&gt;&lt;br /&gt;To turn the above function into a complete script I added this front-end:&lt;pre&gt;//DESCRIPTION: Place Snippet Inline/Anchored&lt;br /&gt;&lt;br /&gt;if (app.documents.length &gt; 0 &amp;&amp;amp;&lt;br /&gt;      app.selection.length &gt; 0 &amp;amp;amp;&amp;&lt;br /&gt;      app.selection[0].hasOwnProperty("baseline")) {&lt;br /&gt;placeSnippet(app.selection[0]);&lt;br /&gt;} else {&lt;br /&gt;alert("There must be a text selection to run this script");&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function placeSnippet(sel) {&lt;br /&gt;if (File.fs == "Windows") {&lt;br /&gt;  var Filter = "Snippet files: *.inds";&lt;br /&gt;} else {&lt;br /&gt;  var xmlFilter = function(file) {&lt;br /&gt;    while(file.alias){&lt;br /&gt;      file = file.resolve();&lt;br /&gt;      if (file == null) return false;&lt;br /&gt;    }&lt;br /&gt;    if (file instanceof Folder) return true;&lt;br /&gt;    return (file.name.slice(file.name.lastIndexOf(".")).toLowerCase() == ".inds");&lt;br /&gt;  }&lt;br /&gt;  var Filter = xmlFilter&lt;br /&gt;}&lt;br /&gt;var myFile = File.openDialog("Choose a snippet file", Filter);&lt;br /&gt;if (myFile == null) { return }&lt;br /&gt;placeSnipInline(myFile, app.selection[0]);&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-883951013335837675?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/883951013335837675/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=883951013335837675' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/883951013335837675'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/883951013335837675'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/09/placing-snippets-inlineanchored.html' title='Placing Snippets Inline/Anchored'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-1081899166681487191</id><published>2007-08-31T07:04:00.000-07:00</published><updated>2007-08-31T07:14:37.647-07:00</updated><title type='text'>Overset Text</title><content type='html'>The question came up this morning on how to get the overset text in a story. I interpreted that as meaning how to get a reference to it and banged out a quick reply. But this got me musing about how to write a release independent function that would do this for either a story or a cell in a table. I came up with this function (which expects to receive either a story or a cell):&lt;pre&gt;function getOversetText(textFlow) {&lt;br /&gt; if (!textFlow.overflows) return null;&lt;br /&gt; var start = textFlow.characters[0];&lt;br /&gt; if (textFlow instanceof Cell) {&lt;br /&gt;   if (textFlow.characters.length &gt; 0) {&lt;br /&gt;     start = textFlow.texts[0].characters[textFlow.contents.length];&lt;br /&gt;   }&lt;br /&gt; } else {&lt;br /&gt;   if (Number(String(app.version.split(".")[0])) &gt; 4) {&lt;br /&gt;     var myTFs = textFlow.textContainers;&lt;br /&gt;   } else {&lt;br /&gt;     var myTFs = textFlow.textFrames;&lt;br /&gt;   }&lt;br /&gt;   for (var j = myTFs.length - 1; j &gt;= 0; j--) {&lt;br /&gt;     if (myTFs[j].characters.length &gt; 0) {&lt;br /&gt;       start = textFlow.characters[myTFs[j].characters[-1].index + 1];&lt;br /&gt;       break;&lt;br /&gt;     }&lt;br /&gt;   }&lt;br /&gt; }&lt;br /&gt; return textFlow.texts.itemByRange(start, textFlow.texts[0].characters[-1]);&lt;br /&gt;}&lt;/pre&gt;I confess that I haven't tested this in CS, but I have in both CS2 and CS3 where it works. It should work in CS also.&lt;br /&gt;&lt;br /&gt;But notice the assymetry between working with cells and working with stories. Even though the contents of a cell returns only the non-overset text, if you access the characters of a cell you get the lot, overset or not. So, to get the index of the first overset character in a cell, I had to use the length of the contents rather than the index of the last character plus 1, which is what I first tried to use.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-1081899166681487191?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/1081899166681487191/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=1081899166681487191' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/1081899166681487191'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/1081899166681487191'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/08/overset-text.html' title='Overset Text'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-2448625012089566484</id><published>2007-08-26T06:51:00.000-07:00</published><updated>2007-08-26T07:01:15.521-07:00</updated><title type='text'>ScriptUI Dialog with drop-down</title><content type='html'>I found the documentation for drop-down lists to be less easy to follow than for simpler objects. The script ended up being a lot simpler than I was at first led to believe by the combination of scant information in the Tools Guide and the structure of the object model. Here's what I ended up with:&lt;pre&gt;//DESCRIPTION: Simple ScriptUI Drop-down List&lt;br /&gt;&lt;br /&gt;listStrings = ["25%", "50%", "75%", "90%", "100%", "110%", "125%", "140%", "200%", "400%"];&lt;br /&gt;myDlg = new Window('dialog', 'Drop-down List');&lt;br /&gt;myDlg.orientation = 'column';&lt;br /&gt;myDlg.alignment = 'right';&lt;br /&gt;//add drop-down&lt;br /&gt;myDlg.DDgroup = myDlg.add('group');&lt;br /&gt;myDlg.DDgroup.orientation = 'row';&lt;br /&gt;myDlg.DDgroup.add('statictext', undefined, "Zoom Percentage");&lt;br /&gt;myDlg.DDgroup.DD = myDlg.DDgroup.add('dropdownlist', undefined, undefined, {items:listStrings})&lt;br /&gt;myDlg.DDgroup.DD.selection = 4;&lt;br /&gt;myDlg.closeBtn = myDlg.add('button', undefined, 'OK');&lt;br /&gt;// add button functions&lt;br /&gt;myDlg.closeBtn.onClick = function() {&lt;br /&gt; this.parent.close();&lt;br /&gt;}&lt;br /&gt;result = myDlg.show();&lt;br /&gt;alert(myDlg.DDgroup.DD.selection);&lt;br /&gt;&lt;/pre&gt;I had the following difficulties:&lt;ol&gt;&lt;li&gt;I didn't know how many undefineds to include in the add statement for the list. I arrived at two by trial and error. I imagine that the first one is for the dimension information. I'm not sure what the second one is.&lt;/li&gt;&lt;li&gt;I expected to have to create an array of listItems for the items property of the drop-down, but the array of strings sufficed.&lt;/li&gt;&lt;li&gt;I went through a number of hoops trying to work out how to set the initial value of the selection before stumbling on the simple use of the index to 100%.&lt;/li&gt;&lt;li&gt;As a result of that, I was amazed to discover that the selection returns the selected value and not the index into the list.&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-2448625012089566484?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/2448625012089566484/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=2448625012089566484' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/2448625012089566484'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/2448625012089566484'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/08/scriptui-dialog-with-drop-down.html' title='ScriptUI Dialog with drop-down'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-1969347281122032558</id><published>2007-08-25T11:20:00.000-07:00</published><updated>2007-08-25T11:39:44.345-07:00</updated><title type='text'>Selecting Paragraphs</title><content type='html'>This is a real-time blog. I don't know the answer as I start to write this. I have a paragraph reference and a number, &lt;i&gt;n&lt;/i&gt;. I want to select &lt;i&gt;n&lt;/i&gt; paragraphs starting with the referenced paragraph.&lt;br /&gt;&lt;br /&gt;The first question anyone ever asks about a problem like this is: why are you selecting text in a script? You don't need to. All you need do is reference it.&lt;br /&gt;&lt;br /&gt;Well, I'm selecting it because this is a feature in an interactive script. But of course, in order to select it, I need to create that reference. And once I have the reference, all will be well. Perhaps the quickest solution is to walk through the paragraphs, until I reach the last one and then grab a text reference from the first character of the first paragraph to the last of the last. So, how would that go:&lt;pre&gt;function getTextRef(myPara, n) {&lt;br /&gt; var tf = myPara.parent.texts[0]; // text flow&lt;br /&gt; var s = myPara.index; // start&lt;br /&gt; var e = myPara.characters[-1].index; // end&lt;br /&gt; for (j = n-1; j &gt; 0; j--) {&lt;br /&gt;   myPara = myPara.insertionPoints[-1].paragraphs[0];&lt;br /&gt;   e = myPara.characters[-1].index; // updated end&lt;br /&gt; }&lt;br /&gt; return tf.characters.itemByRange(s, e).texts[0];&lt;br /&gt;}&lt;/pre&gt;And that just about does the trick. There is one possible fly-in-the-ointment I can think of: what if there isn't enough text to serve up the n paragraphs? A simple try/catch will handle that. Here's the final version of the function inside the text script I used to test it:&lt;pre&gt;myPara = app.selection[0].paragraphs[0];&lt;br /&gt;n = 2;&lt;br /&gt;selectIt(getTextRef(myPara, n));&lt;br /&gt;&lt;br /&gt;function getTextRef(myPara, n) {&lt;br /&gt; var tf = myPara.parent.texts[0]; // text flow&lt;br /&gt; var s = myPara.index; // start&lt;br /&gt; var e = myPara.characters[-1].index; // end&lt;br /&gt; for (j = n-1; j &gt; 0; j--) {&lt;br /&gt;   try {&lt;br /&gt;     myPara = myPara.insertionPoints[-1].paragraphs[0];&lt;br /&gt;   } catch (e) { break }&lt;br /&gt;   e = myPara.characters[-1].index; // updated end&lt;br /&gt; }&lt;br /&gt; return tf.characters.itemByRange(s, e).texts[0];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function selectIt(theObj) {&lt;br /&gt; app.select(theObj,SelectionOptions.replaceWith);&lt;br /&gt; app.activeWindow.zoom(ZoomOptions.fitPage);&lt;br /&gt; app.activeWindow.zoomPercentage = 150&lt;br /&gt;}&lt;/pre&gt;Well, that was easy. But I have to believe it would have taken longer if I hadn't chatted to myself here.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-1969347281122032558?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/1969347281122032558/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=1969347281122032558' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/1969347281122032558'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/1969347281122032558'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/08/selecting-paragraphs.html' title='Selecting Paragraphs'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-3373399246091684210</id><published>2007-08-25T05:28:00.000-07:00</published><updated>2007-08-25T05:31:52.323-07:00</updated><title type='text'>ScriptUI Dialog with interacting buttons</title><content type='html'>Here's a simple script I just banged out to show a simple case of two buttons interacting with each other in a ScriptUI modal dialog:&lt;pre&gt;//DESCRIPTION: Sample Dialog&lt;br /&gt;&lt;br /&gt;myDlg = new Window('dialog', 'Example');&lt;br /&gt;myDlg.orientation = 'row';&lt;br /&gt;&lt;br /&gt;// Add action buttons&lt;br /&gt;myDlg.btn1 = myDlg.add('button', undefined, 'Disable Him');&lt;br /&gt;myDlg.btn2 = myDlg.add('button', undefined, 'Disable Him');&lt;br /&gt;myDlg.closeBtn = myDlg.add('button', undefined, 'Close');&lt;br /&gt;&lt;br /&gt;// Add button functions&lt;br /&gt;myDlg.btn1.onClick = function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (this.text == 'Disable Him') {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.text = 'Enable Him';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myDlg.btn2.enabled = false;&lt;br /&gt;&amp;nbsp;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.text = 'Disable Him';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myDlg.btn2.enabled = true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;myDlg.btn2.onClick = function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (this.text == 'Disable Him') {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.text = 'Enable Him';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myDlg.btn1.enabled = false;&lt;br /&gt;&amp;nbsp;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;this.text = 'Disable Him';&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myDlg.btn1.enabled = true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;myDlg.closeBtn.onClick = function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;this.parent.close(1);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;result = myDlg.show();&lt;br /&gt;if (result == 1) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;alert("You used the Close button");&lt;br /&gt;}&lt;/pre&gt;Notice that if you close the dialog by hitting the Escape key rather than clicking the Close button, you do not get the alert.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-3373399246091684210?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/3373399246091684210/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=3373399246091684210' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/3373399246091684210'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/3373399246091684210'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/08/scriptui-dialog-with-interacting.html' title='ScriptUI Dialog with interacting buttons'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-863376629774380284</id><published>2007-08-25T05:15:00.000-07:00</published><updated>2007-08-25T05:28:30.705-07:00</updated><title type='text'>Methods hit wall</title><content type='html'>I've gone off attaching methods to core JavaScript classes. This week, I've had two scripts fail dismally because of it, so even though the format of working with:&lt;pre&gt;Object.prototype.isInArray = function(myArray){&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var i=0; myArray.length &gt; i; i++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(myArray[i] == this){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;is so convenient&lt;pre&gt;if (myObj.isInArray(myArray)) {&lt;/pre&gt;or with:&lt;pre&gt;Array.prototype.contains = function(myString){&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var i=0; this.length &gt; i; i++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if(myString == this[i]){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;if (myArray.contains(myObj)) {&lt;/pre&gt;They can really backfire and slap you in the face if you're doing work that involves creating objects or arrays and examining their contents. So, I'm back to the prosaic but safe:&lt;pre&gt;function arrayContains(anArray, anItem) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var i = 0; anArray.length &gt; i; i++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (anItem == anArray[i]) return true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;if (arrayContains(myArray, myObj)) {&lt;/pre&gt;Which most people would have been using all along anyway.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-863376629774380284?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/863376629774380284/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=863376629774380284' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/863376629774380284'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/863376629774380284'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/08/methods-hit-wall.html' title='Methods hit wall'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-7810407038945440488</id><published>2007-08-20T06:58:00.000-07:00</published><updated>2007-08-20T07:29:05.547-07:00</updated><title type='text'>Open a Copy</title><content type='html'>In the UI, you get the choice to open a copy of a document in the File &gt; Open dialog. This option is missing from scripting, so how to achieve the same goal?&lt;pre&gt;//DESCRIPTION: Open a copy of a document&lt;br /&gt;openCopy();&lt;br /&gt;function openCopy() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var numDocs = app.documents.length;&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (File.fs == "Windows") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var Filter = "InDesign documents: *.indd";&lt;br /&gt;&amp;nbsp;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var inddFilter = function(file) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while(file.alias){ &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;file = file.resolve(); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (file == null) return false; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (file instanceof Folder) return true; &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return (file.name.slice(file.name.lastIndexOf(".")).toLowerCase() == ".indd"); &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var Filter = inddFilter&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myFile = File.openDialog("Choose an InDesign document", Filter);&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myFile == null) { return } // user canceled&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.neverInteract;  &lt;br /&gt;&amp;nbsp;&amp;nbsp;var myDoc = app.open(myFile, false);&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (numDocs == app.documents.length) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert("Unable to open document; perhaps it is already open by you or another user.");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;var name = "temp";&lt;br /&gt;&amp;nbsp;&amp;nbsp;var inc = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;do {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myTemplate = File("~/Desktop/" + name + inc + ".indt");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inc++;&lt;br /&gt;&amp;nbsp;&amp;nbsp;} while (myTemplate.exists);&lt;br /&gt;&amp;nbsp;&amp;nbsp;myDoc.save(myTemplate, true); // save as template&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.documents[0].close();&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.neverInteract;  &lt;br /&gt;&amp;nbsp;&amp;nbsp;app.open(myTemplate);&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;&lt;br /&gt;&amp;nbsp;&amp;nbsp;myTemplate.remove();&lt;br /&gt;}&lt;/pre&gt;This gets the job done. It's a little more convoluted than just selecting an option. Perhaps some explanation is in order.&lt;br /&gt;&lt;br /&gt;The numDocs variable is used to make sure we actually succeed in opening the selected document. The first time I tested the script, I happened to choose the document I already had open. But I got no warning about this because I had switched off interaction. I ended up with an untitled copy of the document, but that was more luck than judgment.&lt;br /&gt;&lt;br /&gt;The next few lines show how easy Windows users have when it comes to filtering file types. While they get to use a simple string, Macintosh users get to write a function that not only has to deal with the files in question but also folders and aliases. As a result, we're halfway through the script before we even ask the user to locate the file.&lt;br /&gt;&lt;br /&gt;Notice that I open the file without a window. There's no point in letting the user see this step--it just causes unpleasant flashing of the screen. We need somewhere to save the document as a temporary template so we can then open that and get our copy. The desktop seems the obvious place. But we need to make sure that the file name we choose isn't already in use by an existing file. That's what the do/while loop is all about.&lt;br /&gt;&lt;br /&gt;Once we have a unique name, all we have to do is save as a template, then open the template (this time with a visible window), and having opened it, we delete it so it doesn't clutter up the user's desktop.&lt;br /&gt;&lt;br /&gt;And we have achieved our goal: we've opened a copy of the document.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-7810407038945440488?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/7810407038945440488/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=7810407038945440488' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/7810407038945440488'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/7810407038945440488'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/08/open-copy.html' title='Open a Copy'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-4408259202511662150</id><published>2007-08-19T06:28:00.000-07:00</published><updated>2007-08-19T06:43:52.173-07:00</updated><title type='text'>Tables and keystrokes</title><content type='html'>The question was asked in the U2U forum this week: how to use the keyboard to move the insertion point out of a table to the story containing it.&lt;br /&gt;&lt;br /&gt;The answer is to navigate to the start of the text in the top-left cell and then hit Left Arrow on the keyboard. The quick way to navigate there from anywhere in a table is:&lt;br /&gt;&lt;br /&gt;Command-Option-A (or Ctrl-Alt-A)&lt;br /&gt;Escape&lt;br /&gt;Left Arrow&lt;br /&gt;&lt;br /&gt;If the top-left cell is empty, hitting the Left Arrow key is unnecessary. So, depending on the state of the top-left cell, to get out of the table takes three or four keystrokes. None of which has much to do with scripting. But that raised the question, how do you use the keyboard to get into a table?&lt;br /&gt;&lt;br /&gt;And that requires a script (to which you could attache a shortcut). I composed this script to get the cursor into the first cell of the next table in the current text flow -- bear in mind that a table could be inside the text in a cell in another table:&lt;pre&gt;//DESCRIPTION: Move cursor to next table&lt;br /&gt;if (app.documents.length &gt; 0 &amp;amp;&amp;amp; app.selection.length &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;jumpToNextTable(app.selection[0]);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function jumpToNextTable(sel) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (sel.hasOwnProperty("baseline")) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.select(findTable(sel).cells[0].insertionPoints[0]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch(e) {};&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;function findTable(sel) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var story = sel.parent;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (sel.index &amp;lt; story.length - 1) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var tables = story.characters.itemByRange(sel.index, story.length - 1).texts[0].tables;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (tables.length &gt; 0) return tables[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-4408259202511662150?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/4408259202511662150/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=4408259202511662150' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/4408259202511662150'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/4408259202511662150'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/08/tables-and-keystrokes.html' title='Tables and keystrokes'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-8265615500845723179</id><published>2007-08-05T09:51:00.000-07:00</published><updated>2007-08-05T09:59:44.560-07:00</updated><title type='text'>Active Document Gotcha.</title><content type='html'>If you have two documents open with the same name, InDesign scripting gets confused about which is which when you try to use app.activeDocument. For example, I had two documents called sampledocument.indd open at the same time this morning. One was being accessed over the network on a remote volume on my G5 while the other was local on my desktop. [I was doing this for testing purposes only--I don't think I've ever actually needed to have two documents open at the same time with the same name.]&lt;br /&gt;&lt;br /&gt;For example, when I had the desktop version of my document at front, this script:&lt;pre&gt;myDoc = app.documents[0];&lt;br /&gt;$.writeln(myDoc.fullName);&lt;br /&gt;app.activeDocument = app.documents[-1];&lt;br /&gt;$.writeln(myDoc.fullName);&lt;/pre&gt;displayed this:&lt;pre&gt;~/Desktop/sampledocument.indd&lt;br /&gt;/davesaundersG5/Documents/Work/TestFolder/sampledocument.indd&lt;/pre&gt;and, when I ran it a second time, it returned:&lt;pre&gt;/davesaundersG5/Documents/Work/TestFolder/sampledocument.indd&lt;br /&gt;~/Desktop/sampledocument.indd&lt;/pre&gt;These are right both times. But this script:&lt;pre&gt;myDoc = app.activeDocument;&lt;br /&gt;$.writeln(myDoc.fullName);&lt;br /&gt;app.activeDocument = app.documents[-1];&lt;br /&gt;$.writeln(myDoc.fullName);&lt;/pre&gt;returned:&lt;pre&gt;/davesaundersG5/Documents/Work/TestFolder/sampledocument.indd&lt;br /&gt;/davesaundersG5/Documents/Work/TestFolder/sampledocument.indd&lt;/pre&gt;no matter which of the two documents I had at the front.&lt;br /&gt;&lt;br /&gt;Perhaps the root of the issue is that this:&lt;pre&gt;$.writeln(app.activeDocument.toSource());&lt;/pre&gt;returns:&lt;pre&gt;resolve("/document[@name=\"sampledocument.indd\"]")&lt;/pre&gt;which contains an inadequate specifier for the document because the two names are the same. Perhaps this indicates that somewhere the documents are maintained is some fixed order so that the specifier always finds the same one no matter where it might be in the collection of documents.&lt;br /&gt;&lt;br /&gt;The moral to the story is: never use activeDocument if you have two documents with the same name open at once.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-8265615500845723179?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/8265615500845723179/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=8265615500845723179' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/8265615500845723179'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/8265615500845723179'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/08/active-document-gotcha.html' title='Active Document Gotcha.'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-4268804190749330975</id><published>2007-08-02T10:32:00.000-07:00</published><updated>2007-08-02T11:07:49.739-07:00</updated><title type='text'>Last shall be First versus NextItem</title><content type='html'>I have to search through some paragraphs in a story looking for the first one whose applied paragraph style name isn't in a particular list. This kind of search cannot be done using the features of find/change (in either CS2 or CS3) so I have to step through the paragraphs in order looking at each. This would seem to be what the nextItem() method was created for. But I've always been a bit nervous about its performance, so I thought I'd conduct a timing test. I clicked in a story with 83 paragraphs and ran this:&lt;pre&gt;var myStartTime = new Date();&lt;br /&gt;// Do something&lt;br /&gt;var myStory = app.selection[0].parent;&lt;br /&gt;var myPara = myStory.paragraphs[0];&lt;br /&gt;var nextPara = myStory.paragraphs.nextItem(myPara);&lt;br /&gt;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;while (myPara != nextPara) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myPara = nextPara;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;nextPara = myStory.paragraphs.nextItem(myPara);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;} catch(e) {}&lt;br /&gt;var myEndTime = new Date();&lt;br /&gt;var myDuration = (myEndTime - myStartTime)/1000; // Times are in milliseconds&lt;br /&gt;alert(myDuration)&lt;/pre&gt;The first time I ran it, it gave me an error trying to get to the nextItem after quite a long time -- I realized that this might be happening at the end of the story (where there is no nextItem) so I added the try/catch and indeed, that was the problem. The script took:  28.575 seconds to run.&lt;br /&gt;&lt;br /&gt;So, then I tried this approach which relies on the contained paragraph (i.e, paragraphs[0]) of the last insertionPoint of a paragraph being the next paragraph (hence: "last shall be first"):&lt;pre&gt;var myStartTime = new Date();&lt;br /&gt;// Do something&lt;br /&gt;var myStory = app.selection[0].parent;&lt;br /&gt;var myPara = myStory.paragraphs[0];&lt;br /&gt;var nextPara = myPara.insertionPoints[-1].paragraphs[0];&lt;br /&gt;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;while (myPara != nextPara) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myPara = nextPara;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;nextPara = myPara.insertionPoints[-1].paragraphs[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;} catch(e) {beep()}&lt;br /&gt;var myEndTime = new Date();&lt;br /&gt;var myDuration = (myEndTime - myStartTime)/1000; // Times are in milliseconds&lt;br /&gt;alert(myDuration)&lt;/pre&gt;And this took: 3.848 seconds. Over seven times faster. No contest!&lt;br /&gt;&lt;br /&gt;Notice that I inserted a beep in the catch in this version. As I expected, the while loop exited in this case without the beep, so the try/catch was not actually necessary in this case.&lt;br /&gt;&lt;br /&gt;Thinks: these timings were for CS2 on my G5. How about CS3? Because of plug-in issues, I couldn't run exactly the same document, so I picked a story in another that happened to have 141 paragraphs. The first script took 211 seconds on CS3 on my MacIntel iMac/20. The second script took just under 9 seconds. About 23 times faster.&lt;br /&gt;&lt;br /&gt;The length of the story is a definite issue with the speed disadvantage of the nextItem approach. Inserting:&lt;pre&gt;$.writeln(nextPara.index);&lt;/pre&gt;into the loop allows you to watch progress in ESTK 2's Console and you can see that the further into the story the script gets the slower it gets.&lt;br /&gt;&lt;br /&gt;So, the "Last shall be First" technique is the one I shall continue to use.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-4268804190749330975?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/4268804190749330975/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=4268804190749330975' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/4268804190749330975'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/4268804190749330975'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/08/last-shall-be-first-versus-nextitem.html' title='Last shall be First versus NextItem'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-6892176964018669687</id><published>2007-07-30T14:02:00.000-07:00</published><updated>2007-07-30T14:09:32.797-07:00</updated><title type='text'>Adding a Column of Numbers</title><content type='html'>This morning, while working on an invoice, I once again found myself needing to add a column of numbers, so I decided to write a script to do it:&lt;pre&gt;//DESCRIPTION: Add selected column of numbers&lt;br /&gt;&lt;br /&gt;if (app.documents.length &gt; 0 &amp;amp;&amp;amp; app.selection.length &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;addSelection(app.selection[0]);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function addSelection(sel) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myLines = sel.contents.split("\r");&lt;br /&gt;&amp;nbsp;&amp;nbsp;var total = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var j = 0; myLines.length &gt; j; j++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;total = total + getNumber(myLines[j]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.activate()&lt;br /&gt;&amp;nbsp;&amp;nbsp;prompt("Result", String(total));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;function getNumber(text) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var start = text.search(/\d/);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (start == -1) return 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return Number(text.slice(start));&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;Notice the use of search to locate the first digit in the string representing the contents of each line (for which I might have chosen a wiser variable name than "text"). I chose to use the prompt style of alert to simplify copying the result to another application or even into an InDesign document. And, I issued an activate to bring InDesign to the front so that should I be running the script from ESTK, the prompt would be brought in front of the ESTK window.&lt;br /&gt;&lt;br /&gt;Now I'm looking at the script, I find myself thinking: what if the column was a column within a table? I reckon there's room for a part 2 to this blog entry!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-6892176964018669687?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/6892176964018669687/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=6892176964018669687' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/6892176964018669687'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/6892176964018669687'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/07/adding-column-of-numbers.html' title='Adding a Column of Numbers'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-1134617245100812617</id><published>2007-07-24T09:23:00.000-07:00</published><updated>2007-07-24T09:34:58.848-07:00</updated><title type='text'>Get the Lead out!</title><content type='html'>The question was asked in the U2U forum: what does it mean in the InDesign CS3 Object Model Viewer (available from ESTK 2's Help menu) when a property is described as having the value "any"?&lt;br /&gt;&lt;br /&gt;Well, the answer to that specific question is that "any" is a poor choice of terminology. "Varies" or "various" would be better choices.&lt;br /&gt;&lt;br /&gt;Generally speaking, when a property is described as having a value of type "any", what it means is that you can set the value with any of a set of variable types, but what you'll get when you query the value is usually always the same type of variable; that variable type that most closely matches the property's nature.&lt;br /&gt;&lt;br /&gt;And that led me to recall a function I've used countless times to get the leading of a text object (more precisely, the leading of the first character the text object -- unless the object is a single insertionPoint, in which case you get its leading), where two different kinds of value can be returned by the property:&lt;pre&gt;function getLeading(text) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var leading = text.leading;&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (leading == Leading.auto) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;leading = text.pointSize * text.autoLeading/100;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;return leading&lt;br /&gt;}&lt;/pre&gt;Actually, this can be used to get the leading of paragraph styles, too. &lt;br /&gt;&lt;br /&gt;For character styles, you have to be a tad careful because you could have a paragraph style where either the leading or the pointSize (or both) are set to be ignored (in which case you get the value NothingEnum.nothing), so you could use this function for character styles, but you'd be better off constructing one that addresses these other possibilities.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-1134617245100812617?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/1134617245100812617/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=1134617245100812617' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/1134617245100812617'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/1134617245100812617'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/07/get-lead-out.html' title='Get the Lead out!'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-724691968260561034</id><published>2007-07-21T04:40:00.000-07:00</published><updated>2007-07-21T05:05:24.006-07:00</updated><title type='text'>Change of Heart about Kick-offs</title><content type='html'>For a long time, I've had a couple of "kick-off" snippets in my iSnip file that form a framework for kicking off a script. They look like this:&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Document Kick-off&lt;/h4&gt;&lt;pre&gt;if (app.documents.length == 0) { exit() }&lt;br /&gt;processDocument(app.documents[0]);&lt;br /&gt;&lt;br /&gt;function processDocument(aDoc) {&lt;br /&gt;&lt;br /&gt;} // end processDocument&lt;/pre&gt;&lt;h4&gt;Selection Kick-off&lt;/h4&gt;&lt;pre&gt;if (app.documents.length == 0 || app.selection.length == 0) { exit() }&lt;br /&gt;processSelection(app.selection[0]);&lt;br /&gt;&lt;br /&gt;function processSelection(sel) {&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;&lt;h4&gt;Change of Heart&lt;/h4&gt;I used to like this approach. Detecting immediately that the application was not in the state of interest for the script and exiting just seemed an appropriate way to get rid of those conditions without cluttering up your script with an extra level of indenting.&lt;br /&gt;&lt;br /&gt;But there are two problems with this:&lt;ol&gt;&lt;li&gt;There is no extra level of indenting since I switched to doing all the processing in a function. That thinking dates back to the days when I was writing monolithic scripts that did all (or most) of the work at the global level.&lt;/li&gt;&lt;li&gt;When you use &lt;em&gt;exit()&lt;/em&gt;, you don't just exit the script, you exit the scripting system. So, if you are trying to string together a series of scripts, the use of exit by one script prevents any more scripts in the string from running.&lt;/li&gt;&lt;/ol&gt;As a result of these two realizations, I have changed my snippets to look like this:&lt;h4&gt;Document Kick-off&lt;/h4&gt;&lt;pre&gt;if (app.documents.length &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;processDocument(app.documents[0]);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function processDocument(aDoc) {&lt;br /&gt;&lt;br /&gt;} // end processDocument&lt;/pre&gt;&lt;h4&gt;Selection Kick-off&lt;/h4&gt;&lt;pre&gt;if (app.documents.length &gt; 0 &amp;amp;&amp;amp; app.selection.length &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;processSelection(app.selection[0]);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function processSelection(sel) {&lt;br /&gt;&lt;br /&gt;}&lt;/pre&gt;Notice the change from logical OR to logical AND in the selection kick-off. This structure works because if there is no document, the logical expression evaluates to false without the need to evaluate the second part of the expression, which would, in that case, cause an error.&lt;br /&gt;&lt;br /&gt;The extra level of indenting that I used to worry about lasts just one line now, so it is not nearly the big deal it used to be.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-724691968260561034?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/724691968260561034/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=724691968260561034' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/724691968260561034'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/724691968260561034'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/07/change-of-heart-about-kick-offs.html' title='Change of Heart about Kick-offs'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-1343262061492408323</id><published>2007-06-02T07:10:00.000-07:00</published><updated>2007-06-02T13:51:37.500-07:00</updated><title type='text'>More Edit Original</title><content type='html'>Let's replace that alert in the main function with some code that does something useful. First, let's just mimic what the Edit Original command does by simply opening the original document. To find that document, we use the itemLink property. So:&lt;pre&gt;function editOriginalIDPage(sel) {&lt;br /&gt; var myImpPage = getImpPage(sel);&lt;br /&gt; if (myImpPage == null) return;&lt;br /&gt; var myLink = myImpPage.itemLink;&lt;br /&gt; app.open(File(myLink.filePath))&lt;br /&gt;...&lt;/pre&gt;Well, that was easy. But what we really want to do is turn to the page that contains the imported page. Well, that information is available in the object's pageNumber property (which counts from 1 -- grrr):&lt;pre&gt;function editOriginalIDPage(sel) {&lt;br /&gt; var myImpPage = getImpPage(sel);&lt;br /&gt; if (myImpPage == null) return;&lt;br /&gt; var myPgNum = myImpPage.pageNumber;&lt;br /&gt; var myLink = myImpPage.itemLink;&lt;br /&gt; var newDoc = app.open(File(myLink.filePath))&lt;br /&gt; app.activeWindow.activePage = newDoc.pages[myPgNum - 1];&lt;br /&gt; app.activeWindow.zoom(ZoomOptions.fitPage);&lt;br /&gt;...&lt;/pre&gt;And that does the job.&lt;br /&gt;&lt;br /&gt;But of course, this was the easy part—so easy that one wonders why it isn't built into the application, but had it been, we wouldn't be having all this fun! Now the challenge is to try to integrate (a version of) our script into the Edit Original event handler. We don't want to take over the whole event because we only handle imported pages. We want to somehow pass control back to the application to handle all the other kinds of graphic.&lt;br /&gt;&lt;br /&gt;At the moment, I'm going on blind faith that this must be possible. It might be a few days before I can get back to this because I'm in New York at the InDesign Conference this coming week and I have to finalize my preparations. Meanwhile, I've added a link to the current state of this script in the downloads list. [Life intervened and I've not yet added that link.]&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-1343262061492408323?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/1343262061492408323/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=1343262061492408323' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/1343262061492408323'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/1343262061492408323'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/06/more-edit-original.html' title='More Edit Original'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-7091876446627139903</id><published>2007-06-02T04:49:00.000-07:00</published><updated>2007-06-02T05:05:26.022-07:00</updated><title type='text'>Edit Original Continued</title><content type='html'>It turns out that there is a naming bug in the object model. There is a collection object for importedPages but it has the unfortunate name IndesignPageItems. I had noticed that in the object model viewer (in ESTK 2, choose Adobe InDesign CS3 Object Model from the Help menu) but didn't recognize what it was.&lt;br /&gt;&lt;br /&gt;So this raises the question, how to write code that uses this that doesn't get broken when they fix the name? How's this:&lt;pre&gt;    function getImpPage(obj) {&lt;br /&gt;        if (obj.constructor.name == "ImportedPage") return obj;&lt;br /&gt;        try {&lt;br /&gt;            return (obj.hasOwnProperty("importedPages") ? obj.importedPages[0] : obj.indesignPageItems[0]);&lt;br /&gt;        } catch(e) {&lt;br /&gt;            return null;&lt;br /&gt;        }&lt;br /&gt;    } // end of getImpPage&lt;/pre&gt;Yes! That works.&lt;br /&gt;&lt;br /&gt;Dave&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-7091876446627139903?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/7091876446627139903/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=7091876446627139903' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/7091876446627139903'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/7091876446627139903'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/06/edit-original-continued.html' title='Edit Original Continued'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-7708519839315027826</id><published>2007-06-01T12:57:00.000-07:00</published><updated>2007-06-01T13:38:26.806-07:00</updated><title type='text'>Edit Original for Placed Pages</title><content type='html'>Once again, I have used the making of a booklet as a proof of concept for a script of a new version of InDesign. Two releases ago, my BuildBooklet.js (as it was then) filled a gap in the InDesign CS feature set by making it possible to create booklets from documents; the only proviso was that the documents had to have a page count that was exactly a multiple of four. I based the script on a concept I'd heard about from Shane Stanley and used it to learn about managing sections in InDesign documents. I had no expectations that others would find this script useful.&lt;br /&gt;&lt;br /&gt;But it took off. And even after InBooklet was added to InDesign (first to CS via the terribly-named PageMaker Plug-ins and then as part of CS2), some people advocated using my script because it was so simple to use. Well, here we are with CS3 and I've done it again. This time, I've called the script MakeBooklet.jsxbin. I've published it as a binary to emphasize that it works only with CS3. Unlike its predecessor, this script creates a new double-sized, single-sided document, placing the contents of the pages of the original document side-by-side in printer spread order.&lt;br /&gt;&lt;br /&gt;The ability to &lt;span style="font-style: italic;"&gt;place&lt;/span&gt; pages from one document to another is new in InDesign CS3 and that opens up all kinds of possibilities for scripts to perform these kinds of imposition tasks. If you'd like to try the script, I've put a link to it in the downloads area (or will have shortly, if it's not there yet).&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;What does all this have to do with Edit Original?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Well, the first time I created a booklet with my MakeBooklet script, I immediately tested the Edit Original feature and was very disappointed to discover that the original document didn't open at the page I had asked to edit. Yuck!&lt;br /&gt;&lt;br /&gt;So, let's write a script to do the job. I think it's possible; it'll be very disappointing if I fail. Obviously, the script must start in the usual way checking for a selection:&lt;pre&gt;//DESCRIPTION: Edit Original for Placed InDesign Pages&lt;br /&gt;&lt;br /&gt;if (app.documents.length == 0 || app.selection.length == 0) { exit() }&lt;br /&gt;editOriginalIDPage(app.selection[0]);&lt;br /&gt;&lt;br /&gt;function editOriginalIDPage(sel) {&lt;br /&gt;&lt;br /&gt;} // end editOriginalIDPage&lt;/pre&gt; Also, obviously, the user ought to be able to do this with either the page selected or its frame. So, the first thing that my editOriginalIDPage function must do is get a reference to the graphic from the selection.&lt;br /&gt;&lt;br /&gt;Here's my untested first attempt to do this:&lt;pre&gt;//DESCRIPTION: Edit Original for Placed InDesign Pages&lt;br /&gt;&lt;br /&gt;if (app.documents.length == 0 || app.selection.length == 0) { exit() }&lt;br /&gt;editOriginalIDPage(app.selection[0]);&lt;br /&gt;&lt;br /&gt;function editOriginalIDPage(sel) {&lt;br /&gt;    var myImpPage = getImpPage(sel);&lt;br /&gt;    if (myImpPage == null) return;&lt;br /&gt;    alert("Got Here");&lt;br /&gt;   &lt;br /&gt;    function getImpPage(obj) {&lt;br /&gt;        if (obj.constructor.name == "ImportedPage") return obj;&lt;br /&gt;        try {&lt;br /&gt;            return obj.importedPages[0];&lt;br /&gt;        } catch(e) {&lt;br /&gt;            return null;&lt;br /&gt;        }&lt;br /&gt;    } // end of getImpPage&lt;br /&gt;} // end editOriginalIDPage&lt;/pre&gt;Well, golly, it didn't work. What am I doing wrong. Clearly, I need to single step through the script and see what happens. Hello! importPages doesn't exist as a collection. That's weird. That forces a different approach. I rewrote the getImpPage function like this:&lt;pre&gt;    function getImpPage(obj) {&lt;br /&gt;        if (obj.constructor.name == "ImportedPage") return obj;&lt;br /&gt;        if (!obj.hasOwnProperty("graphics")) return null;&lt;br /&gt;        if (obj.graphics.length == 0) return null;&lt;br /&gt;        var myImpPage = obj.graphics[0].getElements()[0];&lt;br /&gt;        return getImpPage(myImpPage);&lt;br /&gt;    } // enf of getImpPage&lt;/pre&gt;And that gets the job done. Notice the neat use of recursion to avoid having to essentially rewrite out the first two lines of the function again at the end.&lt;br /&gt;&lt;br /&gt;This is getting a bit long so I'll follow up in the next message.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-7708519839315027826?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/7708519839315027826/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=7708519839315027826' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/7708519839315027826'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/7708519839315027826'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/06/edit-original-for-placed-pages.html' title='Edit Original for Placed Pages'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-984090107750893531</id><published>2007-04-24T08:25:00.000-07:00</published><updated>2007-04-24T09:00:01.872-07:00</updated><title type='text'>CS3 is Here!</title><content type='html'>The new scripting features in CS3 are both awesome and somewhat intimidating. All the new features of InDesign CS3 are supported and there are new scripting-specific enhancements that most non-scripting users will never notice. The most immediate issue faced by anybody who has accumulated a collection of scripts is compatibility -- will the existing scripts still run?&lt;br /&gt;&lt;br /&gt;The answer is &lt;span style="font-weight: bold;"&gt;mostly&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;First, there's the versioning feature. Put your CS2 scripts in a folder named Version 4.0 Scripts and they'll run as though the application is still InDesign CS2. Although there are some subtle changes that might affect some scripts. For example, CS2 always delivered the members of the selection (when more than one item was selected) in a reliable order. CS3 does not, even with versioning switched on. Frankly, I would never have known that had it not been for Pete Kahrel who has written some CS2 scripts that took advantage of that ordering. So far, among my own scripts, I've yet to discover one that won't work with versioning on, except for those scripts that explicitly check the application's version (which returns 5.0.0.nnn, where nnn is the build number) even with versioning on.&lt;br /&gt;&lt;br /&gt;What about converting scripts. How many need converting? &lt;span style="font-weight: bold;"&gt;Quite a few&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Any script that uses &lt;span style="font-weight: bold;"&gt;Find/Change&lt;/span&gt; of text is going to have to be revised to use the new structure. This part of InDesign's scripting system is tightly bound to the user interface feature and even a cursory glance at the new Find/Change dialog tells you that a lot has happened here.&lt;br /&gt;&lt;br /&gt;Any script that depends on the &lt;span style="font-weight: bold;"&gt;Story.textFrames collection&lt;/span&gt; to manage the text frames (and paths) that constitute a story will instead need to work with the textContainers property, which (unfortunately) is not a collection but an array. Story.textFrames is still a collection, but now it is the collection of inline/anchored text frames inside a story. FWIW, I've already written one script that was able to take great advantage of this:&lt;pre&gt;//DESCRIPTION: Fix overset anchored text frames&lt;br /&gt;&lt;br /&gt;if (app.documents.length == 0) { exit() }&lt;br /&gt;app.documents[0].stories.everyItem().textFrames.everyItem().fit(FitOptions.frameToContent);&lt;/pre&gt;This script came in very handy for eliminating the overset condition of all my anchored figure/caption text frames when I increased the space between the figures and their captions by changing the space before of the caption paragraph style.&lt;br /&gt;&lt;br /&gt;Scripts that &lt;span style="font-weight: bold;"&gt;transform&lt;/span&gt; objects have a whole new way of working that I have yet to investigate.&lt;br /&gt;&lt;br /&gt;Scripts that interact with &lt;span style="font-weight: bold;"&gt;paragraph, character and object styles&lt;/span&gt; now have to deal with grouping of styles. This is more a philosophical issue than a technical one. The chances are that existing scripts will still run, but they might not do what the user really wanted if he has organized his styles into group hierarchies.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;font-size:130%;" &gt;What's New?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;First and foremost for JavaScripters, &lt;span style="font-weight: bold;"&gt;ExtendScript Toolkit 2&lt;/span&gt; is introduced. It represents a major facelift over its predecessor and it includes object model viewers (OMV) accessed through the Help menu that eliminate the need for those horrendously large reference PDFs. Indeed, InDesign CS3's scripting documentation consists of a Guide and Tutorials (one for each scripting language) with the reference available only through the OMV.&lt;br /&gt;&lt;br /&gt;Support is added for &lt;span style="font-weight: bold;"&gt;Script UI&lt;/span&gt;, the scriptable interface system that was previously available with Bridge.&lt;br /&gt;&lt;br /&gt;Scripts can now interact with and add items to the &lt;span style="font-weight: bold;"&gt;menu system&lt;/span&gt; and they can respond to certain &lt;span style="font-weight: bold;"&gt;system events&lt;/span&gt;. Indeed, if you explore the product you'll discover that the File/CrossMedia Export features have been added by scripts.&lt;br /&gt;&lt;br /&gt;JavaScripts can now be saved as &lt;span style="font-weight: bold;"&gt;binary&lt;/span&gt; files.&lt;br /&gt;&lt;br /&gt;I have the distinct impression that I'm selling the new features short here, but "real work" beckons, so I'll be back with more when time allows.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-984090107750893531?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/984090107750893531/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=984090107750893531' title='13 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/984090107750893531'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/984090107750893531'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/04/cs3-is-here.html' title='CS3 is Here!'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>13</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-7064517708542832782</id><published>2007-02-21T07:25:00.000-08:00</published><updated>2007-02-21T07:42:53.842-08:00</updated><title type='text'>Locating a Table Cell</title><content type='html'>They say that necessity is the mother of invention. Yesterday I was faced with needing to know the exact location of the top of a cell in a table so I could precisely position an anchored object. The x-coordinate of the cell was no problem because this table filled the column from side to side, so my anchored rectangle was easy to position horizontally, but vertically is another thing entirely.&lt;br /&gt;&lt;br /&gt;It's worth stepping back a bit to explain what I was trying to achieve. I had two documents each with a huge table in it, over 300 rows and 7 or 8 columns. I wanted to have alternating fills but I could use the regular table features because the fills needed to be transparent so that the complex background could still be seen.&lt;br /&gt;&lt;br /&gt;I conceived of the idea of putting an exactly sized rectangle over every other row by inserting it as an anchored item in the first character position of the first cell of each affected row. This would allow the "fill" to be transparent without the rest of the table also having to be transparent. It was the work of a moment to create the necessary object style to apply the fill and transparency and to set up the anchored object settings. But the heights of the rows vary and so the offset from the baseline of the text also needed to be calculated for each row. Here's the function I constructed to do that:&lt;pre&gt;  function sizeAndPostionRect(table, row, rect) {&lt;br /&gt;    row.cells[0].texts[0].recompose();&lt;br /&gt;    var width = table.width;&lt;br /&gt;    var height = row.cells[0].height;&lt;br /&gt;    var myBounds = rect.geometricBounds;&lt;br /&gt;    myBounds[0] = myBounds[2] - height;&lt;br /&gt;    myBounds[3] = myBounds[1] + width;&lt;br /&gt;    rect.geometricBounds = myBounds;&lt;br /&gt;    var myCell = row.cells[0];&lt;br /&gt;    var tI = myCell.topInset&lt;br /&gt;    var fB = myCell.firstBaselineOffset;&lt;br /&gt;    myCell.firstBaselineOffset = FirstBaseline.fixedHeight;&lt;br /&gt;    myCell.minimumFirstBaselineOffset = 0;&lt;br /&gt;    myCell.verticalJustification = VerticalJustification.topAlign;&lt;br /&gt;    var myCellTop = myCell.texts[0].characters[1].baseline - tI;&lt;br /&gt;    myCell.verticalJustification = VerticalJustification.centerAlign&lt;br /&gt;    myCell.firstBaselineOffset = fB;&lt;br /&gt;    rect.anchoredObjectSettings.anchorYoffset = myCellTop - myCell.texts[0].characters[1].baseline;&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;The trick is to maneuver the baseline of the text in the cell so it sits on the top edge of the cell, then having grabbed the value, put everything back the way it was.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-7064517708542832782?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/7064517708542832782/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=7064517708542832782' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/7064517708542832782'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/7064517708542832782'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/02/locating-table-cell.html' title='Locating a Table Cell'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-117080008714154219</id><published>2007-02-06T14:06:00.000-08:00</published><updated>2007-02-06T14:16:19.703-08:00</updated><title type='text'>Selective Paragraph Style Properties Import</title><content type='html'>I'm working on two very similar posters. They are 30 x45 inches with a highly elaborate background image. So, for proofing purposes, I created a couple of documents that had the text of the posters on regular letter-sized pages. Each of these documents ran to 16 pages and each is a single very long table.&lt;br /&gt;&lt;br /&gt;Everything started out with the same style names and definitions, but the text on one side wouldn't fit at that size, so I had to make it smaller.&lt;br /&gt;&lt;br /&gt;So, here I am with two posters using the same styles but the text needs to be at a different size in one of them. I can't just import the styles from the proof document because the colors are wrong. All I want to do is transfer the font size and leading from the styles in the proof document to the corresponding styles in the poster.&lt;br /&gt;&lt;br /&gt;Given that this is a one-shot script, I decided to simply open both documents with the poster at the back and the proof in front and then transfer the properties from the front document to the back. Here's the script:&lt;pre&gt;//DESCRIPTION: Transfer font size and leading from styles in doc[0] to styles in doc[1]&lt;br /&gt;&lt;br /&gt;myBackDoc = app.documents[1];&lt;br /&gt;myFrontDoc = app.documents[0];&lt;br /&gt;backStyles = myBackDoc.paragraphStyles.everyItem().name;&lt;br /&gt;for (j = backStyles.length - 1; j &gt; 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;frontStyle = myFrontDoc.paragraphStyles.item(backStyles[j]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (frontStyle == null) continue;&lt;br /&gt;&amp;nbsp;&amp;nbsp;backStyle = myBackDoc.paragraphStyles.item(backStyles[j]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;backStyle.pointSize = frontStyle.pointSize;&lt;br /&gt;&amp;nbsp;&amp;nbsp;backStyle.leading = frontStyle.leading;&lt;br /&gt;}&lt;/pre&gt;Notice that I avoid messing with No Paragraph Style by counting down to one and not zero in the loop. Not a very remarkable script, but definitely quicker to write and more reliable than doing the job manually -- provided, of course, that I remembered which was supposed to be at the back and which at the front. I did.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-117080008714154219?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/117080008714154219/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=117080008714154219' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/117080008714154219'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/117080008714154219'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/02/selective-paragraph-style-properties.html' title='Selective Paragraph Style Properties Import'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-117033650776157905</id><published>2007-02-01T05:13:00.000-08:00</published><updated>2007-02-01T05:28:28.106-08:00</updated><title type='text'>Arrays are Associative</title><content type='html'>I owe this technique to Peter Truskier who posted a script on the U2U forum in early January that blew me away. See &lt;a href="http://adobeforums.com/cgi-bin/webx?14@@.3bc2bbb5/9" target="_blank"&gt;Peter's post.&lt;/a&gt; Look at the way Peter formed that array named &lt;i&gt;thePageNames&lt;/i&gt;. Incredible. I just never thought of doing that even though I had been aware all along that JavaScript arrays are associative.&lt;br /&gt;&lt;br /&gt;What this means is that if you have a set of named entities and you want to filter out all the duplicates, rather than use some kind of isInArray test, you can use an associative array. For example, this morning, I wanted a list of all the paragraph style names used in a run of text. Recalling this example by Peter, I came up with this function:&lt;pre&gt;function getParaStyles(text) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var tempArray = new Array();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var textStyles = text.paragraphs.everyItem().appliedParagraphStyle;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for (var j = textStyles.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;tempArray[textStyles[j].name] = "";&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var styles = new Array();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for (var j in tempArray) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;styles.push(j)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return styles&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;/pre&gt;I'm so pleased with this that I had to share it. Thanks a bunch Peter!&lt;br /&gt;&lt;br /&gt;The first loop walks through all the paragraphs getting the name of the style used by the paragraph creating/setting a value (could be any value; we don't care about the value) for the element of tempArray with that name. Thus, by the end of the loop we have precisely one member of that array for each style that was present in the text.&lt;br /&gt;&lt;br /&gt;The second loop pushes those names into a regular indexed array and returns the list of names to the calling script. Great!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-117033650776157905?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/117033650776157905/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=117033650776157905' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/117033650776157905'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/117033650776157905'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/02/arrays-are-associative.html' title='Arrays are Associative'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116965063963373458</id><published>2007-01-24T06:36:00.000-08:00</published><updated>2007-01-24T06:57:19.706-08:00</updated><title type='text'>Making an Array of Objects</title><content type='html'>Every now and then I find myself having to rethink something I know I dealt with just recently but the details have slipped through my grasp. So this blog entry is an aide-memoire for me!&lt;br /&gt;&lt;br /&gt;I'm working on a script where I want a function to read values from a table and return an array of objects that reflect the values in the table. I want each object to have this form:&lt;br /&gt;&lt;br /&gt;{srcFnt:&lt;name&gt;, srcStyl:&lt;name&gt;, targFnt:&lt;name&gt;, targStyl:&lt;name&gt;}&lt;br /&gt;&lt;br /&gt;So, the the loop looks like this (it starts at row 2 to skip over the heading info in the table):&lt;pre&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var List = new Array();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for (var j = 2; myTable.bodyRowCount &gt; j; j++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myTable.rows[j].cells[0].contents == "") { break }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;/pre&gt;So, if I get to that blank line, what do I do to create the object?&lt;br /&gt;&lt;br /&gt;Well, there's nothing like writing a blog to trigger new ideas: previously, it never occurred to me to use a function to create the object directly. Let's see how this works:&lt;pre&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var List = new Array();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for (var j = 2; myTable.bodyRowCount &gt; j; j++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myTable.rows[j].cells[0].contents == "") { break }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;List.push(getObject(myTable.rows[j]))&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;function getObject(theRow) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;srcFnt:theRow.cells[0].texts[0].contents,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;srcStyl:theRow.cells[1].texts[0].contents,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targFnt:theRow.cells[2].texts[0].contents,&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targStyl:theRow.cells[3].texts[0].contents&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&lt;/pre&gt;Well what do you know! That was easy. Much easier than what I did last time I tried to solve this problem.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116965063963373458?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116965063963373458/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116965063963373458' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116965063963373458'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116965063963373458'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/01/making-array-of-objects.html' title='Making an Array of Objects'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116881147627315268</id><published>2007-01-14T12:24:00.000-08:00</published><updated>2007-01-14T13:55:04.730-08:00</updated><title type='text'>Object Specifiers</title><content type='html'>If you do a search of the JavaScript section of the InDesign CS2 Scripting Reference for the words "object specifier" you'll get 448 hits. Half of them are found in the descriptions of the getElements() method. The other half are in the descriptions of the toSpecifier() method.&lt;br /&gt;&lt;br /&gt;So, what are these object specifiers? I confess to having some difficulty explaining what they are in abstract terms. I think of them as dynamic pointers into the object structure of InDesign.&lt;br /&gt;&lt;br /&gt;Which begs the question: what is the object structure when it is at home? Isn't the term "object model"? Well, yes, most people speak of the object model all the time, but I see a difference between the model: all the possible ways that objects might related to each other, and the structure: the current set of objects and their interrelationships. The model describes what might happen at some point; the structure reflects what is happening now.&lt;br /&gt;&lt;br /&gt;For example, if you have no documents open, the model still includes pageItems, but with no documents for those objects to exist in, the structure includes no pageItems right now. Of course, as you work with InDesign, whether with scripts or with the UI, the structure changes to reflect your actions.&lt;br /&gt;&lt;br /&gt;So, an object specifier points at an object in the structure. Well, maybe. It's a little more complicated than that because it might point at an object that doesn't yet exist. Look at this:&lt;pre&gt;myObjSpecifier = app.documents.item("Fred.indd");&lt;br /&gt;if (myObjSpecifier == null) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;alert("'Fred.indd' doesn't exist, yet.");&lt;br /&gt;}&lt;/pre&gt;Assuming you don't have a document named "Fred.indd" open when you run that script, you'll get the alert. Compare that with this:&lt;pre&gt;myObjSpecifier = app.documents.item("Fred.indd");&lt;br /&gt;var myFolder= Folder.selectDialog("Choose a home for Fred.indd");&lt;br /&gt;if (myFolder == null) {exit()}&lt;br /&gt;app.documents.add();&lt;br /&gt;app.documents[0].save(File(myFolder.absoluteURI + "/Fred.indd"));&lt;br /&gt;if (myObjSpecifier == null) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;alert("'Fred.indd' still doesn't exist");&lt;br /&gt;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;alert("'Fred.indd' exists now.");&lt;br /&gt;}&lt;/pre&gt;Run this script, and even though we set the value of myObjSpecifier before the document was created, you get the message that Fred.indd now exists. This is why I call this a dynamic pointer into the object structure. Its value adapts to the moment.&lt;br /&gt;&lt;br /&gt;Let's take a look at that toSpecifier() method:&lt;pre&gt;myObjSpecifier = app.documents.item("Fred.indd");&lt;br /&gt;$.writeln(myObjSpecifier.toSpecifier());&lt;/pre&gt;Run this in ESTK and you see in the console:&lt;pre&gt;/document[@name="Fred.indd"]&lt;br /&gt;undefined&lt;/pre&gt;We can ignore the second line; that's just the result of the script. What's interesting is the first line. That's what an object specifier looks like when it is coerced to text. As you can see, it is a descriptive pointer into the object structure.&lt;br /&gt;&lt;br /&gt;If we modify this last script to read:&lt;pre&gt;myObjSpecifier = app.documents.item("Fred.indd");&lt;br /&gt;$.writeln(myObjSpecifier.toSpecifier());&lt;br /&gt;myObjSpecifier&lt;/pre&gt;And again run it from ESTK, this time, we see in the Console:&lt;pre&gt;/document[@name="Fred.indd"]&lt;br /&gt;[object Document]&lt;/pre&gt;This time, the second line is of interest. The result of our script is an object of class Document. That it doesn't exist (yet) is irrelevant (as far as the ExtendScript engine is concerned).&lt;br /&gt;&lt;br /&gt;Let's take this one step further:&lt;pre&gt;myObjSpecifier = app.documents.item("Fred.indd");&lt;br /&gt;myObjSpecifier == null;&lt;br /&gt;$.writeln(myObjSpecifier.toSpecifier());&lt;/pre&gt;Hmmm, the result of running this is an eye-opening disappointment. To execute the second statement, the engine has to evaluate the variable myObjSpecifier. And, in this case, because we're running with no document named "Fred.indd", it evaluates to null. Apparently, when that happens, it does more than just evaluate the variable; it resolves it, so it becomes undefined, causing the third line of the script to give the error:&lt;pre&gt;Object is invalid&lt;/pre&gt;This leaves me scratching my head a little, making the ability to pre-create object specifiers more than a little dodgy if they are this fragile.&lt;br /&gt;&lt;br /&gt;I'll have more to say on this subject as I explore further.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116881147627315268?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116881147627315268/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116881147627315268' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116881147627315268'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116881147627315268'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/01/object-specifiers.html' title='Object Specifiers'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116838059430302931</id><published>2007-01-09T13:25:00.000-08:00</published><updated>2007-01-09T14:20:20.590-08:00</updated><title type='text'>Back to the Pasteboard</title><content type='html'>OK, I've confirmed my suspicion that the current version of the script hits a run-time error if a locked layer holds a pasteboard item. I'm inclined to take the easy way out here and simply exclude any such layers from this game. If the user wants to add it back in, all he need do is unlock the layer and run the script again.&lt;br /&gt;&lt;br /&gt;So, now let's get down to the hard part: how to prevent items from colliding with pages?&lt;br /&gt;&lt;br /&gt;I think I'm going to assume that only left/right collisions are possible. I don't know enough about the dtptools plug-in that allows different sized pages, but since I don't own it and I'm writing this script as much for me as anyone else (so few people having taken any interest), I might as well not worry about things that aren't going to affect me.&lt;br /&gt;&lt;br /&gt;So, for any item, I have three rectangles to worry about:&lt;ol&gt;&lt;li&gt;The geometric bounds of the item&lt;/li&gt;&lt;li&gt;The bounds of the source spread&lt;/li&gt;&lt;li&gt;The bounds of the target spread&lt;/li&gt;&lt;/ol&gt;The only items I have to worry about are those that are wholly to the left or right of the source spread. I want them to appear the same distance to the left or right of the target spread. Notice that any item on the pasteboard above or below the pages that is not wholly to the left or right will not slide when moved to the target. Being where they are, they can't possibly collide with a page.&lt;br /&gt;&lt;br /&gt;Of course, getting the bounds of a spread is non-trivial unless you're using Spread as the ruler origin for your document, so I'm going to have to switch to spread at the start and switch back afterwards. Hmm, that means we'd better reset the zero point too and restore it. That means that for an item to be wholly left, it's right-bounds value must be negative because the spread left bound will always be zero; still, it's probably safer to compare just in case.&lt;br /&gt;&lt;br /&gt;Oh wait a cotton-picking minute! Because of the way that the spread-based coordinates work, the left side looks after itself. The coordinates are such that an item to the left of the spread will always be the same distance to the left. It's only the right side we have to worry about. Which means that all I have to worry about is the difference in width between the two spreads. So, perhaps I shouldn't even worry about whether or not an item is wholly right, just if it is right of the zero-X I should move it by the difference in widths.&lt;br /&gt;&lt;br /&gt;Well, what do you know? It seems to work!&lt;pre&gt;//DESCRIPTION: Moves all Pasteboard Items to Pasteboard of Current Spread&lt;br /&gt;&lt;br /&gt;if (app.documents.length == 0) { exit() }&lt;br /&gt;movePBitems(app.documents[0]);&lt;br /&gt;&lt;br /&gt;function movePBitems(myDoc) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (app.activeWindow.constructor.name == "StoryWindow") { return }&lt;br /&gt;&amp;nbsp;&amp;nbsp;var uO = myDoc.viewPreferences.rulerOrigin;&lt;br /&gt;&amp;nbsp;&amp;nbsp;var uZP = myDoc.zeroPoint;&lt;br /&gt;&amp;nbsp;&amp;nbsp;myDoc.viewPreferences.rulerOrigin = RulerOrigin.spreadOrigin;&lt;br /&gt;&amp;nbsp;&amp;nbsp;myDoc.zeroPoint = [0,0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;myObjs = myDoc.pageItems.everyItem().parent;&lt;br /&gt;&amp;nbsp;&amp;nbsp;var moveables = new Array();&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var j = myObjs.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myObjs[j].constructor.name == "Page") { continue }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;moveables.push(myDoc.pageItems[j].id);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;var mySpread = app.activeWindow.activeSpread;&lt;br /&gt;&amp;nbsp;&amp;nbsp;var targSpreadWidth = getSpreadWidth(mySpread);&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var j = moveables.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myObj = myDoc.pageItems.itemByID(moveables[j]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var sourceSpread = myObj.parent;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (sourceSpread == mySpread) { continue }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var sourceWidth = getSpreadWidth(sourceSpread);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var Shift = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myObj.geometricBounds[3] &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Shift = targSpreadWidth - sourceWidth;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myBounds = myObj.geometricBounds;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myObj.locked) { continue }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myObj.move(mySpread);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch(e) { continue } // myObj must be on locked layer&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myObj.move([myBounds[1] + Shift, myBounds[0]]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;myDoc.viewPreferences.rulerOrigin = uO;&lt;br /&gt;&amp;nbsp;&amp;nbsp;myDoc.zeroPoint = uZP;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;function getSpreadWidth(spread) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var my1stBounds = spread.pages[0].bounds;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myLastBounds = spread.pages[-1].bounds;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return myLastBounds[3] - my1stBounds[1];&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;} // end function movePBitems&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116838059430302931?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116838059430302931/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116838059430302931' title='23 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116838059430302931'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116838059430302931'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/01/back-to-pasteboard.html' title='Back to the Pasteboard'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>23</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116829236721085309</id><published>2007-01-08T13:34:00.000-08:00</published><updated>2007-01-08T13:39:27.223-08:00</updated><title type='text'>Blanking those Cell Labels</title><content type='html'>With a large table (I was working on one that spanned 18 pages, consisting of 399 rows with 7 columns), having all those cell labels is a blessing while you're looking for the overset text, but once that's out of the way, all they do is slow you down and make your document larger, so&lt;pre&gt;//DESCRIPTION: Clear Labels from Every Cell of Table&lt;br /&gt;&lt;br /&gt;if (app.documents.length == 0 || app.selection.length == 0) { exit() }&lt;br /&gt;processSelection(app.selection[0]);&lt;br /&gt;&lt;br /&gt;function processSelection(sel) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myTable = findTable(sel);&lt;br /&gt;&amp;nbsp;&amp;nbsp;myTable.cells.everyItem().label = "";&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;function findTable(obj) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while (obj.constructor.name != "Table") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;obj = obj.parent;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (obj.constructor.name == "Application") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw "Can't find table"&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return obj&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;Which of course raises the issue: is a blank label still present? From a script, you can't tell the difference between a blank label and an absent one, so functionally it makes no difference, but what about internal resources? All I can say is that my document became more responsive after I'd deleted the contents of all the cell labels.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116829236721085309?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116829236721085309/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116829236721085309' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116829236721085309'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116829236721085309'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/01/blanking-those-cell-labels.html' title='Blanking those Cell Labels'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116827672178077111</id><published>2007-01-08T09:10:00.000-08:00</published><updated>2007-01-08T09:18:41.893-08:00</updated><title type='text'>Aggregate Pasteboard</title><content type='html'>For those of you who provided feedback, many thanks. Here's how far I've gotten after a few minutes of scripting. This version does nothing to deal with the issue of facing pages spreads and the possibility that an item that started on the pasteboard will be moved to a page if the source spread has just one page but the target has two (or more).&lt;br /&gt;&lt;br /&gt;However, for single-sided documents, it works like a charm (I think -- if anyone wants to give it a spin, let me know if you find anything wrong.&lt;br /&gt;&lt;br /&gt;Note the following functional decisions:&lt;br /&gt;&lt;br /&gt;1. Use only with single-sided documents (for now).&lt;br /&gt;2. If the active window is a story editor window, the script does nothing.&lt;br /&gt;3. If a pasteboard item is locked, the script leaves it where it is. This allows the user to lock an item to a particular spread, for whatever reason.&lt;br /&gt;4. If a layer is invisible, any pasteboard item on that layer is nonetheless moved to the new pasteboard.&lt;br /&gt;&lt;br /&gt;Ooh, I bet there's a bug if the layer is locked. Well, I'll have to fix that the next time around. Here's the current state of the script:&lt;pre&gt;//DESCRIPTION: Moves all Pasteboard Items to Pasteboard of Current Spread&lt;br /&gt;&lt;br /&gt;if (app.documents.length == 0) { exit() }&lt;br /&gt;movePBitems(app.documents[0]);&lt;br /&gt;&lt;br /&gt;function movePBitems(myDoc) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (app.activeWindow.constructor.name == "StoryWindow") { return }&lt;br /&gt;&amp;nbsp;&amp;nbsp;myObjs = myDoc.pageItems.everyItem().parent;&lt;br /&gt;&amp;nbsp;&amp;nbsp;var moveables = new Array();&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var j = myObjs.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myObjs[j].constructor.name == "Page") { continue }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;moveables.push(myDoc.pageItems[j].id);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;var mySpread = app.activeWindow.activeSpread;&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var j = moveables.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myObj = myDoc.pageItems.itemByID(moveables[j]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myObj.parent == mySpread) { continue }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myBounds = myObj.geometricBounds;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myObj.locked) { continue }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myObj.move(mySpread);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myObj.move([myBounds[1], myBounds[0]]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;} // end function movePBitems&lt;/pre&gt;Enjoy,&lt;br /&gt;&lt;br /&gt;Dave&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116827672178077111?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116827672178077111/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116827672178077111' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116827672178077111'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116827672178077111'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/01/aggregate-pasteboard.html' title='Aggregate Pasteboard'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116826880051238469</id><published>2007-01-08T07:00:00.000-08:00</published><updated>2007-01-08T07:07:51.996-08:00</updated><title type='text'>Story Editor for Tables?</title><content type='html'>One of the frustrations of working with large tables is that if you have an overset cell, it is hard to find out what's in it from the UI. Would that you could summon the story editor to look at the contents of a cell! But you can't. So, it occurred to me to write a script to label all the cells of a table with their contents. This might be overkill -- perhaps a script to label only the selected cell would be sufficient, but by labeling them all I get the chance to have the Script Label palette open and immediately see the contents of an overset cell without having to do anything more than look. I suppose, later, I should run a script to clear out the labels just because of the amount of extra text my document will be carrying around.&lt;br /&gt;&lt;br /&gt;Here's the script:&lt;pre&gt;//DESCRIPTION: Label Every Cell of Table&lt;br /&gt;&lt;br /&gt;if (app.documents.length == 0 || app.selection.length == 0) { exit() }&lt;br /&gt;processSelection(app.selection[0]);&lt;br /&gt;&lt;br /&gt;function processSelection(sel) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myTable = findTable(sel);&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myCellLabels = myTable.cells.everyItem().texts[0].contents;&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var j = myTable.cells.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myTable.cells[j].label = myCellLabels[j];&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;function findTable(obj) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while (obj.constructor.name != "Table") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;obj = obj.parent;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (obj.constructor.name == "Application") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw "Can't find table"&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return obj&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;Ah, it's not true that I have to do nothing to see the label. I have to hit Command-/ to select the cell.&lt;br /&gt;&lt;br /&gt;Dave&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116826880051238469?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116826880051238469/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116826880051238469' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116826880051238469'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116826880051238469'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/01/story-editor-for-tables.html' title='Story Editor for Tables?'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116800256455899862</id><published>2007-01-05T04:45:00.000-08:00</published><updated>2007-01-05T05:09:24.643-08:00</updated><title type='text'>Universal Pasteboard</title><content type='html'>One disappointment that PageMaker users experience is that InDesign doesn't have a universal pasteboard. Instead, each spread has its own pasteboard. In a discussion this morning on the U2U forum, the idea was voiced that a feature in InDesign to move all pasteboard items to the current spread would be a good substitute.&lt;br /&gt;&lt;br /&gt;Well, that's what scripts are for.&lt;br /&gt;&lt;br /&gt;But before I leap in and write a script, I thought I'd post some immediate thoughts about what the script should do about certain situations.&lt;br /&gt;&lt;br /&gt;First, to find if an item is on the pasteboard, particularly if you know in advance that the item in question is know not to be inline or part of group (for example, the item is a member of the collection returned by document.pageItems).&lt;br /&gt;&lt;br /&gt;Something along these lines works:&lt;pre&gt;myDoc = app.activeDocument;&lt;br /&gt;myObjs = myDoc.pageItems.everyItem().parent;&lt;br /&gt;for (j = myObjs.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myObjs[j].constructor.name == "Page") { continue }&lt;br /&gt;&amp;nbsp;&amp;nbsp;moveToCurrentPB(myDoc.pageItems[j]);&lt;br /&gt;}&lt;/pre&gt;Of course, we still have to write the function, but some issues have to be resolved:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;What if the item is on a hidden layer?&lt;/li&gt;&lt;li&gt;What if the item is locked?&lt;/li&gt;&lt;li&gt;What if the location of the item on its source spread would put it on a page of the current spread?&lt;/li&gt;&lt;li&gt;Conversely, what if, when moving from a two-page spread to a single-page spread, the item will be more than a page width away from the live page?&lt;/li&gt;&lt;li&gt;While we're thinking about that, what about multi-page spreads or even dragged apart single-page spreads?&lt;/li&gt;&lt;/ol&gt;Clearly, these questions need to be answered before I can write the function. I welcome any input.&lt;br /&gt;&lt;br /&gt;Dave&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116800256455899862?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116800256455899862/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116800256455899862' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116800256455899862'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116800256455899862'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/01/universal-pasteboard.html' title='Universal Pasteboard'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116782908915126964</id><published>2007-01-03T04:50:00.000-08:00</published><updated>2007-01-03T04:58:09.170-08:00</updated><title type='text'>Multi-column Text Frames</title><content type='html'>Suddenly, I've found myself doing a lot of work with multi-column text frames, so when the issue came up on the U2U forum of which column within a text frame contained some text, it was fairly short work to bang out this function:&lt;pre&gt;function getColumnNum(mysearch) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;myFrame = mysearch.parentTextFrames[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;myColumnStarts = myFrame.textColumns.everyItem().index;&lt;br /&gt;&amp;nbsp;&amp;nbsp;mySpot = mysearch.index;&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var j = myColumnStarts.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (mySpot &gt;= myColumnStarts[j]) { return j }&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;throw "What a revolting development this is"&lt;br /&gt;}&lt;/pre&gt;I wasn't quite sure what to do if the loop dropped through. It's one of those theoretically impossible happenings that nonetheless is syntactically possible. In theory, if &lt;i&gt;myFrame&lt;/i&gt; is the parent text frame of the text reference contained in &lt;i&gt;mysearch&lt;/i&gt; then the loop will never terminate. The error will never be thrown.&lt;br /&gt;&lt;br /&gt;So, I borrowed a leaf from the immortal William Bendix (start of "A Life of Riley") and threw an amusing, albeit unhelpful, error message.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116782908915126964?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116782908915126964/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116782908915126964' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116782908915126964'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116782908915126964'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/01/multi-column-text-frames.html' title='Multi-column Text Frames'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116766446670224024</id><published>2007-01-01T06:29:00.000-08:00</published><updated>2007-01-01T07:14:26.773-08:00</updated><title type='text'>Installing and Running a Script</title><content type='html'>It is a surprise for me to discover that I've never actually written a blog entry on installing scripts. I guess I took it for granted that visitors here already knew how to do that! But just in case, and also because I need to have a description in a central location I can point people to, here is a capsule description of the process for InDesign CS2.&lt;br /&gt;&lt;br /&gt;For InDesign CS2 to even be aware of a script, it must be located within the Scripts folder of the Presets folder of the Adobe InDesign CS2 application folder. Any scripts located in that Scripts folder or its subfolders are listed in InDesign CS2's Scripts Palette.&lt;br /&gt;&lt;br /&gt;That's it in a nutshell, but there are some subtleties to watch out for and some common mistakes that people make.&lt;h4&gt;Locating the Scripts Palette&lt;/h4&gt;Like most all of InDesign CS2's palettes, the Scripts palette is accessible from the Window menu, but it is not direcly in that menu. It is in the Automation sub-menu along with two other palettes, Data Merge and Script Label. When you activate the Scripts palette, you'll find that it shares space with the Script Label palette. I find this inconvenient and so I've separated the two into different groups. I have Scripts sharing space with the Hyperlinks, Bookmarks and Tags palettes, while the Script Label palette shares with the Pathfinder and Story palettes.&lt;h4&gt;Installing a zipped script&lt;/h4&gt;If you download a script contained in a zipped (or stuffed) archive, the installation process is very simple. First, unzip (or unstuff) the archive. You'll find yourself either with the script file itself, by itself, or a folder containing the script and associated files. In the latter case, if any of those associated files is a read-me file of some kind or a user guide, be sure to read it and follow the instructions it gives—some scripts, for example, depend on other support files being located in particular places. But, if all you have is the single script file, then move or copy it to an appropriate subfolder of the Scripts folder, as described above.&lt;h4&gt;Copying and Pasting the Source of a Script&lt;/h4&gt;Many scripts are posted on forums in the form of listings of their original source texts. To install such a script on to your computer, simply copy the text to a text editor of some kind or even better to ExtendScript Toolkit and then save the script file into a subfolder of the Scripts folder with a name of the form: ScriptName.jsx where "ScriptName" is a name you choose that reflects the purpose of the script. If you choose to use an application like Apple's TextEdit or Microsoft's Word, be sure to save as Plain Text (in the case of TextEdit, you have to use the Format menu to convert to Plain Text before Saving). Should you inadvertently save in some other format, your attempts to run the script will be frustrated by a syntax error message.&lt;h4&gt;Beware the Plug-Ins/Script Folder&lt;/h4&gt;Do not put your scripts into the folder named Script within the Plug-Ins folder of the Adobe InDesign CS2 application folder. That's where the plug-ins that provide scripting support live. Any scripts you put into that folder will not appear in your Scripts palette.&lt;h4&gt;Consider Using Aliased Sub-folders&lt;/h4&gt;Having scripts located physically inside the application folder can be somewhat inconvenient, particularly if you are a scripter, working on the scripts themselves, rather than just using them as tools. But even script users might be frustrated if they forget that the scripts are there and for some reason re-install InDesign, obliterating the Scripts folder in the process.&lt;br /&gt;&lt;br /&gt;While you could use an alias of the whole Scripts folder to work around this issue, I have found it better to work with aliased sub-folders. This allows me to keep my scripts in folders inside my Documents folder, while for some projects, I have a Scripts folder associated with the project itself for custom scripts that apply to just that project.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116766446670224024?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116766446670224024/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116766446670224024' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116766446670224024'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116766446670224024'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2007/01/installing-and-running-script.html' title='Installing and Running a Script'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116684498795329458</id><published>2006-12-22T19:25:00.000-08:00</published><updated>2006-12-22T19:36:27.966-08:00</updated><title type='text'>Cut Contents</title><content type='html'>Every now and then a FreeHand user will pine for its Cut Contents command. This allows FreeHand users to select a frame that is serving as a mask by virtue of a previous Paste Into command. InDesign's user interface actually makes this fairly easy to achieve.&lt;ol&gt;&lt;li&gt;With the selection tool (black pointer), select the frame.&lt;/li&gt;&lt;li&gt;In the Control palette, click the Select Contents button.&lt;/li&gt;&lt;li&gt;Choose Edit/Cut, or use the keyboard shortcut for Cut.&lt;/li&gt;&lt;/ol&gt;But most users seem to take a long time to discover those selection buttons in the Control palette. They also seem to be unaware of the equivalent functionality on the Select submenu of the Object menu. And so, if they're familiar with FreeHand, they search in vain for the Cut Contents command.&lt;br /&gt;&lt;br /&gt;The topic came up again this morning on the U2U forum where I posted a simpler version of the script below. See: http://adobeforums.com/cgi-bin/webx?14@@.3bc2a49a/3&lt;br /&gt;&lt;br /&gt;As you can see in that posting, I immediately realized there was a problem if the user ran that version of the script with text selected. As the day wore on, I realized that groups and xmlElements also are problematic, so I ended up with this version of the script:&lt;pre&gt;//DESCRIPTION: Cut Contents&lt;br /&gt;&lt;br /&gt;Object.prototype.isIneligible = function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;switch(this.constructor.name){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "InsertionPoint":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Character":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Word":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "TextStyleRange":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Line":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Paragraph":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "TextColumn":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Text":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "TextFrame":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Group" :&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "XMLElement" :&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default :&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;if (app.documents.length == 0 || app.selection.length != 1 || app.selection[0].isIneligible()) { exit() }&lt;br /&gt;processSelection(app.selection[0]);&lt;br /&gt;&lt;br /&gt;function processSelection(sel) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.select(sel.pageItems[0])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.cut();&amp;nbsp;&amp;nbsp;} catch(e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert("Selected item has no contents")&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;I keep telling myself that one of these days I'll script the equivalent of FreeHand's Attach Type to Path command, particularly the variation where a two-line text is attached to the top and bottom of an ellipse.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116684498795329458?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116684498795329458/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116684498795329458' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116684498795329458'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116684498795329458'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/12/cut-contents.html' title='Cut Contents'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116557961957899579</id><published>2006-12-08T03:56:00.000-08:00</published><updated>2006-12-08T04:06:59.606-08:00</updated><title type='text'>Story on Live Pages</title><content type='html'>I'm working with a document that has a lot of busy master spreads. But what I needed was to identify a particular story on the live pages. The frames are labeled, but so are the original frames on the master pages. So, it occurred to me to wonder if I could easily get a list of all the frames on the live pages of a document, ignoring the ones on the master pages.&lt;br /&gt;&lt;br /&gt;Turns out that a double-dose of everyItem() gets the job done:&lt;pre&gt;myFrames = doc.pages.everyItem().textFrames.everyItem().getElements();&lt;/pre&gt;This produces an array of all the text frames free-standing on the live pages of a document. So, I can examine that list to find the story of interest without having to be concerned about the master frames that have the same label.&lt;br /&gt;&lt;br /&gt;Speaking of labeling, I used this in a function that records the identity of the story of interest in a document label, so that the story only has to be found once:&lt;pre&gt;&amp;nbsp;&amp;nbsp;function getMainStory(doc) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var label = doc.extractLabel("Main Story")&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (label != "") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return doc.stories.itemByID(Number(label));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myFrames = doc.pages.everyItem().textFrames.everyItem().getElements();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for (var j = myFrames.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myFrames[j].extractLabel("Frame Type") == "Main Story Frame") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myStoryID = myFrames[j].parentStory.id;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;doc.insertLabel("Main Story", String(myStoryID));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return doc.stories.itemByID(myStoryID)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;} // end getMainStory&lt;br /&gt;&lt;/pre&gt;I'm not fully convinced that this is the most efficient way of achieving this goal, but it works and after the first time, it is very quick.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116557961957899579?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116557961957899579/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116557961957899579' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116557961957899579'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116557961957899579'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/12/story-on-live-pages.html' title='Story on Live Pages'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-116050261740605787</id><published>2006-10-10T10:40:00.000-07:00</published><updated>2006-10-10T10:52:53.810-07:00</updated><title type='text'>Avoiding Math</title><content type='html'>Faced with the task of making sure that a rectangle was trimmed so it fit on its page (that is, I needed to crop any part of it that bled off the page), my first thought was to start messing around with comparing geometric bounds, but then it hit me that the Pathfinder functions are available to a script, so what if I create a rectangle that is exactly the same size as the page and then use Intersect?&lt;br /&gt;&lt;br /&gt;Sounds good. In the UI, the resultant page item takes on the characteristics of the front item of the two (or more, I suppose, but I'm only work with two items), so my first thought was to create the rectangle and send it to the back, like this:&lt;pre&gt;myPage = app.selection[0].parent;&lt;br /&gt;myNewRect = myPage.textFrames.add({geometricBounds:myPage.bounds});&lt;br /&gt;myNewRect.sendToBack();&lt;br /&gt;myNewShape = app.selection[0].intersectPath(myNewRect)&lt;/pre&gt;Hey! I hear you say, that's not a rectangle, it's a text frame you created. Well that's to end-run the issues of stroke weights. A frame is rectangular and after using intersectPath, the new shape will have the characteristics of the front item, so it doesn't matter.&lt;br /&gt;&lt;br /&gt;Except that this doesn't work. The UI's front/back rule doesn't apply in scripts. What matters is which object has the method applied to it. So, the script I was really looking for was this:&lt;pre&gt;myPage = app.selection[0].parent;&lt;br /&gt;myNewRect = myPage.textFrames.add({geometricBounds:myPage.bounds});&lt;br /&gt;myNewShape = myNewRect.intersectPath(app.selection[0])&lt;/pre&gt;No need to send to back; just apply the method to the new frame and the result takes on the characteristics (including the script label, I'm pleased to say) of the argument object.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-116050261740605787?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/116050261740605787/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=116050261740605787' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116050261740605787'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/116050261740605787'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/10/avoiding-math.html' title='Avoiding Math'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115867884789845164</id><published>2006-09-19T08:04:00.000-07:00</published><updated>2006-09-19T08:14:09.120-07:00</updated><title type='text'>Drawing Maps</title><content type='html'>Sometimes, the darndest things happen. I know that Illustrator is probably a better choice for drawing maps, but I'm still intimidated by Illustrator and anyway, InDesign has all the drawing tools I need.&lt;br /&gt;&lt;br /&gt;But I ran into two problems that needed a quick and dirty one-line script.&lt;br /&gt;&lt;br /&gt;To give my map a frame, I needed to group the map and paste it inside a rectangle. Having done that, I couldn't persuade the Type on a Path tool to work. So:&lt;pre&gt;myTP = app.selection[0].textPaths.add({contents:"Dummy Text"});&lt;/pre&gt;And bingo! Selecting the right path was easy and this script got the ball rolling so I could now use the UI to massage the text and prettify it!&lt;br /&gt;&lt;br /&gt;But then I made the PDF and there were a whole bunch of elements that were visible when viewed in Mail and even in Acrobat 7 that shouldn't have been there. Somehow, I'd pasted more stuff into my map than I intended, and even though it was invisible in InDesign it was showing up in the PDF.&lt;br /&gt;&lt;br /&gt;Well, I already have a script for seeking out instances of object styles in a document, so I was able to find one of these objects by deploying that script. But it quickly became boring deleting them one at a time when what I wanted to do was delete the lot.&lt;br /&gt;&lt;br /&gt;For some reason, having selected one, clicking the Select Container button on the Control palette to get the group selected worked, but then hitting delete didn't.&lt;br /&gt;&lt;br /&gt;So, another one-line script to the rescue:&lt;pre&gt;app.selection[0].parent.pageItems.everyItem().remove();&lt;/pre&gt;I had to be a bit careful about deploying this one, but eventually, I got rid of all the clutter in my PDF (which reduced in size from 55K to 40K as a result of running this script, so I know it got rid of a bunch of invisible stuff.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115867884789845164?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115867884789845164/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115867884789845164' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115867884789845164'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115867884789845164'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/09/drawing-maps.html' title='Drawing Maps'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115703590785185091</id><published>2006-08-31T06:54:00.000-07:00</published><updated>2006-08-31T07:51:48.546-07:00</updated><title type='text'>Addressing Strings as Arrays</title><content type='html'>I had an eye-0pening shock the other day. I was working on a string that was an exact image of the contents of a text object in my document, except that in the string the "smart quotes" had all been dumbed down. I wanted to write a loop that would look through this string and wherever it encountered a dumb quote, it would set the contents of the corresponding character in the text to the same dumb quote.&lt;br /&gt;&lt;br /&gt;So, I could have used charAt(); I could have written a loop that used lastIndexOf; but I wasn't in the mood. It seemed to me that all I had to do was convert the string to an array of single characters and then iterate through the array in the standard way. I intended to write:&lt;pre&gt;myString = myString.split("");&lt;br /&gt;for (var j = myString.length - 1; j &gt;= 0; j--) {&lt;br /&gt;  if (myString[j] == "'" || myString[j] == '"') {&lt;br /&gt;    myText.characters[j].contents = myString[j];&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;So I tested it and saw that it had worked and all was well and peace descended on my soul.&lt;br /&gt;&lt;br /&gt;But then I had a heart-stopping realization. I'd forgotten to include that first statement which converts myString to an array. What! How did the script work if I'd made this mistake?&lt;br /&gt;&lt;br /&gt;It turns out that you don't need charAt() to read the characters of a string. You can read them by addressing them as though they were members of an array. But this is the only thing you can do in this fashion. If you try to set a character this way it won't work. If you try to sort the characters, it won't work. But if all you want to do is read the individual characters, it works like a charm!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115703590785185091?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115703590785185091/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115703590785185091' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115703590785185091'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115703590785185091'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/08/addressing-strings-as-arrays.html' title='Addressing Strings as Arrays'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115428936027479222</id><published>2006-07-30T12:49:00.000-07:00</published><updated>2006-07-30T12:56:00.286-07:00</updated><title type='text'>How to Crash InDesign</title><content type='html'>Use the script in that last post from yesterday on a table of 10 or more rows!&lt;br /&gt;&lt;br /&gt;Why does it crash? Because of a stupid error that is hard to see. The problem is that the start and end values I'm calculating are strings, not numbers, so the internal loop compares (in the case I was working with) 2 with 11 and concludes that 2 is greater than 11 (because as strings, that's true) and so the loop doesn't execute. This results in the script concluding that each of the columns should have a width of Infinity.&lt;br /&gt;&lt;br /&gt;Believe it or not, this works, causing the table to become overset (there's a surprise). You can even undo to get back to where you were. But if you don't and you try to work with the document it will crash and then it is irretrievably corrupted.&lt;br /&gt;&lt;br /&gt;Here's a corrected version of the script. You'll notice I changed the names of the arguments to getCell because I thought that might have something to do with the problem until I realized what was really going on.&lt;pre&gt;//DESCRIPTION: Table Space Equalizer&lt;br /&gt;&lt;br /&gt; /*&lt;br /&gt;    Expects a multi-column selection within a table. Adjusts widths of selected&lt;br /&gt;    columns so that space after the longest entry in each column is equalized and&lt;br /&gt;    the overall width of the table is unchanged.&lt;br /&gt; */&lt;br /&gt;&lt;br /&gt;if ((app.documents.length == 0) || (app.selection.length == 0)) { exit() }&lt;br /&gt;&lt;br /&gt;aDoc = app.activeDocument;&lt;br /&gt;aSel = app.selection[0];&lt;br /&gt;&lt;br /&gt;try {&lt;br /&gt;    theCols = {start: Number(aSel.cells[0].name.split(":")[0]),end: Number(aSel.cells[-1].name.split(":")[0])};&lt;br /&gt;    theRows = {start: Number(aSel.cells[0].name.split(":")[1]),end: Number(aSel.cells[-1].name.split(":")[1])};&lt;br /&gt;    theTable = aSel.parent;&lt;br /&gt;} catch (e) {&lt;br /&gt;    alert("Selection needs to be in a table, and rectangular"), exit();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// For each column, calculate the min spare space:&lt;br /&gt;minSpaces = new Array();&lt;br /&gt;for (c = theCols.start; theCols.end &gt;= c; c++) {&lt;br /&gt;    minSpace = Infinity;&lt;br /&gt;    for (r = theRows.start; theRows.end &gt;= r; r++) {&lt;br /&gt;        minSpace = Math.min(minSpace, space(getCell(theTable, c, r,)));&lt;br /&gt;    }&lt;br /&gt;    minSpaces.push(minSpace);&lt;br /&gt;}&lt;br /&gt;totSpace = 0;&lt;br /&gt;for (j = 0; minSpaces.length &gt; j; j++) {&lt;br /&gt;    totSpace = totSpace + minSpaces[j]&lt;br /&gt;}&lt;br /&gt;newSpace = totSpace/minSpaces.length;&lt;br /&gt;for (c = theCols.start; theCols.end &gt;= c; c++) {&lt;br /&gt;    theTable.columns[c].width = theTable.columns[c].width + newSpace - minSpaces[c - theCols.start];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function getCell(table, n, m) {&lt;br /&gt;    return table.cells.item(n + ":" + m);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function space(theCell) {&lt;br /&gt;    // This version assumes that the cell is left aligned and there's only one line&lt;br /&gt;    var left = theCell.texts[0].insertionPoints[0].horizontalOffset;&lt;br /&gt;    theCell.texts[0].justification = Justification.rightAlign;&lt;br /&gt;//    theCell.texts[0].recompose();&lt;br /&gt;    var spare = theCell.texts[0].insertionPoints[0].horizontalOffset - left;&lt;br /&gt;    theCell.texts[0].justification = Justification.leftAlign;&lt;br /&gt;    return spare;&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115428936027479222?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115428936027479222/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115428936027479222' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115428936027479222'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115428936027479222'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/07/how-to-crash-indesign.html' title='How to Crash InDesign'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115420799664661934</id><published>2006-07-29T14:17:00.000-07:00</published><updated>2006-07-30T12:57:38.120-07:00</updated><title type='text'>Completed Script</title><content type='html'>As the comment under the description indicates, this script only has applicability in limited situations, but it works for what I'm doing right now and the result is spectacular. I'll revisit this later and make it work for multi-line cells, but don't hold your breath if you need it urgently.&lt;br /&gt;&lt;br /&gt;&lt;h4&gt;Warning: This Script Has a Serious Problem; see next post.&lt;/h4&gt;&lt;br /&gt;&lt;pre&gt;//DESCRIPTION: Table Space Equalizer&lt;br /&gt;&lt;br /&gt;/*&lt;br /&gt;   Expects a multi-column selection within a table. Adjusts widths of selected&lt;br /&gt;   columns so that space after the longest entry in each column is equalized and&lt;br /&gt;   the overall width of the table is unchanged.&lt;br /&gt;*/&lt;br /&gt;&lt;br /&gt;if ((app.documents.length == 0) || (app.selection.length == 0)) { exit() }&lt;br /&gt;&lt;br /&gt;aDoc = app.activeDocument;&lt;br /&gt;aSel = app.selection[0];&lt;br /&gt;&lt;br /&gt;try {&lt;br /&gt;   theCols = {start: aSel.cells[0].name.split(":")[0],end: aSel.cells[-1].name.split(":")[0]};&lt;br /&gt;   theRows = {start: aSel.cells[0].name.split(":")[1],end: aSel.cells[-1].name.split(":")[1]};&lt;br /&gt;   theTable = aSel.parent;&lt;br /&gt;} catch (e) {&lt;br /&gt;   alert("Selection needs to be in a table, and rectangular"), exit();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// For each column, calculate the min spare space:&lt;br /&gt;minSpaces = new Array();&lt;br /&gt;for (c = theCols.start; theCols.end &gt;= c; c++) {&lt;br /&gt;   minSpace = Infinity;&lt;br /&gt;   for (r = theRows.start; theRows.end &gt;= r; r++) {&lt;br /&gt;       minSpace = Math.min(minSpace, space(getCell(theTable, c, r,)));&lt;br /&gt;   }&lt;br /&gt;   minSpaces.push(minSpace);&lt;br /&gt;}&lt;br /&gt;totSpace = 0;&lt;br /&gt;for (j = 0; minSpaces.length &gt; j; j++) {&lt;br /&gt;   totSpace = totSpace + minSpaces[j]&lt;br /&gt;}&lt;br /&gt;newSpace = totSpace/minSpaces.length;&lt;br /&gt;for (c = theCols.start; theCols.end &gt;= c; c++) {&lt;br /&gt;   theTable.columns[c].width = theTable.columns[c].width + newSpace - minSpaces[c - theCols.start];&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function getCell(table, c, r) {&lt;br /&gt;   return table.cells.item(c + ":" + r);&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function space(theCell) {&lt;br /&gt;   // This version assumes that the cell is left aligned and there's only one line&lt;br /&gt;   var left = theCell.texts[0].insertionPoints[0].horizontalOffset;&lt;br /&gt;   theCell.texts[0].justification = Justification.rightAlign;&lt;br /&gt;//    theCell.texts[0].recompose();&lt;br /&gt;   var spare = theCell.texts[0].insertionPoints[0].horizontalOffset - left;&lt;br /&gt;   theCell.texts[0].justification = Justification.leftAlign;&lt;br /&gt;   return spare;&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115420799664661934?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115420799664661934/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115420799664661934' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115420799664661934'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115420799664661934'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/07/completed-script.html' title='Completed Script'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115419133841063326</id><published>2006-07-29T09:35:00.000-07:00</published><updated>2006-07-29T09:42:18.420-07:00</updated><title type='text'>Cracked it, But...</title><content type='html'>First, I walked into another trap. Here is my first attempt to address the issue. I decided to make my two variables theCols and theRows record objects that contain two properties named start and end into which I put the relevant row and column indexes, like this:&lt;pre&gt;if ((app.documents.length == 0) || (app.selection.length == 0)) { exit() }&lt;br /&gt;&lt;br /&gt;aDoc = app.activeDocument;&lt;br /&gt;aSel = app.selection[0];&lt;br /&gt;&lt;br /&gt;try {&lt;br /&gt;    theCols = {start: aSel.cells[0].name.split(":")[0],end: aSel.cells[-1].name.split(":")[0]};&lt;br /&gt;    theRows = {start: aSel.cells[0].name.split(":")[1],end: aSel.cells[-1].name.split(":")[1]};&lt;br /&gt;    theTable = aSel.parent;&lt;br /&gt;} catch (e) {&lt;br /&gt;    alert("Selection needs to be in a table, and rectangular"), exit();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;alert(theTable.columns[theCols.start].cells[theRows.start].contents);&lt;/pre&gt;And blow me but I still got the wrong value. This time, I got the cell below the one I was expecting to get. Why? because of a merged row in the top row of the table. That means that the column I'm looking at doesn't have a cell in that row, so using the cell row number as an index to the column's cells misses by one (in this case).&lt;br /&gt;&lt;br /&gt;The correct way to address the cell I need is:&lt;pre&gt;alert(theTable.cells.item([theCols.start] + ":" + [theRows.start]).contents);&lt;/pre&gt;which looks pretty ugly, but it has the benefit of working!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115419133841063326?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115419133841063326/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115419133841063326' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115419133841063326'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115419133841063326'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/07/cracked-it-but.html' title='Cracked it, But...'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115419029245879400</id><published>2006-07-29T09:18:00.000-07:00</published><updated>2006-07-29T09:24:52.480-07:00</updated><title type='text'>Addressing a Selection inside a Table</title><content type='html'>I'm working on a script this morning that is intended to equalize the spare space in part of a table so that the extra space is equally distributed across the selected columns. Life would be easier if the script had to work on the whole table, but that's not what I need. I need it to work on a selection of rows and columns. So, the first thing the script needs to do is workout which part of the table is selected.&lt;br /&gt;&lt;br /&gt;You'd think this would be easy, but there is a bug (or an implementation decision) that really bites if you go at this what seems the reasonable way: &lt;pre&gt;if ((app.documents.length == 0) || (app.selection.length == 0)) { exit() }&lt;br /&gt;&lt;br /&gt;aDoc = app.activeDocument;&lt;br /&gt;aSel = app.selection[0];&lt;br /&gt;&lt;br /&gt;try {&lt;br /&gt;    theCols = aSel.columns;&lt;br /&gt;    theRows = aSel.rows;&lt;br /&gt;} catch (e) {&lt;br /&gt;    alert("Selection needs to be in a table"), exit();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;alert(theCols[0].cells[0].contents);&lt;/pre&gt;Run this with a selection that does not include the top-left corner of the table and you'll be surprised to see that the alert gives you the contents of that top-left cell. It appears that when asking for the columns or rows of a selection within a table in this straightforward manner returns the wrong columns and rows.&lt;br /&gt;&lt;br /&gt;So watch out. I'll report back later on the technique I end up using to get around this.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115419029245879400?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115419029245879400/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115419029245879400' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115419029245879400'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115419029245879400'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/07/addressing-selection-inside-table.html' title='Addressing a Selection inside a Table'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115411485190214979</id><published>2006-07-28T12:04:00.000-07:00</published><updated>2006-07-29T04:43:32.543-07:00</updated><title type='text'>The Emaciated, Anonymous, Invisible Parent of Root</title><content type='html'>You would think that the Root element would be the bottom of the XML element parental tree. After all, that's what the structure panel shows. But if you ask for: &lt;pre&gt;app.activeDocument.associatedXMLElement.markUpTag.name;&lt;/pre&gt; you'll be surprised to get an error: "Object does not support the property or method 'markUpTag'"--how can this be?&lt;br /&gt;&lt;br /&gt;Well, it turns out that the Root element is not the end of the line when it comes to traversing the XML elements of your document. There's another element that serves as the parent of the Root element. This particular element is somewhat emaciated. It does not have the full capabilities of a regular XML element.&lt;br /&gt;&lt;br /&gt;Contrast the result of:&lt;pre&gt;app.activeDocument.associatedXMLElement.properties.toSource()&lt;/pre&gt;which returns:&lt;pre&gt;({storyOffset:0,&lt;br /&gt;parentStory:null,&lt;br /&gt;contents:"﻿﻿",&lt;br /&gt;id:1,&lt;br /&gt;parent:resolve("/document[@name=\"Untitled-2\"]"),&lt;br /&gt;index:-1})&lt;/pre&gt; with the result of:&lt;pre&gt;app.activeDocument.associatedXMLElement.xmlElements[0].properties.toSource()&lt;/pre&gt; which returns:&lt;pre&gt;({storyOffset:1,&lt;br /&gt;parentStory:null,&lt;br /&gt;markupTag:resolve("/document[@name=\"Untitled-2\"]//XML-tag[@id=171]"),&lt;br /&gt;contents:"",&lt;br /&gt;id:2,&lt;br /&gt;parent:resolve("/document[@name=\"Untitled-2\"]/XML-element[@id=1]"),&lt;br /&gt;index:0})&lt;/pre&gt;See how this second xmlElement, which in fact is the Root element, has more properties than the first. One of those properties is a markUpTag, so let's take a look at that:&lt;pre&gt;app.activeDocument.xmlTags.itemByID(171).properties.toSource();&lt;/pre&gt;returns:&lt;pre&gt;({name:"Root",&lt;br /&gt;tagColor:1766613612,&lt;br /&gt;id:171,&lt;br /&gt;label:"",&lt;br /&gt;parent:resolve("/document[@name=\"Untitled-2\"]"),&lt;br /&gt;index:0})&lt;/pre&gt;So there you have it, the emaciated, anonymous, invisible parent of Root.&lt;br /&gt;&lt;br /&gt;&lt;h2&gt;Update *** Warning&lt;/h2&gt;&lt;br /&gt;Knowing that this element is there can be important if you're debugging a script that stumbles upon it, but please have the discipline to leave it alone. Attempts to change the values of its properties will at best fail and there is some danger of crashing InDesign. The reason this element is anonymous and invisible in the UI is precisely because it is not a regular XML Element to be used along with all the others.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115411485190214979?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115411485190214979/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115411485190214979' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115411485190214979'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115411485190214979'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/07/emaciated-anonymous-invisible-parent.html' title='The Emaciated, Anonymous, Invisible Parent of Root'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115408862887221322</id><published>2006-07-28T04:53:00.000-07:00</published><updated>2007-06-15T07:14:22.760-07:00</updated><title type='text'>Sample Scripts Won't Work</title><content type='html'>What with family weddings and various other interferences, I've been neglecting this blog for a while. Sorry about that, but the description does warn that entries might be sporadic.&lt;br /&gt;&lt;br /&gt;There's been a spate of complaints lately that the sample scripts provided with InDesign CS2 don't work. By which is usually meant: "I double-click the script name in the palette and nothing happens."&lt;br /&gt;&lt;br /&gt;The cause of this problem is the userInteractionLevel property. As I wrote back in November (see: &lt;a href="http://jsid.blogspot.com/2005/11/open-with-no-warnings.html"&gt;Open with no Warnings&lt;/a&gt;), a script can control the amount of interaction it does with the user. This is useful for causing dialogs to not appear with warnings that the script doesn't care about.&lt;br /&gt;&lt;br /&gt;But:&lt;ul&gt;&lt;li&gt;The property is application-wide and sticky.&lt;/li&gt;&lt;li&gt;The property is not stored with the regular InDesign Preferences, so it survives trashing preferences.&lt;/li&gt;&lt;li&gt;Scripts can accidentally (or negligently) leave it switched off.&lt;/li&gt;&lt;/ul&gt;The big problem is in that third bullet. If a script, any script, leaves your installation in the state created by:&lt;pre&gt;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.neverInteract;&lt;/pre&gt;Then any other script you might run afterwards will not be able to show you dialogs. If they try, it will be as though you clicked Cancel even before the dialog was painted.&lt;br /&gt;&lt;br /&gt;The solution is simple, once you realize this possibility: simply precede any attempt to show a dialog with:&lt;pre&gt;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;&lt;/pre&gt;and the problem goes away. In a perfect world, this should not be necessary because any script which switches off user interaction should switch it back on again before quitting. But scripts have no control over the power company -- to name one reason why a script might not run properly to completion.&lt;br /&gt;&lt;br /&gt;So, if you're trying to use those built-in sample scripts and they mysteriously refuse to do anything, the quick solution is to save this:&lt;pre&gt;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;&lt;/pre&gt;as a script named "RestoreInteraction.jsx" and run it. For a more permanent solution, edit those sample scripts to include this line immediately before the line(s) where the show() method is used to display a dialog.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115408862887221322?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115408862887221322/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115408862887221322' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115408862887221322'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115408862887221322'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/07/sample-scripts-wont-work.html' title='Sample Scripts Won&apos;t Work'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115055953780287289</id><published>2006-06-17T08:38:00.000-07:00</published><updated>2006-06-17T08:52:17.843-07:00</updated><title type='text'>Sometimes, You Need a Blunt Instrument</title><content type='html'>I have a very complex script that does a lot of automatic page composition. One of the elements it has to work with is an inline group of figures with a caption. During the process of importing the images into this group, the group changes size. Because of the way the group is constructed, it is impossible to keep the inline sitting properly on the baseline as this happens.&lt;br /&gt;&lt;br /&gt;So, the solution is to move it back inline. Well, I already had a function at hand that does this:&lt;pre&gt;function moveInlineToBaseline(theGraph) {&lt;br /&gt;  var myBase = theGraph.parent.baseline;&lt;br /&gt;  var myBounds = theGraph.geometricBounds;&lt;br /&gt;  theGraph.move(undefined, [0, myBase - myBounds[2]]);&lt;br /&gt; theGraph.parent.parentStory.recompose();&lt;br /&gt;}&lt;/pre&gt;I wrote this for any inline graphic; hence the argument name &lt;i&gt;theGraph&lt;/i&gt;. But for some reason, this logic failed on one instance in a very long document. The move command threw up an error to the effect that the object couldn't be moved by the requested amount because it would fall outside the bounding box of the containing frame. This was not true based on my measurements and examining the data, and I cannot see anything wrong with the logic, but attempting some debugging in ESTK, I discovered that the group in question couldn't be moved at all. Clearly, something odd was going on, but what?&lt;br /&gt;&lt;br /&gt;Well, sometimes you just have to bite the bullet and try some other approach. In this case, I went for the Cut/Paste blunt instrument approach:&lt;pre&gt;function moveInlineToBaseline(theGraph) {&lt;br /&gt;  var myBase = theGraph.parent.baseline;&lt;br /&gt;  var myBounds = theGraph.geometricBounds;&lt;br /&gt;  try {&lt;br /&gt;    theGraph.move(undefined, [0, myBase - myBounds[2]]);&lt;br /&gt;  } catch (e) {&lt;br /&gt;    // I don't know why this should ever fail, but I have an example, so let's go at it a different way in this case&lt;br /&gt;    var myIPindex = theGraph.parent.index;&lt;br /&gt;    var myStory = theGraph.parent.parentStory;&lt;br /&gt;    app.select(theGraph);&lt;br /&gt;    app.cut();&lt;br /&gt;    app.select(myStory.insertionPoints[myIPindex]);&lt;br /&gt;    app.paste();&lt;br /&gt;    myStory.recompose();&lt;br /&gt;    return&lt;br /&gt;  }&lt;br /&gt;  theGraph.parent.parentStory.recompose();&lt;br /&gt;}&lt;/pre&gt;And that worked. With any luck, that Cut/Paste path through the script will never again be used, but if it is, I can be confident it will work because it solved my problem.&lt;br /&gt;&lt;br /&gt;Note that when you paste a frame or group inline, it  sits on the baseline properly, even if, as in this case, it was not on the baseline when it was cut. That's because I selected the object and not the character containing the object. Had I done the latter, the position relative to the baseline would have been part of the cut information.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115055953780287289?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115055953780287289/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115055953780287289' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115055953780287289'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115055953780287289'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/06/sometimes-you-need-blunt-instrument.html' title='Sometimes, You Need a Blunt Instrument'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115047227944423961</id><published>2006-06-16T08:17:00.000-07:00</published><updated>2006-06-16T08:37:59.566-07:00</updated><title type='text'>Smart Title Case Revisited</title><content type='html'>In response to a request, I've updated the Smart Title Case script I posted last August (and wrote about &lt;a href="http://jsid.blogspot.com/2005/08/script-of-day-smart-title-case.html"&gt;here&lt;/a&gt;). The new version is now available in the Featured Scripts Downloads section.&lt;br /&gt;&lt;br /&gt;I've made two changes, one trivial and one significant. The significant change is that the script now adds an initial cap to the first word after a forward slash, so, instead of having:&lt;pre&gt;absolutely/positively&lt;/pre&gt;convert to:&lt;pre&gt;Absolutely/positively&lt;/pre&gt;it now converts to:&lt;pre&gt;Absolutely/Positively&lt;/pre&gt;The lesser change is a clean-up of the built-in list of words to ignore. I had "is" in there. To my eye, capitalizing "is" in title case text looks wrong, but because it is a verb, it apparently ought to be capitalized according to the style guides I have read. My reaction to this is to avoid using "is" in a title, but I don't always have control over the contents of titles in the documents for which I do the composition.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Usage Note&lt;/h3&gt;This script includes two word lists you can edit to customize it to your own use. Or, you can use an external word list in a text file for each of the two lists.&lt;br /&gt;&lt;br /&gt;If you don't use the script very often, it is easy to confuse yourself by creating an external word list and then forgetting that you did this. I've done it myself and found myself trying to debug a non-existent problem because the script was apparently ignoring my changes to the word lists -- it wasn't ignoring my change: it was ignoring the whole list.&lt;br /&gt;&lt;br /&gt;I suggest if you do decide to add an external list you should edit the corresponding list in the script to say so. For example, change the ignoreWords line to read:&lt;pre&gt;var ignoreWords = ["using external file"]&lt;/pre&gt;Then, when you come back to the script a month or so later and want to add another word to the list, the script itself will remind you what you did.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115047227944423961?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115047227944423961/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115047227944423961' title='13 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115047227944423961'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115047227944423961'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/06/smart-title-case-revisited.html' title='Smart Title Case Revisited'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>13</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-115040862485900003</id><published>2006-06-15T14:52:00.000-07:00</published><updated>2006-06-15T14:57:04.870-07:00</updated><title type='text'>Section Prefixes</title><content type='html'>I'm sure there are people who think section prefixes are wonderful, but most of the time I bend over backwards to eliminate them. But you do have to remember to do it when you set up the sections. So, if you're in an after-the-fact situation and you  want to eliminate them, how hard can it be?&lt;pre&gt;//DESCRIPTION: Delete all section prefixes&lt;br /&gt;&lt;br /&gt;if (app.documents.length &gt; 0) {&lt;br /&gt;  aDoc = app.activeDocument;&lt;br /&gt;  aDoc.sections.everyItem().sectionPrefix = "";&lt;br /&gt;  aDoc.sections.everyItem().includeSectionPrefix = false;&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;I know I've been neglecting the blog lately, but I hope people are still finding useful stuff here.&lt;br /&gt;&lt;br /&gt;Dave&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-115040862485900003?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/115040862485900003/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=115040862485900003' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115040862485900003'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/115040862485900003'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/06/section-prefixes.html' title='Section Prefixes'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114883253397218435</id><published>2006-05-28T08:39:00.000-07:00</published><updated>2006-05-28T09:38:56.626-07:00</updated><title type='text'>Thinking About Proxy Points</title><content type='html'>When you work in the UI, a layout window has associated with it a transform reference point (familiarly know as its proxy point). Thus, when you select an object and manipulate it, that action can be performed using what is unambiguously the currently active proxy point.&lt;br /&gt;&lt;br /&gt;But when an object is passed off to a function (or method) in a script for processing, how can that function know which is the appropriate proxy point to use? A quick look at these two captures of the Control palette:&lt;br /&gt;&lt;br /&gt;&lt;img src="http://pdsassoc.com/blogimages/exploring/ProxyTopLeft.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;&lt;img src="http://pdsassoc.com/blogimages/exploring/ProxyBtmRight.jpg" /&gt;&lt;br /&gt;&lt;br /&gt;should quickly convince you that the function cannot work out for itself which proxy point is active because both these were active at the same time for the same object. I happened to have it selected in two different windows and each window has its own proxy point.&lt;br /&gt;&lt;br /&gt;So, the height and width methods I was discussing earlier today cannot of themselves know which proxy point to use when a value is passed telling them to set either the height or the width of an object. The only solution is to have another argument that carries the proxy point.&lt;br /&gt;&lt;br /&gt;I'm thinking that this too should be an optional parameter. If it is missing, the function should work off the top left point, unless the item is directly inline to some text, in which case it needs to operate off the baseline of the containing character (which actually might not directly correspond to a proxy point at all).&lt;br /&gt;&lt;br /&gt;And, as I write this, a nagging thought keeps surfacing: what if the item in question is a group? What then MacDuff?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114883253397218435?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114883253397218435/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114883253397218435' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114883253397218435'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114883253397218435'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/05/thinking-about-proxy-points.html' title='Thinking About Proxy Points'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114882284356557590</id><published>2006-05-28T05:56:00.000-07:00</published><updated>2006-05-28T06:27:23.626-07:00</updated><title type='text'>Working with Heights and Widths</title><content type='html'>One of the frustrations of InDesign's object model is that you can't work directly with the height and width of most items (tables are an exception). PageItems, in all their flavors, come with geometricBounds (and visibleBounds, but I've seldom been moved to use that property).&lt;br /&gt;&lt;br /&gt;Often, when working with geometry, you want to know the height or width of an object, so you'll often find yourself doing something like:&lt;pre&gt;myBds = myPI.geometricBounds;&lt;br /&gt;myHeight = myBds[2] - myBds[0];&lt;/pre&gt;where &lt;i&gt;myPI&lt;/i&gt; is a reference to some PageItem.&lt;br /&gt;Well, there's more than one problem here:&lt;ol&gt;&lt;li&gt;The height of an object ought to take into consideration its rotation. Geometric bounds gives you the coordinates (top, left, bottom, right -- don't forget that bottom is lower on the page and therefore has a larger y value because of the way that InDesign's coordinate system works) of the bounding box, so the height returned here is the height consumed by the item's current position on the page and not its real height.&lt;/li&gt;&lt;li&gt;It takes up two lines of code (more if we solve the rotation problem) when really all you wanted to do was:&lt;pre&gt;myHeight = myPI.height;&lt;/pre&gt;&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;I confess that I don't know how to let you just write this very simple statement, but I can very easily get you a whole lot closer. Just add this method definition to your script:&lt;pre&gt;Object.prototype.height = function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var bds = this.geometricBounds;&lt;br /&gt;&amp;nbsp;&amp;nbsp;return bds[2] - bds[0];&lt;br /&gt;}&lt;/pre&gt;and you can now get the height by simply writing:&lt;pre&gt;myHeight = myPI.height();&lt;/pre&gt;While remembering to include the parentheses is a tad irksome, we can put to use the fact that this is a call to a method.&lt;ol&gt;&lt;li&gt;If we want to solve the rotation problem, we can do so in the method, so the calling code's purpose remains clear and easy to read.&lt;/li&gt;&lt;li&gt;We can extend the method to accept a parameter which would set rather than get the height.&lt;/li&gt;&lt;/ol&gt;Another confession: in all the work I've done, I don't think I've ever cared about the possibility that a page item is rotated when I've needed its height (or width). That's largely because in my own work, I've seldom used rotation and in those cases where I have, I have usually finished processing the items with my scripts before I do the rotation.&lt;br /&gt;&lt;br /&gt;However, setting the height (or width) is something that has come up fairly often, and I've usually done that with code that clutters up my mainline scripts, making them harder to read when I need to come back to them.&lt;br /&gt;&lt;br /&gt;It is perhaps worth a moment or two to discuss the merits of having a single height method (which returns the height if no argument is provided, but which sets the height if the first argument is a number). Perhaps having two methods: &lt;i&gt;getHeight()&lt;/i&gt; and &lt;i&gt;setHeight(ht)&lt;/i&gt; would be a better solution. I think this is a personal choice. In my case, I'd prefer to have just the single method. So, that's how I'll spend this Sunday morning of a holiday weekend, writing the methods &lt;i&gt;height(ht)&lt;/i&gt; and &lt;i&gt;width(wd)&lt;/i&gt; to return the appropriate values if the argument is missing, but to set them if present.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114882284356557590?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114882284356557590/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114882284356557590' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114882284356557590'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114882284356557590'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/05/working-with-heights-and-widths.html' title='Working with Heights and Widths'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114850513286404530</id><published>2006-05-24T13:46:00.000-07:00</published><updated>2006-05-24T14:12:12.953-07:00</updated><title type='text'>Let's Build a Make Fractions Script</title><content type='html'>Making fractions from already set text is at best a tedious process. An attempt was made by the OpenType fonts team to build the solution into the fonts themselves, but they quickly discovered that there is no universal solution to the problem. People type fractions in a wide variety of ways and there's no way that a font-based solution can satisfy everybody. For example, is 11/2 one and a half or eleven over two?&lt;br /&gt;&lt;br /&gt;But a script has more freedom. For one thing, you only use it if you want to. For another, it could be configurable (e.g., it could offer an option whereby if the last three characters of a candidate fraction are one of these three: 1/2, 1/4, or 3/4, then  the candidate would be treated as a fraction suffix to the number, so, in the above example with this option switched on, 11/2 would be one and a half, but with it switched off it would be eleven over two).&lt;br /&gt;&lt;br /&gt;Perhaps more important because it is the more common case, what about fractions that are set as 1 1/2? Surely the script should get rid of that space when it converts the 1/2 to a fraction. Well, until earlier this year, I'd have said "certainly" but a project we're working on requires that the space be left there -- so, we need another option for this case.&lt;br /&gt;&lt;br /&gt;The next problem that comes to mind is: how should the fractions, once identified, be constructed. There are a number of possibilities:&lt;br /&gt;&lt;br /&gt;1. Switch on OpenType Fractions.&lt;br /&gt;2. Use superscript and subscript character styles.&lt;br /&gt;3. Use local formatting for superscript and subscript.&lt;br /&gt;&lt;br /&gt;Perhaps the script has to be smart enough to choose among these. OpenType fractions are the best when they're available. The extent to which they're available is an issue though. Some of the Pro fonts support arbitrary fractions; others don't. Some used not to but now do, creating a scenario where text set using arbitrary fractions might work on some machines but not others -- that's a really big yuck, but one could also argue it is the price of progress. Right now, I don't know how to interrogate an OpenType font to find out what it does or does not support.&lt;br /&gt;&lt;br /&gt;Another issue that springs to mind is the desire if using superscript and subscript to boost the weight of the font to compensate for the effects of scaling the characters to about 60% of their normal weight. A script that can't do this is a bit useless in my mind, but it raises the issue that different fonts use different names for their weights (or have different repertoires of available weights). I deal with this when setting by hand by having different fraction-creating character styles. The script, then, needs to be able to choose among fraction-creating character styles.&lt;br /&gt;&lt;br /&gt;Maybe the script can help out by having a naming convention, but hey, projects have naming conventions for styles, so a script must surely be able to work with the user's styles. I have some vague ideas on how to solve this problem, but they're not cast strongly enough yet to present.&lt;br /&gt;&lt;br /&gt;Another issue that springs to mind is what about "word fractions" such as numerator/denominator = result. Perhaps this is easily solved by yet another option.&lt;br /&gt;&lt;br /&gt;Then there's the issue of mixed numbers and words. What does the script do with 129/133rds -- in particular, how should the "rds" be set?&lt;br /&gt;&lt;br /&gt;Of course, the script has to be smart enough to realize that 5/6/06 is not a fraction, it's a date -- if you want that (or anything like it) to be a stacked fraction then you should be using a Math package of some kind. But what about 100/ or /100? Are they fractions?&lt;br /&gt;&lt;br /&gt;Wow, the list of questions is long. I'd welcome any input on this.&lt;br /&gt;&lt;br /&gt;Dave&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114850513286404530?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114850513286404530/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114850513286404530' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114850513286404530'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114850513286404530'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/05/lets-build-make-fractions-script.html' title='Let&apos;s Build a Make Fractions Script'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114631959526960008</id><published>2006-04-29T06:56:00.000-07:00</published><updated>2006-04-29T07:07:24.406-07:00</updated><title type='text'>Add Object to Group</title><content type='html'>Here's a first cut at a method to add an object to a group. I find myself wanting to do this kind of thing when I'm using InDesign as an illustration tool. There's a lot more could be done by the method (for example: add at front or back, or even add at a designated point -- in front of/behind a member of the group; even in place of), but this first cut demonstrates feasibility:&lt;pre&gt;//DESCRIPTION: Test of method that adds an item to an existing group&lt;br /&gt;&lt;br /&gt;Group.prototype.addObject = function(theObj) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myArray = this.pageItems.everyItem().id;&lt;br /&gt;&amp;nbsp;&amp;nbsp;myArray.push(theObj.id);&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myParent = this.parent;&lt;br /&gt;&amp;nbsp;&amp;nbsp;try { this.ungroup() } catch (e) { throw "Unable to ungroup" }&lt;br /&gt;&amp;nbsp;&amp;nbsp;var gpArray = new Array();&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var j = 0; myArray.length &gt; j; j++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;gpArray.push(myParent.pageItems.itemByID(myArray[j]));&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;try { myParent.groups.add(gpArray) } catch (e) { throw "Unable to regroup" }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;myGroup = app.activeDocument.pages[0].groups[0];&lt;br /&gt;myTF = app.activeDocument.pages[0].textFrames[0];&lt;br /&gt;myGroup.addObject(myTF);&lt;/pre&gt;The two error conditions could be caused by: &lt;br /&gt;&lt;br /&gt;1. Trying to ungroup a group that is inside another group or is an anchored object. &lt;br /&gt;2. Trying to add a text object (other than a text frame) to the group. &lt;br /&gt;&lt;br /&gt;Probably, in the case of 2, I should first reassemble the original group before throwing the error. But for the moment I'm just playing with an idea.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114631959526960008?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114631959526960008/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114631959526960008' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114631959526960008'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114631959526960008'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/04/add-object-to-group.html' title='Add Object to Group'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114580152903929559</id><published>2006-04-23T07:04:00.000-07:00</published><updated>2006-04-23T07:12:09.073-07:00</updated><title type='text'>Running an inline script</title><content type='html'>What follows is an extract from an article I'm writing called How to Pass Information from One Script to Another:&lt;br /&gt;&lt;br /&gt;You can if wish use doScript to run a script contained in a variable. For example:&lt;pre&gt;myScript = "'hello'"&lt;br /&gt;alert(app.doScript(myScript));&lt;/pre&gt;This trivial example is equivalent to:&lt;pre&gt;alert(’hello’)&lt;/pre&gt;and so there doesn’t seem to be much point to the exercise. However, notice the two sets of quotes in the assignment to myScript. With just one set of quotes:&lt;pre&gt;myScript = "hello"&lt;br /&gt;alert(app.doScript(myScript));&lt;/pre&gt;The script is comparable to:&lt;pre&gt;alert(hello);&lt;/pre&gt;which results in a run-time error to the effect that hello is undefined.&lt;br /&gt;&lt;br /&gt;Notice that the error-free versions I dubbed “equivalent” whereas the versions with the error are only “comparable.” The difference is that in the error situations, the call to the alert will immediately give you an error message that hello is not defined whereas the doScript version first gives you that error but then gives you an alert that says “undefined”. &lt;br /&gt;&lt;br /&gt;The reason for this is that the call to doScript creates a new script environment to run the script contained in myScript—you can see this very clearly if you run the script in ESTK—and that script is the one that hits the error condition and throws up the error alert. When you dismiss that error, the first script picks up again and completes the original alert command. Because the called script failed, the result is undefined and so that’s what the second alert says.&lt;br /&gt;&lt;br /&gt;But enough of trivial examples. If all you could do with this were trivial there’d be no point in having the feature. The power of this kind of doScript is that you can construct scripts from data and run them easily. This is not something that comes up all that often, and I do not propose to construct a fabricated example at this point because it too would likely be trivial.&lt;br /&gt;&lt;br /&gt;One not so trivial use for this feature is to provide a JavaScript with a mechanism for contacting the outside world via AppleScript or Visual Basic (depending on the platform in use).&lt;br /&gt;&lt;br /&gt;Here’s an example of constructing a script to get the name of the current track being played by iTunes on your Macintosh computer:&lt;pre&gt;alert(app.doScript(getCurrentSong(),ScriptLanguage.applescriptLanguage));&lt;br /&gt;function getCurrentSong() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myScript = "tell application \"iTunes\"\n"&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myScript = myScript + "get name of current track\n"&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myScript = myScript + "end tell\n"&lt;br /&gt;&amp;nbsp;&amp;nbsp;return myScript&lt;br /&gt;}&lt;/pre&gt;Of course, it goes without saying that this AppleScript could as easily have been in an external file and have been called from there. But non-trivial examples of constructing scripts inside scripts are hard to come by.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114580152903929559?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114580152903929559/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114580152903929559' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114580152903929559'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114580152903929559'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/04/running-inline-script.html' title='Running an inline script'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114485896071396285</id><published>2006-04-12T09:19:00.000-07:00</published><updated>2006-04-12T09:22:40.736-07:00</updated><title type='text'>Can a non-existent file have a parent?</title><content type='html'>I was suddenly unsure about this, so I ran a quick test:&lt;pre&gt;//DESCRIPTION: Can a non-existent file have a parent?&lt;br /&gt;&lt;br /&gt;defFile = File(getScriptPath().parent.fsName + "/test.txt");&lt;br /&gt;parFile = defFile.parent;&lt;br /&gt;alert(parFile.fsName);&lt;br /&gt;&lt;br /&gt;function getScriptPath() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// This function returns the path to the active script, even when running ESTK&lt;br /&gt;&amp;nbsp;&amp;nbsp;try { &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return app.activeScript; &lt;br /&gt;&amp;nbsp;&amp;nbsp;} catch(e) { &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return File(e.fileName); &lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;And the answer is yes!&lt;br /&gt;&lt;br /&gt;The explanation is fairly simple. Whether or not the file exists, the file object does exist. And, in order to exist, it must have a parent, the folder in which the corresponding file will be created should the script go the next step and create the file.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114485896071396285?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114485896071396285/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114485896071396285' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114485896071396285'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114485896071396285'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/04/can-non-existent-file-have-parent.html' title='Can a non-existent file have a parent?'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114468503514058999</id><published>2006-04-10T09:00:00.000-07:00</published><updated>2006-04-10T09:03:58.043-07:00</updated><title type='text'>Swatches vs. Colors as properties</title><content type='html'>In a large number of places in the Scripting Reference, there are color based properties whose Type field reads:&lt;pre&gt;&amp;nbsp;&amp;nbsp;Swatch, String&lt;/pre&gt;with a description that reads: "The color of the &amp;lt;whatever&gt;".&lt;br /&gt;&lt;br /&gt;This is correct for setting these color properties (as long as you realize that the string in question should be the name of a swatch of the active document).&lt;br /&gt;&lt;br /&gt;But, if you interrogate any of these properties:&lt;pre&gt;&amp;nbsp;&amp;nbsp;myColor = myWord.underlineGapColor;&lt;/pre&gt;you'll get a color object, and not a swatch. To detect whether or not the color object returned is represented in the UI by a swatch, check its name. If the name is "" then the color was explicitly applied to the object in question without using a swatch (I don't think that is possible for the particular example chosen above, but it is certainly possible for such properties as strokeColor and fillColor).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114468503514058999?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114468503514058999/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114468503514058999' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114468503514058999'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114468503514058999'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/04/swatches-vs-colors-as-properties.html' title='Swatches vs. Colors as properties'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114458912210019277</id><published>2006-04-09T05:41:00.000-07:00</published><updated>2006-04-09T06:25:38.276-07:00</updated><title type='text'>How to check for missing fonts</title><content type='html'>I've been commissioned to write a script that helps with the back-end of a workflow, exporting/printing completed documents. Or, to be a little pedantic about it, allegedly completed documents. But what if a document has missing fonts or links? Or if links need updating?&lt;br /&gt;&lt;br /&gt;Of these three possibilities, only the third can be dealt with automatically. And finding that links are missing is easy (just check the &lt;i&gt;status&lt;/i&gt; property of each link). That will allow the script to check for either links that need relinking or updating, taking appropriate action for each. But what about missing fonts?&lt;br /&gt;&lt;br /&gt;Well, the first thing I realized was that if fonts are missing, all I need to do is report that to the user and advise them to use the menu item Find Font... on the Type menu to fix it. So, I need a quick way to find out if any font needed by the document is missing. I ran a timing loop against each of the properties of a font, like this:&lt;pre&gt;//DESCRIPTION: Checking for missing document fonts&lt;br /&gt;&lt;br /&gt;var myStartTime = new Date();&lt;br /&gt;aDoc = app.activeDocument;&lt;br /&gt;for (var j = 0; 100 &gt; j; j++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFonts = aDoc.fonts.everyItem().status;&lt;br /&gt;&amp;nbsp;&amp;nbsp;} catch (e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;beep();&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;var myEndTime = new Date();&lt;br /&gt;var myDuration = (myEndTime - myStartTime);&lt;br /&gt;prompt("", "status   " + myDuration)&lt;/pre&gt;This was the last test I ran, so it shows the alphabetically last propery, &lt;i&gt;status&lt;/i&gt;. The results are shown below (the numbers show how many milliseconds it took to attempt to get the properties of a document that had two missing fonts). Notice that some of the properties are available even for missing fonts. If I wanted to warn the user about specific missing fonts, I'd have to use the status property, and then cycle through the results.&lt;br /&gt;&lt;br /&gt;718&amp;nbsp;&amp;nbsp;allowEditableEmbedding&lt;br /&gt;922&amp;nbsp;&amp;nbsp;allowOutlines  &lt;br /&gt;856&amp;nbsp;&amp;nbsp;allowPDFEmbedding  &lt;br /&gt;894&amp;nbsp;&amp;nbsp;allowPrinting&lt;br /&gt;357&amp;nbsp;&amp;nbsp;fontFamily   (doesn't give error)&lt;br /&gt;754&amp;nbsp;&amp;nbsp;fontType  &lt;br /&gt;915&amp;nbsp;&amp;nbsp;index   (doesn't give error)&lt;br /&gt;857&amp;nbsp;&amp;nbsp;location  &lt;br /&gt;460&amp;nbsp;&amp;nbsp;name   (doesn't give error)&lt;br /&gt;371&amp;nbsp;&amp;nbsp;parent   (doesn't give error)&lt;br /&gt;930&amp;nbsp;&amp;nbsp;postscriptName  &lt;br /&gt;4885&amp;nbsp;&amp;nbsp;properties   (doesn't give error)&lt;br /&gt;911&amp;nbsp;&amp;nbsp;restrictedPrinting  &lt;br /&gt;911&amp;nbsp;&amp;nbsp;status   (doesn't give error)&lt;br /&gt;&lt;br /&gt;But if all I want to know is whether or not any fonts are missing, I can simply do this:&lt;pre&gt;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;fontLocs = app.activeDocument.fonts.everyItem().location;&lt;br /&gt;} catch (e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit("Active document has missing fonts; please correct using Type/Find Font and try again.");&lt;br /&gt;}&lt;/pre&gt;Why do I choose the location property? Because that's the first one I thought of user and the timing test says that the results are just about all the same (the minor differences could easily be the result of blips while I was running the script; indeed, I'm showing the lowest result of three or four runs for most of the properties).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114458912210019277?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114458912210019277/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114458912210019277' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114458912210019277'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114458912210019277'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/04/how-to-check-for-missing-fonts.html' title='How to check for missing fonts'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114442086329929688</id><published>2006-04-07T07:31:00.000-07:00</published><updated>2006-04-07T07:41:03.406-07:00</updated><title type='text'>A better Log function</title><content type='html'>Yesterday, I posted a function for creating a log file. In testing that function, I used another function called &lt;i&gt;log&lt;/i&gt; to write messages to the log file. This morning, I've created a slightly more flexible version of that function. In this version, the date-stamping is optional and the header message is passed as a parameter to the function. &lt;br /&gt;&lt;br /&gt;This log function writes messages to the designated text file (File object: aFile). If the file in question does not yet exist, it is created and if requested by the header (string) and incDate (boolean) arguments, the log file is date-stamped and a header is posted.&lt;pre&gt;function log(aFile, message, header, incDate) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var today = new Date();&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (!aFile.exists) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// make new log file&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile.open("w");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (incDate) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile.write(String(today));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (header != null) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile.write(header);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile.close();&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;aFile.open("e");&lt;br /&gt;&amp;nbsp;&amp;nbsp;aFile.seek(0,2);&lt;br /&gt;&amp;nbsp;&amp;nbsp;aFile.write("\n" + message);&lt;br /&gt;&amp;nbsp;&amp;nbsp;aFile.close();&lt;br /&gt;}&lt;/pre&gt;Note that if the first call includes neither a header nor a request to stamp the file with the date, the generated text file will start with a blank line. One approach for dealing with this would be to not include the "\n" before each message, leaving it to the user to include them. I'll see how I get on with it in its current form. So far, I've always wanted a header and date-stamp, so it has been something of a non-issue.&lt;br /&gt;&lt;br /&gt;Once &lt;i&gt;log&lt;/i&gt; has been called the first time to kick things off, the third and fourth arguments can be ignored; indeed, they are ignored by the function, so there's no point in including them. If you want to time-stamp the messages, you can include the time string in the message parameter.&lt;br /&gt;&lt;br /&gt;At the end of a script that created a log file, I have something like this:&lt;pre&gt;if (logFile.exists) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;logFile.execute();&lt;br /&gt;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit("Script ended with no log file.");&lt;br /&gt;}&lt;/pre&gt;This causes the log file to be displayed in the editor of your choice (that which you have associated with .txt files) if it was created by running the script, or you get the alert message (provided by the errorExit() function I've posted previously).&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114442086329929688?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114442086329929688/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114442086329929688' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114442086329929688'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114442086329929688'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/04/better-log-function.html' title='A better Log function'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114436428507901016</id><published>2006-04-06T15:49:00.000-07:00</published><updated>2006-04-06T15:58:07.753-07:00</updated><title type='text'>Logging to a text file</title><content type='html'>Many scripts encounter errors or unusual circumstances as they run. Sometimes, it is desirable for the script to simply stop in such circumstances reporting the error to the user for immediate attention. But in other situations it is better for a script to soldier on, simply logging its findings to a text file.&lt;br /&gt;&lt;br /&gt;The function makeLogFile() facilitates this. It creates a text file (which, in truth could be used for anything) using the string aName as the basis for naming the file. The file is located in the same folder as the document passed in the argument aDoc. If aDoc is untitled, then the log file is created in the same folder as the script. The third argument (delete) is a Boolean. It determines whether the script should delete an existing file of the designated name or create a unique name based on the first argument.&lt;pre&gt;function makeLogFile(aName, aDoc, deleteIt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var logLoc; // path to folder that will hold log file&lt;br /&gt;&amp;nbsp;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logLoc = aDoc.filePath;&lt;br /&gt;&amp;nbsp;&amp;nbsp;} catch (e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logLoc = getScriptPath().parent.fsName&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;var aFile = File(logLoc + "/" + aName + ".txt");&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (deleteIt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile.remove();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return aFile;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;var n = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;while (aFile.exists) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile = File(logLoc + "/" + aName + String(n) + ".txt");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;n++&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;return aFile&lt;br /&gt;}&lt;/pre&gt;Notice that this script depends on the function getScriptPath being available. It also doesn't actually create the log file, merely a file object. This means that a script which never actually logs anything won't create a log file. Here is the code I used to test this function:&lt;pre&gt;//DESCRIPTION: Test making log file&lt;br /&gt;myDoc = app.activeDocument;&lt;br /&gt;log1 = makeLogFile("test",myDoc,true);&lt;br /&gt;log(log1, "Text for log1 file");&lt;br /&gt;log2 = makeLogFile("test",myDoc,false);&lt;br /&gt;log(log2, "Text for log2 file");&lt;br /&gt;log1.execute();&lt;br /&gt;log2.execute();&lt;br /&gt;function makeLogFile(aName, aDoc, deleteIt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var logLoc; // path to folder that will hold log file&lt;br /&gt;&amp;nbsp;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logLoc = aDoc.filePath;&lt;br /&gt;&amp;nbsp;&amp;nbsp;} catch (e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;logLoc = getScriptPath().parent.fsName&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;var aFile = File(logLoc + "/" + aName + ".txt");&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (deleteIt) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile.remove();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return aFile;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;var n = 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;while (aFile.exists) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile = File(logLoc + "/" + aName + String(n) + ".txt");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;n++&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;return aFile&lt;br /&gt;}&lt;br /&gt;function getScriptPath() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// This function returns the path to the active script, even when running ESTK&lt;br /&gt;&amp;nbsp;&amp;nbsp;try { &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return app.activeScript; &lt;br /&gt;&amp;nbsp;&amp;nbsp;} catch(e) { &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return File(e.fileName); &lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;function log(aFile, message) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var today = new Date();&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (!aFile.exists) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// make new log file&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile.open("w");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile.write(String(today) + "\nThe following messages were logged:\n");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;aFile.close();&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;aFile.open("e");&lt;br /&gt;&amp;nbsp;&amp;nbsp;aFile.seek(0,2);&lt;br /&gt;&amp;nbsp;&amp;nbsp;aFile.write("\n" + message);&lt;br /&gt;&amp;nbsp;&amp;nbsp;aFile.close();&lt;br /&gt;}&lt;/pre&gt;Run this and two text files are opened, one named test.txt and one named test1.txt. That's because the first call has delete it set to true while the second has it set false. As a result, because we logged something to the log1 file before creating the log2 file object, two separate files were created.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114436428507901016?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114436428507901016/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114436428507901016' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114436428507901016'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114436428507901016'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/04/logging-to-text-file.html' title='Logging to a text file'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114287328238665671</id><published>2006-03-20T08:35:00.000-08:00</published><updated>2006-03-20T08:48:04.673-08:00</updated><title type='text'>Filtering Files</title><content type='html'>On page 540 of the Scripting Reference it says [I've added emphasis to the two key words]:&lt;br /&gt;&lt;br /&gt;----------------------------------------&lt;br /&gt;&lt;br /&gt;File.openDialog ([prompt][,select])&lt;br /&gt;&lt;br /&gt;Opens the built-in platform-specific file-browsing dialog in which a user can select an existing file to open. If the user clicks OK, returns a File object for the selected file. If the user cancels, returns null. &lt;br /&gt;&lt;br /&gt;[snip]&lt;br /&gt;&lt;br /&gt;select&lt;br /&gt;Optional. A file or files to be preselected when the dialog opens:&lt;br /&gt;&lt;br /&gt;[snip]&lt;br /&gt;&lt;br /&gt;In Mac OS, a &lt;b&gt;string&lt;/b&gt; containing the name of a &lt;b&gt;method&lt;/b&gt; defined in the current JavaScript scope that takes a File object argument. The method is called for each file about to be displayed in the dialog, and the file is displayed only when the method returns true. &lt;br /&gt;&lt;br /&gt;----------------------------------------&lt;br /&gt;&lt;br /&gt;It took me a while to realize that the argument &lt;i&gt;select&lt;/i&gt; is not a string but a reference to the &lt;b&gt;function&lt;/b&gt; (not method) by name, like this:&lt;pre&gt;function indtFilter(myFile) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myFile.name.slice(-5) == ".indt") { return true }&lt;br /&gt;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;var myFile = File.openDialog("Choose a template.", indtFilter);&lt;br /&gt;if (myFile == null) { exit() }&lt;/pre&gt;That works, but put quotes around the name of the function and it won't work -- it shows you all the files.&lt;br /&gt;&lt;br /&gt;Change the function definition to a method definition, like this:&lt;pre&gt;File.prototype.indtFilter = function(myFile) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myFile.name.slice(-5) == ".indt") { return true }&lt;br /&gt;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;}&lt;/pre&gt;and you'll get an error that indtFilter is not defined.&lt;br /&gt;&lt;br /&gt;OK, so this only affects Mac users, but that's what I am most of the time.&lt;br /&gt;&lt;br /&gt;While my observations about the reference are accurate in the above, my assertion that the function &lt;i&gt;indtFilter(myFile)&lt;/i&gt; works is valid only in the narrow sense that it is constructed correctly. It has a bug you can drive a bus through (as my brother likes to say).&lt;br /&gt;&lt;br /&gt;The problem is that the way I have constructed it denies the user the ability to navigate folders. What's needed is this:&lt;pre&gt;function indtFilter(myFile) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myFile.constructor.name == "Folder") { return true }&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myFile.name.slice(-5) == ".indt") { return true }&lt;br /&gt;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;}&lt;/pre&gt;Now, in addition to (in this case) any InDesign templates, the folder items in the file dialog will also be active, allowing the user to navigate the file system.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114287328238665671?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114287328238665671/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114287328238665671' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114287328238665671'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114287328238665671'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/03/filtering-files.html' title='Filtering Files'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114243204173011053</id><published>2006-03-15T05:54:00.000-08:00</published><updated>2006-03-15T06:14:01.776-08:00</updated><title type='text'>Do Away with the Scripts Palette?</title><content type='html'>For the longest time, I've used a workspace that has two columns of palettes down the right side of the screen so I can have the Scripts palette permanently available. Lately, I've been wondering if there isn't a better alternative because the way I have things eats seriously into document space. So the question arises: can I script my way out of the need to have the scripts palette permanently displayed.&lt;br /&gt;&lt;br /&gt;I might not have had this thought at all had it not been for Peter Kahrel mentioning that he uses a script to access recent scripts. Apparently, Peter keeps all his scripts in one folder so, in addition to displaying the recently used scripts he can readily display all the others at the bottom of the menu he throws up.&lt;br /&gt;&lt;br /&gt;I have a large number of folders holding scripts -- so large a number that assembling them into a menu would be slow and generally pointless in that many of them are just sitting there waiting to be used only for a special occasion. So, the thought I have is this: Let's build a script that uses a couple of special folders to hold information. One will contain what I'll call the permanently available scripts. Some scripts I use enough that I know that I'll always want quick access to them even if they happen to have temporarly dropped off the most recent list, so I'll put these into a folder named "Always" and they'll always be there -- indeed, any that are in this folder will never appear on the most recent list because they'll be permanently available anyway.&lt;br /&gt;&lt;br /&gt;A second folder will hold aliases to the most recently used scripts. Also in there will be a preferences file.&lt;br /&gt;&lt;br /&gt;My inital thoughts on what the script that manages all this does is this:&lt;br /&gt;&lt;br /&gt;1. If the Recent folder doesn't exist, it creates it and throws up the preferences dialog. I have three preferences in mind:&lt;br /&gt;&lt;br /&gt;Number of Recent Scripts&lt;br /&gt;Display Script Label of Selection&lt;br /&gt;Display Palette Folders&lt;br /&gt;&lt;br /&gt;The meanings of these will become apparent as we look at the reset of the functions.&lt;br /&gt;&lt;br /&gt;2. The script will manufacture a dialog that will display a drop-down menu of selectable script names, starting with the Always scripts then the Recent scripts, and optionally (see prefs) the names of all the folders in the Scripts palette (top level only). If the user chooses one of the script names, the script in question will be run. If the user chooses a folder, the script will present another dialog with that folder name in it.&lt;br /&gt;&lt;br /&gt;3. If the prefs ask for it and there is a selection, the script label of the selection will be displayed in a static text field -- this is for info only; it is not intended as a mechanism for changing labels. [Hmmm: I might have second thoughts about this -- what if one of the Always available scripts changed the script label. That would do away with the need for the Script Label palette. Maybe.]&lt;br /&gt;&lt;br /&gt;And that's where I am at the moment. Now I need to start on some implementation.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114243204173011053?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114243204173011053/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114243204173011053' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114243204173011053'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114243204173011053'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/03/do-away-with-scripts-palette.html' title='Do Away with the Scripts Palette?'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114166292833343463</id><published>2006-03-06T08:25:00.000-08:00</published><updated>2006-03-06T08:35:28.376-08:00</updated><title type='text'>What and Where's that Glyph?</title><content type='html'>You receive a file from a client and some of the characters have the "dreaded pinkness" indicating that a glyph has been used that isn't present in the font you're using. Other characters in the same font do not show the dreaded pinkness, so this is not a case of a missing font but rather a missing glyph issue.&lt;br /&gt;&lt;br /&gt;So, how do you find out which glyph it is supposed to be? I used this process:&lt;br /&gt;&lt;br /&gt;1. I selected the glyph in my InDesign document.&lt;br /&gt;2. I opened the Info palette.&lt;br /&gt;&lt;br /&gt;And that told me that the missing glyph was unicode value 25CF.&lt;br /&gt;&lt;br /&gt;To find out what it is supposed to look like, I visited: http://www.unicode.org/charts/ and typed the unicode value into the search.&lt;br /&gt;&lt;br /&gt;To find out if any of my fonts had this glyph present, I wrote this quick and dirty script. It relies on the fact that if a glyph is not populated in a particular font and you try to convert it to outlines, you get an error:&lt;pre&gt;//DESCRIPTION: Seek out Fonts with code point populated&lt;br /&gt;&lt;br /&gt;codePoint = "\u25CF";&lt;br /&gt;myFonts = app.fonts;&lt;br /&gt;codePresentList = [];&lt;br /&gt;for (j = 0; myFonts.length &gt; j; j++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;myDoc = app.documents.add();&lt;br /&gt;&amp;nbsp;&amp;nbsp;myFrame = myDoc.pages[0].textFrames.add();&lt;br /&gt;&amp;nbsp;&amp;nbsp;myFrame.geometricBounds = myDoc.pages[0].bounds;&lt;br /&gt;&amp;nbsp;&amp;nbsp;myFrame.insertionPoints[0].contents = codePoint;&lt;br /&gt;&amp;nbsp;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myFrame.parentStory.characters[0].createOutlines();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;codePresentList.push(myFonts[j].name);&lt;br /&gt;&amp;nbsp;&amp;nbsp;} catch (e) { &lt;br /&gt;&amp;nbsp;&amp;nbsp;// Glyph not present, ignore &lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;myDoc.close(SaveOptions.no);&lt;br /&gt;}&lt;br /&gt;alert ("Code present in:\r" + codePresentList.join("\r"));&lt;/pre&gt;Were the glyph to be present in any significant number of fonts, using an alert to inform the user is not a good idea, but in my case none of the 1140 fonts had the glyph so the alert was tolerable.&lt;br /&gt;&lt;br /&gt;I'll come back to this script one day and improve it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114166292833343463?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114166292833343463/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114166292833343463' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114166292833343463'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114166292833343463'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/03/what-and-wheres-that-glyph.html' title='What and Where&apos;s that Glyph?'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-114165746545611249</id><published>2006-03-06T06:52:00.000-08:00</published><updated>2006-03-06T07:04:26.006-08:00</updated><title type='text'>Replacing Links</title><content type='html'>A U2U forum visitor posted a request for a script to replace linked color images with black &amp; white (grayscale, probably) equivalent images using a particular naming scheme. Here's the script I created:&lt;pre&gt;//DESCRIPTION: Replace color linked images with BW alternates&lt;br /&gt;&lt;br /&gt;&amp;nbsp;/*&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;Color images have "_cp" in their names; BW have "_bw"&lt;br /&gt;&amp;nbsp;*/&lt;br /&gt; &lt;br /&gt;myDoc = app.activeDocument;&lt;br /&gt;myLinks = myDoc.links;&lt;br /&gt;for (j = myLinks.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;myName = myLinks[j].filePath;&lt;br /&gt;&amp;nbsp;&amp;nbsp;myNewName = myName.split("_cp").join("_bw");&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myName != myNewName) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Original link includes "-cp" in name&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myNewImageFile = File(myNewName);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (!myNewImageFile.exists) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert ("Can't find file: " + myNewName);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myLinks[j].relink(myNewImageFile);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myLinks[j].update();&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;It occurred to me having written the script for the situation where the two sets of images are in the same folder, the logic for converting the names will also work if the two sets are segregated into two side-by-side folders that use the same naming trick to distinguish one from the other.&lt;br /&gt;&lt;br /&gt;PS: Things have been very hectic lately getting huge jobs out the door, so I've neglected the blog some. Hopefully, this will change now that the jobs are complete.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-114165746545611249?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/114165746545611249/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=114165746545611249' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114165746545611249'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/114165746545611249'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/03/replacing-links.html' title='Replacing Links'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113975742666839468</id><published>2006-02-12T06:30:00.000-08:00</published><updated>2006-02-12T07:17:15.683-08:00</updated><title type='text'>Another Justification Job</title><content type='html'>The vertical justification feature built-into text frame options treats all paragraphs as equal. But for what I need to do right now, this is not the case. I have a story that runs across a number of pages (single-column text frames, actually) that has a paragraph style named "Cytokine" that is in effect a sub-head. I want to vertically justify these pages adding space before only to paragaphs in this style.&lt;br /&gt;&lt;br /&gt;So, the first step is to identify the story. Because this particular story is the only one that uses this particular paragraph style, it is easy to find (assuming myDoc is set to the activeDocument):&lt;pre&gt;app.findPreferences = null;&lt;br /&gt;app.changePreferences = null;&lt;br /&gt;var myStory = myDoc.search("", false, false, undefined, {appliedParagraphStyle:"Cytokine"})[0].parentStory;&lt;/pre&gt;So, now the task is to iterate through the text frames of the story distributing any spare space at the foot of each frame to the relevant paragraphs.&lt;pre&gt;var myTFs = myStory.textFrames;&lt;br /&gt;for (var j = myTFs.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;processTF(myTFs[j]);&lt;br /&gt;}&lt;/pre&gt;It occurs to me as I write this that a more general purpose version of this script would check that myStory is not overset before proceeding, but hey, I know it isn't. So, now all we need to do is write the function to process the text frames.&lt;pre&gt;function processTF(theFrame) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Note: assumes no stroke or inset&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myParaStyles = theFrame.paragraphs.everyItem().appliedParagraphStyle;&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myBase = theFrame.lines[-1].baseline;&lt;br /&gt;&amp;nbsp;&amp;nbsp;var targ = theFrame.geometricBounds[2];&lt;br /&gt;&amp;nbsp;&amp;nbsp;var CytoParas = new Array();&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var k = myParaStyles.length - 1; k &gt;= 0; k--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myParaStyles[k].name == "Cytokine") { CytoParas.push(k) }&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (CytoParas.length == 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;selectIt(theFrame);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;errorExit("Frame has no eligible paragraphs");&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;var delta = (targ - myBase)/CytoParas.length&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var j = CytoParas.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;theFrame.paragraphs[CytoParas[j]].spaceBefore = theFrame.paragraphs[CytoParas[j]].spaceBefore + delta&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;I put in the test for zero eligible paragraphs out of a sense of duty: I didn't want to allow for the possibility of a divide by zero, even though I know the story in question doesn't have this problem. You can find the &lt;i&gt;selectIt&lt;/i&gt; function elsewhere in this blog.&lt;br /&gt;&lt;br /&gt;What you don't see here (because I've cleaned them up) are the four typos I made that resulted in a variety of syntax and runtime errors. But now the job is done and I can for now at least forget about this script.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113975742666839468?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113975742666839468/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113975742666839468' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113975742666839468'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113975742666839468'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/02/another-justification-job.html' title='Another Justification Job'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113871592995669934</id><published>2006-01-31T05:48:00.000-08:00</published><updated>2006-01-31T05:58:49.990-08:00</updated><title type='text'>Subselecting a quote</title><content type='html'>My client sent me a set of corrections in this form:&lt;br /&gt;&lt;br /&gt;Correct Figure Legend should read “Revised text for figure legend”&lt;br /&gt;&lt;br /&gt;Have you ever tried selecting text between quotes? If the text is large enough, it's not that big a deal, but when you want to keep as much information on screen as possible, it is very fiddly. So, I wrote this quick script (would have been quicker had I realized that I needed the parent text flow, not the parent story -- the information was inside a table):&lt;pre&gt;//DESCRIPTION: Subselect to within first pair of quotes&lt;br /&gt;&lt;br /&gt;Object.prototype.isPureText = function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;switch(this.constructor.name){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "InsertionPoint":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Character":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Word":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "TextStyleRange":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Line":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Paragraph":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "TextColumn":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Text":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default :&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;if ((app.documents.length != 0) &amp;&amp; (app.selection.length == 1)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var mySel = app.selection[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (!mySel.isPureText()) { errorExit("Please select some text.") }&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.findPreferences = null;&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.changePreferences = null;&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myStart = mySel.search("^{",false,false)[0].index + 1&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myEnd = mySel.search("^}",false,false)[0].index - 1&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myStory = getParentTextFlow(mySel);&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.select(myStory.characters.itemByRange(myStart,myEnd));&lt;br /&gt;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// +++++++ Functions Start Here +++++++++++++++++++++++&lt;br /&gt;&lt;br /&gt;function getParentTextFlow(theTextRef) {&lt;br /&gt; // Returns reference to parent story or text of cell, as appropriate&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (theTextRef.parent.constructor.name == "Cell") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return theTextRef.parent.texts[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return theTextRef.parentStory;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;function errorExit(message) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (arguments.length &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (app.version != 3) { beep() } // CS2 includes beep() function.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert(message);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;exit(); // CS exits with a beep; CS2 exits silently.&lt;br /&gt;}&lt;/pre&gt;So, all I have to do is select the paragraph (or the cell contents) and the script sub-selects for me.&lt;br /&gt;&lt;br /&gt;You'll notice that I've done no error-checking. If the quotes aren't there, the script will give a run-time error. I'll leave that as an exercise for the reader!&lt;br /&gt;&lt;br /&gt;Also, of course, it is trivially easy to change this script to sub-select between the first occurrences of any two different characters.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113871592995669934?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113871592995669934/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113871592995669934' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113871592995669934'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113871592995669934'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/01/subselecting-quote.html' title='Subselecting a quote'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113759338539320204</id><published>2006-01-18T06:05:00.000-08:00</published><updated>2006-01-18T06:09:45.423-08:00</updated><title type='text'>Toggle Transform Content Preference</title><content type='html'>It's hard to imagine that it has taken me this long to address this constant source of frustration. In the UI, the only way to find out if this Transform Content preference is on or off is to activate the menu (on either the Control Palette or Transform Palette) and look at it.&lt;br /&gt;&lt;br /&gt;You can toggle it with a shortcut, but that won't tell you which state it is in. So:&lt;pre&gt;//DESCRIPTION: Beeps when preference is turned on&lt;br /&gt;&lt;br /&gt;if (!app.transformPreferences.transformContent) beep()&lt;br /&gt;app.transformPreferences.transformContent = !app.transformPreferences.transformContent&lt;/pre&gt;This script beeps when it turns on the preference but turns it off silently. So all I need to do is attach a shortcut and I won't have to keep visiting that blasted menu.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113759338539320204?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113759338539320204/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113759338539320204' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113759338539320204'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113759338539320204'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/01/toggle-transform-content-preference.html' title='Toggle Transform Content Preference'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113737295431074049</id><published>2006-01-15T16:46:00.000-08:00</published><updated>2006-01-16T09:24:42.956-08:00</updated><title type='text'>How many changes were there?</title><content type='html'>The current document I'm working on has 161 pages. It takes about 15 minutes for my iMac G5 (2 GHz) to export a pdf. After sitting through one such export, I suddenly remembered that I'd not run the script I discussed here: &lt;a href="http://jsid.blogspot.com/2005/12/qd-leading-fixer.html"&gt;Quick &amp; Dirty Leading Fixer&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Well, that script was so quick and dirty there was no way to tell if it actually did anything -- indeed, from InDesign's point of view it changed every text frame with a paragraph in the style &lt;i&gt;format&lt;/i&gt;. So, if I ran that script, I'd have no choice but to export the pdf again.&lt;br /&gt;&lt;br /&gt;A new version of the script was called for that would count the changes and tell me how many. So I modified the script to this:&lt;pre&gt;//DESCRIPTION: Format head leading fixer&lt;br /&gt;&lt;br /&gt;myDoc = app.activeDocument;&lt;br /&gt; app.findPreferences = null;&lt;br /&gt; app.changePreferences = null;&lt;br /&gt;myFinds = myDoc.search("",false,false,undefined,{appliedParagraphStyle:myDoc.paragraphStyles.item("format")});&lt;br /&gt;myCount = 0;&lt;br /&gt;for (var j=myFinds.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;if (myFinds[j].parentTextFrames[0].textFramePreferences.firstBaselineOffset != FirstBaseline.leadingOffset) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;myFinds[j].parentTextFrames[0].textFramePreferences.firstBaselineOffset = FirstBaseline.leadingOffset;&lt;br /&gt;&amp;nbsp;&amp;nbsp;myCount++;&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;alert(String(myCount) + " change" + ((myCount == 1)? "" : "s") + " made.");&lt;/pre&gt;Notice that decision in the middle of the alert string. I hate it when alerts say things like "1 changes made." It is that easy to test for the value 1 and suppress the "s" on "changes" so why not?&lt;br /&gt;&lt;br /&gt;Oh, and the good news is that this time I didn't have to re-export the PDF!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113737295431074049?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113737295431074049/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113737295431074049' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113737295431074049'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113737295431074049'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/01/how-many-changes-were-there.html' title='How many changes were there?'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113730020619165008</id><published>2006-01-14T20:32:00.000-08:00</published><updated>2006-01-18T06:11:46.840-08:00</updated><title type='text'>Zoom in on Object</title><content type='html'>Having set myself the goal, I just couldn't resist tackling this job tonight (particularly as our local PBS station has pre-empted its British comedies to do fund-raising this evening). And it's quite straightforward.&lt;br /&gt;&lt;br /&gt;This is an unusual function in that it doesn't return to the caller. It either throws an error if you try to zoom in on an object that doesn't have a geometric bounds property (this will also happen if a story editor window is at the front) or it exits back to the user on the theory that there's not much point in zooming in on something if you don't stop to let the user see the result.&lt;br /&gt;&lt;br /&gt;The script takes advantage of the zoom feature that fits a page into a window. Because we can get the bounds of the page and the zoom percentage of the window, we know what percentage corresponds to the size of a page, so all we have to do is calculate the two ratios of page height to object height and page width to object width and then multiply the page-fitting percentage by the smaller of those two ratios:&lt;pre&gt;function zoomObject(theObj) {&lt;br /&gt;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var objBounds = theObj.geometricBounds;&lt;br /&gt;&amp;nbsp;} catch (e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;throw "Object doesn't have bounds."&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;var ObjHeight = objBounds[2] - objBounds[0];&lt;br /&gt;&amp;nbsp;var ObjWidth = objBounds[3] - objBounds[1];&lt;br /&gt;&amp;nbsp;var myWindow = app.activeWindow;&lt;br /&gt;&amp;nbsp;var pageBounds = myWindow.activePage.bounds;&lt;br /&gt;&amp;nbsp;var PgeHeight = pageBounds[2] - pageBounds[0];&lt;br /&gt;&amp;nbsp;var PgeWidth = pageBounds[3] - pageBounds[1];&lt;br /&gt;&amp;nbsp;var hRatio = PgeHeight/ObjHeight;&lt;br /&gt;&amp;nbsp;var wRatio = PgeWidth/ObjWidth;&lt;br /&gt;&amp;nbsp;var zoomRatio = Math.min(hRatio, wRatio);&lt;br /&gt;&amp;nbsp;app.select(theObj); // to make active the page that holds theObj&lt;br /&gt;&amp;nbsp;myWindow.zoom(ZoomOptions.fitPage);&lt;br /&gt;&amp;nbsp;myWindow.zoomPercentage = myWindow.zoomPercentage * zoomRatio;&lt;br /&gt;&amp;nbsp;exit() // Because there's no point in doing this if you don't exit to let the user see&lt;br /&gt;}&lt;/pre&gt;I'm thinking that for text, it ought to be possible to come up with a set of bounds, so perhaps this could be extended to more kinds of object, but for the moment, I shall satisfy myself with this version.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Update:&lt;/b&gt; This version doesn't seem to be able to turn to the page of an inline or anchored object.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113730020619165008?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113730020619165008/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113730020619165008' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113730020619165008'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113730020619165008'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/01/zoom-in-on-object.html' title='Zoom in on Object'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113728286802901347</id><published>2006-01-14T15:28:00.000-08:00</published><updated>2006-01-14T15:54:28.080-08:00</updated><title type='text'>Better View after Find</title><content type='html'>This morning, I finally bit the bullet and wrote a script to deal with one of InDesign's annoying little habits. After a Find, it will often display the found text in a remote corner of the screen, with most of what you're really interested in (the text around the found item) either off-screen to the left or below the window. So, I borrowed an idea from the &lt;i&gt;selectIt()&lt;/i&gt; function I've written about before and came up with what I call: BetterViewAfterFind.jsx:&lt;pre&gt;//DESCRIPTION: A Better View after Find&lt;br /&gt;&lt;br /&gt;var myZoom = app.activeWindow.zoomPercentage;&lt;br /&gt;app.activeWindow.zoom(ZoomOptions.showPasteboard);&lt;br /&gt;app.activeWindow.zoomPercentage = myZoom;&lt;/pre&gt;There! That's better. The "showPasteboard" command might not be strictly necessary for this situation in that the Find command does make sure that the found item is in the window to start with.&lt;br /&gt;&lt;br /&gt;Perhaps I should take this opportunity to rewrite the &lt;i&gt;selectIt()&lt;/i&gt; function. You may recall that it looks like this:&lt;pre&gt;function selectIt(theObj) {&lt;br /&gt;&amp;nbsp;// Selects object, turns to page and zooms in on it&lt;br /&gt;&amp;nbsp;app.select(theObj,SelectionOptions.replaceWith);&lt;br /&gt;&amp;nbsp;app.activeWindow.zoom = ZoomOptions.fitPage;&lt;br /&gt;&amp;nbsp;app.activeWindow.zoomPercentage = 200&lt;br /&gt;}&lt;/pre&gt;I've never been particularly happy with that arbitrary 200%. I always reasoned that I could change the number for any particular use, but why not just use whatever zoom value the user has set? The objective is to get the item front and center. Also, if &lt;i&gt;theObj&lt;/i&gt; is on the pasteboard, this particular logic will fail because the fitPage command will not bring the object into the window and consequently the zoom to 200% will merely zoom in on the center of the page, not the selected object.&lt;br /&gt;&lt;br /&gt;But wait! Let's think this through a tad more. What this function really does is show the user the object. Selecting it is incidental (although selecting it does clarify for the user just what he's being shown). So, I'm going to change the name to &lt;i&gt;showIt()&lt;/i&gt; and I'm going to change the functionality, making the argument optional. This way, if there is already a selection, then the function will show that rather than selecting the passed object:&lt;pre&gt;function showIt(theObj) {&lt;br /&gt;&amp;nbsp;if (arguments.length &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Select object, turn to page and center it in the window&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.select(theObj);&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;// Note: if no object is passed and there is no selection the current page&lt;br /&gt;&amp;nbsp;// will be centered in the window at whatever zoom is being used&lt;br /&gt;&amp;nbsp;var myZoom = app.activeWindow.zoomPercentage;&lt;br /&gt;&amp;nbsp;app.activeWindow.zoom(ZoomOptions.showPasteboard);&lt;br /&gt;&amp;nbsp;app.activeWindow.zoomPercentage = myZoom;&lt;br /&gt;}&lt;/pre&gt;And that means that my BetterViewAfterFind script can be rewritten to use this function:&lt;pre&gt;//DESCRIPTION: A Better View after Find&lt;br /&gt;&lt;br /&gt;showIt();&lt;/pre&gt;Of course, the real script follows the call with a copy of the function.&lt;br /&gt;&lt;br /&gt;I'm thinking that a smarter version of showIt could be written that would provide functionality similar to that of clicking the Go To Link button in the Links palette. I think this could only work for non-text selections, but basically, the script could calculate the zoom value needed to "fill the window" with the selection. But I'll leave that for another day when I'm less busy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113728286802901347?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113728286802901347/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113728286802901347' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113728286802901347'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113728286802901347'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/01/better-view-after-find.html' title='Better View after Find'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113725833509941513</id><published>2006-01-14T07:41:00.000-08:00</published><updated>2006-01-14T20:53:34.116-08:00</updated><title type='text'>Updating Whole Library</title><content type='html'>I've just worked my way through a 161-page document updating some 20 figures. During the course of doing this, I created a library and popped a copy of each updated figure into the library. However, at some point, I became suspicious that the justification settings for the legend paragraph in each of these figures left something to be desired -- I was getting awful word spacing. Indeed, I needed to change them from the default (on the left) to my preferred settings (on the right) [Sorry about the space that follows -- I don't seem to have control over it.]:&lt;/p&gt;&lt;br /&gt;&lt;table border="0" rules="none"&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt;&lt;td&gt;&lt;br /&gt;&lt;table align="left" border="0" rules="none"&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt; &lt;td&gt;Default&lt;/td&gt;&lt;br /&gt; &lt;td&gt;Min.&lt;/td&gt;&lt;br /&gt; &lt;td&gt;Des.&lt;/td&gt;&lt;br /&gt; &lt;td&gt;Max&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt; &lt;td&gt;Word Spacing&lt;/td&gt;&lt;br /&gt; &lt;td&gt;80%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;100%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;133%&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt; &lt;td&gt;Letter Spacing&lt;/td&gt;&lt;br /&gt; &lt;td&gt;0%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;0%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;0%&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt; &lt;td&gt;Glyph Scaling&lt;/td&gt;&lt;br /&gt; &lt;td&gt;100%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;100%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;100%&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;/td&gt;&lt;td&gt;&lt;br /&gt;&lt;table align="left" border="0" rules="none"&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt; &lt;td&gt;Preferred&lt;/td&gt;&lt;br /&gt; &lt;td&gt;Min.&lt;/td&gt;&lt;br /&gt; &lt;td&gt;Des.&lt;/td&gt;&lt;br /&gt; &lt;td&gt;Max&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt; &lt;td&gt; &lt;/td&gt;&lt;br /&gt; &lt;td&gt;80%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;100%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;125%&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt; &lt;td&gt; &lt;/td&gt;&lt;br /&gt; &lt;td&gt;-2%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;0%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;1%&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;tr&gt;&lt;br /&gt; &lt;td&gt; &lt;/td&gt;&lt;br /&gt; &lt;td&gt;98%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;100%&lt;/td&gt;&lt;br /&gt; &lt;td&gt;101%&lt;/td&gt;&lt;br /&gt;&lt;/tr&gt;&lt;br /&gt;&lt;/table&gt;&lt;br /&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/p&gt;&lt;br /&gt;&lt;br /&gt;So, I did that. Of course, when I say "preferred" I'm talking about preferred for this particular paragraph style used in the particular way it is being used. We could have a long discussion about this, but it is a side issue as far as the script I now need is concerned.&lt;br /&gt;&lt;br /&gt;The issue is to update the library with the latest version of each of these figures without having to step through each of the pages manually looking for them. When I created them, I labeled each of them. The script I used to move them into the library named the library items (assets in a script) for those labels. So, it ought to be as simple as interating through the page items of the document looking for the labeled item and then updating it in the library.&lt;br /&gt;&lt;br /&gt;Well, not quite because I have another series of figures that use the same labels! Aargh. Happens, though, that in those other figures, the labeled item is a rectangle (with an image in it) while the figures I actually care about are groups. So, that makes it easy to tell one from another.&lt;br /&gt;&lt;br /&gt;Because they're all anchored objects, though, I have to get at them indirectly -- I can't just ask for &lt;i&gt;myDoc.groups&lt;/i&gt;. But I can ask for &lt;i&gt;myStory.groups&lt;/i&gt; if I can get myStory pointing at the right story -- and that's easiest because it is the longest story in the document.&lt;br /&gt;&lt;br /&gt;So, here goes:&lt;pre&gt;//DESCRIPTION: Grab library elements from longest story&lt;br /&gt;&lt;br /&gt;Document.prototype.longestStory = function() {&lt;br /&gt; var myStories = this.stories.everyItem().length;&lt;br /&gt; var myLim = myStories.length;&lt;br /&gt; var longStory = 0;&lt;br /&gt; for (var i = 0; myLim &gt; i; i++) {&lt;br /&gt;  if (myStories[i] &gt; longStory) {&lt;br /&gt;   var myStory = i;&lt;br /&gt;   longStory = myStories[i];&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; return this.stories[myStory]&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if (app.libraries.length != 1) errorExit ("Please have exactly one library open for this script");&lt;br /&gt;myDoc = app.activeDocument;&lt;br /&gt;myLib = app.libraries[0];&lt;br /&gt;myStory = myDoc.longestStory();&lt;/pre&gt;And we're all set. We have the library, the document, and the story.&lt;br /&gt;&lt;br /&gt;But I'm starting to think I need to improve my &lt;i&gt;longestStory()&lt;/i&gt; method. The problem is that some of my documents have a prodigious number of stories in them -- this one has 1947 stories -- and so checking the lot takes a fair amount of time. But that's for another day. Right now, I need this script fast and I don't have time for strategic improvements.&lt;br /&gt;&lt;br /&gt;So let's gather the information we need:&lt;pre&gt;myAssetNames = myLib.assets.everyItem().name;&lt;br /&gt;myGroupNames = myStory.groups.everyItem().label;&lt;/pre&gt;That was easy. So now we need to walk down &lt;i&gt;myAssetNames&lt;/i&gt; and find the corresponding group and update the asset. We need to allow for the possibility that the group doesn't exist in this document. The best way to do this is to add another method. This is one I lifted from the &lt;a href="http://www.adobeforums.com/cgi-bin/webx?14@462.RSFrfkPoIyd.268@.eea52bc"&gt;Adobe User to User InDesign_Scripting forum&lt;/a&gt;, although the original message seems to have disappeared into the ether.&lt;br /&gt;&lt;br /&gt;Here's the method:&lt;pre&gt;Array.prototype.indexOf = function(find,offs) {&lt;br /&gt; for( var i = offs == undefined ? 0 : offs; this.length &gt; i; i++ ) {&lt;br /&gt;  if( this[i]==find ) {return i}&lt;br /&gt; }&lt;br /&gt; return -1;&lt;br /&gt;}&lt;/pre&gt;So, with this available to us, our main loop looks like this:&lt;pre&gt;for (var j = myAssetNames.length - 1; j &gt;= 0; j--) {&lt;br /&gt; var myIndex = myGroupNames.indexOf(myAssetNames[j]);&lt;br /&gt; if (myIndex != -1) {&lt;br /&gt;  replaceAsset(myLib, myLib.assets[j], myStory.groups[myIndex]);&lt;br /&gt; }&lt;br /&gt;}&lt;/pre&gt;And that's it, except for writing the &lt;i&gt;replaceAsset(theLib, theAsset, theObject)&lt;/i&gt; function. Notice that this function could easily be useful in another context. That's one reason for making it a function.&lt;pre&gt;function replaceAsset(theLib, theAsset, theObject) {&lt;br /&gt; var theProps = theAsset.properties&lt;br /&gt; theAsset.remove();&lt;br /&gt; var myNewAsset = theLib.store(theObject);&lt;br /&gt; myNewAsset.properties = theProps;&lt;br /&gt;}&lt;/pre&gt;And that should be that! And, indeed it was -- once I'd fixed a couple of minor typos.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113725833509941513?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113725833509941513/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113725833509941513' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113725833509941513'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113725833509941513'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/01/updating-whole-library.html' title='Updating Whole Library'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113715552483634677</id><published>2006-01-13T04:20:00.000-08:00</published><updated>2006-01-13T04:32:04.863-08:00</updated><title type='text'>Working on Selected Group</title><content type='html'>In the job I'm doing right now, I have a lot of grouped objects. I keep them in a library and plop them on to their appropriate pages as needed. Then, I run a script primarily to get the proper formatting on the group as a whole and its contents.&lt;br /&gt;&lt;br /&gt;Here's a simple example:&lt;pre&gt;//DESCRIPTION: Fix up Graphs&lt;br /&gt;&lt;br /&gt;myDoc = app.activeDocument;&lt;br /&gt;&lt;br /&gt;myGroup = app.selection[0];&lt;br /&gt;&lt;br /&gt;processGraph(myGroup);&lt;br /&gt;&lt;br /&gt;function processGraph(theGroup) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;theGroup.applyObjectStyle(myDoc.objectStyles.item("GraphAnchor"));&lt;br /&gt;&amp;nbsp;&amp;nbsp;var thePIs = theGroup.pageItems;&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var j = thePIs.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myItem = thePIs[j].getElements()[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myItem.constructor.name == "TextFrame") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myItem.textFramePreferences.ignoreWrap = true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myItem.textFramePreferences.firstBaselineOffset = FirstBaseline.leadingOffset;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myItem.graphics.length == 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myItem.applyObjectStyle(myDoc.objectStyles.item("DataGroupTextFrame"));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myItem.blendMode = BlendMode.multiply;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;This script falls into the quick &amp; dirty category. I wrote it for a particular kind of group in a particular kind of document containing a specific object style. I don't bother to check that there is a document open nor that there is a selection. I just go ahead and do my thing.&lt;br /&gt;&lt;br /&gt;But, after many uses, it occurred to me that more than 90% of the time, I run this script immediately after placing the group inline from the Library. This means that most of the time, I've been switching to the pointer tool and selecting the group before running the script. Finally, this morning, I realized that by adding:&lt;pre&gt;if (myGroup.constructor.name == "InsertionPoint") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myStory = myGroup.parentStory;&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myGroup = myStory.characters[myGroup.index - 1].groups[0];&lt;br /&gt;}&lt;/pre&gt;to the script immediately after setting &lt;i&gt;myGroup&lt;/i&gt; to point at the selection, I could avoid the need to switch pointer tools and let the script find the group -- again, I've done this in a quick and dirty fashion, relying on myself to remember the appropriate time to run the script and the state it expects.&lt;br /&gt;&lt;br /&gt;You might be wondering why I would have a function to process the contents of the group. Why not just build the group right in the first place. Two reasons:&lt;ol&gt;&lt;li&gt;The groups were prepared by a separate process on a different computer before all the details of how it would be used were fully worked out.&lt;/li&gt;&lt;li&gt;Applying an object style to a group tends to mess up settings inside the group, so they have to be repaired even if they were right in the first place.&lt;/li&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113715552483634677?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113715552483634677/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113715552483634677' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113715552483634677'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113715552483634677'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/01/working-on-selected-group.html' title='Working on Selected Group'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113616996828259774</id><published>2006-01-01T18:30:00.000-08:00</published><updated>2006-01-01T18:47:22.423-08:00</updated><title type='text'>LocationOptions Enumeration</title><content type='html'>&lt;h3&gt;Values&lt;/h3&gt;&amp;nbsp;&amp;nbsp;1650812527&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LocationOptions.before&lt;br&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;1634104421&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LocationOptions.after&lt;br&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;1650945639&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LocationOptions.atBeginning&lt;br&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;1701733408&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LocationOptions.atEnd&lt;br&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;1433299822&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LocationOptions.unknown&lt;br&gt;&lt;h3&gt;Discussion&lt;/h3&gt;As is implicit in the names of the enumerated values (the first four, anyway), this value is relative to some object or to the members of a collection of objects. The purpose of the fifth value, the "unknown" location option, is not at all clear. You certainly would never use it when calling a method that takes a location as an argument (e.g., a move method).&lt;br /&gt;&lt;br /&gt;There are times when InDesign loses track of the location of various items. For example, in order to speed the progress of scripts, they generally run with window updating and story composing switched off. In these circumstances, I could see a method returning the unknown location value, except that a careful check of the reference guide reveals that not a single method returns this information. Perhaps it's there for future expansion.&lt;br /&gt;&lt;br /&gt;Because this enumeration is only ever used as an argument when calling a method, the numerical values above have little more than academic interest. You could use them if you wished, but the result would be a more obscure script -- of course, there are times when obscurity has value, but most of the time you're better off writing "in the clear" by using the named variants.&lt;h3&gt;Usage Examples&lt;/h3&gt;Many variants of the &lt;i&gt;move()&lt;/i&gt; method have a &lt;i&gt;to&lt;/i&gt; argument that takes a member of this enumeration as a parameter. Similarly, some &lt;i&gt;add()&lt;/i&gt; methods have an &lt;i&gt;at&lt;/i&gt; argument to indicate where a new object should be added, and some &lt;i&gt;duplicate()&lt;/i&gt; methods have the &lt;i&gt;to&lt;/i&gt; argument.&lt;br /&gt;&lt;br /&gt;To move the first paragraph of text frame myTF to the end of story myStory:&lt;pre&gt;&amp;nbsp;&amp;nbsp;myTF.paragraphs[0].move(LocationOptions.atEnd, myStory);&lt;/pre&gt;To move a range of paragraphs in a story to before another paragraph in the same story (as you might do if you received a Word document with sections in the wrong order):&lt;pre&gt;&amp;nbsp;&amp;nbsp;myStory.paragraphs.itemByRange(ObjStart,i-1).move(LocationOptions.before,myStory.paragraphs[IntroStart]);&lt;/pre&gt;To add a new row to the end of table myTable:&lt;pre&gt;&amp;nbsp;&amp;nbsp;myTable.rows.add(LocationOptions.atEnd,myTable);&lt;/pre&gt;To add a new page to a document immediately after page myPage, updating the reference contained in myPage to refer to the newly added page:&lt;pre&gt;&amp;nbsp;&amp;nbsp;myPage = myDocument.pages.add(LocationOptions.after, myPage);&lt;/pre&gt;To duplicate page n of document myFdoc after the last page of document myDoc:&lt;pre&gt;&amp;nbsp;&amp;nbsp;myFdoc.pages[n].duplicate(LocationOptions.after,myDoc.pages[-1]);&lt;/pre&gt;&amp;nbsp;&amp;nbsp;This is equivalent to:&lt;pre&gt;&amp;nbsp;&amp;nbsp;myFdoc.pages[n].duplicate(LocationOptions.atEnd,myDoc);&lt;/pre&gt;Sometimes, the form you use for an operation like this depends on the way you happen to think about the maneuver.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113616996828259774?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113616996828259774/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113616996828259774' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113616996828259774'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113616996828259774'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2006/01/locationoptions-enumeration.html' title='LocationOptions Enumeration'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113604089998451327</id><published>2005-12-31T05:29:00.000-08:00</published><updated>2005-12-31T22:10:31.190-08:00</updated><title type='text'>Justifying (that table) at Last!</title><content type='html'>Finally, we get to write the function to actually do the justification! I started with this framework:&lt;pre&gt;function processFrame(frame, table, start, end) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;/*&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;frame: text frame that holds part of the table&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table: table to be processed&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;start: number of first row to be considered&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end: number of last row to be considered&lt;br /&gt;&amp;nbsp;&amp;nbsp;*/&lt;br /&gt;}&lt;/pre&gt; And then I gave some thought to whether or not to validate the passed arguments. For example, implicit in these arguments is that the parent text frame of the last row is the same as the first. But if I check that, I didn't need to pass the frame reference in the first place because I would have to calculate it in order to validate it. It was that realization combined with the fact that this is a special purpose function hardly likely to be used again outside of this script that convinced me to add this comment:&lt;pre&gt;// This special purpose function is never likely to be re-used elsewhere so&lt;br /&gt;// it does no validation of the arguments.&lt;/pre&gt;So, let's go to it. We need to start out by comparing the bottom bound of the frame with the baseline of the last line of the text frame. And then we need to "distribute that gap" to the rows themselves.&lt;br /&gt;&lt;br /&gt;This distribution feature raises a whole bunch of issues, that are worth listing because they demonstrate one of the considerations of writing this kind of script: implicit in any script are a bunch of assumptions that hopefully hold true for the particular purpopse at hand but which might break the script were it applied to a different scenario:&lt;ul&gt;&lt;li&gt;The text frame is assumed to be single-column (if it were multi-column, the script would need to work on each column)&lt;/li&gt;&lt;li&gt;The text in the table does not align to grid (if it did, the whole construction would be fragile and changing the vertical position of the rows could result in chaos)&lt;/li&gt;&lt;li&gt;The row heghts are defined using At Least rather than fixed heights&lt;/li&gt;&lt;li&gt;All the cells in each row have the same bottom inset (that's where we're going to add the space)&lt;/li&gt;&lt;li&gt;The frames have no stroke or insets&lt;/li&gt;&lt;/ul&gt;Wow! That's a lot of assumptions. And yet the table I'm working on falls into this category (as do the vast majority of the tables I ever build).&lt;br /&gt;&lt;br /&gt;OK, so we're cleared for take-off. The basic procedure is bubbling away at the back of my mind. It is largely a question of implementation. A couple of considerations are:&lt;ol&gt;&lt;li&gt;It is safer (although slower) to not assume that all cells in a row have the same bottom-inset value. Indeed, already I'm wondering if I should strike that from my list of restrictions. As long as I process all the cells individually, the distributed increment will affect the largest cell and so will have the correct effect on a row where this rule didn't apply.&lt;/li&gt;&lt;li&gt;It is vital that we allow for the bottom-inset value to be different from one row to another (although inside any frame that happens to be not true for the table I'm working with).&lt;/li&gt;&lt;li&gt;We must verify that after distributing the space we are not victims of a rounding error that pushed the last row to the next frame (or possibly overset) because if that has happened, we need to repair the damage by decreasing that bottom inset a tad.&lt;/li&gt;&lt;/ol&gt;Finally, I'm ready to start writing the code. Because of the last concern, I'm going to make yet another function to do the actual adjusting of the rows. It will return the parent text frame of the last row to facilitate the verification.&lt;br /&gt;&lt;br /&gt;I'm starting my code with what looks like a framework for an infinite loop:&lt;pre&gt;while(true) {&lt;br /&gt;}&lt;/pre&gt;But this script will eventually return to the calling script thereby breaking out of the loop. The loop is only needed if we run into the rounding error problem mentioned above.&lt;br /&gt;&lt;br /&gt;Ack! That was a false start, as I discovered when I got to this point:&lt;pre&gt;while (true) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var gap = frame.geometricBounds[2] - frame.lines[-1].baseline;&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (gap == 0) return // unlikely, but let's not sweat blood doing nothing&lt;br /&gt;&amp;nbsp;&amp;nbsp;var rowInc = gap/(end - start - 1) // increment is gap divided by no. of rows&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (frame == distrIncs(rowInc, table, start, end)) &lt;br /&gt;}&lt;/pre&gt;And then I realized that if this condition evaluates to false, I need to apply different logic to get the row back where it belongs, so the first three statements don't belong in the loop. In fact, I'm wondering just what logic I do need to get it back. A failure here is probably the result of a microscopic rounding error, and I'm not all that good at dealing with microscopic errors. But let's give it a shot. It comes down to: how small an error can I live with in real life? How about a quarter of a point? That'll do. But that means we need to work in points (at least on the vertical dimension):&lt;pre&gt;var userVert =  app.documents[0].viewPreferences.verticalMeasurementUnits;&lt;br /&gt;app.documents[0].viewPreferences.verticalMeasurementUnits = MeasurementUnits.points;&lt;br /&gt;&lt;br /&gt;var gap = frame.geometricBounds[2] - frame.lines[-1].baseline;&lt;br /&gt;if (gap == 0) return; // unlikely, but let's not sweat blood doing nothing&lt;br /&gt;var rowInc = gap/(end - start + 1); // increment is gap divided by no. of rows&lt;br /&gt;while (frame != distIncs(rowInc, table, start, end)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;rowInc = -0.25/(end-start + 1); // fix for rounding errors&lt;br /&gt;}&lt;br /&gt;//restore user's  vertical measurement preferences&lt;br /&gt;app.documents[0].viewPreferences.verticalMeasurementUnits = userVert&lt;br /&gt;return // when we get here, we've succeeded&lt;/pre&gt;So, now all we need is that &lt;i&gt;distIncs()&lt;/i&gt; function. &lt;br /&gt;&lt;br /&gt;Sometimes, I write scripts like this from the bottom up. Doing the lower level functions first and then using them as building blocks. Had I done so for this script, I wonder if I'd have thought to return the frame of the last row? I doubt it. I'd have probably tried to solve the problem inside the function. But by working top-down, I think we have a more elegant solution.&lt;br /&gt;&lt;br /&gt;Again, I started with a framework:&lt;pre&gt;function distIncs(inc, table, start, end) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;/*&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inc: number of points to adjust bottom inset of each indicated cell&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;table: table to work on&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;start: first row number&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;end: last row number&lt;br /&gt;&amp;nbsp;&amp;nbsp;*/&lt;br /&gt;}&lt;/pre&gt;Notice that I'm using the same argument names. Arguments are automatically local to the function they are passed to, so this is quite safe.&lt;br /&gt;&lt;br /&gt;My first instinct is to use a double-loop that operates on each cell in each row. It could be that it is quicker to simply process all the cells from the first of row[start] to the last of row[end], but I'm more interested in clarity than speed this time around:&lt;pre&gt;for (var n = start; end &gt;= n; n++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myCells = table.rows[n].cells;&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var m = myCells.length - 1; m &gt;= 0; m--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myCells[m].bottomInset = myCells[m].bottomInset + inc;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;There are two drawbacks to working top down: when you finally get to do some testing, you have the whole script written so some obvious errors only surface at this point. For example, in the code I wrote yesterday, I used myRows.count in one place where I should have used myRows.length, and I also tried to get myRows[j].texts[0] when I should have asked for myRows[j].cells[0].texts[0]. I also used J in one loop control statement instead of j. [I have edited the articles to eliminate these problems.] But the second drawback to this approach is that the first test is of the whole thing, so even when we're past the obvious errors, finding the more subtle ones can be time-consuming.&lt;br /&gt;&lt;br /&gt;Looking at this first run, the logic about whether or not adding the increment has pushed the last row to the next frame looks flawed. Ah, well that was a dumb error, wasn't it. distIncs() was supposed to return the parent frame of the last row. No wonder the script is getting nowhere!&lt;br /&gt;&lt;br /&gt;Well, what do you know? That was the only problem. Adding:&lt;pre&gt;return table.rows[end].cells[0].texts[0].parentTextFrames[0];&lt;/pre&gt;to the end of the distIncs() function solved the whole problem. The script has run and done its thing beautifully!&lt;br /&gt;&lt;br /&gt;Because I ran the script from within ESTK, I was able to put a breakpoint on the line of the script that deals with the rounding error I was so worried about -- and so I know that that problem never happened because the script never hit the breakpoint.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113604089998451327?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113604089998451327/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113604089998451327' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113604089998451327'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113604089998451327'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/justifying-that-table-at-last.html' title='Justifying (that table) at Last!'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113599841162357265</id><published>2005-12-30T17:43:00.000-08:00</published><updated>2005-12-31T22:09:04.490-08:00</updated><title type='text'>Height of a Table (continued)</title><content type='html'>Some time has passed because I had to make progress on a different part of the project, but now I'm back to this task. So, we have a reference to our table and we know how to get the size of each gap that must be filled. But we still need to work out which rows fall on which pages.&lt;br /&gt;&lt;br /&gt;It appears that the way to tackle this is to simple walk through all the rows of the table looking at the parent text frame of each. However, there's an immediate complicaton:&lt;pre&gt;var myRows = myTable.rows;&lt;br /&gt;app.select(myRows[0].cells[0]);&lt;/pre&gt;reveals that the header row (in this case there is just one) is row 0. This script can't afford to change the header or footer rows (if there are any) because that will affect every page and potentially could change the contents of one of the later frames (possibly even creating an overflow). The two table properties headerRowCount and footerRowCount will help us here:&lt;pre&gt;var myRows = myTable.rows&lt;br /&gt;var myLim = myRows.length - myTable.footerRowCount;&lt;br /&gt;var myTF = myTable.rows[myTable.headerRowCount].cells[0].texts[0].parentTextFrames[0];&lt;br /&gt;var firstRow = myTable.headerRowCount;&lt;br /&gt;for (var j = myTable.headerRowCount; myLim &gt; j; j++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Walk through the rows&lt;br /&gt;}&lt;/pre&gt;By starting at rows[myTable.headerRowCount] we start at the first row after the headers, and by subtracting myTable.footerRowCount from the total number of rows, we avoid operating on the footers.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;myTF&lt;/i&gt; is initialized to be a reference to the first text frame that contains the table and &lt;i&gt;firstRow&lt;/i&gt; tells us the number of the first (non-header) row of the text frame we're about to process. So now we're ready to walk through the rows. We need to look for a change in the parent text frame at which point we can process the frame we just left before moving on. But we mustn't forget that the last frame will end without us moving on to the next, so we need an extra test to see if we're dealing with the last row.&lt;br /&gt;&lt;br /&gt;For the moment, let's put off the actual processing of each text frame because we'll write a function to do that. Let's just get a reference to the frame that needs processing and hand off to the function enough information that it knows what to do. So, inside our loop, we need:&lt;pre&gt;&amp;nbsp;&amp;nbsp;myCurTF = myRows[j].cells[o].texts[0].parentTextFrames[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;if ((myCurTF == myTF) &amp;&amp; (j != myLim - 1)) continue;&lt;br /&gt;&amp;nbsp;&amp;nbsp;(j == myLim - 1) ? k = j : k = j-1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;processFrame(myTF, myTable, firstRow, k);&lt;br /&gt;&amp;nbsp;&amp;nbsp;firstRow = j;&lt;br /&gt;&amp;nbsp;&amp;nbsp;myTF = myCurTF;&lt;/pre&gt;The first thing we do is check to see if we've reached either the start of a new frame or the end of the last frame. Hey! Wait a minute. How can you use an AND construction (&amp;&amp;) to test for an OR condition? Well, even as I wrote that sentence I found myself wondering the same thing.  What converts an AND test to an OR is the "NOT" contained in the second test. That condition is false once only, when we get to the end of the last frame. Every other time it is true, and so, the first text (are the frames the same) totally dominates this test for all but the very last row.&lt;br /&gt;&lt;br /&gt;So, only if there is a frame change or we're reached the last frame will we get past that first test. But now we need a second test because for all but the last frame, the frame we need to process ends at j-1, while the last frame ends at j. So, we test again to see if we're at the last frame, and if so we set a new variable &lt;i&gt;k&lt;/i&gt; to the value of j and otherwise to j-1.&lt;br /&gt;&lt;br /&gt;Aha, there's a couple of syntax issues here. First, when using the ?: construction to make a test, be sure to end the previous line with a semi-colon. JavaScript is relatively loose about semi-colons at the ends of lines, but this is one case where you must have one. Otherwise, you'll get a confusing error about it expecting a semi-colon and you'll waste time checking that you got the syntax of the ?: line right when the error is actually on the previous line. [Guess how I know that!]&lt;br /&gt;&lt;br /&gt;More of an issue is that we really want &lt;i&gt;k&lt;/i&gt; to be a local variable. But where to declare it? If you put it inside the ?: you'll get a syntax error. So you need to declare it in advance. Thus, we need another statement which should really go before the loop that simply reads:&lt;pre&gt;var k;&lt;/pre&gt;In all my examples up until now, I've declared variables to be local as I created them, but you can if you wish declare them in advance without assigning them a value.&lt;br /&gt;&lt;br /&gt;Then we call our function (which we still have to write) passing to it the frame to be processed, the table we're working on and the row numbers of the first an last row of the part of the table that is in the frame in question.&lt;br /&gt;&lt;br /&gt;After the function has done its thing, we need to initialize our &lt;i&gt;firstRow&lt;/i&gt; and &lt;myTF&gt; variables for the next frame. Notice that we ignore the possibility at this stage that we just processed the last frame. In that case, we're going to hit the loop limit on the next pass and so these values will never actually be used.&lt;br /&gt;&lt;br /&gt;And that's it for today. I'll write the function in the morning.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113599841162357265?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113599841162357265/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113599841162357265' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113599841162357265'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113599841162357265'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/height-of-table-continued_113599841162357265.html' title='Height of a Table (continued)'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113597398867243470</id><published>2005-12-30T10:23:00.000-08:00</published><updated>2005-12-30T12:19:48.706-08:00</updated><title type='text'>Height of a Table (continued)</title><content type='html'>One of the golden rules of scripting is: if you think of an ugly solution to something and you are repelled by just how ugly it is, there probably is an easier solution just waiting to be found. That certainly applies here.&lt;pre&gt;myStory = app.selection[0].parentStory;&lt;br /&gt;myTFs = myStory.textFrames;&lt;br /&gt;myBs = myTFs.everyItem().lines[-1].baseline;&lt;/pre&gt;Gives me the base of each table section in the ten frames of this particular story (which consists entirely of the table).&lt;br /&gt;&lt;br /&gt;So, now I need to work out which is the last row on each page. That should be easy enough.&lt;br /&gt;&lt;br /&gt;First, we need to know which table to work on. Let's require the user to have an insertion point in the table of interest:&lt;pre&gt;var ErrMsg = "Please select a table.";&lt;br /&gt;if (app.selection.length == 0) { errorExit(ErrMsg) }&lt;br /&gt;var myTable = app.selection[0];&lt;br /&gt;while (myTable.constructor.name != "Table") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;myTable = myTable.parent;&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myTable.constructor.name == "Application") { errorExit(ErrMsg) }&lt;br /&gt;}&lt;/pre&gt;Next, we need to gather some data: how many frames are part of the table? That raises the question does a table have a parentTextFrames property -- answer: No! What about the "character" holding the table? Let's experiment:&lt;br /&gt;&lt;br /&gt;1. To get the parent story of the table you need:&lt;pre&gt;myTable.parent.parentStory&lt;/pre&gt;because the parent is the first text frame (note that if the table is nested inside another, the parent is a cell of that other table, but I'm not going to worry about that right now).&lt;br /&gt;&lt;br /&gt;2. To find a table in a story, you use the storyOffset property of the table, but this seems to miss by one. My story is only one character long (even though it also has 10 lines) and yet the storyOffset return is 1.&lt;br /&gt;&lt;br /&gt;3. Obviously, in this case, to get all the text frames containing the table, I could just get all the text frames of the story because that's all there are, there ain't no more, but of a mind to experiment. And so I tried:&lt;pre&gt;myChar = myTable.parent.parentStory.characters[myTable.storyOffset - 1];&lt;br /&gt;myTFs = myChar.parentTextFrames;&lt;/pre&gt;but all I got was the one text frame.&lt;br /&gt;&lt;br /&gt;4. So, I tried selecting the character to see what would happen:&lt;pre&gt;app.select(myChar);&lt;br /&gt;myTFs = app.selection[0].parentTextFrames;&lt;/pre&gt;and again, all I got was the one text frame. But then I noticed something interesting. Only the first part of the table was selected, so indeed a singular text frame was all I'd expect.&lt;br /&gt;&lt;br /&gt;5. So I went for:&lt;pre&gt;app.select(myChar.parentStory.texts[0]);&lt;br /&gt;myTFs = app.selection[0].parentTextFrames;&lt;/pre&gt;and even though this did select the whole length of the table, I still only got the first text frame returned (actually, that's an assumption; let's just say I only got one text frame).&lt;br /&gt;&lt;br /&gt;So, it looks as though the only way to get a list of the text frames is to walk through the table getting the parent text frame of each row and adding any not already on the list to the list. Or, I can use a priori knowledge about the particular table I'm working with and just run with the frames of the story.&lt;br /&gt;&lt;br /&gt;Happens, that I'm also going to need to know which is the last frame of each page, so I'm going to have to walk through anyway, so perhaps I'll do that after all.&lt;br /&gt;&lt;br /&gt;More to come ...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113597398867243470?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113597398867243470/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113597398867243470' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113597398867243470'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113597398867243470'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/height-of-table-continued_30.html' title='Height of a Table (continued)'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113596653945009587</id><published>2005-12-30T10:07:00.000-08:00</published><updated>2005-12-30T10:15:39.450-08:00</updated><title type='text'>Height of a Table (continued)</title><content type='html'>It is not clear to me how to measure in a script the height of the gap at the bottom of a page (which really means bottom of a frame) when a table is split from one frame to the next. I can't think of a direct way of measuring the vertical coordinate of the bottom of the bottom row. If anyone knows of a way, I'd be interested to hear about it.&lt;br /&gt;&lt;br /&gt;In the mean time, this trick comes to mind:&lt;ol&gt;&lt;li&gt;The gap must be less than the height of the first row on the next page.&lt;/li&gt;&lt;li&gt;The parent text frame of text in a cell is available information.&lt;/li&gt;&lt;li&gt;Thus, I can adjust the height of bottom row on one page until it flips to the next and use that information to work out how much space I need to distribute.&lt;/li&gt;&lt;/ol&gt;Boy! Is that ugly!&lt;br /&gt;&lt;br /&gt;In fact, that's so ugly, I'm going to research this some more before continuing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113596653945009587?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113596653945009587/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113596653945009587' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113596653945009587'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113596653945009587'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/height-of-table-continued.html' title='Height of a Table (continued)'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113596573028720866</id><published>2005-12-30T09:42:00.000-08:00</published><updated>2005-12-30T10:02:10.306-08:00</updated><title type='text'>Height of a Table</title><content type='html'>I have this ten-page table that looks a bit of a mess because each page is a different height. The question is: how to automatically adjust the table so each page (except perhaps the last) is the same height?&lt;br /&gt;&lt;br /&gt;Were I doing this manually, I'd estimate how much space I had to fill at the bottom of each page and distribute that amount into the bottom inset of each row on the page. So how do I do this in a script? The first thing I checked was that the height return for myTable.height is indeed the complete height of the table:&lt;pre&gt;myTable = app.selection[0].parent.parent;&lt;br /&gt;alert(myTable.height.toPrecision(4));&lt;/pre&gt;And that returns 558.7. Hmm. I should add the measurement units into my alert:&lt;pre&gt;//DESCRIPTION: Height of Table &lt;br /&gt;// Assumes insertion point in text in a cell&lt;br /&gt;myTable = app.selection[0].parent.parent;&lt;br /&gt;alert(myTable.height.toPrecision(4) + " " + decypherUnits(app.activeDocument.viewPreferences.verticalMeasurementUnits));&lt;br /&gt;&lt;br /&gt;function decypherUnits (theNum) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (theNum&lt;257) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Custom settings are in points.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return "pts";&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Decyphers for print purposes, so inches and inches decimal are both returned as 'ins'&lt;br /&gt;&amp;nbsp;&amp;nbsp;theNums = [2054187363,2054188905,2053729891,2053729892,2053991795,2053336435,2053335395]&lt;br /&gt;&amp;nbsp;&amp;nbsp;theMeanings = ["picas","pts","ins","ins","mms","cms","ciceros"]&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var i = 0; theNums.length &gt; i; i++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (theNum == theNums[i])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return theMeanings[i];&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;return theNum&lt;br /&gt;}&lt;/pre&gt;And that returned 558.7 picas.&lt;br /&gt;&lt;br /&gt;In passing, I notice that this is a function I lifted from an old script. I rarely, these days, take advantage of the JavaScript facility whereby an if statement when true will execute the next statement only without the need to use brackets. While it saves a bit of typing, it creates an unbalanced look that can deceive me if I come back and try to edit the script.&lt;br /&gt;&lt;br /&gt;More to come ...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113596573028720866?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113596573028720866/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113596573028720866' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113596573028720866'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113596573028720866'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/height-of-table.html' title='Height of a Table'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113590272775408109</id><published>2005-12-29T16:25:00.000-08:00</published><updated>2005-12-29T16:32:07.766-08:00</updated><title type='text'>Quick &amp; Dirty Paragraph Shuffler</title><content type='html'>My client had me insert new paragraphs into a long structured document. Due to a miscommunication, I initially inserted the paragraphs in the wrong place on each page. It transpired that the paragraphs (in the paragraph style "Workshop") needed to be immediately before the corresponding "Description" paragraph rather than after the "Isotype" paragraph where I'd put it. Here's how I solved the problem:&lt;pre&gt;Document.prototype.longestStory = function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myStories = this.stories.everyItem().length;&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myLim = myStories.length;&lt;br /&gt;&amp;nbsp;&amp;nbsp;var longStory = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;for (var i = 0; myLim &gt; i; i++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myStories[i] &gt; longStory) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myStory = i;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;longStory = myStories[i];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;return this.stories[myStory]&lt;br /&gt;}&lt;br /&gt;myDoc = app.activeDocument;&lt;br /&gt;myStory = myDoc.longestStory();&lt;br /&gt;myStyles = myStory.paragraphs.everyItem().appliedParagraphStyle;&lt;br /&gt;n = 0; // Indicates most recent index of a Description paragraph&lt;br /&gt;for (var j = myStyles.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;theName = myStyles[j].name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (theName == "Description") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;n = j;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;continue&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (theName == "Workshop") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (n == 0 ) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert ("Oops");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exit();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myStory.paragraphs[j].move(LocationOptions.before, myStory.paragraphs[n]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;I put in the test just in case I had misspelled the name of the style. I didn't want all those paragraphs moved to the start of the story.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113590272775408109?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113590272775408109/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113590272775408109' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113590272775408109'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113590272775408109'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/quick-dirty-paragraph-shuffler.html' title='Quick &amp; Dirty Paragraph Shuffler'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113512840294470453</id><published>2005-12-20T17:22:00.000-08:00</published><updated>2005-12-20T17:26:42.956-08:00</updated><title type='text'>Q&amp;D Leading Fixer</title><content type='html'>I was about to deliver a proof PDF to my client when I noticed that one of the heads in one of the frames (on page 531) was not aligned correctly. A quick look confirmed that the leading of the text frame had switched from Leading to Ascent. Further examination of the document revealed other instances of this problem.&lt;br /&gt;&lt;br /&gt;A quick &amp; dirty script to the rescue:&lt;pre&gt;//DESCRIPTION: Format head leading fixer&lt;br /&gt;&lt;br /&gt;myDoc = app.activeDocument;&lt;br /&gt;app.findPreferences = null;&lt;br /&gt;app.changePreferences = null;&lt;br /&gt;myFinds = myDoc.search("",false,false,undefined,{appliedParagraphStyle:myDoc.paragraphStyles.item("format")});&lt;br /&gt;for (var j=myFinds.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;myFinds[j].parentTextFrames[0].textFramePreferences.firstBaselineOffset = FirstBaseline.leadingOffset;&lt;br /&gt;}&lt;/pre&gt;An now all is well. Time to export another proof PDF.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113512840294470453?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113512840294470453/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113512840294470453' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113512840294470453'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113512840294470453'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/qd-leading-fixer.html' title='Q&amp;D Leading Fixer'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113492035840078241</id><published>2005-12-18T07:15:00.000-08:00</published><updated>2005-12-28T08:33:52.576-08:00</updated><title type='text'>Really Commenting Code</title><content type='html'>Most times, when I'm writing scripts, I tend to pay little more than lip-service to commenting the code, but I'm slowly learning to regret that. Each time I have to explore a function I create more than a few days ago, I end up investing an amount of time simply trying to fathom exactly what it does. For trivial functions, this might be OK, but for those that do more, this is proving to not be OK.&lt;br /&gt;&lt;br /&gt;So, this morning, I decided to write a function and comment it "properly". Here's the result:&lt;pre&gt;&lt;br /&gt;function makeFileName(Orig, New, Append) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;/*&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Returns derived filename using:&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Orig&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FileRef or String.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;If FileRef, full name of file used as basis;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;If string, can be path or just name; whichever, that's what's returned&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;New&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;String&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;New name for file, including extension&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Append (Optional; default: true)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Boolean&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;true: append to existing name&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;false: replace existing name&lt;br /&gt;&amp;nbsp;&amp;nbsp;*/&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (arguments.length &lt; 2) {&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw "First two arguments are required."&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (Orig.constructor.name != "String") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;// Should be file or folder; get path&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Orig = Orig.path;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Orig.length;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} catch (e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw "First argument must be a string or file/folder reference."&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (New.constructor.name != "String") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw "Second argument must be a string."&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (arguments.length == 2) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Append = true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Preliminaries complete; arguments are good.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Get platform independent version of Orig string&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myString = File.decode(Orig);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// If last part of name includes period, strip from last period on&lt;br /&gt;&amp;nbsp;&amp;nbsp;var lastDot = myString.lastIndexOf(".");&lt;br /&gt;&amp;nbsp;&amp;nbsp;var lastSlash = myString.lastIndexOf("/");&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (lastDot &gt; lastSlash) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myString = myString.slice(0, lastDot);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// If we're not appending, get rid of last part of name&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (!Append) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (lastSlash == -1) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myString = ""; // there only is a last part&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myString = myString.slice(0, lastSlash);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Append New, encode and return&lt;br /&gt;&amp;nbsp;&amp;nbsp;return File.encode(myString + New);&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;I think this makes the function a whole lot easier to understand, whether I'm coming back to it to use it as-is or if I'm trying to revise it for some extra functionality. For example, I wrote this to help me with a larger script where I'm trying to create a preferences file in the same folder as the running script. So, the initial purpose is to create a filepath that derives from the script's filepath.&lt;br /&gt;&lt;br /&gt;So, the calling script looks like this:&lt;pre&gt;var myPath = getScriptPath();&lt;br /&gt;var PrefsFileName = makeFileName (myPath, "prefs.txt", true);&lt;/pre&gt;where &lt;i&gt;getScriptPath()&lt;/i&gt; is a function that does this:&lt;pre&gt;function getScriptPath() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// This function returns the path to the active script, even when running ESTK&lt;br /&gt;&amp;nbsp;&amp;nbsp;try { &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return app.activeScript; &lt;br /&gt;&amp;nbsp;&amp;nbsp;} catch(e) { &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return e.fileName; &lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;I'm a little torn about what to do about those errors that &lt;i&gt;makeFileName()&lt;/i&gt; throws. If I leave the call as it is above, I'll just get a run-time error should an error be detected by the script, which would be all right if I were writing this script for myself alone (and who knows, perhaps I am). This is a better version:&lt;pre&gt;var myPath = getScriptPath();&lt;br /&gt;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var PrefsFileName = makeFileName (myPath, "prefs.txt", true);&lt;br /&gt;} catch (e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit("makeFileName reports: " + e)&lt;br /&gt;}&lt;/pre&gt;but that &lt;i&gt;try&lt;/i&gt; construction clutters up the script making it hard to follow.&lt;br /&gt;&lt;br /&gt;An alternative approach that I rejected for now would replace those "throw" statements with direct calls to &lt;i&gt;errorExit()&lt;/i&gt;. I chose not to do this because one day I might write a script that makes use of those thrown errors to take some action other than just reporting the problem and quitting.&lt;br /&gt;&lt;br /&gt;I'm not sure there are any rights or wrongs here so much as philosophical decisions. If I were writing solely for myself, I'd just let the run-time errors happen and not blink, but this particular script I'm working on has potential value for a larger community, so I'm leaving things the way they are even though the main script gets a bit more cluttered.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113492035840078241?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113492035840078241/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113492035840078241' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113492035840078241'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113492035840078241'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/really-commenting-code.html' title='Really Commenting Code'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113483017873317891</id><published>2005-12-17T06:21:00.000-08:00</published><updated>2005-12-17T06:36:18.800-08:00</updated><title type='text'>Breaking a Text Reference</title><content type='html'>When I wrote this code, it crossed my mind that it might not work. It didn't take long to discover that it doesn't.&lt;pre&gt;function processFoundClone(theText, theAsset) {&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// [snip]&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Check to see if asset already there, if so replace it, if not add it&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (theText.characters[0].groups.length &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;theText.characters[0].remove();&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.select(theText.insertionPoints[0]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;theAsset.placeAsset(app.activeDocument);&lt;br /&gt;}&lt;/pre&gt;The problem is that if the first character contains (consists of?) an anchored clone already, I want to replace it with the new one from the library (that's what the parameter &lt;i&gt;theAsset&lt;/i&gt; is all about). But, if I delete it in the way shown here, the reference to the text breaks and so I'm unable to select that insertion point.&lt;br /&gt;&lt;br /&gt;It's possible that this is breaking only because the paragraph I happen to be working with is the last in its story and so by deleting the character I'm causing the text reference to point beyond the end of the story. If that's the cause, then fixing this is easy: put the new one in and then take the old one out. Let me try that ...&lt;br /&gt;&lt;br /&gt;Well what do you know! How's this for real-time blogging. That was the problem and this reorganization fixes it:&lt;pre&gt;function processFoundClone(theText, theAsset) {&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// [snip]&lt;br /&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Add new asset&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.select(theText.insertionPoints[0]);&lt;br /&gt;&amp;nbsp;&amp;nbsp;theAsset.placeAsset(app.activeDocument);&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Check to see if there was one already there; if so, delete it&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (theText.characters[1].groups.length &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;theText.characters[1].remove();&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;Notice that because I added a character at the beginning of theText, I now have to check to see if the second character contains a group, and if so delete that. Because the story was first made longer, the reference to theText is still valid (albeit it doesn't point at the exact same set of characters) whereas removing the character first invalidated it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113483017873317891?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113483017873317891/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113483017873317891' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113483017873317891'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113483017873317891'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/breaking-text-reference.html' title='Breaking a Text Reference'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113482641381363804</id><published>2005-12-17T05:02:00.000-08:00</published><updated>2005-12-23T04:24:49.396-08:00</updated><title type='text'>Function Snippets</title><content type='html'>A Mac user, I can't begin to describe how useful &lt;a href="http://www.isnip.net/"&gt;iSnip&lt;/a&gt; is. I'm sure there must be similar utilities for Windows. Most of the snippets I keep are JavaScript related. Here are a couple of the more significant that are currently in my Functions folder:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Dumb Run Pages&lt;/h3&gt;&lt;pre&gt;function DumbRunPages(theDoc, theStory) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// What makes this "dumb" is that default master pages are used.&lt;br /&gt;&amp;nbsp;&amp;nbsp;var uRuler = theDoc.viewPreferences.rulerOrigin;&lt;br /&gt;&amp;nbsp;&amp;nbsp;theDoc.viewPreferences.rulerOrigin = RulerOrigin.spreadOrigin;&lt;br /&gt;&amp;nbsp;&amp;nbsp;while (theStory.textFrames[-1].overflows) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;theDoc.documentPreferences.pagesPerDocument = theDoc.documentPreferences.pagesPerDocument + 1;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var backPage = theDoc.pages[-1];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.activeWindow.activePage = backPage;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;backPage.appliedMaster = theDoc.pages[-2].appliedMaster;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myPbounds = backPage.bounds;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myNewTF = backPage.textFrames.add();&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ((backPage.name % 2 == 1) || (!theDoc.documentPreferences.facingPages)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myNewTF.geometricBounds = &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[myPbounds[0] + backPage.marginPreferences.top, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myPbounds[1] + backPage.marginPreferences.left, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myPbounds[2] - backPage.marginPreferences.bottom, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;myPbounds[3] - backPage.marginPreferences.right];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myNewTF.geometricBounds = &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;[myPbounds[0] + backPage.marginPreferences.top, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myPbounds[1] + backPage.marginPreferences.right, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myPbounds[2] - backPage.marginPreferences.bottom, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myPbounds[3] - backPage.marginPreferences.left];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myNewTF.itemLayer = theStory.textFrames[-1].itemLayer;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myNewTF.previousTextFrame = theStory.textFrames[-1];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myNewTF.textFramePreferences.textColumnCount = backPage.marginPreferences.columnCount;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myNewTF.textFramePreferences.textColumnGutter = backPage.marginPreferences.columnGutter;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (myNewTF.characters.length == 0){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;theDoc.viewPreferences.rulerOrigin = uRuler;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw ("Permanently overset"); // This indicates a permanent overset condition so break out of loop&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;theDoc.viewPreferences.rulerOrigin = uRuler;&lt;br /&gt;}&lt;/pre&gt;I called this "dumb" to distinguish it from other scripts I've written where each master page had, in effect, a "next master" associated with it. That dates back to the days when I used to manage left/right masters to overcome the difficulties of bleeding on all four sides of the page when working with facing-pages documents. It's now over three years since I did any of that kind of work, so perhaps I shouldn't have called this function dumb at all. Mind you, within the context that I wrote this function, there were some smarts in the script that called this function to revisit the added pages and update the applied masters, so that too was part of the rationale for the name.&lt;br /&gt;&lt;br /&gt;Basically, you call it with two parameters: the document you're working on and the story you care about. If that story is overset, then pages are added to the document until either it is not overset or until a permanent overset condition is detected (adding pages doesn't help because the story won't fit into the frames being added -- this might be for two reasons I'm aware of: (1) use of No Break making a line of text too wide (2) inclusing of an inline graphic that is too tall to fit in the added frames.&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;Get Project Library&lt;/h3&gt;&lt;pre&gt;function getProjectLibrary(libName) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myLib = app.libraries.item(libName);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myLib.name;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return myLib&lt;br /&gt;&amp;nbsp;&amp;nbsp;} catch (e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myFolder = app.activeDocument.filePath;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;while (myFolder != null) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (File(myFolder.fsName + "/" + libName).exists) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.open(File(myFolder.fsName + "/" + libName));&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return app.libraries.item(libName);&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myFolder = myFolder.parent;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;alert("Couldn't find library");&lt;br /&gt;&amp;nbsp;&amp;nbsp;exit();&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;This too is a dumbed down version of a longer function that gives the user the opportunity to use the file manager to go find the library if it can't be found. But that other version is so long it makes all my scripts look somewhat overwhelming and so I usually prefer this simpler function that just gives up if it fails to find the library named in the call.&lt;br /&gt;&lt;br /&gt;Why do I call it  a &lt;i&gt;Project&lt;/i&gt; library? Well, the concept is that your currently active document (there'd better be one when you call this function) is part of a project you're working on and the library for that project is either (a) already open or (b) somewhere in the folder hierarchy that leads to the active document. So, if the library is not open, it searches up the folder hierarchy until it either finds the libary or until it reaches the top.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A word of caution:&lt;/b&gt; most of the file systems that InDesign works with are case-independent but JavaScript is not, so if you get the case wrong in the name of the library file, the library may indeed be opened, but if you're using that other name to later try to access the opened library it won't work.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;A second word of caution:&lt;/b&gt; when libraries appear in the palette, they do not display the &lt;i&gt;.indl&lt;/i&gt; extension, but this is considered part of the name by JavaScript, so if you see "B-Library" in the palette, to access it you must use "B-Library.indl".&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113482641381363804?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113482641381363804/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113482641381363804' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113482641381363804'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113482641381363804'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/function-snippets.html' title='Function Snippets'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113465383945707310</id><published>2005-12-15T05:09:00.000-08:00</published><updated>2005-12-15T05:37:19.503-08:00</updated><title type='text'>Replace with Clipboard</title><content type='html'>One oft-requested feature for InDesign is the ability to replace found text with the current contents of the clipboard. It came to me this morning that this is a case where the UI can be used to set-up  a script, and consequently, the script itself is very simple.&lt;br /&gt;&lt;br /&gt;The standard advice for a scripter doing a search is to use:&lt;pre&gt;app.findPreferences = null;&lt;br /&gt;app.changePreferences = null;&lt;/pre&gt;before doing a search. This clears out any settings left over from the last time that &lt;i&gt;Find/Change&lt;/i&gt; was used. But what if you want to use the search options that are already there? Well, this can certainly be done using the following workflow:&lt;ol&gt;&lt;li&gt;Open the &lt;i&gt;Find/Change&lt;/i&gt; dialog and set-up a search of whatever complexity you wish.&lt;/li&gt;&lt;li&gt;Click &lt;i&gt;Done&lt;/i&gt;. Or, move the dialog aside.&lt;/li&gt;&lt;li&gt;Run the following script.&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;app.search();&lt;/pre&gt;And that's it. But not really all that useful in this form because you could as easily have achieved this result by simply clicking &lt;i&gt;Change All&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;But what is not available in &lt;i&gt;Find/Change&lt;/i&gt; is the ability to replace the found text with the current contents of the clipboard. So, let's modify our workflow:&lt;ol&gt;&lt;li&gt;Copy to the clipboard that which you wish to change the found text into.&lt;/li&gt;&lt;li&gt;Open the &lt;i&gt;Find/Change&lt;/i&gt; dialog and set-up a search of whatever complexity you wish.&lt;/li&gt;&lt;li&gt;Click &lt;i&gt;Done&lt;/i&gt;. Or, move the dialog aside.&lt;/li&gt;&lt;li&gt;Run the following script.&lt;/li&gt;&lt;/ol&gt;&lt;pre&gt;myFinds = app.search() &lt;br /&gt;for (j = myFinds.length - 1; j &gt;= 0; j--) { &lt;br /&gt;&amp;nbsp;&amp;nbsp;app.select(myFinds[j]); &lt;br /&gt;&amp;nbsp;&amp;nbsp;app.paste(); &lt;br /&gt;}&lt;/pre&gt;This version assumes that you used the dialog to set-up the change-side formatting. But if that is implicit in the contents of the clipboard, then you should run this minor variation of the script:&lt;pre&gt;app.changePreferences = null;&lt;br /&gt;myFinds = app.search() &lt;br /&gt;for (j = myFinds.length - 1; j &gt;= 0; j--) { &lt;br /&gt;&amp;nbsp;&amp;nbsp;app.select(myFinds[j]); &lt;br /&gt;&amp;nbsp;&amp;nbsp;app.paste(); &lt;br /&gt;}&lt;/pre&gt;This version wipes out any settings on the change side before running the search.&lt;br /&gt;&lt;br /&gt;So, when might you use this script? Two possibilities leap to mind:&lt;ol&gt;&lt;li&gt;If you want to replace with an alternate glyph that can't be inserted into the &lt;i&gt;Change&lt;/i&gt; side in the &lt;i&gt;Find/Change&lt;/i&gt; dialog.&lt;/li&gt;&lt;li&gt;If you want to replace text with an inline graphic or frame of some kind.&lt;/li&gt;&lt;/ol&gt;Note that this second possibility allows you to replace bullets in bulleted paragraphs with an icon of some kind provided that the bullets were actually typed and not created by the &lt;i&gt;Bulleted List&lt;/i&gt; feature of paragraph styles. Although, don't overlook the fact that you can convert such bullets to text using the contextual menu when appropriate text is selected.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113465383945707310?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113465383945707310/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113465383945707310' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113465383945707310'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113465383945707310'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/replace-with-clipboard.html' title='Replace with Clipboard'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113363779978317006</id><published>2005-12-03T10:55:00.000-08:00</published><updated>2005-12-03T11:23:19.846-08:00</updated><title type='text'>How to begin scripting? (Part 1)</title><content type='html'>On December 1st, a visitor to the Adobe InDesign Scripting User to User forum asked:&lt;br /&gt;&lt;br /&gt;I need [a] script for overset text, but I [am a] novice in scripting. Maybe I can download this?&lt;br /&gt;&lt;br /&gt;I interpreted this as a request for help on the issue of how to set about creating a script to solve this problem. In the absence of information about which version of InDesign and which language, I chose to assume that the poster was using the latest version and that a JavaScript solution would meet the need, particularly as that would be cross-platform and again, I had no information about platform. The following is an edited version of my response:&lt;br /&gt;&lt;br /&gt;The first step towards scripting is to choose something that you need to do and focus on what it takes to achieve that goal -- for example, if you were to do it manually, what would you do? That helps you decide which parts of the object model you need to be aware of to achieve your goal. &lt;br /&gt;&lt;br /&gt;The reason for this is that the object model is a huge beast with everything you ever wanted for scripting anything at all (well, nearly anything) in InDesign. The good news is, you do not need to master every aspect to achieve a focused goal. Indeed, you are likely to need only a small percentage of it. If you think about it, the same is true when using the UI. Newcomers in particular just assume that the application is looking after them in countless ways when they take their first steps to creating a document. &lt;br /&gt;&lt;br /&gt;So, in this case, we need to determine if a story or a table cell is overset. In the UI, we get a red + for the story and a red dot for the text cell. &lt;br /&gt;&lt;br /&gt;Well, that means that a script needs to look at every story and at every table cell to see if they are overset. &lt;br /&gt;&lt;br /&gt;Let's go to the JavaScript section of the InDesign CS2 Reference Guide and look at the properties of a Story. The first thing I'd want to know is: "Does the Story object have a property called 'overset'?"&lt;br /&gt;&lt;br /&gt;[Aside: even here, I'm kind of leaping in without introducing at all the concept of a "property" -- does it matter at this stage? Perhaps not, but somewhere I should write about that.]&lt;br /&gt;&lt;br /&gt;Using the Story bookmark in the reference guide, I get myself to page 1069 and there's the list of properties. I scroll down (what a long list!) until I get to the "Os" on 1073/74 and while there is no property called &lt;i&gt;overset&lt;/i&gt;, there is one called &lt;i&gt;overflows&lt;/i&gt; that looks promising. &lt;br /&gt;&lt;br /&gt;Yes, it's just what we need. So, if we can find all instances in our document of stories that have the overflow property set to true, then we've found the overset stories. For the moment, let's set aside the issue of how do we inform the user that an overset story has been found and focus on the code we need to find an instance. &lt;br /&gt;&lt;br /&gt;So, stories live in documents. Let's take a look at the document object and see what it has in the way of properties. Click the bookmark and we're on page 679. Scroll down to 683 and yes indeedy a document has a property called stories. OK, so how do we refer to the document? For that we need to know what the parent of a document is (or could be). &lt;br /&gt;&lt;br /&gt;Unhelpfully, on page 682, the definition of the parent property reads: "The parent of the object." Well -- duh! So we'll have to use our intuition (or preknowledge). There's another object call Application. It's top of the tree (so to speak) and most high-level objects like documents have it as a parent. Another bookmark and another click and we're on page 567. &lt;br /&gt;&lt;br /&gt;Before we storm off to find the documents property, let's spend a moment contemplating the very first property: activeDocument. Hey, that sounds useful. I think we're ready to start writing a script.&lt;br /&gt;&lt;br /&gt;If we did any reading of the documentation, we know that the way to refer to the application in InDesign is by using the built-in object named app. So, let's make a reference to the active document: &lt;pre&gt;myDoc = app.activeDocument;&lt;/pre&gt;Hey, but what if you try to run the script with no documents open? Which is the active document then? Well, there isn't one and you'd expect an error if you ran this little one-line script. &lt;br /&gt;&lt;br /&gt;You'd be disappointed. JavaScript doesn't work that way. It creates a reference to a null document in this case. Only if you try to use myDoc will you get an error. Often, this doesn't make a lot of difference because usually you create a reference like this and then immediately use it. But not always, so it is vital to understand this aspect of JavaScript because otherwise you'll be looking for bugs in all the wrong places. &lt;br /&gt;&lt;br /&gt;There's two ways to deal with this particular issue: 1. check to see if there are any open documents before setting myDoc, or 2. trap the error when you do use myDoc. Choice 1 is probably the better way to go and that's what I'll use, but let me at least show you have to do it the other way because there are times when this comes in handy: &lt;pre&gt;myDoc = app.activeDocument; &lt;br /&gt;try { &lt;br /&gt;  myName = myDoc.name; &lt;br /&gt;} catch (e) { &lt;br /&gt;  alert("Got an error: " + e); &lt;br /&gt;exit(); &lt;br /&gt;} &lt;br /&gt;alert("The active document is named: " + myName)&lt;/pre&gt;OK, that's a complete script. What to do with it? If you're using CS2, copy it to the clipboard, open ExtendScript Toolkit (ESTK for short) which you'll find in your utilities folder in a folder named Adobe Utilities (at least, that's where it is on a Mac). Make a New script from the File menu and paste this script into it. &lt;br /&gt;&lt;br /&gt;Now we could save the script if we wanted to but it's hardly worth it at this stage, let's just run it from ESTK. First, make sure that the target in the drop-down list at top left is set to InDesign CS2 and if so, click the Run button above the script (it's the left-most of the six buttons up there). &lt;br /&gt;&lt;br /&gt;Depending on whether or not you have a document open, you'll get one or other of the two alerts.&lt;br /&gt;&lt;br /&gt;So what's the better way to do it? Well, normally for this particular issue (is there an active document), I use the documents property of the application, like this:&lt;pre&gt;if (app.documents.length == 0) { &lt;br /&gt;  alert("There are no open documents."); &lt;br /&gt;  exit(); &lt;br /&gt;} &lt;br /&gt;myDoc = app.activeDocument;&lt;/pre&gt;Now we know that myDoc is pointing at a real document. &lt;br /&gt;&lt;br /&gt;There's an important point buried in this that might not be obvious to the newcomer. As the application's documents property is properly described on page 568, it refers to all open documents, not all the documents on your computer. So, checking the length of that property tells you how many are indeed open. As long as the answer isn't zero, we're in business. &lt;br /&gt;&lt;br /&gt;So, now we want to find the overflows property of all the stories of myDoc. There's a way of doing this that sadly didn't make it into the reference guide and that is to use the everyItem() method. You use it like this:&lt;pre&gt;myOsetList = myDoc.stories.everyItem().overflows;&lt;/pre&gt;This gives you a list of the overflow state of all the stories in your document. So all we have to do is check them all to see if any is true, and if so find a way of alerting the user.&lt;br /&gt;&lt;br /&gt;For this we need a for-loop. I write them in a slightly odd way to avoid the use of the &amp;lt; because it is a bit of a pain to have to substitute &amp;amp;lt; every time I need it. Hence:&lt;pre&gt;for (j = 0; myOsetList.length &gt; j; j++) { &lt;br /&gt;  if (myOsetList) { &lt;br /&gt;  // j-th story is overset; tell the user and exit &lt;br /&gt;  selectIt(myDoc.stories.textFrames[-1]); &lt;br /&gt;  exit(); &lt;br /&gt;} &lt;br /&gt;alert("No stories are overset");&lt;/pre&gt;If you've been following along adding these lines to the script and you try to run it now, it'll work if there are no overset stories, but if there is one, it'll have a hissy fit when it gets to the "selectIt" line. That's a call to a function that we haven't included yet. &lt;br /&gt;&lt;br /&gt;The idea here is that having located an overset story, what we want to do is make it visible in the UI to the user. Well, you can't select a story because it is only a logical entity. It manifests itself in the UI as text in text frames. These can be selected but not the story itself. &lt;br /&gt;&lt;br /&gt;Selecting the text of the story might be helpful, but more likely selecting the last text frame is what you want. That's why I passed: &lt;i&gt;myDoc.stories[j].textFrames[-1]&lt;/i&gt; to the function we still have to write. That's a reference to the last text frame of the j-th story which at the point we make this call we know to be overset because its &lt;i&gt;overflows&lt;/i&gt; property is true. &lt;br /&gt;&lt;br /&gt;There are lots of ways you might go about trying to select that text frame to show it to the user, but InDesign makes this very easy -- even though it took me over two years to realize this.&lt;br /&gt;&lt;br /&gt;So let's write the selectIt function. It takes one parameter (or argument -- depends on your background which term you use; they mean the same). In our main script, we're passing a reference to a text frame, but the function can have much wider scope. It can work with anything that is selectable. &lt;br /&gt;&lt;br /&gt;We might want to use this same function to highlight some text and show it to the user or a group that has certain characteristics, or anything at all. &lt;br /&gt;&lt;br /&gt;So, our formal definition of the function uses a surrogate name for the argument: &lt;pre&gt;function selectIt(theObj) { &lt;br /&gt;  // Selects object, turns to page and zooms in on it &lt;br /&gt;  app.select(theObj,SelectionOptions.replaceWith); &lt;br /&gt;  app.activeWindow.zoom = ZoomOptions.fitPage; &lt;br /&gt;  app.activeWindow.zoomPercentage = 200 &lt;br /&gt;}&lt;/pre&gt;As written, this assumes that the parameter passed "theObj" is indeed selectable. I suppose it might be worth checking for that and issuing an error message. But I've been happy enough in my use of this function to not bother. &lt;br /&gt;&lt;br /&gt;So, the first statement selects the referenced object, replacing whatever selection there might previously have been -- this is actually the default behavior of the app.select() method describe on page 676 of the guide, but it does no harm to explicitly call for this behavior. &lt;br /&gt;&lt;br /&gt;Now, the script takes advantage of something I didn't cotton on to for a long time. Once you've made a selection, whichever page it is on becomes the activePage of the activeWindow [even if it is not visible! this is like using the vertical scroll bar to view some other part of a document -- you might be looking at page 168, but if you have a selection on page 3, that's still the active page], so the script doesn't have to worry about where it is because InDesign already knows and will do all the hard work for me -- when I think about the stumbling attempts I made to do this myself, looking for the parent to determine the active page, I could kick myself. &lt;br /&gt;&lt;br /&gt;All I have to do is zoom the activeWindow and InDesign turns to the page all by itself. So why the double zoom? &lt;br /&gt;&lt;br /&gt;Because if the selected object is not visible in the window, the zoom to 200% will zoom in on the current center rather than the selected object. &lt;br /&gt;&lt;br /&gt;There you have it. Piece this lot together and you have a script that will detect and select the last text frame of the first overset story it finds. &lt;br /&gt;&lt;br /&gt;I'll leave it as an exercise to handle the table cells. &lt;br /&gt;&lt;br /&gt;By the way, I don't think this script, as is, will work with InDesign CS. I believe for it, you have to check the overflows property of the last text frame of each story because of a bug that is fixed in CS2.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113363779978317006?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113363779978317006/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113363779978317006' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113363779978317006'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113363779978317006'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/12/how-to-begin-scripting-part-1.html' title='How to begin scripting? (Part 1)'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113251936883256530</id><published>2005-11-20T12:32:00.000-08:00</published><updated>2005-11-20T12:42:48.833-08:00</updated><title type='text'>En space after Period</title><content type='html'>I've been toying with writing this script all day, and I finally got fed up with doing this manually and broke down. I'm working on a document where certain paragraphs require an en space after the first period, but the Word documents from the client have a regular space there. Here's the script:&lt;pre&gt;//DESCRIPTION: Utility to insert en space after first period in each para of selection&lt;br /&gt;&lt;br /&gt;Object.prototype.isPureText = function() {&lt;br /&gt;&amp;nbsp;&amp;nbsp;switch(this.constructor.name){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "InsertionPoint":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Character":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Word":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "TextStyleRange":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Line":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Paragraph":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "TextColumn":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case "Text":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;default :&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;if ((app.documents.length != 0) &amp;&amp; (app.selection.length != 0)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;var mySel = app.selection[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;if&amp;nbsp;&amp;nbsp; (mySel.isPureText) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myParas = mySel.paragraphs;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;for (var j = myParas.length - 1; j &gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var myInd = myParas[j].contents.indexOf(". ");&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if ( myInd != -1) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myParas[j].characters[myInd + 1].contents = SpecialCharacters.enSpace;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit();&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// +++++++ Functions Start Here +++++++++++++++++++++++&lt;br /&gt;&lt;br /&gt;function errorExit(message) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (arguments.length &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if (app.version != 3) { beep() } // CS2 includes beep() function.&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;alert(message);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;exit(); // CS exits with a beep; CS2 exits silently.&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;There are probably more elegant ways of doing this; perhaps even more efficient; but I'm operating on selections and they're never likely to be more than a few paragraphs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113251936883256530?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113251936883256530/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113251936883256530' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113251936883256530'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113251936883256530'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/11/en-space-after-period.html' title='En space after Period'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113249274146271551</id><published>2005-11-20T05:10:00.000-08:00</published><updated>2005-11-20T05:20:12.610-08:00</updated><title type='text'>Open with No Warnings</title><content type='html'>In some workflows, the persistent "missing fonts" or "missing links" messages on opening an InDesign document are more annoying than helpful. So it occurred to me to write a script to address the issue. Obviously, to be useful, the script must be called in place of whatever technique is being used to open documents right now. That rules out double-clicking on the document's icon or using File/Open because the script can't insinuate itself into those processes. [Note to self: check out Rogue Sheep's plug-in; perhaps it can help in those cases.]&lt;br /&gt;&lt;br /&gt;When this request came up on the Adobe User-to-User forum this morning, I banged out this script. I've written it to work with both CS and CS2:&lt;pre&gt;//DESCRIPTION: Open Doc with No Warnings&lt;br /&gt;&lt;br /&gt;if (app.version == 3) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.userInteractionLevel = UserInteractionLevels.neverInteract;&lt;br /&gt;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.neverInteract;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;var myFile = File.openDialog("Choose InDesign document to open");&lt;br /&gt;var myErr = "";&lt;br /&gt;if (myFile != null) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;app.open(myFile);&lt;br /&gt;&amp;nbsp;&amp;nbsp;} catch (e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myErr = e;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;if (app.version == 3) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.userInteractionLevel = UserInteractionLevels.interactWithAll;&lt;br /&gt;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.scriptPreferences.userInteractionLevel = UserInteractionLevels.interactWithAll;&lt;br /&gt;}&lt;br /&gt;if (myErr != "") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;alert(myErr);&lt;br /&gt;}&lt;/pre&gt;This script has one important lesson in it: if you're messing around with user preferences, make sure there's no way out of the script that leaves them in their "messed-around" state. That's why the error handling is pushed to the end of the script so that the interaction preference is reset before exiting.&lt;br /&gt;&lt;br /&gt;The script also raises an issue (other than the idea to explore the Rogue Sheep plug-in): how does one filter an openDialog call so only InDesign documents are visible in the resulting dialog? When I have more time, I'll return to that issue.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113249274146271551?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113249274146271551/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113249274146271551' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113249274146271551'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113249274146271551'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/11/open-with-no-warnings.html' title='Open with No Warnings'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113210164035728539</id><published>2005-11-15T16:28:00.000-08:00</published><updated>2005-11-15T16:40:40.373-08:00</updated><title type='text'>Select and Display</title><content type='html'>One of the challenges of complex scripts is showing the user object in a document when things go wrong. For example, say you have a completely overset cell in a table or a text frame that meets some criteria of interest. Well, here's a little function that will do the job:&lt;pre&gt;function selectIt(theObj) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Selects object, turns to page and zooms in on it&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.select(theObj,SelectionOptions.replaceWith);&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.activeWindow.zoom = ZoomOptions.fitPage;&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.activeWindow.zoomPercentage = 200&lt;br /&gt;}&lt;/pre&gt;Why does this work? Well, making the selection makes the page that the selection is on the active page. That causes the zoom command to operate on that page, no matter which page might previously have been active. By zooming to fitPage, we make sure that the selected object (or text) is visible in the window so that zooming in to 200 does so with the selection centered.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113210164035728539?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113210164035728539/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113210164035728539' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113210164035728539'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113210164035728539'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/11/select-and-display.html' title='Select and Display'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113113854046397311</id><published>2005-11-04T12:58:00.000-08:00</published><updated>2005-11-04T13:09:00.480-08:00</updated><title type='text'>Fixing a Numbered List</title><content type='html'>I make most of my numbered lists myself using nested styles and hanging indents (and, where appropriate, right-aligned tabs to allow for 10).&lt;br /&gt;&lt;br /&gt;But one particular list that crops up in the job I'm working on at the moment has as its numbers an inline group. Within each group is an image (of a shadowed circle created in Photoshop) and two text frames, each holding the number, one in white and one in black-- they're slightly offset from each other so the black number forms a shadow of the white one.&lt;br /&gt;&lt;br /&gt;During a correction cycle, my client changed one of the lists, deleting one of the numbers. The thought of using find/replace to fix the list up crossed my mind, but it really would have been tedious. So, instead, I banged out this quick and dirty script. Fortunately, each of these lists is segregated into a separate story, so I didn't have to worry about restarting the list for any reason. Here's the script:&lt;pre&gt;//DESCRIPTION: Q&amp;D script to fix numbering in BCS list&lt;br /&gt;&lt;br /&gt;Object.prototype.isInArray = function(myArray){&lt;br /&gt;&amp;nbsp;for (var i=0; myArray.length &gt; i; i++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if(myArray[i] == this){&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;return false;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;var myStory = app.selection[0].parentStory;&lt;br /&gt;var myParaStyles = myStory.paragraphs.everyItem().appliedParagraphStyle;&lt;br /&gt;var myListStyleNames = ["BasicSkillNL","BasicSkillNLkeep"]&lt;br /&gt;var myCounter = 1;&lt;br /&gt;var myPlim = myParaStyles.length;&lt;br /&gt;for (var j = 0; myPlim &gt; j; j++) {&lt;br /&gt;&amp;nbsp;if (myParaStyles[j].name.isInArray(myListStyleNames)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;fixNumbers(myStory, j, myCounter);&lt;br /&gt;&amp;nbsp;&amp;nbsp;myCounter++&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;exit();&lt;br /&gt;&lt;br /&gt;// +++++++ Functions Start Here +++++++++++++++++++++++&lt;br /&gt;&lt;br /&gt;function fixNumbers(story,paraindex,counter) {&lt;br /&gt;&amp;nbsp;var myGroup = story.paragraphs[paraindex].groups[0];&lt;br /&gt;&amp;nbsp;var myTFs = myGroup.textFrames;&lt;br /&gt;&amp;nbsp;var myLim = myTFs.length&lt;br /&gt;&amp;nbsp;for (var k = 0; myLim &gt; k; k++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;myTFs[k].parentStory.contents = String(counter);&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;It simply cycles through all the paragraphs of the story looking for list members (there are two eligible styles) using &lt;i&gt;myCounter&lt;/i&gt; to hold the current state of the list counter. Worked like a charm and probably took no longer to write than doing the job by hand would have required.&lt;br /&gt;&lt;br /&gt;The &lt;i&gt;exit()&lt;/i&gt; isn't strictly necessary, but I include it for debugging purposes. I can put a breakpoint next to it so that when I'm testing the script (in its final form and at intermediate stages) I can use ESTK's data browser to view the states of the variables the script sets up.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113113854046397311?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113113854046397311/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113113854046397311' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113113854046397311'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113113854046397311'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/11/fixing-numbered-list.html' title='Fixing a Numbered List'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113094475897458662</id><published>2005-11-02T07:09:00.000-08:00</published><updated>2005-11-02T07:19:19.043-08:00</updated><title type='text'>Flagging Images</title><content type='html'>In one of my workflows, I've found it desirable to flag images in Photoshop for needed future actions. For example, some of the images will be resized in InDesign and so I don't want to sharpen them until  the final size is established in InDesign (in some cases, I'm resizing to as small as 20% of the original, so waiting until I know the size before sharpening is vital). Other images are provided in low resolution format so the composition can go ahead and the higher resolution image can be inserted later.&lt;br /&gt;&lt;br /&gt;I probably should be using metadata for what I'm doing, but instead I create a text layer in Photoshop and type "LOW RES" or "Not Sharpened" into it, as appropriate -- I use actions for these. In the low resolution case, the layer is visible so that the reviewers can instantly see that the image is known to still need work. For the "Not Sharpened" images, the layer is invisible.&lt;br /&gt;&lt;br /&gt;I've been worrying about how to make sure I attend to all these. Well, guess what, among the properties of an image is graphicLayerOptions, which in turn has a collection property graphicLayers. And each graphicLayer has a name property. So:&lt;pre&gt;myImage.graphicLayerOptions.graphicLayers.everyItem().name;&lt;/pre&gt;returns the names of all the layers in the image myImage.&lt;br /&gt;&lt;br /&gt;So, it is just a matter of technique to write a script that reviews all the linked images to produce a report of all the images that need attention.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113094475897458662?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113094475897458662/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113094475897458662' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113094475897458662'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113094475897458662'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/11/flagging-images.html' title='Flagging Images'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113032859309150764</id><published>2005-10-26T05:05:00.000-07:00</published><updated>2005-10-26T05:09:53.096-07:00</updated><title type='text'>Page Numbers are Squirrelly</title><content type='html'>The name of a page is a string and might include the section prefix, so subtracting 1 from it might fail.&lt;br /&gt;&lt;br /&gt;It depends, I think, on three things:&lt;ol&gt;&lt;li&gt;The state of the page numbering option in general preferences.&lt;/li&gt;&lt;li&gt;The presence of a section prefix in the Section and Numbering Options panel.&lt;/li&gt;&lt;li&gt;The state of the Include Prefix in Page Numbers option in the same panel.&lt;/li&gt;&lt;/ol&gt;Some scripts will clarify. This one sets the page numbering option and then displays an alert that cofirms what you did:&lt;pre&gt;app.generalPreferences.pageNumbering = [PageNumberingOptions.section,&lt;br /&gt;&amp;nbsp;&amp;nbsp;PageNumberingOptions.absolute][0];&lt;br /&gt;alert(decodePageNumbering(app.generalPreferences.pageNumbering,true));&lt;br /&gt;&lt;br /&gt;function decodePageNumbering(theCode,verbose) {&lt;br /&gt;&amp;nbsp;var theCodes = [[1096971116, ["absolute", "absolute numbering"]],&lt;br /&gt;&amp;nbsp;&amp;nbsp;nbsp;[1935897710, ["section", "section numbering"]]];&lt;br /&gt;&amp;nbsp;var verbosePtr = 0;&lt;br /&gt;&amp;nbsp;if (verbose) { verbosePtr = 1 }&lt;br /&gt;&amp;nbsp;switch (theCode) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;case PageNumberingOptions.absolute :&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return theCodes[0][1][verbosePtr];&lt;br /&gt;&amp;nbsp;&amp;nbsp;case PageNumberingOptions.section :&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return theCodes[1][1][verbosePtr];&lt;br /&gt;&amp;nbsp;&amp;nbsp;default :&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw (String(theCode) + "not recognized")&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;You can try both settings by changing the [0] to [1] at the end of the first statement (on the second line).&lt;br /&gt;&lt;br /&gt;OK, so now let's try this script (with a new, single-page document open):&lt;pre&gt;app.generalPreferences.pageNumbering = [PageNumberingOptions.section, &lt;br /&gt;&amp;nbsp;&amp;nbsp;PageNumberingOptions.absolute][0];&lt;br /&gt;&lt;br /&gt;var myPage = app.activeDocument.pages[0];&lt;br /&gt;myPage.appliedSection.sectionPrefix = "Test-";&lt;br /&gt;myPage.appliedSection.includeSectionPrefix = true;&lt;br /&gt;alert(myPage.name);&lt;/pre&gt;and you'll quickly find that toggling the state of the page numbering options (by changing that [0] to [1] again) has no effect at all on what the alert says. So much for my assertion 1 above. I think that what that option does is determine the form you must use when you supply a page number to certain methods, for example if you want to export a pdf or print a page range from your document. Otherwise, it has no effect on working with page numbers.&lt;br /&gt;&lt;br /&gt;Another test:&lt;pre&gt;var myPage = app.activeDocument.pages[0];&lt;br /&gt;myPage.appliedSection.sectionPrefix = "Test-";&lt;br /&gt;myPage.appliedSection.includeSectionPrefix = false;&lt;br /&gt;alert(myPage.name);&lt;/pre&gt;This gives you an alert that just says 1&lt;br /&gt;&lt;br /&gt;But is it a number?&lt;pre&gt;var myPage = app.activeDocument.pages[0];&lt;br /&gt;myPage.appliedSection.sectionPrefix = "Test-";&lt;br /&gt;myPage.appliedSection.includeSectionPrefix = false;&lt;br /&gt;alert(myPage.name.constructor.name);&lt;/pre&gt;No it isn't; it's a string. But what if we try to treat it as a number?&lt;pre&gt;var myPage = app.activeDocument.pages[0];&lt;br /&gt;myPage.appliedSection.sectionPrefix = "Test-";&lt;br /&gt;myPage.appliedSection.includeSectionPrefix = false;&lt;br /&gt;alert(myPage.name - 1);&lt;/pre&gt;Well, what do you know, it works! This is one of those things about JavaScript that on the one hand allows you to be lazy; after all, most people have a prefix of "" in most of their single-section documents because that's the default when a new document is created, and so most scripts that just treat the page name as a number just work. And that leads you down the path of thinking that the page name is a number -- but it isn't. It's a string, that most of the time contains the string representation of a number, so most of the time a script that thinks it's a number actually does work.&lt;br /&gt;&lt;br /&gt;But it has a bug as big as your nose right there staring you in the face and you won't ever find it until you start working with multi-section documents or you take advantage of the section prefix in one of your documents.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113032859309150764?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113032859309150764/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113032859309150764' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113032859309150764'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113032859309150764'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/10/page-numbers-are-squirrelly.html' title='Page Numbers are Squirrelly'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113009258807659629</id><published>2005-10-23T11:29:00.000-07:00</published><updated>2005-10-23T11:36:28.076-07:00</updated><title type='text'>Vertical Justification Undo</title><content type='html'>Already, I need a second script that will walk through a column restoring the space before to that used by assigned paragraph styles. This is an easy fall-out from the previous script. Instead of the spreadText() function, I have an unSpreadText() function that looks like this:&lt;pre&gt;function unSpreadText(theText) {&lt;br /&gt;&amp;nbsp;for (var j = theText.paragraphs.length - 1; j &gt; 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;theText.paragraphs[j].spaceBefore = theText.paragraphs[j].appliedParagraphStyle.spaceBefore;&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;Well, that was easy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113009258807659629?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113009258807659629/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113009258807659629' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113009258807659629'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113009258807659629'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/10/vertical-justification-undo.html' title='Vertical Justification Undo'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-113009203253903471</id><published>2005-10-23T09:15:00.000-07:00</published><updated>2005-10-23T11:27:12.573-07:00</updated><title type='text'>Vertical Justification</title><content type='html'>You'd think this was easy -- just turn it on in text frame options. But I've run into a case where that doesn't work. My client wants two columns in a text frame vertically aligned with each other, but the second column contains an inline graphic which is overlaid by some text. The graphic extends beyond the bottom of the text frame -- well, it does if I switch on vertical justification via text frame options. So that doesn't do the job because what he really wants is for the text in the left column to align with the bottom of the graphic in the right column.&lt;br /&gt;&lt;br /&gt;So, we need a script.&lt;br /&gt;&lt;br /&gt;The concept of the script would be that the size of the text frame has been manually set. The bottom falls where it falls and the need to is fit the text so that it nestles to that bottom. But which text? If I do this with all the text in each column of the text frame, then it will have precisely the effect that switching on VJ has. So, the user must click in the text column of interest.&lt;br /&gt;&lt;br /&gt;So, task number one is to determine which column of text contains the insertion point. Usually, when I write scripts that depend on a text insertion point, I don't really care if the user has made a larger selection -- in that case, I just take the first insertion point of the selection, but in this case, I'm going to be more fussy. The script will insist on a single insertion point and will decline to proceed for any other kind of selection. &lt;br /&gt;&lt;br /&gt;So, to get started, let's deal with that and then call a function to tell us which column contains the insertion point (note: the errorExit() function is discussed &lt;a href="http://jsid.blogspot.com/2005/10/standard-methods-and-functions.html"&gt;here&lt;/a&gt;):&lt;pre&gt;//DESCRIPTION: Vertically "justify" indicated text column by adding space before.&lt;br /&gt;&lt;br /&gt;if ((app.documents.length != 0) &amp;&amp; (app.selection.length != 0)) {&lt;br /&gt;&amp;nbsp;if (app.selection[0].constructor.name != "InsertionPoint") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit("Indicate column by clicking an insertion point with the text tool.");&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;var myCol = getColNum(app.selection[0]);&lt;br /&gt;} else {&lt;br /&gt;&amp;nbsp;errorExit();&lt;br /&gt;}&lt;/pre&gt;Well, it didn't take long for me to have second thoughts. Just writing this amount of code caused me to take pause and ask: What if the insertion point is in a cell of a table or in text on a path?&lt;br /&gt;&lt;br /&gt;Interesting questions! Text on a path is clearly ineligible for this kind of processing so we must trap for that and issue an error message. Text in a cell could be eligible, but perhaps the user clicked in it by accident -- it is after all also in a text column -- the column that contains the table that contains the cell. So, for now, I'm also going to trap that possibility out of consideration (a decision aided by the fact that my immediate need doesn't involve tables).&lt;br /&gt;&lt;br /&gt;Now the body of the script looks like this:&lt;pre&gt;if ((app.documents.length != 0) &amp;&amp; (app.selection.length != 0)) {&lt;br /&gt;&amp;nbsp;var mySel = app.selection[0];&lt;br /&gt;&amp;nbsp;if (mySel.constructor.name != "InsertionPoint") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit("Indicate column by clicking an insertion point with the text tool.");&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;// What if insertion point is in a cell or text on a path?&lt;br /&gt;&amp;nbsp;if (mySel.parent.constructor.name == "Cell") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit("Script does not operate on text inside tables.");&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;if (mySel.parentTextFrames[0].constructor.name == "TextPath") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit("Script does not operate on text on a path.");&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;var myCol = getColNum(mySel);&lt;br /&gt;} else {&lt;br /&gt; errorExit();&lt;br /&gt;}&lt;/pre&gt;You'll see that I quickly tired of typing "app.selection[0]" so I created a variable to hold a reference to the selection. Because the parent of a text object (insertion point is a kind of text object) is either "Cell" or "Story" differentiating text on a path from text in a text frame requires a different technique from determining if the text is in a cell in a table.&lt;br /&gt;&lt;br /&gt;Now it's time to take a crack at the getColNum function. Here's an irony: I don't care which column it is. All I really need to do this job is a reference to the text in the column. So, let's change the name of the function and the call to it to:&lt;pre&gt;&amp;nbsp;var myText = getColTextRef(mySel);&lt;/pre&gt;Here's the function:&lt;pre&gt;function getColTextRef(myIP) {&lt;br /&gt;&amp;nbsp;// returns reference to text of text column that holds insertion point&lt;br /&gt;&amp;nbsp;var myTF = myIP.parentTextFrames[0];&lt;br /&gt;&amp;nbsp;var myIndex = myIP.index;&lt;br /&gt;&amp;nbsp;var colIndexes = myTF.textColumns.everyItem().index;&lt;br /&gt;&amp;nbsp;for ( j= colIndexes.length - 1; j&gt;= 0; j--) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myIndex &gt;= colIndexes[j]) { &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return myTF.textColumns[j].texts[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;errorExit("Something is ghastly wrong");  &lt;br /&gt;}&lt;/pre&gt;That final error ought to be impossible. The script will always find the column and exit accordingly. But because it is a conceptually possible logic path, I've put an error message in there to let me know if for some unforesee reason we do get there.&lt;br /&gt;&lt;br /&gt;Thinking about errors makes me wonder what will happen if the user tries to run this script on an empty (or nearly empty) text column. The logic of adding space before to all except the first paragraph isn't going to get very far if there's one or fewer paragraphs. We'll need to check for that right now:&lt;pre&gt;&amp;nbsp;var myText = getColTextRef(mySel);&lt;br /&gt;&amp;nbsp;if (myText.paragraphs.length &gt; 1) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;spreadText(myText);&lt;br /&gt;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit("Indicated column has fewer than two paragraphs.");&lt;br /&gt;&amp;nbsp;}&lt;/pre&gt;So now we need the spreadText() function. All the while I've been writing about this script, I've been mulling over the best way to tackle the job of spreading vertically. The process has two parts:&lt;ol&gt;&lt;li&gt;Determine how much space needs to be consumed&lt;/li&gt;&lt;li&gt;Apply to each paragraph in the text, except the first, its portion of that space as extra space before&lt;/li&gt;&lt;/ol&gt;Probably the easiest way to do this is to make use of the vertical justification feature of InDesign, comparing the last baseline before with the last baseline after. If we go this route, we have to make sure that the vertical justification ends up the way it started. Here's how the first part of spreadText() looks:&lt;pre&gt;function spreadText(theText) {&lt;br /&gt;&amp;nbsp;// Calculate spare space by comparing last baseline with VJ on and off&lt;br /&gt;&amp;nbsp;// VJ is not a property of text but its container. So:&lt;br /&gt;&amp;nbsp;var origVJ = theText.parentTextFrames[0].textFramePreferences.verticalJustification;&lt;br /&gt;&amp;nbsp;theText.parentTextFrames[0].textFramePreferences.verticalJustification = VerticalJustification.topAlign;&lt;br /&gt;&amp;nbsp;theText.recompose();&lt;br /&gt;&amp;nbsp;var firstBase = theText.lines[-1].baseline;&lt;br /&gt;&amp;nbsp;theText.parentTextFrames[0].textFramePreferences.verticalJustification = VerticalJustification.justifyAlign;&lt;br /&gt;&amp;nbsp;theText.recompose();&lt;br /&gt;&amp;nbsp;var targetBase = theText.lines[-1].baseline;&lt;br /&gt;&amp;nbsp;theText.parentTextFrames[0].textFramePreferences.verticalJustification = origVJ;&lt;br /&gt;}&lt;/pre&gt;Although this function, so far, does nothing more than collect information, we can use ESTK to test that it works. And indeed a quick test seems to confirm that it does work. I worry that the process of changing the vertical justification to collect information won't always work. For example, what if there is a text wrap interfering with the shape of the text frame? In that case, the attempt to set justifyAlign will probably fail with an error.&lt;br /&gt;&lt;br /&gt;That's serious enough to warrant trapping the error because otherwise we could leave the text frame with the wrong vertical alignment. So&lt;pre&gt;&amp;nbsp;try {&lt;br /&gt;&amp;nbsp;&amp;nbsp;theText.parentTextFrames[0].textFramePreferences.verticalJustification = VerticalJustification.justifyAlign;&lt;br /&gt;&amp;nbsp;} catch (e) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// restore VJ&lt;br /&gt;&amp;nbsp;&amp;nbsp;theText.parentTextFrames[0].textFramePreferences.verticalJustification = origVJ;&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit("Frame has text wrap and can't be processed.");&lt;br /&gt;&amp;nbsp;}&lt;/pre&gt;OK, I'm being a wimp and rolling over in the face of text wrap, but dealing with that is a very big deal and I certainly don't have the time right now.&lt;br /&gt;&lt;br /&gt;Uh oh! A quick test reveals that attempting to set justifyAlign when it's ineligible doesn't cause a run-time error. The command is just ignored. That sounds like a bug to me. However, it does mean that the script will do nothing in that case. Perhaps it's worth adding a test to make sure that targetBase and firstBase are different from each other (indeed, that the former is larger than the latter) otherwise the user might not notice that nothing happened.&lt;br /&gt;&lt;br /&gt;And that's basically it. From here on, everything is simple math and a loop:&lt;pre&gt;&amp;nbsp;if (firstBase &gt;= targetBase) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit("Text Column apparently already spread—perhaps a text wrap is interfering.");&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;// Calculate extra space needed per paragraph&lt;br /&gt;&amp;nbsp;var myExtraSpace = (targetBase - firstBase)/(theText.paragraphs.length - 1);&lt;br /&gt;&amp;nbsp;for (var j = theText.paragraphs.length - 1; j &gt; 0; j--) {&lt;br /&gt;&amp;nbsp;theText.paragraphs[j].spaceBefore = theText.paragraphs[j].spaceBefore + myExtraSpace;&lt;br /&gt;&amp;nbsp;}&lt;/pre&gt;Notice that the first paragraph is ignored. That's because space before the first paragraph in a text column is ignored by the composer.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-113009203253903471?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/113009203253903471/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=113009203253903471' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113009203253903471'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/113009203253903471'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/10/vertical-justification.html' title='Vertical Justification'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-112986285902303577</id><published>2005-10-20T19:28:00.000-07:00</published><updated>2005-10-20T19:47:39.033-07:00</updated><title type='text'>Text Styles Reporter</title><content type='html'>I've just posted my first "Donationware". It's a script that produces a sectonalized report of your text styles. First the paragraph styles and then the character styles. You get to choose which aspects of your styles are reported, and you can choose to have them listed alphabetically or in based-On order. The script also lets you choose a document preset for the report it generates.&lt;br /&gt;&lt;br /&gt;The script works only with InDesign CS2. It comes in two parts, the script and a snippet, both of which must be stored together in the Scripts folder (or a subfolder thereof) in the Presets folder of your InDesign CS2 application folder. The dependency on a snippet and the differences between CS and CS2 styles means that this script works only with CS2.&lt;br /&gt;&lt;br /&gt;If you feel moved to donate, open the script in a text editor or ESTK for instructions. Note that the script will not run to completion if you run it from ESTK. It needs to run from the palette so that the snippet can be located.&lt;br /&gt;&lt;br /&gt;See the Featured Downloads at left to download a copy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-112986285902303577?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/112986285902303577/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=112986285902303577' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/112986285902303577'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/112986285902303577'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/10/text-styles-reporter.html' title='Text Styles Reporter'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-112938126161789554</id><published>2005-10-15T05:58:00.000-07:00</published><updated>2005-10-15T06:53:37.376-07:00</updated><title type='text'>with and Preferences</title><content type='html'>All the time I've being using JS with ID, I've avoided the use of the with statement because various people advised me that it was dodgy. I've even seen it in print. In the JavaScript Pocket Reference published by O'Reilly, David Flanagan writes:&lt;br /&gt;&lt;br /&gt;The &lt;i&gt;with&lt;/i&gt; statement has some complex and non-intuitive side effects; its use is strongly discouraged.&lt;br /&gt;&lt;br /&gt;So I've not been using it. But this morning it occurred to me that I could surely save myself a lot of typing in this function:&lt;pre&gt;function setXLimportPrefs() {&lt;br /&gt;&amp;nbsp;with (app.excelImportPreferences) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;alignmentStyle = [AlignmentStyleOptions.spreadsheet, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AlignmentStyleOptions.leftAlign, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AlignmentStyleOptions.rightAlign, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AlignmentStyleOptions.centerAlign][0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;decimalPlaces = 3;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Ignoring errorCode for now; I don't know what it's for&lt;br /&gt;&amp;nbsp;&amp;nbsp;preserveGraphics = false; // Not sure what this does either&lt;br /&gt;&amp;nbsp;&amp;nbsp;rangeName = ""; // Hopefully leaving this blank will cause ranges to be ignored&lt;br /&gt;&amp;nbsp;&amp;nbsp;sheetIndex = 0;&lt;br /&gt;&amp;nbsp;&amp;nbsp;sheetName = "";&lt;br /&gt;&amp;nbsp;&amp;nbsp;showHiddenCells = true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;tableFormatting = [TableFormattingOptions.excelFormattedTable, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TableFormattingOptions.excelUnformattedTable, &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TableFormattingOptions.excelUnformattedTabbedText][0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;useTypographersQuotes = true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;viewName = "";&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;And it seems to work very well. Perhaps in simple cases like this it is fine to use &lt;i&gt;with&lt;/i&gt;. I can see how it might get more hairy if I were to start nesting them.&lt;br /&gt;&lt;br /&gt;And while I'm sitting here pleased with myself, notice how I set the values of the two enumerations. Doing it this way saves me from having to look them up next time I want to use a variant of these preferences in a script.&lt;br /&gt;&lt;br /&gt;-- Later that morning --&lt;br /&gt;&lt;br /&gt;That's interesting. Using the preferences as I have them here caused the import I did next to fail with "User canceled this action." Perhaps I'd better set the errorCode to 0 after all. &lt;br /&gt;&lt;br /&gt;Oh dear. That didn't help. I seem to have broken my ability to import Excel spreadsheets. And there I was just a few minutes ago feeling so pleased with myself. Let's see if I can still do it in the UI. ... &lt;br /&gt;&lt;br /&gt;Yes I can. &lt;br /&gt;&lt;br /&gt;Hmm. If I switch off the call to this function, it still works in my script, so setting this particular set of preferences causes the import to fail. Changing the preserveGraphics to true didn't help either (it seems to control the import of inline graphics -- I didn't know that Excel supported inline graphics). &lt;br /&gt;&lt;br /&gt;Aha! The errorCode to the rescue. It tells me "Invalid Sheet" -- I bet Excel starts counting from 1 ... Yep! That was it. &lt;br /&gt;&lt;br /&gt;I still don't understand why the errorCode is read/write, but I'm sure glad it is there.&lt;br /&gt;&lt;br /&gt;-- Later still -- &lt;br /&gt;&lt;br /&gt;It is such an easy mistake to make and I seem to trap myself every time I do it. If, in the course of investigating a problem, I disable a call to a function, it is vital to re-enable the call before making changes in the function because otherwise your changes will go unnoticed.&lt;br /&gt;&lt;br /&gt;Sad to say, this happened here. I still don't know what the ultimate issue is because the changes caused InDesign to start crashing rather frequently, so now I'm going through the process of restarting my computer and trying to get back to where I was so I can pin this down.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-112938126161789554?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/112938126161789554/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=112938126161789554' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/112938126161789554'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/112938126161789554'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/10/with-and-preferences.html' title='with and Preferences'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-112820154674484145</id><published>2005-10-01T14:14:00.000-07:00</published><updated>2005-10-01T14:20:26.520-07:00</updated><title type='text'>Regex Tester Revisited</title><content type='html'>It has come to me that for this script to be really useful, it needs to remember the previous version of your Regex find string and use it from run to run so you can refine the string until you get it the way you really want it. &lt;br /&gt;&lt;br /&gt;So, where to keep it between runs? I'm thinking perhaps as a label to the document you're working on or perhaps the object containing the text you're working on. &lt;br /&gt;&lt;br /&gt;Thinks: do stories support labels? Yes, but text in cells don't. On the other hand, text in cells have parent stories, so that's one possibility. While in CS2, cells support labels. &lt;br /&gt;&lt;br /&gt;So many choices! &lt;br /&gt;&lt;br /&gt;Perhaps to get started I should just use the document itself. After all, you're not likely to be working on more than one of these at any particular time, and when you've got it right you can call the script one last time and copy it out of the prompt to where you really need it (probably in some other script). &lt;br /&gt;&lt;br /&gt;OK, but let's not screw up the possibility that you are already using the document label, so I'll use a keyed label. I'll give it the key "PDSregex". &lt;br /&gt;&lt;br /&gt;Changing the script is pretty simple. Once it is determined that there is an active document, I can create a reference to it:&lt;pre&gt;if ((app.documents.length != 0) &amp;&amp; (app.selection.length != 0)) {&lt;br /&gt;&amp;nbsp;var myDoc = app.activeDocument;&lt;/pre&gt;And then, the part that issues the prompt needs a couple of extra lines:&lt;pre&gt;&amp;nbsp;&amp;nbsp;// Invite user to type regular expression&lt;br /&gt;&amp;nbsp;&amp;nbsp;var curExp = myDoc.extractLabel("PDSregex");&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.activate();&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myREtext = prompt("Type your regular expresion",curExp);&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myREtext == null) { errorExit() }&lt;br /&gt;&amp;nbsp;&amp;nbsp;myDoc.insertLabel("PDSregex",myREtext);&lt;/pre&gt;&lt;br /&gt;So, the first time you use the script with a particular document open, you'll get a blank field to start your Regex string, but after that, each time you run the script it will pick up the most recent version of your string and you can continue refining it until you get it right!&lt;br /&gt;&lt;br /&gt;I've updated the linked script to include this logic.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-112820154674484145?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/112820154674484145/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=112820154674484145' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/112820154674484145'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/112820154674484145'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/10/regex-tester-revisited.html' title='Regex Tester Revisited'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-112817966780095726</id><published>2005-10-01T07:35:00.000-07:00</published><updated>2005-10-01T08:14:27.806-07:00</updated><title type='text'>Regex Tester</title><content type='html'>It occurred to me that constructing a script that helps test Regex commands would be a good idea, so I came up initially with this script (which uses all three of the methods and functions in the previous blog entry):&lt;pre&gt;if ((app.documents.length != 0) &amp;&amp; (app.selection.length != 0)) {&lt;br /&gt;&amp;nbsp;var myRange = app.selection[0];&lt;br /&gt;&amp;nbsp;if (myRange.isText()) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// if selection is only an insertion point, process parent text range&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myRange.constructor.name == "InsertionPoint") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;myRange = getParentTextFlow(myRange);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Invite user to type regular expression&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.activate();&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myREtext = prompt("Type your regular expresion","");&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myREtext == null) { errorExit() }&lt;br /&gt;&amp;nbsp;&amp;nbsp;myRE = new RegExp(myREtext);&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myTest = myRE.exec(myRange.contents);&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.activate();&lt;br /&gt;&amp;nbsp;&amp;nbsp;alert(myRE + " finds:\n" + myTest[1]);&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;} else {&lt;br /&gt;&amp;nbsp;errorExit("Please select some text and try again");&lt;br /&gt;}&lt;/pre&gt;To use this script, select some text in an InDesign document (or click in a story or table cell) and run the script. It prompts you for a Regex string and displays the result in an alert. However, even as I sit here describing this version, I see a couple of problems. &lt;br /&gt;&lt;br /&gt;First, the script focuses on the first matched substring of text. But what if you're not interested in substrings? Well, my "solution" to that is a bit clunky: surround your whole search string in parentheses.&lt;br /&gt;&lt;br /&gt;Second, although the script looks as though it deals with non-text selections, it actually doesn't. The "else" statement is only triggered if there is either no document open or no selection.&lt;br /&gt;&lt;br /&gt;Let's try to fix both of these by (a) being a bit smarter with the results of the &lt;i&gt;exec()&lt;/i&gt; call and (b) by giving the script a better internal structure.&lt;pre&gt;var myErr = "Please select some text and try again";&lt;br /&gt;if ((app.documents.length != 0) &amp;&amp; (app.selection.length != 0)) {&lt;br /&gt;&amp;nbsp;var myRange = app.selection[0];&lt;br /&gt;&amp;nbsp;if (!myRange.isText()) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;errorExit(myErr);&lt;br /&gt;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;// if selection is only an insertion point, process parent text range&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myRange.constructor.name == "InsertionPoint") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;myRange = getParentTextFlow(myRange);&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;&lt;br /&gt;&amp;nbsp;&amp;nbsp;// Invite user to type regular expression&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.activate();&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myREtext = prompt("Type your regular expresion","");&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myREtext == null) { errorExit() }&lt;br /&gt;&amp;nbsp;&amp;nbsp;myRE = new RegExp(myREtext);&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myTest = myRE.exec(myRange.contents);&lt;br /&gt;&amp;nbsp;&amp;nbsp;var myReport = myRE + " finds:";&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (myTest == null) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;myReport = myReport + " no match";&lt;br /&gt;&amp;nbsp;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;myReport = myReport + "\n" + myTest[0];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;for (var j = 1; myTest.length &gt; j; j++) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;myReport = myReport + "\n$" + String(j) + " = " + myTest[j];&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;&amp;nbsp;app.activate();&lt;br /&gt;&amp;nbsp;&amp;nbsp;alert(myReport);&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;} else {&lt;br /&gt;&amp;nbsp;errorExit(myErr);&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;There, now that's what I call a useful script!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-112817966780095726?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/112817966780095726/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=112817966780095726' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/112817966780095726'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/112817966780095726'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/10/regex-tester.html' title='Regex Tester'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-15837111.post-112817731019096167</id><published>2005-10-01T07:18:00.000-07:00</published><updated>2005-10-01T07:35:10.196-07:00</updated><title type='text'>Standard Methods and Functions</title><content type='html'>I'm becoming concerned that my script postings here are become unwieldy because of the length of the standard methods and functions I use over and over. By way of an experiment, I'm going to post some of the more common ones here so I won't have to repeat them in posts about specific scripts:&lt;br /&gt;&lt;br /&gt;&lt;h3&gt;isText() Method&lt;/h3&gt;This method returns true if an object is any kind of text object and false otherwise. Constructing it this ways allows it to be called like this:&lt;pre&gt;if (myObj.isText()) { // myObj is text so continue&lt;/pre&gt;Here's the definition of the method. I always put my method definitions at the top of my scripts, immediately after the description.&lt;pre&gt;Object.prototype.isText = function() {&lt;br /&gt;&amp;nbsp;switch(this.constructor.name){&lt;br /&gt;&amp;nbsp;&amp;nbsp;case "InsertionPoint":&lt;br /&gt;&amp;nbsp;&amp;nbsp;case "Character":&lt;br /&gt;&amp;nbsp;&amp;nbsp;case "Word":&lt;br /&gt;&amp;nbsp;&amp;nbsp;case "TextStyleRange":&lt;br /&gt;&amp;nbsp;&amp;nbsp;case "Line":&lt;br /&gt;&amp;nbsp;&amp;nbsp;case "Paragraph":&lt;br /&gt;&amp;nbsp;&amp;nbsp;case "TextColumn":&lt;br /&gt;&amp;nbsp;&amp;nbsp;case "Text":&lt;br /&gt;&amp;nbsp;&amp;nbsp;case "TextFrame":&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return true;&lt;br /&gt;&amp;nbsp;&amp;nbsp;default :&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;return false;&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;/pre&gt;&lt;h3&gt;errorExit(myMsg) function&lt;/h3&gt;This function throws up an alert if an argument is present, otherwise it just exits. For CS2 or later, it issues a beep immediately before displaying the alert.&lt;pre&gt;function errorExit(message) {&lt;br /&gt;&amp;nbsp;if (arguments.length &gt; 0) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;if (app.version != 3) { beep() } // CS2 includes beep() function.&lt;br /&gt;&amp;nbsp;&amp;nbsp;alert(message);&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;&amp;nbsp;exit(); // CS exits with a beep; CS2 exits silently.&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;I always put my functions after the main body of the script, usually preceded by a comment line that indicates the start.&lt;br /&gt;&lt;h3&gt;getParentTextFlow(theTextRef) Function&lt;/h3&gt;This function returns the parent text flow of the referenced text item. Notice that the function does no error checking; if you pass it something that doesn't have a parentStory property you'll get an error. The script checks to see if the referenced text is in a cell in a table, and if so it returns a reference to the text of the cell rather than the parentStory which in this case is the parentStory of the table.&lt;pre&gt;function getParentTextFlow(theTextRef) {&lt;br /&gt;&amp;nbsp;// Returns reference to parent story or text of cell, as appropriate&lt;br /&gt;&amp;nbsp;if (theTextRef.parent.constructor.name == "Cell") {&lt;br /&gt;&amp;nbsp;&amp;nbsp;return theTextRef.parent.texts[0];&lt;br /&gt;&amp;nbsp;} else {&lt;br /&gt;&amp;nbsp;&amp;nbsp;return theTextRef.parentStory;&lt;br /&gt;&amp;nbsp;}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;That's it for now. Last updated 10/1/05.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/15837111-112817731019096167?l=jsid.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://jsid.blogspot.com/feeds/112817731019096167/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=15837111&amp;postID=112817731019096167' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/112817731019096167'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/15837111/posts/default/112817731019096167'/><link rel='alternate' type='text/html' href='http://jsid.blogspot.com/2005/10/standard-methods-and-functions.html' title='Standard Methods and Functions'/><author><name>Dave Saunders</name><uri>http://www.blogger.com/profile/12313675002072365782</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='27' height='32' src='http://www.pdsassoc.com/Adobe/djssmall.jpg'/></author><thr:total>1</thr:total></entry></feed>
