pub struct Content {
pub text: String,
pub lines: Vec<Line>,
pub marks: Vec<Mark>,
pub islands: Vec<Island>,
}Expand description
One content field as a content: the text plus the structure that rides on it.
The mint (Content::into_normalized) establishes the canonical form:
marks sorted and unioned, container paths renumbered, a line’s kind agreeing
with its text, a block island’s slot alone on its line, table props on one
column count. Content::validate reports what the mint cannot repair: the
text holds no \r, no bidi controls, and no line separator (every character
Typst reads as a newline but \n); the count of ISLAND_SLOT equals
islands.len(); lines.len() equals the number of \n-separated segments.
Fields§
§text: StringThe content. \n is a line boundary; ISLAND_SLOT is an island slot.
lines: Vec<Line>One entry per \n-separated segment of text, in order. The line tree
is derived from this flat list plus each line’s containers path,
never stored, so a split/join is a single-char edit with no paragraph
identity to reconcile.
marks: Vec<Mark>Marks over char ranges, kept normalized: sorted by
(start, end, type, attrs), same-kind formatting marks unioned.
islands: Vec<Island>One entry per ISLAND_SLOT, in slot order (ascending char position).
Implementations§
Source§impl Content
impl Content
Sourcepub fn new(text: String, lines: Vec<Line>) -> Self
pub fn new(text: String, lines: Vec<Line>) -> Self
The text and its per-line attributes; marks and islands start empty.
Constructing neither normalizes nor checks: the canonical form is the
caller’s until into_normalized runs, and
validate reports what that cannot repair. The codecs
(crate::import, Content::from_canonical_json) do both.
Sourcepub fn into_normalized(self) -> Normalized
pub fn into_normalized(self) -> Normalized
Normalize and seal. With Normalized::empty, the only mint for
Normalized; the codecs decode through here.
pub fn with_marks(self, marks: Vec<Mark>) -> Self
Sourcepub fn with_islands(self, islands: Vec<Island>) -> Self
pub fn with_islands(self, islands: Vec<Island>) -> Self
Set the islands, one per ISLAND_SLOT in slot order.
Sourcepub fn is_inline(&self) -> bool
pub fn is_inline(&self) -> bool
Whether this content satisfies the richtext(inline) constraint: exactly
one Para line, sitting in no container, with no islands.
Content::empty is inline, so a blank inline field passes.
Sourcepub fn is_plain(&self) -> bool
pub fn is_plain(&self) -> bool
Whether this content satisfies the plaintext constraint: no marks, no
islands, and every line a plain Para sitting in no container.
continues is unconstrained. Content::empty is plain.
The distinguishing property of plaintext over richtext { marks: [] } is
the literal codec (crate::import::from_plaintext), not this
predicate.
Sourcepub fn is_blank(&self) -> bool
pub fn is_blank(&self) -> bool
Whether the text is empty or whitespace-only. An ISLAND_SLOT is not
whitespace, so an island-bearing content is never blank.
Sourcepub fn segment_count(&self) -> usize
pub fn segment_count(&self) -> usize
Number of \n-separated segments: the required lines.len().
Sourcepub fn normalize(&mut self)
pub fn normalize(&mut self)
Normalize in place: canonicalize container ordinal/instance, break a
line around a block-only island’s slot, drop zero-width formatting, union
same-kind formatting that is adjacent or overlapping, recursively
key-sort island props, then sort marks canonically. Idempotent: the
fixed point the canonical serialization commits to.
Source§impl Content
impl Content
Sourcepub fn apply_text_delta(&mut self, delta: &Delta) -> Result<(), ApplyError>
pub fn apply_text_delta(&mut self, delta: &Delta) -> Result<(), ApplyError>
Splice text via delta, rebase marks, sync lines to \n changes,
cascade island removal for any deleted slot, then normalize.
Islands stay in lockstep with their ISLAND_SLOT chars: a delta that
deletes a slot drops the corresponding Island; a delta that
inserts a raw slot is rejected (ApplyError::IslandSlotInInsert).
Inserted text is sanitized first, mirroring what import applies at the
string boundary: \r and Unicode bidi controls are stripped, and a line
separator (VT, FF, NEL, U+2028, U+2029) becomes a space — the chars
Content::validate forbids.
Sourcepub fn apply_mark_ops(&mut self, ops: &[MarkOp]) -> Result<(), ApplyError>
pub fn apply_mark_ops(&mut self, ops: &[MarkOp]) -> Result<(), ApplyError>
Apply mark ops in final-text coordinates, then normalize.
Sourcepub fn apply_island_ops(&mut self, ops: &[IslandOp]) -> Result<(), ApplyError>
pub fn apply_island_ops(&mut self, ops: &[IslandOp]) -> Result<(), ApplyError>
Apply island ops: replace an entry by id, or insert a slot and its entry together.
Sourcepub fn apply_line_ops(&mut self, ops: &[LineOp]) -> Result<(), ApplyError>
pub fn apply_line_ops(&mut self, ops: &[LineOp]) -> Result<(), ApplyError>
Apply line ops: split/join splice \n; set ops touch metadata only.
Sourcepub fn apply_field_change(
&mut self,
bundle: &ChangeBundle,
) -> Result<(), ApplyError>
pub fn apply_field_change( &mut self, bundle: &ChangeBundle, ) -> Result<(), ApplyError>
One committed field edit bundle: text delta, then island ops, then line
ops, then marks, canonicalized by a single terminal
normalize.
All-or-nothing: on any op’s error self is left exactly as it was. A
bundle carrying ops stages on a scratch copy and swaps in only once every
stage succeeds; the pure-text-delta path skips that clone, since
apply_text_delta validates before mutating.
Stage order is a coordinate contract: each stage reads the text the
earlier ones left. An island insert splices a slot, so a
LineOp::SetKind { kind: Island } in the same bundle settles against a
line that already carries it, and Split/Join and every mark range are
then measured in a frame that includes the new slots.
One terminal normalize suffices because split/join rebase marks through
their \n splice, so the formatting-edge \n-trim commutes with the
line ops, and MarkOp::Remove is coverage-set subtraction, which
commutes with normalize’s same-kind union ((A ∪ B) \ R = (A\R) ∪ (B\R)).
Sourcepub fn map_marks(&self, bundle: &ChangeBundle) -> Result<Vec<Mark>, ApplyError>
pub fn map_marks(&self, bundle: &ChangeBundle) -> Result<Vec<Mark>, ApplyError>
Where bundle’s text-moving channels leave the marks this content
already holds: the final-text coordinates ChangeBundle::mark_ops are
written in, under the rebase rule stated on ChangeBundle.
bundle.mark_ops are ignored, so an editor building them diffs its
intended marks against this instead of predicting the rebase. The answer
is normalized, as the store’s is: marks a text move
drops (out of range, zero-width formatting) are absent, and same-kind
runs a move left adjacent arrive already unioned. A bundle whose
mark_ops are empty therefore names the marks the field will hold.
Errors are apply_field_change’s on the same
ops, minus those only a mark op raises.
Source§impl Content
impl Content
Sourcepub fn from_canonical_json(s: &str) -> Result<Normalized, ParseError>
pub fn from_canonical_json(s: &str) -> Result<Normalized, ParseError>
Parse canonical JSON, normalize, and validate. Returns
ParseError::Invalid for a content that violates its invariants, so
storage cannot silently round-trip a malformed value.