Skip to content

RichText & Paragraphs

Template variables are ideal when LibreOffice already contains the document structure. RichText and Paragraph are the foundation for sections whose structure must be assembled in PHP.

A RichText object is a container. It can hold paragraphs, images, tables, lists, and other ODT elements. A Paragraph represents a native ODT paragraph and can contain differently styled text runs, line breaks, tabs, hyperlinks, and embedded elements.

Build a generated section

Suppose the template contains:

{{intro}}

Build the replacement in PHP:

use OdtTemplateEngine\Elements\Paragraph;
use OdtTemplateEngine\Elements\RichText;

$paragraph = new Paragraph();
$paragraph
    ->addText('Important: ', ['bold' => true])
    ->addText('this text remains editable.', ['italic' => true]);

$intro = new RichText();
$intro->addParagraph($paragraph);

$template->setElement('intro', $intro);

The result is native ODT content, not HTML pasted into the document.

RichText as a document-section container

A RichText block can combine several kinds of content:

$section = new RichText();

$section->addParagraph('Profile');
$section->addParagraph(
    'Experienced developer with a focus on document automation.'
);
$section->addBulletList([
    'PHP development',
    'OpenDocument generation',
    'Document workflows',
]);

$template->setElement('profile', $section);

For larger sections, build explicit Paragraph, ListElement, ImageElement, or RichTable objects and add them to the same container.

Paragraph text styles

Paragraph::addText() accepts an inline text-style array:

$paragraph = new Paragraph();
$paragraph
    ->addText('Normal text. ')
    ->addText('Bold text. ', ['bold' => true])
    ->addText('Colored text.', [
        'color' => '#12324a',
        'font-size' => '11pt',
        'font-family' => 'Arial',
    ]);

Common text options include:

[
    'bold' => true,
    'italic' => true,
    'underline' => true,
    'color' => '#333333',
    'background-color' => '#fff4cc',
    'font-size' => '11pt',
    'font-family' => 'Arial',
]

These describe the text itself. Paragraph geometry is a separate concern.

Paragraph styles

A paragraph style controls properties such as spacing, alignment, indentation, borders, padding, and line height.

$paragraph = new Paragraph(null, [
    'margin-top' => '0.3cm',
    'margin-bottom' => '0.1cm',
    'text-align' => 'left',
    'line-height' => '110%',
    'border-left' => '2pt solid #12324a',
    'padding-left' => '0.2cm',
]);

$paragraph->addText('A styled paragraph.');

The important distinction is:

Paragraph style
├── spacing
├── alignment
├── indentation
├── borders
├── padding
└── line height

Text style
├── font family
├── font size
├── bold / italic
├── underline
└── color

Keeping these responsibilities separate makes complex documents easier to reason about and produces cleaner ODF structures.

Named paragraph styles

For a reusable semantic style, define a meaningful named paragraph style on the current document, then reference that name from paragraphs:

$template->styles()->defineParagraph('DocumentSectionHeading', [
    'margin-top' => '0.4cm',
    'margin-bottom' => '0.1cm',
    'border-bottom' => '1.5pt solid #12324a',
    'padding-bottom' => '0.03cm',
]);

$heading = new Paragraph('DocumentSectionHeading');
$heading->addText('Experience', [
    'bold' => true,
    'font-size' => '12pt',
]);

The definition is document-local and semantically owned by the current document. new Paragraph('DocumentSectionHeading') is only a named style reference; it does not define or register the style. This pattern is useful in larger documents because generated ODT styles retain understandable names such as DocumentSectionHeading instead of being only implementation-generated identifiers.

For one-off paragraph styling, pass friendly style options directly to the Paragraph constructor. The document-style facade is for reusable generated named paragraph definitions.

Line breaks and tabs

A paragraph can contain native ODT line breaks and tabs:

$paragraph = new Paragraph();
$paragraph
    ->addText('First line')
    ->addLineBreak()
    ->addText('Second line')
    ->addTab()
    ->addText('After tab');

For aligned tabular text, define tab stops:

$paragraph = new Paragraph();
$paragraph
    ->addTabStopDefinition(4.0, 'left')
    ->addTabStopDefinition(10.0, 'right')
    ->addText('Item')
    ->addTab()
    ->addText('129.90');

Tabs are useful for smaller aligned text structures. Use native ODT tables when the content is genuinely tabular or needs stable cell structure.

Paragraph can add native hyperlinks:

$paragraph->addHyperlink(
    'Project website',
    'https://example.com',
    ['color' => '#1a5fb4', 'underline' => true]
);

Adding arbitrary ODT elements

RichText::addElement() accepts an OdtElement:

$richText->addElement($list);
$richText->addElement($table);

Images can also be placed inside paragraphs when inline placement is needed:

$paragraph->addElement($image);

This compositional model is what makes RichText useful for larger generated regions.

For a complex section, prefer a small rendering method that receives application data and returns a RichText block:

function buildProfile(array $profile): RichText
{
    $content = new RichText();

    $heading = new Paragraph();
    $heading->addText('Profile', ['bold' => true]);
    $content->addParagraph($heading);

    $content->addParagraph($profile['summary']);

    return $content;
}

$template->setElement('profile', buildProfile($profile));

This keeps application data separate from ODT rendering and prevents the LibreOffice template from accumulating large amounts of presentation logic.

Continue with Lists, Tables, and Images for structured child elements.