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