Skip to main content

pdfium_render/pdf/document/page/object/
text.rs

1//! Defines the [PdfPageTextObject] struct, exposing functionality related to a single
2//! page object defining a piece of formatted text.
3
4use 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/// The text rendering modes supported by the PDF standard, as listed in table 5.3
34/// on page 402 in the PDF Reference manual version 1.7.
35#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
36pub enum PdfPageTextRenderMode {
37    /// The text render mode is not recognized by Pdfium.
38    Unknown = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN as isize,
39
40    /// The text will be filled, but not stroked.
41    FilledUnstroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL as isize,
42
43    /// The text will be stroked, but not filled.
44    StrokedUnfilled = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE as isize,
45
46    /// The text will be filled, then stroked.
47    FilledThenStroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE as isize,
48
49    /// The text will be neither filled nor stroked. It will still take up size in the layout, however.
50    Invisible = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE as isize,
51
52    /// The text will be filled and added to the path for clipping.
53    FilledUnstrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP as isize,
54
55    /// The text will be stroked and added to the path for clipping.
56    StrokedUnfilledClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP as isize,
57
58    /// The text will be filled, then stroked, and added to the path for clipping.
59    FilledThenStrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP as isize,
60
61    /// The text will be neither filled nor stroked, only added to the path for clipping.
62    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
103/// A single [PdfPageObject] of type [PdfPageObjectType::Text]. The page object defines a single
104/// piece of formatted text.
105///
106/// Page objects can be created either attached to a [PdfPage] (in which case the page object's
107/// memory is owned by the containing page) or detached from any page (in which case the page
108/// object's memory is owned by the object). Page objects are not rendered until they are
109/// attached to a page; page objects that are never attached to a page will be lost when they
110/// fall out of scope.
111///
112/// The simplest way to create a page text object that is immediately attached to a page
113/// is to call the [PdfPageObjectsCommon::create_text_object()] function.
114///
115/// Creating a detached page text object offers more scope for customization, but you must
116/// add the object to a containing [PdfPage] manually. To create a detached page text object,
117/// use the [PdfPageTextObject::new()] function. The detached page text object can later
118/// be attached to a page by using the [PdfPageObjectsCommon::add_text_object()] function.
119pub 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    /// Creates a new [PdfPageTextObject] from the given arguments. The returned page object
140    /// will not be rendered until it is added to a [PdfPage] using the
141    /// [PdfPageObjectsCommon::add_text_object()] function.
142    ///
143    /// A single space will be used if the given text is empty, in order to avoid
144    /// unexpected behaviour from Pdfium when dealing with empty strings.
145    #[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    /// Returns the text rendering mode for the text contained within this [PdfPageTextObject].
186    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    /// Returns the effective size of the text when rendered, taking into account both the
192    /// font size specified in this text object as well as any vertical scale factor applied
193    /// to the text object's transformation matrix.
194    ///
195    /// To retrieve only the specified font size, ignoring any vertical scaling, use the
196    /// [PdfPageTextObject::unscaled_font_size()] function.
197    #[inline]
198    pub fn scaled_font_size(&self) -> PdfPoints {
199        PdfPoints::new(self.unscaled_font_size().value * self.get_vertical_scale())
200    }
201
202    /// Returns the font size of the text specified in this [PdfPageTextObject].
203    ///
204    /// Note that the effective size of the text when rendered may differ from the font size
205    /// if a scaling factor has been applied to this text object's transformation matrix.
206    /// To retrieve the effective font size, taking vertical scaling into account, use the
207    /// [PdfPageTextObject::scaled_font_size()] function.
208    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    /// Returns the [PdfFont] used to render the text contained within this [PdfPageTextObject].
222    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    /// Returns the text contained within this [PdfPageTextObject].
232    ///
233    /// Text retrieval in Pdfium is handled by the [PdfPageText] object owned by the [PdfPage]
234    /// containing this [PdfPageTextObject]. If this text object has not been attached to a page
235    /// then text retrieval will be unavailable and an empty string will be returned.
236    ///
237    /// When retrieving the text from many [PdfPageTextObject] objects (for instance, as part of
238    /// a loop or an iterator), it may be faster to open the [PdfPageText] object once and keep
239    /// it open while processing the text objects, like so:
240    ///
241    /// ```
242    /// let text_page = page.text()?; // Opens the text page once.
243    ///
244    /// for object in <some object iterator> {
245    ///     let object_text = text_page.for_object(object)?;
246    /// }
247    /// ```
248    ///
249    /// The [PdfPageText] object will be closed when the binding to it (`text_page` in the example above)
250    /// falls out of scope.
251    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    /// Sets the text contained within this [PdfPageTextObject], replacing any existing text.
293    ///
294    /// A single space will be used if the given text is empty, in order to avoid
295    /// unexpected behaviour from Pdfium when dealing with an empty string.
296    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    /// Sets the text rendering mode for the text contained within this [PdfPageTextObject].
312    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    /// Returns a collection of the characters contained within this [PdfPageTextObject],
324    /// using character retrieval functionality provided by the given [PdfPageText] object.
325    #[inline]
326    pub fn chars(&self, text: &'a PdfPageText<'a>) -> Result<PdfPageTextChars<'a>, PdfiumError> {
327        text.chars_for_object(self)
328    }
329
330    /// Returns `true` if any of the characters contained within this [PdfPageTextObject] have a
331    /// glyph shape that descends below the font baseline.
332    ///
333    /// Character retrieval functionality is provided by the given [PdfPageText] object.
334    #[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    /// Returns the descent of this [PdfPageTextObject]. The descent is the maximum distance below
341    /// the baseline reached by any glyph in any of the characters contained in this text object,
342    /// expressed as a negative points value.
343    ///
344    /// Character retrieval and bounds measurement is provided by the given [PdfPageText] object.
345    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    /// Closes this [PdfPageTextObject], releasing held memory.
429    fn drop(&mut self) {
430        self.drop_impl();
431    }
432}