Copy link to clipboard
Copied
Hi all,
I am looking to take a text layer that contains an entire paragraph of text and randomize the font for each letter.
I know that with the TextItem object you can only change the font for the entire layer. However, I have found this script doing the exact thing I want to do, except with colors of the letters rather than the font.
This script in the example is hard for me to understand, but it does work. I was wondering if the script would be simple enough to edit to work with an array of fonts rather than colors? Or if there is a way to do this with a different script?
Copy link to clipboard
Copied
How complex are the Type Layers?
Do they combine different colors, font sizes, leading, tracking, kerning, …?
Copy link to clipboard
Copied
You have to use Action Manager code to style text within a TextItem.
I have a sample script that you can modify, this splits a TextItem into lines and applies formatting but you can loop it with each character if you wanted.
----------------------------------
/*
Utility PS Scripts created by David M. Converse ©2018-21
This script replaces text and sets styling
Last modified 6/2/2021
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#target photoshop
layerUpdate();
function setFormatting(start, end, fontName, fontStyle, fontSize){ //format text per input
var idsetd = app.charIDToTypeID('setd');
var action = new ActionDescriptor();
var idnull = app.charIDToTypeID('null');
var reference = new ActionReference();
var idTxLr = app.charIDToTypeID('TxLr');
var idOrdn = app.charIDToTypeID('Ordn');
var idTrgt = app.charIDToTypeID('Trgt');
reference.putEnumerated(idTxLr, idOrdn, idTrgt);
action.putReference(idnull, reference);
var idT = app.charIDToTypeID('T ');
var textAction = new ActionDescriptor();
var idTxtt = app.charIDToTypeID('Txtt');
var actionList = new ActionList();
var textRange = new ActionDescriptor();
var idFrom = app.charIDToTypeID('From');
textRange.putInteger(idFrom, start);
textRange.putInteger(idT, end);
var idTxtS = app.charIDToTypeID('TxtS');
var formatting = new ActionDescriptor();
var idFntN = app.charIDToTypeID('FntN');
formatting.putString(idFntN, fontName);
var idFntS = app.charIDToTypeID('FntS');
formatting.putString(idFntS, fontStyle);
var idSz = app.charIDToTypeID('Sz ');
var idPnt = app.charIDToTypeID('#Pnt');
formatting.putUnitDouble(idSz, idPnt, fontSize);
textRange.putObject(idTxtS, idTxtS, formatting);
actionList.putObject(idTxtt, textRange);
textAction.putList(idTxtt, actionList);
action.putObject(idT, idTxLr, textAction);
app.executeAction(idsetd, action, DialogModes.NO);
}
function layerUpdate(){
//sample values
var size1 = 96
var size2 = 42;
var font1 = 'Calibri';
var style1 = 'bold';
var texth = 220;
var textw = 460;
if(documents.length > 0){
var originalDialogMode = app.displayDialogs;
app.displayDialogs = DialogModes.ERROR;
var originalRulerUnits = preferences.rulerUnits;
var j = 0;
try{
var docRef = activeDocument;
preferences.rulerUnits = Units.POINTS;
var m = 0;
for(var i = 0; i < docRef.artLayers.length; i++){
var LayerRef = docRef.artLayers[i];
if(LayerRef.kind == LayerKind.TEXT){
var TextRef = LayerRef.textItem;
TextRef.textComposer = TextComposer.ADOBESINGLELINE;
var layerText = TextRef.contents;
//can iterate through multiple find/replace pairs
var newText = layerText.replace('old', 'new');
newText = newText.replace('old-1', 'new-1');
newText = newText.replace('old-2', 'new-2');
if(newText != layerText){
j = i;
TextRef.contents = newText;
if(TextRef.size == size1){
TextRef.size = size2;
TextRef.font = font1;
TextRef.useAutoLeading = false;
TextRef.leading = size2;
var l = TextRef.contents.split(/\r/);
docRef.activeLayer = LayerRef;
setFormatting(0, l[0].length, font1, style1, size1); //send to formatting function
preferences.rulerUnits = Units.PIXELS;
if(TextRef.kind == TextType.PARAGRAPHTEXT){
TextRef.height = texth;
TextRef.width = textw;
}
preferences.rulerUnits = Units.POINTS;
break;
}
}
}
}
}
catch(e){
alert(e + ' ' + e.line);
preferences.rulerUnits = originalRulerUnits;
app.displayDialogs = originalDialogMode;
return;
}
preferences.rulerUnits = originalRulerUnits;
app.displayDialogs = originalDialogMode;
}
else{
alert('You must have a document open to run this script.');
return;
}
}
Copy link to clipboard
Copied
Maybe this Script can help, the Array »newFonts« containing the fontPostScriptNames; kerning is not considered. (edited)
See the screenshots for an example of what it does:
Script updated 2022-05-09
// apply font from array to letters of selected type layer;
// 2022, use it at your own risk;
if (app.documents.length > 0) {
//////////////////
try {
var theFonts = new Array;
var theStyleRanges = 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'));
//var shapeList = textDesc.getList(stringIDToTypeID('textShape'));
var paragraphRangeList = textDesc.getList(stringIDToTypeID('paragraphStyleRange'));
var kernRange = textDesc.getList(stringIDToTypeID('kerningRange'));
var rangeList = textDesc.getList(stringIDToTypeID('textStyleRange'));
// process the list;
for (var o = 0; o < rangeList.count; o++) {
var thisList = rangeList.getObjectValue(o);
var theFrom = thisList.getInteger(stringIDToTypeID('from'));
var theTo = thisList.getInteger(stringIDToTypeID('to'));
var styleDesc = thisList.getObjectValue(stringIDToTypeID('textStyle'));
var aSize = styleDesc.getUnitDoubleValue(charIDToTypeID( "Sz " ));
// check for default font;
if (styleDesc.hasKey(stringIDToTypeID('fontPostScriptName')) == true) {var aFont = styleDesc.getString(stringIDToTypeID('fontPostScriptName'))}
else {
var theDefault = styleDesc.getObjectValue(stringIDToTypeID('baseParentStyle'));
var aFont = theDefault.getString(stringIDToTypeID('fontPostScriptName'));
};
if (styleDesc.hasKey(stringIDToTypeID('impliedFontSize')) == true) {var aFont = styleDesc.getString(stringIDToTypeID('fontPostScriptName'))}
// add to array;
//////////////////
if (o == 0) {
theFonts.push([aFont, aSize, theFrom, theTo]);
theStyleRanges.push(thisList.getObjectValue(stringIDToTypeID('textStyle')));
};
if (o > 0 && theFrom != theFonts[theFonts.length - 1][2] /*&& theTo != theFonts[theFonts.length - 1][3]*/) {
theFonts.push([aFont, aSize, theFrom, theTo]);
theStyleRanges.push(thisList.getObjectValue(stringIDToTypeID('textStyle')));
};
//////////////////
}
}
};
//alert (theFonts.length+"\n"+theFonts.join("\n"));
// the replacement-fonts array;
var newFonts = ["MyriadPro-Regular", "MyriadPro-Bold", "Times-BoldItalic"];
var anIndex = 0;
var theFontsNumber = newFonts.length;
// change text;
// =======================================================
var idPxl = charIDToTypeID( "#Pxl" );
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" );
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 list2 = new ActionList();
//////////////////
// define each letter individually;
var theIndex = 0;
var thisStyleRange = theStyleRanges[theIndex];
var thisFont = theFonts[theIndex];
for (var m = 0; m < theText.length; m++) {
// check for relevant style range;
if (m == thisFont[3]) {
theIndex++;
var thisStyleRange = theStyleRanges[theIndex];
var thisFont = theFonts[theIndex];
};
// apply style range but change font;
var desc14 = new ActionDescriptor();
desc14.putInteger( idFrom, m );
desc14.putInteger( idT, m+1 );
var desc15 = new ActionDescriptor();
var desc15 = thisStyleRange;
var aRandomFont = newFonts[Math.floor(Math.random()*3)];
desc15.putString( stringIDToTypeID( "fontPostScriptName" ), aRandomFont);
desc14.putObject( idTxtS, idTxtS, desc15 );
list2.putObject( idTxtt, desc14 );
};
//////////////////
var desc7 = new ActionDescriptor();
var list3 = new ActionList();
for (var n = kernRange.count-1; n >= 0; n--) {
var thisOne = kernRange.getObjectValue(n);
var desc15 = new ActionDescriptor();
desc15.putInteger( idFrom, thisOne.getInteger(charIDToTypeID("From")) );
desc15.putInteger( idT, thisOne.getInteger(charIDToTypeID( "T " )) );
desc15.putInteger( charIDToTypeID( "Krng" ), thisOne.getInteger(stringIDToTypeID("kerning")));
list3.putObject( stringIDToTypeID( "kerningRange"), desc15);
};
desc7.putList( idTxtt, list2 );
desc7.putList( stringIDToTypeID( "kerningRange"), list3);
desc6.putObject( idT, idTxLr, desc7 );
executeAction( idsetd, desc6, DialogModes.NO );
}
catch (e) {};
};
//////////////////
Copy link to clipboard
Copied
Thank you c.pfaffenbichler for your help, this script appears to work, however with two bugs I am trying to fix:
1) The script tries to identify a default font, and it intermixes with the array of defined fonts. Preferably this script would randomize all letters to the coded array of fonts and nothing else. I am trying to remove that module but I can't quite figure this part out.
2) The script is really picky with what fonts work. I doubt that is the code's fault, as when I use certain fonts it works fine (minus the default font thing) but I cannot figure out why certain ones work and why certain ones don't. All cloud fonts seem to not work, which is fine for my project. however even regular fonts like Arial (and its substyles) don't appear to be recognized. Probably a really easy fix or explanation but I can't figure it out.
Thank you all for your help on this.
Copy link to clipboard
Copied
Try deleting the font caches.
https://helpx.adobe.com/photoshop/kb/troubleshoot-fonts-photoshop.html
Copy link to clipboard
Copied
Ad 1) No idea how that would mix in.
Could you provide a File (original and result) and the Script (or just the Fonts-Array) where this happened?
Ad 2) Again no idea.
Do you get errors or are they »silently« disregarded?
How did you determine the fonts’ PostsScriptNames?
Copy link to clipboard
Copied
I wonder if the array »newFonts« might contain malformed fontPostScriptNames and those instances get resplaced with … not quite sure with what.
Could you apply the fonts you want to some text manually and run the following Script?
Edit: Then compare the names in the alert with the ones in your array.
// 2015, use it at your own risk;
if (app.documents.length > 0) {
var myDocument = app.activeDocument;
//
main ();
};
////////////////////////////////////
function main () {
var theFonts = new Array;
var ref = new ActionReference();
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var applicationDesc = executeActionGet(ref);
var theNumber = applicationDesc.getInteger(stringIDToTypeID("numberOfLayers"));
//////
for (var m = theNumber; m >= 0; 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"));
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 paragraphRangeList = textDesc.getList(stringIDToTypeID('paragraphStyleRange'));
var kernRange = textDesc.getList(stringIDToTypeID('kerningRange'));
var rangeList = textDesc.getList(stringIDToTypeID('textStyleRange'));
for (var o = 0; o < rangeList.count; o++) {
var styleDesc = rangeList.getObjectValue(o).getObjectValue(stringIDToTypeID('textStyle'));
// check for default font;
if (styleDesc.hasKey(stringIDToTypeID('fontPostScriptName')) == true) {var aFont = styleDesc.getString(stringIDToTypeID('fontPostScriptName'))}
else {
var theDefault = styleDesc.getObjectValue(stringIDToTypeID('baseParentStyle'));
var aFont = theDefault.getString(stringIDToTypeID('fontPostScriptName'));
};
// add to array;
var theCheck = true;
for (var n = 0; n < theFonts.length; n++) {
if (theFonts[n] == aFont) {theCheck = false}
};
if (theCheck == true) {theFonts.push(aFont)}
}
}
}
}
catch (e) {};
}
alert ("the result:"+"\n"+theFonts.join("\n"))
};
Copy link to clipboard
Copied
Have you thought through the »randomization«?
With plain »randomness« (and pseudo-randomness) several letters in sequence could assume the same font (similar to rolling dice and getting the same number several times in sequence) and some fonts might be used noticable less than others (or even not at all).
Copy link to clipboard
Copied
This randomness behavior is completely fine, if two, three, or even more letters are the same font in a row does not affect the effect I am trying to accomplish