ocs_doc_api: Phase 2b-a (Text/MText annotations)

- CreateText/CreateMText/SetTextContent ops + GetTextContent query (append-only).
- backend add_text/add_mtext/set_text_content/text_content (Text.value/MText.value).
- facade create_text/create_mtext + Text/MText handles with content()/set_content().
- Text/MText coarse bounds + transform (insertion point); ensure_transformable.
- spec Text/MText families; api_reference + binding_schema regenerated; mocks.
- can_modify backend hook (exists + not-locked) for SetTextContent validation.
- consistency baseline extended with the appended variants.
- Test phase2b_text_mtext_create_content_transform -> 605 host lib + 3 consistency green.
This commit is contained in:
Sebastian committed 2026-09-06 09:07:41 +02:00
1 parent 1925a83a25
commit fc0e2c2c9d
16 files changed
+449 -4

No files matched your search

+23
View File
@@ -21,6 +21,8 @@ struct MockBackend {
undo_steps: u32,
bodies: HashMap<ObjectId, KernelBody>,
kinds: HashMap<ObjectId, String>,
/// Stored text content for Text/MText annotations.
text_values: HashMap<ObjectId, String>,
}
impl MockBackend {
@@ -73,6 +75,27 @@ impl DocApiBackend for MockBackend {
fn add_viewport(&mut self, _spec: &ocs_doc_api::ops::ViewportSpec) -> ApiResult<ObjectId> {
Ok(self.alloc("Viewport"))
}
fn add_text(&mut self, spec: &ocs_doc_api::ops::TextSpec) -> ApiResult<ObjectId> {
let id = self.alloc("Text");
self.text_values.insert(id, spec.value.clone());
Ok(id)
}
fn add_mtext(&mut self, spec: &ocs_doc_api::ops::MTextSpec) -> ApiResult<ObjectId> {
let id = self.alloc("MText");
self.text_values.insert(id, spec.value.clone());
Ok(id)
}
fn set_text_content(&mut self, id: ObjectId, value: &str) -> ApiResult<()> {
if let std::collections::hash_map::Entry::Occupied(mut e) = self.text_values.entry(id) {
e.insert(value.to_string());
Ok(())
} else {
Err(ApiError::UnknownId(id))
}
}
fn text_content(&self, id: ObjectId) -> ApiResult<String> {
self.text_values.get(&id).cloned().ok_or(ApiError::UnknownId(id))
}
fn add_vertex(&mut self, id: ObjectId, _at: usize, _point: [f64; 3]) -> ApiResult<()> {
if self.entity_exists(id) {
Ok(())
@@ -53,6 +53,9 @@ fn operation_variants() -> Vec<String> {
Operation::DeleteMany(vec![]),
Operation::CreateInsert(InsertSpec { block_name: String::new(), insert_point: [0.0; 3], scale: 1.0, rotation: 0.0 }),
Operation::CreateViewport(ViewportSpec { center: [0.0; 3], width: 0.0, height: 0.0, view_target: [0.0; 3], view_height: 0.0 }),
Operation::CreateText(ocs_doc_api::ops::TextSpec { value: String::new(), insertion_point: [0.0; 3], height: 0.0, rotation: 0.0 }),
Operation::CreateMText(ocs_doc_api::ops::MTextSpec { value: String::new(), insertion_point: [0.0; 3], height: 0.0 }),
Operation::SetTextContent { id: ObjectId::from_u64(0), value: String::new() },
];
// Derive the variant names by serializing a probe of each and reading the
// bincode variant index -> name map is not available; instead match by
@@ -77,6 +80,7 @@ fn query_variants() -> Vec<String> {
Query::GetVolume { id: ObjectId::from_u64(0) },
Query::GetIntersects { a: ObjectId::from_u64(0), b: ObjectId::from_u64(0) },
Query::GetGeometryRevision,
Query::GetTextContent { id: ObjectId::from_u64(0) },
];
marker
.iter()
@@ -127,6 +131,9 @@ const OPERATION_BASELINE: &[&str] = &[
"DeleteMany",
"CreateInsert",
"CreateViewport",
"CreateText",
"CreateMText",
"SetTextContent",
];
/// Recorded baseline of the `Query` variant order.
@@ -137,6 +144,7 @@ const QUERY_BASELINE: &[&str] = &[
"GetVolume",
"GetIntersects",
"GetGeometryRevision",
"GetTextContent",
];
#[test]