use std::sync::Arc;
use crate::accept_language::UserLanguage;
use crate::bbox::BoundingBox;
use crate::borrow_element_as;
use crate::css::{Origin, Stylesheet};
use crate::document::{AcquiredNodes, Document, NodeId};
use crate::dpi::Dpi;
use crate::drawing_ctx::{draw_tree, with_saved_cr, DrawingMode, Viewport};
use crate::error::{DefsLookupErrorKind, InternalRenderingError, LoadingError};
use crate::length::*;
use crate::node::{CascadedValues, Node, NodeBorrow};
use crate::rect::Rect;
use crate::rsvg_log;
use crate::session::Session;
use crate::structure::IntrinsicDimensions;
use crate::url_resolver::{AllowedUrl, UrlResolver};
pub struct LoadOptions {
pub url_resolver: UrlResolver,
pub unlimited_size: bool,
pub keep_image_data: bool,
}
impl LoadOptions {
pub fn new(url_resolver: UrlResolver) -> Self {
LoadOptions {
url_resolver,
unlimited_size: false,
keep_image_data: false,
}
}
pub fn with_unlimited_size(mut self, unlimited: bool) -> Self {
self.unlimited_size = unlimited;
self
}
pub fn keep_image_data(mut self, keep: bool) -> Self {
self.keep_image_data = keep;
self
}
pub fn copy_with_base_url(&self, base_url: &AllowedUrl) -> Self {
let mut url_resolver = self.url_resolver.clone();
url_resolver.base_url = Some((**base_url).clone());
LoadOptions {
url_resolver,
unlimited_size: self.unlimited_size,
keep_image_data: self.keep_image_data,
}
}
}
pub struct Handle {
session: Session,
document: Document,
}
impl Handle {
pub fn from_stream(
session: Session,
load_options: Arc<LoadOptions>,
stream: &gio::InputStream,
cancellable: Option<&gio::Cancellable>,
) -> Result<Handle, LoadingError> {
Ok(Handle {
session: session.clone(),
document: Document::load_from_stream(session, load_options, stream, cancellable)?,
})
}
pub fn has_sub(&self, id: &str) -> Result<bool, InternalRenderingError> {
match self.lookup_node(id) {
Ok(_) => Ok(true),
Err(DefsLookupErrorKind::NotFound) => Ok(false),
Err(e) => Err(e.into()),
}
}
pub fn width_height_to_user(&self, dpi: Dpi) -> (f64, f64) {
let dimensions = self.get_intrinsic_dimensions();
let width = dimensions.width;
let height = dimensions.height;
let view_params = Viewport::new(dpi, 0.0, 0.0);
let root = self.document.root();
let cascaded = CascadedValues::new_from_node(&root);
let values = cascaded.get();
let params = NormalizeParams::new(values, &view_params);
(width.to_user(¶ms), height.to_user(¶ms))
}
fn get_node_or_root(&self, id: Option<&str>) -> Result<Node, InternalRenderingError> {
if let Some(id) = id {
Ok(self.lookup_node(id)?)
} else {
Ok(self.document.root())
}
}
fn geometry_for_layer(
&self,
node: Node,
viewport: Rect,
user_language: &UserLanguage,
dpi: Dpi,
is_testing: bool,
) -> Result<(Rect, Rect), InternalRenderingError> {
let root = self.document.root();
let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
let cr = cairo::Context::new(&target)?;
let bbox = draw_tree(
self.session.clone(),
DrawingMode::LimitToStack { node, root },
&cr,
viewport,
user_language,
dpi,
true,
is_testing,
&mut AcquiredNodes::new(&self.document),
)?;
let ink_rect = bbox.ink_rect.unwrap_or_default();
let logical_rect = bbox.rect.unwrap_or_default();
Ok((ink_rect, logical_rect))
}
pub fn get_geometry_for_layer(
&self,
id: Option<&str>,
viewport: &cairo::Rectangle,
user_language: &UserLanguage,
dpi: Dpi,
is_testing: bool,
) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
let viewport = Rect::from(*viewport);
let node = self.get_node_or_root(id)?;
let (ink_rect, logical_rect) =
self.geometry_for_layer(node, viewport, user_language, dpi, is_testing)?;
Ok((
cairo::Rectangle::from(ink_rect),
cairo::Rectangle::from(logical_rect),
))
}
fn lookup_node(&self, id: &str) -> Result<Node, DefsLookupErrorKind> {
let node_id = NodeId::parse(id).map_err(|_| DefsLookupErrorKind::InvalidId)?;
match node_id {
NodeId::Internal(id) => self
.document
.lookup_internal_node(&id)
.ok_or(DefsLookupErrorKind::NotFound),
NodeId::External(_, _) => {
rsvg_log!(
self.session,
"the public API is not allowed to look up external references: {}",
node_id
);
Err(DefsLookupErrorKind::CannotLookupExternalReferences)
}
}
}
pub fn render_document(
&self,
cr: &cairo::Context,
viewport: &cairo::Rectangle,
user_language: &UserLanguage,
dpi: Dpi,
is_testing: bool,
) -> Result<(), InternalRenderingError> {
self.render_layer(cr, None, viewport, user_language, dpi, is_testing)
}
pub fn render_layer(
&self,
cr: &cairo::Context,
id: Option<&str>,
viewport: &cairo::Rectangle,
user_language: &UserLanguage,
dpi: Dpi,
is_testing: bool,
) -> Result<(), InternalRenderingError> {
cr.status()?;
let node = self.get_node_or_root(id)?;
let root = self.document.root();
let viewport = Rect::from(*viewport);
with_saved_cr(cr, || {
draw_tree(
self.session.clone(),
DrawingMode::LimitToStack { node, root },
cr,
viewport,
user_language,
dpi,
false,
is_testing,
&mut AcquiredNodes::new(&self.document),
)
.map(|_bbox| ())
})
}
fn get_bbox_for_element(
&self,
node: &Node,
user_language: &UserLanguage,
dpi: Dpi,
is_testing: bool,
) -> Result<BoundingBox, InternalRenderingError> {
let target = cairo::ImageSurface::create(cairo::Format::Rgb24, 1, 1)?;
let cr = cairo::Context::new(&target)?;
let node = node.clone();
draw_tree(
self.session.clone(),
DrawingMode::OnlyNode(node),
&cr,
unit_rectangle(),
user_language,
dpi,
true,
is_testing,
&mut AcquiredNodes::new(&self.document),
)
}
pub fn get_geometry_for_element(
&self,
id: Option<&str>,
user_language: &UserLanguage,
dpi: Dpi,
is_testing: bool,
) -> Result<(cairo::Rectangle, cairo::Rectangle), InternalRenderingError> {
let node = self.get_node_or_root(id)?;
let bbox = self.get_bbox_for_element(&node, user_language, dpi, is_testing)?;
let ink_rect = bbox.ink_rect.unwrap_or_default();
let logical_rect = bbox.rect.unwrap_or_default();
let ofs = (-ink_rect.x0, -ink_rect.y0);
Ok((
cairo::Rectangle::from(ink_rect.translate(ofs)),
cairo::Rectangle::from(logical_rect.translate(ofs)),
))
}
pub fn render_element(
&self,
cr: &cairo::Context,
id: Option<&str>,
element_viewport: &cairo::Rectangle,
user_language: &UserLanguage,
dpi: Dpi,
is_testing: bool,
) -> Result<(), InternalRenderingError> {
cr.status()?;
let node = self.get_node_or_root(id)?;
let bbox = self.get_bbox_for_element(&node, user_language, dpi, is_testing)?;
if bbox.ink_rect.is_none() || bbox.rect.is_none() {
return Ok(());
}
let ink_r = bbox.ink_rect.unwrap_or_default();
if ink_r.is_empty() {
return Ok(());
}
with_saved_cr(cr, || {
let factor = (element_viewport.width() / ink_r.width())
.min(element_viewport.height() / ink_r.height());
cr.translate(element_viewport.x(), element_viewport.y());
cr.scale(factor, factor);
cr.translate(-ink_r.x0, -ink_r.y0);
draw_tree(
self.session.clone(),
DrawingMode::OnlyNode(node),
cr,
unit_rectangle(),
user_language,
dpi,
false,
is_testing,
&mut AcquiredNodes::new(&self.document),
)
.map(|_bbox| ())
})
}
pub fn get_intrinsic_dimensions(&self) -> IntrinsicDimensions {
let root = self.document.root();
let cascaded = CascadedValues::new_from_node(&root);
let values = cascaded.get();
borrow_element_as!(self.document.root(), Svg).get_intrinsic_dimensions(values)
}
pub fn set_stylesheet(&mut self, css: &str) -> Result<(), LoadingError> {
let stylesheet = Stylesheet::from_data(
css,
&UrlResolver::new(None),
Origin::User,
self.session.clone(),
)?;
self.document.cascade(&[stylesheet], &self.session);
Ok(())
}
}
fn unit_rectangle() -> Rect {
Rect::from_size(1.0, 1.0)
}