Skip to main content
Bedazzled532
Inspiring
March 28, 2025
Answered

Soft return at end of line

  • March 28, 2025
  • 3 replies
  • 1209 views

Hi,

I have multiple textframes threaded. Page as been setup properly as I wanted. I want to put a soft return at the end of line. If there is a new paragraph change, it will ignore that. I have marked it with red circles, the places where I want soft-returns. How to do this with a script. I want to target a particular para style.

Correct answer m1b

Hi @Bedazzled532, try this script. You need to set the name of the target paragraph style in the settings object. Let me know if it works for you.

- Mark

 

/**
 * @file Force Line Breaks.js
 *
 * Adds a line break at the end of every line of text
 * of selected items or, optionally, the whole document.
 *
 * Notes:
 *   - only processes text in the target paragraph style
 *     (edit the settings object below).
 *   - makes a valiant attempt at hardening justified text,
 *     but only handles left-justified text.
 *
 * @author m1b
 * @version 2025-03-29
 * @discussion https://community.adobe.com/t5/indesign-discussions/soft-return-at-end-of-line/m-p/15236469
 */
function main() {

    var settings = {

        /* only paragraphs in this style will be processed (set to `undefined` to disable filtering) */
        paragraphStyleName: 'My Best Paragraph Style',

    };

    app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;

    const EXPAND_JUSTIFIED_TEXT_FRAME = 3,
        SQUASH_AMOUNT = -100;

    var doc = app.activeDocument,
        stories = getSelectedStories(doc.selection);

    if (0 === stories.length) {

        // check with user
        if (!confirm('Do you want to apply to whole document?'))
            return;

        // no selected stories, so get every story from document
        stories = doc.stories.everyItem().getElements();

    }

    var spaceCharacter = /\s/,
        linefeeds = /[\n\r]/;

    // we'll slightly expand the text frames later on
    var framesToAdjust = {};

    storyLoop:
    for (var i = stories.length - 1; i >= 0; i--) {

        var paragraphs = stories[i].paragraphs.everyItem().getElements();

        paragraphsLoop:
        for (var j = paragraphs.length - 1; j >= 0; j--) {

            var paragraph = paragraphs[j];

            if (
                undefined != settings.paragraphStyleName
                && settings.paragraphStyleName !== paragraph.appliedParagraphStyle.name
            )
                continue paragraphsLoop;

            var isJustified = (Justification.LEFT_JUSTIFIED === paragraph.justification);

            var lines = paragraph.lines.everyItem().getElements(),

                // store character positions
                positions = [];

            linesLoop:
            for (var k = lines.length - 1; k >= 0; k--) {

                var line = lines[k],
                    suffix;

                if (0 === line.parentTextFrames.length)
                    continue linesLoop;

                if (
                    linefeeds.test(line.characters[-1].contents)
                    || SpecialCharacters.FORCED_LINE_BREAK === line.characters[-1].contents
                    || !line.parentStory.characters[line.characters[-1].index + 1].isValid
                )
                    // last line in paragraph or story
                    continue linesLoop;

                suffix = !spaceCharacter.test(line.characters[-1].contents)
                    ? suffix = '-\n' // hyphenated word
                    : suffix = '\n';

                if (isJustified)
                    // store positions for later
                    positions.unshift(line.characters.everyItem().horizontalOffset);

                // force line break
                line.insertionPoints[-1].contents = suffix;

            }

            if (!isJustified)
                continue paragraphsLoop;

            // now we harden the justification, line by line

            // start by aligning left (let me know if you need RTL)
            paragraph.justification = Justification.LEFT_ALIGN;

            // collect all the parent text frames
            for (var f = 0; f < paragraph.parentTextFrames.length; f++) {

                var tf = paragraph.parentTextFrames[f];

                if (!framesToAdjust[tf.id])
                    framesToAdjust[tf.id] = tf;

            }

            // temporarily squash text up a bit to stop unwanted line breaking
            squashLoop:
            for (var k = paragraph.lines.length - 2; k >= 0; k--) {

                if (
                    !paragraph.lines[k].isValid
                    || 0 === paragraph.lines[k].parentTextFrames.length
                )
                    continue squashLoop;

                paragraph.lines[k].characters.itemByRange(
                    paragraph.lines[k].characters[0],
                    paragraph.lines[k].characters[-4]
                ).tracking = SQUASH_AMOUNT;

            }

            // refresh the lines reference
            lines = paragraph.lines.everyItem().getElements();

            linePositionsLoop:
            for (var l = 0; l < lines.length; l++) {

                var linePositions = positions[l];

                if (!linePositions)
                    continue linePositionsLoop;

                var len = Math.min(lines[l].characters.length, linePositions.length);

                charactersLoop:
                for (var c = 1; c < len; c++) {

                    var ch0 = paragraph.lines[l].characters[c - 1],
                        ch1 = paragraph.lines[l].characters[c],
                        pos = ch1.horizontalOffset,
                        target = linePositions[c];

                    if (undefined == target)
                        continue charactersLoop;

                    ch0.tracking += (target - pos) * (1000 / ch0.pointSize);

                }

            }

        }

    }

};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Force Line Breaks');

/**
* Returns the document's selected stories.
* @author m1b
* @version 2025-03-26
* @param {Array<PageItem>} items - the items from which to gather stories.
* @returns {Array<Story>}
*/
function getSelectedStories(items) {

    var stories = [],
        unique = {};

    for (var i = 0; i < items.length; i++) {

        if ('TextFrame' === items[i].constructor.name) {

            if (unique[items[i].parentStory.id])
                // already collected this story
                continue;

            unique[items[i].parentStory.id] = true;
            items[i] = items[i].parentStory;
        }

        if ('function' === typeof items[i].changeGrep)
            stories.push(items[i]);

    }

    return stories;

};

Edit 2025-03-29: added handling of left-justified text; it will "harden" the justification using custom tracking and the lines are fixed, even if you expand the text frame. Like this:

3 replies

Robert at ID-Tasker
Legend
March 28, 2025

@Bedazzled532 

 

This one-liner does pretty much what you need - but as it modifies ALL text lines - it requires some cleaning:

 

app.activeDocument.stories.everyItem().lines.everyItem().insertionPoints[-1].contents = "\n";

app.findTextPreferences = NothingEnum.nothing; 
app.changeTextPreferences = NothingEnum.nothing; 
app.findTextPreferences.findWhat = "\r\n"; 
app.changeTextPreferences.changeTo = "\r"; 
app.activeDocument.changeText();  

Bedazzled532
Inspiring
March 28, 2025

@Robert at ID-Tasker Thanks it worked.

Robert at ID-Tasker
Legend
March 28, 2025

@Bedazzled532

 

Great 😉 but I'll have to "fix" it 😉 as you need to only modify a specific ParaStyle - piece of cake - but I'm not at home right now.

 

Willi Adelberger
Community Expert
Community Expert
March 28, 2025

Whalt purpose should those soft return have?

Robert at ID-Tasker
Legend
March 28, 2025
quote

Whalt purpose should those soft return have?


By @Willi Adelberger

 

It's a mockup - but it probably has something to do with the other thread - and changing Word spacing. 

 

m1b
Community Expert
m1bCommunity ExpertCorrect answer
Community Expert
March 28, 2025

Hi @Bedazzled532, try this script. You need to set the name of the target paragraph style in the settings object. Let me know if it works for you.

- Mark

 

/**
 * @file Force Line Breaks.js
 *
 * Adds a line break at the end of every line of text
 * of selected items or, optionally, the whole document.
 *
 * Notes:
 *   - only processes text in the target paragraph style
 *     (edit the settings object below).
 *   - makes a valiant attempt at hardening justified text,
 *     but only handles left-justified text.
 *
 * @author m1b
 * @version 2025-03-29
 * @discussion https://community.adobe.com/t5/indesign-discussions/soft-return-at-end-of-line/m-p/15236469
 */
function main() {

    var settings = {

        /* only paragraphs in this style will be processed (set to `undefined` to disable filtering) */
        paragraphStyleName: 'My Best Paragraph Style',

    };

    app.scriptPreferences.measurementUnit = MeasurementUnits.POINTS;

    const EXPAND_JUSTIFIED_TEXT_FRAME = 3,
        SQUASH_AMOUNT = -100;

    var doc = app.activeDocument,
        stories = getSelectedStories(doc.selection);

    if (0 === stories.length) {

        // check with user
        if (!confirm('Do you want to apply to whole document?'))
            return;

        // no selected stories, so get every story from document
        stories = doc.stories.everyItem().getElements();

    }

    var spaceCharacter = /\s/,
        linefeeds = /[\n\r]/;

    // we'll slightly expand the text frames later on
    var framesToAdjust = {};

    storyLoop:
    for (var i = stories.length - 1; i >= 0; i--) {

        var paragraphs = stories[i].paragraphs.everyItem().getElements();

        paragraphsLoop:
        for (var j = paragraphs.length - 1; j >= 0; j--) {

            var paragraph = paragraphs[j];

            if (
                undefined != settings.paragraphStyleName
                && settings.paragraphStyleName !== paragraph.appliedParagraphStyle.name
            )
                continue paragraphsLoop;

            var isJustified = (Justification.LEFT_JUSTIFIED === paragraph.justification);

            var lines = paragraph.lines.everyItem().getElements(),

                // store character positions
                positions = [];

            linesLoop:
            for (var k = lines.length - 1; k >= 0; k--) {

                var line = lines[k],
                    suffix;

                if (0 === line.parentTextFrames.length)
                    continue linesLoop;

                if (
                    linefeeds.test(line.characters[-1].contents)
                    || SpecialCharacters.FORCED_LINE_BREAK === line.characters[-1].contents
                    || !line.parentStory.characters[line.characters[-1].index + 1].isValid
                )
                    // last line in paragraph or story
                    continue linesLoop;

                suffix = !spaceCharacter.test(line.characters[-1].contents)
                    ? suffix = '-\n' // hyphenated word
                    : suffix = '\n';

                if (isJustified)
                    // store positions for later
                    positions.unshift(line.characters.everyItem().horizontalOffset);

                // force line break
                line.insertionPoints[-1].contents = suffix;

            }

            if (!isJustified)
                continue paragraphsLoop;

            // now we harden the justification, line by line

            // start by aligning left (let me know if you need RTL)
            paragraph.justification = Justification.LEFT_ALIGN;

            // collect all the parent text frames
            for (var f = 0; f < paragraph.parentTextFrames.length; f++) {

                var tf = paragraph.parentTextFrames[f];

                if (!framesToAdjust[tf.id])
                    framesToAdjust[tf.id] = tf;

            }

            // temporarily squash text up a bit to stop unwanted line breaking
            squashLoop:
            for (var k = paragraph.lines.length - 2; k >= 0; k--) {

                if (
                    !paragraph.lines[k].isValid
                    || 0 === paragraph.lines[k].parentTextFrames.length
                )
                    continue squashLoop;

                paragraph.lines[k].characters.itemByRange(
                    paragraph.lines[k].characters[0],
                    paragraph.lines[k].characters[-4]
                ).tracking = SQUASH_AMOUNT;

            }

            // refresh the lines reference
            lines = paragraph.lines.everyItem().getElements();

            linePositionsLoop:
            for (var l = 0; l < lines.length; l++) {

                var linePositions = positions[l];

                if (!linePositions)
                    continue linePositionsLoop;

                var len = Math.min(lines[l].characters.length, linePositions.length);

                charactersLoop:
                for (var c = 1; c < len; c++) {

                    var ch0 = paragraph.lines[l].characters[c - 1],
                        ch1 = paragraph.lines[l].characters[c],
                        pos = ch1.horizontalOffset,
                        target = linePositions[c];

                    if (undefined == target)
                        continue charactersLoop;

                    ch0.tracking += (target - pos) * (1000 / ch0.pointSize);

                }

            }

        }

    }

};
app.doScript(main, ScriptLanguage.JAVASCRIPT, undefined, UndoModes.ENTIRE_SCRIPT, 'Force Line Breaks');

/**
* Returns the document's selected stories.
* @author m1b
* @version 2025-03-26
* @param {Array<PageItem>} items - the items from which to gather stories.
* @returns {Array<Story>}
*/
function getSelectedStories(items) {

    var stories = [],
        unique = {};

    for (var i = 0; i < items.length; i++) {

        if ('TextFrame' === items[i].constructor.name) {

            if (unique[items[i].parentStory.id])
                // already collected this story
                continue;

            unique[items[i].parentStory.id] = true;
            items[i] = items[i].parentStory;
        }

        if ('function' === typeof items[i].changeGrep)
            stories.push(items[i]);

    }

    return stories;

};

Edit 2025-03-29: added handling of left-justified text; it will "harden" the justification using custom tracking and the lines are fixed, even if you expand the text frame. Like this:

Bedazzled532
Inspiring
March 28, 2025
@m1b thanks. I'll chk once i m infront of my computer.