1use crate::bindgen::{
5 FPDF_DOCUMENT, FPDF_FONT, FPDF_PAGEOBJECT, FPDF_TEXT_RENDERMODE,
6 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP, FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL,
7 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP,
8 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE,
9 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP,
10 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE,
11 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE,
12 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP,
13 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN, FPDF_WCHAR,
14};
15use crate::bindings::PdfiumLibraryBindings;
16use crate::error::{PdfiumError, PdfiumInternalError};
17use crate::pdf::document::fonts::ToPdfFontToken;
18use crate::pdf::document::page::object::private::internal::PdfPageObjectPrivate;
19use crate::pdf::document::page::object::PdfPageObjectOwnership;
20use crate::pdf::document::PdfDocument;
21use crate::pdf::font::PdfFont;
22use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
23use crate::pdf::points::PdfPoints;
24use crate::pdfium::PdfiumLibraryBindingsAccessor;
25use crate::utils::mem::create_byte_buffer;
26use crate::utils::utf16le::get_string_from_pdfium_utf16le_bytes;
27use crate::{create_transform_getters, create_transform_setters};
28use std::marker::PhantomData;
29
30#[cfg(any(
31 feature = "pdfium_future",
32 feature = "pdfium_8125",
33 feature = "pdfium_7881",
34 feature = "pdfium_7763",
35 feature = "pdfium_7543",
36 feature = "pdfium_7350",
37 feature = "pdfium_7215",
38 feature = "pdfium_7123",
39 feature = "pdfium_6996",
40 feature = "pdfium_6721",
41 feature = "pdfium_6666",
42 feature = "pdfium_6611",
43))]
44use {
45 crate::pdf::document::page::text::chars::PdfPageTextChars,
46 crate::pdf::document::page::text::PdfPageText,
47};
48
49#[cfg(doc)]
50use {
51 crate::pdf::document::page::object::PdfPageObject,
52 crate::pdf::document::page::object::PdfPageObjectType,
53 crate::pdf::document::page::objects::common::PdfPageObjectsCommon,
54 crate::pdf::document::page::PdfPage,
55};
56
57#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
60pub enum PdfPageTextRenderMode {
61 Unknown = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN as isize,
63
64 FilledUnstroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL as isize,
66
67 StrokedUnfilled = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE as isize,
69
70 FilledThenStroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE as isize,
72
73 Invisible = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE as isize,
75
76 FilledUnstrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP as isize,
78
79 StrokedUnfilledClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP as isize,
81
82 FilledThenStrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP as isize,
84
85 InvisibleClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP as isize,
87}
88
89impl PdfPageTextRenderMode {
90 #[inline]
91 pub(crate) fn from_pdfium(value: i32) -> Result<PdfPageTextRenderMode, PdfiumError> {
92 match value {
93 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN => Ok(PdfPageTextRenderMode::Unknown),
94 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL => {
95 Ok(PdfPageTextRenderMode::FilledUnstroked)
96 }
97 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE => {
98 Ok(PdfPageTextRenderMode::StrokedUnfilled)
99 }
100 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE => {
101 Ok(PdfPageTextRenderMode::FilledThenStroked)
102 }
103 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE => {
104 Ok(PdfPageTextRenderMode::Invisible)
105 }
106 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP => {
107 Ok(PdfPageTextRenderMode::FilledUnstrokedClipping)
108 }
109 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP => {
110 Ok(PdfPageTextRenderMode::StrokedUnfilledClipping)
111 }
112 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP => {
113 Ok(PdfPageTextRenderMode::FilledThenStrokedClipping)
114 }
115 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP => {
116 Ok(PdfPageTextRenderMode::InvisibleClipping)
117 }
118 _ => Err(PdfiumError::UnknownPdfPageTextRenderMode),
119 }
120 }
121
122 #[inline]
123 #[allow(dead_code)]
124 pub(crate) fn as_pdfium(&self) -> FPDF_TEXT_RENDERMODE {
126 match self {
127 PdfPageTextRenderMode::Unknown => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN,
128 PdfPageTextRenderMode::FilledUnstroked => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL,
129 PdfPageTextRenderMode::StrokedUnfilled => {
130 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE
131 }
132 PdfPageTextRenderMode::FilledThenStroked => {
133 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE
134 }
135 PdfPageTextRenderMode::Invisible => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE,
136 PdfPageTextRenderMode::FilledUnstrokedClipping => {
137 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP
138 }
139 PdfPageTextRenderMode::StrokedUnfilledClipping => {
140 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP
141 }
142 PdfPageTextRenderMode::FilledThenStrokedClipping => {
143 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP
144 }
145 PdfPageTextRenderMode::InvisibleClipping => {
146 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP
147 }
148 }
149 }
150}
151
152pub struct PdfPageTextObject<'a> {
169 object_handle: FPDF_PAGEOBJECT,
170 ownership: PdfPageObjectOwnership,
171 lifetime: PhantomData<&'a FPDF_PAGEOBJECT>,
172}
173
174impl<'a> PdfPageTextObject<'a> {
175 #[inline]
176 pub(crate) fn from_pdfium(
177 object_handle: FPDF_PAGEOBJECT,
178 ownership: PdfPageObjectOwnership,
179 ) -> Self {
180 PdfPageTextObject {
181 object_handle,
182 ownership,
183 lifetime: PhantomData,
184 }
185 }
186
187 #[inline]
198 pub fn new(
199 document: &PdfDocument<'a>,
200 text: impl ToString,
201 font: impl ToPdfFontToken,
202 font_size: PdfPoints,
203 ) -> Result<Self, PdfiumError> {
204 Self::new_from_handles(
205 document.handle(),
206 text,
207 font.token().handle(),
208 font_size,
209 document.bindings(),
210 )
211 }
212
213 pub(crate) fn new_from_handles(
216 document: FPDF_DOCUMENT,
217 text: impl ToString,
218 font: FPDF_FONT,
219 font_size: PdfPoints,
220 bindings: &'a dyn PdfiumLibraryBindings,
221 ) -> Result<Self, PdfiumError> {
222 let handle = unsafe { bindings.FPDFPageObj_CreateTextObj(document, font, font_size.value) };
223
224 if handle.is_null() {
225 Err(PdfiumError::PdfiumLibraryInternalError(
226 PdfiumInternalError::Unknown,
227 ))
228 } else {
229 let mut result = PdfPageTextObject {
230 object_handle: handle,
231 ownership: PdfPageObjectOwnership::unowned(),
232 lifetime: PhantomData,
233 };
234
235 result.set_text(text)?;
236
237 Ok(result)
238 }
239 }
240
241 pub fn render_mode(&self) -> PdfPageTextRenderMode {
243 PdfPageTextRenderMode::from_pdfium(unsafe {
244 self.bindings()
245 .FPDFTextObj_GetTextRenderMode(self.object_handle)
246 })
247 .unwrap_or(PdfPageTextRenderMode::Unknown)
248 }
249
250 #[inline]
254 pub fn is_visible(&self) -> bool {
255 match self.render_mode() {
256 PdfPageTextRenderMode::Invisible | PdfPageTextRenderMode::InvisibleClipping => false,
257 _ => true,
258 }
259 }
260
261 #[inline]
268 pub fn scaled_font_size(&self) -> PdfPoints {
269 PdfPoints::new(self.unscaled_font_size().value * self.get_vertical_scale())
270 }
271
272 pub fn unscaled_font_size(&self) -> PdfPoints {
279 let mut result = 0.0;
280
281 if self.bindings().is_true(unsafe {
282 self.bindings()
283 .FPDFTextObj_GetFontSize(self.object_handle(), &mut result)
284 }) {
285 PdfPoints::new(result)
286 } else {
287 PdfPoints::ZERO
288 }
289 }
290
291 #[cfg(any(
292 feature = "pdfium_future",
293 feature = "pdfium_8125",
294 feature = "pdfium_7881"
295 ))]
296 pub fn set_unscaled_font_size(&self, points: PdfPoints) -> Result<(), PdfiumError> {
301 if self.bindings().is_true(unsafe {
302 self.bindings()
303 .FPDFTextObj_SetFontSize(self.object_handle(), points.value)
304 }) {
305 Ok(())
306 } else {
307 Err(PdfiumError::InvalidFontSize)
308 }
309 }
310
311 #[inline]
313 pub fn font(&self) -> PdfFont<'_> {
314 PdfFont::from_pdfium(
315 unsafe { self.bindings().FPDFTextObj_GetFont(self.object_handle) },
316 None,
317 false,
318 )
319 }
320
321 pub fn text(&self) -> String {
342 let page_handle = match self.ownership() {
352 PdfPageObjectOwnership::Page(ownership) => Some(ownership.page_handle()),
353 PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.page_handle()),
354 _ => None,
355 };
356
357 if let Some(page_handle) = page_handle {
358 let text_handle = unsafe { self.bindings().FPDFText_LoadPage(page_handle) };
359
360 if !text_handle.is_null() {
361 let buffer_length = unsafe {
362 self.bindings().FPDFTextObj_GetText(
363 self.object_handle(),
364 text_handle,
365 std::ptr::null_mut(),
366 0,
367 )
368 };
369
370 if buffer_length == 0 {
371 return String::new();
374 }
375
376 let mut buffer = create_byte_buffer(buffer_length as usize);
377
378 let result = unsafe {
379 self.bindings().FPDFTextObj_GetText(
380 self.object_handle(),
381 text_handle,
382 buffer.as_mut_ptr() as *mut FPDF_WCHAR,
383 buffer_length,
384 )
385 };
386
387 assert_eq!(result, buffer_length);
388
389 unsafe {
390 self.bindings().FPDFText_ClosePage(text_handle);
391 }
392
393 get_string_from_pdfium_utf16le_bytes(buffer).unwrap_or_default()
394 } else {
395 String::new()
399 }
400 } else {
401 String::new()
404 }
405 }
406
407 pub fn set_text(&mut self, text: impl ToString) -> Result<(), PdfiumError> {
412 let text = text.to_string();
413
414 let text = if text.is_empty() { " " } else { text.as_str() };
415
416 if self.bindings().is_true(unsafe {
417 self.bindings()
418 .FPDFText_SetText_str(self.object_handle(), text)
419 }) {
420 Ok(())
421 } else {
422 Err(PdfiumError::PdfiumLibraryInternalError(
423 PdfiumInternalError::Unknown,
424 ))
425 }
426 }
427
428 pub fn set_render_mode(
430 &mut self,
431 render_mode: PdfPageTextRenderMode,
432 ) -> Result<(), PdfiumError> {
433 if self.bindings().is_true(unsafe {
434 self.bindings()
435 .FPDFTextObj_SetTextRenderMode(self.object_handle(), render_mode.as_pdfium())
436 }) {
437 Ok(())
438 } else {
439 Err(PdfiumError::PdfiumLibraryInternalError(
440 PdfiumInternalError::Unknown,
441 ))
442 }
443 }
444
445 #[cfg(any(
446 feature = "pdfium_future",
447 feature = "pdfium_8125",
448 feature = "pdfium_7881",
449 feature = "pdfium_7763",
450 feature = "pdfium_7543",
451 feature = "pdfium_7350",
452 feature = "pdfium_7215",
453 feature = "pdfium_7123",
454 feature = "pdfium_6996",
455 feature = "pdfium_6721",
456 feature = "pdfium_6666",
457 feature = "pdfium_6611",
458 ))]
459 #[inline]
462 pub fn chars(&self, text: &'a PdfPageText<'a>) -> Result<PdfPageTextChars<'a>, PdfiumError> {
463 text.chars_for_object(self)
464 }
465
466 #[cfg(any(
467 feature = "pdfium_future",
468 feature = "pdfium_8125",
469 feature = "pdfium_7881",
470 feature = "pdfium_7763",
471 feature = "pdfium_7543",
472 feature = "pdfium_7350",
473 feature = "pdfium_7215",
474 feature = "pdfium_7123",
475 feature = "pdfium_6996",
476 feature = "pdfium_6721",
477 feature = "pdfium_6666",
478 feature = "pdfium_6611",
479 ))]
480 #[inline]
485 pub fn has_descenders(&self, text: &PdfPageText) -> Result<bool, PdfiumError> {
486 self.chars(text)
487 .map(|chars| chars.iter().any(|char| char.has_descender()))
488 }
489
490 #[cfg(any(
491 feature = "pdfium_future",
492 feature = "pdfium_8125",
493 feature = "pdfium_7881",
494 feature = "pdfium_7763",
495 feature = "pdfium_7543",
496 feature = "pdfium_7350",
497 feature = "pdfium_7215",
498 feature = "pdfium_7123",
499 feature = "pdfium_6996",
500 feature = "pdfium_6721",
501 feature = "pdfium_6666",
502 feature = "pdfium_6611",
503 ))]
504 pub fn descent(&self, text: &PdfPageText) -> Result<PdfPoints, PdfiumError> {
510 let object_bottom = self.get_vertical_translation();
511
512 let mut maximum_descent = object_bottom;
513
514 for char in self.chars(text)?.iter() {
515 let char_bottom = char.tight_bounds()?.bottom();
516
517 if char_bottom < maximum_descent {
518 maximum_descent = char_bottom;
519 }
520 }
521
522 Ok(maximum_descent - object_bottom)
523 }
524
525 create_transform_setters!(
526 &mut Self,
527 Result<(), PdfiumError>,
528 "this [PdfPageTextObject]",
529 "this [PdfPageTextObject].",
530 "this [PdfPageTextObject],"
531 );
532
533 create_transform_getters!(
537 "this [PdfPageTextObject]",
538 "this [PdfPageTextObject].",
539 "this [PdfPageTextObject],"
540 );
541
542 }
545
546impl<'a> PdfPageObjectPrivate<'a> for PdfPageTextObject<'a> {
547 #[inline]
548 fn object_handle(&self) -> FPDF_PAGEOBJECT {
549 self.object_handle
550 }
551
552 #[inline]
553 fn ownership(&self) -> &PdfPageObjectOwnership {
554 &self.ownership
555 }
556
557 #[inline]
558 fn set_ownership(&mut self, ownership: PdfPageObjectOwnership) {
559 self.ownership = ownership;
560 }
561}
562
563impl<'a> Drop for PdfPageTextObject<'a> {
564 fn drop(&mut self) {
566 self.drop_impl();
567 }
568}
569
570impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPageTextObject<'a> {}
571
572#[cfg(feature = "thread_safe")]
573unsafe impl<'a> Send for PdfPageTextObject<'a> {}
574
575#[cfg(feature = "thread_safe")]
576unsafe impl<'a> Sync for PdfPageTextObject<'a> {}