Skip to main content

FlowLayout

Struct FlowLayout 

Source
pub struct FlowLayout {
    pub blocks: HashMap<usize, BlockLayout>,
    pub tables: HashMap<usize, TableLayout>,
    pub frames: HashMap<usize, FrameLayout>,
    pub flow_order: Vec<FlowItem>,
    pub content_height: f32,
    pub viewport_width: f32,
    pub viewport_height: f32,
    pub cached_max_content_width: f32,
    pub scale_factor: f32,
    pub font_scale: f32,
    /* private fields */
}

Fields§

§blocks: HashMap<usize, BlockLayout>§tables: HashMap<usize, TableLayout>§frames: HashMap<usize, FrameLayout>§flow_order: Vec<FlowItem>§content_height: f32§viewport_width: f32§viewport_height: f32§cached_max_content_width: f32§scale_factor: f32

Device pixel ratio passed to shapers and rasterizers. Layout is always stored in logical pixels; this only affects precision (physical ppem) and glyph bitmap resolution.

§font_scale: f32

Per-document logical text-magnification factor (1.0 = none). Set from DocumentFlow::font_scale at layout time and threaded into block layout alongside scale_factor; multiplies the resolved font size so all text grows logically (advances, line heights, content height) and reflows.

Implementations§

Source§

impl FlowLayout

Source

pub fn new() -> Self

Source

pub fn add_table( &mut self, registry: &FontRegistry, params: &TableLayoutParams, available_width: f32, )

Add a table to the flow at the current y position.

Source

pub fn add_frame( &mut self, registry: &FontRegistry, params: &FrameLayoutParams, available_width: f32, )

Add a frame to the flow.

  • Inline: placed in normal flow, advances content_height.
  • FloatLeft: placed at current y, x=0. Does not advance content_height (surrounding content wraps around it).
  • FloatRight: placed at current y, x=available_width - frame_width.
  • Absolute: placed at (margin_left, margin_top) from document origin. Does not affect flow at all.
Source

pub fn clear(&mut self)

Clear all layout state. Call before rebuilding from a new FlowSnapshot.

Source

pub fn apply_paint_spans_for( &mut self, spans_by_block: HashMap<usize, Vec<PaintSpan>>, )

Replace the paint-only color overlay for the whole flow.

spans_by_block maps block_id → its disjoint paint spans (from text_document’s extract_paint_spans). Every block is re-derived from its captured base, so colors/decorations change but glyph positions, advances, line breaks, and heights do NOT — no reshape, no reflow. Blocks absent from the map reset to base colors.

Source

pub fn apply_block_paint_spans( &mut self, block_id: usize, spans: &[PaintSpan], ) -> bool

Apply (or clear, when spans is empty) the paint overlay for a single block, re-derived from its base. Returns false if block_id has no captured base.

Source

pub fn add_block( &mut self, registry: &FontRegistry, params: &BlockLayoutParams, available_width: f32, )

Add a single block to the flow at the current y position.

Bulk-path primitive. This deliberately does not capture the block’s paint-overlay base; layout_blocks calls it in a loop and captures every base once at the end, which keeps the bulk path single-pass.

Appending to an existing layout has no such follow-up pass, so use append_block instead. Reaching for this one is silent when wrong: the block lays out and renders fine, but apply_block_paint_spans then early-returns false for it forever, so it never receives syntax, search, or spell highlighting and nothing reports a problem.

Source

pub fn append_block( &mut self, registry: &FontRegistry, params: &BlockLayoutParams, available_width: f32, )

Append one block to the tail of an existing layout, in O(1).

This is add_block plus the paint-overlay bookkeeping that a bulk layout would otherwise do afterwards. layout_blocks calls add_block in a loop and then re-captures every block’s base in one pass (refresh_base_blocks, O(N)); add_block on its own therefore leaves the appended block absent from base_blocks, and a later apply_paint_spans_for would re-derive it from a missing base.

Reusing the bulk refresh for a tail append would re-clone the whole document once per appended line — precisely the O(N)-per-line cost this method exists to avoid — so it refreshes just the appended block (refresh_base_and_overlay_block, the same O(1) call relayout_block already relies on).

add_block itself is left untouched: layout_blocks depends on its current no-base-refresh behaviour to keep the bulk path single-pass.

Source

pub fn remove_leading(&mut self, n: usize) -> usize

Drop the first n top-level blocks, returning how many were removed.

The eviction half of a bounded streaming buffer (a log viewer’s scrollback cap). Usually O(n) map removals plus one Vec memmove of the surviving flow_order entries — no reshaping. The return value is the count actually evicted, which is less than n when the run of leading blocks is shorter than n or a table/frame stops the walk.

Surviving blocks deliberately keep their absolute y. The vacated band at the top simply becomes empty, and content_height is unchanged, so nothing below moves and no scroll position shifts under the user. Rewriting every survivor’s y would make eviction O(remaining) and would yank the viewport; leaving them is both cheaper and correct. flow_order’s ascending-y ordering — which hit_test’s binary search relies on — is preserved, since removing a prefix of a sorted run leaves it sorted.

Only top-level blocks are considered; a leading table or frame stops the eviction (a streaming buffer is a flat run of blocks by construction).

If the widest block in the flow is among those evicted, max_content_width is recomputed from the survivors — O(remaining) for that call only. Evicting narrower blocks (the overwhelmingly common case) cannot lower the maximum and skips the recompute.

Source

pub fn set_uniform_extent(&mut self, total_rows: usize, row_height: f32)

Declare the total extent of a uniform-row-height document without shaping anything.

In windowed mode content_height describes the whole document, not the shaped window, so it cannot come from the usual accumulator. Use this to keep the scrollbar honest when the row count changes outside the shaped window (a line appended while the user is scrolled away from the tail).

Leaves the shaped window untouched.

Source

pub fn layout_window( &mut self, registry: &FontRegistry, window: &[(usize, BlockLayoutParams)], total_rows: usize, row_height: f32, available_width: f32, )

Shape only window — a slice of a much larger uniform-row-height document — placing each row at an arithmetic y = index * row_height.

Shaping every line is what makes a large document expensive: a resident shaped line costs ~6.5 KB, so a 100 000-line buffer costs ~623 MB fully laid out, against ~1 MB for a viewport-sized window (docs/streaming-baseline.md). Rendering already culls to the viewport, so shaping the off-screen remainder buys nothing.

y is arithmetic rather than accumulated precisely so that a row can be placed without having laid out — or even having in memory — any of the rows above it, which is what lets the window start at an arbitrary scroll position. content_height is likewise derived from total_rows, so the scrollbar reflects the whole document rather than the shaped window.

§Invariants

The arithmetic placement is only correct for a document whose rows are genuinely uniform: one row = one visual line of exactly row_height — no wrapping (non_breakable_lines), no embedded newlines, no per-row margins, one font size throughout. That suits log/console output and monospaced code; it does not suit prose. Callers with variable-height content must use layout_blocks. window must be sorted ascending by index, which keeps flow_order ascending by y as hit_test’s binary search requires. Both are checked in debug builds.

After this call, appending at the tail with append_block stays correct: content_height is exactly total_rows * row_height, which is where row total_rows belongs. Trim the window’s front with remove_leading.

§Behaviour worth knowing

Like layout_blocks, this drops any paint overlay: re-apply spans after re-windowing, or the new rows render in base colours. Because this runs on every visible-range change, that re-apply is part of the scroll path, not a one-off.

max_content_width accumulates across windows rather than being re-derived per window: it reports the widest row seen so far, not the widest row in the document (unknowable without shaping all of it) and not the widest row on screen (which would make the horizontal scrollbar jump on every vertical scroll). It therefore only grows during a windowed session.

y is index * row_height in f32, which represents consecutive integers exactly only to 2^24: past ~840 000 rows at a 20 px row height, row positions begin quantizing and neighbouring rows drift out of alignment. Well beyond the sizes this targets, but not infinite.

Source

pub fn layout_blocks( &mut self, registry: &FontRegistry, block_params: Vec<BlockLayoutParams>, available_width: f32, )

Lay out a sequence of blocks vertically.

Source

pub fn relayout_block( &mut self, registry: &FontRegistry, params: &BlockLayoutParams, available_width: f32, )

Update a single block’s layout and shift subsequent items if height changed.

Finds the block in top-level blocks, table cells, or frames, re-layouts it, and propagates any height delta to subsequent flow items.

Trait Implementations§

Source§

impl Default for FlowLayout

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> ErasedDestructor for T
where T: 'static,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.