Skip to main content
Inspiring
September 26, 2017
Question

Changing color of text found with regex

  • September 26, 2017
  • 7 replies
  • 7741 views

Hi,

I have multiple text layers in the document. I would like to filter through the contents of each text layer with regex and change the color of the found result.

In the example below I would like to find all the hastags in the text layer and change the color to blue.

Example:

This is a really long piece of text copy and #hashtag more copy here #anotherhastag #hashtagNo3

I've written a few scripts for indesign that involve GREP(regex) and it's fairly simple to change the color of the found results. I can't seem to find a solution for this in photoshop.

Thanks

This topic has been closed for replies.

7 replies

jonnyr39056025
Known Participant
April 9, 2018

You need the setFormatting() function, described in this Adobe blog post from 2011 http://blogs.adobe.com/cssdk/2011/01/formatting-text-ranges-in-photoshop.html

The main thing to note is that anything not set will revert to the document default - eg. if you change the colour but don't set the typeface, the colour will change but the typeface will change back to whatever is default. I'm not aware of any way to 'get' granular formatting from text, so this works best on a textItem that's initially all one style.

It's easy enough to add parameters to change by lifting code out of Script Listener output.

My example usage here, followed by the function as I'm using it.

var text = docRef.activeLayer.textItem.contents;

// changes font for single or double quotes (') (")

for (var l = 0; l < text.length; l++ ) {

     if (text == '"' || text == "'") {

          setFormatting(l, l+1, "ITC Avant Garde Gothic Std", "Bold Condensed", 120, 80, 100, 0, [255,255,255]);

     }

}

function setFormatting(start, end, fontName, fontStyle, fontSize, vScale, hScale, baseline, colorArray) {

//Sanity checking: is the active layer a text layer?

     if(app.activeDocument.activeLayer is ArtLayer){

          var activeLayer = app.activeDocument.activeLayer;

          if(activeLayer.kind == LayerKind.TEXT){

           //More checking: does the text layer have content, and are start and end set to reasonable values?

               if((activeLayer.textItem.contents != "")&&(start >= 0)&&(end <= activeLayer.textItem.contents.length)){

               var idsetd = app.charIDToTypeID( "setd" );

               var action = new ActionDescriptor();

               var idnull = app.charIDToTypeID( "null" );

           //The action reference specifies the active text layer.

               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" );

           //actionList contains the sequence of formatting actions.

               var actionList = new ActionList();

           //textRange sets the range of characters to format.

               var textRange = new ActionDescriptor();

               var idFrom = app.charIDToTypeID( "From" );

               textRange.putInteger( idFrom, start );

               textRange.putInteger( idT, end );

               var idTxtS = app.charIDToTypeID( "TxtS" );

           //The "formatting" ActionDescriptor holds the formatting. It should be clear that you can

           //add other attributes here--just get the relevant lines (usually 2) from the Script Listener

           //output and graft them into this section.

               var formatting = new ActionDescriptor();

           //Font name.

               var idFntN = app.charIDToTypeID( "FntN" );

               formatting.putString( idFntN, fontName );

           //Font style.

               var idFntS = app.charIDToTypeID( "FntS" );

               formatting.putString( idFntS, fontStyle );

           //Font size.

               var idSz = app.charIDToTypeID( "Sz  " );

               var idPnt = app.charIDToTypeID( "#Pnt" );

               formatting.putUnitDouble( idSz, idPnt, fontSize );

           //Vertical Scale

               var idVrtS = charIDToTypeID( "VrtS");

               formatting.putDouble( idVrtS, vScale);

           //Horizontal Scale

               var idHrzS = charIDToTypeID( "HrzS" );

               formatting.putDouble (idHrzS, hScale);

           //Vertical Baseline Height

               var idBsln = charIDToTypeID( "Bsln" );

               var idPxl = charIDToTypeID( "#Pxl" );

               formatting.putUnitDouble( idBsln, idPxl, baseline );

           //Fill color (as an RGB color).

               var idClr = app.charIDToTypeID( "Clr " );

               var colorAction = new ActionDescriptor();

               var idRd = app.charIDToTypeID( "Rd  " );

               colorAction.putDouble( idRd, colorArray[0] );

               var idGrn = app.charIDToTypeID( "Grn " );

               colorAction.putDouble( idGrn, colorArray[1]);

               var idBl = app.charIDToTypeID( "Bl  " );

               colorAction.putDouble( idBl, colorArray[2] );

               var idRGBC = app.charIDToTypeID( "RGBC" );

               formatting.putObject( idClr, idRGBC, colorAction );

           //end color.

               //

               textRange.putObject( idTxtS, idTxtS, formatting );

               actionList.putObject( idTxtt, textRange );

               textAction.putList( idTxtt, actionList );

               action.putObject( idT, idTxLr, textAction );

               app.executeAction( idsetd, action, DialogModes.NO );

          }

     }

}

marcZ89
Inspiring
April 5, 2018

Hi Jarda,

Thank you so much for all your answers! I'm running into an issue when trying to use your script, I put the Humanizer.jsx into the Script list of Photoshop 2018 but I still get the issue "Line: 65 ->  var descMod = Humanizer.objectToDescriptor(desc)[1]; " when trying to run the script  * Set range and change text style

What am I doing wrong?

Also, before I continue any further, do you think it would be cumbersome to modify the script to parse all text layers and find numbers to change the font?

Thanks!

Kukurykus
Legend
April 5, 2018

It may be useful for you (replies No. 11 & No. 18): Script to Select and Raster similar text-layer (font-family + font weight)

Jarda Bereza
Inspiring
October 10, 2017

Here is first version of my tool:

GitHub - jardicc/ActionManagerHumanizer: This tool will reveal for you occult mystery of Photoshop ActionDescriptors and…

Works good for me. You probably will need CC 2015.5 and higher

Keep in mind that pure AM will be faster something about 10-30ms per conversion into JS object and back.

tushardeAuthor
Inspiring
October 10, 2017

Wow! Thank you. I'll play with it now.

Much appreciated.

Jarda Bereza
Inspiring
October 17, 2017

Here is working code:

/**

* Set range and change text style

*

* @param {ActionDescriptor} sourceRef Layer reference

* @param {number} from The index of string to set the range from

* @param {number} to The index of string to set the range to

* @param {string} face The string of font family name

* @param {string} weight The string of font style name

* @param {string} unit The unit of the type.  "pt","px", or "mm"

* @param {number} size The size of the font.

*      Points: 0.01pt to 1296.00pt

*      Pixels: 0.01px to 1296.00px

*      Millimeters: 0.00mm to 457.19mm

* @param {array} arrayTextColor The array of RGB color [red,green,blue]. 0 to 255

*

* @return {PlayObject} The action descriptor of the text style.

*

* Preconditions:

* Select a text layer

*

* Examples:

* setRangeAndChangeTextStyle(0,1,"Helvetica","Bold","pt",60,[200,100,150]);

* setRangeAndChangeTextStyle(0,3,"","","pt",20,[200,100,150]);

*/

desc = {

            "null": {

"_enum": "ordinal",

"_ref": "layer",

"_value": "targetEnum"

},

            "to": {

                "_obj": "textLayer",

                "_value": {

                    "textStyleRange": [

                        {

                            "_obj": "textStyleRange",

                            "_value": {

                                "from": 5,

                                "to": 15,

                                "textStyle": {

                                    "_obj": "textStyle",

                                    "_value": {

                                        "fontName": "Arial",

                                        "fontStyleName": "Bold",

                                        "size": {

"_unit": "pixelsUnit",

"_value": 20

},

                                        "color": {

"_obj": "RGBColor",

"blue": 255,

"grain": 0,

"red": 17.984434962272644

}

                                    }

                                }

                            }

                        }

                    ]

                }

            }

        }

var descMod = Humanizer.objectToDescriptor(desc)[1];

var data = executeAction( stringIDToTypeID( "set" ), descMod, DialogModes.NO );

You will need to include my "Humanizer" tool.

Pros:

1) don't need specify font face

2) not sure if it is same for size or color

3) you will get new whole action descriptor in "data" variable

4) you don't need care about style ranges around. Photoshop changes "from" and "to" indexes automaticaly.

5) you could maybe add more ranges into textStyle ranges.

Jarda Bereza
Inspiring
October 9, 2017

I will tell you secret.

You can:

0) read layer actionDescriptor

1) use native function for converting actionDescriptor into JSON

2) use eval() on JSON string and create javascript object

3) modify regular JS object with well known object opertions. You need to do same thing as in my screenshot.

4) convert JS object into JSON string with ".toSource()" method

5) convert JSON string into ActionDescriptor with native function

6) and finally you can put modified descriptor into "executeAction"

I think I will create small library for this workflow.

tushardeAuthor
Inspiring
October 10, 2017

Thanks Jarda. This is way above my punching weight, but I'm going to give it a shot. I have managed to get up to step 4. But I am struggling with the step 5 and 6. At the moment I'm keeping it pretty basic: change the color of the entire text to red. Once I get the core functionality working I'll jump into figuring out how to change text ranges to a different color.

Bear in mind that I've written the script referencing different sources across the forms. So it's probably not the cleanest and most efficient way to do it.

Any help on how I can get steps 5 and 6 working would be great.

Thanks

#include "./helpers/JSON.jsx"

// 0) read layer actionDescriptor

var ref = new ActionReference();

ref.putEnumerated(charIDToTypeID("Lyr "), charIDToTypeID("Ordn"), charIDToTypeID("Trgt"));

var desc = executeActionGet(ref)

var convertDesc = new ActionDescriptor();

convertDesc.putObject(stringIDToTypeID("object"), stringIDToTypeID("object"), desc);

var jsonDesc = executeAction(stringIDToTypeID("convertJSONdescriptor"), convertDesc, DialogModes.NO);

// 1) use native function for converting actionDescriptor into JSON

jsonDesc.getString(stringIDToTypeID("json"));

// 2) use eval() on JSON string and create javascript object

var obj = JSON.parse(jsonDesc.getString(stringIDToTypeID("json")));

// 3) modify regular JS object with well known object opertions. You need to do same thing as in my screenshot.

obj["textKey"]["textStyleRange"][0]["textStyle"]["baseParentStyle"]["color"]["red"] = 255;

// 4) convert JS object into JSON string with ".toSource()" method

var objToStr = JSON.stringify(obj);

alert(objToStr);

// 5) convert JSON string into ActionDescriptor with native function

// 6) and finally you can put modified descriptor into "executeAction"

oliverIntergrafika
Inspiring
September 27, 2017

I have a TODO line to implement low level text manipulation in my extension. But, as my collegues suggested, the AM structure is a  bedrock layer under the swamp guarded by hydras. I don't have enough patience and passion and not smart enough to get to the lowest layers.

I wish Adobe would start the whole PS source code from scratch again and implement it a way which is allowing us to properly script the app.

Thanks xbytor2 for the .jsx code, ill try it!

Jarda Bereza
Inspiring
September 26, 2017

So do you want to do something like Font Remaping script here: Magic scripts for Photoshop  but with searched text and colors instead font family?

I agree with others. This can be very difficult.

You need to:

1) read whole text layer action descriptor

2) find all your strings and position where they start and where is end of string

3) if text layer has multiple styles, there is list of these styles and index where they start and where they end. So you need to read this descriptor, make copy and change color and position indexes and then put this descriptors into list and this list replace in original layer descritor

4) finaly you can use "set descriptor" action manager code and change the layer.

Like this:

2017-09-26_184756.jpg

Before:

2017-09-26_183731.jpg

After:

2017-09-26_183925.jpg

tushardeAuthor
Inspiring
September 26, 2017

Wow. This is very helpful. Thank you. I'm going to give it a crack and see where I end up.
Clearly, this looks like a "long walk for a ham sandwich". But it'll be worth the learning experience. I guess??

I'll hit you up with questions as I go along.

Jarda, what app/plugin did you use in your images? It looks incredibly helpful. Where can i get that?

Thanks again, c.pfaffenbichler,  SuperMerlinand Jarda Bereza.

Jarda Bereza
Inspiring
September 26, 2017

It's my own tool. Not public yet, but maybe it could be public soon.

It's based on this code: GitHub - JavierAroche/descriptor-info: JSX module to recursively get all the properties in an ActionDescriptor used in A…

And this code:

descriptor to JSON is already buil-in in Photoshop · Issue #10 · JavierAroche/descriptor-info · GitHub

So this should help you. You also can explore my script with align to baseline and text columns. I am getting here descriptor, changing it and then setting back. Magic scripts for Photoshop

c.pfaffenbichler
Community Expert
Community Expert
September 26, 2017

I recommend abandoning the idea to do this in Photoshop, Indesign is oriented towards sophisticated text editing, so better to do it there.

In Photoshop changing parts of text within a Type Layer necessitates using AM code that can become quite unwieldy in my experience.

tushardeAuthor
Inspiring
September 26, 2017

Thanks for the quick response.Unfortunately, I have to use photoshop for this job. So I'm stuck with photoshop. By "AM code" do you mean action manager? Any pointers in that direction will be very helpful.

Unfortunately, I can only use photoshop for this job. So I'm stuck with photoshop. By "AM code" do you mean action manager? Any pointers in that direction will be very helpful.

Thanks

c.pfaffenbichler
Community Expert
Community Expert
September 26, 2017

Yes, I meant Action Manager code.

Try recording changing the color of one word in a larger text in a Type Layer with ScriptingListener.plugin and see how well manageable the resulting code seems to you.