Rendering Pipeline and Full C++ Architecture in ksTFL
ksTFL Development Team
2026-06-19
Source:vignettes/Rendering_Pipeline.Rmd
Rendering_Pipeline.RmdThis document describes the complete C++ renderer architecture used by ksTFL to convert JSON report specifications into deterministic DOCX output.
1. End-to-End Architecture
R API
-> src/rcpp_bindings.cpp
-> kstfl::Renderer
Renderer pipeline
Parse -> Resolve -> Model -> Measure -> Paginate -> Emit -> Package
Core modules
json_parser.cpp : spec/template/data parsing
style_resolver.cpp : style cascade + page/width resolution
logical_table.cpp : header grid + logical row stream + styleRows actions
font_scanner.cpp : system font discovery + fallback resolution
text_measurer.cpp : HarfBuzz-based deterministic measurement
paginator.cpp : vertical and horizontal pagination
docx_*.cpp : OOXML emission for document parts
zip_writer.cpp : DOCX ZIP packaging
2. C++ Module Map
2.1 Entry and orchestration
- src/rcpp_bindings.cpp
- render_docx_impl()
- render_docx_from_strings_impl()
- src/kstfl/renderer.cpp
- Renderer::render()
- Renderer::render_from_strings()
2.2 Parsing and schema model
- src/kstfl/json_parser.cpp
- parse_spec_json_string()
- parse_template_json_string()
- parse_data_json_string()
- src/kstfl/types.h
- Complete domain model (page config, styles, table model, pagination model)
2.3 Layout and table engine
- src/kstfl/style_resolver.cpp
- resolve_page_config()
- resolve_table_width()
- resolve_column_widths()
- style cascade resolution helpers
- src/kstfl/logical_table.cpp
- LogicalTableBuilder::build()
- build_header_grid(), build_data_rows()
- apply_dedupe(), apply_style_rows(), detect_grouping_boundaries()
2.4 Font discovery, measurement, and pagination
- src/kstfl/font_scanner.cpp
- Platform-specific system font directory enumeration
- FreeType-based font family/style classification
- Global font path map (populated once at package load)
- Target font resolution with fallback assignment
- src/kstfl/font_cache.cpp
- FreeType/HarfBuzz font loading + metrics cache
- Dynamic font resolution via scanner’s global path map
- src/kstfl/text_measurer.cpp
- TextMeasurer::measure_plain()
- measure_cell() with wrapping, spacing, margins, orientation support
- src/kstfl/paginator.cpp
- Paginator::paginate()
- build_segments(), compute_available_height()
- compute_row_heights(), compute_segment_row_heights()
3. Detailed Rendering Pipeline
┌───────────────────────────────────────────────────────────────────────┐
│ Phase 1: Parse │
│ parse_template_bundle() │
│ parse_spec_json_string() -> TFLDocument │
│ parse_data_json_string() -> DataTable │
│ resolve figure media paths for Figure specs │
│ │
│ Phase 1b: Enforce isColBreak layout constraints │
│ for specs with isColBreak columns: force │
│ allow_row_break_across_pages=false, repeat_header_on_each_page=true │
│ (warn if template values were overridden) │
└───────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐
│ Phase 2: Initialize text subsystem │
│ FontCache: load configured font dirs and fallback font │
│ TextMeasurer(font_cache) │
└───────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐
│ Phase 3: Per-spec processing │
│ │
│ 3a) Resolve │
│ StyleResolver(template, spec_styles) │
│ resolve_page_config() │
│ resolve_table_width() │
│ resolve_column_widths() │
│ │
│ 3b) Model │
│ LogicalTableBuilder::build() │
│ - header grid + vMerge metadata │
│ - logical data rows │
│ - styleRows actions (c_style, c_clear, c_merge, c_glue, │
│ c_addrow, c_pageBreak) │
│ │
│ 3c) Measure │
│ Header rows: vMerge-aware 2-pass height balancing │
│ Body cells: TextMeasurer::measure_plain() │
│ - inline markup parsing │
│ - HarfBuzz shaping │
│ - deterministic wrapping and line-height math │
│ │
│ 3d) Paginate │
│ Paginator::paginate() │
│ - build_segments(): split by isColBreak, repeat isID columns │
│ - compute_row_heights(): baseline full-table heights │
│ - compute_segment_column_widths(): segment width scaling │
│ - compute_segment_row_heights(): per-segment row heights │
│ - compute static blocks: titles/subtitles/header/footer/notes │
│ - vertical fill + LastPage post-pass (deterministic mode only) │
│ - row-break mode keeps natural flow; no LastPage reshuffling │
│ │
│ 3e) Dedupe restoration │
│ Restore deduped values at page boundaries │
└───────────────────────────────────────────────────────────────────────┘
┌───────────────────────────────────────────────────────────────────────┐
│ Phase 4: Emit and package DOCX │
│ DocxEmitter::emit() │
│ - document.xml, styles.xml, rels, header/footer parts │
│ - page/section assembly, table/header/body emission │
│ - optional TOC │
│ ZipWriter: package all OOXML parts into .docx │
└───────────────────────────────────────────────────────────────────────┘
4. Core Data Structures
Defined primarily in src/kstfl/types.h.
-
Units and geometry:
Length,PageConfig,Margins -
Style system:
FontProps,ParagraphProps,TableCellProps,StyleDef,StylesTemplate -
Table model:
ColumnSpec,StubColumn,TextGroup,LogicalCell,LogicalRow,HeaderGrid -
Pagination model:
PageSlice,HorizontalSegment,PaginationResult
5. Text and Font Subsystem
-
FontCachereads font metrics from OS/2 tables, withhheaas fallback, to match Word line-height behavior. -
TextMeasureruses HarfBuzz shaping for deterministic run widths and wrapping. - Cell height includes paragraph spacing, wrapped line heights, and top/bottom cell margins.
- Rotated text paths (
btLr,tbRl) are handled explicitly.
6. Pagination Internals
- Vertical pagination starts from usable page height, then subtracts the safety margin, header/footer overflow, titles, table header, and reserved footnote space.
- Horizontal segmentation starts a new segment at
isColBreak, repeatsisIDcolumns in each segment, and scales non-ID columns to the remaining width. WhenisColBreakis active, the renderer also enforcesallow_row_break_across_pages = FALSEandrepeat_header_on_each_page = TRUE. - Last-page footnotes use two paths: deterministic mode keeps them on the true final page through a post-pass, while row-break mode keeps natural Word flow and skips that reshuffling.
- Row heights are tracked both at the full-table level and at the
segment-specific level so emitted
trHeightvalues remain exact. - Warning thresholds compare against the physical page body height, while pagination still uses a safety margin for conservative fitting.
7. Style Resolution Cascade
The renderer applies styles in this effective order (later layers override earlier ones unless marked structural/non-overridable):
- Template default text style
- Region style (tableHeader, tableBody, titles, subtitles, footnotes)
- Template row defaults
- Structural styles (header/body structural constraints)
- Column labelStyleRef or valueStyleRef
- Row action style (styleRows c_style)
- Merge/action style overrides
- AddRow style overrides
8. R/C++ Integration
- Exported functions in src/rcpp_bindings.cpp
- render_docx_impl(…)
- render_docx_from_strings_impl(…)
- Registration in src/init.cpp and src/RcppExports.cpp.
- Error propagation uses RenderError and Rcpp::stop().
9. Build and Dependencies
- Language standard: C++20
- C++20 features used:
-
operator<=>(three-way comparison) forLength - Concepts (
Mergeable) constraining style merge templates -
constexpr std::arraylookup tables for OOXML enum conversions -
std::ranges::sort,std::ranges::any_of,std::ranges::transform -
using enumin switch statements -
std::to_charsfor double formatting -
[[nodiscard]]on all pure/value-returning functions
-
- Core dependencies:
- HarfBuzz (vendored source)
- FreeType2 (vendored source)
- minizip (vendored source)
- zlib
- nlohmann/json (vendored)
- Rcpp
- Build files:
- src/Makevars
- src/Makevars.win
- Linux build model:
-
src/Makevarsalways compiles vendored HarfBuzz, FreeType, and minizip sources undersrc/vendor/ - No
pkg-configprobe or system-library fallback is used on Linux - Windows continues to use
src/Makevars.winwith Rtools-provided static libraries
-
10. Practical Trace (One Table Spec)
spec json + data json + template json
-> Renderer::render_from_strings()
-> StyleResolver::resolve_*()
-> LogicalTableBuilder::build()
-> TextMeasurer::measure_plain()
-> Paginator::paginate()
-> segment row heights
-> DocxEmitter::emit_table_row()
-> exact trHeight from segment.row_heights
-> ZipWriter::close()
-> output.docx