std::future and std::counting_semaphore.FontCache and TextMeasurer; Phase 4
(DOCX assembly) remains single-threaded and sequential.nlohmann/json.hpp 3.12.0 to eliminate
char_traits<unsigned char> deprecation warnings on macOS (Apple Clang 21+).output_adapter ostream constructor is now a constrained member template
(requires clause on C) so std::basic_ostream<uint8_t> is never
instantiated as a dependent type during class template instantiation.to_cbor, to_msgpack, to_ubjson, to_bjdata,
to_bson) now explicitly pass StringType = std::vector<std::uint8_t>,
preventing std::basic_string<uint8_t> default-argument evaluation.compute_cols() now execute sequentially with
c_glue() modifications visible to subsequent c_addrow() calls.seq field to all row actions for ordered execution tracking..finalize_compute_cols() assigns sequence numbers to actions.apply_style_rows() sorts actions by seq and executes sequentially.build_addrow_synthetic() now accepts current_row parameter to access
glued values.c_addrow('below') to snapshot row state at action position instead of
using final row state.missings value changed from "NA" to "" (empty string).tfl_set_options() and related functions.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.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.c_addrow().\\< in the C++
inline parser. Example: \\<i>literal\\</i> now renders as literal
<i>literal</i> text rather than activating italic formatting.has_inline_markup(), parse_inline_markup(), and
get_plain_text() to treat escaped < as literal while preserving quick-path
behavior.inst/examples/full_cycle_render.R: five create_figure() calls
incorrectly passed non-existent width, height, and device arguments.
Replaced with tfl_set_options(figureWidth =, figureHeight =, figureDevice =)
calls placed immediately before each create_figure(). create_figure()
reads figure dimensions and device from session options at call time; only
dpi is a valid direct argument.create_report() now accepts plain list arguments whose elements are
TFL_spec or TFL_report objects. 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> (or spec_<i> when the
outer arg is a literal list(...) call). TFL_report elements 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.
A cli_inform() message is emitted for each renamed key. Key collisions
originating from two separate TFL_report arguments still trigger a hard
error.
List arguments may be freely mixed with variadic TFL_spec and TFL_report
arguments in the same call.
New showcase example:
inst/examples/showcase/18_list_of_specs_bundle.R.
ftmodule.h (restricting the compiled module set to what ksTFL
actually uses) preserved on top of the new tree.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).
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.
stop() / warning() replaced with cli_abort() / cli_warn() in
run_replay_app.R, run_styles_editor.R, ksTFL.R..const_figure_scale_modes
(in spec_context.R, pkg_settings.R).sapply() → vapply() (type-safe) across spec_print.R, spec_context.R,
pkg_settings.R, schema_serialize.R..env_eval() no longer silently defaults to spec$.metadata$data_env:
the env argument is validated (non-null, is.environment()) and cli_abort()
is raised otherwise. Eliminates a class of subtle dispatch bugs where an
evaluation could bind against a stale spec.cs$label %||% cs$colLabel, etc.) in
spec_print.R; schema uses label / format only.font_scanner.h/.cpp): migrated to
std::shared_mutex; reader accessors take std::shared_lock and return
by value, so tfl_rescan_fonts() remains safe to call while renders run.
(std::call_once rejected because rescanning is an explicit user feature.)text_measurer.cpp): each continuation byte
is now checked for 10xxxxxx bits; on malformed sequences the decoder
emits U+FFFD and advances exactly one byte to resync.logical_table.cpp):
apply_column_format() switched from std::strtod to std::from_chars<double>;
column format parsing is no longer affected by the process locale.paginator.cpp):
compute_segment_column_widths() performs all arithmetic in double and
clamps to [0, INT64_MAX] before narrowing to int64 EMU.docx_emitter.cpp): replaced snprintf into
a fixed 64-byte buffer with std::to_string concatenation; buffer
truncation is no longer possible.logical_table.cpp): previously a
silent break; now emits an Rcpp::warning with row and span info
(falls back to rendering without promotion).inline_parser.cpp): replaced
std::stack<TagType> with std::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 uses rbegin()
erase() instead of a copy/push/pop loop.renderer.cpp): reworked
restore_dedupe_at_page_boundaries() from O(pages · dedupe_cols · rows)
backward scans to a single forward pass with running per-column
last-non-empty state, O(rows + pages · dedupe_cols).FT_Set_Char_Size (font_cache.cpp): CachedFace now tracks
last_size_pt; get_hb_font() skips FreeType resizing and
hb_ft_font_changed when the face is already set to the requested size.try_emplace in style-ref cache (paginator.cpp): single hash lookup
on both hit and miss paths.reserve() for output vectors in parse_text_groups,
parse_header_footer, parse_stub_columns, parse_columns
(json_parser.cpp) and for segment column_indices (paginator.cpp).page_config.usable_width() once at the top of
Paginator::paginate() instead of recomputing per segment.dest.reserve() calls in xml_writer.cpp
escape_text_into / escape_attr_into (buffer pre-reserved 64KB).extract_number() in units.cpp: renamed out-param and clarified
contract (end_pos written at end, internal pos local).continuousSection semantics corrected in DOCX renderer: section break type
is now taken from the section/spec being emitted (not the next spec), so
continuousSection = TRUE applies to the intended spec.continuousSection: the last spec can
now emit a body-level w:type="continuous", allowing figure-to-table flow
without an unintended final next-page break.nextPage break while in-body specs can still flow continuously as requested.sectPr behavior.get_plain_text() (inline_parser.cpp): replaced the
parse-full-AST-then-extract approach with a single-pass tag-stripping
scanner that reuses extract_tag()/classify_tag() directly, eliminating
all intermediate ParsedCell/TextRun allocations.get_plain_text() covering all recognised tag
types, <br> → space conversion, nested tags, and literal angle brackets.font_cache.cpp): load_face() now wraps
hb_ft_font_create_referenced() in a try/catch block that calls
FT_Done_Face() before rethrowing, preventing a FreeType handle leak on any
HarfBuzz construction failure.font_cache.cpp,
font_scanner.cpp): font_name_to_stem_hint() already returns a lowercase
string, so the second to_lower pass in find_font_file() was removed.
TargetFallback structs now pre-compute a target_lower field so
per-call lowering inside get_fallback_family() is avoided entirely.style_resolver.h,
style_resolver.cpp): new apply_style_ref_inplace(StyleDef &, const std::string &) mutates the target directly instead of copy-merge-return.
All callers in resolve_header_cell_style(), resolve_body_cell_style(),
resolve_content_style(), resolve_body_text_style() and
resolve_figure_caption_style() converted to the in-place variant,
eliminating one StyleDef copy per style-ref application.paginator.cpp): the row
loop in compute_row_heights_impl now pre-computes base_style_cache and
addrow_style_cache (indexed by column index) before iterating over rows,
cutting repeated template-cascade merges (steps 1–5) to a one-time cost.
A style_ref_cache (std::unordered_map<std::string, const StyleDef*>) is
built once per segment to cache find_style() pointer lookups.
A requires std::invocable<…> constraint was added to the template for
earlier type-checking.docx_emitter.h,
docx_table.cpp): emit_table() builds base_style_cache and
addrow_style_cache once per segment and passes them to
emit_table_row(), which now applies only row/cell overrides on top of the
cached base instead of recomputing the full cascade for every cell.inline_parser.cpp): classify_tag()
replaced a heap-allocated std::string lower with a char lower_buf[8]
stack buffer — tag names are at most 3 characters, so no heap allocation is
needed per tag.std::format for error messages (json_parser.cpp, renderer.cpp,
font_cache.cpp): all throw RenderError("…" + var + "…") patterns
replaced with std::format("…{}", var), removing temporary string
concatenations (15 call sites total).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..merge_recursive() now recurses into nested named sub-lists instead of performing a shallow modifyList() replace. Combining styles that share a top-level category (e.g. two styles both setting font properties) no longer silently drops properties from the first style.._consolidate_styles_in_spec() now merges styles in the order specified by the user (e.g. f_combine("a", "b") applies a first, then b wins) instead of sorting alphabetically.f_combine() accepted in c_style(), c_merge(), c_addrow(): styleRef parameters in row-action functions now accept multi-element style vectors produced by f_combine()..combine_column_styles() handles nested f_combine(): replaced do.call(f_combine, ...) with direct unlist() + class assignment to correctly flatten style refs that already contain multi-element vectors.compute_cols() accepts scalar TRUE/FALSE: a scalar logical condition is now recycled to match nrow(data), allowing compute_cols(spec, TRUE, ...) as shorthand for "all rows".rlang::expr() qualifier: fixed unqualified expr() call in .env_select() that caused could not find function "expr" in clean R sessions.<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.
borders parameter in s_paragraph() accepts a border spec built with s_borders() / s_border().<w:pBdr> elements in paragraph properties.styles_schema_v2.json) updated to allow borders within paragraph definitions.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).s_paragraph(), s_borders(), and s_border() to describe paragraph border usage.libharfbuzz, libfreetype, and libminizip packages on Linux.src/vendor/ plus src/Makevars) instead of pkg-config detection or optional system-library fallbacks.p_page() / set_page_style()) now support partial overrides — only explicitly specified fields override the template defaults, enabling lighter per-spec customisation.style_refs in add_style() now accepts a vector of length equal to the number of columns, with NA to skip individual columns.Listings_times built-in template (Times New Roman variant of the Listings template).add_header() / add_footer() with level replacement now correctly returns the right TFL_options_header / TFL_options_footer class when used via tfl_set_options().A4 / portrait override to NULL, so bare specs inherit all page properties from the active template without unintended overrides.inst/extdata.install.packages() from the release repo on Linux.produces real paragraph boundaries in emitted DOCX text groups (titles, subtitles, body text, footnotes), aligned with pagination measurement logic.
lb_lst_01_en.R, ae_tbl_exmpl.R, tmp_example.R) from Russian to English.font_arial, font_courier_new, font_times_new_roman, font_georgia, font_verdana, and font_trebuchet_ms. These atoms set font_name only and are intended for composition with existing size, colour, alignment, and spacing atoms via f_combine().