1use crate::bindgen::{FPDF_DOCUMENT, FPDF_PAGE, FPDF_TEXTPAGE, FS_MATRIX, FS_RECTF};
5use crate::create_transform_getters;
6use crate::error::{PdfiumError, PdfiumInternalError};
7use crate::pdf::color::PdfColor;
8use crate::pdf::document::page::object::text::PdfPageTextRenderMode;
9use crate::pdf::document::page::text::chars::PdfPageTextCharIndex;
10use crate::pdf::font::{FpdfFontDescriptorFlags, PdfFontWeight};
11use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
12use crate::pdf::points::PdfPoints;
13use crate::pdf::rect::PdfRect;
14use crate::pdfium::PdfiumLibraryBindingsAccessor;
15use crate::utils::mem::create_byte_buffer;
16use std::convert::TryInto;
17use std::marker::PhantomData;
18use std::os::raw::c_void;
19
20#[cfg(any(
21 feature = "pdfium_future",
22 feature = "pdfium_7763",
23 feature = "pdfium_7543",
24 feature = "pdfium_7350",
25 feature = "pdfium_7215",
26 feature = "pdfium_7123",
27 feature = "pdfium_6996",
28 feature = "pdfium_6721",
29 feature = "pdfium_6666",
30 feature = "pdfium_6611"
31))]
32use {
33 crate::pdf::document::page::object::text::PdfPageTextObject,
34 crate::pdf::document::page::PdfPageObjectOwnership,
35};
36
37#[cfg(doc)]
38use crate::pdf::document::page::text::PdfPageTextChars;
39
40pub struct PdfPageTextChar<'a> {
42 document_handle: FPDF_DOCUMENT,
43 page_handle: FPDF_PAGE,
44 text_page_handle: FPDF_TEXTPAGE,
45 index: i32,
46 lifetime: PhantomData<&'a FPDF_TEXTPAGE>,
47}
48
49impl<'a> PdfPageTextChar<'a> {
50 #[inline]
51 pub(crate) fn from_pdfium(
52 document_handle: FPDF_DOCUMENT,
53 page_handle: FPDF_PAGE,
54 text_page_handle: FPDF_TEXTPAGE,
55 index: i32,
56 ) -> Self {
57 PdfPageTextChar {
58 document_handle,
59 page_handle,
60 text_page_handle,
61 index,
62 lifetime: PhantomData,
63 }
64 }
65
66 #[inline]
68 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
69 self.document_handle
70 }
71
72 #[inline]
74 pub(crate) fn page_handle(&self) -> FPDF_PAGE {
75 self.page_handle
76 }
77
78 #[inline]
80 pub(crate) fn text_page_handle(&self) -> FPDF_TEXTPAGE {
81 self.text_page_handle
82 }
83
84 #[inline]
85 pub fn index(&self) -> PdfPageTextCharIndex {
86 self.index as PdfPageTextCharIndex
87 }
88
89 #[inline]
95 pub fn unicode_value(&self) -> u32 {
96 unsafe {
97 self.bindings()
98 .FPDFText_GetUnicode(self.text_page_handle, self.index)
99 }
100 }
101
102 #[inline]
108 pub fn unicode_char(&self) -> Option<char> {
109 char::from_u32(self.unicode_value())
110 }
111
112 #[inline]
119 pub fn unicode_string(&self) -> Option<String> {
120 self.unicode_char().map(|char| char.to_string())
121 }
122
123 #[inline]
130 pub fn scaled_font_size(&self) -> PdfPoints {
131 PdfPoints::new(self.unscaled_font_size().value * self.get_vertical_scale())
132 }
133
134 #[inline]
141 pub fn unscaled_font_size(&self) -> PdfPoints {
142 unsafe {
143 PdfPoints::new(
144 self.bindings()
145 .FPDFText_GetFontSize(self.text_page_handle, self.index) as f32,
146 )
147 }
148 }
149
150 fn font(&self) -> (Option<String>, FpdfFontDescriptorFlags) {
152 let mut flags = 0;
162
163 let buffer_length = unsafe {
164 self.bindings().FPDFText_GetFontInfo(
165 self.text_page_handle,
166 self.index,
167 std::ptr::null_mut(),
168 0,
169 &mut flags,
170 )
171 };
172
173 if buffer_length == 0 {
174 return (
177 None,
178 FpdfFontDescriptorFlags::from_bits_truncate(flags as u32),
179 );
180 }
181
182 let mut buffer = create_byte_buffer(buffer_length as usize);
183
184 let result = unsafe {
185 self.bindings().FPDFText_GetFontInfo(
186 self.text_page_handle,
187 self.index,
188 buffer.as_mut_ptr() as *mut c_void,
189 buffer_length,
190 &mut flags,
191 )
192 };
193
194 assert_eq!(result, buffer_length);
195
196 (
197 String::from_utf8(buffer)
198 .map(|str| str.trim_end_matches(char::from(0)).to_owned())
201 .ok(),
202 FpdfFontDescriptorFlags::from_bits_truncate(flags as u32),
203 )
204 }
205
206 #[inline]
208 pub fn font_name(&self) -> String {
209 self.font().0.unwrap_or_default()
210 }
211
212 #[inline]
216 pub fn font_weight(&self) -> Option<PdfFontWeight> {
217 PdfFontWeight::from_pdfium(unsafe {
218 self.bindings()
219 .FPDFText_GetFontWeight(self.text_page_handle, self.index)
220 })
221 }
222
223 #[inline]
225 fn font_flags_bits(&self) -> FpdfFontDescriptorFlags {
226 self.font().1
227 }
228
229 pub fn font_is_fixed_pitch(&self) -> bool {
233 self.font_flags_bits()
234 .contains(FpdfFontDescriptorFlags::FIXED_PITCH_BIT_1)
235 }
236
237 #[inline]
241 pub fn font_is_proportional_pitch(&self) -> bool {
242 !self.font_is_fixed_pitch()
243 }
244
245 pub fn font_is_serif(&self) -> bool {
251 self.font_flags_bits()
252 .contains(FpdfFontDescriptorFlags::SERIF_BIT_2)
253 }
254
255 #[inline]
261 pub fn font_is_sans_serif(&self) -> bool {
262 !self.font_is_serif()
263 }
264
265 pub fn font_is_symbolic(&self) -> bool {
274 self.font_flags_bits()
277 .contains(FpdfFontDescriptorFlags::SYMBOLIC_BIT_3)
278 }
279
280 pub fn font_is_non_symbolic(&self) -> bool {
289 self.font_flags_bits()
292 .contains(FpdfFontDescriptorFlags::NON_SYMBOLIC_BIT_6)
293 }
294
295 pub fn font_is_cursive(&self) -> bool {
300 self.font_flags_bits()
301 .contains(FpdfFontDescriptorFlags::SCRIPT_BIT_4)
302 }
303
304 pub fn font_is_italic(&self) -> bool {
309 self.font_flags_bits()
310 .contains(FpdfFontDescriptorFlags::ITALIC_BIT_7)
311 }
312
313 pub fn font_is_all_caps(&self) -> bool {
317 self.font_flags_bits()
318 .contains(FpdfFontDescriptorFlags::ALL_CAP_BIT_17)
319 }
320
321 pub fn font_is_small_caps(&self) -> bool {
327 self.font_flags_bits()
328 .contains(FpdfFontDescriptorFlags::SMALL_CAP_BIT_18)
329 }
330
331 pub fn font_is_bold_reenforced(&self) -> bool {
342 self.font_flags_bits()
343 .contains(FpdfFontDescriptorFlags::FORCE_BOLD_BIT_19)
344 }
345
346 #[cfg(any(
347 feature = "pdfium_future",
348 feature = "pdfium_7763",
349 feature = "pdfium_7543",
350 feature = "pdfium_7350",
351 feature = "pdfium_7215",
352 feature = "pdfium_7123",
353 feature = "pdfium_6996",
354 feature = "pdfium_6721",
355 feature = "pdfium_6666",
356 feature = "pdfium_6611"
357 ))]
358 pub fn text_object(&self) -> Result<PdfPageTextObject<'_>, PdfiumError> {
360 let object_handle = unsafe {
361 self.bindings()
362 .FPDFText_GetTextObject(self.text_page_handle(), self.index)
363 };
364
365 if object_handle.is_null() {
366 Err(PdfiumError::PdfiumLibraryInternalError(
367 PdfiumInternalError::Unknown,
368 ))
369 } else {
370 Ok(PdfPageTextObject::from_pdfium(
371 object_handle,
372 PdfPageObjectOwnership::owned_by_page(self.document_handle(), self.page_handle()),
373 ))
374 }
375 }
376
377 #[cfg(any(
378 feature = "pdfium_future",
379 feature = "pdfium_7763",
380 feature = "pdfium_7543",
381 feature = "pdfium_7350",
382 feature = "pdfium_7215",
383 feature = "pdfium_7123",
384 feature = "pdfium_6996",
385 feature = "pdfium_6721",
386 feature = "pdfium_6666",
387 feature = "pdfium_6611"
388 ))]
389 pub fn render_mode(&self) -> Result<PdfPageTextRenderMode, PdfiumError> {
391 self.text_object()
392 .map(|text_object| text_object.render_mode())
393 }
394
395 #[cfg(any(
396 feature = "pdfium_6569",
397 feature = "pdfium_6555",
398 feature = "pdfium_6490",
399 feature = "pdfium_6406",
400 feature = "pdfium_6337",
401 feature = "pdfium_6295",
402 feature = "pdfium_6259",
403 feature = "pdfium_6164",
404 feature = "pdfium_6124",
405 feature = "pdfium_6110",
406 feature = "pdfium_6084",
407 feature = "pdfium_6043",
408 feature = "pdfium_6015",
409 feature = "pdfium_5961"
410 ))]
411 pub fn render_mode(&self) -> Result<PdfPageTextRenderMode, PdfiumError> {
413 PdfPageTextRenderMode::from_pdfium(unsafe {
414 self.bindings()
415 .FPDFText_GetTextRenderMode(self.text_page_handle, self.index)
416 })
417 }
418
419 pub fn fill_color(&self) -> Result<PdfColor, PdfiumError> {
421 let mut r = 0;
422 let mut g = 0;
423 let mut b = 0;
424 let mut a = 0;
425
426 if self.bindings().is_true(unsafe {
427 self.bindings().FPDFText_GetFillColor(
428 self.text_page_handle,
429 self.index,
430 &mut r,
431 &mut g,
432 &mut b,
433 &mut a,
434 )
435 }) {
436 Ok(PdfColor::new(
437 r.try_into()
438 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
439 g.try_into()
440 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
441 b.try_into()
442 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
443 a.try_into()
444 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
445 ))
446 } else {
447 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
448 }
449 }
450
451 pub fn stroke_color(&self) -> Result<PdfColor, PdfiumError> {
453 let mut r = 0;
454 let mut g = 0;
455 let mut b = 0;
456 let mut a = 0;
457
458 if self.bindings().is_true(unsafe {
459 self.bindings().FPDFText_GetStrokeColor(
460 self.text_page_handle(),
461 self.index,
462 &mut r,
463 &mut g,
464 &mut b,
465 &mut a,
466 )
467 }) {
468 Ok(PdfColor::new(
469 r.try_into()
470 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
471 g.try_into()
472 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
473 b.try_into()
474 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
475 a.try_into()
476 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
477 ))
478 } else {
479 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
480 }
481 }
482
483 #[inline]
485 pub fn angle_degrees(&self) -> Result<f32, PdfiumError> {
486 self.angle_radians().map(|result| result.to_degrees())
487 }
488
489 #[inline]
491 pub fn angle_radians(&self) -> Result<f32, PdfiumError> {
492 let result = unsafe {
493 self.bindings()
494 .FPDFText_GetCharAngle(self.text_page_handle, self.index)
495 };
496
497 if result.is_sign_negative() {
498 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
499 } else {
500 Ok(result)
501 }
502 }
503
504 pub fn tight_bounds(&self) -> Result<PdfRect, PdfiumError> {
510 let mut left = 0.0;
511 let mut bottom = 0.0;
512 let mut right = 0.0;
513 let mut top = 0.0;
514
515 let result = unsafe {
516 self.bindings().FPDFText_GetCharBox(
517 self.text_page_handle(),
518 self.index,
519 &mut left,
520 &mut right,
521 &mut bottom,
522 &mut top,
523 )
524 };
525
526 PdfRect::from_pdfium_as_result(
527 result,
528 FS_RECTF {
529 left: left as f32,
530 top: top as f32,
531 right: right as f32,
532 bottom: bottom as f32,
533 },
534 self.bindings(),
535 )
536 }
537
538 pub fn loose_bounds(&self) -> Result<PdfRect, PdfiumError> {
543 let mut bounds = FS_RECTF {
544 left: 0.0,
545 top: 0.0,
546 right: 0.0,
547 bottom: 0.0,
548 };
549
550 let result = unsafe {
551 self.bindings().FPDFText_GetLooseCharBox(
552 self.text_page_handle(),
553 self.index,
554 &mut bounds,
555 )
556 };
557
558 PdfRect::from_pdfium_as_result(result, bounds, self.bindings())
559 }
560
561 fn get_matrix_impl(&self) -> Result<PdfMatrix, PdfiumError> {
563 let mut matrix = FS_MATRIX {
564 a: 0.0,
565 b: 0.0,
566 c: 0.0,
567 d: 0.0,
568 e: 0.0,
569 f: 0.0,
570 };
571
572 if self.bindings().is_true(unsafe {
573 self.bindings()
574 .FPDFText_GetMatrix(self.text_page_handle(), self.index, &mut matrix)
575 }) {
576 Ok(PdfMatrix::from_pdfium(matrix))
577 } else {
578 Err(PdfiumError::PdfiumLibraryInternalError(
579 PdfiumInternalError::Unknown,
580 ))
581 }
582 }
583
584 create_transform_getters!(
585 "this [PdfPageTextChar]",
586 "this [PdfPageTextChar].",
587 "this [PdfPageTextChar],"
588 );
589
590 pub fn origin(&self) -> Result<(PdfPoints, PdfPoints), PdfiumError> {
592 let mut x = 0.0;
593
594 let mut y = 0.0;
595
596 if self.bindings().is_true(unsafe {
597 self.bindings().FPDFText_GetCharOrigin(
598 self.text_page_handle(),
599 self.index,
600 &mut x,
601 &mut y,
602 )
603 }) {
604 Ok((PdfPoints::new(x as f32), PdfPoints::new(y as f32)))
605 } else {
606 Err(PdfiumError::PdfiumLibraryInternalError(
607 PdfiumInternalError::Unknown,
608 ))
609 }
610 }
611
612 #[inline]
614 pub fn origin_x(&self) -> Result<PdfPoints, PdfiumError> {
615 self.origin().map(|result| result.0)
616 }
617
618 #[inline]
620 pub fn origin_y(&self) -> Result<PdfPoints, PdfiumError> {
621 self.origin().map(|result| result.1)
622 }
623
624 #[inline]
626 pub fn has_descender(&self) -> bool {
627 self.tight_bounds()
628 .map(|bounds| bounds.bottom().value)
629 .unwrap_or(0.0)
630 < self
631 .loose_bounds()
632 .map(|bounds| bounds.bottom().value)
633 .unwrap_or(0.0)
634 }
635
636 #[inline]
639 pub fn is_generated(&self) -> Result<bool, PdfiumError> {
640 match unsafe {
641 self.bindings()
642 .FPDFText_IsGenerated(self.text_page_handle(), self.index)
643 } {
644 1 => Ok(true),
645 0 => Ok(false),
646 _ => Err(PdfiumError::PdfiumLibraryInternalError(
647 PdfiumInternalError::Unknown,
648 )),
649 }
650 }
651
652 #[cfg(any(
653 feature = "pdfium_future",
654 feature = "pdfium_7763",
655 feature = "pdfium_7543",
656 feature = "pdfium_7350",
657 feature = "pdfium_7215",
658 feature = "pdfium_7123",
659 feature = "pdfium_6996",
660 feature = "pdfium_6721",
661 feature = "pdfium_6666",
662 feature = "pdfium_6611",
663 feature = "pdfium_6569",
664 feature = "pdfium_6555",
665 feature = "pdfium_6490",
666 feature = "pdfium_6406",
667 feature = "pdfium_6337",
668 feature = "pdfium_6295",
669 feature = "pdfium_6259",
670 feature = "pdfium_6164",
671 feature = "pdfium_6124",
672 feature = "pdfium_6110",
673 feature = "pdfium_6084",
674 feature = "pdfium_6043",
675 feature = "pdfium_6015",
676 ))]
677 #[inline]
679 pub fn is_hyphen(&self) -> Result<bool, PdfiumError> {
680 match unsafe {
681 self.bindings()
682 .FPDFText_IsHyphen(self.text_page_handle(), self.index)
683 } {
684 1 => Ok(true),
685 0 => Ok(false),
686 _ => Err(PdfiumError::PdfiumLibraryInternalError(
687 PdfiumInternalError::Unknown,
688 )),
689 }
690 }
691}
692
693impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPageTextChar<'a> {}
694
695#[cfg(feature = "thread_safe")]
696unsafe impl<'a> Send for PdfPageTextChar<'a> {}
697
698#[cfg(feature = "thread_safe")]
699unsafe impl<'a> Sync for PdfPageTextChar<'a> {}