1pub mod pdf {
2 use std::{ffi::CString, fmt, io, mem, ptr};
3
4 use crate::Canvas;
5
6 use skia_bindings::{
7 self as sb, SkPDF_AttributeList, SkPDF_DateTime, SkPDF_Metadata, SkPDF_StructureElementNode,
8 };
9
10 use crate::{
11 interop::{AsStr, RustWStream, SetStr},
12 prelude::*,
13 scalar, Document, MILESTONE,
14 };
15
16 pub type AttributeList = Handle<SkPDF_AttributeList>;
17 unsafe_send_sync!(AttributeList);
18
19 impl NativeDrop for SkPDF_AttributeList {
20 fn drop(&mut self) {
21 unsafe { sb::C_SkPDF_AttributeList_destruct(self) }
22 }
23 }
24
25 impl Default for AttributeList {
26 fn default() -> Self {
27 AttributeList::from_native_c(unsafe { SkPDF_AttributeList::new() })
28 }
29 }
30
31 impl fmt::Debug for AttributeList {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 f.debug_struct("AttributeList").finish()
34 }
35 }
36
37 /// Attributes for nodes in the PDF tree.
38 ///
39 /// Each attribute must have an owner (e.g. "Layout", "List", "Table", etc)
40 /// and an attribute name (e.g. "BBox", "RowSpan", etc.) from PDF32000_2008 14.8.5,
41 /// and then a value of the proper type according to the spec.
42 impl AttributeList {
43 pub fn append_int(
44 &mut self,
45 owner: impl AsRef<str>,
46 name: impl AsRef<str>,
47 value: i32,
48 ) -> &mut Self {
49 let owner = CString::new(owner.as_ref()).unwrap();
50 let name = CString::new(name.as_ref()).unwrap();
51 unsafe {
52 self.native_mut()
53 .appendInt(owner.as_ptr(), name.as_ptr(), value)
54 }
55 self
56 }
57
58 pub fn append_float(
59 &mut self,
60 owner: impl AsRef<str>,
61 name: impl AsRef<str>,
62 value: f32,
63 ) -> &mut Self {
64 let owner = CString::new(owner.as_ref()).unwrap();
65 let name = CString::new(name.as_ref()).unwrap();
66 unsafe {
67 self.native_mut()
68 .appendFloat(owner.as_ptr(), name.as_ptr(), value)
69 }
70 self
71 }
72
73 pub fn append_float_array(
74 &mut self,
75 owner: impl AsRef<str>,
76 name: impl AsRef<str>,
77 value: &[f32],
78 ) -> &mut Self {
79 let owner = CString::new(owner.as_ref()).unwrap();
80 let name = CString::new(name.as_ref()).unwrap();
81 unsafe {
82 sb::C_SkPDF_AttributeList_appendFloatArray(
83 self.native_mut(),
84 owner.as_ptr(),
85 name.as_ptr(),
86 value.as_ptr(),
87 value.len(),
88 )
89 }
90 self
91 }
92 }
93
94 #[repr(transparent)]
95 pub struct StructureElementNode(ptr::NonNull<SkPDF_StructureElementNode>);
96
97 impl NativeAccess for StructureElementNode {
98 type Native = SkPDF_StructureElementNode;
99
100 fn native(&self) -> &SkPDF_StructureElementNode {
101 unsafe { self.0.as_ref() }
102 }
103 fn native_mut(&mut self) -> &mut SkPDF_StructureElementNode {
104 unsafe { self.0.as_mut() }
105 }
106 }
107
108 impl Drop for StructureElementNode {
109 fn drop(&mut self) {
110 unsafe { sb::C_SkPDF_StructureElementNode_delete(self.native_mut()) }
111 }
112 }
113
114 impl Default for StructureElementNode {
115 fn default() -> Self {
116 Self(ptr::NonNull::new(unsafe { sb::C_SkPDF_StructureElementNode_new() }).unwrap())
117 }
118 }
119
120 impl fmt::Debug for StructureElementNode {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 f.debug_struct("StructureElementNode")
123 .field("type_string", &self.type_string())
124 .field("child_vector", &self.child_vector())
125 .field("node_id", &self.node_id())
126 .field("attributes", &self.attributes())
127 .field("alt", &self.alt())
128 .field("lang", &self.lang())
129 .finish()
130 }
131 }
132
133 /// A node in a PDF structure tree, giving a semantic representation
134 /// of the content. Each node ID is associated with content
135 /// by passing the [`crate::Canvas`] and node ID to [`set_node_id()`] when drawing.
136 /// NodeIDs should be unique within each tree.
137 impl StructureElementNode {
138 pub fn new(type_string: impl AsRef<str>) -> Self {
139 let mut node = Self::default();
140 node.set_type_string(type_string);
141 node
142 }
143
144 pub fn set_type_string(&mut self, type_string: impl AsRef<str>) -> &mut Self {
145 self.native_mut().fTypeString.set_str(type_string);
146 self
147 }
148
149 pub fn type_string(&self) -> &str {
150 self.native().fTypeString.as_str()
151 }
152
153 pub fn set_child_vector(
154 &mut self,
155 mut child_vector: Vec<StructureElementNode>,
156 ) -> &mut Self {
157 // strategy is to move them out by setting them to nullptr (drop() will handle a nullptr on the rust side)
158 unsafe {
159 sb::C_SkPDF_StructureElementNode_setChildVector(
160 self.native_mut(),
161 child_vector.as_mut_ptr() as _,
162 child_vector.len(),
163 )
164 }
165 self
166 }
167
168 pub fn append_child(&mut self, node: StructureElementNode) -> &mut Self {
169 unsafe {
170 sb::C_SkPDF_StructElementNode_appendChild(self.native_mut(), node.0.as_ptr());
171 }
172 mem::forget(node);
173 self
174 }
175
176 pub fn child_vector(&self) -> &[StructureElementNode] {
177 let mut ptr = ptr::null();
178 unsafe {
179 let len = sb::C_SkPDF_StructureElementNode_getChildVector(self.native(), &mut ptr);
180 safer::from_raw_parts(ptr as _, len)
181 }
182 }
183
184 pub fn set_node_id(&mut self, node_id: i32) -> &mut Self {
185 self.native_mut().fNodeId = node_id;
186 self
187 }
188
189 pub fn node_id(&self) -> i32 {
190 self.native().fNodeId
191 }
192
193 pub fn attributes(&self) -> &AttributeList {
194 AttributeList::from_native_ref(&self.native().fAttributes)
195 }
196
197 pub fn attributes_mut(&mut self) -> &mut AttributeList {
198 AttributeList::from_native_ref_mut(&mut self.native_mut().fAttributes)
199 }
200
201 pub fn set_alt(&mut self, alt: impl AsRef<str>) -> &mut Self {
202 self.native_mut().fAlt.set_str(alt);
203 self
204 }
205
206 pub fn alt(&self) -> &str {
207 self.native().fAlt.as_str()
208 }
209
210 pub fn set_lang(&mut self, lang: impl AsRef<str>) -> &mut Self {
211 self.native_mut().fLang.set_str(lang);
212 self
213 }
214
215 pub fn lang(&self) -> &str {
216 self.native().fLang.as_str()
217 }
218 }
219
220 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
221 #[repr(C)]
222 pub struct DateTime {
223 /// The number of minutes that this is ahead of or behind UTC.
224 pub time_zone_minutes: i16,
225 /// e.g. 2005
226 pub year: u16,
227 /// 1..12
228 pub month: u8,
229 /// 0..6, 0==Sunday
230 pub day_of_week: u8,
231 /// 1..31
232 pub day: u8,
233 /// 0..23
234 pub hour: u8,
235 /// 0..59
236 pub minute: u8,
237 /// 0..59
238 pub second: u8,
239 }
240
241 native_transmutable!(SkPDF_DateTime, DateTime, date_time_layout);
242
243 /// Optional metadata to be passed into the PDF factory function.
244 #[derive(Debug)]
245 pub struct Metadata {
246 /// The document's title.
247 pub title: String,
248 /// The name of the person who created the document.
249 pub author: String,
250 /// The subject of the document.
251 pub subject: String,
252 /// Keywords associated with the document. Commas may be used to delineate keywords within
253 /// the string.
254 pub keywords: String,
255 /// If the document was converted to PDF from another format, the name of the conforming
256 /// product that created the original document from which it was converted.
257 pub creator: String,
258 /// The product that is converting this document to PDF.
259 pub producer: String,
260 /// The date and time the document was created.
261 pub creation: Option<DateTime>,
262 /// The date and time the document was most recently modified.
263 pub modified: Option<DateTime>,
264 /// The natural language of the text in the PDF. If `lang` is empty, the root
265 /// StructureElementNode::lang will be used (if not empty). Text not in
266 /// this language should be marked with StructureElementNode::lang.
267 pub lang: String,
268 /// The DPI (pixels-per-inch) at which features without native PDF support
269 /// will be rasterized (e.g. draw image with perspective, draw text with
270 /// perspective, ...) A larger DPI would create a PDF that reflects the
271 /// original intent with better fidelity, but it can make for larger PDF
272 /// files too, which would use more memory while rendering, and it would be
273 /// slower to be processed or sent online or to printer.
274 pub raster_dpi: Option<scalar>,
275 /// If `true`, include XMP metadata, a document UUID, and `s_rgb` output intent
276 /// information. This adds length to the document and makes it
277 /// non-reproducible, but are necessary features for PDF/A-2b conformance
278 pub pdf_a: bool,
279 /// Encoding quality controls the trade-off between size and quality. By default this is set
280 /// to 101 percent, which corresponds to lossless encoding. If this value is set to a value
281 /// <= 100, and the image is opaque, it will be encoded (using JPEG) with that quality
282 /// setting.
283 pub encoding_quality: Option<i32>,
284
285 pub structure_element_tree_root: Option<StructureElementNode>,
286
287 /// PDF streams may be compressed to save space.
288 /// Use this to specify the desired compression vs time tradeoff.
289 pub compression_level: CompressionLevel,
290 }
291
292 impl Default for Metadata {
293 fn default() -> Self {
294 Self {
295 title: Default::default(),
296 author: Default::default(),
297 subject: Default::default(),
298 keywords: Default::default(),
299 creator: Default::default(),
300 producer: format!("Skia/PDF m{}", MILESTONE),
301 creation: Default::default(),
302 modified: Default::default(),
303 lang: Default::default(),
304 raster_dpi: Default::default(),
305 pdf_a: Default::default(),
306 encoding_quality: Default::default(),
307 structure_element_tree_root: None,
308 compression_level: Default::default(),
309 }
310 }
311 }
312
313 pub type CompressionLevel = skia_bindings::SkPDF_Metadata_CompressionLevel;
314 variant_name!(CompressionLevel::HighButSlow);
315
316 /// Create a PDF-backed document.
317 ///
318 /// PDF pages are sized in point units. 1 pt == 1/72 inch == 127/360 mm.
319 ///
320 /// * `metadata` - a PDFmetadata object. Any fields may be left empty.
321 ///
322 /// @returns `None` if there is an error, otherwise a newly created PDF-backed [`Document`].
323 pub fn new_document<'a>(
324 writer: &'a mut impl io::Write,
325 metadata: Option<&Metadata>,
326 ) -> Document<'a> {
327 let mut md = InternalMetadata::default();
328 if let Some(metadata) = metadata {
329 let internal = md.native_mut();
330 internal.fTitle.set_str(&metadata.title);
331 internal.fAuthor.set_str(&metadata.author);
332 internal.fSubject.set_str(&metadata.subject);
333 internal.fKeywords.set_str(&metadata.keywords);
334 internal.fCreator.set_str(&metadata.creator);
335 internal.fProducer.set_str(&metadata.producer);
336 if let Some(creation) = metadata.creation {
337 internal.fCreation = creation.into_native();
338 }
339 if let Some(modified) = metadata.modified {
340 internal.fModified = modified.into_native();
341 }
342 internal.fLang.set_str(&metadata.lang);
343 if let Some(raster_dpi) = metadata.raster_dpi {
344 internal.fRasterDPI = raster_dpi;
345 }
346 internal.fPDFA = metadata.pdf_a;
347 if let Some(encoding_quality) = metadata.encoding_quality {
348 internal.fEncodingQuality = encoding_quality
349 }
350 if let Some(structure_element_tree) = &metadata.structure_element_tree_root {
351 internal.fStructureElementTreeRoot = structure_element_tree.0.as_ptr();
352 }
353 internal.fCompressionLevel = metadata.compression_level
354 }
355
356 // We enable harfbuzz font sub-setting in PDF documents if textlayout is enabled.
357 #[cfg(all(feature = "textlayout", feature = "embed-icudtl"))]
358 crate::icu::init();
359
360 let mut stream = RustWStream::new(writer);
361 let document = RCHandle::from_ptr(unsafe {
362 sb::C_SkPDF_MakeDocument(stream.stream_mut(), md.native())
363 })
364 .unwrap();
365
366 Document::new(stream, document)
367 }
368
369 //
370 // Helper for constructing the internal metadata struct and setting associated strings.
371 //
372
373 type InternalMetadata = Handle<SkPDF_Metadata>;
374
375 impl NativeDrop for SkPDF_Metadata {
376 fn drop(&mut self) {
377 unsafe { sb::C_SkPDF_Metadata_destruct(self) }
378 }
379 }
380
381 impl Default for Handle<SkPDF_Metadata> {
382 fn default() -> Self {
383 Self::construct(|pdf_md| unsafe { sb::C_SkPDF_Metadata_Construct(pdf_md) })
384 }
385 }
386
387 pub fn set_node_id(canvas: &Canvas, node_id: i32) {
388 unsafe {
389 sb::C_SkPDF_SetNodeId(canvas.native_mut(), node_id);
390 }
391 }
392}
393
394#[cfg(test)]
395mod tests {
396 use crate::pdf::StructureElementNode;
397
398 use super::pdf;
399
400 #[test]
401 fn create_attribute_list() {
402 let mut _al = pdf::AttributeList::default();
403 _al.append_float_array("Owner", "Name", &[1.0, 2.0, 3.0]);
404 }
405
406 #[test]
407 fn structure_element_node_child_vector() {
408 let mut root = StructureElementNode::new("root");
409 root.append_child(StructureElementNode::new("nested"));
410 root.append_child(StructureElementNode::new("nested2"));
411 let v = root.child_vector();
412 assert_eq!(v[0].type_string(), "nested");
413 assert_eq!(v[1].type_string(), "nested2");
414 }
415}
416