Saturday, July 19, 2008
Align Left Edge
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: Removing Space Along the Left Edge of Text. 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):
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.
First, we need references to the paragraph and its first character:
var myPara = app.selection[0].paragraphs[0];
var myChar = myPara.characters[0];
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.
var firstCharWidth = getWidth(myChar);
But, as we know, the Align Left Edge feature of InDesign 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:
myPara.dropCapCharacters = 1;
myPara.dropCapLines = 2;
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:
0 means both off
1 means ALE on, SfD off
2 means SfD on, ALE off
3 means both on
myPara.dropcapDetail = 1;
Now we get the character width again, this time we’re getting the size of the dropcap at its unknown point size.
var largeCharWidth = getWidth(myChar);
And, having align left edge switched on, we record where the first insertion point is:
var largeAdjLeft = myChar.horizontalOffset;
Now we switch it off and get the revised position:
myPara.dropcapDetail = 0;
var largeBaseLeft = myChar.horizontalOffset;
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):
var leftSideBearing = (largeBaseLeft - largeAdjLeft)/largeCharWidth;
So we can work out how much we need to shift the original character left by multiplying this fraction by the original widith:
var neededShift = leftSideBearing * firstCharWidth;
We have all the information we need. So, let’s switch off the drop cap:
myPara.dropCapLines = 1;
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):
var normalizedKernAmount = (neededShift * 12/myChar.pointSize) + .5;
Now we insert the hair space:
myPara.insertionPoints[0].contents = SpecialCharacters.hairSpace;
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:
myPara.insertionPoints[1].kerningValue = normalizedKernAmount*-1000/12;
There you go: Align Left Edge for the first line of any paragraph.
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.
First, we need references to the paragraph and its first character:
var myPara = app.selection[0].paragraphs[0];
var myChar = myPara.characters[0];
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.
var firstCharWidth = getWidth(myChar);
But, as we know, the Align Left Edge feature of InDesign 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:
myPara.dropCapCharacters = 1;
myPara.dropCapLines = 2;
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:
0 means both off
1 means ALE on, SfD off
2 means SfD on, ALE off
3 means both on
myPara.dropcapDetail = 1;
Now we get the character width again, this time we’re getting the size of the dropcap at its unknown point size.
var largeCharWidth = getWidth(myChar);
And, having align left edge switched on, we record where the first insertion point is:
var largeAdjLeft = myChar.horizontalOffset;
Now we switch it off and get the revised position:
myPara.dropcapDetail = 0;
var largeBaseLeft = myChar.horizontalOffset;
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):
var leftSideBearing = (largeBaseLeft - largeAdjLeft)/largeCharWidth;
So we can work out how much we need to shift the original character left by multiplying this fraction by the original widith:
var neededShift = leftSideBearing * firstCharWidth;
We have all the information we need. So, let’s switch off the drop cap:
myPara.dropCapLines = 1;
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):
var normalizedKernAmount = (neededShift * 12/myChar.pointSize) + .5;
Now we insert the hair space:
myPara.insertionPoints[0].contents = SpecialCharacters.hairSpace;
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:
myPara.insertionPoints[1].kerningValue = normalizedKernAmount*-1000/12;
There you go: Align Left Edge for the first line of any paragraph.