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_7881",
33 feature = "pdfium_7763",
34 feature = "pdfium_7543",
35 feature = "pdfium_7350",
36 feature = "pdfium_7215",
37 feature = "pdfium_7123",
38 feature = "pdfium_6996",
39 feature = "pdfium_6721",
40 feature = "pdfium_6666",
41 feature = "pdfium_6611",
42))]
43use {
44 crate::pdf::document::page::text::chars::PdfPageTextChars,
45 crate::pdf::document::page::text::PdfPageText,
46};
47
48#[cfg(doc)]
49use {
50 crate::pdf::document::page::object::PdfPageObject,
51 crate::pdf::document::page::object::PdfPageObjectType,
52 crate::pdf::document::page::objects::common::PdfPageObjectsCommon,
53 crate::pdf::document::page::PdfPage,
54};
55
56#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
59pub enum PdfPageTextRenderMode {
60 Unknown = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN as isize,
62
63 FilledUnstroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL as isize,
65
66 StrokedUnfilled = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE as isize,
68
69 FilledThenStroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE as isize,
71
72 Invisible = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE as isize,
74
75 FilledUnstrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP as isize,
77
78 StrokedUnfilledClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP as isize,
80
81 FilledThenStrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP as isize,
83
84 InvisibleClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP as isize,
86}
87
88impl PdfPageTextRenderMode {
89 #[inline]
90 pub(crate) fn from_pdfium(value: i32) -> Result<PdfPageTextRenderMode, PdfiumError> {
91 match value {
92 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN => Ok(PdfPageTextRenderMode::Unknown),
93 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL => {
94 Ok(PdfPageTextRenderMode::FilledUnstroked)
95 }
96 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE => {
97 Ok(PdfPageTextRenderMode::StrokedUnfilled)
98 }
99 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE => {
100 Ok(PdfPageTextRenderMode::FilledThenStroked)
101 }
102 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE => {
103 Ok(PdfPageTextRenderMode::Invisible)
104 }
105 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP => {
106 Ok(PdfPageTextRenderMode::FilledUnstrokedClipping)
107 }
108 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP => {
109 Ok(PdfPageTextRenderMode::StrokedUnfilledClipping)
110 }
111 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP => {
112 Ok(PdfPageTextRenderMode::FilledThenStrokedClipping)
113 }
114 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP => {
115 Ok(PdfPageTextRenderMode::InvisibleClipping)
116 }
117 _ => Err(PdfiumError::UnknownPdfPageTextRenderMode),
118 }
119 }
120
121 #[inline]
122 #[allow(dead_code)]
123 pub(crate) fn as_pdfium(&self) -> FPDF_TEXT_RENDERMODE {
125 match self {
126 PdfPageTextRenderMode::Unknown => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN,
127 PdfPageTextRenderMode::FilledUnstroked => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL,
128 PdfPageTextRenderMode::StrokedUnfilled => {
129 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE
130 }
131 PdfPageTextRenderMode::FilledThenStroked => {
132 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE
133 }
134 PdfPageTextRenderMode::Invisible => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE,
135 PdfPageTextRenderMode::FilledUnstrokedClipping => {
136 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP
137 }
138 PdfPageTextRenderMode::StrokedUnfilledClipping => {
139 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP
140 }
141 PdfPageTextRenderMode::FilledThenStrokedClipping => {
142 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP
143 }
144 PdfPageTextRenderMode::InvisibleClipping => {
145 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP
146 }
147 }
148 }
149}
150
151pub struct PdfPageTextObject<'a> {
168 object_handle: FPDF_PAGEOBJECT,
169 ownership: PdfPageObjectOwnership,
170 lifetime: PhantomData<&'a FPDF_PAGEOBJECT>,
171}
172
173impl<'a> PdfPageTextObject<'a> {
174 #[inline]
175 pub(crate) fn from_pdfium(
176 object_handle: FPDF_PAGEOBJECT,
177 ownership: PdfPageObjectOwnership,
178 ) -> Self {
179 PdfPageTextObject {
180 object_handle,
181 ownership,
182 lifetime: PhantomData,
183 }
184 }
185
186 #[inline]
197 pub fn new(
198 document: &PdfDocument<'a>,
199 text: impl ToString,
200 font: impl ToPdfFontToken,
201 font_size: PdfPoints,
202 ) -> Result<Self, PdfiumError> {
203 Self::new_from_handles(
204 document.handle(),
205 text,
206 font.token().handle(),
207 font_size,
208 document.bindings(),
209 )
210 }
211
212 pub(crate) fn new_from_handles(
215 document: FPDF_DOCUMENT,
216 text: impl ToString,
217 font: FPDF_FONT,
218 font_size: PdfPoints,
219 bindings: &'a dyn PdfiumLibraryBindings,
220 ) -> Result<Self, PdfiumError> {
221 let handle = unsafe { bindings.FPDFPageObj_CreateTextObj(document, font, font_size.value) };
222
223 if handle.is_null() {
224 Err(PdfiumError::PdfiumLibraryInternalError(
225 PdfiumInternalError::Unknown,
226 ))
227 } else {
228 let mut result = PdfPageTextObject {
229 object_handle: handle,
230 ownership: PdfPageObjectOwnership::unowned(),
231 lifetime: PhantomData,
232 };
233
234 result.set_text(text)?;
235
236 Ok(result)
237 }
238 }
239
240 pub fn render_mode(&self) -> PdfPageTextRenderMode {
242 PdfPageTextRenderMode::from_pdfium(unsafe {
243 self.bindings()
244 .FPDFTextObj_GetTextRenderMode(self.object_handle)
245 })
246 .unwrap_or(PdfPageTextRenderMode::Unknown)
247 }
248
249 #[inline]
253 pub fn is_visible(&self) -> bool {
254 match self.render_mode() {
255 PdfPageTextRenderMode::Invisible | PdfPageTextRenderMode::InvisibleClipping => false,
256 _ => true,
257 }
258 }
259
260 #[inline]
267 pub fn scaled_font_size(&self) -> PdfPoints {
268 PdfPoints::new(self.unscaled_font_size().value * self.get_vertical_scale())
269 }
270
271 pub fn unscaled_font_size(&self) -> PdfPoints {
278 let mut result = 0.0;
279
280 if self.bindings().is_true(unsafe {
281 self.bindings()
282 .FPDFTextObj_GetFontSize(self.object_handle(), &mut result)
283 }) {
284 PdfPoints::new(result)
285 } else {
286 PdfPoints::ZERO
287 }
288 }
289
290 #[cfg(any(feature = "pdfium_future", feature = "pdfium_7881"))]
291 pub fn set_unscaled_font_size(&self, points: PdfPoints) -> Result<(), PdfiumError> {
296 if self.bindings().is_true(unsafe {
297 self.bindings()
298 .FPDFTextObj_SetFontSize(self.object_handle(), points.value)
299 }) {
300 Ok(())
301 } else {
302 Err(PdfiumError::InvalidFontSize)
303 }
304 }
305
306 #[inline]
308 pub fn font(&self) -> PdfFont<'_> {
309 PdfFont::from_pdfium(
310 unsafe { self.bindings().FPDFTextObj_GetFont(self.object_handle) },
311 None,
312 false,
313 )
314 }
315
316 pub fn text(&self) -> String {
337 let page_handle = match self.ownership() {
347 PdfPageObjectOwnership::Page(ownership) => Some(ownership.page_handle()),
348 PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.page_handle()),
349 _ => None,
350 };
351
352 if let Some(page_handle) = page_handle {
353 let text_handle = unsafe { self.bindings().FPDFText_LoadPage(page_handle) };
354
355 if !text_handle.is_null() {
356 let buffer_length = unsafe {
357 self.bindings().FPDFTextObj_GetText(
358 self.object_handle(),
359 text_handle,
360 std::ptr::null_mut(),
361 0,
362 )
363 };
364
365 if buffer_length == 0 {
366 return String::new();
369 }
370
371 let mut buffer = create_byte_buffer(buffer_length as usize);
372
373 let result = unsafe {
374 self.bindings().FPDFTextObj_GetText(
375 self.object_handle(),
376 text_handle,
377 buffer.as_mut_ptr() as *mut FPDF_WCHAR,
378 buffer_length,
379 )
380 };
381
382 assert_eq!(result, buffer_length);
383
384 unsafe {
385 self.bindings().FPDFText_ClosePage(text_handle);
386 }
387
388 get_string_from_pdfium_utf16le_bytes(buffer).unwrap_or_default()
389 } else {
390 String::new()
394 }
395 } else {
396 String::new()
399 }
400 }
401
402 pub fn set_text(&mut self, text: impl ToString) -> Result<(), PdfiumError> {
407 let text = text.to_string();
408
409 let text = if text.is_empty() { " " } else { text.as_str() };
410
411 if self.bindings().is_true(unsafe {
412 self.bindings()
413 .FPDFText_SetText_str(self.object_handle(), text)
414 }) {
415 Ok(())
416 } else {
417 Err(PdfiumError::PdfiumLibraryInternalError(
418 PdfiumInternalError::Unknown,
419 ))
420 }
421 }
422
423 pub fn set_render_mode(
425 &mut self,
426 render_mode: PdfPageTextRenderMode,
427 ) -> Result<(), PdfiumError> {
428 if self.bindings().is_true(unsafe {
429 self.bindings()
430 .FPDFTextObj_SetTextRenderMode(self.object_handle(), render_mode.as_pdfium())
431 }) {
432 Ok(())
433 } else {
434 Err(PdfiumError::PdfiumLibraryInternalError(
435 PdfiumInternalError::Unknown,
436 ))
437 }
438 }
439
440 #[cfg(any(
441 feature = "pdfium_future",
442 feature = "pdfium_7881",
443 feature = "pdfium_7763",
444 feature = "pdfium_7543",
445 feature = "pdfium_7350",
446 feature = "pdfium_7215",
447 feature = "pdfium_7123",
448 feature = "pdfium_6996",
449 feature = "pdfium_6721",
450 feature = "pdfium_6666",
451 feature = "pdfium_6611",
452 ))]
453 #[inline]
456 pub fn chars(&self, text: &'a PdfPageText<'a>) -> Result<PdfPageTextChars<'a>, PdfiumError> {
457 text.chars_for_object(self)
458 }
459
460 #[cfg(any(
461 feature = "pdfium_future",
462 feature = "pdfium_7881",
463 feature = "pdfium_7763",
464 feature = "pdfium_7543",
465 feature = "pdfium_7350",
466 feature = "pdfium_7215",
467 feature = "pdfium_7123",
468 feature = "pdfium_6996",
469 feature = "pdfium_6721",
470 feature = "pdfium_6666",
471 feature = "pdfium_6611",
472 ))]
473 #[inline]
478 pub fn has_descenders(&self, text: &PdfPageText) -> Result<bool, PdfiumError> {
479 self.chars(text)
480 .map(|chars| chars.iter().any(|char| char.has_descender()))
481 }
482
483 #[cfg(any(
484 feature = "pdfium_future",
485 feature = "pdfium_7881",
486 feature = "pdfium_7763",
487 feature = "pdfium_7543",
488 feature = "pdfium_7350",
489 feature = "pdfium_7215",
490 feature = "pdfium_7123",
491 feature = "pdfium_6996",
492 feature = "pdfium_6721",
493 feature = "pdfium_6666",
494 feature = "pdfium_6611",
495 ))]
496 pub fn descent(&self, text: &PdfPageText) -> Result<PdfPoints, PdfiumError> {
502 let object_bottom = self.get_vertical_translation();
503
504 let mut maximum_descent = object_bottom;
505
506 for char in self.chars(text)?.iter() {
507 let char_bottom = char.tight_bounds()?.bottom();
508
509 if char_bottom < maximum_descent {
510 maximum_descent = char_bottom;
511 }
512 }
513
514 Ok(maximum_descent - object_bottom)
515 }
516
517 create_transform_setters!(
518 &mut Self,
519 Result<(), PdfiumError>,
520 "this [PdfPageTextObject]",
521 "this [PdfPageTextObject].",
522 "this [PdfPageTextObject],"
523 );
524
525 create_transform_getters!(
529 "this [PdfPageTextObject]",
530 "this [PdfPageTextObject].",
531 "this [PdfPageTextObject],"
532 );
533
534 }
537
538impl<'a> PdfPageObjectPrivate<'a> for PdfPageTextObject<'a> {
539 #[inline]
540 fn object_handle(&self) -> FPDF_PAGEOBJECT {
541 self.object_handle
542 }
543
544 #[inline]
545 fn ownership(&self) -> &PdfPageObjectOwnership {
546 &self.ownership
547 }
548
549 #[inline]
550 fn set_ownership(&mut self, ownership: PdfPageObjectOwnership) {
551 self.ownership = ownership;
552 }
553}
554
555impl<'a> Drop for PdfPageTextObject<'a> {
556 fn drop(&mut self) {
558 self.drop_impl();
559 }
560}
561
562impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPageTextObject<'a> {}
563
564#[cfg(feature = "thread_safe")]
565unsafe impl<'a> Send for PdfPageTextObject<'a> {}
566
567#[cfg(feature = "thread_safe")]
568unsafe impl<'a> Sync for PdfPageTextObject<'a> {}