# Snap vs Anchor in LiteParse's Text Projection System: What's the Difference?

> Understand the difference between Snap and Anchor in LiteParse's text projection system. Learn how Snap is a temporary grid classification and Anchor is the final alignment attribute.

- Repository: [LlamaIndex/liteparse](https://github.com/run-llama/liteparse)
- Tags: deep-dive
- Published: 2026-06-06

---

**Snap is an internal, temporary grid classification used during line grouping, while Anchor is the final, persistent alignment attribute stored on each `ProjectedTextItem` that drives output formatting.**

In the `run-llama/liteparse` crate, the projection pipeline transforms raw PDF text into structured lines. Understanding the **difference between Snap and Anchor** is critical for debugging layout reconstruction or building custom output formatters, because these two enums represent sequential but distinct stages of the text projection algorithm.

## What Is the Difference Between Snap and Anchor?

At the highest level, **Snap** is a volatile, internal heuristic, whereas **Anchor** is a stable, public-facing property.

- **Snap** (used internally as `SnapKind`) lives only inside [`crates/liteparse/src/projection.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/projection.rs) while the algorithm determines line breaks and column edges. It classifies raw items as belonging to a left-snapped, right-snapped, or centered grid line.
- **Anchor** is assigned after snapping completes and is stored permanently on every `ProjectedTextItem`. It describes the item's logical alignment within its final line and is consumed by output modules such as [`crates/liteparse/src/output/text.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/output/text.rs).

This separation prevents transient snapping errors from reaching the renderer.

## Snap: A Transient Grid Classification

**Snap** is defined in [`crates/liteparse/src/types.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/types.rs) at lines 78–82. It is an enum with three variants—`Left`, `Right`, and `Center`—that tells the projector how raw items line up on a snapped y-grid.

During projection, the algorithm groups `TextItem`s whose y-coordinates fall into the same grid cell. It then evaluates the distribution of x-coordinates within that group to decide whether the line is a left-snapped margin, a right-snapped column edge, or a centered block. This logic appears around lines 1735–1740 of [`crates/liteparse/src/projection.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/projection.rs), where the code builds three `AnchorMap`s and selects a `SnapKind` for each block.

Because snapping is iterative, these values can be overwritten as the grid refines. An incorrect `Snap` classification can cause text to merge across columns, so this step is critical for multi-column layout detection.

## Anchor: The Final Logical Alignment

**Anchor** is defined immediately after `Snap` in [`crates/liteparse/src/types.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/types.rs) at lines 86–90. While it mirrors the same three variants, its role is fundamentally different: it records the final logical alignment of an item inside its line after the projection algorithm has finished.

Once the snap heuristic stabilizes, the promotion to `Anchor` happens inside the loop that constructs `ProjectedTextItem`s, specifically at lines 2581–2589 of [`projection.rs`](https://github.com/run-llama/liteparse/blob/main/projection.rs). At this point, every item receives an immutable `anchor` value that will travel through the rest of the pipeline. The `anchor` field is stored directly on `ProjectedTextItem` alongside the temporary `snap` field, and it is serialized into the final `ParsedPage` for downstream consumption.

## How the Projection Pipeline Bridges Snap and Anchor

The separation between these two enums enforces a clean boundary between unstable heuristics and stable output contracts.

1. **SnapKind resolution** – The projector quantizes coordinates, compares them against `AnchorMap`s, and assigns a tentative `SnapKind`.
2. **Anchor assignment** – After the snap decision is finalized, the algorithm copies that value into the `anchor` field of each new `ProjectedTextItem`.
3. **Output formatting** – Code in [`crates/liteparse/src/output/text.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/output/text.rs) reads the immutable `anchor` value to insert the correct spacing, ignoring the temporary `snap`.

This three-stage flow ensures that even if the snapping logic shifts during grid refinement, the rendered output relies on a single, deterministic alignment value.

## Code Examples: From SnapKind to Anchor

### Constructing a ProjectedTextItem

The following excerpt, adapted from [`crates/liteparse/src/projection.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/projection.rs), shows how the transient snap is promoted to a durable anchor when an item is instantiated:

```rust
// Inside projection.rs – after the snap has been resolved
let snap_kind = SnapKind::Left;               // result of the snapping step
let mut projected = ProjectedTextItem {
    item: raw_text_item.clone(),
    snap: snap_kind,                          // temporary snap kind
    anchor: match snap_kind {                 // final alignment
        SnapKind::Left   => Anchor::Left,
        SnapKind::Right  => Anchor::Right,
        SnapKind::Center => Anchor::Center,
    },
    is_dup: false,
    rendered: false,
    num_spaces: 0,
    force_unsnapped: false,
    is_margin_line_number: false,
    rotated: false,
    d: 0.0,
    orig_x: raw_text_item.x,
    orig_y: raw_text_item.y,
    orig_width: raw_text_item.width,
    orig_height: raw_text_item.height,
    orig_rotation: raw_text_item.rotation,
};

```

Note that `snap` retains the internal grid classification, while `anchor` locks in the public alignment that formatters will use.

### Rendering Output with Anchor

Downstream modules such as [`output/text.rs`](https://github.com/run-llama/liteparse/blob/main/output/text.rs) consume the stable `anchor` field to assemble the final text string. The simplified renderer below demonstrates how alignment affects spacing:

```rust
fn render_line(items: &[ProjectedTextItem]) -> String {
    let mut line = String::new();
    for itm in items {
        match itm.anchor {
            Anchor::Left   => line.push_str(&format!("{} ", itm.item.text)),
            Anchor::Right  => line = format!("{} {}", itm.item.text, line),
            Anchor::Center => line.push_str(&format!("{} ", itm.item.text)),
        }
    }
    line.trim_end().to_string()
}

```

By this stage, the temporary `snap` value is irrelevant. Only the immutable `anchor` determines whether an item is placed at the beginning, end, or center of its line.

## Impact on Output Formatting

The `anchor` field directly controls spacing behavior in the text output stage. According to the implementation in [`crates/liteparse/src/output/text.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/output/text.rs), items carrying `Anchor::Center` are rendered without leading spaces, whereas items with `Anchor::Left` or `Anchor::Right` receive left- or right-justified spacing. This distinction allows LiteParse to faithfully reconstruct centered headers, flush-right page numbers, and standard left-aligned body text from complex PDF layouts.

## Summary

- **Snap** is a temporary, internal classification used only inside [`crates/liteparse/src/projection.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/projection.rs) while the algorithm snaps raw items onto a discrete y-grid and resolves line breaks.
- **Anchor** is the persistent alignment attribute stored on every `ProjectedTextItem`, consumed by output formatters to produce correctly spaced final text.
- Both enums are defined in [`crates/liteparse/src/types.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/types.rs)—`Snap` at lines 78–82 and `Anchor` at lines 86–90—and share the same variants, but they are kept distinct to isolate heuristic instability from rendering logic.
- The promotion from `SnapKind` to `Anchor` occurs at lines 2581–2589 of [`projection.rs`](https://github.com/run-llama/liteparse/blob/main/projection.rs), after which the value becomes immutable for the remainder of the pipeline.

## Frequently Asked Questions

### Can Snap values change after they are first assigned?

Yes. **Snap** is deliberately transient and may be overwritten as the projection algorithm refines the snapped grid and re-evaluates `AnchorMap`s. It exists solely to help the projector decide how items group into lines. Only after the heuristic stabilizes does the value graduate into the immutable **Anchor** field.

### Is the Anchor enum part of LiteParse's public API?

No. Both enums are marked `#[doc(hidden)]` in [`crates/liteparse/src/types.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/types.rs), so they are omitted from the official public API documentation. However, the `anchor` field remains present in serialized `ProjectedTextItem` structures, making it available for debugging and custom formatter integrations.

### Why does LiteParse maintain two separate enums for the same alignment concepts?

The separation enforces architectural clarity. **Snap** represents a volatile, grid-relative heuristic that can shift during computation. **Anchor** represents a fixed, logical property of the final output. Decoupling them prevents transient snapping errors from propagating to the text renderer and makes the projection logic easier to test and maintain.

### Where can I find the code that turns a Snap decision into an Anchor value?

The assignment occurs in [`crates/liteparse/src/projection.rs`](https://github.com/run-llama/liteparse/blob/main/crates/liteparse/src/projection.rs) between lines 2581 and 2589. This loop runs after the snapping heuristic has decided a block's `SnapKind`, copying that result into the `anchor` field of every newly created `ProjectedTextItem` before the item proceeds to the output stage.