#[non_exhaustive]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.
Invariants (established once by import normalization, checked by
Content::validate): the text holds no \r and no bidi controls; the
count of ISLAND_SLOT equals islands.len(); lines.len() equals the
number of \n-separated segments; marks are normalized (sorted, unioned).
Fields (Non-exhaustive)§
This struct is marked as non-exhaustive
Struct { .. } syntax; cannot be matched against without a wildcard ..; and struct update syntax will not work.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: it
is never stored, so a split/join is a single-char edit with no identity
crisis (there are no paragraph IDs).
marks: Vec<Mark>Marks over char ranges, kept normalized: sorted by
(start, end, kind-ord, 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 const RESERVED_MARK_TYPES: &'static [&'static str]
pub const RESERVED_MARK_TYPES: &'static [&'static str]
Mark type names the projection reserves; an MarkKind::Unknown may
not reuse one (its serialization would parse back as the built-in,
silently dropping its attrs: non-injective).
Two enforcement points, on two lanes. Content::validate catches an
in-process Rust construction. The wire never reaches it: a decoder resolves
the built-in name before the Unknown fallthrough, so a reserved tag
becomes the built-in rather than arriving as an Unknown. The authored
lane therefore rejects the shape up front
(serial::from_authored_value for
whole content; ops::mark_op_from_value
and ops::line_op_from_value for the op
wire), while storage decode stays lenient by design; see there.
This list and its two siblings are re-spelled by hand on the TypeScript
surface: the unions in crates/bindings/wasm/src/engine.rs and the
isUnknown* guards’ tables in
crates/bindings/wasm/runtime/runtime.js. Both are pinned to these
constants by crates/bindings/wasm/tests/known_names_drift.rs.
Slices, not arrays, on all three: an array’s length is part of its type,
and promoting a name into the projection is the motion these lists exist
to absorb.
Sourcepub const RESERVED_LINE_KINDS: &'static [&'static str]
pub const RESERVED_LINE_KINDS: &'static [&'static str]
Line kind names the projection reserves: the LineKind twin of
RESERVED_MARK_TYPES, for the same
injectivity reason.
Sourcepub const RESERVED_CONTAINERS: &'static [&'static str]
pub const RESERVED_CONTAINERS: &'static [&'static str]
Container names the projection reserves: the Container twin of
RESERVED_MARK_TYPES.
Sourcepub fn new(text: String, lines: Vec<Line>) -> Content
pub fn new(text: String, lines: Vec<Line>) -> Content
The text and its per-line attributes: what a content always carries.
Marks and islands start empty and have with_marks /
with_islands beside them.
Constructing does not normalize or check: the invariants in this type’s
docs are the caller’s until validate runs. The codecs
(crate::import, Content::from_canonical_json) establish them, and
are what a consumer normally builds through.
Sourcepub fn with_marks(self, marks: Vec<Mark>) -> Content
pub fn with_marks(self, marks: Vec<Mark>) -> Content
Set marks, the range-anchored marks over the text.
Sourcepub fn with_islands(self, islands: Vec<Island>) -> Content
pub fn with_islands(self, islands: Vec<Island>) -> Content
Set 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. A single line
can never continues (line 0 is always false), so that dimension is
implied. Content::empty is inline (one empty Para), so a blank or
zero-filled 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 is a plain Para sitting in no container. It is
the multi-line generalization of is_inline (which
additionally pins the content to one line) with the mark/island exclusion
made explicit: a plaintext value carries prose the author navigates but
no formatting. continues is unconstrained: a lone \n may be a
within-paragraph break. Content::empty is plain.
This is the plaintext analogue of is_inline, enforced at coercion and
validation with the NotPlain error; 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 content carries no renderable content: the text is empty or
whitespace-only. An island slot (ISLAND_SLOT, U+FFFC) is not
whitespace, so an island-bearing content is never blank. Body-disabled
validation and round-trip emit key on it.
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 marks in place: drop zero-width formatting, union same-kind formatting that is adjacent or overlapping, recursively key-sort island props and unknown-mark attrs, 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 (the content goes
away with its slot); a delta that inserts a raw slot is rejected
(ApplyError::IslandSlotInInsert), islands are created through
IslandOp::Insert, never a text splice, so a slot arriving here would
orphan.
Inserted text is sanitized first: \r and Unicode bidi controls (the
chars Content::validate forbids) are stripped, mirroring the
normalization import applies at the string boundary. The text-delta
channel is the other way text enters the content, so without this an
insert of \r or a bidi control returned Ok while leaving a content
that fails validate().
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, so a
caller need not snapshot-and-restore around a failed bundle. A bundle
carrying ops has several fallible stages that would otherwise partially
commit, so it is staged on a scratch copy and swapped in only once every
stage succeeds. The pure-text-delta path (the per-keystroke hot path)
skips the clone: apply_text_delta validates the delta before mutating,
so it is already atomic on the errors a caller can provoke.
Stage order is a coordinate contract, not a convenience: each stage
reads the text the earlier ones left. Island ops sit between the delta
and the line ops because both neighbors need them there. An island insert
splices a slot, so a LineOp::SetKind { kind: Island } in the same bundle
can only validate against a line that already carries it; Split/Join
and every mark range are then measured in a frame that includes the new
slots. The one-bundle block island (IslandOp::Insert) follows from
that.
The stages run on their non-normalizing inner forms and normalize runs
once at the end. One terminal normalize suffices because split/join
rebase marks through their \n splice
(map_pos semantics): the
formatting-edge \n-trim then commutes with the line ops (trim-per-stage
and trim-once converge), and MarkOp::Remove is coverage-set
subtraction, which commutes with normalize’s same-kind union
((A ∪ B) \ R = (A\R) ∪ (B\R)). One canonicalization point, one pass.
Source§impl Content
impl Content
Sourcepub fn to_canonical_json(&self) -> String
pub fn to_canonical_json(&self) -> String
Serialize to canonical JSON bytes. Normalizes a copy first, so the output
is canonical regardless of the caller’s mark/island order. Every object
key is sorted recursively so the bytes do not depend on
serde_json’s preserve_order feature being enabled in the consumer’s
crate graph: the canonical form is feature-independent.
Sourcepub fn from_canonical_json(s: &str) -> Result<Content, ParseError>
pub fn from_canonical_json(s: &str) -> Result<Content, ParseError>
Parse canonical JSON, normalize (idempotent), and validate. Returns
ParseError::Invalid for a content that violates its invariants, so
storage cannot silently round-trip a malformed value.
from_canonical_json(to_canonical_json(x)) round-trips to a canonical
value and re-serializes to identical bytes.
Trait Implementations§
impl StructuralPartialEq for Content
Auto Trait Implementations§
impl Freeze for Content
impl RefUnwindSafe for Content
impl Send for Content
impl Sync for Content
impl Unpin for Content
impl UnsafeUnpin for Content
impl UnwindSafe for Content
Blanket Implementations§
Source§impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for Swhere
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
D: AdaptFrom<S, Swp, Dwp, T>,
impl<S, D, Swp, Dwp, T> AdaptInto<D, Swp, Dwp, T> for Swhere
T: Real + Zero + Arithmetics + Clone,
Swp: WhitePoint<T>,
Dwp: WhitePoint<T>,
D: AdaptFrom<S, Swp, Dwp, T>,
Source§fn adapt_into_using<M>(self, method: M) -> Dwhere
M: TransformMatrix<T>,
fn adapt_into_using<M>(self, method: M) -> Dwhere
M: TransformMatrix<T>,
Source§fn adapt_into(self) -> D
fn adapt_into(self) -> D
Source§impl<T, C> ArraysFrom<C> for Twhere
C: IntoArrays<T>,
impl<T, C> ArraysFrom<C> for Twhere
C: IntoArrays<T>,
Source§fn arrays_from(colors: C) -> T
fn arrays_from(colors: C) -> T
Source§impl<T, C> ArraysInto<C> for Twhere
C: FromArrays<T>,
impl<T, C> ArraysInto<C> for Twhere
C: FromArrays<T>,
Source§fn arrays_into(self) -> C
fn arrays_into(self) -> C
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<WpParam, T, U> Cam16IntoUnclamped<WpParam, T> for Uwhere
T: FromCam16Unclamped<WpParam, U>,
impl<WpParam, T, U> Cam16IntoUnclamped<WpParam, T> for Uwhere
T: FromCam16Unclamped<WpParam, U>,
Source§type Scalar = <T as FromCam16Unclamped<WpParam, U>>::Scalar
type Scalar = <T as FromCam16Unclamped<WpParam, U>>::Scalar
parameters when converting.Source§fn cam16_into_unclamped(
self,
parameters: BakedParameters<WpParam, <U as Cam16IntoUnclamped<WpParam, T>>::Scalar>,
) -> T
fn cam16_into_unclamped( self, parameters: BakedParameters<WpParam, <U as Cam16IntoUnclamped<WpParam, T>>::Scalar>, ) -> T
self into C, using the provided parameters.Source§impl<T> CheckedAs for T
impl<T> CheckedAs for T
Source§fn checked_as<Dst>(self) -> Option<Dst>where
T: CheckedCast<Dst>,
fn checked_as<Dst>(self) -> Option<Dst>where
T: CheckedCast<Dst>,
Source§impl<Src, Dst> CheckedCastFrom<Src> for Dstwhere
Src: CheckedCast<Dst>,
impl<Src, Dst> CheckedCastFrom<Src> for Dstwhere
Src: CheckedCast<Dst>,
Source§fn checked_cast_from(src: Src) -> Option<Dst>
fn checked_cast_from(src: Src) -> Option<Dst>
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T, C> ComponentsFrom<C> for Twhere
C: IntoComponents<T>,
impl<T, C> ComponentsFrom<C> for Twhere
C: IntoComponents<T>,
Source§fn components_from(colors: C) -> T
fn components_from(colors: C) -> T
impl<T> ErasedDestructor for Twhere
T: 'static,
Source§impl<T> FromAngle<T> for T
impl<T> FromAngle<T> for T
Source§fn from_angle(angle: T) -> T
fn from_angle(angle: T) -> T
angle.Source§impl<T, U> FromStimulus<U> for Twhere
U: IntoStimulus<T>,
impl<T, U> FromStimulus<U> for Twhere
U: IntoStimulus<T>,
Source§fn from_stimulus(other: U) -> T
fn from_stimulus(other: U) -> T
other into Self, while performing the appropriate scaling,
rounding and clamping.Source§impl<T, U> IntoAngle<U> for Twhere
U: FromAngle<T>,
impl<T, U> IntoAngle<U> for Twhere
U: FromAngle<T>,
Source§fn into_angle(self) -> U
fn into_angle(self) -> U
T.Source§impl<WpParam, T, U> IntoCam16Unclamped<WpParam, T> for Uwhere
T: Cam16FromUnclamped<WpParam, U>,
impl<WpParam, T, U> IntoCam16Unclamped<WpParam, T> for Uwhere
T: Cam16FromUnclamped<WpParam, U>,
Source§type Scalar = <T as Cam16FromUnclamped<WpParam, U>>::Scalar
type Scalar = <T as Cam16FromUnclamped<WpParam, U>>::Scalar
parameters when converting.Source§fn into_cam16_unclamped(
self,
parameters: BakedParameters<WpParam, <U as IntoCam16Unclamped<WpParam, T>>::Scalar>,
) -> T
fn into_cam16_unclamped( self, parameters: BakedParameters<WpParam, <U as IntoCam16Unclamped<WpParam, T>>::Scalar>, ) -> T
self into C, using the provided parameters.Source§impl<T, U> IntoColor<U> for Twhere
U: FromColor<T>,
impl<T, U> IntoColor<U> for Twhere
U: FromColor<T>,
Source§fn into_color(self) -> U
fn into_color(self) -> U
Source§impl<T, U> IntoColorUnclamped<U> for Twhere
U: FromColorUnclamped<T>,
impl<T, U> IntoColorUnclamped<U> for Twhere
U: FromColorUnclamped<T>,
Source§fn into_color_unclamped(self) -> U
fn into_color_unclamped(self) -> U
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoStimulus<T> for T
impl<T> IntoStimulus<T> for T
Source§fn into_stimulus(self) -> T
fn into_stimulus(self) -> T
self into T, while performing the appropriate scaling,
rounding and clamping.Source§impl<T> OverflowingAs for T
impl<T> OverflowingAs for T
Source§fn overflowing_as<Dst>(self) -> (Dst, bool)where
T: OverflowingCast<Dst>,
fn overflowing_as<Dst>(self) -> (Dst, bool)where
T: OverflowingCast<Dst>,
Source§impl<Src, Dst> OverflowingCastFrom<Src> for Dstwhere
Src: OverflowingCast<Dst>,
impl<Src, Dst> OverflowingCastFrom<Src> for Dstwhere
Src: OverflowingCast<Dst>,
Source§fn overflowing_cast_from(src: Src) -> (Dst, bool)
fn overflowing_cast_from(src: Src) -> (Dst, bool)
Source§impl<T> Pointable for T
impl<T> Pointable for T
Source§impl<T> SaturatingAs for T
impl<T> SaturatingAs for T
Source§fn saturating_as<Dst>(self) -> Dstwhere
T: SaturatingCast<Dst>,
fn saturating_as<Dst>(self) -> Dstwhere
T: SaturatingCast<Dst>,
Source§impl<Src, Dst> SaturatingCastFrom<Src> for Dstwhere
Src: SaturatingCast<Dst>,
impl<Src, Dst> SaturatingCastFrom<Src> for Dstwhere
Src: SaturatingCast<Dst>,
Source§fn saturating_cast_from(src: Src) -> Dst
fn saturating_cast_from(src: Src) -> Dst
Source§impl<T> StrictAs for T
impl<T> StrictAs for T
Source§fn strict_as<Dst>(self) -> Dstwhere
T: StrictCast<Dst>,
fn strict_as<Dst>(self) -> Dstwhere
T: StrictCast<Dst>,
Source§impl<Src, Dst> StrictCastFrom<Src> for Dstwhere
Src: StrictCast<Dst>,
impl<Src, Dst> StrictCastFrom<Src> for Dstwhere
Src: StrictCast<Dst>,
Source§fn strict_cast_from(src: Src) -> Dst
fn strict_cast_from(src: Src) -> Dst
Source§impl<U, T> ToOwnedObj<U> for Twhere
U: FromObjRef<T>,
impl<U, T> ToOwnedObj<U> for Twhere
U: FromObjRef<T>,
Source§fn to_owned_obj(&self, data: FontData<'_>) -> U
fn to_owned_obj(&self, data: FontData<'_>) -> U
T, using the provided data to resolve any offsets.Source§impl<U, T> ToOwnedTable<U> for Twhere
U: FromTableRef<T>,
impl<U, T> ToOwnedTable<U> for Twhere
U: FromTableRef<T>,
fn to_owned_table(&self) -> U
Source§impl<T, C> TryComponentsInto<C> for Twhere
C: TryFromComponents<T>,
impl<T, C> TryComponentsInto<C> for Twhere
C: TryFromComponents<T>,
Source§type Error = <C as TryFromComponents<T>>::Error
type Error = <C as TryFromComponents<T>>::Error
try_into_colors fails to cast.Source§fn try_components_into(self) -> Result<C, <T as TryComponentsInto<C>>::Error>
fn try_components_into(self) -> Result<C, <T as TryComponentsInto<C>>::Error>
Source§impl<T, U> TryIntoColor<U> for Twhere
U: TryFromColor<T>,
impl<T, U> TryIntoColor<U> for Twhere
U: TryFromColor<T>,
Source§fn try_into_color(self) -> Result<U, OutOfBounds<U>>
fn try_into_color(self) -> Result<U, OutOfBounds<U>>
OutOfBounds error is returned which contains
the unclamped color. Read more