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:
