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,
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/// The text rendering modes supported by the PDF standard, as listed in table 5.3
58/// on page 402 in the PDF Reference manual version 1.7.
59#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
60pub enum PdfPageTextRenderMode {
61    /// The text render mode is not recognized by Pdfium.
62    Unknown = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN as isize,
63
64    /// The text will be filled, but not stroked.
65    FilledUnstroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL as isize,
66
67    /// The text will be stroked, but not filled.
68    StrokedUnfilled = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE as isize,
69
70    /// The text will be filled, then stroked.
71    FilledThenStroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE as isize,
72
73    /// The text will be neither filled nor stroked. It will still take up size in the layout, however.
74    Invisible = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE as isize,
75
76    /// The text will be filled and added to the path for clipping.
77    FilledUnstrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP as isize,
78
79    /// The text will be stroked and added to the path for clipping.
80    StrokedUnfilledClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP as isize,
81
82    /// The text will be filled, then stroked, and added to the path for clipping.
83    FilledThenStrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP as isize,
84
85    /// The text will be neither filled nor stroked, only added to the path for clipping.
86    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    // The as_pdfium() function is not currently used, but we expect it to be in future
125    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
152/// A single [PdfPageObject] of type [PdfPageObjectType::Text]. The page object defines a single
153/// piece of formatted text.
154///
155/// Page objects can be created either attached to a [PdfPage] (in which case the page object's
156/// memory is owned by the containing page) or detached from any page (in which case the page
157/// object's memory is owned by the object). Page objects are not rendered until they are
158/// attached to a page; page objects that are never attached to a page will be lost when they
159/// fall out of scope.
160///
161/// The simplest way to create a page text object that is immediately attached to a page
162/// is to call the [PdfPageObjectsCommon::create_text_object()] function.
163///
164/// Creating a detached page text object offers more scope for customization, but you must
165/// add the object to a containing [PdfPage] manually. To create a detached page text object,
166/// use the [PdfPageTextObject::new()] function. The detached page text object can later
167/// be attached to a page by using the [PdfPageObjectsCommon::add_text_object()] function.
168pub 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    /// Creates a new [PdfPageTextObject] from the given arguments. The returned page object
188    /// will not be rendered until it is added to a [PdfPage] using the
189    /// [PdfPageObjectsCommon::add_text_object()] function.
190    ///
191    /// A single space will be used if the given text is empty, in order to avoid
192    /// unexpected behaviour from Pdfium when dealing with empty strings.
193    // Specifically, `FPDFPageObj_SetText()` will crash if we try to apply an empty string to a
194    // text object, and `FPDFText_LoadPage()` will crash if any text object on the page contains
195    // an empty string (so it isn't enough to avoid calling `FPDFPageObj_SetText()` for an empty
196    // text object, we _have_ to set a non-empty string to avoid segfaults).
197    #[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    // Take raw `FPDF_DOCUMENT` and `FPDF_FONT` handles to avoid cascading lifetime problems
214    // associated with borrowing `PdfDocument<'a>` and/or `PdfFont<'a>`.
215    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    /// Returns the text rendering mode for the text contained within this [PdfPageTextObject].
242    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    /// Returns `true` if the text rendering mode for the text contained within this
251    /// [PdfPageTextObject] is set to any value other than [PdfPageTextRenderMode::Invisible]
252    /// or [PdfPageTextRenderMode::InvisibleClipping].
253    #[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    /// Returns the effective size of the text when rendered, taking into account both the
262    /// font size specified in this text object as well as any vertical scale factor applied
263    /// to the text object's transformation matrix.
264    ///
265    /// To retrieve only the specified font size, ignoring any vertical scaling, use the
266    /// [PdfPageTextObject::unscaled_font_size()] function.
267    #[inline]
268    pub fn scaled_font_size(&self) -> PdfPoints {
269        PdfPoints::new(self.unscaled_font_size().value * self.get_vertical_scale())
270    }
271
272    /// Returns the font size of the text specified in this [PdfPageTextObject].
273    ///
274    /// Note that the effective size of the text when rendered may differ from the font size
275    /// if a scaling factor has been applied to this text object's transformation matrix.
276    /// To retrieve the effective font size, taking vertical scaling into account, use the
277    /// [PdfPageTextObject::scaled_font_size()] function.
278    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    /// Sets the font size of the text specified in this [PdfPageTextObject].
297    ///
298    /// Note that the effective size of the text when rendered may differ from the font size
299    /// if a scaling factor has been applied to this text object's transformation matrix.
300    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    /// Returns the [PdfFont] used to render the text contained within this [PdfPageTextObject].
312    #[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    /// Returns the text contained within this [PdfPageTextObject].
322    ///
323    /// Text retrieval in Pdfium is handled by the [PdfPageText] object owned by the [PdfPage]
324    /// containing this [PdfPageTextObject]. If this text object has not been attached to a page
325    /// then text retrieval will be unavailable and an empty string will be returned.
326    ///
327    /// When retrieving the text from many [PdfPageTextObject] objects (for instance, as part of
328    /// a loop or an iterator), it may be faster to open the [PdfPageText] object once and keep
329    /// it open while processing the text objects, like so:
330    ///
331    /// ```
332    /// let text_page = page.text()?; // Opens the text page once.
333    ///
334    /// for object in <some object iterator> {
335    ///     let object_text = text_page.for_object(object)?;
336    /// }
337    /// ```
338    ///
339    /// The [PdfPageText] object will be closed when the binding to it (`text_page` in the example above)
340    /// falls out of scope.
341    pub fn text(&self) -> String {
342        // Retrieving the text from Pdfium is a two-step operation. First, we call
343        // FPDFTextObj_GetText() with a null buffer; this will retrieve the length of
344        // the text in bytes. If the length is zero, then there is no text associated
345        // with the page object.
346
347        // If the length is non-zero, then we reserve a byte buffer of the given
348        // length and call FPDFTextObj_GetText() again with a pointer to the buffer;
349        // this will write the text to the buffer in UTF16-LE format.
350
351        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                    // There is no text.
372
373                    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                // The PdfPage containing this page object does not have an associated
396                // FPDF_TEXTPAGE object.
397
398                String::new()
399            }
400        } else {
401            // This page object is not contained by a PdfPage.
402
403            String::new()
404        }
405    }
406
407    /// Sets the text contained within this [PdfPageTextObject], replacing any existing text.
408    ///
409    /// A single space will be used if the given text is empty, in order to avoid
410    /// unexpected behaviour from Pdfium when dealing with an empty string.
411    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    /// Sets the text rendering mode for the text contained within this [PdfPageTextObject].
429    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    /// Returns a collection of the characters contained within this [PdfPageTextObject],
460    /// using character retrieval functionality provided by the given [PdfPageText] object.
461    #[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    /// Returns `true` if any of the characters contained within this [PdfPageTextObject] have a
481    /// glyph shape that descends below the font baseline.
482    ///
483    /// Character retrieval functionality is provided by the given [PdfPageText] object.
484    #[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    /// Returns the descent of this [PdfPageTextObject]. The descent is the maximum distance below
505    /// the baseline reached by any glyph in any of the characters contained in this text object,
506    /// expressed as a negative points value.
507    ///
508    /// Character retrieval and bounds measurement is provided by the given [PdfPageText] object.
509    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    // The transform_impl() function required by the create_transform_setters!() macro
534    // is provided by the PdfPageObjectPrivate trait.
535
536    create_transform_getters!(
537        "this [PdfPageTextObject]",
538        "this [PdfPageTextObject].",
539        "this [PdfPageTextObject],"
540    );
541
542    // The get_matrix_impl() function required by the create_transform_getters!() macro
543    // is provided by the PdfPageObjectPrivate trait.
544}
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    /// Closes this [PdfPageTextObject], releasing held memory.
565    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> {}