mirror of
https://github.com/penpot/penpot.git
synced 2026-04-25 11:18:36 +00:00
🎉 Add background blur for wasm render
This commit is contained in:
parent
5ba53f7296
commit
e630be1509
@ -61,11 +61,13 @@ pub fn wasm_error(_attr: TokenStream, item: TokenStream) -> TokenStream {
|
|||||||
let _: &dyn std::error::Error = &__e;
|
let _: &dyn std::error::Error = &__e;
|
||||||
let __msg = __e.to_string();
|
let __msg = __e.to_string();
|
||||||
crate::mem::set_error_code(__e.into());
|
crate::mem::set_error_code(__e.into());
|
||||||
|
crate::mem::free_bytes().expect("Failed to free bytes");
|
||||||
panic!("WASM error: {}",__msg);
|
panic!("WASM error: {}",__msg);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Err(__payload) => {
|
Err(__payload) => {
|
||||||
crate::mem::set_error_code(0x02); // critical, same as Error::Critical
|
crate::mem::set_error_code(0x02); // critical, same as Error::Critical
|
||||||
|
crate::mem::free_bytes().expect("Failed to free bytes");
|
||||||
std::panic::resume_unwind(__payload);
|
std::panic::resume_unwind(__payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -30,6 +30,9 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
pub(crate) static mut STATE: Option<Box<State>> = None;
|
pub(crate) static mut STATE: Option<Box<State>> = None;
|
||||||
|
|
||||||
|
// FIXME: These with_state* macros should be using our CriticalError instead of expect.
|
||||||
|
// But to do that, we need to not use them at domain-level (i.e. in business logic), just
|
||||||
|
// in the context of the wasm call.
|
||||||
#[macro_export]
|
#[macro_export]
|
||||||
macro_rules! with_state_mut {
|
macro_rules! with_state_mut {
|
||||||
($state:ident, $block:block) => {{
|
($state:ident, $block:block) => {{
|
||||||
@ -102,7 +105,7 @@ macro_rules! with_state_mut_current_shape {
|
|||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn init(width: i32, height: i32) -> Result<()> {
|
pub extern "C" fn init(width: i32, height: i32) -> Result<()> {
|
||||||
let state_box = Box::new(State::new(width, height));
|
let state_box = Box::new(State::try_new(width, height)?);
|
||||||
unsafe {
|
unsafe {
|
||||||
STATE = Some(state_box);
|
STATE = Some(state_box);
|
||||||
}
|
}
|
||||||
@ -138,7 +141,7 @@ pub extern "C" fn set_render_options(debug: u32, dpr: f32) -> Result<()> {
|
|||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
let render_state = state.render_state_mut();
|
let render_state = state.render_state_mut();
|
||||||
render_state.set_debug_flags(debug);
|
render_state.set_debug_flags(debug);
|
||||||
render_state.set_dpr(dpr);
|
render_state.set_dpr(dpr)?;
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -162,7 +165,7 @@ pub extern "C" fn render(_: i32) -> Result<()> {
|
|||||||
state.rebuild_touched_tiles();
|
state.rebuild_touched_tiles();
|
||||||
state
|
state
|
||||||
.start_render_loop(performance::get_time())
|
.start_render_loop(performance::get_time())
|
||||||
.expect("Error rendering");
|
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?;
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -174,7 +177,7 @@ pub extern "C" fn render_sync() -> Result<()> {
|
|||||||
state.rebuild_tiles();
|
state.rebuild_tiles();
|
||||||
state
|
state
|
||||||
.render_sync(performance::get_time())
|
.render_sync(performance::get_time())
|
||||||
.expect("Error rendering");
|
.map_err(|_| Error::RecoverableError("Error rendering".to_string()))?;
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -236,24 +239,12 @@ pub extern "C" fn render_preview() -> Result<()> {
|
|||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn process_animation_frame(timestamp: i32) -> Result<()> {
|
pub extern "C" fn process_animation_frame(timestamp: i32) -> Result<()> {
|
||||||
let result = std::panic::catch_unwind(|| {
|
let result = with_state_mut!(state, { state.process_animation_frame(timestamp) });
|
||||||
with_state_mut!(state, {
|
|
||||||
state
|
|
||||||
.process_animation_frame(timestamp)
|
|
||||||
.expect("Error processing animation frame");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
match result {
|
if let Err(err) = result {
|
||||||
Ok(_) => {}
|
eprintln!("process_animation_frame error: {}", err);
|
||||||
Err(err) => {
|
|
||||||
match err.downcast_ref::<String>() {
|
|
||||||
Some(message) => println!("process_animation_frame error: {}", message),
|
|
||||||
None => println!("process_animation_frame error: {:?}", err),
|
|
||||||
}
|
|
||||||
std::panic::resume_unwind(err);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -270,7 +261,7 @@ pub extern "C" fn reset_canvas() -> Result<()> {
|
|||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn resize_viewbox(width: i32, height: i32) -> Result<()> {
|
pub extern "C" fn resize_viewbox(width: i32, height: i32) -> Result<()> {
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
state.resize(width, height);
|
state.resize(width, height)?;
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -362,8 +353,8 @@ pub extern "C" fn set_focus_mode() -> Result<()> {
|
|||||||
|
|
||||||
let entries: Vec<Uuid> = bytes
|
let entries: Vec<Uuid> = bytes
|
||||||
.chunks(size_of::<<Uuid as SerializableResult>::BytesType>())
|
.chunks(size_of::<<Uuid as SerializableResult>::BytesType>())
|
||||||
.map(|data| Uuid::try_from(data).unwrap())
|
.map(|data| Uuid::try_from(data).map_err(|e| Error::RecoverableError(e.to_string())))
|
||||||
.collect();
|
.collect::<Result<Vec<Uuid>>>()?;
|
||||||
|
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
state.set_focus_mode(entries);
|
state.set_focus_mode(entries);
|
||||||
@ -637,8 +628,8 @@ pub extern "C" fn set_children() -> Result<()> {
|
|||||||
|
|
||||||
let entries: Vec<Uuid> = bytes
|
let entries: Vec<Uuid> = bytes
|
||||||
.chunks(size_of::<<Uuid as SerializableResult>::BytesType>())
|
.chunks(size_of::<<Uuid as SerializableResult>::BytesType>())
|
||||||
.map(|data| Uuid::try_from(data).unwrap())
|
.map(|data| Uuid::try_from(data).map_err(|e| Error::CriticalError(e.to_string())))
|
||||||
.collect();
|
.collect::<Result<Vec<Uuid>>>()?;
|
||||||
|
|
||||||
set_children_set(entries)?;
|
set_children_set(entries)?;
|
||||||
|
|
||||||
@ -761,10 +752,15 @@ pub extern "C" fn get_selection_rect() -> Result<*mut u8> {
|
|||||||
pub extern "C" fn set_structure_modifiers() -> Result<()> {
|
pub extern "C" fn set_structure_modifiers() -> Result<()> {
|
||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
|
|
||||||
let entries: Vec<_> = bytes
|
let entries: Vec<StructureEntry> = bytes
|
||||||
.chunks(44)
|
.chunks(44)
|
||||||
.map(|data| StructureEntry::from_bytes(data.try_into().unwrap()))
|
.map(|chunk| {
|
||||||
.collect();
|
let data = chunk
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::CriticalError("Invalid StructureEntry bytes".to_string()))?;
|
||||||
|
Ok(StructureEntry::from_bytes(data))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
let mut structure = HashMap::new();
|
let mut structure = HashMap::new();
|
||||||
@ -783,7 +779,9 @@ pub extern "C" fn set_structure_modifiers() -> Result<()> {
|
|||||||
structure.entry(entry.parent).or_insert_with(Vec::new);
|
structure.entry(entry.parent).or_insert_with(Vec::new);
|
||||||
structure
|
structure
|
||||||
.get_mut(&entry.parent)
|
.get_mut(&entry.parent)
|
||||||
.expect("Parent not found for entry")
|
.ok_or(Error::CriticalError(
|
||||||
|
"Parent not found for entry".to_string(),
|
||||||
|
))?
|
||||||
.push(entry);
|
.push(entry);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -814,10 +812,10 @@ pub extern "C" fn clean_modifiers() -> Result<()> {
|
|||||||
pub extern "C" fn set_modifiers() -> Result<()> {
|
pub extern "C" fn set_modifiers() -> Result<()> {
|
||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
|
|
||||||
let entries: Vec<_> = bytes
|
let entries: Vec<TransformEntry> = bytes
|
||||||
.chunks(size_of::<<TransformEntry as SerializableResult>::BytesType>())
|
.chunks(size_of::<<TransformEntry as SerializableResult>::BytesType>())
|
||||||
.map(|data| TransformEntry::try_from(data).unwrap())
|
.map(|data| TransformEntry::try_from(data).map_err(|e| Error::CriticalError(e.to_string())))
|
||||||
.collect();
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
let mut modifiers = HashMap::new();
|
let mut modifiers = HashMap::new();
|
||||||
let mut ids = Vec::<Uuid>::new();
|
let mut ids = Vec::<Uuid>::new();
|
||||||
@ -828,7 +826,7 @@ pub extern "C" fn set_modifiers() -> Result<()> {
|
|||||||
|
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
state.set_modifiers(modifiers);
|
state.set_modifiers(modifiers);
|
||||||
state.rebuild_modifier_tiles(ids);
|
state.rebuild_modifier_tiles(ids)?;
|
||||||
});
|
});
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -838,8 +836,10 @@ pub extern "C" fn set_modifiers() -> Result<()> {
|
|||||||
pub extern "C" fn start_temp_objects() -> Result<()> {
|
pub extern "C" fn start_temp_objects() -> Result<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
#[allow(static_mut_refs)]
|
#[allow(static_mut_refs)]
|
||||||
let mut state = STATE.take().expect("Got an invalid state pointer");
|
let mut state = STATE.take().ok_or(Error::CriticalError(
|
||||||
state = Box::new(state.start_temp_objects());
|
"Got an invalid state pointer".to_string(),
|
||||||
|
))?;
|
||||||
|
state = Box::new(state.start_temp_objects()?);
|
||||||
STATE = Some(state);
|
STATE = Some(state);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -850,8 +850,10 @@ pub extern "C" fn start_temp_objects() -> Result<()> {
|
|||||||
pub extern "C" fn end_temp_objects() -> Result<()> {
|
pub extern "C" fn end_temp_objects() -> Result<()> {
|
||||||
unsafe {
|
unsafe {
|
||||||
#[allow(static_mut_refs)]
|
#[allow(static_mut_refs)]
|
||||||
let mut state = STATE.take().expect("Got an invalid state pointer");
|
let mut state = STATE.take().ok_or(Error::CriticalError(
|
||||||
state = Box::new(state.end_temp_objects());
|
"Got an invalid state pointer".to_string(),
|
||||||
|
))?;
|
||||||
|
state = Box::new(state.end_temp_objects()?);
|
||||||
STATE = Some(state);
|
STATE = Some(state);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|||||||
@ -21,6 +21,7 @@ use gpu_state::GpuState;
|
|||||||
use options::RenderOptions;
|
use options::RenderOptions;
|
||||||
pub use surfaces::{SurfaceId, Surfaces};
|
pub use surfaces::{SurfaceId, Surfaces};
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
use crate::performance;
|
use crate::performance;
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
all_with_ancestors, radius_to_sigma, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor,
|
all_with_ancestors, radius_to_sigma, Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor,
|
||||||
@ -326,19 +327,19 @@ pub fn get_cache_size(viewbox: Viewbox, scale: f32) -> skia::ISize {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl RenderState {
|
impl RenderState {
|
||||||
pub fn new(width: i32, height: i32) -> RenderState {
|
pub fn try_new(width: i32, height: i32) -> Result<RenderState> {
|
||||||
// This needs to be done once per WebGL context.
|
// This needs to be done once per WebGL context.
|
||||||
let mut gpu_state = GpuState::new();
|
let mut gpu_state = GpuState::try_new()?;
|
||||||
let sampling_options =
|
let sampling_options =
|
||||||
skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest);
|
skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest);
|
||||||
|
|
||||||
let fonts = FontStore::new();
|
let fonts = FontStore::try_new()?;
|
||||||
let surfaces = Surfaces::new(
|
let surfaces = Surfaces::try_new(
|
||||||
&mut gpu_state,
|
&mut gpu_state,
|
||||||
(width, height),
|
(width, height),
|
||||||
sampling_options,
|
sampling_options,
|
||||||
tiles::get_tile_dimensions(),
|
tiles::get_tile_dimensions(),
|
||||||
);
|
)?;
|
||||||
|
|
||||||
// This is used multiple times everywhere so instead of creating new instances every
|
// This is used multiple times everywhere so instead of creating new instances every
|
||||||
// time we reuse this one.
|
// time we reuse this one.
|
||||||
@ -346,7 +347,7 @@ impl RenderState {
|
|||||||
let viewbox = Viewbox::new(width as f32, height as f32);
|
let viewbox = Viewbox::new(width as f32, height as f32);
|
||||||
let tiles = tiles::TileHashMap::new();
|
let tiles = tiles::TileHashMap::new();
|
||||||
|
|
||||||
RenderState {
|
Ok(RenderState {
|
||||||
gpu_state: gpu_state.clone(),
|
gpu_state: gpu_state.clone(),
|
||||||
options: RenderOptions::default(),
|
options: RenderOptions::default(),
|
||||||
surfaces,
|
surfaces,
|
||||||
@ -377,7 +378,7 @@ impl RenderState {
|
|||||||
touched_ids: HashSet::default(),
|
touched_ids: HashSet::default(),
|
||||||
ignore_nested_blurs: false,
|
ignore_nested_blurs: false,
|
||||||
preview_mode: false,
|
preview_mode: false,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Combines every visible layer blur currently active (ancestors + shape)
|
/// Combines every visible layer blur currently active (ancestors + shape)
|
||||||
@ -531,15 +532,15 @@ impl RenderState {
|
|||||||
/// Runs `f` with `ignore_nested_blurs` temporarily forced to `true`.
|
/// Runs `f` with `ignore_nested_blurs` temporarily forced to `true`.
|
||||||
/// Certain off-screen passes (e.g. shadow masks) must render shapes without
|
/// Certain off-screen passes (e.g. shadow masks) must render shapes without
|
||||||
/// inheriting ancestor blur. This helper guarantees the flag is restored.
|
/// inheriting ancestor blur. This helper guarantees the flag is restored.
|
||||||
fn with_nested_blurs_suppressed<F, R>(&mut self, f: F) -> R
|
fn with_nested_blurs_suppressed<F, R>(&mut self, f: F) -> Result<R>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut RenderState) -> R,
|
F: FnOnce(&mut RenderState) -> Result<R>,
|
||||||
{
|
{
|
||||||
let previous = self.ignore_nested_blurs;
|
let previous = self.ignore_nested_blurs;
|
||||||
self.ignore_nested_blurs = true;
|
self.ignore_nested_blurs = true;
|
||||||
let result = f(self);
|
let result = f(self)?;
|
||||||
self.ignore_nested_blurs = previous;
|
self.ignore_nested_blurs = previous;
|
||||||
result
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fonts(&self) -> &FontStore {
|
pub fn fonts(&self) -> &FontStore {
|
||||||
@ -550,12 +551,7 @@ impl RenderState {
|
|||||||
&mut self.fonts
|
&mut self.fonts
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_image(
|
pub fn add_image(&mut self, id: Uuid, is_thumbnail: bool, image_data: &[u8]) -> Result<()> {
|
||||||
&mut self,
|
|
||||||
id: Uuid,
|
|
||||||
is_thumbnail: bool,
|
|
||||||
image_data: &[u8],
|
|
||||||
) -> Result<(), String> {
|
|
||||||
self.images.add(id, is_thumbnail, image_data)
|
self.images.add(id, is_thumbnail, image_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -567,7 +563,7 @@ impl RenderState {
|
|||||||
texture_id: u32,
|
texture_id: u32,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<()> {
|
||||||
self.images
|
self.images
|
||||||
.add_image_from_gl_texture(id, is_thumbnail, texture_id, width, height)
|
.add_image_from_gl_texture(id, is_thumbnail, texture_id, width, height)
|
||||||
}
|
}
|
||||||
@ -580,15 +576,16 @@ impl RenderState {
|
|||||||
self.options.flags = debug;
|
self.options.flags = debug;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_dpr(&mut self, dpr: f32) {
|
pub fn set_dpr(&mut self, dpr: f32) -> Result<()> {
|
||||||
if Some(dpr) != self.options.dpr {
|
if Some(dpr) != self.options.dpr {
|
||||||
self.options.dpr = Some(dpr);
|
self.options.dpr = Some(dpr);
|
||||||
self.resize(
|
self.resize(
|
||||||
self.viewbox.width.floor() as i32,
|
self.viewbox.width.floor() as i32,
|
||||||
self.viewbox.height.floor() as i32,
|
self.viewbox.height.floor() as i32,
|
||||||
);
|
)?;
|
||||||
self.fonts.set_scale_debug_font(dpr);
|
self.fonts.set_scale_debug_font(dpr);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_background_color(&mut self, color: skia::Color) {
|
pub fn set_background_color(&mut self, color: skia::Color) {
|
||||||
@ -599,13 +596,15 @@ impl RenderState {
|
|||||||
self.preview_mode = enabled;
|
self.preview_mode = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, width: i32, height: i32) {
|
pub fn resize(&mut self, width: i32, height: i32) -> Result<()> {
|
||||||
let dpr_width = (width as f32 * self.options.dpr()).floor() as i32;
|
let dpr_width = (width as f32 * self.options.dpr()).floor() as i32;
|
||||||
let dpr_height = (height as f32 * self.options.dpr()).floor() as i32;
|
let dpr_height = (height as f32 * self.options.dpr()).floor() as i32;
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.resize(&mut self.gpu_state, dpr_width, dpr_height);
|
.resize(&mut self.gpu_state, dpr_width, dpr_height)?;
|
||||||
self.viewbox.set_wh(width as f32, height as f32);
|
self.viewbox.set_wh(width as f32, height as f32);
|
||||||
self.tile_viewbox.update(self.viewbox, self.get_scale());
|
self.tile_viewbox.update(self.viewbox, self.get_scale());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush_and_submit(&mut self) {
|
pub fn flush_and_submit(&mut self) {
|
||||||
@ -627,19 +626,23 @@ impl RenderState {
|
|||||||
self.surfaces.canvas(surface_id).restore();
|
self.surfaces.canvas(surface_id).restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_render_to_final_canvas(&mut self, rect: skia::Rect) {
|
pub fn apply_render_to_final_canvas(&mut self, rect: skia::Rect) -> Result<()> {
|
||||||
let tile_rect = self.get_current_aligned_tile_bounds();
|
let tile_rect = self.get_current_aligned_tile_bounds()?;
|
||||||
self.surfaces.cache_current_tile_texture(
|
self.surfaces.cache_current_tile_texture(
|
||||||
&self.tile_viewbox,
|
&self.tile_viewbox,
|
||||||
&self.current_tile.unwrap(),
|
&self
|
||||||
|
.current_tile
|
||||||
|
.ok_or(Error::CriticalError("Current tile not found".to_string()))?,
|
||||||
&tile_rect,
|
&tile_rect,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.surfaces.draw_cached_tile_surface(
|
self.surfaces.draw_cached_tile_surface(
|
||||||
self.current_tile.unwrap(),
|
self.current_tile
|
||||||
|
.ok_or(Error::CriticalError("Current tile not found".to_string()))?,
|
||||||
rect,
|
rect,
|
||||||
self.background_color,
|
self.background_color,
|
||||||
);
|
);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apply_drawing_to_render_canvas(&mut self, shape: Option<&Shape>) {
|
pub fn apply_drawing_to_render_canvas(&mut self, shape: Option<&Shape>) {
|
||||||
@ -748,7 +751,7 @@ impl RenderState {
|
|||||||
offset: Option<(f32, f32)>,
|
offset: Option<(f32, f32)>,
|
||||||
parent_shadows: Option<Vec<skia_safe::Paint>>,
|
parent_shadows: Option<Vec<skia_safe::Paint>>,
|
||||||
outset: Option<f32>,
|
outset: Option<f32>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
let surface_ids = fills_surface_id as u32
|
let surface_ids = fills_surface_id as u32
|
||||||
| strokes_surface_id as u32
|
| strokes_surface_id as u32
|
||||||
| innershadows_surface_id as u32
|
| innershadows_surface_id as u32
|
||||||
@ -813,7 +816,7 @@ impl RenderState {
|
|||||||
antialias,
|
antialias,
|
||||||
SurfaceId::Current,
|
SurfaceId::Current,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
// Pass strokes in natural order; stroke merging handles top-most ordering internally.
|
// Pass strokes in natural order; stroke merging handles top-most ordering internally.
|
||||||
let visible_strokes: Vec<&Stroke> = shape.visible_strokes().collect();
|
let visible_strokes: Vec<&Stroke> = shape.visible_strokes().collect();
|
||||||
@ -824,7 +827,7 @@ impl RenderState {
|
|||||||
Some(SurfaceId::Current),
|
Some(SurfaceId::Current),
|
||||||
antialias,
|
antialias,
|
||||||
outset,
|
outset,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
self.surfaces.apply_mut(SurfaceId::Current as u32, |s| {
|
self.surfaces.apply_mut(SurfaceId::Current as u32, |s| {
|
||||||
s.canvas().restore();
|
s.canvas().restore();
|
||||||
@ -840,7 +843,7 @@ impl RenderState {
|
|||||||
s.canvas().restore();
|
s.canvas().restore();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// set clipping
|
// set clipping
|
||||||
@ -1017,7 +1020,7 @@ impl RenderState {
|
|||||||
None,
|
None,
|
||||||
text_fill_inset,
|
text_fill_inset,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
for (stroke_paragraphs, layer_opacity) in stroke_paragraphs_list
|
for (stroke_paragraphs, layer_opacity) in stroke_paragraphs_list
|
||||||
.iter_mut()
|
.iter_mut()
|
||||||
@ -1034,7 +1037,7 @@ impl RenderState {
|
|||||||
text_stroke_blur_outset,
|
text_stroke_blur_outset,
|
||||||
None,
|
None,
|
||||||
*layer_opacity,
|
*layer_opacity,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let mut drop_shadows = shape.drop_shadow_paints();
|
let mut drop_shadows = shape.drop_shadow_paints();
|
||||||
@ -1077,7 +1080,7 @@ impl RenderState {
|
|||||||
blur_filter.as_ref(),
|
blur_filter.as_ref(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
shadows::render_text_shadows(
|
shadows::render_text_shadows(
|
||||||
@ -1088,7 +1091,7 @@ impl RenderState {
|
|||||||
text_drop_shadows_surface_id.into(),
|
text_drop_shadows_surface_id.into(),
|
||||||
&parent_shadows,
|
&parent_shadows,
|
||||||
&blur_filter,
|
&blur_filter,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 1. Text drop shadows
|
// 1. Text drop shadows
|
||||||
@ -1104,7 +1107,7 @@ impl RenderState {
|
|||||||
blur_filter.as_ref(),
|
blur_filter.as_ref(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1119,7 +1122,7 @@ impl RenderState {
|
|||||||
blur_filter.as_ref(),
|
blur_filter.as_ref(),
|
||||||
text_fill_inset,
|
text_fill_inset,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
// 3. Stroke drop shadows
|
// 3. Stroke drop shadows
|
||||||
shadows::render_text_shadows(
|
shadows::render_text_shadows(
|
||||||
@ -1130,7 +1133,7 @@ impl RenderState {
|
|||||||
text_drop_shadows_surface_id.into(),
|
text_drop_shadows_surface_id.into(),
|
||||||
&drop_shadows,
|
&drop_shadows,
|
||||||
&blur_filter,
|
&blur_filter,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
// 4. Stroke fills
|
// 4. Stroke fills
|
||||||
for (stroke_paragraphs, layer_opacity) in stroke_paragraphs_list
|
for (stroke_paragraphs, layer_opacity) in stroke_paragraphs_list
|
||||||
@ -1148,7 +1151,7 @@ impl RenderState {
|
|||||||
text_stroke_blur_outset,
|
text_stroke_blur_outset,
|
||||||
None,
|
None,
|
||||||
*layer_opacity,
|
*layer_opacity,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Stroke inner shadows
|
// 5. Stroke inner shadows
|
||||||
@ -1160,7 +1163,7 @@ impl RenderState {
|
|||||||
Some(innershadows_surface_id),
|
Some(innershadows_surface_id),
|
||||||
&inner_shadows,
|
&inner_shadows,
|
||||||
&blur_filter,
|
&blur_filter,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
// 6. Fill Inner shadows
|
// 6. Fill Inner shadows
|
||||||
if !shape.has_visible_strokes() {
|
if !shape.has_visible_strokes() {
|
||||||
@ -1175,7 +1178,7 @@ impl RenderState {
|
|||||||
blur_filter.as_ref(),
|
blur_filter.as_ref(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1223,7 +1226,7 @@ impl RenderState {
|
|||||||
antialias,
|
antialias,
|
||||||
fills_surface_id,
|
fills_surface_id,
|
||||||
outset,
|
outset,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
fills::render(
|
fills::render(
|
||||||
@ -1233,7 +1236,7 @@ impl RenderState {
|
|||||||
antialias,
|
antialias,
|
||||||
fills_surface_id,
|
fills_surface_id,
|
||||||
outset,
|
outset,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip stroke rendering for clipped frames - they are drawn in render_shape_exit
|
// Skip stroke rendering for clipped frames - they are drawn in render_shape_exit
|
||||||
@ -1249,7 +1252,7 @@ impl RenderState {
|
|||||||
Some(strokes_surface_id),
|
Some(strokes_surface_id),
|
||||||
antialias,
|
antialias,
|
||||||
outset,
|
outset,
|
||||||
);
|
)?;
|
||||||
if !fast_mode {
|
if !fast_mode {
|
||||||
for stroke in &visible_strokes {
|
for stroke in &visible_strokes {
|
||||||
shadows::render_stroke_inner_shadows(
|
shadows::render_stroke_inner_shadows(
|
||||||
@ -1258,7 +1261,7 @@ impl RenderState {
|
|||||||
stroke,
|
stroke,
|
||||||
antialias,
|
antialias,
|
||||||
innershadows_surface_id,
|
innershadows_surface_id,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1295,6 +1298,7 @@ impl RenderState {
|
|||||||
s.canvas().restore();
|
s.canvas().restore();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn update_render_context(&mut self, tile: tiles::Tile) {
|
pub fn update_render_context(&mut self, tile: tiles::Tile) {
|
||||||
@ -1373,7 +1377,7 @@ impl RenderState {
|
|||||||
|
|
||||||
/// Render a preview of the shapes during loading.
|
/// Render a preview of the shapes during loading.
|
||||||
/// This rebuilds tiles for touched shapes and renders synchronously.
|
/// This rebuilds tiles for touched shapes and renders synchronously.
|
||||||
pub fn render_preview(&mut self, tree: ShapesPoolRef, timestamp: i32) -> Result<(), String> {
|
pub fn render_preview(&mut self, tree: ShapesPoolRef, timestamp: i32) -> Result<()> {
|
||||||
let _start = performance::begin_timed_log!("render_preview");
|
let _start = performance::begin_timed_log!("render_preview");
|
||||||
performance::begin_measure!("render_preview");
|
performance::begin_measure!("render_preview");
|
||||||
|
|
||||||
@ -1403,7 +1407,7 @@ impl RenderState {
|
|||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
sync_render: bool,
|
sync_render: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<()> {
|
||||||
let _start = performance::begin_timed_log!("start_render_loop");
|
let _start = performance::begin_timed_log!("start_render_loop");
|
||||||
let scale = self.get_scale();
|
let scale = self.get_scale();
|
||||||
|
|
||||||
@ -1430,7 +1434,7 @@ impl RenderState {
|
|||||||
|| viewbox_cache_size.height > cached_viewbox_cache_size.height
|
|| viewbox_cache_size.height > cached_viewbox_cache_size.height
|
||||||
{
|
{
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.resize_cache(viewbox_cache_size, VIEWPORT_INTEREST_AREA_THRESHOLD);
|
.resize_cache(viewbox_cache_size, VIEWPORT_INTEREST_AREA_THRESHOLD)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME - review debug
|
// FIXME - review debug
|
||||||
@ -1475,7 +1479,7 @@ impl RenderState {
|
|||||||
base_object: Option<&Uuid>,
|
base_object: Option<&Uuid>,
|
||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<()> {
|
||||||
performance::begin_measure!("process_animation_frame");
|
performance::begin_measure!("process_animation_frame");
|
||||||
if self.render_in_progress {
|
if self.render_in_progress {
|
||||||
if tree.len() != 0 {
|
if tree.len() != 0 {
|
||||||
@ -1499,7 +1503,7 @@ impl RenderState {
|
|||||||
base_object: Option<&Uuid>,
|
base_object: Option<&Uuid>,
|
||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<()> {
|
||||||
if tree.len() != 0 {
|
if tree.len() != 0 {
|
||||||
self.render_shape_tree_partial(base_object, tree, timestamp, false)?;
|
self.render_shape_tree_partial(base_object, tree, timestamp, false)?;
|
||||||
}
|
}
|
||||||
@ -1589,7 +1593,7 @@ impl RenderState {
|
|||||||
element: &Shape,
|
element: &Shape,
|
||||||
visited_mask: bool,
|
visited_mask: bool,
|
||||||
clip_bounds: Option<ClipStack>,
|
clip_bounds: Option<ClipStack>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
if visited_mask {
|
if visited_mask {
|
||||||
// Because masked groups needs two rendering passes (first drawing
|
// Because masked groups needs two rendering passes (first drawing
|
||||||
// the content and then drawing the mask), we need to do an
|
// the content and then drawing the mask), we need to do an
|
||||||
@ -1660,7 +1664,7 @@ impl RenderState {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only restore if we created a layer (optimization for simple shapes)
|
// Only restore if we created a layer (optimization for simple shapes)
|
||||||
@ -1672,19 +1676,22 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.focus_mode.exit(&element.id);
|
self.focus_mode.exit(&element.id);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_current_tile_bounds(&mut self) -> Rect {
|
pub fn get_current_tile_bounds(&mut self) -> Result<Rect> {
|
||||||
let tiles::Tile(tile_x, tile_y) = self.current_tile.unwrap();
|
let tiles::Tile(tile_x, tile_y) = self
|
||||||
|
.current_tile
|
||||||
|
.ok_or(Error::CriticalError("Current tile not found".to_string()))?;
|
||||||
let scale = self.get_scale();
|
let scale = self.get_scale();
|
||||||
let offset_x = self.viewbox.area.left * scale;
|
let offset_x = self.viewbox.area.left * scale;
|
||||||
let offset_y = self.viewbox.area.top * scale;
|
let offset_y = self.viewbox.area.top * scale;
|
||||||
Rect::from_xywh(
|
Ok(Rect::from_xywh(
|
||||||
(tile_x as f32 * tiles::TILE_SIZE) - offset_x,
|
(tile_x as f32 * tiles::TILE_SIZE) - offset_x,
|
||||||
(tile_y as f32 * tiles::TILE_SIZE) - offset_y,
|
(tile_y as f32 * tiles::TILE_SIZE) - offset_y,
|
||||||
tiles::TILE_SIZE,
|
tiles::TILE_SIZE,
|
||||||
tiles::TILE_SIZE,
|
tiles::TILE_SIZE,
|
||||||
)
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_rect_bounds(&mut self, rect: skia::Rect) -> Rect {
|
pub fn get_rect_bounds(&mut self, rect: skia::Rect) -> Rect {
|
||||||
@ -1732,8 +1739,11 @@ impl RenderState {
|
|||||||
// lower multiple of `TILE_SIZE`. This ensures the tile bounds are aligned
|
// lower multiple of `TILE_SIZE`. This ensures the tile bounds are aligned
|
||||||
// with the global tile grid, which is useful for rendering tiles in a
|
// with the global tile grid, which is useful for rendering tiles in a
|
||||||
/// consistent and predictable layout.
|
/// consistent and predictable layout.
|
||||||
pub fn get_current_aligned_tile_bounds(&mut self) -> Rect {
|
pub fn get_current_aligned_tile_bounds(&mut self) -> Result<Rect> {
|
||||||
self.get_aligned_tile_bounds(self.current_tile.unwrap())
|
Ok(self.get_aligned_tile_bounds(
|
||||||
|
self.current_tile
|
||||||
|
.ok_or(Error::CriticalError("Current tile not found".to_string()))?,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders a drop shadow effect for the given shape.
|
/// Renders a drop shadow effect for the given shape.
|
||||||
@ -1750,7 +1760,7 @@ impl RenderState {
|
|||||||
scale: f32,
|
scale: f32,
|
||||||
translation: (f32, f32),
|
translation: (f32, f32),
|
||||||
extra_layer_blur: Option<Blur>,
|
extra_layer_blur: Option<Blur>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
let mut transformed_shadow: Cow<Shadow> = Cow::Borrowed(shadow);
|
let mut transformed_shadow: Cow<Shadow> = Cow::Borrowed(shadow);
|
||||||
transformed_shadow.to_mut().offset = (0.0, 0.0);
|
transformed_shadow.to_mut().offset = (0.0, 0.0);
|
||||||
transformed_shadow.to_mut().color = skia::Color::BLACK;
|
transformed_shadow.to_mut().color = skia::Color::BLACK;
|
||||||
@ -1805,7 +1815,7 @@ impl RenderState {
|
|||||||
plain_shape_mut.clip_content = false;
|
plain_shape_mut.clip_content = false;
|
||||||
|
|
||||||
let Some(drop_filter) = transformed_shadow.get_drop_shadow_filter() else {
|
let Some(drop_filter) = transformed_shadow.get_drop_shadow_filter() else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut bounds = drop_filter.compute_fast_bounds(shape_bounds);
|
let mut bounds = drop_filter.compute_fast_bounds(shape_bounds);
|
||||||
@ -1813,7 +1823,7 @@ impl RenderState {
|
|||||||
bounds.offset(world_offset);
|
bounds.offset(world_offset);
|
||||||
// Early cull if the shadow bounds are outside the render area.
|
// Early cull if the shadow bounds are outside the render area.
|
||||||
if !bounds.intersects(self.render_area_with_margins) {
|
if !bounds.intersects(self.render_area_with_margins) {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// blur=0 at high zoom: draw directly on DropShadows with geometric spread (no filter).
|
// blur=0 at high zoom: draw directly on DropShadows with geometric spread (no filter).
|
||||||
@ -1835,11 +1845,11 @@ impl RenderState {
|
|||||||
Some(shadow.offset),
|
Some(shadow.offset),
|
||||||
None,
|
None,
|
||||||
Some(shadow.spread),
|
Some(shadow.spread),
|
||||||
);
|
)
|
||||||
});
|
})?;
|
||||||
|
|
||||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create filter with blur only (no offset, no spread - handled geometrically)
|
// Create filter with blur only (no offset, no spread - handled geometrically)
|
||||||
@ -1877,11 +1887,11 @@ impl RenderState {
|
|||||||
Some(shadow.offset), // Offset is geometric
|
Some(shadow.offset), // Offset is geometric
|
||||||
None,
|
None,
|
||||||
Some(shadow.spread),
|
Some(shadow.spread),
|
||||||
);
|
)
|
||||||
});
|
})?;
|
||||||
|
|
||||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adaptive downscale for large blur values (lossless GPU optimization).
|
// Adaptive downscale for large blur values (lossless GPU optimization).
|
||||||
@ -1918,12 +1928,13 @@ impl RenderState {
|
|||||||
Some(shadow.offset), // Offset is geometric
|
Some(shadow.offset), // Offset is geometric
|
||||||
None,
|
None,
|
||||||
Some(shadow.spread),
|
Some(shadow.spread),
|
||||||
);
|
)
|
||||||
});
|
})?;
|
||||||
|
|
||||||
state.surfaces.canvas(temp_surface).restore();
|
state.surfaces.canvas(temp_surface).restore();
|
||||||
|
Ok(())
|
||||||
},
|
},
|
||||||
);
|
)?;
|
||||||
|
|
||||||
if let Some((mut surface, filter_scale)) = filter_result {
|
if let Some((mut surface, filter_scale)) = filter_result {
|
||||||
let drop_canvas = self.surfaces.canvas(SurfaceId::DropShadows);
|
let drop_canvas = self.surfaces.canvas(SurfaceId::DropShadows);
|
||||||
@ -1958,6 +1969,7 @@ impl RenderState {
|
|||||||
}
|
}
|
||||||
drop_canvas.restore();
|
drop_canvas.restore();
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders element drop shadows to DropShadows surface and composites to Current.
|
/// Renders element drop shadows to DropShadows surface and composites to Current.
|
||||||
@ -1972,7 +1984,7 @@ impl RenderState {
|
|||||||
scale: f32,
|
scale: f32,
|
||||||
translation: (f32, f32),
|
translation: (f32, f32),
|
||||||
node_render_state: &NodeRenderState,
|
node_render_state: &NodeRenderState,
|
||||||
) {
|
) -> Result<()> {
|
||||||
let element_extrect = extrect.get_or_insert_with(|| element.extrect(tree, scale));
|
let element_extrect = extrect.get_or_insert_with(|| element.extrect(tree, scale));
|
||||||
let inherited_layer_blur = match element.shape_type {
|
let inherited_layer_blur = match element.shape_type {
|
||||||
Type::Frame(_) | Type::Group(_) => element.blur,
|
Type::Frame(_) | Type::Group(_) => element.blur,
|
||||||
@ -1994,7 +2006,7 @@ impl RenderState {
|
|||||||
scale,
|
scale,
|
||||||
translation,
|
translation,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
if !matches!(element.shape_type, Type::Bool(_)) {
|
if !matches!(element.shape_type, Type::Bool(_)) {
|
||||||
let shadow_children = if element.is_recursive() {
|
let shadow_children = if element.is_recursive() {
|
||||||
@ -2023,7 +2035,7 @@ impl RenderState {
|
|||||||
scale,
|
scale,
|
||||||
translation,
|
translation,
|
||||||
inherited_layer_blur,
|
inherited_layer_blur,
|
||||||
);
|
)?;
|
||||||
} else {
|
} else {
|
||||||
let paint = skia::Paint::default();
|
let paint = skia::Paint::default();
|
||||||
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
let layer_rec = skia::canvas::SaveLayerRec::default().paint(&paint);
|
||||||
@ -2059,8 +2071,8 @@ impl RenderState {
|
|||||||
None,
|
None,
|
||||||
Some(vec![new_shadow_paint.clone()]),
|
Some(vec![new_shadow_paint.clone()]),
|
||||||
None,
|
None,
|
||||||
);
|
)
|
||||||
});
|
})?;
|
||||||
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
self.surfaces.canvas(SurfaceId::DropShadows).restore();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2117,6 +2129,7 @@ impl RenderState {
|
|||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(SurfaceId::DropShadows)
|
.canvas(SurfaceId::DropShadows)
|
||||||
.clear(skia::Color::TRANSPARENT);
|
.clear(skia::Color::TRANSPARENT);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_shape_tree_partial_uncached(
|
pub fn render_shape_tree_partial_uncached(
|
||||||
@ -2124,7 +2137,7 @@ impl RenderState {
|
|||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
allow_stop: bool,
|
allow_stop: bool,
|
||||||
) -> Result<(bool, bool), String> {
|
) -> Result<(bool, bool)> {
|
||||||
let mut iteration = 0;
|
let mut iteration = 0;
|
||||||
let mut is_empty = true;
|
let mut is_empty = true;
|
||||||
|
|
||||||
@ -2152,7 +2165,7 @@ impl RenderState {
|
|||||||
|
|
||||||
if visited_children {
|
if visited_children {
|
||||||
if !node_render_state.flattened {
|
if !node_render_state.flattened {
|
||||||
self.render_shape_exit(element, visited_mask, clip_bounds);
|
self.render_shape_exit(element, visited_mask, clip_bounds)?;
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -2226,7 +2239,13 @@ impl RenderState {
|
|||||||
scale,
|
scale,
|
||||||
translation,
|
translation,
|
||||||
&node_render_state,
|
&node_render_state,
|
||||||
);
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Render background blur BEFORE save_layer so it modifies
|
||||||
|
// the backdrop independently of the shape's opacity.
|
||||||
|
if !node_render_state.is_root() && self.focus_mode.is_active() {
|
||||||
|
self.render_background_blur(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render background blur BEFORE save_layer so it modifies
|
// Render background blur BEFORE save_layer so it modifies
|
||||||
@ -2262,7 +2281,7 @@ impl RenderState {
|
|||||||
scale,
|
scale,
|
||||||
translation,
|
translation,
|
||||||
&node_render_state,
|
&node_render_state,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.render_shape(
|
self.render_shape(
|
||||||
@ -2276,7 +2295,7 @@ impl RenderState {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
self.surfaces
|
self.surfaces
|
||||||
.canvas(SurfaceId::DropShadows)
|
.canvas(SurfaceId::DropShadows)
|
||||||
@ -2373,14 +2392,14 @@ impl RenderState {
|
|||||||
tree: ShapesPoolRef,
|
tree: ShapesPoolRef,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
allow_stop: bool,
|
allow_stop: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<()> {
|
||||||
let mut should_stop = false;
|
let mut should_stop = false;
|
||||||
let root_ids = {
|
let root_ids = {
|
||||||
if let Some(shape_id) = base_object {
|
if let Some(shape_id) = base_object {
|
||||||
vec![*shape_id]
|
vec![*shape_id]
|
||||||
} else {
|
} else {
|
||||||
let Some(root) = tree.get(&Uuid::nil()) else {
|
let Some(root) = tree.get(&Uuid::nil()) else {
|
||||||
return Err(String::from("Root shape not found"));
|
return Err(Error::CriticalError("Root shape not found".to_string()));
|
||||||
};
|
};
|
||||||
root.children_ids(false)
|
root.children_ids(false)
|
||||||
}
|
}
|
||||||
@ -2390,7 +2409,7 @@ impl RenderState {
|
|||||||
if let Some(current_tile) = self.current_tile {
|
if let Some(current_tile) = self.current_tile {
|
||||||
if self.surfaces.has_cached_tile_surface(current_tile) {
|
if self.surfaces.has_cached_tile_surface(current_tile) {
|
||||||
performance::begin_measure!("render_shape_tree::cached");
|
performance::begin_measure!("render_shape_tree::cached");
|
||||||
let tile_rect = self.get_current_tile_bounds();
|
let tile_rect = self.get_current_tile_bounds()?;
|
||||||
self.surfaces.draw_cached_tile_surface(
|
self.surfaces.draw_cached_tile_surface(
|
||||||
current_tile,
|
current_tile,
|
||||||
tile_rect,
|
tile_rect,
|
||||||
@ -2420,9 +2439,9 @@ impl RenderState {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
performance::end_measure!("render_shape_tree::uncached");
|
performance::end_measure!("render_shape_tree::uncached");
|
||||||
let tile_rect = self.get_current_tile_bounds();
|
let tile_rect = self.get_current_tile_bounds()?;
|
||||||
if !is_empty {
|
if !is_empty {
|
||||||
self.apply_render_to_final_canvas(tile_rect);
|
self.apply_render_to_final_canvas(tile_rect)?;
|
||||||
|
|
||||||
if self.options.is_debug_visible() {
|
if self.options.is_debug_visible() {
|
||||||
debug::render_workspace_current_tile(
|
debug::render_workspace_current_tile(
|
||||||
@ -2760,7 +2779,11 @@ impl RenderState {
|
|||||||
///
|
///
|
||||||
/// This is useful when you have a pre-computed set of shape IDs that need to be refreshed,
|
/// This is useful when you have a pre-computed set of shape IDs that need to be refreshed,
|
||||||
/// regardless of their relationship to other shapes (e.g., ancestors, descendants, or any other collection).
|
/// regardless of their relationship to other shapes (e.g., ancestors, descendants, or any other collection).
|
||||||
pub fn update_tiles_shapes(&mut self, shape_ids: &[Uuid], tree: ShapesPoolMutRef<'_>) {
|
pub fn update_tiles_shapes(
|
||||||
|
&mut self,
|
||||||
|
shape_ids: &[Uuid],
|
||||||
|
tree: ShapesPoolMutRef<'_>,
|
||||||
|
) -> Result<()> {
|
||||||
performance::begin_measure!("invalidate_and_update_tiles");
|
performance::begin_measure!("invalidate_and_update_tiles");
|
||||||
let mut all_tiles = HashSet::<tiles::Tile>::new();
|
let mut all_tiles = HashSet::<tiles::Tile>::new();
|
||||||
for shape_id in shape_ids {
|
for shape_id in shape_ids {
|
||||||
@ -2772,6 +2795,7 @@ impl RenderState {
|
|||||||
self.remove_cached_tile(tile);
|
self.remove_cached_tile(tile);
|
||||||
}
|
}
|
||||||
performance::end_measure!("invalidate_and_update_tiles");
|
performance::end_measure!("invalidate_and_update_tiles");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Rebuilds tiles for shapes with modifiers and processes their ancestors
|
/// Rebuilds tiles for shapes with modifiers and processes their ancestors
|
||||||
@ -2780,9 +2804,14 @@ impl RenderState {
|
|||||||
/// Additionally, it processes all ancestors of modified shapes to ensure their
|
/// Additionally, it processes all ancestors of modified shapes to ensure their
|
||||||
/// extended rectangles are properly recalculated and their tiles are updated.
|
/// extended rectangles are properly recalculated and their tiles are updated.
|
||||||
/// This is crucial for frames and groups that contain transformed children.
|
/// This is crucial for frames and groups that contain transformed children.
|
||||||
pub fn rebuild_modifier_tiles(&mut self, tree: ShapesPoolMutRef<'_>, ids: Vec<Uuid>) {
|
pub fn rebuild_modifier_tiles(
|
||||||
|
&mut self,
|
||||||
|
tree: ShapesPoolMutRef<'_>,
|
||||||
|
ids: Vec<Uuid>,
|
||||||
|
) -> Result<()> {
|
||||||
let ancestors = all_with_ancestors(&ids, tree, false);
|
let ancestors = all_with_ancestors(&ids, tree, false);
|
||||||
self.update_tiles_shapes(&ancestors, tree);
|
self.update_tiles_shapes(&ancestors, tree)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_scale(&self) -> f32 {
|
pub fn get_scale(&self) -> f32 {
|
||||||
|
|||||||
@ -179,9 +179,12 @@ pub fn render_debug_shape(
|
|||||||
#[cfg(target_arch = "wasm32")]
|
#[cfg(target_arch = "wasm32")]
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub fn console_debug_surface(render_state: &mut RenderState, id: SurfaceId) {
|
pub fn console_debug_surface(render_state: &mut RenderState, id: SurfaceId) {
|
||||||
let base64_image = render_state.surfaces.base64_snapshot(id);
|
let base64_image = render_state
|
||||||
|
.surfaces
|
||||||
|
.base64_snapshot(id)
|
||||||
|
.expect("Failed to get base64 image");
|
||||||
|
|
||||||
run_script!(format!("console.log('%c ', 'font-size: 1px; background: url(data:image/png;base64,{base64_image}) no-repeat; padding: 100px; background-size: contain;')"))
|
run_script!(format!("console.log('%c ', 'font-size: 1px; background: url(data:image/png;base64,{base64_image}) no-repeat; padding: 100px; background-size: contain;')"));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -194,7 +197,10 @@ pub fn console_debug_surface_rect(render_state: &mut RenderState, id: SurfaceId,
|
|||||||
rect.bottom as i32,
|
rect.bottom as i32,
|
||||||
);
|
);
|
||||||
|
|
||||||
let base64_image = render_state.surfaces.base64_snapshot_rect(id, int_rect);
|
let base64_image = render_state
|
||||||
|
.surfaces
|
||||||
|
.base64_snapshot_rect(id, int_rect)
|
||||||
|
.expect("Failed to get base64 image");
|
||||||
|
|
||||||
if let Some(base64_image) = base64_image {
|
if let Some(base64_image) = base64_image {
|
||||||
run_script!(format!("console.log('%c ', 'font-size: 1px; background: url(data:image/png;base64,{base64_image}) no-repeat; padding: 100px; background-size: contain;')"))
|
run_script!(format!("console.log('%c ', 'font-size: 1px; background: url(data:image/png;base64,{base64_image}) no-repeat; padding: 100px; background-size: contain;')"))
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use skia_safe::{self as skia, Paint, RRect};
|
use skia_safe::{self as skia, Paint, RRect};
|
||||||
|
|
||||||
use super::{filters, RenderState, SurfaceId};
|
use super::{filters, RenderState, SurfaceId};
|
||||||
|
use crate::error::Result;
|
||||||
use crate::render::get_source_rect;
|
use crate::render::get_source_rect;
|
||||||
use crate::shapes::{merge_fills, Fill, Frame, ImageFill, Rect, Shape, StrokeKind, Type};
|
use crate::shapes::{merge_fills, Fill, Frame, ImageFill, Rect, Shape, StrokeKind, Type};
|
||||||
|
|
||||||
@ -20,12 +21,11 @@ fn draw_image_fill(
|
|||||||
antialias: bool,
|
antialias: bool,
|
||||||
surface_id: SurfaceId,
|
surface_id: SurfaceId,
|
||||||
) {
|
) {
|
||||||
let image = render_state.images.get(&image_fill.id());
|
let Some(image) = render_state.images.get(&image_fill.id()) else {
|
||||||
if image.is_none() {
|
|
||||||
return;
|
return;
|
||||||
}
|
};
|
||||||
|
|
||||||
let size = image.unwrap().dimensions();
|
let size = image.dimensions();
|
||||||
let canvas = render_state.surfaces.canvas_and_mark_dirty(surface_id);
|
let canvas = render_state.surfaces.canvas_and_mark_dirty(surface_id);
|
||||||
let container = &shape.selrect;
|
let container = &shape.selrect;
|
||||||
let path_transform = shape.to_path_transform();
|
let path_transform = shape.to_path_transform();
|
||||||
@ -85,15 +85,13 @@ fn draw_image_fill(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Draw the image with the calculated destination rectangle
|
// Draw the image with the calculated destination rectangle
|
||||||
if let Some(image) = image {
|
canvas.draw_image_rect_with_sampling_options(
|
||||||
canvas.draw_image_rect_with_sampling_options(
|
image,
|
||||||
image,
|
Some((&src_rect, skia::canvas::SrcRectConstraint::Strict)),
|
||||||
Some((&src_rect, skia::canvas::SrcRectConstraint::Strict)),
|
dest_rect,
|
||||||
dest_rect,
|
render_state.sampling_options,
|
||||||
render_state.sampling_options,
|
paint,
|
||||||
paint,
|
);
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Restore the canvas to remove the clipping
|
// Restore the canvas to remove the clipping
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
@ -109,9 +107,9 @@ pub fn render(
|
|||||||
antialias: bool,
|
antialias: bool,
|
||||||
surface_id: SurfaceId,
|
surface_id: SurfaceId,
|
||||||
outset: Option<f32>,
|
outset: Option<f32>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
if fills.is_empty() {
|
if fills.is_empty() {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let scale = render_state.get_scale().max(1e-6);
|
let scale = render_state.get_scale().max(1e-6);
|
||||||
@ -134,9 +132,9 @@ pub fn render(
|
|||||||
surface_id,
|
surface_id,
|
||||||
outset,
|
outset,
|
||||||
inset,
|
inset,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut paint = merge_fills(fills, shape.selrect);
|
let mut paint = merge_fills(fills, shape.selrect);
|
||||||
@ -152,15 +150,17 @@ pub fn render(
|
|||||||
let mut filtered_paint = paint.clone();
|
let mut filtered_paint = paint.clone();
|
||||||
filtered_paint.set_image_filter(image_filter.clone());
|
filtered_paint.set_image_filter(image_filter.clone());
|
||||||
draw_fill_to_surface(state, shape, temp_surface, &filtered_paint, outset, inset);
|
draw_fill_to_surface(state, shape, temp_surface, &filtered_paint, outset, inset);
|
||||||
|
Ok(())
|
||||||
},
|
},
|
||||||
) {
|
)? {
|
||||||
return;
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
paint.set_image_filter(image_filter);
|
paint.set_image_filter(image_filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_fill_to_surface(render_state, shape, surface_id, &paint, outset, inset);
|
draw_fill_to_surface(render_state, shape, surface_id, &paint, outset, inset);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a single paint (with a merged shader) to the appropriate surface
|
/// Draws a single paint (with a merged shader) to the appropriate surface
|
||||||
@ -203,7 +203,7 @@ fn render_single_fill(
|
|||||||
surface_id: SurfaceId,
|
surface_id: SurfaceId,
|
||||||
outset: Option<f32>,
|
outset: Option<f32>,
|
||||||
inset: Option<f32>,
|
inset: Option<f32>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
let mut paint = fill.to_paint(&shape.selrect, antialias);
|
let mut paint = fill.to_paint(&shape.selrect, antialias);
|
||||||
if let Some(image_filter) = shape.image_filter(1.) {
|
if let Some(image_filter) = shape.image_filter(1.) {
|
||||||
let bounds = image_filter.compute_fast_bounds(shape.selrect);
|
let bounds = image_filter.compute_fast_bounds(shape.selrect);
|
||||||
@ -224,9 +224,10 @@ fn render_single_fill(
|
|||||||
outset,
|
outset,
|
||||||
inset,
|
inset,
|
||||||
);
|
);
|
||||||
|
Ok(())
|
||||||
},
|
},
|
||||||
) {
|
)? {
|
||||||
return;
|
return Ok(());
|
||||||
} else {
|
} else {
|
||||||
paint.set_image_filter(image_filter);
|
paint.set_image_filter(image_filter);
|
||||||
}
|
}
|
||||||
@ -242,6 +243,7 @@ fn render_single_fill(
|
|||||||
outset,
|
outset,
|
||||||
inset,
|
inset,
|
||||||
);
|
);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use skia_safe::{self as skia, ImageFilter, Rect};
|
use skia_safe::{self as skia, ImageFilter, Rect};
|
||||||
|
|
||||||
use super::{RenderState, SurfaceId};
|
use super::{RenderState, SurfaceId};
|
||||||
|
use crate::error::Result;
|
||||||
|
|
||||||
/// Composes two image filters, returning a combined filter if both are present,
|
/// Composes two image filters, returning a combined filter if both are present,
|
||||||
/// or the individual filter if only one is present, or None if neither is present.
|
/// or the individual filter if only one is present, or None if neither is present.
|
||||||
@ -36,12 +37,12 @@ pub fn render_with_filter_surface<F>(
|
|||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
target_surface: SurfaceId,
|
target_surface: SurfaceId,
|
||||||
draw_fn: F,
|
draw_fn: F,
|
||||||
) -> bool
|
) -> Result<bool>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut RenderState, SurfaceId),
|
F: FnOnce(&mut RenderState, SurfaceId) -> Result<()>,
|
||||||
{
|
{
|
||||||
if let Some((mut surface, scale)) =
|
if let Some((mut surface, scale)) =
|
||||||
render_into_filter_surface(render_state, bounds, 1.0, draw_fn)
|
render_into_filter_surface(render_state, bounds, 1.0, draw_fn)?
|
||||||
{
|
{
|
||||||
let canvas = render_state.surfaces.canvas_and_mark_dirty(target_surface);
|
let canvas = render_state.surfaces.canvas_and_mark_dirty(target_surface);
|
||||||
|
|
||||||
@ -58,9 +59,9 @@ where
|
|||||||
surface.draw(canvas, (0.0, 0.0), render_state.sampling_options, None);
|
surface.draw(canvas, (0.0, 0.0), render_state.sampling_options, None);
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
true
|
Ok(true)
|
||||||
} else {
|
} else {
|
||||||
false
|
Ok(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,12 +82,12 @@ pub fn render_into_filter_surface<F>(
|
|||||||
bounds: Rect,
|
bounds: Rect,
|
||||||
extra_downscale: f32,
|
extra_downscale: f32,
|
||||||
draw_fn: F,
|
draw_fn: F,
|
||||||
) -> Option<(skia::Surface, f32)>
|
) -> Result<Option<(skia::Surface, f32)>>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut RenderState, SurfaceId),
|
F: FnOnce(&mut RenderState, SurfaceId) -> Result<()>,
|
||||||
{
|
{
|
||||||
if !bounds.is_finite() || bounds.width() <= 0.0 || bounds.height() <= 0.0 {
|
if !bounds.is_finite() || bounds.width() <= 0.0 || bounds.height() <= 0.0 {
|
||||||
return None;
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let filter_id = SurfaceId::Filter;
|
let filter_id = SurfaceId::Filter;
|
||||||
@ -125,10 +126,10 @@ where
|
|||||||
canvas.translate((-bounds.left, -bounds.top));
|
canvas.translate((-bounds.left, -bounds.top));
|
||||||
}
|
}
|
||||||
|
|
||||||
draw_fn(render_state, filter_id);
|
draw_fn(render_state, filter_id)?;
|
||||||
|
|
||||||
render_state.surfaces.canvas(filter_id).restore();
|
render_state.surfaces.canvas(filter_id).restore();
|
||||||
|
|
||||||
let filter_surface = render_state.surfaces.surface_clone(filter_id);
|
let filter_surface = render_state.surfaces.surface_clone(filter_id);
|
||||||
Some((filter_surface, scale))
|
Ok(Some((filter_surface, scale)))
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
use skia_safe::{self as skia, textlayout, Font, FontMgr};
|
use skia_safe::{self as skia, textlayout, Font, FontMgr};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
use crate::shapes::{FontFamily, FontStyle};
|
use crate::shapes::{FontFamily, FontStyle};
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ pub struct FontStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FontStore {
|
impl FontStore {
|
||||||
pub fn new() -> Self {
|
pub fn try_new() -> Result<Self> {
|
||||||
let font_mgr = FontMgr::new();
|
let font_mgr = FontMgr::new();
|
||||||
let font_provider = load_default_provider(&font_mgr);
|
let font_provider = load_default_provider(&font_mgr);
|
||||||
let mut font_collection = skia::textlayout::FontCollection::new();
|
let mut font_collection = skia::textlayout::FontCollection::new();
|
||||||
@ -34,17 +35,19 @@ impl FontStore {
|
|||||||
|
|
||||||
let debug_typeface = font_provider
|
let debug_typeface = font_provider
|
||||||
.match_family_style(default_font().as_str(), skia::FontStyle::default())
|
.match_family_style(default_font().as_str(), skia::FontStyle::default())
|
||||||
.unwrap();
|
.ok_or(Error::CriticalError(
|
||||||
|
"Failed to match default font".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
let debug_font = skia::Font::new(debug_typeface, 10.0);
|
let debug_font = skia::Font::new(debug_typeface, 10.0);
|
||||||
|
|
||||||
Self {
|
Ok(Self {
|
||||||
font_mgr,
|
font_mgr,
|
||||||
font_provider,
|
font_provider,
|
||||||
font_collection,
|
font_collection,
|
||||||
debug_font,
|
debug_font,
|
||||||
fallback_fonts: HashSet::new(),
|
fallback_fonts: HashSet::new(),
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_scale_debug_font(&mut self, dpr: f32) {
|
pub fn set_scale_debug_font(&mut self, dpr: f32) {
|
||||||
@ -70,7 +73,7 @@ impl FontStore {
|
|||||||
font_data: &[u8],
|
font_data: &[u8],
|
||||||
is_emoji: bool,
|
is_emoji: bool,
|
||||||
is_fallback: bool,
|
is_fallback: bool,
|
||||||
) -> Result<(), String> {
|
) -> Result<()> {
|
||||||
if self.has_family(&family, is_emoji) {
|
if self.has_family(&family, is_emoji) {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@ -78,7 +81,9 @@ impl FontStore {
|
|||||||
let typeface = self
|
let typeface = self
|
||||||
.font_mgr
|
.font_mgr
|
||||||
.new_from_data(font_data, None)
|
.new_from_data(font_data, None)
|
||||||
.ok_or("Failed to create typeface")?;
|
.ok_or(Error::CriticalError(
|
||||||
|
"Failed to create typeface".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
let alias = format!("{}", family);
|
let alias = format!("{}", family);
|
||||||
let font_name = if is_emoji {
|
let font_name = if is_emoji {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use crate::error::{Error, Result};
|
||||||
use skia_safe::gpu::{self, gl::FramebufferInfo, gl::TextureInfo, DirectContext};
|
use skia_safe::gpu::{self, gl::FramebufferInfo, gl::TextureInfo, DirectContext};
|
||||||
use skia_safe::{self as skia, ISize};
|
use skia_safe::{self as skia, ISize};
|
||||||
|
|
||||||
@ -8,24 +9,30 @@ pub struct GpuState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GpuState {
|
impl GpuState {
|
||||||
pub fn new() -> Self {
|
pub fn try_new() -> Result<Self> {
|
||||||
let interface = gpu::gl::Interface::new_native().unwrap();
|
let interface = gpu::gl::Interface::new_native().ok_or(Error::CriticalError(
|
||||||
let context = gpu::direct_contexts::make_gl(interface, None).unwrap();
|
"Failed to create GL interface".to_string(),
|
||||||
|
))?;
|
||||||
|
let context = gpu::direct_contexts::make_gl(interface, None).ok_or(
|
||||||
|
Error::CriticalError("Failed to create GL context".to_string()),
|
||||||
|
)?;
|
||||||
let framebuffer_info = {
|
let framebuffer_info = {
|
||||||
let mut fboid: gl::types::GLint = 0;
|
let mut fboid: gl::types::GLint = 0;
|
||||||
unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
|
unsafe { gl::GetIntegerv(gl::FRAMEBUFFER_BINDING, &mut fboid) };
|
||||||
|
|
||||||
FramebufferInfo {
|
FramebufferInfo {
|
||||||
fboid: fboid.try_into().unwrap(),
|
fboid: fboid.try_into().map_err(|_| {
|
||||||
|
Error::CriticalError("Failed to convert GL framebuffer ID to u32".to_string())
|
||||||
|
})?,
|
||||||
format: gpu::gl::Format::RGBA8.into(),
|
format: gpu::gl::Format::RGBA8.into(),
|
||||||
protected: gpu::Protected::No,
|
protected: gpu::Protected::No,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
GpuState {
|
Ok(GpuState {
|
||||||
context,
|
context,
|
||||||
framebuffer_info,
|
framebuffer_info,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_webgl_texture(&mut self, width: i32, height: i32) -> gl::types::GLuint {
|
fn create_webgl_texture(&mut self, width: i32, height: i32) -> gl::types::GLuint {
|
||||||
@ -56,7 +63,11 @@ impl GpuState {
|
|||||||
texture_id
|
texture_id
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_surface_with_isize(&mut self, label: String, size: ISize) -> skia::Surface {
|
pub fn create_surface_with_isize(
|
||||||
|
&mut self,
|
||||||
|
label: String,
|
||||||
|
size: ISize,
|
||||||
|
) -> Result<skia::Surface> {
|
||||||
self.create_surface_with_dimensions(label, size.width, size.height)
|
self.create_surface_with_dimensions(label, size.width, size.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +76,7 @@ impl GpuState {
|
|||||||
label: String,
|
label: String,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
) -> skia::Surface {
|
) -> Result<skia::Surface> {
|
||||||
let backend_texture = unsafe {
|
let backend_texture = unsafe {
|
||||||
let texture_id = self.create_webgl_texture(width, height);
|
let texture_id = self.create_webgl_texture(width, height);
|
||||||
let texture_info = TextureInfo {
|
let texture_info = TextureInfo {
|
||||||
@ -77,7 +88,7 @@ impl GpuState {
|
|||||||
gpu::backend_textures::make_gl((width, height), gpu::Mipmapped::No, texture_info, label)
|
gpu::backend_textures::make_gl((width, height), gpu::Mipmapped::No, texture_info, label)
|
||||||
};
|
};
|
||||||
|
|
||||||
gpu::surfaces::wrap_backend_texture(
|
let surface = gpu::surfaces::wrap_backend_texture(
|
||||||
&mut self.context,
|
&mut self.context,
|
||||||
&backend_texture,
|
&backend_texture,
|
||||||
gpu::SurfaceOrigin::BottomLeft,
|
gpu::SurfaceOrigin::BottomLeft,
|
||||||
@ -86,15 +97,19 @@ impl GpuState {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.ok_or(Error::CriticalError(
|
||||||
|
"Failed to create Skia surface".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
Ok(surface)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a Skia surface that will be used for rendering.
|
/// Create a Skia surface that will be used for rendering.
|
||||||
pub fn create_target_surface(&mut self, width: i32, height: i32) -> skia::Surface {
|
pub fn create_target_surface(&mut self, width: i32, height: i32) -> Result<skia::Surface> {
|
||||||
let backend_render_target =
|
let backend_render_target =
|
||||||
gpu::backend_render_targets::make_gl((width, height), 1, 8, self.framebuffer_info);
|
gpu::backend_render_targets::make_gl((width, height), 1, 8, self.framebuffer_info);
|
||||||
|
|
||||||
gpu::surfaces::wrap_backend_render_target(
|
let surface = gpu::surfaces::wrap_backend_render_target(
|
||||||
&mut self.context,
|
&mut self.context,
|
||||||
&backend_render_target,
|
&backend_render_target,
|
||||||
gpu::SurfaceOrigin::BottomLeft,
|
gpu::SurfaceOrigin::BottomLeft,
|
||||||
@ -102,6 +117,10 @@ impl GpuState {
|
|||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.unwrap()
|
.ok_or(Error::CriticalError(
|
||||||
|
"Failed to create Skia surface".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
|
Ok(surface)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use crate::math::Rect as MathRect;
|
|||||||
use crate::shapes::ImageFill;
|
use crate::shapes::ImageFill;
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
use skia_safe::gpu::{surfaces, Budgeted, DirectContext};
|
use skia_safe::gpu::{surfaces, Budgeted, DirectContext};
|
||||||
use skia_safe::{self as skia, Codec, ISize};
|
use skia_safe::{self as skia, Codec, ISize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@ -70,7 +71,7 @@ fn create_image_from_gl_texture(
|
|||||||
texture_id: u32,
|
texture_id: u32,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
) -> Result<Image, String> {
|
) -> Result<Image> {
|
||||||
use skia_safe::gpu;
|
use skia_safe::gpu;
|
||||||
use skia_safe::gpu::gl::TextureInfo;
|
use skia_safe::gpu::gl::TextureInfo;
|
||||||
|
|
||||||
@ -99,7 +100,9 @@ fn create_image_from_gl_texture(
|
|||||||
skia::AlphaType::Premul,
|
skia::AlphaType::Premul,
|
||||||
None,
|
None,
|
||||||
)
|
)
|
||||||
.ok_or("Failed to create Skia image from GL texture")?;
|
.ok_or(crate::error::Error::CriticalError(
|
||||||
|
"Failed to create Skia image from GL texture".to_string(),
|
||||||
|
))?;
|
||||||
|
|
||||||
Ok(image)
|
Ok(image)
|
||||||
}
|
}
|
||||||
@ -147,11 +150,16 @@ impl ImageStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add(&mut self, id: Uuid, is_thumbnail: bool, image_data: &[u8]) -> Result<(), String> {
|
pub fn add(
|
||||||
|
&mut self,
|
||||||
|
id: Uuid,
|
||||||
|
is_thumbnail: bool,
|
||||||
|
image_data: &[u8],
|
||||||
|
) -> crate::error::Result<()> {
|
||||||
let key = (id, is_thumbnail);
|
let key = (id, is_thumbnail);
|
||||||
|
|
||||||
if self.images.contains_key(&key) {
|
if self.images.contains_key(&key) {
|
||||||
return Err("Image already exists".to_string());
|
return Err(Error::RecoverableError("Image already exists".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let raw_data = image_data.to_vec();
|
let raw_data = image_data.to_vec();
|
||||||
@ -174,11 +182,11 @@ impl ImageStore {
|
|||||||
texture_id: u32,
|
texture_id: u32,
|
||||||
width: i32,
|
width: i32,
|
||||||
height: i32,
|
height: i32,
|
||||||
) -> Result<(), String> {
|
) -> Result<()> {
|
||||||
let key = (id, is_thumbnail);
|
let key = (id, is_thumbnail);
|
||||||
|
|
||||||
if self.images.contains_key(&key) {
|
if self.images.contains_key(&key) {
|
||||||
return Err("Image already exists".to_string());
|
return Err(Error::RecoverableError("Image already exists".to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a Skia image from the existing GL texture
|
// Create a Skia image from the existing GL texture
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use crate::render::strokes;
|
|||||||
use crate::shapes::{ParagraphBuilderGroup, Shadow, Shape, Stroke, Type};
|
use crate::shapes::{ParagraphBuilderGroup, Shadow, Shape, Stroke, Type};
|
||||||
use skia_safe::{canvas::SaveLayerRec, Paint, Path};
|
use skia_safe::{canvas::SaveLayerRec, Paint, Path};
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
use crate::render::text;
|
use crate::render::text;
|
||||||
|
|
||||||
// Fill Shadows
|
// Fill Shadows
|
||||||
@ -36,7 +37,7 @@ pub fn render_stroke_inner_shadows(
|
|||||||
stroke: &Stroke,
|
stroke: &Stroke,
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
surface_id: SurfaceId,
|
surface_id: SurfaceId,
|
||||||
) {
|
) -> Result<()> {
|
||||||
if !shape.has_fills() {
|
if !shape.has_fills() {
|
||||||
for shadow in shape.inner_shadows_visible() {
|
for shadow in shape.inner_shadows_visible() {
|
||||||
let filter = shadow.get_inner_shadow_filter();
|
let filter = shadow.get_inner_shadow_filter();
|
||||||
@ -48,9 +49,10 @@ pub fn render_stroke_inner_shadows(
|
|||||||
filter.as_ref(),
|
filter.as_ref(),
|
||||||
antialias,
|
antialias,
|
||||||
None, // Inner shadows don't use spread
|
None, // Inner shadows don't use spread
|
||||||
)
|
)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render text paths (unused)
|
// Render text paths (unused)
|
||||||
@ -133,9 +135,9 @@ pub fn render_text_shadows(
|
|||||||
surface_id: Option<SurfaceId>,
|
surface_id: Option<SurfaceId>,
|
||||||
shadows: &[Paint],
|
shadows: &[Paint],
|
||||||
blur_filter: &Option<skia_safe::ImageFilter>,
|
blur_filter: &Option<skia_safe::ImageFilter>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
if stroke_paragraphs_group.is_empty() {
|
if stroke_paragraphs_group.is_empty() {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let canvas = render_state
|
let canvas = render_state
|
||||||
@ -156,7 +158,7 @@ pub fn render_text_shadows(
|
|||||||
blur_filter.as_ref(),
|
blur_filter.as_ref(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
for stroke_paragraphs in stroke_paragraphs_group.iter_mut() {
|
for stroke_paragraphs in stroke_paragraphs_group.iter_mut() {
|
||||||
text::render(
|
text::render(
|
||||||
@ -169,9 +171,10 @@ pub fn render_text_shadows(
|
|||||||
blur_filter.as_ref(),
|
blur_filter.as_ref(),
|
||||||
None,
|
None,
|
||||||
None,
|
None,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ use crate::shapes::{
|
|||||||
use skia_safe::{self as skia, ImageFilter, RRect};
|
use skia_safe::{self as skia, ImageFilter, RRect};
|
||||||
|
|
||||||
use super::{filters, RenderState, SurfaceId};
|
use super::{filters, RenderState, SurfaceId};
|
||||||
|
use crate::error::{Error, Result};
|
||||||
use crate::render::filters::compose_filters;
|
use crate::render::filters::compose_filters;
|
||||||
use crate::render::{get_dest_rect, get_source_rect};
|
use crate::render::{get_dest_rect, get_source_rect};
|
||||||
|
|
||||||
@ -294,16 +295,16 @@ fn handle_stroke_caps(
|
|||||||
blur: Option<&ImageFilter>,
|
blur: Option<&ImageFilter>,
|
||||||
_antialias: bool,
|
_antialias: bool,
|
||||||
) {
|
) {
|
||||||
let mut points = path.points().to_vec();
|
|
||||||
// Curves can have duplicated points, so let's remove consecutive duplicated points
|
|
||||||
points.dedup();
|
|
||||||
let c_points = points.len();
|
|
||||||
|
|
||||||
// Closed shapes don't have caps
|
// Closed shapes don't have caps
|
||||||
if c_points >= 2 && is_open {
|
if !is_open {
|
||||||
let first_point = points.first().unwrap();
|
return;
|
||||||
let last_point = points.last().unwrap();
|
}
|
||||||
|
|
||||||
|
// Curves can have duplicated points, so let's remove consecutive duplicated points
|
||||||
|
let mut points = path.points().to_vec();
|
||||||
|
points.dedup();
|
||||||
|
|
||||||
|
if let [first_point, .., last_point] = points.as_slice() {
|
||||||
let mut paint_stroke = paint.clone();
|
let mut paint_stroke = paint.clone();
|
||||||
|
|
||||||
if let Some(filter) = blur {
|
if let Some(filter) = blur {
|
||||||
@ -328,7 +329,7 @@ fn handle_stroke_caps(
|
|||||||
stroke.width,
|
stroke.width,
|
||||||
&mut paint_stroke,
|
&mut paint_stroke,
|
||||||
last_point,
|
last_point,
|
||||||
&points[c_points - 2],
|
&points[points.len() - 2],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -456,14 +457,13 @@ fn draw_image_stroke_in_container(
|
|||||||
image_fill: &ImageFill,
|
image_fill: &ImageFill,
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
surface_id: SurfaceId,
|
surface_id: SurfaceId,
|
||||||
) {
|
) -> Result<()> {
|
||||||
let scale = render_state.get_scale();
|
let scale = render_state.get_scale();
|
||||||
let image = render_state.images.get(&image_fill.id());
|
let Some(image) = render_state.images.get(&image_fill.id()) else {
|
||||||
if image.is_none() {
|
return Ok(());
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
|
||||||
let size = image.unwrap().dimensions();
|
let size = image.dimensions();
|
||||||
let canvas = render_state.surfaces.canvas_and_mark_dirty(surface_id);
|
let canvas = render_state.surfaces.canvas_and_mark_dirty(surface_id);
|
||||||
let container = &shape.selrect;
|
let container = &shape.selrect;
|
||||||
let path_transform = shape.to_path_transform();
|
let path_transform = shape.to_path_transform();
|
||||||
@ -509,7 +509,10 @@ fn draw_image_stroke_in_container(
|
|||||||
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
|
shape_type @ (Type::Path(_) | Type::Bool(_)) => {
|
||||||
if let Some(p) = shape_type.path() {
|
if let Some(p) = shape_type.path() {
|
||||||
canvas.save();
|
canvas.save();
|
||||||
let path = p.to_skia_path().make_transform(&path_transform.unwrap());
|
|
||||||
|
let path = p.to_skia_path().make_transform(
|
||||||
|
&path_transform.ok_or(Error::CriticalError("No path transform".to_string()))?,
|
||||||
|
);
|
||||||
let stroke_kind = stroke.render_kind(p.is_open());
|
let stroke_kind = stroke.render_kind(p.is_open());
|
||||||
match stroke_kind {
|
match stroke_kind {
|
||||||
StrokeKind::Inner => {
|
StrokeKind::Inner => {
|
||||||
@ -561,7 +564,7 @@ fn draw_image_stroke_in_container(
|
|||||||
|
|
||||||
canvas.clip_rect(dest_rect, skia::ClipOp::Intersect, antialias);
|
canvas.clip_rect(dest_rect, skia::ClipOp::Intersect, antialias);
|
||||||
canvas.draw_image_rect_with_sampling_options(
|
canvas.draw_image_rect_with_sampling_options(
|
||||||
image.unwrap(),
|
image,
|
||||||
Some((&src_rect, skia::canvas::SrcRectConstraint::Strict)),
|
Some((&src_rect, skia::canvas::SrcRectConstraint::Strict)),
|
||||||
dest_rect,
|
dest_rect,
|
||||||
render_state.sampling_options,
|
render_state.sampling_options,
|
||||||
@ -571,7 +574,9 @@ fn draw_image_stroke_in_container(
|
|||||||
// Clear outer stroke for paths if necessary. When adding an outer stroke we need to empty the stroke added too in the inner area.
|
// Clear outer stroke for paths if necessary. When adding an outer stroke we need to empty the stroke added too in the inner area.
|
||||||
if let Type::Path(p) = &shape.shape_type {
|
if let Type::Path(p) = &shape.shape_type {
|
||||||
if stroke.render_kind(p.is_open()) == StrokeKind::Outer {
|
if stroke.render_kind(p.is_open()) == StrokeKind::Outer {
|
||||||
let path = p.to_skia_path().make_transform(&path_transform.unwrap());
|
let path = p.to_skia_path().make_transform(
|
||||||
|
&path_transform.ok_or(Error::CriticalError("No path transform".to_string()))?,
|
||||||
|
);
|
||||||
let mut clear_paint = skia::Paint::default();
|
let mut clear_paint = skia::Paint::default();
|
||||||
clear_paint.set_blend_mode(skia::BlendMode::Clear);
|
clear_paint.set_blend_mode(skia::BlendMode::Clear);
|
||||||
clear_paint.set_anti_alias(antialias);
|
clear_paint.set_anti_alias(antialias);
|
||||||
@ -581,6 +586,7 @@ fn draw_image_stroke_in_container(
|
|||||||
|
|
||||||
// Restore canvas state
|
// Restore canvas state
|
||||||
canvas.restore();
|
canvas.restore();
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders all strokes for a shape. Merges strokes that share the same
|
/// Renders all strokes for a shape. Merges strokes that share the same
|
||||||
@ -593,9 +599,9 @@ pub fn render(
|
|||||||
surface_id: Option<SurfaceId>,
|
surface_id: Option<SurfaceId>,
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
outset: Option<f32>,
|
outset: Option<f32>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
if strokes.is_empty() {
|
if strokes.is_empty() {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let has_image_fills = strokes.iter().any(|s| matches!(s.fill, Fill::Image(_)));
|
let has_image_fills = strokes.iter().any(|s| matches!(s.fill, Fill::Image(_)));
|
||||||
@ -655,13 +661,14 @@ pub fn render(
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
outset,
|
outset,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.surfaces.canvas(temp_surface).restore();
|
state.surfaces.canvas(temp_surface).restore();
|
||||||
|
Ok(())
|
||||||
},
|
},
|
||||||
) {
|
)? {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -675,9 +682,9 @@ pub fn render(
|
|||||||
None,
|
None,
|
||||||
antialias,
|
antialias,
|
||||||
outset,
|
outset,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
render_merged(
|
render_merged(
|
||||||
@ -688,7 +695,7 @@ pub fn render(
|
|||||||
antialias,
|
antialias,
|
||||||
false,
|
false,
|
||||||
outset,
|
outset,
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn strokes_share_geometry(strokes: &[&Stroke]) -> bool {
|
fn strokes_share_geometry(strokes: &[&Stroke]) -> bool {
|
||||||
@ -709,7 +716,7 @@ fn render_merged(
|
|||||||
antialias: bool,
|
antialias: bool,
|
||||||
bypass_filter: bool,
|
bypass_filter: bool,
|
||||||
outset: Option<f32>,
|
outset: Option<f32>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
let representative = *strokes
|
let representative = *strokes
|
||||||
.last()
|
.last()
|
||||||
.expect("render_merged expects at least one stroke");
|
.expect("render_merged expects at least one stroke");
|
||||||
@ -761,14 +768,15 @@ fn render_merged(
|
|||||||
antialias,
|
antialias,
|
||||||
true,
|
true,
|
||||||
outset,
|
outset,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
state.surfaces.apply_mut(temp_surface as u32, |surface| {
|
state.surfaces.apply_mut(temp_surface as u32, |surface| {
|
||||||
surface.canvas().restore();
|
surface.canvas().restore();
|
||||||
});
|
});
|
||||||
|
Ok(())
|
||||||
},
|
},
|
||||||
) {
|
)? {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -844,6 +852,7 @@ fn render_merged(
|
|||||||
}
|
}
|
||||||
_ => unreachable!("This shape should not have strokes"),
|
_ => unreachable!("This shape should not have strokes"),
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renders a single stroke. Used by the shadow module which needs per-stroke
|
/// Renders a single stroke. Used by the shadow module which needs per-stroke
|
||||||
@ -857,7 +866,7 @@ pub fn render_single(
|
|||||||
shadow: Option<&ImageFilter>,
|
shadow: Option<&ImageFilter>,
|
||||||
antialias: bool,
|
antialias: bool,
|
||||||
outset: Option<f32>,
|
outset: Option<f32>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
render_single_internal(
|
render_single_internal(
|
||||||
render_state,
|
render_state,
|
||||||
shape,
|
shape,
|
||||||
@ -868,7 +877,7 @@ pub fn render_single(
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
outset,
|
outset,
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@ -882,7 +891,7 @@ fn render_single_internal(
|
|||||||
bypass_filter: bool,
|
bypass_filter: bool,
|
||||||
skip_blur: bool,
|
skip_blur: bool,
|
||||||
outset: Option<f32>,
|
outset: Option<f32>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
if !bypass_filter {
|
if !bypass_filter {
|
||||||
if let Some(image_filter) = shape.image_filter(1.) {
|
if let Some(image_filter) = shape.image_filter(1.) {
|
||||||
let mut content_bounds = shape.selrect;
|
let mut content_bounds = shape.selrect;
|
||||||
@ -916,10 +925,10 @@ fn render_single_internal(
|
|||||||
true,
|
true,
|
||||||
true,
|
true,
|
||||||
outset,
|
outset,
|
||||||
);
|
)
|
||||||
},
|
},
|
||||||
) {
|
)? {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -949,7 +958,7 @@ fn render_single_internal(
|
|||||||
image_fill,
|
image_fill,
|
||||||
antialias,
|
antialias,
|
||||||
target_surface,
|
target_surface,
|
||||||
);
|
)?;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
match &shape.shape_type {
|
match &shape.shape_type {
|
||||||
@ -1014,6 +1023,7 @@ fn render_single_internal(
|
|||||||
_ => unreachable!("This shape should not have strokes"),
|
_ => unreachable!("This shape should not have strokes"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render text paths (unused)
|
// Render text paths (unused)
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use crate::error::{Error, Result};
|
||||||
use crate::performance;
|
use crate::performance;
|
||||||
use crate::shapes::Shape;
|
use crate::shapes::Shape;
|
||||||
|
|
||||||
@ -61,38 +62,39 @@ pub struct Surfaces {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl Surfaces {
|
impl Surfaces {
|
||||||
pub fn new(
|
pub fn try_new(
|
||||||
gpu_state: &mut GpuState,
|
gpu_state: &mut GpuState,
|
||||||
(width, height): (i32, i32),
|
(width, height): (i32, i32),
|
||||||
sampling_options: skia::SamplingOptions,
|
sampling_options: skia::SamplingOptions,
|
||||||
tile_dims: skia::ISize,
|
tile_dims: skia::ISize,
|
||||||
) -> Self {
|
) -> Result<Self> {
|
||||||
let extra_tile_dims = skia::ISize::new(
|
let extra_tile_dims = skia::ISize::new(
|
||||||
tile_dims.width * TILE_SIZE_MULTIPLIER,
|
tile_dims.width * TILE_SIZE_MULTIPLIER,
|
||||||
tile_dims.height * TILE_SIZE_MULTIPLIER,
|
tile_dims.height * TILE_SIZE_MULTIPLIER,
|
||||||
);
|
);
|
||||||
let margins = skia::ISize::new(extra_tile_dims.width / 4, extra_tile_dims.height / 4);
|
let margins = skia::ISize::new(extra_tile_dims.width / 4, extra_tile_dims.height / 4);
|
||||||
|
|
||||||
let target = gpu_state.create_target_surface(width, height);
|
let target = gpu_state.create_target_surface(width, height)?;
|
||||||
let filter = gpu_state.create_surface_with_isize("filter".to_string(), extra_tile_dims);
|
let filter = gpu_state.create_surface_with_isize("filter".to_string(), extra_tile_dims)?;
|
||||||
let cache = gpu_state.create_surface_with_dimensions("cache".to_string(), width, height);
|
let cache = gpu_state.create_surface_with_dimensions("cache".to_string(), width, height)?;
|
||||||
let current = gpu_state.create_surface_with_isize("current".to_string(), extra_tile_dims);
|
let current =
|
||||||
|
gpu_state.create_surface_with_isize("current".to_string(), extra_tile_dims)?;
|
||||||
let drop_shadows =
|
let drop_shadows =
|
||||||
gpu_state.create_surface_with_isize("drop_shadows".to_string(), extra_tile_dims);
|
gpu_state.create_surface_with_isize("drop_shadows".to_string(), extra_tile_dims)?;
|
||||||
let inner_shadows =
|
let inner_shadows =
|
||||||
gpu_state.create_surface_with_isize("inner_shadows".to_string(), extra_tile_dims);
|
gpu_state.create_surface_with_isize("inner_shadows".to_string(), extra_tile_dims)?;
|
||||||
let text_drop_shadows =
|
let text_drop_shadows = gpu_state
|
||||||
gpu_state.create_surface_with_isize("text_drop_shadows".to_string(), extra_tile_dims);
|
.create_surface_with_isize("text_drop_shadows".to_string(), extra_tile_dims)?;
|
||||||
let shape_fills =
|
let shape_fills =
|
||||||
gpu_state.create_surface_with_isize("shape_fills".to_string(), extra_tile_dims);
|
gpu_state.create_surface_with_isize("shape_fills".to_string(), extra_tile_dims)?;
|
||||||
let shape_strokes =
|
let shape_strokes =
|
||||||
gpu_state.create_surface_with_isize("shape_strokes".to_string(), extra_tile_dims);
|
gpu_state.create_surface_with_isize("shape_strokes".to_string(), extra_tile_dims)?;
|
||||||
|
|
||||||
let ui = gpu_state.create_surface_with_dimensions("ui".to_string(), width, height);
|
let ui = gpu_state.create_surface_with_dimensions("ui".to_string(), width, height)?;
|
||||||
let debug = gpu_state.create_surface_with_dimensions("debug".to_string(), width, height);
|
let debug = gpu_state.create_surface_with_dimensions("debug".to_string(), width, height)?;
|
||||||
|
|
||||||
let tiles = TileTextureCache::new();
|
let tiles = TileTextureCache::new();
|
||||||
Surfaces {
|
Ok(Surfaces {
|
||||||
target,
|
target,
|
||||||
filter,
|
filter,
|
||||||
cache,
|
cache,
|
||||||
@ -108,7 +110,7 @@ impl Surfaces {
|
|||||||
sampling_options,
|
sampling_options,
|
||||||
margins,
|
margins,
|
||||||
dirty_surfaces: 0,
|
dirty_surfaces: 0,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_tiles(&mut self) {
|
pub fn clear_tiles(&mut self) {
|
||||||
@ -119,8 +121,14 @@ impl Surfaces {
|
|||||||
self.margins
|
self.margins
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, gpu_state: &mut GpuState, new_width: i32, new_height: i32) {
|
pub fn resize(
|
||||||
self.reset_from_target(gpu_state.create_target_surface(new_width, new_height));
|
&mut self,
|
||||||
|
gpu_state: &mut GpuState,
|
||||||
|
new_width: i32,
|
||||||
|
new_height: i32,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.reset_from_target(gpu_state.create_target_surface(new_width, new_height)?)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn snapshot(&mut self, id: SurfaceId) -> skia::Image {
|
pub fn snapshot(&mut self, id: SurfaceId) -> skia::Image {
|
||||||
@ -132,26 +140,33 @@ impl Surfaces {
|
|||||||
(self.filter.width(), self.filter.height())
|
(self.filter.width(), self.filter.height())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base64_snapshot(&mut self, id: SurfaceId) -> String {
|
pub fn base64_snapshot(&mut self, id: SurfaceId) -> Result<String> {
|
||||||
let surface = self.get_mut(id);
|
let surface = self.get_mut(id);
|
||||||
let image = surface.image_snapshot();
|
let image = surface.image_snapshot();
|
||||||
let mut context = surface.direct_context();
|
let mut context = surface.direct_context();
|
||||||
let encoded_image = image
|
let encoded_image = image
|
||||||
.encode(context.as_mut(), skia::EncodedImageFormat::PNG, None)
|
.encode(context.as_mut(), skia::EncodedImageFormat::PNG, None)
|
||||||
.unwrap();
|
.ok_or(Error::CriticalError("Failed to encode image".to_string()))?;
|
||||||
general_purpose::STANDARD.encode(encoded_image.as_bytes())
|
Ok(general_purpose::STANDARD.encode(encoded_image.as_bytes()))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn base64_snapshot_rect(&mut self, id: SurfaceId, irect: skia::IRect) -> Option<String> {
|
pub fn base64_snapshot_rect(
|
||||||
|
&mut self,
|
||||||
|
id: SurfaceId,
|
||||||
|
irect: skia::IRect,
|
||||||
|
) -> Result<Option<String>> {
|
||||||
let surface = self.get_mut(id);
|
let surface = self.get_mut(id);
|
||||||
if let Some(image) = surface.image_snapshot_with_bounds(irect) {
|
if let Some(image) = surface.image_snapshot_with_bounds(irect) {
|
||||||
let mut context = surface.direct_context();
|
let mut context = surface.direct_context();
|
||||||
let encoded_image = image
|
let encoded_image = image
|
||||||
.encode(context.as_mut(), skia::EncodedImageFormat::PNG, None)
|
.encode(context.as_mut(), skia::EncodedImageFormat::PNG, None)
|
||||||
.unwrap();
|
.ok_or(Error::CriticalError("Failed to encode image".to_string()))?;
|
||||||
return Some(general_purpose::STANDARD.encode(encoded_image.as_bytes()));
|
Ok(Some(
|
||||||
|
general_purpose::STANDARD.encode(encoded_image.as_bytes()),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
}
|
}
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a mutable reference to the canvas and automatically marks
|
/// Returns a mutable reference to the canvas and automatically marks
|
||||||
@ -341,22 +356,41 @@ impl Surfaces {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn reset_from_target(&mut self, target: skia::Surface) {
|
fn reset_from_target(&mut self, target: skia::Surface) -> Result<()> {
|
||||||
let dim = (target.width(), target.height());
|
let dim = (target.width(), target.height());
|
||||||
self.target = target;
|
self.target = target;
|
||||||
self.filter = self.target.new_surface_with_dimensions(dim).unwrap();
|
self.filter = self
|
||||||
self.debug = self.target.new_surface_with_dimensions(dim).unwrap();
|
.target
|
||||||
self.ui = self.target.new_surface_with_dimensions(dim).unwrap();
|
.new_surface_with_dimensions(dim)
|
||||||
|
.ok_or(Error::CriticalError("Failed to create surface".to_string()))?;
|
||||||
|
self.debug = self
|
||||||
|
.target
|
||||||
|
.new_surface_with_dimensions(dim)
|
||||||
|
.ok_or(Error::CriticalError("Failed to create surface".to_string()))?;
|
||||||
|
self.ui = self
|
||||||
|
.target
|
||||||
|
.new_surface_with_dimensions(dim)
|
||||||
|
.ok_or(Error::CriticalError("Failed to create surface".to_string()))?;
|
||||||
// The rest are tile size surfaces
|
// The rest are tile size surfaces
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize_cache(&mut self, cache_dims: skia::ISize, interest_area_threshold: i32) {
|
pub fn resize_cache(
|
||||||
self.cache = self.target.new_surface_with_dimensions(cache_dims).unwrap();
|
&mut self,
|
||||||
|
cache_dims: skia::ISize,
|
||||||
|
interest_area_threshold: i32,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.cache = self
|
||||||
|
.target
|
||||||
|
.new_surface_with_dimensions(cache_dims)
|
||||||
|
.ok_or(Error::CriticalError("Failed to create surface".to_string()))?;
|
||||||
self.cache.canvas().reset_matrix();
|
self.cache.canvas().reset_matrix();
|
||||||
self.cache.canvas().translate((
|
self.cache.canvas().translate((
|
||||||
(interest_area_threshold as f32 * TILE_SIZE),
|
(interest_area_threshold as f32 * TILE_SIZE),
|
||||||
(interest_area_threshold as f32 * TILE_SIZE),
|
(interest_area_threshold as f32 * TILE_SIZE),
|
||||||
));
|
));
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw_rect_to(
|
pub fn draw_rect_to(
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use super::{filters, RenderState, Shape, SurfaceId};
|
use super::{filters, RenderState, Shape, SurfaceId};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
error::Result,
|
||||||
math::Rect,
|
math::Rect,
|
||||||
shapes::{
|
shapes::{
|
||||||
calculate_position_data, calculate_text_layout_data, merge_fills, set_paint_fill,
|
calculate_position_data, calculate_text_layout_data, merge_fills, set_paint_fill,
|
||||||
@ -66,7 +67,7 @@ pub fn stroke_paragraph_builder_group_from_text(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let stroke_paragraphs: Vec<ParagraphBuilder> = (0..stroke_paragraphs_map.len())
|
let stroke_paragraphs: Vec<ParagraphBuilder> = (0..stroke_paragraphs_map.len())
|
||||||
.map(|i| stroke_paragraphs_map.remove(&i).unwrap())
|
.filter_map(|i| stroke_paragraphs_map.remove(&i))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
paragraph_group.push(stroke_paragraphs);
|
paragraph_group.push(stroke_paragraphs);
|
||||||
@ -195,7 +196,7 @@ pub fn render_with_bounds_outset(
|
|||||||
stroke_bounds_outset: f32,
|
stroke_bounds_outset: f32,
|
||||||
fill_inset: Option<f32>,
|
fill_inset: Option<f32>,
|
||||||
layer_opacity: Option<f32>,
|
layer_opacity: Option<f32>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
if let Some(render_state) = render_state {
|
if let Some(render_state) = render_state {
|
||||||
let target_surface = surface_id.unwrap_or(SurfaceId::Fills);
|
let target_surface = surface_id.unwrap_or(SurfaceId::Fills);
|
||||||
|
|
||||||
@ -225,9 +226,10 @@ pub fn render_with_bounds_outset(
|
|||||||
fill_inset,
|
fill_inset,
|
||||||
layer_opacity,
|
layer_opacity,
|
||||||
);
|
);
|
||||||
|
Ok(())
|
||||||
},
|
},
|
||||||
) {
|
)? {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -242,7 +244,7 @@ pub fn render_with_bounds_outset(
|
|||||||
fill_inset,
|
fill_inset,
|
||||||
layer_opacity,
|
layer_opacity,
|
||||||
);
|
);
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(canvas) = canvas {
|
if let Some(canvas) = canvas {
|
||||||
@ -256,6 +258,7 @@ pub fn render_with_bounds_outset(
|
|||||||
layer_opacity,
|
layer_opacity,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@ -269,7 +272,7 @@ pub fn render(
|
|||||||
blur: Option<&ImageFilter>,
|
blur: Option<&ImageFilter>,
|
||||||
fill_inset: Option<f32>,
|
fill_inset: Option<f32>,
|
||||||
layer_opacity: Option<f32>,
|
layer_opacity: Option<f32>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
render_with_bounds_outset(
|
render_with_bounds_outset(
|
||||||
render_state,
|
render_state,
|
||||||
canvas,
|
canvas,
|
||||||
@ -281,7 +284,7 @@ pub fn render(
|
|||||||
0.0,
|
0.0,
|
||||||
fill_inset,
|
fill_inset,
|
||||||
layer_opacity,
|
layer_opacity,
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_text_on_canvas(
|
fn render_text_on_canvas(
|
||||||
|
|||||||
@ -9,6 +9,7 @@ pub mod grid_layout;
|
|||||||
use crate::math::{self as math, bools, identitish, is_close_to, Bounds, Matrix, Point};
|
use crate::math::{self as math, bools, identitish, is_close_to, Bounds, Matrix, Point};
|
||||||
use common::GetBounds;
|
use common::GetBounds;
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, TransformEntry,
|
ConstraintH, ConstraintV, Frame, Group, GrowType, Layout, Modifier, Shape, TransformEntry,
|
||||||
TransformEntrySource, Type,
|
TransformEntrySource, Type,
|
||||||
@ -24,9 +25,9 @@ fn propagate_children(
|
|||||||
parent_bounds_after: &Bounds,
|
parent_bounds_after: &Bounds,
|
||||||
transform: Matrix,
|
transform: Matrix,
|
||||||
bounds: &HashMap<Uuid, Bounds>,
|
bounds: &HashMap<Uuid, Bounds>,
|
||||||
) -> VecDeque<Modifier> {
|
) -> Result<VecDeque<Modifier>> {
|
||||||
if identitish(&transform) {
|
if identitish(&transform) {
|
||||||
return VecDeque::new();
|
return Ok(VecDeque::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut result = VecDeque::new();
|
let mut result = VecDeque::new();
|
||||||
@ -74,12 +75,12 @@ fn propagate_children(
|
|||||||
constraint_v,
|
constraint_v,
|
||||||
transform,
|
transform,
|
||||||
child.ignore_constraints,
|
child.ignore_constraints,
|
||||||
);
|
)?;
|
||||||
|
|
||||||
result.push_back(Modifier::transform_propagate(*child_id, transform));
|
result.push_back(Modifier::transform_propagate(*child_id, transform));
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_group_bounds(
|
fn calculate_group_bounds(
|
||||||
@ -172,9 +173,9 @@ fn propagate_transform(
|
|||||||
entries: &mut VecDeque<Modifier>,
|
entries: &mut VecDeque<Modifier>,
|
||||||
bounds: &mut HashMap<Uuid, Bounds>,
|
bounds: &mut HashMap<Uuid, Bounds>,
|
||||||
modifiers: &mut HashMap<Uuid, Matrix>,
|
modifiers: &mut HashMap<Uuid, Matrix>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
let Some(shape) = state.shapes.get(&entry.id) else {
|
let Some(shape) = state.shapes.get(&entry.id) else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let shapes = &state.shapes;
|
let shapes = &state.shapes;
|
||||||
@ -249,7 +250,7 @@ fn propagate_transform(
|
|||||||
&shape_bounds_after,
|
&shape_bounds_after,
|
||||||
transform,
|
transform,
|
||||||
bounds,
|
bounds,
|
||||||
);
|
)?;
|
||||||
entries.append(&mut children);
|
entries.append(&mut children);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -275,6 +276,7 @@ fn propagate_transform(
|
|||||||
entries.push_back(Modifier::reflow(parent.id, false));
|
entries.push_back(Modifier::reflow(parent.id, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn propagate_reflow(
|
fn propagate_reflow(
|
||||||
@ -338,34 +340,35 @@ fn reflow_shape(
|
|||||||
reflown: &mut HashSet<Uuid>,
|
reflown: &mut HashSet<Uuid>,
|
||||||
entries: &mut VecDeque<Modifier>,
|
entries: &mut VecDeque<Modifier>,
|
||||||
bounds: &mut HashMap<Uuid, Bounds>,
|
bounds: &mut HashMap<Uuid, Bounds>,
|
||||||
) {
|
) -> Result<()> {
|
||||||
let Some(shape) = state.shapes.get(id) else {
|
let Some(shape) = state.shapes.get(id) else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
let shapes = &state.shapes;
|
let shapes = &state.shapes;
|
||||||
|
|
||||||
let Type::Frame(frame_data) = &shape.shape_type else {
|
let Type::Frame(frame_data) = &shape.shape_type else {
|
||||||
return;
|
return Ok(());
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(Layout::FlexLayout(layout_data, flex_data)) = &frame_data.layout {
|
if let Some(Layout::FlexLayout(layout_data, flex_data)) = &frame_data.layout {
|
||||||
let mut children =
|
let mut children =
|
||||||
flex_layout::reflow_flex_layout(shape, layout_data, flex_data, shapes, bounds);
|
flex_layout::reflow_flex_layout(shape, layout_data, flex_data, shapes, bounds)?;
|
||||||
entries.append(&mut children);
|
entries.append(&mut children);
|
||||||
} else if let Some(Layout::GridLayout(layout_data, grid_data)) = &frame_data.layout {
|
} else if let Some(Layout::GridLayout(layout_data, grid_data)) = &frame_data.layout {
|
||||||
let mut children =
|
let mut children =
|
||||||
grid_layout::reflow_grid_layout(shape, layout_data, grid_data, shapes, bounds);
|
grid_layout::reflow_grid_layout(shape, layout_data, grid_data, shapes, bounds)?;
|
||||||
entries.append(&mut children);
|
entries.append(&mut children);
|
||||||
}
|
}
|
||||||
reflown.insert(*id);
|
reflown.insert(*id);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn propagate_modifiers(
|
pub fn propagate_modifiers(
|
||||||
state: &State,
|
state: &State,
|
||||||
modifiers: &[TransformEntry],
|
modifiers: &[TransformEntry],
|
||||||
pixel_precision: bool,
|
pixel_precision: bool,
|
||||||
) -> Vec<TransformEntry> {
|
) -> Result<Vec<TransformEntry>> {
|
||||||
let mut entries: VecDeque<_> = modifiers
|
let mut entries: VecDeque<_> = modifiers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|entry| {
|
.map(|entry| {
|
||||||
@ -399,7 +402,7 @@ pub fn propagate_modifiers(
|
|||||||
&mut entries,
|
&mut entries,
|
||||||
&mut bounds,
|
&mut bounds,
|
||||||
&mut modifiers,
|
&mut modifiers,
|
||||||
),
|
)?,
|
||||||
Modifier::Reflow(id, force_reflow) => {
|
Modifier::Reflow(id, force_reflow) => {
|
||||||
if force_reflow {
|
if force_reflow {
|
||||||
reflown.remove(&id);
|
reflown.remove(&id);
|
||||||
@ -437,16 +440,16 @@ pub fn propagate_modifiers(
|
|||||||
if reflown.contains(id) {
|
if reflown.contains(id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
reflow_shape(id, state, &mut reflown, &mut entries, &mut bounds_temp);
|
reflow_shape(id, state, &mut reflown, &mut entries, &mut bounds_temp)?;
|
||||||
}
|
}
|
||||||
layout_reflows = HashSet::new();
|
layout_reflows = HashSet::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
// #[allow(dead_code)]
|
||||||
modifiers
|
Ok(modifiers
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(key, val)| TransformEntry::from_input(*key, *val))
|
.map(|(key, val)| TransformEntry::from_input(*key, *val))
|
||||||
.collect()
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -494,7 +497,8 @@ mod tests {
|
|||||||
&bounds_after,
|
&bounds_after,
|
||||||
transform,
|
transform,
|
||||||
&HashMap::new(),
|
&HashMap::new(),
|
||||||
);
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(result.len(), 1);
|
assert_eq!(result.len(), 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use crate::error::{Error, Result};
|
||||||
use crate::math::{is_move_only_matrix, Bounds, Matrix};
|
use crate::math::{is_move_only_matrix, Bounds, Matrix};
|
||||||
use crate::shapes::{ConstraintH, ConstraintV};
|
use crate::shapes::{ConstraintH, ConstraintV};
|
||||||
|
|
||||||
@ -105,14 +106,14 @@ pub fn propagate_shape_constraints(
|
|||||||
constraint_v: ConstraintV,
|
constraint_v: ConstraintV,
|
||||||
transform: Matrix,
|
transform: Matrix,
|
||||||
ignore_constrainst: bool,
|
ignore_constrainst: bool,
|
||||||
) -> Matrix {
|
) -> Result<Matrix> {
|
||||||
// if the constrains are scale & scale or the transform has only moves we
|
// if the constrains are scale & scale or the transform has only moves we
|
||||||
// can propagate as is
|
// can propagate as is
|
||||||
if (ignore_constrainst
|
if (ignore_constrainst
|
||||||
|| constraint_h == ConstraintH::Scale && constraint_v == ConstraintV::Scale)
|
|| constraint_h == ConstraintH::Scale && constraint_v == ConstraintV::Scale)
|
||||||
|| is_move_only_matrix(&transform)
|
|| is_move_only_matrix(&transform)
|
||||||
{
|
{
|
||||||
return transform;
|
return Ok(transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut transform = transform;
|
let mut transform = transform;
|
||||||
@ -133,7 +134,9 @@ pub fn propagate_shape_constraints(
|
|||||||
parent_transform.post_translate(center);
|
parent_transform.post_translate(center);
|
||||||
parent_transform.pre_translate(-center);
|
parent_transform.pre_translate(-center);
|
||||||
|
|
||||||
let parent_transform_inv = &parent_transform.invert().unwrap();
|
let parent_transform_inv = &parent_transform.invert().ok_or(Error::CriticalError(
|
||||||
|
"Failed to invert parent transform".to_string(),
|
||||||
|
))?;
|
||||||
let origin = parent_transform_inv.map_point(child_bounds_after.nw);
|
let origin = parent_transform_inv.map_point(child_bounds_after.nw);
|
||||||
|
|
||||||
let mut scale = Matrix::scale((scale_width, scale_height));
|
let mut scale = Matrix::scale((scale_width, scale_height));
|
||||||
@ -160,5 +163,5 @@ pub fn propagate_shape_constraints(
|
|||||||
transform.post_concat(&Matrix::translate(th + tv));
|
transform.post_concat(&Matrix::translate(th + tv));
|
||||||
}
|
}
|
||||||
|
|
||||||
transform
|
Ok(transform)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
use crate::math::{self as math, Bounds, Matrix, Point, Vector, VectorExt};
|
use crate::math::{self as math, Bounds, Matrix, Point, Vector, VectorExt};
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, LayoutData, LayoutItem,
|
AlignContent, AlignItems, AlignSelf, FlexData, JustifyContent, LayoutData, LayoutItem,
|
||||||
@ -588,7 +590,7 @@ pub fn reflow_flex_layout(
|
|||||||
flex_data: &FlexData,
|
flex_data: &FlexData,
|
||||||
shapes: ShapesPoolRef,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &mut HashMap<Uuid, Bounds>,
|
bounds: &mut HashMap<Uuid, Bounds>,
|
||||||
) -> VecDeque<Modifier> {
|
) -> Result<VecDeque<Modifier>> {
|
||||||
let mut result = VecDeque::new();
|
let mut result = VecDeque::new();
|
||||||
let layout_bounds = &bounds.find(shape);
|
let layout_bounds = &bounds.find(shape);
|
||||||
let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
|
let layout_axis = LayoutAxis::new(shape, layout_bounds, layout_data, flex_data);
|
||||||
@ -724,7 +726,9 @@ pub fn reflow_flex_layout(
|
|||||||
|
|
||||||
let parent_transform = layout_bounds.transform_matrix().unwrap_or_default();
|
let parent_transform = layout_bounds.transform_matrix().unwrap_or_default();
|
||||||
|
|
||||||
let parent_transform_inv = &parent_transform.invert().unwrap();
|
let parent_transform_inv = &parent_transform.invert().ok_or(Error::CriticalError(
|
||||||
|
"Failed to invert parent transform".to_string(),
|
||||||
|
))?;
|
||||||
let origin = parent_transform_inv.map_point(layout_bounds.nw);
|
let origin = parent_transform_inv.map_point(layout_bounds.nw);
|
||||||
|
|
||||||
let mut scale = Matrix::scale((scale_width, scale_height));
|
let mut scale = Matrix::scale((scale_width, scale_height));
|
||||||
@ -737,5 +741,5 @@ pub fn reflow_flex_layout(
|
|||||||
result.push_back(Modifier::parent(shape.id, scale));
|
result.push_back(Modifier::parent(shape.id, scale));
|
||||||
bounds.insert(shape.id, layout_bounds_after);
|
bounds.insert(shape.id, layout_bounds_after);
|
||||||
}
|
}
|
||||||
result
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use crate::error::{Error, Result};
|
||||||
use crate::math::{self as math, intersect_rays, Bounds, Matrix, Point, Ray, Vector, VectorExt};
|
use crate::math::{self as math, intersect_rays, Bounds, Matrix, Point, Ray, Vector, VectorExt};
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
AlignContent, AlignItems, AlignSelf, Frame, GridCell, GridData, GridTrack, GridTrackType,
|
AlignContent, AlignItems, AlignSelf, Frame, GridCell, GridData, GridTrack, GridTrackType,
|
||||||
@ -6,6 +7,7 @@ use crate::shapes::{
|
|||||||
};
|
};
|
||||||
use crate::state::ShapesPoolRef;
|
use crate::state::ShapesPoolRef;
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::{HashMap, HashSet, VecDeque};
|
||||||
|
|
||||||
use super::common::GetBounds;
|
use super::common::GetBounds;
|
||||||
@ -704,7 +706,7 @@ pub fn reflow_grid_layout(
|
|||||||
grid_data: &GridData,
|
grid_data: &GridData,
|
||||||
shapes: ShapesPoolRef,
|
shapes: ShapesPoolRef,
|
||||||
bounds: &mut HashMap<Uuid, Bounds>,
|
bounds: &mut HashMap<Uuid, Bounds>,
|
||||||
) -> VecDeque<Modifier> {
|
) -> Result<VecDeque<Modifier>> {
|
||||||
let mut result = VecDeque::new();
|
let mut result = VecDeque::new();
|
||||||
let layout_bounds = bounds.find(shape);
|
let layout_bounds = bounds.find(shape);
|
||||||
let children: HashSet<Uuid> = shape.children_ids_iter(true).copied().collect();
|
let children: HashSet<Uuid> = shape.children_ids_iter(true).copied().collect();
|
||||||
@ -825,7 +827,9 @@ pub fn reflow_grid_layout(
|
|||||||
|
|
||||||
let parent_transform = layout_bounds.transform_matrix().unwrap_or_default();
|
let parent_transform = layout_bounds.transform_matrix().unwrap_or_default();
|
||||||
|
|
||||||
let parent_transform_inv = &parent_transform.invert().unwrap();
|
let parent_transform_inv = &parent_transform.invert().ok_or(Error::CriticalError(
|
||||||
|
"Failed to invert parent transform".to_string(),
|
||||||
|
))?;
|
||||||
let origin = parent_transform_inv.map_point(layout_bounds.nw);
|
let origin = parent_transform_inv.map_point(layout_bounds.nw);
|
||||||
|
|
||||||
let mut scale = Matrix::scale((scale_width, scale_height));
|
let mut scale = Matrix::scale((scale_width, scale_height));
|
||||||
@ -839,5 +843,5 @@ pub fn reflow_grid_layout(
|
|||||||
bounds.insert(shape.id, layout_bounds_after);
|
bounds.insert(shape.id, layout_bounds_after);
|
||||||
}
|
}
|
||||||
|
|
||||||
result
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ mod text_editor;
|
|||||||
pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef};
|
pub use shapes_pool::{ShapesPool, ShapesPoolMutRef, ShapesPoolRef};
|
||||||
pub use text_editor::*;
|
pub use text_editor::*;
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
use crate::render::RenderState;
|
use crate::render::RenderState;
|
||||||
use crate::shapes::Shape;
|
use crate::shapes::Shape;
|
||||||
use crate::tiles;
|
use crate::tiles;
|
||||||
@ -28,41 +29,44 @@ pub(crate) struct State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn new(width: i32, height: i32) -> Self {
|
pub fn try_new(width: i32, height: i32) -> Result<Self> {
|
||||||
State {
|
Ok(State {
|
||||||
render_state: RenderState::new(width, height),
|
render_state: RenderState::try_new(width, height)?,
|
||||||
text_editor_state: TextEditorState::new(),
|
text_editor_state: TextEditorState::new(),
|
||||||
current_id: None,
|
current_id: None,
|
||||||
current_browser: 0,
|
current_browser: 0,
|
||||||
shapes: ShapesPool::new(),
|
shapes: ShapesPool::new(),
|
||||||
// TODO: Maybe this can be moved to a different object
|
// TODO: Maybe this can be moved to a different object
|
||||||
saved_shapes: None,
|
saved_shapes: None,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a new temporary shapes pool.
|
// Creates a new temporary shapes pool.
|
||||||
// Will panic if a previous temporary pool exists.
|
// Will panic if a previous temporary pool exists.
|
||||||
pub fn start_temp_objects(mut self) -> Self {
|
pub fn start_temp_objects(mut self) -> Result<Self> {
|
||||||
if self.saved_shapes.is_some() {
|
if self.saved_shapes.is_some() {
|
||||||
panic!("Tried to start a temp objects while the previous have not been restored");
|
return Err(Error::CriticalError(
|
||||||
|
"Tried to start a temp objects while the previous have not been restored"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
self.saved_shapes = Some(self.shapes);
|
self.saved_shapes = Some(self.shapes);
|
||||||
self.shapes = ShapesPool::new();
|
self.shapes = ShapesPool::new();
|
||||||
self
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disposes of the temporary shapes pool restoring the normal pool
|
// Disposes of the temporary shapes pool restoring the normal pool
|
||||||
// Will panic if a there is no temporary pool.
|
// Will panic if a there is no temporary pool.
|
||||||
pub fn end_temp_objects(mut self) -> Self {
|
pub fn end_temp_objects(mut self) -> Result<Self> {
|
||||||
self.shapes = self
|
self.shapes = self.saved_shapes.ok_or(Error::CriticalError(
|
||||||
.saved_shapes
|
"Tried to end temp objects but not content to be restored is present".to_string(),
|
||||||
.expect("Tried to end temp objects but not content to be restored is present");
|
))?;
|
||||||
self.saved_shapes = None;
|
self.saved_shapes = None;
|
||||||
self
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn resize(&mut self, width: i32, height: i32) {
|
pub fn resize(&mut self, width: i32, height: i32) -> Result<()> {
|
||||||
self.render_state.resize(width, height);
|
self.render_state.resize(width, height)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_state_mut(&mut self) -> &mut RenderState {
|
pub fn render_state_mut(&mut self) -> &mut RenderState {
|
||||||
@ -87,19 +91,17 @@ impl State {
|
|||||||
self.render_state.render_from_cache(&self.shapes);
|
self.render_state.render_from_cache(&self.shapes);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_sync(&mut self, timestamp: i32) -> Result<(), String> {
|
pub fn render_sync(&mut self, timestamp: i32) -> Result<()> {
|
||||||
self.render_state
|
self.render_state
|
||||||
.start_render_loop(None, &self.shapes, timestamp, true)?;
|
.start_render_loop(None, &self.shapes, timestamp, true)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_sync_shape(&mut self, id: &Uuid, timestamp: i32) -> Result<(), String> {
|
pub fn render_sync_shape(&mut self, id: &Uuid, timestamp: i32) -> Result<()> {
|
||||||
self.render_state
|
self.render_state
|
||||||
.start_render_loop(Some(id), &self.shapes, timestamp, true)?;
|
.start_render_loop(Some(id), &self.shapes, timestamp, true)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<(), String> {
|
pub fn start_render_loop(&mut self, timestamp: i32) -> Result<()> {
|
||||||
// If zoom changed, we MUST rebuild the tile index before using it.
|
// If zoom changed, we MUST rebuild the tile index before using it.
|
||||||
// Otherwise, the index will have tiles from the old zoom level, causing visible
|
// Otherwise, the index will have tiles from the old zoom level, causing visible
|
||||||
// tiles to appear empty. This can happen if start_render_loop() is called before
|
// tiles to appear empty. This can happen if start_render_loop() is called before
|
||||||
@ -111,14 +113,12 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
self.render_state
|
self.render_state
|
||||||
.start_render_loop(None, &self.shapes, timestamp, false)?;
|
.start_render_loop(None, &self.shapes, timestamp, false)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<(), String> {
|
pub fn process_animation_frame(&mut self, timestamp: i32) -> Result<()> {
|
||||||
self.render_state
|
self.render_state
|
||||||
.process_animation_frame(None, &self.shapes, timestamp)?;
|
.process_animation_frame(None, &self.shapes, timestamp)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn clear_focus_mode(&mut self) {
|
pub fn clear_focus_mode(&mut self) {
|
||||||
@ -227,10 +227,10 @@ impl State {
|
|||||||
let _ = self.render_state.render_preview(&self.shapes, timestamp);
|
let _ = self.render_state.render_preview(&self.shapes, timestamp);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn rebuild_modifier_tiles(&mut self, ids: Vec<Uuid>) {
|
pub fn rebuild_modifier_tiles(&mut self, ids: Vec<Uuid>) -> Result<()> {
|
||||||
// Index-based storage is safe
|
// Index-based storage is safe
|
||||||
self.render_state
|
self.render_state
|
||||||
.rebuild_modifier_tiles(&mut self.shapes, ids);
|
.rebuild_modifier_tiles(&mut self.shapes, ids)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn font_collection(&self) -> &FontCollection {
|
pub fn font_collection(&self) -> &FontCollection {
|
||||||
|
|||||||
@ -3,7 +3,6 @@ use crate::uuid::Uuid;
|
|||||||
use crate::view::Viewbox;
|
use crate::view::Viewbox;
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
#[derive(PartialEq, Eq, Hash, Clone, Copy, Debug)]
|
||||||
pub struct Tile(pub i32, pub i32);
|
pub struct Tile(pub i32, pub i32);
|
||||||
|
|
||||||
@ -178,13 +177,10 @@ impl TileHashMap {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_shape_at(&mut self, tile: Tile, shape_id: Uuid) {
|
pub fn add_shape_at(&mut self, tile: Tile, shape_id: Uuid) {
|
||||||
self.grid.entry(tile).or_default();
|
let tile_set = self.grid.entry(tile).or_default();
|
||||||
self.index.entry(shape_id).or_default();
|
|
||||||
|
|
||||||
let tile_set = self.grid.get_mut(&tile).unwrap();
|
|
||||||
tile_set.insert(shape_id);
|
tile_set.insert(shape_id);
|
||||||
|
|
||||||
let index_set = self.index.get_mut(&shape_id).unwrap();
|
let index_set = self.index.entry(shape_id).or_default();
|
||||||
index_set.insert(tile);
|
index_set.insert(tile);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
|
use crate::error::{Error, Result};
|
||||||
use crate::mem;
|
use crate::mem;
|
||||||
use macros::wasm_error;
|
|
||||||
// use crate::mem::SerializableResult;
|
|
||||||
use crate::error::Error;
|
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use crate::with_state_mut;
|
use crate::with_state_mut;
|
||||||
use crate::STATE;
|
use crate::STATE;
|
||||||
use crate::{shapes::ImageFill, utils::uuid_from_u32_quartet};
|
use crate::{shapes::ImageFill, utils::uuid_from_u32_quartet};
|
||||||
|
use macros::wasm_error;
|
||||||
|
|
||||||
const FLAG_KEEP_ASPECT_RATIO: u8 = 1 << 0;
|
const FLAG_KEEP_ASPECT_RATIO: u8 = 1 << 0;
|
||||||
const IMAGE_IDS_SIZE: usize = 32;
|
const IMAGE_IDS_SIZE: usize = 32;
|
||||||
@ -50,6 +49,7 @@ pub struct ShapeImageIds {
|
|||||||
|
|
||||||
impl From<[u8; IMAGE_IDS_SIZE]> for ShapeImageIds {
|
impl From<[u8; IMAGE_IDS_SIZE]> for ShapeImageIds {
|
||||||
fn from(bytes: [u8; IMAGE_IDS_SIZE]) -> Self {
|
fn from(bytes: [u8; IMAGE_IDS_SIZE]) -> Self {
|
||||||
|
// FIXME: this should probably be a try_from instead
|
||||||
let shape_id = Uuid::try_from(&bytes[0..16]).unwrap();
|
let shape_id = Uuid::try_from(&bytes[0..16]).unwrap();
|
||||||
let image_id = Uuid::try_from(&bytes[16..32]).unwrap();
|
let image_id = Uuid::try_from(&bytes[16..32]).unwrap();
|
||||||
ShapeImageIds { shape_id, image_id }
|
ShapeImageIds { shape_id, image_id }
|
||||||
@ -57,9 +57,9 @@ impl From<[u8; IMAGE_IDS_SIZE]> for ShapeImageIds {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<Vec<u8>> for ShapeImageIds {
|
impl TryFrom<Vec<u8>> for ShapeImageIds {
|
||||||
type Error = &'static str;
|
type Error = Error;
|
||||||
|
|
||||||
fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
|
fn try_from(value: Vec<u8>) -> Result<Self> {
|
||||||
let mut arr = [0u8; IMAGE_IDS_SIZE];
|
let mut arr = [0u8; IMAGE_IDS_SIZE];
|
||||||
arr.copy_from_slice(&value);
|
arr.copy_from_slice(&value);
|
||||||
Ok(ShapeImageIds::from(arr))
|
Ok(ShapeImageIds::from(arr))
|
||||||
@ -68,13 +68,16 @@ impl TryFrom<Vec<u8>> for ShapeImageIds {
|
|||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn store_image() -> crate::error::Result<()> {
|
pub extern "C" fn store_image() -> Result<()> {
|
||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
let ids = ShapeImageIds::try_from(bytes[0..IMAGE_IDS_SIZE].to_vec()).unwrap();
|
let ids = ShapeImageIds::try_from(bytes[0..IMAGE_IDS_SIZE].to_vec())?;
|
||||||
|
|
||||||
// Read is_thumbnail flag (4 bytes as u32)
|
// Read is_thumbnail flag (4 bytes as u32)
|
||||||
let is_thumbnail_bytes = &bytes[IMAGE_IDS_SIZE..IMAGE_HEADER_SIZE];
|
let is_thumbnail_bytes = &bytes[IMAGE_IDS_SIZE..IMAGE_HEADER_SIZE];
|
||||||
let is_thumbnail_value = u32::from_le_bytes(is_thumbnail_bytes.try_into().unwrap());
|
let is_thumbnail_value =
|
||||||
|
u32::from_le_bytes(is_thumbnail_bytes.try_into().map_err(|_| {
|
||||||
|
Error::CriticalError("Invalid bytes for is_thumbnail flag".to_string())
|
||||||
|
})?);
|
||||||
let is_thumbnail = is_thumbnail_value != 0;
|
let is_thumbnail = is_thumbnail_value != 0;
|
||||||
|
|
||||||
let image_bytes = &bytes[IMAGE_HEADER_SIZE..];
|
let image_bytes = &bytes[IMAGE_HEADER_SIZE..];
|
||||||
@ -104,9 +107,10 @@ pub extern "C" fn store_image() -> crate::error::Result<()> {
|
|||||||
/// - bytes 44-47: height (i32)
|
/// - bytes 44-47: height (i32)
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn store_image_from_texture() -> crate::error::Result<()> {
|
pub extern "C" fn store_image_from_texture() -> Result<()> {
|
||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
|
|
||||||
|
// FIXME: where does this 48 come from?
|
||||||
if bytes.len() < 48 {
|
if bytes.len() < 48 {
|
||||||
// FIXME: Review if this should be an critical or a recoverable error.
|
// FIXME: Review if this should be an critical or a recoverable error.
|
||||||
eprintln!("store_image_from_texture: insufficient data");
|
eprintln!("store_image_from_texture: insufficient data");
|
||||||
@ -116,23 +120,41 @@ pub extern "C" fn store_image_from_texture() -> crate::error::Result<()> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let ids = ShapeImageIds::try_from(bytes[0..IMAGE_IDS_SIZE].to_vec()).unwrap();
|
let ids = ShapeImageIds::try_from(bytes[0..IMAGE_IDS_SIZE].to_vec())
|
||||||
|
.map_err(|_| Error::CriticalError("Invalid image ids".to_string()))?;
|
||||||
|
|
||||||
|
// FIXME: read bytes in a safe way
|
||||||
|
|
||||||
// Read is_thumbnail flag (4 bytes as u32)
|
// Read is_thumbnail flag (4 bytes as u32)
|
||||||
let is_thumbnail_bytes = &bytes[IMAGE_IDS_SIZE..IMAGE_HEADER_SIZE];
|
let is_thumbnail_bytes = &bytes[IMAGE_IDS_SIZE..IMAGE_HEADER_SIZE];
|
||||||
let is_thumbnail_value = u32::from_le_bytes(is_thumbnail_bytes.try_into().unwrap());
|
let is_thumbnail_value =
|
||||||
|
u32::from_le_bytes(is_thumbnail_bytes.try_into().map_err(|_| {
|
||||||
|
Error::CriticalError("Invalid bytes for is_thumbnail flag".to_string())
|
||||||
|
})?);
|
||||||
let is_thumbnail = is_thumbnail_value != 0;
|
let is_thumbnail = is_thumbnail_value != 0;
|
||||||
|
|
||||||
// Read GL texture ID (4 bytes as u32)
|
// Read GL texture ID (4 bytes as u32)
|
||||||
let texture_id_bytes = &bytes[36..40];
|
let texture_id_bytes = &bytes[36..40];
|
||||||
let texture_id = u32::from_le_bytes(texture_id_bytes.try_into().unwrap());
|
let texture_id = u32::from_le_bytes(
|
||||||
|
texture_id_bytes
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::CriticalError("Invalid bytes for texture id".to_string()))?,
|
||||||
|
);
|
||||||
|
|
||||||
// Read width and height (8 bytes as two i32s)
|
// Read width and height (8 bytes as two i32s)
|
||||||
let width_bytes = &bytes[40..44];
|
let width_bytes = &bytes[40..44];
|
||||||
let width = i32::from_le_bytes(width_bytes.try_into().unwrap());
|
let width = i32::from_le_bytes(
|
||||||
|
width_bytes
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::CriticalError("Invalid bytes for width".to_string()))?,
|
||||||
|
);
|
||||||
|
|
||||||
let height_bytes = &bytes[44..48];
|
let height_bytes = &bytes[44..48];
|
||||||
let height = i32::from_le_bytes(height_bytes.try_into().unwrap());
|
let height = i32::from_le_bytes(
|
||||||
|
height_bytes
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::CriticalError("Invalid bytes for height".to_string()))?,
|
||||||
|
);
|
||||||
|
|
||||||
with_state_mut!(state, {
|
with_state_mut!(state, {
|
||||||
if let Err(msg) = state.render_state_mut().add_image_from_gl_texture(
|
if let Err(msg) = state.render_state_mut().add_image_from_gl_texture(
|
||||||
@ -142,6 +164,7 @@ pub extern "C" fn store_image_from_texture() -> crate::error::Result<()> {
|
|||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
) {
|
) {
|
||||||
|
// FIXME: Review if we should return a RecoverableError
|
||||||
eprintln!("store_image_from_texture error: {}", msg);
|
eprintln!("store_image_from_texture error: {}", msg);
|
||||||
}
|
}
|
||||||
state.touch_shape(ids.shape_id);
|
state.touch_shape(ids.shape_id);
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use crate::{uuid_from_u32_quartet, with_current_shape_mut, with_state, with_stat
|
|||||||
use super::align;
|
use super::align;
|
||||||
|
|
||||||
#[allow(unused_imports)]
|
#[allow(unused_imports)]
|
||||||
use crate::error::Result;
|
use crate::error::{Error, Result};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[repr(C, align(1))]
|
#[repr(C, align(1))]
|
||||||
@ -177,9 +177,13 @@ pub extern "C" fn set_grid_columns() -> Result<()> {
|
|||||||
|
|
||||||
let entries: Vec<GridTrack> = bytes
|
let entries: Vec<GridTrack> = bytes
|
||||||
.chunks(size_of::<RawGridTrack>())
|
.chunks(size_of::<RawGridTrack>())
|
||||||
.map(|data| data.try_into().unwrap())
|
.map(|data| {
|
||||||
.map(|data: [u8; size_of::<RawGridTrack>()]| RawGridTrack::from(data).into())
|
let track_bytes: [u8; size_of::<RawGridTrack>()] = data
|
||||||
.collect();
|
.try_into()
|
||||||
|
.map_err(|_| Error::CriticalError("Invalid bytes for grid track".to_string()))?;
|
||||||
|
Ok(RawGridTrack::from(track_bytes).into())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
shape.set_grid_columns(entries);
|
shape.set_grid_columns(entries);
|
||||||
@ -196,9 +200,13 @@ pub extern "C" fn set_grid_rows() -> Result<()> {
|
|||||||
|
|
||||||
let entries: Vec<GridTrack> = bytes
|
let entries: Vec<GridTrack> = bytes
|
||||||
.chunks(size_of::<RawGridTrack>())
|
.chunks(size_of::<RawGridTrack>())
|
||||||
.map(|data| data.try_into().unwrap())
|
.map(|data| {
|
||||||
.map(|data: [u8; size_of::<RawGridTrack>()]| RawGridTrack::from(data).into())
|
let track_bytes: [u8; size_of::<RawGridTrack>()] = data
|
||||||
.collect();
|
.try_into()
|
||||||
|
.map_err(|_| Error::CriticalError("Invalid bytes for grid track".to_string()))?;
|
||||||
|
Ok(RawGridTrack::from(track_bytes).into())
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
shape.set_grid_rows(entries);
|
shape.set_grid_rows(entries);
|
||||||
@ -215,9 +223,13 @@ pub extern "C" fn set_grid_cells() -> Result<()> {
|
|||||||
|
|
||||||
let cells: Vec<RawGridCell> = bytes
|
let cells: Vec<RawGridCell> = bytes
|
||||||
.chunks(size_of::<RawGridCell>())
|
.chunks(size_of::<RawGridCell>())
|
||||||
.map(|data| data.try_into().expect("Invalid grid cell data"))
|
.map(|data| {
|
||||||
.map(|data: [u8; size_of::<RawGridCell>()]| RawGridCell::from(data))
|
let cell_bytes: [u8; size_of::<RawGridCell>()] = data
|
||||||
.collect();
|
.try_into()
|
||||||
|
.map_err(|_| Error::CriticalError("Invalid bytes for grid cell".to_string()))?;
|
||||||
|
Ok(RawGridCell::from(cell_bytes))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
shape.set_grid_cells(cells.into_iter().map(|raw| raw.into()).collect());
|
shape.set_grid_cells(cells.into_iter().map(|raw| raw.into()).collect());
|
||||||
|
|||||||
@ -4,6 +4,7 @@ use mem::SerializableResult;
|
|||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::sync::{Mutex, OnceLock};
|
use std::sync::{Mutex, OnceLock};
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
use crate::shapes::{Path, Segment, ToPath};
|
use crate::shapes::{Path, Segment, ToPath};
|
||||||
use crate::{mem, with_current_shape, with_current_shape_mut, STATE};
|
use crate::{mem, with_current_shape, with_current_shape_mut, STATE};
|
||||||
|
|
||||||
@ -41,12 +42,12 @@ impl From<[u8; size_of::<RawSegmentData>()]> for RawSegmentData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for RawSegmentData {
|
impl TryFrom<&[u8]> for RawSegmentData {
|
||||||
type Error = String;
|
type Error = Error;
|
||||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
fn try_from(bytes: &[u8]) -> Result<Self> {
|
||||||
let data: [u8; RAW_SEGMENT_DATA_SIZE] = bytes
|
let data: [u8; RAW_SEGMENT_DATA_SIZE] = bytes
|
||||||
.get(0..RAW_SEGMENT_DATA_SIZE)
|
.get(0..RAW_SEGMENT_DATA_SIZE)
|
||||||
.and_then(|slice| slice.try_into().ok())
|
.and_then(|slice| slice.try_into().ok())
|
||||||
.ok_or("Invalid path data".to_string())?;
|
.ok_or(Error::CriticalError("Invalid path data".to_string()))?;
|
||||||
Ok(RawSegmentData::from(data))
|
Ok(RawSegmentData::from(data))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -154,10 +155,14 @@ fn get_path_upload_buffer() -> &'static Mutex<Vec<u8>> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn start_shape_path_buffer() {
|
#[wasm_error]
|
||||||
|
pub extern "C" fn start_shape_path_buffer() -> Result<()> {
|
||||||
let buffer = get_path_upload_buffer();
|
let buffer = get_path_upload_buffer();
|
||||||
let mut buffer = buffer.lock().unwrap();
|
let mut buffer = buffer
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| Error::CriticalError("Failed to lock path buffer".to_string()))?;
|
||||||
buffer.clear();
|
buffer.clear();
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
@ -165,32 +170,40 @@ pub extern "C" fn start_shape_path_buffer() {
|
|||||||
pub extern "C" fn set_shape_path_chunk_buffer() -> Result<()> {
|
pub extern "C" fn set_shape_path_chunk_buffer() -> Result<()> {
|
||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
let buffer = get_path_upload_buffer();
|
let buffer = get_path_upload_buffer();
|
||||||
let mut buffer = buffer.lock().unwrap();
|
let mut buffer = buffer
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| Error::CriticalError("Failed to lock path buffer".to_string()))?;
|
||||||
buffer.extend_from_slice(&bytes);
|
buffer.extend_from_slice(&bytes);
|
||||||
mem::free_bytes()?;
|
mem::free_bytes()?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn set_shape_path_buffer() {
|
#[wasm_error]
|
||||||
|
pub extern "C" fn set_shape_path_buffer() -> Result<()> {
|
||||||
|
let buffer = get_path_upload_buffer();
|
||||||
|
let mut buffer = buffer
|
||||||
|
.lock()
|
||||||
|
.map_err(|_| Error::CriticalError("Failed to lock path buffer".to_string()))?;
|
||||||
|
let chunk_size = size_of::<RawSegmentData>();
|
||||||
|
if !buffer.len().is_multiple_of(chunk_size) {
|
||||||
|
// FIXME
|
||||||
|
println!("Warning: buffer length is not a multiple of chunk size!");
|
||||||
|
}
|
||||||
|
let mut segments = Vec::new();
|
||||||
|
for (i, chunk) in buffer.chunks(chunk_size).enumerate() {
|
||||||
|
match RawSegmentData::try_from(chunk) {
|
||||||
|
Ok(seg) => segments.push(Segment::from(seg)),
|
||||||
|
Err(e) => println!("Error at segment {}: {}", i, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
let buffer = get_path_upload_buffer();
|
|
||||||
let mut buffer = buffer.lock().unwrap();
|
|
||||||
let chunk_size = size_of::<RawSegmentData>();
|
|
||||||
if !buffer.len().is_multiple_of(chunk_size) {
|
|
||||||
// FIXME
|
|
||||||
println!("Warning: buffer length is not a multiple of chunk size!");
|
|
||||||
}
|
|
||||||
let mut segments = Vec::new();
|
|
||||||
for (i, chunk) in buffer.chunks(chunk_size).enumerate() {
|
|
||||||
match RawSegmentData::try_from(chunk) {
|
|
||||||
Ok(seg) => segments.push(Segment::from(seg)),
|
|
||||||
Err(e) => println!("Error at segment {}: {}", i, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shape.set_path_segments(segments);
|
shape.set_path_segments(segments);
|
||||||
buffer.clear();
|
|
||||||
});
|
});
|
||||||
|
buffer.clear();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
|
|||||||
@ -6,6 +6,10 @@ use crate::wasm::blend::RawBlendMode;
|
|||||||
use crate::wasm::layouts::constraints::{RawConstraintH, RawConstraintV};
|
use crate::wasm::layouts::constraints::{RawConstraintH, RawConstraintV};
|
||||||
use crate::{with_state_mut, STATE};
|
use crate::{with_state_mut, STATE};
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
use macros::wasm_error;
|
||||||
|
|
||||||
use super::RawShapeType;
|
use super::RawShapeType;
|
||||||
|
|
||||||
const FLAG_CLIP_CONTENT: u8 = 0b0000_0001;
|
const FLAG_CLIP_CONTENT: u8 = 0b0000_0001;
|
||||||
@ -106,14 +110,18 @@ impl From<[u8; RAW_BASE_PROPS_SIZE]> for RawBasePropsData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn set_shape_base_props() {
|
#[wasm_error]
|
||||||
|
pub extern "C" fn set_shape_base_props() -> Result<()> {
|
||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
|
|
||||||
if bytes.len() < RAW_BASE_PROPS_SIZE {
|
if bytes.len() < RAW_BASE_PROPS_SIZE {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let data: [u8; RAW_BASE_PROPS_SIZE] = bytes[..RAW_BASE_PROPS_SIZE].try_into().unwrap();
|
// FIXME: this should just be a try_from
|
||||||
|
let data: [u8; RAW_BASE_PROPS_SIZE] = bytes[..RAW_BASE_PROPS_SIZE]
|
||||||
|
.try_into()
|
||||||
|
.map_err(|_| Error::CriticalError("Invalid bytes for base props".to_string()))?;
|
||||||
let raw = RawBasePropsData::from(data);
|
let raw = RawBasePropsData::from(data);
|
||||||
|
|
||||||
let id = raw.id();
|
let id = raw.id();
|
||||||
@ -151,6 +159,7 @@ pub extern "C" fn set_shape_base_props() {
|
|||||||
shape.set_corners((raw.corner_r1, raw.corner_r2, raw.corner_r3, raw.corner_r4));
|
shape.set_corners((raw.corner_r1, raw.corner_r2, raw.corner_r3, raw.corner_r4));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -292,9 +292,10 @@ pub extern "C" fn clear_shape_text() {
|
|||||||
#[wasm_error]
|
#[wasm_error]
|
||||||
pub extern "C" fn set_shape_text_content() -> crate::error::Result<()> {
|
pub extern "C" fn set_shape_text_content() -> crate::error::Result<()> {
|
||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
let raw_text_data = RawParagraph::try_from(&bytes)
|
||||||
let raw_text_data = RawParagraph::try_from(&bytes).unwrap();
|
.map_err(|_| Error::CriticalError("Invalid text data".to_string()))?;
|
||||||
|
|
||||||
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
shape.add_paragraph(raw_text_data.into()).map_err(|_| {
|
shape.add_paragraph(raw_text_data.into()).map_err(|_| {
|
||||||
Error::RecoverableError(format!(
|
Error::RecoverableError(format!(
|
||||||
"Error with set_shape_text_content on {:?}",
|
"Error with set_shape_text_content on {:?}",
|
||||||
|
|||||||
@ -1,4 +1,6 @@
|
|||||||
use macros::ToJs;
|
#[allow(unused_imports)]
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
use macros::{wasm_error, ToJs};
|
||||||
|
|
||||||
use skia_safe as skia;
|
use skia_safe as skia;
|
||||||
|
|
||||||
@ -39,11 +41,11 @@ impl From<[u8; RAW_TRANSFORM_ENTRY_SIZE]> for RawTransformEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&[u8]> for RawTransformEntry {
|
impl TryFrom<&[u8]> for RawTransformEntry {
|
||||||
type Error = String;
|
type Error = Error;
|
||||||
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
|
fn try_from(bytes: &[u8]) -> Result<Self> {
|
||||||
let bytes: [u8; RAW_TRANSFORM_ENTRY_SIZE] = bytes
|
let bytes: [u8; RAW_TRANSFORM_ENTRY_SIZE] = bytes
|
||||||
.try_into()
|
.try_into()
|
||||||
.map_err(|_| "Invalid transform entry bytes".to_string())?;
|
.map_err(|_| Error::CriticalError("Invalid transform entry bytes".to_string()))?;
|
||||||
Ok(RawTransformEntry::from(bytes))
|
Ok(RawTransformEntry::from(bytes))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,16 +75,17 @@ impl From<RawTransformEntry> for TransformEntry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn propagate_modifiers(pixel_precision: bool) -> *mut u8 {
|
#[wasm_error]
|
||||||
|
pub extern "C" fn propagate_modifiers(pixel_precision: bool) -> Result<*mut u8> {
|
||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
|
|
||||||
let entries: Vec<TransformEntry> = bytes
|
let entries: Vec<TransformEntry> = bytes
|
||||||
.chunks(RAW_TRANSFORM_ENTRY_SIZE)
|
.chunks(RAW_TRANSFORM_ENTRY_SIZE)
|
||||||
.map(|data| RawTransformEntry::try_from(data).unwrap().into())
|
.map(|data| RawTransformEntry::try_from(data).map(|entry| entry.into()))
|
||||||
.collect();
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
with_state!(state, {
|
with_state!(state, {
|
||||||
let result = shapes::propagate_modifiers(state, &entries, pixel_precision);
|
let result = shapes::propagate_modifiers(state, &entries, pixel_precision)?;
|
||||||
mem::write_vec(result)
|
Ok(mem::write_vec(result))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user