Skip to main content

common/
format_runs_query.rs

1//! Store-aware readers that synthesize inline content views from
2//! per-block `format_runs` + `block_images`. The canonical entry
3//! point is [`inline_segments_for_block`], which returns the
4//! `Vec<InlineSegment>` view used by export, fragments, cursor, and
5//! tests.
6
7use crate::database::Store;
8use crate::database::rope_helpers::block_document_position;
9use crate::entities::Block;
10use crate::format_runs::{
11    AddressableInlinePiece, FootnoteRefAnchor, FormatRun, ImageAnchor, InlineSegment,
12    addressable_inline_pieces, block_anchors, inline_segments_view,
13};
14use crate::types::EntityId;
15
16/// Fetch the format runs for a block. Returns an empty Vec if the block
17/// has no runs (treated the same as a missing entry).
18pub fn get_format_runs(store: &Store, block_id: EntityId) -> Vec<FormatRun> {
19    store
20        .format_runs
21        .read()
22        .get(&block_id)
23        .cloned()
24        .unwrap_or_default()
25}
26
27/// Fetch the footnote references anchored in a block.
28pub fn get_block_footnote_refs(store: &Store, block_id: EntityId) -> Vec<FootnoteRefAnchor> {
29    store
30        .block_footnote_refs
31        .read()
32        .get(&block_id)
33        .cloned()
34        .unwrap_or_default()
35}
36
37/// Fetch the image anchors for a block.
38pub fn get_block_images(store: &Store, block_id: EntityId) -> Vec<ImageAnchor> {
39    store
40        .block_images
41        .read()
42        .get(&block_id)
43        .cloned()
44        .unwrap_or_default()
45}
46
47/// Synthesize the `Vec<InlineSegment>` view for a block from its
48/// format_runs and block_images. Callers must pass the block's
49/// `plain_text` (which they already have in scope from a prior
50/// `get_block` call) — this avoids re-locking the blocks table.
51///
52/// This is the Phase 1.14b-and-forward reader function. Returns segments
53/// in document order.
54pub fn inline_segments_for_block(
55    store: &Store,
56    block_id: EntityId,
57    block_plain_text: &str,
58) -> Vec<InlineSegment> {
59    let runs = get_format_runs(store, block_id);
60    let images = get_block_images(store, block_id);
61    let notes = get_block_footnote_refs(store, block_id);
62    inline_segments_view(block_plain_text, &runs, &images, &notes)
63}
64
65/// The block's inline pieces (text runs, images, footnote references), addressed in the
66/// document's own **addressable character space** — the same space `TextDocument::
67/// to_addressable_text()`, `find_all` match positions, and a block's `document_position` all
68/// share. See [`crate::format_runs::AddressableInlinePiece`]'s doc comment for the hazard
69/// this closes: `FormatRun`/`ImageAnchor` offsets are UTF-8 *bytes* local to this one block,
70/// document-wide offsets are *characters*, and a caller bridging the two by hand (or not at
71/// all) is exactly how a comment anchored right after a table landed two characters off
72/// (see `TextDocument::to_addressable_text`'s doc comment for that incident).
73///
74/// This is the accessor a writer splitting a comment's character range across runs, images
75/// and footnote references — the DOCX and ODT exporters, in particular — should reach for
76/// instead of re-deriving `InlinePiece`'s byte offsets into document space by hand: one
77/// definition of the weave, shared the same way [`inline_segments_for_block`] already shares
78/// it for the byte-space view.
79///
80/// Takes the whole [`Block`] entity rather than just its id (unlike
81/// [`inline_segments_for_block`]): landing in document-wide space needs the block's own
82/// `document_position`, and [`block_document_position`]'s fallback path — for a block not yet
83/// mirrored into the rope — reads `block.document_position` directly, so the id alone isn't
84/// enough. Every existing caller of `inline_segments_for_block` already has the full entity in
85/// scope (it's where `block_plain_text` came from), so this costs nothing extra to call.
86pub fn addressable_inline_pieces_for_block(
87    store: &Store,
88    block: &Block,
89    block_plain_text: &str,
90) -> Vec<AddressableInlinePiece> {
91    let runs = get_format_runs(store, block.id);
92    let images = get_block_images(store, block.id);
93    let notes = get_block_footnote_refs(store, block.id);
94    let anchors = block_anchors(&images, &notes);
95    // `chars ≤ bytes`, and the rope's own byte length is already `u32`-bounded
96    // (`BlockOffsetIndex::total_bytes`) everywhere else in this crate — a document large
97    // enough to overflow this cast would already have overflowed the rope it lives in.
98    let base_char_offset = block_document_position(block, store) as u32;
99    addressable_inline_pieces(block_plain_text, &runs, &anchors, base_char_offset)
100}