1use std::{fmt, ptr};
2
3use skia_bindings::{self as sb, SkDocument, SkRefCntBase};
4
5use crate::{interop::RustWStream, prelude::*, Canvas, Rect, Size};
6
7pub struct Document<'a, State = state::Open> {
8 // Order matters here, first the document must be dropped _and then_ the stream.
9 document: RCHandle<SkDocument>,
10 stream: RustWStream<'a>,
11
12 state: State,
13}
14
15require_type_equality!(sb::SkDocument_INHERITED, sb::SkRefCnt);
16
17impl NativeRefCountedBase for SkDocument {
18 type Base = SkRefCntBase;
19}
20
21impl<State: fmt::Debug> fmt::Debug for Document<'_, State> {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 f&mut DebugStruct<'_, '_>.debug_struct("Document")
24 .field(name:"state", &self.state)
25 .finish()
26 }
27}
28
29pub mod state {
30 use std::{fmt, ptr};
31
32 use skia_bindings::SkCanvas;
33
34 use crate::Canvas;
35
36 /// Document is currently open. May contain several pages.
37 #[derive(Debug)]
38 pub struct Open {
39 pub(crate) pages: usize,
40 }
41
42 /// Document is currently on a page and can be drawn onto.
43 pub struct OnPage {
44 pub(crate) page: usize,
45 pub(crate) canvas: ptr::NonNull<SkCanvas>,
46 }
47
48 impl fmt::Debug for OnPage {
49 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
50 f.debug_struct("OnPage")
51 .field("page", &self.page)
52 .field(
53 "canvas",
54 Canvas::borrow_from_native(unsafe { self.canvas.as_ref() }),
55 )
56 .finish()
57 }
58 }
59}
60
61impl<State> Document<'_, State> {
62 pub fn abort(mut self) {
63 unsafe { self.document.native_mut().abort() }
64 drop(self)
65 }
66}
67
68impl<'a> Document<'a, state::Open> {
69 pub(crate) fn new(stream: RustWStream<'a>, document: RCHandle<SkDocument>) -> Self {
70 Document {
71 document,
72 stream,
73 state: state::Open { pages: 0 },
74 }
75 }
76
77 /// The number of pages in this document.
78 pub fn pages(&self) -> usize {
79 self.state.pages
80 }
81
82 // This function consumes the document and returns a document containing a
83 // canvas that represents the page it's currently drawing on.
84 pub fn begin_page(
85 mut self,
86 size: impl Into<Size>,
87 content: Option<&Rect>,
88 ) -> Document<'a, state::OnPage> {
89 let size = size.into();
90 let canvas = unsafe {
91 self.document.native_mut().beginPage(
92 size.width,
93 size.height,
94 content.native_ptr_or_null(),
95 )
96 };
97
98 Document {
99 stream: self.stream,
100 document: self.document,
101 state: state::OnPage {
102 canvas: ptr::NonNull::new(canvas).unwrap(),
103 page: self.state.pages + 1,
104 },
105 }
106 }
107
108 /// Close the document and return the encoded representation.
109 ///
110 /// This function consumes and drops the document.
111 pub fn close(mut self) {
112 unsafe {
113 self.document.native_mut().close();
114 };
115 }
116}
117
118impl<'a> Document<'a, state::OnPage> {
119 /// The current page we are currently drawing on.
120 pub fn page(&self) -> usize {
121 self.state.page
122 }
123
124 /// Borrows the canvas for the current page on the document.
125 pub fn canvas(&mut self) -> &Canvas {
126 Canvas::borrow_from_native(unsafe { self.state.canvas.as_ref() })
127 }
128
129 /// Ends the page.
130 ///
131 /// This function consumes the document and returns a new open document that
132 /// contains the pages drawn so far.
133 pub fn end_page(mut self) -> Document<'a> {
134 unsafe {
135 self.document.native_mut().endPage();
136 }
137
138 Document {
139 stream: self.stream,
140 document: self.document,
141 state: state::Open {
142 pages: self.state.page,
143 },
144 }
145
146 // TODO: Think about providing a close() function that implicitly ends the page
147 // and calls close() on the Open document.
148 // TODO: Think about providing a begin_page() function that implicitly ends the
149 // current page.
150 }
151}
152