1use rpdfium_core::{Matrix, Name};
10use rpdfium_font::FontEncoding;
11use rpdfium_graphics::{
12 Bitmap, BitmapFormat, BlendMode, Color, DashPattern, PathOp, TextRenderingMode,
13};
14use rpdfium_parser::object::ObjectId;
15
16#[derive(Debug, Clone, PartialEq)]
26pub enum MarkParamValue {
27 Integer(i64),
32 Float(f32),
37 String(String),
42 Blob(Vec<u8>),
47 Dictionary(Vec<(String, MarkParamValue)>),
57}
58
59#[derive(Debug, Clone, PartialEq)]
79pub struct ContentMark {
80 pub name: String,
82 pub params: Vec<(String, MarkParamValue)>,
84}
85
86impl ContentMark {
87 pub fn new(name: impl Into<String>) -> Self {
91 Self {
92 name: name.into(),
93 params: Vec::new(),
94 }
95 }
96
97 pub fn name(&self) -> &str {
101 &self.name
102 }
103
104 #[inline]
108 pub fn page_obj_mark_get_name(&self) -> &str {
109 self.name()
110 }
111
112 #[deprecated(
114 note = "use `page_obj_mark_get_name()` — matches upstream `FPDFPageObjMark_GetName`"
115 )]
116 #[inline]
117 pub fn get_name(&self) -> &str {
118 self.name()
119 }
120
121 pub fn param_count(&self) -> usize {
125 self.params.len()
126 }
127
128 #[inline]
132 pub fn page_obj_mark_count_params(&self) -> usize {
133 self.param_count()
134 }
135
136 #[deprecated(
138 note = "use `page_obj_mark_count_params()` — matches upstream `FPDFPageObjMark_CountParams`"
139 )]
140 #[inline]
141 pub fn count_params(&self) -> usize {
142 self.param_count()
143 }
144
145 pub fn param_key(&self, index: usize) -> Option<&str> {
149 self.params.get(index).map(|(k, _)| k.as_str())
150 }
151
152 #[inline]
156 pub fn page_obj_mark_get_param_key(&self, index: usize) -> Option<&str> {
157 self.param_key(index)
158 }
159
160 #[deprecated(
162 note = "use `page_obj_mark_get_param_key()` — matches upstream `FPDFPageObjMark_GetParamKey`"
163 )]
164 #[inline]
165 pub fn get_param_key(&self, index: usize) -> Option<&str> {
166 self.param_key(index)
167 }
168
169 pub fn int_param(&self, key: &str) -> Option<i64> {
174 self.params.iter().find_map(|(k, v)| {
175 if k == key {
176 if let MarkParamValue::Integer(n) = v {
177 Some(*n)
178 } else {
179 None
180 }
181 } else {
182 None
183 }
184 })
185 }
186
187 #[inline]
191 pub fn page_obj_mark_get_param_int_value(&self, key: &str) -> Option<i64> {
192 self.int_param(key)
193 }
194
195 #[deprecated(
196 note = "use `page_obj_mark_get_param_int_value()` — matches upstream `FPDFPageObjMark_GetParamIntValue`"
197 )]
198 #[inline]
199 pub fn get_param_int_value(&self, key: &str) -> Option<i64> {
200 self.int_param(key)
201 }
202
203 #[deprecated(
205 note = "use `page_obj_mark_get_param_int_value()` — matches upstream `FPDFPageObjMark_GetParamIntValue`"
206 )]
207 #[inline]
208 pub fn get_int_param(&self, key: &str) -> Option<i64> {
209 self.int_param(key)
210 }
211
212 pub fn string_param(&self, key: &str) -> Option<&str> {
217 self.params.iter().find_map(|(k, v)| {
218 if k == key {
219 if let MarkParamValue::String(s) = v {
220 Some(s.as_str())
221 } else {
222 None
223 }
224 } else {
225 None
226 }
227 })
228 }
229
230 #[inline]
234 pub fn page_obj_mark_get_param_string_value(&self, key: &str) -> Option<&str> {
235 self.string_param(key)
236 }
237
238 #[deprecated(
239 note = "use `page_obj_mark_get_param_string_value()` — matches upstream `FPDFPageObjMark_GetParamStringValue`"
240 )]
241 #[inline]
242 pub fn get_param_string_value(&self, key: &str) -> Option<&str> {
243 self.string_param(key)
244 }
245
246 #[deprecated(
248 note = "use `page_obj_mark_get_param_string_value()` — matches upstream `FPDFPageObjMark_GetParamStringValue`"
249 )]
250 #[inline]
251 pub fn get_string_param(&self, key: &str) -> Option<&str> {
252 self.string_param(key)
253 }
254
255 pub fn blob_param(&self, key: &str) -> Option<&[u8]> {
260 self.params.iter().find_map(|(k, v)| {
261 if k == key {
262 if let MarkParamValue::Blob(b) = v {
263 Some(b.as_slice())
264 } else {
265 None
266 }
267 } else {
268 None
269 }
270 })
271 }
272
273 #[inline]
277 pub fn page_obj_mark_get_param_blob_value(&self, key: &str) -> Option<&[u8]> {
278 self.blob_param(key)
279 }
280
281 #[deprecated(
282 note = "use `page_obj_mark_get_param_blob_value()` — matches upstream `FPDFPageObjMark_GetParamBlobValue`"
283 )]
284 #[inline]
285 pub fn get_param_blob_value(&self, key: &str) -> Option<&[u8]> {
286 self.blob_param(key)
287 }
288
289 #[deprecated(
291 note = "use `page_obj_mark_get_param_blob_value()` — matches upstream `FPDFPageObjMark_GetParamBlobValue`"
292 )]
293 #[inline]
294 pub fn get_blob_param(&self, key: &str) -> Option<&[u8]> {
295 self.blob_param(key)
296 }
297
298 pub fn set_int_param(&mut self, key: impl Into<String>, value: i64) {
305 let key = key.into();
306 for (k, v) in &mut self.params {
307 if *k == key {
308 *v = MarkParamValue::Integer(value);
309 return;
310 }
311 }
312 self.params.push((key, MarkParamValue::Integer(value)));
313 }
314
315 #[inline]
319 pub fn page_obj_mark_set_int_param(&mut self, key: impl Into<String>, value: i64) {
320 self.set_int_param(key, value)
321 }
322
323 pub fn set_string_param(&mut self, key: impl Into<String>, value: impl Into<String>) {
330 let key = key.into();
331 let value = value.into();
332 for (k, v) in &mut self.params {
333 if *k == key {
334 *v = MarkParamValue::String(value);
335 return;
336 }
337 }
338 self.params.push((key, MarkParamValue::String(value)));
339 }
340
341 #[inline]
345 pub fn page_obj_mark_set_string_param(
346 &mut self,
347 key: impl Into<String>,
348 value: impl Into<String>,
349 ) {
350 self.set_string_param(key, value)
351 }
352
353 pub fn float_param(&self, key: &str) -> Option<f32> {
362 self.params.iter().find_map(|(k, v)| {
363 if k == key {
364 if let MarkParamValue::Float(f) = v {
365 Some(*f)
366 } else {
367 None
368 }
369 } else {
370 None
371 }
372 })
373 }
374
375 #[inline]
379 pub fn page_obj_mark_get_param_float_value(&self, key: &str) -> Option<f32> {
380 self.float_param(key)
381 }
382
383 #[deprecated(
384 note = "use `page_obj_mark_get_param_float_value()` — matches upstream `FPDFPageObjMark_GetParamFloatValue`"
385 )]
386 #[inline]
387 pub fn get_param_float_value(&self, key: &str) -> Option<f32> {
388 self.float_param(key)
389 }
390
391 pub fn set_float_param(&mut self, key: impl Into<String>, value: f32) {
398 let key = key.into();
399 for (k, v) in &mut self.params {
400 if *k == key {
401 *v = MarkParamValue::Float(value);
402 return;
403 }
404 }
405 self.params.push((key, MarkParamValue::Float(value)));
406 }
407
408 #[inline]
412 pub fn page_obj_mark_set_float_param(&mut self, key: impl Into<String>, value: f32) {
413 self.set_float_param(key, value)
414 }
415
416 pub fn set_blob_param(&mut self, key: impl Into<String>, value: Vec<u8>) {
423 let key = key.into();
424 for (k, v) in &mut self.params {
425 if *k == key {
426 *v = MarkParamValue::Blob(value);
427 return;
428 }
429 }
430 self.params.push((key, MarkParamValue::Blob(value)));
431 }
432
433 #[inline]
437 pub fn page_obj_mark_set_blob_param(&mut self, key: impl Into<String>, value: Vec<u8>) {
438 self.set_blob_param(key, value)
439 }
440
441 pub fn remove_param(&mut self, key: &str) -> bool {
448 let before = self.params.len();
449 self.params.retain(|(k, _)| k != key);
450 self.params.len() < before
451 }
452
453 #[inline]
457 pub fn page_obj_mark_remove_param(&mut self, key: &str) -> bool {
458 self.remove_param(key)
459 }
460
461 pub fn marked_content_id(&self) -> Option<i64> {
468 self.int_param("MCID")
469 }
470
471 pub fn param_value_type(&self, key: &str) -> u32 {
481 self.params
482 .iter()
483 .find_map(|(k, v)| {
484 if k == key {
485 Some(match v {
486 MarkParamValue::Integer(_) | MarkParamValue::Float(_) => 2,
487 MarkParamValue::String(_) | MarkParamValue::Blob(_) => 3,
488 MarkParamValue::Dictionary(_) => 7,
489 })
490 } else {
491 None
492 }
493 })
494 .unwrap_or(0)
495 }
496
497 #[inline]
501 pub fn page_obj_mark_get_param_value_type(&self, key: &str) -> u32 {
502 self.param_value_type(key)
503 }
504
505 #[deprecated(
507 note = "use `page_obj_mark_get_param_value_type()` — matches upstream `FPDFPageObjMark_GetParamValueType`"
508 )]
509 #[inline]
510 pub fn get_param_value_type(&self, key: &str) -> u32 {
511 self.param_value_type(key)
512 }
513}
514
515#[derive(Debug, Clone)]
536pub struct ClipPath {
537 pub paths: Vec<Vec<PathSegment>>,
539}
540
541impl ClipPath {
542 pub fn from_rect(left: f64, bottom: f64, right: f64, top: f64) -> Self {
546 let rect_path = vec![PathSegment::Rect(left, bottom, right - left, top - bottom)];
547 Self {
548 paths: vec![rect_path],
549 }
550 }
551
552 #[inline]
554 pub fn create_clip_path(left: f64, bottom: f64, right: f64, top: f64) -> Self {
555 Self::from_rect(left, bottom, right, top)
556 }
557
558 pub fn new(paths: Vec<Vec<PathSegment>>) -> Self {
560 Self { paths }
561 }
562
563 pub fn path_count(&self) -> usize {
567 self.paths.len()
568 }
569
570 #[inline]
574 pub fn clip_path_count_paths(&self) -> usize {
575 self.path_count()
576 }
577
578 #[deprecated(
580 note = "use `clip_path_count_paths()` — matches upstream `FPDFClipPath_CountPaths`"
581 )]
582 #[inline]
583 pub fn count_paths(&self) -> usize {
584 self.path_count()
585 }
586
587 pub fn segment_count(&self, path_index: usize) -> usize {
593 self.paths.get(path_index).map(|p| p.len()).unwrap_or(0)
594 }
595
596 #[inline]
600 pub fn clip_path_count_path_segments(&self, path_index: usize) -> usize {
601 self.segment_count(path_index)
602 }
603
604 #[deprecated(
606 note = "use `clip_path_count_path_segments()` — matches upstream `FPDFClipPath_CountPathSegments`"
607 )]
608 #[inline]
609 pub fn count_path_segments(&self, path_index: usize) -> usize {
610 self.segment_count(path_index)
611 }
612
613 pub fn segment(&self, path_index: usize, seg_index: usize) -> Option<&PathSegment> {
617 self.paths.get(path_index)?.get(seg_index)
618 }
619
620 #[inline]
624 pub fn clip_path_get_path_segment(
625 &self,
626 path_index: usize,
627 seg_index: usize,
628 ) -> Option<&PathSegment> {
629 self.segment(path_index, seg_index)
630 }
631
632 #[deprecated(
634 note = "use `clip_path_get_path_segment()` — matches upstream `FPDFClipPath_GetPathSegment`"
635 )]
636 #[inline]
637 pub fn get_path_segment(&self, path_index: usize, seg_index: usize) -> Option<&PathSegment> {
638 self.segment(path_index, seg_index)
639 }
640
641 pub fn transform(&mut self, matrix: &Matrix) {
645 use rpdfium_core::Point;
646 for sub_path in &mut self.paths {
647 for seg in sub_path {
648 match seg {
649 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => {
650 let p = matrix.transform_point(Point::new(*x, *y));
651 *x = p.x;
652 *y = p.y;
653 }
654 PathSegment::CurveTo(x1, y1, x2, y2, x3, y3) => {
655 let p1 = matrix.transform_point(Point::new(*x1, *y1));
656 let p2 = matrix.transform_point(Point::new(*x2, *y2));
657 let p3 = matrix.transform_point(Point::new(*x3, *y3));
658 *x1 = p1.x;
659 *y1 = p1.y;
660 *x2 = p2.x;
661 *y2 = p2.y;
662 *x3 = p3.x;
663 *y3 = p3.y;
664 }
665 PathSegment::Rect(x, y, w, h) => {
666 let p = matrix.transform_point(Point::new(*x, *y));
667 let scale_x = (matrix.a * matrix.a + matrix.b * matrix.b).sqrt();
668 let scale_y = (matrix.c * matrix.c + matrix.d * matrix.d).sqrt();
669 *x = p.x;
670 *y = p.y;
671 *w *= scale_x;
672 *h *= scale_y;
673 }
674 PathSegment::Close => {}
675 }
676 }
677 }
678 }
679}
680
681#[derive(Debug, Clone)]
683pub enum PageObject {
684 Path(PathObject),
686 Text(TextObject),
688 Image(ImageObject),
690 Form(FormObject),
692}
693
694impl PageObject {
695 pub fn page_obj_destroy(self) -> crate::error::EditResult<()> {
705 Err(crate::error::EditError::NotSupported(
709 "page_obj_destroy: object lifecycle is managed by RAII (ADR-002: Rust ownership model)"
710 .into(),
711 ))
712 }
713
714 pub fn is_active(&self) -> bool {
720 match self {
721 PageObject::Path(p) => p.active,
722 PageObject::Text(t) => t.active,
723 PageObject::Image(img) => img.active,
724 PageObject::Form(f) => f.active,
725 }
726 }
727
728 #[inline]
732 pub fn page_obj_get_is_active(&self) -> bool {
733 self.is_active()
734 }
735
736 #[deprecated(
738 note = "use `page_obj_get_is_active()` — matches upstream `FPDFPageObj_GetIsActive`"
739 )]
740 #[inline]
741 pub fn get_is_active(&self) -> bool {
742 self.is_active()
743 }
744
745 pub fn set_active(&mut self, active: bool) {
751 match self {
752 PageObject::Path(p) => p.active = active,
753 PageObject::Text(t) => t.active = active,
754 PageObject::Image(img) => img.active = active,
755 PageObject::Form(f) => f.active = active,
756 }
757 }
758
759 #[inline]
763 pub fn page_obj_set_is_active(&mut self, active: bool) {
764 self.set_active(active)
765 }
766
767 #[deprecated(
769 note = "use `page_obj_set_is_active()` — matches upstream `FPDFPageObj_SetIsActive`"
770 )]
771 #[inline]
772 pub fn set_is_active(&mut self, active: bool) {
773 self.set_active(active)
774 }
775
776 pub fn set_fill_color(&mut self, color: Color) {
782 match self {
783 PageObject::Path(p) => p.set_fill_color(color),
784 PageObject::Text(t) => t.set_fill_color(color),
785 PageObject::Image(_) | PageObject::Form(_) => {}
786 }
787 }
788
789 #[inline]
793 pub fn page_obj_set_fill_color(&mut self, color: Color) {
794 self.set_fill_color(color)
795 }
796
797 pub fn fill_color(&self) -> Option<&Color> {
803 match self {
804 PageObject::Path(p) => p.fill_color(),
805 PageObject::Text(t) => t.fill_color(),
806 PageObject::Image(_) | PageObject::Form(_) => None,
807 }
808 }
809
810 #[inline]
814 pub fn page_obj_get_fill_color(&self) -> Option<&Color> {
815 self.fill_color()
816 }
817
818 #[deprecated(
820 note = "use `page_obj_get_fill_color()` — matches upstream `FPDFPageObj_GetFillColor`"
821 )]
822 #[inline]
823 pub fn get_fill_color(&self) -> Option<&Color> {
824 self.fill_color()
825 }
826
827 pub fn set_stroke_color(&mut self, color: Color) {
833 match self {
834 PageObject::Path(p) => p.set_stroke_color(color),
835 PageObject::Text(t) => t.set_stroke_color(color),
836 PageObject::Image(_) | PageObject::Form(_) => {}
837 }
838 }
839
840 #[inline]
844 pub fn page_obj_set_stroke_color(&mut self, color: Color) {
845 self.set_stroke_color(color)
846 }
847
848 pub fn stroke_color(&self) -> Option<&Color> {
854 match self {
855 PageObject::Path(p) => p.stroke_color(),
856 PageObject::Text(t) => t.stroke_color(),
857 PageObject::Image(_) | PageObject::Form(_) => None,
858 }
859 }
860
861 #[inline]
865 pub fn page_obj_get_stroke_color(&self) -> Option<&Color> {
866 self.stroke_color()
867 }
868
869 #[deprecated(
871 note = "use `page_obj_get_stroke_color()` — matches upstream `FPDFPageObj_GetStrokeColor`"
872 )]
873 #[inline]
874 pub fn get_stroke_color(&self) -> Option<&Color> {
875 self.stroke_color()
876 }
877
878 pub fn bounds(&self) -> Option<rpdfium_core::Rect> {
888 match self {
889 PageObject::Path(p) => p.bounds(),
890 PageObject::Image(img) => img.bounds(),
891 PageObject::Form(f) => f.bounds(),
892 PageObject::Text(_) => None,
893 }
894 }
895
896 #[inline]
900 pub fn page_obj_get_bounds(&self) -> Option<rpdfium_core::Rect> {
901 self.bounds()
902 }
903
904 #[deprecated(note = "use `page_obj_get_bounds()` — matches upstream `FPDFPageObj_GetBounds`")]
906 #[inline]
907 pub fn get_bounds(&self) -> Option<rpdfium_core::Rect> {
908 self.bounds()
909 }
910
911 pub fn rotated_bounds(&self) -> Option<[rpdfium_core::Point; 4]> {
921 match self {
922 PageObject::Text(t) => t.rotated_bounds(),
923 PageObject::Image(img) => img.rotated_bounds(),
924 PageObject::Path(p) => p.bounds().map(|r| {
925 [
926 rpdfium_core::Point {
927 x: r.left,
928 y: r.bottom,
929 },
930 rpdfium_core::Point {
931 x: r.right,
932 y: r.bottom,
933 },
934 rpdfium_core::Point {
935 x: r.right,
936 y: r.top,
937 },
938 rpdfium_core::Point {
939 x: r.left,
940 y: r.top,
941 },
942 ]
943 }),
944 PageObject::Form(f) => f.bounds().map(|r| {
945 [
946 rpdfium_core::Point {
947 x: r.left,
948 y: r.bottom,
949 },
950 rpdfium_core::Point {
951 x: r.right,
952 y: r.bottom,
953 },
954 rpdfium_core::Point {
955 x: r.right,
956 y: r.top,
957 },
958 rpdfium_core::Point {
959 x: r.left,
960 y: r.top,
961 },
962 ]
963 }),
964 }
965 }
966
967 #[inline]
971 pub fn page_obj_get_rotated_bounds(&self) -> Option<[rpdfium_core::Point; 4]> {
972 self.rotated_bounds()
973 }
974
975 #[deprecated(
977 note = "use `page_obj_get_rotated_bounds()` — matches upstream `FPDFPageObj_GetRotatedBounds`"
978 )]
979 #[inline]
980 pub fn get_rotated_bounds(&self) -> Option<[rpdfium_core::Point; 4]> {
981 self.rotated_bounds()
982 }
983
984 pub fn transform(&mut self, matrix: &Matrix) {
992 match self {
993 PageObject::Path(p) => p.transform(matrix),
994 PageObject::Text(t) => t.transform(matrix),
995 PageObject::Image(img) => img.transform(matrix),
996 PageObject::Form(f) => f.transform(matrix),
997 }
998 }
999
1000 #[inline]
1004 pub fn page_obj_transform(&mut self, matrix: &Matrix) {
1005 self.transform(matrix)
1006 }
1007
1008 #[inline]
1015 pub fn page_obj_transform_f(&mut self, matrix: &Matrix) {
1016 self.transform(matrix)
1017 }
1018
1019 #[deprecated(note = "use `page_obj_transform_f()` — matches upstream `FPDFPageObj_TransformF`")]
1020 #[inline]
1021 pub fn transform_f(&mut self, matrix: &Matrix) {
1022 self.transform(matrix)
1023 }
1024
1025 pub fn blend_mode(&self) -> Option<BlendMode> {
1031 match self {
1032 PageObject::Path(p) => p.blend_mode,
1033 PageObject::Text(t) => t.blend_mode,
1034 PageObject::Image(img) => img.blend_mode,
1035 PageObject::Form(f) => f.blend_mode,
1036 }
1037 }
1038
1039 #[inline]
1043 pub fn page_obj_get_blend_mode(&self) -> Option<BlendMode> {
1044 self.blend_mode()
1045 }
1046
1047 #[deprecated(
1049 note = "use `page_obj_get_blend_mode()` — matches upstream `FPDFPageObj_GetBlendMode`"
1050 )]
1051 #[inline]
1052 pub fn get_blend_mode(&self) -> Option<BlendMode> {
1053 self.blend_mode()
1054 }
1055
1056 pub fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
1062 match self {
1063 PageObject::Path(p) => p.blend_mode = mode,
1064 PageObject::Text(t) => t.blend_mode = mode,
1065 PageObject::Image(img) => img.blend_mode = mode,
1066 PageObject::Form(f) => f.blend_mode = mode,
1067 }
1068 }
1069
1070 #[inline]
1074 pub fn page_obj_set_blend_mode(&mut self, mode: Option<BlendMode>) {
1075 self.set_blend_mode(mode)
1076 }
1077
1078 pub fn matrix(&self) -> Matrix {
1091 match self {
1092 PageObject::Image(img) => *img.matrix(),
1093 PageObject::Form(f) => *f.matrix(),
1094 PageObject::Path(p) => p.matrix(),
1095 PageObject::Text(_) => Matrix::identity(),
1096 }
1097 }
1098
1099 #[inline]
1103 pub fn page_obj_get_matrix(&self) -> Matrix {
1104 self.matrix()
1105 }
1106
1107 #[deprecated(note = "use `page_obj_get_matrix()` — matches upstream `FPDFPageObj_GetMatrix`")]
1109 #[inline]
1110 pub fn get_matrix(&self) -> Matrix {
1111 self.matrix()
1112 }
1113
1114 pub fn set_matrix(&mut self, matrix: Matrix) {
1122 match self {
1123 PageObject::Image(img) => img.set_matrix(matrix),
1124 PageObject::Form(f) => f.set_matrix(matrix),
1125 PageObject::Path(_) | PageObject::Text(_) => {}
1126 }
1127 }
1128
1129 #[inline]
1133 pub fn page_obj_set_matrix(&mut self, matrix: Matrix) {
1134 self.set_matrix(matrix)
1135 }
1136
1137 pub fn has_transparency(&self) -> bool {
1148 match self {
1149 PageObject::Path(p) => p.has_transparency(),
1150 PageObject::Text(t) => t.has_transparency(),
1151 PageObject::Image(img) => img.has_transparency(),
1152 PageObject::Form(f) => f.has_transparency(),
1153 }
1154 }
1155
1156 #[deprecated(note = "use `has_transparency()` — matches upstream FPDFPageObj_HasTransparency")]
1161 #[inline]
1162 pub fn is_transparent(&self) -> bool {
1163 self.has_transparency()
1164 }
1165
1166 pub fn mark_count(&self) -> usize {
1175 match self {
1176 PageObject::Path(p) => p.mark_count(),
1177 PageObject::Text(t) => t.mark_count(),
1178 PageObject::Image(img) => img.mark_count(),
1179 PageObject::Form(f) => f.mark_count(),
1180 }
1181 }
1182
1183 #[inline]
1187 pub fn page_obj_count_marks(&self) -> usize {
1188 self.mark_count()
1189 }
1190
1191 #[deprecated(note = "use `page_obj_count_marks()` — matches upstream `FPDFPageObj_CountMarks`")]
1193 #[inline]
1194 pub fn count_marks(&self) -> usize {
1195 self.mark_count()
1196 }
1197
1198 pub fn mark(&self, index: usize) -> Option<&ContentMark> {
1202 match self {
1203 PageObject::Path(p) => p.mark(index),
1204 PageObject::Text(t) => t.mark(index),
1205 PageObject::Image(img) => img.mark(index),
1206 PageObject::Form(f) => f.mark(index),
1207 }
1208 }
1209
1210 #[inline]
1214 pub fn page_obj_get_mark(&self, index: usize) -> Option<&ContentMark> {
1215 self.mark(index)
1216 }
1217
1218 #[deprecated(note = "use `page_obj_get_mark()` — matches upstream `FPDFPageObj_GetMark`")]
1220 #[inline]
1221 pub fn get_mark(&self, index: usize) -> Option<&ContentMark> {
1222 self.mark(index)
1223 }
1224
1225 pub fn add_mark(&mut self, name: impl Into<String>) -> &mut ContentMark {
1230 match self {
1231 PageObject::Path(p) => p.add_mark(name),
1232 PageObject::Text(t) => t.add_mark(name),
1233 PageObject::Image(img) => img.add_mark(name),
1234 PageObject::Form(f) => f.add_mark(name),
1235 }
1236 }
1237
1238 #[inline]
1242 pub fn page_obj_add_mark(&mut self, name: impl Into<String>) -> &mut ContentMark {
1243 self.add_mark(name)
1244 }
1245
1246 pub fn remove_mark(&mut self, index: usize) -> bool {
1253 match self {
1254 PageObject::Path(p) => p.remove_mark(index),
1255 PageObject::Text(t) => t.remove_mark(index),
1256 PageObject::Image(img) => img.remove_mark(index),
1257 PageObject::Form(f) => f.remove_mark(index),
1258 }
1259 }
1260
1261 #[inline]
1265 pub fn page_obj_remove_mark(&mut self, index: usize) -> bool {
1266 self.remove_mark(index)
1267 }
1268
1269 pub fn marked_content_id(&self) -> Option<i64> {
1274 match self {
1275 PageObject::Path(p) => p.marked_content_id(),
1276 PageObject::Text(t) => t.marked_content_id(),
1277 PageObject::Image(img) => img.marked_content_id(),
1278 PageObject::Form(f) => f.marked_content_id(),
1279 }
1280 }
1281
1282 #[inline]
1286 pub fn page_obj_get_marked_content_id(&self) -> Option<i64> {
1287 self.marked_content_id()
1288 }
1289
1290 #[deprecated(
1292 note = "use `page_obj_get_marked_content_id()` — matches upstream `FPDFPageObj_GetMarkedContentID`"
1293 )]
1294 #[inline]
1295 pub fn get_marked_content_id(&self) -> Option<i64> {
1296 self.marked_content_id()
1297 }
1298
1299 pub fn clip_path(&self) -> Option<&ClipPath> {
1307 match self {
1308 PageObject::Path(p) => p.clip_path.as_ref(),
1309 PageObject::Text(t) => t.clip_path.as_ref(),
1310 PageObject::Image(img) => img.clip_path.as_ref(),
1311 PageObject::Form(f) => f.clip_path.as_ref(),
1312 }
1313 }
1314
1315 #[inline]
1319 pub fn page_obj_get_clip_path(&self) -> Option<&ClipPath> {
1320 self.clip_path()
1321 }
1322
1323 #[deprecated(
1327 note = "use `page_obj_get_clip_path()` — matches upstream `FPDFPageObj_GetClipPath`"
1328 )]
1329 #[inline]
1330 pub fn get_clip_path(&self) -> Option<&ClipPath> {
1331 self.clip_path()
1332 }
1333
1334 pub fn set_clip_path(&mut self, clip: Option<ClipPath>) {
1336 match self {
1337 PageObject::Path(p) => p.clip_path = clip,
1338 PageObject::Text(t) => t.clip_path = clip,
1339 PageObject::Image(img) => img.clip_path = clip,
1340 PageObject::Form(f) => f.clip_path = clip,
1341 }
1342 }
1343
1344 pub fn transform_clip_path(&mut self, matrix: &Matrix) {
1350 let clip = match self {
1351 PageObject::Path(p) => p.clip_path.as_mut(),
1352 PageObject::Text(t) => t.clip_path.as_mut(),
1353 PageObject::Image(img) => img.clip_path.as_mut(),
1354 PageObject::Form(f) => f.clip_path.as_mut(),
1355 };
1356 if let Some(c) = clip {
1357 c.transform(matrix);
1358 }
1359 }
1360
1361 #[inline]
1365 pub fn page_obj_transform_clip_path(&mut self, matrix: &Matrix) {
1366 self.transform_clip_path(matrix);
1367 }
1368
1369 pub fn is_text(&self) -> bool {
1371 matches!(self, PageObject::Text(_))
1372 }
1373
1374 pub fn is_path(&self) -> bool {
1376 matches!(self, PageObject::Path(_))
1377 }
1378
1379 pub fn is_image(&self) -> bool {
1381 matches!(self, PageObject::Image(_))
1382 }
1383
1384 pub fn is_form(&self) -> bool {
1386 matches!(self, PageObject::Form(_))
1387 }
1388
1389 pub fn object_type(&self) -> u32 {
1398 match self {
1399 PageObject::Path(o) => o.object_type(),
1400 PageObject::Text(o) => o.object_type(),
1401 PageObject::Image(o) => o.object_type(),
1402 PageObject::Form(o) => o.object_type(),
1403 }
1404 }
1405
1406 #[inline]
1410 pub fn page_obj_get_type(&self) -> u32 {
1411 self.object_type()
1412 }
1413
1414 #[deprecated(note = "use `page_obj_get_type()` — matches upstream FPDFPageObj_GetType")]
1419 #[inline]
1420 pub fn get_object_type(&self) -> u32 {
1421 self.object_type()
1422 }
1423
1424 #[deprecated(note = "use `page_obj_get_type()` — matches upstream FPDFPageObj_GetType")]
1426 #[inline]
1427 pub fn get_type(&self) -> u32 {
1428 self.object_type()
1429 }
1430
1431 pub fn stroke_width(&self) -> Option<f32> {
1442 match self {
1443 PageObject::Path(p) => Some(p.line_width()),
1444 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => None,
1445 }
1446 }
1447
1448 #[inline]
1452 pub fn page_obj_get_stroke_width(&self) -> Option<f32> {
1453 self.stroke_width()
1454 }
1455
1456 #[deprecated(
1457 note = "use `page_obj_get_stroke_width()` — matches upstream `FPDFPageObj_GetStrokeWidth`"
1458 )]
1459 #[inline]
1460 pub fn get_stroke_width(&self) -> Option<f32> {
1461 self.stroke_width()
1462 }
1463
1464 pub fn set_stroke_width(&mut self, width: f32) {
1470 match self {
1471 PageObject::Path(p) => p.set_line_width(width),
1472 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => {}
1473 }
1474 }
1475
1476 #[inline]
1480 pub fn page_obj_set_stroke_width(&mut self, width: f32) {
1481 self.set_stroke_width(width)
1482 }
1483
1484 pub fn line_join(&self) -> Option<u8> {
1494 match self {
1495 PageObject::Path(p) => Some(p.line_join()),
1496 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => None,
1497 }
1498 }
1499
1500 #[inline]
1504 pub fn page_obj_get_line_join(&self) -> Option<u8> {
1505 self.line_join()
1506 }
1507
1508 #[deprecated(
1509 note = "use `page_obj_get_line_join()` — matches upstream `FPDFPageObj_GetLineJoin`"
1510 )]
1511 #[inline]
1512 pub fn get_line_join(&self) -> Option<u8> {
1513 self.line_join()
1514 }
1515
1516 pub fn set_line_join(&mut self, join: u8) {
1522 match self {
1523 PageObject::Path(p) => p.set_line_join(join),
1524 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => {}
1525 }
1526 }
1527
1528 #[inline]
1532 pub fn page_obj_set_line_join(&mut self, join: u8) {
1533 self.set_line_join(join)
1534 }
1535
1536 pub fn line_cap(&self) -> Option<u8> {
1546 match self {
1547 PageObject::Path(p) => Some(p.line_cap()),
1548 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => None,
1549 }
1550 }
1551
1552 #[inline]
1556 pub fn page_obj_get_line_cap(&self) -> Option<u8> {
1557 self.line_cap()
1558 }
1559
1560 #[deprecated(
1561 note = "use `page_obj_get_line_cap()` — matches upstream `FPDFPageObj_GetLineCap`"
1562 )]
1563 #[inline]
1564 pub fn get_line_cap(&self) -> Option<u8> {
1565 self.line_cap()
1566 }
1567
1568 pub fn set_line_cap(&mut self, cap: u8) {
1574 match self {
1575 PageObject::Path(p) => p.set_line_cap(cap),
1576 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => {}
1577 }
1578 }
1579
1580 #[inline]
1584 pub fn page_obj_set_line_cap(&mut self, cap: u8) {
1585 self.set_line_cap(cap)
1586 }
1587
1588 pub fn dash_phase(&self) -> Option<f32> {
1600 match self {
1601 PageObject::Path(p) => Some(p.dash_phase()),
1602 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => None,
1603 }
1604 }
1605
1606 #[inline]
1610 pub fn page_obj_get_dash_phase(&self) -> Option<f32> {
1611 self.dash_phase()
1612 }
1613
1614 #[deprecated(
1615 note = "use `page_obj_get_dash_phase()` — matches upstream `FPDFPageObj_GetDashPhase`"
1616 )]
1617 #[inline]
1618 pub fn get_dash_phase(&self) -> Option<f32> {
1619 self.dash_phase()
1620 }
1621
1622 pub fn set_dash_phase(&mut self, phase: f32) {
1628 match self {
1629 PageObject::Path(p) => p.set_dash_phase(phase),
1630 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => {}
1631 }
1632 }
1633
1634 #[inline]
1638 pub fn page_obj_set_dash_phase(&mut self, phase: f32) {
1639 self.set_dash_phase(phase)
1640 }
1641
1642 pub fn dash_count(&self) -> Option<usize> {
1648 match self {
1649 PageObject::Path(p) => Some(p.dash_count()),
1650 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => None,
1651 }
1652 }
1653
1654 #[inline]
1658 pub fn page_obj_get_dash_count(&self) -> Option<usize> {
1659 self.dash_count()
1660 }
1661
1662 #[deprecated(
1663 note = "use `page_obj_get_dash_count()` — matches upstream `FPDFPageObj_GetDashCount`"
1664 )]
1665 #[inline]
1666 pub fn get_dash_count(&self) -> Option<usize> {
1667 self.dash_count()
1668 }
1669
1670 pub fn dash_array(&self) -> Option<&[f32]> {
1676 match self {
1677 PageObject::Path(p) => Some(p.dash_array()),
1678 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => None,
1679 }
1680 }
1681
1682 #[inline]
1686 pub fn page_obj_get_dash_array(&self) -> Option<&[f32]> {
1687 self.dash_array()
1688 }
1689
1690 #[deprecated(
1691 note = "use `page_obj_get_dash_array()` — matches upstream `FPDFPageObj_GetDashArray`"
1692 )]
1693 #[inline]
1694 pub fn get_dash_array(&self) -> Option<&[f32]> {
1695 self.dash_array()
1696 }
1697
1698 pub fn set_dash_array(&mut self, array: Vec<f32>, phase: f32) {
1704 match self {
1705 PageObject::Path(p) => p.set_dash_array(array, phase),
1706 PageObject::Text(_) | PageObject::Image(_) | PageObject::Form(_) => {}
1707 }
1708 }
1709
1710 #[inline]
1714 pub fn page_obj_set_dash_array(&mut self, array: Vec<f32>, phase: f32) {
1715 self.set_dash_array(array, phase)
1716 }
1717}
1718
1719#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1721pub enum FillMode {
1722 None,
1724 EvenOdd,
1726 NonZero,
1728}
1729
1730#[derive(Debug, Clone)]
1732pub enum PathSegment {
1733 MoveTo(f64, f64),
1735 LineTo(f64, f64),
1737 CurveTo(f64, f64, f64, f64, f64, f64),
1739 Rect(f64, f64, f64, f64),
1741 Close,
1743}
1744
1745impl PathSegment {
1746 pub fn point(&self) -> Option<(f64, f64)> {
1756 match self {
1757 PathSegment::MoveTo(x, y) => Some((*x, *y)),
1758 PathSegment::LineTo(x, y) => Some((*x, *y)),
1759 PathSegment::CurveTo(_, _, _, _, x3, y3) => Some((*x3, *y3)),
1760 PathSegment::Rect(x, y, _, _) => Some((*x, *y)),
1761 PathSegment::Close => None,
1762 }
1763 }
1764
1765 #[inline]
1769 pub fn path_segment_get_point(&self) -> Option<(f64, f64)> {
1770 self.point()
1771 }
1772
1773 #[deprecated(
1774 note = "use `path_segment_get_point()` — matches upstream `FPDFPathSegment_GetPoint`"
1775 )]
1776 #[inline]
1777 pub fn get_point(&self) -> Option<(f64, f64)> {
1778 self.point()
1779 }
1780
1781 pub fn segment_type(&self) -> i32 {
1791 match self {
1792 PathSegment::MoveTo(_, _) => 1, PathSegment::LineTo(_, _) => 2, PathSegment::CurveTo(_, _, _, _, _, _) => 3, PathSegment::Rect(_, _, _, _) => -1, PathSegment::Close => -1, }
1798 }
1799
1800 #[inline]
1804 pub fn path_segment_get_type(&self) -> i32 {
1805 self.segment_type()
1806 }
1807
1808 #[deprecated(
1809 note = "use `path_segment_get_type()` — matches upstream `FPDFPathSegment_GetType`"
1810 )]
1811 #[inline]
1812 pub fn get_type(&self) -> i32 {
1813 self.segment_type()
1814 }
1815
1816 pub fn is_close(&self) -> bool {
1822 matches!(self, PathSegment::Close)
1823 }
1824
1825 #[inline]
1829 pub fn path_segment_get_close(&self) -> bool {
1830 self.is_close()
1831 }
1832
1833 #[deprecated(
1834 note = "use `path_segment_get_close()` — matches upstream `FPDFPathSegment_GetClose`"
1835 )]
1836 #[inline]
1837 pub fn get_close(&self) -> bool {
1838 self.is_close()
1839 }
1840}
1841
1842#[derive(Debug, Clone)]
1844pub struct PathObject {
1845 pub segments: Vec<PathSegment>,
1847 pub matrix: Matrix,
1849 pub fill_color: Option<Color>,
1851 pub stroke_color: Option<Color>,
1853 pub line_width: f32,
1855 pub line_cap: u8,
1857 pub line_join: u8,
1859 pub fill_mode: FillMode,
1861 pub dash_pattern: Option<DashPattern>,
1865 pub blend_mode: Option<BlendMode>,
1869 pub active: bool,
1873 pub marks: Vec<ContentMark>,
1880 pub clip_path: Option<ClipPath>,
1884}
1885
1886impl Default for PathObject {
1887 fn default() -> Self {
1888 Self {
1889 segments: Vec::new(),
1890 matrix: Matrix::identity(),
1891 fill_color: None,
1892 stroke_color: None,
1893 line_width: 1.0,
1894 line_cap: 0,
1895 line_join: 0,
1896 fill_mode: FillMode::None,
1897 dash_pattern: None,
1898 blend_mode: None,
1899 active: true,
1900 marks: Vec::new(),
1901 clip_path: None,
1902 }
1903 }
1904}
1905
1906impl PathObject {
1907 pub fn set_fill_color(&mut self, color: Color) {
1911 self.fill_color = Some(color);
1912 }
1913
1914 pub fn fill_color(&self) -> Option<&Color> {
1916 self.fill_color.as_ref()
1917 }
1918
1919 #[inline]
1923 pub fn page_obj_get_fill_color(&self) -> Option<&Color> {
1924 self.fill_color()
1925 }
1926
1927 #[deprecated(
1931 note = "use `page_obj_get_fill_color()` — matches upstream `FPDFPageObj_GetFillColor`"
1932 )]
1933 #[inline]
1934 pub fn get_fill_color(&self) -> Option<&Color> {
1935 self.fill_color()
1936 }
1937
1938 pub fn set_stroke_color(&mut self, color: Color) {
1942 self.stroke_color = Some(color);
1943 }
1944
1945 pub fn stroke_color(&self) -> Option<&Color> {
1947 self.stroke_color.as_ref()
1948 }
1949
1950 #[inline]
1954 pub fn page_obj_get_stroke_color(&self) -> Option<&Color> {
1955 self.stroke_color()
1956 }
1957
1958 #[deprecated(
1962 note = "use `page_obj_get_stroke_color()` — matches upstream `FPDFPageObj_GetStrokeColor`"
1963 )]
1964 #[inline]
1965 pub fn get_stroke_color(&self) -> Option<&Color> {
1966 self.stroke_color()
1967 }
1968
1969 pub fn set_line_width(&mut self, width: f32) {
1973 self.line_width = width;
1974 }
1975
1976 #[inline]
1980 pub fn page_obj_set_stroke_width(&mut self, width: f32) {
1981 self.set_line_width(width);
1982 }
1983
1984 #[deprecated(
1988 note = "use `page_obj_set_stroke_width()` — matches upstream `FPDFPageObj_SetStrokeWidth`"
1989 )]
1990 #[inline]
1991 pub fn set_stroke_width(&mut self, width: f32) {
1992 self.set_line_width(width);
1993 }
1994
1995 pub fn line_width(&self) -> f32 {
1997 self.line_width
1998 }
1999
2000 #[deprecated(
2005 note = "use `page_obj_get_stroke_width()` — matches upstream FPDFPageObj_GetStrokeWidth"
2006 )]
2007 #[inline]
2008 pub fn get_line_width(&self) -> f32 {
2009 self.line_width()
2010 }
2011
2012 #[inline]
2016 pub fn page_obj_get_stroke_width(&self) -> f32 {
2017 self.line_width()
2018 }
2019
2020 #[deprecated(
2024 note = "use `page_obj_get_stroke_width()` — matches upstream `FPDFPageObj_GetStrokeWidth`"
2025 )]
2026 #[inline]
2027 pub fn get_stroke_width(&self) -> f32 {
2028 self.line_width()
2029 }
2030
2031 pub fn set_line_join(&mut self, join: u8) {
2035 self.line_join = join;
2036 }
2037
2038 pub fn line_join(&self) -> u8 {
2040 self.line_join
2041 }
2042
2043 #[inline]
2047 pub fn page_obj_get_line_join(&self) -> u8 {
2048 self.line_join()
2049 }
2050
2051 #[deprecated(
2055 note = "use `page_obj_get_line_join()` — matches upstream `FPDFPageObj_GetLineJoin`"
2056 )]
2057 #[inline]
2058 pub fn get_line_join(&self) -> u8 {
2059 self.line_join()
2060 }
2061
2062 pub fn set_line_cap(&mut self, cap: u8) {
2066 self.line_cap = cap;
2067 }
2068
2069 pub fn line_cap(&self) -> u8 {
2071 self.line_cap
2072 }
2073
2074 #[inline]
2078 pub fn page_obj_get_line_cap(&self) -> u8 {
2079 self.line_cap()
2080 }
2081
2082 #[deprecated(
2086 note = "use `page_obj_get_line_cap()` — matches upstream `FPDFPageObj_GetLineCap`"
2087 )]
2088 #[inline]
2089 pub fn get_line_cap(&self) -> u8 {
2090 self.line_cap()
2091 }
2092
2093 pub fn dash_pattern(&self) -> Option<&DashPattern> {
2102 self.dash_pattern.as_ref()
2103 }
2104
2105 pub fn set_dash_pattern(&mut self, pattern: Option<DashPattern>) {
2111 self.dash_pattern = pattern;
2112 }
2113
2114 pub fn dash_phase(&self) -> f32 {
2120 self.dash_pattern.as_ref().map(|d| d.phase).unwrap_or(0.0)
2121 }
2122
2123 #[inline]
2127 pub fn path_get_dash_phase(&self) -> f32 {
2128 self.dash_phase()
2129 }
2130
2131 #[deprecated(note = "use `path_get_dash_phase()` — matches upstream `FPDFPath_GetDashPhase`")]
2135 #[inline]
2136 pub fn get_dash_phase(&self) -> f32 {
2137 self.dash_phase()
2138 }
2139
2140 pub fn set_dash_phase(&mut self, phase: f32) {
2146 if let Some(ref mut d) = self.dash_pattern {
2147 d.phase = phase;
2148 }
2149 }
2150
2151 pub fn dash_count(&self) -> usize {
2157 self.dash_pattern
2158 .as_ref()
2159 .map(|d| d.array.len())
2160 .unwrap_or(0)
2161 }
2162
2163 #[inline]
2167 pub fn page_obj_get_dash_count(&self) -> usize {
2168 self.dash_count()
2169 }
2170
2171 #[deprecated(
2175 note = "use `page_obj_get_dash_count()` — matches upstream `FPDFPageObj_GetDashCount`"
2176 )]
2177 #[inline]
2178 pub fn get_dash_count(&self) -> usize {
2179 self.dash_count()
2180 }
2181
2182 pub fn dash_array(&self) -> &[f32] {
2188 self.dash_pattern
2189 .as_ref()
2190 .map(|d| d.array.as_slice())
2191 .unwrap_or(&[])
2192 }
2193
2194 #[inline]
2198 pub fn path_get_dash_array(&self) -> &[f32] {
2199 self.dash_array()
2200 }
2201
2202 #[deprecated(note = "use `path_get_dash_array()` — matches upstream `FPDFPath_GetDashArray`")]
2206 #[inline]
2207 pub fn get_dash_array(&self) -> &[f32] {
2208 self.dash_array()
2209 }
2210
2211 pub fn set_dash_array(&mut self, array: Vec<f32>, phase: f32) {
2217 if array.is_empty() {
2218 self.dash_pattern = None;
2219 } else {
2220 self.dash_pattern = Some(DashPattern { array, phase });
2221 }
2222 }
2223
2224 pub fn blend_mode(&self) -> Option<BlendMode> {
2232 self.blend_mode
2233 }
2234
2235 #[inline]
2239 pub fn page_obj_get_blend_mode(&self) -> Option<BlendMode> {
2240 self.blend_mode()
2241 }
2242
2243 #[deprecated(
2247 note = "use `page_obj_get_blend_mode()` — matches upstream `FPDFPageObj_GetBlendMode`"
2248 )]
2249 #[inline]
2250 pub fn get_blend_mode(&self) -> Option<BlendMode> {
2251 self.blend_mode()
2252 }
2253
2254 pub fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
2258 self.blend_mode = mode;
2259 }
2260
2261 pub fn mark_count(&self) -> usize {
2269 self.marks.len()
2270 }
2271
2272 #[inline]
2276 pub fn page_obj_count_marks(&self) -> usize {
2277 self.mark_count()
2278 }
2279
2280 #[deprecated(note = "use `page_obj_count_marks()` — matches upstream `FPDFPageObj_CountMarks`")]
2284 #[inline]
2285 pub fn count_marks(&self) -> usize {
2286 self.mark_count()
2287 }
2288
2289 pub fn mark(&self, index: usize) -> Option<&ContentMark> {
2293 self.marks.get(index)
2294 }
2295
2296 #[inline]
2300 pub fn page_obj_get_mark(&self, index: usize) -> Option<&ContentMark> {
2301 self.mark(index)
2302 }
2303
2304 #[deprecated(note = "use `page_obj_get_mark()` — matches upstream `FPDFPageObj_GetMark`")]
2308 #[inline]
2309 pub fn get_mark(&self, index: usize) -> Option<&ContentMark> {
2310 self.mark(index)
2311 }
2312
2313 pub fn add_mark(&mut self, name: impl Into<String>) -> &mut ContentMark {
2318 self.marks.push(ContentMark::new(name));
2319 self.marks.last_mut().unwrap()
2320 }
2321
2322 pub fn remove_mark(&mut self, index: usize) -> bool {
2329 if index < self.marks.len() {
2330 self.marks.remove(index);
2331 true
2332 } else {
2333 false
2334 }
2335 }
2336
2337 pub fn marks(&self) -> &[ContentMark] {
2339 &self.marks
2340 }
2341
2342 pub fn marked_content_id(&self) -> Option<i64> {
2347 self.marks.iter().find_map(|m| m.marked_content_id())
2348 }
2349
2350 #[inline]
2354 pub fn page_obj_get_marked_content_id(&self) -> Option<i64> {
2355 self.marked_content_id()
2356 }
2357
2358 #[deprecated(
2362 note = "use `page_obj_get_marked_content_id()` — matches upstream `FPDFPageObj_GetMarkedContentID`"
2363 )]
2364 #[inline]
2365 pub fn get_marked_content_id(&self) -> Option<i64> {
2366 self.marked_content_id()
2367 }
2368
2369 pub fn has_transparency(&self) -> bool {
2375 matches!(self.blend_mode, Some(bm) if bm != BlendMode::Normal)
2376 }
2377
2378 #[deprecated(note = "use `has_transparency()` — matches upstream FPDFPageObj_HasTransparency")]
2383 #[inline]
2384 pub fn is_transparent(&self) -> bool {
2385 self.has_transparency()
2386 }
2387
2388 pub fn object_type(&self) -> u32 {
2392 2
2393 }
2394
2395 #[deprecated(note = "use `page_obj_get_type()` — matches upstream `FPDFPageObj_GetType`")]
2400 #[inline]
2401 pub fn get_object_type(&self) -> u32 {
2402 self.object_type()
2403 }
2404
2405 #[inline]
2409 pub fn page_obj_get_type(&self) -> u32 {
2410 self.object_type()
2411 }
2412
2413 #[deprecated(note = "use `page_obj_get_type()` — matches upstream `FPDFPageObj_GetType`")]
2414 #[inline]
2415 pub fn get_type(&self) -> u32 {
2416 self.object_type()
2417 }
2418
2419 pub fn transform(&mut self, matrix: &Matrix) {
2426 use rpdfium_core::Point;
2427 for seg in &mut self.segments {
2428 match seg {
2429 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => {
2430 let p = matrix.transform_point(Point::new(*x, *y));
2431 *x = p.x;
2432 *y = p.y;
2433 }
2434 PathSegment::CurveTo(x1, y1, x2, y2, x3, y3) => {
2435 let p1 = matrix.transform_point(Point::new(*x1, *y1));
2436 let p2 = matrix.transform_point(Point::new(*x2, *y2));
2437 let p3 = matrix.transform_point(Point::new(*x3, *y3));
2438 *x1 = p1.x;
2439 *y1 = p1.y;
2440 *x2 = p2.x;
2441 *y2 = p2.y;
2442 *x3 = p3.x;
2443 *y3 = p3.y;
2444 }
2445 PathSegment::Rect(x, y, w, h) => {
2446 let p = matrix.transform_point(Point::new(*x, *y));
2447 let scale_x = (matrix.a * matrix.a + matrix.b * matrix.b).sqrt();
2448 let scale_y = (matrix.c * matrix.c + matrix.d * matrix.d).sqrt();
2449 *x = p.x;
2450 *y = p.y;
2451 *w *= scale_x;
2452 *h *= scale_y;
2453 }
2454 PathSegment::Close => {}
2455 }
2456 }
2457 }
2458
2459 pub fn matrix(&self) -> Matrix {
2467 self.matrix
2468 }
2469
2470 #[inline]
2474 pub fn page_obj_get_matrix(&self) -> Matrix {
2475 self.matrix()
2476 }
2477
2478 #[deprecated(note = "use `page_obj_get_matrix()` — matches upstream `FPDFPageObj_GetMatrix`")]
2479 #[inline]
2480 pub fn get_matrix(&self) -> Matrix {
2481 self.matrix()
2482 }
2483
2484 pub fn segment_count(&self) -> usize {
2488 self.segments.len()
2489 }
2490
2491 #[inline]
2495 pub fn path_count_segments(&self) -> usize {
2496 self.segment_count()
2497 }
2498
2499 #[deprecated(note = "use `path_count_segments()` — matches upstream `FPDFPath_CountSegments`")]
2500 #[inline]
2501 pub fn count_segments(&self) -> usize {
2502 self.segment_count()
2503 }
2504
2505 pub fn draw_mode(&self) -> (FillMode, bool) {
2512 (self.fill_mode, self.stroke_color.is_some())
2513 }
2514
2515 #[inline]
2519 pub fn path_get_draw_mode(&self) -> (FillMode, bool) {
2520 self.draw_mode()
2521 }
2522
2523 #[deprecated(note = "use `path_get_draw_mode()` — matches upstream `FPDFPath_GetDrawMode`")]
2524 #[inline]
2525 pub fn get_draw_mode(&self) -> (FillMode, bool) {
2526 self.draw_mode()
2527 }
2528
2529 pub fn set_draw_mode(&mut self, fill_mode: FillMode, stroke: bool) {
2537 self.fill_mode = fill_mode;
2538 if !stroke {
2539 self.stroke_color = None;
2540 }
2541 }
2542
2543 #[inline]
2547 pub fn path_set_draw_mode(&mut self, fill_mode: FillMode, stroke: bool) {
2548 self.set_draw_mode(fill_mode, stroke)
2549 }
2550
2551 pub fn fill_mode(&self) -> FillMode {
2555 self.fill_mode
2556 }
2557
2558 pub fn set_fill_mode(&mut self, mode: FillMode) {
2562 self.fill_mode = mode;
2563 }
2564
2565 pub fn is_stroked(&self) -> bool {
2569 self.stroke_color.is_some()
2570 }
2571
2572 pub fn bounds(&self) -> Option<rpdfium_core::Rect> {
2574 let mut min_x = f64::MAX;
2575 let mut min_y = f64::MAX;
2576 let mut max_x = f64::MIN;
2577 let mut max_y = f64::MIN;
2578 let mut has_point = false;
2579
2580 for seg in &self.segments {
2581 match seg {
2582 PathSegment::MoveTo(x, y) | PathSegment::LineTo(x, y) => {
2583 min_x = min_x.min(*x);
2584 max_x = max_x.max(*x);
2585 min_y = min_y.min(*y);
2586 max_y = max_y.max(*y);
2587 has_point = true;
2588 }
2589 PathSegment::CurveTo(x1, y1, x2, y2, x3, y3) => {
2590 for &(x, y) in &[(*x1, *y1), (*x2, *y2), (*x3, *y3)] {
2591 min_x = min_x.min(x);
2592 max_x = max_x.max(x);
2593 min_y = min_y.min(y);
2594 max_y = max_y.max(y);
2595 }
2596 has_point = true;
2597 }
2598 PathSegment::Rect(x, y, w, h) => {
2599 min_x = min_x.min(*x);
2600 max_x = max_x.max(*x + *w);
2601 min_y = min_y.min(*y);
2602 max_y = max_y.max(*y + *h);
2603 has_point = true;
2604 }
2605 PathSegment::Close => {}
2606 }
2607 }
2608
2609 if has_point {
2610 Some(rpdfium_core::Rect {
2611 left: min_x,
2612 bottom: min_y,
2613 right: max_x,
2614 top: max_y,
2615 })
2616 } else {
2617 None
2618 }
2619 }
2620
2621 pub fn segment(&self, index: usize) -> Option<&PathSegment> {
2625 self.segments.get(index)
2626 }
2627
2628 #[inline]
2632 pub fn path_get_path_segment(&self, index: usize) -> Option<&PathSegment> {
2633 self.segment(index)
2634 }
2635
2636 #[deprecated(
2637 note = "use `path_get_path_segment()` — matches upstream `FPDFPath_GetPathSegment`"
2638 )]
2639 #[inline]
2640 pub fn get_path_segment(&self, index: usize) -> Option<&PathSegment> {
2641 self.segment(index)
2642 }
2643
2644 pub fn create_at(x: f64, y: f64) -> Self {
2652 let mut path = PathObject::default();
2653 path.segments.push(PathSegment::MoveTo(x, y));
2654 path
2655 }
2656
2657 #[inline]
2661 pub fn page_obj_create_new_path(x: f64, y: f64) -> Self {
2662 Self::create_at(x, y)
2663 }
2664
2665 pub fn create_rect(x: f64, y: f64, width: f64, height: f64) -> Self {
2673 let mut path = PathObject::default();
2674 path.segments.push(PathSegment::Rect(x, y, width, height));
2675 path
2676 }
2677
2678 #[inline]
2682 pub fn page_obj_create_new_rect(x: f64, y: f64, width: f64, height: f64) -> Self {
2683 Self::create_rect(x, y, width, height)
2684 }
2685
2686 pub fn move_to(&mut self, x: f64, y: f64) -> &mut Self {
2690 self.segments.push(PathSegment::MoveTo(x, y));
2691 self
2692 }
2693
2694 #[inline]
2698 pub fn path_move_to(&mut self, x: f64, y: f64) -> &mut Self {
2699 self.move_to(x, y)
2700 }
2701
2702 pub fn line_to(&mut self, x: f64, y: f64) -> &mut Self {
2706 self.segments.push(PathSegment::LineTo(x, y));
2707 self
2708 }
2709
2710 #[inline]
2714 pub fn path_line_to(&mut self, x: f64, y: f64) -> &mut Self {
2715 self.line_to(x, y)
2716 }
2717
2718 pub fn bezier_to(&mut self, x1: f64, y1: f64, x2: f64, y2: f64, x3: f64, y3: f64) -> &mut Self {
2725 self.segments
2726 .push(PathSegment::CurveTo(x1, y1, x2, y2, x3, y3));
2727 self
2728 }
2729
2730 #[inline]
2734 pub fn path_bezier_to(
2735 &mut self,
2736 x1: f64,
2737 y1: f64,
2738 x2: f64,
2739 y2: f64,
2740 x3: f64,
2741 y3: f64,
2742 ) -> &mut Self {
2743 self.bezier_to(x1, y1, x2, y2, x3, y3)
2744 }
2745
2746 pub fn close(&mut self) -> &mut Self {
2750 self.segments.push(PathSegment::Close);
2751 self
2752 }
2753
2754 #[inline]
2758 pub fn path_close(&mut self) -> &mut Self {
2759 self.close()
2760 }
2761}
2762
2763#[derive(Debug, Clone)]
2765pub struct TextObject {
2766 pub font_name: Name,
2768 pub font_size: f32,
2770 pub matrix: Matrix,
2772 pub text: String,
2774 pub fill_color: Option<Color>,
2776 pub stroke_color: Option<Color>,
2778 pub rendering_mode: TextRenderingMode,
2780 pub char_spacing: f32,
2782 pub word_spacing: f32,
2784 pub(crate) font_object_id: Option<ObjectId>,
2788 pub(crate) encoding: FontEncoding,
2793 pub blend_mode: Option<BlendMode>,
2797 pub active: bool,
2801 pub marks: Vec<ContentMark>,
2808 pub clip_path: Option<ClipPath>,
2812}
2813
2814impl Default for TextObject {
2815 fn default() -> Self {
2816 Self {
2817 font_name: Name::from_bytes(b"Helvetica".to_vec()),
2818 font_size: 12.0,
2819 matrix: Matrix::identity(),
2820 text: String::new(),
2821 fill_color: Some(Color::gray(0.0)),
2822 stroke_color: None,
2823 rendering_mode: TextRenderingMode::Fill,
2824 char_spacing: 0.0,
2825 word_spacing: 0.0,
2826 font_object_id: None,
2827 encoding: FontEncoding::WinAnsi,
2828 blend_mode: None,
2829 active: true,
2830 marks: Vec::new(),
2831 clip_path: None,
2832 }
2833 }
2834}
2835
2836impl TextObject {
2837 pub fn with_standard_font(font_name: &str, size: f32) -> Self {
2846 Self {
2847 font_name: Name::from_bytes(font_name.as_bytes().to_vec()),
2848 font_size: size,
2849 ..Default::default()
2850 }
2851 }
2852
2853 pub fn with_font(registration: &crate::font_reg::FontRegistration, size: f32) -> Self {
2860 Self {
2861 font_name: Name::from_bytes(registration.base_font_name().as_bytes().to_vec()),
2862 font_size: size,
2863 font_object_id: Some(registration.object_id),
2864 ..Default::default()
2865 }
2866 }
2867
2868 #[deprecated(
2873 note = "use `with_font()` — `from_font_registration` is not an upstream FPDF name"
2874 )]
2875 #[inline]
2876 pub fn from_font_registration(
2877 font_registration: &crate::font_reg::FontRegistration,
2878 font_size: f32,
2879 ) -> Self {
2880 Self::with_font(font_registration, font_size)
2881 }
2882
2883 pub fn text(&self) -> &str {
2891 &self.text
2892 }
2893
2894 #[inline]
2898 pub fn text_obj_get_text(&self) -> &str {
2899 self.text()
2900 }
2901
2902 #[deprecated(note = "use `text_obj_get_text()` — matches upstream `FPDFTextObj_GetText`")]
2903 #[inline]
2904 pub fn get_text(&self) -> &str {
2905 self.text()
2906 }
2907
2908 pub fn font_size(&self) -> f32 {
2912 self.font_size
2913 }
2914
2915 #[inline]
2919 pub fn text_obj_get_font_size(&self) -> f32 {
2920 self.font_size()
2921 }
2922
2923 #[deprecated(
2924 note = "use `text_obj_get_font_size()` — matches upstream `FPDFTextObj_GetFontSize`"
2925 )]
2926 #[inline]
2927 pub fn get_font_size(&self) -> f32 {
2928 self.font_size()
2929 }
2930
2931 pub fn rendering_mode(&self) -> TextRenderingMode {
2935 self.rendering_mode
2936 }
2937
2938 #[inline]
2942 pub fn text_obj_get_text_render_mode(&self) -> TextRenderingMode {
2943 self.rendering_mode()
2944 }
2945
2946 #[deprecated(
2947 note = "use `text_obj_get_text_render_mode()` — matches upstream `FPDFTextObj_GetTextRenderMode`"
2948 )]
2949 #[inline]
2950 pub fn get_text_render_mode(&self) -> TextRenderingMode {
2951 self.rendering_mode()
2952 }
2953
2954 pub fn font_name(&self) -> &Name {
2959 &self.font_name
2960 }
2961
2962 #[inline]
2966 pub fn text_obj_get_font(&self) -> &Name {
2967 self.font_name()
2968 }
2969
2970 #[deprecated(note = "use `text_obj_get_font()` — matches upstream `FPDFTextObj_GetFont`")]
2971 #[inline]
2972 pub fn get_font(&self) -> &Name {
2973 self.font_name()
2974 }
2975
2976 pub fn set_fill_color(&mut self, color: Color) {
2980 self.fill_color = Some(color);
2981 }
2982
2983 pub fn fill_color(&self) -> Option<&Color> {
2985 self.fill_color.as_ref()
2986 }
2987
2988 #[inline]
2992 pub fn page_obj_get_fill_color(&self) -> Option<&Color> {
2993 self.fill_color()
2994 }
2995
2996 #[deprecated(
3000 note = "use `page_obj_get_fill_color()` — matches upstream `FPDFPageObj_GetFillColor`"
3001 )]
3002 #[inline]
3003 pub fn get_fill_color(&self) -> Option<&Color> {
3004 self.fill_color()
3005 }
3006
3007 pub fn set_stroke_color(&mut self, color: Color) {
3011 self.stroke_color = Some(color);
3012 }
3013
3014 pub fn stroke_color(&self) -> Option<&Color> {
3016 self.stroke_color.as_ref()
3017 }
3018
3019 #[inline]
3023 pub fn page_obj_get_stroke_color(&self) -> Option<&Color> {
3024 self.stroke_color()
3025 }
3026
3027 #[deprecated(
3031 note = "use `page_obj_get_stroke_color()` — matches upstream `FPDFPageObj_GetStrokeColor`"
3032 )]
3033 #[inline]
3034 pub fn get_stroke_color(&self) -> Option<&Color> {
3035 self.stroke_color()
3036 }
3037
3038 pub fn set_font_size(&mut self, size: f32) {
3043 self.font_size = size;
3044 }
3045
3046 pub fn set_text(&mut self, text: impl Into<String>) {
3050 self.text = text.into();
3051 }
3052
3053 #[inline]
3057 pub fn text_obj_set_text(&mut self, text: impl Into<String>) {
3058 self.set_text(text)
3059 }
3060
3061 #[deprecated(note = "use `set_text()` — matches upstream FPDFText_SetText")]
3066 #[inline]
3067 pub fn set_text_content(&mut self, text: String) {
3068 self.set_text(text);
3069 }
3070
3071 pub fn set_rendering_mode(&mut self, mode: TextRenderingMode) {
3075 self.rendering_mode = mode;
3076 }
3077
3078 #[inline]
3082 pub fn text_obj_set_text_render_mode(&mut self, mode: TextRenderingMode) {
3083 self.set_rendering_mode(mode);
3084 }
3085
3086 #[deprecated(
3090 note = "use `text_obj_set_text_render_mode()` — matches upstream `FPDFTextObj_SetTextRenderMode`"
3091 )]
3092 #[inline]
3093 pub fn set_text_render_mode(&mut self, mode: TextRenderingMode) {
3094 self.set_rendering_mode(mode);
3095 }
3096
3097 pub fn transform(&mut self, m: &Matrix) {
3101 self.matrix = m.pre_concat(&self.matrix);
3102 }
3103
3104 pub fn blend_mode(&self) -> Option<BlendMode> {
3108 self.blend_mode
3109 }
3110
3111 #[inline]
3115 pub fn page_obj_get_blend_mode(&self) -> Option<BlendMode> {
3116 self.blend_mode()
3117 }
3118
3119 #[deprecated(
3123 note = "use `page_obj_get_blend_mode()` — matches upstream `FPDFPageObj_GetBlendMode`"
3124 )]
3125 #[inline]
3126 pub fn get_blend_mode(&self) -> Option<BlendMode> {
3127 self.blend_mode()
3128 }
3129
3130 pub fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
3134 self.blend_mode = mode;
3135 }
3136
3137 pub fn mark_count(&self) -> usize {
3145 self.marks.len()
3146 }
3147
3148 #[inline]
3152 pub fn page_obj_count_marks(&self) -> usize {
3153 self.mark_count()
3154 }
3155
3156 #[deprecated(note = "use `page_obj_count_marks()` — matches upstream `FPDFPageObj_CountMarks`")]
3160 #[inline]
3161 pub fn count_marks(&self) -> usize {
3162 self.mark_count()
3163 }
3164
3165 pub fn mark(&self, index: usize) -> Option<&ContentMark> {
3169 self.marks.get(index)
3170 }
3171
3172 #[inline]
3176 pub fn page_obj_get_mark(&self, index: usize) -> Option<&ContentMark> {
3177 self.mark(index)
3178 }
3179
3180 #[deprecated(note = "use `page_obj_get_mark()` — matches upstream `FPDFPageObj_GetMark`")]
3184 #[inline]
3185 pub fn get_mark(&self, index: usize) -> Option<&ContentMark> {
3186 self.mark(index)
3187 }
3188
3189 pub fn add_mark(&mut self, name: impl Into<String>) -> &mut ContentMark {
3194 self.marks.push(ContentMark::new(name));
3195 self.marks.last_mut().unwrap()
3196 }
3197
3198 pub fn remove_mark(&mut self, index: usize) -> bool {
3205 if index < self.marks.len() {
3206 self.marks.remove(index);
3207 true
3208 } else {
3209 false
3210 }
3211 }
3212
3213 pub fn marks(&self) -> &[ContentMark] {
3215 &self.marks
3216 }
3217
3218 pub fn marked_content_id(&self) -> Option<i64> {
3223 self.marks.iter().find_map(|m| m.marked_content_id())
3224 }
3225
3226 #[inline]
3230 pub fn page_obj_get_marked_content_id(&self) -> Option<i64> {
3231 self.marked_content_id()
3232 }
3233
3234 #[deprecated(
3238 note = "use `page_obj_get_marked_content_id()` — matches upstream `FPDFPageObj_GetMarkedContentID`"
3239 )]
3240 #[inline]
3241 pub fn get_marked_content_id(&self) -> Option<i64> {
3242 self.marked_content_id()
3243 }
3244
3245 pub fn has_transparency(&self) -> bool {
3252 matches!(self.blend_mode, Some(bm) if bm != BlendMode::Normal)
3253 }
3254
3255 #[deprecated(note = "use `has_transparency()` — matches upstream FPDFPageObj_HasTransparency")]
3260 #[inline]
3261 pub fn is_transparent(&self) -> bool {
3262 self.has_transparency()
3263 }
3264
3265 pub fn object_type(&self) -> u32 {
3269 1
3270 }
3271
3272 #[deprecated(note = "use `page_obj_get_type()` — matches upstream `FPDFPageObj_GetType`")]
3277 #[inline]
3278 pub fn get_object_type(&self) -> u32 {
3279 self.object_type()
3280 }
3281
3282 #[inline]
3286 pub fn page_obj_get_type(&self) -> u32 {
3287 self.object_type()
3288 }
3289
3290 #[deprecated(note = "use `page_obj_get_type()` — matches upstream `FPDFPageObj_GetType`")]
3291 #[inline]
3292 pub fn get_type(&self) -> u32 {
3293 self.object_type()
3294 }
3295
3296 pub fn rotated_bounds(&self) -> Option<[rpdfium_core::Point; 4]> {
3304 let m = &self.matrix;
3305 let corners = [
3306 rpdfium_core::Point { x: m.e, y: m.f },
3307 rpdfium_core::Point {
3308 x: m.a + m.e,
3309 y: m.b + m.f,
3310 },
3311 rpdfium_core::Point {
3312 x: m.a + m.c + m.e,
3313 y: m.b + m.d + m.f,
3314 },
3315 rpdfium_core::Point {
3316 x: m.c + m.e,
3317 y: m.d + m.f,
3318 },
3319 ];
3320 Some(corners)
3321 }
3322
3323 #[inline]
3327 pub fn page_obj_get_rotated_bounds(&self) -> Option<[rpdfium_core::Point; 4]> {
3328 self.rotated_bounds()
3329 }
3330
3331 #[deprecated(
3335 note = "use `page_obj_get_rotated_bounds()` — matches upstream `FPDFPageObj_GetRotatedBounds`"
3336 )]
3337 #[inline]
3338 pub fn get_rotated_bounds(&self) -> Option<[rpdfium_core::Point; 4]> {
3339 self.rotated_bounds()
3340 }
3341
3342 pub fn set_char_codes(&mut self, codes: &[u32]) {
3351 self.text = codes
3352 .iter()
3353 .map(|&c| char::from_u32(c).unwrap_or('\u{FFFD}'))
3354 .collect();
3355 }
3356
3357 #[inline]
3361 pub fn text_set_charcodes(&mut self, codes: &[u32]) {
3362 self.set_char_codes(codes)
3363 }
3364
3365 #[deprecated(note = "use `text_set_charcodes()` — matches upstream `FPDFText_SetCharcodes`")]
3369 #[inline]
3370 pub fn set_charcodes(&mut self, codes: &[u32]) {
3371 self.set_char_codes(codes)
3372 }
3373
3374 pub fn rendered_bitmap(
3383 &self,
3384 _page_index: usize,
3385 _scale: f32,
3386 ) -> Result<Bitmap, crate::error::EditError> {
3387 Err(crate::error::EditError::NotSupported(
3388 "rendered_bitmap: rendering page objects to bitmap requires document context (ADR-002: read-only)".into()
3389 ))
3390 }
3391
3392 #[inline]
3396 pub fn text_obj_get_rendered_bitmap(
3397 &self,
3398 page_index: usize,
3399 scale: f32,
3400 ) -> Result<Bitmap, crate::error::EditError> {
3401 self.rendered_bitmap(page_index, scale)
3402 }
3403
3404 #[deprecated(
3408 note = "use `text_obj_get_rendered_bitmap()` — matches upstream `FPDFTextObj_GetRenderedBitmap`"
3409 )]
3410 #[inline]
3411 pub fn get_rendered_bitmap(
3412 &self,
3413 page_index: usize,
3414 scale: f32,
3415 ) -> Result<Bitmap, crate::error::EditError> {
3416 self.rendered_bitmap(page_index, scale)
3417 }
3418}
3419
3420#[derive(Debug, Clone)]
3422pub struct ImageObject {
3423 pub image_data: Vec<u8>,
3425 pub width: u32,
3427 pub height: u32,
3429 pub bits_per_component: u8,
3431 pub color_space: Name,
3433 pub matrix: Matrix,
3435 pub filter: Option<Name>,
3437 pub xobject_id: Option<ObjectId>,
3442 pub blend_mode: Option<BlendMode>,
3446 pub active: bool,
3450 pub marks: Vec<ContentMark>,
3457 pub clip_path: Option<ClipPath>,
3461}
3462
3463#[derive(Debug, Clone, PartialEq)]
3467pub struct ImageMetadata {
3468 pub width: u32,
3470 pub height: u32,
3472 pub horizontal_dpi: f32,
3477 pub vertical_dpi: f32,
3482 pub bits_per_component: u8,
3484 pub color_space: Name,
3486 pub filter: Option<Name>,
3488 pub marked_content_id: i32,
3492}
3493
3494#[derive(Debug, Clone)]
3496pub struct FormObject {
3497 pub xobject_id: ObjectId,
3499 pub matrix: Matrix,
3501 pub blend_mode: Option<BlendMode>,
3505 pub active: bool,
3509 pub marks: Vec<ContentMark>,
3516 pub clip_path: Option<ClipPath>,
3520}
3521
3522impl FormObject {
3523 pub fn from_xobject(xobject_id: ObjectId, matrix: Matrix) -> Self {
3530 Self {
3531 xobject_id,
3532 matrix,
3533 blend_mode: None,
3534 active: true,
3535 marks: Vec::new(),
3536 clip_path: None,
3537 }
3538 }
3539
3540 #[inline]
3544 pub fn new_form_object_from_x_object(xobject_id: ObjectId, matrix: Matrix) -> Self {
3545 Self::from_xobject(xobject_id, matrix)
3546 }
3547
3548 #[deprecated(
3552 note = "use `new_form_object_from_x_object()` — matches upstream `FPDF_NewFormObjectFromXObject`"
3553 )]
3554 #[inline]
3555 pub fn new_form_object_from_xobject(xobject_id: ObjectId, matrix: Matrix) -> Self {
3556 Self::from_xobject(xobject_id, matrix)
3557 }
3558
3559 pub fn transform(&mut self, m: &Matrix) {
3563 self.matrix = m.pre_concat(&self.matrix);
3564 }
3565
3566 pub fn blend_mode(&self) -> Option<BlendMode> {
3570 self.blend_mode
3571 }
3572
3573 #[inline]
3577 pub fn page_obj_get_blend_mode(&self) -> Option<BlendMode> {
3578 self.blend_mode()
3579 }
3580
3581 #[deprecated(
3585 note = "use `page_obj_get_blend_mode()` — matches upstream `FPDFPageObj_GetBlendMode`"
3586 )]
3587 #[inline]
3588 pub fn get_blend_mode(&self) -> Option<BlendMode> {
3589 self.blend_mode()
3590 }
3591
3592 pub fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
3596 self.blend_mode = mode;
3597 }
3598
3599 pub fn matrix(&self) -> &Matrix {
3603 &self.matrix
3604 }
3605
3606 #[inline]
3610 pub fn page_obj_get_matrix(&self) -> &Matrix {
3611 self.matrix()
3612 }
3613
3614 #[deprecated(note = "use `page_obj_get_matrix()` — matches upstream `FPDFPageObj_GetMatrix`")]
3615 #[inline]
3616 pub fn get_matrix(&self) -> &Matrix {
3617 self.matrix()
3618 }
3619
3620 pub fn set_matrix(&mut self, matrix: Matrix) {
3624 self.matrix = matrix;
3625 }
3626
3627 pub fn bounds(&self) -> Option<rpdfium_core::Rect> {
3634 let unit = rpdfium_core::Rect::new(0.0, 0.0, 1.0, 1.0);
3635 let r = self.matrix.transform_rect(unit);
3636 if r.is_empty() { None } else { Some(r) }
3637 }
3638
3639 pub fn mark_count(&self) -> usize {
3647 self.marks.len()
3648 }
3649
3650 #[inline]
3654 pub fn page_obj_count_marks(&self) -> usize {
3655 self.mark_count()
3656 }
3657
3658 #[deprecated(note = "use `page_obj_count_marks()` — matches upstream `FPDFPageObj_CountMarks`")]
3662 #[inline]
3663 pub fn count_marks(&self) -> usize {
3664 self.mark_count()
3665 }
3666
3667 pub fn mark(&self, index: usize) -> Option<&ContentMark> {
3671 self.marks.get(index)
3672 }
3673
3674 #[inline]
3678 pub fn page_obj_get_mark(&self, index: usize) -> Option<&ContentMark> {
3679 self.mark(index)
3680 }
3681
3682 #[deprecated(note = "use `page_obj_get_mark()` — matches upstream `FPDFPageObj_GetMark`")]
3686 #[inline]
3687 pub fn get_mark(&self, index: usize) -> Option<&ContentMark> {
3688 self.mark(index)
3689 }
3690
3691 pub fn add_mark(&mut self, name: impl Into<String>) -> &mut ContentMark {
3696 self.marks.push(ContentMark::new(name));
3697 self.marks.last_mut().unwrap()
3698 }
3699
3700 pub fn remove_mark(&mut self, index: usize) -> bool {
3707 if index < self.marks.len() {
3708 self.marks.remove(index);
3709 true
3710 } else {
3711 false
3712 }
3713 }
3714
3715 pub fn marks(&self) -> &[ContentMark] {
3717 &self.marks
3718 }
3719
3720 pub fn marked_content_id(&self) -> Option<i64> {
3725 self.marks.iter().find_map(|m| m.marked_content_id())
3726 }
3727
3728 #[inline]
3732 pub fn page_obj_get_marked_content_id(&self) -> Option<i64> {
3733 self.marked_content_id()
3734 }
3735
3736 #[deprecated(
3740 note = "use `page_obj_get_marked_content_id()` — matches upstream `FPDFPageObj_GetMarkedContentID`"
3741 )]
3742 #[inline]
3743 pub fn get_marked_content_id(&self) -> Option<i64> {
3744 self.marked_content_id()
3745 }
3746
3747 pub fn has_transparency(&self) -> bool {
3754 matches!(self.blend_mode, Some(bm) if bm != BlendMode::Normal)
3755 }
3756
3757 #[deprecated(note = "use `has_transparency()` — matches upstream FPDFPageObj_HasTransparency")]
3762 #[inline]
3763 pub fn is_transparent(&self) -> bool {
3764 self.has_transparency()
3765 }
3766
3767 pub fn object_type(&self) -> u32 {
3771 5
3772 }
3773
3774 #[deprecated(note = "use `page_obj_get_type()` — matches upstream `FPDFPageObj_GetType`")]
3779 #[inline]
3780 pub fn get_object_type(&self) -> u32 {
3781 self.object_type()
3782 }
3783
3784 #[inline]
3788 pub fn page_obj_get_type(&self) -> u32 {
3789 self.object_type()
3790 }
3791
3792 #[deprecated(note = "use `page_obj_get_type()` — matches upstream `FPDFPageObj_GetType`")]
3793 #[inline]
3794 pub fn get_type(&self) -> u32 {
3795 self.object_type()
3796 }
3797
3798 pub fn object_count(&self) -> Result<usize, crate::error::EditError> {
3807 Err(crate::error::EditError::NotSupported(
3808 "object_count: Form XObject child enumeration not supported (requires document context)"
3809 .into(),
3810 ))
3811 }
3812
3813 #[inline]
3817 pub fn form_obj_count_objects(&self) -> Result<usize, crate::error::EditError> {
3818 self.object_count()
3819 }
3820
3821 #[deprecated(
3825 note = "use `form_obj_count_objects()` — matches upstream `FPDFFormObj_CountObjects`"
3826 )]
3827 #[inline]
3828 pub fn count_objects(&self) -> Result<usize, crate::error::EditError> {
3829 self.object_count()
3830 }
3831
3832 pub fn object_at(&self, _index: usize) -> Result<PageObject, crate::error::EditError> {
3840 Err(crate::error::EditError::NotSupported(
3841 "object_at: Form XObject child enumeration not supported (requires document context)"
3842 .into(),
3843 ))
3844 }
3845
3846 #[inline]
3850 pub fn form_obj_get_object(&self, index: usize) -> Result<PageObject, crate::error::EditError> {
3851 self.object_at(index)
3852 }
3853
3854 #[deprecated(note = "use `form_obj_get_object()` — matches upstream `FPDFFormObj_GetObject`")]
3858 #[inline]
3859 pub fn get_object(&self, index: usize) -> Result<PageObject, crate::error::EditError> {
3860 self.object_at(index)
3861 }
3862
3863 pub fn remove_object(&mut self, _index: usize) -> Result<PageObject, crate::error::EditError> {
3871 Err(crate::error::EditError::NotSupported(
3872 "remove_object: Form XObject child mutation not supported".into(),
3873 ))
3874 }
3875}
3876
3877impl ImageObject {
3878 pub fn from_bitmap(bitmap: &Bitmap, matrix: Matrix) -> Self {
3892 assert_eq!(
3893 bitmap.format,
3894 BitmapFormat::Rgba32,
3895 "from_bitmap requires Rgba32 format"
3896 );
3897
3898 let w = bitmap.width as usize;
3899 let h = bitmap.height as usize;
3900 let stride = bitmap.stride as usize;
3901 let mut rgb_data = Vec::with_capacity(w * h * 3);
3902
3903 for row in 0..h {
3904 for col in 0..w {
3905 let offset = row * stride + col * 4;
3906 let r = bitmap.data[offset];
3907 let g = bitmap.data[offset + 1];
3908 let b = bitmap.data[offset + 2];
3909 let a = bitmap.data[offset + 3];
3910 if a == 0 {
3911 rgb_data.extend_from_slice(&[0, 0, 0]);
3912 } else if a == 255 {
3913 rgb_data.extend_from_slice(&[r, g, b]);
3914 } else {
3915 let a32 = a as u32;
3916 let ru = ((r as u32 * 255 + a32 / 2) / a32).min(255) as u8;
3917 let gu = ((g as u32 * 255 + a32 / 2) / a32).min(255) as u8;
3918 let bu = ((b as u32 * 255 + a32 / 2) / a32).min(255) as u8;
3919 rgb_data.extend_from_slice(&[ru, gu, bu]);
3920 }
3921 }
3922 }
3923
3924 let compressed = crate::cpdf_flateencoder::flate_compress(&rgb_data);
3925
3926 Self {
3927 image_data: compressed,
3928 width: bitmap.width,
3929 height: bitmap.height,
3930 bits_per_component: 8,
3931 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
3932 matrix,
3933 filter: Some(Name::from_bytes(b"FlateDecode".to_vec())),
3934 xobject_id: None,
3935 blend_mode: None,
3936 active: true,
3937 marks: Vec::new(),
3938 clip_path: None,
3939 }
3940 }
3941
3942 pub fn from_jpeg_bytes(
3954 jpeg_data: Vec<u8>,
3955 matrix: Matrix,
3956 ) -> Result<Self, crate::error::EditError> {
3957 let (width, height) = parse_jpeg_dimensions(&jpeg_data).ok_or_else(|| {
3958 crate::error::EditError::Other(
3959 "from_jpeg_bytes: could not parse JPEG dimensions (no SOF0/SOF2 marker found)"
3960 .to_owned(),
3961 )
3962 })?;
3963
3964 Ok(Self {
3965 image_data: jpeg_data,
3966 width,
3967 height,
3968 bits_per_component: 8,
3969 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
3970 matrix,
3971 filter: Some(Name::from_bytes(b"DCTDecode".to_vec())),
3972 xobject_id: None,
3973 blend_mode: None,
3974 active: true,
3975 marks: Vec::new(),
3976 clip_path: None,
3977 })
3978 }
3979
3980 pub fn transform(&mut self, m: &Matrix) {
3984 self.matrix = m.pre_concat(&self.matrix);
3985 }
3986
3987 pub fn blend_mode(&self) -> Option<BlendMode> {
3991 self.blend_mode
3992 }
3993
3994 #[inline]
3998 pub fn page_obj_get_blend_mode(&self) -> Option<BlendMode> {
3999 self.blend_mode()
4000 }
4001
4002 #[deprecated(
4006 note = "use `page_obj_get_blend_mode()` — matches upstream `FPDFPageObj_GetBlendMode`"
4007 )]
4008 #[inline]
4009 pub fn get_blend_mode(&self) -> Option<BlendMode> {
4010 self.blend_mode()
4011 }
4012
4013 pub fn set_blend_mode(&mut self, mode: Option<BlendMode>) {
4017 self.blend_mode = mode;
4018 }
4019
4020 pub fn metadata(&self) -> ImageMetadata {
4024 let m = &self.matrix;
4028 let scale_x = (m.a * m.a + m.b * m.b).sqrt() as f32;
4029 let scale_y = (m.c * m.c + m.d * m.d).sqrt() as f32;
4030 let horizontal_dpi = if scale_x > 0.0 {
4031 self.width as f32 * 72.0 / scale_x
4032 } else {
4033 0.0
4034 };
4035 let vertical_dpi = if scale_y > 0.0 {
4036 self.height as f32 * 72.0 / scale_y
4037 } else {
4038 0.0
4039 };
4040
4041 ImageMetadata {
4042 width: self.width,
4043 height: self.height,
4044 horizontal_dpi,
4045 vertical_dpi,
4046 bits_per_component: self.bits_per_component,
4047 color_space: self.color_space.clone(),
4048 filter: self.filter.clone(),
4049 marked_content_id: -1,
4050 }
4051 }
4052
4053 #[inline]
4057 pub fn image_obj_get_image_metadata(&self) -> ImageMetadata {
4058 self.metadata()
4059 }
4060
4061 #[deprecated(
4062 note = "use `image_obj_get_image_metadata()` — matches upstream `FPDFImageObj_GetImageMetadata`"
4063 )]
4064 #[inline]
4065 pub fn get_image_metadata(&self) -> ImageMetadata {
4066 self.metadata()
4067 }
4068
4069 pub fn raw_data(&self) -> &[u8] {
4073 &self.image_data
4074 }
4075
4076 #[inline]
4080 pub fn image_obj_get_image_data_raw(&self) -> &[u8] {
4081 self.raw_data()
4082 }
4083
4084 #[deprecated(
4085 note = "use `image_obj_get_image_data_raw()` — matches upstream `FPDFImageObj_GetImageDataRaw`"
4086 )]
4087 #[inline]
4088 pub fn get_image_data_raw(&self) -> &[u8] {
4089 self.raw_data()
4090 }
4091
4092 pub fn decoded_data(&self) -> Result<Vec<u8>, crate::error::EditError> {
4100 if let Some(ref filter_name) = self.filter {
4101 let filter = parse_image_filter(filter_name)?;
4102 rpdfium_codec::apply_filter_chain(
4103 &self.image_data,
4104 &[(filter, rpdfium_codec::FilterParams::default())],
4105 )
4106 .map_err(|e| crate::error::EditError::Other(format!("image decode error: {e}")))
4107 } else {
4108 Ok(self.image_data.clone())
4109 }
4110 }
4111
4112 #[inline]
4116 pub fn image_obj_get_image_data_decoded(&self) -> Result<Vec<u8>, crate::error::EditError> {
4117 self.decoded_data()
4118 }
4119
4120 #[deprecated(
4121 note = "use `image_obj_get_image_data_decoded()` — matches upstream `FPDFImageObj_GetImageDataDecoded`"
4122 )]
4123 #[inline]
4124 pub fn get_image_data_decoded(&self) -> Result<Vec<u8>, crate::error::EditError> {
4125 self.decoded_data()
4126 }
4127
4128 pub fn to_bitmap(&self) -> Result<Bitmap, crate::error::EditError> {
4138 let decoded = self.decoded_data()?;
4139 let cs = self.color_space.as_bytes();
4140 if cs == b"DeviceRGB" || cs == b"RGB" {
4141 let w = self.width as usize;
4142 let h = self.height as usize;
4143 let expected = w * h * 3;
4144 if decoded.len() < expected {
4145 return Err(crate::error::EditError::Other(format!(
4146 "decoded image too short: {} < {} (DeviceRGB {}x{})",
4147 decoded.len(),
4148 expected,
4149 self.width,
4150 self.height
4151 )));
4152 }
4153 let mut bmp = Bitmap::new(self.width, self.height, BitmapFormat::Rgba32);
4154 let stride = bmp.stride as usize;
4155 for row in 0..h {
4156 for col in 0..w {
4157 let src = (row * w + col) * 3;
4158 let dst = row * stride + col * 4;
4159 bmp.data[dst] = decoded[src];
4160 bmp.data[dst + 1] = decoded[src + 1];
4161 bmp.data[dst + 2] = decoded[src + 2];
4162 bmp.data[dst + 3] = 255;
4163 }
4164 }
4165 Ok(bmp)
4166 } else if cs == b"DeviceGray" || cs == b"Gray" {
4167 let w = self.width as usize;
4168 let h = self.height as usize;
4169 let expected = w * h;
4170 if decoded.len() < expected {
4171 return Err(crate::error::EditError::Other(format!(
4172 "decoded image too short: {} < {} (DeviceGray {}x{})",
4173 decoded.len(),
4174 expected,
4175 self.width,
4176 self.height
4177 )));
4178 }
4179 let mut bmp = Bitmap::new(self.width, self.height, BitmapFormat::Gray8);
4180 let stride = bmp.stride as usize;
4181 for row in 0..h {
4182 let src = row * w;
4183 let dst = row * stride;
4184 bmp.data[dst..dst + w].copy_from_slice(&decoded[src..src + w]);
4185 }
4186 Ok(bmp)
4187 } else {
4188 Err(crate::error::EditError::Other(format!(
4189 "to_bitmap: unsupported color space {:?}",
4190 self.color_space
4191 )))
4192 }
4193 }
4194
4195 #[inline]
4199 pub fn image_obj_get_bitmap(&self) -> Result<Bitmap, crate::error::EditError> {
4200 self.to_bitmap()
4201 }
4202
4203 #[deprecated(note = "use `image_obj_get_bitmap()` — matches upstream `FPDFImageObj_GetBitmap`")]
4207 #[inline]
4208 pub fn get_bitmap(&self) -> Result<Bitmap, crate::error::EditError> {
4209 self.to_bitmap()
4210 }
4211
4212 pub fn matrix(&self) -> &Matrix {
4216 &self.matrix
4217 }
4218
4219 #[inline]
4223 pub fn page_obj_get_matrix(&self) -> &Matrix {
4224 self.matrix()
4225 }
4226
4227 #[deprecated(note = "use `page_obj_get_matrix()` — matches upstream `FPDFPageObj_GetMatrix`")]
4228 #[inline]
4229 pub fn get_matrix(&self) -> &Matrix {
4230 self.matrix()
4231 }
4232
4233 pub fn set_matrix(&mut self, matrix: Matrix) {
4237 self.matrix = matrix;
4238 }
4239
4240 #[inline]
4244 pub fn image_obj_set_matrix(&mut self, matrix: Matrix) {
4245 self.set_matrix(matrix)
4246 }
4247
4248 pub fn bounds(&self) -> Option<rpdfium_core::Rect> {
4255 let unit = rpdfium_core::Rect::new(0.0, 0.0, 1.0, 1.0);
4256 let r = self.matrix.transform_rect(unit);
4257 if r.is_empty() { None } else { Some(r) }
4258 }
4259
4260 pub fn mark_count(&self) -> usize {
4268 self.marks.len()
4269 }
4270
4271 #[inline]
4275 pub fn page_obj_count_marks(&self) -> usize {
4276 self.mark_count()
4277 }
4278
4279 #[deprecated(note = "use `page_obj_count_marks()` — matches upstream `FPDFPageObj_CountMarks`")]
4283 #[inline]
4284 pub fn count_marks(&self) -> usize {
4285 self.mark_count()
4286 }
4287
4288 pub fn mark(&self, index: usize) -> Option<&ContentMark> {
4292 self.marks.get(index)
4293 }
4294
4295 #[inline]
4299 pub fn page_obj_get_mark(&self, index: usize) -> Option<&ContentMark> {
4300 self.mark(index)
4301 }
4302
4303 #[deprecated(note = "use `page_obj_get_mark()` — matches upstream `FPDFPageObj_GetMark`")]
4307 #[inline]
4308 pub fn get_mark(&self, index: usize) -> Option<&ContentMark> {
4309 self.mark(index)
4310 }
4311
4312 pub fn add_mark(&mut self, name: impl Into<String>) -> &mut ContentMark {
4317 self.marks.push(ContentMark::new(name));
4318 self.marks.last_mut().unwrap()
4319 }
4320
4321 pub fn remove_mark(&mut self, index: usize) -> bool {
4328 if index < self.marks.len() {
4329 self.marks.remove(index);
4330 true
4331 } else {
4332 false
4333 }
4334 }
4335
4336 pub fn marks(&self) -> &[ContentMark] {
4338 &self.marks
4339 }
4340
4341 pub fn marked_content_id(&self) -> Option<i64> {
4346 self.marks.iter().find_map(|m| m.marked_content_id())
4347 }
4348
4349 #[inline]
4353 pub fn page_obj_get_marked_content_id(&self) -> Option<i64> {
4354 self.marked_content_id()
4355 }
4356
4357 #[deprecated(
4361 note = "use `page_obj_get_marked_content_id()` — matches upstream `FPDFPageObj_GetMarkedContentID`"
4362 )]
4363 #[inline]
4364 pub fn get_marked_content_id(&self) -> Option<i64> {
4365 self.marked_content_id()
4366 }
4367
4368 pub fn has_transparency(&self) -> bool {
4375 matches!(self.blend_mode, Some(bm) if bm != BlendMode::Normal)
4376 }
4377
4378 #[deprecated(note = "use `has_transparency()` — matches upstream FPDFPageObj_HasTransparency")]
4383 #[inline]
4384 pub fn is_transparent(&self) -> bool {
4385 self.has_transparency()
4386 }
4387
4388 pub fn object_type(&self) -> u32 {
4392 3
4393 }
4394
4395 #[deprecated(note = "use `page_obj_get_type()` — matches upstream `FPDFPageObj_GetType`")]
4400 #[inline]
4401 pub fn get_object_type(&self) -> u32 {
4402 self.object_type()
4403 }
4404
4405 #[inline]
4409 pub fn page_obj_get_type(&self) -> u32 {
4410 self.object_type()
4411 }
4412
4413 #[deprecated(note = "use `page_obj_get_type()` — matches upstream `FPDFPageObj_GetType`")]
4414 #[inline]
4415 pub fn get_type(&self) -> u32 {
4416 self.object_type()
4417 }
4418
4419 pub fn filter_count(&self) -> usize {
4426 if self.filter.is_some() { 1 } else { 0 }
4427 }
4428
4429 #[inline]
4433 pub fn image_obj_get_image_filter_count(&self) -> usize {
4434 self.filter_count()
4435 }
4436
4437 #[deprecated(
4441 note = "use `image_obj_get_image_filter_count()` — matches upstream `FPDFImageObj_GetImageFilterCount`"
4442 )]
4443 #[inline]
4444 pub fn get_image_filter_count(&self) -> usize {
4445 self.filter_count()
4446 }
4447
4448 pub fn filter(&self, index: usize) -> Option<&str> {
4455 if index == 0 {
4456 self.filter
4457 .as_ref()
4458 .and_then(|n| std::str::from_utf8(n.as_bytes()).ok())
4459 } else {
4460 None
4461 }
4462 }
4463
4464 #[inline]
4468 pub fn image_obj_get_image_filter(&self, index: usize) -> Option<&str> {
4469 self.filter(index)
4470 }
4471
4472 #[deprecated(
4476 note = "use `image_obj_get_image_filter()` — matches upstream `FPDFImageObj_GetImageFilter`"
4477 )]
4478 #[inline]
4479 pub fn get_image_filter(&self, index: usize) -> Option<&str> {
4480 self.filter(index)
4481 }
4482
4483 #[deprecated(
4487 note = "use `image_obj_get_image_filter()` — matches upstream FPDFImageObj_GetImageFilter"
4488 )]
4489 #[inline]
4490 pub fn get_filter(&self, index: usize) -> Option<&str> {
4491 self.image_obj_get_image_filter(index)
4492 }
4493
4494 pub fn pixel_size(&self) -> (u32, u32) {
4500 (self.width, self.height)
4501 }
4502
4503 #[inline]
4507 pub fn image_obj_get_image_pixel_size(&self) -> (u32, u32) {
4508 self.pixel_size()
4509 }
4510
4511 #[deprecated(
4515 note = "use `image_obj_get_image_pixel_size()` — matches upstream `FPDFImageObj_GetImagePixelSize`"
4516 )]
4517 #[inline]
4518 pub fn get_image_pixel_size(&self) -> (u32, u32) {
4519 self.pixel_size()
4520 }
4521
4522 pub fn rotated_bounds(&self) -> Option<[rpdfium_core::Point; 4]> {
4531 let m = &self.matrix;
4532 let corners = [
4533 rpdfium_core::Point { x: m.e, y: m.f },
4534 rpdfium_core::Point {
4535 x: m.a + m.e,
4536 y: m.b + m.f,
4537 },
4538 rpdfium_core::Point {
4539 x: m.a + m.c + m.e,
4540 y: m.b + m.d + m.f,
4541 },
4542 rpdfium_core::Point {
4543 x: m.c + m.e,
4544 y: m.d + m.f,
4545 },
4546 ];
4547 Some(corners)
4548 }
4549
4550 #[inline]
4554 pub fn page_obj_get_rotated_bounds(&self) -> Option<[rpdfium_core::Point; 4]> {
4555 self.rotated_bounds()
4556 }
4557
4558 #[deprecated(
4562 note = "use `page_obj_get_rotated_bounds()` — matches upstream `FPDFPageObj_GetRotatedBounds`"
4563 )]
4564 #[inline]
4565 pub fn get_rotated_bounds(&self) -> Option<[rpdfium_core::Point; 4]> {
4566 self.rotated_bounds()
4567 }
4568
4569 pub fn rendered_bitmap(
4578 &self,
4579 _page_index: usize,
4580 _scale: f32,
4581 ) -> Result<Bitmap, crate::error::EditError> {
4582 Err(crate::error::EditError::NotSupported(
4583 "rendered_bitmap: rendering page objects to bitmap requires document context (ADR-002: read-only)".into()
4584 ))
4585 }
4586
4587 #[inline]
4591 pub fn image_obj_get_rendered_bitmap(
4592 &self,
4593 page_index: usize,
4594 scale: f32,
4595 ) -> Result<Bitmap, crate::error::EditError> {
4596 self.rendered_bitmap(page_index, scale)
4597 }
4598
4599 #[deprecated(
4603 note = "use `image_obj_get_rendered_bitmap()` — matches upstream `FPDFImageObj_GetRenderedBitmap`"
4604 )]
4605 #[inline]
4606 pub fn get_rendered_bitmap(
4607 &self,
4608 page_index: usize,
4609 scale: f32,
4610 ) -> Result<Bitmap, crate::error::EditError> {
4611 self.rendered_bitmap(page_index, scale)
4612 }
4613
4614 pub fn icc_profile_data_decoded(&self) -> Option<&[u8]> {
4622 None
4626 }
4627
4628 #[inline]
4632 pub fn image_obj_get_icc_profile_data_decoded(&self) -> Option<&[u8]> {
4633 self.icc_profile_data_decoded()
4634 }
4635
4636 #[deprecated(
4640 note = "use `image_obj_get_icc_profile_data_decoded()` — matches upstream `FPDFImageObj_GetIccProfileDataDecoded`"
4641 )]
4642 #[inline]
4643 pub fn get_icc_profile_data_decoded(&self) -> Option<&[u8]> {
4644 self.icc_profile_data_decoded()
4645 }
4646}
4647
4648pub fn path_ops_to_segments(ops: &[PathOp]) -> Vec<PathSegment> {
4653 ops.iter()
4654 .map(|op| match op {
4655 PathOp::MoveTo { x, y } => PathSegment::MoveTo(*x as f64, *y as f64),
4656 PathOp::LineTo { x, y } => PathSegment::LineTo(*x as f64, *y as f64),
4657 PathOp::CurveTo {
4658 x1,
4659 y1,
4660 x2,
4661 y2,
4662 x3,
4663 y3,
4664 } => PathSegment::CurveTo(
4665 *x1 as f64, *y1 as f64, *x2 as f64, *y2 as f64, *x3 as f64, *y3 as f64,
4666 ),
4667 PathOp::Close => PathSegment::Close,
4668 })
4669 .collect()
4670}
4671
4672pub fn segments_to_path_ops(segs: &[PathSegment]) -> Vec<PathOp> {
4678 let mut out = Vec::with_capacity(segs.len());
4679 for seg in segs {
4680 match seg {
4681 PathSegment::MoveTo(x, y) => {
4682 out.push(PathOp::MoveTo {
4683 x: *x as f32,
4684 y: *y as f32,
4685 });
4686 }
4687 PathSegment::LineTo(x, y) => {
4688 out.push(PathOp::LineTo {
4689 x: *x as f32,
4690 y: *y as f32,
4691 });
4692 }
4693 PathSegment::CurveTo(x1, y1, x2, y2, x3, y3) => {
4694 out.push(PathOp::CurveTo {
4695 x1: *x1 as f32,
4696 y1: *y1 as f32,
4697 x2: *x2 as f32,
4698 y2: *y2 as f32,
4699 x3: *x3 as f32,
4700 y3: *y3 as f32,
4701 });
4702 }
4703 PathSegment::Rect(x, y, w, h) => {
4704 let (x, y, w, h) = (*x as f32, *y as f32, *w as f32, *h as f32);
4705 out.push(PathOp::MoveTo { x, y });
4706 out.push(PathOp::LineTo { x: x + w, y });
4707 out.push(PathOp::LineTo { x: x + w, y: y + h });
4708 out.push(PathOp::LineTo { x, y: y + h });
4709 out.push(PathOp::Close);
4710 }
4711 PathSegment::Close => {
4712 out.push(PathOp::Close);
4713 }
4714 }
4715 }
4716 out
4717}
4718
4719fn parse_image_filter(name: &Name) -> Result<rpdfium_codec::DecodeFilter, crate::error::EditError> {
4723 match name.as_bytes() {
4724 b"FlateDecode" | b"Fl" => Ok(rpdfium_codec::DecodeFilter::Flate),
4725 b"DCTDecode" | b"DCT" => Ok(rpdfium_codec::DecodeFilter::DCT),
4726 b"LZWDecode" | b"LZW" => Ok(rpdfium_codec::DecodeFilter::LZW),
4727 b"RunLengthDecode" | b"RL" => Ok(rpdfium_codec::DecodeFilter::RunLength),
4728 b"ASCII85Decode" | b"A85" => Ok(rpdfium_codec::DecodeFilter::ASCII85),
4729 b"ASCIIHexDecode" | b"AHx" => Ok(rpdfium_codec::DecodeFilter::ASCIIHex),
4730 b"CCITTFaxDecode" | b"CCF" => Ok(rpdfium_codec::DecodeFilter::CCITTFax),
4731 b"JBIG2Decode" => Ok(rpdfium_codec::DecodeFilter::JBIG2),
4732 b"JPXDecode" => Ok(rpdfium_codec::DecodeFilter::JPX),
4733 _ => Err(crate::error::EditError::Other(format!(
4734 "unsupported image filter: {:?}",
4735 name
4736 ))),
4737 }
4738}
4739
4740pub fn parse_jpeg_dimensions(data: &[u8]) -> Option<(u32, u32)> {
4753 if data.len() < 2 || data[0] != 0xFF || data[1] != 0xD8 {
4755 return None;
4756 }
4757
4758 let mut i = 2usize;
4759 while i + 3 < data.len() {
4760 if data[i] != 0xFF {
4762 return None;
4763 }
4764 while i < data.len() && data[i] == 0xFF {
4766 i += 1;
4767 }
4768 if i >= data.len() {
4769 return None;
4770 }
4771 let marker = data[i];
4772 i += 1;
4773
4774 match marker {
4776 0xD8 | 0xD9 | 0x01 => continue,
4777 _ => {}
4778 }
4779
4780 if i + 2 > data.len() {
4782 return None;
4783 }
4784 let seg_len = u16::from_be_bytes([data[i], data[i + 1]]) as usize;
4785 if seg_len < 2 {
4786 return None;
4787 }
4788
4789 if marker == 0xC0 || marker == 0xC2 {
4791 if i + 7 > data.len() {
4798 return None;
4799 }
4800 let height = u16::from_be_bytes([data[i + 3], data[i + 4]]) as u32;
4801 let width = u16::from_be_bytes([data[i + 5], data[i + 6]]) as u32;
4802 return Some((width, height));
4803 }
4804
4805 let next = i.checked_add(seg_len)?;
4807 if next > data.len() {
4808 return None;
4809 }
4810 i = next;
4811 }
4812 None
4813}
4814
4815#[cfg(test)]
4816mod tests {
4817 use super::*;
4818 use crate::font_reg::{FontRegistration, FontType};
4819
4820 fn make_id(n: u32) -> ObjectId {
4821 ObjectId::new(n, 0)
4822 }
4823
4824 #[test]
4825 fn test_path_object_default() {
4826 let p = PathObject::default();
4827 assert!(p.segments.is_empty());
4828 assert!(p.fill_color.is_none());
4829 assert!(p.stroke_color.is_none());
4830 assert_eq!(p.fill_mode, FillMode::None);
4831 assert!(p.active, "PathObject::default() should be active");
4832 }
4833
4834 #[test]
4835 fn test_path_object_create_at_has_move_to() {
4836 let p = PathObject::create_at(10.0, 20.0);
4837 assert_eq!(p.segments.len(), 1);
4838 assert!(matches!(p.segments[0], PathSegment::MoveTo(x, y) if x == 10.0 && y == 20.0));
4839 }
4840
4841 #[test]
4842 fn test_path_object_create_at_alias() {
4843 let p1 = PathObject::create_at(5.0, 7.0);
4844 let p2 = PathObject::page_obj_create_new_path(5.0, 7.0);
4845 assert_eq!(p1.segments.len(), p2.segments.len());
4846 assert!(matches!(p1.segments[0], PathSegment::MoveTo(x, y) if x == 5.0 && y == 7.0));
4847 }
4848
4849 #[test]
4850 fn test_path_object_create_rect_has_rect_segment() {
4851 let p = PathObject::create_rect(0.0, 0.0, 100.0, 50.0);
4852 assert_eq!(p.segments.len(), 1);
4853 assert!(matches!(
4854 p.segments[0],
4855 PathSegment::Rect(x, y, w, h) if x == 0.0 && y == 0.0 && w == 100.0 && h == 50.0
4856 ));
4857 }
4858
4859 #[test]
4860 fn test_path_object_create_rect_alias() {
4861 let p1 = PathObject::create_rect(1.0, 2.0, 3.0, 4.0);
4862 let p2 = PathObject::page_obj_create_new_rect(1.0, 2.0, 3.0, 4.0);
4863 assert_eq!(p1.segments.len(), p2.segments.len());
4864 }
4865
4866 #[test]
4867 fn test_page_obj_destroy_returns_not_supported() {
4868 let p = PageObject::Path(PathObject::default());
4869 let result = p.page_obj_destroy();
4870 assert!(result.is_err());
4871 }
4872
4873 #[test]
4874 fn test_text_object_default() {
4875 let t = TextObject::default();
4876 assert_eq!(t.font_size, 12.0);
4877 assert!(t.fill_color.is_some());
4878 assert!(t.text.is_empty());
4879 assert!(t.font_object_id.is_none());
4880 assert!(t.active, "TextObject::default() should be active");
4881 }
4882
4883 #[test]
4884 fn test_page_object_is_active_set_active() {
4885 let mut p = PageObject::Path(PathObject::default());
4886 assert!(p.is_active());
4887 p.set_active(false);
4888 assert!(!p.is_active());
4889 p.set_active(true);
4890 assert!(p.is_active());
4891
4892 let mut t = PageObject::Text(TextObject::default());
4893 assert!(t.is_active());
4894 t.set_active(false);
4895 assert!(!t.is_active());
4896 }
4897
4898 #[test]
4899 fn test_fill_mode_variants() {
4900 assert_ne!(FillMode::None, FillMode::EvenOdd);
4901 assert_ne!(FillMode::EvenOdd, FillMode::NonZero);
4902 }
4903
4904 #[test]
4905 fn test_page_object_enum_variants() {
4906 let p = PageObject::Path(PathObject::default());
4907 assert!(matches!(p, PageObject::Path(_)));
4908
4909 let t = PageObject::Text(TextObject::default());
4910 assert!(matches!(t, PageObject::Text(_)));
4911 }
4912
4913 #[test]
4918 fn test_text_object_with_standard_font_name_matches() {
4919 let t = TextObject::with_standard_font("Times-Roman", 14.0);
4920 assert_eq!(t.font_name(), &Name::from_bytes(b"Times-Roman".to_vec()));
4921 assert_eq!(t.font_size(), 14.0);
4922 assert!(t.font_object_id.is_none());
4923 }
4924
4925 #[test]
4926 fn test_text_object_with_font_uses_registration_id() {
4927 let reg = FontRegistration::new_standard(make_id(42), "Courier");
4928 let t = TextObject::with_font(®, 10.0);
4929 assert_eq!(t.font_object_id, Some(make_id(42)));
4930 assert_eq!(t.font_name(), &Name::from_bytes(b"Courier".to_vec()));
4931 assert_eq!(t.font_size(), 10.0);
4932 }
4933
4934 #[test]
4935 fn test_text_object_getters_return_correct_values() {
4936 let mut t = TextObject::with_standard_font("Helvetica", 12.0);
4937 t.text = "Hello, PDF!".into();
4938 t.rendering_mode = TextRenderingMode::FillStroke;
4939
4940 assert_eq!(t.text(), "Hello, PDF!");
4941 assert_eq!(t.font_size(), 12.0);
4942 assert_eq!(t.rendering_mode(), TextRenderingMode::FillStroke);
4943 assert_eq!(t.font_name(), &Name::from_bytes(b"Helvetica".to_vec()));
4944 }
4945
4946 #[test]
4947 fn test_text_object_with_embedded_font() {
4948 let reg = FontRegistration::new_embedded(
4949 make_id(99),
4950 "MyFont",
4951 FontType::TrueType,
4952 vec![1, 2, 3],
4953 );
4954 let t = TextObject::with_font(®, 16.0);
4955 assert_eq!(t.font_object_id, Some(make_id(99)));
4956 assert_eq!(t.font_name(), &Name::from_bytes(b"MyFont".to_vec()));
4957 }
4958
4959 #[test]
4964 fn test_round_trip_basic_path() {
4965 let ops = vec![
4966 PathOp::MoveTo { x: 10.0, y: 20.0 },
4967 PathOp::LineTo { x: 50.0, y: 60.0 },
4968 PathOp::Close,
4969 ];
4970 let segs = path_ops_to_segments(&ops);
4971 assert!(matches!(segs[0], PathSegment::MoveTo(10.0, 20.0)));
4972 assert!(matches!(segs[1], PathSegment::LineTo(50.0, 60.0)));
4973 assert!(matches!(segs[2], PathSegment::Close));
4974
4975 let back = segments_to_path_ops(&segs);
4976 assert_eq!(back.len(), ops.len());
4977 assert!(matches!(back[0], PathOp::MoveTo { x, y } if x == 10.0 && y == 20.0));
4978 assert!(matches!(back[1], PathOp::LineTo { x, y } if x == 50.0 && y == 60.0));
4979 assert!(matches!(back[2], PathOp::Close));
4980 }
4981
4982 #[test]
4983 fn test_rect_segment_expands_to_5_ops() {
4984 let segs = vec![PathSegment::Rect(0.0, 0.0, 10.0, 20.0)];
4985 let ops = segments_to_path_ops(&segs);
4986 assert_eq!(ops.len(), 5);
4987 assert!(matches!(ops[0], PathOp::MoveTo { x, y } if x == 0.0 && y == 0.0));
4988 assert!(matches!(ops[1], PathOp::LineTo { x, y } if x == 10.0 && y == 0.0));
4989 assert!(matches!(ops[2], PathOp::LineTo { x, y } if x == 10.0 && y == 20.0));
4990 assert!(matches!(ops[3], PathOp::LineTo { x, y } if x == 0.0 && y == 20.0));
4991 assert!(matches!(ops[4], PathOp::Close));
4992 }
4993
4994 #[test]
4995 fn test_f32_precision_preserved() {
4996 let ops = vec![PathOp::MoveTo {
4997 x: 0.5_f32,
4998 y: 1.25_f32,
4999 }];
5000 let segs = path_ops_to_segments(&ops);
5001 if let PathSegment::MoveTo(x, y) = segs[0] {
5002 assert!((x - 0.5_f64).abs() < 1e-6);
5003 assert!((y - 1.25_f64).abs() < 1e-6);
5004 } else {
5005 panic!("expected MoveTo");
5006 }
5007 }
5008
5009 fn make_rgba_bitmap(pixels: &[(u8, u8, u8, u8)], w: u32, h: u32) -> Bitmap {
5014 let mut bmp = Bitmap::new(w, h, BitmapFormat::Rgba32);
5015 for (i, &(r, g, b, a)) in pixels.iter().enumerate() {
5016 let offset = i * 4;
5017 bmp.data[offset] = r;
5018 bmp.data[offset + 1] = g;
5019 bmp.data[offset + 2] = b;
5020 bmp.data[offset + 3] = a;
5021 }
5022 bmp
5023 }
5024
5025 #[test]
5026 fn test_bitmap_to_image_object_dimensions() {
5027 let bmp = Bitmap::new(2, 2, BitmapFormat::Rgba32);
5028 let obj = ImageObject::from_bitmap(&bmp, Matrix::identity());
5029 assert_eq!(obj.width, 2);
5030 assert_eq!(obj.height, 2);
5031 assert_eq!(obj.bits_per_component, 8);
5032 }
5033
5034 #[test]
5035 fn test_bitmap_to_image_object_unpremultiply() {
5036 let bmp = make_rgba_bitmap(&[(128, 64, 0, 128)], 1, 1);
5038 let obj = ImageObject::from_bitmap(&bmp, Matrix::identity());
5039 let rgb = rpdfium_codec::apply_filter_chain(
5041 &obj.image_data,
5042 &[(
5043 rpdfium_codec::DecodeFilter::Flate,
5044 rpdfium_codec::FilterParams::default(),
5045 )],
5046 )
5047 .unwrap();
5048 assert_eq!(rgb.len(), 3);
5049 assert_eq!(rgb[0], 255); assert_eq!(rgb[1], 128); assert_eq!(rgb[2], 0); }
5053
5054 #[test]
5055 fn test_bitmap_to_image_object_opaque_pixel_unchanged() {
5056 let bmp = make_rgba_bitmap(&[(200, 100, 50, 255)], 1, 1);
5057 let obj = ImageObject::from_bitmap(&bmp, Matrix::identity());
5058 let rgb = rpdfium_codec::apply_filter_chain(
5059 &obj.image_data,
5060 &[(
5061 rpdfium_codec::DecodeFilter::Flate,
5062 rpdfium_codec::FilterParams::default(),
5063 )],
5064 )
5065 .unwrap();
5066 assert_eq!(rgb, vec![200, 100, 50]);
5067 }
5068
5069 #[test]
5070 fn test_bitmap_to_image_object_transparent_pixel_is_black() {
5071 let bmp = make_rgba_bitmap(&[(100, 100, 100, 0)], 1, 1);
5072 let obj = ImageObject::from_bitmap(&bmp, Matrix::identity());
5073 let rgb = rpdfium_codec::apply_filter_chain(
5074 &obj.image_data,
5075 &[(
5076 rpdfium_codec::DecodeFilter::Flate,
5077 rpdfium_codec::FilterParams::default(),
5078 )],
5079 )
5080 .unwrap();
5081 assert_eq!(rgb, vec![0, 0, 0]);
5082 }
5083
5084 #[test]
5085 fn test_bitmap_to_image_object_filter_is_flatedecode() {
5086 let bmp = Bitmap::new(1, 1, BitmapFormat::Rgba32);
5087 let obj = ImageObject::from_bitmap(&bmp, Matrix::identity());
5088 assert_eq!(obj.filter, Some(Name::from_bytes(b"FlateDecode".to_vec())));
5089 }
5090
5091 #[test]
5096 fn test_path_object_set_fill_color() {
5097 let mut p = PathObject::default();
5098 assert!(p.fill_color().is_none());
5099 p.set_fill_color(Color::rgb(1.0, 0.0, 0.0));
5100 let c = p.fill_color().unwrap();
5101 assert_eq!(c.components, vec![1.0, 0.0, 0.0]);
5102 }
5103
5104 #[test]
5105 fn test_path_object_set_stroke_color() {
5106 let mut p = PathObject::default();
5107 assert!(p.stroke_color().is_none());
5108 p.set_stroke_color(Color::gray(0.5));
5109 let c = p.stroke_color().unwrap();
5110 assert_eq!(c.components, vec![0.5]);
5111 }
5112
5113 #[test]
5114 fn test_path_object_set_line_width() {
5115 let mut p = PathObject::default();
5116 assert_eq!(p.line_width(), 1.0);
5117 p.set_line_width(3.5);
5118 assert_eq!(p.line_width(), 3.5);
5119 }
5120
5121 #[test]
5122 fn test_path_object_set_line_join_and_cap() {
5123 let mut p = PathObject::default();
5124 assert_eq!(p.line_join(), 0);
5125 assert_eq!(p.line_cap(), 0);
5126 p.set_line_join(2);
5127 p.set_line_cap(1);
5128 assert_eq!(p.line_join(), 2);
5129 assert_eq!(p.line_cap(), 1);
5130 }
5131
5132 #[test]
5134 fn test_pdf_edit_line_cap() {
5135 let mut path = PathObject::default();
5136 assert_eq!(path.line_cap(), 0);
5138
5139 path.set_line_cap(2);
5141 assert_eq!(path.line_cap(), 2);
5142
5143 path.set_line_cap(1);
5145 assert_eq!(path.line_cap(), 1);
5146
5147 path.set_line_cap(0);
5149 assert_eq!(path.line_cap(), 0);
5150
5151 let page_obj = PageObject::Path(path);
5153 assert_eq!(page_obj.line_cap(), Some(0));
5154
5155 let text_obj = PageObject::Text(TextObject::default());
5156 assert_eq!(text_obj.line_cap(), None);
5157 }
5158
5159 #[test]
5160 fn test_path_object_bounds_simple_rect() {
5161 let p = PathObject {
5162 segments: vec![PathSegment::Rect(10.0, 20.0, 100.0, 50.0)],
5163 ..Default::default()
5164 };
5165 let b = p.bounds().unwrap();
5166 assert_eq!(b.left, 10.0);
5167 assert_eq!(b.bottom, 20.0);
5168 assert_eq!(b.right, 110.0);
5169 assert_eq!(b.top, 70.0);
5170 }
5171
5172 #[test]
5173 fn test_path_object_bounds_with_lines() {
5174 let p = PathObject {
5175 segments: vec![
5176 PathSegment::MoveTo(0.0, 0.0),
5177 PathSegment::LineTo(100.0, 0.0),
5178 PathSegment::LineTo(100.0, 50.0),
5179 PathSegment::Close,
5180 ],
5181 ..Default::default()
5182 };
5183 let b = p.bounds().unwrap();
5184 assert_eq!(b.left, 0.0);
5185 assert_eq!(b.bottom, 0.0);
5186 assert_eq!(b.right, 100.0);
5187 assert_eq!(b.top, 50.0);
5188 }
5189
5190 #[test]
5191 fn test_path_object_bounds_empty_returns_none() {
5192 let p = PathObject {
5193 segments: vec![PathSegment::Close],
5194 ..Default::default()
5195 };
5196 assert!(p.bounds().is_none());
5197 }
5198
5199 #[test]
5200 fn test_path_object_transform_translate() {
5201 let mut p = PathObject {
5202 segments: vec![
5203 PathSegment::MoveTo(10.0, 20.0),
5204 PathSegment::LineTo(30.0, 40.0),
5205 ],
5206 ..Default::default()
5207 };
5208 let m = Matrix::new(1.0, 0.0, 0.0, 1.0, 100.0, 200.0);
5209 p.transform(&m);
5210 assert!(
5211 matches!(p.segments[0], PathSegment::MoveTo(x, y) if (x - 110.0).abs() < 1e-9 && (y - 220.0).abs() < 1e-9)
5212 );
5213 assert!(
5214 matches!(p.segments[1], PathSegment::LineTo(x, y) if (x - 130.0).abs() < 1e-9 && (y - 240.0).abs() < 1e-9)
5215 );
5216 }
5217
5218 #[test]
5219 fn test_path_object_transform_scale_curve() {
5220 let mut p = PathObject {
5221 segments: vec![PathSegment::CurveTo(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)],
5222 ..Default::default()
5223 };
5224 let m = Matrix::new(2.0, 0.0, 0.0, 3.0, 0.0, 0.0);
5225 p.transform(&m);
5226 if let PathSegment::CurveTo(x1, y1, x2, y2, x3, y3) = p.segments[0] {
5227 assert!((x1 - 2.0).abs() < 1e-9);
5228 assert!((y1 - 6.0).abs() < 1e-9);
5229 assert!((x2 - 6.0).abs() < 1e-9);
5230 assert!((y2 - 12.0).abs() < 1e-9);
5231 assert!((x3 - 10.0).abs() < 1e-9);
5232 assert!((y3 - 18.0).abs() < 1e-9);
5233 } else {
5234 panic!("expected CurveTo");
5235 }
5236 }
5237
5238 #[test]
5239 fn test_text_object_set_fill_and_stroke_color() {
5240 let mut t = TextObject::default();
5241 t.set_fill_color(Color::rgb(0.0, 1.0, 0.0));
5242 assert_eq!(t.fill_color().unwrap().components, vec![0.0, 1.0, 0.0]);
5243 t.set_stroke_color(Color::gray(0.25));
5244 assert_eq!(t.stroke_color().unwrap().components, vec![0.25]);
5245 }
5246
5247 #[test]
5248 fn test_text_object_set_font_size_and_text() {
5249 let mut t = TextObject::default();
5250 t.set_font_size(24.0);
5251 assert_eq!(t.font_size(), 24.0);
5252 t.set_text("new text");
5253 assert_eq!(t.text(), "new text");
5254 }
5255
5256 #[test]
5257 fn test_text_object_set_rendering_mode() {
5258 let mut t = TextObject::default();
5259 t.set_rendering_mode(TextRenderingMode::Stroke);
5260 assert_eq!(t.rendering_mode(), TextRenderingMode::Stroke);
5261 }
5262
5263 #[test]
5264 fn test_text_object_set_text_updates_content() {
5265 let mut t = TextObject::default();
5266 assert_eq!(t.text(), "");
5267 t.set_text("Hello, world!");
5268 assert_eq!(t.text(), "Hello, world!");
5269 }
5270
5271 #[test]
5272 fn test_text_object_set_text_accepts_string_and_str() {
5273 let mut t = TextObject::default();
5274 t.set_text("foo");
5276 assert_eq!(t.text(), "foo");
5277 t.set_text(String::from("bar"));
5279 assert_eq!(t.text(), "bar");
5280 }
5281
5282 #[test]
5283 fn test_page_object_delegated_fill_color() {
5284 let mut po = PageObject::Path(PathObject::default());
5285 po.set_fill_color(Color::rgb(1.0, 0.5, 0.0));
5286 assert_eq!(po.fill_color().unwrap().components, vec![1.0, 0.5, 0.0]);
5287
5288 let mut to = PageObject::Text(TextObject::default());
5289 to.set_fill_color(Color::gray(0.8));
5290 assert_eq!(to.fill_color().unwrap().components, vec![0.8]);
5291 }
5292
5293 #[test]
5294 fn test_page_object_delegated_bounds() {
5295 let po = PageObject::Path(PathObject {
5296 segments: vec![PathSegment::Rect(0.0, 0.0, 50.0, 30.0)],
5297 ..Default::default()
5298 });
5299 let b = po.bounds().unwrap();
5300 assert_eq!(b.right, 50.0);
5301 assert_eq!(b.top, 30.0);
5302
5303 let to = PageObject::Text(TextObject::default());
5305 assert!(to.bounds().is_none());
5306
5307 let img_matrix = Matrix::new(100.0, 0.0, 0.0, 200.0, 50.0, 60.0);
5309 let img = ImageObject {
5310 image_data: vec![],
5311 width: 1,
5312 height: 1,
5313 bits_per_component: 8,
5314 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
5315 matrix: img_matrix,
5316 filter: None,
5317 xobject_id: None,
5318 blend_mode: None,
5319 active: true,
5320 marks: vec![],
5321 clip_path: None,
5322 };
5323 let ib = PageObject::Image(img).bounds().unwrap();
5324 assert!((ib.left - 50.0).abs() < 1e-9);
5325 assert!((ib.bottom - 60.0).abs() < 1e-9);
5326 assert!((ib.right - 150.0).abs() < 1e-9);
5327 assert!((ib.top - 260.0).abs() < 1e-9);
5328
5329 let form_matrix = Matrix::new(0.0, 1.0, -1.0, 0.0, 5.0, 10.0);
5331 let form = FormObject {
5332 xobject_id: rpdfium_parser::object::ObjectId::new(1, 0),
5333 matrix: form_matrix,
5334 blend_mode: None,
5335 active: true,
5336 marks: vec![],
5337 clip_path: None,
5338 };
5339 let fb = PageObject::Form(form).bounds().unwrap();
5340 assert!((fb.left - 4.0).abs() < 1e-9);
5343 assert!((fb.bottom - 10.0).abs() < 1e-9);
5344 assert!((fb.right - 5.0).abs() < 1e-9);
5345 assert!((fb.top - 11.0).abs() < 1e-9);
5346 }
5347
5348 #[test]
5353 fn test_image_object_metadata_round_trip() {
5354 let bmp = Bitmap::new(4, 3, BitmapFormat::Rgba32);
5355 let obj = ImageObject::from_bitmap(&bmp, Matrix::identity());
5356 let meta = obj.metadata();
5357 assert_eq!(meta.width, 4);
5358 assert_eq!(meta.height, 3);
5359 assert_eq!(meta.horizontal_dpi, 4.0 * 72.0);
5361 assert_eq!(meta.vertical_dpi, 3.0 * 72.0);
5362 assert_eq!(meta.bits_per_component, 8);
5363 assert_eq!(meta.color_space, Name::from_bytes(b"DeviceRGB".to_vec()));
5364 assert_eq!(meta.filter, Some(Name::from_bytes(b"FlateDecode".to_vec())));
5365 assert_eq!(meta.marked_content_id, -1);
5366 }
5367
5368 #[test]
5369 fn test_image_object_metadata_dpi_from_matrix() {
5370 let bmp = Bitmap::new(100, 200, BitmapFormat::Rgba32);
5375 let mat = Matrix {
5376 a: 2.0,
5377 b: 0.0,
5378 c: 0.0,
5379 d: 4.0,
5380 e: 0.0,
5381 f: 0.0,
5382 };
5383 let obj = ImageObject::from_bitmap(&bmp, mat);
5384 let meta = obj.metadata();
5385 assert!((meta.horizontal_dpi - 3600.0).abs() < 0.1);
5386 assert!((meta.vertical_dpi - 3600.0).abs() < 0.1);
5387 }
5388
5389 #[test]
5390 fn test_image_object_metadata_dpi_degenerate_matrix() {
5391 let bmp = Bitmap::new(10, 10, BitmapFormat::Rgba32);
5393 let mat = Matrix {
5394 a: 0.0,
5395 b: 0.0,
5396 c: 0.0,
5397 d: 0.0,
5398 e: 0.0,
5399 f: 0.0,
5400 };
5401 let obj = ImageObject::from_bitmap(&bmp, mat);
5402 let meta = obj.metadata();
5403 assert_eq!(meta.horizontal_dpi, 0.0);
5404 assert_eq!(meta.vertical_dpi, 0.0);
5405 }
5406
5407 #[test]
5408 fn test_image_object_raw_data_is_compressed() {
5409 let bmp = Bitmap::new(2, 2, BitmapFormat::Rgba32);
5410 let obj = ImageObject::from_bitmap(&bmp, Matrix::identity());
5411 let raw = obj.raw_data();
5413 assert!(!raw.is_empty());
5414 }
5415
5416 #[test]
5417 fn test_image_object_decoded_data_matches_rgb_pixels() {
5418 let mut bmp = Bitmap::new(1, 1, BitmapFormat::Rgba32);
5420 bmp.data[0] = 200; bmp.data[1] = 100; bmp.data[2] = 50; bmp.data[3] = 255; let obj = ImageObject::from_bitmap(&bmp, Matrix::identity());
5425 let decoded = obj.decoded_data().unwrap();
5426 assert_eq!(decoded, vec![200, 100, 50]);
5428 }
5429
5430 #[test]
5431 fn test_image_object_decoded_data_no_filter_returns_raw() {
5432 let obj = ImageObject {
5433 image_data: vec![1, 2, 3, 4, 5],
5434 width: 5,
5435 height: 1,
5436 bits_per_component: 8,
5437 color_space: Name::from_bytes(b"DeviceGray".to_vec()),
5438 matrix: Matrix::identity(),
5439 filter: None,
5440 xobject_id: None,
5441 blend_mode: None,
5442 active: true,
5443 marks: vec![],
5444 clip_path: None,
5445 };
5446 let decoded = obj.decoded_data().unwrap();
5447 assert_eq!(decoded, vec![1, 2, 3, 4, 5]);
5448 }
5449
5450 #[test]
5451 fn test_image_object_to_bitmap_rgb() {
5452 let mut bmp_src = Bitmap::new(2, 1, BitmapFormat::Rgba32);
5453 bmp_src.data[0] = 10;
5455 bmp_src.data[1] = 20;
5456 bmp_src.data[2] = 30;
5457 bmp_src.data[3] = 255;
5458 bmp_src.data[4] = 40;
5460 bmp_src.data[5] = 50;
5461 bmp_src.data[6] = 60;
5462 bmp_src.data[7] = 255;
5463 let obj = ImageObject::from_bitmap(&bmp_src, Matrix::identity());
5464 let bmp_out = obj.to_bitmap().unwrap();
5465 assert_eq!(bmp_out.width, 2);
5466 assert_eq!(bmp_out.height, 1);
5467 assert_eq!(bmp_out.format, BitmapFormat::Rgba32);
5468 assert_eq!(bmp_out.data[0], 10);
5470 assert_eq!(bmp_out.data[1], 20);
5471 assert_eq!(bmp_out.data[2], 30);
5472 assert_eq!(bmp_out.data[3], 255);
5473 assert_eq!(bmp_out.data[4], 40);
5475 assert_eq!(bmp_out.data[5], 50);
5476 assert_eq!(bmp_out.data[6], 60);
5477 assert_eq!(bmp_out.data[7], 255);
5478 }
5479
5480 #[test]
5481 fn test_image_object_to_bitmap_gray() {
5482 let obj = ImageObject {
5483 image_data: vec![128, 64],
5484 width: 2,
5485 height: 1,
5486 bits_per_component: 8,
5487 color_space: Name::from_bytes(b"DeviceGray".to_vec()),
5488 matrix: Matrix::identity(),
5489 filter: None,
5490 xobject_id: None,
5491 blend_mode: None,
5492 active: true,
5493 marks: vec![],
5494 clip_path: None,
5495 };
5496 let bmp = obj.to_bitmap().unwrap();
5497 assert_eq!(bmp.format, BitmapFormat::Gray8);
5498 assert_eq!(bmp.data[0], 128);
5499 assert_eq!(bmp.data[1], 64);
5500 }
5501
5502 #[test]
5507 fn test_form_object_from_xobject_stores_id_and_matrix() {
5508 let id = ObjectId::new(42, 0);
5509 let m = Matrix::new(2.0, 0.0, 0.0, 3.0, 10.0, 20.0);
5510 let fo = FormObject::from_xobject(id, m);
5511 assert_eq!(fo.xobject_id, id);
5512 assert_eq!(fo.matrix.a, 2.0);
5513 assert_eq!(fo.matrix.f, 20.0);
5514 assert!(fo.active);
5515 }
5516
5517 #[test]
5522 fn test_dash_pattern_get_set_roundtrip() {
5523 let mut p = PathObject::default();
5524 assert!(p.dash_pattern().is_none());
5525 assert_eq!(p.dash_phase(), 0.0);
5526 assert_eq!(p.dash_array(), &[] as &[f32]);
5527
5528 p.set_dash_array(vec![5.0, 3.0], 1.0);
5529 assert_eq!(p.dash_array(), &[5.0, 3.0]);
5530 assert_eq!(p.dash_phase(), 1.0);
5531
5532 p.set_dash_phase(2.5);
5534 assert_eq!(p.dash_phase(), 2.5);
5535
5536 p.set_dash_array(vec![], 0.0);
5538 assert!(p.dash_pattern().is_none());
5539 }
5540
5541 #[test]
5542 fn test_dash_pattern_set_and_clear() {
5543 let mut p = PathObject::default();
5544 p.set_dash_pattern(Some(DashPattern {
5545 array: vec![10.0, 5.0],
5546 phase: 3.0,
5547 }));
5548 assert_eq!(p.dash_pattern().unwrap().array, vec![10.0, 5.0]);
5549 p.set_dash_pattern(None);
5550 assert!(p.dash_pattern().is_none());
5551 }
5552
5553 #[test]
5558 fn test_text_object_transform_translates_matrix() {
5559 let mut t = TextObject::default();
5560 let m = Matrix::new(1.0, 0.0, 0.0, 1.0, 10.0, 20.0);
5562 t.transform(&m);
5563 assert!((t.matrix.e - 10.0).abs() < 1e-9);
5564 assert!((t.matrix.f - 20.0).abs() < 1e-9);
5565 }
5566
5567 #[test]
5568 fn test_image_object_transform_scales_matrix() {
5569 let bmp = Bitmap::new(2, 2, BitmapFormat::Rgba32);
5570 let mut img = ImageObject::from_bitmap(&bmp, Matrix::identity());
5571 let scale = Matrix::new(2.0, 0.0, 0.0, 3.0, 0.0, 0.0);
5572 img.transform(&scale);
5573 assert!((img.matrix.a - 2.0).abs() < 1e-9);
5574 assert!((img.matrix.d - 3.0).abs() < 1e-9);
5575 }
5576
5577 #[test]
5578 fn test_form_object_transform_updates_matrix() {
5579 let id = ObjectId::new(1, 0);
5580 let mut fo = FormObject::from_xobject(id, Matrix::identity());
5581 let m = Matrix::new(1.0, 0.0, 0.0, 1.0, 5.0, 10.0);
5582 fo.transform(&m);
5583 assert!((fo.matrix.e - 5.0).abs() < 1e-9);
5584 assert!((fo.matrix.f - 10.0).abs() < 1e-9);
5585 }
5586
5587 #[test]
5588 fn test_page_object_transform_dispatches_to_all_types() {
5589 let m = Matrix::new(1.0, 0.0, 0.0, 1.0, 3.0, 4.0);
5590
5591 let mut t = PageObject::Text(TextObject::default());
5592 t.transform(&m);
5593 if let PageObject::Text(inner) = &t {
5594 assert!((inner.matrix.e - 3.0).abs() < 1e-9);
5595 }
5596
5597 let bmp = Bitmap::new(1, 1, BitmapFormat::Rgba32);
5598 let mut img = PageObject::Image(ImageObject::from_bitmap(&bmp, Matrix::identity()));
5599 img.transform(&m);
5600 if let PageObject::Image(inner) = &img {
5601 assert!((inner.matrix.e - 3.0).abs() < 1e-9);
5602 }
5603
5604 let id = ObjectId::new(1, 0);
5605 let mut fo = PageObject::Form(FormObject::from_xobject(id, Matrix::identity()));
5606 fo.transform(&m);
5607 if let PageObject::Form(inner) = &fo {
5608 assert!((inner.matrix.e - 3.0).abs() < 1e-9);
5609 }
5610 }
5611
5612 #[test]
5617 fn test_blend_mode_get_set_path() {
5618 let mut p = PathObject::default();
5619 assert!(p.blend_mode().is_none());
5620 p.set_blend_mode(Some(BlendMode::Multiply));
5621 assert_eq!(p.blend_mode(), Some(BlendMode::Multiply));
5622 p.set_blend_mode(None);
5623 assert!(p.blend_mode().is_none());
5624 }
5625
5626 #[test]
5627 fn test_blend_mode_page_object_enum() {
5628 let mut po = PageObject::Path(PathObject::default());
5629 assert!(po.blend_mode().is_none());
5630 po.set_blend_mode(Some(BlendMode::Screen));
5631 assert_eq!(po.blend_mode(), Some(BlendMode::Screen));
5632
5633 let mut to = PageObject::Text(TextObject::default());
5634 to.set_blend_mode(Some(BlendMode::Overlay));
5635 assert_eq!(to.blend_mode(), Some(BlendMode::Overlay));
5636 }
5637
5638 fn make_minimal_jpeg(width: u16, height: u16) -> Vec<u8> {
5645 let mut data: Vec<u8> = Vec::new();
5646 data.extend_from_slice(&[0xFF, 0xD8]);
5648 data.extend_from_slice(&[0xFF, 0xC0]);
5650 let seg_len: u16 = 8;
5652 data.extend_from_slice(&seg_len.to_be_bytes());
5653 data.push(8);
5655 data.extend_from_slice(&height.to_be_bytes());
5657 data.extend_from_slice(&width.to_be_bytes());
5659 data.push(3);
5661 data.extend_from_slice(&[0xFF, 0xD9]);
5663 data
5664 }
5665
5666 #[test]
5667 fn test_parse_jpeg_dimensions_baseline() {
5668 let jpeg = make_minimal_jpeg(320, 240);
5669 let result = parse_jpeg_dimensions(&jpeg);
5670 assert_eq!(result, Some((320, 240)));
5671 }
5672
5673 #[test]
5674 fn test_parse_jpeg_dimensions_progressive() {
5675 let mut data: Vec<u8> = Vec::new();
5677 data.extend_from_slice(&[0xFF, 0xD8]);
5678 data.extend_from_slice(&[0xFF, 0xC2]); let seg_len: u16 = 8;
5680 data.extend_from_slice(&seg_len.to_be_bytes());
5681 data.push(8); data.extend_from_slice(&100u16.to_be_bytes()); data.extend_from_slice(&200u16.to_be_bytes()); data.push(3);
5685 data.extend_from_slice(&[0xFF, 0xD9]);
5686 assert_eq!(parse_jpeg_dimensions(&data), Some((200, 100)));
5687 }
5688
5689 #[test]
5690 fn test_parse_jpeg_dimensions_sof_after_app0() {
5691 let mut data: Vec<u8> = Vec::new();
5693 data.extend_from_slice(&[0xFF, 0xD8]);
5695 data.extend_from_slice(&[0xFF, 0xE0]);
5697 let app0_len: u16 = 18; data.extend_from_slice(&app0_len.to_be_bytes());
5699 data.extend_from_slice(&[0u8; 16]);
5700 data.extend_from_slice(&[0xFF, 0xC0]);
5702 let seg_len: u16 = 8;
5703 data.extend_from_slice(&seg_len.to_be_bytes());
5704 data.push(8); data.extend_from_slice(&480u16.to_be_bytes()); data.extend_from_slice(&640u16.to_be_bytes()); data.push(3);
5708 data.extend_from_slice(&[0xFF, 0xD9]);
5710 assert_eq!(parse_jpeg_dimensions(&data), Some((640, 480)));
5711 }
5712
5713 #[test]
5714 fn test_parse_jpeg_dimensions_invalid_no_soi() {
5715 let data = vec![0x00, 0x01, 0x02];
5716 assert_eq!(parse_jpeg_dimensions(&data), None);
5717 }
5718
5719 #[test]
5720 fn test_parse_jpeg_dimensions_empty() {
5721 assert_eq!(parse_jpeg_dimensions(&[]), None);
5722 }
5723
5724 #[test]
5725 fn test_parse_jpeg_dimensions_truncated() {
5726 let data = vec![0xFF, 0xD8];
5728 assert_eq!(parse_jpeg_dimensions(&data), None);
5729 }
5730
5731 #[test]
5732 fn test_from_jpeg_bytes_creates_image_object() {
5733 let jpeg = make_minimal_jpeg(100, 75);
5734 let obj =
5735 ImageObject::from_jpeg_bytes(jpeg, Matrix::identity()).expect("should parse JPEG");
5736 assert_eq!(obj.width, 100);
5737 assert_eq!(obj.height, 75);
5738 assert_eq!(
5739 obj.filter.as_ref().map(|n| n.as_bytes()),
5740 Some(b"DCTDecode".as_ref())
5741 );
5742 assert_eq!(obj.bits_per_component, 8);
5743 assert!(obj.active);
5744 assert!(obj.xobject_id.is_none());
5745 }
5746
5747 #[test]
5748 fn test_from_jpeg_bytes_metadata_dimensions() {
5749 let jpeg = make_minimal_jpeg(800, 600);
5750 let obj = ImageObject::from_jpeg_bytes(jpeg, Matrix::new(800.0, 0.0, 0.0, 600.0, 0.0, 0.0))
5751 .expect("should parse JPEG");
5752 let meta = obj.metadata();
5753 assert_eq!(meta.width, 800);
5754 assert_eq!(meta.height, 600);
5755 assert_eq!(
5756 meta.filter.as_ref().map(|n| n.as_bytes()),
5757 Some(b"DCTDecode".as_ref())
5758 );
5759 assert!(
5761 (meta.horizontal_dpi - 72.0).abs() < 0.1,
5762 "horizontal_dpi={}",
5763 meta.horizontal_dpi
5764 );
5765 assert!(
5766 (meta.vertical_dpi - 72.0).abs() < 0.1,
5767 "vertical_dpi={}",
5768 meta.vertical_dpi
5769 );
5770 }
5771
5772 #[test]
5773 fn test_from_jpeg_bytes_invalid_data_returns_error() {
5774 let bad_data = vec![0xDE, 0xAD, 0xBE, 0xEF];
5775 let result = ImageObject::from_jpeg_bytes(bad_data, Matrix::identity());
5776 assert!(result.is_err());
5777 }
5778
5779 #[test]
5780 fn test_from_jpeg_bytes_raw_data_preserved() {
5781 let jpeg = make_minimal_jpeg(50, 50);
5783 let jpeg_clone = jpeg.clone();
5784 let obj = ImageObject::from_jpeg_bytes(jpeg, Matrix::identity()).unwrap();
5785 assert_eq!(obj.raw_data(), jpeg_clone.as_slice());
5786 }
5787
5788 #[test]
5793 fn test_content_mark_add_and_count() {
5794 let mut p = PathObject::default();
5795 assert_eq!(p.mark_count(), 0);
5796 p.add_mark("Span");
5797 assert_eq!(p.mark_count(), 1);
5798 p.add_mark("P");
5799 assert_eq!(p.mark_count(), 2);
5800 }
5801
5802 #[test]
5803 fn test_content_mark_get_by_index() {
5804 let mut p = PathObject::default();
5805 p.add_mark("Figure");
5806 p.add_mark("Artifact");
5807
5808 let m0 = p.mark(0).unwrap();
5809 assert_eq!(m0.name, "Figure");
5810
5811 let m1 = p.page_obj_get_mark(1).unwrap();
5812 assert_eq!(m1.name, "Artifact");
5813
5814 assert!(p.mark(2).is_none());
5816 assert!(p.page_obj_get_mark(99).is_none());
5817 }
5818
5819 #[test]
5820 fn test_content_mark_int_param_roundtrip() {
5821 let mut mark = ContentMark::new("Span");
5822 assert_eq!(mark.param_count(), 0);
5823 assert_eq!(mark.page_obj_mark_count_params(), 0);
5824
5825 mark.set_int_param("MCID", 7);
5826 assert_eq!(mark.param_count(), 1);
5827 assert_eq!(mark.int_param("MCID"), Some(7));
5828 assert_eq!(mark.page_obj_mark_get_param_int_value("MCID"), Some(7)); mark.set_int_param("MCID", 42);
5832 assert_eq!(mark.param_count(), 1);
5833 assert_eq!(mark.int_param("MCID"), Some(42));
5834
5835 assert!(mark.string_param("MCID").is_none());
5837 assert!(mark.blob_param("MCID").is_none());
5838 }
5839
5840 #[test]
5841 fn test_content_mark_string_param_roundtrip() {
5842 let mut mark = ContentMark::new("Link");
5843 mark.set_string_param("ActualText", "Hello world");
5844 assert_eq!(mark.string_param("ActualText"), Some("Hello world"));
5845 assert_eq!(
5846 mark.page_obj_mark_get_param_string_value("ActualText"),
5847 Some("Hello world")
5848 ); assert_eq!(mark.param_key(0), Some("ActualText"));
5850 assert_eq!(mark.page_obj_mark_get_param_key(0), Some("ActualText"));
5851
5852 mark.set_string_param("ActualText", "Goodbye");
5854 assert_eq!(mark.string_param("ActualText"), Some("Goodbye"));
5855 assert_eq!(mark.param_count(), 1);
5856 }
5857
5858 #[test]
5859 fn test_content_mark_blob_param_roundtrip() {
5860 let mut mark = ContentMark::new("CustomTag");
5861 mark.set_blob_param("Raw", vec![0xDE, 0xAD, 0xBE, 0xEF]);
5862 assert_eq!(mark.blob_param("Raw"), Some(&[0xDE, 0xAD, 0xBE, 0xEF][..]));
5863 assert_eq!(
5864 mark.page_obj_mark_get_param_blob_value("Raw"),
5865 Some(&[0xDE, 0xAD, 0xBE, 0xEF][..])
5866 ); mark.set_blob_param("Raw", vec![0x01, 0x02]);
5870 assert_eq!(mark.blob_param("Raw"), Some(&[0x01, 0x02][..]));
5871 assert_eq!(mark.param_count(), 1);
5872 }
5873
5874 #[test]
5875 fn test_content_mark_marked_content_id() {
5876 let mut p = PathObject::default();
5877
5878 assert!(p.marked_content_id().is_none());
5880
5881 p.add_mark("NoId");
5883 assert!(p.marked_content_id().is_none());
5884
5885 p.add_mark("Span").set_int_param("MCID", 5);
5887 assert_eq!(p.marked_content_id(), Some(5));
5888
5889 let mut m = ContentMark::new("P");
5891 m.set_int_param("MCID", 99);
5892 assert_eq!(m.marked_content_id(), Some(99));
5893 }
5894
5895 #[test]
5896 fn test_content_mark_remove_mark_valid_and_invalid_index() {
5897 let mut p = PathObject::default();
5898 p.add_mark("A");
5899 p.add_mark("B");
5900
5901 assert!(!p.remove_mark(5));
5903 assert_eq!(p.mark_count(), 2);
5904
5905 assert!(p.remove_mark(0));
5907 assert_eq!(p.mark_count(), 1);
5908 assert_eq!(p.mark(0).unwrap().name, "B");
5909 }
5910
5911 #[test]
5912 fn test_content_mark_remove_param() {
5913 let mut mark = ContentMark::new("Tag");
5914 mark.set_int_param("A", 1);
5915 mark.set_string_param("B", "hello");
5916 assert_eq!(mark.param_count(), 2);
5917
5918 assert!(mark.remove_param("A"));
5920 assert_eq!(mark.param_count(), 1);
5921 assert!(mark.int_param("A").is_none());
5922
5923 assert!(!mark.remove_param("NotHere"));
5925 assert_eq!(mark.param_count(), 1);
5926 }
5927
5928 #[test]
5929 fn test_content_mark_page_object_enum_dispatch() {
5930 let mut po = PageObject::Text(TextObject::default());
5932
5933 assert_eq!(po.mark_count(), 0);
5934 po.add_mark("Span").set_int_param("MCID", 3);
5935 assert_eq!(po.mark_count(), 1);
5936 assert_eq!(po.mark(0).unwrap().name, "Span");
5937 assert_eq!(po.marked_content_id(), Some(3));
5938
5939 assert!(po.remove_mark(0));
5940 assert_eq!(po.mark_count(), 0);
5941 assert!(po.marked_content_id().is_none());
5942 }
5943
5944 #[test]
5949 fn test_clip_path_from_rect() {
5950 let clip = ClipPath::from_rect(10.0, 20.0, 110.0, 120.0);
5951 assert_eq!(clip.path_count(), 1);
5952 assert_eq!(clip.clip_path_count_paths(), 1);
5953 assert_eq!(clip.segment_count(0), 1);
5954 assert_eq!(clip.clip_path_count_path_segments(0), 1);
5955
5956 match clip.segment(0, 0) {
5958 Some(PathSegment::Rect(x, y, w, h)) => {
5959 assert!((x - 10.0).abs() < 1e-9);
5960 assert!((y - 20.0).abs() < 1e-9);
5961 assert!((w - 100.0).abs() < 1e-9);
5962 assert!((h - 100.0).abs() < 1e-9);
5963 }
5964 other => panic!("expected Rect segment, got {:?}", other),
5965 }
5966 }
5967
5968 #[test]
5969 fn test_clip_path_from_rect_alias() {
5970 let clip = ClipPath::create_clip_path(0.0, 0.0, 100.0, 200.0);
5971 assert_eq!(clip.path_count(), 1);
5972 match clip.segment(0, 0) {
5973 Some(PathSegment::Rect(x, y, w, h)) => {
5974 assert!((x - 0.0).abs() < 1e-9);
5975 assert!((y - 0.0).abs() < 1e-9);
5976 assert!((w - 100.0).abs() < 1e-9);
5977 assert!((h - 200.0).abs() < 1e-9);
5978 }
5979 other => panic!("expected Rect, got {:?}", other),
5980 }
5981 }
5982
5983 #[test]
5984 fn test_clip_path_multi_sub_paths() {
5985 let clip = ClipPath::new(vec![
5986 vec![
5987 PathSegment::MoveTo(0.0, 0.0),
5988 PathSegment::LineTo(100.0, 0.0),
5989 PathSegment::LineTo(100.0, 100.0),
5990 PathSegment::Close,
5991 ],
5992 vec![
5993 PathSegment::MoveTo(200.0, 200.0),
5994 PathSegment::LineTo(300.0, 200.0),
5995 PathSegment::Close,
5996 ],
5997 ]);
5998
5999 assert_eq!(clip.path_count(), 2);
6000 assert_eq!(clip.segment_count(0), 4);
6001 assert_eq!(clip.segment_count(1), 3);
6002 assert_eq!(clip.segment_count(999), 0); assert!(clip.segment(0, 0).is_some());
6005 assert!(clip.segment(0, 3).is_some());
6006 assert!(clip.segment(0, 4).is_none());
6007 assert!(clip.segment(1, 2).is_some());
6008 assert!(clip.segment(999, 0).is_none());
6009
6010 assert!(clip.clip_path_get_path_segment(0, 0).is_some());
6012 }
6013
6014 #[test]
6015 fn test_clip_path_transform() {
6016 let mut clip = ClipPath::from_rect(0.0, 0.0, 100.0, 100.0);
6017 let matrix = Matrix::new(2.0, 0.0, 0.0, 2.0, 10.0, 20.0);
6018 clip.transform(&matrix);
6019
6020 match clip.segment(0, 0) {
6021 Some(PathSegment::Rect(x, y, w, h)) => {
6022 assert!((x - 10.0).abs() < 1e-9, "x={x}");
6023 assert!((y - 20.0).abs() < 1e-9, "y={y}");
6024 assert!((w - 200.0).abs() < 1e-9, "w={w}");
6025 assert!((h - 200.0).abs() < 1e-9, "h={h}");
6026 }
6027 other => panic!("expected Rect, got {:?}", other),
6028 }
6029 }
6030
6031 #[test]
6032 fn test_page_object_clip_path_dispatch() {
6033 let mut po = PageObject::Path(PathObject::default());
6034 assert!(po.clip_path().is_none());
6035 assert!(po.page_obj_get_clip_path().is_none());
6036
6037 let clip = ClipPath::from_rect(0.0, 0.0, 100.0, 100.0);
6038 po.set_clip_path(Some(clip));
6039 assert!(po.clip_path().is_some());
6040 assert_eq!(po.clip_path().unwrap().path_count(), 1);
6041
6042 let matrix = Matrix::new(1.0, 0.0, 0.0, 1.0, 50.0, 50.0);
6044 po.transform_clip_path(&matrix);
6045 assert!(po.clip_path().is_some());
6047
6048 po.set_clip_path(None);
6050 assert!(po.clip_path().is_none());
6051
6052 po.transform_clip_path(&Matrix::identity()); }
6055
6056 #[test]
6057 fn test_page_object_clip_path_all_types() {
6058 for mut po in [
6060 PageObject::Path(PathObject::default()),
6061 PageObject::Text(TextObject::default()),
6062 PageObject::Image(ImageObject {
6063 image_data: vec![],
6064 width: 1,
6065 height: 1,
6066 bits_per_component: 8,
6067 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
6068 matrix: Matrix::identity(),
6069 filter: None,
6070 xobject_id: None,
6071 blend_mode: None,
6072 active: true,
6073 marks: Vec::new(),
6074 clip_path: None,
6075 }),
6076 PageObject::Form(FormObject::from_xobject(make_id(1), Matrix::identity())),
6077 ] {
6078 assert!(po.clip_path().is_none());
6079 po.set_clip_path(Some(ClipPath::from_rect(0.0, 0.0, 50.0, 50.0)));
6080 assert!(po.clip_path().is_some());
6081 }
6082 }
6083
6084 #[test]
6089 fn test_path_object_has_transparency_false_when_no_blend_mode() {
6090 let p = PathObject::default();
6091 assert!(!p.has_transparency());
6092 }
6093
6094 #[test]
6095 fn test_path_object_has_transparency_true_for_non_normal_blend_mode() {
6096 let mut p = PathObject::default();
6097 p.set_blend_mode(Some(BlendMode::Multiply));
6098 assert!(p.has_transparency());
6099
6100 p.set_blend_mode(Some(BlendMode::Normal));
6102 assert!(!p.has_transparency());
6103 }
6104
6105 #[test]
6106 fn test_page_object_has_transparency_dispatches_all_types() {
6107 let mut path_po = PageObject::Path(PathObject::default());
6109 assert!(!path_po.has_transparency());
6110 path_po.set_blend_mode(Some(BlendMode::Screen));
6111 assert!(path_po.has_transparency());
6112
6113 let mut text_po = PageObject::Text(TextObject::default());
6115 assert!(!text_po.has_transparency());
6116 text_po.set_blend_mode(Some(BlendMode::Overlay));
6117 assert!(text_po.has_transparency());
6118
6119 let img_po = PageObject::Image(ImageObject {
6121 image_data: vec![],
6122 width: 1,
6123 height: 1,
6124 bits_per_component: 8,
6125 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
6126 matrix: Matrix::identity(),
6127 filter: None,
6128 xobject_id: None,
6129 blend_mode: Some(BlendMode::Darken),
6130 active: true,
6131 marks: Vec::new(),
6132 clip_path: None,
6133 });
6134 assert!(img_po.has_transparency());
6135
6136 let mut form_po =
6138 PageObject::Form(FormObject::from_xobject(make_id(2), Matrix::identity()));
6139 assert!(!form_po.has_transparency());
6140 form_po.set_blend_mode(Some(BlendMode::Luminosity));
6141 assert!(form_po.has_transparency());
6142 }
6143
6144 #[test]
6149 fn test_image_object_matrix_get_set() {
6150 let scale = Matrix::new(100.0, 0.0, 0.0, 150.0, 50.0, 75.0);
6151 let mut img = ImageObject {
6152 image_data: vec![],
6153 width: 10,
6154 height: 10,
6155 bits_per_component: 8,
6156 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
6157 matrix: Matrix::identity(),
6158 filter: None,
6159 xobject_id: None,
6160 blend_mode: None,
6161 active: true,
6162 marks: Vec::new(),
6163 clip_path: None,
6164 };
6165 assert_eq!(*img.matrix(), Matrix::identity());
6167 img.set_matrix(scale);
6169 assert_eq!(img.matrix().a, 100.0);
6170 assert_eq!(img.matrix().d, 150.0);
6171 assert_eq!(img.matrix().e, 50.0);
6172 assert_eq!(img.matrix().f, 75.0);
6173 }
6174
6175 #[test]
6176 fn test_form_object_matrix_get_set() {
6177 let m = Matrix::new(2.0, 0.0, 0.0, 3.0, 10.0, 20.0);
6178 let mut f = FormObject::from_xobject(make_id(5), Matrix::identity());
6179 assert_eq!(*f.matrix(), Matrix::identity());
6180 f.set_matrix(m);
6181 assert_eq!(f.matrix().a, 2.0);
6182 assert_eq!(f.matrix().d, 3.0);
6183 }
6184
6185 #[test]
6186 fn test_page_object_matrix_dispatch() {
6187 let m = Matrix::new(4.0, 0.0, 0.0, 4.0, 0.0, 0.0);
6188
6189 let mut img_po = PageObject::Image(ImageObject {
6191 image_data: vec![],
6192 width: 1,
6193 height: 1,
6194 bits_per_component: 8,
6195 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
6196 matrix: Matrix::identity(),
6197 filter: None,
6198 xobject_id: None,
6199 blend_mode: None,
6200 active: true,
6201 marks: Vec::new(),
6202 clip_path: None,
6203 });
6204 assert_eq!(img_po.matrix(), Matrix::identity());
6205 img_po.set_matrix(m);
6206 assert_eq!(img_po.matrix().a, 4.0);
6207
6208 let mut path_po = PageObject::Path(PathObject::default());
6210 assert_eq!(path_po.matrix(), Matrix::identity());
6211 path_po.set_matrix(m); assert_eq!(path_po.matrix(), Matrix::identity());
6213
6214 let text_po = PageObject::Text(TextObject::default());
6216 assert_eq!(text_po.matrix(), Matrix::identity());
6217 }
6218
6219 #[test]
6224 fn test_path_object_type_is_2() {
6225 let p = PathObject::default();
6226 assert_eq!(p.object_type(), 2);
6227 assert_eq!(p.page_obj_get_type(), 2);
6228 let po = PageObject::Path(PathObject::default());
6229 assert_eq!(po.object_type(), 2);
6230 assert_eq!(po.page_obj_get_type(), 2);
6231 }
6232
6233 #[test]
6234 fn test_text_object_type_is_1() {
6235 let t = TextObject::default();
6236 assert_eq!(t.object_type(), 1);
6237 assert_eq!(t.page_obj_get_type(), 1);
6238 let po = PageObject::Text(TextObject::default());
6239 assert_eq!(po.object_type(), 1);
6240 assert_eq!(po.page_obj_get_type(), 1);
6241 }
6242
6243 #[test]
6244 fn test_image_object_type_is_3() {
6245 let img = ImageObject {
6246 image_data: vec![],
6247 width: 1,
6248 height: 1,
6249 bits_per_component: 8,
6250 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
6251 matrix: Matrix::identity(),
6252 filter: None,
6253 xobject_id: None,
6254 blend_mode: None,
6255 active: true,
6256 marks: Vec::new(),
6257 clip_path: None,
6258 };
6259 assert_eq!(img.object_type(), 3);
6260 assert_eq!(img.page_obj_get_type(), 3);
6261 let img2 = ImageObject {
6262 image_data: vec![],
6263 width: 1,
6264 height: 1,
6265 bits_per_component: 8,
6266 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
6267 matrix: Matrix::identity(),
6268 filter: None,
6269 xobject_id: None,
6270 blend_mode: None,
6271 active: true,
6272 marks: Vec::new(),
6273 clip_path: None,
6274 };
6275 let po = PageObject::Image(img2);
6276 assert_eq!(po.object_type(), 3);
6277 assert_eq!(po.page_obj_get_type(), 3);
6278 }
6279
6280 #[test]
6281 fn test_form_object_type_is_5() {
6282 let fo = FormObject::from_xobject(ObjectId::new(1, 0), Matrix::identity());
6283 assert_eq!(fo.object_type(), 5);
6284 assert_eq!(fo.page_obj_get_type(), 5);
6285 let fo2 = FormObject::from_xobject(ObjectId::new(2, 0), Matrix::identity());
6286 let po = PageObject::Form(fo2);
6287 assert_eq!(po.object_type(), 5);
6288 assert_eq!(po.page_obj_get_type(), 5);
6289 }
6290
6291 #[test]
6297 fn test_image_filter_count_no_filter() {
6298 let img = ImageObject {
6299 image_data: vec![],
6300 width: 1,
6301 height: 1,
6302 bits_per_component: 8,
6303 color_space: Name::from_bytes(b"DeviceGray".to_vec()),
6304 matrix: Matrix::identity(),
6305 filter: None,
6306 xobject_id: None,
6307 blend_mode: None,
6308 active: true,
6309 marks: Vec::new(),
6310 clip_path: None,
6311 };
6312 assert_eq!(img.filter_count(), 0);
6313 assert_eq!(img.image_obj_get_image_filter_count(), 0);
6314 assert!(img.filter(0).is_none());
6315 assert!(img.image_obj_get_image_filter(0).is_none());
6316 }
6317
6318 #[test]
6319 fn test_image_filter_count_with_filter() {
6320 let img = ImageObject {
6321 image_data: vec![],
6322 width: 1,
6323 height: 1,
6324 bits_per_component: 8,
6325 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
6326 matrix: Matrix::identity(),
6327 filter: Some(Name::from_bytes(b"FlateDecode".to_vec())),
6328 xobject_id: None,
6329 blend_mode: None,
6330 active: true,
6331 marks: Vec::new(),
6332 clip_path: None,
6333 };
6334 assert_eq!(img.filter_count(), 1);
6335 assert_eq!(img.image_obj_get_image_filter_count(), 1);
6336 assert_eq!(img.filter(0), Some("FlateDecode"));
6337 assert_eq!(img.image_obj_get_image_filter(0), Some("FlateDecode"));
6338 }
6339
6340 #[test]
6341 fn test_image_filter_index_out_of_range_returns_none() {
6342 let img = ImageObject {
6343 image_data: vec![],
6344 width: 1,
6345 height: 1,
6346 bits_per_component: 8,
6347 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
6348 matrix: Matrix::identity(),
6349 filter: Some(Name::from_bytes(b"DCTDecode".to_vec())),
6350 xobject_id: None,
6351 blend_mode: None,
6352 active: true,
6353 marks: Vec::new(),
6354 clip_path: None,
6355 };
6356 assert_eq!(img.filter(0), Some("DCTDecode"));
6358 assert!(img.filter(1).is_none());
6360 assert!(img.image_obj_get_image_filter(1).is_none());
6361 }
6362
6363 #[test]
6368 fn test_image_obj_rendered_bitmap_returns_not_supported() {
6369 let img = ImageObject {
6370 image_data: vec![],
6371 width: 1,
6372 height: 1,
6373 bits_per_component: 8,
6374 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
6375 matrix: Matrix::identity(),
6376 filter: None,
6377 xobject_id: None,
6378 blend_mode: None,
6379 active: true,
6380 marks: Vec::new(),
6381 clip_path: None,
6382 };
6383 assert!(img.rendered_bitmap(0, 1.0).is_err());
6384 assert!(img.image_obj_get_rendered_bitmap(0, 1.0).is_err());
6385 }
6386
6387 #[test]
6388 fn test_image_obj_icc_profile_returns_none() {
6389 let img = ImageObject {
6390 image_data: vec![],
6391 width: 1,
6392 height: 1,
6393 bits_per_component: 8,
6394 color_space: Name::from_bytes(b"DeviceRGB".to_vec()),
6395 matrix: Matrix::identity(),
6396 filter: None,
6397 xobject_id: None,
6398 blend_mode: None,
6399 active: true,
6400 marks: Vec::new(),
6401 clip_path: None,
6402 };
6403 assert!(img.icc_profile_data_decoded().is_none());
6404 assert!(img.image_obj_get_icc_profile_data_decoded().is_none());
6405 }
6406
6407 #[test]
6408 fn test_text_obj_rendered_bitmap_returns_not_supported() {
6409 let obj = TextObject::with_standard_font("Helvetica", 12.0);
6410 assert!(obj.rendered_bitmap(0, 1.0).is_err());
6411 assert!(obj.text_obj_get_rendered_bitmap(0, 1.0).is_err());
6412 }
6413
6414 #[test]
6419 fn test_text_obj_set_char_codes_ascii() {
6420 let mut t = TextObject::default();
6421 t.set_char_codes(&[0x41, 0x42, 0x43]);
6422 assert_eq!(t.text(), "ABC");
6423 }
6424
6425 #[test]
6426 fn test_text_obj_set_char_codes_alias() {
6427 let mut t = TextObject::default();
6428 t.text_set_charcodes(&[0x48, 0x69]); assert_eq!(t.text(), "Hi");
6430 }
6431
6432 #[test]
6433 fn test_text_obj_set_char_codes_invalid_replaced_with_replacement_char() {
6434 let mut t = TextObject::default();
6435 t.set_char_codes(&[0x41, 0xD800, 0x42]);
6437 let s = t.text();
6438 let chars: Vec<char> = s.chars().collect();
6439 assert_eq!(chars[0], 'A');
6440 assert_eq!(chars[1], '\u{FFFD}');
6441 assert_eq!(chars[2], 'B');
6442 }
6443
6444 #[allow(deprecated)]
6445 #[test]
6446 fn test_text_obj_from_font_registration_has_correct_size() {
6447 let reg = FontRegistration::new_standard(make_id(10), "Courier-Bold");
6448 let t = TextObject::from_font_registration(®, 12.0);
6449 assert_eq!(t.font_size(), 12.0);
6450 assert_eq!(t.font_name(), &Name::from_bytes(b"Courier-Bold".to_vec()));
6451 assert_eq!(t.font_object_id, Some(make_id(10)));
6452 }
6453
6454 #[allow(deprecated)]
6455 #[test]
6456 fn test_text_obj_from_font_registration_equals_with_font() {
6457 let reg = FontRegistration::new_standard(make_id(5), "Helvetica");
6458 let t1 = TextObject::with_font(®, 14.0);
6459 let t2 = TextObject::from_font_registration(®, 14.0);
6460 assert_eq!(t1.font_name(), t2.font_name());
6461 assert_eq!(t1.font_size(), t2.font_size());
6462 assert_eq!(t1.font_object_id, t2.font_object_id);
6463 }
6464
6465 #[test]
6466 fn test_page_obj_mark_float_param_set_and_get() {
6467 let mut mark = ContentMark::new("Tag");
6468 mark.set_float_param("Scale", 1.5_f32);
6469 assert_eq!(mark.float_param("Scale"), Some(1.5_f32));
6470 assert_eq!(
6471 mark.page_obj_mark_get_param_float_value("Scale"),
6472 Some(1.5_f32)
6473 );
6474 }
6475
6476 #[test]
6477 fn test_page_obj_mark_float_param_replace_existing() {
6478 let mut mark = ContentMark::new("Tag");
6479 mark.set_float_param("X", 0.5_f32);
6480 mark.set_float_param("X", 2.0_f32);
6481 assert_eq!(mark.float_param("X"), Some(2.0_f32));
6482 assert_eq!(mark.param_count(), 1);
6483 }
6484
6485 #[test]
6486 fn test_page_obj_mark_float_param_absent_returns_none() {
6487 let mark = ContentMark::new("Tag");
6488 assert!(mark.float_param("Missing").is_none());
6489 }
6490
6491 #[test]
6492 fn test_page_obj_mark_float_wrong_type_returns_none() {
6493 let mut mark = ContentMark::new("Tag");
6494 mark.set_int_param("Key", 42);
6495 assert!(mark.float_param("Key").is_none());
6496 mark.set_float_param("FKey", 1.0);
6497 assert!(mark.int_param("FKey").is_none());
6498 }
6499
6500 #[test]
6501 fn test_form_obj_object_count_stub() {
6502 let id = ObjectId::new(1, 0);
6503 let fo = FormObject::from_xobject(id, Matrix::identity());
6504 let result = fo.object_count();
6505 assert!(matches!(
6506 result,
6507 Err(crate::error::EditError::NotSupported(_))
6508 ));
6509 let result2 = fo.form_obj_count_objects();
6510 assert!(matches!(
6511 result2,
6512 Err(crate::error::EditError::NotSupported(_))
6513 ));
6514 }
6515
6516 #[test]
6517 fn test_form_obj_object_at_stub() {
6518 let id = ObjectId::new(1, 0);
6519 let fo = FormObject::from_xobject(id, Matrix::identity());
6520 let result = fo.object_at(0);
6521 assert!(matches!(
6522 result,
6523 Err(crate::error::EditError::NotSupported(_))
6524 ));
6525 let result2 = fo.form_obj_get_object(0);
6526 assert!(matches!(
6527 result2,
6528 Err(crate::error::EditError::NotSupported(_))
6529 ));
6530 }
6531
6532 #[test]
6533 fn test_form_obj_remove_object_stub() {
6534 let id = ObjectId::new(1, 0);
6535 let mut fo = FormObject::from_xobject(id, Matrix::identity());
6536 let result = fo.remove_object(0);
6537 assert!(matches!(
6538 result,
6539 Err(crate::error::EditError::NotSupported(_))
6540 ));
6541 }
6542
6543 #[test]
6544 fn test_text_obj_rotated_bounds_identity_matrix() {
6545 let mut t = TextObject::default();
6546 t.matrix = Matrix::new(1.0, 0.0, 0.0, 1.0, 10.0, 20.0);
6547 let bounds = t.rotated_bounds().unwrap();
6548 assert!((bounds[0].x - 10.0).abs() < 1e-9);
6549 assert!((bounds[0].y - 20.0).abs() < 1e-9);
6550 }
6551
6552 #[test]
6553 fn test_image_obj_rotated_bounds_uses_matrix() {
6554 let bmp = Bitmap::new(1, 1, BitmapFormat::Rgba32);
6555 let c = std::f64::consts::FRAC_1_SQRT_2;
6556 let mat = Matrix {
6557 a: c,
6558 b: c,
6559 c: -c,
6560 d: c,
6561 e: 0.0,
6562 f: 0.0,
6563 };
6564 let obj = ImageObject::from_bitmap(&bmp, mat);
6565 let bounds = obj.rotated_bounds().unwrap();
6566 assert_eq!(bounds.len(), 4);
6567 assert!((bounds[0].x).abs() < 1e-9);
6568 assert!((bounds[0].y).abs() < 1e-9);
6569 }
6570
6571 #[test]
6572 fn test_page_object_rotated_bounds_dispatch() {
6573 let t = PageObject::Text(TextObject::default());
6574 assert!(t.rotated_bounds().is_some());
6575 assert!(t.page_obj_get_rotated_bounds().is_some());
6576
6577 let bmp = Bitmap::new(1, 1, BitmapFormat::Rgba32);
6578 let img = PageObject::Image(ImageObject::from_bitmap(&bmp, Matrix::identity()));
6579 assert!(img.rotated_bounds().is_some());
6580
6581 let path_empty = PageObject::Path(PathObject::default());
6583 assert!(path_empty.rotated_bounds().is_none());
6584
6585 let path_with_segs = PageObject::Path(PathObject {
6587 segments: vec![PathSegment::Rect(0.0, 0.0, 10.0, 20.0)],
6588 ..Default::default()
6589 });
6590 assert!(path_with_segs.rotated_bounds().is_some());
6591
6592 let id = ObjectId::new(1, 0);
6594 let fo = PageObject::Form(FormObject::from_xobject(
6595 id,
6596 Matrix::new(2.0, 0.0, 0.0, 3.0, 5.0, 10.0),
6597 ));
6598 assert!(fo.rotated_bounds().is_some());
6599 }
6600}