pdfium_render/pdf/document/page/text/char.rs
1//! Defines the [PdfPageTextChar] struct, exposing functionality related to a single character
2//! in a [PdfPageTextChars] collection.
3
4use crate::bindgen::{FPDF_DOCUMENT, FPDF_PAGE, FPDF_TEXTPAGE, FS_MATRIX, FS_RECTF};
5use crate::bindings::PdfiumLibraryBindings;
6use crate::create_transform_getters;
7use crate::error::{PdfiumError, PdfiumInternalError};
8use crate::pdf::color::PdfColor;
9use crate::pdf::document::page::object::text::PdfPageTextRenderMode;
10use crate::pdf::document::page::text::chars::PdfPageTextCharIndex;
11use crate::pdf::font::{FpdfFontDescriptorFlags, PdfFontWeight};
12use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
13use crate::pdf::points::PdfPoints;
14use crate::pdf::rect::PdfRect;
15use crate::utils::mem::create_byte_buffer;
16use std::convert::TryInto;
17use std::ffi::c_void;
18
19use {crate::pdf::document::page::PdfPageObjectOwnership, crate::pdf::document::page::object::text::PdfPageTextObject};
20
21#[cfg(doc)]
22use crate::pdf::document::page::text::PdfPageTextChars;
23
24/// A single character in a [PdfPageTextChars] collection.
25pub struct PdfPageTextChar<'a> {
26 document_handle: FPDF_DOCUMENT,
27 page_handle: FPDF_PAGE,
28 text_page_handle: FPDF_TEXTPAGE,
29 index: i32,
30 bindings: &'a dyn PdfiumLibraryBindings,
31}
32
33impl<'a> PdfPageTextChar<'a> {
34 #[inline]
35 pub(crate) fn from_pdfium(
36 document_handle: FPDF_DOCUMENT,
37 page_handle: FPDF_PAGE,
38 text_page_handle: FPDF_TEXTPAGE,
39 index: i32,
40 bindings: &'a dyn PdfiumLibraryBindings,
41 ) -> Self {
42 PdfPageTextChar {
43 document_handle,
44 page_handle,
45 text_page_handle,
46 index,
47 bindings,
48 }
49 }
50
51 /// Returns the internal `FPDF_DOCUMENT` handle of the [PdfDocument] containing this [PdfPageTextChar].
52 #[inline]
53 pub(crate) fn document_handle(&self) -> FPDF_DOCUMENT {
54 self.document_handle
55 }
56
57 /// Returns the internal `FPDF_PAGE` handle of the [PdfPage] containing this [PdfPageTextChar].
58 #[inline]
59 pub(crate) fn page_handle(&self) -> FPDF_PAGE {
60 self.page_handle
61 }
62
63 /// Returns the internal `FPDF_TEXTPAGE` handle for this [PdfPageTextChar].
64 #[inline]
65 pub(crate) fn text_page_handle(&self) -> FPDF_TEXTPAGE {
66 self.text_page_handle
67 }
68
69 /// Returns the [PdfiumLibraryBindings] used by this [PdfPageTextChar].
70 #[inline]
71 pub fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
72 self.bindings
73 }
74
75 #[inline]
76 pub fn index(&self) -> PdfPageTextCharIndex {
77 self.index as PdfPageTextCharIndex
78 }
79
80 /// Returns the raw Unicode literal value for this character.
81 ///
82 /// To return Rust's Unicode `char` representation of this Unicode literal, use the
83 /// [PdfPageTextChar::unicode_char] function. To return the string representation of this
84 /// Unicode literal, use the [PdfPageTextChar::unicode_string] function.
85 #[inline]
86 pub fn unicode_value(&self) -> u32 {
87 self.bindings.FPDFText_GetUnicode(self.text_page_handle, self.index)
88 }
89
90 /// Returns Rust's Unicode `char` representation for this character, if available.
91 ///
92 /// To return the raw Unicode literal value for this character,
93 /// use the [PdfPageTextChar::unicode_value] function. To return the string representation of
94 /// this `char`, use the [PdfPageTextChar::unicode_string] function.
95 #[inline]
96 pub fn unicode_char(&self) -> Option<char> {
97 char::from_u32(self.unicode_value())
98 }
99
100 /// Returns a string containing Rust's Unicode `char` representation for this character,
101 /// if available.
102 ///
103 /// To return the raw Unicode literal value for this character,
104 /// use the [PdfPageTextChar::unicode_value] function. To return Rust's Unicode `char`
105 /// representation of this Unicode literal, use the [PdfPageTextChar::unicode_char] function.
106 #[inline]
107 pub fn unicode_string(&self) -> Option<String> {
108 self.unicode_char().map(|char| char.to_string())
109 }
110
111 /// Returns the effective size of this character when rendered, taking into account both the
112 /// font size applied to the character as well as any vertical scale factor applied
113 /// to the character's transformation matrix.
114 ///
115 /// To retrieve only the specified font size, ignoring any vertical scaling, use the
116 /// [PdfPageTextChar::unscaled_font_size] function.
117 #[inline]
118 pub fn scaled_font_size(&self) -> PdfPoints {
119 PdfPoints::new(self.unscaled_font_size().value * self.get_vertical_scale())
120 }
121
122 /// Returns the font size applied to this character.
123 ///
124 /// Note that the effective size of the character when rendered may differ from the font size
125 /// if a scaling factor has been applied to this character's transformation matrix.
126 /// To retrieve the effective font size, taking vertical scaling into account, use the
127 /// [PdfPageTextChar::scaled_font_size] function.
128 #[inline]
129 pub fn unscaled_font_size(&self) -> PdfPoints {
130 PdfPoints::new(self.bindings.FPDFText_GetFontSize(self.text_page_handle, self.index) as f32)
131 }
132
133 /// Returns the font name and raw font descriptor flags for the font applied to this character.
134 fn font(&self) -> (Option<String>, FpdfFontDescriptorFlags) {
135 let mut flags = 0;
136
137 let buffer_length =
138 self.bindings
139 .FPDFText_GetFontInfo(self.text_page_handle, self.index, std::ptr::null_mut(), 0, &mut flags);
140
141 if buffer_length == 0 {
142 return (None, FpdfFontDescriptorFlags::from_bits_truncate(flags as u32));
143 }
144
145 let mut buffer = create_byte_buffer(buffer_length as usize);
146
147 let result = self.bindings.FPDFText_GetFontInfo(
148 self.text_page_handle,
149 self.index,
150 buffer.as_mut_ptr() as *mut c_void,
151 buffer_length,
152 &mut flags,
153 );
154
155 assert_eq!(result, buffer_length);
156
157 (
158 String::from_utf8(buffer)
159 .map(|str| str.trim_end_matches(char::from(0)).to_owned())
160 .ok(),
161 FpdfFontDescriptorFlags::from_bits_truncate(flags as u32),
162 )
163 }
164
165 /// Returns the name of the font applied to this character.
166 #[inline]
167 pub fn font_name(&self) -> String {
168 self.font().0.unwrap_or_default()
169 }
170
171 /// Returns the weight of the font applied to this character.
172 ///
173 /// Pdfium may not reliably return the correct value of this property for built-in fonts.
174 #[inline]
175 pub fn font_weight(&self) -> Option<PdfFontWeight> {
176 PdfFontWeight::from_pdfium(self.bindings.FPDFText_GetFontWeight(self.text_page_handle, self.index))
177 }
178
179 /// Returns the raw font descriptor bitflags for the font applied to this character.
180 #[inline]
181 fn font_flags_bits(&self) -> FpdfFontDescriptorFlags {
182 self.font().1
183 }
184
185 /// Returns `true` if all the glyphs in the font applied to this character have the same width.
186 ///
187 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
188 pub fn font_is_fixed_pitch(&self) -> bool {
189 self.font_flags_bits()
190 .contains(FpdfFontDescriptorFlags::FIXED_PITCH_BIT_1)
191 }
192
193 /// Returns `true` if the glyphs in the font applied to this character have variable widths.
194 ///
195 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
196 #[inline]
197 pub fn font_is_proportional_pitch(&self) -> bool {
198 !self.font_is_fixed_pitch()
199 }
200
201 /// Returns `true` if one or more glyphs in the font applied to this character have serifs -
202 /// short strokes drawn at an angle on the top or bottom of glyph stems to decorate the glyphs.
203 /// For example, Times New Roman is a serif font.
204 ///
205 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
206 pub fn font_is_serif(&self) -> bool {
207 self.font_flags_bits().contains(FpdfFontDescriptorFlags::SERIF_BIT_2)
208 }
209
210 /// Returns `true` if no glyphs in the font applied to this character have serifs -
211 /// short strokes drawn at an angle on the top or bottom of glyph stems to decorate the glyphs.
212 /// For example, Helvetica is a sans-serif font.
213 ///
214 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
215 #[inline]
216 pub fn font_is_sans_serif(&self) -> bool {
217 !self.font_is_serif()
218 }
219
220 /// Returns `true` if the font applied to this character contains glyphs outside the
221 /// Adobe standard Latin character set.
222 ///
223 /// This classification of non-symbolic and symbolic fonts is peculiar to PDF. A font may
224 /// contain additional characters that are used in Latin writing systems but are outside the
225 /// Adobe standard Latin character set; PDF considers such a font to be symbolic.
226 ///
227 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
228 pub fn font_is_symbolic(&self) -> bool {
229 self.font_flags_bits().contains(FpdfFontDescriptorFlags::SYMBOLIC_BIT_3)
230 }
231
232 /// Returns `true` if the font applied to this character does not contain glyphs outside the
233 /// Adobe standard Latin character set.
234 ///
235 /// This classification of non-symbolic and symbolic fonts is peculiar to PDF. A font may
236 /// contain additional characters that are used in Latin writing systems but are outside the
237 /// Adobe standard Latin character set; PDF considers such a font to be symbolic.
238 ///
239 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
240 pub fn font_is_non_symbolic(&self) -> bool {
241 self.font_flags_bits()
242 .contains(FpdfFontDescriptorFlags::NON_SYMBOLIC_BIT_6)
243 }
244
245 /// Returns `true` if the glyphs in the font applied to this character are designed to resemble
246 /// cursive handwriting.
247 ///
248 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
249 pub fn font_is_cursive(&self) -> bool {
250 self.font_flags_bits().contains(FpdfFontDescriptorFlags::SCRIPT_BIT_4)
251 }
252
253 /// Returns `true` if the glyphs in the font applied to this character include dominant
254 /// vertical strokes that are slanted.
255 ///
256 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
257 pub fn font_is_italic(&self) -> bool {
258 self.font_flags_bits().contains(FpdfFontDescriptorFlags::ITALIC_BIT_7)
259 }
260
261 /// Returns `true` if the font applied to this character contains no lowercase letters by design.
262 ///
263 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
264 pub fn font_is_all_caps(&self) -> bool {
265 self.font_flags_bits().contains(FpdfFontDescriptorFlags::ALL_CAP_BIT_17)
266 }
267
268 /// Returns `true` if the lowercase letters in the font applied to this character have the
269 /// same shapes as the corresponding uppercase letters but are sized proportionally
270 /// so they have the same size and stroke weight as lowercase glyphs in the same typeface family.
271 ///
272 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
273 pub fn font_is_small_caps(&self) -> bool {
274 self.font_flags_bits()
275 .contains(FpdfFontDescriptorFlags::SMALL_CAP_BIT_18)
276 }
277
278 /// Returns `true` if bold glyphs in the font applied to this character are painted with
279 /// extra pixels at very small font sizes.
280 ///
281 /// Typically when glyphs are painted at small sizes on low-resolution devices, individual strokes
282 /// of bold glyphs may appear only one pixel wide. Because this is the minimum width of a pixel
283 /// based device, individual strokes of non-bold glyphs may also appear as one pixel wide
284 /// and therefore cannot be distinguished from bold glyphs. If this flag is set, individual
285 /// strokes of bold glyphs may be thickened at small font sizes.
286 ///
287 /// Pdfium may not reliably return the correct value of this flag for built-in fonts.
288 pub fn font_is_bold_reenforced(&self) -> bool {
289 self.font_flags_bits()
290 .contains(FpdfFontDescriptorFlags::FORCE_BOLD_BIT_19)
291 }
292
293 /// Returns combined font style information from a single FFI call.
294 ///
295 /// This is more efficient than calling `font_name()`, `font_is_bold_reenforced()`,
296 /// and `font_is_italic()` separately, as those each independently invoke the
297 /// underlying `FPDFText_GetFontInfo()` FFI function.
298 ///
299 /// Returns a tuple of (font_name, is_bold, is_italic).
300 pub fn font_info(&self) -> (String, bool, bool) {
301 let (name, flags) = self.font();
302 let name = name.unwrap_or_default();
303 let is_bold =
304 flags.contains(FpdfFontDescriptorFlags::FORCE_BOLD_BIT_19) || name.to_ascii_lowercase().contains("bold");
305 let is_italic = flags.contains(FpdfFontDescriptorFlags::ITALIC_BIT_7);
306 (name, is_bold, is_italic)
307 }
308
309 /// Returns the page text object that contains this character.
310 pub fn text_object(&self) -> Result<PdfPageTextObject<'_>, PdfiumError> {
311 let object_handle = self
312 .bindings()
313 .FPDFText_GetTextObject(self.text_page_handle(), self.index);
314
315 if object_handle.is_null() {
316 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
317 } else {
318 Ok(PdfPageTextObject::from_pdfium(
319 object_handle,
320 PdfPageObjectOwnership::owned_by_page(self.document_handle(), self.page_handle()),
321 self.bindings(),
322 ))
323 }
324 }
325
326 /// Returns the text rendering mode for this character.
327 pub fn render_mode(&self) -> Result<PdfPageTextRenderMode, PdfiumError> {
328 self.text_object().map(|text_object| text_object.render_mode())
329 }
330
331 /// Returns the fill color applied to this character.
332 pub fn fill_color(&self) -> Result<PdfColor, PdfiumError> {
333 let mut r = 0;
334
335 let mut g = 0;
336
337 let mut b = 0;
338
339 let mut a = 0;
340
341 if self.bindings.is_true(self.bindings.FPDFText_GetFillColor(
342 self.text_page_handle,
343 self.index,
344 &mut r,
345 &mut g,
346 &mut b,
347 &mut a,
348 )) {
349 Ok(PdfColor::new(
350 r.try_into()
351 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
352 g.try_into()
353 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
354 b.try_into()
355 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
356 a.try_into()
357 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
358 ))
359 } else {
360 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
361 }
362 }
363
364 /// Returns the stroke color applied to this character.
365 pub fn stroke_color(&self) -> Result<PdfColor, PdfiumError> {
366 let mut r = 0;
367
368 let mut g = 0;
369
370 let mut b = 0;
371
372 let mut a = 0;
373
374 if self.bindings().is_true(self.bindings.FPDFText_GetStrokeColor(
375 self.text_page_handle(),
376 self.index,
377 &mut r,
378 &mut g,
379 &mut b,
380 &mut a,
381 )) {
382 Ok(PdfColor::new(
383 r.try_into()
384 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
385 g.try_into()
386 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
387 b.try_into()
388 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
389 a.try_into()
390 .map_err(PdfiumError::UnableToConvertPdfiumColorValueToRustu8)?,
391 ))
392 } else {
393 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
394 }
395 }
396
397 /// Returns the rotation angle of this character, expressed in degrees.
398 #[inline]
399 pub fn angle_degrees(&self) -> Result<f32, PdfiumError> {
400 self.angle_radians().map(|result| result.to_degrees())
401 }
402
403 /// Returns the rotation angle of this character, expressed in radians.
404 #[inline]
405 pub fn angle_radians(&self) -> Result<f32, PdfiumError> {
406 let result = self.bindings.FPDFText_GetCharAngle(self.text_page_handle, self.index);
407
408 if result == -1.0 {
409 Err(PdfiumError::PdfiumFunctionReturnValueIndicatedFailure)
410 } else {
411 Ok(result)
412 }
413 }
414
415 /// Returns a precise bounding box for this character, taking the character's specific
416 /// shape into account.
417 ///
418 /// To return a loose bounding box that contains the entire glyph bounds, use the
419 /// [PdfPageTextChar::loose_bounds] function.
420 pub fn tight_bounds(&self) -> Result<PdfRect, PdfiumError> {
421 let mut left = 0.0;
422
423 let mut bottom = 0.0;
424
425 let mut right = 0.0;
426
427 let mut top = 0.0;
428
429 let result = self.bindings().FPDFText_GetCharBox(
430 self.text_page_handle(),
431 self.index,
432 &mut left,
433 &mut right,
434 &mut bottom,
435 &mut top,
436 );
437
438 PdfRect::from_pdfium_as_result(
439 result,
440 FS_RECTF {
441 left: left as f32,
442 top: top as f32,
443 right: right as f32,
444 bottom: bottom as f32,
445 },
446 self.bindings(),
447 )
448 }
449
450 /// Returns a loose bounding box for this character, containing the entire glyph bounds.
451 ///
452 /// To return a tight bounding box that takes this character's specific shape into
453 /// account, use the [PdfPageTextChar::tight_bounds] function.
454 pub fn loose_bounds(&self) -> Result<PdfRect, PdfiumError> {
455 let mut bounds = FS_RECTF {
456 left: 0.0,
457 top: 0.0,
458 right: 0.0,
459 bottom: 0.0,
460 };
461
462 let result = self
463 .bindings
464 .FPDFText_GetLooseCharBox(self.text_page_handle(), self.index, &mut bounds);
465
466 PdfRect::from_pdfium_as_result(result, bounds, self.bindings())
467 }
468
469 /// Returns the current raw transformation matrix for this character.
470 fn get_matrix_impl(&self) -> Result<PdfMatrix, PdfiumError> {
471 let mut matrix = FS_MATRIX {
472 a: 0.0,
473 b: 0.0,
474 c: 0.0,
475 d: 0.0,
476 e: 0.0,
477 f: 0.0,
478 };
479
480 if self.bindings().is_true(
481 self.bindings()
482 .FPDFText_GetMatrix(self.text_page_handle(), self.index, &mut matrix),
483 ) {
484 Ok(PdfMatrix::from_pdfium(matrix))
485 } else {
486 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
487 }
488 }
489
490 create_transform_getters!(
491 "this [PdfPageTextChar]",
492 "this [PdfPageTextChar].",
493 "this [PdfPageTextChar],"
494 );
495
496 /// Returns the origin x and y positions of this character relative to its containing page.
497 pub fn origin(&self) -> Result<(PdfPoints, PdfPoints), PdfiumError> {
498 let mut x = 0.0;
499
500 let mut y = 0.0;
501
502 if self.bindings().is_true(self.bindings().FPDFText_GetCharOrigin(
503 self.text_page_handle(),
504 self.index,
505 &mut x,
506 &mut y,
507 )) {
508 Ok((PdfPoints::new(x as f32), PdfPoints::new(y as f32)))
509 } else {
510 Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown))
511 }
512 }
513
514 /// Returns the origin x position of this character relative to its containing page.
515 #[inline]
516 pub fn origin_x(&self) -> Result<PdfPoints, PdfiumError> {
517 self.origin().map(|result| result.0)
518 }
519
520 /// Returns the origin y position of this character relative to its containing page.
521 #[inline]
522 pub fn origin_y(&self) -> Result<PdfPoints, PdfiumError> {
523 self.origin().map(|result| result.1)
524 }
525
526 /// Returns `true` if the glyph shape of this character descends below the font baseline.
527 #[inline]
528 pub fn has_descender(&self) -> bool {
529 self.tight_bounds().map(|bounds| bounds.bottom().value).unwrap_or(0.0)
530 < self.loose_bounds().map(|bounds| bounds.bottom().value).unwrap_or(0.0)
531 }
532
533 /// Returns `true` if this character was generated by Pdfium. This can be the case for
534 /// certain spacing, breaking, and justification-related characters.
535 #[inline]
536 pub fn is_generated(&self) -> Result<bool, PdfiumError> {
537 match self
538 .bindings()
539 .FPDFText_IsGenerated(self.text_page_handle(), self.index)
540 {
541 1 => Ok(true),
542 0 => Ok(false),
543 _ => Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown)),
544 }
545 }
546
547 /// Returns `true` if this character is recognized as a hyphen by Pdfium.
548 #[inline]
549 pub fn is_hyphen(&self) -> Result<bool, PdfiumError> {
550 match self.bindings().FPDFText_IsHyphen(self.text_page_handle(), self.index) {
551 1 => Ok(true),
552 0 => Ok(false),
553 _ => Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown)),
554 }
555 }
556
557 /// Returns `true` if this character has an invalid unicode mapping in the PDF font.
558 ///
559 /// This indicates the font's ToUnicode CMap is broken or missing for this glyph,
560 /// meaning the extracted text for this character is unreliable (tofu/garbage).
561 #[inline]
562 pub fn has_unicode_map_error(&self) -> Result<bool, PdfiumError> {
563 match self
564 .bindings()
565 .FPDFText_HasUnicodeMapError(self.text_page_handle(), self.index)
566 {
567 1 => Ok(true),
568 0 => Ok(false),
569 _ => Err(PdfiumError::PdfiumLibraryInternalError(PdfiumInternalError::Unknown)),
570 }
571 }
572}