Skip to main content

pdfium_render/pdf/font/
glyphs.rs

1//! Defines the [PdfFontGlyphs] struct, a collection of all the [PdfFontGlyph] objects in a
2//! [PdfFont].
3
4use crate::bindgen::FPDF_FONT;
5use crate::error::PdfiumError;
6use crate::pdf::font::glyph::PdfFontGlyph;
7use crate::pdfium::PdfiumLibraryBindingsAccessor;
8use std::cell::Cell;
9use std::marker::PhantomData;
10use std::ops::{Range, RangeInclusive};
11use std::os::raw::c_uint;
12
13#[cfg(doc)]
14use crate::pdf::font::PdfFont;
15
16/// The zero-based index of a single [PdfFontGlyph] inside its containing [PdfFontGlyphs] collection.
17pub type PdfFontGlyphIndex = u16;
18
19/// A collection of all the [PdfFontGlyph] objects in a [PdfFont].
20pub struct PdfFontGlyphs<'a> {
21    handle: FPDF_FONT,
22    len: Cell<Option<PdfFontGlyphIndex>>,
23    lifetime: PhantomData<&'a FPDF_FONT>,
24}
25
26impl<'a> PdfFontGlyphs<'a> {
27    #[inline]
28    pub(crate) fn from_pdfium(handle: FPDF_FONT) -> Self {
29        Self {
30            handle,
31            len: Cell::new(None),
32            lifetime: PhantomData,
33        }
34    }
35
36    /// Initializes the length of this [PdfFontGlyphs] collection.
37    ///
38    /// We avoid doing this on instantiation as it may not take constant time.
39    /// We only incur the cost of initializing this value if the user actually requests the
40    /// [PdfFontGlyphs] collection by calling the [PdfFont::glyphs] function.
41    #[inline]
42    pub(crate) fn initialize_len(&self) {
43        if self.len.get().is_none() {
44            // Pdfium does not provide a function that returns the number of glyphs in a font.
45            // We use a binary search algorithm to determine the number of glyphs as efficiently
46            // as possible.
47
48            let len = self
49                .find_maximum_valid_glyph_index(u16::MIN, u16::MAX)
50                .unwrap_or(0);
51
52            self.len.replace(Some(len));
53        }
54    }
55
56    /// Returns the highest index position of an extant glyph within the given index range.
57    fn find_maximum_valid_glyph_index(&self, min: u16, max: u16) -> Option<u16> {
58        // Exit immediately if the maximum valid glyph index lies outside the given index boundaries.
59
60        if !(unsafe {
61            self.bindings()
62                .FPDFFont_GetGlyphPath(self.handle, max as c_uint, 1.0)
63        })
64        .is_null()
65        {
66            return Some(max);
67        }
68
69        if (unsafe {
70            self.bindings()
71                .FPDFFont_GetGlyphPath(self.handle, min as c_uint, 1.0)
72        })
73        .is_null()
74        {
75            return None;
76        }
77
78        // Partition the given index boundaries and recursively search.
79
80        let mid = min + (max - min) / 2;
81
82        if (unsafe {
83            self.bindings()
84                .FPDFFont_GetGlyphPath(self.handle, mid as c_uint, 1.0)
85        })
86        .is_null()
87        {
88            // The maximum valid glyph index must lie before the partition mid point.
89
90            if mid > min {
91                self.find_maximum_valid_glyph_index(min, mid - 1)
92            } else {
93                None
94            }
95        } else {
96            // The maximum valid glyph index must lie after the partition mid point.
97
98            if mid < max {
99                self.find_maximum_valid_glyph_index(mid + 1, max)
100            } else {
101                Some(max)
102            }
103        }
104    }
105
106    /// Returns the number of glyphs in this [PdfFontGlyphs] collection.
107    #[inline]
108    pub fn len(&self) -> PdfFontGlyphIndex {
109        self.len.get().unwrap_or(0)
110    }
111
112    /// Returns `true` if this [PdfFontGlyphs] collection is empty.
113    #[inline]
114    pub fn is_empty(&self) -> bool {
115        self.len() == 0
116    }
117
118    /// Returns a Range from `0..(number of glyphs)` for this [PdfFontGlyphs] collection.
119    #[inline]
120    pub fn as_range(&self) -> Range<PdfFontGlyphIndex> {
121        0..self.len()
122    }
123
124    /// Returns an inclusive Range from `0..=(number of glyphs - 1)` for this [PdfFontGlyphs] collection.
125    #[inline]
126    pub fn as_range_inclusive(&self) -> RangeInclusive<PdfFontGlyphIndex> {
127        if self.is_empty() {
128            0..=0
129        } else {
130            0..=(self.len() - 1)
131        }
132    }
133
134    /// Returns a single [PdfFontGlyph] from this [PdfFontGlyphs] collection.
135    pub fn get(&self, index: PdfFontGlyphIndex) -> Result<PdfFontGlyph<'a>, PdfiumError> {
136        if index >= self.len() {
137            return Err(PdfiumError::FontGlyphIndexOutOfBounds);
138        }
139
140        Ok(PdfFontGlyph::from_pdfium(self.handle, index))
141    }
142
143    /// Returns an iterator over all the glyphs in this [PdfFontGlyphs] collection.
144    #[inline]
145    pub fn iter(&self) -> PdfFontGlyphsIterator<'_> {
146        PdfFontGlyphsIterator::new(self)
147    }
148}
149
150impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfFontGlyphs<'a> {}
151
152#[cfg(feature = "thread_safe")]
153unsafe impl<'a> Send for PdfFontGlyphs<'a> {}
154
155#[cfg(feature = "thread_safe")]
156unsafe impl<'a> Sync for PdfFontGlyphs<'a> {}
157
158/// An iterator over all the [PdfFontGlyph] objects in a [PdfFontGlyphs] collection.
159pub struct PdfFontGlyphsIterator<'a> {
160    glyphs: &'a PdfFontGlyphs<'a>,
161    next_index: PdfFontGlyphIndex,
162}
163
164impl<'a> PdfFontGlyphsIterator<'a> {
165    #[inline]
166    pub(crate) fn new(glyphs: &'a PdfFontGlyphs<'a>) -> Self {
167        PdfFontGlyphsIterator {
168            glyphs,
169            next_index: 0,
170        }
171    }
172}
173
174impl<'a> Iterator for PdfFontGlyphsIterator<'a> {
175    type Item = PdfFontGlyph<'a>;
176
177    fn next(&mut self) -> Option<Self::Item> {
178        let next = self.glyphs.get(self.next_index);
179
180        self.next_index += 1;
181
182        next.ok()
183    }
184}