diff --git a/Cargo.lock b/Cargo.lock index 8be9926..c9a81f3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7866,3 +7866,15 @@ dependencies = [ "syn 2.0.114", "winnow 0.7.14", ] + +[[patch.unused]] +name = "cosmic-config" +version = "1.0.0" + +[[patch.unused]] +name = "cosmic-theme" +version = "1.0.0" + +[[patch.unused]] +name = "libcosmic-yoda" +version = "0.1.0-yoda.2" diff --git a/Cargo.toml b/Cargo.toml index aaaf56c..c81d358 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -81,7 +81,7 @@ features = [ ] # Uncomment to test a locally-cloned libcosmic -# [patch.'https://github.com/pop-os/libcosmic'] -# libcosmic = { path = "../libcosmic" } -# cosmic-config = { path = "../libcosmic/cosmic-config" } -# cosmic-theme = { path = "../libcosmic/cosmic-theme" } +[patch.'https://github.com/pop-os/libcosmic'] +libcosmic-yoda = { path = "../libcosmic" } +cosmic-config = { path = "../libcosmic/cosmic-config" } +cosmic-theme = { path = "../libcosmic/cosmic-theme" } diff --git a/src/domain/document/types/portable.rs b/src/domain/document/types/portable.rs index 45317e4..4d41b5b 100644 --- a/src/domain/document/types/portable.rs +++ b/src/domain/document/types/portable.rs @@ -3,17 +3,16 @@ // // Portable documents (PDF) with poppler backend. -use std::io::Cursor; use std::path::{Path, PathBuf}; /// PDF page render quality multiplier (2.0 = double resolution for sharp display). -const PDF_RENDER_QUALITY: f64 = 2.0; +const PDF_RENDER_QUALITY: f64 = 3.0; /// PDF thumbnail size multiplier (0.25 = 25% for fast preview generation). const PDF_THUMBNAIL_SIZE: f64 = 0.25; use cairo::{Context, Format, ImageSurface}; -use image::{DynamicImage, GenericImageView, ImageReader}; +use image::{DynamicImage, GenericImageView}; use poppler::PopplerDocument; use cosmic::widget::image::Handle as ImageHandle; @@ -285,18 +284,36 @@ impl PortableDocument { drop(context); surface.flush(); - let mut png_data: Vec = Vec::new(); - surface - .write_to_png(&mut png_data) - .map_err(|e| anyhow::anyhow!("Failed to write PNG: {e}"))?; + // Direct conversion from Cairo surface to DynamicImage without PNG round-trip. + // This preserves exact pixel data and avoids ARgb32 → PNG → RGBA conversion artifacts. + let width = surface.width() as u32; + let height = surface.height() as u32; + let stride = surface.stride(); - let image = ImageReader::new(Cursor::new(png_data)) - .with_guessed_format() - .map_err(|e| anyhow::anyhow!("Failed to read PNG format: {e}"))? - .decode() - .map_err(|e| anyhow::anyhow!("Failed to decode PNG: {e}"))?; + let data = surface + .take_data() + .map_err(|e| anyhow::anyhow!("Failed to take Cairo surface data: {e}"))?; - Ok(image) + // ARgb32 in Cairo is 4 bytes per pixel (A, R, G, B) with stride padding. + // Convert to standard RGBA by copying pixel-by-pixel, skipping stride padding. + let mut rgba_bytes = Vec::with_capacity((width * 4) as usize); + for y in 0..height { + let row_offset = (y as i32 * stride) as usize; + for x in 0..width { + let col_offset = (x as i32 * 4) as usize; + let idx = row_offset + col_offset; + // Cairo ARgb32: bytes are [A, R, G, B] + let a = data[idx]; + let r = data[idx + 1]; + let g = data[idx + 2]; + let b = data[idx + 3]; + // Output standard RGBA: [R, G, B, A] + rgba_bytes.extend_from_slice(&[r, g, b, a]); + } + } + + Ok(DynamicImage::ImageRgba8(image::RgbaImage::from_raw(width, height, rgba_bytes) + .expect("Failed to create RgbaImage from Cairo surface data"))) } /// Re-render the current page with current transform. diff --git a/src/ui/views/canvas.rs b/src/ui/views/canvas.rs index b140374..68d4729 100644 --- a/src/ui/views/canvas.rs +++ b/src/ui/views/canvas.rs @@ -52,7 +52,7 @@ pub fn view<'a>( .width(Length::Fill) .height(Length::Fill) .content_fit(content_fit) - .filter_method(FilterMethod::Nearest) + .filter_method(FilterMethod::Linear) .min_scale(config.min_scale) .max_scale(config.max_scale) .scale_step(config.scale_step - 1.0)