Skip to content

Extract text layout logic into reusable text_layout.h module - #4

Closed
imakris wants to merge 6 commits into
masterfrom
claude/investigate-template-deduplication-PxeDm
Closed

Extract text layout logic into reusable text_layout.h module#4
imakris wants to merge 6 commits into
masterfrom
claude/investigate-template-deduplication-PxeDm

Conversation

@imakris

@imakris imakris commented May 19, 2026

Copy link
Copy Markdown
Owner

Summary

Refactors text layout and measurement logic into a new text_layout.h header module to eliminate code duplication between render.cpp and table_layout.cpp. This improves maintainability and allows both modules to share consistent text wrapping and measurement implementations.

Key Changes

  • New text_layout.h module: Extracted Token, Line, font_for(), tokenize_runs(), wrap_tokens(), wrap_runs(), and total_height() into a reusable namespace that both rendering and table layout can use
  • Refactored table_layout.cpp:
    • Removed duplicate text layout code
    • Added metrics_measurer() helper to bridge Measurement_context into the generic (font, text, size_pt) -> width_pt interface
    • Extracted bisect() template for binary search refinement (used in column width optimization)
    • Updated all text wrapping calls to use text_layout::wrap_runs()
  • Refactored render.cpp: Removed duplicate implementations and now uses text_layout namespace functions via using declarations
  • Updated render.cpp callers: Changed from passing leading factor to passing pre-computed line_height_pt (e.g., size_pt * leadingsize_pt * leading)
  • New test_common.h: Extracted common test utilities (executable_dir(), load_default_metrics()) to reduce duplication in test files
  • Enhanced io_helpers.h: Added read_be<T>() template for reading big-endian integers, replacing inline bit-shift code in png_image.cpp and ttf_font.cpp
  • Simplified pdf_writer.cpp: Templated build_stream_object() to accept any container with .data() and .size() methods

Notable Implementation Details

  • The wrap_runs() function now accepts a generic MeasureFn callable, allowing callers to provide custom measurement logic without coupling to Measurement_context
  • The bisect() template performs 18 iterations of midpoint refinement for ~3.8e-6 precision relative to the initial range
  • Text layout logic is now inline in the header to avoid ODR violations and allow template instantiation across translation units

https://claude.ai/code/session_013Q7H5C4AZ5XTpHwVa8UWmG

claude added 6 commits May 19, 2026 18:38
Pull font_for, Token, Line, tokenize_runs, wrap_tokens/wrap_runs and
total_height out of render.cpp and table_layout.cpp into a new private
header src/text_layout.h. Both translation units used to carry their
own byte-identical copies and a near-clone wrap engine; the templated
wrap_tokens<MeasureFn> in render.cpp already had the right shape but
wasn't reachable from table_layout.cpp.

The shared template now coalesces consecutive same-style spans on a
line (an optimisation that previously only existed in table_layout's
copy). render.cpp's path inherits it for free, dropping Tj operator
count ~4x in the example renders without changing the visible output.

The wrap API now takes line_height_pt directly instead of (size_pt,
leading_factor); render.cpp call sites pre-multiply at the call,
table_layout passes style.text_leading_pt unchanged.

src/render.cpp       | -190 lines
src/table_layout.cpp | -197 lines
src/text_layout.h    | +213 lines (new)
png_image.cpp had free functions read_be16/read_be32, and ttf_font.cpp's
Bytes_view had u16/u32 methods, each duplicating the same byte-shifted
read. Replace both with a single constexpr template read_be<T> in
io_helpers.h that works for any unsigned 1/2/4/8-byte integer. The
existing per-call-site wrappers (read_be16, read_be32, Bytes_view::u16,
::u32) stay as thin delegates so call sites read unchanged.
table_layout.cpp had three open-coded 18-step midpoint refinements that
differed only in the predicate (cell line count vs total table height).
Collapse them behind one anonymous-namespace bisect<Predicate>(lo, hi)
template and pass the per-site predicate as a lambda. Behavior is
identical: same iteration count, same accept-hi/reject-lo semantics,
byte-identical smoke-render output.
test_png.cpp and test_pdf_writer.cpp both did the same argv[0] parent-dir
derivation and Measurement_context bring-up with briefutil_default fonts.
Promote those two patterns into inline helpers executable_dir(argc, argv)
and load_default_metrics(exe_dir) in a new private header tests/test_common.h.

test_png.cpp's path derivation is now normalized via fs::absolute, matching
what test_pdf_writer.cpp already did; the font_context resolver is already
cwd-aware so this is at worst a no-op.
The two thin overloads for std::string and std::vector<uint8_t> both
just delegated to the raw-pointer build_stream_object. Replace them
with a single template on Container (anything with .data()/.size()),
which subsumes both call sites without changing behavior. The raw-
pointer version remains the authoritative implementation.
The downstream-consumer smoke test still referenced mark2haru::render_options_t,
which was renamed to mark2haru::Render_options in 9aa1c0e (May 3) as part of
the Varinomics API style pass. The workflow file was missed at the time.
@imakris imakris closed this May 19, 2026
@imakris
imakris deleted the claude/investigate-template-deduplication-PxeDm branch May 19, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants