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