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 once_cell::sync::OnceCell;
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: OnceCell<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: OnceCell::new(),
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        self.len.get_or_init(|| {
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            self.find_maximum_valid_glyph_index(u16::MIN, u16::MAX)
49                .unwrap_or(0)
50        });
51    }
52
53    /// Returns the highest index position of an extant glyph within the given index range.
54    fn find_maximum_valid_glyph_index(&self, min: u16, max: u16) -> Option<u16> {
55        // Exit immediately if the maximum valid glyph index lies outside the given index boundaries.
56
57        if !(unsafe {
58            self.bindings()
59                .FPDFFont_GetGlyphPath(self.handle, max as c_uint, 1.0)
60        })
61        .is_null()
62        {
63            return Some(max);
64        }
65
66        if (unsafe {
67            self.bindings()
68                .FPDFFont_GetGlyphPath(self.handle, min as c_uint, 1.0)
69        })
70        .is_null()
71        {
72            return None;
73        }
74
75        // Partition the given index boundaries and recursively search.
76
77        let mid = min + (max - min) / 2;
78
79        if (unsafe {
80            self.bindings()
81                .FPDFFont_GetGlyphPath(self.handle, mid as c_uint, 1.0)
82        })
83        .is_null()
84        {
85            // The maximum valid glyph index must lie before the partition mid point.
86
87            if mid > min {
88                self.find_maximum_valid_glyph_index(min, mid - 1)
89            } else {
90                None
91            }
92        } else {
93            // The maximum valid glyph index must lie after the partition mid point.
94
95            if mid < max {
96                self.find_maximum_valid_glyph_index(mid + 1, max)
97            } else {
98                Some(max)
99            }
100        }
101    }
102
103    /// Returns the number of glyphs in this [PdfFontGlyphs] collection.
104    #[inline]
105    pub fn len(&self) -> PdfFontGlyphIndex {
106        self.len.get().copied().unwrap_or(0)
107    }
108
109    /// Returns `true` if this [PdfFontGlyphs] collection is empty.
110    #[inline]
111    pub fn is_empty(&self) -> bool {
112        self.len() == 0
113    }
114
115    /// Returns a Range from `0..(number of glyphs)` for this [PdfFontGlyphs] collection.
116    #[inline]
117    pub fn as_range(&self) -> Range<PdfFontGlyphIndex> {
118        0..self.len()
119    }
120
121    /// Returns an inclusive Range from `0..=(number of glyphs - 1)` for this [PdfFontGlyphs] collection.
122    #[inline]
123    pub fn as_range_inclusive(&self) -> RangeInclusive<PdfFontGlyphIndex> {
124        if self.is_empty() {
125            0..=0
126        } else {
127            0..=(self.len() - 1)
128        }
129    }
130
131    /// Returns a single [PdfFontGlyph] from this [PdfFontGlyphs] collection.
132    pub fn get(&self, index: PdfFontGlyphIndex) -> Result<PdfFontGlyph<'a>, PdfiumError> {
133        if index >= self.len() {
134            return Err(PdfiumError::FontGlyphIndexOutOfBounds);
135        }
136
137        Ok(PdfFontGlyph::from_pdfium(self.handle, index))
138    }
139
140    /// Returns an iterator over all the glyphs in this [PdfFontGlyphs] collection.
141    #[inline]
142    pub fn iter(&self) -> PdfFontGlyphsIterator<'_> {
143        PdfFontGlyphsIterator::new(self)
144    }
145}
146
147impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfFontGlyphs<'a> {}
148
149#[cfg(feature = "thread_safe")]
150unsafe impl<'a> Send for PdfFontGlyphs<'a> {}
151
152#[cfg(feature = "thread_safe")]
153unsafe impl<'a> Sync for PdfFontGlyphs<'a> {}
154
155/// An iterator over all the [PdfFontGlyph] objects in a [PdfFontGlyphs] collection.
156pub struct PdfFontGlyphsIterator<'a> {
157    glyphs: &'a PdfFontGlyphs<'a>,
158    next_index: PdfFontGlyphIndex,
159}
160
161impl<'a> PdfFontGlyphsIterator<'a> {
162    #[inline]
163    pub(crate) fn new(glyphs: &'a PdfFontGlyphs<'a>) -> Self {
164        PdfFontGlyphsIterator {
165            glyphs,
166            next_index: 0,
167        }
168    }
169}
170
171impl<'a> Iterator for PdfFontGlyphsIterator<'a> {
172    type Item = PdfFontGlyph<'a>;
173
174    fn next(&mut self) -> Option<Self::Item> {
175        let next = self.glyphs.get(self.next_index);
176
177        self.next_index += 1;
178
179        next.ok()
180    }
181}