pub mod pdf {
use std::{ffi::CString, fmt, io, mem, ptr};
use crate::Canvas;
use skia_bindings::{
self as sb, SkPDF_AttributeList, SkPDF_DateTime, SkPDF_Metadata, SkPDF_StructureElementNode,
};
use crate::{
interop::{AsStr, RustWStream, SetStr},
prelude::*,
scalar, Document, MILESTONE,
};
pub type AttributeList = Handle<SkPDF_AttributeList>;
unsafe_send_sync!(AttributeList);
impl NativeDrop for SkPDF_AttributeList {
fn drop(&mut self) {
unsafe { sb::C_SkPDF_AttributeList_destruct(self) }
}
}
impl Default for AttributeList {
fn default() -> Self {
AttributeList::from_native_c(unsafe { SkPDF_AttributeList::new() })
}
}
impl fmt::Debug for AttributeList {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AttributeList").finish()
}
}
impl AttributeList {
pub fn append_int(
&mut self,
owner: impl AsRef<str>,
name: impl AsRef<str>,
value: i32,
) -> &mut Self {
let owner = CString::new(owner.as_ref()).unwrap();
let name = CString::new(name.as_ref()).unwrap();
unsafe {
self.native_mut()
.appendInt(owner.as_ptr(), name.as_ptr(), value)
}
self
}
pub fn append_float(
&mut self,
owner: impl AsRef<str>,
name: impl AsRef<str>,
value: f32,
) -> &mut Self {
let owner = CString::new(owner.as_ref()).unwrap();
let name = CString::new(name.as_ref()).unwrap();
unsafe {
self.native_mut()
.appendFloat(owner.as_ptr(), name.as_ptr(), value)
}
self
}
pub fn append_float_array(
&mut self,
owner: impl AsRef<str>,
name: impl AsRef<str>,
value: &[f32],
) -> &mut Self {
let owner = CString::new(owner.as_ref()).unwrap();
let name = CString::new(name.as_ref()).unwrap();
unsafe {
sb::C_SkPDF_AttributeList_appendFloatArray(
self.native_mut(),
owner.as_ptr(),
name.as_ptr(),
value.as_ptr(),
value.len(),
)
}
self
}
}
#[repr(transparent)]
pub struct StructureElementNode(ptr::NonNull<SkPDF_StructureElementNode>);
impl NativeAccess for StructureElementNode {
type Native = SkPDF_StructureElementNode;
fn native(&self) -> &SkPDF_StructureElementNode {
unsafe { self.0.as_ref() }
}
fn native_mut(&mut self) -> &mut SkPDF_StructureElementNode {
unsafe { self.0.as_mut() }
}
}
impl Drop for StructureElementNode {
fn drop(&mut self) {
unsafe { sb::C_SkPDF_StructureElementNode_delete(self.native_mut()) }
}
}
impl Default for StructureElementNode {
fn default() -> Self {
Self(ptr::NonNull::new(unsafe { sb::C_SkPDF_StructureElementNode_new() }).unwrap())
}
}
impl fmt::Debug for StructureElementNode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("StructureElementNode")
.field("type_string", &self.type_string())
.field("child_vector", &self.child_vector())
.field("node_id", &self.node_id())
.field("attributes", &self.attributes())
.field("alt", &self.alt())
.field("lang", &self.lang())
.finish()
}
}
impl StructureElementNode {
pub fn new(type_string: impl AsRef<str>) -> Self {
let mut node = Self::default();
node.set_type_string(type_string);
node
}
pub fn set_type_string(&mut self, type_string: impl AsRef<str>) -> &mut Self {
self.native_mut().fTypeString.set_str(type_string);
self
}
pub fn type_string(&self) -> &str {
self.native().fTypeString.as_str()
}
pub fn set_child_vector(
&mut self,
mut child_vector: Vec<StructureElementNode>,
) -> &mut Self {
unsafe {
sb::C_SkPDF_StructureElementNode_setChildVector(
self.native_mut(),
child_vector.as_mut_ptr() as _,
child_vector.len(),
)
}
self
}
pub fn append_child(&mut self, node: StructureElementNode) -> &mut Self {
unsafe {
sb::C_SkPDF_StructElementNode_appendChild(self.native_mut(), node.0.as_ptr());
}
mem::forget(node);
self
}
pub fn child_vector(&self) -> &[StructureElementNode] {
let mut ptr = ptr::null();
unsafe {
let len = sb::C_SkPDF_StructureElementNode_getChildVector(self.native(), &mut ptr);
safer::from_raw_parts(ptr as _, len)
}
}
pub fn set_node_id(&mut self, node_id: i32) -> &mut Self {
self.native_mut().fNodeId = node_id;
self
}
pub fn node_id(&self) -> i32 {
self.native().fNodeId
}
pub fn attributes(&self) -> &AttributeList {
AttributeList::from_native_ref(&self.native().fAttributes)
}
pub fn attributes_mut(&mut self) -> &mut AttributeList {
AttributeList::from_native_ref_mut(&mut self.native_mut().fAttributes)
}
pub fn set_alt(&mut self, alt: impl AsRef<str>) -> &mut Self {
self.native_mut().fAlt.set_str(alt);
self
}
pub fn alt(&self) -> &str {
self.native().fAlt.as_str()
}
pub fn set_lang(&mut self, lang: impl AsRef<str>) -> &mut Self {
self.native_mut().fLang.set_str(lang);
self
}
pub fn lang(&self) -> &str {
self.native().fLang.as_str()
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
#[repr(C)]
pub struct DateTime {
pub time_zone_minutes: i16,
pub year: u16,
pub month: u8,
pub day_of_week: u8,
pub day: u8,
pub hour: u8,
pub minute: u8,
pub second: u8,
}
native_transmutable!(SkPDF_DateTime, DateTime, date_time_layout);
#[derive(Debug)]
pub struct Metadata {
pub title: String,
pub author: String,
pub subject: String,
pub keywords: String,
pub creator: String,
pub producer: String,
pub creation: Option<DateTime>,
pub modified: Option<DateTime>,
pub lang: String,
pub raster_dpi: Option<scalar>,
pub pdf_a: bool,
pub encoding_quality: Option<i32>,
pub structure_element_tree_root: Option<StructureElementNode>,
pub compression_level: CompressionLevel,
}
impl Default for Metadata {
fn default() -> Self {
Self {
title: Default::default(),
author: Default::default(),
subject: Default::default(),
keywords: Default::default(),
creator: Default::default(),
producer: format!("Skia/PDF m{}", MILESTONE),
creation: Default::default(),
modified: Default::default(),
lang: Default::default(),
raster_dpi: Default::default(),
pdf_a: Default::default(),
encoding_quality: Default::default(),
structure_element_tree_root: None,
compression_level: Default::default(),
}
}
}
pub type CompressionLevel = skia_bindings::SkPDF_Metadata_CompressionLevel;
variant_name!(CompressionLevel::HighButSlow);
pub fn new_document<'a>(
writer: &'a mut impl io::Write,
metadata: Option<&Metadata>,
) -> Document<'a> {
let mut md = InternalMetadata::default();
if let Some(metadata) = metadata {
let internal = md.native_mut();
internal.fTitle.set_str(&metadata.title);
internal.fAuthor.set_str(&metadata.author);
internal.fSubject.set_str(&metadata.subject);
internal.fKeywords.set_str(&metadata.keywords);
internal.fCreator.set_str(&metadata.creator);
internal.fProducer.set_str(&metadata.producer);
if let Some(creation) = metadata.creation {
internal.fCreation = creation.into_native();
}
if let Some(modified) = metadata.modified {
internal.fModified = modified.into_native();
}
internal.fLang.set_str(&metadata.lang);
if let Some(raster_dpi) = metadata.raster_dpi {
internal.fRasterDPI = raster_dpi;
}
internal.fPDFA = metadata.pdf_a;
if let Some(encoding_quality) = metadata.encoding_quality {
internal.fEncodingQuality = encoding_quality
}
if let Some(structure_element_tree) = &metadata.structure_element_tree_root {
internal.fStructureElementTreeRoot = structure_element_tree.0.as_ptr();
}
internal.fCompressionLevel = metadata.compression_level
}
#[cfg(all(feature = "textlayout", feature = "embed-icudtl"))]
crate::icu::init();
let mut stream = RustWStream::new(writer);
let document = RCHandle::from_ptr(unsafe {
sb::C_SkPDF_MakeDocument(stream.stream_mut(), md.native())
})
.unwrap();
Document::new(stream, document)
}
type InternalMetadata = Handle<SkPDF_Metadata>;
impl NativeDrop for SkPDF_Metadata {
fn drop(&mut self) {
unsafe { sb::C_SkPDF_Metadata_destruct(self) }
}
}
impl Default for Handle<SkPDF_Metadata> {
fn default() -> Self {
Self::construct(|pdf_md| unsafe { sb::C_SkPDF_Metadata_Construct(pdf_md) })
}
}
pub fn set_node_id(canvas: &Canvas, node_id: i32) {
unsafe {
sb::C_SkPDF_SetNodeId(canvas.native_mut(), node_id);
}
}
}
#[cfg(test)]
mod tests {
use crate::pdf::StructureElementNode;
use super::pdf;
#[test]
fn create_attribute_list() {
let mut _al = pdf::AttributeList::default();
_al.append_float_array("Owner", "Name", &[1.0, 2.0, 3.0]);
}
#[test]
fn structure_element_node_child_vector() {
let mut root = StructureElementNode::new("root");
root.append_child(StructureElementNode::new("nested"));
root.append_child(StructureElementNode::new("nested2"));
let v = root.child_vector();
assert_eq!(v[0].type_string(), "nested");
assert_eq!(v[1].type_string(), "nested2");
}
}