Skip to content

Commit 2b829d8

Browse files
authored
Return more information for outlines (#145)
* Return more information for Locations * Use DestinationKind for coordinates * Fix lint * Convert to From impl and improve field names
1 parent 2e76d2f commit 2b829d8

File tree

8 files changed

+242
-129
lines changed

8 files changed

+242
-129
lines changed

mupdf-sys/wrapper.c

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,13 +2141,12 @@ pdf_document *mupdf_convert_to_pdf(fz_context *ctx, fz_document *doc, int fp, in
21412141
return pdf;
21422142
}
21432143

2144-
fz_location mupdf_resolve_link(fz_context *ctx, fz_document *doc, const char *uri, mupdf_error_t **errptr)
2144+
fz_location mupdf_resolve_link(fz_context *ctx, fz_document *doc, const char *uri, float *xp, float *yp, mupdf_error_t **errptr)
21452145
{
21462146
fz_location loc = { -1, -1 };
2147-
float xp = 0.0f, yp = 0.0f;
21482147
fz_try(ctx)
21492148
{
2150-
loc = fz_resolve_link(ctx, doc, uri, &xp, &yp);
2149+
loc = fz_resolve_link(ctx, doc, uri, xp, yp);
21512150
}
21522151
fz_catch(ctx)
21532152
{
@@ -2156,6 +2155,21 @@ fz_location mupdf_resolve_link(fz_context *ctx, fz_document *doc, const char *ur
21562155
return loc;
21572156
}
21582157

2158+
2159+
fz_link_dest mupdf_resolve_link_dest(fz_context *ctx, fz_document *doc, const char *uri, mupdf_error_t **errptr)
2160+
{
2161+
fz_link_dest dest;
2162+
fz_try(ctx)
2163+
{
2164+
dest = fz_resolve_link_dest(ctx, doc, uri);
2165+
}
2166+
fz_catch(ctx)
2167+
{
2168+
mupdf_save_error(ctx, errptr);
2169+
}
2170+
return dest;
2171+
}
2172+
21592173
fz_colorspace *mupdf_document_output_intent(fz_context *ctx, fz_document *doc, mupdf_error_t **errptr)
21602174
{
21612175
fz_colorspace *cs = NULL;

src/destination.rs

Lines changed: 119 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use crate::pdf::PdfObject;
2-
use crate::Error;
2+
use crate::{Error, Matrix, Point, Rect};
3+
4+
use mupdf_sys::*;
35

46
#[derive(Debug, Clone)]
57
pub struct Destination {
@@ -8,42 +10,6 @@ pub struct Destination {
810
kind: DestinationKind,
911
}
1012

11-
#[derive(Debug, Clone, PartialEq)]
12-
pub enum DestinationKind {
13-
/// Display the page at a scale which just fits the whole page
14-
/// in the window both horizontally and vertically.
15-
Fit,
16-
/// Display the page with the vertical coordinate `top` at the top edge of the window,
17-
/// and the magnification set to fit the document horizontally.
18-
FitH { top: f32 },
19-
/// Display the page with the horizontal coordinate `left` at the left edge of the window,
20-
/// and the magnification set to fit the document vertically.
21-
FitV { left: f32 },
22-
/// Display the page with (`left`, `top`) at the upper-left corner
23-
/// of the window and the page magnified by factor `zoom`.
24-
XYZ {
25-
left: Option<f32>,
26-
top: Option<f32>,
27-
zoom: Option<f32>,
28-
},
29-
/// Display the page zoomed to show the rectangle specified by `left`, `bottom`, `right`, and `top`.
30-
FitR {
31-
left: f32,
32-
bottom: f32,
33-
right: f32,
34-
top: f32,
35-
},
36-
/// Display the page like `/Fit`, but use the bounding box of the page’s contents,
37-
/// rather than the crop box.
38-
FitB,
39-
/// Display the page like `/FitH`, but use the bounding box of the page’s contents,
40-
/// rather than the crop box.
41-
FitBH { top: f32 },
42-
/// Display the page like `/FitV`, but use the bounding box of the page’s contents,
43-
/// rather than the crop box.
44-
FitBV { left: f32 },
45-
}
46-
4713
impl Destination {
4814
pub(crate) fn new(page: PdfObject, kind: DestinationKind) -> Self {
4915
Self { page, kind }
@@ -108,3 +74,119 @@ impl Destination {
10874
Ok(())
10975
}
11076
}
77+
78+
#[derive(Debug, Clone, Copy, PartialEq)]
79+
pub enum DestinationKind {
80+
/// Display the page at a scale which just fits the whole page
81+
/// in the window both horizontally and vertically.
82+
Fit,
83+
/// Display the page with the vertical coordinate `top` at the top edge of the window,
84+
/// and the magnification set to fit the document horizontally.
85+
FitH { top: f32 },
86+
/// Display the page with the horizontal coordinate `left` at the left edge of the window,
87+
/// and the magnification set to fit the document vertically.
88+
FitV { left: f32 },
89+
/// Display the page with (`left`, `top`) at the upper-left corner
90+
/// of the window and the page magnified by factor `zoom`.
91+
XYZ {
92+
left: Option<f32>,
93+
top: Option<f32>,
94+
zoom: Option<f32>,
95+
},
96+
/// Display the page zoomed to show the rectangle specified by `left`, `bottom`, `right`, and `top`.
97+
FitR {
98+
left: f32,
99+
bottom: f32,
100+
right: f32,
101+
top: f32,
102+
},
103+
/// Display the page like `/Fit`, but use the bounding box of the page’s contents,
104+
/// rather than the crop box.
105+
FitB,
106+
/// Display the page like `/FitH`, but use the bounding box of the page’s contents,
107+
/// rather than the crop box.
108+
FitBH { top: f32 },
109+
/// Display the page like `/FitV`, but use the bounding box of the page’s contents,
110+
/// rather than the crop box.
111+
FitBV { left: f32 },
112+
}
113+
114+
impl DestinationKind {
115+
pub fn transform(self, matrix: &Matrix) -> Self {
116+
match self {
117+
Self::Fit => Self::Fit,
118+
Self::FitB => Self::FitB,
119+
Self::FitH { top } => {
120+
let p = Point::new(0.0, top).transform(matrix);
121+
Self::FitH { top: p.y }
122+
}
123+
Self::FitBH { top } => {
124+
let p = Point::new(0.0, top).transform(matrix);
125+
Self::FitBH { top: p.y }
126+
}
127+
Self::FitV { left } => {
128+
let p = Point::new(left, 0.0).transform(matrix);
129+
Self::FitV { left: p.x }
130+
}
131+
Self::FitBV { left } => {
132+
let p = Point::new(left, 0.0).transform(matrix);
133+
Self::FitBV { left: p.x }
134+
}
135+
Self::XYZ { left, top, zoom } => {
136+
let p =
137+
Point::new(left.unwrap_or_default(), top.unwrap_or_default()).transform(matrix);
138+
Self::XYZ {
139+
left: Some(p.x),
140+
top: Some(p.y),
141+
zoom,
142+
}
143+
}
144+
Self::FitR {
145+
left,
146+
bottom,
147+
right,
148+
top,
149+
} => {
150+
let r = Rect {
151+
x0: left,
152+
y0: bottom,
153+
x1: right,
154+
y1: top,
155+
};
156+
let tr = r.transform(matrix);
157+
Self::FitR {
158+
left: tr.x0,
159+
bottom: tr.y0,
160+
right: tr.x1,
161+
top: tr.y1,
162+
}
163+
}
164+
}
165+
}
166+
}
167+
168+
impl From<fz_link_dest> for DestinationKind {
169+
#[allow(non_upper_case_globals)]
170+
fn from(value: fz_link_dest) -> Self {
171+
match value.type_ {
172+
fz_link_dest_type_FZ_LINK_DEST_FIT => Self::Fit,
173+
fz_link_dest_type_FZ_LINK_DEST_FIT_B => Self::FitB,
174+
fz_link_dest_type_FZ_LINK_DEST_FIT_H => Self::FitH { top: value.y },
175+
fz_link_dest_type_FZ_LINK_DEST_FIT_BH => Self::FitBH { top: value.y },
176+
fz_link_dest_type_FZ_LINK_DEST_FIT_V => Self::FitV { left: value.x },
177+
fz_link_dest_type_FZ_LINK_DEST_FIT_BV => Self::FitBV { left: value.x },
178+
fz_link_dest_type_FZ_LINK_DEST_XYZ => Self::XYZ {
179+
left: Some(value.x),
180+
top: Some(value.y),
181+
zoom: Some(value.zoom),
182+
},
183+
fz_link_dest_type_FZ_LINK_DEST_FIT_R => Self::FitR {
184+
left: value.x,
185+
bottom: value.y,
186+
right: value.x + value.w,
187+
top: value.y + value.h,
188+
},
189+
_ => unreachable!(),
190+
}
191+
}
192+
}

src/document.rs

Lines changed: 39 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use std::ptr;
44

55
use mupdf_sys::*;
66

7+
use crate::link::LinkDestination;
78
use crate::pdf::PdfDocument;
89
use crate::{context, Buffer, Colorspace, Cookie, Error, FilePath, Outline, Page};
910

@@ -42,8 +43,15 @@ impl MetadataName {
4243

4344
#[derive(Debug, Clone, Copy, PartialEq)]
4445
pub struct Location {
45-
pub chapter: i32,
46-
pub page: i32,
46+
pub chapter: u32,
47+
/// Index of the page inside the [`chapter`](Location::chapter).
48+
///
49+
/// See [`page_number`](Location::page_number) for the absolute page number.
50+
pub page_in_chapter: u32,
51+
/// Page number absolute to the start of the document.
52+
///
53+
/// See [`page_in_chapter`](Location::page_in_chapter) for the page index relative to the [`chapter`](Location::chapter).
54+
pub page_number: u32,
4755
}
4856

4957
#[derive(Debug)]
@@ -116,16 +124,9 @@ impl Document {
116124
Ok(info)
117125
}
118126

119-
pub fn resolve_link(&self, uri: &str) -> Result<Option<Location>, Error> {
127+
pub fn resolve_link(&self, uri: &str) -> Result<Option<LinkDestination>, Error> {
120128
let c_uri = CString::new(uri)?;
121-
let loc = unsafe { ffi_try!(mupdf_resolve_link(context(), self.inner, c_uri.as_ptr())) }?;
122-
if loc.page >= 0 {
123-
return Ok(Some(Location {
124-
chapter: loc.chapter,
125-
page: loc.page,
126-
}));
127-
}
128-
Ok(None)
129+
LinkDestination::from_uri(self, &c_uri)
129130
}
130131

131132
pub fn is_reflowable(&self) -> Result<bool, Error> {
@@ -233,35 +234,27 @@ impl Document {
233234
let mut outlines = Vec::new();
234235
let mut next = outline;
235236
while !next.is_null() {
236-
let mut x = 0.0;
237-
let mut y = 0.0;
238-
let mut page = None;
239237
let title = CStr::from_ptr((*next).title).to_string_lossy().into_owned();
240-
let uri = if !(*next).uri.is_null() {
241-
if fz_is_external_link(context(), (*next).uri) > 0 {
242-
Some(CStr::from_ptr((*next).uri).to_string_lossy().into_owned())
243-
} else {
244-
page = Some(
245-
fz_resolve_link(context(), self.inner, (*next).uri, &mut x, &mut y).page
246-
as u32,
247-
);
248-
None
249-
}
238+
239+
let (uri, dest) = if !(*next).uri.is_null() {
240+
let uri = CStr::from_ptr((*next).uri);
241+
let dest = LinkDestination::from_uri(self, uri).unwrap();
242+
(Some(uri.to_string_lossy().into_owned()), dest)
250243
} else {
251-
None
244+
(None, None)
252245
};
246+
253247
let down = if !(*next).down.is_null() {
254248
self.walk_outlines((*next).down)
255249
} else {
256250
Vec::new()
257251
};
252+
258253
outlines.push(Outline {
259254
title,
260255
uri,
261-
page,
256+
dest,
262257
down,
263-
x,
264-
y,
265258
});
266259
next = (*next).next;
267260
}
@@ -357,6 +350,8 @@ pub(crate) use test_document;
357350

358351
#[cfg(test)]
359352
mod test {
353+
use crate::{document::Location, link::LinkDestination, DestinationKind};
354+
360355
use super::{Document, MetadataName, Page};
361356

362357
#[test]
@@ -461,10 +456,22 @@ mod test {
461456
assert_eq!(outlines.len(), 1);
462457

463458
let out1 = &outlines[0];
464-
assert_eq!(out1.page, Some(0));
459+
assert_eq!(
460+
out1.dest,
461+
Some(LinkDestination {
462+
loc: Location {
463+
chapter: 0,
464+
page_in_chapter: 0,
465+
page_number: 0,
466+
},
467+
kind: DestinationKind::XYZ {
468+
left: Some(56.7),
469+
top: Some(68.70001),
470+
zoom: Some(100.0),
471+
}
472+
})
473+
);
465474
assert_eq!(out1.title, "Dummy PDF file");
466-
assert!(out1.uri.is_none());
467-
assert_eq!(out1.x, 56.7);
468-
assert_eq!(out1.y, 68.70001);
475+
assert_eq!(out1.uri.as_deref(), Some("#page=1&zoom=100,56.7,68.70001"));
469476
}
470477
}

src/link.rs

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,45 @@
1-
use std::fmt;
1+
use std::ffi::CStr;
22

3-
use crate::Rect;
3+
use crate::{context, document::Location, DestinationKind, Document, Error, Rect};
4+
5+
use mupdf_sys::*;
46

57
/// A list of interactive links on a page.
68
#[derive(Debug, Clone)]
79
pub struct Link {
810
pub bounds: Rect,
9-
pub page: u32,
11+
pub dest: Option<LinkDestination>,
1012
pub uri: String,
1113
}
1214

13-
impl fmt::Display for Link {
14-
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
15-
write!(
16-
f,
17-
"Link(b={},page={},uri={})",
18-
self.bounds, self.page, self.uri
19-
)
15+
#[derive(Debug, Clone, Copy, PartialEq)]
16+
pub struct LinkDestination {
17+
pub loc: Location,
18+
pub kind: DestinationKind,
19+
}
20+
21+
impl LinkDestination {
22+
pub(crate) fn from_uri(doc: &Document, uri: &CStr) -> Result<Option<Self>, Error> {
23+
let external = unsafe { fz_is_external_link(context(), uri.as_ptr()) } != 0;
24+
if external {
25+
return Ok(None);
26+
}
27+
28+
let dest =
29+
unsafe { ffi_try!(mupdf_resolve_link_dest(context(), doc.inner, uri.as_ptr())) }?;
30+
if dest.loc.page < 0 {
31+
return Ok(None);
32+
}
33+
34+
let page_number = unsafe { fz_page_number_from_location(context(), doc.inner, dest.loc) };
35+
36+
Ok(Some(Self {
37+
loc: Location {
38+
chapter: dest.loc.chapter as u32,
39+
page_in_chapter: dest.loc.page as u32,
40+
page_number: page_number as u32,
41+
},
42+
kind: dest.into(),
43+
}))
2044
}
2145
}

0 commit comments

Comments
 (0)