use crate::bindgen::{
FPDF_ANNOTATION, FPDF_BOOL, FPDF_FILLMODE_ALTERNATE, FPDF_FILLMODE_NONE, FPDF_FILLMODE_WINDING,
FPDF_PAGE, FPDF_PAGEOBJECT,
};
use crate::bindings::PdfiumLibraryBindings;
use crate::color::PdfColor;
use crate::error::{PdfiumError, PdfiumInternalError};
use crate::page::{PdfPoints, PdfRect};
use crate::page_object::{PdfPageObject, PdfPageObjectCommon};
use crate::page_object_private::internal::PdfPageObjectPrivate;
use crate::path_segment::{PdfPathSegment, PdfPathSegmentType};
use crate::path_segments::{PdfPathSegmentIndex, PdfPathSegments, PdfPathSegmentsIterator};
use crate::prelude::PdfDocument;
use std::convert::TryInto;
use std::os::raw::{c_int, c_uint};
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum PdfPathFillMode {
None = FPDF_FILLMODE_NONE as isize,
EvenOdd = FPDF_FILLMODE_ALTERNATE as isize,
Winding = FPDF_FILLMODE_WINDING as isize,
}
impl PdfPathFillMode {
#[inline]
pub(crate) fn from_pdfium(value: c_int) -> Result<PdfPathFillMode, PdfiumError> {
match value as u32 {
FPDF_FILLMODE_NONE => Ok(PdfPathFillMode::None),
FPDF_FILLMODE_ALTERNATE => Ok(PdfPathFillMode::EvenOdd),
FPDF_FILLMODE_WINDING => Ok(PdfPathFillMode::Winding),
_ => Err(PdfiumError::UnknownPdfPagePathFillMode),
}
}
#[inline]
#[allow(dead_code)]
pub(crate) fn as_pdfium(&self) -> c_uint {
match self {
PdfPathFillMode::None => FPDF_FILLMODE_NONE,
PdfPathFillMode::EvenOdd => FPDF_FILLMODE_ALTERNATE,
PdfPathFillMode::Winding => FPDF_FILLMODE_WINDING,
}
}
}
impl Default for PdfPathFillMode {
#[inline]
fn default() -> Self {
PdfPathFillMode::Winding
}
}
pub struct PdfPagePathObject<'a> {
object_handle: FPDF_PAGEOBJECT,
page_handle: Option<FPDF_PAGE>,
annotation_handle: Option<FPDF_ANNOTATION>,
bindings: &'a dyn PdfiumLibraryBindings,
current_point_x: PdfPoints,
current_point_y: PdfPoints,
}
impl<'a> PdfPagePathObject<'a> {
#[inline]
pub(crate) fn from_pdfium(
object_handle: FPDF_PAGEOBJECT,
page_handle: Option<FPDF_PAGE>,
annotation_handle: Option<FPDF_ANNOTATION>,
bindings: &'a dyn PdfiumLibraryBindings,
) -> Self {
PdfPagePathObject {
object_handle,
page_handle,
annotation_handle,
bindings,
current_point_x: PdfPoints::ZERO,
current_point_y: PdfPoints::ZERO,
}
}
#[inline]
pub fn new(
document: &PdfDocument<'a>,
x: PdfPoints,
y: PdfPoints,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
Self::new_from_bindings(
document.bindings(),
x,
y,
stroke_color,
stroke_width,
fill_color,
)
}
pub(crate) fn new_from_bindings(
bindings: &'a dyn PdfiumLibraryBindings,
x: PdfPoints,
y: PdfPoints,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
let handle = bindings.FPDFPageObj_CreateNewPath(x.value, y.value);
if handle.is_null() {
if let Some(error) = bindings.get_pdfium_last_error() {
Err(PdfiumError::PdfiumLibraryInternalError(error))
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
}
} else {
let mut result = PdfPagePathObject {
object_handle: handle,
page_handle: None,
annotation_handle: None,
bindings,
current_point_x: x,
current_point_y: y,
};
result.move_to(x, y)?;
let do_stroke = if let Some(stroke_color) = stroke_color {
if let Some(stroke_width) = stroke_width {
result.set_stroke_color(stroke_color)?;
result.set_stroke_width(stroke_width)?;
true
} else {
false
}
} else {
false
};
let fill_mode = if let Some(fill_color) = fill_color {
result.set_fill_color(fill_color)?;
PdfPathFillMode::default()
} else {
PdfPathFillMode::None
};
result.set_fill_and_stroke_mode(fill_mode, do_stroke)?;
Ok(result)
}
}
#[inline]
pub(crate) fn new_line_from_bindings(
bindings: &'a dyn PdfiumLibraryBindings,
x1: PdfPoints,
y1: PdfPoints,
x2: PdfPoints,
y2: PdfPoints,
stroke_color: PdfColor,
stroke_width: PdfPoints,
) -> Result<Self, PdfiumError> {
let mut result = Self::new_from_bindings(
bindings,
x1,
y1,
Some(stroke_color),
Some(stroke_width),
None,
)?;
result.line_to(x2, y2)?;
Ok(result)
}
#[inline]
pub fn new_line(
document: &PdfDocument<'a>,
x1: PdfPoints,
y1: PdfPoints,
x2: PdfPoints,
y2: PdfPoints,
stroke_color: PdfColor,
stroke_width: PdfPoints,
) -> Result<Self, PdfiumError> {
Self::new_line_from_bindings(
document.bindings(),
x1,
y1,
x2,
y2,
stroke_color,
stroke_width,
)
}
#[allow(clippy::too_many_arguments)]
#[inline]
pub(crate) fn new_bezier_from_bindings(
bindings: &'a dyn PdfiumLibraryBindings,
x1: PdfPoints,
y1: PdfPoints,
x2: PdfPoints,
y2: PdfPoints,
control1_x: PdfPoints,
control1_y: PdfPoints,
control2_x: PdfPoints,
control2_y: PdfPoints,
stroke_color: PdfColor,
stroke_width: PdfPoints,
) -> Result<Self, PdfiumError> {
let mut result = Self::new_from_bindings(
bindings,
x1,
y1,
Some(stroke_color),
Some(stroke_width),
None,
)?;
result.bezier_to(x2, y2, control1_x, control1_y, control2_x, control2_y)?;
Ok(result)
}
#[allow(clippy::too_many_arguments)]
#[inline]
pub fn new_bezier(
document: &PdfDocument<'a>,
x1: PdfPoints,
y1: PdfPoints,
x2: PdfPoints,
y2: PdfPoints,
control1_x: PdfPoints,
control1_y: PdfPoints,
control2_x: PdfPoints,
control2_y: PdfPoints,
stroke_color: PdfColor,
stroke_width: PdfPoints,
) -> Result<Self, PdfiumError> {
Self::new_bezier_from_bindings(
document.bindings(),
x1,
y1,
x2,
y2,
control1_x,
control1_y,
control2_x,
control2_y,
stroke_color,
stroke_width,
)
}
#[inline]
pub(crate) fn new_rect_from_bindings(
bindings: &'a dyn PdfiumLibraryBindings,
rect: PdfRect,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
let mut result = Self::new_from_bindings(
bindings,
rect.left,
rect.bottom,
stroke_color,
stroke_width,
fill_color,
)?;
result.rect_to(rect.right, rect.top)?;
Ok(result)
}
#[inline]
pub fn new_rect(
document: &PdfDocument<'a>,
rect: PdfRect,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
Self::new_rect_from_bindings(
document.bindings(),
rect,
stroke_color,
stroke_width,
fill_color,
)
}
#[inline]
pub(crate) fn new_circle_from_bindings(
bindings: &'a dyn PdfiumLibraryBindings,
rect: PdfRect,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
let mut result = Self::new_from_bindings(
bindings,
rect.left,
rect.bottom,
stroke_color,
stroke_width,
fill_color,
)?;
result.circle_to(rect.right, rect.top)?;
Ok(result)
}
#[inline]
pub fn new_circle(
document: &PdfDocument<'a>,
rect: PdfRect,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
Self::new_circle_from_bindings(
document.bindings(),
rect,
stroke_color,
stroke_width,
fill_color,
)
}
#[inline]
pub(crate) fn new_circle_at_from_bindings(
bindings: &'a dyn PdfiumLibraryBindings,
center_x: PdfPoints,
center_y: PdfPoints,
radius: PdfPoints,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
Self::new_circle_from_bindings(
bindings,
PdfRect::new(
center_y - radius,
center_x - radius,
center_y + radius,
center_x + radius,
),
stroke_color,
stroke_width,
fill_color,
)
}
#[inline]
pub fn new_circle_at(
document: &PdfDocument<'a>,
center_x: PdfPoints,
center_y: PdfPoints,
radius: PdfPoints,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
Self::new_circle_at_from_bindings(
document.bindings(),
center_x,
center_y,
radius,
stroke_color,
stroke_width,
fill_color,
)
}
#[inline]
pub(crate) fn new_ellipse_from_bindings(
bindings: &'a dyn PdfiumLibraryBindings,
rect: PdfRect,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
let mut result = Self::new_from_bindings(
bindings,
rect.left,
rect.bottom,
stroke_color,
stroke_width,
fill_color,
)?;
result.ellipse_to(rect.right, rect.top)?;
Ok(result)
}
#[inline]
pub fn new_ellipse(
document: &PdfDocument<'a>,
rect: PdfRect,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
Self::new_ellipse_from_bindings(
document.bindings(),
rect,
stroke_color,
stroke_width,
fill_color,
)
}
#[allow(clippy::too_many_arguments)]
#[inline]
pub(crate) fn new_ellipse_at_from_bindings(
bindings: &'a dyn PdfiumLibraryBindings,
center_x: PdfPoints,
center_y: PdfPoints,
x_radius: PdfPoints,
y_radius: PdfPoints,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
Self::new_ellipse_from_bindings(
bindings,
PdfRect::new(
center_y - y_radius,
center_x - x_radius,
center_y + y_radius,
center_x + x_radius,
),
stroke_color,
stroke_width,
fill_color,
)
}
#[allow(clippy::too_many_arguments)]
#[inline]
pub fn new_ellipse_at(
document: &PdfDocument<'a>,
center_x: PdfPoints,
center_y: PdfPoints,
x_radius: PdfPoints,
y_radius: PdfPoints,
stroke_color: Option<PdfColor>,
stroke_width: Option<PdfPoints>,
fill_color: Option<PdfColor>,
) -> Result<Self, PdfiumError> {
Self::new_ellipse_at_from_bindings(
document.bindings(),
center_x,
center_y,
x_radius,
y_radius,
stroke_color,
stroke_width,
fill_color,
)
}
pub fn move_to(&mut self, x: PdfPoints, y: PdfPoints) -> Result<(), PdfiumError> {
if self.bindings.is_true(self.bindings.FPDFPath_MoveTo(
self.object_handle,
x.value,
y.value,
)) {
self.current_point_x = x;
self.current_point_y = y;
Ok(())
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
self.bindings
.get_pdfium_last_error()
.unwrap_or(PdfiumInternalError::Unknown),
))
}
}
pub fn line_to(&mut self, x: PdfPoints, y: PdfPoints) -> Result<(), PdfiumError> {
if self.bindings.is_true(self.bindings.FPDFPath_LineTo(
self.object_handle,
x.value,
y.value,
)) {
self.current_point_x = x;
self.current_point_y = y;
Ok(())
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
self.bindings
.get_pdfium_last_error()
.unwrap_or(PdfiumInternalError::Unknown),
))
}
}
pub fn bezier_to(
&mut self,
x: PdfPoints,
y: PdfPoints,
control1_x: PdfPoints,
control1_y: PdfPoints,
control2_x: PdfPoints,
control2_y: PdfPoints,
) -> Result<(), PdfiumError> {
if self.bindings.is_true(self.bindings.FPDFPath_BezierTo(
self.object_handle,
control1_x.value,
control1_y.value,
control2_x.value,
control2_y.value,
x.value,
y.value,
)) {
self.current_point_x = x;
self.current_point_y = y;
Ok(())
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
self.bindings
.get_pdfium_last_error()
.unwrap_or(PdfiumInternalError::Unknown),
))
}
}
pub fn rect_to(&mut self, x: PdfPoints, y: PdfPoints) -> Result<(), PdfiumError> {
let orig_x = self.current_point_x;
let orig_y = self.current_point_y;
self.close_path()?;
self.line_to(orig_x, y)?;
self.line_to(x, y)?;
self.line_to(x, orig_y)?;
self.close_path()?;
self.move_to(x, y)
}
pub fn ellipse_to(&mut self, x: PdfPoints, y: PdfPoints) -> Result<(), PdfiumError> {
let x_radius = (x - self.current_point_x) / 2.0;
let y_radius = (y - self.current_point_y) / 2.0;
self.close_path()?;
self.move_to(
self.current_point_x + x_radius,
self.current_point_y + y_radius,
)?;
self.ellipse(x_radius, y_radius)?;
self.move_to(x, y)
}
pub fn circle_to(&mut self, x: PdfPoints, y: PdfPoints) -> Result<(), PdfiumError> {
let radius = (x - self.current_point_x) / 2.0;
self.move_to(self.current_point_x + radius, self.current_point_y + radius)?;
self.ellipse(radius, radius)?;
self.move_to(x, y)
}
fn ellipse(&mut self, x_radius: PdfPoints, y_radius: PdfPoints) -> Result<(), PdfiumError> {
const C: f32 = 0.551915;
let x_c = x_radius * C;
let y_c = y_radius * C;
let orig_x = self.current_point_x;
let orig_y = self.current_point_y;
self.move_to(orig_x - x_radius, orig_y)?;
self.bezier_to(
orig_x,
orig_y + y_radius,
orig_x - x_radius,
orig_y + y_c,
orig_x - x_c,
orig_y + y_radius,
)?;
self.bezier_to(
orig_x + x_radius,
orig_y,
orig_x + x_c,
orig_y + y_radius,
orig_x + x_radius,
orig_y + y_c,
)?;
self.bezier_to(
orig_x,
orig_y - y_radius,
orig_x + x_radius,
orig_y - y_c,
orig_x + x_c,
orig_y - y_radius,
)?;
self.bezier_to(
orig_x - x_radius,
orig_y,
orig_x - x_c,
orig_y - y_radius,
orig_x - x_radius,
orig_y - y_c,
)?;
self.close_path()
}
pub fn close_path(&mut self) -> Result<(), PdfiumError> {
if self
.bindings
.is_true(self.bindings.FPDFPath_Close(self.object_handle))
{
Ok(())
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
self.bindings
.get_pdfium_last_error()
.unwrap_or(PdfiumInternalError::Unknown),
))
}
}
pub fn fill_mode(&self) -> Result<PdfPathFillMode, PdfiumError> {
let mut raw_fill_mode: c_int = 0;
let mut _raw_stroke: FPDF_BOOL = self.bindings().FALSE();
if self
.bindings()
.is_true(self.bindings().FPDFPath_GetDrawMode(
*self.get_object_handle(),
&mut raw_fill_mode,
&mut _raw_stroke,
))
{
PdfPathFillMode::from_pdfium(raw_fill_mode)
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
self.bindings()
.get_pdfium_last_error()
.unwrap_or(PdfiumInternalError::Unknown),
))
}
}
pub fn is_stroked(&self) -> Result<bool, PdfiumError> {
let mut _raw_fill_mode: c_int = 0;
let mut raw_stroke: FPDF_BOOL = self.bindings().FALSE();
if self
.bindings()
.is_true(self.bindings().FPDFPath_GetDrawMode(
*self.get_object_handle(),
&mut _raw_fill_mode,
&mut raw_stroke,
))
{
Ok(self.bindings().is_true(raw_stroke))
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
self.bindings()
.get_pdfium_last_error()
.unwrap_or(PdfiumInternalError::Unknown),
))
}
}
pub fn set_fill_and_stroke_mode(
&mut self,
fill_mode: PdfPathFillMode,
do_stroke: bool,
) -> Result<(), PdfiumError> {
if self
.bindings()
.is_true(self.bindings().FPDFPath_SetDrawMode(
*self.get_object_handle(),
fill_mode.as_pdfium() as c_int,
self.bindings().bool_to_pdfium(do_stroke),
))
{
Ok(())
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
self.bindings()
.get_pdfium_last_error()
.unwrap_or(PdfiumInternalError::Unknown),
))
}
}
#[inline]
pub fn segments(&self) -> PdfPagePathObjectSegments {
PdfPagePathObjectSegments::from_pdfium(self.object_handle, self.bindings())
}
}
impl<'a> PdfPageObjectPrivate<'a> for PdfPagePathObject<'a> {
#[inline]
fn get_object_handle(&self) -> &FPDF_PAGEOBJECT {
&self.object_handle
}
#[inline]
fn get_page_handle(&self) -> &Option<FPDF_PAGE> {
&self.page_handle
}
#[inline]
fn set_page_handle(&mut self, page: FPDF_PAGE) {
self.page_handle = Some(page);
}
#[inline]
fn clear_page_handle(&mut self) {
self.page_handle = None;
}
#[inline]
fn get_annotation_handle(&self) -> &Option<FPDF_ANNOTATION> {
&self.annotation_handle
}
#[inline]
fn set_annotation_handle(&mut self, annotation: FPDF_ANNOTATION) {
self.annotation_handle = Some(annotation);
}
#[inline]
fn clear_annotation_handle(&mut self) {
self.annotation_handle = None;
}
#[inline]
fn bindings(&self) -> &dyn PdfiumLibraryBindings {
self.bindings
}
#[inline]
fn is_cloneable_impl(&self) -> bool {
!self
.segments()
.iter()
.any(|segment| segment.segment_type() == PdfPathSegmentType::BezierTo)
}
fn try_clone_impl<'b>(
&self,
document: &PdfDocument<'b>,
) -> Result<PdfPageObject<'b>, PdfiumError> {
let mut clone =
PdfPagePathObject::new(document, PdfPoints::ZERO, PdfPoints::ZERO, None, None, None)?;
clone.set_fill_and_stroke_mode(self.fill_mode()?, self.is_stroked()?)?;
clone.set_fill_color(self.fill_color()?)?;
clone.set_stroke_color(self.stroke_color()?)?;
clone.set_stroke_width(self.stroke_width()?)?;
clone.set_line_join(self.line_join()?)?;
clone.set_line_cap(self.line_cap()?)?;
for segment in self.segments().iter() {
if segment.segment_type() == PdfPathSegmentType::Unknown {
return Err(PdfiumError::PathObjectUnknownSegmentTypeNotCloneable);
} else if segment.segment_type() == PdfPathSegmentType::BezierTo {
return Err(PdfiumError::PathObjectBezierControlPointsNotCloneable);
} else {
match segment.segment_type() {
PdfPathSegmentType::Unknown | PdfPathSegmentType::BezierTo => {}
PdfPathSegmentType::LineTo => clone.line_to(segment.x(), segment.y())?,
PdfPathSegmentType::MoveTo => clone.move_to(segment.x(), segment.y())?,
}
if segment.is_close() {
clone.close_path()?;
}
}
}
clone.set_matrix(self.matrix()?)?;
Ok(PdfPageObject::Path(clone))
}
}
pub struct PdfPagePathObjectSegments<'a> {
handle: FPDF_PAGEOBJECT,
bindings: &'a dyn PdfiumLibraryBindings,
}
impl<'a> PdfPagePathObjectSegments<'a> {
#[inline]
pub(crate) fn from_pdfium(
handle: FPDF_PAGEOBJECT,
bindings: &'a dyn PdfiumLibraryBindings,
) -> Self {
Self { handle, bindings }
}
}
impl<'a> PdfPathSegments<'a> for PdfPagePathObjectSegments<'a> {
#[inline]
fn bindings(&self) -> &'a dyn PdfiumLibraryBindings {
self.bindings
}
#[inline]
fn len(&self) -> PdfPathSegmentIndex {
self.bindings()
.FPDFPath_CountSegments(self.handle)
.try_into()
.unwrap_or(0)
}
fn get(&self, index: PdfPathSegmentIndex) -> Result<PdfPathSegment<'a>, PdfiumError> {
let handle = self
.bindings()
.FPDFPath_GetPathSegment(self.handle, index as c_int);
if handle.is_null() {
if let Some(error) = self.bindings().get_pdfium_last_error() {
Err(PdfiumError::PdfiumLibraryInternalError(error))
} else {
Err(PdfiumError::PdfiumLibraryInternalError(
PdfiumInternalError::Unknown,
))
}
} else {
Ok(PdfPathSegment::from_pdfium(handle, self.bindings()))
}
}
#[inline]
fn iter(&'a self) -> PdfPathSegmentsIterator<'a> {
PdfPathSegmentsIterator::new(self)
}
}