PHP ODT Generator
Pure PHP library for generating OpenDocument Text (.odt) files. Functional component architecture with schema validation.
Requirements
- PHP 8.2+
zip extension
xml extension
Installation
composer require odt/generator
Quick Start
use Odt\Odt\Document;
Document::save(
['title' => 'My Document', 'creator' => 'My App'],
'output.odt',
Document::paragraph('Hello, World!'),
Document::centeredParagraph('Centered Title', ['fontSize' => '18pt', 'font-weight' => 'bold']),
);
Architecture
Components are plain arrays validated against schemas. The Document class provides static factory methods and a single root() / save() entry point.
src/Odt/
Core/
H.php # h()/hc() hyperscript + XmlElement
Schema.php # Schema definition (tag, attrs, children, validateFn)
Validator.php # Static registry, validate()/validateTree()
Schema/
ParagraphSchema.php
TableSchema.php
StyleSchema.php
Document.php # Static factories + root()/save()/send()
Render.php # Static XML generation
Zipper.php # Static ZIP packaging
Component.php # Function wrappers → Document::
API Reference
Document
| Method |
Description |
root(array $meta, ...$components) |
Build ODT, return ZIP data as string |
save(array $meta, string $path, ...$components) |
Build and write .odt file |
send(array $meta, string $filename, ...$components) |
Stream .odt to browser |
Component Factories
| Method |
Description |
paragraph(string $text, array $props) |
Add a paragraph |
centeredParagraph(string $text, array $props) |
Add centered paragraph |
rightParagraph(string $text, array $props) |
Add right-aligned paragraph |
spans(array $parts, array $props) |
Paragraph with inline styled spans |
lineBreak() |
Insert an empty line |
table(string $name, array $headers, array $rows, array $style) |
Add a table |
Paragraph Properties
| Property |
Description |
Example |
fontSize |
Font size |
'12pt', '14pt' |
font-weight |
Font weight |
'bold' |
font-style |
Font style |
'italic' |
color |
Text color |
'#2e75b6' |
align |
Alignment |
'left', 'center', 'right' |
Table Style
| Method |
Description |
tableStyle(array $props) |
Build a table style array |
thinBorder() |
0.05pt solid black border |
thickBorder() |
0.1cm solid black border |
noBorder() |
No visible border |
dashedBorder() |
Dashed line style |
dottedBorder() |
Dotted line style |
headerStyle(array $props) |
Configure header cell style |
| Property |
Description |
Example |
fontSize |
Font size |
'10pt', '8pt' |
font-weight |
Font weight |
'bold', 'normal' |
color |
Text color |
'#ffffff', '#333333' |
backgroundColor |
Cell background |
'#d9e2f3', '#f0f0f0' |
align |
Text alignment |
'left', 'center', 'right' |
Examples
Basic document
Document::save(
['title' => 'Report'],
'report.odt',
Document::paragraph('Introduction text here.'),
Document::paragraph('More content.', ['fontSize' => '14pt']),
);
Styled paragraphs
Document::save(
['title' => 'Styled'],
'styled.odt',
Document::paragraph('Normal text.'),
Document::centeredParagraph('Title', ['fontSize' => '18pt', 'font-weight' => 'bold']),
Document::centeredParagraph('Subtitle', ['fontSize' => '14pt', 'color' => '#2e75b6']),
Document::rightParagraph('Date: July 2026', ['font-style' => 'italic']),
);
Inline styled spans
Document::save(
['title' => 'Spans'],
'spans.odt',
Document::spans([
'This is ',
['text' => 'bold', 'font-weight' => 'bold'],
' and this is ',
['text' => 'colored', 'color' => '#ff0000'],
'.',
]),
);
Default bordered table
Document::save(
['title' => 'Inventory'],
'inventory.odt',
Document::paragraph('Product Inventory:'),
Document::table(
'Products',
['Name', 'Price', 'Stock'],
[
['Widget A', '$10.00', '100'],
['Widget B', '$25.00', '50'],
],
),
);
Custom styled table
Document::save(
['title' => 'Styled Table'],
'styled-table.odt',
Document::table(
'Sales',
['Product', 'Price', 'Qty'],
[
['Widget A', '$10.00', '100'],
['Widget B', '$25.00', '50'],
],
Document::tableStyle(
Document::thickBorder(
Document::headerStyle(['backgroundColor' => '#cc0000', 'color' => '#ffffff'])
)
),
),
);
Multiple tables with different styles
Document::save(
['title' => 'Multi Table'],
'multi-table.odt',
Document::table('Default', ['A', 'B', 'C'], [['1', '2', '3']]),
Document::lineBreak(),
Document::table(
'Minimal',
['Name', 'Value'],
[['Alpha', '100'], ['Beta', '200']],
Document::tableStyle(
Document::noBorder(
Document::headerStyle(['backgroundColor' => '#f0f0f0'])
)
),
),
);
Full document
Document::save(
['title' => 'Monthly Report', 'creator' => 'Reporting System'],
'report.odt',
Document::centeredParagraph('Monthly Sales Report', ['fontSize' => '20pt', 'font-weight' => 'bold']),
Document::centeredParagraph('July 2026', ['fontSize' => '14pt', 'color' => '#2e75b6']),
Document::lineBreak(),
Document::paragraph('This report summarizes sales data for the month.'),
Document::lineBreak(),
Document::table(
'Sales',
['Product', 'Units Sold', 'Revenue'],
[
['Product A', '150', '$4,500'],
['Product B', '89', '$3,560'],
['Product C', '210', '$6,300'],
],
Document::tableStyle(
Document::thinBorder(
Document::headerStyle(['backgroundColor' => '#e8e8e8'])
)
),
),
Document::lineBreak(),
Document::paragraph('Total revenue: $14,360', ['font-weight' => 'bold']),
);
Schema validation
use Odt\Odt\Core\Validator;
$errors = Validator::validate(Document::paragraph('Hello'));
if ($errors) {
foreach ($errors as $error) {
echo "Validation error: {$error}\n";
}
}