Skip to content

Paragraphs, line changes and page breaks

var letter = Letter.builder()
.body("Dear Ana,\n\nFirst paragraph.\nSecond line of the same paragraph.")
.body("A new paragraph from a new body argument.", "Another paragraph.")
.pageBreak()
.body("Page two")
.build();

Plain text rules:

Input Result
Single \n New line in the same paragraph
Blank line (\n\n) New paragraph
A separate string argument Separate paragraph
CRLF or CR Normalized to newline
Letter.PAGE_BREAK or \f inside body text New page
.pageBreak() New page between body blocks

Consecutive, leading and trailing explicit page breaks are preserved. That can intentionally create blank pages. Page breaks are valid only in body content, not headers, footers or signatures.

In HTML, ordinary whitespace/newlines collapse. Use <p> for paragraphs, <br> for a line change, and paired <page-break></page-break> for a page change. Do not use <page-break/>; it is a custom HTML element, not a void tag. A newline in a replacement value follows these HTML whitespace rules. For a multiline address, put <br> between separate placeholders, or use the plain Java header method.

Content appends in call order: body(...).table(...).bodyHtml(...).body(...). Header, footer and signature content calls replace the prior content of that region. To combine several items in a region, use one rich fragment, one block array, or one signature call with multiple parts.

import com.nsdev.lettergenerator.model.layout.Keep;
import com.nsdev.lettergenerator.model.block.GroupBlock;
var policy = Style.builder()
.keepTogether(Keep.PREFER)
.keepWithNext(Keep.AUTO)
.widows(3).orphans(3)
.build();
var together = new GroupBlock(List.of(firstBlock, secondBlock),
Style.builder().keepTogether(Keep.ALWAYS).build());
var letter = Letter.builder().block(together).group("Address line 1\nAddress line 2").build();

AUTO allows splitting, PREFER requests a breakable keep, and ALWAYS requests a strong keep. The no-argument style methods keepTogether() and keepWithNext() select ALWAYS. Headings ordinarily keep with their successor; an explicit keepWithNext(Keep.AUTO) disables that default. keepWithNext links a block’s end to the next block’s beginning; use a kept group, or keep the successor together, when its whole content must move with it. Oversized strong keeps can cause layout rejection; the library does not shrink them.

Groups inherit typography into their children, apply margins around the group, and keep their pagination controls on the group itself. Their ordinary policy is PREFER. group(Block...) and group(String...) are fluent shortcuts. Explicit AUTO groups and ordinary grouping divs in body flow may contain page breaks; kept groups, signatures, fixed content and table cells cannot.

<group style="keep-together:always">
<h2>Account details</h2><p>These details should travel with the heading.</p>
</group>
<p style="keep-with-next:always">Please review the following:</p>
<p style="keep-together:prefer;widows:3;orphans:3">A naturally wrapped paragraph...</p>

Widows are the minimum trailing lines and orphans the minimum opening lines of a naturally wrapped paragraph. Counts must be positive integers. FOP treats forced newlines and br as separate layout paragraphs, so widow/orphan counts do not span those forced boundaries. Use a kept group or keep-together for content with explicit line breaks. These are native layout controls, subject to available space and FOP’s supported behavior.

For typed page fields, use TextRun.currentPage() and TextRun.pageCount() among ordinary runs. In JSON, set "text":"" and "pageNumber":"CURRENT" or "TOTAL". Totals include an automatically recovered final signature page. Page fields work in any text run, including headers, footers and cells.