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