Sunday, October 23, 2005
Vertical Justification
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.
So, we need a script.
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.
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.
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 here):
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).
Now the body of the script looks like this:
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:
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:
That's serious enough to warrant trapping the error because otherwise we could leave the text frame with the wrong vertical alignment. So
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.
And that's basically it. From here on, everything is simple math and a loop:
So, we need a script.
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.
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.
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 here):
//DESCRIPTION: Vertically "justify" indicated text column by adding space before.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?
if ((app.documents.length != 0) && (app.selection.length != 0)) {
if (app.selection[0].constructor.name != "InsertionPoint") {
errorExit("Indicate column by clicking an insertion point with the text tool.");
}
var myCol = getColNum(app.selection[0]);
} else {
errorExit();
}
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).
Now the body of the script looks like this:
if ((app.documents.length != 0) && (app.selection.length != 0)) {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.
var mySel = app.selection[0];
if (mySel.constructor.name != "InsertionPoint") {
errorExit("Indicate column by clicking an insertion point with the text tool.");
}
// What if insertion point is in a cell or text on a path?
if (mySel.parent.constructor.name == "Cell") {
errorExit("Script does not operate on text inside tables.");
}
if (mySel.parentTextFrames[0].constructor.name == "TextPath") {
errorExit("Script does not operate on text on a path.");
}
var myCol = getColNum(mySel);
} else {
errorExit();
}
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:
var myText = getColTextRef(mySel);Here's the function:
function getColTextRef(myIP) {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.
// returns reference to text of text column that holds insertion point
var myTF = myIP.parentTextFrames[0];
var myIndex = myIP.index;
var colIndexes = myTF.textColumns.everyItem().index;
for ( j= colIndexes.length - 1; j>= 0; j--) {
if (myIndex >= colIndexes[j]) {
return myTF.textColumns[j].texts[0];
}
}
errorExit("Something is ghastly wrong");
}
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:
var myText = getColTextRef(mySel);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:
if (myText.paragraphs.length > 1) {
spreadText(myText);
} else {
errorExit("Indicated column has fewer than two paragraphs.");
}
- Determine how much space needs to be consumed
- Apply to each paragraph in the text, except the first, its portion of that space as extra space before
function spreadText(theText) {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.
// Calculate spare space by comparing last baseline with VJ on and off
// VJ is not a property of text but its container. So:
var origVJ = theText.parentTextFrames[0].textFramePreferences.verticalJustification;
theText.parentTextFrames[0].textFramePreferences.verticalJustification = VerticalJustification.topAlign;
theText.recompose();
var firstBase = theText.lines[-1].baseline;
theText.parentTextFrames[0].textFramePreferences.verticalJustification = VerticalJustification.justifyAlign;
theText.recompose();
var targetBase = theText.lines[-1].baseline;
theText.parentTextFrames[0].textFramePreferences.verticalJustification = origVJ;
}
That's serious enough to warrant trapping the error because otherwise we could leave the text frame with the wrong vertical alignment. So
try {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.
theText.parentTextFrames[0].textFramePreferences.verticalJustification = VerticalJustification.justifyAlign;
} catch (e) {
// restore VJ
theText.parentTextFrames[0].textFramePreferences.verticalJustification = origVJ;
errorExit("Frame has text wrap and can't be processed.");
}
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.
And that's basically it. From here on, everything is simple math and a loop:
if (firstBase >= targetBase) {Notice that the first paragraph is ignored. That's because space before the first paragraph in a text column is ignored by the composer.
errorExit("Text Column apparently already spread—perhaps a text wrap is interfering.");
}
// Calculate extra space needed per paragraph
var myExtraSpace = (targetBase - firstBase)/(theText.paragraphs.length - 1);
for (var j = theText.paragraphs.length - 1; j > 0; j--) {
theText.paragraphs[j].spaceBefore = theText.paragraphs[j].spaceBefore + myExtraSpace;
}
Comments:
<< Home
Hi Dave,
a few days ago I have made some experience with script to vertcal align text. I have developed a script for "constrictive" vertical justification which add space proportionaly only to paragraphs with space before/after.
Please take a look at http://www.hilfdirselbst.ch/gforum/gforum.cgi?post=191814#191814
Martin
a few days ago I have made some experience with script to vertcal align text. I have developed a script for "constrictive" vertical justification which add space proportionaly only to paragraphs with space before/after.
Please take a look at http://www.hilfdirselbst.ch/gforum/gforum.cgi?post=191814#191814
Martin
Interesting that you should have done that -- I was thinking along the same lines for a more general script.
Sorry it took me so long to find your comment -- I've been up to my eyes for the last week or so.
Sorry it took me so long to find your comment -- I've been up to my eyes for the last week or so.
Only from IP?
can i use TextFramePreference.VerticalJustification.JUSTIFY_ALIGN
if my selection is to textframe?
"
myTextFrame.TextFramePreference.VerticalJustification.JUSTIFY_ALIGN;
"
-dosn't work :(
can i use TextFramePreference.VerticalJustification.JUSTIFY_ALIGN
if my selection is to textframe?
"
myTextFrame.TextFramePreference.VerticalJustification.JUSTIFY_ALIGN;
"
-dosn't work :(
BoomKa,
Which language are you using? If JavaScript, then you are confusing the name of the object (TextFramePreference) with the name of the property (textFramePreferences). Use the latter.
Dave
Post a Comment
Which language are you using? If JavaScript, then you are confusing the name of the object (TextFramePreference) with the name of the property (textFramePreferences). Use the latter.
Dave
<< Home