Content rework 2 - Electric Boogaloo (#2504)
This commit is contained in:
parent
8fd546760c
commit
c0f6d2004a
22
Cargo.lock
generated
22
Cargo.lock
generated
@ -930,6 +930,15 @@ dependencies = [
|
||||
"ahash 0.7.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e"
|
||||
dependencies = [
|
||||
"ahash 0.8.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.2"
|
||||
@ -1398,6 +1407,17 @@ dependencies = [
|
||||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lasso"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4644821e1c3d7a560fe13d842d13f587c07348a1a05d3a797152d41c90c56df2"
|
||||
dependencies = [
|
||||
"ahash 0.8.6",
|
||||
"dashmap",
|
||||
"hashbrown 0.13.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
@ -2881,6 +2901,7 @@ dependencies = [
|
||||
"image",
|
||||
"indexmap 2.0.2",
|
||||
"kurbo",
|
||||
"lasso",
|
||||
"log",
|
||||
"miniz_oxide",
|
||||
"once_cell",
|
||||
@ -2893,6 +2914,7 @@ dependencies = [
|
||||
"rustybuzz",
|
||||
"serde",
|
||||
"siphasher",
|
||||
"smallvec",
|
||||
"stacker",
|
||||
"subsetter",
|
||||
"svg2pdf",
|
||||
|
@ -89,7 +89,7 @@ fn format(elements: Vec<Content>, command: &QueryCommand) -> StrResult<String> {
|
||||
let mapped: Vec<_> = elements
|
||||
.into_iter()
|
||||
.filter_map(|c| match &command.field {
|
||||
Some(field) => c.field(field),
|
||||
Some(field) => c.get_by_name(field),
|
||||
_ => Some(c.into_value()),
|
||||
})
|
||||
.collect();
|
||||
|
@ -85,10 +85,10 @@ pub fn analyze_labels(
|
||||
|
||||
// Labels in the document.
|
||||
for elem in introspector.all() {
|
||||
let Some(label) = elem.label().cloned() else { continue };
|
||||
let Some(label) = elem.label() else { continue };
|
||||
let details = elem
|
||||
.field("caption")
|
||||
.or_else(|| elem.field("body"))
|
||||
.get_by_name("caption")
|
||||
.or_else(|| elem.get_by_name("body"))
|
||||
.and_then(|field| match field {
|
||||
Value::Content(content) => Some(content),
|
||||
_ => None,
|
||||
@ -103,7 +103,7 @@ pub fn analyze_labels(
|
||||
|
||||
// Bibliography keys.
|
||||
for (key, detail) in (items.bibliography_keys)(introspector.track()) {
|
||||
output.push((Label(key), detail));
|
||||
output.push((Label::new(&key), detail));
|
||||
}
|
||||
|
||||
(output, split)
|
||||
|
@ -1119,11 +1119,11 @@ impl<'a> CompletionContext<'a> {
|
||||
eco_format!(
|
||||
"{}{}{}",
|
||||
if open { "<" } else { "" },
|
||||
label.0,
|
||||
label.as_str(),
|
||||
if close { ">" } else { "" }
|
||||
)
|
||||
}),
|
||||
label: label.0,
|
||||
label: label.as_str().into(),
|
||||
detail,
|
||||
});
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ fn label_tooltip(
|
||||
};
|
||||
|
||||
for (label, detail) in analyze_labels(world, frames).0 {
|
||||
if label.0 == target {
|
||||
if label.as_str() == target {
|
||||
return Some(Tooltip::Text(detail?));
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ roxmltree = "0.18"
|
||||
rustybuzz = "0.10"
|
||||
serde_json = "1"
|
||||
serde_yaml = "0.9"
|
||||
smallvec = "1.10"
|
||||
smallvec = { version = "1.11.1", features = ["union", "const_generics", "const_new"] }
|
||||
syntect = { version = "5", default-features = false, features = ["parsing", "regex-fancy", "plist-load", "yaml-load"] }
|
||||
time = { version = "0.3.20", features = ["formatting"] }
|
||||
toml = { version = "0.8" }
|
||||
|
@ -66,6 +66,7 @@ pub enum Encoding {
|
||||
}
|
||||
|
||||
/// A value that can be read from a file.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum Readable {
|
||||
/// A decoded string.
|
||||
Str(Str),
|
||||
|
@ -38,6 +38,9 @@ pub struct AlignElem {
|
||||
impl Show for AlignElem {
|
||||
#[tracing::instrument(name = "AlignElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(Self::set_alignment(self.alignment(styles))))
|
||||
Ok(self
|
||||
.body()
|
||||
.clone()
|
||||
.styled(Self::set_alignment(self.alignment(styles))))
|
||||
}
|
||||
}
|
||||
|
@ -107,6 +107,7 @@ pub struct EnumElem {
|
||||
/// + Numbering!
|
||||
/// ```
|
||||
#[default(Numbering::Pattern(NumberingPattern::from_str("1.").unwrap()))]
|
||||
#[borrowed]
|
||||
pub numbering: Numbering,
|
||||
|
||||
/// Which number to start the enumeration with.
|
||||
@ -215,7 +216,7 @@ impl Layout for EnumElem {
|
||||
ParElem::leading_in(styles).into()
|
||||
} else {
|
||||
self.spacing(styles)
|
||||
.unwrap_or_else(|| BlockElem::below_in(styles).amount())
|
||||
.unwrap_or_else(|| *BlockElem::below_in(styles).amount())
|
||||
};
|
||||
|
||||
let mut cells = vec![];
|
||||
@ -238,7 +239,7 @@ impl Layout for EnumElem {
|
||||
parents.pop();
|
||||
content
|
||||
} else {
|
||||
match &numbering {
|
||||
match numbering {
|
||||
Numbering::Pattern(pattern) => {
|
||||
TextElem::packed(pattern.apply_kth(parents.len(), number))
|
||||
}
|
||||
@ -254,7 +255,7 @@ impl Layout for EnumElem {
|
||||
cells.push(Content::empty());
|
||||
cells.push(resolved);
|
||||
cells.push(Content::empty());
|
||||
cells.push(item.body().styled(Self::set_parents(Parent(number))));
|
||||
cells.push(item.body().clone().styled(Self::set_parents(Parent(number))));
|
||||
number = number.saturating_add(1);
|
||||
}
|
||||
|
||||
@ -301,6 +302,7 @@ cast! {
|
||||
v: Content => v.to::<Self>().cloned().unwrap_or_else(|| Self::new(v.clone())),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
|
||||
struct Parent(usize);
|
||||
|
||||
cast! {
|
||||
|
@ -1,5 +1,7 @@
|
||||
use std::mem;
|
||||
|
||||
use comemo::Prehashed;
|
||||
|
||||
use super::{
|
||||
AlignElem, BlockElem, ColbreakElem, ColumnsElem, ParElem, PlaceElem, Spacing, VElem,
|
||||
};
|
||||
@ -18,7 +20,7 @@ use crate::visualize::{
|
||||
pub struct FlowElem {
|
||||
/// The children that will be arranges into a flow.
|
||||
#[variadic]
|
||||
pub children: Vec<Content>,
|
||||
pub children: Vec<Prehashed<Content>>,
|
||||
}
|
||||
|
||||
impl Layout for FlowElem {
|
||||
@ -37,7 +39,7 @@ impl Layout for FlowElem {
|
||||
}
|
||||
let mut layouter = FlowLayouter::new(regions, styles);
|
||||
|
||||
for mut child in &self.children() {
|
||||
for mut child in self.children().iter().map(|c| &**c) {
|
||||
let outer = styles;
|
||||
let mut styles = styles;
|
||||
if let Some((elem, map)) = child.to_styled() {
|
||||
@ -199,7 +201,7 @@ impl<'a> FlowLayouter<'a> {
|
||||
rel.resolve(styles).relative_to(self.initial.y),
|
||||
v.weakness(styles) > 0,
|
||||
),
|
||||
Spacing::Fr(fr) => FlowItem::Fractional(fr),
|
||||
Spacing::Fr(fr) => FlowItem::Fractional(*fr),
|
||||
},
|
||||
)
|
||||
}
|
||||
@ -701,7 +703,7 @@ fn find_footnotes(notes: &mut Vec<FootnoteElem>, frame: &Frame) {
|
||||
match item {
|
||||
FrameItem::Group(group) => find_footnotes(notes, &group.frame),
|
||||
FrameItem::Meta(Meta::Elem(content), _)
|
||||
if !notes.iter().any(|note| note.0.location() == content.location()) =>
|
||||
if !notes.iter().any(|note| note.location() == content.location()) =>
|
||||
{
|
||||
let Some(footnote) = content.to::<FootnoteElem>() else { continue };
|
||||
notes.push(footnote.clone());
|
||||
|
@ -1,3 +1,5 @@
|
||||
use smallvec::{smallvec, SmallVec};
|
||||
|
||||
use crate::prelude::*;
|
||||
use crate::text::TextElem;
|
||||
|
||||
@ -66,12 +68,14 @@ pub struct GridElem {
|
||||
/// with that many `{auto}`-sized columns. Note that opposed to rows and
|
||||
/// gutters, providing a single track size will only ever create a single
|
||||
/// column.
|
||||
#[borrowed]
|
||||
pub columns: TrackSizings,
|
||||
|
||||
/// The row sizes.
|
||||
///
|
||||
/// If there are more cells than fit the defined rows, the last row is
|
||||
/// repeated until there are no more cells.
|
||||
#[borrowed]
|
||||
pub rows: TrackSizings,
|
||||
|
||||
/// The gaps between rows & columns.
|
||||
@ -85,10 +89,12 @@ pub struct GridElem {
|
||||
let gutter = args.named("gutter")?;
|
||||
args.named("column-gutter")?.or_else(|| gutter.clone())
|
||||
)]
|
||||
#[borrowed]
|
||||
pub column_gutter: TrackSizings,
|
||||
|
||||
/// The gaps between rows. Takes precedence over `gutter`.
|
||||
#[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))]
|
||||
#[borrowed]
|
||||
pub row_gutter: TrackSizings,
|
||||
|
||||
/// The contents of the grid cells.
|
||||
@ -106,12 +112,16 @@ impl Layout for GridElem {
|
||||
styles: StyleChain,
|
||||
regions: Regions,
|
||||
) -> SourceResult<Fragment> {
|
||||
let columns = self.columns(styles);
|
||||
let rows = self.rows(styles);
|
||||
let column_gutter = self.column_gutter(styles);
|
||||
let row_gutter = self.row_gutter(styles);
|
||||
|
||||
// Prepare grid layout by unifying content and gutter tracks.
|
||||
let cells = self.children();
|
||||
let layouter = GridLayouter::new(
|
||||
Axes::new(&self.columns(styles).0, &self.rows(styles).0),
|
||||
Axes::new(&self.column_gutter(styles).0, &self.row_gutter(styles).0),
|
||||
&cells,
|
||||
Axes::new(&columns.0, &rows.0),
|
||||
Axes::new(&column_gutter.0, &row_gutter.0),
|
||||
&self.children,
|
||||
regions,
|
||||
styles,
|
||||
self.span(),
|
||||
@ -124,13 +134,13 @@ impl Layout for GridElem {
|
||||
|
||||
/// Track sizing definitions.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct TrackSizings(pub Vec<Sizing>);
|
||||
pub struct TrackSizings(pub SmallVec<[Sizing; 4]>);
|
||||
|
||||
cast! {
|
||||
TrackSizings,
|
||||
self => self.0.into_value(),
|
||||
sizing: Sizing => Self(vec![sizing]),
|
||||
count: NonZeroUsize => Self(vec![Sizing::Auto; count.get()]),
|
||||
sizing: Sizing => Self(smallvec![sizing]),
|
||||
count: NonZeroUsize => Self(smallvec![Sizing::Auto; count.get()]),
|
||||
values: Array => Self(values.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
use smallvec::smallvec;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Hides content without affecting layout.
|
||||
@ -22,6 +24,6 @@ pub struct HideElem {
|
||||
impl Show for HideElem {
|
||||
#[tracing::instrument(name = "HideElem::show", skip(self))]
|
||||
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(MetaElem::set_data(vec![Meta::Hide])))
|
||||
Ok(self.body().clone().styled(MetaElem::set_data(smallvec![Meta::Hide])))
|
||||
}
|
||||
}
|
||||
|
@ -77,6 +77,7 @@ pub struct ListElem {
|
||||
/// - Items
|
||||
/// - Items
|
||||
/// ```
|
||||
#[borrowed]
|
||||
#[default(ListMarker::Content(vec![TextElem::packed('•')]))]
|
||||
pub marker: ListMarker,
|
||||
|
||||
@ -133,7 +134,7 @@ impl Layout for ListElem {
|
||||
ParElem::leading_in(styles).into()
|
||||
} else {
|
||||
self.spacing(styles)
|
||||
.unwrap_or_else(|| BlockElem::below_in(styles).amount())
|
||||
.unwrap_or_else(|| *BlockElem::below_in(styles).amount())
|
||||
};
|
||||
|
||||
let depth = self.depth(styles);
|
||||
@ -148,7 +149,7 @@ impl Layout for ListElem {
|
||||
cells.push(Content::empty());
|
||||
cells.push(marker.clone());
|
||||
cells.push(Content::empty());
|
||||
cells.push(item.body().styled(Self::set_depth(Depth)));
|
||||
cells.push(item.body().clone().styled(Self::set_depth(Depth)));
|
||||
}
|
||||
|
||||
let layouter = GridLayouter::new(
|
||||
@ -183,7 +184,7 @@ cast! {
|
||||
}
|
||||
|
||||
/// A list's marker.
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum ListMarker {
|
||||
Content(Vec<Content>),
|
||||
Func(Func),
|
||||
@ -221,6 +222,7 @@ cast! {
|
||||
v: Func => Self::Func(v),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash)]
|
||||
struct Depth;
|
||||
|
||||
cast! {
|
||||
|
@ -46,6 +46,7 @@ pub use self::table::*;
|
||||
pub use self::terms::*;
|
||||
pub use self::transform::*;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::mem;
|
||||
|
||||
use typed_arena::Arena;
|
||||
@ -242,16 +243,16 @@ fn realize_root<'a>(
|
||||
scratch: &'a Scratch<'a>,
|
||||
content: &'a Content,
|
||||
styles: StyleChain<'a>,
|
||||
) -> SourceResult<(Content, StyleChain<'a>)> {
|
||||
) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> {
|
||||
if content.can::<dyn LayoutRoot>() && !applicable(content, styles) {
|
||||
return Ok((content.clone(), styles));
|
||||
return Ok((Cow::Borrowed(content), styles));
|
||||
}
|
||||
|
||||
let mut builder = Builder::new(vt, scratch, true);
|
||||
builder.accept(content, styles)?;
|
||||
builder.interrupt_page(Some(styles), true)?;
|
||||
let (pages, shared) = builder.doc.unwrap().pages.finish();
|
||||
Ok((DocumentElem::new(pages.to_vec()).pack(), shared))
|
||||
Ok((Cow::Owned(DocumentElem::new(pages.to_vec()).pack()), shared))
|
||||
}
|
||||
|
||||
/// Realize into an element that is capable of block-level layout.
|
||||
@ -261,7 +262,7 @@ fn realize_block<'a>(
|
||||
scratch: &'a Scratch<'a>,
|
||||
content: &'a Content,
|
||||
styles: StyleChain<'a>,
|
||||
) -> SourceResult<(Content, StyleChain<'a>)> {
|
||||
) -> SourceResult<(Cow<'a, Content>, StyleChain<'a>)> {
|
||||
// These elements implement `Layout` but still require a flow for
|
||||
// proper layout.
|
||||
if content.can::<dyn Layout>()
|
||||
@ -277,14 +278,14 @@ fn realize_block<'a>(
|
||||
&& !content.is::<PlaceElem>()
|
||||
&& !applicable(content, styles)
|
||||
{
|
||||
return Ok((content.clone(), styles));
|
||||
return Ok((Cow::Borrowed(content), styles));
|
||||
}
|
||||
|
||||
let mut builder = Builder::new(vt, scratch, false);
|
||||
builder.accept(content, styles)?;
|
||||
builder.interrupt_par()?;
|
||||
let (children, shared) = builder.flow.0.finish();
|
||||
Ok((FlowElem::new(children.to_vec()).pack(), shared))
|
||||
Ok((Cow::Owned(FlowElem::new(children.to_vec()).pack()), shared))
|
||||
}
|
||||
|
||||
/// Builds a document or a flow element from content.
|
||||
@ -509,7 +510,7 @@ impl<'a, 'v, 't> Builder<'a, 'v, 't> {
|
||||
/// Accepts pagebreaks and pages.
|
||||
struct DocBuilder<'a> {
|
||||
/// The page runs built so far.
|
||||
pages: StyleVecBuilder<'a, Content>,
|
||||
pages: StyleVecBuilder<'a, Cow<'a, Content>>,
|
||||
/// Whether to keep a following page even if it is empty.
|
||||
keep_next: bool,
|
||||
/// Whether the next page should be cleared to an even or odd number.
|
||||
@ -517,7 +518,7 @@ struct DocBuilder<'a> {
|
||||
}
|
||||
|
||||
impl<'a> DocBuilder<'a> {
|
||||
fn accept(&mut self, content: &Content, styles: StyleChain<'a>) -> bool {
|
||||
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
||||
if let Some(pagebreak) = content.to::<PagebreakElem>() {
|
||||
self.keep_next = !pagebreak.weak(styles);
|
||||
self.clear_next = pagebreak.to(styles);
|
||||
@ -528,9 +529,9 @@ impl<'a> DocBuilder<'a> {
|
||||
let elem = if let Some(clear_to) = self.clear_next.take() {
|
||||
let mut page = page.clone();
|
||||
page.push_clear_to(Some(clear_to));
|
||||
page.pack()
|
||||
Cow::Owned(page.pack())
|
||||
} else {
|
||||
content.clone()
|
||||
Cow::Borrowed(content)
|
||||
};
|
||||
|
||||
self.pages.push(elem, styles);
|
||||
@ -571,7 +572,7 @@ impl<'a> FlowBuilder<'a> {
|
||||
|| content.is::<MetaElem>()
|
||||
|| content.is::<PlaceElem>()
|
||||
{
|
||||
self.0.push(content.clone(), styles);
|
||||
self.0.push(Cow::Borrowed(content), styles);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -589,7 +590,7 @@ impl<'a> FlowBuilder<'a> {
|
||||
if !last_was_parbreak && is_tight_list {
|
||||
let leading = ParElem::leading_in(styles);
|
||||
let spacing = VElem::list_attach(leading.into());
|
||||
self.0.push(spacing.pack(), styles);
|
||||
self.0.push(Cow::Owned(spacing.pack()), styles);
|
||||
}
|
||||
|
||||
let (above, below) = if let Some(block) = content.to::<BlockElem>() {
|
||||
@ -598,9 +599,9 @@ impl<'a> FlowBuilder<'a> {
|
||||
(BlockElem::above_in(styles), BlockElem::below_in(styles))
|
||||
};
|
||||
|
||||
self.0.push(above.pack(), styles);
|
||||
self.0.push(content.clone(), styles);
|
||||
self.0.push(below.pack(), styles);
|
||||
self.0.push(Cow::Owned(above.pack()), styles);
|
||||
self.0.push(Cow::Borrowed(content), styles);
|
||||
self.0.push(Cow::Owned(below.pack()), styles);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -616,7 +617,7 @@ impl<'a> ParBuilder<'a> {
|
||||
fn accept(&mut self, content: &'a Content, styles: StyleChain<'a>) -> bool {
|
||||
if content.is::<MetaElem>() {
|
||||
if self.0.has_strong_elements(false) {
|
||||
self.0.push(content.clone(), styles);
|
||||
self.0.push(Cow::Borrowed(content), styles);
|
||||
return true;
|
||||
}
|
||||
} else if content.is::<SpaceElem>()
|
||||
@ -627,7 +628,7 @@ impl<'a> ParBuilder<'a> {
|
||||
|| content.to::<EquationElem>().map_or(false, |elem| !elem.block(styles))
|
||||
|| content.is::<BoxElem>()
|
||||
{
|
||||
self.0.push(content.clone(), styles);
|
||||
self.0.push(Cow::Borrowed(content), styles);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -643,7 +644,7 @@ impl<'a> ParBuilder<'a> {
|
||||
/// Accepts list / enum items, spaces, paragraph breaks.
|
||||
struct ListBuilder<'a> {
|
||||
/// The list items collected so far.
|
||||
items: StyleVecBuilder<'a, Content>,
|
||||
items: StyleVecBuilder<'a, Cow<'a, Content>>,
|
||||
/// Whether the list contains no paragraph breaks.
|
||||
tight: bool,
|
||||
/// Trailing content for which it is unclear whether it is part of the list.
|
||||
@ -668,7 +669,7 @@ impl<'a> ListBuilder<'a> {
|
||||
.next()
|
||||
.map_or(true, |first| first.func() == content.func())
|
||||
{
|
||||
self.items.push(content.clone(), styles);
|
||||
self.items.push(Cow::Borrowed(content), styles);
|
||||
self.tight &= self.staged.drain(..).all(|(t, _)| !t.is::<ParbreakElem>());
|
||||
return true;
|
||||
}
|
||||
@ -685,7 +686,8 @@ impl<'a> ListBuilder<'a> {
|
||||
.iter()
|
||||
.map(|(item, local)| {
|
||||
let item = item.to::<ListItem>().unwrap();
|
||||
item.clone().with_body(item.body().styled_with_map(local.clone()))
|
||||
item.clone()
|
||||
.with_body(item.body().clone().styled_with_map(local.clone()))
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
@ -697,7 +699,8 @@ impl<'a> ListBuilder<'a> {
|
||||
.iter()
|
||||
.map(|(item, local)| {
|
||||
let item = item.to::<EnumItem>().unwrap();
|
||||
item.clone().with_body(item.body().styled_with_map(local.clone()))
|
||||
item.clone()
|
||||
.with_body(item.body().clone().styled_with_map(local.clone()))
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
@ -710,9 +713,9 @@ impl<'a> ListBuilder<'a> {
|
||||
.map(|(item, local)| {
|
||||
let item = item.to::<TermItem>().unwrap();
|
||||
item.clone()
|
||||
.with_term(item.term().styled_with_map(local.clone()))
|
||||
.with_term(item.term().clone().styled_with_map(local.clone()))
|
||||
.with_description(
|
||||
item.description().styled_with_map(local.clone()),
|
||||
item.description().clone().styled_with_map(local.clone()),
|
||||
)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::ptr;
|
||||
use std::str::FromStr;
|
||||
|
||||
@ -172,6 +173,7 @@ pub struct PageElem {
|
||||
/// #set text(fill: rgb("fdfdfd"))
|
||||
/// *Dark mode enabled.*
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub fill: Option<Paint>,
|
||||
|
||||
/// How to [number]($numbering) the pages.
|
||||
@ -188,6 +190,7 @@ pub struct PageElem {
|
||||
///
|
||||
/// #lorem(48)
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub numbering: Option<Numbering>,
|
||||
|
||||
/// The alignment of the page numbering.
|
||||
@ -233,6 +236,7 @@ pub struct PageElem {
|
||||
///
|
||||
/// #lorem(19)
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub header: Option<Content>,
|
||||
|
||||
/// The amount the header is raised into the top margin.
|
||||
@ -263,6 +267,7 @@ pub struct PageElem {
|
||||
///
|
||||
/// #lorem(48)
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub footer: Option<Content>,
|
||||
|
||||
/// The amount the footer is lowered into the bottom margin.
|
||||
@ -286,6 +291,7 @@ pub struct PageElem {
|
||||
/// In the year 2023, we plan to take
|
||||
/// over the world (of typesetting).
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub background: Option<Content>,
|
||||
|
||||
/// Content in the page's foreground.
|
||||
@ -299,6 +305,7 @@ pub struct PageElem {
|
||||
/// "Weak Reject" because they did
|
||||
/// not understand our approach...
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub foreground: Option<Content>,
|
||||
|
||||
/// The contents of the page(s).
|
||||
@ -364,7 +371,7 @@ impl PageElem {
|
||||
});
|
||||
|
||||
// Realize columns.
|
||||
let mut child = self.body();
|
||||
let mut child = self.body().clone();
|
||||
let columns = self.columns(styles);
|
||||
if columns.get() > 1 {
|
||||
child = ColumnsElem::new(child).with_count(columns).pack();
|
||||
@ -388,25 +395,25 @@ impl PageElem {
|
||||
}
|
||||
|
||||
let fill = self.fill(styles);
|
||||
let foreground = self.foreground(styles);
|
||||
let background = self.background(styles);
|
||||
let foreground = Cow::Borrowed(self.foreground(styles));
|
||||
let background = Cow::Borrowed(self.background(styles));
|
||||
let header_ascent = self.header_ascent(styles);
|
||||
let footer_descent = self.footer_descent(styles);
|
||||
let numbering = self.numbering(styles);
|
||||
let numbering_meta = Meta::PageNumbering(numbering.clone().into_value());
|
||||
let number_align = self.number_align(styles);
|
||||
let mut header = self.header(styles);
|
||||
let mut footer = self.footer(styles);
|
||||
let mut header = Cow::Borrowed(self.header(styles));
|
||||
let mut footer = Cow::Borrowed(self.footer(styles));
|
||||
|
||||
// Construct the numbering (for header or footer).
|
||||
let numbering_marginal = numbering.clone().map(|numbering| {
|
||||
let both = match &numbering {
|
||||
let numbering_marginal = Cow::Owned(numbering.as_ref().map(|numbering| {
|
||||
let both = match numbering {
|
||||
Numbering::Pattern(pattern) => pattern.pieces() >= 2,
|
||||
Numbering::Func(_) => true,
|
||||
};
|
||||
|
||||
let mut counter =
|
||||
Counter::new(CounterKey::Page).display(Some(numbering), both);
|
||||
Counter::new(CounterKey::Page).display(Some(numbering.clone()), both);
|
||||
|
||||
// We interpret the Y alignment as selecting header or footer
|
||||
// and then ignore it for aligning the actual number.
|
||||
@ -415,12 +422,12 @@ impl PageElem {
|
||||
}
|
||||
|
||||
counter
|
||||
});
|
||||
}));
|
||||
|
||||
if matches!(number_align.y(), Some(VAlign::Top)) {
|
||||
header = header.or(numbering_marginal);
|
||||
header = if header.is_some() { header } else { numbering_marginal };
|
||||
} else {
|
||||
footer = footer.or(numbering_marginal);
|
||||
footer = if footer.is_some() { footer } else { numbering_marginal };
|
||||
}
|
||||
|
||||
// Post-process pages.
|
||||
@ -455,7 +462,7 @@ impl PageElem {
|
||||
] {
|
||||
tracing::info!("Layouting {name}");
|
||||
|
||||
let Some(content) = marginal else { continue };
|
||||
let Some(content) = &**marginal else { continue };
|
||||
|
||||
let (pos, area, align);
|
||||
if ptr::eq(marginal, &header) {
|
||||
@ -488,14 +495,14 @@ impl PageElem {
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(fill) = &fill {
|
||||
if let Some(fill) = fill {
|
||||
frame.fill(fill.clone());
|
||||
}
|
||||
|
||||
page_counter.visit(vt, frame)?;
|
||||
|
||||
// Add a PDF page label if there is a numbering.
|
||||
if let Some(num) = &numbering {
|
||||
if let Some(num) = numbering {
|
||||
if let Some(page_label) = num.apply_pdf(page_counter.logical()) {
|
||||
frame.push_positionless_meta(Meta::PdfPageLabel(page_label));
|
||||
}
|
||||
@ -657,10 +664,10 @@ pub enum Marginal {
|
||||
|
||||
impl Marginal {
|
||||
/// Resolve the marginal based on the page number.
|
||||
pub fn resolve(&self, vt: &mut Vt, page: usize) -> SourceResult<Content> {
|
||||
pub fn resolve(&self, vt: &mut Vt, page: usize) -> SourceResult<Cow<'_, Content>> {
|
||||
Ok(match self {
|
||||
Self::Content(content) => content.clone(),
|
||||
Self::Func(func) => func.call_vt(vt, [page])?.display(),
|
||||
Self::Content(content) => Cow::Borrowed(content),
|
||||
Self::Func(func) => Cow::Owned(func.call_vt(vt, [page])?.display()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use comemo::Prehashed;
|
||||
use typst::eval::Tracer;
|
||||
use typst::model::DelayedErrors;
|
||||
use unicode_bidi::{BidiInfo, Level as BidiLevel};
|
||||
@ -104,7 +105,7 @@ pub struct ParElem {
|
||||
/// The paragraph's children.
|
||||
#[internal]
|
||||
#[variadic]
|
||||
pub children: Vec<Content>,
|
||||
pub children: Vec<Prehashed<Content>>,
|
||||
}
|
||||
|
||||
impl Construct for ParElem {
|
||||
@ -158,12 +159,12 @@ impl ParElem {
|
||||
let children = par.children();
|
||||
|
||||
// Collect all text into one string for BiDi analysis.
|
||||
let (text, segments, spans) = collect(&children, &styles, consecutive)?;
|
||||
let (text, segments, spans) = collect(children, &styles, consecutive)?;
|
||||
|
||||
// Perform BiDi analysis and then prepare paragraph layout by building a
|
||||
// representation on which we can do line breaking without layouting
|
||||
// each and every line from scratch.
|
||||
let p = prepare(&mut vt, &children, &text, segments, spans, styles, region)?;
|
||||
let p = prepare(&mut vt, children, &text, segments, spans, styles, region)?;
|
||||
|
||||
// Break the paragraph into lines.
|
||||
let lines = linebreak(&vt, &p, region.x - p.hang);
|
||||
@ -246,8 +247,6 @@ pub(crate) struct Preparation<'a> {
|
||||
pub items: Vec<Item<'a>>,
|
||||
/// The span mapper.
|
||||
pub spans: SpanMapper,
|
||||
/// The styles shared by all children.
|
||||
pub styles: StyleChain<'a>,
|
||||
/// Whether to hyphenate if it's the same for all children.
|
||||
pub hyphenate: Option<bool>,
|
||||
/// The text language if it's the same for all children.
|
||||
@ -258,8 +257,16 @@ pub(crate) struct Preparation<'a> {
|
||||
pub justify: bool,
|
||||
/// The paragraph's hanging indent.
|
||||
pub hang: Abs,
|
||||
/// The CJK-latin spacing.
|
||||
/// Whether to add spacing between CJK and Latin characters.
|
||||
pub cjk_latin_spacing: bool,
|
||||
/// Whether font fallback is enabled for this paragraph.
|
||||
pub fallback: bool,
|
||||
/// The leading of the paragraph.
|
||||
pub leading: Abs,
|
||||
/// How to determine line breaks.
|
||||
pub linebreaks: Smart<Linebreaks>,
|
||||
/// The text size.
|
||||
pub size: Abs,
|
||||
}
|
||||
|
||||
impl<'a> Preparation<'a> {
|
||||
@ -525,15 +532,15 @@ impl<'a> Line<'a> {
|
||||
/// string-level preprocessing like case transformations.
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn collect<'a>(
|
||||
children: &'a [Content],
|
||||
children: &'a [Prehashed<Content>],
|
||||
styles: &'a StyleChain<'a>,
|
||||
consecutive: bool,
|
||||
) -> SourceResult<(String, Vec<(Segment<'a>, StyleChain<'a>)>, SpanMapper)> {
|
||||
let mut full = String::new();
|
||||
let mut quoter = Quoter::new();
|
||||
let mut segments = vec![];
|
||||
let mut segments = Vec::with_capacity(2 + children.len());
|
||||
let mut spans = SpanMapper::new();
|
||||
let mut iter = children.iter().peekable();
|
||||
let mut iter = children.iter().map(|c| &**c).peekable();
|
||||
|
||||
let first_line_indent = ParElem::first_line_indent_in(*styles);
|
||||
if !first_line_indent.is_zero()
|
||||
@ -565,9 +572,9 @@ fn collect<'a>(
|
||||
} else if let Some(elem) = child.to::<TextElem>() {
|
||||
let prev = full.len();
|
||||
if let Some(case) = TextElem::case_in(styles) {
|
||||
full.push_str(&case.apply(&elem.text()));
|
||||
full.push_str(&case.apply(elem.text()));
|
||||
} else {
|
||||
full.push_str(&elem.text());
|
||||
full.push_str(elem.text());
|
||||
}
|
||||
Segment::Text(full.len() - prev)
|
||||
} else if let Some(elem) = child.to::<HElem>() {
|
||||
@ -576,7 +583,7 @@ fn collect<'a>(
|
||||
}
|
||||
|
||||
full.push(SPACING_REPLACE);
|
||||
Segment::Spacing(elem.amount())
|
||||
Segment::Spacing(*elem.amount())
|
||||
} else if let Some(elem) = child.to::<LinebreakElem>() {
|
||||
let c = if elem.justify(styles) { '\u{2028}' } else { '\n' };
|
||||
full.push(c);
|
||||
@ -588,7 +595,7 @@ fn collect<'a>(
|
||||
let lang = TextElem::lang_in(styles);
|
||||
let region = TextElem::region_in(styles);
|
||||
let quotes = Quotes::new(
|
||||
"es,
|
||||
quotes,
|
||||
lang,
|
||||
region,
|
||||
SmartquoteElem::alternative_in(styles),
|
||||
@ -656,7 +663,7 @@ fn collect<'a>(
|
||||
/// contained inline-level content.
|
||||
fn prepare<'a>(
|
||||
vt: &mut Vt,
|
||||
children: &'a [Content],
|
||||
children: &'a [Prehashed<Content>],
|
||||
text: &'a str,
|
||||
segments: Vec<(Segment<'a>, StyleChain<'a>)>,
|
||||
spans: SpanMapper,
|
||||
@ -674,7 +681,7 @@ fn prepare<'a>(
|
||||
);
|
||||
|
||||
let mut cursor = 0;
|
||||
let mut items = vec![];
|
||||
let mut items = Vec::with_capacity(segments.len());
|
||||
|
||||
// Shape / layout the children and collect them into items.
|
||||
for (segment, styles) in segments {
|
||||
@ -727,13 +734,16 @@ fn prepare<'a>(
|
||||
bidi,
|
||||
items,
|
||||
spans,
|
||||
styles,
|
||||
hyphenate: shared_get(styles, children, TextElem::hyphenate_in),
|
||||
lang: shared_get(styles, children, TextElem::lang_in),
|
||||
align: AlignElem::alignment_in(styles).resolve(styles).x,
|
||||
justify: ParElem::justify_in(styles),
|
||||
hang: ParElem::hanging_indent_in(styles),
|
||||
cjk_latin_spacing,
|
||||
fallback: TextElem::fallback_in(styles),
|
||||
leading: ParElem::leading_in(styles),
|
||||
linebreaks: ParElem::linebreaks_in(styles),
|
||||
size: TextElem::size_in(styles),
|
||||
})
|
||||
}
|
||||
|
||||
@ -852,7 +862,7 @@ fn is_compatible(a: Script, b: Script) -> bool {
|
||||
/// paragraph.
|
||||
fn shared_get<T: PartialEq>(
|
||||
styles: StyleChain<'_>,
|
||||
children: &[Content],
|
||||
children: &[Prehashed<Content>],
|
||||
getter: fn(StyleChain) -> T,
|
||||
) -> Option<T> {
|
||||
let value = getter(styles);
|
||||
@ -865,8 +875,8 @@ fn shared_get<T: PartialEq>(
|
||||
|
||||
/// Find suitable linebreaks.
|
||||
fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
|
||||
let linebreaks = ParElem::linebreaks_in(p.styles).unwrap_or_else(|| {
|
||||
if ParElem::justify_in(p.styles) {
|
||||
let linebreaks = p.linebreaks.unwrap_or_else(|| {
|
||||
if p.justify {
|
||||
Linebreaks::Optimized
|
||||
} else {
|
||||
Linebreaks::Simple
|
||||
@ -883,7 +893,7 @@ fn linebreak<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
|
||||
/// lines greedily, always taking the longest possible line. This may lead to
|
||||
/// very unbalanced line, but is fast and simple.
|
||||
fn linebreak_simple<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<Line<'a>> {
|
||||
let mut lines = vec![];
|
||||
let mut lines = Vec::with_capacity(16);
|
||||
let mut start = 0;
|
||||
let mut last = None;
|
||||
|
||||
@ -964,8 +974,8 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
|
||||
line: line(vt, p, 0..0, Breakpoint::Mandatory),
|
||||
}];
|
||||
|
||||
let em = TextElem::size_in(p.styles);
|
||||
|
||||
let em = p.size;
|
||||
let mut lines = Vec::with_capacity(16);
|
||||
breakpoints(p, |end, breakpoint| {
|
||||
let k = table.len();
|
||||
let eof = end == p.bidi.text.len();
|
||||
@ -1071,7 +1081,6 @@ fn linebreak_optimized<'a>(vt: &Vt, p: &'a Preparation<'a>, width: Abs) -> Vec<L
|
||||
});
|
||||
|
||||
// Retrace the best path.
|
||||
let mut lines = vec![];
|
||||
let mut idx = table.len() - 1;
|
||||
while idx != 0 {
|
||||
table.truncate(idx + 1);
|
||||
@ -1151,7 +1160,7 @@ fn line<'a>(
|
||||
if hyphen || start < range.end || before.is_empty() {
|
||||
let mut reshaped = shaped.reshape(vt, &p.spans, start..range.end);
|
||||
if hyphen || shy {
|
||||
reshaped.push_hyphen(vt, TextElem::fallback_in(p.styles));
|
||||
reshaped.push_hyphen(vt, p.fallback);
|
||||
}
|
||||
|
||||
if let Some(last_glyph) = reshaped.glyphs.last() {
|
||||
@ -1287,11 +1296,10 @@ fn finalize(
|
||||
.collect::<SourceResult<_>>()?;
|
||||
|
||||
// Prevent orphans.
|
||||
let leading = ParElem::leading_in(p.styles);
|
||||
if frames.len() >= 2 && !frames[1].is_empty() {
|
||||
let second = frames.remove(1);
|
||||
let first = &mut frames[0];
|
||||
merge(first, second, leading);
|
||||
merge(first, second, p.leading);
|
||||
}
|
||||
|
||||
// Prevent widows.
|
||||
@ -1299,7 +1307,7 @@ fn finalize(
|
||||
if len >= 2 && !frames[len - 2].is_empty() {
|
||||
let second = frames.pop().unwrap();
|
||||
let first = frames.last_mut().unwrap();
|
||||
merge(first, second, leading);
|
||||
merge(first, second, p.leading);
|
||||
}
|
||||
|
||||
Ok(Fragment::frames(frames))
|
||||
|
@ -106,7 +106,10 @@ impl Layout for PlaceElem {
|
||||
.at(self.span());
|
||||
}
|
||||
|
||||
let child = self.body().aligned(alignment.unwrap_or_else(|| Align::CENTER));
|
||||
let child = self
|
||||
.body()
|
||||
.clone()
|
||||
.aligned(alignment.unwrap_or_else(|| Align::CENTER));
|
||||
|
||||
let pod = Regions::one(base, Axes::splat(false));
|
||||
let frame = child.layout(vt, styles, pod)?.into_frame();
|
||||
|
@ -1,3 +1,5 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Inserts horizontal spacing into a paragraph.
|
||||
@ -71,7 +73,7 @@ impl Behave for HElem {
|
||||
|
||||
fn larger(
|
||||
&self,
|
||||
prev: &(Content, Behaviour, StyleChain),
|
||||
prev: &(Cow<Content>, Behaviour, StyleChain),
|
||||
styles: StyleChain,
|
||||
) -> bool {
|
||||
let Some(other) = prev.0.to::<Self>() else { return false };
|
||||
@ -173,7 +175,7 @@ impl Behave for VElem {
|
||||
|
||||
fn larger(
|
||||
&self,
|
||||
prev: &(Content, Behaviour, StyleChain),
|
||||
prev: &(Cow<Content>, Behaviour, StyleChain),
|
||||
styles: StyleChain,
|
||||
) -> bool {
|
||||
let Some(other) = prev.0.to::<Self>() else { return false };
|
||||
|
@ -60,7 +60,7 @@ impl Layout for StackElem {
|
||||
for child in self.children() {
|
||||
match child {
|
||||
StackChild::Spacing(kind) => {
|
||||
layouter.layout_spacing(kind);
|
||||
layouter.layout_spacing(*kind);
|
||||
deferred = None;
|
||||
}
|
||||
StackChild::Block(block) => {
|
||||
@ -68,7 +68,7 @@ impl Layout for StackElem {
|
||||
layouter.layout_spacing(kind);
|
||||
}
|
||||
|
||||
layouter.layout_block(vt, &block, styles)?;
|
||||
layouter.layout_block(vt, block, styles)?;
|
||||
deferred = spacing;
|
||||
}
|
||||
}
|
||||
@ -79,7 +79,7 @@ impl Layout for StackElem {
|
||||
}
|
||||
|
||||
/// A child of a stack element.
|
||||
#[derive(Hash)]
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
pub enum StackChild {
|
||||
/// Spacing between other children.
|
||||
Spacing(Spacing),
|
||||
|
@ -1,7 +1,7 @@
|
||||
use typst::eval::{CastInfo, Reflect};
|
||||
|
||||
use crate::layout::{AlignElem, GridLayouter, TrackSizings};
|
||||
use crate::meta::{Figurable, LocalName};
|
||||
use crate::meta::Figurable;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A table of items.
|
||||
@ -38,10 +38,12 @@ use crate::prelude::*;
|
||||
pub struct TableElem {
|
||||
/// The column sizes. See the [grid documentation]($grid) for more
|
||||
/// information on track sizing.
|
||||
#[borrowed]
|
||||
pub columns: TrackSizings,
|
||||
|
||||
/// The row sizes. See the [grid documentation]($grid) for more information
|
||||
/// on track sizing.
|
||||
#[borrowed]
|
||||
pub rows: TrackSizings,
|
||||
|
||||
/// The gaps between rows & columns. See the [grid documentation]($grid) for
|
||||
@ -51,6 +53,7 @@ pub struct TableElem {
|
||||
|
||||
/// The gaps between columns. Takes precedence over `gutter`. See the
|
||||
/// [grid documentation]($grid) for more information on gutters.
|
||||
#[borrowed]
|
||||
#[parse(
|
||||
let gutter = args.named("gutter")?;
|
||||
args.named("column-gutter")?.or_else(|| gutter.clone())
|
||||
@ -60,6 +63,7 @@ pub struct TableElem {
|
||||
/// The gaps between rows. Takes precedence over `gutter`. See the
|
||||
/// [grid documentation]($grid) for more information on gutters.
|
||||
#[parse(args.named("row-gutter")?.or_else(|| gutter.clone()))]
|
||||
#[borrowed]
|
||||
pub row_gutter: TrackSizings,
|
||||
|
||||
/// How to fill the cells.
|
||||
@ -82,6 +86,7 @@ pub struct TableElem {
|
||||
/// [Profit:], [500 €], [1000 €], [1500 €],
|
||||
/// )
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub fill: Celled<Option<Paint>>,
|
||||
|
||||
/// How to align the cells' content.
|
||||
@ -99,6 +104,7 @@ pub struct TableElem {
|
||||
/// [A], [B], [C],
|
||||
/// )
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub align: Celled<Smart<Align>>,
|
||||
|
||||
/// How to [stroke]($stroke) the cells.
|
||||
@ -151,16 +157,20 @@ impl Layout for TableElem {
|
||||
) -> SourceResult<Fragment> {
|
||||
let inset = self.inset(styles);
|
||||
let align = self.align(styles);
|
||||
let columns = self.columns(styles);
|
||||
let rows = self.rows(styles);
|
||||
let column_gutter = self.column_gutter(styles);
|
||||
let row_gutter = self.row_gutter(styles);
|
||||
|
||||
let tracks = Axes::new(self.columns(styles).0, self.rows(styles).0);
|
||||
let gutter = Axes::new(self.column_gutter(styles).0, self.row_gutter(styles).0);
|
||||
let tracks = Axes::new(columns.0.as_slice(), rows.0.as_slice());
|
||||
let gutter = Axes::new(column_gutter.0.as_slice(), row_gutter.0.as_slice());
|
||||
let cols = tracks.x.len().max(1);
|
||||
let cells: Vec<_> = self
|
||||
.children()
|
||||
.into_iter()
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, child)| {
|
||||
let mut child = child.padded(inset);
|
||||
let mut child = child.clone().padded(inset);
|
||||
|
||||
let x = i % cols;
|
||||
let y = i / cols;
|
||||
@ -176,14 +186,8 @@ impl Layout for TableElem {
|
||||
let stroke = self.stroke(styles).map(Stroke::unwrap_or_default);
|
||||
|
||||
// Prepare grid layout by unifying content and gutter tracks.
|
||||
let layouter = GridLayouter::new(
|
||||
tracks.as_deref(),
|
||||
gutter.as_deref(),
|
||||
&cells,
|
||||
regions,
|
||||
styles,
|
||||
self.span(),
|
||||
);
|
||||
let layouter =
|
||||
GridLayouter::new(tracks, gutter, &cells, regions, styles, self.span());
|
||||
|
||||
// Measure the columns and layout the grid row-by-row.
|
||||
let mut layout = layouter.layout(vt)?;
|
||||
@ -321,7 +325,7 @@ impl<T: FromValue> FromValue for Celled<T> {
|
||||
}
|
||||
|
||||
impl LocalName for TableElem {
|
||||
fn local_name(&self, lang: Lang, _: Option<Region>) -> &'static str {
|
||||
fn local_name(lang: Lang, _: Option<Region>) -> &'static str {
|
||||
match lang {
|
||||
Lang::ALBANIAN => "Tabel",
|
||||
Lang::ARABIC => "جدول",
|
||||
|
@ -55,6 +55,7 @@ pub struct TermsElem {
|
||||
/// / Colon: A nice separator symbol.
|
||||
/// ```
|
||||
#[default(HElem::new(Em::new(0.6).into()).with_weak(true).pack())]
|
||||
#[borrowed]
|
||||
pub separator: Content,
|
||||
|
||||
/// The indentation of each item.
|
||||
@ -114,20 +115,20 @@ impl Layout for TermsElem {
|
||||
ParElem::leading_in(styles).into()
|
||||
} else {
|
||||
self.spacing(styles)
|
||||
.unwrap_or_else(|| BlockElem::below_in(styles).amount())
|
||||
.unwrap_or_else(|| *BlockElem::below_in(styles).amount())
|
||||
};
|
||||
|
||||
let mut seq = vec![];
|
||||
for (i, child) in self.children().into_iter().enumerate() {
|
||||
for (i, child) in self.children().iter().enumerate() {
|
||||
if i > 0 {
|
||||
seq.push(VElem::new(gutter).with_weakness(1).pack());
|
||||
}
|
||||
if !indent.is_zero() {
|
||||
seq.push(HElem::new(indent.into()).pack());
|
||||
}
|
||||
seq.push(child.term().strong());
|
||||
seq.push(separator.clone());
|
||||
seq.push(child.description());
|
||||
seq.push(child.term().clone().strong());
|
||||
seq.push((*separator).clone());
|
||||
seq.push(child.description().clone());
|
||||
}
|
||||
|
||||
Content::sequence(seq)
|
||||
|
@ -52,7 +52,7 @@ impl LayoutMath for AccentElem {
|
||||
#[tracing::instrument(skip(ctx))]
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
ctx.style(ctx.style.with_cramped(true));
|
||||
let base = ctx.layout_fragment(&self.base())?;
|
||||
let base = ctx.layout_fragment(self.base())?;
|
||||
ctx.unstyle();
|
||||
|
||||
// Preserve class to preserve automatic spacing.
|
||||
@ -67,7 +67,7 @@ impl LayoutMath for AccentElem {
|
||||
// Forcing the accent to be at least as large as the base makes it too
|
||||
// wide in many case.
|
||||
let Accent(c) = self.accent();
|
||||
let glyph = GlyphFragment::new(ctx, c, self.span());
|
||||
let glyph = GlyphFragment::new(ctx, *c, self.span());
|
||||
let short_fall = ACCENT_SHORT_FALL.scaled(ctx);
|
||||
let variant = glyph.stretch_horizontal(ctx, base.width(), short_fall);
|
||||
let accent = variant.frame;
|
||||
@ -116,6 +116,7 @@ fn attachment(ctx: &MathContext, id: GlyphId, italics_correction: Abs) -> Abs {
|
||||
}
|
||||
|
||||
/// An accent character.
|
||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Accent(char);
|
||||
|
||||
impl Accent {
|
||||
@ -130,7 +131,7 @@ cast! {
|
||||
self => self.0.into_value(),
|
||||
v: char => Self::new(v),
|
||||
v: Content => match v.to::<TextElem>() {
|
||||
Some(elem) => Value::Str(elem.text().into()).cast()?,
|
||||
Some(elem) => Value::Str(elem.text().clone().into()).cast()?,
|
||||
None => bail!("expected text"),
|
||||
},
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ impl LayoutMath for AttachElem {
|
||||
.transpose()
|
||||
};
|
||||
|
||||
let base = ctx.layout_fragment(&self.base())?;
|
||||
let base = ctx.layout_fragment(self.base())?;
|
||||
|
||||
ctx.style(ctx.style.for_superscript());
|
||||
let tl = layout_attachment(ctx, Self::tl)?;
|
||||
@ -91,7 +91,7 @@ pub struct PrimesElem {
|
||||
impl LayoutMath for PrimesElem {
|
||||
#[tracing::instrument(skip(ctx))]
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
match self.count() {
|
||||
match *self.count() {
|
||||
count @ 1..=4 => {
|
||||
let f = ctx.layout_fragment(&TextElem::packed(match count {
|
||||
1 => '′',
|
||||
@ -137,7 +137,7 @@ pub struct ScriptsElem {
|
||||
impl LayoutMath for ScriptsElem {
|
||||
#[tracing::instrument(skip(ctx))]
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
let mut fragment = ctx.layout_fragment(&self.body())?;
|
||||
let mut fragment = ctx.layout_fragment(self.body())?;
|
||||
fragment.set_limits(Limits::Never);
|
||||
ctx.push(fragment);
|
||||
Ok(())
|
||||
@ -166,7 +166,7 @@ pub struct LimitsElem {
|
||||
impl LayoutMath for LimitsElem {
|
||||
#[tracing::instrument(skip(ctx))]
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
let mut fragment = ctx.layout_fragment(&self.body())?;
|
||||
let mut fragment = ctx.layout_fragment(self.body())?;
|
||||
fragment.set_limits(if self.inline(ctx.styles()) {
|
||||
Limits::Always
|
||||
} else {
|
||||
|
@ -97,7 +97,7 @@ pub struct CancelElem {
|
||||
|
||||
impl LayoutMath for CancelElem {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
let body = ctx.layout_fragment(&self.body())?;
|
||||
let body = ctx.layout_fragment(self.body())?;
|
||||
// Use the same math class as the body, in order to preserve automatic spacing around it.
|
||||
let body_class = body.class().unwrap_or(MathClass::Special);
|
||||
let mut body = body.into_frame();
|
||||
@ -146,6 +146,7 @@ impl LayoutMath for CancelElem {
|
||||
}
|
||||
|
||||
/// Defines the cancel line.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum CancelAngle {
|
||||
Angle(Angle),
|
||||
Func(Func),
|
||||
|
@ -27,11 +27,11 @@ pub struct ClassElem {
|
||||
|
||||
impl LayoutMath for ClassElem {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
ctx.style(ctx.style.with_class(self.class()));
|
||||
let mut fragment = ctx.layout_fragment(&self.body())?;
|
||||
ctx.style(ctx.style.with_class(*self.class()));
|
||||
let mut fragment = ctx.layout_fragment(self.body())?;
|
||||
ctx.unstyle();
|
||||
|
||||
fragment.set_class(self.class());
|
||||
fragment.set_class(*self.class());
|
||||
ctx.push(fragment);
|
||||
Ok(())
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use comemo::Prehashed;
|
||||
use ttf_parser::gsub::SubstitutionSubtable;
|
||||
use ttf_parser::math::MathValue;
|
||||
use typst::font::{FontStyle, FontWeight};
|
||||
@ -251,7 +252,7 @@ impl<'a, 'b, 'v> MathContext<'a, 'b, 'v> {
|
||||
// because it will be placed somewhere probably not at the left margin
|
||||
// it will overflow. So emulate an `hbox` instead and allow the paragraph
|
||||
// to extend as far as needed.
|
||||
let frame = ParElem::new(vec![elem])
|
||||
let frame = ParElem::new(vec![Prehashed::new(elem)])
|
||||
.layout(
|
||||
self.vt,
|
||||
self.outer.chain(&self.local),
|
||||
|
@ -29,7 +29,7 @@ pub struct FracElem {
|
||||
impl LayoutMath for FracElem {
|
||||
#[tracing::instrument(skip(ctx))]
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout(ctx, &self.num(), &[self.denom()], false, self.span())
|
||||
layout(ctx, self.num(), std::slice::from_ref(self.denom()), false, self.span())
|
||||
}
|
||||
}
|
||||
|
||||
@ -62,7 +62,7 @@ pub struct BinomElem {
|
||||
|
||||
impl LayoutMath for BinomElem {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout(ctx, &self.upper(), &self.lower(), true, self.span())
|
||||
layout(ctx, self.upper(), self.lower(), true, self.span())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,7 +196,7 @@ pub struct GlyphFragment {
|
||||
pub font_size: Abs,
|
||||
pub class: Option<MathClass>,
|
||||
pub span: Span,
|
||||
pub meta: Vec<Meta>,
|
||||
pub meta: SmallVec<[Meta; 1]>,
|
||||
pub limits: Limits,
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ impl LayoutMath for LrElem {
|
||||
}
|
||||
}
|
||||
|
||||
let mut fragments = ctx.layout_fragments(&body)?;
|
||||
let mut fragments = ctx.layout_fragments(body)?;
|
||||
let axis = scaled!(ctx, axis_height);
|
||||
let max_extent = fragments
|
||||
.iter()
|
||||
|
@ -49,7 +49,7 @@ impl LayoutMath for VecElem {
|
||||
let delim = self.delim(ctx.styles());
|
||||
let frame = layout_vec_body(
|
||||
ctx,
|
||||
&self.children(),
|
||||
self.children(),
|
||||
FixedAlign::Center,
|
||||
self.gap(ctx.styles()),
|
||||
)?;
|
||||
@ -233,7 +233,7 @@ impl LayoutMath for MatElem {
|
||||
let delim = self.delim(ctx.styles());
|
||||
let frame = layout_mat_body(
|
||||
ctx,
|
||||
&rows,
|
||||
rows,
|
||||
augment,
|
||||
Axes::new(self.column_gap(ctx.styles()), self.row_gap(ctx.styles())),
|
||||
self.span(),
|
||||
@ -303,7 +303,7 @@ impl LayoutMath for CasesElem {
|
||||
let delim = self.delim(ctx.styles());
|
||||
let frame = layout_vec_body(
|
||||
ctx,
|
||||
&self.children(),
|
||||
self.children(),
|
||||
FixedAlign::Start,
|
||||
self.gap(ctx.styles()),
|
||||
)?;
|
||||
@ -562,7 +562,7 @@ fn layout_delimiters(
|
||||
|
||||
/// Parameters specifying how augmentation lines
|
||||
/// should be drawn on a matrix.
|
||||
#[derive(Default, Clone, Hash)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
||||
pub struct Augment<T: Numeric = Length> {
|
||||
pub hline: Offsets,
|
||||
pub vline: Offsets,
|
||||
@ -573,7 +573,7 @@ impl Augment<Abs> {
|
||||
fn stroke_or(&self, fallback: FixedStroke) -> FixedStroke {
|
||||
match &self.stroke {
|
||||
Smart::Custom(v) => v.clone().unwrap_or(fallback),
|
||||
_ => fallback,
|
||||
Smart::Auto => fallback,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -594,7 +594,13 @@ impl Fold for Augment<Abs> {
|
||||
type Output = Augment<Abs>;
|
||||
|
||||
fn fold(mut self, outer: Self::Output) -> Self::Output {
|
||||
self.stroke = self.stroke.fold(outer.stroke);
|
||||
// Special case for handling `auto` strokes in subsequent `Augment`.
|
||||
if self.stroke.is_auto() && outer.stroke.is_custom() {
|
||||
self.stroke = outer.stroke;
|
||||
} else {
|
||||
self.stroke = self.stroke.fold(outer.stroke);
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
@ -602,19 +608,22 @@ impl Fold for Augment<Abs> {
|
||||
cast! {
|
||||
Augment,
|
||||
self => {
|
||||
let stroke = self.stroke.unwrap_or_default();
|
||||
// if the stroke is auto and there is only one vertical line,
|
||||
if self.stroke.is_auto() && self.hline.0.is_empty() && self.vline.0.len() == 1 {
|
||||
return self.vline.0[0].into_value();
|
||||
}
|
||||
|
||||
let d = dict! {
|
||||
"hline" => self.hline.into_value(),
|
||||
"vline" => self.vline.into_value(),
|
||||
"stroke" => stroke.into_value()
|
||||
"stroke" => self.stroke.into_value()
|
||||
};
|
||||
|
||||
d.into_value()
|
||||
},
|
||||
v: isize => Augment {
|
||||
hline: Offsets::default(),
|
||||
vline: Offsets(vec![v]),
|
||||
vline: Offsets(smallvec![v]),
|
||||
stroke: Smart::Auto,
|
||||
},
|
||||
mut dict: Dict => {
|
||||
@ -636,14 +645,13 @@ cast! {
|
||||
self => self.into_value(),
|
||||
}
|
||||
|
||||
/// The offsets at which augmentation lines
|
||||
/// should be drawn on a matrix.
|
||||
#[derive(Debug, Default, Clone, Hash)]
|
||||
pub struct Offsets(Vec<isize>);
|
||||
/// The offsets at which augmentation lines should be drawn on a matrix.
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct Offsets(SmallVec<[isize; 1]>);
|
||||
|
||||
cast! {
|
||||
Offsets,
|
||||
self => self.0.into_value(),
|
||||
v: isize => Self(vec![v]),
|
||||
v: isize => Self(smallvec![v]),
|
||||
v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
|
||||
}
|
||||
|
@ -32,6 +32,8 @@ pub use self::root::*;
|
||||
pub use self::style::*;
|
||||
pub use self::underover::*;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use ttf_parser::{GlyphId, Rect};
|
||||
use typst::eval::{Module, Scope};
|
||||
use typst::font::{Font, FontWeight};
|
||||
@ -46,7 +48,7 @@ use self::spacing::*;
|
||||
use crate::layout::{AlignElem, BoxElem, HElem, ParElem, Spacing};
|
||||
use crate::meta::Supplement;
|
||||
use crate::meta::{
|
||||
Count, Counter, CounterUpdate, LocalName, Numbering, Outlinable, Refable,
|
||||
Count, Counter, CounterUpdate, LocalNameIn, Numbering, Outlinable, Refable,
|
||||
};
|
||||
use crate::prelude::*;
|
||||
use crate::shared::BehavedBuilder;
|
||||
@ -182,7 +184,7 @@ impl Synthesize for EquationElem {
|
||||
fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
// Resolve the supplement.
|
||||
let supplement = match self.supplement(styles) {
|
||||
Smart::Auto => TextElem::packed(self.local_name_in(styles)),
|
||||
Smart::Auto => TextElem::packed(Self::local_name_in(styles)),
|
||||
Smart::Custom(None) => Content::empty(),
|
||||
Smart::Custom(Some(supplement)) => supplement.resolve(vt, [self.clone()])?,
|
||||
};
|
||||
@ -236,7 +238,7 @@ impl Layout for EquationElem {
|
||||
let variant = variant(styles);
|
||||
let world = vt.world;
|
||||
let Some(font) = families(styles).find_map(|family| {
|
||||
let id = world.book().select(family.as_str(), variant)?;
|
||||
let id = world.book().select(family, variant)?;
|
||||
let font = world.font(id)?;
|
||||
let _ = font.ttf().tables().math?.constants?;
|
||||
Some(font)
|
||||
@ -312,7 +314,7 @@ impl Count for EquationElem {
|
||||
}
|
||||
|
||||
impl LocalName for EquationElem {
|
||||
fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str {
|
||||
fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
|
||||
match lang {
|
||||
Lang::ALBANIAN => "Ekuacion",
|
||||
Lang::ARABIC => "معادلة",
|
||||
@ -381,7 +383,7 @@ impl Outlinable for EquationElem {
|
||||
|
||||
let numbers = self
|
||||
.counter()
|
||||
.at(vt, self.0.location().unwrap())?
|
||||
.at(vt, self.location().unwrap())?
|
||||
.display(vt, &numbering)?;
|
||||
|
||||
Ok(Some(supplement + numbers))
|
||||
@ -419,7 +421,7 @@ impl LayoutMath for Content {
|
||||
if self.is_sequence() {
|
||||
let mut bb = BehavedBuilder::new();
|
||||
self.sequence_recursive_for_each(&mut |child: &Content| {
|
||||
bb.push(child.clone(), StyleChain::default())
|
||||
bb.push(Cow::Owned(child.clone()), StyleChain::default())
|
||||
});
|
||||
|
||||
for (child, _) in bb.finish().0.iter() {
|
||||
|
@ -32,7 +32,7 @@ impl LayoutMath for OpElem {
|
||||
#[tracing::instrument(skip(ctx))]
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
let fragment =
|
||||
ctx.layout_text(&TextElem::new(self.text()).spanned(self.span()))?;
|
||||
ctx.layout_text(&TextElem::new(self.text().clone()).spanned(self.span()))?;
|
||||
ctx.push(
|
||||
FrameFragment::new(ctx, fragment.into_frame())
|
||||
.with_class(MathClass::Large)
|
||||
|
@ -32,7 +32,7 @@ pub struct RootElem {
|
||||
impl LayoutMath for RootElem {
|
||||
#[tracing::instrument(skip(ctx))]
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout(ctx, self.index(ctx.styles()).as_ref(), &self.radicand(), self.span())
|
||||
layout(ctx, self.index(ctx.styles()).as_ref(), self.radicand(), self.span())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -344,7 +344,7 @@ impl MathStyle {
|
||||
/// The size of elements in an equation.
|
||||
///
|
||||
/// See the TeXbook p. 141.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Cast)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Cast, Hash)]
|
||||
pub enum MathSize {
|
||||
/// Second-level sub- and superscripts.
|
||||
ScriptScript,
|
||||
@ -367,7 +367,7 @@ impl MathSize {
|
||||
}
|
||||
|
||||
/// A mathematical style variant, as defined by Unicode.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Cast)]
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Cast, Hash)]
|
||||
pub enum MathVariant {
|
||||
Serif,
|
||||
Sans,
|
||||
|
@ -24,7 +24,7 @@ pub struct UnderlineElem {
|
||||
impl LayoutMath for UnderlineElem {
|
||||
#[tracing::instrument(skip(ctx))]
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout_underoverline(ctx, &self.body(), self.span(), LineKind::Under)
|
||||
layout_underoverline(ctx, self.body(), self.span(), LineKind::Under)
|
||||
}
|
||||
}
|
||||
|
||||
@ -43,7 +43,7 @@ pub struct OverlineElem {
|
||||
impl LayoutMath for OverlineElem {
|
||||
#[tracing::instrument(skip(ctx))]
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout_underoverline(ctx, &self.body(), self.span(), LineKind::Over)
|
||||
layout_underoverline(ctx, self.body(), self.span(), LineKind::Over)
|
||||
}
|
||||
}
|
||||
|
||||
@ -130,7 +130,7 @@ impl LayoutMath for UnderbraceElem {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout_underoverspreader(
|
||||
ctx,
|
||||
&self.body(),
|
||||
self.body(),
|
||||
&self.annotation(ctx.styles()),
|
||||
'⏟',
|
||||
BRACE_GAP,
|
||||
@ -161,7 +161,7 @@ impl LayoutMath for OverbraceElem {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout_underoverspreader(
|
||||
ctx,
|
||||
&self.body(),
|
||||
self.body(),
|
||||
&self.annotation(ctx.styles()),
|
||||
'⏞',
|
||||
BRACE_GAP,
|
||||
@ -192,7 +192,7 @@ impl LayoutMath for UnderbracketElem {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout_underoverspreader(
|
||||
ctx,
|
||||
&self.body(),
|
||||
self.body(),
|
||||
&self.annotation(ctx.styles()),
|
||||
'⎵',
|
||||
BRACKET_GAP,
|
||||
@ -223,7 +223,7 @@ impl LayoutMath for OverbracketElem {
|
||||
fn layout_math(&self, ctx: &mut MathContext) -> SourceResult<()> {
|
||||
layout_underoverspreader(
|
||||
ctx,
|
||||
&self.body(),
|
||||
self.body(),
|
||||
&self.annotation(ctx.styles()),
|
||||
'⎴',
|
||||
BRACKET_GAP,
|
||||
|
@ -19,13 +19,13 @@ use typed_arena::Arena;
|
||||
use typst::diag::FileError;
|
||||
use typst::eval::{eval_string, Bytes, CastInfo, EvalMode, Reflect};
|
||||
use typst::font::FontStyle;
|
||||
use typst::util::option_eq;
|
||||
use typst::util::{option_eq, PicoStr};
|
||||
|
||||
use super::{CitationForm, CiteGroup, LocalName};
|
||||
use crate::layout::{
|
||||
BlockElem, GridElem, HElem, PadElem, ParElem, Sizing, TrackSizings, VElem,
|
||||
};
|
||||
use crate::meta::{FootnoteElem, HeadingElem};
|
||||
use crate::meta::{FootnoteElem, HeadingElem, LocalNameIn};
|
||||
use crate::prelude::*;
|
||||
use crate::text::{Delta, SubElem, SuperElem, TextElem};
|
||||
|
||||
@ -130,7 +130,7 @@ pub struct BibliographyElem {
|
||||
}
|
||||
|
||||
/// A list of bibliography file paths.
|
||||
#[derive(Debug, Default, Clone, Hash)]
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct BibPaths(Vec<EcoString>);
|
||||
|
||||
cast! {
|
||||
@ -153,11 +153,12 @@ impl BibliographyElem {
|
||||
bail!("multiple bibliographies are not yet supported");
|
||||
}
|
||||
|
||||
Ok(elem.to::<Self>().unwrap().clone())
|
||||
Ok(elem.to::<Self>().cloned().unwrap())
|
||||
}
|
||||
|
||||
/// Whether the bibliography contains the given key.
|
||||
pub fn has(vt: &Vt, key: &str) -> bool {
|
||||
pub fn has(vt: &Vt, key: impl Into<PicoStr>) -> bool {
|
||||
let key = key.into();
|
||||
vt.introspector
|
||||
.query(&Self::elem().select())
|
||||
.iter()
|
||||
@ -199,14 +200,9 @@ impl Show for BibliographyElem {
|
||||
|
||||
let mut seq = vec![];
|
||||
if let Some(title) = self.title(styles) {
|
||||
let title =
|
||||
title.unwrap_or_else(|| {
|
||||
TextElem::packed(self.local_name(
|
||||
TextElem::lang_in(styles),
|
||||
TextElem::region_in(styles),
|
||||
))
|
||||
.spanned(self.span())
|
||||
});
|
||||
let title = title.unwrap_or_else(|| {
|
||||
TextElem::packed(Self::local_name_in(styles)).spanned(self.span())
|
||||
});
|
||||
|
||||
seq.push(HeadingElem::new(title).with_level(NonZeroUsize::ONE).pack());
|
||||
}
|
||||
@ -220,7 +216,7 @@ impl Show for BibliographyElem {
|
||||
.ok_or("CSL style is not suitable for bibliographies")
|
||||
.at(span)?;
|
||||
|
||||
let row_gutter = BlockElem::below_in(styles).amount();
|
||||
let row_gutter = *BlockElem::below_in(styles).amount();
|
||||
if references.iter().any(|(prefix, _)| prefix.is_some()) {
|
||||
let mut cells = vec![];
|
||||
for (prefix, reference) in references {
|
||||
@ -231,9 +227,9 @@ impl Show for BibliographyElem {
|
||||
seq.push(VElem::new(row_gutter).with_weakness(3).pack());
|
||||
seq.push(
|
||||
GridElem::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_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
|
||||
.with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
|
||||
.with_row_gutter(TrackSizings(smallvec![(row_gutter).into()]))
|
||||
.pack(),
|
||||
);
|
||||
} else {
|
||||
@ -263,7 +259,7 @@ impl Finalize for BibliographyElem {
|
||||
}
|
||||
|
||||
impl LocalName for BibliographyElem {
|
||||
fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str {
|
||||
fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
|
||||
match lang {
|
||||
Lang::ALBANIAN => "Bibliografi",
|
||||
Lang::ARABIC => "المراجع",
|
||||
@ -300,7 +296,7 @@ impl LocalName for BibliographyElem {
|
||||
#[ty]
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Bibliography {
|
||||
map: Arc<IndexMap<EcoString, hayagriva::Entry>>,
|
||||
map: Arc<IndexMap<PicoStr, hayagriva::Entry>>,
|
||||
hash: u128,
|
||||
}
|
||||
|
||||
@ -371,8 +367,8 @@ impl Bibliography {
|
||||
})
|
||||
}
|
||||
|
||||
fn has(&self, key: &str) -> bool {
|
||||
self.map.contains_key(key)
|
||||
fn has(&self, key: impl Into<PicoStr>) -> bool {
|
||||
self.map.contains_key(&key.into())
|
||||
}
|
||||
|
||||
fn entries(&self) -> impl Iterator<Item = &hayagriva::Entry> {
|
||||
@ -645,7 +641,7 @@ impl<'a> Generator<'a> {
|
||||
let mut driver = BibliographyDriver::new();
|
||||
for elem in &self.groups {
|
||||
let group = elem.to::<CiteGroup>().unwrap();
|
||||
let location = group.0.location().unwrap();
|
||||
let location = group.location().unwrap();
|
||||
let children = group.children();
|
||||
|
||||
// Groups should never be empty.
|
||||
@ -657,12 +653,13 @@ impl<'a> Generator<'a> {
|
||||
let mut normal = true;
|
||||
|
||||
// Create infos and items for each child in the group.
|
||||
for child in &children {
|
||||
let key = child.key();
|
||||
let Some(entry) = database.map.get(&key.0) else {
|
||||
for child in children {
|
||||
let key = *child.key();
|
||||
let Some(entry) = database.map.get(&key.into_inner()) else {
|
||||
errors.push(error!(
|
||||
child.span(),
|
||||
"key `{}` does not exist in the bibliography", key.0
|
||||
"key `{}` does not exist in the bibliography",
|
||||
key.as_str()
|
||||
));
|
||||
continue;
|
||||
};
|
||||
@ -714,13 +711,13 @@ impl<'a> Generator<'a> {
|
||||
driver.citation(CitationRequest::new(
|
||||
items,
|
||||
style,
|
||||
Some(locale(first.lang(), first.region())),
|
||||
Some(locale(*first.lang(), *first.region())),
|
||||
&LOCALES,
|
||||
None,
|
||||
));
|
||||
}
|
||||
|
||||
let locale = locale(self.bibliography.lang(), self.bibliography.region());
|
||||
let locale = locale(*self.bibliography.lang(), *self.bibliography.region());
|
||||
|
||||
// Add hidden items for everything if we should print the whole
|
||||
// bibliography.
|
||||
@ -761,7 +758,7 @@ impl<'a> Generator<'a> {
|
||||
// so that we can link there.
|
||||
let mut links = HashMap::new();
|
||||
if let Some(bibliography) = &rendered.bibliography {
|
||||
let location = self.bibliography.0.location().unwrap();
|
||||
let location = self.bibliography.location().unwrap();
|
||||
for (k, item) in bibliography.items.iter().enumerate() {
|
||||
links.insert(item.key.as_str(), location.variant(k + 1));
|
||||
}
|
||||
@ -770,8 +767,7 @@ impl<'a> Generator<'a> {
|
||||
let mut output = std::mem::take(&mut self.failures);
|
||||
for (info, citation) in self.infos.iter().zip(&rendered.citations) {
|
||||
let supplement = |i: usize| info.subinfos.get(i)?.supplement.clone();
|
||||
let link =
|
||||
|i: usize| links.get(info.subinfos.get(i)?.key.0.as_str()).copied();
|
||||
let link = |i: usize| links.get(info.subinfos.get(i)?.key.as_str()).copied();
|
||||
|
||||
let renderer = ElemRenderer {
|
||||
world: self.world,
|
||||
@ -811,13 +807,13 @@ impl<'a> Generator<'a> {
|
||||
let mut first_occurances = HashMap::new();
|
||||
for info in &self.infos {
|
||||
for subinfo in &info.subinfos {
|
||||
let key = subinfo.key.0.as_str();
|
||||
let key = subinfo.key.as_str();
|
||||
first_occurances.entry(key).or_insert(info.location);
|
||||
}
|
||||
}
|
||||
|
||||
// The location of the bibliography.
|
||||
let location = self.bibliography.0.location().unwrap();
|
||||
let location = self.bibliography.location().unwrap();
|
||||
|
||||
let mut output = vec![];
|
||||
for (k, item) in rendered.items.iter().enumerate() {
|
||||
@ -918,8 +914,8 @@ impl ElemRenderer<'_> {
|
||||
if let Some(prefix) = suf_prefix {
|
||||
const COLUMN_GUTTER: Em = Em::new(0.65);
|
||||
content = GridElem::new(vec![prefix, content])
|
||||
.with_columns(TrackSizings(vec![Sizing::Auto; 2]))
|
||||
.with_column_gutter(TrackSizings(vec![COLUMN_GUTTER.into()]))
|
||||
.with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
|
||||
.with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
|
||||
.pack();
|
||||
}
|
||||
|
||||
|
@ -141,7 +141,7 @@ impl Show for CiteGroup {
|
||||
#[tracing::instrument(name = "CiteGroup::show", skip(self, vt))]
|
||||
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(vt.delayed(|vt| {
|
||||
let location = self.0.location().unwrap();
|
||||
let location = self.location().unwrap();
|
||||
let span = self.span();
|
||||
Works::generate(vt.world, vt.introspector)
|
||||
.at(span)?
|
||||
|
@ -37,7 +37,7 @@ impl Show for LocateElem {
|
||||
#[tracing::instrument(name = "LocateElem::show", skip(self, vt))]
|
||||
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(vt.delayed(|vt| {
|
||||
let location = self.0.location().unwrap();
|
||||
let location = self.location().unwrap();
|
||||
Ok(self.func().call_vt(vt, [location])?.display())
|
||||
}))
|
||||
}
|
||||
|
@ -284,9 +284,9 @@ impl Counter {
|
||||
}
|
||||
|
||||
if let Some(update) = match elem.to::<UpdateElem>() {
|
||||
Some(elem) => Some(elem.update()),
|
||||
Some(elem) => Some(elem.update().clone()),
|
||||
None => match elem.with::<dyn Count>() {
|
||||
Some(countable) => countable.update(),
|
||||
Some(countable) => countable.update().clone(),
|
||||
None => Some(CounterUpdate::Step(NonZeroUsize::ONE)),
|
||||
},
|
||||
} {
|
||||
@ -301,8 +301,7 @@ impl Counter {
|
||||
|
||||
/// The selector relevant for this counter's updates.
|
||||
fn selector(&self) -> Selector {
|
||||
let mut selector =
|
||||
Selector::Elem(UpdateElem::elem(), Some(dict! { "key" => self.0.clone() }));
|
||||
let mut selector = select_where!(UpdateElem, Key => self.0.clone());
|
||||
|
||||
if let CounterKey::Selector(key) = &self.0 {
|
||||
selector = Selector::Or(eco_vec![selector, key.clone()]);
|
||||
@ -613,17 +612,18 @@ impl Show for DisplayElem {
|
||||
#[tracing::instrument(name = "DisplayElem::show", skip_all)]
|
||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(vt.delayed(|vt| {
|
||||
let location = self.0.location().unwrap();
|
||||
let location = self.location().unwrap();
|
||||
let counter = self.counter();
|
||||
let numbering = self
|
||||
.numbering()
|
||||
.clone()
|
||||
.or_else(|| {
|
||||
let CounterKey::Selector(Selector::Elem(func, _)) = counter.0 else {
|
||||
return None;
|
||||
};
|
||||
|
||||
if func == HeadingElem::elem() {
|
||||
HeadingElem::numbering_in(styles)
|
||||
HeadingElem::numbering_in(styles).clone()
|
||||
} else if func == FigureElem::elem() {
|
||||
FigureElem::numbering_in(styles)
|
||||
} else if func == EquationElem::elem() {
|
||||
@ -634,7 +634,7 @@ impl Show for DisplayElem {
|
||||
})
|
||||
.unwrap_or_else(|| NumberingPattern::from_str("1.1").unwrap().into());
|
||||
|
||||
let state = if self.both() {
|
||||
let state = if *self.both() {
|
||||
counter.both(vt, location)?
|
||||
} else {
|
||||
counter.at(vt, location)?
|
||||
@ -695,9 +695,9 @@ impl ManualPageCounter {
|
||||
FrameItem::Group(group) => self.visit(vt, &group.frame)?,
|
||||
FrameItem::Meta(Meta::Elem(elem), _) => {
|
||||
let Some(elem) = elem.to::<UpdateElem>() else { continue };
|
||||
if elem.key() == CounterKey::Page {
|
||||
if *elem.key() == CounterKey::Page {
|
||||
let mut state = CounterState(smallvec![self.logical]);
|
||||
state.update(vt, elem.update())?;
|
||||
state.update(vt, elem.update().clone())?;
|
||||
self.logical = state.first();
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
use comemo::Prehashed;
|
||||
use typst::eval::Datetime;
|
||||
|
||||
use crate::layout::{LayoutRoot, PageElem};
|
||||
@ -43,9 +44,8 @@ pub struct DocumentElem {
|
||||
pub date: Smart<Option<Datetime>>,
|
||||
|
||||
/// The page runs.
|
||||
#[internal]
|
||||
#[variadic]
|
||||
pub children: Vec<Content>,
|
||||
pub children: Vec<Prehashed<Content>>,
|
||||
}
|
||||
|
||||
impl Construct for DocumentElem {
|
||||
@ -64,7 +64,7 @@ impl LayoutRoot for DocumentElem {
|
||||
let mut page_counter = ManualPageCounter::new();
|
||||
|
||||
let children = self.children();
|
||||
let mut iter = children.iter().peekable();
|
||||
let mut iter = children.iter().map(|c| &**c).peekable();
|
||||
|
||||
while let Some(mut child) = iter.next() {
|
||||
let outer = styles;
|
||||
@ -99,7 +99,7 @@ impl LayoutRoot for DocumentElem {
|
||||
}
|
||||
|
||||
/// A list of authors.
|
||||
#[derive(Debug, Default, Clone, Hash)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
||||
pub struct Author(Vec<EcoString>);
|
||||
|
||||
cast! {
|
||||
@ -110,7 +110,7 @@ cast! {
|
||||
}
|
||||
|
||||
/// A list of keywords.
|
||||
#[derive(Debug, Default, Clone, Hash)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
||||
pub struct Keywords(Vec<EcoString>);
|
||||
|
||||
cast! {
|
||||
|
@ -1,9 +1,8 @@
|
||||
use std::borrow::Cow;
|
||||
use std::str::FromStr;
|
||||
use typst::util::option_eq;
|
||||
|
||||
use super::{
|
||||
Count, Counter, CounterKey, CounterUpdate, LocalName, Numbering, NumberingPattern,
|
||||
};
|
||||
use super::{Count, Counter, CounterKey, CounterUpdate, Numbering, NumberingPattern};
|
||||
use crate::layout::{BlockElem, PlaceElem, VElem};
|
||||
use crate::meta::{Outlinable, Refable, Supplement};
|
||||
use crate::prelude::*;
|
||||
@ -170,6 +169,7 @@ pub struct FigureElem {
|
||||
/// kind: "foo",
|
||||
/// )
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub supplement: Smart<Option<Supplement>>,
|
||||
|
||||
/// How to number the figure. Accepts a
|
||||
@ -212,25 +212,21 @@ impl Synthesize for FigureElem {
|
||||
let kind = self.kind(styles).unwrap_or_else(|| {
|
||||
self.body()
|
||||
.query_first(Selector::can::<dyn Figurable>())
|
||||
.cloned()
|
||||
.map(|elem| FigureKind::Elem(elem.func()))
|
||||
.unwrap_or_else(|| FigureKind::Elem(ImageElem::elem()))
|
||||
});
|
||||
|
||||
// Resolve the supplement.
|
||||
let supplement = match self.supplement(styles) {
|
||||
let supplement = match self.supplement(styles).as_ref() {
|
||||
Smart::Auto => {
|
||||
// Default to the local name for the kind, if available.
|
||||
let name = match &kind {
|
||||
FigureKind::Elem(func) => {
|
||||
let empty = Content::new(*func);
|
||||
empty.with::<dyn LocalName>().map(|c| {
|
||||
TextElem::packed(c.local_name(
|
||||
TextElem::lang_in(styles),
|
||||
TextElem::region_in(styles),
|
||||
))
|
||||
})
|
||||
}
|
||||
FigureKind::Elem(func) => func
|
||||
.local_name(
|
||||
TextElem::lang_in(styles),
|
||||
TextElem::region_in(styles),
|
||||
)
|
||||
.map(TextElem::packed),
|
||||
FigureKind::Name(_) => None,
|
||||
};
|
||||
|
||||
@ -245,24 +241,21 @@ impl Synthesize for FigureElem {
|
||||
// Resolve the supplement with the first descendant of the kind or
|
||||
// just the body, if none was found.
|
||||
let descendant = match kind {
|
||||
FigureKind::Elem(func) => {
|
||||
self.body().query_first(Selector::Elem(func, None)).cloned()
|
||||
}
|
||||
FigureKind::Elem(func) => self
|
||||
.body()
|
||||
.query_first(Selector::Elem(func, None))
|
||||
.map(Cow::Owned),
|
||||
FigureKind::Name(_) => None,
|
||||
};
|
||||
|
||||
let target = descendant.unwrap_or_else(|| self.body());
|
||||
let target = descendant.unwrap_or_else(|| Cow::Borrowed(self.body()));
|
||||
Some(supplement.resolve(vt, [target])?)
|
||||
}
|
||||
};
|
||||
|
||||
// Construct the figure's counter.
|
||||
let counter = Counter::new(CounterKey::Selector(Selector::Elem(
|
||||
Self::elem(),
|
||||
Some(dict! {
|
||||
"kind" => kind.clone(),
|
||||
}),
|
||||
)));
|
||||
let counter =
|
||||
Counter::new(CounterKey::Selector(select_where!(Self, Kind => kind.clone())));
|
||||
|
||||
// Fill the figure's caption.
|
||||
let mut caption = self.caption(styles);
|
||||
@ -271,7 +264,7 @@ impl Synthesize for FigureElem {
|
||||
caption.push_supplement(supplement.clone());
|
||||
caption.push_numbering(numbering.clone());
|
||||
caption.push_counter(Some(counter.clone()));
|
||||
caption.push_location(self.0.location());
|
||||
caption.push_figure_location(self.location());
|
||||
}
|
||||
|
||||
self.push_placement(self.placement(styles));
|
||||
@ -289,7 +282,7 @@ impl Synthesize for FigureElem {
|
||||
impl Show for FigureElem {
|
||||
#[tracing::instrument(name = "FigureElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let mut realized = self.body();
|
||||
let mut realized = self.body().clone();
|
||||
|
||||
// Build the caption, if any.
|
||||
if let Some(caption) = self.caption(styles) {
|
||||
@ -339,14 +332,15 @@ impl Count for FigureElem {
|
||||
impl Refable for FigureElem {
|
||||
fn supplement(&self) -> Content {
|
||||
// After synthesis, this should always be custom content.
|
||||
match self.supplement(StyleChain::default()) {
|
||||
Smart::Custom(Some(Supplement::Content(content))) => content,
|
||||
let default = StyleChain::default();
|
||||
match self.supplement(default).as_ref() {
|
||||
Smart::Custom(Some(Supplement::Content(content))) => content.clone(),
|
||||
_ => Content::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn counter(&self) -> Counter {
|
||||
self.counter().unwrap_or_else(|| Counter::of(Self::elem()))
|
||||
self.counter().clone().unwrap_or_else(|| Counter::of(Self::elem()))
|
||||
}
|
||||
|
||||
fn numbering(&self) -> Option<Numbering> {
|
||||
@ -364,17 +358,17 @@ impl Outlinable for FigureElem {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut realized = caption.body();
|
||||
let mut realized = caption.body().clone();
|
||||
if let (
|
||||
Smart::Custom(Some(Supplement::Content(mut supplement))),
|
||||
Some(counter),
|
||||
Some(numbering),
|
||||
) = (
|
||||
self.supplement(StyleChain::default()),
|
||||
self.supplement(StyleChain::default()).clone(),
|
||||
self.counter(),
|
||||
self.numbering(StyleChain::default()),
|
||||
) {
|
||||
let location = self.0.location().unwrap();
|
||||
let location = self.location().unwrap();
|
||||
let numbers = counter.at(vt, location)?.display(vt, &numbering)?;
|
||||
|
||||
if !supplement.is_empty() {
|
||||
@ -497,8 +491,9 @@ pub struct FigureCaption {
|
||||
pub counter: Option<Counter>,
|
||||
|
||||
/// The figure's location.
|
||||
#[internal]
|
||||
#[synthesized]
|
||||
pub location: Option<Location>,
|
||||
pub figure_location: Option<Location>,
|
||||
}
|
||||
|
||||
impl FigureCaption {
|
||||
@ -542,12 +537,15 @@ impl Synthesize for FigureCaption {
|
||||
impl Show for FigureCaption {
|
||||
#[tracing::instrument(name = "FigureCaption::show", skip_all)]
|
||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let mut realized = self.body();
|
||||
let mut realized = self.body().clone();
|
||||
|
||||
if let (Some(mut supplement), Some(numbering), Some(counter), Some(location)) =
|
||||
(self.supplement(), self.numbering(), self.counter(), self.location())
|
||||
{
|
||||
let numbers = counter.at(vt, location)?.display(vt, &numbering)?;
|
||||
if let (Some(mut supplement), Some(numbering), Some(counter), Some(location)) = (
|
||||
self.supplement().clone(),
|
||||
self.numbering(),
|
||||
self.counter(),
|
||||
self.figure_location(),
|
||||
) {
|
||||
let numbers = counter.at(vt, *location)?.display(vt, numbering)?;
|
||||
if !supplement.is_empty() {
|
||||
supplement += TextElem::packed('\u{a0}');
|
||||
}
|
||||
@ -564,7 +562,7 @@ cast! {
|
||||
}
|
||||
|
||||
/// The `kind` parameter of a [`FigureElem`].
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum FigureKind {
|
||||
/// The kind is an element function.
|
||||
Elem(Element),
|
||||
@ -585,4 +583,4 @@ cast! {
|
||||
/// An element that can be auto-detected in a figure.
|
||||
///
|
||||
/// This trait is used to determine the type of a figure.
|
||||
pub trait Figurable: LocalName {}
|
||||
pub trait Figurable {}
|
||||
|
@ -59,6 +59,7 @@ pub struct FootnoteElem {
|
||||
/// #footnote[Star],
|
||||
/// #footnote[Dagger]
|
||||
/// ```
|
||||
#[borrowed]
|
||||
#[default(Numbering::Pattern(NumberingPattern::from_str("1").unwrap()))]
|
||||
pub numbering: Numbering,
|
||||
|
||||
@ -91,7 +92,7 @@ impl FootnoteElem {
|
||||
}
|
||||
|
||||
/// Returns the content of the body of this footnote if it is not a ref.
|
||||
pub fn body_content(&self) -> Option<Content> {
|
||||
pub fn body_content(&self) -> Option<&Content> {
|
||||
match self.body() {
|
||||
FootnoteBody::Content(content) => Some(content),
|
||||
_ => None,
|
||||
@ -102,20 +103,20 @@ impl FootnoteElem {
|
||||
pub fn declaration_location(&self, vt: &Vt) -> StrResult<Location> {
|
||||
match self.body() {
|
||||
FootnoteBody::Reference(label) => {
|
||||
let element: Prehashed<Content> = vt.introspector.query_label(&label)?;
|
||||
let element: Prehashed<Content> = vt.introspector.query_label(*label)?;
|
||||
let footnote = element
|
||||
.to::<FootnoteElem>()
|
||||
.ok_or("referenced element should be a footnote")?;
|
||||
footnote.declaration_location(vt)
|
||||
}
|
||||
_ => Ok(self.0.location().unwrap()),
|
||||
_ => Ok(self.location().unwrap()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Synthesize for FootnoteElem {
|
||||
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
self.push_numbering(self.numbering(styles));
|
||||
self.push_numbering(self.numbering(styles).clone());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -127,7 +128,7 @@ impl Show for FootnoteElem {
|
||||
let loc = self.declaration_location(vt).at(self.span())?;
|
||||
let numbering = self.numbering(styles);
|
||||
let counter = Counter::of(Self::elem());
|
||||
let num = counter.at(vt, loc)?.display(vt, &numbering)?;
|
||||
let num = counter.at(vt, loc)?.display(vt, numbering)?;
|
||||
let sup = SuperElem::new(num).pack();
|
||||
let loc = loc.variant(1);
|
||||
// Add zero-width weak spacing to make the footnote "sticky".
|
||||
@ -144,7 +145,7 @@ impl Count for FootnoteElem {
|
||||
|
||||
/// The body of a footnote can be either some content or a label referencing
|
||||
/// another footnote.
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum FootnoteBody {
|
||||
Content(Content),
|
||||
Reference(Label),
|
||||
@ -266,10 +267,11 @@ impl Show for FootnoteEntry {
|
||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let note = self.note();
|
||||
let number_gap = Em::new(0.05);
|
||||
let numbering = note.numbering(StyleChain::default());
|
||||
let default = StyleChain::default();
|
||||
let numbering = note.numbering(default);
|
||||
let counter = Counter::of(FootnoteElem::elem());
|
||||
let loc = note.0.location().unwrap();
|
||||
let num = counter.at(vt, loc)?.display(vt, &numbering)?;
|
||||
let loc = note.location().unwrap();
|
||||
let num = counter.at(vt, loc)?.display(vt, numbering)?;
|
||||
let sup = SuperElem::new(num)
|
||||
.pack()
|
||||
.linked(Destination::Location(loc))
|
||||
@ -278,7 +280,7 @@ impl Show for FootnoteEntry {
|
||||
HElem::new(self.indent(styles).into()).pack(),
|
||||
sup,
|
||||
HElem::new(number_gap.into()).with_weak(true).pack(),
|
||||
note.body_content().unwrap(),
|
||||
note.body_content().unwrap().clone(),
|
||||
]))
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ use typst::util::option_eq;
|
||||
|
||||
use super::{Counter, CounterUpdate, LocalName, Numbering, Outlinable, Refable};
|
||||
use crate::layout::{BlockElem, HElem, VElem};
|
||||
use crate::meta::{Count, Supplement};
|
||||
use crate::meta::{Count, LocalNameIn, Supplement};
|
||||
use crate::prelude::*;
|
||||
use crate::text::{SpaceElem, TextElem, TextSize};
|
||||
|
||||
@ -54,6 +54,7 @@ pub struct HeadingElem {
|
||||
/// == A subsection
|
||||
/// === A sub-subsection
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub numbering: Option<Numbering>,
|
||||
|
||||
/// A supplement for the heading.
|
||||
@ -124,13 +125,13 @@ impl Synthesize for HeadingElem {
|
||||
fn synthesize(&mut self, vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
// Resolve the supplement.
|
||||
let supplement = match self.supplement(styles) {
|
||||
Smart::Auto => TextElem::packed(self.local_name_in(styles)),
|
||||
Smart::Auto => TextElem::packed(Self::local_name_in(styles)),
|
||||
Smart::Custom(None) => Content::empty(),
|
||||
Smart::Custom(Some(supplement)) => supplement.resolve(vt, [self.clone()])?,
|
||||
};
|
||||
|
||||
self.push_level(self.level(styles));
|
||||
self.push_numbering(self.numbering(styles));
|
||||
self.push_numbering(self.numbering(styles).clone());
|
||||
self.push_supplement(Smart::Custom(Some(Supplement::Content(supplement))));
|
||||
self.push_outlined(self.outlined(styles));
|
||||
self.push_bookmarked(self.bookmarked(styles));
|
||||
@ -142,10 +143,10 @@ impl Synthesize for HeadingElem {
|
||||
impl Show for HeadingElem {
|
||||
#[tracing::instrument(name = "HeadingElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let mut realized = self.body();
|
||||
if let Some(numbering) = self.numbering(styles) {
|
||||
let mut realized = self.body().clone();
|
||||
if let Some(numbering) = self.numbering(styles).as_ref() {
|
||||
realized = Counter::of(Self::elem())
|
||||
.display(Some(numbering), false)
|
||||
.display(Some(numbering.clone()), false)
|
||||
.spanned(self.span())
|
||||
+ HElem::new(Em::new(0.3).into()).with_weak(true).pack()
|
||||
+ realized;
|
||||
@ -204,7 +205,7 @@ impl Refable for HeadingElem {
|
||||
}
|
||||
|
||||
fn numbering(&self) -> Option<Numbering> {
|
||||
self.numbering(StyleChain::default())
|
||||
self.numbering(StyleChain::default()).clone()
|
||||
}
|
||||
}
|
||||
|
||||
@ -214,11 +215,12 @@ impl Outlinable for HeadingElem {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut content = self.body();
|
||||
if let Some(numbering) = self.numbering(StyleChain::default()) {
|
||||
let mut content = self.body().clone();
|
||||
let default = StyleChain::default();
|
||||
if let Some(numbering) = self.numbering(default).as_ref() {
|
||||
let numbers = Counter::of(Self::elem())
|
||||
.at(vt, self.0.location().unwrap())?
|
||||
.display(vt, &numbering)?;
|
||||
.at(vt, self.location().unwrap())?
|
||||
.display(vt, numbering)?;
|
||||
content = numbers + SpaceElem::new().pack() + content;
|
||||
};
|
||||
|
||||
@ -231,7 +233,7 @@ impl Outlinable for HeadingElem {
|
||||
}
|
||||
|
||||
impl LocalName for HeadingElem {
|
||||
fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str {
|
||||
fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
|
||||
match lang {
|
||||
Lang::ALBANIAN => "Kapitull",
|
||||
Lang::ARABIC => "الفصل",
|
||||
|
@ -84,12 +84,12 @@ impl LinkElem {
|
||||
impl Show for LinkElem {
|
||||
#[tracing::instrument(name = "LinkElem::show", skip(self, vt))]
|
||||
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||
let body = self.body();
|
||||
let body = self.body().clone();
|
||||
let linked = match self.dest() {
|
||||
LinkTarget::Dest(dest) => body.linked(dest),
|
||||
LinkTarget::Dest(dest) => body.linked(dest.clone()),
|
||||
LinkTarget::Label(label) => vt
|
||||
.delayed(|vt| {
|
||||
let elem = vt.introspector.query_label(&label).at(self.span())?;
|
||||
let elem = vt.introspector.query_label(*label).at(self.span())?;
|
||||
let dest = Destination::Location(elem.location().unwrap());
|
||||
Ok(Some(body.clone().linked(dest)))
|
||||
})
|
||||
@ -110,7 +110,7 @@ fn body_from_url(url: &EcoString) -> Content {
|
||||
}
|
||||
|
||||
/// A target where a link can go.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum LinkTarget {
|
||||
Dest(Destination),
|
||||
Label(Label),
|
||||
|
@ -62,13 +62,15 @@ pub(super) fn define(global: &mut Scope) {
|
||||
global.define_func::<query>();
|
||||
}
|
||||
|
||||
/// The named with which an element is referenced.
|
||||
pub trait LocalName {
|
||||
/// Get the name in the given language and (optionally) region.
|
||||
fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str;
|
||||
|
||||
/// Resolve the local name with a style chain.
|
||||
fn local_name_in(&self, styles: StyleChain) -> &'static str {
|
||||
self.local_name(TextElem::lang_in(styles), TextElem::region_in(styles))
|
||||
/// An element that has a local name.
|
||||
pub trait LocalNameIn: LocalName {
|
||||
/// Gets the local name from the style chain.
|
||||
fn local_name_in(styles: StyleChain) -> &'static str
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Self::local_name(TextElem::lang_in(styles), TextElem::region_in(styles))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: LocalName> LocalNameIn for T {}
|
||||
|
@ -6,6 +6,7 @@ use super::{
|
||||
Counter, CounterKey, HeadingElem, LocalName, Numbering, NumberingPattern, Refable,
|
||||
};
|
||||
use crate::layout::{BoxElem, HElem, HideElem, ParbreakElem, RepeatElem, Spacing};
|
||||
use crate::meta::LocalNameIn;
|
||||
use crate::prelude::*;
|
||||
use crate::text::{LinebreakElem, SpaceElem, TextElem};
|
||||
|
||||
@ -88,10 +89,8 @@ pub struct OutlineElem {
|
||||
/// caption: [Experiment results],
|
||||
/// )
|
||||
/// ```
|
||||
#[default(LocatableSelector(Selector::Elem(
|
||||
HeadingElem::elem(),
|
||||
Some(dict! { "outlined" => true })
|
||||
)))]
|
||||
#[default(LocatableSelector(select_where!(HeadingElem, Outlined => true)))]
|
||||
#[borrowed]
|
||||
pub target: LocatableSelector,
|
||||
|
||||
/// The maximum level up to which elements are included in the outline. When
|
||||
@ -161,6 +160,7 @@ pub struct OutlineElem {
|
||||
/// #lorem(10)
|
||||
/// ```
|
||||
#[default(None)]
|
||||
#[borrowed]
|
||||
pub indent: Option<Smart<OutlineIndent>>,
|
||||
|
||||
/// Content to fill the space between the title and the page number. Can be
|
||||
@ -187,14 +187,9 @@ impl Show for OutlineElem {
|
||||
let mut seq = vec![ParbreakElem::new().pack()];
|
||||
// Build the outline title.
|
||||
if let Some(title) = self.title(styles) {
|
||||
let title =
|
||||
title.unwrap_or_else(|| {
|
||||
TextElem::packed(self.local_name(
|
||||
TextElem::lang_in(styles),
|
||||
TextElem::region_in(styles),
|
||||
))
|
||||
.spanned(self.span())
|
||||
});
|
||||
let title = title.unwrap_or_else(|| {
|
||||
TextElem::packed(Self::local_name_in(styles)).spanned(self.span())
|
||||
});
|
||||
|
||||
seq.push(HeadingElem::new(title).with_level(NonZeroUsize::ONE).pack());
|
||||
}
|
||||
@ -217,7 +212,7 @@ impl Show for OutlineElem {
|
||||
};
|
||||
|
||||
let level = entry.level();
|
||||
if depth < level {
|
||||
if depth < *level {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -226,12 +221,12 @@ impl Show for OutlineElem {
|
||||
while ancestors
|
||||
.last()
|
||||
.and_then(|ancestor| ancestor.with::<dyn Outlinable>())
|
||||
.map_or(false, |last| last.level() >= level)
|
||||
.map_or(false, |last| last.level() >= *level)
|
||||
{
|
||||
ancestors.pop();
|
||||
}
|
||||
|
||||
OutlineIndent::apply(&indent, vt, &ancestors, &mut seq, self.span())?;
|
||||
OutlineIndent::apply(indent, vt, &ancestors, &mut seq, self.span())?;
|
||||
|
||||
// Add the overridable outline entry, followed by a line break.
|
||||
seq.push(entry.pack());
|
||||
@ -255,7 +250,7 @@ impl Finalize for OutlineElem {
|
||||
}
|
||||
|
||||
impl LocalName for OutlineElem {
|
||||
fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str {
|
||||
fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
|
||||
match lang {
|
||||
Lang::ALBANIAN => "Përmbajtja",
|
||||
Lang::ARABIC => "المحتويات",
|
||||
@ -301,7 +296,7 @@ pub trait Outlinable: Refable {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum OutlineIndent {
|
||||
Bool(bool),
|
||||
Rel(Rel<Length>),
|
||||
@ -497,14 +492,14 @@ impl Show for OutlineEntry {
|
||||
};
|
||||
|
||||
// The body text remains overridable.
|
||||
seq.push(self.body().linked(Destination::Location(location)));
|
||||
seq.push(self.body().clone().linked(Destination::Location(location)));
|
||||
|
||||
// Add filler symbols between the section name and page number.
|
||||
if let Some(filler) = self.fill() {
|
||||
seq.push(SpaceElem::new().pack());
|
||||
seq.push(
|
||||
BoxElem::new()
|
||||
.with_body(Some(filler))
|
||||
.with_body(Some(filler.clone()))
|
||||
.with_width(Fr::one().into())
|
||||
.pack(),
|
||||
);
|
||||
@ -514,7 +509,7 @@ impl Show for OutlineEntry {
|
||||
}
|
||||
|
||||
// Add the page number.
|
||||
let page = self.page().linked(Destination::Location(location));
|
||||
let page = self.page().clone().linked(Destination::Location(location));
|
||||
seq.push(page);
|
||||
|
||||
Ok(Content::sequence(seq))
|
||||
|
@ -115,6 +115,7 @@ pub struct RefElem {
|
||||
/// in @intro[Part], it is done
|
||||
/// manually.
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub supplement: Smart<Option<Supplement>>,
|
||||
|
||||
/// A synthesized citation.
|
||||
@ -132,9 +133,9 @@ impl Synthesize for RefElem {
|
||||
self.push_citation(Some(citation));
|
||||
self.push_element(None);
|
||||
|
||||
let target = self.target();
|
||||
if !BibliographyElem::has(vt, &target.0) {
|
||||
if let Ok(elem) = vt.introspector.query_label(&target) {
|
||||
let target = *self.target();
|
||||
if !BibliographyElem::has(vt, target) {
|
||||
if let Ok(elem) = vt.introspector.query_label(target) {
|
||||
self.push_element(Some(elem.into_inner()));
|
||||
return Ok(());
|
||||
}
|
||||
@ -148,22 +149,22 @@ impl Show for RefElem {
|
||||
#[tracing::instrument(name = "RefElem::show", skip_all)]
|
||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(vt.delayed(|vt| {
|
||||
let target = self.target();
|
||||
let elem = vt.introspector.query_label(&self.target());
|
||||
let target = *self.target();
|
||||
let elem = vt.introspector.query_label(target);
|
||||
let span = self.span();
|
||||
|
||||
if BibliographyElem::has(vt, &target.0) {
|
||||
if BibliographyElem::has(vt, target) {
|
||||
if elem.is_ok() {
|
||||
bail!(span, "label occurs in the document and its bibliography");
|
||||
}
|
||||
|
||||
return Ok(self.to_citation(vt, styles)?.pack().spanned(span));
|
||||
return Ok(self.to_citation(vt, styles)?.spanned(span).pack());
|
||||
}
|
||||
|
||||
let elem = elem.at(span)?;
|
||||
|
||||
if elem.func() == FootnoteElem::elem() {
|
||||
return Ok(FootnoteElem::with_label(target).pack().spanned(span));
|
||||
return Ok(FootnoteElem::with_label(target).spanned(span).pack());
|
||||
}
|
||||
|
||||
let refable = elem
|
||||
@ -204,7 +205,7 @@ impl Show for RefElem {
|
||||
.at(vt, elem.location().unwrap())?
|
||||
.display(vt, &numbering.trimmed())?;
|
||||
|
||||
let supplement = match self.supplement(styles) {
|
||||
let supplement = match self.supplement(styles).as_ref() {
|
||||
Smart::Auto => refable.supplement(),
|
||||
Smart::Custom(None) => Content::empty(),
|
||||
Smart::Custom(Some(supplement)) => {
|
||||
@ -225,10 +226,10 @@ impl Show for RefElem {
|
||||
impl RefElem {
|
||||
/// Turn the reference into a citation.
|
||||
pub fn to_citation(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<CiteElem> {
|
||||
let mut elem = CiteElem::new(self.target());
|
||||
elem.0.set_location(self.0.location().unwrap());
|
||||
let mut elem = CiteElem::new(*self.target());
|
||||
elem.set_location(self.location().unwrap());
|
||||
elem.synthesize(vt, styles)?;
|
||||
elem.push_supplement(match self.supplement(styles) {
|
||||
elem.push_supplement(match self.supplement(styles).clone() {
|
||||
Smart::Custom(Some(Supplement::Content(content))) => Some(content),
|
||||
_ => None,
|
||||
});
|
||||
@ -238,6 +239,7 @@ impl RefElem {
|
||||
}
|
||||
|
||||
/// Additional content for a reference.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum Supplement {
|
||||
Content(Content),
|
||||
Func(Func),
|
||||
|
@ -233,7 +233,7 @@ impl State {
|
||||
for elem in introspector.query(&self.selector()) {
|
||||
let elem = elem.to::<UpdateElem>().unwrap();
|
||||
match elem.update() {
|
||||
StateUpdate::Set(value) => state = value,
|
||||
StateUpdate::Set(value) => state = value.clone(),
|
||||
StateUpdate::Func(func) => state = func.call_vt(&mut vt, [state])?,
|
||||
}
|
||||
stops.push(state.clone());
|
||||
@ -244,7 +244,7 @@ impl State {
|
||||
|
||||
/// The selector for this state's updates.
|
||||
fn selector(&self) -> Selector {
|
||||
Selector::Elem(UpdateElem::elem(), Some(dict! { "key" => self.key.clone() }))
|
||||
select_where!(UpdateElem, Key => self.key.clone())
|
||||
}
|
||||
}
|
||||
|
||||
@ -383,7 +383,7 @@ impl Show for DisplayElem {
|
||||
#[tracing::instrument(name = "DisplayElem::show", skip(self, vt))]
|
||||
fn show(&self, vt: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(vt.delayed(|vt| {
|
||||
let location = self.0.location().unwrap();
|
||||
let location = self.location().unwrap();
|
||||
let value = self.state().at(vt, location)?;
|
||||
Ok(match self.func() {
|
||||
Some(func) => func.call_vt(vt, [value])?.display(),
|
||||
|
@ -10,6 +10,8 @@ pub use comemo::{Track, Tracked, TrackedMut};
|
||||
#[doc(no_inline)]
|
||||
pub use ecow::{eco_format, EcoString};
|
||||
#[doc(no_inline)]
|
||||
pub use smallvec::{smallvec, SmallVec};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::diag::{bail, error, At, Hint, SourceResult, StrResult};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::doc::*;
|
||||
@ -22,10 +24,10 @@ pub use typst::eval::{
|
||||
pub use typst::geom::*;
|
||||
#[doc(no_inline)]
|
||||
pub use typst::model::{
|
||||
elem, Behave, Behaviour, Construct, Content, Element, Finalize, Fold, Introspector,
|
||||
Label, Locatable, LocatableSelector, Location, Locator, MetaElem, NativeElement,
|
||||
PlainText, Resolve, Selector, Set, Show, StyleChain, StyleVec, Styles, Synthesize,
|
||||
Unlabellable, Vt,
|
||||
elem, select_where, Behave, Behaviour, Construct, Content, Element, ElementFields,
|
||||
Finalize, Fold, Introspector, Label, LocalName, Locatable, LocatableSelector,
|
||||
Location, Locator, MetaElem, NativeElement, PlainText, Resolve, Selector, Set, Show,
|
||||
StyleChain, StyleVec, Styles, Synthesize, Unlabellable, Vt,
|
||||
};
|
||||
#[doc(no_inline)]
|
||||
pub use typst::syntax::{FileId, Span, Spanned};
|
||||
|
@ -1,16 +1,18 @@
|
||||
//! Element interaction.
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
use typst::model::{Behave, Behaviour, Content, StyleChain, StyleVec, StyleVecBuilder};
|
||||
|
||||
/// A wrapper around a [`StyleVecBuilder`] that allows elements to interact.
|
||||
#[derive(Debug)]
|
||||
pub struct BehavedBuilder<'a> {
|
||||
/// The internal builder.
|
||||
builder: StyleVecBuilder<'a, Content>,
|
||||
builder: StyleVecBuilder<'a, Cow<'a, Content>>,
|
||||
/// Staged weak and ignorant elements that we can't yet commit to the
|
||||
/// builder. The option is `Some(_)` for weak elements and `None` for
|
||||
/// ignorant elements.
|
||||
staged: Vec<(Content, Behaviour, StyleChain<'a>)>,
|
||||
staged: Vec<(Cow<'a, Content>, Behaviour, StyleChain<'a>)>,
|
||||
/// What the last non-ignorant item was.
|
||||
last: Behaviour,
|
||||
}
|
||||
@ -41,7 +43,7 @@ impl<'a> BehavedBuilder<'a> {
|
||||
}
|
||||
|
||||
/// Push an item into the sequence.
|
||||
pub fn push(&mut self, elem: Content, styles: StyleChain<'a>) {
|
||||
pub fn push(&mut self, elem: Cow<'a, Content>, styles: StyleChain<'a>) {
|
||||
let interaction = elem
|
||||
.with::<dyn Behave>()
|
||||
.map_or(Behaviour::Supportive, Behave::behaviour);
|
||||
@ -81,12 +83,12 @@ impl<'a> BehavedBuilder<'a> {
|
||||
}
|
||||
|
||||
/// Iterate over the contained elements.
|
||||
pub fn elems(&self) -> impl DoubleEndedIterator<Item = &Content> {
|
||||
pub fn elems(&self) -> impl DoubleEndedIterator<Item = &Cow<'a, Content>> {
|
||||
self.builder.elems().chain(self.staged.iter().map(|(item, ..)| item))
|
||||
}
|
||||
|
||||
/// Return the finish style vec and the common prefix chain.
|
||||
pub fn finish(mut self) -> (StyleVec<Content>, StyleChain<'a>) {
|
||||
pub fn finish(mut self) -> (StyleVec<Cow<'a, Content>>, StyleChain<'a>) {
|
||||
self.flush(false);
|
||||
self.builder.finish()
|
||||
}
|
||||
|
@ -47,13 +47,13 @@ impl ContentExt for Content {
|
||||
}
|
||||
|
||||
fn linked(self, dest: Destination) -> Self {
|
||||
self.styled(MetaElem::set_data(vec![Meta::Link(dest)]))
|
||||
self.styled(MetaElem::set_data(smallvec![Meta::Link(dest)]))
|
||||
}
|
||||
|
||||
fn backlinked(self, loc: Location) -> Self {
|
||||
let mut backlink = Content::empty();
|
||||
backlink.set_location(loc);
|
||||
self.styled(MetaElem::set_data(vec![Meta::Elem(backlink)]))
|
||||
self.styled(MetaElem::set_data(smallvec![Meta::Elem(backlink)]))
|
||||
}
|
||||
|
||||
fn aligned(self, align: Align) -> Self {
|
||||
@ -85,7 +85,7 @@ impl StylesExt for Styles {
|
||||
fn set_family(&mut self, preferred: FontFamily, existing: StyleChain) {
|
||||
self.set(TextElem::set_font(FontList(
|
||||
std::iter::once(preferred)
|
||||
.chain(TextElem::font_in(existing))
|
||||
.chain(TextElem::font_in(existing).into_iter().cloned())
|
||||
.collect(),
|
||||
)));
|
||||
}
|
||||
|
@ -78,7 +78,7 @@ pub struct UnderlineElem {
|
||||
impl Show for UnderlineElem {
|
||||
#[tracing::instrument(name = "UnderlineElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
||||
Ok(self.body().clone().styled(TextElem::set_deco(Decoration {
|
||||
line: DecoLine::Underline {
|
||||
stroke: self.stroke(styles).unwrap_or_default(),
|
||||
offset: self.offset(styles),
|
||||
@ -170,7 +170,7 @@ pub struct OverlineElem {
|
||||
impl Show for OverlineElem {
|
||||
#[tracing::instrument(name = "OverlineElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
||||
Ok(self.body().clone().styled(TextElem::set_deco(Decoration {
|
||||
line: DecoLine::Overline {
|
||||
stroke: self.stroke(styles).unwrap_or_default(),
|
||||
offset: self.offset(styles),
|
||||
@ -247,7 +247,7 @@ pub struct StrikeElem {
|
||||
impl Show for StrikeElem {
|
||||
#[tracing::instrument(name = "StrikeElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
||||
Ok(self.body().clone().styled(TextElem::set_deco(Decoration {
|
||||
// Note that we do not support evade option for strikethrough.
|
||||
line: DecoLine::Strikethrough {
|
||||
stroke: self.stroke(styles).unwrap_or_default(),
|
||||
@ -317,7 +317,7 @@ pub struct HighlightElem {
|
||||
impl Show for HighlightElem {
|
||||
#[tracing::instrument(name = "HighlightElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(TextElem::set_deco(Decoration {
|
||||
Ok(self.body().clone().styled(TextElem::set_deco(Decoration {
|
||||
line: DecoLine::Highlight {
|
||||
fill: self.fill(styles),
|
||||
top_edge: self.top_edge(styles),
|
||||
|
@ -2,9 +2,15 @@ use super::TextElem;
|
||||
use crate::prelude::*;
|
||||
|
||||
/// A text space.
|
||||
#[elem(Behave, Unlabellable, PlainText)]
|
||||
#[elem(Behave, Unlabellable, PlainText, Repr)]
|
||||
pub struct SpaceElem {}
|
||||
|
||||
impl Repr for SpaceElem {
|
||||
fn repr(&self) -> EcoString {
|
||||
EcoString::inline("[ ]")
|
||||
}
|
||||
}
|
||||
|
||||
impl Behave for SpaceElem {
|
||||
fn behaviour(&self) -> Behaviour {
|
||||
Behaviour::Weak(2)
|
||||
@ -98,7 +104,10 @@ pub struct StrongElem {
|
||||
impl Show for StrongElem {
|
||||
#[tracing::instrument(name = "StrongElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(TextElem::set_delta(Delta(self.delta(styles)))))
|
||||
Ok(self
|
||||
.body()
|
||||
.clone()
|
||||
.styled(TextElem::set_delta(Delta(self.delta(styles)))))
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,7 +162,7 @@ pub struct EmphElem {
|
||||
impl Show for EmphElem {
|
||||
#[tracing::instrument(name = "EmphElem::show", skip(self))]
|
||||
fn show(&self, _: &mut Vt, _: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body().styled(TextElem::set_emph(Toggle)))
|
||||
Ok(self.body().clone().styled(TextElem::set_emph(Toggle)))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ pub(super) fn define(global: &mut Scope) {
|
||||
/// With a function call.
|
||||
/// ])
|
||||
/// ```
|
||||
#[elem(Construct, PlainText)]
|
||||
#[elem(Construct, PlainText, Repr)]
|
||||
pub struct TextElem {
|
||||
/// A font family name or priority list of font family names.
|
||||
///
|
||||
@ -97,6 +97,7 @@ pub struct TextElem {
|
||||
/// هذا عربي.
|
||||
/// ```
|
||||
#[default(FontList(vec![FontFamily::new("Linux Libertine")]))]
|
||||
#[borrowed]
|
||||
pub font: FontList,
|
||||
|
||||
/// Whether to allow last resort font fallback when the primary font list
|
||||
@ -558,8 +559,8 @@ pub struct TextElem {
|
||||
pub body: Content,
|
||||
|
||||
/// The text.
|
||||
#[internal]
|
||||
#[required]
|
||||
#[variant(0)]
|
||||
pub text: EcoString,
|
||||
|
||||
/// A delta to apply on the font weight.
|
||||
@ -595,6 +596,12 @@ impl TextElem {
|
||||
}
|
||||
}
|
||||
|
||||
impl Repr for TextElem {
|
||||
fn repr(&self) -> EcoString {
|
||||
eco_format!("[{}]", self.text)
|
||||
}
|
||||
}
|
||||
|
||||
impl Construct for TextElem {
|
||||
fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||
// The text constructor is special: It doesn't create a text element.
|
||||
@ -608,7 +615,7 @@ impl Construct for TextElem {
|
||||
|
||||
impl PlainText for TextElem {
|
||||
fn plain_text(&self, text: &mut EcoString) {
|
||||
text.push_str(&self.text());
|
||||
text.push_str(self.text());
|
||||
}
|
||||
}
|
||||
|
||||
@ -644,12 +651,12 @@ cast! {
|
||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||
pub struct FontList(pub Vec<FontFamily>);
|
||||
|
||||
impl IntoIterator for FontList {
|
||||
type IntoIter = std::vec::IntoIter<FontFamily>;
|
||||
type Item = FontFamily;
|
||||
impl<'a> IntoIterator for &'a FontList {
|
||||
type IntoIter = std::slice::Iter<'a, FontFamily>;
|
||||
type Item = &'a FontFamily;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
self.0.into_iter()
|
||||
self.0.iter()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,6 +101,7 @@ pub struct QuoteElem {
|
||||
///
|
||||
/// #bibliography("works.bib")
|
||||
/// ```
|
||||
#[borrowed]
|
||||
attribution: Option<Attribution>,
|
||||
|
||||
/// The quote.
|
||||
@ -108,7 +109,7 @@ pub struct QuoteElem {
|
||||
body: Content,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub enum Attribution {
|
||||
Content(Content),
|
||||
Label(Label),
|
||||
@ -126,7 +127,7 @@ cast! {
|
||||
|
||||
impl Show for QuoteElem {
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let mut realized = self.body();
|
||||
let mut realized = self.body().clone();
|
||||
let block = self.block(styles);
|
||||
|
||||
if self.quotes(styles) == Smart::Custom(true) || !block {
|
||||
@ -140,16 +141,16 @@ impl Show for QuoteElem {
|
||||
if block {
|
||||
realized = BlockElem::new().with_body(Some(realized)).pack();
|
||||
|
||||
if let Some(attribution) = self.attribution(styles) {
|
||||
if let Some(attribution) = self.attribution(styles).as_ref() {
|
||||
let mut seq = vec![TextElem::packed('—'), SpaceElem::new().pack()];
|
||||
|
||||
match attribution {
|
||||
Attribution::Content(content) => {
|
||||
seq.push(content);
|
||||
seq.push(content.clone());
|
||||
}
|
||||
Attribution::Label(label) => {
|
||||
seq.push(
|
||||
CiteElem::new(label)
|
||||
CiteElem::new(*label)
|
||||
.with_form(Some(CitationForm::Prose))
|
||||
.pack(),
|
||||
);
|
||||
@ -164,7 +165,7 @@ impl Show for QuoteElem {
|
||||
|
||||
realized = PadElem::new(realized).pack();
|
||||
} else if let Some(Attribution::Label(label)) = self.attribution(styles) {
|
||||
realized += SpaceElem::new().pack() + CiteElem::new(label).pack();
|
||||
realized += SpaceElem::new().pack() + CiteElem::new(*label).pack();
|
||||
}
|
||||
|
||||
Ok(realized)
|
||||
|
@ -78,6 +78,7 @@ pub struct SmartquoteElem {
|
||||
/// #set smartquote(quotes: (single: ("[[", "]]"), double: auto))
|
||||
/// 'Das sind eigene Anführungszeichen.'
|
||||
/// ```
|
||||
#[borrowed]
|
||||
pub quotes: Smart<QuoteDict>,
|
||||
}
|
||||
|
||||
|
@ -17,7 +17,7 @@ use super::{
|
||||
FontFamily, FontList, Hyphenate, LinebreakElem, SmartquoteElem, TextElem, TextSize,
|
||||
};
|
||||
use crate::layout::BlockElem;
|
||||
use crate::meta::{Figurable, LocalName};
|
||||
use crate::meta::Figurable;
|
||||
use crate::prelude::*;
|
||||
|
||||
// Shorthand for highlighter closures.
|
||||
@ -145,6 +145,7 @@ pub struct RawElem {
|
||||
///
|
||||
/// This is ```typ also *Typst*```, but inline!
|
||||
/// ````
|
||||
#[borrowed]
|
||||
pub lang: Option<EcoString>,
|
||||
|
||||
/// The horizontal alignment that each line in a raw block should have.
|
||||
@ -225,11 +226,13 @@ pub struct RawElem {
|
||||
let (theme_path, theme_data) = parse_theme(vm, args)?;
|
||||
theme_path.map(Some)
|
||||
)]
|
||||
#[borrowed]
|
||||
pub theme: Option<EcoString>,
|
||||
|
||||
/// The raw file buffer of syntax theme file.
|
||||
#[internal]
|
||||
#[parse(theme_data.map(Some))]
|
||||
#[borrowed]
|
||||
pub theme_data: Option<Bytes>,
|
||||
|
||||
/// The size for a tab stop in spaces. A tab is replaced with enough spaces to
|
||||
@ -252,7 +255,7 @@ pub struct RawElem {
|
||||
/// Made accessible for the [`raw.line` element]($raw.line).
|
||||
/// Allows more styling control in `show` rules.
|
||||
#[synthesized]
|
||||
pub lines: Vec<Content>,
|
||||
pub lines: Vec<RawLine>,
|
||||
}
|
||||
|
||||
#[scope]
|
||||
@ -280,17 +283,20 @@ impl RawElem {
|
||||
|
||||
impl Synthesize for RawElem {
|
||||
fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> {
|
||||
self.push_lang(self.lang(styles));
|
||||
self.push_lang(self.lang(styles).clone());
|
||||
|
||||
let mut text = self.text();
|
||||
let mut text = self.text().clone();
|
||||
if text.contains('\t') {
|
||||
let tab_size = RawElem::tab_size_in(styles);
|
||||
text = align_tabs(&text, tab_size);
|
||||
}
|
||||
|
||||
let count = text.lines().count() as i64;
|
||||
|
||||
let lang = self
|
||||
.lang(styles)
|
||||
.as_ref()
|
||||
.as_ref()
|
||||
.map(|s| s.to_lowercase())
|
||||
.or(Some("txt".into()));
|
||||
|
||||
@ -298,12 +304,12 @@ impl Synthesize for RawElem {
|
||||
load_syntaxes(&self.syntaxes(styles), &self.syntaxes_data(styles)).unwrap()
|
||||
});
|
||||
|
||||
let theme = self.theme(styles).map(|theme_path| {
|
||||
load_theme(theme_path, self.theme_data(styles).unwrap()).unwrap()
|
||||
let theme = self.theme(styles).as_ref().as_ref().map(|theme_path| {
|
||||
load_theme(theme_path, self.theme_data(styles).as_ref().as_ref().unwrap())
|
||||
.unwrap()
|
||||
});
|
||||
|
||||
let theme = theme.as_deref().unwrap_or(&THEME);
|
||||
|
||||
let foreground = theme.settings.foreground.unwrap_or(synt::Color::BLACK);
|
||||
|
||||
let mut seq = vec![];
|
||||
@ -319,15 +325,12 @@ impl Synthesize for RawElem {
|
||||
synt::Highlighter::new(theme),
|
||||
&mut |_, range, style| styled(&text[range], foreground, style),
|
||||
&mut |i, range, line| {
|
||||
seq.push(
|
||||
RawLine::new(
|
||||
i + 1,
|
||||
text.split(is_newline).count() as i64,
|
||||
EcoString::from(&text[range]),
|
||||
Content::sequence(line.drain(..)),
|
||||
)
|
||||
.pack(),
|
||||
);
|
||||
seq.push(RawLine::new(
|
||||
i + 1,
|
||||
count,
|
||||
EcoString::from(&text[range]),
|
||||
Content::sequence(line.drain(..)),
|
||||
));
|
||||
},
|
||||
)
|
||||
.highlight();
|
||||
@ -342,7 +345,6 @@ impl Synthesize for RawElem {
|
||||
})
|
||||
}) {
|
||||
let mut highlighter = syntect::easy::HighlightLines::new(syntax, theme);
|
||||
let len = text.lines().count();
|
||||
for (i, line) in text.lines().enumerate() {
|
||||
let mut line_content = vec![];
|
||||
for (style, piece) in
|
||||
@ -351,18 +353,23 @@ impl Synthesize for RawElem {
|
||||
line_content.push(styled(piece, foreground, style));
|
||||
}
|
||||
|
||||
seq.push(
|
||||
RawLine::new(
|
||||
i as i64 + 1,
|
||||
len as i64,
|
||||
EcoString::from(line),
|
||||
Content::sequence(line_content),
|
||||
)
|
||||
.pack(),
|
||||
);
|
||||
seq.push(RawLine::new(
|
||||
i as i64 + 1,
|
||||
count,
|
||||
EcoString::from(line),
|
||||
Content::sequence(line_content),
|
||||
));
|
||||
}
|
||||
} else {
|
||||
seq.extend(text.lines().map(TextElem::packed));
|
||||
let lines = text.lines();
|
||||
seq.extend(lines.enumerate().map(|(i, line)| {
|
||||
RawLine::new(
|
||||
i as i64 + 1,
|
||||
count,
|
||||
EcoString::from(line),
|
||||
TextElem::packed(line),
|
||||
)
|
||||
}));
|
||||
};
|
||||
|
||||
self.push_lines(seq);
|
||||
@ -375,12 +382,12 @@ impl Show for RawElem {
|
||||
#[tracing::instrument(name = "RawElem::show", skip_all)]
|
||||
fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let mut lines = EcoVec::with_capacity((2 * self.lines().len()).saturating_sub(1));
|
||||
for (i, line) in self.lines().into_iter().enumerate() {
|
||||
for (i, line) in self.lines().iter().enumerate() {
|
||||
if i != 0 {
|
||||
lines.push(LinebreakElem::new().pack());
|
||||
}
|
||||
|
||||
lines.push(line);
|
||||
lines.push(line.clone().pack());
|
||||
}
|
||||
|
||||
let mut realized = Content::sequence(lines);
|
||||
@ -408,7 +415,7 @@ impl Finalize for RawElem {
|
||||
}
|
||||
|
||||
impl LocalName for RawElem {
|
||||
fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str {
|
||||
fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
|
||||
match lang {
|
||||
Lang::ALBANIAN => "List",
|
||||
Lang::ARABIC => "قائمة",
|
||||
@ -443,7 +450,7 @@ impl Figurable for RawElem {}
|
||||
|
||||
impl PlainText for RawElem {
|
||||
fn plain_text(&self, text: &mut EcoString) {
|
||||
text.push_str(&self.text());
|
||||
text.push_str(self.text());
|
||||
}
|
||||
}
|
||||
|
||||
@ -475,13 +482,13 @@ pub struct RawLine {
|
||||
|
||||
impl Show for RawLine {
|
||||
fn show(&self, _vt: &mut Vt, _styles: StyleChain) -> SourceResult<Content> {
|
||||
Ok(self.body())
|
||||
Ok(self.body().clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl PlainText for RawLine {
|
||||
fn plain_text(&self, text: &mut EcoString) {
|
||||
text.push_str(&self.text());
|
||||
text.push_str(self.text());
|
||||
}
|
||||
}
|
||||
|
||||
@ -617,7 +624,7 @@ fn to_syn(color: Color) -> synt::Color {
|
||||
}
|
||||
|
||||
/// A list of bibliography file paths.
|
||||
#[derive(Debug, Default, Clone, Hash)]
|
||||
#[derive(Debug, Default, Clone, PartialEq, Hash)]
|
||||
pub struct SyntaxPaths(Vec<EcoString>);
|
||||
|
||||
cast! {
|
||||
@ -681,7 +688,7 @@ fn parse_syntaxes(
|
||||
}
|
||||
|
||||
#[comemo::memoize]
|
||||
fn load_theme(path: EcoString, bytes: Bytes) -> StrResult<Arc<synt::Theme>> {
|
||||
fn load_theme(path: &str, bytes: &Bytes) -> StrResult<Arc<synt::Theme>> {
|
||||
let mut cursor = std::io::Cursor::new(bytes.as_slice());
|
||||
|
||||
synt::ThemeSet::load_from_reader(&mut cursor)
|
||||
@ -705,7 +712,7 @@ fn parse_theme(
|
||||
let data = vm.world().file(id).at(span)?;
|
||||
|
||||
// Check that parsing works.
|
||||
let _ = load_theme(path.clone(), data.clone()).at(span)?;
|
||||
let _ = load_theme(&path, &data).at(span)?;
|
||||
|
||||
Ok((Some(path), Some(data)))
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use typst::font::{Font, FontStyle, FontVariant};
|
||||
use typst::util::SliceExt;
|
||||
use unicode_script::{Script, UnicodeScript};
|
||||
|
||||
use super::{decorate, FontFamily, NumberType, NumberWidth, TextElem};
|
||||
use super::{decorate, NumberType, NumberWidth, TextElem};
|
||||
use crate::layout::SpanMapper;
|
||||
use crate::prelude::*;
|
||||
|
||||
@ -320,7 +320,7 @@ impl<'a> ShapedText<'a> {
|
||||
for family in families(self.styles) {
|
||||
if let Some(font) = world
|
||||
.book()
|
||||
.select(family.as_str(), self.variant)
|
||||
.select(family, self.variant)
|
||||
.and_then(|id| world.font(id))
|
||||
{
|
||||
expand(&font, None);
|
||||
@ -424,7 +424,7 @@ impl<'a> ShapedText<'a> {
|
||||
None
|
||||
};
|
||||
let mut chain = families(self.styles)
|
||||
.map(|family| book.select(family.as_str(), self.variant))
|
||||
.map(|family| book.select(family, self.variant))
|
||||
.chain(fallback_func.iter().map(|f| f()))
|
||||
.flatten();
|
||||
|
||||
@ -593,11 +593,11 @@ pub fn shape<'a>(
|
||||
}
|
||||
|
||||
/// Shape text with font fallback using the `families` iterator.
|
||||
fn shape_segment(
|
||||
fn shape_segment<'a>(
|
||||
ctx: &mut ShapingContext,
|
||||
base: usize,
|
||||
text: &str,
|
||||
mut families: impl Iterator<Item = FontFamily> + Clone,
|
||||
mut families: impl Iterator<Item = &'a str> + Clone,
|
||||
) {
|
||||
// Fonts dont have newlines and tabs.
|
||||
if text.chars().all(|c| c == '\n' || c == '\t') {
|
||||
@ -608,7 +608,7 @@ fn shape_segment(
|
||||
let world = ctx.vt.world;
|
||||
let book = world.book();
|
||||
let mut selection = families.find_map(|family| {
|
||||
book.select(family.as_str(), ctx.variant)
|
||||
book.select(family, ctx.variant)
|
||||
.and_then(|id| world.font(id))
|
||||
.filter(|font| !ctx.used.contains(font))
|
||||
});
|
||||
@ -871,7 +871,7 @@ pub fn variant(styles: StyleChain) -> FontVariant {
|
||||
}
|
||||
|
||||
/// Resolve a prioritized iterator over the font families.
|
||||
pub fn families(styles: StyleChain) -> impl Iterator<Item = FontFamily> + Clone {
|
||||
pub fn families(styles: StyleChain) -> impl Iterator<Item = &str> + Clone {
|
||||
const FALLBACKS: &[&str] = &[
|
||||
"linux libertine",
|
||||
"twitter color emoji",
|
||||
@ -883,7 +883,8 @@ pub fn families(styles: StyleChain) -> impl Iterator<Item = FontFamily> + Clone
|
||||
let tail = if TextElem::fallback_in(styles) { FALLBACKS } else { &[] };
|
||||
TextElem::font_in(styles)
|
||||
.into_iter()
|
||||
.chain(tail.iter().copied().map(FontFamily::new))
|
||||
.map(|family| family.as_str())
|
||||
.chain(tail.iter().copied())
|
||||
}
|
||||
|
||||
/// Collect the tags of the OpenType features to apply.
|
||||
|
@ -44,7 +44,7 @@ pub struct SubElem {
|
||||
impl Show for SubElem {
|
||||
#[tracing::instrument(name = "SubElem::show", skip_all)]
|
||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let body = self.body();
|
||||
let body = self.body().clone();
|
||||
let mut transformed = None;
|
||||
if self.typographic(styles) {
|
||||
if let Some(text) = search_text(&body, true) {
|
||||
@ -104,7 +104,7 @@ pub struct SuperElem {
|
||||
impl Show for SuperElem {
|
||||
#[tracing::instrument(name = "SuperElem::show", skip_all)]
|
||||
fn show(&self, vt: &mut Vt, styles: StyleChain) -> SourceResult<Content> {
|
||||
let body = self.body();
|
||||
let body = self.body().clone();
|
||||
let mut transformed = None;
|
||||
if self.typographic(styles) {
|
||||
if let Some(text) = search_text(&body, false) {
|
||||
@ -127,7 +127,7 @@ fn search_text(content: &Content, sub: bool) -> Option<EcoString> {
|
||||
if content.is::<SpaceElem>() {
|
||||
Some(' '.into())
|
||||
} else if let Some(elem) = content.to::<TextElem>() {
|
||||
convert_script(&elem.text(), sub)
|
||||
convert_script(elem.text(), sub)
|
||||
} else if let Some(children) = content.to_sequence() {
|
||||
let mut full = EcoString::new();
|
||||
for item in children {
|
||||
|
@ -6,7 +6,7 @@ use typst::image::{Image, ImageFormat, RasterFormat, VectorFormat};
|
||||
use typst::util::option_eq;
|
||||
|
||||
use crate::compute::Readable;
|
||||
use crate::meta::{Figurable, LocalName};
|
||||
use crate::meta::Figurable;
|
||||
use crate::prelude::*;
|
||||
use crate::text::families;
|
||||
|
||||
@ -41,6 +41,7 @@ pub struct ImageElem {
|
||||
let data = vm.world().file(id).at(span)?;
|
||||
path
|
||||
)]
|
||||
#[borrowed]
|
||||
pub path: EcoString,
|
||||
|
||||
/// The raw file data.
|
||||
@ -157,11 +158,11 @@ impl Layout for ImageElem {
|
||||
};
|
||||
|
||||
let image = Image::with_fonts(
|
||||
data.into(),
|
||||
data.clone().into(),
|
||||
format,
|
||||
self.alt(styles),
|
||||
vt.world,
|
||||
&families(styles).map(|s| s.as_str().into()).collect::<Vec<_>>(),
|
||||
&families(styles).map(|s| s.into()).collect::<Vec<_>>(),
|
||||
)
|
||||
.at(self.span())?;
|
||||
|
||||
@ -223,7 +224,7 @@ impl Layout for ImageElem {
|
||||
}
|
||||
|
||||
impl LocalName for ImageElem {
|
||||
fn local_name(&self, lang: Lang, region: Option<Region>) -> &'static str {
|
||||
fn local_name(lang: Lang, region: Option<Region>) -> &'static str {
|
||||
match lang {
|
||||
Lang::ALBANIAN => "Figurë",
|
||||
Lang::ARABIC => "شكل",
|
||||
|
@ -77,7 +77,7 @@ impl Layout for PathElem {
|
||||
.to_point()
|
||||
};
|
||||
|
||||
let vertices: Vec<PathVertex> = self.vertices();
|
||||
let vertices = self.vertices();
|
||||
let points: Vec<Point> = vertices.iter().map(|c| resolve(c.vertex())).collect();
|
||||
|
||||
let mut size = Size::zero();
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -189,6 +189,9 @@ pub fn ty(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||
/// - `#[synthesized]`: The field cannot be specified in a constructor or set
|
||||
/// rule. Instead, it is added to an element before its show rule runs
|
||||
/// through the `Synthesize` trait.
|
||||
/// - `#[variant]`: Allows setting the ID of a field's variant. This is used
|
||||
/// for fields that are accessed in `typst` and not `typst-library`. It gives
|
||||
/// the field a stable ID that can be used to access it.
|
||||
#[proc_macro_attribute]
|
||||
pub fn elem(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
|
||||
let item = syn::parse_macro_input!(item as syn::ItemStruct);
|
||||
|
@ -28,6 +28,7 @@ fontdb = { version = "0.15", default-features = false }
|
||||
image = { version = "0.24", default-features = false, features = ["png", "jpeg", "gif"] }
|
||||
indexmap = { version = "2", features = ["serde"] }
|
||||
kurbo = "0.9"
|
||||
lasso = { version = "0.7.2", features = ["ahasher", "multi-threaded"] }
|
||||
log = "0.4"
|
||||
miniz_oxide = "0.7"
|
||||
once_cell = "1"
|
||||
@ -57,6 +58,7 @@ xmp-writer = "0.2"
|
||||
time = { version = "0.3.20", features = ["std", "formatting", "macros", "parsing"] }
|
||||
wasmi = "0.31.0"
|
||||
xmlparser = "0.13.5"
|
||||
smallvec = { version = "1.11.1", features = ["union", "const_generics", "const_new"] }
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
stacker = "0.1.15"
|
||||
|
@ -190,13 +190,13 @@ impl Frame {
|
||||
///
|
||||
/// This panics if the layer is greater than the number of layers present.
|
||||
#[track_caller]
|
||||
pub fn insert(&mut self, layer: usize, pos: Point, items: FrameItem) {
|
||||
Arc::make_mut(&mut self.items).insert(layer, (pos, items));
|
||||
pub fn insert(&mut self, layer: usize, pos: Point, item: FrameItem) {
|
||||
Arc::make_mut(&mut self.items).insert(layer, (pos, item));
|
||||
}
|
||||
|
||||
/// Add an item at a position in the background.
|
||||
pub fn prepend(&mut self, pos: Point, item: FrameItem) {
|
||||
Arc::make_mut(&mut self.items).insert(0, (pos, item));
|
||||
self.insert(0, pos, item);
|
||||
}
|
||||
|
||||
/// Add multiple items at a position in the background.
|
||||
|
@ -5,6 +5,7 @@ use std::ops::{Add, AddAssign};
|
||||
|
||||
use ecow::{eco_format, EcoString, EcoVec};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::{
|
||||
cast, func, ops, scope, ty, Args, Bytes, CastInfo, FromValue, Func, IntoValue,
|
||||
@ -907,18 +908,44 @@ impl<T> Reflect for Vec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Reflect, const N: usize> Reflect for SmallVec<[T; N]> {
|
||||
fn input() -> CastInfo {
|
||||
Array::input()
|
||||
}
|
||||
|
||||
fn output() -> CastInfo {
|
||||
Array::output()
|
||||
}
|
||||
|
||||
fn castable(value: &Value) -> bool {
|
||||
Array::castable(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoValue> IntoValue for Vec<T> {
|
||||
fn into_value(self) -> Value {
|
||||
Value::Array(self.into_iter().map(IntoValue::into_value).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoValue, const N: usize> IntoValue for SmallVec<[T; N]> {
|
||||
fn into_value(self) -> Value {
|
||||
Value::Array(self.into_iter().map(IntoValue::into_value).collect())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromValue> FromValue for Vec<T> {
|
||||
fn from_value(value: Value) -> StrResult<Self> {
|
||||
value.cast::<Array>()?.into_iter().map(Value::cast).collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromValue, const N: usize> FromValue for SmallVec<[T; N]> {
|
||||
fn from_value(value: Value) -> StrResult<Self> {
|
||||
value.cast::<Array>()?.into_iter().map(Value::cast).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// The error message when the array is empty.
|
||||
#[cold]
|
||||
fn array_is_empty() -> EcoString {
|
||||
|
@ -1,10 +1,14 @@
|
||||
pub use typst_macros::{cast, Cast};
|
||||
use unicode_math_class::MathClass;
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::Write;
|
||||
use std::hash::Hash;
|
||||
use std::ops::Add;
|
||||
|
||||
use comemo::Prehashed;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use smallvec::SmallVec;
|
||||
use unicode_math_class::MathClass;
|
||||
|
||||
use super::{Repr, Type, Value};
|
||||
use crate::diag::{At, SourceResult, StrResult};
|
||||
@ -80,6 +84,20 @@ impl<T: Reflect> Reflect for Spanned<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Reflect> Reflect for Prehashed<T> {
|
||||
fn input() -> CastInfo {
|
||||
T::input()
|
||||
}
|
||||
|
||||
fn output() -> CastInfo {
|
||||
T::output()
|
||||
}
|
||||
|
||||
fn castable(value: &Value) -> bool {
|
||||
T::castable(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Reflect> Reflect for StrResult<T> {
|
||||
fn input() -> CastInfo {
|
||||
T::input()
|
||||
@ -150,12 +168,24 @@ impl IntoValue for Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoValue + Clone> IntoValue for Cow<'_, T> {
|
||||
fn into_value(self) -> Value {
|
||||
self.into_owned().into_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoValue> IntoValue for Spanned<T> {
|
||||
fn into_value(self) -> Value {
|
||||
self.v.into_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: IntoValue + Hash + 'static> IntoValue for Prehashed<T> {
|
||||
fn into_value(self) -> Value {
|
||||
self.into_inner().into_value()
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast a Rust type or result into a [`SourceResult<Value>`].
|
||||
///
|
||||
/// Converts `T`, [`StrResult<T>`], or [`SourceResult<T>`] into
|
||||
@ -203,6 +233,12 @@ impl FromValue for Value {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromValue + Hash + 'static> FromValue for Prehashed<T> {
|
||||
fn from_value(value: Value) -> StrResult<Self> {
|
||||
Ok(Self::new(T::from_value(value)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: FromValue> FromValue<Spanned<Value>> for T {
|
||||
fn from_value(value: Spanned<Value>) -> StrResult<Self> {
|
||||
T::from_value(value.v)
|
||||
@ -217,7 +253,7 @@ impl<T: FromValue> FromValue<Spanned<Value>> for Spanned<T> {
|
||||
}
|
||||
|
||||
/// Describes a possible value for a cast.
|
||||
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd)]
|
||||
#[derive(Debug, Clone, PartialEq, Hash, PartialOrd)]
|
||||
pub enum CastInfo {
|
||||
/// Any value is okay.
|
||||
Any,
|
||||
@ -335,7 +371,12 @@ impl<T> Container for Vec<T> {
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Container for SmallVec<[T; N]> {
|
||||
type Inner = T;
|
||||
}
|
||||
|
||||
/// An uninhabitable type.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
pub enum Never {}
|
||||
|
||||
impl Reflect for Never {
|
||||
|
@ -2,7 +2,7 @@ use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use comemo::{Prehashed, Tracked, TrackedMut};
|
||||
use ecow::EcoString;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::{
|
||||
@ -368,10 +368,25 @@ impl Func {
|
||||
) -> StrResult<Selector> {
|
||||
let fields = args.to_named();
|
||||
args.items.retain(|arg| arg.name.is_none());
|
||||
Ok(self
|
||||
|
||||
let element = self
|
||||
.element()
|
||||
.ok_or("`where()` can only be called on element functions")?
|
||||
.where_(fields))
|
||||
.ok_or("`where()` can only be called on element functions")?;
|
||||
|
||||
let fields = fields
|
||||
.into_iter()
|
||||
.map(|(key, value)| {
|
||||
element.field_id(&key).map(|id| (id, value)).ok_or_else(|| {
|
||||
eco_format!(
|
||||
"element `{}` does not have field `{}`",
|
||||
element.name(),
|
||||
key
|
||||
)
|
||||
})
|
||||
})
|
||||
.collect::<StrResult<smallvec::SmallVec<_>>>()?;
|
||||
|
||||
Ok(element.where_(fields))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ pub struct LangItems {
|
||||
/// The text element.
|
||||
pub text_elem: Element,
|
||||
/// Get the string if this is a text element.
|
||||
pub text_str: fn(&Content) -> Option<EcoString>,
|
||||
pub text_str: fn(&Content) -> Option<&EcoString>,
|
||||
/// A smart quote: `'` or `"`.
|
||||
pub smart_quote: fn(double: bool) -> Content,
|
||||
/// A paragraph break.
|
||||
|
@ -663,7 +663,7 @@ impl Eval for ast::Label<'_> {
|
||||
|
||||
#[tracing::instrument(name = "Label::eval", skip_all)]
|
||||
fn eval(self, _: &mut Vm) -> SourceResult<Self::Output> {
|
||||
Ok(Value::Label(Label(self.get().into())))
|
||||
Ok(Value::Label(Label::new(self.get())))
|
||||
}
|
||||
}
|
||||
|
||||
@ -672,7 +672,7 @@ impl Eval for ast::Ref<'_> {
|
||||
|
||||
#[tracing::instrument(name = "Ref::eval", skip_all)]
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let label = Label(self.target().into());
|
||||
let label = Label::new(self.target());
|
||||
let supplement = self.supplement().map(|block| block.eval(vm)).transpose()?;
|
||||
Ok((vm.items.reference)(label, supplement))
|
||||
}
|
||||
@ -1295,7 +1295,7 @@ impl Eval for ast::Args<'_> {
|
||||
type Output = Args;
|
||||
|
||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||
let mut items = EcoVec::new();
|
||||
let mut items = EcoVec::with_capacity(self.items().count());
|
||||
|
||||
for arg in self.items() {
|
||||
let span = arg.span();
|
||||
|
@ -617,7 +617,7 @@ cast! {
|
||||
.map_err(|_| "bytes are not valid utf-8")?
|
||||
.into()
|
||||
),
|
||||
v: Label => Self::Str(v.0.into()),
|
||||
v: Label => Self::Str(v.as_str().into()),
|
||||
v: Type => Self::Str(v.long_name().into()),
|
||||
v: Str => Self::Str(v),
|
||||
}
|
||||
|
@ -158,7 +158,7 @@ impl Value {
|
||||
Self::Symbol(symbol) => symbol.clone().modified(field).map(Self::Symbol),
|
||||
Self::Version(version) => version.component(field).map(Self::Int),
|
||||
Self::Dict(dict) => dict.get(field).cloned(),
|
||||
Self::Content(content) => content.get(field),
|
||||
Self::Content(content) => content.field_by_name(field),
|
||||
Self::Type(ty) => ty.field(field).cloned(),
|
||||
Self::Func(func) => func.field(field).cloned(),
|
||||
Self::Module(module) => module.field(field).cloned(),
|
||||
|
@ -113,11 +113,11 @@ struct HeadingNode {
|
||||
impl HeadingNode {
|
||||
fn leaf(element: Content) -> Self {
|
||||
HeadingNode {
|
||||
level: element.expect_field::<NonZeroUsize>("level"),
|
||||
level: element.expect_field_by_name::<NonZeroUsize>("level"),
|
||||
// 'bookmarked' set to 'auto' falls back to the value of 'outlined'.
|
||||
bookmarked: element
|
||||
.expect_field::<Smart<bool>>("bookmarked")
|
||||
.unwrap_or_else(|| element.expect_field::<bool>("outlined")),
|
||||
.expect_field_by_name::<Smart<bool>>("bookmarked")
|
||||
.unwrap_or_else(|| element.expect_field_by_name::<bool>("outlined")),
|
||||
element,
|
||||
children: Vec::new(),
|
||||
}
|
||||
@ -158,7 +158,7 @@ fn write_outline_item(
|
||||
outline.count(-(node.children.len() as i32));
|
||||
}
|
||||
|
||||
let body = node.element.expect_field::<Content>("body");
|
||||
let body = node.element.expect_field_by_name::<Content>("body");
|
||||
outline.title(TextStr(body.plain_text().trim()));
|
||||
|
||||
let loc = node.element.location().unwrap();
|
||||
|
82
crates/typst/src/model/block.rs
Normal file
82
crates/typst/src/model/block.rs
Normal file
@ -0,0 +1,82 @@
|
||||
use std::any::Any;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
/// A block storage for storing stylechain values either on the stack (if they
|
||||
/// fit) or on the heap.
|
||||
///
|
||||
/// We're using a `Box` since values will either be contained in an `Arc` and
|
||||
/// therefore already on the heap or they will be small enough that we can just
|
||||
/// clone them.
|
||||
pub struct Block(Box<dyn Blockable>);
|
||||
|
||||
impl Block {
|
||||
/// Creates a new block.
|
||||
pub fn new<T: Blockable>(value: T) -> Self {
|
||||
Self(Box::new(value))
|
||||
}
|
||||
|
||||
/// Downcasts the block to the specified type.
|
||||
pub fn downcast<T: Blockable>(&self) -> Option<&T> {
|
||||
self.0.as_any().downcast_ref()
|
||||
}
|
||||
|
||||
/// Downcasts mutably the block to the specified type.
|
||||
pub fn downcast_mut<T: Blockable>(&mut self) -> Option<&mut T> {
|
||||
self.0.as_any_mut().downcast_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Block {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.dyn_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Block {
|
||||
fn clone(&self) -> Self {
|
||||
self.0.dyn_clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Block {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that can be stored in a block.
|
||||
///
|
||||
/// Auto derived for all types that implement [`Any`], [`Clone`], [`Hash`],
|
||||
/// [`Debug`], [`Send`] and [`Sync`].
|
||||
pub trait Blockable: Debug + Send + Sync + 'static {
|
||||
/// Equivalent to `downcast_ref` for the block.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Equivalent to `downcast_mut` for the block.
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
|
||||
/// Equivalent to [`Hash`] for the block.
|
||||
fn dyn_hash(&self, state: &mut dyn Hasher);
|
||||
|
||||
/// Equivalent to [`Clone`] for the block.
|
||||
fn dyn_clone(&self) -> Block;
|
||||
}
|
||||
|
||||
impl<T: Clone + Hash + Debug + Send + Sync + 'static> Blockable for T {
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any {
|
||||
self
|
||||
}
|
||||
|
||||
fn dyn_hash(&self, mut state: &mut dyn Hasher) {
|
||||
self.hash(&mut state);
|
||||
}
|
||||
|
||||
fn dyn_clone(&self) -> Block {
|
||||
Block(Box::new(self.clone()))
|
||||
}
|
||||
}
|
@ -2,14 +2,17 @@ use std::any::TypeId;
|
||||
use std::fmt::Debug;
|
||||
use std::iter::{self, Sum};
|
||||
use std::ops::{Add, AddAssign};
|
||||
use std::sync::Arc;
|
||||
|
||||
use comemo::Prehashed;
|
||||
use ecow::{eco_format, EcoString, EcoVec};
|
||||
use ecow::{eco_format, EcoString};
|
||||
use serde::{Serialize, Serializer};
|
||||
use smallvec::SmallVec;
|
||||
use typst_macros::elem;
|
||||
|
||||
use super::{
|
||||
elem, Behave, Behaviour, Element, Guard, Label, Locatable, Location, NativeElement,
|
||||
Recipe, Selector, Style, Styles, Synthesize,
|
||||
Behave, Behaviour, Element, Guard, Label, Location, NativeElement, Recipe, Selector,
|
||||
Style, Styles,
|
||||
};
|
||||
use crate::diag::{SourceResult, StrResult};
|
||||
use crate::doc::Meta;
|
||||
@ -61,35 +64,146 @@ use crate::util::pretty_array_like;
|
||||
/// elements the content is composed of and what fields they have.
|
||||
/// Alternatively, you can inspect the output of the [`repr`]($repr) function.
|
||||
#[ty(scope)]
|
||||
#[derive(Debug, Clone, Hash)]
|
||||
#[allow(clippy::derived_hash_with_manual_eq)]
|
||||
pub struct Content {
|
||||
elem: Element,
|
||||
attrs: EcoVec<Attr>,
|
||||
}
|
||||
|
||||
/// Attributes that can be attached to content.
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
enum Attr {
|
||||
Span(Span),
|
||||
Field(EcoString),
|
||||
Value(Prehashed<Value>),
|
||||
Child(Prehashed<Content>),
|
||||
Styles(Styles),
|
||||
Prepared,
|
||||
Guard(Guard),
|
||||
Location(Location),
|
||||
}
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Content(Arc<dyn NativeElement>);
|
||||
|
||||
impl Content {
|
||||
/// Create an empty element.
|
||||
pub fn new(elem: Element) -> Self {
|
||||
Self { elem, attrs: EcoVec::new() }
|
||||
/// Creates a new content from an element.
|
||||
#[inline]
|
||||
pub fn new<E: NativeElement>(elem: E) -> Self {
|
||||
Self(Arc::new(elem))
|
||||
}
|
||||
|
||||
/// Create empty content.
|
||||
/// Creates a new empty sequence content.
|
||||
#[inline]
|
||||
pub fn empty() -> Self {
|
||||
Self::new(SequenceElem::elem())
|
||||
Self::new(SequenceElem::default())
|
||||
}
|
||||
|
||||
/// Get the element of this content.
|
||||
#[inline]
|
||||
pub fn elem(&self) -> Element {
|
||||
self.0.dyn_elem()
|
||||
}
|
||||
|
||||
/// Get the span of the content.
|
||||
#[inline]
|
||||
pub fn span(&self) -> Span {
|
||||
self.0.span()
|
||||
}
|
||||
|
||||
/// Set the span of the content.
|
||||
pub fn spanned(mut self, span: Span) -> Self {
|
||||
self.make_mut().set_span(span);
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the label of the content.
|
||||
#[inline]
|
||||
pub fn label(&self) -> Option<Label> {
|
||||
self.0.label()
|
||||
}
|
||||
|
||||
/// Set the label of the content.
|
||||
pub fn labelled(mut self, label: Label) -> Self {
|
||||
self.make_mut().set_label(label);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the location of the content.
|
||||
pub fn set_location(&mut self, location: Location) {
|
||||
self.make_mut().set_location(location);
|
||||
}
|
||||
|
||||
/// Disable a show rule recipe.
|
||||
pub fn guarded(mut self, guard: Guard) -> Self {
|
||||
self.make_mut().push_guard(guard);
|
||||
self.0.into()
|
||||
}
|
||||
|
||||
/// Whether the content needs to be realized specially.
|
||||
pub fn needs_preparation(&self) -> bool {
|
||||
self.0.needs_preparation()
|
||||
}
|
||||
|
||||
/// Check whether a show rule recipe is disabled.
|
||||
pub fn is_guarded(&self, guard: Guard) -> bool {
|
||||
self.0.is_guarded(guard)
|
||||
}
|
||||
|
||||
/// Whether no show rule was executed for this content so far.
|
||||
pub fn is_pristine(&self) -> bool {
|
||||
self.0.is_pristine()
|
||||
}
|
||||
|
||||
/// Whether this content has already been prepared.
|
||||
pub fn is_prepared(&self) -> bool {
|
||||
self.0.is_prepared()
|
||||
}
|
||||
|
||||
/// Mark this content as prepared.
|
||||
pub fn mark_prepared(&mut self) {
|
||||
self.make_mut().mark_prepared();
|
||||
}
|
||||
|
||||
/// Get a field by ID.
|
||||
///
|
||||
/// This is the preferred way to access fields. However, you can only use it
|
||||
/// if you have set the field IDs yourself or are using the field IDs
|
||||
/// generated by the `#[elem]` macro.
|
||||
#[inline]
|
||||
pub fn get(&self, id: u8) -> Option<Value> {
|
||||
self.0.field(id)
|
||||
}
|
||||
|
||||
/// Get a field by name.
|
||||
///
|
||||
/// If you have access to the field IDs of the element, use [`Self::get`]
|
||||
/// instead.
|
||||
#[inline]
|
||||
pub fn get_by_name(&self, name: &str) -> Option<Value> {
|
||||
let id = self.elem().field_id(name)?;
|
||||
self.get(id)
|
||||
}
|
||||
|
||||
/// Get a field by ID, returning a missing field error if it does not exist.
|
||||
///
|
||||
/// This is the preferred way to access fields. However, you can only use it
|
||||
/// if you have set the field IDs yourself or are using the field IDs
|
||||
/// generated by the `#[elem]` macro.
|
||||
#[inline]
|
||||
pub fn field(&self, id: u8) -> StrResult<Value> {
|
||||
self.get(id)
|
||||
.ok_or_else(|| missing_field(self.elem().field_name(id).unwrap()))
|
||||
}
|
||||
|
||||
/// Get a field by name, returning a missing field error if it does not
|
||||
/// exist.
|
||||
///
|
||||
/// If you have access to the field IDs of the element, use [`Self::field`]
|
||||
/// instead.
|
||||
#[inline]
|
||||
pub fn field_by_name(&self, name: &str) -> StrResult<Value> {
|
||||
let id = self.elem().field_id(name).ok_or_else(|| missing_field(name))?;
|
||||
self.field(id)
|
||||
}
|
||||
|
||||
/// Expect a field on the content to exist as a specified type.
|
||||
#[track_caller]
|
||||
pub fn expect_field<T: FromValue>(&self, id: u8) -> T {
|
||||
self.field(id).unwrap().cast().unwrap()
|
||||
}
|
||||
|
||||
/// Expect a field on the content to exist as a specified type.
|
||||
#[track_caller]
|
||||
pub fn expect_field_by_name<T: FromValue>(&self, name: &str) -> T {
|
||||
self.field_by_name(name).unwrap().cast().unwrap()
|
||||
}
|
||||
|
||||
/// Set a field to the content.
|
||||
pub fn with_field(mut self, id: u8, value: impl IntoValue) -> Self {
|
||||
self.make_mut().set_field(id, value.into_value()).unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
/// Create a new sequence element from multiples elements.
|
||||
@ -97,42 +211,80 @@ impl Content {
|
||||
let mut iter = iter.into_iter();
|
||||
let Some(first) = iter.next() else { return Self::empty() };
|
||||
let Some(second) = iter.next() else { return first };
|
||||
let mut content = Content::empty();
|
||||
content.attrs.push(Attr::Child(Prehashed::new(first)));
|
||||
content.attrs.push(Attr::Child(Prehashed::new(second)));
|
||||
content
|
||||
.attrs
|
||||
.extend(iter.map(|child| Attr::Child(Prehashed::new(child))));
|
||||
content
|
||||
SequenceElem::new(
|
||||
std::iter::once(Prehashed::new(first))
|
||||
.chain(std::iter::once(Prehashed::new(second)))
|
||||
.chain(iter.map(Prehashed::new))
|
||||
.collect(),
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Whether the content is an empty sequence.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.is::<SequenceElem>() && self.attrs.is_empty()
|
||||
/// Access the children if this is a sequence.
|
||||
pub fn to_sequence(&self) -> Option<impl Iterator<Item = &Prehashed<Content>>> {
|
||||
let Some(sequence) = self.to::<SequenceElem>() else {
|
||||
return None;
|
||||
};
|
||||
|
||||
Some(sequence.children.iter())
|
||||
}
|
||||
|
||||
/// Whether the contained element is of type `T`.
|
||||
pub fn is<T: NativeElement>(&self) -> bool {
|
||||
self.elem == T::elem()
|
||||
self.elem() == T::elem()
|
||||
}
|
||||
|
||||
/// Cast to `T` if the contained element is of type `T`.
|
||||
pub fn to<T: NativeElement>(&self) -> Option<&T> {
|
||||
T::unpack(self)
|
||||
/// Whether the contained element has the given capability.
|
||||
pub fn can<C>(&self) -> bool
|
||||
where
|
||||
C: ?Sized + 'static,
|
||||
{
|
||||
self.elem().can::<C>()
|
||||
}
|
||||
|
||||
/// Access the children if this is a sequence.
|
||||
pub fn to_sequence(&self) -> Option<impl Iterator<Item = &Self>> {
|
||||
if !self.is_sequence() {
|
||||
return None;
|
||||
}
|
||||
Some(self.attrs.iter().filter_map(Attr::child))
|
||||
/// Whether the contained element has the given capability where the
|
||||
/// capability is given by a `TypeId`.
|
||||
pub fn can_type_id(&self, type_id: TypeId) -> bool {
|
||||
self.elem().can_type_id(type_id)
|
||||
}
|
||||
|
||||
/// Cast to a trait object if the contained element has the given
|
||||
/// capability.
|
||||
pub fn with<C>(&self) -> Option<&C>
|
||||
where
|
||||
C: ?Sized + 'static,
|
||||
{
|
||||
let vtable = self.elem().vtable()(TypeId::of::<C>())?;
|
||||
let data = Arc::as_ptr(&self.0) as *const ();
|
||||
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) })
|
||||
}
|
||||
|
||||
/// Cast to a mutable trait object if the contained element has the given
|
||||
/// capability.
|
||||
pub fn with_mut<C>(&mut self) -> Option<&mut C>
|
||||
where
|
||||
C: ?Sized + 'static,
|
||||
{
|
||||
// Safety: We ensure the element is not shared.
|
||||
let vtable = self.elem().vtable()(TypeId::of::<C>())?;
|
||||
let data = self.make_mut() as *mut dyn NativeElement as *mut ();
|
||||
Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) })
|
||||
}
|
||||
|
||||
/// Whether the content is a sequence.
|
||||
pub fn is_sequence(&self) -> bool {
|
||||
self.is::<SequenceElem>()
|
||||
}
|
||||
|
||||
/// Whether the content is an empty sequence.
|
||||
pub fn is_empty(&self) -> bool {
|
||||
let Some(sequence) = self.to::<SequenceElem>() else {
|
||||
return false;
|
||||
};
|
||||
|
||||
sequence.children.is_empty()
|
||||
}
|
||||
|
||||
/// Also auto expands sequence of sequences into flat sequence
|
||||
pub fn sequence_recursive_for_each(&self, f: &mut impl FnMut(&Self)) {
|
||||
if let Some(children) = self.to_sequence() {
|
||||
@ -144,183 +296,13 @@ impl Content {
|
||||
|
||||
/// Access the child and styles.
|
||||
pub fn to_styled(&self) -> Option<(&Content, &Styles)> {
|
||||
if !self.is::<StyledElem>() {
|
||||
return None;
|
||||
}
|
||||
let child = self.attrs.iter().find_map(Attr::child)?;
|
||||
let styles = self.attrs.iter().find_map(Attr::styles)?;
|
||||
let styled = self.to::<StyledElem>()?;
|
||||
|
||||
let child = styled.child();
|
||||
let styles = styled.styles();
|
||||
Some((child, styles))
|
||||
}
|
||||
|
||||
/// Whether the contained element has the given capability.
|
||||
pub fn can<C>(&self) -> bool
|
||||
where
|
||||
C: ?Sized + 'static,
|
||||
{
|
||||
self.elem.can::<C>()
|
||||
}
|
||||
|
||||
/// Whether the contained element has the given capability where the
|
||||
/// capability is given by a `TypeId`.
|
||||
pub fn can_type_id(&self, type_id: TypeId) -> bool {
|
||||
self.elem.can_type_id(type_id)
|
||||
}
|
||||
|
||||
/// Cast to a trait object if the contained element has the given
|
||||
/// capability.
|
||||
pub fn with<C>(&self) -> Option<&C>
|
||||
where
|
||||
C: ?Sized + 'static,
|
||||
{
|
||||
let vtable = self.elem.vtable()(TypeId::of::<C>())?;
|
||||
let data = self as *const Self as *const ();
|
||||
Some(unsafe { &*crate::util::fat::from_raw_parts(data, vtable) })
|
||||
}
|
||||
|
||||
/// Cast to a mutable trait object if the contained element has the given
|
||||
/// capability.
|
||||
pub fn with_mut<C>(&mut self) -> Option<&mut C>
|
||||
where
|
||||
C: ?Sized + 'static,
|
||||
{
|
||||
let vtable = self.elem.vtable()(TypeId::of::<C>())?;
|
||||
let data = self as *mut Self as *mut ();
|
||||
Some(unsafe { &mut *crate::util::fat::from_raw_parts_mut(data, vtable) })
|
||||
}
|
||||
|
||||
/// The content's span.
|
||||
pub fn span(&self) -> Span {
|
||||
self.attrs.iter().find_map(Attr::span).unwrap_or(Span::detached())
|
||||
}
|
||||
|
||||
/// Attach a span to the content if it doesn't already have one.
|
||||
pub fn spanned(mut self, span: Span) -> Self {
|
||||
if self.span().is_detached() {
|
||||
self.attrs.push(Attr::Span(span));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Attach a field to the content.
|
||||
pub fn with_field(
|
||||
mut self,
|
||||
name: impl Into<EcoString>,
|
||||
value: impl IntoValue,
|
||||
) -> Self {
|
||||
self.push_field(name, value);
|
||||
self
|
||||
}
|
||||
|
||||
/// Attach a field to the content.
|
||||
pub fn push_field(&mut self, name: impl Into<EcoString>, value: impl IntoValue) {
|
||||
let name = name.into();
|
||||
if let Some(i) = self.attrs.iter().position(|attr| match attr {
|
||||
Attr::Field(field) => *field == name,
|
||||
_ => false,
|
||||
}) {
|
||||
self.attrs.make_mut()[i + 1] =
|
||||
Attr::Value(Prehashed::new(value.into_value()));
|
||||
} else {
|
||||
self.attrs.push(Attr::Field(name));
|
||||
self.attrs.push(Attr::Value(Prehashed::new(value.into_value())));
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a field on the content.
|
||||
pub fn field(&self, name: &str) -> Option<Value> {
|
||||
if let (Some(iter), "children") = (self.to_sequence(), name) {
|
||||
Some(Value::Array(iter.cloned().map(Value::Content).collect()))
|
||||
} else if let (Some((child, _)), "child") = (self.to_styled(), name) {
|
||||
Some(Value::Content(child.clone()))
|
||||
} else {
|
||||
self.field_ref(name).cloned()
|
||||
}
|
||||
}
|
||||
|
||||
/// Access a field on the content by reference.
|
||||
///
|
||||
/// Does not include synthesized fields for sequence and styled elements.
|
||||
pub fn field_ref(&self, name: &str) -> Option<&Value> {
|
||||
self.fields_ref()
|
||||
.find(|&(field, _)| field == name)
|
||||
.map(|(_, value)| value)
|
||||
}
|
||||
|
||||
/// Iter over all fields on the content.
|
||||
///
|
||||
/// Does not include synthesized fields for sequence and styled elements.
|
||||
pub fn fields_ref(&self) -> impl Iterator<Item = (&EcoString, &Value)> {
|
||||
let mut iter = self.attrs.iter();
|
||||
std::iter::from_fn(move || {
|
||||
let field = iter.find_map(Attr::field)?;
|
||||
let value = iter.next()?.value()?;
|
||||
Some((field, value))
|
||||
})
|
||||
}
|
||||
|
||||
/// Borrow the value of the given field.
|
||||
pub fn get(&self, key: &str) -> StrResult<Value> {
|
||||
self.field(key).ok_or_else(|| missing_field(key))
|
||||
}
|
||||
|
||||
/// Try to access a field on the content as a specified type.
|
||||
pub fn cast_field<T: FromValue>(&self, name: &str) -> Option<T> {
|
||||
match self.field(name) {
|
||||
Some(value) => value.cast().ok(),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Expect a field on the content to exist as a specified type.
|
||||
#[track_caller]
|
||||
pub fn expect_field<T: FromValue>(&self, name: &str) -> T {
|
||||
self.field(name).unwrap().cast().unwrap()
|
||||
}
|
||||
|
||||
/// The content's label.
|
||||
pub fn label(&self) -> Option<&Label> {
|
||||
match self.field_ref("label")? {
|
||||
Value::Label(label) => Some(label),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Attach a label to the content.
|
||||
pub fn labelled(self, label: Label) -> Self {
|
||||
self.with_field("label", label)
|
||||
}
|
||||
|
||||
/// Style this content with a style entry.
|
||||
pub fn styled(mut self, style: impl Into<Style>) -> Self {
|
||||
if self.is::<StyledElem>() {
|
||||
let prev =
|
||||
self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap();
|
||||
prev.apply_one(style.into());
|
||||
self
|
||||
} else {
|
||||
self.styled_with_map(style.into().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Style this content with a full style map.
|
||||
pub fn styled_with_map(mut self, styles: Styles) -> Self {
|
||||
if styles.is_empty() {
|
||||
return self;
|
||||
}
|
||||
|
||||
if self.is::<StyledElem>() {
|
||||
let prev =
|
||||
self.attrs.make_mut().iter_mut().find_map(Attr::styles_mut).unwrap();
|
||||
prev.apply(styles);
|
||||
self
|
||||
} else {
|
||||
let mut content = Content::new(StyledElem::elem());
|
||||
content.attrs.push(Attr::Child(Prehashed::new(self)));
|
||||
content.attrs.push(Attr::Styles(styles));
|
||||
content
|
||||
}
|
||||
}
|
||||
|
||||
/// Style this content with a recipe, eagerly applying it if possible.
|
||||
pub fn styled_with_recipe(self, vm: &mut Vm, recipe: Recipe) -> SourceResult<Self> {
|
||||
if recipe.selector.is_none() {
|
||||
@ -332,56 +314,41 @@ impl Content {
|
||||
|
||||
/// Repeat this content `count` times.
|
||||
pub fn repeat(&self, count: usize) -> Self {
|
||||
Self::sequence(vec![self.clone(); count])
|
||||
Self::sequence(std::iter::repeat_with(|| self.clone()).take(count))
|
||||
}
|
||||
|
||||
/// Disable a show rule recipe.
|
||||
pub fn guarded(mut self, guard: Guard) -> Self {
|
||||
self.attrs.push(Attr::Guard(guard));
|
||||
self
|
||||
/// Style this content with a style entry.
|
||||
pub fn styled(mut self, style: impl Into<Style>) -> Self {
|
||||
if let Some(style_elem) = self.to_mut::<StyledElem>() {
|
||||
style_elem.styles.apply_one(style.into());
|
||||
self
|
||||
} else {
|
||||
self.styled_with_map(style.into().into())
|
||||
}
|
||||
}
|
||||
|
||||
/// Check whether a show rule recipe is disabled.
|
||||
pub fn is_guarded(&self, guard: Guard) -> bool {
|
||||
self.attrs.contains(&Attr::Guard(guard))
|
||||
}
|
||||
/// Style this content with a full style map.
|
||||
pub fn styled_with_map(mut self, styles: Styles) -> Self {
|
||||
if styles.is_empty() {
|
||||
return self;
|
||||
}
|
||||
|
||||
/// Whether no show rule was executed for this content so far.
|
||||
pub fn is_pristine(&self) -> bool {
|
||||
!self.attrs.iter().any(|modifier| matches!(modifier, Attr::Guard(_)))
|
||||
}
|
||||
|
||||
/// Whether this content has already been prepared.
|
||||
pub fn is_prepared(&self) -> bool {
|
||||
self.attrs.contains(&Attr::Prepared)
|
||||
}
|
||||
|
||||
/// Mark this content as prepared.
|
||||
pub fn mark_prepared(&mut self) {
|
||||
self.attrs.push(Attr::Prepared);
|
||||
}
|
||||
|
||||
/// Whether the content needs to be realized specially.
|
||||
pub fn needs_preparation(&self) -> bool {
|
||||
(self.can::<dyn Locatable>()
|
||||
|| self.can::<dyn Synthesize>()
|
||||
|| self.label().is_some())
|
||||
&& !self.is_prepared()
|
||||
}
|
||||
|
||||
/// Attach a location to this content.
|
||||
pub fn set_location(&mut self, location: Location) {
|
||||
self.attrs.push(Attr::Location(location));
|
||||
if let Some(style_elem) = self.to_mut::<StyledElem>() {
|
||||
style_elem.styles.apply(styles);
|
||||
self
|
||||
} else {
|
||||
StyledElem::new(Prehashed::new(self), styles).into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Queries the content tree for all elements that match the given selector.
|
||||
///
|
||||
/// Elements produced in `show` rules will not be included in the results.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn query(&self, selector: Selector) -> Vec<&Content> {
|
||||
pub fn query(&self, selector: Selector) -> Vec<Content> {
|
||||
let mut results = Vec::new();
|
||||
self.traverse(&mut |element| {
|
||||
if selector.matches(element) {
|
||||
if selector.matches(&element) {
|
||||
results.push(element);
|
||||
}
|
||||
});
|
||||
@ -393,10 +360,10 @@ impl Content {
|
||||
///
|
||||
/// Elements produced in `show` rules will not be included in the results.
|
||||
#[tracing::instrument(skip_all)]
|
||||
pub fn query_first(&self, selector: Selector) -> Option<&Content> {
|
||||
pub fn query_first(&self, selector: Selector) -> Option<Content> {
|
||||
let mut result = None;
|
||||
self.traverse(&mut |element| {
|
||||
if result.is_none() && selector.matches(element) {
|
||||
if result.is_none() && selector.matches(&element) {
|
||||
result = Some(element);
|
||||
}
|
||||
});
|
||||
@ -415,24 +382,21 @@ impl Content {
|
||||
}
|
||||
|
||||
/// Traverse this content.
|
||||
fn traverse<'a, F>(&'a self, f: &mut F)
|
||||
fn traverse<F>(&self, f: &mut F)
|
||||
where
|
||||
F: FnMut(&'a Content),
|
||||
F: FnMut(Content),
|
||||
{
|
||||
f(self);
|
||||
f(self.clone());
|
||||
|
||||
for attr in &self.attrs {
|
||||
match attr {
|
||||
Attr::Child(child) => child.traverse(f),
|
||||
Attr::Value(value) => walk_value(value, f),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
self.0
|
||||
.fields()
|
||||
.into_iter()
|
||||
.for_each(|(_, value)| walk_value(value, f));
|
||||
|
||||
/// Walks a given value to find any content that matches the selector.
|
||||
fn walk_value<'a, F>(value: &'a Value, f: &mut F)
|
||||
fn walk_value<F>(value: Value, f: &mut F)
|
||||
where
|
||||
F: FnMut(&'a Content),
|
||||
F: FnMut(Content),
|
||||
{
|
||||
match value {
|
||||
Value::Content(content) => content.traverse(f),
|
||||
@ -445,6 +409,51 @@ impl Content {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Downcasts the element to the specified type.
|
||||
#[inline]
|
||||
pub fn to<T: NativeElement>(&self) -> Option<&T> {
|
||||
// Early check for performance.
|
||||
if !self.is::<T>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.0.as_any().downcast_ref()
|
||||
}
|
||||
|
||||
/// Downcasts mutably the element to the specified type.
|
||||
#[inline]
|
||||
pub fn to_mut<T: NativeElement>(&mut self) -> Option<&mut T> {
|
||||
// Early check for performance.
|
||||
if !self.is::<T>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
self.make_mut().as_any_mut().downcast_mut()
|
||||
}
|
||||
|
||||
/// Downcast the element into an owned value.
|
||||
#[inline]
|
||||
pub fn unpack<T: NativeElement>(self) -> Option<Arc<T>> {
|
||||
// Early check for performance.
|
||||
if !self.is::<T>() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Arc::downcast(self.0.into_any()).ok()
|
||||
}
|
||||
|
||||
/// Makes sure the content is not shared and returns a mutable reference to
|
||||
/// the inner element.
|
||||
#[inline]
|
||||
fn make_mut(&mut self) -> &mut dyn NativeElement {
|
||||
let arc = &mut self.0;
|
||||
if Arc::strong_count(arc) > 1 || Arc::weak_count(arc) > 0 {
|
||||
*arc = arc.dyn_clone();
|
||||
}
|
||||
|
||||
Arc::get_mut(arc).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
#[scope]
|
||||
@ -456,7 +465,7 @@ impl Content {
|
||||
/// kind of element.
|
||||
#[func]
|
||||
pub fn func(&self) -> Element {
|
||||
self.elem
|
||||
self.elem()
|
||||
}
|
||||
|
||||
/// Whether the content has the specified field.
|
||||
@ -466,7 +475,11 @@ impl Content {
|
||||
/// The field to look for.
|
||||
field: Str,
|
||||
) -> bool {
|
||||
self.field(&field).is_some()
|
||||
let Some(id) = self.elem().field_id(&field) else {
|
||||
return false;
|
||||
};
|
||||
|
||||
self.get(id).is_some()
|
||||
}
|
||||
|
||||
/// Access the specified field on the content. Returns the default value if
|
||||
@ -481,7 +494,11 @@ impl Content {
|
||||
#[named]
|
||||
default: Option<Value>,
|
||||
) -> StrResult<Value> {
|
||||
self.field(&field)
|
||||
let Some(id) = self.elem().field_id(&field) else {
|
||||
return default.ok_or_else(|| missing_field_no_default(&field));
|
||||
};
|
||||
|
||||
self.get(id)
|
||||
.or(default)
|
||||
.ok_or_else(|| missing_field_no_default(&field))
|
||||
}
|
||||
@ -496,22 +513,7 @@ impl Content {
|
||||
/// ```
|
||||
#[func]
|
||||
pub fn fields(&self) -> Dict {
|
||||
static CHILD: EcoString = EcoString::inline("child");
|
||||
static CHILDREN: EcoString = EcoString::inline("children");
|
||||
|
||||
let option = if let Some(iter) = self.to_sequence() {
|
||||
Some((&CHILDREN, Value::Array(iter.cloned().map(Value::Content).collect())))
|
||||
} else if let Some((child, _)) = self.to_styled() {
|
||||
Some((&CHILD, Value::Content(child.clone())))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.fields_ref()
|
||||
.map(|(name, value)| (name, value.clone()))
|
||||
.chain(option)
|
||||
.map(|(key, value)| (key.to_owned().into(), value))
|
||||
.collect()
|
||||
self.0.fields()
|
||||
}
|
||||
|
||||
/// The location of the content. This is only available on content returned
|
||||
@ -521,33 +523,7 @@ impl Content {
|
||||
/// [counters]($counter), [state]($state) and [queries]($query).
|
||||
#[func]
|
||||
pub fn location(&self) -> Option<Location> {
|
||||
self.attrs.iter().find_map(|modifier| match modifier {
|
||||
Attr::Location(location) => Some(*location),
|
||||
_ => None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Repr for Content {
|
||||
fn repr(&self) -> EcoString {
|
||||
let name = self.elem.name();
|
||||
if let Some(text) = item!(text_str)(self) {
|
||||
return eco_format!("[{}]", text);
|
||||
} else if name == "space" {
|
||||
return ("[ ]").into();
|
||||
}
|
||||
|
||||
let mut pieces: Vec<_> = self
|
||||
.fields()
|
||||
.into_iter()
|
||||
.map(|(name, value)| eco_format!("{}: {}", name, value.repr()))
|
||||
.collect();
|
||||
|
||||
if self.is::<StyledElem>() {
|
||||
pieces.push(EcoString::from(".."));
|
||||
}
|
||||
|
||||
eco_format!("{}{}", name, pretty_array_like(&pieces, false))
|
||||
self.0.location()
|
||||
}
|
||||
}
|
||||
|
||||
@ -557,15 +533,34 @@ impl Default for Content {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NativeElement> From<T> for Content {
|
||||
fn from(value: T) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Arc<dyn NativeElement>> for Content {
|
||||
fn from(value: Arc<dyn NativeElement>) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Content {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.0.dyn_hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Content {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
if let (Some(left), Some(right)) = (self.to_sequence(), other.to_sequence()) {
|
||||
left.eq(right)
|
||||
} else if let (Some(left), Some(right)) = (self.to_styled(), other.to_styled()) {
|
||||
left == right
|
||||
} else {
|
||||
self.elem == other.elem && self.fields_ref().eq(other.fields_ref())
|
||||
}
|
||||
// Additional short circuit for different elements.
|
||||
self.elem() == other.elem() && self.0.dyn_eq(other)
|
||||
}
|
||||
}
|
||||
|
||||
impl Repr for Content {
|
||||
fn repr(&self) -> EcoString {
|
||||
self.0.repr()
|
||||
}
|
||||
}
|
||||
|
||||
@ -574,20 +569,47 @@ impl Add for Content {
|
||||
|
||||
fn add(self, mut rhs: Self) -> Self::Output {
|
||||
let mut lhs = self;
|
||||
match (lhs.is::<SequenceElem>(), rhs.is::<SequenceElem>()) {
|
||||
(true, true) => {
|
||||
lhs.attrs.extend(rhs.attrs);
|
||||
match (lhs.to_mut::<SequenceElem>(), rhs.to_mut::<SequenceElem>()) {
|
||||
(Some(seq_lhs), Some(rhs)) => {
|
||||
seq_lhs.children.extend(rhs.children.iter().cloned());
|
||||
lhs
|
||||
}
|
||||
(true, false) => {
|
||||
lhs.attrs.push(Attr::Child(Prehashed::new(rhs)));
|
||||
(Some(seq_lhs), None) => {
|
||||
seq_lhs.children.push(Prehashed::new(rhs));
|
||||
lhs
|
||||
}
|
||||
(false, true) => {
|
||||
rhs.attrs.insert(0, Attr::Child(Prehashed::new(lhs)));
|
||||
(None, Some(rhs_seq)) => {
|
||||
rhs_seq.children.insert(0, Prehashed::new(lhs));
|
||||
rhs
|
||||
}
|
||||
(false, false) => Self::sequence([lhs, rhs]),
|
||||
(None, None) => Self::sequence([lhs, rhs]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Add<&'a Self> for Content {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: &'a Self) -> Self::Output {
|
||||
let mut lhs = self;
|
||||
match (lhs.to_mut::<SequenceElem>(), rhs.to::<SequenceElem>()) {
|
||||
(Some(seq_lhs), Some(rhs)) => {
|
||||
seq_lhs.children.extend(rhs.children.iter().cloned());
|
||||
lhs
|
||||
}
|
||||
(Some(seq_lhs), None) => {
|
||||
seq_lhs.children.push(Prehashed::new(rhs.clone()));
|
||||
lhs
|
||||
}
|
||||
(None, Some(_)) => {
|
||||
let mut rhs = rhs.clone();
|
||||
rhs.to_mut::<SequenceElem>()
|
||||
.unwrap()
|
||||
.children
|
||||
.insert(0, Prehashed::new(lhs));
|
||||
rhs
|
||||
}
|
||||
(None, None) => Self::sequence([lhs, rhs.clone()]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -598,6 +620,12 @@ impl AddAssign for Content {
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign<&Self> for Content {
|
||||
fn add_assign(&mut self, rhs: &Self) {
|
||||
*self = std::mem::take(self) + rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sum for Content {
|
||||
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
||||
Self::sequence(iter)
|
||||
@ -610,63 +638,80 @@ impl Serialize for Content {
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.collect_map(
|
||||
iter::once((&"func".into(), &self.func().name().into_value()))
|
||||
.chain(self.fields_ref()),
|
||||
iter::once((
|
||||
Str::from(EcoString::inline("func")),
|
||||
self.func().name().into_value(),
|
||||
))
|
||||
.chain(self.fields()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Attr {
|
||||
fn child(&self) -> Option<&Content> {
|
||||
match self {
|
||||
Self::Child(child) => Some(child),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Defines the `ElemFunc` for sequences.
|
||||
#[elem(Repr, PartialEq)]
|
||||
struct SequenceElem {
|
||||
#[required]
|
||||
children: Vec<Prehashed<Content>>,
|
||||
}
|
||||
|
||||
fn styles(&self) -> Option<&Styles> {
|
||||
match self {
|
||||
Self::Styles(styles) => Some(styles),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn styles_mut(&mut self) -> Option<&mut Styles> {
|
||||
match self {
|
||||
Self::Styles(styles) => Some(styles),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn field(&self) -> Option<&EcoString> {
|
||||
match self {
|
||||
Self::Field(field) => Some(field),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn value(&self) -> Option<&Value> {
|
||||
match self {
|
||||
Self::Value(value) => Some(value),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn span(&self) -> Option<Span> {
|
||||
match self {
|
||||
Self::Span(span) => Some(*span),
|
||||
_ => None,
|
||||
impl Default for SequenceElem {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
span: Span::detached(),
|
||||
location: Default::default(),
|
||||
label: Default::default(),
|
||||
prepared: Default::default(),
|
||||
guards: Default::default(),
|
||||
children: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the `ElemFunc` for sequences.
|
||||
#[elem]
|
||||
struct SequenceElem {}
|
||||
impl PartialEq for SequenceElem {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.children
|
||||
.iter()
|
||||
.map(|c| &**c)
|
||||
.eq(other.children.iter().map(|c| &**c))
|
||||
}
|
||||
}
|
||||
|
||||
impl Repr for SequenceElem {
|
||||
fn repr(&self) -> EcoString {
|
||||
if self.children.is_empty() {
|
||||
EcoString::inline("[]")
|
||||
} else {
|
||||
eco_format!(
|
||||
"[{}]",
|
||||
pretty_array_like(
|
||||
&self.children.iter().map(|c| c.0.repr()).collect::<Vec<_>>(),
|
||||
false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Defines the `ElemFunc` for styled elements.
|
||||
#[elem]
|
||||
struct StyledElem {}
|
||||
#[elem(Repr, PartialEq)]
|
||||
struct StyledElem {
|
||||
#[required]
|
||||
child: Prehashed<Content>,
|
||||
#[required]
|
||||
styles: Styles,
|
||||
}
|
||||
|
||||
impl PartialEq for StyledElem {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
*self.child == *other.child
|
||||
}
|
||||
}
|
||||
|
||||
impl Repr for StyledElem {
|
||||
fn repr(&self) -> EcoString {
|
||||
eco_format!("styled(child: {}, ..)", self.child.0.repr())
|
||||
}
|
||||
}
|
||||
|
||||
/// Hosts metadata and ensures metadata is produced even for empty elements.
|
||||
#[elem(Behave)]
|
||||
@ -674,7 +719,7 @@ pub struct MetaElem {
|
||||
/// Metadata that should be attached to all elements affected by this style
|
||||
/// property.
|
||||
#[fold]
|
||||
pub data: Vec<Meta>,
|
||||
pub data: SmallVec<[Meta; 1]>,
|
||||
}
|
||||
|
||||
impl Behave for MetaElem {
|
||||
@ -691,7 +736,7 @@ pub trait PlainText {
|
||||
|
||||
/// The missing field access error message.
|
||||
#[cold]
|
||||
fn missing_field(field: &str) -> EcoString {
|
||||
pub fn missing_field(field: &str) -> EcoString {
|
||||
eco_format!("content does not contain field {}", field.repr())
|
||||
}
|
||||
|
||||
|
@ -1,13 +1,19 @@
|
||||
use ecow::EcoString;
|
||||
use std::any::TypeId;
|
||||
use std::any::{Any, TypeId};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::fmt::{self, Debug};
|
||||
use std::hash::Hasher;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ecow::EcoString;
|
||||
use once_cell::sync::Lazy;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::{Content, Selector, Styles};
|
||||
use crate::diag::SourceResult;
|
||||
use crate::diag::{SourceResult, StrResult};
|
||||
use crate::doc::{Lang, Region};
|
||||
use crate::eval::{cast, Args, Dict, Func, ParamInfo, Repr, Scope, Value, Vm};
|
||||
use crate::model::{Guard, Label, Location};
|
||||
use crate::syntax::Span;
|
||||
use crate::util::Static;
|
||||
|
||||
/// A document element.
|
||||
@ -20,6 +26,16 @@ impl Element {
|
||||
T::elem()
|
||||
}
|
||||
|
||||
/// Extract the field ID for the given field name.
|
||||
pub fn field_id(&self, name: &str) -> Option<u8> {
|
||||
(self.0.field_id)(name)
|
||||
}
|
||||
|
||||
/// Extract the field name for the given field ID.
|
||||
pub fn field_name(&self, id: u8) -> Option<&'static str> {
|
||||
(self.0.field_name)(id)
|
||||
}
|
||||
|
||||
/// The element's normal name (e.g. `enum`).
|
||||
pub fn name(self) -> &'static str {
|
||||
self.0.name
|
||||
@ -79,7 +95,7 @@ impl Element {
|
||||
|
||||
/// Create a selector for this element, filtering for those
|
||||
/// that [fields](super::Content::field) match the given argument.
|
||||
pub fn where_(self, fields: Dict) -> Selector {
|
||||
pub fn where_(self, fields: SmallVec<[(u8, Value); 1]>) -> Selector {
|
||||
Selector::Elem(self, Some(fields))
|
||||
}
|
||||
|
||||
@ -92,10 +108,15 @@ impl Element {
|
||||
pub fn params(&self) -> &'static [ParamInfo] {
|
||||
&(self.0).0.params
|
||||
}
|
||||
|
||||
/// The element's local name, if any.
|
||||
pub fn local_name(&self, lang: Lang, region: Option<Region>) -> Option<&'static str> {
|
||||
(self.0).0.local_name.map(|f| f(lang, region))
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for Element {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.pad(self.name())
|
||||
}
|
||||
}
|
||||
@ -124,21 +145,120 @@ cast! {
|
||||
v: Func => v.element().ok_or("expected element")?,
|
||||
}
|
||||
|
||||
/// Fields of an element.
|
||||
pub trait ElementFields {
|
||||
/// The fields of the element.
|
||||
type Fields;
|
||||
}
|
||||
|
||||
/// A Typst element that is defined by a native Rust type.
|
||||
pub trait NativeElement: Construct + Set + Sized + 'static {
|
||||
pub trait NativeElement: Debug + Repr + Construct + Set + Send + Sync + 'static {
|
||||
/// Get the element for the native Rust element.
|
||||
fn elem() -> Element {
|
||||
fn elem() -> Element
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Element::from(Self::data())
|
||||
}
|
||||
|
||||
/// Get the element data for the native Rust element.
|
||||
fn data() -> &'static NativeElementData;
|
||||
|
||||
/// Pack the element into type-erased content.
|
||||
fn pack(self) -> Content;
|
||||
fn pack(self) -> Content
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Content::new(self)
|
||||
}
|
||||
|
||||
/// Extract this element from type-erased content.
|
||||
fn unpack(content: &Content) -> Option<&Self>;
|
||||
/// Get the element data for the native Rust element.
|
||||
fn data() -> &'static NativeElementData
|
||||
where
|
||||
Self: Sized;
|
||||
|
||||
/// Get the element data for the native Rust element.
|
||||
fn dyn_elem(&self) -> Element;
|
||||
|
||||
/// Dynamically hash the element.
|
||||
fn dyn_hash(&self, hasher: &mut dyn Hasher);
|
||||
|
||||
/// Dynamically compare the element.
|
||||
fn dyn_eq(&self, other: &Content) -> bool;
|
||||
|
||||
/// Dynamically clone the element.
|
||||
fn dyn_clone(&self) -> Arc<dyn NativeElement>;
|
||||
|
||||
/// Get the element as a dynamic value.
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
|
||||
/// Get the element as a mutable dynamic value.
|
||||
fn as_any_mut(&mut self) -> &mut dyn Any;
|
||||
|
||||
/// Get the element as a dynamic value.
|
||||
fn into_any(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
|
||||
|
||||
/// Get the element's span.
|
||||
///
|
||||
/// May be detached if it has not been set.
|
||||
fn span(&self) -> Span;
|
||||
|
||||
/// Sets the span of this element.
|
||||
fn set_span(&mut self, span: Span);
|
||||
|
||||
/// Set the element's span.
|
||||
fn spanned(mut self, span: Span) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.set_span(span);
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the element's label.
|
||||
fn label(&self) -> Option<Label>;
|
||||
|
||||
/// Sets the label of this element.
|
||||
fn set_label(&mut self, label: Label);
|
||||
|
||||
/// Set the element's label.
|
||||
fn labelled(mut self, label: ::typst::model::Label) -> Self
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
self.set_label(label);
|
||||
self
|
||||
}
|
||||
|
||||
/// Get the element's location.
|
||||
fn location(&self) -> Option<Location>;
|
||||
|
||||
/// Sets the location of this element.
|
||||
fn set_location(&mut self, location: Location);
|
||||
|
||||
/// Checks whether the element is guarded by the given guard.
|
||||
fn is_guarded(&self, guard: Guard) -> bool;
|
||||
|
||||
/// Pushes a guard onto the element.
|
||||
fn push_guard(&mut self, guard: Guard);
|
||||
|
||||
/// Whether the element is pristine.
|
||||
fn is_pristine(&self) -> bool;
|
||||
|
||||
/// Mark the element as having been prepared.
|
||||
fn mark_prepared(&mut self);
|
||||
|
||||
/// Whether this element needs preparations.
|
||||
fn needs_preparation(&self) -> bool;
|
||||
|
||||
/// Whether this element has been prepared.
|
||||
fn is_prepared(&self) -> bool;
|
||||
|
||||
/// Get the field with the given field ID.
|
||||
fn field(&self, id: u8) -> Option<Value>;
|
||||
|
||||
/// Set the field with the given ID.
|
||||
fn set_field(&mut self, id: u8, value: Value) -> StrResult<()>;
|
||||
|
||||
/// Get the fields of the element.
|
||||
fn fields(&self) -> Dict;
|
||||
}
|
||||
|
||||
/// An element's constructor function.
|
||||
@ -147,13 +267,17 @@ pub trait Construct {
|
||||
///
|
||||
/// This is passed only the arguments that remain after execution of the
|
||||
/// element's set rule.
|
||||
fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>;
|
||||
fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// An element's set rule.
|
||||
pub trait Set {
|
||||
/// Parse relevant arguments into style properties for this element.
|
||||
fn set(vm: &mut Vm, args: &mut Args) -> SourceResult<Styles>;
|
||||
fn set(vm: &mut Vm, args: &mut Args) -> SourceResult<Styles>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// Defines a native element.
|
||||
@ -166,6 +290,9 @@ pub struct NativeElementData {
|
||||
pub construct: fn(&mut Vm, &mut Args) -> SourceResult<Content>,
|
||||
pub set: fn(&mut Vm, &mut Args) -> SourceResult<Styles>,
|
||||
pub vtable: fn(of: TypeId) -> Option<*const ()>,
|
||||
pub field_id: fn(name: &str) -> Option<u8>,
|
||||
pub field_name: fn(u8) -> Option<&'static str>,
|
||||
pub local_name: Option<fn(Lang, Option<Region>) -> &'static str>,
|
||||
pub scope: Lazy<Scope>,
|
||||
pub params: Lazy<Vec<ParamInfo>>,
|
||||
}
|
||||
@ -180,3 +307,9 @@ cast! {
|
||||
&'static NativeElementData,
|
||||
self => Element::from(self).into_value(),
|
||||
}
|
||||
|
||||
/// The named with which an element is referenced.
|
||||
pub trait LocalName {
|
||||
/// Get the name in the given language and (optionally) region.
|
||||
fn local_name(lang: Lang, region: Option<Region>) -> &'static str;
|
||||
}
|
||||
|
@ -384,7 +384,7 @@ impl Introspector {
|
||||
}
|
||||
|
||||
/// Query for a unique element with the label.
|
||||
pub fn query_label(&self, label: &Label) -> StrResult<Prehashed<Content>> {
|
||||
pub fn query_label(&self, label: Label) -> StrResult<Prehashed<Content>> {
|
||||
let mut found = None;
|
||||
for elem in self.all().filter(|elem| elem.label() == Some(label)) {
|
||||
if found.is_some() {
|
||||
|
@ -3,6 +3,7 @@ use std::fmt::Debug;
|
||||
use ecow::{eco_format, EcoString};
|
||||
|
||||
use crate::eval::{func, scope, ty, Repr};
|
||||
use crate::util::PicoStr;
|
||||
|
||||
/// A label for an element.
|
||||
///
|
||||
@ -29,8 +30,27 @@ use crate::eval::{func, scope, ty, Repr};
|
||||
/// Currently, labels can only be attached to elements in markup mode, not in
|
||||
/// code mode. This might change in the future.
|
||||
#[ty(scope)]
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Label(pub EcoString);
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct Label(PicoStr);
|
||||
|
||||
impl Label {
|
||||
/// Creates a label from a string, interning it.
|
||||
pub fn new(name: impl Into<PicoStr>) -> Self {
|
||||
Self(name.into())
|
||||
}
|
||||
|
||||
/// Resolves the label to a string.
|
||||
#[inline]
|
||||
pub fn as_str(&self) -> &'static str {
|
||||
self.0.resolve()
|
||||
}
|
||||
|
||||
/// Turns this label into its inner interned string.
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> PicoStr {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[scope]
|
||||
impl Label {
|
||||
@ -38,7 +58,7 @@ impl Label {
|
||||
#[func(constructor)]
|
||||
pub fn construct(
|
||||
/// The name of the label.
|
||||
name: EcoString,
|
||||
name: PicoStr,
|
||||
) -> Label {
|
||||
Self(name)
|
||||
}
|
||||
@ -46,7 +66,13 @@ impl Label {
|
||||
|
||||
impl Repr for Label {
|
||||
fn repr(&self) -> EcoString {
|
||||
eco_format!("<{}>", self.0)
|
||||
eco_format!("<{}>", self.0.resolve())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Label> for PicoStr {
|
||||
fn from(value: Label) -> Self {
|
||||
value.into_inner()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! The document model.
|
||||
|
||||
mod block;
|
||||
mod content;
|
||||
mod element;
|
||||
mod introspect;
|
||||
@ -12,14 +13,17 @@ use ecow::EcoVec;
|
||||
#[doc(inline)]
|
||||
pub use typst_macros::elem;
|
||||
|
||||
pub use self::block::{Block, Blockable};
|
||||
pub use self::content::{Content, MetaElem, PlainText};
|
||||
pub use self::element::{Construct, Element, NativeElement, NativeElementData, Set};
|
||||
pub use self::element::{
|
||||
Construct, Element, ElementFields, LocalName, NativeElement, NativeElementData, Set,
|
||||
};
|
||||
pub use self::introspect::{Introspector, Location, Locator};
|
||||
pub use self::label::{Label, Unlabellable};
|
||||
pub use self::realize::{
|
||||
applicable, realize, Behave, Behaviour, Finalize, Guard, Locatable, Show, Synthesize,
|
||||
};
|
||||
pub use self::selector::{LocatableSelector, Selector, ShowableSelector};
|
||||
pub use self::selector::{select_where, LocatableSelector, Selector, ShowableSelector};
|
||||
pub use self::styles::{
|
||||
Fold, Property, Recipe, Resolve, Style, StyleChain, StyleVec, StyleVecBuilder,
|
||||
Styles, Transform,
|
||||
|
@ -1,3 +1,7 @@
|
||||
use std::borrow::Cow;
|
||||
|
||||
use smallvec::smallvec;
|
||||
|
||||
use super::{
|
||||
Content, Element, MetaElem, NativeElement, Recipe, Selector, StyleChain, Vt,
|
||||
};
|
||||
@ -54,7 +58,7 @@ pub fn realize(
|
||||
let meta = Meta::Elem(elem.clone());
|
||||
return Ok(Some(
|
||||
(elem + MetaElem::new().pack().spanned(span))
|
||||
.styled(MetaElem::set_data(vec![meta])),
|
||||
.styled(MetaElem::set_data(smallvec![meta])),
|
||||
));
|
||||
}
|
||||
|
||||
@ -114,7 +118,7 @@ fn try_apply(
|
||||
}
|
||||
|
||||
Some(Selector::Label(label)) => {
|
||||
if target.label() != Some(label) {
|
||||
if target.label() != Some(*label) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
@ -126,11 +130,12 @@ fn try_apply(
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let make = |s: &str| target.clone().with_field("text", s);
|
||||
// We know we are on a `TextElem` and the `text` is always at ID = 0.
|
||||
let make = |s: &str| target.clone().with_field(0, s);
|
||||
let mut result = vec![];
|
||||
let mut cursor = 0;
|
||||
|
||||
for m in regex.find_iter(&text) {
|
||||
for m in regex.find_iter(text) {
|
||||
let start = m.start();
|
||||
if cursor < start {
|
||||
result.push(make(&text[cursor..start]));
|
||||
@ -200,7 +205,7 @@ pub trait Behave {
|
||||
#[allow(unused_variables)]
|
||||
fn larger(
|
||||
&self,
|
||||
prev: &(Content, Behaviour, StyleChain),
|
||||
prev: &(Cow<Content>, Behaviour, StyleChain),
|
||||
styles: StyleChain,
|
||||
) -> bool {
|
||||
false
|
||||
|
@ -3,6 +3,7 @@ use std::fmt::Debug;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ecow::{eco_format, EcoString, EcoVec};
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::{Content, Element, Label, Locatable, Location};
|
||||
use crate::diag::{bail, StrResult};
|
||||
@ -12,6 +13,33 @@ use crate::eval::{
|
||||
};
|
||||
use crate::util::pretty_array_like;
|
||||
|
||||
/// A helper macro to create a field selector used in [`Selector::Elem`]
|
||||
///
|
||||
/// ```ignore
|
||||
/// select_where!(SequenceElem, Children => vec![]);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
#[doc(hidden)]
|
||||
macro_rules! __select_where {
|
||||
($ty:ty $(, $field:ident => $value:expr)* $(,)?) => {{
|
||||
#[allow(unused_mut)]
|
||||
let mut fields = ::smallvec::SmallVec::new();
|
||||
$(
|
||||
fields.push((
|
||||
<$ty as ::typst::model::ElementFields>::Fields::$field as u8,
|
||||
$crate::eval::IntoValue::into_value($value),
|
||||
));
|
||||
)*
|
||||
::typst::model::Selector::Elem(
|
||||
<$ty as ::typst::model::NativeElement>::elem(),
|
||||
Some(fields),
|
||||
)
|
||||
}};
|
||||
}
|
||||
|
||||
#[doc(inline)]
|
||||
pub use crate::__select_where as select_where;
|
||||
|
||||
/// A filter for selecting elements within the document.
|
||||
///
|
||||
/// You can construct a selector in the following ways:
|
||||
@ -55,7 +83,7 @@ pub enum Selector {
|
||||
///
|
||||
/// If there is a dictionary, only elements with the fields from the
|
||||
/// dictionary match.
|
||||
Elem(Element, Option<Dict>),
|
||||
Elem(Element, Option<SmallVec<[(u8, Value); 1]>>),
|
||||
/// Matches the element at the specified location.
|
||||
Location(Location),
|
||||
/// Matches elements with a specific label.
|
||||
@ -101,20 +129,21 @@ impl Selector {
|
||||
|
||||
/// Whether the selector matches for the target.
|
||||
pub fn matches(&self, target: &Content) -> bool {
|
||||
// TODO: optimize field access to not clone.
|
||||
match self {
|
||||
Self::Elem(element, dict) => {
|
||||
target.func() == *element
|
||||
&& dict
|
||||
.iter()
|
||||
.flat_map(|dict| dict.iter())
|
||||
.all(|(name, value)| target.field_ref(name) == Some(value))
|
||||
.all(|(id, value)| target.get(*id).as_ref() == Some(value))
|
||||
}
|
||||
Self::Label(label) => target.label() == Some(label),
|
||||
Self::Label(label) => target.label() == Some(*label),
|
||||
Self::Regex(regex) => {
|
||||
target.func() == item!(text_elem)
|
||||
&& item!(text_str)(target).map_or(false, |text| regex.is_match(&text))
|
||||
&& item!(text_str)(target).map_or(false, |text| regex.is_match(text))
|
||||
}
|
||||
Self::Can(cap) => target.can_type_id(*cap),
|
||||
Self::Can(cap) => target.func().can_type_id(*cap),
|
||||
Self::Or(selectors) => selectors.iter().any(move |sel| sel.matches(target)),
|
||||
Self::And(selectors) => selectors.iter().all(move |sel| sel.matches(target)),
|
||||
Self::Location(location) => target.location() == Some(*location),
|
||||
@ -214,6 +243,11 @@ impl Repr for Selector {
|
||||
match self {
|
||||
Self::Elem(elem, dict) => {
|
||||
if let Some(dict) = dict {
|
||||
let dict = dict
|
||||
.iter()
|
||||
.map(|(id, value)| (elem.field_name(*id).unwrap(), value.clone()))
|
||||
.map(|(name, value)| (EcoString::from(name).into(), value))
|
||||
.collect::<Dict>();
|
||||
eco_format!("{}.where{}", elem.name(), dict.repr())
|
||||
} else {
|
||||
elem.name().into()
|
||||
@ -261,7 +295,7 @@ cast! {
|
||||
///
|
||||
/// Hopefully, this is made obsolete by a more powerful query mechanism in the
|
||||
/// future.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct LocatableSelector(pub Selector);
|
||||
|
||||
impl Reflect for LocatableSelector {
|
||||
|
@ -1,3 +1,4 @@
|
||||
use std::borrow::Cow;
|
||||
use std::fmt::{self, Debug, Formatter, Write};
|
||||
use std::iter;
|
||||
use std::mem;
|
||||
@ -5,10 +6,12 @@ use std::ptr;
|
||||
|
||||
use comemo::Prehashed;
|
||||
use ecow::{eco_vec, EcoString, EcoVec};
|
||||
use once_cell::sync::Lazy;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use super::{Content, Element, NativeElement, Selector, Vt};
|
||||
use super::{Block, Blockable, Content, Element, NativeElement, Selector, Vt};
|
||||
use crate::diag::{SourceResult, Trace, Tracepoint};
|
||||
use crate::eval::{cast, ty, Args, FromValue, Func, IntoValue, Repr, Value, Vm};
|
||||
use crate::eval::{cast, ty, Args, Func, Repr, Value, Vm};
|
||||
use crate::syntax::Span;
|
||||
|
||||
/// A list of style properties.
|
||||
@ -93,7 +96,7 @@ impl Repr for Styles {
|
||||
}
|
||||
|
||||
/// A single style property or recipe.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[derive(Clone, Hash)]
|
||||
pub enum Style {
|
||||
/// A style property originating from a set rule or constructor.
|
||||
Property(Property),
|
||||
@ -141,32 +144,27 @@ impl From<Recipe> for Style {
|
||||
}
|
||||
|
||||
/// A style property originating from a set rule or constructor.
|
||||
#[derive(Clone, PartialEq, Hash)]
|
||||
#[derive(Clone, Hash)]
|
||||
pub struct Property {
|
||||
/// The element the property belongs to.
|
||||
elem: Element,
|
||||
/// The property's name.
|
||||
name: EcoString,
|
||||
/// The property's ID.
|
||||
id: u8,
|
||||
/// The property's value.
|
||||
value: Value,
|
||||
value: Block,
|
||||
/// The span of the set rule the property stems from.
|
||||
span: Option<Span>,
|
||||
}
|
||||
|
||||
impl Property {
|
||||
/// Create a new property from a key-value pair.
|
||||
pub fn new(elem: Element, name: impl Into<EcoString>, value: impl IntoValue) -> Self {
|
||||
Self {
|
||||
elem,
|
||||
name: name.into(),
|
||||
value: value.into_value(),
|
||||
span: None,
|
||||
}
|
||||
pub fn new<T: Blockable>(elem: Element, id: u8, value: T) -> Self {
|
||||
Self { elem, id, value: Block::new(value), span: None }
|
||||
}
|
||||
|
||||
/// Whether this property is the given one.
|
||||
pub fn is(&self, elem: Element, name: &str) -> bool {
|
||||
self.elem == elem && self.name == name
|
||||
pub fn is(&self, elem: Element, id: u8) -> bool {
|
||||
self.elem == elem && self.id == id
|
||||
}
|
||||
|
||||
/// Whether this property belongs to the given element.
|
||||
@ -177,7 +175,7 @@ impl Property {
|
||||
|
||||
impl Debug for Property {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
write!(f, "set {}({}: {:?})", self.elem.name(), self.name, self.value)?;
|
||||
write!(f, "set {}({}: {:?})", self.elem.name(), self.id, self.value)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -317,61 +315,72 @@ impl<'a> StyleChain<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Cast the first value for the given property in the chain.
|
||||
pub fn get<T: FromValue>(
|
||||
/// Cast the first value for the given property in the chain,
|
||||
/// returning a borrowed value if possible.
|
||||
pub fn get_borrowed<T: Blockable + Clone>(
|
||||
self,
|
||||
func: Element,
|
||||
name: &'a str,
|
||||
inherent: Option<Value>,
|
||||
default: impl Fn() -> T,
|
||||
) -> T {
|
||||
self.properties::<T>(func, name, inherent)
|
||||
id: u8,
|
||||
inherent: Option<&'a T>,
|
||||
default: &'static Lazy<T>,
|
||||
) -> &'a T {
|
||||
self.properties::<T>(func, id, inherent)
|
||||
.next()
|
||||
.unwrap_or_else(default)
|
||||
.unwrap_or_else(|| default)
|
||||
}
|
||||
|
||||
/// Cast the first value for the given property in the chain.
|
||||
pub fn get_resolve<T: FromValue + Resolve>(
|
||||
pub fn get<T: Blockable + Clone>(
|
||||
self,
|
||||
func: Element,
|
||||
name: &'a str,
|
||||
inherent: Option<Value>,
|
||||
default: impl Fn() -> T,
|
||||
id: u8,
|
||||
inherent: Option<&T>,
|
||||
default: &'static Lazy<T>,
|
||||
) -> T {
|
||||
self.get_borrowed(func, id, inherent, default).clone()
|
||||
}
|
||||
|
||||
/// Cast the first value for the given property in the chain.
|
||||
pub fn get_resolve<T: Blockable + Clone + Resolve>(
|
||||
self,
|
||||
func: Element,
|
||||
id: u8,
|
||||
inherent: Option<&T>,
|
||||
default: &'static Lazy<T>,
|
||||
) -> T::Output {
|
||||
self.get(func, name, inherent, default).resolve(self)
|
||||
self.get(func, id, inherent, default).resolve(self)
|
||||
}
|
||||
|
||||
/// Cast the first value for the given property in the chain.
|
||||
pub fn get_fold<T: FromValue + Fold>(
|
||||
pub fn get_fold<T: Blockable + Clone + Fold>(
|
||||
self,
|
||||
func: Element,
|
||||
name: &'a str,
|
||||
inherent: Option<Value>,
|
||||
id: u8,
|
||||
inherent: Option<&T>,
|
||||
default: impl Fn() -> T::Output,
|
||||
) -> T::Output {
|
||||
fn next<T: Fold>(
|
||||
mut values: impl Iterator<Item = T>,
|
||||
_styles: StyleChain,
|
||||
default: &impl Fn() -> T::Output,
|
||||
) -> T::Output {
|
||||
values
|
||||
.next()
|
||||
.map(|value| value.fold(next(values, _styles, default)))
|
||||
.map(|value| value.fold(next(values, default)))
|
||||
.unwrap_or_else(default)
|
||||
}
|
||||
next(self.properties::<T>(func, name, inherent), self, &default)
|
||||
next(self.properties::<T>(func, id, inherent).cloned(), &default)
|
||||
}
|
||||
|
||||
/// Cast the first value for the given property in the chain.
|
||||
pub fn get_resolve_fold<T>(
|
||||
self,
|
||||
func: Element,
|
||||
name: &'a str,
|
||||
inherent: Option<Value>,
|
||||
id: u8,
|
||||
inherent: Option<&T>,
|
||||
default: impl Fn() -> <T::Output as Fold>::Output,
|
||||
) -> <T::Output as Fold>::Output
|
||||
where
|
||||
T: FromValue + Resolve,
|
||||
T: Blockable + Clone + Resolve,
|
||||
T::Output: Fold,
|
||||
{
|
||||
fn next<T>(
|
||||
@ -380,7 +389,7 @@ impl<'a> StyleChain<'a> {
|
||||
default: &impl Fn() -> <T::Output as Fold>::Output,
|
||||
) -> <T::Output as Fold>::Output
|
||||
where
|
||||
T: Resolve,
|
||||
T: Blockable + Resolve,
|
||||
T::Output: Fold,
|
||||
{
|
||||
values
|
||||
@ -388,7 +397,8 @@ impl<'a> StyleChain<'a> {
|
||||
.map(|value| value.resolve(styles).fold(next(values, styles, default)))
|
||||
.unwrap_or_else(default)
|
||||
}
|
||||
next(self.properties::<T>(func, name, inherent), self, &default)
|
||||
|
||||
next(self.properties::<T>(func, id, inherent).cloned(), self, &default)
|
||||
}
|
||||
|
||||
/// Iterate over all style recipes in the chain.
|
||||
@ -397,25 +407,28 @@ impl<'a> StyleChain<'a> {
|
||||
}
|
||||
|
||||
/// Iterate over all values for the given property in the chain.
|
||||
pub fn properties<T: FromValue + 'a>(
|
||||
pub fn properties<T: Blockable>(
|
||||
self,
|
||||
func: Element,
|
||||
name: &'a str,
|
||||
inherent: Option<Value>,
|
||||
) -> impl Iterator<Item = T> + '_ {
|
||||
inherent
|
||||
.into_iter()
|
||||
.chain(
|
||||
self.entries()
|
||||
.filter_map(Style::property)
|
||||
.filter(move |property| property.is(func, name))
|
||||
.map(|property| property.value.clone()),
|
||||
)
|
||||
.map(move |value| {
|
||||
value.cast().unwrap_or_else(|err| {
|
||||
panic!("{} (for {}.{})", err, func.name(), name)
|
||||
})
|
||||
})
|
||||
id: u8,
|
||||
inherent: Option<&'a T>,
|
||||
) -> impl Iterator<Item = &'a T> {
|
||||
inherent.into_iter().chain(
|
||||
self.entries()
|
||||
.filter_map(Style::property)
|
||||
.filter(move |property| property.is(func, id))
|
||||
.map(|property| &property.value)
|
||||
.map(move |value| {
|
||||
value.downcast().unwrap_or_else(|| {
|
||||
panic!(
|
||||
"attempted to read a value of a different type than was written {}.{}: {:?}",
|
||||
func.name(),
|
||||
func.field_name(id).unwrap(),
|
||||
value
|
||||
)
|
||||
})
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
/// Convert to a style map.
|
||||
@ -579,8 +592,8 @@ impl<T> StyleVec<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl StyleVec<Content> {
|
||||
pub fn to_vec(self) -> Vec<Content> {
|
||||
impl<'a> StyleVec<Cow<'a, Content>> {
|
||||
pub fn to_vec(self) -> Vec<Prehashed<Content>> {
|
||||
self.items
|
||||
.into_iter()
|
||||
.zip(
|
||||
@ -588,7 +601,8 @@ impl StyleVec<Content> {
|
||||
.iter()
|
||||
.flat_map(|(map, count)| iter::repeat(map).take(*count)),
|
||||
)
|
||||
.map(|(content, styles)| content.styled_with_map(styles.clone()))
|
||||
.map(|(content, styles)| content.into_owned().styled_with_map(styles.clone()))
|
||||
.map(Prehashed::new)
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
@ -759,3 +773,12 @@ impl<T> Fold for Vec<T> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Fold for SmallVec<[T; N]> {
|
||||
type Output = SmallVec<[T; N]>;
|
||||
|
||||
fn fold(mut self, outer: Self::Output) -> Self::Output {
|
||||
self.extend(outer);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,10 @@
|
||||
|
||||
pub mod fat;
|
||||
pub mod fmt;
|
||||
mod str;
|
||||
|
||||
pub use self::fmt::{pretty_array_like, pretty_comma_list, separated_list};
|
||||
pub use self::str::PicoStr;
|
||||
|
||||
use std::fmt::{Debug, Formatter};
|
||||
use std::hash::Hash;
|
||||
|
65
crates/typst/src/util/str.rs
Normal file
65
crates/typst/src/util/str.rs
Normal file
@ -0,0 +1,65 @@
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
|
||||
use ecow::EcoString;
|
||||
use lasso::{Spur, ThreadedRodeo};
|
||||
use once_cell::sync::Lazy;
|
||||
use typst_macros::cast;
|
||||
|
||||
/// The global string interner.
|
||||
static INTERNER: Lazy<ThreadedRodeo> = Lazy::new(ThreadedRodeo::new);
|
||||
|
||||
/// An interned string.
|
||||
///
|
||||
/// The API is purposefully kept small. This is because it might be relatively
|
||||
/// slow to look up a string in the interner, so we want to avoid doing it
|
||||
/// unnecessarily. For this reason, the user should use the [`PicoStr::resolve`]
|
||||
/// method to get the underlying string, such that the lookup is done only once.
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||
pub struct PicoStr(Spur);
|
||||
|
||||
impl PicoStr {
|
||||
/// Creates a new interned string.
|
||||
pub fn new(s: impl AsRef<str>) -> Self {
|
||||
Self(INTERNER.get_or_intern(s.as_ref()))
|
||||
}
|
||||
|
||||
/// Creates a new interned string from a static string.
|
||||
pub fn static_(s: &'static str) -> Self {
|
||||
Self(INTERNER.get_or_intern_static(s))
|
||||
}
|
||||
|
||||
/// Resolves the interned string.
|
||||
pub fn resolve(&self) -> &'static str {
|
||||
INTERNER.resolve(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
cast! {
|
||||
PicoStr,
|
||||
self => self.resolve().into_value(),
|
||||
v: EcoString => Self::new(&v),
|
||||
}
|
||||
|
||||
impl Debug for PicoStr {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
self.resolve().fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<str> for PicoStr {
|
||||
fn as_ref(&self) -> &str {
|
||||
self.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for PicoStr {
|
||||
fn from(value: &str) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EcoString> for PicoStr {
|
||||
fn from(value: &EcoString) -> Self {
|
||||
Self::new(value)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user