Changelog
Source:NEWS.md
ksTFL 0.11.7
Parallel spec processing
- Renderer Phase 3 (Resolve → Model → Measure → Paginate) now executes per-spec tasks in parallel using C++
std::futureandstd::counting_semaphore. - Each worker thread creates its own
FontCacheandTextMeasurer; Phase 4 (DOCX assembly) remains single-threaded and sequential. - Verbose logging is suppressed on worker threads to avoid unsafe Rcpp I/O.
Vendor fix: nlohmann/json macOS warnings
- Patched vendored
nlohmann/json.hpp3.12.0 to eliminatechar_traits<unsigned char>deprecation warnings on macOS (Apple Clang 21+). - The
output_adapterostream constructor is now a constrained member template (requiresclause onC) sostd::basic_ostream<uint8_t>is never instantiated as a dependent type during class template instantiation. - Binary-format overloads (
to_cbor,to_msgpack,to_ubjson,to_bjdata,to_bson) now explicitly passStringType = std::vector<std::uint8_t>, preventingstd::basic_string<uint8_t>default-argument evaluation.
ksTFL 0.11.6
Sequential action execution
- Actions in
compute_cols()now execute sequentially withc_glue()modifications visible to subsequentc_addrow()calls. - Added
seqfield to all row actions for ordered execution tracking. - R:
.finalize_compute_cols()assigns sequence numbers to actions. - C++:
apply_style_rows()sorts actions byseqand executes sequentially. - C++:
build_addrow_synthetic()now acceptscurrent_rowparameter to access glued values. - Fixed
c_addrow('below')to snapshot row state at action position instead of using final row state. - Added 6 integration tests verifying stackable behavior.
Default missings change
- Default
missingsvalue changed from"NA"to""(empty string). - Missing values now render as empty cells by default instead of showing “NA”.
- Updated documentation in
tfl_set_options()and related functions.
ksTFL 0.11.5
Pagination and footnotes
- Fixed
footnotePlace = "last_page"pagination when templates allow row breaks (allow_row_break_across_pages = true): the paginator now skips deterministic LastPage reshuffling in row-break mode and keeps a single logical table flow. - In deterministic mode (
allow_row_break_across_pages = false), last-page footnote reservation is now applied only to the true final page; non-final overflow/interim pages no longer reserve footnote height. - Added report-writer regression coverage for both row-break and deterministic paths with grouped rows and
c_addrow().
ksTFL 0.11.4
Inline markup escaping
- Added support for escaped literal inline tag markers via
\\<in the C++ inline parser. Example:\\<i>literal\\</i>now renders as literal<i>literal</i>text rather than activating italic formatting. - Updated
has_inline_markup(),parse_inline_markup(), andget_plain_text()to treat escaped<as literal while preserving quick-path behavior. - Added C++ and testthat coverage for escaped-tag handling and documented the syntax in README/pkgdown docs.
ksTFL 0.11.3
Bug fixes
- Fixed
inst/examples/full_cycle_render.R: fivecreate_figure()calls incorrectly passed non-existentwidth,height, anddevicearguments. Replaced withtfl_set_options(figureWidth =, figureHeight =, figureDevice =)calls placed immediately before eachcreate_figure().create_figure()reads figure dimensions and device from session options at call time; onlydpiis a valid direct argument.
ksTFL 0.11.2
create_report() accepts list-of-specs arguments
-
create_report()now accepts plainlistarguments whose elements areTFL_specorTFL_reportobjects. Specs may be built independently (e.g. in a loop or across separate program files), collected into a named list, and then passed in a single call:specs <- list(t_dm = spec_dm, t_ae = spec_ae, l_subj = spec_listing) report <- create_report(specs)List slot names become the key prefix (
t_dm_<hash>,t_ae_<hash>, …). Unnamed slots fall back to<outer_arg_name>_<i>(orspec_<i>when the outer arg is a literallist(...)call).TFL_reportelements inside a list are flattened with their original keys preserved. Nested lists are rejected. create_report()now auto-disambiguates duplicate keys among newly-added specs by appending a numeric suffix (_2,_3, …) instead of erroring. Acli_inform()message is emitted for each renamed key. Key collisions originating from two separateTFL_reportarguments still trigger a hard error.List arguments may be freely mixed with variadic
TFL_specandTFL_reportarguments in the same call.New showcase example:
inst/examples/showcase/18_list_of_specs_bundle.R.
ksTFL 0.11.1
Vendor library upgrades
-
FreeType upgraded 2.13.3 → 2.14.3 (latest upstream). Picks up ~18 months of glyph loader, CFF, and auto-hinter fixes. Customised
ftmodule.h(restricting the compiled module set to what ksTFL actually uses) preserved on top of the new tree. -
HarfBuzz upgraded 10.2.0 → 14.2.0 (latest upstream). Incorporates four major releases of shaping/OpenType improvements and bug fixes. Our feature-disable flags (
HB_NO_SUBSET,HB_NO_COLOR,HB_NO_PAINT,HB_NO_STYLE) carry over unchanged.
No behavioural change in ksTFL itself; all 19 test files continue to pass, including measurement-sensitive paths (width recalc, column computation, ggplot figure rendering, end-to-end DOCX write).
ksTFL 0.11.0
Code audit: bug fixes, correctness & performance
Three-batch sweep over the R and C++ codebases covering project-convention compliance, thread safety, numeric correctness, locale independence, and hot paths in the rendering engine.
R — convention & correctness
-
stop()/warning()replaced withcli_abort()/cli_warn()inrun_replay_app.R,run_styles_editor.R,ksTFL.R. - Figure scale modes now consistently reference
.const_figure_scale_modes(inspec_context.R,pkg_settings.R). -
sapply()→vapply()(type-safe) acrossspec_print.R,spec_context.R,pkg_settings.R,schema_serialize.R. -
.env_eval()no longer silently defaults tospec$.metadata$data_env: theenvargument is validated (non-null,is.environment()) andcli_abort()is raised otherwise. Eliminates a class of subtle dispatch bugs where an evaluation could bind against a stalespec. - Removed dead schema fallbacks (
cs$label %||% cs$colLabel, etc.) inspec_print.R; schema useslabel/formatonly.
C++ — correctness & safety
-
Font registry thread-safety (
font_scanner.h/.cpp): migrated tostd::shared_mutex; reader accessors takestd::shared_lockand return by value, sotfl_rescan_fonts()remains safe to call while renders run. (std::call_oncerejected because rescanning is an explicit user feature.) -
UTF-8 decoder validation (
text_measurer.cpp): each continuation byte is now checked for10xxxxxxbits; on malformed sequences the decoder emits U+FFFD and advances exactly one byte to resync. -
Locale-independent numeric formatting (
logical_table.cpp):apply_column_format()switched fromstd::strtodtostd::from_chars<double>; column format parsing is no longer affected by the process locale. -
Column-width scaling precision (
paginator.cpp):compute_segment_column_widths()performs all arithmetic indoubleand clamps to[0, INT64_MAX]before narrowing to int64 EMU. -
TOC style id generation (
docx_emitter.cpp): replacedsnprintfinto a fixed 64-byte buffer withstd::to_stringconcatenation; buffer truncation is no longer possible. -
Header-grid span/gap mismatch (
logical_table.cpp): previously a silentbreak; now emits anRcpp::warningwith row and span info (falls back to rendering without promotion).
C++ — performance
-
Inline parser stack container (
inline_parser.cpp): replacedstd::stack<TagType>withstd::vector<TagType>;ParserState::from_stack()reduced from two full stack copies to a single reverse-iterator pass with inline Sup/Sub mutual exclusion. Closing-tag lookup usesrbegin()-
erase()instead of a copy/push/pop loop.
-
-
Dedupe restoration at page boundaries (
renderer.cpp): reworkedrestore_dedupe_at_page_boundaries()fromO(pages · dedupe_cols · rows)backward scans to a single forward pass with running per-column last-non-empty state,O(rows + pages · dedupe_cols). -
Redundant
FT_Set_Char_Size(font_cache.cpp):CachedFacenow trackslast_size_pt;get_hb_font()skips FreeType resizing andhb_ft_font_changedwhen the face is already set to the requested size. -
try_emplacein style-ref cache (paginator.cpp): single hash lookup on both hit and miss paths. -
reserve()for output vectors inparse_text_groups,parse_header_footer,parse_stub_columns,parse_columns(json_parser.cpp) and for segmentcolumn_indices(paginator.cpp). -
Cache
page_config.usable_width()once at the top ofPaginator::paginate()instead of recomputing per segment. - Removed redundant
dest.reserve()calls inxml_writer.cppescape_text_into/escape_attr_into(buffer pre-reserved 64KB).
ksTFL 0.10.4
Fixes
-
continuousSectionsemantics corrected in DOCX renderer: section break type is now taken from the section/spec being emitted (not the next spec), socontinuousSection = TRUEapplies to the intended spec. -
Body-level section properties honor
continuousSection: the last spec can now emit a body-levelw:type="continuous", allowing figure-to-table flow without an unintended final next-page break. -
TOC-to-body transition behavior restored: TOC remains separated with a
nextPagebreak while in-body specs can still flow continuously as requested. - Added/updated regression tests for multi-spec section break typing, including body-level
sectPrbehavior.
ksTFL 0.10.3
Internal C++ Engine
-
Optimised
get_plain_text()(inline_parser.cpp): replaced the parse-full-AST-then-extract approach with a single-pass tag-stripping scanner that reusesextract_tag()/classify_tag()directly, eliminating all intermediateParsedCell/TextRunallocations. - Added 16 unit tests for
get_plain_text()covering all recognised tag types,<br>→ space conversion, nested tags, and literal angle brackets.
ksTFL 0.10.2
Internal C++ Engine — Safety, Performance & Modernisation
-
Exception-safe font loading (
font_cache.cpp):load_face()now wrapshb_ft_font_create_referenced()in a try/catch block that callsFT_Done_Face()before rethrowing, preventing a FreeType handle leak on any HarfBuzz construction failure. -
Eliminated redundant string lowering (
font_cache.cpp,font_scanner.cpp):font_name_to_stem_hint()already returns a lowercase string, so the secondto_lowerpass infind_font_file()was removed.TargetFallbackstructs now pre-compute atarget_lowerfield so per-call lowering insideget_fallback_family()is avoided entirely. -
In-place style cascade application (
style_resolver.h,style_resolver.cpp): newapply_style_ref_inplace(StyleDef &, const std::string &)mutates the target directly instead of copy-merge-return. All callers inresolve_header_cell_style(),resolve_body_cell_style(),resolve_content_style(),resolve_body_text_style()andresolve_figure_caption_style()converted to the in-place variant, eliminating oneStyleDefcopy per style-ref application. -
Per-column base-style caches in paginator (
paginator.cpp): the row loop incompute_row_heights_implnow pre-computesbase_style_cacheandaddrow_style_cache(indexed by column index) before iterating over rows, cutting repeated template-cascade merges (steps 1–5) to a one-time cost. Astyle_ref_cache(std::unordered_map<std::string, const StyleDef*>) is built once per segment to cachefind_style()pointer lookups. Arequires std::invocable<…>constraint was added to the template for earlier type-checking. -
Pre-computed style caches in DOCX table emission (
docx_emitter.h,docx_table.cpp):emit_table()buildsbase_style_cacheandaddrow_style_cacheonce per segment and passes them toemit_table_row(), which now applies only row/cell overrides on top of the cached base instead of recomputing the full cascade for every cell. -
Stack-allocated tag buffer (
inline_parser.cpp):classify_tag()replaced a heap-allocatedstd::string lowerwith achar lower_buf[8]stack buffer — tag names are at most 3 characters, so no heap allocation is needed per tag. -
std::formatfor error messages (json_parser.cpp,renderer.cpp,font_cache.cpp): allthrow RenderError("…" + var + "…")patterns replaced withstd::format("…{}", var), removing temporary string concatenations (15 call sites total).
ksTFL 0.10.1
Fixes
-
Style deep-merge across
compute_cols()blocks:.append_style_action()no longer removes entire multi-column style actions when a later block partially overlaps columns; only the overlapping columns are surgically replaced, preserving styles on non-overlapping columns. -
True recursive style merge:
.merge_recursive()now recurses into nested named sub-lists instead of performing a shallowmodifyList()replace. Combining styles that share a top-level category (e.g. two styles both settingfontproperties) no longer silently drops properties from the first style. -
Style application order preserved:
._consolidate_styles_in_spec()now merges styles in the order specified by the user (e.g.f_combine("a", "b")appliesafirst, thenbwins) instead of sorting alphabetically. -
f_combine()accepted inc_style(),c_merge(),c_addrow():styleRefparameters in row-action functions now accept multi-element style vectors produced byf_combine(). -
.combine_column_styles()handles nestedf_combine(): replaceddo.call(f_combine, ...)with directunlist()+ class assignment to correctly flatten style refs that already contain multi-element vectors. -
compute_cols()accepts scalarTRUE/FALSE: a scalar logical condition is now recycled to matchnrow(data), allowingcompute_cols(spec, TRUE, ...)as shorthand for “all rows”. -
Missing
rlang::expr()qualifier: fixed unqualifiedexpr()call in.env_select()that causedcould not find function "expr"in clean R sessions.
ksTFL 0.10.0
New Features
-
Paragraph-level borders: styles now support
<w:pBdr>(paragraph borders) in addition to existing cell-level<w:tcBorders>. Paragraph borders underline only the text content within a cell, enabling visual gaps between spanning header groups without inserting dummy columns.- New
bordersparameter ins_paragraph()accepts a border spec built withs_borders()/s_border(). - C++ rendering engine emits
<w:pBdr>elements in paragraph properties. - JSON schema (
styles_schema_v2.json) updated to allowborderswithin paragraph definitions.
- New
- New built-in atomic styles:
brw_thick(4pt white right border),blw_thick(4pt white left border),pb(1pt paragraph bottom border),pb_th(0.5pt thin paragraph bottom border).
Shiny Styles Editor
- Paragraph border controls (top, bottom, left, right) added to all text-style panels in the Shiny styles editor.
Documentation
- Updated roxygen documentation for
s_paragraph(),s_borders(), ands_border()to describe paragraph border usage. - New “Paragraph-level borders” subsection and updated atom reference table in the Styling Guide vignette.
- New Example 3 (paragraph borders on spanning headers) in the Reporting Examples vignette.
- New Example 6 (spanning header gap technique) in the Real Examples vignette.
ksTFL 0.9.0
Build System
- HarfBuzz, FreeType, and minizip are now always compiled from vendored source during package installation, removing the runtime dependency on system
libharfbuzz,libfreetype, andlibminizippackages on Linux. - The Linux build path now uses a committed vendored-source toolchain (
src/vendor/plussrc/Makevars) instead ofpkg-configdetection or optional system-library fallbacks.
ksTFL 0.8.0
New Features
- Page settings (
p_page()/set_page_style()) now support partial overrides — only explicitly specified fields override the template defaults, enabling lighter per-spec customisation. - Per-column style mapping: character vector
style_refsinadd_style()now accepts a vector of length equal to the number of columns, withNAto skip individual columns. - New
Listings_timesbuilt-in template (Times New Roman variant of the Listings template).
Fixes
-
add_header()/add_footer()withlevelreplacement now correctly returns the rightTFL_options_header/TFL_options_footerclass when used viatfl_set_options(). - Default page settings changed from a fixed
A4 / portraitoverride toNULL, so bare specs inherit all page properties from the active template without unintended overrides.
ksTFL 0.7.9
CI/CD
- Added CRAN-like repository structure for Linux binaries (Ubuntu and Fedora), enabling
install.packages()from the release repo on Linux. - Added Red Hat / Fedora binary build using a Fedora container in CI.
- GitHub Releases are now published to both the private and public (ksTFL-release) repositories.
- New manual workflow (“Upload Binary to Release Repo”) for adding locally built packages (e.g., macOS) to the release repository.
- Added explicit pandoc setup to ensure vignettes build correctly on all platforms.
- Updated README installation instructions with per-platform sections.
ksTFL 0.7.6
Fixes
- Fixed inline markup paragraph handling so
produces real paragraph boundaries in emitted DOCX text groups (titles, subtitles, body text, footnotes), aligned with pagination measurement logic.
- Improved spacer-row border behavior: left/right borders are preserved through top/bottom empty spacer rows, while top/bottom lines remain suppressed as intended.
ksTFL 0.7.5
Documentation
- New vignette: “Real Examples with ksTFL” — five end-to-end clinical reporting examples (demographics table, UTF-8/multilingual table, AE spanning-header table, data listing with two-level TOC, and multi-figure combined report), each with embedded PDF output.
- Improved code comments and narrative descriptions across all example vignettes.
- Translated example scripts (
lb_lst_01_en.R,ae_tbl_exmpl.R,tmp_example.R) from Russian to English.
ksTFL 0.7.0
New Features
- Added built-in style atoms for target font families:
font_arial,font_courier_new,font_times_new_roman,font_georgia,font_verdana, andfont_trebuchet_ms. These atoms setfont_nameonly and are intended for composition with existing size, colour, alignment, and spacing atoms viaf_combine().