diff --git a/Cargo.lock b/Cargo.lock index 26d334147..fe30ceec1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2409,21 +2409,11 @@ dependencies = [ "jian-ops-schema", "jian-skia", "op-editor-core", - "openpencil-shell-core", + "op-editor-ui", "serde", "serde_json", ] -[[package]] -name = "openpencil-shell-core" -version = "0.1.0" -dependencies = [ - "jian-core", - "op-editor-core", - "op-editor-ui", - "op-i18n", -] - [[package]] name = "option-ext" version = "0.2.0" diff --git a/crates/op-editor-core/Cargo.toml b/crates/op-editor-core/Cargo.toml index d7df5d1db..20c1fd0f0 100644 --- a/crates/op-editor-core/Cargo.toml +++ b/crates/op-editor-core/Cargo.toml @@ -16,5 +16,5 @@ jian-core = { path = "../../vendor/jian/crates/jian-core" } op-i18n = { path = "../op-i18n" } # render_backend.rs uses glam::Vec2 as Point2D (spec §5.2). Pinned to the -# same version openpencil-shell-core uses so both crates resolve one glam. +# same version op-editor-ui uses so both crates resolve one glam. glam = { version = "0.29", default-features = false, features = ["std"] } diff --git a/crates/op-host-desktop/Cargo.toml b/crates/op-host-desktop/Cargo.toml index 6c98bcbd4..1c79417e6 100644 --- a/crates/op-host-desktop/Cargo.toml +++ b/crates/op-host-desktop/Cargo.toml @@ -27,7 +27,7 @@ op-host-native = { path = "../op-host-native", version = "0.1.0" } # old shell-core `Document`. op-editor-core = { path = "../op-editor-core" } # Phase 7 strangler reorg: the MCP tool registry + JSON-RPC stdio layer -# (`mcp.rs` + `mcp/*`) was extracted out of openpencil-shell-core into the +# (`mcp.rs` + `mcp/*`) was extracted out of the dissolved shell-core into the # op-mcp crate; `mcp_serve.rs` registers tools through `op_mcp::*`. op-mcp = { path = "../op-mcp" } # Phase 7 strangler reorg: the transport-free AI chat data shapes @@ -37,7 +37,7 @@ op-mcp = { path = "../op-mcp" } op-ai = { path = "../op-ai" } # Canonical `.op` (PenDocument) → shell `Document` loader / adapter. # Extracted out of this binary into a shared library crate so library -# crates (openpencil-shell-native) can reuse the conversion; +# crates (op-host-native) can reuse the conversion; # `persistence.rs` keeps only the desktop-only `rfd` Save/Open + error # dialogs. op-pen-loader = { path = "../op-pen-loader" } diff --git a/crates/op-pen-loader/Cargo.toml b/crates/op-pen-loader/Cargo.toml index 334144b0e..3237f6fc6 100644 --- a/crates/op-pen-loader/Cargo.toml +++ b/crates/op-pen-loader/Cargo.toml @@ -17,9 +17,10 @@ jian-core = { path = "../../vendor/jian/crates/jian-core" } # `MeasureBackend` plugged into the layout engine. Desktop-side, so # depending on skia is fine; this crate does NOT need to be wasm-clean. jian-skia = { path = "../../vendor/jian/crates/jian-skia", features = ["textlayout"] } -# `Document` / `Node` / `VariableTable` etc. — the shell-core model -# this crate converts INTO. -openpencil-shell-core = { path = "../openpencil-shell-core", version = "0.1.0" } +# Phase 7.3 reorg: openpencil-shell-core dissolved. The layout scene / +# scene-var / render-backend facade this crate converts INTO now +# resolves through the op-editor-ui crate. +op-editor-ui = { path = "../op-editor-ui" } # `EditorState` — the canonical editor-state superset this crate # derives a paint-only `Document` (incl. its `var_table`) FROM. Lives # in op-pen-loader, NOT op-editor-core, so op-editor-core stays free of diff --git a/crates/op-pen-loader/src/adapter.rs b/crates/op-pen-loader/src/adapter.rs index 5c4462b0f..01d71cb18 100644 --- a/crates/op-pen-loader/src/adapter.rs +++ b/crates/op-pen-loader/src/adapter.rs @@ -91,8 +91,8 @@ pub fn pen_document_to_payload(doc: &PenDocument) -> LoadedDoc { /// the supported `VariableDefinition` variants; unknown future /// `VariableKind`s round-trip via their `Color/Number/Boolean/String` /// label since the enums are isomorphic. -pub fn build_var_table(doc: &PenDocument) -> openpencil_shell_core::scene_vars::VariableTable { - use openpencil_shell_core::scene_vars::{ +pub fn build_var_table(doc: &PenDocument) -> op_editor_ui::scene_vars::VariableTable { + use op_editor_ui::scene_vars::{ ThemeAxis, ThemedValue, Variable, VariableKind, VariableTable, VariableValue, }; let mut out = VariableTable::default(); @@ -137,8 +137,8 @@ pub fn build_var_table(doc: &PenDocument) -> openpencil_shell_core::scene_vars:: fn map_scalar( s: &jian_ops_schema::variable::VariableScalar, -) -> openpencil_shell_core::scene_vars::VariableScalar { - use openpencil_shell_core::scene_vars::VariableScalar; +) -> op_editor_ui::scene_vars::VariableScalar { + use op_editor_ui::scene_vars::VariableScalar; match s { jian_ops_schema::variable::VariableScalar::Bool(b) => VariableScalar::Bool(*b), jian_ops_schema::variable::VariableScalar::Num(n) => VariableScalar::Num(*n), diff --git a/crates/op-pen-loader/src/effects.rs b/crates/op-pen-loader/src/effects.rs index 541be5564..8daaf8b71 100644 --- a/crates/op-pen-loader/src/effects.rs +++ b/crates/op-pen-loader/src/effects.rs @@ -4,7 +4,7 @@ use jian_ops_schema::node::PenNode; use jian_ops_schema::style::PenEffect; -use openpencil_shell_core::layout_scene::{DropShadow, Effect}; +use op_editor_ui::layout_scene::{DropShadow, Effect}; use serde::{Deserialize, Serialize}; /// Serializable mirror of `document::DropShadow`. @@ -50,7 +50,7 @@ fn shadow_payload_to_effect(s: &ShadowPayload) -> Effect { offset_x: s.offset_x, offset_y: s.offset_y, blur: s.blur, - color: openpencil_shell_core::Color { + color: op_editor_ui::Color { r: s.color[0], g: s.color[1], b: s.color[2], @@ -177,7 +177,7 @@ fn parse_hex(s: &str) -> Option<[f32; 4]> { #[cfg(test)] mod tests { use super::*; - use openpencil_shell_core::Color; + use op_editor_ui::Color; #[test] fn parse_css_color_handles_rgba_and_hex() { diff --git a/crates/op-pen-loader/src/layout_scene.rs b/crates/op-pen-loader/src/layout_scene.rs index 713a5815e..57879d8f7 100644 --- a/crates/op-pen-loader/src/layout_scene.rs +++ b/crates/op-pen-loader/src/layout_scene.rs @@ -21,12 +21,12 @@ //! transforms on them are lossless format conversions (colour array → //! struct, kind / fill-type string → enum). -use openpencil_shell_core::layout_scene::NodeKind; -use openpencil_shell_core::scene_vars::VariableTable; -use openpencil_shell_core::layout_scene::{ +use op_editor_ui::layout_scene::NodeKind; +use op_editor_ui::scene_vars::VariableTable; +use op_editor_ui::layout_scene::{ LayoutScene, SceneFillType, SceneNode, SceneStroke, ScenePage, }; -use openpencil_shell_core::Color; +use op_editor_ui::Color; use crate::editor_state_var_table; use crate::payload::{DocPayload, NodePayload, StrokePayload}; @@ -82,7 +82,7 @@ pub fn editor_state_to_layout_scene(state: &op_editor_core::EditorState) -> Layo /// wins over the node's authored colour, mirroring the canvas /// painter's `var_table.fill_for(id).or(node.fill)`. fn node_payload_to_scene(node: &NodePayload, var_table: &VariableTable) -> SceneNode { - use openpencil_shell_core::{Point2D, Rect}; + use op_editor_ui::{Point2D, Rect}; let node_id = op_editor_core::NodeId::new(node.id.clone()); SceneNode { id: node.id.clone(), diff --git a/crates/op-pen-loader/src/lib.rs b/crates/op-pen-loader/src/lib.rs index 92b1512bc..01d2f45e4 100644 --- a/crates/op-pen-loader/src/lib.rs +++ b/crates/op-pen-loader/src/lib.rs @@ -1,7 +1,7 @@ //! Canonical `.op` (`PenDocument`) → `LayoutScene` loader / adapter. //! -//! Extracted from `openpencil-desktop` so library crates (notably -//! `openpencil-shell-native`) can reuse the conversion without +//! Extracted from `op-host-desktop` so library crates (notably +//! `op-host-native`) can reuse the conversion without //! depending on a binary crate. //! //! Three layers: @@ -13,7 +13,7 @@ //! - [`payload`] holds the `DocPayload` serde DTOs + the strict / //! best-effort `load_canonical` parser. //! - [`layout_scene`] re-shapes a resolved `DocPayload` into the -//! paint-only `openpencil_shell_core::layout_scene::LayoutScene`. +//! paint-only `op_editor_ui::layout_scene::LayoutScene`. //! //! This crate is desktop-side and may depend on skia; it does NOT //! need to be wasm-clean. The `rfd` Save/Open dialogs + error @@ -63,10 +63,10 @@ pub use variables::{var_table_from_payload, var_table_to_payload, VarTablePayloa /// any shell-core dependency to keep its `wasm32-unknown-unknown` /// invariant. /// -/// [`VariableTable`]: openpencil_shell_core::scene_vars::VariableTable +/// [`VariableTable`]: op_editor_ui::scene_vars::VariableTable pub fn editor_state_var_table( state: &op_editor_core::EditorState, -) -> openpencil_shell_core::scene_vars::VariableTable { +) -> op_editor_ui::scene_vars::VariableTable { use op_editor_core::NodeId; // Persisted definitions + theme axes — `EditorState.doc` is a // `PenDocument`, so `build_var_table` harvests them directly. diff --git a/crates/op-pen-loader/src/variables.rs b/crates/op-pen-loader/src/variables.rs index 85e2de2e3..600c13d57 100644 --- a/crates/op-pen-loader/src/variables.rs +++ b/crates/op-pen-loader/src/variables.rs @@ -11,7 +11,7 @@ use std::collections::BTreeMap; -use openpencil_shell_core::scene_vars::{ +use op_editor_ui::scene_vars::{ ThemeAxis, ThemedValue, Variable, VariableKind, VariableScalar, VariableTable, VariableValue, }; @@ -219,7 +219,7 @@ pub fn var_table_from_payload(p: &VarTablePayload) -> VariableTable { #[cfg(test)] mod tests { use super::*; - use openpencil_shell_core::scene_vars::VariableValue; + use op_editor_ui::scene_vars::VariableValue; fn sample_table() -> VariableTable { let mut t = VariableTable::default(); diff --git a/crates/openpencil-shell-core/Cargo.toml b/crates/openpencil-shell-core/Cargo.toml deleted file mode 100644 index e53daa37b..000000000 --- a/crates/openpencil-shell-core/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "openpencil-shell-core" -version.workspace = true -edition.workspace = true -rust-version.workspace = true -license.workspace = true -description = "OpenPencil shell — re-export shim over op-editor-ui / op-editor-core / op-i18n (Phase 7 strangler reorg; dissolved in Task 7.3)" - -[lib] -name = "openpencil_shell_core" -path = "src/lib.rs" - -[dependencies] -# v19 pivot: re-export Jian render/geometry/scene types (spec §5.2). The -# `jian` module wraps jian-core; the crate root also re-exports -# `jian_core::gesture` event types. -jian-core = { path = "../../vendor/jian/crates/jian-core", version = "0.0.1" } - -# Phase 3 strangler reorg: i18n extracted into op-i18n; re-exported as `i18n`. -op-i18n = { path = "../op-i18n" } - -# Phase 4 strangler reorg: the wasm-clean RenderBackend trait + facade -# types live in op-editor-core; re-exported as `render_backend`. -op-editor-core = { path = "../op-editor-core" } - -# Phase 7 strangler reorg: the widget facade + theme + layout scene + -# scene-var aggregation live in op-editor-ui; re-exported so the hosts -# keep resolving `openpencil_shell_core::widgets` / `::theme` / etc. -op-editor-ui = { path = "../op-editor-ui" } diff --git a/crates/openpencil-shell-core/src/jian.rs b/crates/openpencil-shell-core/src/jian.rs deleted file mode 100644 index 365510cff..000000000 --- a/crates/openpencil-shell-core/src/jian.rs +++ /dev/null @@ -1,41 +0,0 @@ -//! Jian re-export module (spec v19 §2 / §1.2). -//! -//! `openpencil-shell-core` is a thin Jian wrapper — it re-exports -//! `jian_core::render::{DrawOp, Paint, TextRun, …}` through the OP layer for -//! shell-native's internal translation. geometry / scene types get a `Jian*` -//! prefix to avoid clashing with OP's own `Rect` / `Color` (`render_backend.rs`). -//! -//! **Contract (spec §5.2.1, line 746)**: inside the NativeBackend impl, the -//! v19 wrapper **must not let widget code see `jian_core::render::DrawOp` -//! directly** — widgets only call OP `RenderBackend` methods. These re-exports -//! are for **shell-native internal** translation, not for widget code. - -// ──────────────────────────────────────────────────────────────────────────── -// jian_core::render — DrawOp command buffer + Paint / TextRun / other draw descriptors -// ──────────────────────────────────────────────────────────────────────────── - -pub use jian_core::render::{ - BorderRadii, DrawOp, GradientStop, ImageSource, LinearGradient, Paint, PathCommand, - RadialGradient, RenderCommand, ShadowSpec, StrokeOp, TextAlign, TextRun, -}; - -// ──────────────────────────────────────────────────────────────────────────── -// jian_core::geometry — `Jian*` prefix to avoid clashing with OP `Rect` -// ──────────────────────────────────────────────────────────────────────────── - -/// Jian's axis-aligned rectangle (`euclid::Rect`). -/// OP's own `crate::render_backend::Rect` is for the widget facade; shell-native -/// translates OP `Rect` → `JianRect` internally (`origin/size` → `euclid::Rect::new`). -pub type JianRect = jian_core::geometry::Rect; -pub type Size = jian_core::geometry::Size; -pub type JianPoint = jian_core::geometry::Point; -pub type Affine2 = jian_core::geometry::Affine2; - -// ──────────────────────────────────────────────────────────────────────────── -// jian_core::scene — Color (packed u32) gets a `Jian*` prefix -// ──────────────────────────────────────────────────────────────────────────── - -/// Jian's packed-RGBA color (`pub struct Color(pub u32)`); distinct from OP's -/// `crate::render_backend::Color` (RGBA f32 quad). NativeBackend bit-packs when -/// translating OP Color → JianColor. -pub type JianColor = jian_core::scene::Color; diff --git a/crates/openpencil-shell-core/src/lib.rs b/crates/openpencil-shell-core/src/lib.rs deleted file mode 100644 index feed72a2d..000000000 --- a/crates/openpencil-shell-core/src/lib.rs +++ /dev/null @@ -1,47 +0,0 @@ -//! OpenPencil shell core — re-export shim. -//! -//! Per spec v19 §1.2 (FROZEN 2026-05-04): this crate must compile on -//! wasm32-unknown-unknown. winit / accesskit_winit / skia-safe live in -//! `openpencil-shell-native`; wasm-bindgen / web-sys / CanvasKit live in -//! `openpencil-shell-web`. -//! -//! Phase 7 strangler reorg: the widget facade, theme, layout-resolved -//! render scene, and design-variable aggregation were extracted into the -//! `op-editor-ui` crate; i18n into `op-i18n`; the `RenderBackend` trait -//! into `op-editor-core`; the MCP / codegen / figma / AI-chat modules -//! into their own `op-*` crates. What remains here is a thin re-export -//! shim so the hosts (`openpencil-shell-native` / `openpencil-shell-web` -//! / `openpencil-desktop`) keep resolving `openpencil_shell_core::*` -//! paths unchanged until the Task 7.3 host rename dissolves this crate. -//! -//! The [`jian`] module re-exports `jian_core::render::{DrawOp, Paint, -//! TextRun, …}` + geometry/scene aliases for shell-native's internal -//! translation (widget code never sees them). - -pub mod jian; - -// Phase 3 strangler reorg: i18n extracted into op-i18n. Re-exported as -// `i18n` so `crate::i18n::translate` / `crate::i18n::Locale` resolve. -pub use op_i18n as i18n; - -// Phase 4 strangler reorg: the wasm-clean RenderBackend trait moved into -// op-editor-core. Re-exported as `render_backend`. -pub use op_editor_core::render_backend; - -// Phase 7 strangler reorg: the widget facade + theme + layout scene + -// scene-var aggregation moved into op-editor-ui. Re-exported so -// `openpencil_shell_core::widgets` / `::theme` / `::layout_scene` / -// `::layout_scene_hit` / `::scene_vars` paths still resolve. -pub use op_editor_ui::{layout_scene, layout_scene_hit, scene_vars, theme, widgets}; - -// Re-export the primary API for upstream crates / widgets / tests. -pub use op_editor_core::render_backend::{Color, Point2D, Rect, RenderBackend, TextLayout}; -pub use op_editor_ui::Theme; - -/// Re-exports of Jian gesture / event types so shell consumers can use the -/// canonical Jian types directly without an OP-specific translation layer. -pub use jian_core::gesture::{ - FocusEvent, ImeEvent, ImeKind, KeyCode, KeyEvent, KeyLocation, KeyState, KeyValue, Modifiers, - MouseButtons, NamedKey, PointerEvent, PointerId, PointerKind, PointerPhase, ScrollMode, - WheelEvent, -}; diff --git a/crates/openpencil-shell-core/tests/gesture_re_export.rs b/crates/openpencil-shell-core/tests/gesture_re_export.rs deleted file mode 100644 index e5f141999..000000000 --- a/crates/openpencil-shell-core/tests/gesture_re_export.rs +++ /dev/null @@ -1,111 +0,0 @@ -use openpencil_shell_core::{ - FocusEvent, ImeEvent, ImeKind, KeyCode, KeyEvent, KeyLocation, KeyState, KeyValue, Modifiers, - MouseButtons, NamedKey, PointerEvent, PointerId, PointerKind, PointerPhase, ScrollMode, - WheelEvent, -}; - -#[test] -fn pointer_event_is_re_exported_from_jian() { - let event = PointerEvent::simple( - 7, - PointerPhase::Down, - jian_core::geometry::Point::new(12.0, 34.0), - ); - - let _: PointerEvent = event.clone(); - let _: jian_core::gesture::PointerEvent = event.clone(); - - assert_eq!(event.id, PointerId(7)); - assert_eq!(event.kind, PointerKind::Touch); - assert_eq!(event.phase, PointerPhase::Down); - assert_eq!(event.position.x, 12.0); - assert_eq!(event.position.y, 34.0); - assert!(event.buttons.contains(MouseButtons::LEFT)); - assert!(event.modifiers.is_empty()); -} - -#[test] -fn pointer_modifier_and_button_flags_keep_jian_names() { - let mods = Modifiers::SHIFT | Modifiers::CMD; - assert!(mods.contains(Modifiers::SHIFT)); - assert!(mods.contains(Modifiers::CMD)); - assert!(!mods.contains(Modifiers::CTRL)); - - let buttons = MouseButtons::LEFT | MouseButtons::RIGHT; - assert!(buttons.contains(MouseButtons::LEFT)); - assert!(buttons.contains(MouseButtons::RIGHT)); - assert!(!buttons.contains(MouseButtons::MIDDLE)); -} - -#[test] -fn key_event_is_re_exported_from_jian_with_all_w3c_fields() { - let event = KeyEvent { - key: KeyValue::Named(NamedKey::Enter), - code: KeyCode::Enter, - location: KeyLocation::Right, - modifiers: Modifiers::SHIFT, - state: KeyState::Pressed, - repeat: true, - is_composing: true, - }; - let _: jian_core::gesture::KeyEvent = event.clone(); - // Round 2 Q5 fix: assert every W3C field reads back the value we set - // so cross-crate type identity AND field-level binary compat are - // both verified through the OP re-export path. - assert_eq!(event.key, KeyValue::Named(NamedKey::Enter)); - assert_eq!(event.code, KeyCode::Enter); - assert_eq!(event.location, KeyLocation::Right); - assert!(event.modifiers.contains(Modifiers::SHIFT)); - assert_eq!(event.state, KeyState::Pressed); - assert!(event.repeat); - assert!(event.is_composing); -} - -#[test] -fn ime_event_is_re_exported_from_jian() { - let event = ImeEvent { - kind: ImeKind::CompositionUpdate { - selection: Some(0..6), - }, - text: "你好".to_string(), - }; - let _: jian_core::gesture::ImeEvent = event.clone(); - assert_eq!(event.text, "你好"); - match event.kind { - ImeKind::CompositionUpdate { selection } => assert_eq!(selection, Some(0..6)), - _ => panic!("expected CompositionUpdate"), - } -} - -#[test] -fn focus_event_is_re_exported_from_jian_with_all_w3c_fields() { - let event = FocusEvent { - gained: false, - node_id_hint: Some(11), - related_node_id_hint: Some(7), - }; - let _: jian_core::gesture::FocusEvent = event; - // Round 2 Q5 fix: assert all three W3C fields, not just gained. - assert!(!event.gained); - assert_eq!(event.node_id_hint, Some(11)); - assert_eq!(event.related_node_id_hint, Some(7)); -} - -#[test] -fn wheel_event_is_re_exported_from_jian_with_w3c_fields() { - let mut event = WheelEvent::simple( - jian_core::geometry::Point::new(10.0, 20.0), - jian_core::geometry::Point::new(0.0, 120.0), - ); - // Defaults from WheelEvent::simple - assert_eq!(event.mode, ScrollMode::Pixel); - assert_eq!(event.delta_z, 0.0); - // Round 2 Q5 fix: assert mode + delta_z mutability + roundtrip - // through the OP re-export path matches Jian's behavior. - event.mode = ScrollMode::Line; - event.delta_z = -3.0; - assert_eq!(event.mode, ScrollMode::Line); - assert_eq!(event.delta_z, -3.0); - assert_eq!(event.delta.x, 0.0); - assert_eq!(event.delta.y, 120.0); -} diff --git a/crates/openpencil-shell-core/tests/jian_re_export.rs b/crates/openpencil-shell-core/tests/jian_re_export.rs deleted file mode 100644 index e2aceb31f..000000000 --- a/crates/openpencil-shell-core/tests/jian_re_export.rs +++ /dev/null @@ -1,87 +0,0 @@ -//! Task 1 Step 20-21: prove the Jian re-export wrapper compiles & is usable. -//! -//! Two anchor invariants for spec v19 §2 / §5.2: -//! 1. `DrawOp::Rect` constructible **through the OP re-export path** -//! (`openpencil_shell_core::jian::DrawOp` — used by shell-native's internal translation). -//! 2. `TextLayout::single_run` produces **exactly one** `TextRun` (spec §5.2, -//! confirming the explicit-field replacement for `..Default::default()` is semantically equivalent). - -use openpencil_shell_core::jian::{DrawOp, JianRect, Paint}; -use openpencil_shell_core::render_backend::{Color, Point2D, TextLayout}; - -#[test] -fn drawop_rect_constructible_via_re_export() { - // Construct a Jian DrawOp::Rect via the OP re-export path — proves shell-core's - // jian module exposes jian_core::render::{DrawOp, Paint}. - let rect = JianRect::new( - jian_core::geometry::Point::new(0.0, 0.0), - jian_core::geometry::Size::new(100.0, 50.0), - ); - let paint = Paint::solid(jian_core::scene::Color::rgb(255, 0, 0)); - let op = DrawOp::Rect { rect, paint }; - - match op { - DrawOp::Rect { rect, .. } => { - assert_eq!(rect.size.width, 100.0); - assert_eq!(rect.size.height, 50.0); - } - _ => panic!("expected DrawOp::Rect"), - } -} - -#[test] -fn text_layout_single_run_creates_one_run() { - // Explicit-field construction (TextRun has no Default impl) — proves the - // spec §5.2 single_run path produces exactly one run with content / - // font_family / font_size set from the caller's arguments. - let layout = TextLayout::single_run( - "Hello", - "system-ui", - 16.0, - jian_core::scene::Color::rgb(0, 0, 0), - Point2D::new(10.0, 20.0), - ); - - assert_eq!(layout.runs().len(), 1); - - let run = &layout.runs()[0]; - assert_eq!(run.content, "Hello"); - assert_eq!(run.font_family, "system-ui"); - assert_eq!(run.font_size, 16.0); - assert_eq!(run.font_weight, 400); - assert_eq!(run.origin.x, 10.0); - assert_eq!(run.origin.y, 20.0); - assert_eq!(run.max_width, 0.0); - assert_eq!(run.line_height, 0.0); -} - -#[test] -fn text_layout_translated_offsets_origin() { - // translated() adds offset to each run's origin; the original layout is unchanged. - let layout = TextLayout::single_run( - "World", - "system-ui", - 14.0, - jian_core::scene::Color::rgb(0, 0, 0), - Point2D::new(5.0, 10.0), - ); - let shifted = layout.translated(Point2D::new(100.0, 200.0)); - - assert_eq!(shifted.runs()[0].origin.x, 105.0); - assert_eq!(shifted.runs()[0].origin.y, 210.0); - - // Original layout untouched. - assert_eq!(layout.runs()[0].origin.x, 5.0); - assert_eq!(layout.runs()[0].origin.y, 10.0); -} - -#[test] -fn op_color_constants_distinct() { - // spec §5.2 names all six constants — RED/GREEN/BLUE/BLACK/WHITE/TRANSPARENT. - assert_eq!(Color::RED.r, 1.0); - assert_eq!(Color::GREEN.g, 1.0); - assert_eq!(Color::BLUE.b, 1.0); - assert_eq!(Color::BLACK.r, 0.0); - assert_eq!(Color::WHITE.r, 1.0); - assert_eq!(Color::TRANSPARENT.a, 0.0); -}