Skip to content

Latest commit

 

History

49 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PForm2 — Report Engine

Part of the Olevsoft toolset — used in production by IMS2, a warehouse management system (live demo, login admin / USR). See also UIKit (UI components) and IMSD (desktop distribution).

User Guide


Chapter 1. Introduction

What is PForm2

PForm2 is a report engine that turns an HTML template and data into a print-ready document. Templates are designed in the visual Editor2 — you see a table, place fields, format cells, and get a finished report.

How it works

                  ┌──────────────┐
                  │  Editor2     │  ← you design the template here
                  │  (browser)   │
                  └──────┬───────┘
                         │ saves
                         ▼
                  ┌──────────────┐
                  │  Template    │  ← .html file with blocks and fields
                  │  (.html)     │
                  └──────┬───────┘
                         │ + data from database
                         ▼
                  ┌──────────────┐
                  │ ReportEngine │  ← PHP engine renders the report
                  │   (PHP)      │
                  └──────┬───────┘
                         │
                         ▼
                  ┌──────────────┐
                  │  Output      │  ← printable HTML page
                  │  (HTML)      │
                  └──────────────┘

Chapter 2. Concepts

Blocks

A report consists of blocks. Each block is a group of table rows that prints at a specific moment. Similar to bands in FastReport.

Block names

The engine does not impose any naming rules — you can name blocks anything you want. However, these names are commonly used by convention:

Name Typical usage
header Report/document header
colheads Column headers
data Data rows
endReport Footer with totals
pagefooter Bottom of page
serial Sub-detail rows
groupheader / groupfooter Group breaks

These are just conventions — the engine treats all block names equally. Your PHP code decides which block to render and when.

The Marker Column

The first column (column A) in every template is special — it contains block names. This column is never printed. It tells the engine where each block starts and ends.

Column A          Column B              Column C
──────────────────────────────────────────────────
{header}
                  INVOICE               Date: {$date}
                  Customer: {$customer}
{/}
──────────────────────────────────────────────────
{data}
                  {$item_name}          {$price:f2}
{/}
──────────────────────────────────────────────────
  • {blockname} — starts a block
  • {/} — ends a block
  • Everything between them belongs to the block

Important! Each block is a separate <table> in the HTML file. This means each block can have different column count and widths.

Fields

Fields are placeholders that get replaced with actual data values. They are written as {$field_name}.

{$customer_name}     →  "Acme Corporation"
{$total:f2}          →  "1,234.50"
{$__rownum__}        →  "1", "2", "3", ...

Chapter 3. Field Formatting

Syntax

{$field_name:flag1,flag2,...}

Flags are added after the colon, separated by commas.

Numeric formatting

Flag Example Result Description
f2 {$price:f2} 12.50 2 decimal places
f4 {$qty:f4} 3.0000 4 decimal places
f0 {$count:f0} 150 No decimals

Display control

Flag Effect
z Suppress zeros — empty string instead of "0"
s Suppress repeats — show value only when it changes
nowrap Never break this field's text mid-word — wraps output in <span style="white-space:nowrap">

Example:

{$group_name:s}     — prints group name only on first row

{$qty:z,f2}         — "15.00" or "" (not "0.00")

{$title:nowrap}     — long header text stays on one line

nowrap exists because block tables (setBlockTables(true)) force word-break:break-all on every <td> so long values don't overflow narrow columns — fine for body text, but it can split short labels (report titles, codes) mid-character on tight columns. Use nowrap on the specific fields that must stay intact; {$__rownum__} gets this behavior automatically without the flag.

Text formatting

Flag Effect
upper UPPERCASE
lower lowercase
max20 Truncate to 20 characters
date Format as date (DD.MM.YYYY)
datetime Format as date+time

System fields

These fields are filled automatically.

Field Value
{$__rownum__} Row number (1, 2, 3...)
{$__page__} Page number
{$__date__} Current date
{$__time__} Current time

Chapter 4. Accumulators (Totals)

What are accumulators

Accumulators automatically sum up field values as data rows are printed. When you need a total at the bottom of the report — accumulators do it for you.

How to use

Step 1. Mark fields for accumulation

In the {data} block, add SUM(N) flag to the field. N is the accumulator level (1, 2, 3...).

{$qty:SUM(1),f2}       — accumulates qty into level 1
{$amount:SUM(2),f2}    — accumulates amount into levels 1 and 2

SUM(2) means: accumulate into level 1 AND level 2 simultaneously.

Step 2. Output totals

In the {endReport} or footer block, use {$SUM($field,N)} to output the accumulated value.

{$SUM($qty,1):f2}      — outputs total qty, resets level 1
{$SUM($amount,1):f2}   — outputs total amount, resets level 1

Important! After outputting, the accumulator is automatically reset to zero.

Complete example

{data}
  #     Item              Qty          Amount
        {$item}           {$qty:SUM(1),f2}   {$amount:SUM(1),f2}
{/}

{endReport}
  TOTAL:                  {$SUM($qty,1):f2}  {$SUM($amount,1):f2}
{/}

If data is: Laptop (2, $1998), Mouse (10, $290), Monitor (3, $1497):

Result:

  #     Item              Qty          Amount
  1     Laptop            2.00         1998.00
  2     Mouse             10.00        290.00
  3     Monitor           3.00         1497.00

  TOTAL:                  15.00        3785.00

Multi-level accumulators

Use multiple levels for subtotals within groups. Level 1 resets at group breaks, level 2 keeps the grand total.

{data}
        {$category:s}    {$item}    {$amount:SUM(2),f2}
{/}

{groupfooter}
        Subtotal:                   {$SUM($amount,1):f2}
{/}

{endReport}
        Grand total:                {$SUM($amount,2):f2}
{/}

Chapter 5. Pagination

Setting page size

$gen->setPageSize(40);  // 40 data rows per page

When data exceeds the page size, the engine automatically starts a new page.

Repeating headers

To repeat column headers on each new page:

$gen->onStartPage('colheads');

Now colheads block will be printed at the top of every page.

Manual page break

$gen->newPage();

Chapter 6. Using the Editor2

Starting the editor

Start servers first:

ims
# Or: ~/Desktop/start_servers.sh

Open in browser: http://localhost:8001/editor2/index.html

Opening a template directly

You can open a specific template by adding ?file= parameter to the URL:

http://localhost:8001/editor2/index.html?file=ims2/reports/company_invoice/company_invoice.html

The path is relative to the base directory (/home/evge/Desktop/). The template loads automatically when the page opens.

Browsing a directory

Open editor with a file panel showing all templates in a directory:

http://localhost:8001/editor2/index.html?dir=ims2/reports

A panel appears on the left with all .html files. Click any file to load it. You can combine both parameters:

http://localhost:8001/editor2/index.html?dir=ims2/reports&file=ims2/reports/company_invoice/company_invoice.html

Creating a new template

Step 1. Add blocks

  1. Click "Add Block" in the toolbar
  2. Enter the block name (e.g. header)
  3. The block appears as a separate grid with its own columns

Repeat for each block you need: header, colheads, data, endReport.

Step 2. Enter content

  1. Click on a cell
  2. Type text or a field placeholder: {$customer_name}
  3. Press Enter to confirm or Escape to cancel

Tip: You can start typing immediately — just click a cell and type. No need to press Enter first.

Step 3. Format cells

Use the formatting toolbar above the grid:

  • Bold (Ctrl+B) / Italic (Ctrl+I)
  • Font size — select from dropdown (8-24px)
  • Alignment — left, center, right
  • Text color / Background color — pick from palette
  • Borders — 15 types (top, bottom, left, right, all, none, etc.) with thickness (thin, medium, thick)

Step 4. Adjust column widths

Drag the column border in the header row left or right.

Note: Each block has independent columns. If you need the same widths across blocks, use Link Blocks.

Step 5. Preview

  1. Click "View" button
  2. Paste test data in JSON format into the text area
  3. See the rendered report

Step 6. Save

  1. Click "Save"
  2. Enter file path (e.g. reports/invoice/invoice.html)
  3. The file is saved to the server

Editing operations

Rows

Action How
Insert row below Shift+Enter
Delete row Alt+Delete
Move row up/down Alt+Shift+Up/Down

Blocks

Action How
Move block up/down Alt+Up/Down
Link blocks (sync widths) Link Blocks button

Formatting

Action How
Bold Ctrl+B
Italic Ctrl+I
Copy format to row Fmt → Row button

Undo / Redo

Action How
Undo (60 levels) Ctrl+Z
Redo Ctrl+Y

Navigation

Key Action
Arrow keys Move between cells
Tab / Shift+Tab Next/previous cell (cross-block)
Enter / F2 Start editing
Escape Cancel editing
Delete / Backspace Clear cell

Page guide

The dashed vertical line shows the page width boundary:

  • A4 portrait — 740px
  • A4 landscape — 1050px

Chapter 7. Writing a Report Script

Minimal example

<?php
require_once '/path/to/PForm2/ReportEngine/src/ReportEngine.php';

// 1. Create engine and load template
$gen = new ReportEngine();
$gen->read(__DIR__ . '/my_report.html');
$gen->setBlockTables(true);

// 2. Open document
echo $gen->startDocument();

// 3. Print header
echo $gen->renderBlock('header', [
    'title' => 'Monthly Report',
    'date'  => date('d.m.Y'),
]);

// 4. Print data rows
foreach ($data as $row) {
    $gen->rowNumber++;
    $gen->print('data', $row);
}

// 5. Print footer with totals
echo $gen->renderBlock('endReport', []);

// 6. Close document
echo $gen->endDocument();

With pagination and column headers

$gen = new ReportEngine();
$gen->read(__DIR__ . '/invoice.html');
$gen->setBlockTables(true);
$gen->setPageSize(40);                       // 40 rows per page
if ($gen->hasBlock('colheads'))
    $gen->onStartPage('colheads');           // repeat on each page

echo $gen->startDocument();
echo $gen->renderBlock('header', $headerData);
echo $gen->renderBlock('colheads', []);

foreach ($items as $item) {
    $gen->rowNumber++;
    $gen->print('data', $item);              // auto page break + colheads repeat
}

echo $gen->renderBlock('endReport', []);
echo $gen->endDocument();

With master-detail

// Register sub-detail handler
$gen->onSub('data', 'serialhdr', 'serial', function($parentRow) use ($conn) {
    // return detail rows for this parent
    $stmt = $conn->prepare("SELECT * FROM serials WHERE item_id = ?");
    $stmt->execute([$parentRow['item_id']]);
    return $stmt->fetchAll(PDO::FETCH_ASSOC);
});

// Now print('data', ...) will automatically render serial rows after each item

With group breaks

foreach ($data as $row) {
    // Check if group changed
    if ($gen->checkBreak(1, $row['category'])) {
        // Print group footer for previous group
        echo $gen->renderBlock('groupfooter', []);
        // Print group header for new group
        echo $gen->renderBlock('groupheader', ['category' => $row['category']]);
    }

    $gen->rowNumber++;
    $gen->print('data', $row);
}
// Don't forget the last group footer
echo $gen->renderBlock('groupfooter', []);

HTML page wrapper

Every report PHP script should output a complete HTML page with print-friendly CSS:

echo '<!DOCTYPE html><html><head><meta charset="utf-8">
<title>My Report</title>
<style>
  body { margin:0; padding:0; background:#e0e0e0 }
  .page { background:#fff; width:800px; margin:20px auto;
          padding:30px 25px; box-shadow:0 2px 8px rgba(0,0,0,.25) }
  @media print {
    body { background:#fff }
    .page { margin:0; padding:15mm; width:auto; box-shadow:none }
  }
</style></head><body><div class="page">';

// ... report output ...

echo '</div></body></html>';

Chapter 8. ReportEngine Reference

Template loading

Method Description
read($path) Load template from file
loadFromHtml($html) Load from string
include($path, $override) Merge blocks from another template

Configuration

Method Description
setBlockTables($bool) Each block = independent table with own widths
setPageSize($lines) Rows per page. 0 = no pagination
onStartPage($block) Repeat block on each new page
onSub($parent, $subHdr, $subData, $fn) Register master-detail
hasBlock($name) Check if block exists
getBlockNames() List all blocks
getConfig($key) Read from {config} block

Rendering

Method Description
startDocument() Opening HTML tags
endDocument() Closing HTML tags
renderBlock($name, $vars) Render block, return HTML
print($name, $vars) echo + auto-pagination + sub-details
newPage() Force page break

Accumulators

Method Description
getAcc($field, $N) Read accumulator value
popAcc($field, $N) Read and reset
resetAcc($field, $N) Reset to zero
getAllAccumulators() All accumulators as array

Group breaks

Method Description
checkBreak($N, $value) Returns true when value changes
resetBreak($N) Reset break state

State properties

Property Description
$rowNumber Current row number (set by you)
$pageNumber Current page (read-only)
$linesLeft Lines remaining on page
$lineNumber Total lines rendered

Chapter 9. Editor1 (Simple)

Editor1 is a simpler version of Editor2. It uses a single table for all blocks. Block names go in column A (the marker column), just like in Editor2.

When to use Editor1: Simple reports where all blocks have the same column widths.

When to use Editor2: Complex reports with different column layouts per block (invoices, multi-section reports).

URL: http://localhost:8000/editor/index.html


Chapter 10. Text Templates (Matrix Printers)

What are text templates

TextTemplateParser is a separate engine for plain-text reports. It generates raw text output with ESC-codes for matrix/dot-matrix printers (Epson, Star, Okidata, etc.).

Unlike the HTML engine (ReportEngine), text templates produce fixed-width columnar output suitable for direct printing without a browser.

Template syntax

Blocks use named tags (not the marker column):

{header}
  WAREHOUSE REPORT
  Date: {$date}
{/header}

{data}
  {$item_name:L30}  {$qty:R8,z}  {$price:R10}
{/data}

{footer}
  Total: {$total:R10,B}
{/footer}

Field formatting

Format Example Description
L20 {$name:L20} Left-aligned, 20 characters wide
R10 {$price:R10} Right-aligned, 10 characters wide
C15 {$title:C15} Centered, 15 characters wide
z {$qty:R8,z} Suppress zeros (empty instead of "0")
dup {$group:L20,dup} Suppress duplicates (show only when value changes)
B {$total:R10,B} Bold (ESC-code double-strike)

Formats can be combined: {$amount:R10,z,B} — right-aligned, suppress zeros, bold.

Font codes

Set default font for the entire template:

{setfr B}
Code Effect
N Normal (default)
B Bold — ESC E / ESC F (double-strike)
U Underline — adds underline row below text
D Double-height — adds empty line after each line

Usage in PHP

$parser = new TextTemplateParser('/path/to/template.txt');

// Print header
echo $parser->renderBlock('header', [
    'date' => date('d.m.Y'),
]);

// Print data rows
foreach ($items as $item) {
    echo $parser->renderBlock('data', [
        'item_name' => $item['name'],
        'qty'       => $item['qty'],
        'price'     => $item['price'],
    ]);
}

// Reset duplicate suppression (e.g. on new page)
$parser->resetDuplicateState();

echo $parser->renderBlock('footer', [
    'total' => $grandTotal,
]);

API

Method Description
new TextTemplateParser($path) Load text template from file
renderBlock($name, $data) Render block with data, return text string
hasBlock($name) Check if block exists
getBlocks() List all block names
resetDuplicateState() Reset dup suppression (for new page/group)
setColumnSeparator($char) Set column separator (e.g. '|')
getType() Returns 'text'

Output example

Template:

{data}
{$name:L20}  {$qty:R6}  {$price:R10}
{/data}

Data: Laptop/2/999, Mouse/10/29

Output:

Laptop                    2      999.00
Mouse                    10       29.00

Chapter 11. Testing

ReportEngine has a test suite with 51 tests covering template compilation, accumulators, pagination, and group breaks.

cd ReportEngine
composer install
./vendor/bin/phpunit

Appendix A. File Structure

PForm2/
├── ReportEngine/
│   ├── src/
│   │   ├── ReportEngine.php        — main engine
│   │   ├── TemplateCompiler.php    — template parser
│   │   └── TextTemplateParser.php  — plain text engine
│   └── tests/                      — PHPUnit tests
│
├── editor2/                        — visual multi-grid editor
│   ├── index.html                  — editor application
│   ├── save.php                    — save template
│   ├── load.php                    — load template
│   └── browse.php                  — file browser
│
├── editor/                         — simple single-table editor
│   ├── index.html
│   └── save.php
│
└── examples/                       — usage examples
    ├── company_invoice.html        — template example
    ├── report.php                  — PHP render example
    └── template.html               — basic template

Appendix B. Quick Reference Card

Template syntax

{blockname}            — start block
{/}                    — end block
{$field}               — data field
{$field:f2}            — 2 decimal places
{$field:z}             — suppress zeros
{$field:s}             — suppress repeats
{$field:nowrap}        — never break this field's text mid-word
{$field:SUM(N),f2}     — accumulate into levels 1..N
{$SUM($field,N):f2}    — output accumulated total
{$__rownum__}          — row number
{$__rownum__,N}        — row number, width reserved for N digits
{$__page__}            — page number

PHP calls

$gen = new ReportEngine();
$gen->read('template.html');
$gen->setBlockTables(true);
$gen->setPageSize(40);
$gen->onStartPage('colheads');

echo $gen->startDocument();
echo $gen->renderBlock('header', $vars);
echo $gen->renderBlock('colheads', []);
foreach ($data as $row) {
    $gen->rowNumber++;
    $gen->print('data', $row);
}
echo $gen->renderBlock('endReport', []);
echo $gen->endDocument();

Editor2 shortcuts

Ctrl+B/I          — bold/italic
Ctrl+Z/Y          — undo/redo
Enter/F2          — edit cell
Escape            — cancel
Shift+Enter       — insert row
Alt+Delete        — delete row
Alt+Shift+Up/Down — move row
Alt+Up/Down       — move block
Tab/Shift+Tab     — next/prev cell

PForm2 — HTML template engine for business reports.

License: Free for any use.

About

PForm2 — HTML/CSS report engine for print-ready documents (PDF/XLSX export, marker-column templating). Part of the Olevsoft toolset.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages