librsvg source for verification 2026-05-22
This commit is contained in:
1
librsvg-c/COPYING.LIB
Symbolic link
1
librsvg-c/COPYING.LIB
Symbolic link
@@ -0,0 +1 @@
|
||||
../COPYING.LIB
|
||||
39
librsvg-c/Cargo.toml
Normal file
39
librsvg-c/Cargo.toml
Normal file
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "librsvg-c"
|
||||
version.workspace = true
|
||||
authors.workspace = true
|
||||
description.workspace = true
|
||||
license.workspace = true
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
cairo-rs = { workspace = true, features = ["v1_16", "png", "pdf", "ps", "svg"] }
|
||||
cast.workspace = true
|
||||
float-cmp.workspace = true
|
||||
gdk-pixbuf = { workspace = true, optional = true }
|
||||
gio.workspace = true
|
||||
glib.workspace = true
|
||||
libc.workspace = true
|
||||
librsvg = { workspace = true, features = ["capi"] }
|
||||
rgb = { workspace = true, features = ["argb"] }
|
||||
url.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
regex.workspace = true
|
||||
|
||||
[features]
|
||||
avif = ["librsvg/avif"]
|
||||
capi = ["librsvg/capi"]
|
||||
pixbuf = ["dep:gdk-pixbuf"]
|
||||
|
||||
[package.metadata.capi]
|
||||
min_version = "0.10.10"
|
||||
|
||||
[package.metadata.capi.library]
|
||||
name = "rsvg_2"
|
||||
|
||||
[package.metadata.capi.header]
|
||||
enabled = false
|
||||
88
librsvg-c/build.rs
Normal file
88
librsvg-c/build.rs
Normal file
@@ -0,0 +1,88 @@
|
||||
use regex::Regex;
|
||||
use std::env;
|
||||
use std::fs::File;
|
||||
use std::io::{BufRead, BufReader, Result, Write};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let version = read_version().expect("could not parse version from meson.build");
|
||||
write_version_rs(&version);
|
||||
}
|
||||
|
||||
struct Version {
|
||||
major: String,
|
||||
minor: String,
|
||||
micro: String,
|
||||
}
|
||||
|
||||
fn read_version() -> Result<Version> {
|
||||
{
|
||||
let output =
|
||||
Path::new(&env::var("CARGO_MANIFEST_DIR").expect("Manifest directory unknown"))
|
||||
.parent()
|
||||
.expect("Invalid manifest path")
|
||||
.join("meson.build");
|
||||
let file = File::open(output)?;
|
||||
|
||||
// This function reads one of the build configuration files (meson.build) and scans
|
||||
// it for the package's version number.
|
||||
//
|
||||
// The start of meson.build should contain this:
|
||||
//
|
||||
// project('librsvg',
|
||||
// 'rust',
|
||||
// 'c',
|
||||
// version: '2.53.0',
|
||||
// meson_version: '>= 0.59')
|
||||
//
|
||||
// This regex looks for the "version" line.
|
||||
|
||||
let regex = Regex::new(r#"^\s+version: '(\d+\.\d+\.\d+)'"#).unwrap();
|
||||
|
||||
for line in BufReader::new(file).lines() {
|
||||
match line {
|
||||
Ok(line) => {
|
||||
if let Some(caps) = regex.captures(&line) {
|
||||
let version_str = &caps[1];
|
||||
let mut components = version_str.split('.');
|
||||
let major = components.next().unwrap().to_string();
|
||||
let minor = components.next().unwrap().to_string();
|
||||
let micro = components.next().unwrap().to_string();
|
||||
return Ok(Version {
|
||||
major,
|
||||
minor,
|
||||
micro,
|
||||
});
|
||||
}
|
||||
}
|
||||
Err(e) => return Err(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panic!("Version not found in meson.build");
|
||||
}
|
||||
|
||||
fn write_version_rs(version: &Version) {
|
||||
let output = Path::new(&env::var("OUT_DIR").unwrap()).join("version.rs");
|
||||
let mut file = File::create(output).expect("open version.rs for writing");
|
||||
file.write_all(
|
||||
format!(
|
||||
r#"
|
||||
use std::os::raw::c_uint;
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static rsvg_major_version: c_uint = {};
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static rsvg_minor_version: c_uint = {};
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub static rsvg_micro_version: c_uint = {};
|
||||
"#,
|
||||
version.major, version.minor, version.micro,
|
||||
)
|
||||
.as_bytes(),
|
||||
)
|
||||
.expect("write version.rs");
|
||||
}
|
||||
11
librsvg-c/meson.build
Normal file
11
librsvg-c/meson.build
Normal file
@@ -0,0 +1,11 @@
|
||||
librsvg_c_sources = files(
|
||||
'Cargo.toml',
|
||||
'build.rs',
|
||||
'src/dpi.rs',
|
||||
'src/handle.rs',
|
||||
'src/lib.rs',
|
||||
'src/messages.rs',
|
||||
'src/pixbuf_utils.rs',
|
||||
'src/sizing.rs',
|
||||
'tests/legacy_sizing.rs',
|
||||
)
|
||||
71
librsvg-c/src/dpi.rs
Normal file
71
librsvg-c/src/dpi.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
//! Legacy C API for setting a default DPI (dots per inch = DPI).
|
||||
//!
|
||||
//! There are two deprecated functions, `rsvg_set_default_dpi` and
|
||||
//! `rsvg_set_default_dpi_x_y`, which set global values for the default DPI to be used
|
||||
//! with `RsvgHandle`. In turn, `RsvgHandle` assumes that when its own DPI value is set
|
||||
//! to `0.0` (which is in fact its default), it will fall back to the global DPI.
|
||||
//!
|
||||
//! This is clearly not thread-safe, but it is the legacy behavior.
|
||||
//!
|
||||
//! This module encapsulates that behavior so that the `rsvg_internals` crate
|
||||
//! can always have immutable DPI values as intended.
|
||||
|
||||
// This is configurable at runtime
|
||||
const DEFAULT_DPI_X: f64 = 90.0;
|
||||
const DEFAULT_DPI_Y: f64 = 90.0;
|
||||
|
||||
static mut DPI_X: f64 = DEFAULT_DPI_X;
|
||||
static mut DPI_Y: f64 = DEFAULT_DPI_Y;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
pub(crate) struct Dpi {
|
||||
x: f64,
|
||||
y: f64,
|
||||
}
|
||||
|
||||
impl Dpi {
|
||||
pub(crate) fn new(x: f64, y: f64) -> Dpi {
|
||||
Dpi { x, y }
|
||||
}
|
||||
|
||||
pub(crate) fn x(&self) -> f64 {
|
||||
if self.x <= 0.0 {
|
||||
unsafe { DPI_X }
|
||||
} else {
|
||||
self.x
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn y(&self) -> f64 {
|
||||
if self.y <= 0.0 {
|
||||
unsafe { DPI_Y }
|
||||
} else {
|
||||
self.y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rsvg_set_default_dpi_x_y(dpi_x: libc::c_double, dpi_y: libc::c_double) {
|
||||
// mutating global statics is unsafe
|
||||
unsafe {
|
||||
if dpi_x <= 0.0 {
|
||||
DPI_X = DEFAULT_DPI_X;
|
||||
} else {
|
||||
DPI_X = dpi_x;
|
||||
}
|
||||
|
||||
if dpi_y <= 0.0 {
|
||||
DPI_Y = DEFAULT_DPI_Y;
|
||||
} else {
|
||||
DPI_Y = dpi_y;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
pub unsafe extern "C" fn rsvg_set_default_dpi(dpi: libc::c_double) {
|
||||
unsafe {
|
||||
rsvg_set_default_dpi_x_y(dpi, dpi);
|
||||
}
|
||||
}
|
||||
2206
librsvg-c/src/handle.rs
Normal file
2206
librsvg-c/src/handle.rs
Normal file
File diff suppressed because it is too large
Load Diff
63
librsvg-c/src/lib.rs
Normal file
63
librsvg-c/src/lib.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
//! C API for librsvg, based on GObject.
|
||||
//!
|
||||
//! The main API is in the [`handle`] module. The other modules have utility functions
|
||||
//! and the legacy pixbuf-based API in the `pixbuf_utils` module.
|
||||
|
||||
#![allow(clippy::missing_safety_doc)]
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub use handle::{
|
||||
rsvg_error_get_type,
|
||||
rsvg_handle_close,
|
||||
rsvg_handle_flags_get_type,
|
||||
rsvg_handle_get_base_uri,
|
||||
rsvg_handle_get_dimensions,
|
||||
rsvg_handle_get_dimensions_sub,
|
||||
rsvg_handle_get_geometry_for_element,
|
||||
rsvg_handle_get_geometry_for_layer,
|
||||
rsvg_handle_get_intrinsic_dimensions,
|
||||
rsvg_handle_get_intrinsic_size_in_pixels,
|
||||
rsvg_handle_get_position_sub,
|
||||
rsvg_handle_has_sub,
|
||||
rsvg_handle_internal_set_testing,
|
||||
rsvg_handle_new_from_data,
|
||||
rsvg_handle_new_from_file,
|
||||
rsvg_handle_new_from_gfile_sync,
|
||||
rsvg_handle_new_from_stream_sync,
|
||||
rsvg_handle_new_with_flags,
|
||||
rsvg_handle_read_stream_sync,
|
||||
rsvg_handle_render_cairo_sub,
|
||||
rsvg_handle_render_element,
|
||||
rsvg_handle_render_document,
|
||||
rsvg_handle_render_layer,
|
||||
rsvg_handle_set_base_gfile,
|
||||
rsvg_handle_set_base_uri,
|
||||
rsvg_handle_set_dpi_x_y,
|
||||
rsvg_handle_set_size_callback,
|
||||
rsvg_handle_write,
|
||||
};
|
||||
|
||||
#[cfg(feature = "pixbuf")]
|
||||
pub use handle::rsvg_handle_get_pixbuf_sub;
|
||||
|
||||
pub use dpi::{rsvg_set_default_dpi, rsvg_set_default_dpi_x_y};
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[cfg(feature = "pixbuf")]
|
||||
pub use pixbuf_utils::{
|
||||
rsvg_pixbuf_from_file,
|
||||
rsvg_pixbuf_from_file_at_max_size,
|
||||
rsvg_pixbuf_from_file_at_size,
|
||||
rsvg_pixbuf_from_file_at_zoom,
|
||||
rsvg_pixbuf_from_file_at_zoom_with_max,
|
||||
};
|
||||
|
||||
#[macro_use]
|
||||
mod messages;
|
||||
|
||||
mod dpi;
|
||||
pub mod handle;
|
||||
|
||||
#[cfg(feature = "pixbuf")]
|
||||
pub mod pixbuf_utils;
|
||||
pub mod sizing;
|
||||
162
librsvg-c/src/messages.rs
Normal file
162
librsvg-c/src/messages.rs
Normal file
@@ -0,0 +1,162 @@
|
||||
//! Logging functions, plus Rust versions of `g_return_if_fail()`.
|
||||
//!
|
||||
//! Glib's `g_return_if_fail()`, `g_warning()`, etc. are all C macros, so they cannot be
|
||||
//! used from Rust. This module defines equivalent functions or macros with an `rsvg_`
|
||||
//! prefix, to be clear that they should only be used from the implementation of the C API
|
||||
//! and not from the main Rust code of the library.
|
||||
|
||||
use glib::ffi::{G_LOG_LEVEL_CRITICAL, G_LOG_LEVEL_WARNING, GLogField, g_log_structured_array};
|
||||
use glib::translate::*;
|
||||
|
||||
/*
|
||||
G_LOG_LEVEL_CRITICAL = 1 << 3,
|
||||
G_LOG_LEVEL_WARNING = 1 << 4,
|
||||
|
||||
#define g_critical(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_CRITICAL, \
|
||||
__FILE__, G_STRINGIFY (__LINE__), \
|
||||
G_STRFUNC, __VA_ARGS__)
|
||||
#define g_warning(...) g_log_structured_standard (G_LOG_DOMAIN, G_LOG_LEVEL_WARNING, \
|
||||
__FILE__, G_STRINGIFY (__LINE__), \
|
||||
G_STRFUNC, __VA_ARGS__)
|
||||
GLogField fields[] =
|
||||
{
|
||||
{ "PRIORITY", log_level_to_priority (log_level), -1 },
|
||||
{ "CODE_FILE", file, -1 },
|
||||
{ "CODE_LINE", line, -1 },
|
||||
{ "CODE_FUNC", func, -1 },
|
||||
/* Filled in later: */
|
||||
{ "MESSAGE", NULL, -1 },
|
||||
/* If @log_domain is %NULL, we will not pass this field: */
|
||||
{ "GLIB_DOMAIN", log_domain, -1 },
|
||||
};
|
||||
|
||||
g_log_structured_array (log_level, fields, n_fields);
|
||||
*/
|
||||
|
||||
/// Helper function for converting string literals to C char pointers.
|
||||
#[macro_export]
|
||||
macro_rules! rsvg_c_str {
|
||||
($txt:expr) => {
|
||||
// SAFETY: it's important that the type we pass to `from_bytes_with_nul` is 'static,
|
||||
// so that the storage behind the returned pointer doesn't get freed while it's still
|
||||
// being used. We get that by only allowing string literals.
|
||||
std::ffi::CStr::from_bytes_with_nul(concat!($txt, "\0").as_bytes())
|
||||
.unwrap()
|
||||
.as_ptr()
|
||||
};
|
||||
}
|
||||
|
||||
/// Helper for `rsvg_g_warning` and `rsvg_g_critical`
|
||||
///
|
||||
/// This simulates what in C would be a call to the g_warning() or g_critical()
|
||||
/// macros, but with the underlying function g_log_structured_array().
|
||||
///
|
||||
/// If the implementation of g_warning() or g_critical() changes, we'll have
|
||||
/// to change this function.
|
||||
fn rsvg_g_log(level: glib::ffi::GLogLevelFlags, msg: &str) {
|
||||
// stolen from gmessages.c:log_level_to_priority()
|
||||
let priority = match level {
|
||||
G_LOG_LEVEL_WARNING | G_LOG_LEVEL_CRITICAL => c"4".as_ptr(),
|
||||
_ => unreachable!("please add another log level priority to rsvg_g_log()"),
|
||||
};
|
||||
|
||||
let c_msg = msg.to_glib_none();
|
||||
let c_char_msg: *const libc::c_char = c_msg.0;
|
||||
|
||||
// Glib's g_log_structured_standard() adds a few more fields for the source
|
||||
// file, line number, etc., but those are not terribly useful without a stack
|
||||
// trace. So, we'll omit them.
|
||||
let fields = [
|
||||
GLogField {
|
||||
key: c"PRIORITY".as_ptr(),
|
||||
value: priority.cast(),
|
||||
length: -1,
|
||||
},
|
||||
GLogField {
|
||||
key: c"MESSAGE".as_ptr(),
|
||||
value: c_char_msg.cast(),
|
||||
length: msg.len() as _,
|
||||
},
|
||||
// This is the G_LOG_DOMAIN set from the Makefile
|
||||
GLogField {
|
||||
key: c"GLIB_DOMAIN".as_ptr(),
|
||||
value: c"librsvg".as_ptr().cast(),
|
||||
length: -1,
|
||||
},
|
||||
];
|
||||
|
||||
unsafe {
|
||||
g_log_structured_array(level, fields.as_ptr(), fields.len());
|
||||
}
|
||||
}
|
||||
|
||||
/// Replacement for `g_warning()`.
|
||||
///
|
||||
/// Use this to signal an error condition in the following situations:
|
||||
///
|
||||
/// * The C API does not have an adequate error code for the error in question (and cannot
|
||||
/// be changed to have one, for ABI compatibility reasons).
|
||||
///
|
||||
/// * Applications using the C API would be prone to ignoring an important error,
|
||||
/// so it's best to have a warning on the console to at least have a hope of someone
|
||||
/// noticing the error.
|
||||
pub(crate) fn rsvg_g_warning(msg: &str) {
|
||||
rsvg_g_log(glib::ffi::G_LOG_LEVEL_WARNING, msg);
|
||||
}
|
||||
|
||||
/// Replacement for `g_critical()`.
|
||||
///
|
||||
/// Use this to signal a programming error from the caller of the C API, like passing
|
||||
/// incorrect arguments or calling the API out of order. Rust code conventionally panics
|
||||
/// in such situations, but C/Glib code does not, so it's best to "do nothing", print a
|
||||
/// critical message, and return. Development versions of GNOME will crash the program
|
||||
/// if this happens; release versions will ignore the error.
|
||||
pub(crate) fn rsvg_g_critical(msg: &str) {
|
||||
rsvg_g_log(glib::ffi::G_LOG_LEVEL_CRITICAL, msg);
|
||||
}
|
||||
|
||||
/// Replacement for `g_return_if_fail()`.
|
||||
// Once Rust has a function! macro that gives us the current function name, we
|
||||
// can remove the $func_name argument.
|
||||
#[macro_export]
|
||||
macro_rules! rsvg_return_if_fail {
|
||||
{
|
||||
$func_name:ident;
|
||||
$($condition:expr,)+
|
||||
} => {
|
||||
$(
|
||||
if !$condition {
|
||||
unsafe {
|
||||
glib::ffi::g_return_if_fail_warning(
|
||||
c"librsvg".as_ptr(),
|
||||
rsvg_c_str!(stringify!($func_name)),
|
||||
rsvg_c_str!(stringify!($condition)),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
|
||||
/// Replacement for `g_return_val_if_fail()`.
|
||||
#[macro_export]
|
||||
macro_rules! rsvg_return_val_if_fail {
|
||||
{
|
||||
$func_name:ident => $retval:expr;
|
||||
$($condition:expr,)+
|
||||
} => {
|
||||
$(
|
||||
if !$condition {
|
||||
unsafe {
|
||||
glib::ffi::g_return_if_fail_warning(
|
||||
c"librsvg".as_ptr(),
|
||||
rsvg_c_str!(stringify!($func_name)),
|
||||
rsvg_c_str!(stringify!($condition)),
|
||||
);
|
||||
}
|
||||
return $retval;
|
||||
}
|
||||
)+
|
||||
}
|
||||
}
|
||||
381
librsvg-c/src/pixbuf_utils.rs
Normal file
381
librsvg-c/src/pixbuf_utils.rs
Normal file
@@ -0,0 +1,381 @@
|
||||
//! Legacy C API for functions that render directly to a `GdkPixbuf`.
|
||||
//!
|
||||
//! This is the implementation of the `rsvg_pixbuf_*` family of functions.
|
||||
|
||||
#![cfg(feature = "pixbuf")]
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::ptr;
|
||||
|
||||
use gdk_pixbuf::{Colorspace, Pixbuf};
|
||||
use glib::translate::*;
|
||||
use rgb::FromSlice;
|
||||
|
||||
use super::dpi::Dpi;
|
||||
use super::handle::{checked_i32, set_gerror};
|
||||
use super::sizing::LegacySize;
|
||||
|
||||
use rsvg::c_api_only::{Pixel, PixelOps, Session, SharedImageSurface, SurfaceType, ToPixel};
|
||||
use rsvg::{CairoRenderer, Loader, RenderingError};
|
||||
|
||||
/// GdkPixbuf's endian-independent RGBA8 pixel layout.
|
||||
type GdkPixbufRGBA = rgb::RGBA8;
|
||||
|
||||
/// Trait to convert pixels in various formats to RGBA, for GdkPixbuf.
|
||||
///
|
||||
/// GdkPixbuf unconditionally uses RGBA ordering regardless of endianness,
|
||||
/// but we need to convert to it from Cairo's endian-dependent 0xaarrggbb.
|
||||
trait ToGdkPixbufRGBA {
|
||||
fn to_pixbuf_rgba(&self) -> GdkPixbufRGBA;
|
||||
}
|
||||
|
||||
impl ToGdkPixbufRGBA for Pixel {
|
||||
#[inline]
|
||||
fn to_pixbuf_rgba(&self) -> GdkPixbufRGBA {
|
||||
GdkPixbufRGBA {
|
||||
r: self.r,
|
||||
g: self.g,
|
||||
b: self.b,
|
||||
a: self.a,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty_pixbuf() -> Result<Pixbuf, RenderingError> {
|
||||
// GdkPixbuf does not allow zero-sized pixbufs, but Cairo allows zero-sized
|
||||
// surfaces. In this case, return a 1-pixel transparent pixbuf.
|
||||
|
||||
let pixbuf = Pixbuf::new(Colorspace::Rgb, true, 8, 1, 1)
|
||||
.ok_or_else(|| RenderingError::OutOfMemory(String::from("creating a Pixbuf")))?;
|
||||
pixbuf.put_pixel(0, 0, 0, 0, 0, 0);
|
||||
|
||||
Ok(pixbuf)
|
||||
}
|
||||
|
||||
pub fn pixbuf_from_surface(surface: &SharedImageSurface) -> Result<Pixbuf, RenderingError> {
|
||||
let width = surface.width();
|
||||
let height = surface.height();
|
||||
|
||||
let pixbuf = Pixbuf::new(Colorspace::Rgb, true, 8, width, height)
|
||||
.ok_or_else(|| RenderingError::OutOfMemory(String::from("creating a Pixbuf")))?;
|
||||
|
||||
assert!(pixbuf.colorspace() == Colorspace::Rgb);
|
||||
assert!(pixbuf.bits_per_sample() == 8);
|
||||
assert!(pixbuf.n_channels() == 4);
|
||||
|
||||
let pixbuf_data = unsafe { pixbuf.pixels() };
|
||||
let stride = pixbuf.rowstride() as usize;
|
||||
|
||||
// We use chunks_mut(), not chunks_exact_mut(), because gdk-pixbuf tends
|
||||
// to make the last row *not* have the full stride (i.e. it is
|
||||
// only as wide as the pixels in that row).
|
||||
pixbuf_data
|
||||
.chunks_mut(stride)
|
||||
.take(height as usize)
|
||||
.map(|row| row.as_rgba_mut())
|
||||
.zip(surface.rows())
|
||||
.flat_map(|(dest_row, src_row)| src_row.iter().zip(dest_row.iter_mut()))
|
||||
.for_each(|(src, dest)| *dest = src.to_pixel().unpremultiply().to_pixbuf_rgba());
|
||||
|
||||
Ok(pixbuf)
|
||||
}
|
||||
|
||||
enum SizeKind {
|
||||
Zoom,
|
||||
WidthHeight,
|
||||
WidthHeightMax,
|
||||
ZoomMax,
|
||||
}
|
||||
|
||||
struct SizeMode {
|
||||
kind: SizeKind,
|
||||
x_zoom: f64,
|
||||
y_zoom: f64,
|
||||
width: i32,
|
||||
height: i32,
|
||||
}
|
||||
|
||||
fn get_final_size(in_width: f64, in_height: f64, size_mode: &SizeMode) -> (f64, f64) {
|
||||
if in_width == 0.0 || in_height == 0.0 {
|
||||
return (0.0, 0.0);
|
||||
}
|
||||
|
||||
let mut out_width;
|
||||
let mut out_height;
|
||||
|
||||
match size_mode.kind {
|
||||
SizeKind::Zoom => {
|
||||
out_width = size_mode.x_zoom * in_width;
|
||||
out_height = size_mode.y_zoom * in_height;
|
||||
}
|
||||
|
||||
SizeKind::ZoomMax => {
|
||||
out_width = size_mode.x_zoom * in_width;
|
||||
out_height = size_mode.y_zoom * in_height;
|
||||
|
||||
if out_width > f64::from(size_mode.width) || out_height > f64::from(size_mode.height) {
|
||||
let zoom_x = f64::from(size_mode.width) / out_width;
|
||||
let zoom_y = f64::from(size_mode.height) / out_height;
|
||||
let zoom = zoom_x.min(zoom_y);
|
||||
|
||||
out_width *= zoom;
|
||||
out_height *= zoom;
|
||||
}
|
||||
}
|
||||
|
||||
SizeKind::WidthHeightMax => {
|
||||
let zoom_x = f64::from(size_mode.width) / in_width;
|
||||
let zoom_y = f64::from(size_mode.height) / in_height;
|
||||
|
||||
let zoom = zoom_x.min(zoom_y);
|
||||
|
||||
out_width = zoom * in_width;
|
||||
out_height = zoom * in_height;
|
||||
}
|
||||
|
||||
SizeKind::WidthHeight => {
|
||||
if size_mode.width != -1 {
|
||||
out_width = f64::from(size_mode.width);
|
||||
} else {
|
||||
out_width = in_width;
|
||||
}
|
||||
|
||||
if size_mode.height != -1 {
|
||||
out_height = f64::from(size_mode.height);
|
||||
} else {
|
||||
out_height = in_height;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(out_width, out_height)
|
||||
}
|
||||
|
||||
pub fn render_to_pixbuf_at_size(
|
||||
renderer: &CairoRenderer<'_>,
|
||||
document_width: f64,
|
||||
document_height: f64,
|
||||
desired_width: f64,
|
||||
desired_height: f64,
|
||||
) -> Result<Pixbuf, RenderingError> {
|
||||
if desired_width == 0.0
|
||||
|| desired_height == 0.0
|
||||
|| document_width == 0.0
|
||||
|| document_height == 0.0
|
||||
{
|
||||
return empty_pixbuf();
|
||||
}
|
||||
|
||||
let surface = cairo::ImageSurface::create(
|
||||
cairo::Format::ARgb32,
|
||||
checked_i32(desired_width.ceil())?,
|
||||
checked_i32(desired_height.ceil())?,
|
||||
)?;
|
||||
|
||||
{
|
||||
let cr = cairo::Context::new(&surface)?;
|
||||
cr.scale(
|
||||
desired_width / document_width,
|
||||
desired_height / document_height,
|
||||
);
|
||||
|
||||
let viewport = cairo::Rectangle::new(0.0, 0.0, document_width, document_height);
|
||||
|
||||
// We do it with a cr transform so we can scale non-proportionally.
|
||||
renderer.render_document(&cr, &viewport)?;
|
||||
}
|
||||
|
||||
let shared_surface = SharedImageSurface::wrap(surface, SurfaceType::SRgb)?;
|
||||
|
||||
pixbuf_from_surface(&shared_surface)
|
||||
}
|
||||
|
||||
unsafe fn pixbuf_from_file_with_size_mode(
|
||||
filename: *const libc::c_char,
|
||||
size_mode: &SizeMode,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
|
||||
let path = unsafe { PathBuf::from_glib_none(filename) };
|
||||
|
||||
let session = Session::default();
|
||||
|
||||
let handle = match Loader::new_with_session(session.clone()).read_path(path) {
|
||||
Ok(handle) => handle,
|
||||
Err(e) => {
|
||||
set_gerror(&session, error, 0, &format!("{e}"));
|
||||
return ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
let dpi = Dpi::default();
|
||||
let renderer = CairoRenderer::new(&handle).with_dpi(dpi.x(), dpi.y());
|
||||
|
||||
let (document_width, document_height) = match renderer.legacy_document_size() {
|
||||
Ok(dim) => dim,
|
||||
Err(e) => {
|
||||
set_gerror(&session, error, 0, &format!("{e}"));
|
||||
return ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
let (desired_width, desired_height) =
|
||||
get_final_size(document_width, document_height, size_mode);
|
||||
|
||||
render_to_pixbuf_at_size(
|
||||
&renderer,
|
||||
document_width,
|
||||
document_height,
|
||||
desired_width,
|
||||
desired_height,
|
||||
)
|
||||
.map(|pixbuf| pixbuf.to_glib_full())
|
||||
.unwrap_or_else(|e| {
|
||||
set_gerror(&session, error, 0, &format!("{e}"));
|
||||
ptr::null_mut()
|
||||
})
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
#[allow(unsafe_op_in_unsafe_fn)]
|
||||
pub unsafe extern "C" fn rsvg_pixbuf_from_file(
|
||||
filename: *const libc::c_char,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
|
||||
rsvg_return_val_if_fail! {
|
||||
rsvg_pixbuf_from_file => ptr::null_mut();
|
||||
|
||||
!filename.is_null(),
|
||||
error.is_null() || (*error).is_null(),
|
||||
}
|
||||
|
||||
pixbuf_from_file_with_size_mode(
|
||||
filename,
|
||||
&SizeMode {
|
||||
kind: SizeKind::WidthHeight,
|
||||
x_zoom: 0.0,
|
||||
y_zoom: 0.0,
|
||||
width: -1,
|
||||
height: -1,
|
||||
},
|
||||
error,
|
||||
)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
#[allow(unsafe_op_in_unsafe_fn)]
|
||||
pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_size(
|
||||
filename: *const libc::c_char,
|
||||
width: libc::c_int,
|
||||
height: libc::c_int,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
|
||||
rsvg_return_val_if_fail! {
|
||||
rsvg_pixbuf_from_file_at_size => ptr::null_mut();
|
||||
|
||||
!filename.is_null(),
|
||||
(width >= 1 && height >= 1) || (width == -1 && height == -1),
|
||||
error.is_null() || (*error).is_null(),
|
||||
}
|
||||
|
||||
pixbuf_from_file_with_size_mode(
|
||||
filename,
|
||||
&SizeMode {
|
||||
kind: SizeKind::WidthHeight,
|
||||
x_zoom: 0.0,
|
||||
y_zoom: 0.0,
|
||||
width,
|
||||
height,
|
||||
},
|
||||
error,
|
||||
)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
#[allow(unsafe_op_in_unsafe_fn)]
|
||||
pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_zoom(
|
||||
filename: *const libc::c_char,
|
||||
x_zoom: libc::c_double,
|
||||
y_zoom: libc::c_double,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
|
||||
rsvg_return_val_if_fail! {
|
||||
rsvg_pixbuf_from_file_at_zoom => ptr::null_mut();
|
||||
|
||||
!filename.is_null(),
|
||||
x_zoom > 0.0 && y_zoom > 0.0,
|
||||
error.is_null() || (*error).is_null(),
|
||||
}
|
||||
|
||||
pixbuf_from_file_with_size_mode(
|
||||
filename,
|
||||
&SizeMode {
|
||||
kind: SizeKind::Zoom,
|
||||
x_zoom,
|
||||
y_zoom,
|
||||
width: 0,
|
||||
height: 0,
|
||||
},
|
||||
error,
|
||||
)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
#[allow(unsafe_op_in_unsafe_fn)]
|
||||
pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_zoom_with_max(
|
||||
filename: *const libc::c_char,
|
||||
x_zoom: libc::c_double,
|
||||
y_zoom: libc::c_double,
|
||||
max_width: libc::c_int,
|
||||
max_height: libc::c_int,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
|
||||
rsvg_return_val_if_fail! {
|
||||
rsvg_pixbuf_from_file_at_zoom_with_max => ptr::null_mut();
|
||||
|
||||
!filename.is_null(),
|
||||
x_zoom > 0.0 && y_zoom > 0.0,
|
||||
max_width >= 1 && max_height >= 1,
|
||||
error.is_null() || (*error).is_null(),
|
||||
}
|
||||
|
||||
pixbuf_from_file_with_size_mode(
|
||||
filename,
|
||||
&SizeMode {
|
||||
kind: SizeKind::ZoomMax,
|
||||
x_zoom,
|
||||
y_zoom,
|
||||
width: max_width,
|
||||
height: max_height,
|
||||
},
|
||||
error,
|
||||
)
|
||||
}
|
||||
|
||||
#[unsafe(no_mangle)]
|
||||
#[allow(unsafe_op_in_unsafe_fn)]
|
||||
pub unsafe extern "C" fn rsvg_pixbuf_from_file_at_max_size(
|
||||
filename: *const libc::c_char,
|
||||
max_width: libc::c_int,
|
||||
max_height: libc::c_int,
|
||||
error: *mut *mut glib::ffi::GError,
|
||||
) -> *mut gdk_pixbuf::ffi::GdkPixbuf {
|
||||
rsvg_return_val_if_fail! {
|
||||
rsvg_pixbuf_from_file_at_max_size => ptr::null_mut();
|
||||
|
||||
!filename.is_null(),
|
||||
max_width >= 1 && max_height >= 1,
|
||||
error.is_null() || (*error).is_null(),
|
||||
}
|
||||
|
||||
pixbuf_from_file_with_size_mode(
|
||||
filename,
|
||||
&SizeMode {
|
||||
kind: SizeKind::WidthHeightMax,
|
||||
x_zoom: 0.0,
|
||||
y_zoom: 0.0,
|
||||
width: max_width,
|
||||
height: max_height,
|
||||
},
|
||||
error,
|
||||
)
|
||||
}
|
||||
114
librsvg-c/src/sizing.rs
Normal file
114
librsvg-c/src/sizing.rs
Normal file
@@ -0,0 +1,114 @@
|
||||
//! Compute an SVG document's size with the legacy logic.
|
||||
//!
|
||||
//! See the documentation for [`LegacySize`]. The legacy C API functions like
|
||||
//! `rsvg_handle_render_cairo()` do not take a viewport argument: they do not know how big
|
||||
//! the caller would like to render the document; instead they compute a "natural size"
|
||||
//! for the document based on its `width`/`height`/`viewBox` and some heuristics for when
|
||||
//! they are missing.
|
||||
//!
|
||||
//! The new style C functions like `rsvg_handle_render_document()` actually take a
|
||||
//! viewport, which indicates how big the result should be. This matches the expectations
|
||||
//! of the web platform, which assumes that all embedded content goes into a viewport.
|
||||
|
||||
use float_cmp::approx_eq;
|
||||
|
||||
use rsvg::c_api_only::Dpi;
|
||||
use rsvg::{CairoRenderer, IntrinsicDimensions, RenderingError};
|
||||
|
||||
use super::handle::CairoRectangleExt;
|
||||
|
||||
/// Extension methods to compute the SVG's size suitable for the legacy C API.
|
||||
///
|
||||
/// The legacy C API can compute an SVG document's size from the
|
||||
/// `width`, `height`, and `viewBox` attributes of the toplevel `<svg>`
|
||||
/// element. If these are not available, then the size must be computed
|
||||
/// by actually measuring the geometries of elements in the document.
|
||||
///
|
||||
/// See <https://www.w3.org/TR/css-images-3/#sizing-terms> for terminology and logic.
|
||||
pub trait LegacySize {
|
||||
fn legacy_document_size(&self) -> Result<(f64, f64), RenderingError> {
|
||||
let (ink_r, _) = self.legacy_layer_geometry(None)?;
|
||||
Ok((ink_r.width(), ink_r.height()))
|
||||
}
|
||||
|
||||
fn legacy_layer_geometry(
|
||||
&self,
|
||||
id: Option<&str>,
|
||||
) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError>;
|
||||
}
|
||||
|
||||
impl<'a> LegacySize for CairoRenderer<'a> {
|
||||
fn legacy_layer_geometry(
|
||||
&self,
|
||||
id: Option<&str>,
|
||||
) -> Result<(cairo::Rectangle, cairo::Rectangle), RenderingError> {
|
||||
match id {
|
||||
Some(id) => Ok(self.geometry_for_layer(Some(id), &unit_rectangle())?),
|
||||
|
||||
None => {
|
||||
let size_from_intrinsic_dimensions =
|
||||
self.intrinsic_size_in_pixels().or_else(|| {
|
||||
size_in_pixels_from_percentage_width_and_height(
|
||||
self,
|
||||
&self.intrinsic_dimensions(),
|
||||
self.dpi(),
|
||||
)
|
||||
});
|
||||
|
||||
if let Some((w, h)) = size_from_intrinsic_dimensions {
|
||||
// We have a size directly computed from the <svg> attributes
|
||||
let rect = cairo::Rectangle::from_size(w, h);
|
||||
Ok((rect, rect))
|
||||
} else {
|
||||
self.geometry_for_layer(None, &unit_rectangle())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unit_rectangle() -> cairo::Rectangle {
|
||||
cairo::Rectangle::from_size(1.0, 1.0)
|
||||
}
|
||||
|
||||
/// If the width and height are in percentage units, computes a size equal to the
|
||||
/// `viewBox`'s aspect ratio if it exists, or else returns None.
|
||||
///
|
||||
/// For example, a `viewBox="0 0 100 200"` will yield `Some(100.0, 200.0)`.
|
||||
///
|
||||
/// Note that this only checks that the width and height are in percentage units, but
|
||||
/// it actually ignores their values. This is because at the point this function is
|
||||
/// called, there is no viewport to embed the SVG document in, so those percentage
|
||||
/// units cannot be resolved against anything in particular. The idea is to return
|
||||
/// some dimensions with the correct aspect ratio.
|
||||
fn size_in_pixels_from_percentage_width_and_height(
|
||||
renderer: &CairoRenderer,
|
||||
dim: &IntrinsicDimensions,
|
||||
dpi: Dpi,
|
||||
) -> Option<(f64, f64)> {
|
||||
let IntrinsicDimensions {
|
||||
width,
|
||||
height,
|
||||
vbox,
|
||||
} = *dim;
|
||||
|
||||
use rsvg::LengthUnit::*;
|
||||
|
||||
// Unwrap or return None if we don't know the aspect ratio -> Let the caller handle it.
|
||||
let vbox = vbox?;
|
||||
|
||||
let (w, h) = renderer.width_height_to_user(dpi);
|
||||
|
||||
// Avoid division by zero below. If the viewBox is zero-sized, there's
|
||||
// not much we can do.
|
||||
if approx_eq!(f64, vbox.width(), 0.0) || approx_eq!(f64, vbox.height(), 0.0) {
|
||||
return Some((0.0, 0.0));
|
||||
}
|
||||
|
||||
match (width.unit, height.unit) {
|
||||
(Percent, Percent) => Some((vbox.width(), vbox.height())),
|
||||
(_, Percent) => Some((w, w * vbox.height() / vbox.width())),
|
||||
(Percent, _) => Some((h * vbox.width() / vbox.height(), h)),
|
||||
(_, _) => unreachable!("should have been called with percentage units"),
|
||||
}
|
||||
}
|
||||
1874
librsvg-c/tests-c/api.c
Normal file
1874
librsvg-c/tests-c/api.c
Normal file
File diff suppressed because it is too large
Load Diff
37
librsvg-c/tests-c/meson.build
Normal file
37
librsvg-c/tests-c/meson.build
Normal file
@@ -0,0 +1,37 @@
|
||||
api_test_sources = files(
|
||||
'api.c',
|
||||
'test-utils.c',
|
||||
'test-utils.h',
|
||||
)
|
||||
|
||||
test_c_args = [
|
||||
'-DTEST_DATA_DIR=@0@'.format(meson.current_source_dir()),
|
||||
'-DTEST_SRC_DIR=@0@'.format(meson.current_build_dir()),
|
||||
'-DTOP_SRC_DIR=@0@'.format(meson.project_source_root()),
|
||||
]
|
||||
|
||||
if pixbuf_dep.found()
|
||||
test_c_args += ['-DHAVE_PIXBUF']
|
||||
endif
|
||||
|
||||
api_test = executable(
|
||||
'api',
|
||||
api_test_sources,
|
||||
c_args: test_c_args,
|
||||
dependencies: librsvg_dep,
|
||||
gnu_symbol_visibility: 'hidden',
|
||||
install: false,
|
||||
)
|
||||
|
||||
test(
|
||||
'C API tests',
|
||||
api_test,
|
||||
|
||||
# the following incantation is for glib's gtestutils
|
||||
timeout: -1,
|
||||
args: [ '--tap', 'k' ],
|
||||
env: [
|
||||
'G_TEST_SRCDIR=@0@'.format(meson.current_source_dir()),
|
||||
'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()),
|
||||
]
|
||||
)
|
||||
258
librsvg-c/tests-c/test-utils.c
Normal file
258
librsvg-c/tests-c/test-utils.c
Normal file
@@ -0,0 +1,258 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim: set sw=4 sts=4 ts=4 expandtab: */
|
||||
|
||||
#include "test-utils.h"
|
||||
|
||||
#include <string.h>
|
||||
#include <pango/pango.h>
|
||||
#include <pango/pangocairo.h>
|
||||
|
||||
#if !PANGO_VERSION_CHECK (1, 44, 0)
|
||||
# include <hb.h>
|
||||
#endif
|
||||
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
|
||||
/* Compare two buffers, returning the number of pixels that are
|
||||
* different and the maximum difference of any single color channel in
|
||||
* result_ret.
|
||||
*
|
||||
* This function should be rewritten to compare all formats supported by
|
||||
* cairo_format_t instead of taking a mask as a parameter.
|
||||
*/
|
||||
static void
|
||||
buffer_diff_core (unsigned char *_buf_a,
|
||||
unsigned char *_buf_b,
|
||||
unsigned char *_buf_diff,
|
||||
int width,
|
||||
int height,
|
||||
int stride,
|
||||
guint32 mask,
|
||||
TestUtilsBufferDiffResult *result_ret)
|
||||
{
|
||||
int x, y;
|
||||
guint32 *row_a, *row_b, *row;
|
||||
TestUtilsBufferDiffResult result = {0, 0};
|
||||
guint32 *buf_a = (guint32 *) _buf_a;
|
||||
guint32 *buf_b = (guint32 *) _buf_b;
|
||||
guint32 *buf_diff = (guint32 *) _buf_diff;
|
||||
|
||||
stride /= sizeof(guint32);
|
||||
for (y = 0; y < height; y++)
|
||||
{
|
||||
row_a = buf_a + y * stride;
|
||||
row_b = buf_b + y * stride;
|
||||
row = buf_diff + y * stride;
|
||||
for (x = 0; x < width; x++)
|
||||
{
|
||||
/* check if the pixels are the same */
|
||||
if ((row_a[x] & mask) != (row_b[x] & mask)) {
|
||||
int channel;
|
||||
guint32 diff_pixel = 0;
|
||||
|
||||
/* calculate a difference value for all 4 channels */
|
||||
for (channel = 0; channel < 4; channel++) {
|
||||
int value_a = (row_a[x] >> (channel*8)) & 0xff;
|
||||
int value_b = (row_b[x] >> (channel*8)) & 0xff;
|
||||
unsigned int diff;
|
||||
diff = abs (value_a - value_b);
|
||||
if (diff > result.max_diff)
|
||||
result.max_diff = diff;
|
||||
diff *= 4; /* emphasize */
|
||||
if (diff)
|
||||
diff += 128; /* make sure it's visible */
|
||||
if (diff > 255)
|
||||
diff = 255;
|
||||
diff_pixel |= diff << (channel*8);
|
||||
}
|
||||
|
||||
result.pixels_changed++;
|
||||
if ((diff_pixel & 0x00ffffff) == 0) {
|
||||
/* alpha only difference, convert to luminance */
|
||||
guint8 alpha = diff_pixel >> 24;
|
||||
diff_pixel = alpha * 0x010101;
|
||||
}
|
||||
row[x] = diff_pixel;
|
||||
} else {
|
||||
row[x] = 0;
|
||||
}
|
||||
row[x] |= 0xff000000; /* Set ALPHA to 100% (opaque) */
|
||||
}
|
||||
}
|
||||
|
||||
*result_ret = result;
|
||||
}
|
||||
|
||||
void
|
||||
test_utils_compare_surfaces (cairo_surface_t *surface_a,
|
||||
cairo_surface_t *surface_b,
|
||||
cairo_surface_t *surface_diff,
|
||||
TestUtilsBufferDiffResult *result)
|
||||
{
|
||||
/* Here, we run cairo's old buffer_diff algorithm which looks for
|
||||
* pixel-perfect images.
|
||||
*/
|
||||
buffer_diff_core (cairo_image_surface_get_data (surface_a),
|
||||
cairo_image_surface_get_data (surface_b),
|
||||
cairo_image_surface_get_data (surface_diff),
|
||||
cairo_image_surface_get_width (surface_a),
|
||||
cairo_image_surface_get_height (surface_a),
|
||||
cairo_image_surface_get_stride (surface_a),
|
||||
0xffffffff,
|
||||
result);
|
||||
if (result->pixels_changed == 0)
|
||||
return;
|
||||
|
||||
g_test_message ("%d pixels differ (with maximum difference of %d) from reference image\n",
|
||||
result->pixels_changed, result->max_diff);
|
||||
}
|
||||
|
||||
#ifdef HAVE_PIXBUF
|
||||
/* Copied from gdk_cairo_surface_paint_pixbuf in gdkcairo.c,
|
||||
* we do not want to depend on GDK
|
||||
*/
|
||||
static void
|
||||
test_utils_cairo_surface_paint_pixbuf (cairo_surface_t *surface,
|
||||
const GdkPixbuf *pixbuf)
|
||||
{
|
||||
gint width, height;
|
||||
guchar *gdk_pixels, *cairo_pixels;
|
||||
int gdk_rowstride, cairo_stride;
|
||||
int n_channels;
|
||||
int j;
|
||||
|
||||
if (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)
|
||||
return;
|
||||
|
||||
/* This function can't just copy any pixbuf to any surface, be
|
||||
* sure to read the invariants here before calling it */
|
||||
|
||||
g_assert (cairo_surface_get_type (surface) == CAIRO_SURFACE_TYPE_IMAGE);
|
||||
g_assert (cairo_image_surface_get_format (surface) == CAIRO_FORMAT_RGB24 ||
|
||||
cairo_image_surface_get_format (surface) == CAIRO_FORMAT_ARGB32);
|
||||
g_assert (cairo_image_surface_get_width (surface) == gdk_pixbuf_get_width (pixbuf));
|
||||
g_assert (cairo_image_surface_get_height (surface) == gdk_pixbuf_get_height (pixbuf));
|
||||
|
||||
cairo_surface_flush (surface);
|
||||
|
||||
width = gdk_pixbuf_get_width (pixbuf);
|
||||
height = gdk_pixbuf_get_height (pixbuf);
|
||||
gdk_pixels = gdk_pixbuf_get_pixels (pixbuf);
|
||||
gdk_rowstride = gdk_pixbuf_get_rowstride (pixbuf);
|
||||
n_channels = gdk_pixbuf_get_n_channels (pixbuf);
|
||||
cairo_stride = cairo_image_surface_get_stride (surface);
|
||||
cairo_pixels = cairo_image_surface_get_data (surface);
|
||||
|
||||
for (j = height; j; j--)
|
||||
{
|
||||
guchar *p = gdk_pixels;
|
||||
guchar *q = cairo_pixels;
|
||||
|
||||
if (n_channels == 3)
|
||||
{
|
||||
guchar *end = p + 3 * width;
|
||||
|
||||
while (p < end)
|
||||
{
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
q[0] = p[2];
|
||||
q[1] = p[1];
|
||||
q[2] = p[0];
|
||||
#else
|
||||
q[1] = p[0];
|
||||
q[2] = p[1];
|
||||
q[3] = p[2];
|
||||
#endif
|
||||
p += 3;
|
||||
q += 4;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
guchar *end = p + 4 * width;
|
||||
guint t1,t2,t3;
|
||||
|
||||
#define MULT(d,c,a,t) G_STMT_START { t = c * a + 0x80; d = ((t >> 8) + t) >> 8; } G_STMT_END
|
||||
|
||||
while (p < end)
|
||||
{
|
||||
#if G_BYTE_ORDER == G_LITTLE_ENDIAN
|
||||
MULT(q[0], p[2], p[3], t1);
|
||||
MULT(q[1], p[1], p[3], t2);
|
||||
MULT(q[2], p[0], p[3], t3);
|
||||
q[3] = p[3];
|
||||
#else
|
||||
q[0] = p[3];
|
||||
MULT(q[1], p[0], p[3], t1);
|
||||
MULT(q[2], p[1], p[3], t2);
|
||||
MULT(q[3], p[2], p[3], t3);
|
||||
#endif
|
||||
|
||||
p += 4;
|
||||
q += 4;
|
||||
}
|
||||
|
||||
#undef MULT
|
||||
}
|
||||
|
||||
gdk_pixels += gdk_rowstride;
|
||||
cairo_pixels += cairo_stride;
|
||||
}
|
||||
|
||||
cairo_surface_mark_dirty (surface);
|
||||
}
|
||||
|
||||
cairo_surface_t *
|
||||
test_utils_cairo_surface_from_pixbuf (const GdkPixbuf *pixbuf)
|
||||
{
|
||||
cairo_surface_t *surface;
|
||||
|
||||
g_return_val_if_fail (GDK_IS_PIXBUF (pixbuf), NULL);
|
||||
g_return_val_if_fail (gdk_pixbuf_get_n_channels (pixbuf) == 4, NULL);
|
||||
|
||||
surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
|
||||
gdk_pixbuf_get_width (pixbuf),
|
||||
gdk_pixbuf_get_height (pixbuf));
|
||||
|
||||
test_utils_cairo_surface_paint_pixbuf (surface, pixbuf);
|
||||
|
||||
return surface;
|
||||
}
|
||||
#endif /* defined(HAVE_PIXBUF) */
|
||||
|
||||
static gchar *data_path = NULL;
|
||||
|
||||
const gchar *
|
||||
test_utils_get_test_data_path (void)
|
||||
{
|
||||
if (data_path)
|
||||
return data_path;
|
||||
|
||||
data_path = g_test_build_filename (G_TEST_DIST, "../../rsvg/tests/fixtures", NULL);
|
||||
|
||||
return data_path;
|
||||
}
|
||||
|
||||
void
|
||||
test_utils_print_dependency_versions (void)
|
||||
{
|
||||
FT_Library ft_lib;
|
||||
FT_Int ft_major = 0;
|
||||
FT_Int ft_minor = 0;
|
||||
FT_Int ft_patch = 0;
|
||||
|
||||
FT_Init_FreeType (&ft_lib);
|
||||
FT_Library_Version (ft_lib, &ft_major, &ft_minor, &ft_patch);
|
||||
FT_Done_FreeType (ft_lib);
|
||||
|
||||
g_test_message ("Cairo version: %s", cairo_version_string ());
|
||||
g_test_message ("Pango version: %s", pango_version_string ());
|
||||
g_test_message ("Freetype version: %d.%d.%d", ft_major, ft_minor, ft_patch);
|
||||
#if PANGO_VERSION_CHECK (1, 44, 0)
|
||||
g_test_message ("Harfbuzz version: %s", hb_version_string ());
|
||||
#else
|
||||
g_test_message ("Not printing Harfbuzz version since Pango is older than 1.44");
|
||||
#endif
|
||||
}
|
||||
40
librsvg-c/tests-c/test-utils.h
Normal file
40
librsvg-c/tests-c/test-utils.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||
/* vim: set sw=4 sts=4 ts=4 expandtab: */
|
||||
|
||||
#ifndef TEST_UTILS_H
|
||||
#define TEST_UTILS_H
|
||||
|
||||
#include <cairo.h>
|
||||
#include <gio/gio.h>
|
||||
|
||||
#ifdef HAVE_PIXBUF
|
||||
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||
#endif
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
typedef struct {
|
||||
unsigned int pixels_changed;
|
||||
unsigned int max_diff;
|
||||
} TestUtilsBufferDiffResult;
|
||||
|
||||
void test_utils_compare_surfaces (cairo_surface_t *surface_a,
|
||||
cairo_surface_t *surface_b,
|
||||
cairo_surface_t *surface_diff,
|
||||
TestUtilsBufferDiffResult *result);
|
||||
|
||||
#ifdef HAVE_PIXBUF
|
||||
cairo_surface_t *test_utils_cairo_surface_from_pixbuf (const GdkPixbuf *pixbuf);
|
||||
#endif
|
||||
|
||||
typedef gboolean (* AddTestFunc) (GFile *file);
|
||||
|
||||
const gchar *test_utils_get_test_data_path (void);
|
||||
|
||||
void test_utils_print_dependency_versions (void);
|
||||
|
||||
void test_utils_setup_font_map (void);
|
||||
|
||||
G_END_DECLS
|
||||
|
||||
#endif /* TEST_UTILS_H */
|
||||
193
librsvg-c/tests/legacy_sizing.rs
Normal file
193
librsvg-c/tests/legacy_sizing.rs
Normal file
@@ -0,0 +1,193 @@
|
||||
use librsvg_c::sizing::LegacySize;
|
||||
use rsvg::{CairoRenderer, Loader, LoadingError, SvgHandle};
|
||||
|
||||
fn load_svg(input: &'static [u8]) -> Result<SvgHandle, LoadingError> {
|
||||
let bytes = glib::Bytes::from_static(input);
|
||||
let stream = gio::MemoryInputStream::from_bytes(&bytes);
|
||||
|
||||
Loader::new().read_stream(&stream, None::<&gio::File>, None::<&gio::Cancellable>)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn just_viewbox_uses_viewbox_size() {
|
||||
let svg = load_svg(
|
||||
br#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 200"/>
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
CairoRenderer::new(&svg)
|
||||
.legacy_layer_geometry(None)
|
||||
.unwrap(),
|
||||
(
|
||||
cairo::Rectangle::new(0.0, 0.0, 100.0, 200.0),
|
||||
cairo::Rectangle::new(0.0, 0.0, 100.0, 200.0),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_intrinsic_size_uses_element_geometries() {
|
||||
let svg = load_svg(
|
||||
br#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="10" y="20" width="30" height="40" fill="black"/>
|
||||
</svg>
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
CairoRenderer::new(&svg)
|
||||
.legacy_layer_geometry(None)
|
||||
.unwrap(),
|
||||
(
|
||||
cairo::Rectangle::new(10.0, 20.0, 30.0, 40.0),
|
||||
cairo::Rectangle::new(10.0, 20.0, 30.0, 40.0),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hundred_percent_width_height_uses_viewbox() {
|
||||
let svg = load_svg(
|
||||
br#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 100 200"/>
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
CairoRenderer::new(&svg)
|
||||
.legacy_layer_geometry(None)
|
||||
.unwrap(),
|
||||
(
|
||||
cairo::Rectangle::new(0.0, 0.0, 100.0, 200.0),
|
||||
cairo::Rectangle::new(0.0, 0.0, 100.0, 200.0),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hundred_percent_width_height_no_viewbox_uses_element_geometries() {
|
||||
let svg = load_svg(
|
||||
br#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%">
|
||||
<rect x="10" y="20" width="30" height="40" fill="black"/>
|
||||
</svg>
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
CairoRenderer::new(&svg)
|
||||
.legacy_layer_geometry(None)
|
||||
.unwrap(),
|
||||
(
|
||||
cairo::Rectangle::new(10.0, 20.0, 30.0, 40.0),
|
||||
cairo::Rectangle::new(10.0, 20.0, 30.0, 40.0),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn width_and_viewbox_preserves_aspect_ratio() {
|
||||
let svg = load_svg(
|
||||
br#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="60" viewBox="0 0 30 40">
|
||||
<rect x="10" y="20" width="30" height="40" fill="black"/>
|
||||
</svg>
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// Per the spec, the height property should default to 100%, so the above would end up
|
||||
// like <svg width="60" height="100%" viewBox="0 0 30 40">
|
||||
//
|
||||
// If that were being *rendered* to a viewport, no problem, just use units horizontally
|
||||
// and 100% of the viewport vertically.
|
||||
//
|
||||
// But we are being asked to compute the SVG's natural geometry, so the best we can do
|
||||
// is to take the aspect ratio defined by the viewBox and apply it to the width.
|
||||
|
||||
assert_eq!(
|
||||
CairoRenderer::new(&svg)
|
||||
.legacy_layer_geometry(None)
|
||||
.unwrap(),
|
||||
(
|
||||
cairo::Rectangle::new(0.0, 0.0, 60.0, 80.0),
|
||||
cairo::Rectangle::new(0.0, 0.0, 60.0, 80.0),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn height_and_viewbox_preserves_aspect_ratio() {
|
||||
let svg = load_svg(
|
||||
br#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="80" viewBox="0 0 30 40">
|
||||
<rect x="10" y="20" width="30" height="40" fill="black"/>
|
||||
</svg>
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
// See the comment above in width_and_viewbox_preserves_aspect_ratio(); this
|
||||
// is equivalent but for the height.
|
||||
|
||||
assert_eq!(
|
||||
CairoRenderer::new(&svg)
|
||||
.legacy_layer_geometry(None)
|
||||
.unwrap(),
|
||||
(
|
||||
cairo::Rectangle::new(0.0, 0.0, 60.0, 80.0),
|
||||
cairo::Rectangle::new(0.0, 0.0, 60.0, 80.0),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_width_vbox() {
|
||||
let svg = load_svg(
|
||||
br#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="80" viewBox="0 0 0 40">
|
||||
<rect x="10" y="20" width="30" height="40" fill="black"/>
|
||||
</svg>
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
CairoRenderer::new(&svg)
|
||||
.legacy_layer_geometry(None)
|
||||
.unwrap(),
|
||||
(
|
||||
cairo::Rectangle::new(0.0, 0.0, 0.0, 0.0),
|
||||
cairo::Rectangle::new(0.0, 0.0, 0.0, 0.0)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_height_vbox() {
|
||||
let svg = load_svg(
|
||||
br#"<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="80" viewBox="0 0 30 0">
|
||||
<rect x="10" y="20" width="30" height="40" fill="black"/>
|
||||
</svg>
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
CairoRenderer::new(&svg)
|
||||
.legacy_layer_geometry(None)
|
||||
.unwrap(),
|
||||
(
|
||||
cairo::Rectangle::new(0.0, 0.0, 0.0, 0.0),
|
||||
cairo::Rectangle::new(0.0, 0.0, 0.0, 0.0)
|
||||
)
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user