mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-01 22:26:49 -06:00
105 lines
4.5 KiB
Rust
105 lines
4.5 KiB
Rust
//! Interactive PDF: hyperlink annotations (frames and text ranges → URLs, e-mail, pages) and the
|
|
//! bookmark outline.
|
|
|
|
use designcraft_doc::{Bookmark, Document, HyperlinkDest, HyperlinkSource, SpreadRef};
|
|
use designcraft_geom::{Affine, Rect};
|
|
use krilla::action::{Action, LinkAction};
|
|
use krilla::annotation::{Annotation, LinkAnnotation, Target};
|
|
use krilla::destination::XyzDestination;
|
|
use krilla::outline::{Outline, OutlineNode};
|
|
|
|
use crate::export::Sheet;
|
|
|
|
/// Index of the output sheet showing absolute page `abs`.
|
|
pub(crate) fn sheet_of_page(doc: &Document, sheets: &[Sheet], abs: usize) -> Option<usize> {
|
|
let (si, pi) = doc.page_loc(abs)?;
|
|
let page = doc.spreads.get(si)?.pages.get(pi)?.bounds();
|
|
sheets.iter().position(|s| s.spread == si && s.trim.x0 <= page.center().x && page.center().x <= s.trim.x1)
|
|
}
|
|
|
|
/// Spread-space rectangles covered by a hyperlink source on spread `si`.
|
|
fn source_rects(doc: &Document, cache: &designcraft_compose::Cache, src: &HyperlinkSource, si: usize) -> Vec<Rect> {
|
|
match src {
|
|
HyperlinkSource::Item { id } => match (doc.find(*id), doc.item(*id)) {
|
|
(Some(loc), Some(it)) if loc.spread == SpreadRef::Doc(si) => vec![doc.parent_xf(&loc).transform_rect_bbox(it.bounds())],
|
|
_ => vec![],
|
|
},
|
|
HyperlinkSource::Text { story, start, end } => {
|
|
let cs = cache.get(doc, *story, None);
|
|
let mut out = Vec::new();
|
|
for ft in &cs.frames {
|
|
let (Some(loc), Some(it)) = (doc.find(ft.frame), doc.item(ft.frame)) else { continue };
|
|
if loc.spread != SpreadRef::Doc(si) {
|
|
continue;
|
|
}
|
|
let m: Affine = doc.parent_xf(&loc) * it.xf;
|
|
for l in &ft.lines {
|
|
if l.range.end <= *start || l.range.start >= *end {
|
|
continue;
|
|
}
|
|
let gs: Vec<_> = l.glyphs.iter().filter(|g| g.len > 0 && g.byte >= *start && g.byte < *end).collect();
|
|
let (Some(a), Some(b)) = (gs.first(), gs.last()) else { continue };
|
|
let r = Rect::new(a.x, l.baseline - l.ascent, b.x + b.adv, l.baseline + l.descent);
|
|
out.push(m.transform_rect_bbox(r));
|
|
}
|
|
}
|
|
out
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Link annotations for the sheet at `idx`.
|
|
pub(crate) fn annotations(doc: &Document, cache: &designcraft_compose::Cache, sheets: &[Sheet], idx: usize) -> Vec<Annotation> {
|
|
let sh = &sheets[idx];
|
|
let mut out = Vec::new();
|
|
for h in &doc.hyperlinks {
|
|
for r in source_rects(doc, cache, &h.source, sh.spread) {
|
|
let r = r.intersect(sh.bleed);
|
|
if r.width() <= 0.0 || r.height() <= 0.0 {
|
|
continue;
|
|
}
|
|
let rect = krilla::geom::Rect::from_ltrb(
|
|
(r.x0 - sh.media.x0) as f32,
|
|
(r.y0 - sh.media.y0) as f32,
|
|
(r.x1 - sh.media.x0) as f32,
|
|
(r.y1 - sh.media.y0) as f32,
|
|
);
|
|
let Some(rect) = rect else { continue };
|
|
let target = match &h.dest {
|
|
HyperlinkDest::Url(u) => Target::Action(Action::Link(LinkAction::new(u.clone()))),
|
|
HyperlinkDest::Email(e) => Target::Action(Action::Link(LinkAction::new(format!("mailto:{e}")))),
|
|
HyperlinkDest::Page(p) => match sheet_of_page(doc, sheets, *p) {
|
|
Some(i) => Target::Destination(XyzDestination::new(i, krilla::geom::Point::from_xy(0.0, 0.0)).into()),
|
|
None => continue,
|
|
},
|
|
};
|
|
out.push(Annotation::new_link(LinkAnnotation::new(rect, target), Some(h.name.clone())));
|
|
}
|
|
}
|
|
out
|
|
}
|
|
|
|
/// The document outline from the bookmarks (bookmarks to pages that weren't exported are skipped).
|
|
pub(crate) fn outline(doc: &Document, sheets: &[Sheet]) -> Option<Outline> {
|
|
fn node(doc: &Document, sheets: &[Sheet], b: &Bookmark) -> Option<OutlineNode> {
|
|
let i = sheet_of_page(doc, sheets, b.page)?;
|
|
let mut n = OutlineNode::new(b.name.clone(), XyzDestination::new(i, krilla::geom::Point::from_xy(0.0, 0.0)));
|
|
for c in &b.children {
|
|
if let Some(cn) = node(doc, sheets, c) {
|
|
n.push_child(cn);
|
|
}
|
|
}
|
|
Some(n)
|
|
}
|
|
if doc.bookmarks.is_empty() {
|
|
return None;
|
|
}
|
|
let mut o = Outline::new();
|
|
for b in &doc.bookmarks {
|
|
if let Some(n) = node(doc, sheets, b) {
|
|
o.push_child(n);
|
|
}
|
|
}
|
|
Some(o)
|
|
}
|