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_8125",
23 feature = "pdfium_7881",
24 feature = "pdfium_7763",
25 feature = "pdfium_7543",
26 feature = "pdfium_7350",
27 feature = "pdfium_7215",
28 feature = "pdfium_7123",
29 feature = "pdfium_6996",
30 feature = "pdfium_6721",
31 feature = "pdfium_6666",
32 feature = "pdfium_6611"
33))]
34use {
35 crate::pdf::document::page::object::text::PdfPageTextObject,
36 crate::pdf::document::page::PdfPageObjectOwnership,
37};
38
39#[cfg(doc)]
40use crate::pdf::document::page::text::PdfPageTextChars;
41
42pub struct PdfPageTextChar<'a> {
44 document_handle: FPDF_DOCUMENT,
45 page_handle: FPDF_PAGE,
46 text_page_handle: FPDF_TEXTPAGE,
47 index: i32,
48 lifetime: PhantomData<&'a FPDF_TEXTPAGE>,
49}
50
51impl<'a> PdfPageTextChar<'a> {
52 #[inline]
53 pub(crate) fn from_pdfium(
54 document_handle: FPDF_DOCUMENT,
55 page_handle: FPDF_PAGE,
56 text_page_handle: FPDF_TEXTPAGE,
57 index: i32,
58 ) -> Self {
59 PdfPageTextChar {
60 document_handle,
61 page_handle,
62 text_page_handle,
63 index,
64 lifetime: PhantomData,
65 }
66 }
67
68 #[inline]
70 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
71 self.document_handle
72 }
73
74 #[inline]
76 pub(crate) fn page_handle(&self) -> FPDF_PAGE {
77 self.page_handle
78 }
79
80 #[inline]
82 pub(crate) fn text_page_handle(&self) -> FPDF_TEXTPAGE {
83 self.text_page_handle
84 }
85
86 #[inline]
87 pub fn index(&self) -> PdfPageTextCharIndex {
88 self.index as PdfPageTextCharIndex
89 }
90
91 #[inline]
97 pub fn unicode_value(&self) -> u32 {
98 unsafe {
99 self.bindings()
100 .FPDFText_GetUnicode(self.text_page_handle, self.index)
101 }
102 }
103
104 #[inline]
110 pub fn unicode_char(&self) -> Option<char> {
111 char::from_u32(self.unicode_value())
112 }
113
114 #[inline]
121 pub fn unicode_string(&self) -> Option<String> {
122 self.unicode_char().map(|char| char.to_string())
123 }
124
125 #[inline]
132 pub fn scaled_font_size(&self) -> PdfPoints {
133 PdfPoints::new(self.unscaled_font_size().value * self.get_vertical_scale())
134 }
135
136 #[inline]
143 pub fn unscaled_font_size(&self) -> PdfPoints {
144 unsafe {
145 PdfPoints::new(
146 self.bindings()
147 .FPDFText_GetFontSize(self.text_page_handle, self.index) as f32,
148 )
149 }
150 }
151
152 fn font(&self) -> (Option<String>, FpdfFontDescriptorFlags) {
154 let mut flags = 0;
164
165 let buffer_length = unsafe {
166 self.bindings().FPDFText_GetFontInfo(
167 self.text_page_handle,
168 self.index,
169 std::ptr::null_mut(),
170 0,
171 &mut flags,
172 )
173 };
174
175 if buffer_length == 0 {
176 return (
179 None,
180 FpdfFontDescriptorFlags::from_bits_truncate(flags as u32),
181 );
182 }
183
184 let mut buffer = create_byte_buffer(buffer_length as usize);
185
186 let result = unsafe {
187 self.bindings().FPDFText_GetFontInfo(
188 self.text_page_handle,
189 self.index,
190 buffer.as_mut_ptr() as *mut c_void,
191 buffer_length,
192 &mut flags,
193 )
194 };
195
196 assert_eq!(result, buffer_length);
197
198 (
199 String::from_utf8(buffer)
200 .map(|str| str.trim_end_matches(char::from(0)).to_owned())
203 .ok(),
204 FpdfFontDescriptorFlags::from_bits_truncate(flags as u32),
205 )
206 }
207
208 #[inline]
210 pub fn font_name(&self) -> String {
211 self.font().0.unwrap_or_default()
212 }
213
214 #[inline]
218 pub fn font_weight(&self) -> Option<PdfFontWeight> {
219 PdfFontWeight::from_pdfium(unsafe {
220 self.bindings()
221 .FPDFText_GetFontWeight(self.text_page_handle, self.index)
222 })
223 }
224
225 #[inline]
227 fn font_flags_bits(&self) -> FpdfFontDescriptorFlags {
228 self.font().1
229 }
230
231 pub fn font_is_fixed_pitch(&self) -> bool {
235 self.font_flags_bits()
236 .contains(FpdfFontDescriptorFlags::FIXED_PITCH_BIT_1)
237 }
238
239 #[inline]
243 pub fn font_is_proportional_pitch(&self) -> bool {
244 !self.font_is_fixed_pitch()
245 }
246
247 pub fn font_is_serif(&self) -> bool {
253 self.font_flags_bits()
254 .contains(FpdfFontDescriptorFlags::SERIF_BIT_2)
255 }
256
257 #[inline]
263 pub fn font_is_sans_serif(&self) -> bool {
264 !self.font_is_serif()
265 }
266
267 pub fn font_is_symbolic(&self) -> bool {
276 self.font_flags_bits()
279 .contains(FpdfFontDescriptorFlags::SYMBOLIC_BIT_3)
280 }
281
282 pub fn font_is_non_symbolic(&self) -> bool {
291 self.font_flags_bits()
294 .contains(FpdfFontDescriptorFlags::NON_SYMBOLIC_BIT_6)
295 }
296
297 pub fn font_is_cursive(&self) -> bool {
302 self.font_flags_bits()
303 .contains(FpdfFontDescriptorFlags::SCRIPT_BIT_4)
304 }
305
306 pub fn font_is_italic(&self) -> bool {
311 self.font_flags_bits()
312 .contains(FpdfFontDescriptorFlags::ITALIC_BIT_7)
313 }
314
315 pub fn font_is_all_caps(&self) -> bool {
319 self.font_flags_bits()
320 .contains(FpdfFontDescriptorFlags::ALL_CAP_BIT_17)
321 }
322
323 pub fn font_is_small_caps(&self) -> bool {
329 self.font_flags_bits()
330 .contains(FpdfFontDescriptorFlags::SMALL_CAP_BIT_18)
331 }
332
333 pub fn font_is_bold_reenforced(&self) -> bool {
344 self.font_flags_bits()
345 .contains(FpdfFontDescriptorFlags::FORCE_BOLD_BIT_19)
346 }
347
348 #[cfg(any(
349 feature = "pdfium_future",
350 feature = "pdfium_8125",
351 feature = "pdfium_7881",
352 feature = "pdfium_7763",
353 feature = "pdfium_7543",
354 feature = "pdfium_7350",
355 feature = "pdfium_7215",
356 feature = "pdfium_7123",
357 feature = "pdfium_6996",
358 feature = "pdfium_6721",
359 feature = "pdfium_6666",
360 feature = "pdfium_6611"
361 ))]
362 pub fn text_object(&self) -> Result<PdfPageTextObject<'_>, PdfiumError> {
364 let object_handle = unsafe {
365 self.bindings()
366 .FPDFText_GetTextObject(self.text_page_handle(), self.index)
367 };
368
369 if object_handle.is_null() {
370 Err(PdfiumError::PdfiumLibraryInternalError(
371 PdfiumInternalError::Unknown,
372 ))
373 } else {
374 Ok(PdfPageTextObject::from_pdfium(
375 object_handle,
376 PdfPageObjectOwnership::owned_by_page(self.document_handle(), self.page_handle()),
377 ))
378 }
379 }
380
381 #[cfg(any(
382 feature = "pdfium_future",
383 feature = "pdfium_8125",
384 feature = "pdfium_7881",
385 feature = "pdfium_7763",
386 feature = "pdfium_7543",
387 feature = "pdfium_7350",
388 feature = "pdfium_7215",
389 feature = "pdfium_7123",
390 feature = "pdfium_6996",
391 feature = "pdfium_6721",
392 feature = "pdfium_6666",
393 feature = "pdfium_6611"
394 ))]
395 pub fn render_mode(&self) -> Result<PdfPageTextRenderMode, PdfiumError> {
397 self.text_object()
398 .map(|text_object| text_object.render_mode())
399 }
400
401 #[cfg(any(
402 feature = "pdfium_6569",
403 feature = "pdfium_6555",
404 feature = "pdfium_6490",
405 feature = "pdfium_6406",
406 feature = "pdfium_6337",
407 feature = "pdfium_6295",
408 feature = "pdfium_6259",
409 feature = "pdfium_6164",
410 feature = "pdfium_6124",
411 feature = "pdfium_6110",
412 feature = "pdfium_6084",
413 feature = "pdfium_6043",
414 feature = "pdfium_6015",
415 feature = "pdfium_5961"
416 ))]
417 pub fn render_mode(&self) -> Result<PdfPageTextRenderMode, PdfiumError> {
419 PdfPageTextRenderMode::from_pdfium(unsafe {
420 self.bindings()
421 .FPDFText_GetTextRenderMode(self.text_page_handle, self.index)
422 })
423 }
424
425 pub fn fill_color(&self) -> Result<PdfColor, PdfiumError> {
427 let mut r = 0;
428 let mut g = 0;
429 let mut b = 0;
430 let mut a = 0;
431
432 if self.bindings().is_true(unsafe {
433 self.bindings().FPDFText_GetFillColor(
434 self.text_page_handle,
435 self.index,
436 &mut r,
437 &mut g,
438 &mut b,
439 &mut a,
440 )
441 }) {
442 Ok(PdfColor::new(
443 r.try_into()
444 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
445 g.try_into()
446 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
447 b.try_into()
448 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
449 a.try_into()
450 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
451 ))
452 } else {
453 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
454 }
455 }
456
457 pub fn stroke_color(&self) -> Result<PdfColor, PdfiumError> {
459 let mut r = 0;
460 let mut g = 0;
461 let mut b = 0;
462 let mut a = 0;
463
464 if self.bindings().is_true(unsafe {
465 self.bindings().FPDFText_GetStrokeColor(
466 self.text_page_handle(),
467 self.index,
468 &mut r,
469 &mut g,
470 &mut b,
471 &mut a,
472 )
473 }) {
474 Ok(PdfColor::new(
475 r.try_into()
476 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
477 g.try_into()
478 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
479 b.try_into()
480 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
481 a.try_into()
482 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
483 ))
484 } else {
485 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
486 }
487 }
488
489 #[inline]
491 pub fn angle_degrees(&self) -> Result<f32, PdfiumError> {
492 self.angle_radians().map(|result| result.to_degrees())
493 }
494
495 #[inline]
497 pub fn angle_radians(&self) -> Result<f32, PdfiumError> {
498 let result = unsafe {
499 self.bindings()
500 .FPDFText_GetCharAngle(self.text_page_handle, self.index)
501 };
502
503 if result.is_sign_negative() {
504 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
505 } else {
506 Ok(result)
507 }
508 }
509
510 pub fn tight_bounds(&self) -> Result<PdfRect, PdfiumError> {
516 let mut left = 0.0;
517 let mut bottom = 0.0;
518 let mut right = 0.0;
519 let mut top = 0.0;
520
521 let result = unsafe {
522 self.bindings().FPDFText_GetCharBox(
523 self.text_page_handle(),
524 self.index,
525 &mut left,
526 &mut right,
527 &mut bottom,
528 &mut top,
529 )
530 };
531
532 PdfRect::from_pdfium_as_result(
533 result,
534 FS_RECTF {
535 left: left as f32,
536 top: top as f32,
537 right: right as f32,
538 bottom: bottom as f32,
539 },
540 self.bindings(),
541 )
542 }
543
544 pub fn loose_bounds(&self) -> Result<PdfRect, PdfiumError> {
549 let mut bounds = FS_RECTF {
550 left: 0.0,
551 top: 0.0,
552 right: 0.0,
553 bottom: 0.0,
554 };
555
556 let result = unsafe {
557 self.bindings().FPDFText_GetLooseCharBox(
558 self.text_page_handle(),
559 self.index,
560 &mut bounds,
561 )
562 };
563
564 PdfRect::from_pdfium_as_result(result, bounds, self.bindings())
565 }
566
567 fn get_matrix_impl(&self) -> Result<PdfMatrix, PdfiumError> {
569 let mut matrix = FS_MATRIX {
570 a: 0.0,
571 b: 0.0,
572 c: 0.0,
573 d: 0.0,
574 e: 0.0,
575 f: 0.0,
576 };
577
578 if self.bindings().is_true(unsafe {
579 self.bindings()
580 .FPDFText_GetMatrix(self.text_page_handle(), self.index, &mut matrix)
581 }) {
582 Ok(PdfMatrix::from_pdfium(matrix))
583 } else {
584 Err(PdfiumError::PdfiumLibraryInternalError(
585 PdfiumInternalError::Unknown,
586 ))
587 }
588 }
589
590 create_transform_getters!(
591 "this [PdfPageTextChar]",
592 "this [PdfPageTextChar].",
593 "this [PdfPageTextChar],"
594 );
595
596 pub fn origin(&self) -> Result<(PdfPoints, PdfPoints), PdfiumError> {
598 let mut x = 0.0;
599
600 let mut y = 0.0;
601
602 if self.bindings().is_true(unsafe {
603 self.bindings().FPDFText_GetCharOrigin(
604 self.text_page_handle(),
605 self.index,
606 &mut x,
607 &mut y,
608 )
609 }) {
610 Ok((PdfPoints::new(x as f32), PdfPoints::new(y as f32)))
611 } else {
612 Err(PdfiumError::PdfiumLibraryInternalError(
613 PdfiumInternalError::Unknown,
614 ))
615 }
616 }
617
618 #[inline]
620 pub fn origin_x(&self) -> Result<PdfPoints, PdfiumError> {
621 self.origin().map(|result| result.0)
622 }
623
624 #[inline]
626 pub fn origin_y(&self) -> Result<PdfPoints, PdfiumError> {
627 self.origin().map(|result| result.1)
628 }
629
630 #[inline]
632 pub fn has_descender(&self) -> bool {
633 self.tight_bounds()
634 .map(|bounds| bounds.bottom().value)
635 .unwrap_or(0.0)
636 < self
637 .loose_bounds()
638 .map(|bounds| bounds.bottom().value)
639 .unwrap_or(0.0)
640 }
641
642 #[inline]
645 pub fn is_generated(&self) -> Result<bool, PdfiumError> {
646 match unsafe {
647 self.bindings()
648 .FPDFText_IsGenerated(self.text_page_handle(), self.index)
649 } {
650 1 => Ok(true),
651 0 => Ok(false),
652 _ => Err(PdfiumError::PdfiumLibraryInternalError(
653 PdfiumInternalError::Unknown,
654 )),
655 }
656 }
657
658 #[cfg(any(
659 feature = "pdfium_future",
660 feature = "pdfium_8125",
661 feature = "pdfium_7881",
662 feature = "pdfium_7763",
663 feature = "pdfium_7543",
664 feature = "pdfium_7350",
665 feature = "pdfium_7215",
666 feature = "pdfium_7123",
667 feature = "pdfium_6996",
668 feature = "pdfium_6721",
669 feature = "pdfium_6666",
670 feature = "pdfium_6611",
671 feature = "pdfium_6569",
672 feature = "pdfium_6555",
673 feature = "pdfium_6490",
674 feature = "pdfium_6406",
675 feature = "pdfium_6337",
676 feature = "pdfium_6295",
677 feature = "pdfium_6259",
678 feature = "pdfium_6164",
679 feature = "pdfium_6124",
680 feature = "pdfium_6110",
681 feature = "pdfium_6084",
682 feature = "pdfium_6043",
683 feature = "pdfium_6015",
684 ))]
685 #[inline]
687 pub fn is_hyphen(&self) -> Result<bool, PdfiumError> {
688 match unsafe {
689 self.bindings()
690 .FPDFText_IsHyphen(self.text_page_handle(), self.index)
691 } {
692 1 => Ok(true),
693 0 => Ok(false),
694 _ => Err(PdfiumError::PdfiumLibraryInternalError(
695 PdfiumInternalError::Unknown,
696 )),
697 }
698 }
699}
700
701impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPageTextChar<'a> {}
702
703#[cfg(feature = "thread_safe")]
704unsafe impl<'a> Send for PdfPageTextChar<'a> {}
705
706#[cfg(feature = "thread_safe")]
707unsafe impl<'a> Sync for PdfPageTextChar<'a> {}