1use crate::bindgen::{FPDF_DOCUMENT, FPDF_PAGE, FPDF_TEXTPAGE, FS_MATRIX, FS_RECTF};
5use crate::bindings::PdfiumLibraryBindings;
6use crate::create_transform_getters;
7use crate::error::{PdfiumError, PdfiumInternalError};
8use crate::pdf::color::PdfColor;
9use crate::pdf::document::page::object::text::PdfPageTextRenderMode;
10use crate::pdf::document::page::text::chars::PdfPageTextCharIndex;
11use crate::pdf::font::{FpdfFontDescriptorFlags, PdfFontWeight};
12use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
13use crate::pdf::points::PdfPoints;
14use crate::pdf::rect::PdfRect;
15use crate::utils::mem::create_byte_buffer;
16use std::convert::TryInto;
17use std::ffi::c_void;
18
19#[cfg(any(
20 feature = "pdfium_future",
21 feature = "pdfium_7350",
22 feature = "pdfium_7215",
23 feature = "pdfium_7123",
24 feature = "pdfium_6996",
25 feature = "pdfium_6721",
26 feature = "pdfium_6666",
27 feature = "pdfium_6611"
28))]
29use {
30 crate::pdf::document::page::object::text::PdfPageTextObject,
31 crate::pdf::document::page::PdfPageObjectOwnership,
32};
33
34#[cfg(doc)]
35use crate::pdf::document::page::text::PdfPageTextChars;
36
37pub struct PdfPageTextChar<'a> {
39 document_handle: FPDF_DOCUMENT,
40 page_handle: FPDF_PAGE,
41 text_page_handle: FPDF_TEXTPAGE,
42 index: i32,
43 bindings: &'a dyn PdfiumLibraryBindings,
44}
45
46impl<'a> PdfPageTextChar<'a> {
47 #[inline]
48 pub(crate) fn from_pdfium(
49 document_handle: FPDF_DOCUMENT,
50 page_handle: FPDF_PAGE,
51 text_page_handle: FPDF_TEXTPAGE,
52 index: i32,
53 bindings: &'a dyn PdfiumLibraryBindings,
54 ) -> Self {
55 PdfPageTextChar {
56 document_handle,
57 page_handle,
58 text_page_handle,
59 index,
60 bindings,
61 }
62 }
63
64 #[inline]
66 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
67 self.document_handle
68 }
69
70 #[inline]
72 pub(crate) fn page_handle(&self) -> FPDF_PAGE {
73 self.page_handle
74 }
75
76 #[inline]
78 pub(crate) fn text_page_handle(&self) -> FPDF_TEXTPAGE {
79 self.text_page_handle
80 }
81
82 #[inline]
84 pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
85 self.bindings
86 }
87
88 #[inline]
89 pub fn index(&self) -> PdfPageTextCharIndex {
90 self.index as PdfPageTextCharIndex
91 }
92
93 #[inline]
99 pub fn unicode_value(&self) -> u32 {
100 self.bindings
101 .FPDFText_GetUnicode(self.text_page_handle, self.index)
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 PdfPoints::new(
145 self.bindings
146 .FPDFText_GetFontSize(self.text_page_handle, self.index) as f32,
147 )
148 }
149
150 fn font(&self) -> (Option<String>, FpdfFontDescriptorFlags) {
152 let mut flags = 0;
162
163 let buffer_length = self.bindings.FPDFText_GetFontInfo(
164 self.text_page_handle,
165 self.index,
166 std::ptr::null_mut(),
167 0,
168 &mut flags,
169 );
170
171 if buffer_length == 0 {
172 return (
175 None,
176 FpdfFontDescriptorFlags::from_bits_truncate(flags as u32),
177 );
178 }
179
180 let mut buffer = create_byte_buffer(buffer_length as usize);
181
182 let result = self.bindings.FPDFText_GetFontInfo(
183 self.text_page_handle,
184 self.index,
185 buffer.as_mut_ptr() as *mut c_void,
186 buffer_length,
187 &mut flags,
188 );
189
190 assert_eq!(result, buffer_length);
191
192 (
193 String::from_utf8(buffer)
194 .map(|str| str.trim_end_matches(char::from(0)).to_owned())
197 .ok(),
198 FpdfFontDescriptorFlags::from_bits_truncate(flags as u32),
199 )
200 }
201
202 #[inline]
204 pub fn font_name(&self) -> String {
205 self.font().0.unwrap_or_default()
206 }
207
208 #[inline]
212 pub fn font_weight(&self) -> Option<PdfFontWeight> {
213 PdfFontWeight::from_pdfium(
214 self.bindings
215 .FPDFText_GetFontWeight(self.text_page_handle, self.index),
216 )
217 }
218
219 #[inline]
221 fn font_flags_bits(&self) -> FpdfFontDescriptorFlags {
222 self.font().1
223 }
224
225 pub fn font_is_fixed_pitch(&self) -> bool {
229 self.font_flags_bits()
230 .contains(FpdfFontDescriptorFlags::FIXED_PITCH_BIT_1)
231 }
232
233 #[inline]
237 pub fn font_is_proportional_pitch(&self) -> bool {
238 !self.font_is_fixed_pitch()
239 }
240
241 pub fn font_is_serif(&self) -> bool {
247 self.font_flags_bits()
248 .contains(FpdfFontDescriptorFlags::SERIF_BIT_2)
249 }
250
251 #[inline]
257 pub fn font_is_sans_serif(&self) -> bool {
258 !self.font_is_serif()
259 }
260
261 pub fn font_is_symbolic(&self) -> bool {
270 self.font_flags_bits()
273 .contains(FpdfFontDescriptorFlags::SYMBOLIC_BIT_3)
274 }
275
276 pub fn font_is_non_symbolic(&self) -> bool {
285 self.font_flags_bits()
288 .contains(FpdfFontDescriptorFlags::NON_SYMBOLIC_BIT_6)
289 }
290
291 pub fn font_is_cursive(&self) -> bool {
296 self.font_flags_bits()
297 .contains(FpdfFontDescriptorFlags::SCRIPT_BIT_4)
298 }
299
300 pub fn font_is_italic(&self) -> bool {
305 self.font_flags_bits()
306 .contains(FpdfFontDescriptorFlags::ITALIC_BIT_7)
307 }
308
309 pub fn font_is_all_caps(&self) -> bool {
313 self.font_flags_bits()
314 .contains(FpdfFontDescriptorFlags::ALL_CAP_BIT_17)
315 }
316
317 pub fn font_is_small_caps(&self) -> bool {
323 self.font_flags_bits()
324 .contains(FpdfFontDescriptorFlags::SMALL_CAP_BIT_18)
325 }
326
327 pub fn font_is_bold_reenforced(&self) -> bool {
338 self.font_flags_bits()
339 .contains(FpdfFontDescriptorFlags::FORCE_BOLD_BIT_19)
340 }
341
342 #[cfg(any(
343 feature = "pdfium_future",
344 feature = "pdfium_7350",
345 feature = "pdfium_7215",
346 feature = "pdfium_7123",
347 feature = "pdfium_6996",
348 feature = "pdfium_6721",
349 feature = "pdfium_6666",
350 feature = "pdfium_6611"
351 ))]
352 pub fn text_object(&self) -> Result<PdfPageTextObject, PdfiumError> {
354 let object_handle = self
355 .bindings()
356 .FPDFText_GetTextObject(self.text_page_handle(), self.index);
357
358 if object_handle.is_null() {
359 Err(PdfiumError::PdfiumLibraryInternalError(
360 PdfiumInternalError::Unknown,
361 ))
362 } else {
363 Ok(PdfPageTextObject::from_pdfium(
364 object_handle,
365 PdfPageObjectOwnership::owned_by_page(self.document_handle(), self.page_handle()),
366 self.bindings(),
367 ))
368 }
369 }
370
371 #[cfg(any(
372 feature = "pdfium_future",
373 feature = "pdfium_7350",
374 feature = "pdfium_7215",
375 feature = "pdfium_7123",
376 feature = "pdfium_6996",
377 feature = "pdfium_6721",
378 feature = "pdfium_6666",
379 feature = "pdfium_6611"
380 ))]
381 pub fn render_mode(&self) -> Result<PdfPageTextRenderMode, PdfiumError> {
383 self.text_object()
384 .map(|text_object| text_object.render_mode())
385 }
386
387 #[cfg(any(
388 feature = "pdfium_6569",
389 feature = "pdfium_6555",
390 feature = "pdfium_6490",
391 feature = "pdfium_6406",
392 feature = "pdfium_6337",
393 feature = "pdfium_6295",
394 feature = "pdfium_6259",
395 feature = "pdfium_6164",
396 feature = "pdfium_6124",
397 feature = "pdfium_6110",
398 feature = "pdfium_6084",
399 feature = "pdfium_6043",
400 feature = "pdfium_6015",
401 feature = "pdfium_5961"
402 ))]
403 pub fn render_mode(&self) -> Result<PdfPageTextRenderMode, PdfiumError> {
405 PdfPageTextRenderMode::from_pdfium(
406 self.bindings()
407 .FPDFText_GetTextRenderMode(self.text_page_handle, self.index),
408 )
409 }
410
411 pub fn fill_color(&self) -> Result<PdfColor, PdfiumError> {
413 let mut r = 0;
414
415 let mut g = 0;
416
417 let mut b = 0;
418
419 let mut a = 0;
420
421 if self.bindings.is_true(self.bindings.FPDFText_GetFillColor(
422 self.text_page_handle,
423 self.index,
424 &mut r,
425 &mut g,
426 &mut b,
427 &mut a,
428 )) {
429 Ok(PdfColor::new(
430 r.try_into()
431 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
432 g.try_into()
433 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
434 b.try_into()
435 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
436 a.try_into()
437 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
438 ))
439 } else {
440 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
441 }
442 }
443
444 pub fn stroke_color(&self) -> Result<PdfColor, PdfiumError> {
446 let mut r = 0;
447
448 let mut g = 0;
449
450 let mut b = 0;
451
452 let mut a = 0;
453
454 if self
455 .bindings()
456 .is_true(self.bindings.FPDFText_GetStrokeColor(
457 self.text_page_handle(),
458 self.index,
459 &mut r,
460 &mut g,
461 &mut b,
462 &mut a,
463 ))
464 {
465 Ok(PdfColor::new(
466 r.try_into()
467 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
468 g.try_into()
469 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
470 b.try_into()
471 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
472 a.try_into()
473 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
474 ))
475 } else {
476 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
477 }
478 }
479
480 #[inline]
482 pub fn angle_degrees(&self) -> Result<f32, PdfiumError> {
483 self.angle_radians().map(|result| result.to_degrees())
484 }
485
486 #[inline]
488 pub fn angle_radians(&self) -> Result<f32, PdfiumError> {
489 let result = self
490 .bindings
491 .FPDFText_GetCharAngle(self.text_page_handle, self.index);
492
493 if result == -1.0 {
494 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
495 } else {
496 Ok(result)
497 }
498 }
499
500 pub fn tight_bounds(&self) -> Result<PdfRect, PdfiumError> {
506 let mut left = 0.0;
507
508 let mut bottom = 0.0;
509
510 let mut right = 0.0;
511
512 let mut top = 0.0;
513
514 let result = self.bindings().FPDFText_GetCharBox(
515 self.text_page_handle(),
516 self.index,
517 &mut left,
518 &mut right,
519 &mut bottom,
520 &mut top,
521 );
522
523 PdfRect::from_pdfium_as_result(
524 result,
525 FS_RECTF {
526 left: left as f32,
527 top: top as f32,
528 right: right as f32,
529 bottom: bottom as f32,
530 },
531 self.bindings(),
532 )
533 }
534
535 pub fn loose_bounds(&self) -> Result<PdfRect, PdfiumError> {
540 let mut bounds = FS_RECTF {
541 left: 0.0,
542 top: 0.0,
543 right: 0.0,
544 bottom: 0.0,
545 };
546
547 let result = self.bindings.FPDFText_GetLooseCharBox(
548 self.text_page_handle(),
549 self.index,
550 &mut bounds,
551 );
552
553 PdfRect::from_pdfium_as_result(result, bounds, self.bindings())
554 }
555
556 fn get_matrix_impl(&self) -> Result<PdfMatrix, PdfiumError> {
558 let mut matrix = FS_MATRIX {
559 a: 0.0,
560 b: 0.0,
561 c: 0.0,
562 d: 0.0,
563 e: 0.0,
564 f: 0.0,
565 };
566
567 if self.bindings().is_true(self.bindings().FPDFText_GetMatrix(
568 self.text_page_handle(),
569 self.index,
570 &mut matrix,
571 )) {
572 Ok(PdfMatrix::from_pdfium(matrix))
573 } else {
574 Err(PdfiumError::PdfiumLibraryInternalError(
575 PdfiumInternalError::Unknown,
576 ))
577 }
578 }
579
580 create_transform_getters!(
581 "this [PdfPageTextChar]",
582 "this [PdfPageTextChar].",
583 "this [PdfPageTextChar],"
584 );
585
586 pub fn origin(&self) -> Result<(PdfPoints, PdfPoints), PdfiumError> {
588 let mut x = 0.0;
589
590 let mut y = 0.0;
591
592 if self
593 .bindings()
594 .is_true(self.bindings().FPDFText_GetCharOrigin(
595 self.text_page_handle(),
596 self.index,
597 &mut x,
598 &mut y,
599 ))
600 {
601 Ok((PdfPoints::new(x as f32), PdfPoints::new(y as f32)))
602 } else {
603 Err(PdfiumError::PdfiumLibraryInternalError(
604 PdfiumInternalError::Unknown,
605 ))
606 }
607 }
608
609 #[inline]
611 pub fn origin_x(&self) -> Result<PdfPoints, PdfiumError> {
612 self.origin().map(|result| result.0)
613 }
614
615 #[inline]
617 pub fn origin_y(&self) -> Result<PdfPoints, PdfiumError> {
618 self.origin().map(|result| result.1)
619 }
620
621 #[inline]
623 pub fn has_descender(&self) -> bool {
624 self.tight_bounds()
625 .map(|bounds| bounds.bottom().value)
626 .unwrap_or(0.0)
627 < self
628 .loose_bounds()
629 .map(|bounds| bounds.bottom().value)
630 .unwrap_or(0.0)
631 }
632
633 #[inline]
636 pub fn is_generated(&self) -> Result<bool, PdfiumError> {
637 match self
638 .bindings()
639 .FPDFText_IsGenerated(self.text_page_handle(), self.index)
640 {
641 1 => Ok(true),
642 0 => Ok(false),
643 _ => Err(PdfiumError::PdfiumLibraryInternalError(
644 PdfiumInternalError::Unknown,
645 )),
646 }
647 }
648
649 #[cfg(any(
650 feature = "pdfium_future",
651 feature = "pdfium_7350",
652 feature = "pdfium_7215",
653 feature = "pdfium_7123",
654 feature = "pdfium_6996",
655 feature = "pdfium_6721",
656 feature = "pdfium_6666",
657 feature = "pdfium_6611",
658 feature = "pdfium_6569",
659 feature = "pdfium_6555",
660 feature = "pdfium_6490",
661 feature = "pdfium_6406",
662 feature = "pdfium_6337",
663 feature = "pdfium_6295",
664 feature = "pdfium_6259",
665 feature = "pdfium_6164",
666 feature = "pdfium_6124",
667 feature = "pdfium_6110",
668 feature = "pdfium_6084",
669 feature = "pdfium_6043",
670 feature = "pdfium_6015",
671 ))]
672 #[inline]
674 pub fn is_hyphen(&self) -> Result<bool, PdfiumError> {
675 match self
676 .bindings()
677 .FPDFText_IsHyphen(self.text_page_handle(), self.index)
678 {
679 1 => Ok(true),
680 0 => Ok(false),
681 _ => Err(PdfiumError::PdfiumLibraryInternalError(
682 PdfiumInternalError::Unknown,
683 )),
684 }
685 }
686}