use crate::bindgen::{
FPDF_DOCUMENT, FPDF_FONT, FPDF_PAGEOBJECT, FPDF_TEXT_RENDERMODE,
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP, FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL,
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP,
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE,
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP,
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE,
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE,
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP,
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN, FPDF_WCHAR,
};
use crate::bindings::PdfiumLibraryBindings;
use crate::error::{PdfiumError, PdfiumInternalError};
use crate::pdf::document::fonts::ToPdfFontToken;
use crate::pdf::document::page::object::private::internal::PdfPageObjectPrivate;
use crate::pdf::document::page::object::PdfPageObjectOwnership;
use crate::pdf::document::PdfDocument;
use crate::pdf::font::PdfFont;
use crate::pdf::matrix::{PdfMatrix, PdfMatrixValue};
use crate::pdf::points::PdfPoints;
use crate::pdfium::PdfiumLibraryBindingsAccessor;
use crate::utils::mem::create_byte_buffer;
use crate::utils::utf16le::get_string_from_pdfium_utf16le_bytes;
use crate::{create_transform_getters, create_transform_setters};
use std::marker::PhantomData;
#[cfg(any(
feature = "pdfium_future",
feature = "pdfium_7543",
feature = "pdfium_7350",
feature = "pdfium_7215",
feature = "pdfium_7123",
feature = "pdfium_6996",
feature = "pdfium_6721",
feature = "pdfium_6666",
feature = "pdfium_6611",
))]
use {
crate::pdf::document::page::text::chars::PdfPageTextChars,
crate::pdf::document::page::text::PdfPageText,
};
#[cfg(doc)]
use {
crate::pdf::document::page::object::PdfPageObject,
crate::pdf::document::page::object::PdfPageObjectType,
crate::pdf::document::page::objects::common::PdfPageObjectsCommon,
crate::pdf::document::page::PdfPage,
};
#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
pub enum PdfPageTextRenderMode {
Unknown = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN as isize,
FilledUnstroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL as isize,
StrokedUnfilled = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE as isize,
FilledThenStroked = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE as isize,
Invisible = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE as isize,
FilledUnstrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP as isize,
StrokedUnfilledClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP as isize,
FilledThenStrokedClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP as isize,
InvisibleClipping = FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP as isize,
}
impl PdfPageTextRenderMode {
#[inline]
pub(crate) fn from_pdfium(value: i32) -> Result<PdfPageTextRenderMode, PdfiumError> {
match value {
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN => Ok(PdfPageTextRenderMode::Unknown),
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL => {
Ok(PdfPageTextRenderMode::FilledUnstroked)
}
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE => {
Ok(PdfPageTextRenderMode::StrokedUnfilled)
}
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE => {
Ok(PdfPageTextRenderMode::FilledThenStroked)
}
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE => {
Ok(PdfPageTextRenderMode::Invisible)
}
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP => {
Ok(PdfPageTextRenderMode::FilledUnstrokedClipping)
}
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP => {
Ok(PdfPageTextRenderMode::StrokedUnfilledClipping)
}
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP => {
Ok(PdfPageTextRenderMode::FilledThenStrokedClipping)
}
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP => {
Ok(PdfPageTextRenderMode::InvisibleClipping)
}
_ => Err(PdfiumError::UnknownPdfPageTextRenderMode),
}
}
#[inline]
#[allow(dead_code)]
pub(crate) fn as_pdfium(&self) -> FPDF_TEXT_RENDERMODE {
match self {
PdfPageTextRenderMode::Unknown => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_UNKNOWN,
PdfPageTextRenderMode::FilledUnstroked => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL,
PdfPageTextRenderMode::StrokedUnfilled => {
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE
}
PdfPageTextRenderMode::FilledThenStroked => {
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE
}
PdfPageTextRenderMode::Invisible => FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_INVISIBLE,
PdfPageTextRenderMode::FilledUnstrokedClipping => {
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_CLIP
}
PdfPageTextRenderMode::StrokedUnfilledClipping => {
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_STROKE_CLIP
}
PdfPageTextRenderMode::FilledThenStrokedClipping => {
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_FILL_STROKE_CLIP
}
PdfPageTextRenderMode::InvisibleClipping => {
FPDF_TEXT_RENDERMODE_FPDF_TEXTRENDERMODE_CLIP
}
}
}
}
pub struct PdfPageTextObject<'a> {
object_handle: FPDF_PAGEOBJECT,
ownership: PdfPageObjectOwnership,
lifetime: PhantomData<&'a FPDF_PAGEOBJECT>,
}
impl<'a> PdfPageTextObject<'a> {
#[inline]
pub(crate) fn from_pdfium(
object_handle: FPDF_PAGEOBJECT,
ownership: PdfPageObjectOwnership,
) -> Self {
PdfPageTextObject {
object_handle,
ownership,
lifetime: PhantomData,
}
}
#[inline]
pub fn new(
document: &PdfDocument<'a>,
text: impl ToString,
font: impl ToPdfFontToken,
font_size: PdfPoints,
) -> Result<Self, PdfiumError> {
Self::new_from_handles(
document.handle(),
text,
font.token().handle(),
font_size,
document.bindings(),
)
}
pub(crate) fn new_from_handles(
document: FPDF_DOCUMENT,
text: impl ToString,
font: FPDF_FONT,
font_size: PdfPoints,
bindings: &'a dyn PdfiumLibraryBindings,
) -> Result<Self, PdfiumError> {
let handle = unsafe { bindings.FPDFPageObj_CreateTextObj(document, font, font_size.value) };
if handle.is_null() {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
} else {
let mut result = PdfPageTextObject {
object_handle: handle,
ownership: PdfPageObjectOwnership::unowned(),
lifetime: PhantomData,
};
result.set_text(text)?;
Ok(result)
}
}
pub fn render_mode(&self) -> PdfPageTextRenderMode {
PdfPageTextRenderMode::from_pdfium(unsafe {
self.bindings()
.FPDFTextObj_GetTextRenderMode(self.object_handle)
})
.unwrap_or(PdfPageTextRenderMode::Unknown)
}
#[inline]
pub fn is_visible(&self) -> bool {
match self.render_mode() {
PdfPageTextRenderMode::Invisible | PdfPageTextRenderMode::InvisibleClipping => false,
_ => true,
}
}
#[inline]
pub fn scaled_font_size(&self) -> PdfPoints {
PdfPoints::new(self.unscaled_font_size().value * self.get_vertical_scale())
}
pub fn unscaled_font_size(&self) -> PdfPoints {
let mut result = 0.0;
if self.bindings().is_true(unsafe {
self.bindings()
.FPDFTextObj_GetFontSize(self.object_handle, &mut result)
}) {
PdfPoints::new(result)
} else {
PdfPoints::ZERO
}
}
pub fn font(&self) -> PdfFont<'_> {
PdfFont::from_pdfium(
unsafe { self.bindings().FPDFTextObj_GetFont(self.object_handle) },
None,
false,
)
}
pub fn text(&self) -> String {
let page_handle = match self.ownership() {
PdfPageObjectOwnership::Page(ownership) => Some(ownership.page_handle()),
PdfPageObjectOwnership::AttachedAnnotation(ownership) => Some(ownership.page_handle()),
_ => None,
};
if let Some(page_handle) = page_handle {
let text_handle = unsafe { self.bindings().FPDFText_LoadPage(page_handle) };
if !text_handle.is_null() {
let buffer_length = unsafe {
self.bindings().FPDFTextObj_GetText(
self.object_handle(),
text_handle,
std::ptr::null_mut(),
0,
)
};
if buffer_length == 0 {
return String::new();
}
let mut buffer = create_byte_buffer(buffer_length as usize);
let result = unsafe {
self.bindings().FPDFTextObj_GetText(
self.object_handle(),
text_handle,
buffer.as_mut_ptr() as *mut FPDF_WCHAR,
buffer_length,
)
};
assert_eq!(result, buffer_length);
unsafe {
self.bindings().FPDFText_ClosePage(text_handle);
}
get_string_from_pdfium_utf16le_bytes(buffer).unwrap_or_default()
} else {
String::new()
}
} else {
String::new()
}
}
pub fn set_text(&mut self, text: impl ToString) -> Result<(), PdfiumError> {
let text = text.to_string();
let text = if text.is_empty() { " " } else { text.as_str() };
if self.bindings().is_true(unsafe {
self.bindings()
.FPDFText_SetText_str(self.object_handle(), text)
}) {
Ok(())
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
}
}
pub fn set_render_mode(
&mut self,
render_mode: PdfPageTextRenderMode,
) -> Result<(), PdfiumError> {
if self.bindings().is_true(unsafe {
self.bindings()
.FPDFTextObj_SetTextRenderMode(self.object_handle(), render_mode.as_pdfium())
}) {
Ok(())
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
}
}
#[cfg(any(
feature = "pdfium_future",
feature = "pdfium_7543",
feature = "pdfium_7350",
feature = "pdfium_7215",
feature = "pdfium_7123",
feature = "pdfium_6996",
feature = "pdfium_6721",
feature = "pdfium_6666",
feature = "pdfium_6611",
))]
#[inline]
pub fn chars(&self, text: &'a PdfPageText<'a>) -> Result<PdfPageTextChars<'a>, PdfiumError> {
text.chars_for_object(self)
}
#[cfg(any(
feature = "pdfium_future",
feature = "pdfium_7543",
feature = "pdfium_7350",
feature = "pdfium_7215",
feature = "pdfium_7123",
feature = "pdfium_6996",
feature = "pdfium_6721",
feature = "pdfium_6666",
feature = "pdfium_6611",
))]
#[inline]
pub fn has_descenders(&self, text: &PdfPageText) -> Result<bool, PdfiumError> {
self.chars(text)
.map(|chars| chars.iter().any(|char| char.has_descender()))
}
#[cfg(any(
feature = "pdfium_future",
feature = "pdfium_7543",
feature = "pdfium_7350",
feature = "pdfium_7215",
feature = "pdfium_7123",
feature = "pdfium_6996",
feature = "pdfium_6721",
feature = "pdfium_6666",
feature = "pdfium_6611",
))]
pub fn descent(&self, text: &PdfPageText) -> Result<PdfPoints, PdfiumError> {
let object_bottom = self.get_vertical_translation();
let mut maximum_descent = object_bottom;
for char in self.chars(text)?.iter() {
let char_bottom = char.tight_bounds()?.bottom();
if char_bottom < maximum_descent {
maximum_descent = char_bottom;
}
}
Ok(maximum_descent - object_bottom)
}
create_transform_setters!(
&mut Self,
Result<(), PdfiumError>,
"this [PdfPageTextObject]",
"this [PdfPageTextObject].",
"this [PdfPageTextObject],"
);
create_transform_getters!(
"this [PdfPageTextObject]",
"this [PdfPageTextObject].",
"this [PdfPageTextObject],"
);
}
impl<'a> PdfPageObjectPrivate<'a> for PdfPageTextObject<'a> {
#[inline]
fn object_handle(&self) -> FPDF_PAGEOBJECT {
self.object_handle
}
#[inline]
fn ownership(&self) -> &PdfPageObjectOwnership {
&self.ownership
}
#[inline]
fn set_ownership(&mut self, ownership: PdfPageObjectOwnership) {
self.ownership = ownership;
}
}
impl<'a> Drop for PdfPageTextObject<'a> {
fn drop(&mut self) {
self.drop_impl();
}
}
impl<'a> PdfiumLibraryBindingsAccessor<'a> for PdfPageTextObject<'a> {}
#[cfg(feature = "thread_safe")]
unsafe impl<'a> Send for PdfPageTextObject<'a> {}
#[cfg(feature = "thread_safe")]
unsafe impl<'a> Sync for PdfPageTextObject<'a> {}