summaryrefslogtreecommitdiff
path: root/library
diff options
context:
space:
mode:
Diffstat (limited to 'library')
-rw-r--r--library/src/layout/hide.rs2
-rw-r--r--library/src/meta/bibliography.rs239
-rw-r--r--library/src/meta/figure.rs2
-rw-r--r--library/src/meta/heading.rs2
-rw-r--r--library/src/meta/link.rs2
-rw-r--r--library/src/meta/outline.rs24
-rw-r--r--library/src/meta/reference.rs7
-rw-r--r--library/src/shared/ext.rs8
8 files changed, 169 insertions, 117 deletions
diff --git a/library/src/layout/hide.rs b/library/src/layout/hide.rs
index e939e6c3..1d87d3e8 100644
--- a/library/src/layout/hide.rs
+++ b/library/src/layout/hide.rs
@@ -24,6 +24,6 @@ pub struct HideNode {
impl Show for HideNode {
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
- Ok(self.body().styled(MetaNode::set_data(vec![Meta::Hidden])))
+ Ok(self.body().styled(MetaNode::set_data(vec![Meta::Hide])))
}
}
diff --git a/library/src/meta/bibliography.rs b/library/src/meta/bibliography.rs
index 65bdafb6..8549624a 100644
--- a/library/src/meta/bibliography.rs
+++ b/library/src/meta/bibliography.rs
@@ -5,14 +5,14 @@ use std::sync::Arc;
use ecow::EcoVec;
use hayagriva::io::{BibLaTeXError, YamlBibliographyError};
-use hayagriva::style::{self, Citation, Database, DisplayString, Formatting};
-use typst::font::{FontStyle, FontWeight};
+use hayagriva::style::{self, Brackets, Citation, Database, DisplayString, Formatting};
+use hayagriva::Entry;
use super::LocalName;
-use crate::layout::{GridNode, ParNode, Sizing, TrackSizings, VNode};
+use crate::layout::{BlockNode, GridNode, ParNode, Sizing, TrackSizings, VNode};
use crate::meta::HeadingNode;
use crate::prelude::*;
-use crate::text::{Hyphenate, TextNode};
+use crate::text::TextNode;
/// A bibliography / reference listing.
///
@@ -48,7 +48,7 @@ pub struct BibliographyNode {
impl BibliographyNode {
/// Find the document's bibliography.
pub fn find(introspector: Tracked<Introspector>) -> StrResult<Self> {
- let mut iter = introspector.locate(Selector::node::<Self>()).into_iter();
+ let mut iter = introspector.query(Selector::node::<Self>()).into_iter();
let Some(node) = iter.next() else {
return Err("the document does not contain a bibliography".into());
};
@@ -63,7 +63,7 @@ impl BibliographyNode {
/// Whether the bibliography contains the given key.
pub fn has(vt: &Vt, key: &str) -> bool {
vt.introspector
- .locate(Selector::node::<Self>())
+ .query(Selector::node::<Self>())
.into_iter()
.flat_map(|node| load(vt.world(), &node.to::<Self>().unwrap().path()))
.flatten()
@@ -98,24 +98,19 @@ impl Synthesize for BibliographyNode {
impl Show for BibliographyNode {
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
const COLUMN_GUTTER: Em = Em::new(0.65);
- const ROW_GUTTER: Em = Em::new(1.0);
const INDENT: Em = Em::new(1.5);
let works = match Works::new(vt) {
Ok(works) => works,
- Err(error) => {
- if vt.locatable() {
- bail!(self.span(), error)
- } else {
- return Ok(TextNode::packed("bibliography"));
- }
- }
+ Err(error) if vt.locatable() => bail!(self.span(), error),
+ Err(_) => Arc::new(Works::default()),
};
let mut seq = vec![];
if let Some(title) = self.title(styles) {
let title = title.clone().unwrap_or_else(|| {
TextNode::packed(self.local_name(TextNode::lang_in(styles)))
+ .spanned(self.span())
});
seq.push(
@@ -126,6 +121,7 @@ impl Show for BibliographyNode {
);
}
+ let row_gutter = BlockNode::below_in(styles).amount();
if works.references.iter().any(|(prefix, _)| prefix.is_some()) {
let mut cells = vec![];
for (prefix, reference) in &works.references {
@@ -133,19 +129,18 @@ impl Show for BibliographyNode {
cells.push(reference.clone());
}
+ seq.push(VNode::new(row_gutter).with_weakness(3).pack());
seq.push(
GridNode::new(cells)
.with_columns(TrackSizings(vec![Sizing::Auto; 2]))
.with_column_gutter(TrackSizings(vec![COLUMN_GUTTER.into()]))
- .with_row_gutter(TrackSizings(vec![ROW_GUTTER.into()]))
+ .with_row_gutter(TrackSizings(vec![row_gutter.into()]))
.pack(),
);
} else {
let mut entries = vec![];
- for (i, (_, reference)) in works.references.iter().enumerate() {
- if i > 0 {
- entries.push(VNode::new(ROW_GUTTER.into()).with_weakness(1).pack());
- }
+ for (_, reference) in &works.references {
+ entries.push(VNode::new(row_gutter).with_weakness(3).pack());
entries.push(reference.clone());
}
@@ -204,13 +199,17 @@ impl BibliographyStyle {
#[node(Locatable, Synthesize, Show)]
pub struct CiteNode {
/// The citation key.
- #[required]
- pub key: EcoString,
+ #[variadic]
+ pub keys: Vec<EcoString>,
/// A supplement for the citation such as page or chapter number.
#[positional]
pub supplement: Option<Content>,
+ /// Whether the citation should include brackets.
+ #[default(true)]
+ pub brackets: bool,
+
/// The citation style.
///
/// When set to `{auto}`, automatically picks the preferred citation style
@@ -221,6 +220,7 @@ pub struct CiteNode {
impl Synthesize for CiteNode {
fn synthesize(&mut self, _: &Vt, styles: StyleChain) {
self.push_supplement(self.supplement(styles));
+ self.push_brackets(self.brackets(styles));
self.push_style(self.style(styles));
}
}
@@ -230,17 +230,12 @@ impl Show for CiteNode {
let id = self.0.stable_id().unwrap();
let works = match Works::new(vt) {
Ok(works) => works,
- Err(error) => {
- if vt.locatable() {
- bail!(self.span(), error)
- } else {
- return Ok(TextNode::packed("citation"));
- }
- }
+ Err(error) if vt.locatable() => bail!(self.span(), error),
+ Err(_) => Arc::new(Works::default()),
};
let Some(citation) = works.citations.get(&id).cloned() else {
- return Ok(TextNode::packed("citation"));
+ return Ok(TextNode::packed("[1]"));
};
citation
@@ -268,6 +263,7 @@ pub enum CitationStyle {
}
/// Fully formatted citations and references.
+#[derive(Default)]
pub struct Works {
citations: HashMap<StableId, Option<Content>>,
references: Vec<(Option<Content>, Content)>,
@@ -277,20 +273,8 @@ impl Works {
/// Prepare all things need to cite a work or format a bibliography.
pub fn new(vt: &Vt) -> StrResult<Arc<Self>> {
let bibliography = BibliographyNode::find(vt.introspector)?;
- let style = bibliography.style(StyleChain::default());
- let citations = vt
- .locate_node::<CiteNode>()
- .map(|node| {
- (
- node.0.stable_id().unwrap(),
- node.key(),
- node.supplement(StyleChain::default()),
- node.style(StyleChain::default())
- .unwrap_or(style.default_citation_style()),
- )
- })
- .collect();
- Ok(create(vt.world(), &bibliography.path(), style, citations))
+ let citations = vt.query_node::<CiteNode>().collect();
+ Ok(create(vt.world(), &bibliography, citations))
}
}
@@ -298,21 +282,37 @@ impl Works {
#[comemo::memoize]
fn create(
world: Tracked<dyn World>,
- path: &str,
- style: BibliographyStyle,
- citations: Vec<(StableId, EcoString, Option<Content>, CitationStyle)>,
+ bibliography: &BibliographyNode,
+ citations: Vec<&CiteNode>,
) -> Arc<Works> {
- let entries = load(world, path).unwrap();
+ let entries = load(world, &bibliography.path()).unwrap();
+ let style = bibliography.style(StyleChain::default());
+ let bib_id = bibliography.0.stable_id().unwrap();
+ let ref_id = |target: &Entry| {
+ let i = entries
+ .iter()
+ .position(|entry| entry.key() == target.key())
+ .unwrap_or_default();
+ bib_id.variant(i as u64)
+ };
let mut db = Database::new();
+ let mut ids = HashMap::new();
let mut preliminary = vec![];
- for (id, key, supplement, style) in citations {
- let entry = entries.iter().find(|entry| entry.key() == key);
- if let Some(entry) = &entry {
- db.push(entry);
- }
- preliminary.push((id, entry, supplement, style));
+ for citation in citations {
+ let cite_id = citation.0.stable_id().unwrap();
+ let entries = citation
+ .keys()
+ .into_iter()
+ .map(|key| {
+ let entry = entries.iter().find(|entry| entry.key() == key)?;
+ ids.entry(entry.key()).or_insert(cite_id);
+ db.push(entry);
+ Some(entry)
+ })
+ .collect::<Option<Vec<_>>>();
+ preliminary.push((citation, entries));
}
let mut current = CitationStyle::Numerical;
@@ -321,34 +321,71 @@ fn create(
let citations = preliminary
.into_iter()
- .map(|(id, result, supplement, style)| {
- let formatted = result.map(|entry| {
- if style != current {
- current = style;
- citation_style = match style {
- CitationStyle::Numerical => Box::new(style::Numerical::new()),
- CitationStyle::Alphanumerical => {
- Box::new(style::Alphanumerical::new())
- }
- CitationStyle::AuthorDate => {
- Box::new(style::ChicagoAuthorDate::new())
- }
- CitationStyle::AuthorTitle => Box::new(style::AuthorTitle::new()),
- CitationStyle::Keys => Box::new(style::Keys::new()),
- };
+ .map(|(citation, cited)| {
+ let id = citation.0.stable_id().unwrap();
+ let Some(cited) = cited else { return (id, None) };
+
+ let mut supplement = citation.supplement(StyleChain::default());
+ let brackets = citation.brackets(StyleChain::default());
+ let style = citation
+ .style(StyleChain::default())
+ .unwrap_or(style.default_citation_style());
+
+ if style != current {
+ current = style;
+ citation_style = match style {
+ CitationStyle::Numerical => Box::new(style::Numerical::new()),
+ CitationStyle::Alphanumerical => {
+ Box::new(style::Alphanumerical::new())
+ }
+ CitationStyle::AuthorDate => {
+ Box::new(style::ChicagoAuthorDate::new())
+ }
+ CitationStyle::AuthorTitle => Box::new(style::AuthorTitle::new()),
+ CitationStyle::Keys => Box::new(style::Keys::new()),
+ };
+ }
+
+ let len = cited.len();
+ let mut content = Content::empty();
+ for (i, entry) in cited.into_iter().enumerate() {
+ let supplement = if i + 1 == len { supplement.take() } else { None };
+ let mut display = db
+ .citation(
+ &mut *citation_style,
+ &[Citation {
+ entry,
+ supplement: supplement.is_some().then(|| SUPPLEMENT),
+ }],
+ )
+ .display;
+
+ if brackets && len == 1 {
+ display = display.with_default_brackets(&*citation_style);
}
- let citation = db.citation(
- &mut *citation_style,
- &[Citation {
- entry,
- supplement: supplement.is_some().then(|| SUPPLEMENT),
- }],
- );
- let bracketed = citation.display.with_default_brackets(&*citation_style);
- format_display_string(&bracketed, supplement)
- });
- (id, formatted)
+ if i > 0 {
+ content += TextNode::packed(",\u{a0}");
+ }
+
+ // Format and link to the reference entry.
+ content += format_display_string(&display, supplement)
+ .linked(Link::Node(ref_id(entry)));
+ }
+
+ if brackets && len > 1 {
+ content = match citation_style.brackets() {
+ Brackets::None => content,
+ Brackets::Round => {
+ TextNode::packed('(') + content + TextNode::packed(')')
+ }
+ Brackets::Square => {
+ TextNode::packed('[') + content + TextNode::packed(']')
+ }
+ };
+ }
+
+ (id, Some(content))
})
.collect();
@@ -363,11 +400,26 @@ fn create(
.bibliography(&*bibliography_style, None)
.into_iter()
.map(|reference| {
+ // Make link from citation to here work.
+ let backlink = {
+ let mut content = Content::empty();
+ content.set_stable_id(ref_id(&reference.entry));
+ MetaNode::set_data(vec![Meta::Node(content)])
+ };
+
let prefix = reference.prefix.map(|prefix| {
+ // Format and link to first citation.
let bracketed = prefix.with_default_brackets(&*citation_style);
format_display_string(&bracketed, None)
+ .linked(Link::Node(ids[reference.entry.key()]))
+ .styled(backlink.clone())
});
- let reference = format_display_string(&reference.display, None);
+
+ let mut reference = format_display_string(&reference.display, None);
+ if prefix.is_none() {
+ reference = reference.styled(backlink);
+ }
+
(prefix, reference)
})
.collect();
@@ -443,28 +495,27 @@ fn format_display_string(
continue;
}
- let mut styles = StyleMap::new();
+ let mut content = if segment == SUPPLEMENT && supplement.is_some() {
+ supplement.take().unwrap_or_default()
+ } else {
+ TextNode::packed(segment)
+ };
+
for (range, fmt) in &string.formatting {
if !range.contains(&start) {
continue;
}
- styles.set(match fmt {
- Formatting::Bold => TextNode::set_weight(FontWeight::BOLD),
- Formatting::Italic => TextNode::set_style(FontStyle::Italic),
- Formatting::NoHyphenation => {
- TextNode::set_hyphenate(Hyphenate(Smart::Custom(false)))
+ content = match fmt {
+ Formatting::Bold => content.strong(),
+ Formatting::Italic => content.emph(),
+ Formatting::Link(link) => {
+ content.linked(Link::Dest(Destination::Url(link.as_str().into())))
}
- });
+ };
}
- let content = if segment == SUPPLEMENT && supplement.is_some() {
- supplement.take().unwrap_or_default()
- } else {
- TextNode::packed(segment)
- };
-
- seq.push(content.styled_with_map(styles));
+ seq.push(content);
start = stop;
}
diff --git a/library/src/meta/figure.rs b/library/src/meta/figure.rs
index 562e43bb..a7668ffb 100644
--- a/library/src/meta/figure.rs
+++ b/library/src/meta/figure.rs
@@ -66,7 +66,7 @@ impl Synthesize for FigureNode {
if numbering.is_some() {
number = NonZeroUsize::new(
1 + vt
- .locate_node::<Self>()
+ .query_node::<Self>()
.take_while(|figure| figure.0.stable_id() != my_id)
.filter(|figure| figure.element() == element)
.count(),
diff --git a/library/src/meta/heading.rs b/library/src/meta/heading.rs
index 44a940f8..527a93a3 100644
--- a/library/src/meta/heading.rs
+++ b/library/src/meta/heading.rs
@@ -91,7 +91,7 @@ impl Synthesize for HeadingNode {
if numbering.is_some() {
// Advance past existing headings.
for heading in vt
- .locate_node::<Self>()
+ .query_node::<Self>()
.take_while(|figure| figure.0.stable_id() != my_id)
{
if heading.numbering(StyleChain::default()).is_some() {
diff --git a/library/src/meta/link.rs b/library/src/meta/link.rs
index bca1945a..e9b8bcc6 100644
--- a/library/src/meta/link.rs
+++ b/library/src/meta/link.rs
@@ -86,7 +86,7 @@ impl Show for LinkNode {
impl Finalize for LinkNode {
fn finalize(&self, realized: Content, _: StyleChain) -> Content {
realized
- .linked(self.dest())
+ .linked(Link::Dest(self.dest()))
.styled(TextNode::set_hyphenate(Hyphenate(Smart::Custom(false))))
}
}
diff --git a/library/src/meta/outline.rs b/library/src/meta/outline.rs
index 216f7a90..1a3e8606 100644
--- a/library/src/meta/outline.rs
+++ b/library/src/meta/outline.rs
@@ -76,9 +76,13 @@ pub struct OutlineNode {
impl Synthesize for OutlineNode {
fn synthesize(&mut self, vt: &Vt, _: StyleChain) {
let headings = vt
- .locate_node::<HeadingNode>()
- .filter(|node| node.outlined(StyleChain::default()))
- .cloned()
+ .introspector
+ .query(Selector::Node(
+ NodeId::of::<HeadingNode>(),
+ Some(dict! { "outlined" => true }),
+ ))
+ .into_iter()
+ .map(|node| node.to::<HeadingNode>().unwrap().clone())
.collect();
self.push_headings(headings);
@@ -91,6 +95,7 @@ impl Show for OutlineNode {
if let Some(title) = self.title(styles) {
let title = title.clone().unwrap_or_else(|| {
TextNode::packed(self.local_name(TextNode::lang_in(styles)))
+ .spanned(self.span())
});
seq.push(
@@ -107,6 +112,7 @@ impl Show for OutlineNode {
let mut ancestors: Vec<&HeadingNode> = vec![];
for heading in self.headings().iter() {
+ let stable_id = heading.0.stable_id().unwrap();
if !heading.outlined(StyleChain::default()) {
continue;
}
@@ -123,11 +129,6 @@ impl Show for OutlineNode {
ancestors.pop();
}
- // Adjust the link destination a bit to the topleft so that the
- // heading is fully visible.
- let mut loc = heading.0.expect_field::<Location>("location");
- loc.pos -= Point::splat(Abs::pt(10.0));
-
// Add hidden ancestors numberings to realize the indent.
if indent {
let mut hidden = Content::empty();
@@ -155,7 +156,7 @@ impl Show for OutlineNode {
};
// Add the numbering and section name.
- seq.push(start.linked(Destination::Internal(loc)));
+ seq.push(start.linked(Link::Node(stable_id)));
// Add filler symbols between the section name and page number.
if let Some(filler) = self.fill(styles) {
@@ -172,8 +173,9 @@ impl Show for OutlineNode {
}
// Add the page number and linebreak.
- let end = TextNode::packed(eco_format!("{}", loc.page));
- seq.push(end.linked(Destination::Internal(loc)));
+ let page = vt.introspector.page(stable_id).unwrap();
+ let end = TextNode::packed(eco_format!("{}", page));
+ seq.push(end.linked(Link::Node(stable_id)));
seq.push(LinebreakNode::new().pack());
ancestors.push(heading);
}
diff --git a/library/src/meta/reference.rs b/library/src/meta/reference.rs
index e84da56b..1616adb3 100644
--- a/library/src/meta/reference.rs
+++ b/library/src/meta/reference.rs
@@ -68,14 +68,14 @@ impl Show for RefNode {
let target = self.target();
let supplement = self.supplement(styles);
- let matches: Vec<_> = vt.locate(Selector::Label(self.target())).collect();
+ let matches = vt.introspector.query(Selector::Label(self.target()));
if !vt.locatable() || BibliographyNode::has(vt, &target.0) {
if !matches.is_empty() {
bail!(self.span(), "label occurs in the document and its bibliography");
}
- return Ok(CiteNode::new(target.0)
+ return Ok(CiteNode::new(vec![target.0])
.with_supplement(match supplement {
Smart::Custom(Some(Supplement::Content(content))) => Some(content),
_ => None,
@@ -133,8 +133,7 @@ impl Show for RefNode {
bail!(self.span(), "cannot reference {}", target.id().name);
};
- let loc = target.expect_field::<Location>("location");
- Ok(formatted.linked(Destination::Internal(loc)))
+ Ok(formatted.linked(Link::Node(target.stable_id().unwrap())))
}
}
diff --git a/library/src/shared/ext.rs b/library/src/shared/ext.rs
index e335b4c8..14674c9d 100644
--- a/library/src/shared/ext.rs
+++ b/library/src/shared/ext.rs
@@ -15,8 +15,8 @@ pub trait ContentExt {
/// Underline this content.
fn underlined(self) -> Self;
- /// Link the content to a destination.
- fn linked(self, dest: Destination) -> Self;
+ /// Link the content somewhere.
+ fn linked(self, link: Link) -> Self;
/// Set alignments for this content.
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self;
@@ -41,8 +41,8 @@ impl ContentExt for Content {
UnderlineNode::new(self).pack()
}
- fn linked(self, dest: Destination) -> Self {
- self.styled(MetaNode::set_data(vec![Meta::Link(dest)]))
+ fn linked(self, link: Link) -> Self {
+ self.styled(MetaNode::set_data(vec![Meta::Link(link)]))
}
fn aligned(self, aligns: Axes<Option<GenAlign>>) -> Self {