Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions mupdf-sys/wrapper.c
Original file line number Diff line number Diff line change
Expand Up @@ -2141,13 +2141,12 @@ pdf_document *mupdf_convert_to_pdf(fz_context *ctx, fz_document *doc, int fp, in
return pdf;
}

fz_location mupdf_resolve_link(fz_context *ctx, fz_document *doc, const char *uri, mupdf_error_t **errptr)
fz_location mupdf_resolve_link(fz_context *ctx, fz_document *doc, const char *uri, float *xp, float *yp, mupdf_error_t **errptr)
{
fz_location loc = { -1, -1 };
float xp = 0.0f, yp = 0.0f;
fz_try(ctx)
{
loc = fz_resolve_link(ctx, doc, uri, &xp, &yp);
loc = fz_resolve_link(ctx, doc, uri, xp, yp);
}
fz_catch(ctx)
{
Expand All @@ -2156,6 +2155,21 @@ fz_location mupdf_resolve_link(fz_context *ctx, fz_document *doc, const char *ur
return loc;
}


fz_link_dest mupdf_resolve_link_dest(fz_context *ctx, fz_document *doc, const char *uri, mupdf_error_t **errptr)
{
fz_link_dest dest;
fz_try(ctx)
{
dest = fz_resolve_link_dest(ctx, doc, uri);
}
fz_catch(ctx)
{
mupdf_save_error(ctx, errptr);
}
return dest;
}

fz_colorspace *mupdf_document_output_intent(fz_context *ctx, fz_document *doc, mupdf_error_t **errptr)
{
fz_colorspace *cs = NULL;
Expand Down
156 changes: 119 additions & 37 deletions src/destination.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use crate::pdf::PdfObject;
use crate::Error;
use crate::{Error, Matrix, Point, Rect};

use mupdf_sys::*;

#[derive(Debug, Clone)]
pub struct Destination {
Expand All @@ -8,42 +10,6 @@ pub struct Destination {
kind: DestinationKind,
}

#[derive(Debug, Clone, PartialEq)]
pub enum DestinationKind {
/// Display the page at a scale which just fits the whole page
/// in the window both horizontally and vertically.
Fit,
/// Display the page with the vertical coordinate `top` at the top edge of the window,
/// and the magnification set to fit the document horizontally.
FitH { top: f32 },
/// Display the page with the horizontal coordinate `left` at the left edge of the window,
/// and the magnification set to fit the document vertically.
FitV { left: f32 },
/// Display the page with (`left`, `top`) at the upper-left corner
/// of the window and the page magnified by factor `zoom`.
XYZ {
left: Option<f32>,
top: Option<f32>,
zoom: Option<f32>,
},
/// Display the page zoomed to show the rectangle specified by `left`, `bottom`, `right`, and `top`.
FitR {
left: f32,
bottom: f32,
right: f32,
top: f32,
},
/// Display the page like `/Fit`, but use the bounding box of the page’s contents,
/// rather than the crop box.
FitB,
/// Display the page like `/FitH`, but use the bounding box of the page’s contents,
/// rather than the crop box.
FitBH { top: f32 },
/// Display the page like `/FitV`, but use the bounding box of the page’s contents,
/// rather than the crop box.
FitBV { left: f32 },
}

impl Destination {
pub(crate) fn new(page: PdfObject, kind: DestinationKind) -> Self {
Self { page, kind }
Expand Down Expand Up @@ -108,3 +74,119 @@ impl Destination {
Ok(())
}
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub enum DestinationKind {
/// Display the page at a scale which just fits the whole page
/// in the window both horizontally and vertically.
Fit,
/// Display the page with the vertical coordinate `top` at the top edge of the window,
/// and the magnification set to fit the document horizontally.
FitH { top: f32 },
/// Display the page with the horizontal coordinate `left` at the left edge of the window,
/// and the magnification set to fit the document vertically.
FitV { left: f32 },
/// Display the page with (`left`, `top`) at the upper-left corner
/// of the window and the page magnified by factor `zoom`.
XYZ {
left: Option<f32>,
top: Option<f32>,
zoom: Option<f32>,
},
/// Display the page zoomed to show the rectangle specified by `left`, `bottom`, `right`, and `top`.
FitR {
left: f32,
bottom: f32,
right: f32,
top: f32,
},
/// Display the page like `/Fit`, but use the bounding box of the page’s contents,
/// rather than the crop box.
FitB,
/// Display the page like `/FitH`, but use the bounding box of the page’s contents,
/// rather than the crop box.
FitBH { top: f32 },
/// Display the page like `/FitV`, but use the bounding box of the page’s contents,
/// rather than the crop box.
FitBV { left: f32 },
}

impl DestinationKind {
pub fn transform(self, matrix: &Matrix) -> Self {
match self {
Self::Fit => Self::Fit,
Self::FitB => Self::FitB,
Self::FitH { top } => {
let p = Point::new(0.0, top).transform(matrix);
Self::FitH { top: p.y }
}
Self::FitBH { top } => {
let p = Point::new(0.0, top).transform(matrix);
Self::FitBH { top: p.y }
}
Self::FitV { left } => {
let p = Point::new(left, 0.0).transform(matrix);
Self::FitV { left: p.x }
}
Self::FitBV { left } => {
let p = Point::new(left, 0.0).transform(matrix);
Self::FitBV { left: p.x }
}
Self::XYZ { left, top, zoom } => {
let p =
Point::new(left.unwrap_or_default(), top.unwrap_or_default()).transform(matrix);
Self::XYZ {
left: Some(p.x),
top: Some(p.y),
zoom,
}
}
Self::FitR {
left,
bottom,
right,
top,
} => {
let r = Rect {
x0: left,
y0: bottom,
x1: right,
y1: top,
};
let tr = r.transform(matrix);
Self::FitR {
left: tr.x0,
bottom: tr.y0,
right: tr.x1,
top: tr.y1,
}
}
}
}
}

impl From<fz_link_dest> for DestinationKind {
#[allow(non_upper_case_globals)]
fn from(value: fz_link_dest) -> Self {
match value.type_ {
fz_link_dest_type_FZ_LINK_DEST_FIT => Self::Fit,
fz_link_dest_type_FZ_LINK_DEST_FIT_B => Self::FitB,
fz_link_dest_type_FZ_LINK_DEST_FIT_H => Self::FitH { top: value.y },
fz_link_dest_type_FZ_LINK_DEST_FIT_BH => Self::FitBH { top: value.y },
fz_link_dest_type_FZ_LINK_DEST_FIT_V => Self::FitV { left: value.x },
fz_link_dest_type_FZ_LINK_DEST_FIT_BV => Self::FitBV { left: value.x },
fz_link_dest_type_FZ_LINK_DEST_XYZ => Self::XYZ {
left: Some(value.x),
top: Some(value.y),
zoom: Some(value.zoom),
},
fz_link_dest_type_FZ_LINK_DEST_FIT_R => Self::FitR {
left: value.x,
bottom: value.y,
right: value.x + value.w,
top: value.y + value.h,
},
_ => unreachable!(),
}
}
}
71 changes: 39 additions & 32 deletions src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::ptr;

use mupdf_sys::*;

use crate::link::LinkDestination;
use crate::pdf::PdfDocument;
use crate::{context, Buffer, Colorspace, Cookie, Error, FilePath, Outline, Page};

Expand Down Expand Up @@ -42,8 +43,15 @@ impl MetadataName {

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Location {
pub chapter: i32,
pub page: i32,
pub chapter: u32,
/// Index of the page inside the [`chapter`](Location::chapter).
///
/// See [`page_number`](Location::page_number) for the absolute page number.
pub page_in_chapter: u32,
/// Page number absolute to the start of the document.
///
/// See [`page_in_chapter`](Location::page_in_chapter) for the page index relative to the [`chapter`](Location::chapter).
pub page_number: u32,
}

#[derive(Debug)]
Expand Down Expand Up @@ -116,16 +124,9 @@ impl Document {
Ok(info)
}

pub fn resolve_link(&self, uri: &str) -> Result<Option<Location>, Error> {
pub fn resolve_link(&self, uri: &str) -> Result<Option<LinkDestination>, Error> {
let c_uri = CString::new(uri)?;
let loc = unsafe { ffi_try!(mupdf_resolve_link(context(), self.inner, c_uri.as_ptr())) }?;
if loc.page >= 0 {
return Ok(Some(Location {
chapter: loc.chapter,
page: loc.page,
}));
}
Ok(None)
LinkDestination::from_uri(self, &c_uri)
}

pub fn is_reflowable(&self) -> Result<bool, Error> {
Expand Down Expand Up @@ -233,35 +234,27 @@ impl Document {
let mut outlines = Vec::new();
let mut next = outline;
while !next.is_null() {
let mut x = 0.0;
let mut y = 0.0;
let mut page = None;
let title = CStr::from_ptr((*next).title).to_string_lossy().into_owned();
let uri = if !(*next).uri.is_null() {
if fz_is_external_link(context(), (*next).uri) > 0 {
Some(CStr::from_ptr((*next).uri).to_string_lossy().into_owned())
} else {
page = Some(
fz_resolve_link(context(), self.inner, (*next).uri, &mut x, &mut y).page
as u32,
);
None
}

let (uri, dest) = if !(*next).uri.is_null() {
let uri = CStr::from_ptr((*next).uri);
let dest = LinkDestination::from_uri(self, uri).unwrap();
(Some(uri.to_string_lossy().into_owned()), dest)
} else {
None
(None, None)
};

let down = if !(*next).down.is_null() {
self.walk_outlines((*next).down)
} else {
Vec::new()
};

outlines.push(Outline {
title,
uri,
page,
dest,
down,
x,
y,
});
next = (*next).next;
}
Expand Down Expand Up @@ -357,6 +350,8 @@ pub(crate) use test_document;

#[cfg(test)]
mod test {
use crate::{document::Location, link::LinkDestination, DestinationKind};

use super::{Document, MetadataName, Page};

#[test]
Expand Down Expand Up @@ -461,10 +456,22 @@ mod test {
assert_eq!(outlines.len(), 1);

let out1 = &outlines[0];
assert_eq!(out1.page, Some(0));
assert_eq!(
out1.dest,
Some(LinkDestination {
loc: Location {
chapter: 0,
page_in_chapter: 0,
page_number: 0,
},
kind: DestinationKind::XYZ {
left: Some(56.7),
top: Some(68.70001),
zoom: Some(100.0),
}
})
);
assert_eq!(out1.title, "Dummy PDF file");
assert!(out1.uri.is_none());
assert_eq!(out1.x, 56.7);
assert_eq!(out1.y, 68.70001);
assert_eq!(out1.uri.as_deref(), Some("#page=1&zoom=100,56.7,68.70001"));
}
}
44 changes: 34 additions & 10 deletions src/link.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,45 @@
use std::fmt;
use std::ffi::CStr;

use crate::Rect;
use crate::{context, document::Location, DestinationKind, Document, Error, Rect};

use mupdf_sys::*;

/// A list of interactive links on a page.
#[derive(Debug, Clone)]
pub struct Link {
pub bounds: Rect,
pub page: u32,
pub dest: Option<LinkDestination>,
pub uri: String,
}

impl fmt::Display for Link {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"Link(b={},page={},uri={})",
self.bounds, self.page, self.uri
)
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LinkDestination {
pub loc: Location,
pub kind: DestinationKind,
}

impl LinkDestination {
pub(crate) fn from_uri(doc: &Document, uri: &CStr) -> Result<Option<Self>, Error> {
let external = unsafe { fz_is_external_link(context(), uri.as_ptr()) } != 0;
if external {
return Ok(None);
}

let dest =
unsafe { ffi_try!(mupdf_resolve_link_dest(context(), doc.inner, uri.as_ptr())) }?;
if dest.loc.page < 0 {
return Ok(None);
}

let page_number = unsafe { fz_page_number_from_location(context(), doc.inner, dest.loc) };

Ok(Some(Self {
loc: Location {
chapter: dest.loc.chapter as u32,
page_in_chapter: dest.loc.page as u32,
page_number: page_number as u32,
},
kind: dest.into(),
}))
}
}
Loading