Copy link to clipboard
Copied
I'm not happy with the hanging punctuation in photoshop, but I can't work in indesign, where this feature is much more elaborate. I want to make a script with manual kerning on punctuation marks, but I can't figure out what I'm doing wrong.
function adjustPunctuationKerning() {
var textLayer = app.activeDocument.activeLayer;
if (textLayer.kind !== LayerKind.TEXT) {
return;
}
var textItem = textLayer.textItem;
for (var i = 0; i < textItem.contents.length; i++) {
var currentCharacter = textItem.contents[i];
switch (currentCharacter) {
case '?':
textItem.characters[i+1].kerning = -300;
break;
case '!':
textItem.characters[i+1].kerning = -100;
break;
case '.':
textItem.characters[i+1].kerning = -100;
break;
}
}
}
adjustPunctuationKerning();
I don't know js well and i hope photoshop gives kerning manipulation in text layer
// apply properties like fonts, size, … to letters of words in selected type layer;
// kerning in this case and for all type layers in the document;
// 2024, use it at your own risk;
if (app.documents.length > 0) {
var theRegExp = /\?/gi;
var theTypeLayers = collectTypeLayers ();
for (var m = 0; m < theTypeLayers.length; m++) {
changeWordProperties (theTypeLayers[m][1], theRegExp, -500)
}
};
//////////////////
////// change word properties //////
function changeWordProperties (
...
Copy link to clipboard
Copied
Where did you get the code from?
Copy link to clipboard
Copied
gpt and plus I tried
Copy link to clipboard
Copied
Chat GPT is getting better, but it can often make stuff up to be "helpful".
https://theiviaxx.github.io/photoshop-docs/Photoshop/TextItem.html
Copy link to clipboard
Copied
Thank you! But now I'm confused, as I understood, manual kerning can only be done on the whole text, but not on certain characters?
https://theiviaxx.github.io/photoshop-docs/Photoshop/AutoKernType/MANUAL.html#autokerntype-manual
Copy link to clipboard
Copied
DOM code is limited in what it can do. Scripting text isn't fun.
If what you want to accomplish is even possible, it will likely require advanced Action Manager coding, which is beyond my knowledge and capabilities.
Search the Photoshop forum and make sure that the topic is for Photoshop and not Illustrator or InDesign etc.
Or look into the new UXP/BatchPlay scripting instead of legacy ExtendScript.
Copy link to clipboard
Copied
as I understood, manual kerning can only be done on the whole text, but not on certain characters?
By @enperror
Kerning is only for a pair of characters. Tracking is for a range of characters.
https://helpx.adobe.com/indesign/using/kerning-tracking.html
Jane
Copy link to clipboard
Copied
I misspoke, just below clarified what I was getting at.
Copy link to clipboard
Copied
More precisely, it triggers on the first pair of letters, but how to specify the right one - no idea.
Copy link to clipboard
Copied
How important is this?
Do you have to perform this task multiple times every day?
As @Stephen Marsh mentioned, the AM-code that could be used to achieve this is not exactly »easy«.
Copy link to clipboard
Copied
Since I have to do it in photoshop, it takes me quite a bit of time.
Copy link to clipboard
Copied
Please clarify what »quite a bit of time« actually means.
Copy link to clipboard
Copied
I letterer comics/manga, it takes me a couple hours to lettering, let's say, one chapter. of those couple hours, at least 10-15 minutes are spent compensating for punctuation with kerning. Automating this function would help save a lot of time.
Copy link to clipboard
Copied
By @enperrorSince I have to do it in photoshop, it takes me quite a bit of time.
Yes, you should expect that trying to work with text in Photoshop will take significantly more time than it does in InDesign. InDesign has excellent type controls and Illustrator has good text controls. Photoshop's main focus is images so it has limited text controls.
When I started using PS, text was pixels on the current layer. Kerning meant selecting all the letter to the right and using the Move tool. Tracking meant repeating the process for the other letters.
Photoshop's type controls have come a long way since then, but it is not expected that it will ever duplicate those in InDesign. I know you said you can't use InDesign for this project, so unfortunately you will have to deal with Photoshop's limitations. If a script won't work, then expect it to take a long time and hope that you don't need to make edits later.
Jane
Copy link to clipboard
Copied
This seems tricky me indeed.
I have provided Scripts for changing the color of words in the past, but that happens in the textStyleRange, changing the kerning would necessitate splitting the kerningRange at the indices of the character (the question mark in this case).
Copy link to clipboard
Copied
I tried writing new code, it triggers, but instead of executing the function, I have a kerning 0 applied to all the text
if (app.documents.length > 0) {
var doc = app.activeDocument;
for (var i = 0; i < doc.artLayers.length; i++) {
var layer = doc.artLayers[i];
if (layer.kind === LayerKind.TEXT) {
var textItem = layer.textItem;
textItem.autoKerning = AutoKernType.MANUAL;
for (var j = 0; j < textItem.contents.length; j++) {
if (textItem.contents[j] === "?") {
textItem.kerning = 220;
}
}
}
}
alert("yep.");
} else {
alert("nope.");
}
Most likely, as @Stephen_A_Marsh wrote to me earlier, UXP is required
Copy link to clipboard
Copied
Most likely, as @Stephen_A_Marsh wrote to me earlier, UXP is required
While UXP code may be better at handling it AM code can handle the task, too, DOM code is the useless one in this case.
Copy link to clipboard
Copied
Edit: The Script is kind of a proof-of-concept (based on some old scripts, so it contains superfluous lines) and applies to the selected Type Layer.
// apply properties like fonts, size, … to letters of words in selected type layer;
// kerning in this case;
// 2024, use it at your own risk;
if (app.documents.length > 0) {
var theRegExp = /\?/gi;
changeWordProperties (theRegExp, -500)
};
//////////////////
////// change word properties //////
function changeWordProperties (theRegExp, newKerning) {
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
//////////////////
try {
var theFonts = new Array;
var theStyleRanges = new Array;
var theStyleRanges2 = new Array;
// get font of active layer;
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var layerDesc = executeActionGet(ref);
var layerSet = typeIDToStringID(layerDesc.getEnumerationValue(stringIDToTypeID("layerSection")));
var isBackground = layerDesc.getBoolean(stringIDToTypeID("background"));
var theName = layerDesc.getString(stringIDToTypeID('name'));
// if not layer group collect values;
if (layerSet != "layerSectionEnd" && layerSet != "layerSectionStart" && isBackground != true) {
var hasText = layerDesc.hasKey(stringIDToTypeID("textKey"));
if (hasText == true) {
var textDesc = layerDesc.getObjectValue(stringIDToTypeID('textKey'));
var theText = textDesc.getString(stringIDToTypeID('textKey'));
// get indices for string;
var indicesCount = 0;
var theIndices = new Array;
while ((result = theRegExp.exec(theText))!=null) {
theIndices.push([result.index, result.index+result[0].length])
};
//var shapeList = textDesc.getList(stringIDToTypeID('textShape'));
var paragraphRangeList = textDesc.getList(stringIDToTypeID('paragraphStyleRange'));
var kernRange = textDesc.getList(stringIDToTypeID('kerningRange'));
var rangeList = textDesc.getList(stringIDToTypeID('textStyleRange'));
var idPxl = charIDToTypeID( "#Pxl" );
var idPnt = charIDToTypeID( "#Pnt" );
var idTxtt = charIDToTypeID( "Txtt" );
var idFrom = charIDToTypeID( "From" );
var idT = charIDToTypeID( "T " );
var idTxtS = charIDToTypeID( "TxtS" );
var idTxLr = charIDToTypeID( "TxLr" );
var idTxt = charIDToTypeID( "Txt " );
var idsetd = charIDToTypeID( "setd" );
// change text;
var desc6 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref1 = new ActionReference();
ref1.putEnumerated( idTxLr, charIDToTypeID( "Ordn" ), charIDToTypeID( "Trgt" ));
desc6.putReference( idnull, ref1 );
var desc7 = new ActionDescriptor();
desc7.putString( idTxt, theText );
//////////////////////////////////////////////////////
var kerningRanges = new ActionList;
var theCounter = 0;
if (kernRange.count > 0) {var thisKerningRange = kernRange.getObjectValue(theCounter)};
// kernRange
for (var m = 0; m < theText.length; m++) {
// check for relevant existing kerning range;
if (kernRange.count > 0) {
if (thisKerningRange.getInteger(idFrom) == m) {
kerningRanges.putObject( stringIDToTypeID( "kerningRange"), thisKerningRange);
theCounter++;
if (theCounter < kernRange.count) {
var thisKerningRange = kernRange.getObjectValue(theCounter)
}
};
};
// check for regexp;
if (theText[m].match(theRegExp) != null) {
var desc15 = new ActionDescriptor();
desc15.putInteger( idFrom, m - 1 );
desc15.putInteger( idT, m );
desc15.putInteger( stringIDToTypeID("kerning"), newKerning );
kerningRanges.putObject( stringIDToTypeID( "kerningRange"), desc15);
};
};
//////////////////////////////////////////////////////
//desc7.putList( idTxtt, list2 );
desc7.putList( idTxtt, rangeList );
desc7.putList( stringIDToTypeID( "kerningRange"), kerningRanges);
//////////////////////////////////////////////////////
desc6.putObject( idT, idTxLr, desc7 );
executeAction( idsetd, desc6, DialogModes.NO );
}
}
}
catch (e) {};
app.preferences.rulerUnits = originalRulerUnits;
};
Copy link to clipboard
Copied
// apply properties like fonts, size, … to letters of words in selected type layer;
// kerning in this case and for all type layers in the document;
// 2024, use it at your own risk;
if (app.documents.length > 0) {
var theRegExp = /\?/gi;
var theTypeLayers = collectTypeLayers ();
for (var m = 0; m < theTypeLayers.length; m++) {
changeWordProperties (theTypeLayers[m][1], theRegExp, -500)
}
};
//////////////////
////// change word properties //////
function changeWordProperties (theId, theRegExp, newKerning) {
var originalRulerUnits = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
//////////////////
try {
// get font of active layer;
var ref = new ActionReference();
ref.putIdentifier( charIDToTypeID("Lyr "), theId );
var layerDesc = executeActionGet(ref);
var layerSet = typeIDToStringID(layerDesc.getEnumerationValue(stringIDToTypeID("layerSection")));
var isBackground = layerDesc.getBoolean(stringIDToTypeID("background"));
// if not layer group collect values;
if (layerSet != "layerSectionEnd" && layerSet != "layerSectionStart" && isBackground != true) {
var hasText = layerDesc.hasKey(stringIDToTypeID("textKey"));
if (hasText == true) {
var textDesc = layerDesc.getObjectValue(stringIDToTypeID('textKey'));
var theText = textDesc.getString(stringIDToTypeID('textKey'));
//var shapeList = textDesc.getList(stringIDToTypeID('textShape'));
var kernRange = textDesc.getList(stringIDToTypeID('kerningRange'));
var rangeList = textDesc.getList(stringIDToTypeID('textStyleRange'));
var idTxtt = charIDToTypeID( "Txtt" );
var idFrom = charIDToTypeID( "From" );
var idT = charIDToTypeID( "T " );
var idTxLr = charIDToTypeID( "TxLr" );
var idTxt = charIDToTypeID( "Txt " );
var idsetd = charIDToTypeID( "setd" );
// change text;
var desc6 = new ActionDescriptor();
var idnull = charIDToTypeID( "null" );
var ref1 = new ActionReference();
ref1.putIdentifier( idTxLr, theId );
desc6.putReference( idnull, ref1 );
var desc7 = new ActionDescriptor();
desc7.putString( idTxt, theText );
//////////////////////////////////////////////////////
var kerningRanges = new ActionList;
var theCounter = 0;
if (kernRange.count > 0) {var thisKerningRange = kernRange.getObjectValue(theCounter)};
// kernRange
for (var m = 0; m < theText.length; m++) {
// check for relevant existing kerning range;
if (kernRange.count > 0) {
if (thisKerningRange.getInteger(idFrom) == m) {
kerningRanges.putObject( stringIDToTypeID( "kerningRange"), thisKerningRange);
theCounter++;
if (theCounter < kernRange.count) {
var thisKerningRange = kernRange.getObjectValue(theCounter)
}
};
};
// check for regexp;
if (theText[m].match(theRegExp) != null) {
var desc15 = new ActionDescriptor();
desc15.putInteger( idFrom, m - 1 );
desc15.putInteger( idT, m );
desc15.putInteger( stringIDToTypeID("kerning"), newKerning );
kerningRanges.putObject( stringIDToTypeID( "kerningRange"), desc15);
};
};
//////////////////////////////////////////////////////
desc7.putList( idTxtt, rangeList );
desc7.putList( stringIDToTypeID( "kerningRange"), kerningRanges);
//////////////////////////////////////////////////////
desc6.putObject( idT, idTxLr, desc7 );
executeAction( idsetd, desc6, DialogModes.NO );
}
}
}
catch (e) {};
app.preferences.rulerUnits = originalRulerUnits;
};
////// collect type layers from active document //////
function collectTypeLayers () {
// get number of layers;
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var applicationDesc = executeActionGet(ref);
var theNumber = applicationDesc.getInteger(stringIDToTypeID("numberOfLayers"));
// process the layers;
var theLayers = new Array;
for (var m = 0; m <= theNumber; m++) {
try {
var ref = new ActionReference();
ref.putIndex( charIDToTypeID( "Lyr " ), m);
var layerDesc = executeActionGet(ref);
var layerSet = typeIDToStringID(layerDesc.getEnumerationValue(stringIDToTypeID("layerSection")));
var isBackground = layerDesc.getBoolean(stringIDToTypeID("background"));
// if not layer group collect values;
if (layerSet != "layerSectionEnd" && layerSet != "layerSectionStart" && isBackground != true && layerDesc.hasKey(stringIDToTypeID("textKey")) == true) {
var visible = layerDesc.getBoolean(stringIDToTypeID("visible"));
var theName = layerDesc.getString(stringIDToTypeID('name'));
var theID = layerDesc.getInteger(stringIDToTypeID('layerID'));
theLayers.push([theName, theID, visible])
};
}
catch (e) {};
};
return theLayers
};
Copy link to clipboard
Copied
@enperror , have you tested the Script yet?
Copy link to clipboard
Copied
Hi, yes it works, thx! but there is one problem. The selected kerning is applied to the character, but the previous character becomes 0 kerning. I attached a screenshot, it shows ? has a kerning of -250 and the previous character became 0 kerning
I tried to figure out what the problem is, but it's probably because the kerning is trying to apply to both sides of the character
Copy link to clipboard
Copied
Please provide the original file and the resulting file.
Copy link to clipboard
Copied
before “?” kerning 0 in the resulting file
Copy link to clipboard
Copied
Please provide the original file and the resulting file, not screenshots.
Copy link to clipboard
Copied