openpencil/crates/openpencil-shell-core/src/render_backend.rs
Kayshen-X 656b57a024 style(shell): convert all comments to English
Open-source codebase convention: all source-code comments in English.
Translates Chinese comments across openpencil-shell-{core,native,web}
.rs and Cargo.toml files. Logic, identifiers, and string literals
unchanged; the literal CJK fixture "Hello 你好" in raster_text_smoke
stays since it exercises the textlayout CJK path.
2026-05-05 21:12:00 +08:00

190 lines
6.4 KiB
Rust

//! OP `RenderBackend` widget-facing facade (spec v19 §5.2).
//!
//! This is OP's design contract (method-style API: `fill_rect / stroke_rect /
//! draw_text / clip_rect / save / restore / translate / resize / dpi_scale`),
//! which does not line up directly with Jian's
//! `jian_core::render::RenderBackend` (command-buffer style: `new_surface /
//! begin_frame / draw(&DrawOp)` etc.).
//!
//! Implementation paths (per §5.2.1):
//! - `NativeBackend` (shell-native, frame-scoped design from Step 1a onward)
//! does **not** impl this trait directly; instead it exposes same-named
//! methods that take an explicit `canvas: &skia_safe::Canvas` argument.
//! The Step 1a basic_window demo invokes NativeBackend methods inside a
//! `SharedSkiaContext::with_frame` closure. Once Step 1c+ wires in a real
//! widget tree we can revisit a `WithCanvas<'a>` newtype to inject the
//! canvas into a trait impl.
//! - `WebCanvasKitBackend` (shell-web, Step 1b): backed by CanvasKit JS bindings.
//! - `MobileBackend` (Step 1f): backed by Metal / Vulkan / OpenGL ES.
//!
//! This module does **not** pull in skia-safe / Canvas / GL types — shell-core
//! must stay wasm32-clean (per spec §1.2 boundary).
use jian_core::render::{TextAlign, TextRun};
/// 2D coordinate point (spec §5.2 fixes this to `glam::Vec2`).
pub type Point2D = glam::Vec2;
/// Rectangle (origin + size as two Vec2s).
#[derive(Debug, Clone, Copy)]
pub struct Rect {
pub origin: Point2D,
pub size: Point2D,
}
/// RGBA color (widget facade layer; all components 0.0..=1.0).
///
/// Named constants live at the OP layer (spec v19 round 5 CONCERN-R5-3 fix);
/// `jian_core::scene::Color` is `Color(pub u32)` packed RGBA without RED/BLACK/etc
/// named constants — callers must construct it explicitly via `JianColor::rgb(...)`.
#[derive(Debug, Clone, Copy)]
pub struct Color {
pub r: f32,
pub g: f32,
pub b: f32,
pub a: f32,
}
impl Color {
pub const RED: Self = Self {
r: 1.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const GREEN: Self = Self {
r: 0.0,
g: 1.0,
b: 0.0,
a: 1.0,
};
pub const BLUE: Self = Self {
r: 0.0,
g: 0.0,
b: 1.0,
a: 1.0,
};
pub const BLACK: Self = Self {
r: 0.0,
g: 0.0,
b: 0.0,
a: 1.0,
};
pub const WHITE: Self = Self {
r: 1.0,
g: 1.0,
b: 1.0,
a: 1.0,
};
pub const TRANSPARENT: Self = Self {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
}
/// OP's TextLayout — defined at the OP layer after v19 removed parley; a thin
/// wrapper over `jian_core::render::TextRun`.
///
/// It does not hold a layout context or glyph cache (shell-core is wasm32-clean
/// and cannot pull in skia/parley/icu); the real layout happens inside
/// NativeBackend::draw_text via `jian_skia::SkiaBackend`'s textlayout feature
/// (skia textlayout, ICU+harfbuzz) for shaping + line breaking.
///
/// In Step 1a, TextLayout is a placeholder for "a collection of already-shaped
/// TextRuns"; caret/selection/bidi/wrap are deferred to Step 1c+. The
/// signatures are pinned to avoid breaking the API in later steps.
#[derive(Debug, Clone)]
pub struct TextLayout {
runs: Vec<TextRun>,
}
impl TextLayout {
/// Single-run constructor (the only active path in Step 1a; used by the
/// Phase B/C demo + raster_text_smoke).
///
/// Fields align with `jian_core::render::TextRun`
/// (`vendor/jian/crates/jian-core/src/render/paint.rs:77-94`):
/// **TextRun has no `Default` impl**, so every field must be set explicitly.
/// - `content` / `font_family` / `font_size` / `color` / `origin`: passed in by caller
/// - `font_weight: 400` (CSS Normal)
/// - `max_width: 0.0` ("unknown; render at origin with no alignment adjustment")
/// - `align: TextAlign::Start`
/// - `line_height: 0.0` ("default")
pub fn single_run(
content: &str,
font_family: &str,
font_size: f32,
color: jian_core::scene::Color,
origin: Point2D,
) -> Self {
let run = TextRun {
content: content.to_string(),
font_family: font_family.to_string(),
font_size,
font_weight: 400,
color,
origin: jian_core::geometry::Point::new(origin.x, origin.y),
max_width: 0.0,
align: TextAlign::Start,
line_height: 0.0,
};
Self { runs: vec![run] }
}
/// View of the already-shaped TextRun collection.
pub fn runs(&self) -> &[TextRun] {
&self.runs
}
/// Translates every run's origin (NativeBackend::draw_text adds this on top
/// of the widget origin). Returns a new layout; the original is unchanged.
pub fn translated(&self, offset: Point2D) -> Self {
let runs = self
.runs
.iter()
.map(|r| {
let mut r2 = r.clone();
r2.origin =
jian_core::geometry::Point::new(r.origin.x + offset.x, r.origin.y + offset.y);
r2
})
.collect();
Self { runs }
}
}
/// Backend abstraction (widget-facing facade, spec §5.2).
///
/// Note: the trait has no `Send` bound — skia-safe types are `!Send`
/// (rust-skia is thread-bound), and the Backend is used solely on the render
/// thread, so cross-thread access is unnecessary.
///
/// In 1a, `NativeBackend` (shell-native) does **not** impl this trait directly
/// (v19 round 3 BLOCK-R3-3 fix — the frame-scoped design avoids cross-frame
/// borrows); instead it exposes the same-named methods with an explicit
/// `canvas: &skia_safe::Canvas` argument. The trait signature contains no
/// Skia / GPU-backend-specific types.
pub trait RenderBackend {
/// Begin a frame; the backend tracks current frame state internally
/// (canvas type is not exposed).
fn begin_frame(&mut self);
fn end_frame(&mut self);
// Drawing primitives — widgets call these and never touch the canvas directly.
fn fill_rect(&mut self, rect: Rect, color: Color);
fn stroke_rect(&mut self, rect: Rect, color: Color, width: f32);
fn draw_text(&mut self, layout: &TextLayout, origin: Point2D);
fn clip_rect(&mut self, rect: Rect);
// Transform stack.
fn save(&mut self);
fn restore(&mut self);
fn translate(&mut self, offset: Point2D);
// Viewport / DPI.
fn resize(&mut self, width: u32, height: u32);
fn dpi_scale(&self) -> f32;
}