pdfium_render/pdf/document/page/object/
text.rs1use crate::bindgen::{
5 FPDF_DOCUMENT, FPDF_FONT, FPDF_PAGEOBJECT, FPDF_TEXT_RENDERMODE, FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP,
6 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL, FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP,
7 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE, FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP,
8 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE, FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE,
9 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP, FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN, FPDF_WCHAR,
10};
11use crate::bindings::PdfiumLibraryBindings;
12use crate::error::{PdfiumError, PdfiumInternalError};
13use crate::pdf::document::fonts::ToPdfFontToken;
14use crate::pdf::document::page::object::private::internal::PdfPageObjectPrivate;
15use crate::pdf::document::page::object::{PdfPageObject, PdfPageObjectCommon, PdfPageObjectOwnership};
16
17use crate::pdf::document::PdfDocument;
18use crate::pdf::font::PdfFont;
19use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
20use crate::pdf::points::PdfPoints;
21use crate::utils::mem::create_byte_buffer;
22use crate::utils::utf16le::get_string_from_pdfium_utf16le_bytes;
23use crate::{create_transform_getters, create_transform_setters};
24
25use {crate::pdf::document::page::text::PdfPageText, crate::pdf::document::page::text::chars::PdfPageTextChars};
26
27#[cfg(doc)]
28use {
29 crate::pdf::document::page::PdfPage, crate::pdf::document::page::object::PdfPageObjectType,
30 crate::pdf::document::page::objects::common::PdfPageObjectsCommon,
31};
32
33#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
36pub enum PdfPageTextRenderMode {
37 Unknown = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN as isize,
39
40 FilledUnstroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL as isize,
42
43 StrokedUnfilled = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE as isize,
45
46 FilledThenStroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE as isize,
48
49 Invisible = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE as isize,
51
52 FilledUnstrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP as isize,
54
55 StrokedUnfilledClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP as isize,
57
58 FilledThenStrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP as isize,
60
61 InvisibleClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP as isize,
63}
64
65impl PdfPageTextRenderMode {
66 #[inline]
67 pub(crate) fn from_pdfium(value: i32) -> Result<PdfPageTextRenderMode, PdfiumError> {
68 match value {
69 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN => Ok(PdfPageTextRenderMode::Unknown),
70 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL => Ok(PdfPageTextRenderMode::FilledUnstroked),
71 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE => Ok(PdfPageTextRenderMode::StrokedUnfilled),
72 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE => Ok(PdfPageTextRenderMode::FilledThenStroked),
73 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE => Ok(PdfPageTextRenderMode::Invisible),
74 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP => Ok(PdfPageTextRenderMode::FilledUnstrokedClipping),
75 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP => Ok(PdfPageTextRenderMode::StrokedUnfilledClipping),
76 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP => {
77 Ok(PdfPageTextRenderMode::FilledThenStrokedClipping)
78 }
79 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP => Ok(PdfPageTextRenderMode::InvisibleClipping),
80 _ => Err(PdfiumError::UnknownPdfPageTextRenderMode),
81 }
82 }
83
84 #[inline]
85 #[allow(dead_code)]
86 pub(crate) fn as_pdfium(&self) -> FPDF_TEXT_RENDERMODE {
87 match self {
88 PdfPageTextRenderMode::Unknown => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN,
89 PdfPageTextRenderMode::FilledUnstroked => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL,
90 PdfPageTextRenderMode::StrokedUnfilled => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE,
91 PdfPageTextRenderMode::FilledThenStroked => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE,
92 PdfPageTextRenderMode::Invisible => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE,
93 PdfPageTextRenderMode::FilledUnstrokedClipping => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP,
94 PdfPageTextRenderMode::StrokedUnfilledClipping => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP,
95 PdfPageTextRenderMode::FilledThenStrokedClipping => {
96 FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP
97 }
98 PdfPageTextRenderMode::InvisibleClipping => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP,
99 }
100 }
101}
102
103pub struct PdfPageTextObject<'a> {
120 object_handle: FPDF_PAGEOBJECT,
121 ownership: PdfPageObjectOwnership,
122 bindings: &'a dyn PdfiumLibraryBindings,
123}
124
125impl<'a> PdfPageTextObject<'a> {
126 #[inline]
127 pub(crate) fn from_pdfium(
128 object_handle: FPDF_PAGEOBJECT,
129 ownership: PdfPageObjectOwnership,
130 bindings: &'a dyn PdfiumLibraryBindings,
131 ) -> Self {
132 PdfPageTextObject {
133 object_handle,
134 ownership,
135 bindings,
136 }
137 }
138
139 #[inline]
146 pub fn new(
147 document: &PdfDocument<'a>,
148 text: impl ToString,
149 font: impl ToPdfFontToken,
150 font_size: PdfPoints,
151 ) -> Result<Self, PdfiumError> {
152 Self::new_from_handles(
153 document.handle(),
154 text,
155 font.token().handle(),
156 font_size,
157 document.bindings(),
158 )
159 }
160
161 pub(crate) fn new_from_handles(
162 document: FPDF_DOCUMENT,
163 text: impl ToString,
164 font: FPDF_FONT,
165 font_size: PdfPoints,
166 bindings: &'a dyn PdfiumLibraryBindings,
167 ) -> Result<Self, PdfiumError> {
168 let handle = bindings.FPDFPageObj_CreateTextObj(document, font, font_size.value);
169
170 if handle.is_null() {
171 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
172 } else {
173 let mut result = PdfPageTextObject {
174 object_handle: handle,
175 ownership: PdfPageObjectOwnership::unowned(),
176 bindings,
177 };
178
179 result.set_text(text)?;
180
181 Ok(result)
182 }
183 }
184
185 pub fn render_mode(&self) -> PdfPageTextRenderMode {
187 PdfPageTextRenderMode::from_pdfium(self.bindings().FPDFTextObj_GetTextRenderMode(self.object_handle))
188 .unwrap_or(PdfPageTextRenderMode::Unknown)
189 }
190
191 #[inline]
198 pub fn scaled_font_size(&self) -> PdfPoints {
199 PdfPoints::new(self.unscaled_font_size().value * self.get_vertical_scale())
200 }
201
202 pub fn unscaled_font_size(&self) -> PdfPoints {
209 let mut result = 0.0;
210
211 if self
212 .bindings()
213 .is_true(self.bindings().FPDFTextObj_GetFontSize(self.object_handle, &mut result))
214 {
215 PdfPoints::new(result)
216 } else {
217 PdfPoints::ZERO
218 }
219 }
220
221 pub fn font(&self) -> PdfFont<'_> {
223 PdfFont::from_pdfium(
224 self.bindings().FPDFTextObj_GetFont(self.object_handle),
225 self.bindings(),
226 None,
227 false,
228 )
229 }
230
231 pub fn text(&self) -> String {
252 let page_handle = match self.ownership() {
253 PdfPageObjectOwnership::Page(ownership) => Some(ownership.page_handle()),
254 PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.page_handle()),
255 _ => None,
256 };
257
258 if let Some(page_handle) = page_handle {
259 let text_handle = self.bindings().FPDFText_LoadPage(page_handle);
260
261 if !text_handle.is_null() {
262 let buffer_length =
263 self.bindings()
264 .FPDFTextObj_GetText(self.object_handle(), text_handle, std::ptr::null_mut(), 0);
265
266 if buffer_length == 0 {
267 return String::new();
268 }
269
270 let mut buffer = create_byte_buffer(buffer_length as usize);
271
272 let result = self.bindings().FPDFTextObj_GetText(
273 self.object_handle(),
274 text_handle,
275 buffer.as_mut_ptr() as *mut FPDF_WCHAR,
276 buffer_length,
277 );
278
279 assert_eq!(result, buffer_length);
280
281 self.bindings.FPDFText_ClosePage(text_handle);
282
283 get_string_from_pdfium_utf16le_bytes(buffer).unwrap_or_default()
284 } else {
285 String::new()
286 }
287 } else {
288 String::new()
289 }
290 }
291
292 pub fn set_text(&mut self, text: impl ToString) -> Result<(), PdfiumError> {
297 let text = text.to_string();
298
299 let text = if text.is_empty() { " " } else { text.as_str() };
300
301 if self
302 .bindings()
303 .is_true(self.bindings().FPDFText_SetText_str(self.object_handle(), text))
304 {
305 Ok(())
306 } else {
307 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
308 }
309 }
310
311 pub fn set_render_mode(&mut self, render_mode: PdfPageTextRenderMode) -> Result<(), PdfiumError> {
313 if self.bindings().is_true(
314 self.bindings()
315 .FPDFTextObj_SetTextRenderMode(self.object_handle(), render_mode.as_pdfium()),
316 ) {
317 Ok(())
318 } else {
319 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
320 }
321 }
322
323 #[inline]
326 pub fn chars(&self, text: &'a PdfPageText<'a>) -> Result<PdfPageTextChars<'a>, PdfiumError> {
327 text.chars_for_object(self)
328 }
329
330 #[inline]
335 pub fn has_descenders(&self, text: &PdfPageText) -> Result<bool, PdfiumError> {
336 self.chars(text)
337 .map(|chars| chars.iter().any(|char| char.has_descender()))
338 }
339
340 pub fn descent(&self, text: &PdfPageText) -> Result<PdfPoints, PdfiumError> {
346 let object_bottom = self.get_vertical_translation();
347
348 let mut maximum_descent = object_bottom;
349
350 for char in self.chars(text)?.iter() {
351 let char_bottom = char.tight_bounds()?.bottom();
352
353 if char_bottom < maximum_descent {
354 maximum_descent = char_bottom;
355 }
356 }
357
358 Ok(maximum_descent - object_bottom)
359 }
360
361 create_transform_setters!(
362 &mut Self,
363 Result<(), PdfiumError>,
364 "this [PdfPageTextObject]",
365 "this [PdfPageTextObject].",
366 "this [PdfPageTextObject],"
367 );
368
369 create_transform_getters!(
370 "this [PdfPageTextObject]",
371 "this [PdfPageTextObject].",
372 "this [PdfPageTextObject],"
373 );
374}
375
376impl<'a> PdfPageObjectPrivate<'a> for PdfPageTextObject<'a> {
377 #[inline]
378 fn object_handle(&self) -> FPDF_PAGEOBJECT {
379 self.object_handle
380 }
381
382 #[inline]
383 fn ownership(&self) -> &PdfPageObjectOwnership {
384 &self.ownership
385 }
386
387 #[inline]
388 fn set_ownership(&mut self, ownership: PdfPageObjectOwnership) {
389 self.ownership = ownership;
390 }
391
392 #[inline]
393 fn bindings(&self) -> &dyn PdfiumLibraryBindings {
394 self.bindings
395 }
396
397 #[inline]
398 fn is_copyable_impl(&self) -> bool {
399 true
400 }
401
402 #[inline]
403 fn try_copy_impl<'b>(
404 &self,
405 document: FPDF_DOCUMENT,
406 bindings: &'b dyn PdfiumLibraryBindings,
407 ) -> Result<PdfPageObject<'b>, PdfiumError> {
408 let mut copy = PdfPageTextObject::new_from_handles(
409 document,
410 self.text(),
411 self.font().handle(),
412 self.unscaled_font_size(),
413 bindings,
414 )?;
415
416 copy.set_fill_color(self.fill_color()?)?;
417 copy.set_stroke_color(self.stroke_color()?)?;
418 copy.set_stroke_width(self.stroke_width()?)?;
419 copy.set_line_join(self.line_join()?)?;
420 copy.set_line_cap(self.line_cap()?)?;
421 copy.reset_matrix(self.matrix()?)?;
422
423 Ok(PdfPageObject::Text(copy))
424 }
425}
426
427impl<'a> Drop for PdfPageTextObject<'a> {
428 fn drop(&mut self) {
430 self.drop_impl();
431 }
432}