use crate::coords::{PagePoint, PageRect};
use crate::error::{Error, Result};
use crate::page::PdfPage;
use crate::sys;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PageChar {
pub unicode: Option<char>,
pub code: u32,
pub bounds: PageRect,
pub loose_bounds: PageRect,
pub origin: PagePoint,
}
#[derive(Debug, Clone, Default)]
pub struct PageText {
text: String,
chars: Vec<PageChar>,
}
impl PageText {
pub fn text(&self) -> &str {
&self.text
}
pub fn chars(&self) -> &[PageChar] {
&self.chars
}
pub fn len(&self) -> usize {
self.chars.len()
}
pub fn is_empty(&self) -> bool {
self.chars.is_empty()
}
}
pub const DEFAULT_MAX_TEXT_CHARS: usize = 1_000_000;
impl<'doc> PdfPage<'doc> {
pub fn text(&self) -> Result<PageText> {
self.text_with_limit(DEFAULT_MAX_TEXT_CHARS)
}
pub fn text_with_limit(&self, max_chars: usize) -> Result<PageText> {
let index = self.index();
self.ffi(|b| -> Result<PageText> {
let text_page = unsafe { b.FPDFText_LoadPage(self.handle()) };
if text_page.is_null() {
return Err(Error::TextLoadFailed { index });
}
let result = extract(b, text_page, max_chars);
unsafe { b.FPDFText_ClosePage(text_page) };
match result {
Ok(text) => Ok(text),
Err(ExtractFail::CountFailed) => Err(Error::TextLoadFailed { index }),
Err(ExtractFail::TooLarge { chars }) => Err(Error::TextTooLarge {
chars,
limit: max_chars,
}),
}
})
}
}
enum ExtractFail {
CountFailed,
TooLarge { chars: usize },
}
fn extract(
b: &sys::Bindings,
text_page: sys::FPDF_TEXTPAGE,
max_chars: usize,
) -> std::result::Result<PageText, ExtractFail> {
let count = unsafe { b.FPDFText_CountChars(text_page) };
if count < 0 {
return Err(ExtractFail::CountFailed);
}
if count == 0 {
return Ok(PageText::default());
}
if count as usize > max_chars {
return Err(ExtractFail::TooLarge {
chars: count as usize,
});
}
let mut units = vec![0u16; count as usize + 1];
let written = unsafe { b.FPDFText_GetText(text_page, 0, count, units.as_mut_ptr()) };
let text_units = if written > 0 {
&units[..(written as usize - 1)]
} else {
&[][..]
};
let text = String::from_utf16_lossy(text_units);
let mut chars = Vec::with_capacity((count as usize).min(65_536));
for i in 0..count {
let code = unsafe { b.FPDFText_GetUnicode(text_page, i) };
let (mut left, mut right, mut bottom, mut top) = (0.0f64, 0.0f64, 0.0f64, 0.0f64);
let have_box = unsafe {
b.FPDFText_GetCharBox(text_page, i, &mut left, &mut right, &mut bottom, &mut top)
} != 0;
let bounds = if have_box {
PageRect::new(left, bottom, right, top)
} else {
PageRect::new(0.0, 0.0, 0.0, 0.0)
};
let mut loose = sys::FS_RECTF::default();
let have_loose = unsafe { b.FPDFText_GetLooseCharBox(text_page, i, &mut loose) } != 0;
let loose_bounds = if have_loose {
PageRect::new(
loose.left as f64,
loose.bottom as f64,
loose.right as f64,
loose.top as f64,
)
} else {
bounds
};
let (mut ox, mut oy) = (0.0f64, 0.0f64);
let have_origin = unsafe { b.FPDFText_GetCharOrigin(text_page, i, &mut ox, &mut oy) } != 0;
let origin = if have_origin {
PagePoint::new(ox, oy)
} else {
PagePoint::new(bounds.left, bounds.bottom)
};
chars.push(PageChar {
unicode: char::from_u32(code),
code,
bounds,
loose_bounds,
origin,
});
}
Ok(PageText { text, chars })
}