wip: bibliography entry

This commit is contained in:
xkevio 2025-02-19 17:26:58 +01:00
parent a543ee9445
commit f4b02fa705

View File

@ -16,6 +16,7 @@ use hayagriva::{
}; };
use indexmap::IndexMap; use indexmap::IndexMap;
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use typst_macros::{cast, scope};
use typst_syntax::{Span, Spanned}; use typst_syntax::{Span, Spanned};
use typst_utils::{Get, ManuallyHash, NonZeroExt, PicoStr}; use typst_utils::{Get, ManuallyHash, NonZeroExt, PicoStr};
@ -28,8 +29,8 @@ use crate::foundations::{
}; };
use crate::introspection::{Introspector, Locatable, Location}; use crate::introspection::{Introspector, Locatable, Location};
use crate::layout::{ use crate::layout::{
BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem, BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, Length,
Sides, Sizing, TrackSizings, PadElem, Rel, Sides, Sizing, Spacing, TrackSizings,
}; };
use crate::loading::{DataSource, Load}; use crate::loading::{DataSource, Load};
use crate::model::{ use crate::model::{
@ -85,7 +86,7 @@ use crate::World;
/// ///
/// #bibliography("works.bib") /// #bibliography("works.bib")
/// ``` /// ```
#[elem(Locatable, Synthesize, Show, ShowSet, LocalName)] #[elem(scope, Locatable, Synthesize, Show, ShowSet, LocalName)]
pub struct BibliographyElem { pub struct BibliographyElem {
/// One or multiple paths to or raw bytes for Hayagriva `.yml` and/or /// One or multiple paths to or raw bytes for Hayagriva `.yml` and/or
/// BibLaTeX `.bib` files. /// BibLaTeX `.bib` files.
@ -152,6 +153,12 @@ pub struct BibliographyElem {
pub region: Option<Region>, pub region: Option<Region>,
} }
#[scope]
impl BibliographyElem {
#[elem]
type BibliographyEntry;
}
impl BibliographyElem { impl BibliographyElem {
/// Find the document's bibliography. /// Find the document's bibliography.
pub fn find(introspector: Tracked<Introspector>) -> StrResult<Packed<Self>> { pub fn find(introspector: Tracked<Introspector>) -> StrResult<Packed<Self>> {
@ -220,6 +227,8 @@ impl Show for Packed<BibliographyElem> {
); );
} }
// TODO: Does this get the keys in the same order as the loop below with references?
let keys = BibliographyElem::keys(engine.introspector);
let works = Works::generate(engine).at(span)?; let works = Works::generate(engine).at(span)?;
let references = works let references = works
.references .references
@ -229,17 +238,24 @@ impl Show for Packed<BibliographyElem> {
if references.iter().any(|(prefix, _)| prefix.is_some()) { if references.iter().any(|(prefix, _)| prefix.is_some()) {
let row_gutter = ParElem::spacing_in(styles); let row_gutter = ParElem::spacing_in(styles);
let mut cells = vec![]; let mut cells = vec![];
for (prefix, reference) in references {
cells.push(GridChild::Item(GridItem::Cell( // FIXME: Not quite correct as `prefix` and `body` are now one grid cell instead of two (how to "split" content?)!
Packed::new(GridCell::new(prefix.clone().unwrap_or_default())) for (idx, (prefix, reference)) in references.iter().enumerate() {
.spanned(span), let indent =
))); if works.hanging_indent { Some(INDENT.into()) } else { None };
cells.push(GridChild::Item(GridItem::Cell( let entry = BibliographyEntry::new(keys[idx].0, reference.clone())
Packed::new(GridCell::new(reference.clone())).spanned(span), .with_prefix(prefix.clone())
))); .with_indent(indent);
cells.push(
entry
.pack()
.try_into()
.expect("todo: conversion content to GridChild"),
);
} }
seq.push( seq.push(
GridElem::new(cells) GridElem::new(cells)
.with_columns(TrackSizings(smallvec![Sizing::Auto; 2])) .with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
@ -249,20 +265,9 @@ impl Show for Packed<BibliographyElem> {
.spanned(span), .spanned(span),
); );
} else { } else {
for (_, reference) in references { for (idx, (_, reference)) in references.iter().enumerate() {
let realized = reference.clone(); let entry = BibliographyEntry::new(keys[idx].0, reference.clone());
let block = if works.hanging_indent { seq.push(entry.pack().spanned(span));
let body = HElem::new((-INDENT).into()).pack() + realized;
let inset = Sides::default()
.with(TextElem::dir_in(styles).start(), Some(INDENT.into()));
BlockElem::new()
.with_body(Some(BlockBody::Content(body)))
.with_inset(inset)
} else {
BlockElem::new().with_body(Some(BlockBody::Content(realized)))
};
seq.push(block.pack().spanned(span));
} }
} }
@ -350,6 +355,67 @@ impl Debug for Bibliography {
} }
} }
/// Represents a single entry in a bibliography.
///
/// Exposes the citation key, the citation prefix and the formatted entry.
#[elem(scope, name = "entry", title = "Bibliography Entry", Show)]
pub struct BibliographyEntry {
/// The citation key that identifies the entry in the bibliography.
#[required]
pub key: Label,
/// The fully formatted entry body.
#[required]
pub body: Content,
/// Optional prefix for citation styles which use them, e.g., IEEE.
pub prefix: Option<Content>,
/// Whether the citation style has a hanging indent.
indent: Option<Rel<Length>>,
}
#[scope]
impl BibliographyEntry {}
impl Show for Packed<BibliographyEntry> {
#[typst_macros::time(name = "bibliography.entry", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
if self.prefix.is_some() {
let prefix = GridChild::Item(GridItem::Cell(
Packed::new(GridCell::new(self.prefix(styles).unwrap()))
.spanned(self.span()),
))
.into_value();
let body = GridChild::Item(GridItem::Cell(
Packed::new(GridCell::new(self.body.clone())).spanned(self.span()),
))
.into_value();
return Ok(Content::sequence([prefix.display(), body.display()]));
} else {
let block = if let Some(indent) = self.indent(styles) {
let body = HElem::new(Spacing::Rel(-indent)).pack() + self.body.clone();
let inset =
Sides::default().with(TextElem::dir_in(styles).start(), Some(indent));
BlockElem::new()
.with_body(Some(BlockBody::Content(body)))
.with_inset(inset)
} else {
BlockElem::new().with_body(Some(BlockBody::Content(self.body.clone())))
};
return Ok(block.pack().spanned(self.span()));
}
}
}
cast! {
BibliographyEntry,
v: Content => v.unpack::<Self>().map_err(|_| "expected bibliography entry")?
}
/// Decode on library from one data source. /// Decode on library from one data source.
fn decode_library(source: &DataSource, data: &Bytes) -> StrResult<Library> { fn decode_library(source: &DataSource, data: &Bytes) -> StrResult<Library> {
let src = data.as_str().map_err(FileError::from)?; let src = data.as_str().map_err(FileError::from)?;