1pub mod pdf {
2 use std::{ffi::CString, fmt, io, marker::PhantomData, 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, Canvas, Document, MILESTONE,
12 };
13
14 #[repr(transparent)]
15 pub struct AttributeList<'a>(Handle<SkPDF_AttributeList>, PhantomData<&'a ()>);
16 unsafe_send_sync!(AttributeList<'_>);
17
18 impl NativeDrop for SkPDF_AttributeList {
19 fn drop(&mut self) {
20 unsafe { sb::C_SkPDF_AttributeList_destruct(self) }
21 }
22 }
23
24 impl Default for AttributeList<'_> {
25 fn default() -> Self {
26 Self(
27 Handle::from_native_c(unsafe { SkPDF_AttributeList::new() }),
28 PhantomData,
29 )
30 }
31 }
32
33 impl fmt::Debug for AttributeList<'_> {
34 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
35 f.debug_struct("AttributeList").finish()
36 }
37 }
38
39 impl<'a> AttributeList<'a> {
50 pub fn append_int(
51 &mut self,
52 owner: &'a CString,
53 name: &'a CString,
54 value: i32,
55 ) -> &mut Self {
56 unsafe {
57 self.0
58 .native_mut()
59 .appendInt(owner.as_ptr(), name.as_ptr(), value)
60 }
61 self
62 }
63
64 pub fn append_float(
65 &mut self,
66 owner: &'a CString,
67 name: &'a CString,
68 value: f32,
69 ) -> &mut Self {
70 unsafe {
71 self.0
72 .native_mut()
73 .appendFloat(owner.as_ptr(), name.as_ptr(), value)
74 }
75 self
76 }
77
78 pub fn append_float_array(
79 &mut self,
80 owner: &'a CString,
81 name: &'a CString,
82 value: &[f32],
83 ) -> &mut Self {
84 unsafe {
85 sb::C_SkPDF_AttributeList_appendFloatArray(
86 self.0.native_mut(),
87 owner.as_ptr(),
88 name.as_ptr(),
89 value.as_ptr(),
90 value.len(),
91 )
92 }
93 self
94 }
95
96 pub fn append_name(
97 &mut self,
98 owner: &'a CString,
99 name: &'a CString,
100 value: &'a CString,
101 ) -> &mut Self {
102 unsafe {
103 sb::C_SkPDF_AttributeList_appendName(
104 self.0.native_mut(),
105 owner.as_ptr(),
106 name.as_ptr(),
107 value.as_ptr(),
108 )
109 }
110 self
111 }
112
113 pub fn append_text_string(
114 &mut self,
115 owner: &'a CString,
116 name: &'a CString,
117 value: &'a CString,
118 ) -> &mut Self {
119 unsafe {
120 sb::C_SkPDF_AttributeList_appendTextString(
121 self.0.native_mut(),
122 owner.as_ptr(),
123 name.as_ptr(),
124 value.as_ptr(),
125 )
126 }
127 self
128 }
129
130 pub fn append_node_id_array(
131 &mut self,
132 owner: &'a CString,
133 name: &'a CString,
134 node_ids: &[i32],
135 ) -> &mut Self {
136 unsafe {
137 sb::C_SkPDF_AttributeList_appendNodeIdArray(
138 self.0.native_mut(),
139 owner.as_ptr(),
140 name.as_ptr(),
141 node_ids.as_ptr(),
142 node_ids.len(),
143 )
144 }
145 self
146 }
147 }
148
149 #[repr(transparent)]
150 pub struct StructureElementNode<'a>(
151 ptr::NonNull<SkPDF_StructureElementNode>,
152 PhantomData<&'a ()>,
153 );
154
155 impl NativeAccess for StructureElementNode<'_> {
156 type Native = SkPDF_StructureElementNode;
157
158 fn native(&self) -> &SkPDF_StructureElementNode {
159 unsafe { self.0.as_ref() }
160 }
161 fn native_mut(&mut self) -> &mut SkPDF_StructureElementNode {
162 unsafe { self.0.as_mut() }
163 }
164 }
165
166 impl Drop for StructureElementNode<'_> {
167 fn drop(&mut self) {
168 unsafe { sb::C_SkPDF_StructureElementNode_delete(self.native_mut()) }
169 }
170 }
171
172 impl Default for StructureElementNode<'_> {
173 fn default() -> Self {
174 Self(
175 ptr::NonNull::new(unsafe { sb::C_SkPDF_StructureElementNode_new() }).unwrap(),
176 PhantomData,
177 )
178 }
179 }
180
181 impl fmt::Debug for StructureElementNode<'_> {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 f.debug_struct("StructureElementNode")
184 .field("type_string", &self.type_string())
185 .field("child_vector", &self.child_vector())
186 .field("node_id", &self.node_id())
187 .field("attributes", &self.attributes())
188 .field("alt", &self.alt())
189 .field("lang", &self.lang())
190 .finish()
191 }
192 }
193
194 impl<'a> StructureElementNode<'a> {
199 pub fn new(type_string: impl AsRef<str>) -> Self {
200 let mut node = Self::default();
201 node.set_type_string(type_string);
202 node
203 }
204
205 pub fn set_type_string(&mut self, type_string: impl AsRef<str>) -> &mut Self {
206 self.native_mut().fTypeString.set_str(type_string);
207 self
208 }
209
210 pub fn type_string(&self) -> &str {
211 self.native().fTypeString.as_str()
212 }
213
214 pub fn set_child_vector(
215 &mut self,
216 mut child_vector: Vec<StructureElementNode>,
217 ) -> &mut Self {
218 unsafe {
220 sb::C_SkPDF_StructureElementNode_setChildVector(
221 self.native_mut(),
222 child_vector.as_mut_ptr() as _,
223 child_vector.len(),
224 )
225 }
226 self
227 }
228
229 pub fn append_child(&mut self, node: StructureElementNode) -> &mut Self {
230 unsafe {
231 sb::C_SkPDF_StructElementNode_appendChild(self.native_mut(), node.0.as_ptr());
232 }
233 mem::forget(node);
234 self
235 }
236
237 pub fn child_vector(&self) -> &[StructureElementNode] {
238 let mut ptr = ptr::null();
239 unsafe {
240 let len = sb::C_SkPDF_StructureElementNode_getChildVector(self.native(), &mut ptr);
241 safer::from_raw_parts(ptr as _, len)
242 }
243 }
244
245 pub fn set_node_id(&mut self, node_id: i32) -> &mut Self {
246 self.native_mut().fNodeId = node_id;
247 self
248 }
249
250 pub fn node_id(&self) -> i32 {
251 self.native().fNodeId
252 }
253
254 pub fn attributes(&self) -> &AttributeList<'a> {
255 unsafe { transmute_ref(Handle::from_native_ref(&self.native().fAttributes)) }
256 }
257
258 pub fn attributes_mut(&mut self) -> &mut AttributeList<'a> {
259 unsafe {
260 transmute_ref_mut(Handle::from_native_ref_mut(
261 &mut self.native_mut().fAttributes,
262 ))
263 }
264 }
265
266 pub fn set_alt(&mut self, alt: impl AsRef<str>) -> &mut Self {
267 self.native_mut().fAlt.set_str(alt);
268 self
269 }
270
271 pub fn alt(&self) -> &str {
272 self.native().fAlt.as_str()
273 }
274
275 pub fn set_lang(&mut self, lang: impl AsRef<str>) -> &mut Self {
276 self.native_mut().fLang.set_str(lang);
277 self
278 }
279
280 pub fn lang(&self) -> &str {
281 self.native().fLang.as_str()
282 }
283 }
284
285 #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
286 #[repr(C)]
287 pub struct DateTime {
288 pub time_zone_minutes: i16,
290 pub year: u16,
292 pub month: u8,
294 pub day_of_week: u8,
296 pub day: u8,
298 pub hour: u8,
300 pub minute: u8,
302 pub second: u8,
304 }
305
306 native_transmutable!(SkPDF_DateTime, DateTime);
307
308 #[derive(Debug)]
310 pub struct Metadata<'a> {
311 pub title: String,
313 pub author: String,
315 pub subject: String,
317 pub keywords: String,
320 pub creator: String,
323 pub producer: String,
325 pub creation: Option<DateTime>,
328 pub modified: Option<DateTime>,
331 pub lang: String,
335 pub raster_dpi: Option<scalar>,
341 pub pdf_a: bool,
345 pub encoding_quality: Option<i32>,
350
351 pub structure_element_tree_root: Option<StructureElementNode<'a>>,
354
355 pub outline: Outline,
356
357 pub compression_level: CompressionLevel,
360 }
361
362 impl Default for Metadata<'_> {
363 fn default() -> Self {
364 Self {
365 title: Default::default(),
366 author: Default::default(),
367 subject: Default::default(),
368 keywords: Default::default(),
369 creator: Default::default(),
370 producer: format!("Skia/PDF m{MILESTONE}"),
371 creation: Default::default(),
372 modified: Default::default(),
373 lang: Default::default(),
374 raster_dpi: Default::default(),
375 pdf_a: Default::default(),
376 encoding_quality: Default::default(),
377 structure_element_tree_root: None,
378 outline: Outline::None,
379 compression_level: Default::default(),
380 }
381 }
382 }
383
384 pub type Outline = skia_bindings::SkPDF_Metadata_Outline;
385 variant_name!(Outline::StructureElements);
386
387 pub type CompressionLevel = skia_bindings::SkPDF_Metadata_CompressionLevel;
388 variant_name!(CompressionLevel::HighButSlow);
389
390 pub fn new_document<'a>(
401 writer: &'a mut impl io::Write,
402 metadata: Option<&'a Metadata<'a>>,
404 ) -> Document<'a> {
405 let mut md = InternalMetadata::default();
406 if let Some(metadata) = metadata {
407 let internal = md.native_mut();
408 internal.fTitle.set_str(&metadata.title);
409 internal.fAuthor.set_str(&metadata.author);
410 internal.fSubject.set_str(&metadata.subject);
411 internal.fKeywords.set_str(&metadata.keywords);
412 internal.fCreator.set_str(&metadata.creator);
413 internal.fProducer.set_str(&metadata.producer);
414 if let Some(creation) = metadata.creation {
415 internal.fCreation = creation.into_native();
416 }
417 if let Some(modified) = metadata.modified {
418 internal.fModified = modified.into_native();
419 }
420 internal.fLang.set_str(&metadata.lang);
421 if let Some(raster_dpi) = metadata.raster_dpi {
422 internal.fRasterDPI = raster_dpi;
423 }
424 internal.fPDFA = metadata.pdf_a;
425 if let Some(encoding_quality) = metadata.encoding_quality {
426 internal.fEncodingQuality = encoding_quality
427 }
428 if let Some(structure_element_tree) = &metadata.structure_element_tree_root {
429 internal.fStructureElementTreeRoot = structure_element_tree.0.as_ptr();
430 }
431 internal.fOutline = metadata.outline;
432 internal.fCompressionLevel = metadata.compression_level
433 }
434
435 #[cfg(all(feature = "textlayout", feature = "embed-icudtl"))]
437 crate::icu::init();
438
439 let mut stream = RustWStream::new(writer);
440 let document = RCHandle::from_ptr(unsafe {
441 sb::C_SkPDF_MakeDocument(stream.stream_mut(), md.native())
442 })
443 .unwrap();
444
445 Document::new(stream, document)
446 }
447
448 type InternalMetadata = Handle<SkPDF_Metadata>;
453
454 impl NativeDrop for SkPDF_Metadata {
455 fn drop(&mut self) {
456 unsafe { sb::C_SkPDF_Metadata_destruct(self) }
457 }
458 }
459
460 impl Default for Handle<SkPDF_Metadata> {
461 fn default() -> Self {
462 Self::construct(|pdf_md| unsafe { sb::C_SkPDF_Metadata_Construct(pdf_md) })
463 }
464 }
465
466 pub mod node_id {
467 pub const NOTHING: i32 = 0;
468 pub const OTHER_ARTIFACT: i32 = -1;
469 pub const PAGINATION_ARTIFACT: i32 = -2;
470 pub const PAGINATION_HEADER_ARTIFACT: i32 = -3;
471 pub const PAGINATION_FOOTER_ARTIFACT: i32 = -4;
472 pub const PAGINATION_WATERMARK_ARTIFACT: i32 = -5;
473 pub const LAYOUT_ARTIFACT: i32 = -6;
474 pub const PAGE_ARTIFACT: i32 = -7;
475 pub const BACKGROUND_ARTIFACT: i32 = -8;
476 }
477
478 pub fn set_node_id(canvas: &Canvas, node_id: i32) {
488 unsafe {
489 sb::C_SkPDF_SetNodeId(canvas.native_mut(), node_id);
490 }
491 }
492}
493
494#[cfg(test)]
495mod tests {
496 use std::ffi::CString;
497
498 use crate::{
499 pdf::{self, Metadata, StructureElementNode},
500 Color, Paint, Rect,
501 };
502
503 #[test]
504 fn create_attribute_list() {
505 let mut _al = pdf::AttributeList::default();
506 let owner = CString::new("Owner").unwrap();
507 let name = CString::new("Name").unwrap();
508 _al.append_float_array(&owner, &name, &[1.0, 2.0, 3.0]);
509 }
510
511 #[test]
512 fn structure_element_node_child_vector() {
513 let mut root = StructureElementNode::new("root");
514 root.append_child(StructureElementNode::new("nested"));
515 root.append_child(StructureElementNode::new("nested2"));
516 let v = root.child_vector();
517 assert_eq!(v[0].type_string(), "nested");
518 assert_eq!(v[1].type_string(), "nested2");
519 }
520
521 #[test]
522 fn generate_pdf_with_structure_and_attributes() {
523 let layout_owner = CString::new("Layout").unwrap();
525 let bbox_name = CString::new("BBox").unwrap();
526 let table_owner = CString::new("Table").unwrap();
527 let colspan_name = CString::new("ColSpan").unwrap();
528 let rowspan_name = CString::new("RowSpan").unwrap();
529
530 let mut root = StructureElementNode::new("Document");
532 root.set_node_id(1);
533
534 let mut paragraph = StructureElementNode::new("P");
536 paragraph.set_node_id(2);
537 paragraph.attributes_mut().append_float_array(
538 &layout_owner,
539 &bbox_name,
540 &[10.0, 10.0, 200.0, 50.0],
541 );
542
543 let mut table = StructureElementNode::new("Table");
545 table.set_node_id(3);
546 table
547 .attributes_mut()
548 .append_int(&table_owner, &colspan_name, 2)
549 .append_int(&table_owner, &rowspan_name, 3);
550
551 root.append_child(paragraph);
552 root.append_child(table);
553
554 let metadata = Metadata {
556 title: "Test Document with Structure".to_string(),
557 author: "Rust Skia Test".to_string(),
558 structure_element_tree_root: Some(root),
559 ..Default::default()
560 };
561
562 let mut output = Vec::new();
564 let document = pdf::new_document(&mut output, Some(&metadata));
565
566 let mut page = document.begin_page((200, 200), None);
568 let canvas = page.canvas();
569
570 pdf::set_node_id(canvas, 2);
572 let mut paint = Paint::default();
573 paint.set_color(Color::from_rgb(100, 150, 200));
574 canvas.draw_rect(Rect::from_xywh(10.0, 10.0, 190.0, 40.0), &paint);
575
576 pdf::set_node_id(canvas, 3);
578 paint.set_color(Color::from_rgb(200, 150, 100));
579 canvas.draw_rect(Rect::from_xywh(10.0, 60.0, 190.0, 130.0), &paint);
580
581 let document = page.end_page();
582 document.close();
583
584 assert!(!output.is_empty(), "PDF output should not be empty");
586
587 assert!(
589 output.starts_with(b"%PDF-"),
590 "Output should start with PDF header"
591 );
592 assert!(
593 output.windows(5).any(|w| w == b"%%EOF"),
594 "Output should contain EOF marker"
595 );
596
597 let pdf_str = String::from_utf8_lossy(&output);
599 assert!(
600 pdf_str.contains("/StructTreeRoot") || pdf_str.contains("Document"),
601 "PDF should contain structure tree references"
602 );
603 }
604}