Skip to main content
New Participant
July 29, 2025
Answered

Style sheets to split text and shading on different layers?

  • July 29, 2025
  • 4 replies
  • 650 views

I do not think this is possible but as a book designer it would be very nice if there was a script. Is there a way to set my font style where my text is on one layer and my shading or rule (essentially a colour) is on a seperate layer? This is useful if the book needs a language change.

thanks
Dom

Correct answer DomRob

Hi, Peter is correct, you do it as a 5 colour job, text as a spot black on its own layer. Printers print the CMYK and spot for one language, then replace the spot for the second language.

4 replies

m1b
Community Expert
September 2, 2025

Hi @DomRob, I wanted to write a script similar to what you are asking about, so here it is. Perhaps it could be helpful. All it does is search for specific paragraph-styled text and draw a box behind it. If you edit the text (eg. replace with translation) then re-run the script and it will update. Try out the attached demo.indd if you want to see it in action.

- Mark

 

 

/**
 * @file Draw Boxes Around Styled Text.js
 *
 * Instructions:
 * 1. Configure the `searchTerms` at the start of the script,
 *    so that script will search for text set in a particular
 *    paragraph style and draw a box around it.
 * 
 * 2. Ensure that your document has the configured paragraph style
 *    and object style.
 * 
 * 3. Run script.
 * 
 * @author m1b
 * @version 2025-09-02
 * @discussion https://community.adobe.com/t5/indesign-discussions/style-sheets-to-split-text-and-shading-on-different-layers/m-p/15435479
 */
function main() {

    /**
     * searchTerm properties
     * @property {String} paragraphStyleName - the paragraph style to search for.
     * @property {String} objectStyleName - the object style to apply to the box.
     * @property {String} label - *unique* label used to identify each box type.
     * @property {String} layerName - the layer to draw boxes on.
     * @property {Array<Number>} padding - the padding, in points, from each side of the text [T,L,B,R].
     * @property {Boolean} oneBoxPerParagraph - if `true` draws one box per unbroken paragraph; if `false`, consectutive paragraphs will fit in the same box (default: false).
     */
    var searchTerms = [

        // demo search 1
        {
            paragraphStyleName: 'Breakout 1',
            objectStyleName: 'Breakout 1',
            label: 'box type 1',
            layerName: 'Boxes',
            padding: [2, 2, 2, 2],
            oneBoxPerParagraph: true,
        },

        // demo search 2
        {
            paragraphStyleName: 'Breakout 2',
            objectStyleName: 'Breakout 2',
            label: 'box type 2',
            layerName: 'Boxes',
            padding: [10, 2, 10, 2],
            oneBoxPerParagraph: true,
        },

    ];

    app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;
    const isEmpty = /^[\s\r\n]*$/;

    var doc = app.activeDocument;
    var errors = [];

    searchTermLoop:
    for (var s = 0; s < searchTerms.length; s++) {

        var searchTerm = searchTerms[s];

        // collect the destination layer
        var layer = getThing(doc.layers, 'name', searchTerm.layerName);

        if (!layer) {
            layer = doc.layers.add({ name: searchTerm.layerName });
            layer.move(LocationOptions.AFTER, doc.layers.lastItem());
        }

        // collect the box object style
        var objectStyle = getThing(doc.objectStyles, 'name', searchTerm.objectStyleName);

        if (!objectStyle) {
            errors.push('No object style "' + searchTerm.objectStyleName + '" found.');
            continue searchTermLoop;
        }

        var paragraphStyle = getThing(doc.allParagraphStyles, 'name', searchTerm.paragraphStyleName);

        if (!paragraphStyle) {
            errors.push('No parragraph style "' + searchTerm.paragraphStyleName + '" found.');
            continue searchTermLoop;
        }

        // collect the texts
        var texts = findText(doc, { appliedParagraphStyle: paragraphStyle });

        if (0 === texts.length) {
            errors.push('No texts found for "' + searchTerm.paragraphStyleName + '" found.');
            continue searchTermLoop;
        }

        if (searchTerm.oneBoxPerParagraph) {

            // collect each paragraph from the texts
            var paragraphs = [];

            for (var i = 0; i < texts.length; i++)
                paragraphs = paragraphs.concat(texts[i].paragraphs.everyItem().getElements());

            texts = paragraphs;

        }

        // remove the previous boxes
        var oldBoxes = doc.allPageItems;

        for (var i = oldBoxes.length - 1; i >= 0; i--) {
            if (searchTerm.label === oldBoxes[i].label)
                oldBoxes[i].remove();
        }

        // draw the boxes
        drawBoxesLoop:
        for (var i = 0, bounds, box; i < texts.length; i++) {

            if (isEmpty.test(texts[i].contents))
                continue drawBoxesLoop;

            bounds = getTextBounds(texts[i]);
            bounds = [bounds[0] - searchTerm.padding[0], bounds[1] - searchTerm.padding[1], bounds[2] + searchTerm.padding[2], bounds[3] + searchTerm.padding[3]];

            box = drawRectangleIndesign(doc, bounds, { label: searchTerm.label, appliedObjectStyle: objectStyle });
            box.move(layer);

        }

    } // end search terms loop

    if (errors.length)
        alert('Errors:\n' + errors.join('\n'));

};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Draw Boxes Around Styled Text');

/**
 * Draws and returns a Rectangle.
 * For Indesign
 * @author m1b
 * @version 2023-08-24
 * @param {Document|Layer|Group} container - the container for the rectangle.
 * @param {Array<Number>} bounds - rectangle bounds [T,L,B,R].
 * @returns {Rectangle}
 */
function drawRectangleIndesign(container, bounds, props) {

    var rectangle = container.rectangles.add({
        geometricBounds: bounds,
    });

    if (props)
        rectangle.properties = props;

    return rectangle;

};


/**
 * Returns the bounds of the text's metrics (not geometry).
 * @author m1b
 * @version 2025-09-02
 * @param {Text} text
 */
function getTextBounds(text) {

    if (
        !text.hasOwnProperty('lines')
        || 0 === text.lines.length
    )
        throw new Error('getTextBounds: bad `lines` property.');

    var top = Infinity;
    var left = Infinity;
    var bottom = -Infinity;
    var right = -Infinity;

    var tsrs = text.lines;

    for (var i = 0, tsr, ltr, first, last; i < tsrs.length; i++) {

        tsr = tsrs[i];
        ltr = ParagraphDirectionOptions.LEFT_TO_RIGHT_DIRECTION === tsr.paragraphDirection;
        first = tsr.characters.firstItem();
        last = tsr.characters.lastItem();

        if (ltr) {

            if (first.horizontalOffset < left)
                left = first.horizontalOffset;

            if (last.endHorizontalOffset > right)
                right = last.endHorizontalOffset;

        }

        else {

            if (last.horizontalOffset < left)
                left = last.horizontalOffset;

            if (first.endHorizontalOffset > right)
                right = first.endHorizontalOffset;

        }

        if (first.baseline - first.ascent < top)
            top = first.baseline - first.ascent;

        if (first.baseline + first.descent > bottom)
            bottom = first.baseline + first.descent;

    }

    return [top, left, bottom, right];

};

/**
 * Returns result of search in the supplied `findIn` object
 * using its findText method.
 *
 * Example:
 *    var headingParas = findText(doc, { appliedParagraphStyle: headingStyle });
 *
 * @author m1b
 * @version 2025-04-18
 * @param {Document|Text} findIn - any object with a valid `findText` method.
 * @param {Object} findWhat - an object containing findChangeTextOptions properties.
 * @returns {Array<Text>}
 */
function findText(findIn, findWhat) {

    if ('function' !== typeof findIn.findText)
        throw new Error('findText: bad `where` supplied.');

    // configure the find
    app.findChangeTextOptions.properties = {
        caseSensitive: false,
        includeFootnotes: true,
        includeHiddenLayers: false,
        includeLockedLayersForFind: false,
        includeLockedStoriesForFind: false,
        includeMasterPages: false,
        wholeWord: false,
    };

    // reset
    app.findTextPreferences = NothingEnum.NOTHING;

    for (var key in findWhat) {

        if (
            findWhat.hasOwnProperty(key)
            && app.findTextPreferences.hasOwnProperty(key)
        )
            app.findTextPreferences[key] = findWhat[key];

    }

    // perform the find
    return findIn.findText();

};

/**
 * Returns a thing with matching property.
 * If `key` is undefined, evaluate the object itself.
 * @author m1b
 * @version 2024-04-21
 * @param {Array|Collection} things - the things to look through.
 * @param {String} [key] - the property name (default: undefined).
 * @param {*} value - the value to match.
 * @returns {*?} - the thing, if found.
 */
function getThing(things, key, value) {

    for (var i = 0; i < things.length; i++)
        if ((undefined == key ? things[i] : things[i][key]) == value)
            return things[i];

};

Edit 2025-09-03: removed debugging line.

New Participant
September 3, 2025

Hi @m1b Please  view my PM in your inbox. Thank You

brian_p_dts
Community Expert
July 29, 2025

Seems like you would need to draw the rules/shading as separate shapes if you don't want them to change with text. Don't really see any other way around it. 

Scott Falkner
Community Expert
July 30, 2025

You could duplicate the text frame to a new layer then give the copied text no fill and no stroke. Apply paragraph shading to the invisible text instead of the text on the language layer. Since the text is still there and matches whichever language you used as your source for copying the shading will match that language if you make any style (but not copy) changes.

DomRobAuthor
New Participant
July 30, 2025

Hi Scott

I have done this before, it is possible but it gets complicated, I do books with images, maps, graphs and at the moment I am on a 250 page book. The problem can be solved this way but for a second reprint the author has little changes which is where it gets complicated. Thanks, I thought it was unlikely but just needed to confirm it. 

m1b
Community Expert
July 29, 2025

Hi @DomRob Can you explain how it would be more useful than using the paragraph shading if the book needs a language change? Wouldn't it be better if, after changing the language, the shading just updated automatically to match the new text? Maybe you could post an example screen shot of what you mean?

- Mark

Peter Spier
Community Expert
July 29, 2025

@m1b for a language change if the text is separate from any colored content it's possible to change the language by replacing only one plate (the one with the text). if the shading or rules are tied to the text you risk a shift in postion and then multiple plates must change.

m1b
Community Expert
July 29, 2025

Thanks @Peter Spier that could well be the salient factor.

 

Still, I have no idea what a hypothetical script would do. 

rob day
Community Expert
July 29, 2025

Is there a way to set my font style where my text is on one layer and my shading or rule (essentially a colour) is on a seperate layer?

 

No, Paragraph Rules and Paragraph Borders and Shading are paragraph attributes. If you wanted a rule or shading on a separate layer you would have to create the rules or shadows as separate objects.