Compare commits
17 Commits
85647ec576
...
56707649a8
Author | SHA1 | Date | |
---|---|---|---|
|
56707649a8 | ||
|
a45c3388a6 | ||
|
f9b01f595d | ||
|
eed3407051 | ||
|
1bbb58c43f | ||
|
1dc4c248d1 | ||
|
9e6adb6f45 | ||
|
4534167656 | ||
|
9ad1879e9d | ||
|
e5e813219e | ||
|
52a708b988 | ||
|
e71674f6b3 | ||
|
e5e1dcd9c0 | ||
|
6daae2e292 | ||
|
7f24cd9253 | ||
|
fea153a6fc | ||
|
cdb8a42c68 |
9
Cargo.lock
generated
@ -413,7 +413,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "codex"
|
name = "codex"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
source = "git+https://github.com/typst/codex?rev=a5428cb#a5428cb9c81a41354d44b44dbd5a16a710bbd928"
|
source = "git+https://github.com/typst/codex?rev=9ac86f9#9ac86f96af5b89fce555e6bba8b6d1ac7b44ef00"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "color-print"
|
name = "color-print"
|
||||||
@ -2861,7 +2861,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-assets"
|
name = "typst-assets"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
source = "git+https://github.com/typst/typst-assets?rev=c1089b4#c1089b46c461bdde579c55caa941a3cc7dec3e8a"
|
source = "git+https://github.com/typst/typst-assets?rev=edf0d64#edf0d648376e29738a05a933af9ea99bb81557b1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-cli"
|
name = "typst-cli"
|
||||||
@ -2971,8 +2971,12 @@ dependencies = [
|
|||||||
name = "typst-html"
|
name = "typst-html"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
"comemo",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
|
"palette",
|
||||||
|
"time",
|
||||||
|
"typst-assets",
|
||||||
"typst-library",
|
"typst-library",
|
||||||
"typst-macros",
|
"typst-macros",
|
||||||
"typst-svg",
|
"typst-svg",
|
||||||
@ -3028,6 +3032,7 @@ version = "0.13.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"az",
|
"az",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
|
"codex",
|
||||||
"comemo",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"hypher",
|
"hypher",
|
||||||
|
@ -32,7 +32,7 @@ typst-svg = { path = "crates/typst-svg", version = "0.13.1" }
|
|||||||
typst-syntax = { path = "crates/typst-syntax", version = "0.13.1" }
|
typst-syntax = { path = "crates/typst-syntax", version = "0.13.1" }
|
||||||
typst-timing = { path = "crates/typst-timing", version = "0.13.1" }
|
typst-timing = { path = "crates/typst-timing", version = "0.13.1" }
|
||||||
typst-utils = { path = "crates/typst-utils", version = "0.13.1" }
|
typst-utils = { path = "crates/typst-utils", version = "0.13.1" }
|
||||||
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "c1089b4" }
|
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "edf0d64" }
|
||||||
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "bfa947f" }
|
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "bfa947f" }
|
||||||
arrayvec = "0.7.4"
|
arrayvec = "0.7.4"
|
||||||
az = "1.2"
|
az = "1.2"
|
||||||
@ -47,7 +47,7 @@ clap = { version = "4.4", features = ["derive", "env", "wrap_help"] }
|
|||||||
clap_complete = "4.2.1"
|
clap_complete = "4.2.1"
|
||||||
clap_mangen = "0.2.10"
|
clap_mangen = "0.2.10"
|
||||||
codespan-reporting = "0.11"
|
codespan-reporting = "0.11"
|
||||||
codex = { git = "https://github.com/typst/codex", rev = "a5428cb" }
|
codex = { git = "https://github.com/typst/codex", rev = "9ac86f9" }
|
||||||
color-print = "0.3.6"
|
color-print = "0.3.6"
|
||||||
comemo = "0.4"
|
comemo = "0.4"
|
||||||
csv = "1"
|
csv = "1"
|
||||||
|
@ -29,6 +29,7 @@ typst-svg = { workspace = true }
|
|||||||
typst-timing = { workspace = true }
|
typst-timing = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
|
clap_complete = { workspace = true }
|
||||||
codespan-reporting = { workspace = true }
|
codespan-reporting = { workspace = true }
|
||||||
color-print = { workspace = true }
|
color-print = { workspace = true }
|
||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
|
@ -7,6 +7,7 @@ use std::str::FromStr;
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use clap::builder::{TypedValueParser, ValueParser};
|
use clap::builder::{TypedValueParser, ValueParser};
|
||||||
use clap::{ArgAction, Args, ColorChoice, Parser, Subcommand, ValueEnum, ValueHint};
|
use clap::{ArgAction, Args, ColorChoice, Parser, Subcommand, ValueEnum, ValueHint};
|
||||||
|
use clap_complete::Shell;
|
||||||
use semver::Version;
|
use semver::Version;
|
||||||
|
|
||||||
/// The character typically used to separate path components
|
/// The character typically used to separate path components
|
||||||
@ -81,6 +82,9 @@ pub enum Command {
|
|||||||
/// Self update the Typst CLI.
|
/// Self update the Typst CLI.
|
||||||
#[cfg_attr(not(feature = "self-update"), clap(hide = true))]
|
#[cfg_attr(not(feature = "self-update"), clap(hide = true))]
|
||||||
Update(UpdateCommand),
|
Update(UpdateCommand),
|
||||||
|
|
||||||
|
/// Generates shell completion scripts.
|
||||||
|
Completions(CompletionsCommand),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compiles an input file into a supported output format.
|
/// Compiles an input file into a supported output format.
|
||||||
@ -198,6 +202,14 @@ pub struct UpdateCommand {
|
|||||||
pub backup_path: Option<PathBuf>,
|
pub backup_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generates shell completion scripts.
|
||||||
|
#[derive(Debug, Clone, Parser)]
|
||||||
|
pub struct CompletionsCommand {
|
||||||
|
/// The shell to generate completions for.
|
||||||
|
#[arg(value_enum)]
|
||||||
|
pub shell: Shell,
|
||||||
|
}
|
||||||
|
|
||||||
/// Arguments for compilation and watching.
|
/// Arguments for compilation and watching.
|
||||||
#[derive(Debug, Clone, Args)]
|
#[derive(Debug, Clone, Args)]
|
||||||
pub struct CompileArgs {
|
pub struct CompileArgs {
|
||||||
|
13
crates/typst-cli/src/completions.rs
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
use std::io::stdout;
|
||||||
|
|
||||||
|
use clap::CommandFactory;
|
||||||
|
use clap_complete::generate;
|
||||||
|
|
||||||
|
use crate::args::{CliArguments, CompletionsCommand};
|
||||||
|
|
||||||
|
/// Execute the completions command.
|
||||||
|
pub fn completions(command: &CompletionsCommand) {
|
||||||
|
let mut cmd = CliArguments::command();
|
||||||
|
let bin_name = cmd.get_name().to_string();
|
||||||
|
generate(command.shell, &mut cmd, bin_name, &mut stdout());
|
||||||
|
}
|
@ -1,5 +1,6 @@
|
|||||||
mod args;
|
mod args;
|
||||||
mod compile;
|
mod compile;
|
||||||
|
mod completions;
|
||||||
mod download;
|
mod download;
|
||||||
mod fonts;
|
mod fonts;
|
||||||
mod greet;
|
mod greet;
|
||||||
@ -71,6 +72,7 @@ fn dispatch() -> HintedStrResult<()> {
|
|||||||
Command::Query(command) => crate::query::query(command)?,
|
Command::Query(command) => crate::query::query(command)?,
|
||||||
Command::Fonts(command) => crate::fonts::fonts(command),
|
Command::Fonts(command) => crate::fonts::fonts(command),
|
||||||
Command::Update(command) => crate::update::update(command)?,
|
Command::Update(command) => crate::update::update(command)?,
|
||||||
|
Command::Completions(command) => crate::completions::completions(command),
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -12,7 +12,7 @@ use typst::foundations::{Bytes, Datetime, Dict, IntoValue};
|
|||||||
use typst::syntax::{FileId, Lines, Source, VirtualPath};
|
use typst::syntax::{FileId, Lines, Source, VirtualPath};
|
||||||
use typst::text::{Font, FontBook};
|
use typst::text::{Font, FontBook};
|
||||||
use typst::utils::LazyHash;
|
use typst::utils::LazyHash;
|
||||||
use typst::{Library, World};
|
use typst::{Library, LibraryExt, World};
|
||||||
use typst_kit::fonts::{FontSlot, Fonts};
|
use typst_kit::fonts::{FontSlot, Fonts};
|
||||||
use typst_kit::package::PackageStorage;
|
use typst_kit::package::PackageStorage;
|
||||||
use typst_timing::timed;
|
use typst_timing::timed;
|
||||||
|
@ -13,14 +13,18 @@ keywords = { workspace = true }
|
|||||||
readme = { workspace = true }
|
readme = { workspace = true }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
typst-assets = { workspace = true }
|
||||||
typst-library = { workspace = true }
|
typst-library = { workspace = true }
|
||||||
typst-macros = { workspace = true }
|
typst-macros = { workspace = true }
|
||||||
typst-syntax = { workspace = true }
|
typst-syntax = { workspace = true }
|
||||||
typst-timing = { workspace = true }
|
typst-timing = { workspace = true }
|
||||||
typst-utils = { workspace = true }
|
typst-utils = { workspace = true }
|
||||||
typst-svg = { workspace = true }
|
typst-svg = { workspace = true }
|
||||||
|
bumpalo = { workspace = true }
|
||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
|
palette = { workspace = true }
|
||||||
|
time = { workspace = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
135
crates/typst-html/src/css.rs
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
//! Conversion from Typst data types into CSS data types.
|
||||||
|
|
||||||
|
use std::fmt::{self, Display};
|
||||||
|
|
||||||
|
use typst_library::layout::Length;
|
||||||
|
use typst_library::visualize::{Color, Hsl, LinearRgb, Oklab, Oklch, Rgb};
|
||||||
|
use typst_utils::Numeric;
|
||||||
|
|
||||||
|
pub fn length(length: Length) -> impl Display {
|
||||||
|
typst_utils::display(move |f| match (length.abs.is_zero(), length.em.is_zero()) {
|
||||||
|
(false, false) => {
|
||||||
|
write!(f, "calc({}pt + {}em)", length.abs.to_pt(), length.em.get())
|
||||||
|
}
|
||||||
|
(true, false) => write!(f, "{}em", length.em.get()),
|
||||||
|
(_, true) => write!(f, "{}pt", length.abs.to_pt()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn color(color: Color) -> impl Display {
|
||||||
|
typst_utils::display(move |f| match color {
|
||||||
|
Color::Rgb(_) | Color::Cmyk(_) | Color::Luma(_) => rgb(f, color.to_rgb()),
|
||||||
|
Color::Oklab(v) => oklab(f, v),
|
||||||
|
Color::Oklch(v) => oklch(f, v),
|
||||||
|
Color::LinearRgb(v) => linear_rgb(f, v),
|
||||||
|
Color::Hsl(_) | Color::Hsv(_) => hsl(f, color.to_hsl()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn oklab(f: &mut fmt::Formatter<'_>, v: Oklab) -> fmt::Result {
|
||||||
|
write!(f, "oklab({} {} {}{})", percent(v.l), number(v.a), number(v.b), alpha(v.alpha))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn oklch(f: &mut fmt::Formatter<'_>, v: Oklch) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"oklch({} {} {}deg{})",
|
||||||
|
percent(v.l),
|
||||||
|
number(v.chroma),
|
||||||
|
number(v.hue.into_degrees()),
|
||||||
|
alpha(v.alpha)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn rgb(f: &mut fmt::Formatter<'_>, v: Rgb) -> fmt::Result {
|
||||||
|
if let Some(v) = rgb_to_8_bit_lossless(v) {
|
||||||
|
let (r, g, b, a) = v.into_components();
|
||||||
|
write!(f, "#{r:02x}{g:02x}{b:02x}")?;
|
||||||
|
if a != u8::MAX {
|
||||||
|
write!(f, "{a:02x}")?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"rgb({} {} {}{})",
|
||||||
|
percent(v.red),
|
||||||
|
percent(v.green),
|
||||||
|
percent(v.blue),
|
||||||
|
alpha(v.alpha)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts an f32 RGBA color to its 8-bit representation if the result is
|
||||||
|
/// [very close](is_very_close) to the original.
|
||||||
|
fn rgb_to_8_bit_lossless(
|
||||||
|
v: Rgb,
|
||||||
|
) -> Option<palette::rgb::Rgba<palette::encoding::Srgb, u8>> {
|
||||||
|
let l = v.into_format::<u8, u8>();
|
||||||
|
let h = l.into_format::<f32, f32>();
|
||||||
|
(is_very_close(v.red, h.red)
|
||||||
|
&& is_very_close(v.blue, h.blue)
|
||||||
|
&& is_very_close(v.green, h.green)
|
||||||
|
&& is_very_close(v.alpha, h.alpha))
|
||||||
|
.then_some(l)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn linear_rgb(f: &mut fmt::Formatter<'_>, v: LinearRgb) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"color(srgb-linear {} {} {}{})",
|
||||||
|
percent(v.red),
|
||||||
|
percent(v.green),
|
||||||
|
percent(v.blue),
|
||||||
|
alpha(v.alpha),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn hsl(f: &mut fmt::Formatter<'_>, v: Hsl) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"hsl({}deg {} {}{})",
|
||||||
|
number(v.hue.into_degrees()),
|
||||||
|
percent(v.saturation),
|
||||||
|
percent(v.lightness),
|
||||||
|
alpha(v.alpha),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays an alpha component if it not 1.
|
||||||
|
fn alpha(value: f32) -> impl Display {
|
||||||
|
typst_utils::display(move |f| {
|
||||||
|
if !is_very_close(value, 1.0) {
|
||||||
|
write!(f, " / {}", percent(value))?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Displays a rounded percentage.
|
||||||
|
///
|
||||||
|
/// For a percentage, two significant digits after the comma gives us a
|
||||||
|
/// precision of 1/10_000, which is more than 12 bits (see `is_very_close`).
|
||||||
|
fn percent(ratio: f32) -> impl Display {
|
||||||
|
typst_utils::display(move |f| {
|
||||||
|
write!(f, "{}%", typst_utils::round_with_precision(ratio as f64 * 100.0, 2))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Rounds a number for display.
|
||||||
|
///
|
||||||
|
/// For a number between 0 and 1, four significant digits give us a
|
||||||
|
/// precision of 1/10_000, which is more than 12 bits (see `is_very_close`).
|
||||||
|
fn number(value: f32) -> impl Display {
|
||||||
|
typst_utils::round_with_precision(value as f64, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Whether two component values are close enough that there is no
|
||||||
|
/// difference when encoding them with 12-bit. 12 bit is the highest
|
||||||
|
/// reasonable color bit depth found in the industry.
|
||||||
|
fn is_very_close(a: f32, b: f32) -> bool {
|
||||||
|
const MAX_BIT_DEPTH: u32 = 12;
|
||||||
|
const EPS: f32 = 0.5 / 2_i32.pow(MAX_BIT_DEPTH) as f32;
|
||||||
|
(a - b).abs() < EPS
|
||||||
|
}
|
@ -1,13 +1,19 @@
|
|||||||
//! Typst's HTML exporter.
|
//! Typst's HTML exporter.
|
||||||
|
|
||||||
|
mod css;
|
||||||
mod encode;
|
mod encode;
|
||||||
|
mod rules;
|
||||||
|
mod typed;
|
||||||
|
|
||||||
pub use self::encode::html;
|
pub use self::encode::html;
|
||||||
|
pub use self::rules::register;
|
||||||
|
|
||||||
use comemo::{Track, Tracked, TrackedMut};
|
use comemo::{Track, Tracked, TrackedMut};
|
||||||
use typst_library::diag::{bail, warning, At, SourceResult};
|
use typst_library::diag::{bail, warning, At, SourceResult};
|
||||||
use typst_library::engine::{Engine, Route, Sink, Traced};
|
use typst_library::engine::{Engine, Route, Sink, Traced};
|
||||||
use typst_library::foundations::{Content, StyleChain, Target, TargetElem};
|
use typst_library::foundations::{
|
||||||
|
Content, Module, Scope, StyleChain, Target, TargetElem,
|
||||||
|
};
|
||||||
use typst_library::html::{
|
use typst_library::html::{
|
||||||
attr, tag, FrameElem, HtmlDocument, HtmlElem, HtmlElement, HtmlFrame, HtmlNode,
|
attr, tag, FrameElem, HtmlDocument, HtmlElem, HtmlElement, HtmlFrame, HtmlNode,
|
||||||
};
|
};
|
||||||
@ -18,9 +24,19 @@ use typst_library::layout::{Abs, Axes, BlockBody, BlockElem, BoxElem, Region, Si
|
|||||||
use typst_library::model::{DocumentInfo, ParElem};
|
use typst_library::model::{DocumentInfo, ParElem};
|
||||||
use typst_library::routines::{Arenas, FragmentKind, Pair, RealizationKind, Routines};
|
use typst_library::routines::{Arenas, FragmentKind, Pair, RealizationKind, Routines};
|
||||||
use typst_library::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
|
use typst_library::text::{LinebreakElem, SmartQuoteElem, SpaceElem, TextElem};
|
||||||
use typst_library::World;
|
use typst_library::{Category, World};
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
|
|
||||||
|
/// Create a module with all HTML definitions.
|
||||||
|
pub fn module() -> Module {
|
||||||
|
let mut html = Scope::deduplicating();
|
||||||
|
html.start_category(Category::Html);
|
||||||
|
html.define_elem::<HtmlElem>();
|
||||||
|
html.define_elem::<FrameElem>();
|
||||||
|
crate::typed::define(&mut html);
|
||||||
|
Module::new("html", html)
|
||||||
|
}
|
||||||
|
|
||||||
/// Produce an HTML document from content.
|
/// Produce an HTML document from content.
|
||||||
///
|
///
|
||||||
/// This first performs root-level realization and then turns the resulting
|
/// This first performs root-level realization and then turns the resulting
|
||||||
|
411
crates/typst-html/src/rules.rs
Normal file
@ -0,0 +1,411 @@
|
|||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
use ecow::{eco_format, EcoVec};
|
||||||
|
use typst_library::diag::warning;
|
||||||
|
use typst_library::foundations::{
|
||||||
|
Content, NativeElement, NativeRuleMap, ShowFn, StyleChain, Target,
|
||||||
|
};
|
||||||
|
use typst_library::html::{attr, tag, HtmlAttrs, HtmlElem, HtmlTag};
|
||||||
|
use typst_library::introspection::{Counter, Locator};
|
||||||
|
use typst_library::layout::resolve::{table_to_cellgrid, Cell, CellGrid, Entry};
|
||||||
|
use typst_library::layout::OuterVAlignment;
|
||||||
|
use typst_library::model::{
|
||||||
|
Attribution, CiteElem, CiteGroup, Destination, EmphElem, EnumElem, FigureCaption,
|
||||||
|
FigureElem, HeadingElem, LinkElem, LinkTarget, ListElem, ParbreakElem, QuoteElem,
|
||||||
|
RefElem, StrongElem, TableCell, TableElem, TermsElem,
|
||||||
|
};
|
||||||
|
use typst_library::text::{
|
||||||
|
HighlightElem, LinebreakElem, OverlineElem, RawElem, RawLine, SpaceElem, StrikeElem,
|
||||||
|
SubElem, SuperElem, UnderlineElem,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Register show rules for the [HTML target](Target::Html).
|
||||||
|
pub fn register(rules: &mut NativeRuleMap) {
|
||||||
|
use Target::Html;
|
||||||
|
|
||||||
|
// Model.
|
||||||
|
rules.register(Html, STRONG_RULE);
|
||||||
|
rules.register(Html, EMPH_RULE);
|
||||||
|
rules.register(Html, LIST_RULE);
|
||||||
|
rules.register(Html, ENUM_RULE);
|
||||||
|
rules.register(Html, TERMS_RULE);
|
||||||
|
rules.register(Html, LINK_RULE);
|
||||||
|
rules.register(Html, HEADING_RULE);
|
||||||
|
rules.register(Html, FIGURE_RULE);
|
||||||
|
rules.register(Html, FIGURE_CAPTION_RULE);
|
||||||
|
rules.register(Html, QUOTE_RULE);
|
||||||
|
rules.register(Html, REF_RULE);
|
||||||
|
rules.register(Html, CITE_GROUP_RULE);
|
||||||
|
rules.register(Html, TABLE_RULE);
|
||||||
|
|
||||||
|
// Text.
|
||||||
|
rules.register(Html, SUB_RULE);
|
||||||
|
rules.register(Html, SUPER_RULE);
|
||||||
|
rules.register(Html, UNDERLINE_RULE);
|
||||||
|
rules.register(Html, OVERLINE_RULE);
|
||||||
|
rules.register(Html, STRIKE_RULE);
|
||||||
|
rules.register(Html, HIGHLIGHT_RULE);
|
||||||
|
rules.register(Html, RAW_RULE);
|
||||||
|
rules.register(Html, RAW_LINE_RULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
const STRONG_RULE: ShowFn<StrongElem> = |elem, _, _| {
|
||||||
|
Ok(HtmlElem::new(tag::strong)
|
||||||
|
.with_body(Some(elem.body.clone()))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const EMPH_RULE: ShowFn<EmphElem> = |elem, _, _| {
|
||||||
|
Ok(HtmlElem::new(tag::em)
|
||||||
|
.with_body(Some(elem.body.clone()))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const LIST_RULE: ShowFn<ListElem> = |elem, _, styles| {
|
||||||
|
Ok(HtmlElem::new(tag::ul)
|
||||||
|
.with_body(Some(Content::sequence(elem.children.iter().map(|item| {
|
||||||
|
// Text in wide lists shall always turn into paragraphs.
|
||||||
|
let mut body = item.body.clone();
|
||||||
|
if !elem.tight.get(styles) {
|
||||||
|
body += ParbreakElem::shared();
|
||||||
|
}
|
||||||
|
HtmlElem::new(tag::li)
|
||||||
|
.with_body(Some(body))
|
||||||
|
.pack()
|
||||||
|
.spanned(item.span())
|
||||||
|
}))))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const ENUM_RULE: ShowFn<EnumElem> = |elem, _, styles| {
|
||||||
|
let mut ol = HtmlElem::new(tag::ol);
|
||||||
|
|
||||||
|
if elem.reversed.get(styles) {
|
||||||
|
ol = ol.with_attr(attr::reversed, "reversed");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(n) = elem.start.get(styles).custom() {
|
||||||
|
ol = ol.with_attr(attr::start, eco_format!("{n}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = Content::sequence(elem.children.iter().map(|item| {
|
||||||
|
let mut li = HtmlElem::new(tag::li);
|
||||||
|
if let Some(nr) = item.number.get(styles) {
|
||||||
|
li = li.with_attr(attr::value, eco_format!("{nr}"));
|
||||||
|
}
|
||||||
|
// Text in wide enums shall always turn into paragraphs.
|
||||||
|
let mut body = item.body.clone();
|
||||||
|
if !elem.tight.get(styles) {
|
||||||
|
body += ParbreakElem::shared();
|
||||||
|
}
|
||||||
|
li.with_body(Some(body)).pack().spanned(item.span())
|
||||||
|
}));
|
||||||
|
|
||||||
|
Ok(ol.with_body(Some(body)).pack().spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const TERMS_RULE: ShowFn<TermsElem> = |elem, _, styles| {
|
||||||
|
Ok(HtmlElem::new(tag::dl)
|
||||||
|
.with_body(Some(Content::sequence(elem.children.iter().flat_map(|item| {
|
||||||
|
// Text in wide term lists shall always turn into paragraphs.
|
||||||
|
let mut description = item.description.clone();
|
||||||
|
if !elem.tight.get(styles) {
|
||||||
|
description += ParbreakElem::shared();
|
||||||
|
}
|
||||||
|
|
||||||
|
[
|
||||||
|
HtmlElem::new(tag::dt)
|
||||||
|
.with_body(Some(item.term.clone()))
|
||||||
|
.pack()
|
||||||
|
.spanned(item.term.span()),
|
||||||
|
HtmlElem::new(tag::dd)
|
||||||
|
.with_body(Some(description))
|
||||||
|
.pack()
|
||||||
|
.spanned(item.description.span()),
|
||||||
|
]
|
||||||
|
}))))
|
||||||
|
.pack())
|
||||||
|
};
|
||||||
|
|
||||||
|
const LINK_RULE: ShowFn<LinkElem> = |elem, engine, _| {
|
||||||
|
let body = elem.body.clone();
|
||||||
|
Ok(if let LinkTarget::Dest(Destination::Url(url)) = &elem.dest {
|
||||||
|
HtmlElem::new(tag::a)
|
||||||
|
.with_attr(attr::href, url.clone().into_inner())
|
||||||
|
.with_body(Some(body))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span())
|
||||||
|
} else {
|
||||||
|
engine.sink.warn(warning!(
|
||||||
|
elem.span(),
|
||||||
|
"non-URL links are not yet supported by HTML export"
|
||||||
|
));
|
||||||
|
body
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const HEADING_RULE: ShowFn<HeadingElem> = |elem, engine, styles| {
|
||||||
|
let span = elem.span();
|
||||||
|
|
||||||
|
let mut realized = elem.body.clone();
|
||||||
|
if let Some(numbering) = elem.numbering.get_ref(styles).as_ref() {
|
||||||
|
let location = elem.location().unwrap();
|
||||||
|
let numbering = Counter::of(HeadingElem::ELEM)
|
||||||
|
.display_at_loc(engine, location, styles, numbering)?
|
||||||
|
.spanned(span);
|
||||||
|
realized = numbering + SpaceElem::shared().clone() + realized;
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTML's h1 is closer to a title element. There should only be one.
|
||||||
|
// Meanwhile, a level 1 Typst heading is a section heading. For this
|
||||||
|
// reason, levels are offset by one: A Typst level 1 heading becomes
|
||||||
|
// a `<h2>`.
|
||||||
|
let level = elem.resolve_level(styles).get();
|
||||||
|
Ok(if level >= 6 {
|
||||||
|
engine.sink.warn(warning!(
|
||||||
|
span,
|
||||||
|
"heading of level {} was transformed to \
|
||||||
|
<div role=\"heading\" aria-level=\"{}\">, which is not \
|
||||||
|
supported by all assistive technology",
|
||||||
|
level, level + 1;
|
||||||
|
hint: "HTML only supports <h1> to <h6>, not <h{}>", level + 1;
|
||||||
|
hint: "you may want to restructure your document so that \
|
||||||
|
it doesn't contain deep headings"
|
||||||
|
));
|
||||||
|
HtmlElem::new(tag::div)
|
||||||
|
.with_body(Some(realized))
|
||||||
|
.with_attr(attr::role, "heading")
|
||||||
|
.with_attr(attr::aria_level, eco_format!("{}", level + 1))
|
||||||
|
.pack()
|
||||||
|
.spanned(span)
|
||||||
|
} else {
|
||||||
|
let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level - 1];
|
||||||
|
HtmlElem::new(t).with_body(Some(realized)).pack().spanned(span)
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const FIGURE_RULE: ShowFn<FigureElem> = |elem, _, styles| {
|
||||||
|
let span = elem.span();
|
||||||
|
let mut realized = elem.body.clone();
|
||||||
|
|
||||||
|
// Build the caption, if any.
|
||||||
|
if let Some(caption) = elem.caption.get_cloned(styles) {
|
||||||
|
realized = match caption.position.get(styles) {
|
||||||
|
OuterVAlignment::Top => caption.pack() + realized,
|
||||||
|
OuterVAlignment::Bottom => realized + caption.pack(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the body is considered a paragraph.
|
||||||
|
realized += ParbreakElem::shared().clone().spanned(span);
|
||||||
|
|
||||||
|
Ok(HtmlElem::new(tag::figure)
|
||||||
|
.with_body(Some(realized))
|
||||||
|
.pack()
|
||||||
|
.spanned(span))
|
||||||
|
};
|
||||||
|
|
||||||
|
const FIGURE_CAPTION_RULE: ShowFn<FigureCaption> = |elem, engine, styles| {
|
||||||
|
Ok(HtmlElem::new(tag::figcaption)
|
||||||
|
.with_body(Some(elem.realize(engine, styles)?))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const QUOTE_RULE: ShowFn<QuoteElem> = |elem, _, styles| {
|
||||||
|
let span = elem.span();
|
||||||
|
let block = elem.block.get(styles);
|
||||||
|
|
||||||
|
let mut realized = elem.body.clone();
|
||||||
|
|
||||||
|
if elem.quotes.get(styles).unwrap_or(!block) {
|
||||||
|
realized = QuoteElem::quoted(realized, styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
let attribution = elem.attribution.get_ref(styles);
|
||||||
|
|
||||||
|
if block {
|
||||||
|
let mut blockquote = HtmlElem::new(tag::blockquote).with_body(Some(realized));
|
||||||
|
if let Some(Attribution::Content(attribution)) = attribution {
|
||||||
|
if let Some(link) = attribution.to_packed::<LinkElem>() {
|
||||||
|
if let LinkTarget::Dest(Destination::Url(url)) = &link.dest {
|
||||||
|
blockquote =
|
||||||
|
blockquote.with_attr(attr::cite, url.clone().into_inner());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
realized = blockquote.pack().spanned(span);
|
||||||
|
|
||||||
|
if let Some(attribution) = attribution.as_ref() {
|
||||||
|
realized += attribution.realize(span);
|
||||||
|
}
|
||||||
|
} else if let Some(Attribution::Label(label)) = attribution {
|
||||||
|
realized += SpaceElem::shared().clone();
|
||||||
|
realized += CiteElem::new(*label).pack().spanned(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(realized)
|
||||||
|
};
|
||||||
|
|
||||||
|
const REF_RULE: ShowFn<RefElem> = |elem, engine, styles| elem.realize(engine, styles);
|
||||||
|
|
||||||
|
const CITE_GROUP_RULE: ShowFn<CiteGroup> = |elem, engine, _| elem.realize(engine);
|
||||||
|
|
||||||
|
const TABLE_RULE: ShowFn<TableElem> = |elem, engine, styles| {
|
||||||
|
// The locator is not used by HTML export, so we can just fabricate one.
|
||||||
|
let locator = Locator::root();
|
||||||
|
Ok(show_cellgrid(table_to_cellgrid(elem, engine, locator, styles)?, styles))
|
||||||
|
};
|
||||||
|
|
||||||
|
fn show_cellgrid(grid: CellGrid, styles: StyleChain) -> Content {
|
||||||
|
let elem = |tag, body| HtmlElem::new(tag).with_body(Some(body)).pack();
|
||||||
|
let mut rows: Vec<_> = grid.entries.chunks(grid.non_gutter_column_count()).collect();
|
||||||
|
|
||||||
|
let tr = |tag, row: &[Entry]| {
|
||||||
|
let row = row
|
||||||
|
.iter()
|
||||||
|
.flat_map(|entry| entry.as_cell())
|
||||||
|
.map(|cell| show_cell(tag, cell, styles));
|
||||||
|
elem(tag::tr, Content::sequence(row))
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO(subfooters): similarly to headers, take consecutive footers from
|
||||||
|
// the end for 'tfoot'.
|
||||||
|
let footer = grid.footer.map(|ft| {
|
||||||
|
let rows = rows.drain(ft.start..);
|
||||||
|
elem(tag::tfoot, Content::sequence(rows.map(|row| tr(tag::td, row))))
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store all consecutive headers at the start in 'thead'. All remaining
|
||||||
|
// headers are just 'th' rows across the table body.
|
||||||
|
let mut consecutive_header_end = 0;
|
||||||
|
let first_mid_table_header = grid
|
||||||
|
.headers
|
||||||
|
.iter()
|
||||||
|
.take_while(|hd| {
|
||||||
|
let is_consecutive = hd.range.start == consecutive_header_end;
|
||||||
|
consecutive_header_end = hd.range.end;
|
||||||
|
is_consecutive
|
||||||
|
})
|
||||||
|
.count();
|
||||||
|
|
||||||
|
let (y_offset, header) = if first_mid_table_header > 0 {
|
||||||
|
let removed_header_rows =
|
||||||
|
grid.headers.get(first_mid_table_header - 1).unwrap().range.end;
|
||||||
|
let rows = rows.drain(..removed_header_rows);
|
||||||
|
|
||||||
|
(
|
||||||
|
removed_header_rows,
|
||||||
|
Some(elem(tag::thead, Content::sequence(rows.map(|row| tr(tag::th, row))))),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
(0, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Consider improving accessibility properties of multi-level headers
|
||||||
|
// inside tables in the future, e.g. indicating which columns they are
|
||||||
|
// relative to and so on. See also:
|
||||||
|
// https://www.w3.org/WAI/tutorials/tables/multi-level/
|
||||||
|
let mut next_header = first_mid_table_header;
|
||||||
|
let mut body =
|
||||||
|
Content::sequence(rows.into_iter().enumerate().map(|(relative_y, row)| {
|
||||||
|
let y = relative_y + y_offset;
|
||||||
|
if let Some(current_header) =
|
||||||
|
grid.headers.get(next_header).filter(|h| h.range.contains(&y))
|
||||||
|
{
|
||||||
|
if y + 1 == current_header.range.end {
|
||||||
|
next_header += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr(tag::th, row)
|
||||||
|
} else {
|
||||||
|
tr(tag::td, row)
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
if header.is_some() || footer.is_some() {
|
||||||
|
body = elem(tag::tbody, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
let content = header.into_iter().chain(core::iter::once(body)).chain(footer);
|
||||||
|
elem(tag::table, Content::sequence(content))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn show_cell(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content {
|
||||||
|
let cell = cell.body.clone();
|
||||||
|
let Some(cell) = cell.to_packed::<TableCell>() else { return cell };
|
||||||
|
let mut attrs = HtmlAttrs::default();
|
||||||
|
let span = |n: NonZeroUsize| (n != NonZeroUsize::MIN).then(|| n.to_string());
|
||||||
|
if let Some(colspan) = span(cell.colspan.get(styles)) {
|
||||||
|
attrs.push(attr::colspan, colspan);
|
||||||
|
}
|
||||||
|
if let Some(rowspan) = span(cell.rowspan.get(styles)) {
|
||||||
|
attrs.push(attr::rowspan, rowspan);
|
||||||
|
}
|
||||||
|
HtmlElem::new(tag)
|
||||||
|
.with_body(Some(cell.body.clone()))
|
||||||
|
.with_attrs(attrs)
|
||||||
|
.pack()
|
||||||
|
.spanned(cell.span())
|
||||||
|
}
|
||||||
|
|
||||||
|
const SUB_RULE: ShowFn<SubElem> = |elem, _, _| {
|
||||||
|
Ok(HtmlElem::new(tag::sub)
|
||||||
|
.with_body(Some(elem.body.clone()))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const SUPER_RULE: ShowFn<SuperElem> = |elem, _, _| {
|
||||||
|
Ok(HtmlElem::new(tag::sup)
|
||||||
|
.with_body(Some(elem.body.clone()))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const UNDERLINE_RULE: ShowFn<UnderlineElem> = |elem, _, _| {
|
||||||
|
// Note: In modern HTML, `<u>` is not the underline element, but
|
||||||
|
// rather an "Unarticulated Annotation" element (see HTML spec
|
||||||
|
// 4.5.22). Using `text-decoration` instead is recommended by MDN.
|
||||||
|
Ok(HtmlElem::new(tag::span)
|
||||||
|
.with_attr(attr::style, "text-decoration: underline")
|
||||||
|
.with_body(Some(elem.body.clone()))
|
||||||
|
.pack())
|
||||||
|
};
|
||||||
|
|
||||||
|
const OVERLINE_RULE: ShowFn<OverlineElem> = |elem, _, _| {
|
||||||
|
Ok(HtmlElem::new(tag::span)
|
||||||
|
.with_attr(attr::style, "text-decoration: overline")
|
||||||
|
.with_body(Some(elem.body.clone()))
|
||||||
|
.pack())
|
||||||
|
};
|
||||||
|
|
||||||
|
const STRIKE_RULE: ShowFn<StrikeElem> =
|
||||||
|
|elem, _, _| Ok(HtmlElem::new(tag::s).with_body(Some(elem.body.clone())).pack());
|
||||||
|
|
||||||
|
const HIGHLIGHT_RULE: ShowFn<HighlightElem> =
|
||||||
|
|elem, _, _| Ok(HtmlElem::new(tag::mark).with_body(Some(elem.body.clone())).pack());
|
||||||
|
|
||||||
|
const RAW_RULE: ShowFn<RawElem> = |elem, _, styles| {
|
||||||
|
let lines = elem.lines.as_deref().unwrap_or_default();
|
||||||
|
|
||||||
|
let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1));
|
||||||
|
for (i, line) in lines.iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
seq.push(LinebreakElem::shared().clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
seq.push(line.clone().pack());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(HtmlElem::new(if elem.block.get(styles) { tag::pre } else { tag::code })
|
||||||
|
.with_body(Some(Content::sequence(seq)))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const RAW_LINE_RULE: ShowFn<RawLine> = |elem, _, _| Ok(elem.body.clone());
|
@ -11,19 +11,20 @@ use bumpalo::Bump;
|
|||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use ecow::{eco_format, eco_vec, EcoString};
|
use ecow::{eco_format, eco_vec, EcoString};
|
||||||
use typst_assets::html as data;
|
use typst_assets::html as data;
|
||||||
use typst_macros::cast;
|
use typst_library::diag::{bail, At, Hint, HintedStrResult, SourceResult};
|
||||||
|
use typst_library::engine::Engine;
|
||||||
use crate::diag::{bail, At, Hint, HintedStrResult, SourceResult};
|
use typst_library::foundations::{
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
|
||||||
Args, Array, AutoValue, CastInfo, Content, Context, Datetime, Dict, Duration,
|
Args, Array, AutoValue, CastInfo, Content, Context, Datetime, Dict, Duration,
|
||||||
FromValue, IntoValue, NativeFuncData, NativeFuncPtr, NoneValue, ParamInfo,
|
FromValue, IntoValue, NativeFuncData, NativeFuncPtr, NoneValue, ParamInfo,
|
||||||
PositiveF64, Reflect, Scope, Str, Type, Value,
|
PositiveF64, Reflect, Scope, Str, Type, Value,
|
||||||
};
|
};
|
||||||
use crate::html::tag;
|
use typst_library::html::tag;
|
||||||
use crate::html::{HtmlAttr, HtmlAttrs, HtmlElem, HtmlTag};
|
use typst_library::html::{HtmlAttr, HtmlAttrs, HtmlElem, HtmlTag};
|
||||||
use crate::layout::{Axes, Axis, Dir, Length};
|
use typst_library::layout::{Axes, Axis, Dir, Length};
|
||||||
use crate::visualize::Color;
|
use typst_library::visualize::Color;
|
||||||
|
use typst_macros::cast;
|
||||||
|
|
||||||
|
use crate::css;
|
||||||
|
|
||||||
/// Hook up all typed HTML definitions.
|
/// Hook up all typed HTML definitions.
|
||||||
pub(super) fn define(html: &mut Scope) {
|
pub(super) fn define(html: &mut Scope) {
|
||||||
@ -705,153 +706,6 @@ impl IntoAttr for SourceSize {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Conversion from Typst data types into CSS data types.
|
|
||||||
///
|
|
||||||
/// This can be moved elsewhere once we start supporting more CSS stuff.
|
|
||||||
mod css {
|
|
||||||
use std::fmt::{self, Display};
|
|
||||||
|
|
||||||
use typst_utils::Numeric;
|
|
||||||
|
|
||||||
use crate::layout::Length;
|
|
||||||
use crate::visualize::{Color, Hsl, LinearRgb, Oklab, Oklch, Rgb};
|
|
||||||
|
|
||||||
pub fn length(length: Length) -> impl Display {
|
|
||||||
typst_utils::display(move |f| match (length.abs.is_zero(), length.em.is_zero()) {
|
|
||||||
(false, false) => {
|
|
||||||
write!(f, "calc({}pt + {}em)", length.abs.to_pt(), length.em.get())
|
|
||||||
}
|
|
||||||
(true, false) => write!(f, "{}em", length.em.get()),
|
|
||||||
(_, true) => write!(f, "{}pt", length.abs.to_pt()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn color(color: Color) -> impl Display {
|
|
||||||
typst_utils::display(move |f| match color {
|
|
||||||
Color::Rgb(_) | Color::Cmyk(_) | Color::Luma(_) => rgb(f, color.to_rgb()),
|
|
||||||
Color::Oklab(v) => oklab(f, v),
|
|
||||||
Color::Oklch(v) => oklch(f, v),
|
|
||||||
Color::LinearRgb(v) => linear_rgb(f, v),
|
|
||||||
Color::Hsl(_) | Color::Hsv(_) => hsl(f, color.to_hsl()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn oklab(f: &mut fmt::Formatter<'_>, v: Oklab) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"oklab({} {} {}{})",
|
|
||||||
percent(v.l),
|
|
||||||
number(v.a),
|
|
||||||
number(v.b),
|
|
||||||
alpha(v.alpha)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn oklch(f: &mut fmt::Formatter<'_>, v: Oklch) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"oklch({} {} {}deg{})",
|
|
||||||
percent(v.l),
|
|
||||||
number(v.chroma),
|
|
||||||
number(v.hue.into_degrees()),
|
|
||||||
alpha(v.alpha)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn rgb(f: &mut fmt::Formatter<'_>, v: Rgb) -> fmt::Result {
|
|
||||||
if let Some(v) = rgb_to_8_bit_lossless(v) {
|
|
||||||
let (r, g, b, a) = v.into_components();
|
|
||||||
write!(f, "#{r:02x}{g:02x}{b:02x}")?;
|
|
||||||
if a != u8::MAX {
|
|
||||||
write!(f, "{a:02x}")?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"rgb({} {} {}{})",
|
|
||||||
percent(v.red),
|
|
||||||
percent(v.green),
|
|
||||||
percent(v.blue),
|
|
||||||
alpha(v.alpha)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Converts an f32 RGBA color to its 8-bit representation if the result is
|
|
||||||
/// [very close](is_very_close) to the original.
|
|
||||||
fn rgb_to_8_bit_lossless(
|
|
||||||
v: Rgb,
|
|
||||||
) -> Option<palette::rgb::Rgba<palette::encoding::Srgb, u8>> {
|
|
||||||
let l = v.into_format::<u8, u8>();
|
|
||||||
let h = l.into_format::<f32, f32>();
|
|
||||||
(is_very_close(v.red, h.red)
|
|
||||||
&& is_very_close(v.blue, h.blue)
|
|
||||||
&& is_very_close(v.green, h.green)
|
|
||||||
&& is_very_close(v.alpha, h.alpha))
|
|
||||||
.then_some(l)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn linear_rgb(f: &mut fmt::Formatter<'_>, v: LinearRgb) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"color(srgb-linear {} {} {}{})",
|
|
||||||
percent(v.red),
|
|
||||||
percent(v.green),
|
|
||||||
percent(v.blue),
|
|
||||||
alpha(v.alpha),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn hsl(f: &mut fmt::Formatter<'_>, v: Hsl) -> fmt::Result {
|
|
||||||
write!(
|
|
||||||
f,
|
|
||||||
"hsl({}deg {} {}{})",
|
|
||||||
number(v.hue.into_degrees()),
|
|
||||||
percent(v.saturation),
|
|
||||||
percent(v.lightness),
|
|
||||||
alpha(v.alpha),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Displays an alpha component if it not 1.
|
|
||||||
fn alpha(value: f32) -> impl Display {
|
|
||||||
typst_utils::display(move |f| {
|
|
||||||
if !is_very_close(value, 1.0) {
|
|
||||||
write!(f, " / {}", percent(value))?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Displays a rounded percentage.
|
|
||||||
///
|
|
||||||
/// For a percentage, two significant digits after the comma gives us a
|
|
||||||
/// precision of 1/10_000, which is more than 12 bits (see `is_very_close`).
|
|
||||||
fn percent(ratio: f32) -> impl Display {
|
|
||||||
typst_utils::display(move |f| {
|
|
||||||
write!(f, "{}%", typst_utils::round_with_precision(ratio as f64 * 100.0, 2))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rounds a number for display.
|
|
||||||
///
|
|
||||||
/// For a number between 0 and 1, four significant digits give us a
|
|
||||||
/// precision of 1/10_000, which is more than 12 bits (see `is_very_close`).
|
|
||||||
fn number(value: f32) -> impl Display {
|
|
||||||
typst_utils::round_with_precision(value as f64, 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether two component values are close enough that there is no
|
|
||||||
/// difference when encoding them with 12-bit. 12 bit is the highest
|
|
||||||
/// reasonable color bit depth found in the industry.
|
|
||||||
fn is_very_close(a: f32, b: f32) -> bool {
|
|
||||||
const MAX_BIT_DEPTH: u32 = 12;
|
|
||||||
const EPS: f32 = 0.5 / 2_i32.pow(MAX_BIT_DEPTH) as f32;
|
|
||||||
(a - b).abs() < EPS
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
@ -10,7 +10,7 @@ use typst::syntax::package::{PackageSpec, PackageVersion};
|
|||||||
use typst::syntax::{FileId, Source, VirtualPath};
|
use typst::syntax::{FileId, Source, VirtualPath};
|
||||||
use typst::text::{Font, FontBook, TextElem, TextSize};
|
use typst::text::{Font, FontBook, TextElem, TextSize};
|
||||||
use typst::utils::{singleton, LazyHash};
|
use typst::utils::{singleton, LazyHash};
|
||||||
use typst::{Feature, Library, World};
|
use typst::{Feature, Library, LibraryExt, World};
|
||||||
|
|
||||||
use crate::IdeWorld;
|
use crate::IdeWorld;
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ typst-timing = { workspace = true }
|
|||||||
typst-utils = { workspace = true }
|
typst-utils = { workspace = true }
|
||||||
az = { workspace = true }
|
az = { workspace = true }
|
||||||
bumpalo = { workspace = true }
|
bumpalo = { workspace = true }
|
||||||
|
codex = { workspace = true }
|
||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
hypher = { workspace = true }
|
hypher = { workspace = true }
|
||||||
|
@ -10,21 +10,11 @@ mod modifiers;
|
|||||||
mod pad;
|
mod pad;
|
||||||
mod pages;
|
mod pages;
|
||||||
mod repeat;
|
mod repeat;
|
||||||
|
mod rules;
|
||||||
mod shapes;
|
mod shapes;
|
||||||
mod stack;
|
mod stack;
|
||||||
mod transforms;
|
mod transforms;
|
||||||
|
|
||||||
pub use self::flow::{layout_columns, layout_fragment, layout_frame};
|
pub use self::flow::{layout_fragment, layout_frame};
|
||||||
pub use self::grid::{layout_grid, layout_table};
|
|
||||||
pub use self::image::layout_image;
|
|
||||||
pub use self::lists::{layout_enum, layout_list};
|
|
||||||
pub use self::math::{layout_equation_block, layout_equation_inline};
|
|
||||||
pub use self::pad::layout_pad;
|
|
||||||
pub use self::pages::layout_document;
|
pub use self::pages::layout_document;
|
||||||
pub use self::repeat::layout_repeat;
|
pub use self::rules::register;
|
||||||
pub use self::shapes::{
|
|
||||||
layout_circle, layout_curve, layout_ellipse, layout_line, layout_path,
|
|
||||||
layout_polygon, layout_rect, layout_square,
|
|
||||||
};
|
|
||||||
pub use self::stack::layout_stack;
|
|
||||||
pub use self::transforms::{layout_move, layout_rotate, layout_scale, layout_skew};
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use std::f64::consts::SQRT_2;
|
use std::f64::consts::SQRT_2;
|
||||||
|
|
||||||
|
use codex::styling::{to_style, MathStyle};
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use typst_library::diag::SourceResult;
|
use typst_library::diag::SourceResult;
|
||||||
use typst_library::foundations::{Packed, StyleChain, SymbolElem};
|
use typst_library::foundations::{Packed, StyleChain, SymbolElem};
|
||||||
use typst_library::layout::{Abs, Size};
|
use typst_library::layout::{Abs, Size};
|
||||||
use typst_library::math::{EquationElem, MathSize, MathVariant};
|
use typst_library::math::{EquationElem, MathSize};
|
||||||
use typst_library::text::{
|
use typst_library::text::{
|
||||||
BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric,
|
BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric,
|
||||||
};
|
};
|
||||||
@ -64,12 +65,21 @@ fn layout_inline_text(
|
|||||||
ctx: &mut MathContext,
|
ctx: &mut MathContext,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<FrameFragment> {
|
) -> SourceResult<FrameFragment> {
|
||||||
|
let variant = styles.get(EquationElem::variant);
|
||||||
|
let bold = styles.get(EquationElem::bold);
|
||||||
|
// Disable auto-italic.
|
||||||
|
let italic = styles.get(EquationElem::italic).or(Some(false));
|
||||||
|
|
||||||
if text.chars().all(|c| c.is_ascii_digit() || c == '.') {
|
if text.chars().all(|c| c.is_ascii_digit() || c == '.') {
|
||||||
// Small optimization for numbers. Note that this lays out slightly
|
// Small optimization for numbers. Note that this lays out slightly
|
||||||
// differently to normal text and is worth re-evaluating in the future.
|
// differently to normal text and is worth re-evaluating in the future.
|
||||||
let mut fragments = vec![];
|
let mut fragments = vec![];
|
||||||
for unstyled_c in text.chars() {
|
for unstyled_c in text.chars() {
|
||||||
let c = styled_char(styles, unstyled_c, false);
|
// This is fine as ascii digits and '.' can never end up as more
|
||||||
|
// than a single char after styling.
|
||||||
|
let style = MathStyle::select(unstyled_c, variant, bold, italic);
|
||||||
|
let c = to_style(unstyled_c, style).next().unwrap();
|
||||||
|
|
||||||
let glyph = GlyphFragment::new_char(ctx.font, styles, c, span)?;
|
let glyph = GlyphFragment::new_char(ctx.font, styles, c, span)?;
|
||||||
fragments.push(glyph.into());
|
fragments.push(glyph.into());
|
||||||
}
|
}
|
||||||
@ -83,8 +93,10 @@ fn layout_inline_text(
|
|||||||
.map(|p| p.wrap());
|
.map(|p| p.wrap());
|
||||||
|
|
||||||
let styles = styles.chain(&local);
|
let styles = styles.chain(&local);
|
||||||
let styled_text: EcoString =
|
let styled_text: EcoString = text
|
||||||
text.chars().map(|c| styled_char(styles, c, false)).collect();
|
.chars()
|
||||||
|
.flat_map(|c| to_style(c, MathStyle::select(c, variant, bold, italic)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
let spaced = styled_text.graphemes(true).nth(1).is_some();
|
let spaced = styled_text.graphemes(true).nth(1).is_some();
|
||||||
let elem = TextElem::packed(styled_text).spanned(span);
|
let elem = TextElem::packed(styled_text).spanned(span);
|
||||||
@ -124,9 +136,16 @@ pub fn layout_symbol(
|
|||||||
Some(c) if has_dtls_feat(ctx.font) => (c, styles.chain(&dtls)),
|
Some(c) if has_dtls_feat(ctx.font) => (c, styles.chain(&dtls)),
|
||||||
_ => (elem.text, styles),
|
_ => (elem.text, styles),
|
||||||
};
|
};
|
||||||
let c = styled_char(styles, unstyled_c, true);
|
|
||||||
|
let variant = styles.get(EquationElem::variant);
|
||||||
|
let bold = styles.get(EquationElem::bold);
|
||||||
|
let italic = styles.get(EquationElem::italic);
|
||||||
|
|
||||||
|
let style = MathStyle::select(unstyled_c, variant, bold, italic);
|
||||||
|
let text: EcoString = to_style(unstyled_c, style).collect();
|
||||||
|
|
||||||
let fragment: MathFragment =
|
let fragment: MathFragment =
|
||||||
match GlyphFragment::new_char(ctx.font, symbol_styles, c, elem.span()) {
|
match GlyphFragment::new(ctx.font, symbol_styles, &text, elem.span()) {
|
||||||
Ok(mut glyph) => {
|
Ok(mut glyph) => {
|
||||||
adjust_glyph_layout(&mut glyph, ctx, styles);
|
adjust_glyph_layout(&mut glyph, ctx, styles);
|
||||||
glyph.into()
|
glyph.into()
|
||||||
@ -134,8 +153,7 @@ pub fn layout_symbol(
|
|||||||
Err(_) => {
|
Err(_) => {
|
||||||
// Not in the math font, fallback to normal inline text layout.
|
// Not in the math font, fallback to normal inline text layout.
|
||||||
// TODO: Should replace this with proper fallback in [`GlyphFragment::new`].
|
// TODO: Should replace this with proper fallback in [`GlyphFragment::new`].
|
||||||
layout_inline_text(c.encode_utf8(&mut [0; 4]), elem.span(), ctx, styles)?
|
layout_inline_text(&text, elem.span(), ctx, styles)?.into()
|
||||||
.into()
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ctx.push(fragment);
|
ctx.push(fragment);
|
||||||
@ -161,226 +179,6 @@ fn adjust_glyph_layout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Style the character by selecting the unicode codepoint for italic, bold,
|
|
||||||
/// caligraphic, etc.
|
|
||||||
///
|
|
||||||
/// <https://www.w3.org/TR/mathml-core/#new-text-transform-mappings>
|
|
||||||
/// <https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols>
|
|
||||||
fn styled_char(styles: StyleChain, c: char, auto_italic: bool) -> char {
|
|
||||||
use MathVariant::*;
|
|
||||||
|
|
||||||
let variant = styles.get(EquationElem::variant);
|
|
||||||
let bold = styles.get(EquationElem::bold);
|
|
||||||
let italic = styles.get(EquationElem::italic).unwrap_or(
|
|
||||||
auto_italic
|
|
||||||
&& matches!(
|
|
||||||
c,
|
|
||||||
'a'..='z' | 'ħ' | 'ı' | 'ȷ' | 'A'..='Z' |
|
|
||||||
'α'..='ω' | '∂' | 'ϵ' | 'ϑ' | 'ϰ' | 'ϕ' | 'ϱ' | 'ϖ'
|
|
||||||
)
|
|
||||||
&& matches!(variant, Sans | Serif),
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(c) = basic_exception(c) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(c) = latin_exception(c, variant, bold, italic) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(c) = greek_exception(c, variant, bold, italic) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
let base = match c {
|
|
||||||
'A'..='Z' => 'A',
|
|
||||||
'a'..='z' => 'a',
|
|
||||||
'Α'..='Ω' => 'Α',
|
|
||||||
'α'..='ω' => 'α',
|
|
||||||
'0'..='9' => '0',
|
|
||||||
// Hebrew Alef -> Dalet.
|
|
||||||
'\u{05D0}'..='\u{05D3}' => '\u{05D0}',
|
|
||||||
_ => return c,
|
|
||||||
};
|
|
||||||
|
|
||||||
let tuple = (variant, bold, italic);
|
|
||||||
let start = match c {
|
|
||||||
// Latin upper.
|
|
||||||
'A'..='Z' => match tuple {
|
|
||||||
(Serif, false, false) => 0x0041,
|
|
||||||
(Serif, true, false) => 0x1D400,
|
|
||||||
(Serif, false, true) => 0x1D434,
|
|
||||||
(Serif, true, true) => 0x1D468,
|
|
||||||
(Sans, false, false) => 0x1D5A0,
|
|
||||||
(Sans, true, false) => 0x1D5D4,
|
|
||||||
(Sans, false, true) => 0x1D608,
|
|
||||||
(Sans, true, true) => 0x1D63C,
|
|
||||||
(Cal, false, _) => 0x1D49C,
|
|
||||||
(Cal, true, _) => 0x1D4D0,
|
|
||||||
(Frak, false, _) => 0x1D504,
|
|
||||||
(Frak, true, _) => 0x1D56C,
|
|
||||||
(Mono, _, _) => 0x1D670,
|
|
||||||
(Bb, _, _) => 0x1D538,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Latin lower.
|
|
||||||
'a'..='z' => match tuple {
|
|
||||||
(Serif, false, false) => 0x0061,
|
|
||||||
(Serif, true, false) => 0x1D41A,
|
|
||||||
(Serif, false, true) => 0x1D44E,
|
|
||||||
(Serif, true, true) => 0x1D482,
|
|
||||||
(Sans, false, false) => 0x1D5BA,
|
|
||||||
(Sans, true, false) => 0x1D5EE,
|
|
||||||
(Sans, false, true) => 0x1D622,
|
|
||||||
(Sans, true, true) => 0x1D656,
|
|
||||||
(Cal, false, _) => 0x1D4B6,
|
|
||||||
(Cal, true, _) => 0x1D4EA,
|
|
||||||
(Frak, false, _) => 0x1D51E,
|
|
||||||
(Frak, true, _) => 0x1D586,
|
|
||||||
(Mono, _, _) => 0x1D68A,
|
|
||||||
(Bb, _, _) => 0x1D552,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Greek upper.
|
|
||||||
'Α'..='Ω' => match tuple {
|
|
||||||
(Serif, false, false) => 0x0391,
|
|
||||||
(Serif, true, false) => 0x1D6A8,
|
|
||||||
(Serif, false, true) => 0x1D6E2,
|
|
||||||
(Serif, true, true) => 0x1D71C,
|
|
||||||
(Sans, _, false) => 0x1D756,
|
|
||||||
(Sans, _, true) => 0x1D790,
|
|
||||||
(Cal | Frak | Mono | Bb, _, _) => return c,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Greek lower.
|
|
||||||
'α'..='ω' => match tuple {
|
|
||||||
(Serif, false, false) => 0x03B1,
|
|
||||||
(Serif, true, false) => 0x1D6C2,
|
|
||||||
(Serif, false, true) => 0x1D6FC,
|
|
||||||
(Serif, true, true) => 0x1D736,
|
|
||||||
(Sans, _, false) => 0x1D770,
|
|
||||||
(Sans, _, true) => 0x1D7AA,
|
|
||||||
(Cal | Frak | Mono | Bb, _, _) => return c,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Hebrew Alef -> Dalet.
|
|
||||||
'\u{05D0}'..='\u{05D3}' => 0x2135,
|
|
||||||
|
|
||||||
// Numbers.
|
|
||||||
'0'..='9' => match tuple {
|
|
||||||
(Serif, false, _) => 0x0030,
|
|
||||||
(Serif, true, _) => 0x1D7CE,
|
|
||||||
(Bb, _, _) => 0x1D7D8,
|
|
||||||
(Sans, false, _) => 0x1D7E2,
|
|
||||||
(Sans, true, _) => 0x1D7EC,
|
|
||||||
(Mono, _, _) => 0x1D7F6,
|
|
||||||
(Cal | Frak, _, _) => return c,
|
|
||||||
},
|
|
||||||
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
|
|
||||||
std::char::from_u32(start + (c as u32 - base as u32)).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn basic_exception(c: char) -> Option<char> {
|
|
||||||
Some(match c {
|
|
||||||
'〈' => '⟨',
|
|
||||||
'〉' => '⟩',
|
|
||||||
'《' => '⟪',
|
|
||||||
'》' => '⟫',
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn latin_exception(
|
|
||||||
c: char,
|
|
||||||
variant: MathVariant,
|
|
||||||
bold: bool,
|
|
||||||
italic: bool,
|
|
||||||
) -> Option<char> {
|
|
||||||
use MathVariant::*;
|
|
||||||
Some(match (c, variant, bold, italic) {
|
|
||||||
('B', Cal, false, _) => 'ℬ',
|
|
||||||
('E', Cal, false, _) => 'ℰ',
|
|
||||||
('F', Cal, false, _) => 'ℱ',
|
|
||||||
('H', Cal, false, _) => 'ℋ',
|
|
||||||
('I', Cal, false, _) => 'ℐ',
|
|
||||||
('L', Cal, false, _) => 'ℒ',
|
|
||||||
('M', Cal, false, _) => 'ℳ',
|
|
||||||
('R', Cal, false, _) => 'ℛ',
|
|
||||||
('C', Frak, false, _) => 'ℭ',
|
|
||||||
('H', Frak, false, _) => 'ℌ',
|
|
||||||
('I', Frak, false, _) => 'ℑ',
|
|
||||||
('R', Frak, false, _) => 'ℜ',
|
|
||||||
('Z', Frak, false, _) => 'ℨ',
|
|
||||||
('C', Bb, ..) => 'ℂ',
|
|
||||||
('H', Bb, ..) => 'ℍ',
|
|
||||||
('N', Bb, ..) => 'ℕ',
|
|
||||||
('P', Bb, ..) => 'ℙ',
|
|
||||||
('Q', Bb, ..) => 'ℚ',
|
|
||||||
('R', Bb, ..) => 'ℝ',
|
|
||||||
('Z', Bb, ..) => 'ℤ',
|
|
||||||
('D', Bb, _, true) => 'ⅅ',
|
|
||||||
('d', Bb, _, true) => 'ⅆ',
|
|
||||||
('e', Bb, _, true) => 'ⅇ',
|
|
||||||
('i', Bb, _, true) => 'ⅈ',
|
|
||||||
('j', Bb, _, true) => 'ⅉ',
|
|
||||||
('h', Serif, false, true) => 'ℎ',
|
|
||||||
('e', Cal, false, _) => 'ℯ',
|
|
||||||
('g', Cal, false, _) => 'ℊ',
|
|
||||||
('o', Cal, false, _) => 'ℴ',
|
|
||||||
('ħ', Serif, .., true) => 'ℏ',
|
|
||||||
('ı', Serif, .., true) => '𝚤',
|
|
||||||
('ȷ', Serif, .., true) => '𝚥',
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn greek_exception(
|
|
||||||
c: char,
|
|
||||||
variant: MathVariant,
|
|
||||||
bold: bool,
|
|
||||||
italic: bool,
|
|
||||||
) -> Option<char> {
|
|
||||||
use MathVariant::*;
|
|
||||||
if c == 'Ϝ' && variant == Serif && bold {
|
|
||||||
return Some('𝟊');
|
|
||||||
}
|
|
||||||
if c == 'ϝ' && variant == Serif && bold {
|
|
||||||
return Some('𝟋');
|
|
||||||
}
|
|
||||||
|
|
||||||
let list = match c {
|
|
||||||
'ϴ' => ['𝚹', '𝛳', '𝜭', '𝝧', '𝞡', 'ϴ'],
|
|
||||||
'∇' => ['𝛁', '𝛻', '𝜵', '𝝯', '𝞩', '∇'],
|
|
||||||
'∂' => ['𝛛', '𝜕', '𝝏', '𝞉', '𝟃', '∂'],
|
|
||||||
'ϵ' => ['𝛜', '𝜖', '𝝐', '𝞊', '𝟄', 'ϵ'],
|
|
||||||
'ϑ' => ['𝛝', '𝜗', '𝝑', '𝞋', '𝟅', 'ϑ'],
|
|
||||||
'ϰ' => ['𝛞', '𝜘', '𝝒', '𝞌', '𝟆', 'ϰ'],
|
|
||||||
'ϕ' => ['𝛟', '𝜙', '𝝓', '𝞍', '𝟇', 'ϕ'],
|
|
||||||
'ϱ' => ['𝛠', '𝜚', '𝝔', '𝞎', '𝟈', 'ϱ'],
|
|
||||||
'ϖ' => ['𝛡', '𝜛', '𝝕', '𝞏', '𝟉', 'ϖ'],
|
|
||||||
'Γ' => ['𝚪', '𝛤', '𝜞', '𝝘', '𝞒', 'ℾ'],
|
|
||||||
'γ' => ['𝛄', '𝛾', '𝜸', '𝝲', '𝞬', 'ℽ'],
|
|
||||||
'Π' => ['𝚷', '𝛱', '𝜫', '𝝥', '𝞟', 'ℿ'],
|
|
||||||
'π' => ['𝛑', '𝜋', '𝝅', '𝝿', '𝞹', 'ℼ'],
|
|
||||||
'∑' => ['∑', '∑', '∑', '∑', '∑', '⅀'],
|
|
||||||
_ => return None,
|
|
||||||
};
|
|
||||||
|
|
||||||
Some(match (variant, bold, italic) {
|
|
||||||
(Serif, true, false) => list[0],
|
|
||||||
(Serif, false, true) => list[1],
|
|
||||||
(Serif, true, true) => list[2],
|
|
||||||
(Sans, _, false) => list[3],
|
|
||||||
(Sans, _, true) => list[4],
|
|
||||||
(Bb, ..) => list[5],
|
|
||||||
_ => return None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The non-dotless version of a dotless character that can be used with the
|
/// The non-dotless version of a dotless character that can be used with the
|
||||||
/// `dtls` OpenType feature.
|
/// `dtls` OpenType feature.
|
||||||
pub fn try_dotless(c: char) -> Option<char> {
|
pub fn try_dotless(c: char) -> Option<char> {
|
||||||
|
890
crates/typst-layout/src/rules.rs
Normal file
@ -0,0 +1,890 @@
|
|||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
use comemo::Track;
|
||||||
|
use ecow::{eco_format, EcoVec};
|
||||||
|
use smallvec::smallvec;
|
||||||
|
use typst_library::diag::{bail, At, SourceResult};
|
||||||
|
use typst_library::foundations::{
|
||||||
|
dict, Content, Context, NativeElement, NativeRuleMap, Packed, Resolve, ShowFn, Smart,
|
||||||
|
StyleChain, Target,
|
||||||
|
};
|
||||||
|
use typst_library::introspection::{Counter, Locator, LocatorLink};
|
||||||
|
use typst_library::layout::{
|
||||||
|
Abs, AlignElem, Alignment, Axes, BlockBody, BlockElem, ColumnsElem, Em, GridCell,
|
||||||
|
GridChild, GridElem, GridItem, HAlignment, HElem, HideElem, InlineElem, LayoutElem,
|
||||||
|
Length, MoveElem, OuterVAlignment, PadElem, PlaceElem, PlacementScope, Region, Rel,
|
||||||
|
RepeatElem, RotateElem, ScaleElem, Sides, Size, Sizing, SkewElem, Spacing,
|
||||||
|
StackChild, StackElem, TrackSizings, VElem,
|
||||||
|
};
|
||||||
|
use typst_library::math::EquationElem;
|
||||||
|
use typst_library::model::{
|
||||||
|
Attribution, BibliographyElem, CiteElem, CiteGroup, CslSource, Destination, EmphElem,
|
||||||
|
EnumElem, FigureCaption, FigureElem, FootnoteElem, FootnoteEntry, HeadingElem,
|
||||||
|
LinkElem, LinkTarget, ListElem, Outlinable, OutlineElem, OutlineEntry, ParElem,
|
||||||
|
ParbreakElem, QuoteElem, RefElem, StrongElem, TableCell, TableElem, TermsElem, Works,
|
||||||
|
};
|
||||||
|
use typst_library::pdf::EmbedElem;
|
||||||
|
use typst_library::text::{
|
||||||
|
DecoLine, Decoration, HighlightElem, ItalicToggle, LinebreakElem, LocalName,
|
||||||
|
OverlineElem, RawElem, RawLine, ScriptKind, ShiftSettings, Smallcaps, SmallcapsElem,
|
||||||
|
SpaceElem, StrikeElem, SubElem, SuperElem, TextElem, TextSize, UnderlineElem,
|
||||||
|
WeightDelta,
|
||||||
|
};
|
||||||
|
use typst_library::visualize::{
|
||||||
|
CircleElem, CurveElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem,
|
||||||
|
RectElem, SquareElem, Stroke,
|
||||||
|
};
|
||||||
|
use typst_utils::{Get, NonZeroExt, Numeric};
|
||||||
|
|
||||||
|
/// Register show rules for the [paged target](Target::Paged).
|
||||||
|
pub fn register(rules: &mut NativeRuleMap) {
|
||||||
|
use Target::Paged;
|
||||||
|
|
||||||
|
// Model.
|
||||||
|
rules.register(Paged, STRONG_RULE);
|
||||||
|
rules.register(Paged, EMPH_RULE);
|
||||||
|
rules.register(Paged, LIST_RULE);
|
||||||
|
rules.register(Paged, ENUM_RULE);
|
||||||
|
rules.register(Paged, TERMS_RULE);
|
||||||
|
rules.register(Paged, LINK_RULE);
|
||||||
|
rules.register(Paged, HEADING_RULE);
|
||||||
|
rules.register(Paged, FIGURE_RULE);
|
||||||
|
rules.register(Paged, FIGURE_CAPTION_RULE);
|
||||||
|
rules.register(Paged, QUOTE_RULE);
|
||||||
|
rules.register(Paged, FOOTNOTE_RULE);
|
||||||
|
rules.register(Paged, FOOTNOTE_ENTRY_RULE);
|
||||||
|
rules.register(Paged, OUTLINE_RULE);
|
||||||
|
rules.register(Paged, OUTLINE_ENTRY_RULE);
|
||||||
|
rules.register(Paged, REF_RULE);
|
||||||
|
rules.register(Paged, CITE_GROUP_RULE);
|
||||||
|
rules.register(Paged, BIBLIOGRAPHY_RULE);
|
||||||
|
rules.register(Paged, TABLE_RULE);
|
||||||
|
rules.register(Paged, TABLE_CELL_RULE);
|
||||||
|
|
||||||
|
// Text.
|
||||||
|
rules.register(Paged, SUB_RULE);
|
||||||
|
rules.register(Paged, SUPER_RULE);
|
||||||
|
rules.register(Paged, UNDERLINE_RULE);
|
||||||
|
rules.register(Paged, OVERLINE_RULE);
|
||||||
|
rules.register(Paged, STRIKE_RULE);
|
||||||
|
rules.register(Paged, HIGHLIGHT_RULE);
|
||||||
|
rules.register(Paged, SMALLCAPS_RULE);
|
||||||
|
rules.register(Paged, RAW_RULE);
|
||||||
|
rules.register(Paged, RAW_LINE_RULE);
|
||||||
|
|
||||||
|
// Layout.
|
||||||
|
rules.register(Paged, ALIGN_RULE);
|
||||||
|
rules.register(Paged, PAD_RULE);
|
||||||
|
rules.register(Paged, COLUMNS_RULE);
|
||||||
|
rules.register(Paged, STACK_RULE);
|
||||||
|
rules.register(Paged, GRID_RULE);
|
||||||
|
rules.register(Paged, GRID_CELL_RULE);
|
||||||
|
rules.register(Paged, MOVE_RULE);
|
||||||
|
rules.register(Paged, SCALE_RULE);
|
||||||
|
rules.register(Paged, ROTATE_RULE);
|
||||||
|
rules.register(Paged, SKEW_RULE);
|
||||||
|
rules.register(Paged, REPEAT_RULE);
|
||||||
|
rules.register(Paged, HIDE_RULE);
|
||||||
|
rules.register(Paged, LAYOUT_RULE);
|
||||||
|
|
||||||
|
// Visualize.
|
||||||
|
rules.register(Paged, IMAGE_RULE);
|
||||||
|
rules.register(Paged, LINE_RULE);
|
||||||
|
rules.register(Paged, RECT_RULE);
|
||||||
|
rules.register(Paged, SQUARE_RULE);
|
||||||
|
rules.register(Paged, ELLIPSE_RULE);
|
||||||
|
rules.register(Paged, CIRCLE_RULE);
|
||||||
|
rules.register(Paged, POLYGON_RULE);
|
||||||
|
rules.register(Paged, CURVE_RULE);
|
||||||
|
rules.register(Paged, PATH_RULE);
|
||||||
|
|
||||||
|
// Math.
|
||||||
|
rules.register(Paged, EQUATION_RULE);
|
||||||
|
|
||||||
|
// PDF.
|
||||||
|
rules.register(Paged, EMBED_RULE);
|
||||||
|
}
|
||||||
|
|
||||||
|
const STRONG_RULE: ShowFn<StrongElem> = |elem, _, styles| {
|
||||||
|
Ok(elem
|
||||||
|
.body
|
||||||
|
.clone()
|
||||||
|
.set(TextElem::delta, WeightDelta(elem.delta.get(styles))))
|
||||||
|
};
|
||||||
|
|
||||||
|
const EMPH_RULE: ShowFn<EmphElem> =
|
||||||
|
|elem, _, _| Ok(elem.body.clone().set(TextElem::emph, ItalicToggle(true)));
|
||||||
|
|
||||||
|
const LIST_RULE: ShowFn<ListElem> = |elem, _, styles| {
|
||||||
|
let tight = elem.tight.get(styles);
|
||||||
|
|
||||||
|
let mut realized = BlockElem::multi_layouter(elem.clone(), crate::lists::layout_list)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span());
|
||||||
|
|
||||||
|
if tight {
|
||||||
|
let spacing = elem
|
||||||
|
.spacing
|
||||||
|
.get(styles)
|
||||||
|
.unwrap_or_else(|| styles.get(ParElem::leading));
|
||||||
|
let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack();
|
||||||
|
realized = v + realized;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(realized)
|
||||||
|
};
|
||||||
|
|
||||||
|
const ENUM_RULE: ShowFn<EnumElem> = |elem, _, styles| {
|
||||||
|
let tight = elem.tight.get(styles);
|
||||||
|
|
||||||
|
let mut realized = BlockElem::multi_layouter(elem.clone(), crate::lists::layout_enum)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span());
|
||||||
|
|
||||||
|
if tight {
|
||||||
|
let spacing = elem
|
||||||
|
.spacing
|
||||||
|
.get(styles)
|
||||||
|
.unwrap_or_else(|| styles.get(ParElem::leading));
|
||||||
|
let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack();
|
||||||
|
realized = v + realized;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(realized)
|
||||||
|
};
|
||||||
|
|
||||||
|
const TERMS_RULE: ShowFn<TermsElem> = |elem, _, styles| {
|
||||||
|
let span = elem.span();
|
||||||
|
let tight = elem.tight.get(styles);
|
||||||
|
|
||||||
|
let separator = elem.separator.get_ref(styles);
|
||||||
|
let indent = elem.indent.get(styles);
|
||||||
|
let hanging_indent = elem.hanging_indent.get(styles);
|
||||||
|
let gutter = elem.spacing.get(styles).unwrap_or_else(|| {
|
||||||
|
if tight {
|
||||||
|
styles.get(ParElem::leading)
|
||||||
|
} else {
|
||||||
|
styles.get(ParElem::spacing)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let pad = hanging_indent + indent;
|
||||||
|
let unpad = (!hanging_indent.is_zero())
|
||||||
|
.then(|| HElem::new((-hanging_indent).into()).pack().spanned(span));
|
||||||
|
|
||||||
|
let mut children = vec![];
|
||||||
|
for child in elem.children.iter() {
|
||||||
|
let mut seq = vec![];
|
||||||
|
seq.extend(unpad.clone());
|
||||||
|
seq.push(child.term.clone().strong());
|
||||||
|
seq.push(separator.clone());
|
||||||
|
seq.push(child.description.clone());
|
||||||
|
|
||||||
|
// Text in wide term lists shall always turn into paragraphs.
|
||||||
|
if !tight {
|
||||||
|
seq.push(ParbreakElem::shared().clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
children.push(StackChild::Block(Content::sequence(seq)));
|
||||||
|
}
|
||||||
|
|
||||||
|
let padding =
|
||||||
|
Sides::default().with(styles.resolve(TextElem::dir).start(), pad.into());
|
||||||
|
|
||||||
|
let mut realized = StackElem::new(children)
|
||||||
|
.with_spacing(Some(gutter.into()))
|
||||||
|
.pack()
|
||||||
|
.spanned(span)
|
||||||
|
.padded(padding)
|
||||||
|
.set(TermsElem::within, true);
|
||||||
|
|
||||||
|
if tight {
|
||||||
|
let spacing = elem
|
||||||
|
.spacing
|
||||||
|
.get(styles)
|
||||||
|
.unwrap_or_else(|| styles.get(ParElem::leading));
|
||||||
|
let v = VElem::new(spacing.into())
|
||||||
|
.with_weak(true)
|
||||||
|
.with_attach(true)
|
||||||
|
.pack()
|
||||||
|
.spanned(span);
|
||||||
|
realized = v + realized;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(realized)
|
||||||
|
};
|
||||||
|
|
||||||
|
const LINK_RULE: ShowFn<LinkElem> = |elem, engine, _| {
|
||||||
|
let body = elem.body.clone();
|
||||||
|
Ok(match &elem.dest {
|
||||||
|
LinkTarget::Dest(dest) => body.linked(dest.clone()),
|
||||||
|
LinkTarget::Label(label) => {
|
||||||
|
let elem = engine.introspector.query_label(*label).at(elem.span())?;
|
||||||
|
let dest = Destination::Location(elem.location().unwrap());
|
||||||
|
body.linked(dest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
const HEADING_RULE: ShowFn<HeadingElem> = |elem, engine, styles| {
|
||||||
|
const SPACING_TO_NUMBERING: Em = Em::new(0.3);
|
||||||
|
|
||||||
|
let span = elem.span();
|
||||||
|
let mut realized = elem.body.clone();
|
||||||
|
|
||||||
|
let hanging_indent = elem.hanging_indent.get(styles);
|
||||||
|
let mut indent = match hanging_indent {
|
||||||
|
Smart::Custom(length) => length.resolve(styles),
|
||||||
|
Smart::Auto => Abs::zero(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(numbering) = elem.numbering.get_ref(styles).as_ref() {
|
||||||
|
let location = elem.location().unwrap();
|
||||||
|
let numbering = Counter::of(HeadingElem::ELEM)
|
||||||
|
.display_at_loc(engine, location, styles, numbering)?
|
||||||
|
.spanned(span);
|
||||||
|
|
||||||
|
if hanging_indent.is_auto() {
|
||||||
|
let pod = Region::new(Axes::splat(Abs::inf()), Axes::splat(false));
|
||||||
|
|
||||||
|
// We don't have a locator for the numbering here, so we just
|
||||||
|
// use the measurement infrastructure for now.
|
||||||
|
let link = LocatorLink::measure(location);
|
||||||
|
let size = (engine.routines.layout_frame)(
|
||||||
|
engine,
|
||||||
|
&numbering,
|
||||||
|
Locator::link(&link),
|
||||||
|
styles,
|
||||||
|
pod,
|
||||||
|
)?
|
||||||
|
.size();
|
||||||
|
|
||||||
|
indent = size.x + SPACING_TO_NUMBERING.resolve(styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
let spacing = HElem::new(SPACING_TO_NUMBERING.into()).with_weak(true).pack();
|
||||||
|
|
||||||
|
realized = numbering + spacing + realized;
|
||||||
|
}
|
||||||
|
|
||||||
|
let block = if indent != Abs::zero() {
|
||||||
|
let body = HElem::new((-indent).into()).pack() + realized;
|
||||||
|
let inset = Sides::default()
|
||||||
|
.with(styles.resolve(TextElem::dir).start(), Some(indent.into()));
|
||||||
|
BlockElem::new()
|
||||||
|
.with_body(Some(BlockBody::Content(body)))
|
||||||
|
.with_inset(inset)
|
||||||
|
} else {
|
||||||
|
BlockElem::new().with_body(Some(BlockBody::Content(realized)))
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(block.pack().spanned(span))
|
||||||
|
};
|
||||||
|
|
||||||
|
const FIGURE_RULE: ShowFn<FigureElem> = |elem, _, styles| {
|
||||||
|
let span = elem.span();
|
||||||
|
let mut realized = elem.body.clone();
|
||||||
|
|
||||||
|
// Build the caption, if any.
|
||||||
|
if let Some(caption) = elem.caption.get_cloned(styles) {
|
||||||
|
let (first, second) = match caption.position.get(styles) {
|
||||||
|
OuterVAlignment::Top => (caption.pack(), realized),
|
||||||
|
OuterVAlignment::Bottom => (realized, caption.pack()),
|
||||||
|
};
|
||||||
|
realized = Content::sequence(vec![
|
||||||
|
first,
|
||||||
|
VElem::new(elem.gap.get(styles).into())
|
||||||
|
.with_weak(true)
|
||||||
|
.pack()
|
||||||
|
.spanned(span),
|
||||||
|
second,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure that the body is considered a paragraph.
|
||||||
|
realized += ParbreakElem::shared().clone().spanned(span);
|
||||||
|
|
||||||
|
// Wrap the contents in a block.
|
||||||
|
realized = BlockElem::new()
|
||||||
|
.with_body(Some(BlockBody::Content(realized)))
|
||||||
|
.pack()
|
||||||
|
.spanned(span);
|
||||||
|
|
||||||
|
// Wrap in a float.
|
||||||
|
if let Some(align) = elem.placement.get(styles) {
|
||||||
|
realized = PlaceElem::new(realized)
|
||||||
|
.with_alignment(align.map(|align| HAlignment::Center + align))
|
||||||
|
.with_scope(elem.scope.get(styles))
|
||||||
|
.with_float(true)
|
||||||
|
.pack()
|
||||||
|
.spanned(span);
|
||||||
|
} else if elem.scope.get(styles) == PlacementScope::Parent {
|
||||||
|
bail!(
|
||||||
|
span,
|
||||||
|
"parent-scoped placement is only available for floating figures";
|
||||||
|
hint: "you can enable floating placement with `figure(placement: auto, ..)`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(realized)
|
||||||
|
};
|
||||||
|
|
||||||
|
const FIGURE_CAPTION_RULE: ShowFn<FigureCaption> = |elem, engine, styles| {
|
||||||
|
Ok(BlockElem::new()
|
||||||
|
.with_body(Some(BlockBody::Content(elem.realize(engine, styles)?)))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const QUOTE_RULE: ShowFn<QuoteElem> = |elem, _, styles| {
|
||||||
|
let span = elem.span();
|
||||||
|
let block = elem.block.get(styles);
|
||||||
|
|
||||||
|
let mut realized = elem.body.clone();
|
||||||
|
|
||||||
|
if elem.quotes.get(styles).unwrap_or(!block) {
|
||||||
|
// Add zero-width weak spacing to make the quotes "sticky".
|
||||||
|
let hole = HElem::hole().pack();
|
||||||
|
let sticky = Content::sequence([hole.clone(), realized, hole]);
|
||||||
|
realized = QuoteElem::quoted(sticky, styles);
|
||||||
|
}
|
||||||
|
|
||||||
|
let attribution = elem.attribution.get_ref(styles);
|
||||||
|
|
||||||
|
if block {
|
||||||
|
realized = BlockElem::new()
|
||||||
|
.with_body(Some(BlockBody::Content(realized)))
|
||||||
|
.pack()
|
||||||
|
.spanned(span);
|
||||||
|
|
||||||
|
if let Some(attribution) = attribution.as_ref() {
|
||||||
|
// Bring the attribution a bit closer to the quote.
|
||||||
|
let gap = Spacing::Rel(Em::new(0.9).into());
|
||||||
|
let v = VElem::new(gap).with_weak(true).pack();
|
||||||
|
realized += v;
|
||||||
|
realized += BlockElem::new()
|
||||||
|
.with_body(Some(BlockBody::Content(attribution.realize(span))))
|
||||||
|
.pack()
|
||||||
|
.aligned(Alignment::END);
|
||||||
|
}
|
||||||
|
|
||||||
|
realized = PadElem::new(realized).pack();
|
||||||
|
} else if let Some(Attribution::Label(label)) = attribution {
|
||||||
|
realized += SpaceElem::shared().clone();
|
||||||
|
realized += CiteElem::new(*label).pack().spanned(span);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(realized)
|
||||||
|
};
|
||||||
|
|
||||||
|
const FOOTNOTE_RULE: ShowFn<FootnoteElem> = |elem, engine, styles| {
|
||||||
|
let span = elem.span();
|
||||||
|
let loc = elem.declaration_location(engine).at(span)?;
|
||||||
|
let numbering = elem.numbering.get_ref(styles);
|
||||||
|
let counter = Counter::of(FootnoteElem::ELEM);
|
||||||
|
let num = counter.display_at_loc(engine, loc, styles, numbering)?;
|
||||||
|
let sup = SuperElem::new(num).pack().spanned(span);
|
||||||
|
let loc = loc.variant(1);
|
||||||
|
// Add zero-width weak spacing to make the footnote "sticky".
|
||||||
|
Ok(HElem::hole().pack() + sup.linked(Destination::Location(loc)))
|
||||||
|
};
|
||||||
|
|
||||||
|
const FOOTNOTE_ENTRY_RULE: ShowFn<FootnoteEntry> = |elem, engine, styles| {
|
||||||
|
let span = elem.span();
|
||||||
|
let number_gap = Em::new(0.05);
|
||||||
|
let default = StyleChain::default();
|
||||||
|
let numbering = elem.note.numbering.get_ref(default);
|
||||||
|
let counter = Counter::of(FootnoteElem::ELEM);
|
||||||
|
let Some(loc) = elem.note.location() else {
|
||||||
|
bail!(
|
||||||
|
span, "footnote entry must have a location";
|
||||||
|
hint: "try using a query or a show rule to customize the footnote instead"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let num = counter.display_at_loc(engine, loc, styles, numbering)?;
|
||||||
|
let sup = SuperElem::new(num)
|
||||||
|
.pack()
|
||||||
|
.spanned(span)
|
||||||
|
.linked(Destination::Location(loc))
|
||||||
|
.located(loc.variant(1));
|
||||||
|
|
||||||
|
Ok(Content::sequence([
|
||||||
|
HElem::new(elem.indent.get(styles).into()).pack(),
|
||||||
|
sup,
|
||||||
|
HElem::new(number_gap.into()).with_weak(true).pack(),
|
||||||
|
elem.note.body_content().unwrap().clone(),
|
||||||
|
]))
|
||||||
|
};
|
||||||
|
|
||||||
|
const OUTLINE_RULE: ShowFn<OutlineElem> = |elem, engine, styles| {
|
||||||
|
let span = elem.span();
|
||||||
|
|
||||||
|
// Build the outline title.
|
||||||
|
let mut seq = vec![];
|
||||||
|
if let Some(title) = elem.title.get_cloned(styles).unwrap_or_else(|| {
|
||||||
|
Some(TextElem::packed(Packed::<OutlineElem>::local_name_in(styles)).spanned(span))
|
||||||
|
}) {
|
||||||
|
seq.push(
|
||||||
|
HeadingElem::new(title)
|
||||||
|
.with_depth(NonZeroUsize::ONE)
|
||||||
|
.pack()
|
||||||
|
.spanned(span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let elems = engine.introspector.query(&elem.target.get_ref(styles).0);
|
||||||
|
let depth = elem.depth.get(styles).unwrap_or(NonZeroUsize::MAX);
|
||||||
|
|
||||||
|
// Build the outline entries.
|
||||||
|
for elem in elems {
|
||||||
|
let Some(outlinable) = elem.with::<dyn Outlinable>() else {
|
||||||
|
bail!(span, "cannot outline {}", elem.func().name());
|
||||||
|
};
|
||||||
|
|
||||||
|
let level = outlinable.level();
|
||||||
|
if outlinable.outlined() && level <= depth {
|
||||||
|
let entry = OutlineEntry::new(level, elem);
|
||||||
|
seq.push(entry.pack().spanned(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Content::sequence(seq))
|
||||||
|
};
|
||||||
|
|
||||||
|
const OUTLINE_ENTRY_RULE: ShowFn<OutlineEntry> = |elem, engine, styles| {
|
||||||
|
let span = elem.span();
|
||||||
|
let context = Context::new(None, Some(styles));
|
||||||
|
let context = context.track();
|
||||||
|
|
||||||
|
let prefix = elem.prefix(engine, context, span)?;
|
||||||
|
let inner = elem.inner(engine, context, span)?;
|
||||||
|
let block = if elem.element.is::<EquationElem>() {
|
||||||
|
let body = prefix.unwrap_or_default() + inner;
|
||||||
|
BlockElem::new()
|
||||||
|
.with_body(Some(BlockBody::Content(body)))
|
||||||
|
.pack()
|
||||||
|
.spanned(span)
|
||||||
|
} else {
|
||||||
|
elem.indented(engine, context, span, prefix, inner, Em::new(0.5).into())?
|
||||||
|
};
|
||||||
|
|
||||||
|
let loc = elem.element_location().at(span)?;
|
||||||
|
Ok(block.linked(Destination::Location(loc)))
|
||||||
|
};
|
||||||
|
|
||||||
|
const REF_RULE: ShowFn<RefElem> = |elem, engine, styles| elem.realize(engine, styles);
|
||||||
|
|
||||||
|
const CITE_GROUP_RULE: ShowFn<CiteGroup> = |elem, engine, _| elem.realize(engine);
|
||||||
|
|
||||||
|
const BIBLIOGRAPHY_RULE: ShowFn<BibliographyElem> = |elem, engine, styles| {
|
||||||
|
const COLUMN_GUTTER: Em = Em::new(0.65);
|
||||||
|
const INDENT: Em = Em::new(1.5);
|
||||||
|
|
||||||
|
let span = elem.span();
|
||||||
|
|
||||||
|
let mut seq = vec![];
|
||||||
|
if let Some(title) = elem.title.get_ref(styles).clone().unwrap_or_else(|| {
|
||||||
|
Some(
|
||||||
|
TextElem::packed(Packed::<BibliographyElem>::local_name_in(styles))
|
||||||
|
.spanned(span),
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
seq.push(
|
||||||
|
HeadingElem::new(title)
|
||||||
|
.with_depth(NonZeroUsize::ONE)
|
||||||
|
.pack()
|
||||||
|
.spanned(span),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let works = Works::generate(engine).at(span)?;
|
||||||
|
let references = works
|
||||||
|
.references
|
||||||
|
.as_ref()
|
||||||
|
.ok_or_else(|| match elem.style.get_ref(styles).source {
|
||||||
|
CslSource::Named(style) => eco_format!(
|
||||||
|
"CSL style \"{}\" is not suitable for bibliographies",
|
||||||
|
style.display_name()
|
||||||
|
),
|
||||||
|
CslSource::Normal(..) => {
|
||||||
|
"CSL style is not suitable for bibliographies".into()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.at(span)?;
|
||||||
|
|
||||||
|
if references.iter().any(|(prefix, _)| prefix.is_some()) {
|
||||||
|
let row_gutter = styles.get(ParElem::spacing);
|
||||||
|
|
||||||
|
let mut cells = vec![];
|
||||||
|
for (prefix, reference) in references {
|
||||||
|
cells.push(GridChild::Item(GridItem::Cell(
|
||||||
|
Packed::new(GridCell::new(prefix.clone().unwrap_or_default()))
|
||||||
|
.spanned(span),
|
||||||
|
)));
|
||||||
|
cells.push(GridChild::Item(GridItem::Cell(
|
||||||
|
Packed::new(GridCell::new(reference.clone())).spanned(span),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
seq.push(
|
||||||
|
GridElem::new(cells)
|
||||||
|
.with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
|
||||||
|
.with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
|
||||||
|
.with_row_gutter(TrackSizings(smallvec![row_gutter.into()]))
|
||||||
|
.pack()
|
||||||
|
.spanned(span),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
for (_, reference) in references {
|
||||||
|
let realized = reference.clone();
|
||||||
|
let block = if works.hanging_indent {
|
||||||
|
let body = HElem::new((-INDENT).into()).pack() + realized;
|
||||||
|
let inset = Sides::default()
|
||||||
|
.with(styles.resolve(TextElem::dir).start(), Some(INDENT.into()));
|
||||||
|
BlockElem::new()
|
||||||
|
.with_body(Some(BlockBody::Content(body)))
|
||||||
|
.with_inset(inset)
|
||||||
|
} else {
|
||||||
|
BlockElem::new().with_body(Some(BlockBody::Content(realized)))
|
||||||
|
};
|
||||||
|
|
||||||
|
seq.push(block.pack().spanned(span));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Content::sequence(seq))
|
||||||
|
};
|
||||||
|
|
||||||
|
const TABLE_RULE: ShowFn<TableElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::multi_layouter(elem.clone(), crate::grid::layout_table)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const TABLE_CELL_RULE: ShowFn<TableCell> = |elem, _, styles| {
|
||||||
|
show_cell(elem.body.clone(), elem.inset.get(styles), elem.align.get(styles))
|
||||||
|
};
|
||||||
|
|
||||||
|
const SUB_RULE: ShowFn<SubElem> = |elem, _, styles| {
|
||||||
|
show_script(
|
||||||
|
styles,
|
||||||
|
elem.body.clone(),
|
||||||
|
elem.typographic.get(styles),
|
||||||
|
elem.baseline.get(styles),
|
||||||
|
elem.size.get(styles),
|
||||||
|
ScriptKind::Sub,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
const SUPER_RULE: ShowFn<SuperElem> = |elem, _, styles| {
|
||||||
|
show_script(
|
||||||
|
styles,
|
||||||
|
elem.body.clone(),
|
||||||
|
elem.typographic.get(styles),
|
||||||
|
elem.baseline.get(styles),
|
||||||
|
elem.size.get(styles),
|
||||||
|
ScriptKind::Super,
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
fn show_script(
|
||||||
|
styles: StyleChain,
|
||||||
|
body: Content,
|
||||||
|
typographic: bool,
|
||||||
|
baseline: Smart<Length>,
|
||||||
|
size: Smart<TextSize>,
|
||||||
|
kind: ScriptKind,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
|
let font_size = styles.resolve(TextElem::size);
|
||||||
|
Ok(body.set(
|
||||||
|
TextElem::shift_settings,
|
||||||
|
Some(ShiftSettings {
|
||||||
|
typographic,
|
||||||
|
shift: baseline.map(|l| -Em::from_length(l, font_size)),
|
||||||
|
size: size.map(|t| Em::from_length(t.0, font_size)),
|
||||||
|
kind,
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
const UNDERLINE_RULE: ShowFn<UnderlineElem> = |elem, _, styles| {
|
||||||
|
Ok(elem.body.clone().set(
|
||||||
|
TextElem::deco,
|
||||||
|
smallvec![Decoration {
|
||||||
|
line: DecoLine::Underline {
|
||||||
|
stroke: elem.stroke.resolve(styles).unwrap_or_default(),
|
||||||
|
offset: elem.offset.resolve(styles),
|
||||||
|
evade: elem.evade.get(styles),
|
||||||
|
background: elem.background.get(styles),
|
||||||
|
},
|
||||||
|
extent: elem.extent.resolve(styles),
|
||||||
|
}],
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
const OVERLINE_RULE: ShowFn<OverlineElem> = |elem, _, styles| {
|
||||||
|
Ok(elem.body.clone().set(
|
||||||
|
TextElem::deco,
|
||||||
|
smallvec![Decoration {
|
||||||
|
line: DecoLine::Overline {
|
||||||
|
stroke: elem.stroke.resolve(styles).unwrap_or_default(),
|
||||||
|
offset: elem.offset.resolve(styles),
|
||||||
|
evade: elem.evade.get(styles),
|
||||||
|
background: elem.background.get(styles),
|
||||||
|
},
|
||||||
|
extent: elem.extent.resolve(styles),
|
||||||
|
}],
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
const STRIKE_RULE: ShowFn<StrikeElem> = |elem, _, styles| {
|
||||||
|
Ok(elem.body.clone().set(
|
||||||
|
TextElem::deco,
|
||||||
|
smallvec![Decoration {
|
||||||
|
// Note that we do not support evade option for strikethrough.
|
||||||
|
line: DecoLine::Strikethrough {
|
||||||
|
stroke: elem.stroke.resolve(styles).unwrap_or_default(),
|
||||||
|
offset: elem.offset.resolve(styles),
|
||||||
|
background: elem.background.get(styles),
|
||||||
|
},
|
||||||
|
extent: elem.extent.resolve(styles),
|
||||||
|
}],
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
const HIGHLIGHT_RULE: ShowFn<HighlightElem> = |elem, _, styles| {
|
||||||
|
Ok(elem.body.clone().set(
|
||||||
|
TextElem::deco,
|
||||||
|
smallvec![Decoration {
|
||||||
|
line: DecoLine::Highlight {
|
||||||
|
fill: elem.fill.get_cloned(styles),
|
||||||
|
stroke: elem
|
||||||
|
.stroke
|
||||||
|
.resolve(styles)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.map(|stroke| stroke.map(Stroke::unwrap_or_default)),
|
||||||
|
top_edge: elem.top_edge.get(styles),
|
||||||
|
bottom_edge: elem.bottom_edge.get(styles),
|
||||||
|
radius: elem.radius.resolve(styles).unwrap_or_default(),
|
||||||
|
},
|
||||||
|
extent: elem.extent.resolve(styles),
|
||||||
|
}],
|
||||||
|
))
|
||||||
|
};
|
||||||
|
|
||||||
|
const SMALLCAPS_RULE: ShowFn<SmallcapsElem> = |elem, _, styles| {
|
||||||
|
let sc = if elem.all.get(styles) { Smallcaps::All } else { Smallcaps::Minuscules };
|
||||||
|
Ok(elem.body.clone().set(TextElem::smallcaps, Some(sc)))
|
||||||
|
};
|
||||||
|
|
||||||
|
const RAW_RULE: ShowFn<RawElem> = |elem, _, styles| {
|
||||||
|
let lines = elem.lines.as_deref().unwrap_or_default();
|
||||||
|
|
||||||
|
let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1));
|
||||||
|
for (i, line) in lines.iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
seq.push(LinebreakElem::shared().clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
seq.push(line.clone().pack());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut realized = Content::sequence(seq);
|
||||||
|
|
||||||
|
if elem.block.get(styles) {
|
||||||
|
// Align the text before inserting it into the block.
|
||||||
|
realized = realized.aligned(elem.align.get(styles).into());
|
||||||
|
realized = BlockElem::new()
|
||||||
|
.with_body(Some(BlockBody::Content(realized)))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(realized)
|
||||||
|
};
|
||||||
|
|
||||||
|
const RAW_LINE_RULE: ShowFn<RawLine> = |elem, _, _| Ok(elem.body.clone());
|
||||||
|
|
||||||
|
const ALIGN_RULE: ShowFn<AlignElem> =
|
||||||
|
|elem, _, styles| Ok(elem.body.clone().aligned(elem.alignment.get(styles)));
|
||||||
|
|
||||||
|
const PAD_RULE: ShowFn<PadElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::multi_layouter(elem.clone(), crate::pad::layout_pad)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const COLUMNS_RULE: ShowFn<ColumnsElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::multi_layouter(elem.clone(), crate::flow::layout_columns)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const STACK_RULE: ShowFn<StackElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::multi_layouter(elem.clone(), crate::stack::layout_stack)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const GRID_RULE: ShowFn<GridElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::multi_layouter(elem.clone(), crate::grid::layout_grid)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const GRID_CELL_RULE: ShowFn<GridCell> = |elem, _, styles| {
|
||||||
|
show_cell(elem.body.clone(), elem.inset.get(styles), elem.align.get(styles))
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Function with common code to display a grid cell or table cell.
|
||||||
|
fn show_cell(
|
||||||
|
mut body: Content,
|
||||||
|
inset: Smart<Sides<Option<Rel<Length>>>>,
|
||||||
|
align: Smart<Alignment>,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
|
let inset = inset.unwrap_or_default().map(Option::unwrap_or_default);
|
||||||
|
|
||||||
|
if inset != Sides::default() {
|
||||||
|
// Only pad if some inset is not 0pt.
|
||||||
|
// Avoids a bug where using .padded() in any way inside Show causes
|
||||||
|
// alignment in align(...) to break.
|
||||||
|
body = body.padded(inset);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Smart::Custom(alignment) = align {
|
||||||
|
body = body.aligned(alignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(body)
|
||||||
|
}
|
||||||
|
|
||||||
|
const MOVE_RULE: ShowFn<MoveElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_move)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const SCALE_RULE: ShowFn<ScaleElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_scale)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const ROTATE_RULE: ShowFn<RotateElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_rotate)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const SKEW_RULE: ShowFn<SkewElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::transforms::layout_skew)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const REPEAT_RULE: ShowFn<RepeatElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::repeat::layout_repeat)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const HIDE_RULE: ShowFn<HideElem> =
|
||||||
|
|elem, _, _| Ok(elem.body.clone().set(HideElem::hidden, true));
|
||||||
|
|
||||||
|
const LAYOUT_RULE: ShowFn<LayoutElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::multi_layouter(
|
||||||
|
elem.clone(),
|
||||||
|
|elem, engine, locator, styles, regions| {
|
||||||
|
// Gets the current region's base size, which will be the size of the
|
||||||
|
// outer container, or of the page if there is no such container.
|
||||||
|
let Size { x, y } = regions.base();
|
||||||
|
let loc = elem.location().unwrap();
|
||||||
|
let context = Context::new(Some(loc), Some(styles));
|
||||||
|
let result = elem
|
||||||
|
.func
|
||||||
|
.call(engine, context.track(), [dict! { "width" => x, "height" => y }])?
|
||||||
|
.display();
|
||||||
|
crate::flow::layout_fragment(engine, &result, locator, styles, regions)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const IMAGE_RULE: ShowFn<ImageElem> = |elem, _, styles| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::image::layout_image)
|
||||||
|
.with_width(elem.width.get(styles))
|
||||||
|
.with_height(elem.height.get(styles))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const LINE_RULE: ShowFn<LineElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_line)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const RECT_RULE: ShowFn<RectElem> = |elem, _, styles| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_rect)
|
||||||
|
.with_width(elem.width.get(styles))
|
||||||
|
.with_height(elem.height.get(styles))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const SQUARE_RULE: ShowFn<SquareElem> = |elem, _, styles| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_square)
|
||||||
|
.with_width(elem.width.get(styles))
|
||||||
|
.with_height(elem.height.get(styles))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const ELLIPSE_RULE: ShowFn<EllipseElem> = |elem, _, styles| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_ellipse)
|
||||||
|
.with_width(elem.width.get(styles))
|
||||||
|
.with_height(elem.height.get(styles))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const CIRCLE_RULE: ShowFn<CircleElem> = |elem, _, styles| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_circle)
|
||||||
|
.with_width(elem.width.get(styles))
|
||||||
|
.with_height(elem.height.get(styles))
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const POLYGON_RULE: ShowFn<PolygonElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_polygon)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const CURVE_RULE: ShowFn<CurveElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_curve)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const PATH_RULE: ShowFn<PathElem> = |elem, _, _| {
|
||||||
|
Ok(BlockElem::single_layouter(elem.clone(), crate::shapes::layout_path)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
};
|
||||||
|
|
||||||
|
const EQUATION_RULE: ShowFn<EquationElem> = |elem, _, styles| {
|
||||||
|
if elem.block.get(styles) {
|
||||||
|
Ok(BlockElem::multi_layouter(elem.clone(), crate::math::layout_equation_block)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
} else {
|
||||||
|
Ok(InlineElem::layouter(elem.clone(), crate::math::layout_equation_inline)
|
||||||
|
.pack()
|
||||||
|
.spanned(elem.span()))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const EMBED_RULE: ShowFn<EmbedElem> = |_, _, _| Ok(Content::empty());
|
@ -246,12 +246,6 @@ pub trait Synthesize {
|
|||||||
-> SourceResult<()>;
|
-> SourceResult<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines a built-in show rule for an element.
|
|
||||||
pub trait Show {
|
|
||||||
/// Execute the base recipe for this element.
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content>;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Defines built-in show set rules for an element.
|
/// Defines built-in show set rules for an element.
|
||||||
///
|
///
|
||||||
/// This is a bit more powerful than a user-defined show-set because it can
|
/// This is a bit more powerful than a user-defined show-set because it can
|
||||||
|
@ -64,6 +64,16 @@ impl<T: NativeElement> Packed<T> {
|
|||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pack back into a reference to content.
|
||||||
|
pub fn pack_ref(&self) -> &Content {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pack back into a mutable reference to content.
|
||||||
|
pub fn pack_mut(&mut self) -> &mut Content {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
|
||||||
/// Extract the raw underlying element.
|
/// Extract the raw underlying element.
|
||||||
pub fn unpack(self) -> T {
|
pub fn unpack(self) -> T {
|
||||||
// This function doesn't yet need owned self, but might in the future.
|
// This function doesn't yet need owned self, but might in the future.
|
||||||
@ -94,10 +104,6 @@ impl<T: NativeElement> Packed<T> {
|
|||||||
pub fn set_location(&mut self, location: Location) {
|
pub fn set_location(&mut self, location: Location) {
|
||||||
self.0.set_location(location);
|
self.0.set_location(location);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn as_content(&self) -> &Content {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: NativeElement> AsRef<T> for Packed<T> {
|
impl<T: NativeElement> AsRef<T> for Packed<T> {
|
||||||
|
@ -141,7 +141,7 @@ impl RawContent {
|
|||||||
|
|
||||||
/// Clones a packed element into new raw content.
|
/// Clones a packed element into new raw content.
|
||||||
pub(super) fn clone_impl<E: NativeElement>(elem: &Packed<E>) -> Self {
|
pub(super) fn clone_impl<E: NativeElement>(elem: &Packed<E>) -> Self {
|
||||||
let raw = &elem.as_content().0;
|
let raw = &elem.pack_ref().0;
|
||||||
let header = raw.header();
|
let header = raw.header();
|
||||||
RawContent::create(
|
RawContent::create(
|
||||||
elem.as_ref().clone(),
|
elem.as_ref().clone(),
|
||||||
|
@ -3,7 +3,7 @@ use comemo::Track;
|
|||||||
use crate::diag::{bail, Hint, HintedStrResult, SourceResult};
|
use crate::diag::{bail, Hint, HintedStrResult, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
elem, Args, Construct, Content, Func, Packed, Show, StyleChain, Value,
|
elem, Args, Construct, Content, Func, ShowFn, StyleChain, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Locatable, Location};
|
use crate::introspection::{Locatable, Location};
|
||||||
|
|
||||||
@ -61,7 +61,7 @@ fn require<T>(val: Option<T>) -> HintedStrResult<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Executes a `context` block.
|
/// Executes a `context` block.
|
||||||
#[elem(Construct, Locatable, Show)]
|
#[elem(Construct, Locatable)]
|
||||||
pub struct ContextElem {
|
pub struct ContextElem {
|
||||||
/// The function to call with the context.
|
/// The function to call with the context.
|
||||||
#[required]
|
#[required]
|
||||||
@ -75,11 +75,8 @@ impl Construct for ContextElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<ContextElem> {
|
pub const CONTEXT_RULE: ShowFn<ContextElem> = |elem, engine, styles| {
|
||||||
#[typst_macros::time(name = "context", span = self.span())]
|
let loc = elem.location().unwrap();
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
let context = Context::new(Some(loc), Some(styles));
|
||||||
let loc = self.location().unwrap();
|
Ok(elem.func.call::<[Value; 0]>(engine, context.track(), [])?.display())
|
||||||
let context = Context::new(Some(loc), Some(styles));
|
};
|
||||||
Ok(self.func.call::<[Value; 0]>(engine, context.track(), [])?.display())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -179,24 +179,40 @@ impl Str {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the first grapheme cluster of the string.
|
/// Extracts the first grapheme cluster of the string.
|
||||||
/// Fails with an error if the string is empty.
|
///
|
||||||
|
/// Returns the provided default value if the string is empty or fails with
|
||||||
|
/// an error if no default value was specified.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn first(&self) -> StrResult<Str> {
|
pub fn first(
|
||||||
|
&self,
|
||||||
|
/// A default value to return if the string is empty.
|
||||||
|
#[named]
|
||||||
|
default: Option<Str>,
|
||||||
|
) -> StrResult<Str> {
|
||||||
self.0
|
self.0
|
||||||
.graphemes(true)
|
.graphemes(true)
|
||||||
.next()
|
.next()
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
|
.or(default)
|
||||||
.ok_or_else(string_is_empty)
|
.ok_or_else(string_is_empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extracts the last grapheme cluster of the string.
|
/// Extracts the last grapheme cluster of the string.
|
||||||
/// Fails with an error if the string is empty.
|
///
|
||||||
|
/// Returns the provided default value if the string is empty or fails with
|
||||||
|
/// an error if no default value was specified.
|
||||||
#[func]
|
#[func]
|
||||||
pub fn last(&self) -> StrResult<Str> {
|
pub fn last(
|
||||||
|
&self,
|
||||||
|
/// A default value to return if the string is empty.
|
||||||
|
#[named]
|
||||||
|
default: Option<Str>,
|
||||||
|
) -> StrResult<Str> {
|
||||||
self.0
|
self.0
|
||||||
.graphemes(true)
|
.graphemes(true)
|
||||||
.next_back()
|
.next_back()
|
||||||
.map(Into::into)
|
.map(Into::into)
|
||||||
|
.or(default)
|
||||||
.ok_or_else(string_is_empty)
|
.ok_or_else(string_is_empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::any::{Any, TypeId};
|
use std::any::{Any, TypeId};
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::{mem, ptr};
|
use std::{mem, ptr};
|
||||||
@ -13,7 +14,7 @@ use crate::diag::{SourceResult, Trace, Tracepoint};
|
|||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, ty, Content, Context, Element, Field, Func, NativeElement, OneOrMultiple,
|
cast, ty, Content, Context, Element, Field, Func, NativeElement, OneOrMultiple,
|
||||||
RefableProperty, Repr, Selector, SettableProperty,
|
Packed, RefableProperty, Repr, Selector, SettableProperty, Target,
|
||||||
};
|
};
|
||||||
use crate::text::{FontFamily, FontList, TextElem};
|
use crate::text::{FontFamily, FontList, TextElem};
|
||||||
|
|
||||||
@ -938,3 +939,129 @@ fn block_wrong_type(func: Element, id: u8, value: &Block) -> ! {
|
|||||||
value
|
value
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Holds native show rules.
|
||||||
|
pub struct NativeRuleMap {
|
||||||
|
rules: HashMap<(Element, Target), NativeShowRule>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The signature of a native show rule.
|
||||||
|
pub type ShowFn<T> = fn(
|
||||||
|
elem: &Packed<T>,
|
||||||
|
engine: &mut Engine,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content>;
|
||||||
|
|
||||||
|
impl NativeRuleMap {
|
||||||
|
/// Creates a new rule map.
|
||||||
|
///
|
||||||
|
/// Should be populated with rules for all target-element combinations that
|
||||||
|
/// are supported.
|
||||||
|
///
|
||||||
|
/// Contains built-in rules for a few special elements.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
let mut rules = Self { rules: HashMap::new() };
|
||||||
|
|
||||||
|
// ContextElem is as special as SequenceElem and StyledElem and could,
|
||||||
|
// in theory, also be special cased in realization.
|
||||||
|
rules.register_builtin(crate::foundations::CONTEXT_RULE);
|
||||||
|
|
||||||
|
// CounterDisplayElem only exists because the compiler can't currently
|
||||||
|
// express the equivalent of `context counter(..).display(..)` in native
|
||||||
|
// code (no native closures).
|
||||||
|
rules.register_builtin(crate::introspection::COUNTER_DISPLAY_RULE);
|
||||||
|
|
||||||
|
// These are all only for introspection and empty on all targets.
|
||||||
|
rules.register_empty::<crate::introspection::CounterUpdateElem>();
|
||||||
|
rules.register_empty::<crate::introspection::StateUpdateElem>();
|
||||||
|
rules.register_empty::<crate::introspection::MetadataElem>();
|
||||||
|
rules.register_empty::<crate::model::PrefixInfo>();
|
||||||
|
|
||||||
|
rules
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a rule for all targets.
|
||||||
|
fn register_empty<T: NativeElement>(&mut self) {
|
||||||
|
self.register_builtin::<T>(|_, _, _| Ok(Content::empty()));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a rule for all targets.
|
||||||
|
fn register_builtin<T: NativeElement>(&mut self, f: ShowFn<T>) {
|
||||||
|
self.register(Target::Paged, f);
|
||||||
|
self.register(Target::Html, f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Registers a rule for a target.
|
||||||
|
///
|
||||||
|
/// Panics if a rule already exists for this target-element combination.
|
||||||
|
pub fn register<T: NativeElement>(&mut self, target: Target, f: ShowFn<T>) {
|
||||||
|
let res = self.rules.insert((T::ELEM, target), NativeShowRule::new(f));
|
||||||
|
if res.is_some() {
|
||||||
|
panic!(
|
||||||
|
"duplicate native show rule for `{}` on {target:?} target",
|
||||||
|
T::ELEM.name()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Retrieves the rule that applies to the `content` on the current
|
||||||
|
/// `target`.
|
||||||
|
pub fn get(&self, target: Target, content: &Content) -> Option<NativeShowRule> {
|
||||||
|
self.rules.get(&(content.func(), target)).copied()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for NativeRuleMap {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub use rule::NativeShowRule;
|
||||||
|
|
||||||
|
mod rule {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
/// The show rule for a native element.
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub struct NativeShowRule {
|
||||||
|
/// The element to which this rule applies.
|
||||||
|
elem: Element,
|
||||||
|
/// Must only be called with content of the appropriate type.
|
||||||
|
f: unsafe fn(
|
||||||
|
elem: &Content,
|
||||||
|
engine: &mut Engine,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NativeShowRule {
|
||||||
|
/// Create a new type-erased show rule.
|
||||||
|
pub fn new<T: NativeElement>(f: ShowFn<T>) -> Self {
|
||||||
|
Self {
|
||||||
|
elem: T::ELEM,
|
||||||
|
// Safety: The two function pointer types only differ in the
|
||||||
|
// first argument, which changes from `&Packed<T>` to
|
||||||
|
// `&Content`. `Packed<T>` is a transparent wrapper around
|
||||||
|
// `Content`. The resulting function is unsafe to call because
|
||||||
|
// content of the correct type must be passed to it.
|
||||||
|
#[allow(clippy::missing_transmute_annotations)]
|
||||||
|
f: unsafe { std::mem::transmute(f) },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Applies the rule to content. Panics if the content is of the wrong
|
||||||
|
/// type.
|
||||||
|
pub fn apply(
|
||||||
|
&self,
|
||||||
|
content: &Content,
|
||||||
|
engine: &mut Engine,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
|
assert_eq!(content.elem(), self.elem);
|
||||||
|
|
||||||
|
// Safety: We just checked that the element is of the correct type.
|
||||||
|
unsafe { (self.f)(content, engine, styles) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,7 +4,7 @@ use crate::diag::HintedStrResult;
|
|||||||
use crate::foundations::{elem, func, Cast, Context};
|
use crate::foundations::{elem, func, Cast, Context};
|
||||||
|
|
||||||
/// The export target.
|
/// The export target.
|
||||||
#[derive(Debug, Default, Copy, Clone, PartialEq, Hash, Cast)]
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||||
pub enum Target {
|
pub enum Target {
|
||||||
/// The target that is used for paged, fully laid-out content.
|
/// The target that is used for paged, fully laid-out content.
|
||||||
#[default]
|
#[default]
|
||||||
|
@ -1,23 +1,12 @@
|
|||||||
//! HTML output.
|
//! HTML output.
|
||||||
|
|
||||||
mod dom;
|
mod dom;
|
||||||
mod typed;
|
|
||||||
|
|
||||||
pub use self::dom::*;
|
pub use self::dom::*;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
|
|
||||||
use crate::foundations::{elem, Content, Module, Scope};
|
use crate::foundations::{elem, Content};
|
||||||
|
|
||||||
/// Create a module with all HTML definitions.
|
|
||||||
pub fn module() -> Module {
|
|
||||||
let mut html = Scope::deduplicating();
|
|
||||||
html.start_category(crate::Category::Html);
|
|
||||||
html.define_elem::<HtmlElem>();
|
|
||||||
html.define_elem::<FrameElem>();
|
|
||||||
self::typed::define(&mut html);
|
|
||||||
Module::new("html", html)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An HTML element that can contain Typst content.
|
/// An HTML element that can contain Typst content.
|
||||||
///
|
///
|
||||||
|
@ -12,7 +12,7 @@ use crate::engine::{Engine, Route, Sink, Traced};
|
|||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, func, scope, select_where, ty, Args, Array, Construct, Content, Context,
|
cast, elem, func, scope, select_where, ty, Args, Array, Construct, Content, Context,
|
||||||
Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr,
|
Element, Func, IntoValue, Label, LocatableSelector, NativeElement, Packed, Repr,
|
||||||
Selector, Show, Smart, Str, StyleChain, Value,
|
Selector, ShowFn, Smart, Str, StyleChain, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Introspector, Locatable, Location, Tag};
|
use crate::introspection::{Introspector, Locatable, Location, Tag};
|
||||||
use crate::layout::{Frame, FrameItem, PageElem};
|
use crate::layout::{Frame, FrameItem, PageElem};
|
||||||
@ -683,8 +683,8 @@ cast! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Executes an update of a counter.
|
/// Executes an update of a counter.
|
||||||
#[elem(Construct, Locatable, Show, Count)]
|
#[elem(Construct, Locatable, Count)]
|
||||||
struct CounterUpdateElem {
|
pub struct CounterUpdateElem {
|
||||||
/// The key that identifies the counter.
|
/// The key that identifies the counter.
|
||||||
#[required]
|
#[required]
|
||||||
key: CounterKey,
|
key: CounterKey,
|
||||||
@ -701,12 +701,6 @@ impl Construct for CounterUpdateElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<CounterUpdateElem> {
|
|
||||||
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(Content::empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Count for Packed<CounterUpdateElem> {
|
impl Count for Packed<CounterUpdateElem> {
|
||||||
fn update(&self) -> Option<CounterUpdate> {
|
fn update(&self) -> Option<CounterUpdate> {
|
||||||
Some(self.update.clone())
|
Some(self.update.clone())
|
||||||
@ -714,7 +708,7 @@ impl Count for Packed<CounterUpdateElem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Executes a display of a counter.
|
/// Executes a display of a counter.
|
||||||
#[elem(Construct, Locatable, Show)]
|
#[elem(Construct, Locatable)]
|
||||||
pub struct CounterDisplayElem {
|
pub struct CounterDisplayElem {
|
||||||
/// The counter.
|
/// The counter.
|
||||||
#[required]
|
#[required]
|
||||||
@ -738,20 +732,18 @@ impl Construct for CounterDisplayElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<CounterDisplayElem> {
|
pub const COUNTER_DISPLAY_RULE: ShowFn<CounterDisplayElem> = |elem, engine, styles| {
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
Ok(elem
|
||||||
Ok(self
|
.counter
|
||||||
.counter
|
.display_impl(
|
||||||
.display_impl(
|
engine,
|
||||||
engine,
|
elem.location().unwrap(),
|
||||||
self.location().unwrap(),
|
elem.numbering.clone(),
|
||||||
self.numbering.clone(),
|
elem.both,
|
||||||
self.both,
|
Some(styles),
|
||||||
Some(styles),
|
)?
|
||||||
)?
|
.display())
|
||||||
.display())
|
};
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An specialized handler of the page counter that tracks both the physical
|
/// An specialized handler of the page counter that tracks both the physical
|
||||||
/// and the logical page counter.
|
/// and the logical page counter.
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, Value};
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{elem, Content, Packed, Show, StyleChain, Value};
|
|
||||||
use crate::introspection::Locatable;
|
use crate::introspection::Locatable;
|
||||||
|
|
||||||
/// Exposes a value to the query system without producing visible content.
|
/// Exposes a value to the query system without producing visible content.
|
||||||
@ -24,15 +22,9 @@ use crate::introspection::Locatable;
|
|||||||
/// query(<note>).first().value
|
/// query(<note>).first().value
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show, Locatable)]
|
#[elem(Locatable)]
|
||||||
pub struct MetadataElem {
|
pub struct MetadataElem {
|
||||||
/// The value to embed into the document.
|
/// The value to embed into the document.
|
||||||
#[required]
|
#[required]
|
||||||
pub value: Value,
|
pub value: Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<MetadataElem> {
|
|
||||||
fn show(&self, _: &mut Engine, _styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(Content::empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -6,8 +6,7 @@ use crate::diag::{bail, At, SourceResult};
|
|||||||
use crate::engine::{Engine, Route, Sink, Traced};
|
use crate::engine::{Engine, Route, Sink, Traced};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, func, scope, select_where, ty, Args, Construct, Content, Context, Func,
|
cast, elem, func, scope, select_where, ty, Args, Construct, Content, Context, Func,
|
||||||
LocatableSelector, NativeElement, Packed, Repr, Selector, Show, Str, StyleChain,
|
LocatableSelector, NativeElement, Repr, Selector, Str, Value,
|
||||||
Value,
|
|
||||||
};
|
};
|
||||||
use crate::introspection::{Introspector, Locatable, Location};
|
use crate::introspection::{Introspector, Locatable, Location};
|
||||||
use crate::routines::Routines;
|
use crate::routines::Routines;
|
||||||
@ -372,8 +371,8 @@ cast! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Executes a display of a state.
|
/// Executes a display of a state.
|
||||||
#[elem(Construct, Locatable, Show)]
|
#[elem(Construct, Locatable)]
|
||||||
struct StateUpdateElem {
|
pub struct StateUpdateElem {
|
||||||
/// The key that identifies the state.
|
/// The key that identifies the state.
|
||||||
#[required]
|
#[required]
|
||||||
key: Str,
|
key: Str,
|
||||||
@ -389,9 +388,3 @@ impl Construct for StateUpdateElem {
|
|||||||
bail!(args.span, "cannot be constructed manually");
|
bail!(args.span, "cannot be constructed manually");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<StateUpdateElem> {
|
|
||||||
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(Content::empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -2,11 +2,10 @@ use std::ops::Add;
|
|||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
|
|
||||||
use crate::diag::{bail, HintedStrResult, SourceResult, StrResult};
|
use crate::diag::{bail, HintedStrResult, StrResult};
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, func, scope, ty, CastInfo, Content, Fold, FromValue, IntoValue, Packed,
|
cast, elem, func, scope, ty, CastInfo, Content, Fold, FromValue, IntoValue, Reflect,
|
||||||
Reflect, Repr, Resolve, Show, StyleChain, Value,
|
Repr, Resolve, StyleChain, Value,
|
||||||
};
|
};
|
||||||
use crate::layout::{Abs, Axes, Axis, Dir, Side};
|
use crate::layout::{Abs, Axes, Axis, Dir, Side};
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
@ -73,7 +72,7 @@ use crate::text::TextElem;
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// Start #h(1fr) End
|
/// Start #h(1fr) End
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct AlignElem {
|
pub struct AlignElem {
|
||||||
/// The [alignment] along both axes.
|
/// The [alignment] along both axes.
|
||||||
///
|
///
|
||||||
@ -97,13 +96,6 @@ pub struct AlignElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<AlignElem> {
|
|
||||||
#[typst_macros::time(name = "align", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(self.body.clone().aligned(self.alignment.get(styles)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Where to align something along an axis.
|
/// Where to align something along an axis.
|
||||||
///
|
///
|
||||||
/// Possible values are:
|
/// Possible values are:
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, Content};
|
||||||
use crate::engine::Engine;
|
use crate::layout::{Length, Ratio, Rel};
|
||||||
use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
|
|
||||||
use crate::layout::{BlockElem, Length, Ratio, Rel};
|
|
||||||
|
|
||||||
/// Separates a region into multiple equally sized columns.
|
/// Separates a region into multiple equally sized columns.
|
||||||
///
|
///
|
||||||
@ -41,7 +39,7 @@ use crate::layout::{BlockElem, Length, Ratio, Rel};
|
|||||||
///
|
///
|
||||||
/// #lorem(40)
|
/// #lorem(40)
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct ColumnsElem {
|
pub struct ColumnsElem {
|
||||||
/// The number of columns.
|
/// The number of columns.
|
||||||
#[positional]
|
#[positional]
|
||||||
@ -57,14 +55,6 @@ pub struct ColumnsElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<ColumnsElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_columns)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Forces a column break.
|
/// Forces a column break.
|
||||||
///
|
///
|
||||||
/// The function will behave like a [page break]($pagebreak) when used in a
|
/// The function will behave like a [page break]($pagebreak) when used in a
|
||||||
|
@ -11,10 +11,10 @@ use crate::diag::{bail, At, HintedStrResult, HintedString, SourceResult};
|
|||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, scope, Array, CastInfo, Content, Context, Fold, FromValue, Func,
|
cast, elem, scope, Array, CastInfo, Content, Context, Fold, FromValue, Func,
|
||||||
IntoValue, NativeElement, Packed, Reflect, Resolve, Show, Smart, StyleChain, Value,
|
IntoValue, Packed, Reflect, Resolve, Smart, StyleChain, Value,
|
||||||
};
|
};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Alignment, BlockElem, Length, OuterHAlignment, OuterVAlignment, Rel, Sides, Sizing,
|
Alignment, Length, OuterHAlignment, OuterVAlignment, Rel, Sides, Sizing,
|
||||||
};
|
};
|
||||||
use crate::model::{TableCell, TableFooter, TableHLine, TableHeader, TableVLine};
|
use crate::model::{TableCell, TableFooter, TableHLine, TableHeader, TableVLine};
|
||||||
use crate::visualize::{Paint, Stroke};
|
use crate::visualize::{Paint, Stroke};
|
||||||
@ -136,7 +136,7 @@ use crate::visualize::{Paint, Stroke};
|
|||||||
///
|
///
|
||||||
/// Furthermore, strokes of a repeated grid header or footer will take
|
/// Furthermore, strokes of a repeated grid header or footer will take
|
||||||
/// precedence over regular cell strokes.
|
/// precedence over regular cell strokes.
|
||||||
#[elem(scope, Show)]
|
#[elem(scope)]
|
||||||
pub struct GridElem {
|
pub struct GridElem {
|
||||||
/// The column sizes.
|
/// The column sizes.
|
||||||
///
|
///
|
||||||
@ -320,14 +320,6 @@ impl GridElem {
|
|||||||
type GridFooter;
|
type GridFooter;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<GridElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_grid)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Track sizing definitions.
|
/// Track sizing definitions.
|
||||||
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct TrackSizings(pub SmallVec<[Sizing; 4]>);
|
pub struct TrackSizings(pub SmallVec<[Sizing; 4]>);
|
||||||
@ -648,7 +640,7 @@ pub struct GridVLine {
|
|||||||
/// which allows you, for example, to apply styles based on a cell's position.
|
/// which allows you, for example, to apply styles based on a cell's position.
|
||||||
/// Refer to the examples of the [`table.cell`]($table.cell) element to learn
|
/// Refer to the examples of the [`table.cell`]($table.cell) element to learn
|
||||||
/// more about this.
|
/// more about this.
|
||||||
#[elem(name = "cell", title = "Grid Cell", Show)]
|
#[elem(name = "cell", title = "Grid Cell")]
|
||||||
pub struct GridCell {
|
pub struct GridCell {
|
||||||
/// The cell's body.
|
/// The cell's body.
|
||||||
#[required]
|
#[required]
|
||||||
@ -748,12 +740,6 @@ cast! {
|
|||||||
v: Content => v.into(),
|
v: Content => v.into(),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<GridCell> {
|
|
||||||
fn show(&self, _engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
show_grid_cell(self.body.clone(), self.inset.get(styles), self.align.get(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Packed<GridCell> {
|
impl Default for Packed<GridCell> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Packed::new(
|
Packed::new(
|
||||||
@ -774,28 +760,6 @@ impl From<Content> for GridCell {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Function with common code to display a grid cell or table cell.
|
|
||||||
pub(crate) fn show_grid_cell(
|
|
||||||
mut body: Content,
|
|
||||||
inset: Smart<Sides<Option<Rel<Length>>>>,
|
|
||||||
align: Smart<Alignment>,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
let inset = inset.unwrap_or_default().map(Option::unwrap_or_default);
|
|
||||||
|
|
||||||
if inset != Sides::default() {
|
|
||||||
// Only pad if some inset is not 0pt.
|
|
||||||
// Avoids a bug where using .padded() in any way inside Show causes
|
|
||||||
// alignment in align(...) to break.
|
|
||||||
body = body.padded(inset);
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Smart::Custom(alignment) = align {
|
|
||||||
body = body.aligned(alignment);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(body)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A value that can be configured per cell.
|
/// A value that can be configured per cell.
|
||||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||||
pub enum Celled<T> {
|
pub enum Celled<T> {
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, Content};
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{elem, Content, Packed, Show, StyleChain};
|
|
||||||
|
|
||||||
/// Hides content without affecting layout.
|
/// Hides content without affecting layout.
|
||||||
///
|
///
|
||||||
@ -14,7 +12,7 @@ use crate::foundations::{elem, Content, Packed, Show, StyleChain};
|
|||||||
/// Hello Jane \
|
/// Hello Jane \
|
||||||
/// #hide[Hello] Joe
|
/// #hide[Hello] Joe
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct HideElem {
|
pub struct HideElem {
|
||||||
/// The content to hide.
|
/// The content to hide.
|
||||||
#[required]
|
#[required]
|
||||||
@ -25,10 +23,3 @@ pub struct HideElem {
|
|||||||
#[ghost]
|
#[ghost]
|
||||||
pub hidden: bool,
|
pub hidden: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<HideElem> {
|
|
||||||
#[typst_macros::time(name = "hide", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(self.body.clone().set(HideElem::hidden, true))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,13 +1,7 @@
|
|||||||
use comemo::Track;
|
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
|
|
||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, func, Content, Func, NativeElement};
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
|
||||||
dict, elem, func, Content, Context, Func, NativeElement, Packed, Show, StyleChain,
|
|
||||||
};
|
|
||||||
use crate::introspection::Locatable;
|
use crate::introspection::Locatable;
|
||||||
use crate::layout::{BlockElem, Size};
|
|
||||||
|
|
||||||
/// Provides access to the current outer container's (or page's, if none)
|
/// Provides access to the current outer container's (or page's, if none)
|
||||||
/// dimensions (width and height).
|
/// dimensions (width and height).
|
||||||
@ -86,37 +80,9 @@ pub fn layout(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Executes a `layout` call.
|
/// Executes a `layout` call.
|
||||||
#[elem(Locatable, Show)]
|
#[elem(Locatable)]
|
||||||
struct LayoutElem {
|
pub struct LayoutElem {
|
||||||
/// The function to call with the outer container's (or page's) size.
|
/// The function to call with the outer container's (or page's) size.
|
||||||
#[required]
|
#[required]
|
||||||
func: Func,
|
pub func: Func,
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for Packed<LayoutElem> {
|
|
||||||
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::multi_layouter(
|
|
||||||
self.clone(),
|
|
||||||
|elem, engine, locator, styles, regions| {
|
|
||||||
// Gets the current region's base size, which will be the size of the
|
|
||||||
// outer container, or of the page if there is no such container.
|
|
||||||
let Size { x, y } = regions.base();
|
|
||||||
let loc = elem.location().unwrap();
|
|
||||||
let context = Context::new(Some(loc), Some(styles));
|
|
||||||
let result = elem
|
|
||||||
.func
|
|
||||||
.call(
|
|
||||||
engine,
|
|
||||||
context.track(),
|
|
||||||
[dict! { "width" => x, "height" => y }],
|
|
||||||
)?
|
|
||||||
.display();
|
|
||||||
(engine.routines.layout_fragment)(
|
|
||||||
engine, &result, locator, styles, regions,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, Content};
|
||||||
use crate::engine::Engine;
|
use crate::layout::{Length, Rel};
|
||||||
use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
|
|
||||||
use crate::layout::{BlockElem, Length, Rel};
|
|
||||||
|
|
||||||
/// Adds spacing around content.
|
/// Adds spacing around content.
|
||||||
///
|
///
|
||||||
@ -16,7 +14,7 @@ use crate::layout::{BlockElem, Length, Rel};
|
|||||||
/// _Typing speeds can be
|
/// _Typing speeds can be
|
||||||
/// measured in words per minute._
|
/// measured in words per minute._
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(title = "Padding", Show)]
|
#[elem(title = "Padding")]
|
||||||
pub struct PadElem {
|
pub struct PadElem {
|
||||||
/// The padding at the left side.
|
/// The padding at the left side.
|
||||||
#[parse(
|
#[parse(
|
||||||
@ -55,11 +53,3 @@ pub struct PadElem {
|
|||||||
#[required]
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<PadElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_pad)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, Content};
|
||||||
use crate::engine::Engine;
|
use crate::layout::Length;
|
||||||
use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
|
|
||||||
use crate::layout::{BlockElem, Length};
|
|
||||||
|
|
||||||
/// Repeats content to the available space.
|
/// Repeats content to the available space.
|
||||||
///
|
///
|
||||||
@ -24,7 +22,7 @@ use crate::layout::{BlockElem, Length};
|
|||||||
/// Berlin, the 22nd of December, 2022
|
/// Berlin, the 22nd of December, 2022
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct RepeatElem {
|
pub struct RepeatElem {
|
||||||
/// The content to repeat.
|
/// The content to repeat.
|
||||||
#[required]
|
#[required]
|
||||||
@ -39,11 +37,3 @@ pub struct RepeatElem {
|
|||||||
#[default(true)]
|
#[default(true)]
|
||||||
pub justify: bool,
|
pub justify: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<RepeatElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_repeat)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use crate::diag::SourceResult;
|
use crate::foundations::{cast, elem, Content};
|
||||||
use crate::engine::Engine;
|
use crate::layout::{Dir, Spacing};
|
||||||
use crate::foundations::{cast, elem, Content, NativeElement, Packed, Show, StyleChain};
|
|
||||||
use crate::layout::{BlockElem, Dir, Spacing};
|
|
||||||
|
|
||||||
/// Arranges content and spacing horizontally or vertically.
|
/// Arranges content and spacing horizontally or vertically.
|
||||||
///
|
///
|
||||||
@ -19,7 +17,7 @@ use crate::layout::{BlockElem, Dir, Spacing};
|
|||||||
/// rect(width: 90pt),
|
/// rect(width: 90pt),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct StackElem {
|
pub struct StackElem {
|
||||||
/// The direction along which the items are stacked. Possible values are:
|
/// The direction along which the items are stacked. Possible values are:
|
||||||
///
|
///
|
||||||
@ -47,14 +45,6 @@ pub struct StackElem {
|
|||||||
pub children: Vec<StackChild>,
|
pub children: Vec<StackChild>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<StackElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::multi_layouter(self.clone(), engine.routines.layout_stack)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A child of a stack element.
|
/// A child of a stack element.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub enum StackChild {
|
pub enum StackChild {
|
||||||
|
@ -1,11 +1,5 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::foundations::{cast, elem, Content, Smart};
|
||||||
use crate::engine::Engine;
|
use crate::layout::{Abs, Alignment, Angle, HAlignment, Length, Ratio, Rel, VAlignment};
|
||||||
use crate::foundations::{
|
|
||||||
cast, elem, Content, NativeElement, Packed, Show, Smart, StyleChain,
|
|
||||||
};
|
|
||||||
use crate::layout::{
|
|
||||||
Abs, Alignment, Angle, BlockElem, HAlignment, Length, Ratio, Rel, VAlignment,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Moves content without affecting layout.
|
/// Moves content without affecting layout.
|
||||||
///
|
///
|
||||||
@ -25,7 +19,7 @@ use crate::layout::{
|
|||||||
/// )
|
/// )
|
||||||
/// ))
|
/// ))
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct MoveElem {
|
pub struct MoveElem {
|
||||||
/// The horizontal displacement of the content.
|
/// The horizontal displacement of the content.
|
||||||
pub dx: Rel<Length>,
|
pub dx: Rel<Length>,
|
||||||
@ -38,14 +32,6 @@ pub struct MoveElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<MoveElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_move)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Rotates content without affecting layout.
|
/// Rotates content without affecting layout.
|
||||||
///
|
///
|
||||||
/// Rotates an element by a given angle. The layout will act as if the element
|
/// Rotates an element by a given angle. The layout will act as if the element
|
||||||
@ -60,7 +46,7 @@ impl Show for Packed<MoveElem> {
|
|||||||
/// .map(i => rotate(24deg * i)[X]),
|
/// .map(i => rotate(24deg * i)[X]),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct RotateElem {
|
pub struct RotateElem {
|
||||||
/// The amount of rotation.
|
/// The amount of rotation.
|
||||||
///
|
///
|
||||||
@ -107,14 +93,6 @@ pub struct RotateElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<RotateElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_rotate)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Scales content without affecting layout.
|
/// Scales content without affecting layout.
|
||||||
///
|
///
|
||||||
/// Lets you mirror content by specifying a negative scale on a single axis.
|
/// Lets you mirror content by specifying a negative scale on a single axis.
|
||||||
@ -125,7 +103,7 @@ impl Show for Packed<RotateElem> {
|
|||||||
/// #scale(x: -100%)[This is mirrored.]
|
/// #scale(x: -100%)[This is mirrored.]
|
||||||
/// #scale(x: -100%, reflow: true)[This is mirrored.]
|
/// #scale(x: -100%, reflow: true)[This is mirrored.]
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct ScaleElem {
|
pub struct ScaleElem {
|
||||||
/// The scaling factor for both axes, as a positional argument. This is just
|
/// The scaling factor for both axes, as a positional argument. This is just
|
||||||
/// an optional shorthand notation for setting `x` and `y` to the same
|
/// an optional shorthand notation for setting `x` and `y` to the same
|
||||||
@ -179,14 +157,6 @@ pub struct ScaleElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<ScaleElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_scale)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// To what size something shall be scaled.
|
/// To what size something shall be scaled.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum ScaleAmount {
|
pub enum ScaleAmount {
|
||||||
@ -215,7 +185,7 @@ cast! {
|
|||||||
/// This is some fake italic text.
|
/// This is some fake italic text.
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct SkewElem {
|
pub struct SkewElem {
|
||||||
/// The horizontal skewing angle.
|
/// The horizontal skewing angle.
|
||||||
///
|
///
|
||||||
@ -265,14 +235,6 @@ pub struct SkewElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<SkewElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_skew)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A scale-skew-translate transformation.
|
/// A scale-skew-translate transformation.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Transform {
|
pub struct Transform {
|
||||||
|
@ -36,6 +36,7 @@ use typst_utils::{LazyHash, SmallBitSet};
|
|||||||
use crate::diag::FileResult;
|
use crate::diag::FileResult;
|
||||||
use crate::foundations::{Array, Binding, Bytes, Datetime, Dict, Module, Scope, Styles};
|
use crate::foundations::{Array, Binding, Bytes, Datetime, Dict, Module, Scope, Styles};
|
||||||
use crate::layout::{Alignment, Dir};
|
use crate::layout::{Alignment, Dir};
|
||||||
|
use crate::routines::Routines;
|
||||||
use crate::text::{Font, FontBook};
|
use crate::text::{Font, FontBook};
|
||||||
use crate::visualize::Color;
|
use crate::visualize::Color;
|
||||||
|
|
||||||
@ -139,6 +140,11 @@ impl<T: World + ?Sized> WorldExt for T {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Definition of Typst's standard library.
|
/// Definition of Typst's standard library.
|
||||||
|
///
|
||||||
|
/// To create and configure the standard library, use the `LibraryExt` trait
|
||||||
|
/// and call
|
||||||
|
/// - `Library::default()` for a standard configuration
|
||||||
|
/// - `Library::builder().build()` if you want to customize the library
|
||||||
#[derive(Debug, Clone, Hash)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
pub struct Library {
|
pub struct Library {
|
||||||
/// The module that contains the definitions that are available everywhere.
|
/// The module that contains the definitions that are available everywhere.
|
||||||
@ -154,30 +160,27 @@ pub struct Library {
|
|||||||
pub features: Features,
|
pub features: Features,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Library {
|
|
||||||
/// Create a new builder for a library.
|
|
||||||
pub fn builder() -> LibraryBuilder {
|
|
||||||
LibraryBuilder::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Library {
|
|
||||||
/// Constructs the standard library with the default configuration.
|
|
||||||
fn default() -> Self {
|
|
||||||
Self::builder().build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configurable builder for the standard library.
|
/// Configurable builder for the standard library.
|
||||||
///
|
///
|
||||||
/// This struct is created by [`Library::builder`].
|
/// Constructed via the `LibraryExt` trait.
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct LibraryBuilder {
|
pub struct LibraryBuilder {
|
||||||
|
routines: &'static Routines,
|
||||||
inputs: Option<Dict>,
|
inputs: Option<Dict>,
|
||||||
features: Features,
|
features: Features,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LibraryBuilder {
|
impl LibraryBuilder {
|
||||||
|
/// Creates a new builder.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn from_routines(routines: &'static Routines) -> Self {
|
||||||
|
Self {
|
||||||
|
routines,
|
||||||
|
inputs: None,
|
||||||
|
features: Features::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Configure the inputs visible through `sys.inputs`.
|
/// Configure the inputs visible through `sys.inputs`.
|
||||||
pub fn with_inputs(mut self, inputs: Dict) -> Self {
|
pub fn with_inputs(mut self, inputs: Dict) -> Self {
|
||||||
self.inputs = Some(inputs);
|
self.inputs = Some(inputs);
|
||||||
@ -196,7 +199,7 @@ impl LibraryBuilder {
|
|||||||
pub fn build(self) -> Library {
|
pub fn build(self) -> Library {
|
||||||
let math = math::module();
|
let math = math::module();
|
||||||
let inputs = self.inputs.unwrap_or_default();
|
let inputs = self.inputs.unwrap_or_default();
|
||||||
let global = global(math.clone(), inputs, &self.features);
|
let global = global(self.routines, math.clone(), inputs, &self.features);
|
||||||
Library {
|
Library {
|
||||||
global: global.clone(),
|
global: global.clone(),
|
||||||
math,
|
math,
|
||||||
@ -278,7 +281,12 @@ impl Category {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Construct the module with global definitions.
|
/// Construct the module with global definitions.
|
||||||
fn global(math: Module, inputs: Dict, features: &Features) -> Module {
|
fn global(
|
||||||
|
routines: &Routines,
|
||||||
|
math: Module,
|
||||||
|
inputs: Dict,
|
||||||
|
features: &Features,
|
||||||
|
) -> Module {
|
||||||
let mut global = Scope::deduplicating();
|
let mut global = Scope::deduplicating();
|
||||||
|
|
||||||
self::foundations::define(&mut global, inputs, features);
|
self::foundations::define(&mut global, inputs, features);
|
||||||
@ -293,7 +301,7 @@ fn global(math: Module, inputs: Dict, features: &Features) -> Module {
|
|||||||
global.define("math", math);
|
global.define("math", math);
|
||||||
global.define("pdf", self::pdf::module());
|
global.define("pdf", self::pdf::module());
|
||||||
if features.is_enabled(Feature::Html) {
|
if features.is_enabled(Feature::Html) {
|
||||||
global.define("html", self::html::module());
|
global.define("html", (routines.html_module)());
|
||||||
}
|
}
|
||||||
|
|
||||||
prelude(&mut global);
|
prelude(&mut global);
|
||||||
|
@ -1,20 +1,19 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
|
use codex::styling::MathVariant;
|
||||||
use typst_utils::NonZeroExt;
|
use typst_utils::NonZeroExt;
|
||||||
use unicode_math_class::MathClass;
|
use unicode_math_class::MathClass;
|
||||||
|
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
elem, Content, NativeElement, Packed, Show, ShowSet, Smart, StyleChain, Styles,
|
elem, Content, NativeElement, Packed, ShowSet, Smart, StyleChain, Styles, Synthesize,
|
||||||
Synthesize,
|
|
||||||
};
|
};
|
||||||
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
|
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
AlignElem, Alignment, BlockElem, InlineElem, OuterHAlignment, SpecificAlignment,
|
AlignElem, Alignment, BlockElem, OuterHAlignment, SpecificAlignment, VAlignment,
|
||||||
VAlignment,
|
|
||||||
};
|
};
|
||||||
use crate::math::{MathSize, MathVariant};
|
use crate::math::MathSize;
|
||||||
use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement};
|
use crate::model::{Numbering, Outlinable, ParLine, Refable, Supplement};
|
||||||
use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem};
|
use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem};
|
||||||
|
|
||||||
@ -46,7 +45,7 @@ use crate::text::{FontFamily, FontList, FontWeight, LocalName, TextElem};
|
|||||||
/// least one space lifts it into a separate block that is centered
|
/// least one space lifts it into a separate block that is centered
|
||||||
/// horizontally. For more details about math syntax, see the
|
/// horizontally. For more details about math syntax, see the
|
||||||
/// [main math page]($category/math).
|
/// [main math page]($category/math).
|
||||||
#[elem(Locatable, Synthesize, Show, ShowSet, Count, LocalName, Refable, Outlinable)]
|
#[elem(Locatable, Synthesize, ShowSet, Count, LocalName, Refable, Outlinable)]
|
||||||
pub struct EquationElem {
|
pub struct EquationElem {
|
||||||
/// Whether the equation is displayed as a separate block.
|
/// Whether the equation is displayed as a separate block.
|
||||||
#[default(false)]
|
#[default(false)]
|
||||||
@ -113,7 +112,7 @@ pub struct EquationElem {
|
|||||||
/// The style variant to select.
|
/// The style variant to select.
|
||||||
#[internal]
|
#[internal]
|
||||||
#[ghost]
|
#[ghost]
|
||||||
pub variant: MathVariant,
|
pub variant: Option<MathVariant>,
|
||||||
|
|
||||||
/// Affects the height of exponents.
|
/// Affects the height of exponents.
|
||||||
#[internal]
|
#[internal]
|
||||||
@ -130,7 +129,7 @@ pub struct EquationElem {
|
|||||||
/// Whether to use italic glyphs.
|
/// Whether to use italic glyphs.
|
||||||
#[internal]
|
#[internal]
|
||||||
#[ghost]
|
#[ghost]
|
||||||
pub italic: Smart<bool>,
|
pub italic: Option<bool>,
|
||||||
|
|
||||||
/// A forced class to use for all fragment.
|
/// A forced class to use for all fragment.
|
||||||
#[internal]
|
#[internal]
|
||||||
@ -165,23 +164,6 @@ impl Synthesize for Packed<EquationElem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<EquationElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
if self.block.get(styles) {
|
|
||||||
Ok(BlockElem::multi_layouter(
|
|
||||||
self.clone(),
|
|
||||||
engine.routines.layout_equation_block,
|
|
||||||
)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
} else {
|
|
||||||
Ok(InlineElem::layouter(self.clone(), engine.routines.layout_equation_inline)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShowSet for Packed<EquationElem> {
|
impl ShowSet for Packed<EquationElem> {
|
||||||
fn show_set(&self, styles: StyleChain) -> Styles {
|
fn show_set(&self, styles: StyleChain) -> Styles {
|
||||||
let mut out = Styles::new();
|
let mut out = Styles::new();
|
||||||
|
@ -80,6 +80,7 @@ pub fn module() -> Module {
|
|||||||
math.define_func::<italic>();
|
math.define_func::<italic>();
|
||||||
math.define_func::<serif>();
|
math.define_func::<serif>();
|
||||||
math.define_func::<sans>();
|
math.define_func::<sans>();
|
||||||
|
math.define_func::<scr>();
|
||||||
math.define_func::<cal>();
|
math.define_func::<cal>();
|
||||||
math.define_func::<frak>();
|
math.define_func::<frak>();
|
||||||
math.define_func::<mono>();
|
math.define_func::<mono>();
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use crate::foundations::{func, Cast, Content, Smart};
|
use codex::styling::MathVariant;
|
||||||
|
|
||||||
|
use crate::foundations::{func, Cast, Content};
|
||||||
use crate::math::EquationElem;
|
use crate::math::EquationElem;
|
||||||
|
|
||||||
/// Bold font style in math.
|
/// Bold font style in math.
|
||||||
@ -24,7 +26,7 @@ pub fn upright(
|
|||||||
/// The content to style.
|
/// The content to style.
|
||||||
body: Content,
|
body: Content,
|
||||||
) -> Content {
|
) -> Content {
|
||||||
body.set(EquationElem::italic, Smart::Custom(false))
|
body.set(EquationElem::italic, Some(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Italic font style in math.
|
/// Italic font style in math.
|
||||||
@ -35,7 +37,7 @@ pub fn italic(
|
|||||||
/// The content to style.
|
/// The content to style.
|
||||||
body: Content,
|
body: Content,
|
||||||
) -> Content {
|
) -> Content {
|
||||||
body.set(EquationElem::italic, Smart::Custom(true))
|
body.set(EquationElem::italic, Some(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Serif (roman) font style in math.
|
/// Serif (roman) font style in math.
|
||||||
@ -46,7 +48,7 @@ pub fn serif(
|
|||||||
/// The content to style.
|
/// The content to style.
|
||||||
body: Content,
|
body: Content,
|
||||||
) -> Content {
|
) -> Content {
|
||||||
body.set(EquationElem::variant, MathVariant::Serif)
|
body.set(EquationElem::variant, Some(MathVariant::Plain))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sans-serif font style in math.
|
/// Sans-serif font style in math.
|
||||||
@ -59,23 +61,39 @@ pub fn sans(
|
|||||||
/// The content to style.
|
/// The content to style.
|
||||||
body: Content,
|
body: Content,
|
||||||
) -> Content {
|
) -> Content {
|
||||||
body.set(EquationElem::variant, MathVariant::Sans)
|
body.set(EquationElem::variant, Some(MathVariant::SansSerif))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Calligraphic font style in math.
|
/// Calligraphic (chancery) font style in math.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// Let $cal(P)$ be the set of ...
|
/// Let $cal(P)$ be the set of ...
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// This corresponds both to LaTeX's `\mathcal` and `\mathscr` as both of these
|
/// This is the default calligraphic/script style for most math fonts. See
|
||||||
/// styles share the same Unicode codepoints. Switching between the styles is
|
/// [`scr`]($math.scr) for more on how to get the other style (roundhand).
|
||||||
/// thus only possible if supported by the font via
|
#[func(title = "Calligraphic", keywords = ["mathcal", "chancery"])]
|
||||||
/// [font features]($text.features).
|
pub fn cal(
|
||||||
|
/// The content to style.
|
||||||
|
body: Content,
|
||||||
|
) -> Content {
|
||||||
|
body.set(EquationElem::variant, Some(MathVariant::Chancery))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Script (roundhand) font style in math.
|
||||||
///
|
///
|
||||||
/// For the default math font, the roundhand style is available through the
|
/// ```example
|
||||||
/// `ss01` feature. Therefore, you could define your own version of `\mathscr`
|
/// $ scr(S) $
|
||||||
/// like this:
|
/// ```
|
||||||
|
///
|
||||||
|
/// There are two ways that fonts can support differentiating `cal` and `scr`.
|
||||||
|
/// The first is using Unicode variation sequences. This works out of the box
|
||||||
|
/// in Typst, however only a few math fonts currently support this.
|
||||||
|
///
|
||||||
|
/// The other way is using [font features]($text.features). For example, the
|
||||||
|
/// roundhand style might be available in a font through the `ss01` feature.
|
||||||
|
/// To use it in Typst, you could then define your own version of `scr` like
|
||||||
|
/// this:
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #let scr(it) = text(
|
/// #let scr(it) = text(
|
||||||
@ -88,12 +106,12 @@ pub fn sans(
|
|||||||
///
|
///
|
||||||
/// (The box is not conceptually necessary, but unfortunately currently needed
|
/// (The box is not conceptually necessary, but unfortunately currently needed
|
||||||
/// due to limitations in Typst's text style handling in math.)
|
/// due to limitations in Typst's text style handling in math.)
|
||||||
#[func(title = "Calligraphic", keywords = ["mathcal", "mathscr"])]
|
#[func(title = "Script Style", keywords = ["mathscr", "roundhand"])]
|
||||||
pub fn cal(
|
pub fn scr(
|
||||||
/// The content to style.
|
/// The content to style.
|
||||||
body: Content,
|
body: Content,
|
||||||
) -> Content {
|
) -> Content {
|
||||||
body.set(EquationElem::variant, MathVariant::Cal)
|
body.set(EquationElem::variant, Some(MathVariant::Roundhand))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fraktur font style in math.
|
/// Fraktur font style in math.
|
||||||
@ -106,7 +124,7 @@ pub fn frak(
|
|||||||
/// The content to style.
|
/// The content to style.
|
||||||
body: Content,
|
body: Content,
|
||||||
) -> Content {
|
) -> Content {
|
||||||
body.set(EquationElem::variant, MathVariant::Frak)
|
body.set(EquationElem::variant, Some(MathVariant::Fraktur))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Monospace font style in math.
|
/// Monospace font style in math.
|
||||||
@ -119,7 +137,7 @@ pub fn mono(
|
|||||||
/// The content to style.
|
/// The content to style.
|
||||||
body: Content,
|
body: Content,
|
||||||
) -> Content {
|
) -> Content {
|
||||||
body.set(EquationElem::variant, MathVariant::Mono)
|
body.set(EquationElem::variant, Some(MathVariant::Monospace))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Blackboard bold (double-struck) font style in math.
|
/// Blackboard bold (double-struck) font style in math.
|
||||||
@ -137,7 +155,7 @@ pub fn bb(
|
|||||||
/// The content to style.
|
/// The content to style.
|
||||||
body: Content,
|
body: Content,
|
||||||
) -> Content {
|
) -> Content {
|
||||||
body.set(EquationElem::variant, MathVariant::Bb)
|
body.set(EquationElem::variant, Some(MathVariant::DoubleStruck))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Forced display style in math.
|
/// Forced display style in math.
|
||||||
@ -240,15 +258,3 @@ pub enum MathSize {
|
|||||||
/// Math on its own line.
|
/// Math on its own line.
|
||||||
Display,
|
Display,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A mathematical style variant, as defined by Unicode.
|
|
||||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Cast, Hash)]
|
|
||||||
pub enum MathVariant {
|
|
||||||
#[default]
|
|
||||||
Serif,
|
|
||||||
Sans,
|
|
||||||
Cal,
|
|
||||||
Frak,
|
|
||||||
Mono,
|
|
||||||
Bb,
|
|
||||||
}
|
|
||||||
|
@ -2,7 +2,6 @@ use std::any::TypeId;
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, LazyLock};
|
use std::sync::{Arc, LazyLock};
|
||||||
|
|
||||||
@ -17,7 +16,7 @@ use hayagriva::{
|
|||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::{smallvec, SmallVec};
|
||||||
use typst_syntax::{Span, Spanned, SyntaxMode};
|
use typst_syntax::{Span, Spanned, SyntaxMode};
|
||||||
use typst_utils::{Get, ManuallyHash, NonZeroExt, PicoStr};
|
use typst_utils::{ManuallyHash, PicoStr};
|
||||||
|
|
||||||
use crate::diag::{
|
use crate::diag::{
|
||||||
bail, error, At, HintedStrResult, LoadError, LoadResult, LoadedWithin, ReportPos,
|
bail, error, At, HintedStrResult, LoadError, LoadResult, LoadedWithin, ReportPos,
|
||||||
@ -26,18 +25,17 @@ use crate::diag::{
|
|||||||
use crate::engine::{Engine, Sink};
|
use crate::engine::{Engine, Sink};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
elem, Bytes, CastInfo, Content, Derived, FromValue, IntoValue, Label, NativeElement,
|
elem, Bytes, CastInfo, Content, Derived, FromValue, IntoValue, Label, NativeElement,
|
||||||
OneOrMultiple, Packed, Reflect, Scope, Show, ShowSet, Smart, StyleChain, Styles,
|
OneOrMultiple, Packed, Reflect, Scope, ShowSet, Smart, StyleChain, Styles,
|
||||||
Synthesize, Value,
|
Synthesize, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Introspector, Locatable, Location};
|
use crate::introspection::{Introspector, Locatable, Location};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem,
|
BlockBody, BlockElem, Em, GridCell, GridChild, GridElem, GridItem, HElem, PadElem,
|
||||||
Sides, Sizing, TrackSizings,
|
Sizing, TrackSizings,
|
||||||
};
|
};
|
||||||
use crate::loading::{format_yaml_error, DataSource, Load, LoadSource, Loaded};
|
use crate::loading::{format_yaml_error, DataSource, Load, LoadSource, Loaded};
|
||||||
use crate::model::{
|
use crate::model::{
|
||||||
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, ParElem,
|
CitationForm, CiteGroup, Destination, FootnoteElem, HeadingElem, LinkElem, Url,
|
||||||
Url,
|
|
||||||
};
|
};
|
||||||
use crate::routines::Routines;
|
use crate::routines::Routines;
|
||||||
use crate::text::{
|
use crate::text::{
|
||||||
@ -88,7 +86,7 @@ use crate::World;
|
|||||||
///
|
///
|
||||||
/// #bibliography("works.bib")
|
/// #bibliography("works.bib")
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Locatable, Synthesize, Show, ShowSet, LocalName)]
|
#[elem(Locatable, Synthesize, ShowSet, LocalName)]
|
||||||
pub struct BibliographyElem {
|
pub struct BibliographyElem {
|
||||||
/// One or multiple paths to or raw bytes for Hayagriva `.yaml` and/or
|
/// One or multiple paths to or raw bytes for Hayagriva `.yaml` and/or
|
||||||
/// BibLaTeX `.bib` files.
|
/// BibLaTeX `.bib` files.
|
||||||
@ -203,84 +201,6 @@ impl Synthesize for Packed<BibliographyElem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<BibliographyElem> {
|
|
||||||
#[typst_macros::time(name = "bibliography", span = self.span())]
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
const COLUMN_GUTTER: Em = Em::new(0.65);
|
|
||||||
const INDENT: Em = Em::new(1.5);
|
|
||||||
|
|
||||||
let span = self.span();
|
|
||||||
|
|
||||||
let mut seq = vec![];
|
|
||||||
if let Some(title) = self.title.get_ref(styles).clone().unwrap_or_else(|| {
|
|
||||||
Some(TextElem::packed(Self::local_name_in(styles)).spanned(span))
|
|
||||||
}) {
|
|
||||||
seq.push(
|
|
||||||
HeadingElem::new(title)
|
|
||||||
.with_depth(NonZeroUsize::ONE)
|
|
||||||
.pack()
|
|
||||||
.spanned(span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let works = Works::generate(engine).at(span)?;
|
|
||||||
let references = works
|
|
||||||
.references
|
|
||||||
.as_ref()
|
|
||||||
.ok_or_else(|| match self.style.get_ref(styles).source {
|
|
||||||
CslSource::Named(style) => eco_format!(
|
|
||||||
"CSL style \"{}\" is not suitable for bibliographies",
|
|
||||||
style.display_name()
|
|
||||||
),
|
|
||||||
CslSource::Normal(..) => {
|
|
||||||
"CSL style is not suitable for bibliographies".into()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.at(span)?;
|
|
||||||
|
|
||||||
if references.iter().any(|(prefix, _)| prefix.is_some()) {
|
|
||||||
let row_gutter = styles.get(ParElem::spacing);
|
|
||||||
|
|
||||||
let mut cells = vec![];
|
|
||||||
for (prefix, reference) in references {
|
|
||||||
cells.push(GridChild::Item(GridItem::Cell(
|
|
||||||
Packed::new(GridCell::new(prefix.clone().unwrap_or_default()))
|
|
||||||
.spanned(span),
|
|
||||||
)));
|
|
||||||
cells.push(GridChild::Item(GridItem::Cell(
|
|
||||||
Packed::new(GridCell::new(reference.clone())).spanned(span),
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
seq.push(
|
|
||||||
GridElem::new(cells)
|
|
||||||
.with_columns(TrackSizings(smallvec![Sizing::Auto; 2]))
|
|
||||||
.with_column_gutter(TrackSizings(smallvec![COLUMN_GUTTER.into()]))
|
|
||||||
.with_row_gutter(TrackSizings(smallvec![row_gutter.into()]))
|
|
||||||
.pack()
|
|
||||||
.spanned(span),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
for (_, reference) in references {
|
|
||||||
let realized = reference.clone();
|
|
||||||
let block = if works.hanging_indent {
|
|
||||||
let body = HElem::new((-INDENT).into()).pack() + realized;
|
|
||||||
let inset = Sides::default()
|
|
||||||
.with(styles.resolve(TextElem::dir).start(), Some(INDENT.into()));
|
|
||||||
BlockElem::new()
|
|
||||||
.with_body(Some(BlockBody::Content(body)))
|
|
||||||
.with_inset(inset)
|
|
||||||
} else {
|
|
||||||
BlockElem::new().with_body(Some(BlockBody::Content(realized)))
|
|
||||||
};
|
|
||||||
|
|
||||||
seq.push(block.pack().spanned(span));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Content::sequence(seq))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShowSet for Packed<BibliographyElem> {
|
impl ShowSet for Packed<BibliographyElem> {
|
||||||
fn show_set(&self, _: StyleChain) -> Styles {
|
fn show_set(&self, _: StyleChain) -> Styles {
|
||||||
const INDENT: Em = Em::new(1.0);
|
const INDENT: Em = Em::new(1.0);
|
||||||
@ -564,7 +484,7 @@ impl IntoValue for CslSource {
|
|||||||
/// memoization) for the whole document. This setup is necessary because
|
/// memoization) for the whole document. This setup is necessary because
|
||||||
/// citation formatting is inherently stateful and we need access to all
|
/// citation formatting is inherently stateful and we need access to all
|
||||||
/// citations to do it.
|
/// citations to do it.
|
||||||
pub(super) struct Works {
|
pub struct Works {
|
||||||
/// Maps from the location of a citation group to its rendered content.
|
/// Maps from the location of a citation group to its rendered content.
|
||||||
pub citations: HashMap<Location, SourceResult<Content>>,
|
pub citations: HashMap<Location, SourceResult<Content>>,
|
||||||
/// Lists all references in the bibliography, with optional prefix, or
|
/// Lists all references in the bibliography, with optional prefix, or
|
||||||
|
@ -3,8 +3,7 @@ use typst_syntax::Spanned;
|
|||||||
use crate::diag::{error, At, HintedString, SourceResult};
|
use crate::diag::{error, At, HintedString, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, Cast, Content, Derived, Label, Packed, Show, Smart, StyleChain,
|
cast, elem, Cast, Content, Derived, Label, Packed, Smart, StyleChain, Synthesize,
|
||||||
Synthesize,
|
|
||||||
};
|
};
|
||||||
use crate::introspection::Locatable;
|
use crate::introspection::Locatable;
|
||||||
use crate::model::bibliography::Works;
|
use crate::model::bibliography::Works;
|
||||||
@ -153,16 +152,15 @@ pub enum CitationForm {
|
|||||||
///
|
///
|
||||||
/// This is automatically created from adjacent citations during show rule
|
/// This is automatically created from adjacent citations during show rule
|
||||||
/// application.
|
/// application.
|
||||||
#[elem(Locatable, Show)]
|
#[elem(Locatable)]
|
||||||
pub struct CiteGroup {
|
pub struct CiteGroup {
|
||||||
/// The citations.
|
/// The citations.
|
||||||
#[required]
|
#[required]
|
||||||
pub children: Vec<Packed<CiteElem>>,
|
pub children: Vec<Packed<CiteElem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<CiteGroup> {
|
impl Packed<CiteGroup> {
|
||||||
#[typst_macros::time(name = "cite", span = self.span())]
|
pub fn realize(&self, engine: &mut Engine) -> SourceResult<Content> {
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
let location = self.location().unwrap();
|
let location = self.location().unwrap();
|
||||||
let span = self.span();
|
let span = self.span();
|
||||||
Works::generate(engine)
|
Works::generate(engine)
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, Content};
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
|
||||||
elem, Content, NativeElement, Packed, Show, StyleChain, TargetElem,
|
|
||||||
};
|
|
||||||
use crate::html::{tag, HtmlElem};
|
|
||||||
use crate::text::{ItalicToggle, TextElem};
|
|
||||||
|
|
||||||
/// Emphasizes content by toggling italics.
|
/// Emphasizes content by toggling italics.
|
||||||
///
|
///
|
||||||
@ -29,24 +23,9 @@ use crate::text::{ItalicToggle, TextElem};
|
|||||||
/// This function also has dedicated syntax: To emphasize content, simply
|
/// This function also has dedicated syntax: To emphasize content, simply
|
||||||
/// enclose it in underscores (`_`). Note that this only works at word
|
/// enclose it in underscores (`_`). Note that this only works at word
|
||||||
/// boundaries. To emphasize part of a word, you have to use the function.
|
/// boundaries. To emphasize part of a word, you have to use the function.
|
||||||
#[elem(title = "Emphasis", keywords = ["italic"], Show)]
|
#[elem(title = "Emphasis", keywords = ["italic"])]
|
||||||
pub struct EmphElem {
|
pub struct EmphElem {
|
||||||
/// The content to emphasize.
|
/// The content to emphasize.
|
||||||
#[required]
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<EmphElem> {
|
|
||||||
#[typst_macros::time(name = "emph", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let body = self.body.clone();
|
|
||||||
Ok(if styles.get(TargetElem::target).is_html() {
|
|
||||||
HtmlElem::new(tag::em)
|
|
||||||
.with_body(Some(body))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span())
|
|
||||||
} else {
|
|
||||||
body.set(TextElem::emph, ItalicToggle(true))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,19 +1,11 @@
|
|||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use ecow::eco_format;
|
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
|
|
||||||
use crate::diag::{bail, SourceResult};
|
use crate::diag::bail;
|
||||||
use crate::engine::Engine;
|
use crate::foundations::{cast, elem, scope, Array, Content, Packed, Smart, Styles};
|
||||||
use crate::foundations::{
|
use crate::layout::{Alignment, Em, HAlignment, Length, VAlignment};
|
||||||
cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
|
use crate::model::{ListItemLike, ListLike, Numbering, NumberingPattern};
|
||||||
Styles, TargetElem,
|
|
||||||
};
|
|
||||||
use crate::html::{attr, tag, HtmlElem};
|
|
||||||
use crate::layout::{Alignment, BlockElem, Em, HAlignment, Length, VAlignment, VElem};
|
|
||||||
use crate::model::{
|
|
||||||
ListItemLike, ListLike, Numbering, NumberingPattern, ParElem, ParbreakElem,
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A numbered list.
|
/// A numbered list.
|
||||||
///
|
///
|
||||||
@ -71,7 +63,7 @@ use crate::model::{
|
|||||||
/// Enumeration items can contain multiple paragraphs and other block-level
|
/// Enumeration items can contain multiple paragraphs and other block-level
|
||||||
/// content. All content that is indented more than an item's marker becomes
|
/// content. All content that is indented more than an item's marker becomes
|
||||||
/// part of that item.
|
/// part of that item.
|
||||||
#[elem(scope, title = "Numbered List", Show)]
|
#[elem(scope, title = "Numbered List")]
|
||||||
pub struct EnumElem {
|
pub struct EnumElem {
|
||||||
/// Defines the default [spacing]($enum.spacing) of the enumeration. If it
|
/// Defines the default [spacing]($enum.spacing) of the enumeration. If it
|
||||||
/// is `{false}`, the items are spaced apart with
|
/// is `{false}`, the items are spaced apart with
|
||||||
@ -223,51 +215,6 @@ impl EnumElem {
|
|||||||
type EnumItem;
|
type EnumItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<EnumElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let tight = self.tight.get(styles);
|
|
||||||
|
|
||||||
if styles.get(TargetElem::target).is_html() {
|
|
||||||
let mut elem = HtmlElem::new(tag::ol);
|
|
||||||
if self.reversed.get(styles) {
|
|
||||||
elem = elem.with_attr(attr::reversed, "reversed");
|
|
||||||
}
|
|
||||||
if let Some(n) = self.start.get(styles).custom() {
|
|
||||||
elem = elem.with_attr(attr::start, eco_format!("{n}"));
|
|
||||||
}
|
|
||||||
let body = Content::sequence(self.children.iter().map(|item| {
|
|
||||||
let mut li = HtmlElem::new(tag::li);
|
|
||||||
if let Some(nr) = item.number.get(styles) {
|
|
||||||
li = li.with_attr(attr::value, eco_format!("{nr}"));
|
|
||||||
}
|
|
||||||
// Text in wide enums shall always turn into paragraphs.
|
|
||||||
let mut body = item.body.clone();
|
|
||||||
if !tight {
|
|
||||||
body += ParbreakElem::shared();
|
|
||||||
}
|
|
||||||
li.with_body(Some(body)).pack().spanned(item.span())
|
|
||||||
}));
|
|
||||||
return Ok(elem.with_body(Some(body)).pack().spanned(self.span()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut realized =
|
|
||||||
BlockElem::multi_layouter(self.clone(), engine.routines.layout_enum)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span());
|
|
||||||
|
|
||||||
if tight {
|
|
||||||
let spacing = self
|
|
||||||
.spacing
|
|
||||||
.get(styles)
|
|
||||||
.unwrap_or_else(|| styles.get(ParElem::leading));
|
|
||||||
let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack();
|
|
||||||
realized = v + realized;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(realized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An enumeration item.
|
/// An enumeration item.
|
||||||
#[elem(name = "item", title = "Numbered List Item")]
|
#[elem(name = "item", title = "Numbered List Item")]
|
||||||
pub struct EnumItem {
|
pub struct EnumItem {
|
||||||
|
@ -9,19 +9,16 @@ use crate::diag::{bail, SourceResult};
|
|||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, scope, select_where, Content, Element, NativeElement, Packed, Selector,
|
cast, elem, scope, select_where, Content, Element, NativeElement, Packed, Selector,
|
||||||
Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem,
|
ShowSet, Smart, StyleChain, Styles, Synthesize,
|
||||||
};
|
};
|
||||||
use crate::html::{tag, HtmlElem};
|
|
||||||
use crate::introspection::{
|
use crate::introspection::{
|
||||||
Count, Counter, CounterKey, CounterUpdate, Locatable, Location,
|
Count, Counter, CounterKey, CounterUpdate, Locatable, Location,
|
||||||
};
|
};
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
AlignElem, Alignment, BlockBody, BlockElem, Em, HAlignment, Length, OuterVAlignment,
|
AlignElem, Alignment, BlockElem, Em, Length, OuterVAlignment, PlacementScope,
|
||||||
PlaceElem, PlacementScope, VAlignment, VElem,
|
VAlignment,
|
||||||
};
|
|
||||||
use crate::model::{
|
|
||||||
Numbering, NumberingPattern, Outlinable, ParbreakElem, Refable, Supplement,
|
|
||||||
};
|
};
|
||||||
|
use crate::model::{Numbering, NumberingPattern, Outlinable, Refable, Supplement};
|
||||||
use crate::text::{Lang, Region, TextElem};
|
use crate::text::{Lang, Region, TextElem};
|
||||||
use crate::visualize::ImageElem;
|
use crate::visualize::ImageElem;
|
||||||
|
|
||||||
@ -104,7 +101,7 @@ use crate::visualize::ImageElem;
|
|||||||
/// caption: [I'm up here],
|
/// caption: [I'm up here],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(scope, Locatable, Synthesize, Count, Show, ShowSet, Refable, Outlinable)]
|
#[elem(scope, Locatable, Synthesize, Count, ShowSet, Refable, Outlinable)]
|
||||||
pub struct FigureElem {
|
pub struct FigureElem {
|
||||||
/// The content of the figure. Often, an [image].
|
/// The content of the figure. Often, an [image].
|
||||||
#[required]
|
#[required]
|
||||||
@ -328,65 +325,6 @@ impl Synthesize for Packed<FigureElem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<FigureElem> {
|
|
||||||
#[typst_macros::time(name = "figure", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let span = self.span();
|
|
||||||
let target = styles.get(TargetElem::target);
|
|
||||||
let mut realized = self.body.clone();
|
|
||||||
|
|
||||||
// Build the caption, if any.
|
|
||||||
if let Some(caption) = self.caption.get_cloned(styles) {
|
|
||||||
let (first, second) = match caption.position.get(styles) {
|
|
||||||
OuterVAlignment::Top => (caption.pack(), realized),
|
|
||||||
OuterVAlignment::Bottom => (realized, caption.pack()),
|
|
||||||
};
|
|
||||||
let mut seq = Vec::with_capacity(3);
|
|
||||||
seq.push(first);
|
|
||||||
if !target.is_html() {
|
|
||||||
let v = VElem::new(self.gap.get(styles).into()).with_weak(true);
|
|
||||||
seq.push(v.pack().spanned(span))
|
|
||||||
}
|
|
||||||
seq.push(second);
|
|
||||||
realized = Content::sequence(seq)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that the body is considered a paragraph.
|
|
||||||
realized += ParbreakElem::shared().clone().spanned(span);
|
|
||||||
|
|
||||||
if target.is_html() {
|
|
||||||
return Ok(HtmlElem::new(tag::figure)
|
|
||||||
.with_body(Some(realized))
|
|
||||||
.pack()
|
|
||||||
.spanned(span));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap the contents in a block.
|
|
||||||
realized = BlockElem::new()
|
|
||||||
.with_body(Some(BlockBody::Content(realized)))
|
|
||||||
.pack()
|
|
||||||
.spanned(span);
|
|
||||||
|
|
||||||
// Wrap in a float.
|
|
||||||
if let Some(align) = self.placement.get(styles) {
|
|
||||||
realized = PlaceElem::new(realized)
|
|
||||||
.with_alignment(align.map(|align| HAlignment::Center + align))
|
|
||||||
.with_scope(self.scope.get(styles))
|
|
||||||
.with_float(true)
|
|
||||||
.pack()
|
|
||||||
.spanned(span);
|
|
||||||
} else if self.scope.get(styles) == PlacementScope::Parent {
|
|
||||||
bail!(
|
|
||||||
span,
|
|
||||||
"parent-scoped placement is only available for floating figures";
|
|
||||||
hint: "you can enable floating placement with `figure(placement: auto, ..)`"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(realized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShowSet for Packed<FigureElem> {
|
impl ShowSet for Packed<FigureElem> {
|
||||||
fn show_set(&self, _: StyleChain) -> Styles {
|
fn show_set(&self, _: StyleChain) -> Styles {
|
||||||
// Still allows breakable figures with
|
// Still allows breakable figures with
|
||||||
@ -471,7 +409,7 @@ impl Outlinable for Packed<FigureElem> {
|
|||||||
/// caption: [A rectangle],
|
/// caption: [A rectangle],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(name = "caption", Synthesize, Show)]
|
#[elem(name = "caption", Synthesize)]
|
||||||
pub struct FigureCaption {
|
pub struct FigureCaption {
|
||||||
/// The caption's position in the figure. Either `{top}` or `{bottom}`.
|
/// The caption's position in the figure. Either `{top}` or `{bottom}`.
|
||||||
///
|
///
|
||||||
@ -559,6 +497,35 @@ pub struct FigureCaption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl FigureCaption {
|
impl FigureCaption {
|
||||||
|
/// Realizes the textual caption content.
|
||||||
|
pub fn realize(
|
||||||
|
&self,
|
||||||
|
engine: &mut Engine,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
|
let mut realized = self.body.clone();
|
||||||
|
|
||||||
|
if let (
|
||||||
|
Some(Some(mut supplement)),
|
||||||
|
Some(Some(numbering)),
|
||||||
|
Some(Some(counter)),
|
||||||
|
Some(Some(location)),
|
||||||
|
) = (
|
||||||
|
self.supplement.clone(),
|
||||||
|
&self.numbering,
|
||||||
|
&self.counter,
|
||||||
|
&self.figure_location,
|
||||||
|
) {
|
||||||
|
let numbers = counter.display_at_loc(engine, *location, styles, numbering)?;
|
||||||
|
if !supplement.is_empty() {
|
||||||
|
supplement += TextElem::packed('\u{a0}');
|
||||||
|
}
|
||||||
|
realized = supplement + numbers + self.get_separator(styles) + realized;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(realized)
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the default separator in the given language and (optionally)
|
/// Gets the default separator in the given language and (optionally)
|
||||||
/// region.
|
/// region.
|
||||||
fn local_separator(lang: Lang, _: Option<Region>) -> &'static str {
|
fn local_separator(lang: Lang, _: Option<Region>) -> &'static str {
|
||||||
@ -588,43 +555,6 @@ impl Synthesize for Packed<FigureCaption> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<FigureCaption> {
|
|
||||||
#[typst_macros::time(name = "figure.caption", span = self.span())]
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let mut realized = self.body.clone();
|
|
||||||
|
|
||||||
if let (
|
|
||||||
Some(Some(mut supplement)),
|
|
||||||
Some(Some(numbering)),
|
|
||||||
Some(Some(counter)),
|
|
||||||
Some(Some(location)),
|
|
||||||
) = (
|
|
||||||
self.supplement.clone(),
|
|
||||||
&self.numbering,
|
|
||||||
&self.counter,
|
|
||||||
&self.figure_location,
|
|
||||||
) {
|
|
||||||
let numbers = counter.display_at_loc(engine, *location, styles, numbering)?;
|
|
||||||
if !supplement.is_empty() {
|
|
||||||
supplement += TextElem::packed('\u{a0}');
|
|
||||||
}
|
|
||||||
realized = supplement + numbers + self.get_separator(styles) + realized;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(if styles.get(TargetElem::target).is_html() {
|
|
||||||
HtmlElem::new(tag::figcaption)
|
|
||||||
.with_body(Some(realized))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span())
|
|
||||||
} else {
|
|
||||||
BlockElem::new()
|
|
||||||
.with_body(Some(BlockBody::Content(realized)))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cast! {
|
cast! {
|
||||||
FigureCaption,
|
FigureCaption,
|
||||||
v: Content => v.unpack::<Self>().unwrap_or_else(Self::new),
|
v: Content => v.unpack::<Self>().unwrap_or_else(Self::new),
|
||||||
|
@ -3,16 +3,16 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use typst_utils::NonZeroExt;
|
use typst_utils::NonZeroExt;
|
||||||
|
|
||||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, scope, Content, Label, NativeElement, Packed, Show, ShowSet, Smart,
|
cast, elem, scope, Content, Label, NativeElement, Packed, ShowSet, Smart, StyleChain,
|
||||||
StyleChain, Styles,
|
Styles,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Count, Counter, CounterUpdate, Locatable, Location};
|
use crate::introspection::{Count, CounterUpdate, Locatable, Location};
|
||||||
use crate::layout::{Abs, Em, HElem, Length, Ratio};
|
use crate::layout::{Abs, Em, Length, Ratio};
|
||||||
use crate::model::{Destination, Numbering, NumberingPattern, ParElem};
|
use crate::model::{Numbering, NumberingPattern, ParElem};
|
||||||
use crate::text::{SuperElem, TextElem, TextSize};
|
use crate::text::{TextElem, TextSize};
|
||||||
use crate::visualize::{LineElem, Stroke};
|
use crate::visualize::{LineElem, Stroke};
|
||||||
|
|
||||||
/// A footnote.
|
/// A footnote.
|
||||||
@ -51,7 +51,7 @@ use crate::visualize::{LineElem, Stroke};
|
|||||||
/// apply to the footnote's content. See [here][issue] for more information.
|
/// apply to the footnote's content. See [here][issue] for more information.
|
||||||
///
|
///
|
||||||
/// [issue]: https://github.com/typst/typst/issues/1467#issuecomment-1588799440
|
/// [issue]: https://github.com/typst/typst/issues/1467#issuecomment-1588799440
|
||||||
#[elem(scope, Locatable, Show, Count)]
|
#[elem(scope, Locatable, Count)]
|
||||||
pub struct FootnoteElem {
|
pub struct FootnoteElem {
|
||||||
/// How to number footnotes.
|
/// How to number footnotes.
|
||||||
///
|
///
|
||||||
@ -135,21 +135,6 @@ impl Packed<FootnoteElem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<FootnoteElem> {
|
|
||||||
#[typst_macros::time(name = "footnote", span = self.span())]
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let span = self.span();
|
|
||||||
let loc = self.declaration_location(engine).at(span)?;
|
|
||||||
let numbering = self.numbering.get_ref(styles);
|
|
||||||
let counter = Counter::of(FootnoteElem::ELEM);
|
|
||||||
let num = counter.display_at_loc(engine, loc, styles, numbering)?;
|
|
||||||
let sup = SuperElem::new(num).pack().spanned(span);
|
|
||||||
let loc = loc.variant(1);
|
|
||||||
// Add zero-width weak spacing to make the footnote "sticky".
|
|
||||||
Ok(HElem::hole().pack() + sup.linked(Destination::Location(loc)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Count for Packed<FootnoteElem> {
|
impl Count for Packed<FootnoteElem> {
|
||||||
fn update(&self) -> Option<CounterUpdate> {
|
fn update(&self) -> Option<CounterUpdate> {
|
||||||
(!self.is_ref()).then(|| CounterUpdate::Step(NonZeroUsize::ONE))
|
(!self.is_ref()).then(|| CounterUpdate::Step(NonZeroUsize::ONE))
|
||||||
@ -191,7 +176,7 @@ cast! {
|
|||||||
/// page run is a sequence of pages without an explicit pagebreak in between).
|
/// page run is a sequence of pages without an explicit pagebreak in between).
|
||||||
/// For this reason, set and show rules for footnote entries should be defined
|
/// For this reason, set and show rules for footnote entries should be defined
|
||||||
/// before any page content, typically at the very start of the document.
|
/// before any page content, typically at the very start of the document.
|
||||||
#[elem(name = "entry", title = "Footnote Entry", Show, ShowSet)]
|
#[elem(name = "entry", title = "Footnote Entry", ShowSet)]
|
||||||
pub struct FootnoteEntry {
|
pub struct FootnoteEntry {
|
||||||
/// The footnote for this entry. Its location can be used to determine
|
/// The footnote for this entry. Its location can be used to determine
|
||||||
/// the footnote counter state.
|
/// the footnote counter state.
|
||||||
@ -274,37 +259,6 @@ pub struct FootnoteEntry {
|
|||||||
pub indent: Length,
|
pub indent: Length,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<FootnoteEntry> {
|
|
||||||
#[typst_macros::time(name = "footnote.entry", span = self.span())]
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let span = self.span();
|
|
||||||
let number_gap = Em::new(0.05);
|
|
||||||
let default = StyleChain::default();
|
|
||||||
let numbering = self.note.numbering.get_ref(default);
|
|
||||||
let counter = Counter::of(FootnoteElem::ELEM);
|
|
||||||
let Some(loc) = self.note.location() else {
|
|
||||||
bail!(
|
|
||||||
span, "footnote entry must have a location";
|
|
||||||
hint: "try using a query or a show rule to customize the footnote instead"
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
let num = counter.display_at_loc(engine, loc, styles, numbering)?;
|
|
||||||
let sup = SuperElem::new(num)
|
|
||||||
.pack()
|
|
||||||
.spanned(span)
|
|
||||||
.linked(Destination::Location(loc))
|
|
||||||
.located(loc.variant(1));
|
|
||||||
|
|
||||||
Ok(Content::sequence([
|
|
||||||
HElem::new(self.indent.get(styles).into()).pack(),
|
|
||||||
sup,
|
|
||||||
HElem::new(number_gap.into()).with_weak(true).pack(),
|
|
||||||
self.note.body_content().unwrap().clone(),
|
|
||||||
]))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShowSet for Packed<FootnoteEntry> {
|
impl ShowSet for Packed<FootnoteEntry> {
|
||||||
fn show_set(&self, _: StyleChain) -> Styles {
|
fn show_set(&self, _: StyleChain) -> Styles {
|
||||||
let mut out = Styles::new();
|
let mut out = Styles::new();
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use ecow::eco_format;
|
use typst_utils::NonZeroExt;
|
||||||
use typst_utils::{Get, NonZeroExt};
|
|
||||||
|
|
||||||
use crate::diag::{warning, SourceResult};
|
use crate::diag::SourceResult;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
elem, Content, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
|
elem, Content, NativeElement, Packed, ShowSet, Smart, StyleChain, Styles, Synthesize,
|
||||||
Styles, Synthesize, TargetElem,
|
|
||||||
};
|
};
|
||||||
use crate::html::{attr, tag, HtmlElem};
|
use crate::introspection::{Count, Counter, CounterUpdate, Locatable};
|
||||||
use crate::introspection::{
|
use crate::layout::{BlockElem, Em, Length};
|
||||||
Count, Counter, CounterUpdate, Locatable, Locator, LocatorLink,
|
|
||||||
};
|
|
||||||
use crate::layout::{Abs, Axes, BlockBody, BlockElem, Em, HElem, Length, Region, Sides};
|
|
||||||
use crate::model::{Numbering, Outlinable, Refable, Supplement};
|
use crate::model::{Numbering, Outlinable, Refable, Supplement};
|
||||||
use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
|
use crate::text::{FontWeight, LocalName, TextElem, TextSize};
|
||||||
|
|
||||||
/// A section heading.
|
/// A section heading.
|
||||||
///
|
///
|
||||||
@ -49,7 +44,7 @@ use crate::text::{FontWeight, LocalName, SpaceElem, TextElem, TextSize};
|
|||||||
/// one or multiple equals signs, followed by a space. The number of equals
|
/// one or multiple equals signs, followed by a space. The number of equals
|
||||||
/// signs determines the heading's logical nesting depth. The `{offset}` field
|
/// signs determines the heading's logical nesting depth. The `{offset}` field
|
||||||
/// can be set to configure the starting depth.
|
/// can be set to configure the starting depth.
|
||||||
#[elem(Locatable, Synthesize, Count, Show, ShowSet, LocalName, Refable, Outlinable)]
|
#[elem(Locatable, Synthesize, Count, ShowSet, LocalName, Refable, Outlinable)]
|
||||||
pub struct HeadingElem {
|
pub struct HeadingElem {
|
||||||
/// The absolute nesting depth of the heading, starting from one. If set
|
/// The absolute nesting depth of the heading, starting from one. If set
|
||||||
/// to `{auto}`, it is computed from `{offset + depth}`.
|
/// to `{auto}`, it is computed from `{offset + depth}`.
|
||||||
@ -215,96 +210,6 @@ impl Synthesize for Packed<HeadingElem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<HeadingElem> {
|
|
||||||
#[typst_macros::time(name = "heading", span = self.span())]
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let html = styles.get(TargetElem::target).is_html();
|
|
||||||
|
|
||||||
const SPACING_TO_NUMBERING: Em = Em::new(0.3);
|
|
||||||
|
|
||||||
let span = self.span();
|
|
||||||
let mut realized = self.body.clone();
|
|
||||||
|
|
||||||
let hanging_indent = self.hanging_indent.get(styles);
|
|
||||||
let mut indent = match hanging_indent {
|
|
||||||
Smart::Custom(length) => length.resolve(styles),
|
|
||||||
Smart::Auto => Abs::zero(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(numbering) = self.numbering.get_ref(styles).as_ref() {
|
|
||||||
let location = self.location().unwrap();
|
|
||||||
let numbering = Counter::of(HeadingElem::ELEM)
|
|
||||||
.display_at_loc(engine, location, styles, numbering)?
|
|
||||||
.spanned(span);
|
|
||||||
|
|
||||||
if hanging_indent.is_auto() && !html {
|
|
||||||
let pod = Region::new(Axes::splat(Abs::inf()), Axes::splat(false));
|
|
||||||
|
|
||||||
// We don't have a locator for the numbering here, so we just
|
|
||||||
// use the measurement infrastructure for now.
|
|
||||||
let link = LocatorLink::measure(location);
|
|
||||||
let size = (engine.routines.layout_frame)(
|
|
||||||
engine,
|
|
||||||
&numbering,
|
|
||||||
Locator::link(&link),
|
|
||||||
styles,
|
|
||||||
pod,
|
|
||||||
)?
|
|
||||||
.size();
|
|
||||||
|
|
||||||
indent = size.x + SPACING_TO_NUMBERING.resolve(styles);
|
|
||||||
}
|
|
||||||
|
|
||||||
let spacing = if html {
|
|
||||||
SpaceElem::shared().clone()
|
|
||||||
} else {
|
|
||||||
HElem::new(SPACING_TO_NUMBERING.into()).with_weak(true).pack()
|
|
||||||
};
|
|
||||||
|
|
||||||
realized = numbering + spacing + realized;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(if html {
|
|
||||||
// HTML's h1 is closer to a title element. There should only be one.
|
|
||||||
// Meanwhile, a level 1 Typst heading is a section heading. For this
|
|
||||||
// reason, levels are offset by one: A Typst level 1 heading becomes
|
|
||||||
// a `<h2>`.
|
|
||||||
let level = self.resolve_level(styles).get();
|
|
||||||
if level >= 6 {
|
|
||||||
engine.sink.warn(warning!(span,
|
|
||||||
"heading of level {} was transformed to \
|
|
||||||
<div role=\"heading\" aria-level=\"{}\">, which is not \
|
|
||||||
supported by all assistive technology",
|
|
||||||
level, level + 1;
|
|
||||||
hint: "HTML only supports <h1> to <h6>, not <h{}>", level + 1;
|
|
||||||
hint: "you may want to restructure your document so that \
|
|
||||||
it doesn't contain deep headings"));
|
|
||||||
HtmlElem::new(tag::div)
|
|
||||||
.with_body(Some(realized))
|
|
||||||
.with_attr(attr::role, "heading")
|
|
||||||
.with_attr(attr::aria_level, eco_format!("{}", level + 1))
|
|
||||||
.pack()
|
|
||||||
.spanned(span)
|
|
||||||
} else {
|
|
||||||
let t = [tag::h2, tag::h3, tag::h4, tag::h5, tag::h6][level - 1];
|
|
||||||
HtmlElem::new(t).with_body(Some(realized)).pack().spanned(span)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let block = if indent != Abs::zero() {
|
|
||||||
let body = HElem::new((-indent).into()).pack() + realized;
|
|
||||||
let inset = Sides::default()
|
|
||||||
.with(styles.resolve(TextElem::dir).start(), Some(indent.into()));
|
|
||||||
BlockElem::new()
|
|
||||||
.with_body(Some(BlockBody::Content(body)))
|
|
||||||
.with_inset(inset)
|
|
||||||
} else {
|
|
||||||
BlockElem::new().with_body(Some(BlockBody::Content(realized)))
|
|
||||||
};
|
|
||||||
block.pack().spanned(span)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShowSet for Packed<HeadingElem> {
|
impl ShowSet for Packed<HeadingElem> {
|
||||||
fn show_set(&self, styles: StyleChain) -> Styles {
|
fn show_set(&self, styles: StyleChain) -> Styles {
|
||||||
let level = self.resolve_level(styles).get();
|
let level = self.resolve_level(styles).get();
|
||||||
|
@ -2,13 +2,10 @@ use std::ops::Deref;
|
|||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
|
|
||||||
use crate::diag::{bail, warning, At, SourceResult, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, Content, Label, NativeElement, Packed, Repr, Show, ShowSet, Smart,
|
cast, elem, Content, Label, Packed, Repr, ShowSet, Smart, StyleChain, Styles,
|
||||||
StyleChain, Styles, TargetElem,
|
|
||||||
};
|
};
|
||||||
use crate::html::{attr, tag, HtmlElem};
|
|
||||||
use crate::introspection::Location;
|
use crate::introspection::Location;
|
||||||
use crate::layout::Position;
|
use crate::layout::Position;
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
@ -38,7 +35,7 @@ use crate::text::TextElem;
|
|||||||
/// # Syntax
|
/// # Syntax
|
||||||
/// This function also has dedicated syntax: Text that starts with `http://` or
|
/// This function also has dedicated syntax: Text that starts with `http://` or
|
||||||
/// `https://` is automatically turned into a link.
|
/// `https://` is automatically turned into a link.
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct LinkElem {
|
pub struct LinkElem {
|
||||||
/// The destination the link points to.
|
/// The destination the link points to.
|
||||||
///
|
///
|
||||||
@ -103,38 +100,6 @@ impl LinkElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<LinkElem> {
|
|
||||||
#[typst_macros::time(name = "link", span = self.span())]
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let body = self.body.clone();
|
|
||||||
|
|
||||||
Ok(if styles.get(TargetElem::target).is_html() {
|
|
||||||
if let LinkTarget::Dest(Destination::Url(url)) = &self.dest {
|
|
||||||
HtmlElem::new(tag::a)
|
|
||||||
.with_attr(attr::href, url.clone().into_inner())
|
|
||||||
.with_body(Some(body))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span())
|
|
||||||
} else {
|
|
||||||
engine.sink.warn(warning!(
|
|
||||||
self.span(),
|
|
||||||
"non-URL links are not yet supported by HTML export"
|
|
||||||
));
|
|
||||||
body
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
match &self.dest {
|
|
||||||
LinkTarget::Dest(dest) => body.linked(dest.clone()),
|
|
||||||
LinkTarget::Label(label) => {
|
|
||||||
let elem = engine.introspector.query_label(*label).at(self.span())?;
|
|
||||||
let dest = Destination::Location(elem.location().unwrap());
|
|
||||||
body.clone().linked(dest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShowSet for Packed<LinkElem> {
|
impl ShowSet for Packed<LinkElem> {
|
||||||
fn show_set(&self, _: StyleChain) -> Styles {
|
fn show_set(&self, _: StyleChain) -> Styles {
|
||||||
let mut out = Styles::new();
|
let mut out = Styles::new();
|
||||||
|
@ -3,12 +3,10 @@ use comemo::Track;
|
|||||||
use crate::diag::{bail, SourceResult};
|
use crate::diag::{bail, SourceResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, scope, Array, Content, Context, Depth, Func, NativeElement, Packed, Show,
|
cast, elem, scope, Array, Content, Context, Depth, Func, NativeElement, Packed,
|
||||||
Smart, StyleChain, Styles, TargetElem, Value,
|
Smart, StyleChain, Styles, Value,
|
||||||
};
|
};
|
||||||
use crate::html::{tag, HtmlElem};
|
use crate::layout::{Em, Length};
|
||||||
use crate::layout::{BlockElem, Em, Length, VElem};
|
|
||||||
use crate::model::{ParElem, ParbreakElem};
|
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
|
|
||||||
/// A bullet list.
|
/// A bullet list.
|
||||||
@ -42,7 +40,7 @@ use crate::text::TextElem;
|
|||||||
/// followed by a space to create a list item. A list item can contain multiple
|
/// followed by a space to create a list item. A list item can contain multiple
|
||||||
/// paragraphs and other block-level content. All content that is indented
|
/// paragraphs and other block-level content. All content that is indented
|
||||||
/// more than an item's marker becomes part of that item.
|
/// more than an item's marker becomes part of that item.
|
||||||
#[elem(scope, title = "Bullet List", Show)]
|
#[elem(scope, title = "Bullet List")]
|
||||||
pub struct ListElem {
|
pub struct ListElem {
|
||||||
/// Defines the default [spacing]($list.spacing) of the list. If it is
|
/// Defines the default [spacing]($list.spacing) of the list. If it is
|
||||||
/// `{false}`, the items are spaced apart with
|
/// `{false}`, the items are spaced apart with
|
||||||
@ -136,45 +134,6 @@ impl ListElem {
|
|||||||
type ListItem;
|
type ListItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<ListElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let tight = self.tight.get(styles);
|
|
||||||
|
|
||||||
if styles.get(TargetElem::target).is_html() {
|
|
||||||
return Ok(HtmlElem::new(tag::ul)
|
|
||||||
.with_body(Some(Content::sequence(self.children.iter().map(|item| {
|
|
||||||
// Text in wide lists shall always turn into paragraphs.
|
|
||||||
let mut body = item.body.clone();
|
|
||||||
if !tight {
|
|
||||||
body += ParbreakElem::shared();
|
|
||||||
}
|
|
||||||
HtmlElem::new(tag::li)
|
|
||||||
.with_body(Some(body))
|
|
||||||
.pack()
|
|
||||||
.spanned(item.span())
|
|
||||||
}))))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut realized =
|
|
||||||
BlockElem::multi_layouter(self.clone(), engine.routines.layout_list)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span());
|
|
||||||
|
|
||||||
if tight {
|
|
||||||
let spacing = self
|
|
||||||
.spacing
|
|
||||||
.get(styles)
|
|
||||||
.unwrap_or_else(|| styles.get(ParElem::leading));
|
|
||||||
let v = VElem::new(spacing.into()).with_weak(true).with_attach(true).pack();
|
|
||||||
realized = v + realized;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(realized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A bullet list item.
|
/// A bullet list item.
|
||||||
#[elem(name = "item", title = "Bullet List Item")]
|
#[elem(name = "item", title = "Bullet List Item")]
|
||||||
pub struct ListItem {
|
pub struct ListItem {
|
||||||
|
@ -46,23 +46,23 @@ use crate::foundations::Scope;
|
|||||||
pub fn define(global: &mut Scope) {
|
pub fn define(global: &mut Scope) {
|
||||||
global.start_category(crate::Category::Model);
|
global.start_category(crate::Category::Model);
|
||||||
global.define_elem::<DocumentElem>();
|
global.define_elem::<DocumentElem>();
|
||||||
global.define_elem::<RefElem>();
|
global.define_elem::<ParElem>();
|
||||||
|
global.define_elem::<ParbreakElem>();
|
||||||
|
global.define_elem::<StrongElem>();
|
||||||
|
global.define_elem::<EmphElem>();
|
||||||
|
global.define_elem::<ListElem>();
|
||||||
|
global.define_elem::<EnumElem>();
|
||||||
|
global.define_elem::<TermsElem>();
|
||||||
global.define_elem::<LinkElem>();
|
global.define_elem::<LinkElem>();
|
||||||
global.define_elem::<OutlineElem>();
|
|
||||||
global.define_elem::<HeadingElem>();
|
global.define_elem::<HeadingElem>();
|
||||||
global.define_elem::<FigureElem>();
|
global.define_elem::<FigureElem>();
|
||||||
global.define_elem::<FootnoteElem>();
|
|
||||||
global.define_elem::<QuoteElem>();
|
global.define_elem::<QuoteElem>();
|
||||||
|
global.define_elem::<FootnoteElem>();
|
||||||
|
global.define_elem::<OutlineElem>();
|
||||||
|
global.define_elem::<RefElem>();
|
||||||
global.define_elem::<CiteElem>();
|
global.define_elem::<CiteElem>();
|
||||||
global.define_elem::<BibliographyElem>();
|
global.define_elem::<BibliographyElem>();
|
||||||
global.define_elem::<EnumElem>();
|
|
||||||
global.define_elem::<ListElem>();
|
|
||||||
global.define_elem::<ParbreakElem>();
|
|
||||||
global.define_elem::<ParElem>();
|
|
||||||
global.define_elem::<TableElem>();
|
global.define_elem::<TableElem>();
|
||||||
global.define_elem::<TermsElem>();
|
|
||||||
global.define_elem::<EmphElem>();
|
|
||||||
global.define_elem::<StrongElem>();
|
|
||||||
global.define_func::<numbering>();
|
global.define_func::<numbering>();
|
||||||
global.reset_category();
|
global.reset_category();
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ use crate::foundations::{cast, func, Context, Func, Str, Value};
|
|||||||
///
|
///
|
||||||
/// A numbering pattern consists of counting symbols, for which the actual
|
/// A numbering pattern consists of counting symbols, for which the actual
|
||||||
/// number is substituted, their prefixes, and one suffix. The prefixes and the
|
/// number is substituted, their prefixes, and one suffix. The prefixes and the
|
||||||
/// suffix are repeated as-is.
|
/// suffix are displayed as-is.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```example
|
/// ```example
|
||||||
@ -66,10 +66,10 @@ pub fn numbering(
|
|||||||
/// items, the number is represented using repeated symbols.
|
/// items, the number is represented using repeated symbols.
|
||||||
///
|
///
|
||||||
/// **Suffixes** are all characters after the last counting symbol. They are
|
/// **Suffixes** are all characters after the last counting symbol. They are
|
||||||
/// repeated as-is at the end of any rendered number.
|
/// displayed as-is at the end of any rendered number.
|
||||||
///
|
///
|
||||||
/// **Prefixes** are all characters that are neither counting symbols nor
|
/// **Prefixes** are all characters that are neither counting symbols nor
|
||||||
/// suffixes. They are repeated as-is at in front of their rendered
|
/// suffixes. They are displayed as-is at in front of their rendered
|
||||||
/// equivalent of their counting symbol.
|
/// equivalent of their counting symbol.
|
||||||
///
|
///
|
||||||
/// This parameter can also be an arbitrary function that gets each number
|
/// This parameter can also be an arbitrary function that gets each number
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use comemo::{Track, Tracked};
|
use comemo::Tracked;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
use typst_utils::{Get, NonZeroExt};
|
use typst_utils::{Get, NonZeroExt};
|
||||||
@ -10,7 +10,7 @@ use crate::diag::{bail, error, At, HintedStrResult, SourceResult, StrResult};
|
|||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, func, scope, select_where, Args, Construct, Content, Context, Func,
|
cast, elem, func, scope, select_where, Args, Construct, Content, Context, Func,
|
||||||
LocatableSelector, NativeElement, Packed, Resolve, Show, ShowSet, Smart, StyleChain,
|
LocatableSelector, NativeElement, Packed, Resolve, ShowSet, Smart, StyleChain,
|
||||||
Styles,
|
Styles,
|
||||||
};
|
};
|
||||||
use crate::introspection::{
|
use crate::introspection::{
|
||||||
@ -20,8 +20,7 @@ use crate::layout::{
|
|||||||
Abs, Axes, BlockBody, BlockElem, BoxElem, Dir, Em, Fr, HElem, Length, Region, Rel,
|
Abs, Axes, BlockBody, BlockElem, BoxElem, Dir, Em, Fr, HElem, Length, Region, Rel,
|
||||||
RepeatElem, Sides,
|
RepeatElem, Sides,
|
||||||
};
|
};
|
||||||
use crate::math::EquationElem;
|
use crate::model::{HeadingElem, NumberingPattern, ParElem, Refable};
|
||||||
use crate::model::{Destination, HeadingElem, NumberingPattern, ParElem, Refable};
|
|
||||||
use crate::text::{LocalName, SpaceElem, TextElem};
|
use crate::text::{LocalName, SpaceElem, TextElem};
|
||||||
|
|
||||||
/// A table of contents, figures, or other elements.
|
/// A table of contents, figures, or other elements.
|
||||||
@ -147,7 +146,7 @@ use crate::text::{LocalName, SpaceElem, TextElem};
|
|||||||
///
|
///
|
||||||
/// [^1]: The outline of equations is the exception to this rule as it does not
|
/// [^1]: The outline of equations is the exception to this rule as it does not
|
||||||
/// have a body and thus does not use indented layout.
|
/// have a body and thus does not use indented layout.
|
||||||
#[elem(scope, keywords = ["Table of Contents", "toc"], Show, ShowSet, LocalName, Locatable)]
|
#[elem(scope, keywords = ["Table of Contents", "toc"], ShowSet, LocalName, Locatable)]
|
||||||
pub struct OutlineElem {
|
pub struct OutlineElem {
|
||||||
/// The title of the outline.
|
/// The title of the outline.
|
||||||
///
|
///
|
||||||
@ -249,44 +248,6 @@ impl OutlineElem {
|
|||||||
type OutlineEntry;
|
type OutlineEntry;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<OutlineElem> {
|
|
||||||
#[typst_macros::time(name = "outline", span = self.span())]
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let span = self.span();
|
|
||||||
|
|
||||||
// Build the outline title.
|
|
||||||
let mut seq = vec![];
|
|
||||||
if let Some(title) = self.title.get_cloned(styles).unwrap_or_else(|| {
|
|
||||||
Some(TextElem::packed(Self::local_name_in(styles)).spanned(span))
|
|
||||||
}) {
|
|
||||||
seq.push(
|
|
||||||
HeadingElem::new(title)
|
|
||||||
.with_depth(NonZeroUsize::ONE)
|
|
||||||
.pack()
|
|
||||||
.spanned(span),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let elems = engine.introspector.query(&self.target.get_ref(styles).0);
|
|
||||||
let depth = self.depth.get(styles).unwrap_or(NonZeroUsize::MAX);
|
|
||||||
|
|
||||||
// Build the outline entries.
|
|
||||||
for elem in elems {
|
|
||||||
let Some(outlinable) = elem.with::<dyn Outlinable>() else {
|
|
||||||
bail!(span, "cannot outline {}", elem.func().name());
|
|
||||||
};
|
|
||||||
|
|
||||||
let level = outlinable.level();
|
|
||||||
if outlinable.outlined() && level <= depth {
|
|
||||||
let entry = OutlineEntry::new(level, elem);
|
|
||||||
seq.push(entry.pack().spanned(span));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Content::sequence(seq))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShowSet for Packed<OutlineElem> {
|
impl ShowSet for Packed<OutlineElem> {
|
||||||
fn show_set(&self, styles: StyleChain) -> Styles {
|
fn show_set(&self, styles: StyleChain) -> Styles {
|
||||||
let mut out = Styles::new();
|
let mut out = Styles::new();
|
||||||
@ -363,7 +324,7 @@ pub trait Outlinable: Refable {
|
|||||||
/// With show-set and show rules on outline entries, you can richly customize
|
/// With show-set and show rules on outline entries, you can richly customize
|
||||||
/// the outline's appearance. See the
|
/// the outline's appearance. See the
|
||||||
/// [section on styling the outline]($outline/#styling-the-outline) for details.
|
/// [section on styling the outline]($outline/#styling-the-outline) for details.
|
||||||
#[elem(scope, name = "entry", title = "Outline Entry", Show)]
|
#[elem(scope, name = "entry", title = "Outline Entry")]
|
||||||
pub struct OutlineEntry {
|
pub struct OutlineEntry {
|
||||||
/// The nesting level of this outline entry. Starts at `{1}` for top-level
|
/// The nesting level of this outline entry. Starts at `{1}` for top-level
|
||||||
/// entries.
|
/// entries.
|
||||||
@ -408,30 +369,6 @@ pub struct OutlineEntry {
|
|||||||
pub parent: Option<Packed<OutlineElem>>,
|
pub parent: Option<Packed<OutlineElem>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<OutlineEntry> {
|
|
||||||
#[typst_macros::time(name = "outline.entry", span = self.span())]
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let span = self.span();
|
|
||||||
let context = Context::new(None, Some(styles));
|
|
||||||
let context = context.track();
|
|
||||||
|
|
||||||
let prefix = self.prefix(engine, context, span)?;
|
|
||||||
let inner = self.inner(engine, context, span)?;
|
|
||||||
let block = if self.element.is::<EquationElem>() {
|
|
||||||
let body = prefix.unwrap_or_default() + inner;
|
|
||||||
BlockElem::new()
|
|
||||||
.with_body(Some(BlockBody::Content(body)))
|
|
||||||
.pack()
|
|
||||||
.spanned(span)
|
|
||||||
} else {
|
|
||||||
self.indented(engine, context, span, prefix, inner, Em::new(0.5).into())?
|
|
||||||
};
|
|
||||||
|
|
||||||
let loc = self.element_location().at(span)?;
|
|
||||||
Ok(block.linked(Destination::Location(loc)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[scope]
|
#[scope]
|
||||||
impl OutlineEntry {
|
impl OutlineEntry {
|
||||||
/// A helper function for producing an indented entry layout: Lays out a
|
/// A helper function for producing an indented entry layout: Lays out a
|
||||||
@ -654,7 +591,8 @@ impl OutlineEntry {
|
|||||||
.ok_or_else(|| error!("cannot outline {}", self.element.func().name()))
|
.ok_or_else(|| error!("cannot outline {}", self.element.func().name()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn element_location(&self) -> HintedStrResult<Location> {
|
/// Returns the location of the outlined element.
|
||||||
|
pub fn element_location(&self) -> HintedStrResult<Location> {
|
||||||
let elem = &self.element;
|
let elem = &self.element;
|
||||||
elem.location().ok_or_else(|| {
|
elem.location().ok_or_else(|| {
|
||||||
if elem.can::<dyn Locatable>() && elem.can::<dyn Outlinable>() {
|
if elem.can::<dyn Locatable>() && elem.can::<dyn Outlinable>() {
|
||||||
@ -730,8 +668,8 @@ fn query_prefix_widths(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper type for introspection-based prefix alignment.
|
/// Helper type for introspection-based prefix alignment.
|
||||||
#[elem(Construct, Locatable, Show)]
|
#[elem(Construct, Locatable)]
|
||||||
struct PrefixInfo {
|
pub(crate) struct PrefixInfo {
|
||||||
/// The location of the outline this prefix is part of. This is used to
|
/// The location of the outline this prefix is part of. This is used to
|
||||||
/// scope prefix computations to a specific outline.
|
/// scope prefix computations to a specific outline.
|
||||||
#[required]
|
#[required]
|
||||||
@ -753,9 +691,3 @@ impl Construct for PrefixInfo {
|
|||||||
bail!(args.span, "cannot be constructed manually");
|
bail!(args.span, "cannot be constructed manually");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<PrefixInfo> {
|
|
||||||
fn show(&self, _: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(Content::empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,16 +1,13 @@
|
|||||||
use crate::diag::SourceResult;
|
use typst_syntax::Span;
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, Content, Depth, Label, NativeElement, Packed, Show, ShowSet, Smart,
|
cast, elem, Content, Depth, Label, NativeElement, Packed, ShowSet, Smart, StyleChain,
|
||||||
StyleChain, Styles, TargetElem,
|
Styles,
|
||||||
};
|
};
|
||||||
use crate::html::{attr, tag, HtmlElem};
|
|
||||||
use crate::introspection::Locatable;
|
use crate::introspection::Locatable;
|
||||||
use crate::layout::{
|
use crate::layout::{BlockElem, Em, PadElem};
|
||||||
Alignment, BlockBody, BlockElem, Em, HElem, PadElem, Spacing, VElem,
|
use crate::model::{CitationForm, CiteElem};
|
||||||
};
|
use crate::text::{SmartQuotes, SpaceElem, TextElem};
|
||||||
use crate::model::{CitationForm, CiteElem, Destination, LinkElem, LinkTarget};
|
|
||||||
use crate::text::{SmartQuoteElem, SmartQuotes, SpaceElem, TextElem};
|
|
||||||
|
|
||||||
/// Displays a quote alongside an optional attribution.
|
/// Displays a quote alongside an optional attribution.
|
||||||
///
|
///
|
||||||
@ -44,7 +41,7 @@ use crate::text::{SmartQuoteElem, SmartQuotes, SpaceElem, TextElem};
|
|||||||
/// flame of Udûn. Go back to the Shadow! You cannot pass.
|
/// flame of Udûn. Go back to the Shadow! You cannot pass.
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Locatable, ShowSet, Show)]
|
#[elem(Locatable, ShowSet)]
|
||||||
pub struct QuoteElem {
|
pub struct QuoteElem {
|
||||||
/// Whether this is a block quote.
|
/// Whether this is a block quote.
|
||||||
///
|
///
|
||||||
@ -62,7 +59,7 @@ pub struct QuoteElem {
|
|||||||
/// Ich bin ein Berliner.
|
/// Ich bin ein Berliner.
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
block: bool,
|
pub block: bool,
|
||||||
|
|
||||||
/// Whether double quotes should be added around this quote.
|
/// Whether double quotes should be added around this quote.
|
||||||
///
|
///
|
||||||
@ -88,7 +85,7 @@ pub struct QuoteElem {
|
|||||||
/// translate the quote:
|
/// translate the quote:
|
||||||
/// #quote[I am a Berliner.]
|
/// #quote[I am a Berliner.]
|
||||||
/// ```
|
/// ```
|
||||||
quotes: Smart<bool>,
|
pub quotes: Smart<bool>,
|
||||||
|
|
||||||
/// The attribution of this quote, usually the author or source. Can be a
|
/// The attribution of this quote, usually the author or source. Can be a
|
||||||
/// label pointing to a bibliography entry or any content. By default only
|
/// label pointing to a bibliography entry or any content. By default only
|
||||||
@ -105,7 +102,7 @@ pub struct QuoteElem {
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #quote(
|
/// #quote(
|
||||||
/// attribution: link("https://typst.app/home")[typst.com]
|
/// attribution: link("https://typst.app/home")[typst.app]
|
||||||
/// )[
|
/// )[
|
||||||
/// Compose papers faster
|
/// Compose papers faster
|
||||||
/// ]
|
/// ]
|
||||||
@ -123,17 +120,36 @@ pub struct QuoteElem {
|
|||||||
///
|
///
|
||||||
/// #bibliography("works.bib", style: "apa")
|
/// #bibliography("works.bib", style: "apa")
|
||||||
/// ```
|
/// ```
|
||||||
attribution: Option<Attribution>,
|
pub attribution: Option<Attribution>,
|
||||||
|
|
||||||
/// The quote.
|
/// The quote.
|
||||||
#[required]
|
#[required]
|
||||||
body: Content,
|
pub body: Content,
|
||||||
|
|
||||||
/// The nesting depth.
|
/// The nesting depth.
|
||||||
#[internal]
|
#[internal]
|
||||||
#[fold]
|
#[fold]
|
||||||
#[ghost]
|
#[ghost]
|
||||||
depth: Depth,
|
pub depth: Depth,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QuoteElem {
|
||||||
|
/// Quotes the body content with the appropriate quotes based on the current
|
||||||
|
/// styles and surroundings.
|
||||||
|
pub fn quoted(body: Content, styles: StyleChain<'_>) -> Content {
|
||||||
|
let quotes = SmartQuotes::get_in(styles);
|
||||||
|
|
||||||
|
// Alternate between single and double quotes.
|
||||||
|
let Depth(depth) = styles.get(QuoteElem::depth);
|
||||||
|
let double = depth % 2 == 0;
|
||||||
|
|
||||||
|
Content::sequence([
|
||||||
|
TextElem::packed(quotes.open(double)),
|
||||||
|
body,
|
||||||
|
TextElem::packed(quotes.close(double)),
|
||||||
|
])
|
||||||
|
.set(QuoteElem::depth, Depth(1))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Attribution for a [quote](QuoteElem).
|
/// Attribution for a [quote](QuoteElem).
|
||||||
@ -143,6 +159,23 @@ pub enum Attribution {
|
|||||||
Label(Label),
|
Label(Label),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Attribution {
|
||||||
|
/// Realize as an em dash followed by text or a citation.
|
||||||
|
pub fn realize(&self, span: Span) -> Content {
|
||||||
|
Content::sequence([
|
||||||
|
TextElem::packed('—'),
|
||||||
|
SpaceElem::shared().clone(),
|
||||||
|
match self {
|
||||||
|
Attribution::Content(content) => content.clone(),
|
||||||
|
Attribution::Label(label) => CiteElem::new(*label)
|
||||||
|
.with_form(Some(CitationForm::Prose))
|
||||||
|
.pack()
|
||||||
|
.spanned(span),
|
||||||
|
},
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cast! {
|
cast! {
|
||||||
Attribution,
|
Attribution,
|
||||||
self => match self {
|
self => match self {
|
||||||
@ -153,96 +186,6 @@ cast! {
|
|||||||
label: Label => Self::Label(label),
|
label: Label => Self::Label(label),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<QuoteElem> {
|
|
||||||
#[typst_macros::time(name = "quote", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let mut realized = self.body.clone();
|
|
||||||
let block = self.block.get(styles);
|
|
||||||
let html = styles.get(TargetElem::target).is_html();
|
|
||||||
|
|
||||||
if self.quotes.get(styles).unwrap_or(!block) {
|
|
||||||
let quotes = SmartQuotes::get(
|
|
||||||
styles.get_ref(SmartQuoteElem::quotes),
|
|
||||||
styles.get(TextElem::lang),
|
|
||||||
styles.get(TextElem::region),
|
|
||||||
styles.get(SmartQuoteElem::alternative),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Alternate between single and double quotes.
|
|
||||||
let Depth(depth) = styles.get(QuoteElem::depth);
|
|
||||||
let double = depth % 2 == 0;
|
|
||||||
|
|
||||||
if !html {
|
|
||||||
// Add zero-width weak spacing to make the quotes "sticky".
|
|
||||||
let hole = HElem::hole().pack();
|
|
||||||
realized = Content::sequence([hole.clone(), realized, hole]);
|
|
||||||
}
|
|
||||||
realized = Content::sequence([
|
|
||||||
TextElem::packed(quotes.open(double)),
|
|
||||||
realized,
|
|
||||||
TextElem::packed(quotes.close(double)),
|
|
||||||
])
|
|
||||||
.set(QuoteElem::depth, Depth(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
let attribution = self.attribution.get_ref(styles);
|
|
||||||
|
|
||||||
if block {
|
|
||||||
realized = if html {
|
|
||||||
let mut elem = HtmlElem::new(tag::blockquote).with_body(Some(realized));
|
|
||||||
if let Some(Attribution::Content(attribution)) = attribution {
|
|
||||||
if let Some(link) = attribution.to_packed::<LinkElem>() {
|
|
||||||
if let LinkTarget::Dest(Destination::Url(url)) = &link.dest {
|
|
||||||
elem = elem.with_attr(attr::cite, url.clone().into_inner());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
elem.pack()
|
|
||||||
} else {
|
|
||||||
BlockElem::new().with_body(Some(BlockBody::Content(realized))).pack()
|
|
||||||
}
|
|
||||||
.spanned(self.span());
|
|
||||||
|
|
||||||
if let Some(attribution) = attribution {
|
|
||||||
let attribution = match attribution {
|
|
||||||
Attribution::Content(content) => content.clone(),
|
|
||||||
Attribution::Label(label) => CiteElem::new(*label)
|
|
||||||
.with_form(Some(CitationForm::Prose))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()),
|
|
||||||
};
|
|
||||||
let attribution = Content::sequence([
|
|
||||||
TextElem::packed('—'),
|
|
||||||
SpaceElem::shared().clone(),
|
|
||||||
attribution,
|
|
||||||
]);
|
|
||||||
|
|
||||||
if html {
|
|
||||||
realized += attribution;
|
|
||||||
} else {
|
|
||||||
// Bring the attribution a bit closer to the quote.
|
|
||||||
let gap = Spacing::Rel(Em::new(0.9).into());
|
|
||||||
let v = VElem::new(gap).with_weak(true).pack();
|
|
||||||
realized += v;
|
|
||||||
realized += BlockElem::new()
|
|
||||||
.with_body(Some(BlockBody::Content(attribution)))
|
|
||||||
.pack()
|
|
||||||
.aligned(Alignment::END);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !html {
|
|
||||||
realized = PadElem::new(realized).pack();
|
|
||||||
}
|
|
||||||
} else if let Some(Attribution::Label(label)) = attribution {
|
|
||||||
realized += SpaceElem::shared().clone()
|
|
||||||
+ CiteElem::new(*label).pack().spanned(self.span());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(realized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShowSet for Packed<QuoteElem> {
|
impl ShowSet for Packed<QuoteElem> {
|
||||||
fn show_set(&self, styles: StyleChain) -> Styles {
|
fn show_set(&self, styles: StyleChain) -> Styles {
|
||||||
let mut out = Styles::new();
|
let mut out = Styles::new();
|
||||||
|
@ -5,7 +5,7 @@ use crate::diag::{bail, At, Hint, SourceResult};
|
|||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, Cast, Content, Context, Func, IntoValue, Label, NativeElement, Packed,
|
cast, elem, Cast, Content, Context, Func, IntoValue, Label, NativeElement, Packed,
|
||||||
Repr, Show, Smart, StyleChain, Synthesize,
|
Repr, Smart, StyleChain, Synthesize,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Counter, CounterKey, Locatable};
|
use crate::introspection::{Counter, CounterKey, Locatable};
|
||||||
use crate::math::EquationElem;
|
use crate::math::EquationElem;
|
||||||
@ -134,7 +134,7 @@ use crate::text::TextElem;
|
|||||||
/// In @beginning we prove @pythagoras.
|
/// In @beginning we prove @pythagoras.
|
||||||
/// $ a^2 + b^2 = c^2 $ <pythagoras>
|
/// $ a^2 + b^2 = c^2 $ <pythagoras>
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(title = "Reference", Synthesize, Locatable, Show)]
|
#[elem(title = "Reference", Synthesize, Locatable)]
|
||||||
pub struct RefElem {
|
pub struct RefElem {
|
||||||
/// The target label that should be referenced.
|
/// The target label that should be referenced.
|
||||||
///
|
///
|
||||||
@ -220,9 +220,13 @@ impl Synthesize for Packed<RefElem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<RefElem> {
|
impl Packed<RefElem> {
|
||||||
#[typst_macros::time(name = "ref", span = self.span())]
|
/// Realize as a linked, textual reference.
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
pub fn realize(
|
||||||
|
&self,
|
||||||
|
engine: &mut Engine,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<Content> {
|
||||||
let elem = engine.introspector.query_label(self.target);
|
let elem = engine.introspector.query_label(self.target);
|
||||||
let span = self.span();
|
let span = self.span();
|
||||||
|
|
||||||
@ -242,7 +246,7 @@ impl Show for Packed<RefElem> {
|
|||||||
.at(span)?;
|
.at(span)?;
|
||||||
let supplement = engine.introspector.page_supplement(loc);
|
let supplement = engine.introspector.page_supplement(loc);
|
||||||
|
|
||||||
return show_reference(
|
return realize_reference(
|
||||||
self,
|
self,
|
||||||
engine,
|
engine,
|
||||||
styles,
|
styles,
|
||||||
@ -306,7 +310,7 @@ impl Show for Packed<RefElem> {
|
|||||||
))
|
))
|
||||||
.at(span)?;
|
.at(span)?;
|
||||||
|
|
||||||
show_reference(
|
realize_reference(
|
||||||
self,
|
self,
|
||||||
engine,
|
engine,
|
||||||
styles,
|
styles,
|
||||||
@ -319,7 +323,7 @@ impl Show for Packed<RefElem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Show a reference.
|
/// Show a reference.
|
||||||
fn show_reference(
|
fn realize_reference(
|
||||||
reference: &Packed<RefElem>,
|
reference: &Packed<RefElem>,
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
|
@ -1,10 +1,4 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, Content};
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
|
||||||
elem, Content, NativeElement, Packed, Show, StyleChain, TargetElem,
|
|
||||||
};
|
|
||||||
use crate::html::{tag, HtmlElem};
|
|
||||||
use crate::text::{TextElem, WeightDelta};
|
|
||||||
|
|
||||||
/// Strongly emphasizes content by increasing the font weight.
|
/// Strongly emphasizes content by increasing the font weight.
|
||||||
///
|
///
|
||||||
@ -24,7 +18,7 @@ use crate::text::{TextElem, WeightDelta};
|
|||||||
/// simply enclose it in stars/asterisks (`*`). Note that this only works at
|
/// simply enclose it in stars/asterisks (`*`). Note that this only works at
|
||||||
/// word boundaries. To strongly emphasize part of a word, you have to use the
|
/// word boundaries. To strongly emphasize part of a word, you have to use the
|
||||||
/// function.
|
/// function.
|
||||||
#[elem(title = "Strong Emphasis", keywords = ["bold", "weight"], Show)]
|
#[elem(title = "Strong Emphasis", keywords = ["bold", "weight"])]
|
||||||
pub struct StrongElem {
|
pub struct StrongElem {
|
||||||
/// The delta to apply on the font weight.
|
/// The delta to apply on the font weight.
|
||||||
///
|
///
|
||||||
@ -39,18 +33,3 @@ pub struct StrongElem {
|
|||||||
#[required]
|
#[required]
|
||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<StrongElem> {
|
|
||||||
#[typst_macros::time(name = "strong", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let body = self.body.clone();
|
|
||||||
Ok(if styles.get(TargetElem::target).is_html() {
|
|
||||||
HtmlElem::new(tag::strong)
|
|
||||||
.with_body(Some(body))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span())
|
|
||||||
} else {
|
|
||||||
body.set(TextElem::delta, WeightDelta(self.delta.get(styles)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -3,19 +3,11 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use typst_utils::NonZeroExt;
|
use typst_utils::NonZeroExt;
|
||||||
|
|
||||||
use crate::diag::{bail, HintedStrResult, HintedString, SourceResult};
|
use crate::diag::{bail, HintedStrResult, HintedString};
|
||||||
use crate::engine::Engine;
|
use crate::foundations::{cast, elem, scope, Content, Packed, Smart};
|
||||||
use crate::foundations::{
|
|
||||||
cast, elem, scope, Content, NativeElement, Packed, Show, Smart, StyleChain,
|
|
||||||
TargetElem,
|
|
||||||
};
|
|
||||||
use crate::html::{attr, tag, HtmlAttrs, HtmlElem, HtmlTag};
|
|
||||||
use crate::introspection::Locator;
|
|
||||||
use crate::layout::grid::resolve::{table_to_cellgrid, Cell, CellGrid, Entry};
|
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
show_grid_cell, Abs, Alignment, BlockElem, Celled, GridCell, GridFooter, GridHLine,
|
Abs, Alignment, Celled, GridCell, GridFooter, GridHLine, GridHeader, GridVLine,
|
||||||
GridHeader, GridVLine, Length, OuterHAlignment, OuterVAlignment, Rel, Sides,
|
Length, OuterHAlignment, OuterVAlignment, Rel, Sides, TrackSizings,
|
||||||
TrackSizings,
|
|
||||||
};
|
};
|
||||||
use crate::model::Figurable;
|
use crate::model::Figurable;
|
||||||
use crate::text::LocalName;
|
use crate::text::LocalName;
|
||||||
@ -121,7 +113,7 @@ use crate::visualize::{Paint, Stroke};
|
|||||||
/// [Robert], b, a, b,
|
/// [Robert], b, a, b,
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(scope, Show, LocalName, Figurable)]
|
#[elem(scope, LocalName, Figurable)]
|
||||||
pub struct TableElem {
|
pub struct TableElem {
|
||||||
/// The column sizes. See the [grid documentation]($grid) for more
|
/// The column sizes. See the [grid documentation]($grid) for more
|
||||||
/// information on track sizing.
|
/// information on track sizing.
|
||||||
@ -255,113 +247,6 @@ impl TableElem {
|
|||||||
type TableFooter;
|
type TableFooter;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_cell_html(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content {
|
|
||||||
let cell = cell.body.clone();
|
|
||||||
let Some(cell) = cell.to_packed::<TableCell>() else { return cell };
|
|
||||||
let mut attrs = HtmlAttrs::default();
|
|
||||||
let span = |n: NonZeroUsize| (n != NonZeroUsize::MIN).then(|| n.to_string());
|
|
||||||
if let Some(colspan) = span(cell.colspan.get(styles)) {
|
|
||||||
attrs.push(attr::colspan, colspan);
|
|
||||||
}
|
|
||||||
if let Some(rowspan) = span(cell.rowspan.get(styles)) {
|
|
||||||
attrs.push(attr::rowspan, rowspan);
|
|
||||||
}
|
|
||||||
HtmlElem::new(tag)
|
|
||||||
.with_body(Some(cell.body.clone()))
|
|
||||||
.with_attrs(attrs)
|
|
||||||
.pack()
|
|
||||||
.spanned(cell.span())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_cellgrid_html(grid: CellGrid, styles: StyleChain) -> Content {
|
|
||||||
let elem = |tag, body| HtmlElem::new(tag).with_body(Some(body)).pack();
|
|
||||||
let mut rows: Vec<_> = grid.entries.chunks(grid.non_gutter_column_count()).collect();
|
|
||||||
|
|
||||||
let tr = |tag, row: &[Entry]| {
|
|
||||||
let row = row
|
|
||||||
.iter()
|
|
||||||
.flat_map(|entry| entry.as_cell())
|
|
||||||
.map(|cell| show_cell_html(tag, cell, styles));
|
|
||||||
elem(tag::tr, Content::sequence(row))
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO(subfooters): similarly to headers, take consecutive footers from
|
|
||||||
// the end for 'tfoot'.
|
|
||||||
let footer = grid.footer.map(|ft| {
|
|
||||||
let rows = rows.drain(ft.start..);
|
|
||||||
elem(tag::tfoot, Content::sequence(rows.map(|row| tr(tag::td, row))))
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store all consecutive headers at the start in 'thead'. All remaining
|
|
||||||
// headers are just 'th' rows across the table body.
|
|
||||||
let mut consecutive_header_end = 0;
|
|
||||||
let first_mid_table_header = grid
|
|
||||||
.headers
|
|
||||||
.iter()
|
|
||||||
.take_while(|hd| {
|
|
||||||
let is_consecutive = hd.range.start == consecutive_header_end;
|
|
||||||
consecutive_header_end = hd.range.end;
|
|
||||||
|
|
||||||
is_consecutive
|
|
||||||
})
|
|
||||||
.count();
|
|
||||||
|
|
||||||
let (y_offset, header) = if first_mid_table_header > 0 {
|
|
||||||
let removed_header_rows =
|
|
||||||
grid.headers.get(first_mid_table_header - 1).unwrap().range.end;
|
|
||||||
let rows = rows.drain(..removed_header_rows);
|
|
||||||
|
|
||||||
(
|
|
||||||
removed_header_rows,
|
|
||||||
Some(elem(tag::thead, Content::sequence(rows.map(|row| tr(tag::th, row))))),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
(0, None)
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Consider improving accessibility properties of multi-level headers
|
|
||||||
// inside tables in the future, e.g. indicating which columns they are
|
|
||||||
// relative to and so on. See also:
|
|
||||||
// https://www.w3.org/WAI/tutorials/tables/multi-level/
|
|
||||||
let mut next_header = first_mid_table_header;
|
|
||||||
let mut body =
|
|
||||||
Content::sequence(rows.into_iter().enumerate().map(|(relative_y, row)| {
|
|
||||||
let y = relative_y + y_offset;
|
|
||||||
if let Some(current_header) =
|
|
||||||
grid.headers.get(next_header).filter(|h| h.range.contains(&y))
|
|
||||||
{
|
|
||||||
if y + 1 == current_header.range.end {
|
|
||||||
next_header += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr(tag::th, row)
|
|
||||||
} else {
|
|
||||||
tr(tag::td, row)
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
if header.is_some() || footer.is_some() {
|
|
||||||
body = elem(tag::tbody, body);
|
|
||||||
}
|
|
||||||
|
|
||||||
let content = header.into_iter().chain(core::iter::once(body)).chain(footer);
|
|
||||||
elem(tag::table, Content::sequence(content))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Show for Packed<TableElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(if styles.get(TargetElem::target).is_html() {
|
|
||||||
// TODO: This is a hack, it is not clear whether the locator is actually used by HTML.
|
|
||||||
// How can we find out whether locator is actually used?
|
|
||||||
let locator = Locator::root();
|
|
||||||
show_cellgrid_html(table_to_cellgrid(self, engine, locator, styles)?, styles)
|
|
||||||
} else {
|
|
||||||
BlockElem::multi_layouter(self.clone(), engine.routines.layout_table).pack()
|
|
||||||
}
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LocalName for Packed<TableElem> {
|
impl LocalName for Packed<TableElem> {
|
||||||
const KEY: &'static str = "table";
|
const KEY: &'static str = "table";
|
||||||
}
|
}
|
||||||
@ -761,7 +646,7 @@ pub struct TableVLine {
|
|||||||
/// [Vikram], [49], [Perseverance],
|
/// [Vikram], [49], [Perseverance],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(name = "cell", title = "Table Cell", Show)]
|
#[elem(name = "cell", title = "Table Cell")]
|
||||||
pub struct TableCell {
|
pub struct TableCell {
|
||||||
/// The cell's body.
|
/// The cell's body.
|
||||||
#[required]
|
#[required]
|
||||||
@ -808,12 +693,6 @@ cast! {
|
|||||||
v: Content => v.into(),
|
v: Content => v.into(),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<TableCell> {
|
|
||||||
fn show(&self, _engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
show_grid_cell(self.body.clone(), self.inset.get(styles), self.align.get(styles))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Packed<TableCell> {
|
impl Default for Packed<TableCell> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Packed::new(
|
Packed::new(
|
||||||
|
@ -1,15 +1,9 @@
|
|||||||
use typst_utils::{Get, Numeric};
|
use crate::diag::bail;
|
||||||
|
|
||||||
use crate::diag::{bail, SourceResult};
|
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, scope, Array, Content, NativeElement, Packed, Show, Smart, StyleChain,
|
cast, elem, scope, Array, Content, NativeElement, Packed, Smart, Styles,
|
||||||
Styles, TargetElem,
|
|
||||||
};
|
};
|
||||||
use crate::html::{tag, HtmlElem};
|
use crate::layout::{Em, HElem, Length};
|
||||||
use crate::layout::{Em, HElem, Length, Sides, StackChild, StackElem, VElem};
|
use crate::model::{ListItemLike, ListLike};
|
||||||
use crate::model::{ListItemLike, ListLike, ParElem, ParbreakElem};
|
|
||||||
use crate::text::TextElem;
|
|
||||||
|
|
||||||
/// A list of terms and their descriptions.
|
/// A list of terms and their descriptions.
|
||||||
///
|
///
|
||||||
@ -27,7 +21,7 @@ use crate::text::TextElem;
|
|||||||
/// # Syntax
|
/// # Syntax
|
||||||
/// This function also has dedicated syntax: Starting a line with a slash,
|
/// This function also has dedicated syntax: Starting a line with a slash,
|
||||||
/// followed by a term, a colon and a description creates a term list item.
|
/// followed by a term, a colon and a description creates a term list item.
|
||||||
#[elem(scope, title = "Term List", Show)]
|
#[elem(scope, title = "Term List")]
|
||||||
pub struct TermsElem {
|
pub struct TermsElem {
|
||||||
/// Defines the default [spacing]($terms.spacing) of the term list. If it is
|
/// Defines the default [spacing]($terms.spacing) of the term list. If it is
|
||||||
/// `{false}`, the items are spaced apart with
|
/// `{false}`, the items are spaced apart with
|
||||||
@ -117,94 +111,6 @@ impl TermsElem {
|
|||||||
type TermItem;
|
type TermItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<TermsElem> {
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let span = self.span();
|
|
||||||
let tight = self.tight.get(styles);
|
|
||||||
|
|
||||||
if styles.get(TargetElem::target).is_html() {
|
|
||||||
return Ok(HtmlElem::new(tag::dl)
|
|
||||||
.with_body(Some(Content::sequence(self.children.iter().flat_map(
|
|
||||||
|item| {
|
|
||||||
// Text in wide term lists shall always turn into paragraphs.
|
|
||||||
let mut description = item.description.clone();
|
|
||||||
if !tight {
|
|
||||||
description += ParbreakElem::shared();
|
|
||||||
}
|
|
||||||
|
|
||||||
[
|
|
||||||
HtmlElem::new(tag::dt)
|
|
||||||
.with_body(Some(item.term.clone()))
|
|
||||||
.pack()
|
|
||||||
.spanned(item.term.span()),
|
|
||||||
HtmlElem::new(tag::dd)
|
|
||||||
.with_body(Some(description))
|
|
||||||
.pack()
|
|
||||||
.spanned(item.description.span()),
|
|
||||||
]
|
|
||||||
},
|
|
||||||
))))
|
|
||||||
.pack());
|
|
||||||
}
|
|
||||||
|
|
||||||
let separator = self.separator.get_ref(styles);
|
|
||||||
let indent = self.indent.get(styles);
|
|
||||||
let hanging_indent = self.hanging_indent.get(styles);
|
|
||||||
let gutter = self.spacing.get(styles).unwrap_or_else(|| {
|
|
||||||
if tight {
|
|
||||||
styles.get(ParElem::leading)
|
|
||||||
} else {
|
|
||||||
styles.get(ParElem::spacing)
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let pad = hanging_indent + indent;
|
|
||||||
let unpad = (!hanging_indent.is_zero())
|
|
||||||
.then(|| HElem::new((-hanging_indent).into()).pack().spanned(span));
|
|
||||||
|
|
||||||
let mut children = vec![];
|
|
||||||
for child in self.children.iter() {
|
|
||||||
let mut seq = vec![];
|
|
||||||
seq.extend(unpad.clone());
|
|
||||||
seq.push(child.term.clone().strong());
|
|
||||||
seq.push((*separator).clone());
|
|
||||||
seq.push(child.description.clone());
|
|
||||||
|
|
||||||
// Text in wide term lists shall always turn into paragraphs.
|
|
||||||
if !tight {
|
|
||||||
seq.push(ParbreakElem::shared().clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
children.push(StackChild::Block(Content::sequence(seq)));
|
|
||||||
}
|
|
||||||
|
|
||||||
let padding =
|
|
||||||
Sides::default().with(styles.resolve(TextElem::dir).start(), pad.into());
|
|
||||||
|
|
||||||
let mut realized = StackElem::new(children)
|
|
||||||
.with_spacing(Some(gutter.into()))
|
|
||||||
.pack()
|
|
||||||
.spanned(span)
|
|
||||||
.padded(padding)
|
|
||||||
.set(TermsElem::within, true);
|
|
||||||
|
|
||||||
if tight {
|
|
||||||
let spacing = self
|
|
||||||
.spacing
|
|
||||||
.get(styles)
|
|
||||||
.unwrap_or_else(|| styles.get(ParElem::leading));
|
|
||||||
let v = VElem::new(spacing.into())
|
|
||||||
.with_weak(true)
|
|
||||||
.with_attach(true)
|
|
||||||
.pack()
|
|
||||||
.spanned(span);
|
|
||||||
realized = v + realized;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(realized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A term list item.
|
/// A term list item.
|
||||||
#[elem(name = "item", title = "Term List Item")]
|
#[elem(name = "item", title = "Term List Item")]
|
||||||
pub struct TermItem {
|
pub struct TermItem {
|
||||||
|
@ -1,12 +1,8 @@
|
|||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use typst_library::foundations::Target;
|
|
||||||
use typst_syntax::Spanned;
|
use typst_syntax::Spanned;
|
||||||
|
|
||||||
use crate::diag::{warning, At, SourceResult};
|
use crate::diag::At;
|
||||||
use crate::engine::Engine;
|
use crate::foundations::{elem, Bytes, Cast, Derived};
|
||||||
use crate::foundations::{
|
|
||||||
elem, Bytes, Cast, Content, Derived, Packed, Show, StyleChain, TargetElem,
|
|
||||||
};
|
|
||||||
use crate::introspection::Locatable;
|
use crate::introspection::Locatable;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
@ -33,7 +29,7 @@ use crate::World;
|
|||||||
/// - This element is ignored if exporting to a format other than PDF.
|
/// - This element is ignored if exporting to a format other than PDF.
|
||||||
/// - File embeddings are not currently supported for PDF/A-2, even if the
|
/// - File embeddings are not currently supported for PDF/A-2, even if the
|
||||||
/// embedded file conforms to PDF/A-1 or PDF/A-2.
|
/// embedded file conforms to PDF/A-1 or PDF/A-2.
|
||||||
#[elem(Show, Locatable)]
|
#[elem(Locatable)]
|
||||||
pub struct EmbedElem {
|
pub struct EmbedElem {
|
||||||
/// The [path]($syntax/#paths) of the file to be embedded.
|
/// The [path]($syntax/#paths) of the file to be embedded.
|
||||||
///
|
///
|
||||||
@ -77,17 +73,6 @@ pub struct EmbedElem {
|
|||||||
pub description: Option<EcoString>,
|
pub description: Option<EcoString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<EmbedElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
if styles.get(TargetElem::target) == Target::Html {
|
|
||||||
engine
|
|
||||||
.sink
|
|
||||||
.warn(warning!(self.span(), "embed was ignored during HTML export"));
|
|
||||||
}
|
|
||||||
Ok(Content::empty())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The relationship of an embedded file with the document.
|
/// The relationship of an embedded file with the document.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||||
pub enum EmbeddedFileRelationship {
|
pub enum EmbeddedFileRelationship {
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
#![allow(unused)]
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
|
|
||||||
use comemo::{Tracked, TrackedMut};
|
use comemo::{Tracked, TrackedMut};
|
||||||
use typst_syntax::{Span, SyntaxMode};
|
use typst_syntax::{Span, SyntaxMode};
|
||||||
@ -10,20 +8,12 @@ use typst_utils::LazyHash;
|
|||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::engine::{Engine, Route, Sink, Traced};
|
use crate::engine::{Engine, Route, Sink, Traced};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
Args, Cast, Closure, Content, Context, Func, Packed, Scope, StyleChain, Styles, Value,
|
Args, Closure, Content, Context, Func, Module, NativeRuleMap, Scope, StyleChain,
|
||||||
|
Styles, Value,
|
||||||
};
|
};
|
||||||
use crate::introspection::{Introspector, Locator, SplitLocator};
|
use crate::introspection::{Introspector, Locator, SplitLocator};
|
||||||
use crate::layout::{
|
use crate::layout::{Frame, Region};
|
||||||
Abs, BoxElem, ColumnsElem, Fragment, Frame, GridElem, InlineItem, MoveElem, PadElem,
|
use crate::model::DocumentInfo;
|
||||||
PagedDocument, Region, Regions, Rel, RepeatElem, RotateElem, ScaleElem, Size,
|
|
||||||
SkewElem, StackElem,
|
|
||||||
};
|
|
||||||
use crate::math::EquationElem;
|
|
||||||
use crate::model::{DocumentInfo, EnumElem, ListElem, TableElem};
|
|
||||||
use crate::visualize::{
|
|
||||||
CircleElem, CurveElem, EllipseElem, ImageElem, LineElem, PathElem, PolygonElem,
|
|
||||||
RectElem, SquareElem,
|
|
||||||
};
|
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
/// Defines the `Routines` struct.
|
/// Defines the `Routines` struct.
|
||||||
@ -38,6 +28,8 @@ macro_rules! routines {
|
|||||||
/// This is essentially dynamic linking and done to allow for crate
|
/// This is essentially dynamic linking and done to allow for crate
|
||||||
/// splitting.
|
/// splitting.
|
||||||
pub struct Routines {
|
pub struct Routines {
|
||||||
|
/// Native show rules.
|
||||||
|
pub rules: NativeRuleMap,
|
||||||
$(
|
$(
|
||||||
$(#[$attr])*
|
$(#[$attr])*
|
||||||
pub $name: $(for<$($time),*>)? fn ($($args)*) -> $ret
|
pub $name: $(for<$($time),*>)? fn ($($args)*) -> $ret
|
||||||
@ -47,6 +39,12 @@ macro_rules! routines {
|
|||||||
impl Hash for Routines {
|
impl Hash for Routines {
|
||||||
fn hash<H: Hasher>(&self, _: &mut H) {}
|
fn hash<H: Hasher>(&self, _: &mut H) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for Routines {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
f.pad("Routines(..)")
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,15 +84,6 @@ routines! {
|
|||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> SourceResult<Vec<Pair<'a>>>
|
) -> SourceResult<Vec<Pair<'a>>>
|
||||||
|
|
||||||
/// Lays out content into multiple regions.
|
|
||||||
fn layout_fragment(
|
|
||||||
engine: &mut Engine,
|
|
||||||
content: &Content,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment>
|
|
||||||
|
|
||||||
/// Lays out content into a single region, producing a single frame.
|
/// Lays out content into a single region, producing a single frame.
|
||||||
fn layout_frame(
|
fn layout_frame(
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
@ -104,212 +93,8 @@ routines! {
|
|||||||
region: Region,
|
region: Region,
|
||||||
) -> SourceResult<Frame>
|
) -> SourceResult<Frame>
|
||||||
|
|
||||||
/// Lays out a [`ListElem`].
|
/// Constructs the `html` module.
|
||||||
fn layout_list(
|
fn html_module() -> Module
|
||||||
elem: &Packed<ListElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment>
|
|
||||||
|
|
||||||
/// Lays out an [`EnumElem`].
|
|
||||||
fn layout_enum(
|
|
||||||
elem: &Packed<EnumElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment>
|
|
||||||
|
|
||||||
/// Lays out a [`GridElem`].
|
|
||||||
fn layout_grid(
|
|
||||||
elem: &Packed<GridElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment>
|
|
||||||
|
|
||||||
/// Lays out a [`TableElem`].
|
|
||||||
fn layout_table(
|
|
||||||
elem: &Packed<TableElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment>
|
|
||||||
|
|
||||||
/// Lays out a [`StackElem`].
|
|
||||||
fn layout_stack(
|
|
||||||
elem: &Packed<StackElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment>
|
|
||||||
|
|
||||||
/// Lays out a [`ColumnsElem`].
|
|
||||||
fn layout_columns(
|
|
||||||
elem: &Packed<ColumnsElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment>
|
|
||||||
|
|
||||||
/// Lays out a [`MoveElem`].
|
|
||||||
fn layout_move(
|
|
||||||
elem: &Packed<MoveElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`RotateElem`].
|
|
||||||
fn layout_rotate(
|
|
||||||
elem: &Packed<RotateElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`ScaleElem`].
|
|
||||||
fn layout_scale(
|
|
||||||
elem: &Packed<ScaleElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`SkewElem`].
|
|
||||||
fn layout_skew(
|
|
||||||
elem: &Packed<SkewElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`RepeatElem`].
|
|
||||||
fn layout_repeat(
|
|
||||||
elem: &Packed<RepeatElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`PadElem`].
|
|
||||||
fn layout_pad(
|
|
||||||
elem: &Packed<PadElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment>
|
|
||||||
|
|
||||||
/// Lays out a [`LineElem`].
|
|
||||||
fn layout_line(
|
|
||||||
elem: &Packed<LineElem>,
|
|
||||||
_: &mut Engine,
|
|
||||||
_: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`CurveElem`].
|
|
||||||
fn layout_curve(
|
|
||||||
elem: &Packed<CurveElem>,
|
|
||||||
_: &mut Engine,
|
|
||||||
_: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`PathElem`].
|
|
||||||
fn layout_path(
|
|
||||||
elem: &Packed<PathElem>,
|
|
||||||
_: &mut Engine,
|
|
||||||
_: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`PolygonElem`].
|
|
||||||
fn layout_polygon(
|
|
||||||
elem: &Packed<PolygonElem>,
|
|
||||||
_: &mut Engine,
|
|
||||||
_: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`RectElem`].
|
|
||||||
fn layout_rect(
|
|
||||||
elem: &Packed<RectElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`SquareElem`].
|
|
||||||
fn layout_square(
|
|
||||||
elem: &Packed<SquareElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`EllipseElem`].
|
|
||||||
fn layout_ellipse(
|
|
||||||
elem: &Packed<EllipseElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out a [`CircleElem`].
|
|
||||||
fn layout_circle(
|
|
||||||
elem: &Packed<CircleElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out an [`ImageElem`].
|
|
||||||
fn layout_image(
|
|
||||||
elem: &Packed<ImageElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Region,
|
|
||||||
) -> SourceResult<Frame>
|
|
||||||
|
|
||||||
/// Lays out an [`EquationElem`] in a paragraph.
|
|
||||||
fn layout_equation_inline(
|
|
||||||
elem: &Packed<EquationElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
region: Size,
|
|
||||||
) -> SourceResult<Vec<InlineItem>>
|
|
||||||
|
|
||||||
/// Lays out an [`EquationElem`] in a flow.
|
|
||||||
fn layout_equation_block(
|
|
||||||
elem: &Packed<EquationElem>,
|
|
||||||
engine: &mut Engine,
|
|
||||||
locator: Locator,
|
|
||||||
styles: StyleChain,
|
|
||||||
regions: Regions,
|
|
||||||
) -> SourceResult<Fragment>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines what kind of realization we are performing.
|
/// Defines what kind of realization we are performing.
|
||||||
|
@ -1,13 +1,6 @@
|
|||||||
use smallvec::smallvec;
|
use crate::foundations::{elem, Content, Smart};
|
||||||
|
|
||||||
use crate::diag::SourceResult;
|
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
|
||||||
elem, Content, NativeElement, Packed, Show, Smart, StyleChain, TargetElem,
|
|
||||||
};
|
|
||||||
use crate::html::{attr, tag, HtmlElem};
|
|
||||||
use crate::layout::{Abs, Corners, Length, Rel, Sides};
|
use crate::layout::{Abs, Corners, Length, Rel, Sides};
|
||||||
use crate::text::{BottomEdge, BottomEdgeMetric, TextElem, TopEdge, TopEdgeMetric};
|
use crate::text::{BottomEdge, BottomEdgeMetric, TopEdge, TopEdgeMetric};
|
||||||
use crate::visualize::{Color, FixedStroke, Paint, Stroke};
|
use crate::visualize::{Color, FixedStroke, Paint, Stroke};
|
||||||
|
|
||||||
/// Underlines text.
|
/// Underlines text.
|
||||||
@ -16,7 +9,7 @@ use crate::visualize::{Color, FixedStroke, Paint, Stroke};
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// This is #underline[important].
|
/// This is #underline[important].
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct UnderlineElem {
|
pub struct UnderlineElem {
|
||||||
/// How to [stroke] the line.
|
/// How to [stroke] the line.
|
||||||
///
|
///
|
||||||
@ -78,41 +71,13 @@ pub struct UnderlineElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<UnderlineElem> {
|
|
||||||
#[typst_macros::time(name = "underline", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
if styles.get(TargetElem::target).is_html() {
|
|
||||||
// Note: In modern HTML, `<u>` is not the underline element, but
|
|
||||||
// rather an "Unarticulated Annotation" element (see HTML spec
|
|
||||||
// 4.5.22). Using `text-decoration` instead is recommended by MDN.
|
|
||||||
return Ok(HtmlElem::new(tag::span)
|
|
||||||
.with_attr(attr::style, "text-decoration: underline")
|
|
||||||
.with_body(Some(self.body.clone()))
|
|
||||||
.pack());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.body.clone().set(
|
|
||||||
TextElem::deco,
|
|
||||||
smallvec![Decoration {
|
|
||||||
line: DecoLine::Underline {
|
|
||||||
stroke: self.stroke.resolve(styles).unwrap_or_default(),
|
|
||||||
offset: self.offset.resolve(styles),
|
|
||||||
evade: self.evade.get(styles),
|
|
||||||
background: self.background.get(styles),
|
|
||||||
},
|
|
||||||
extent: self.extent.resolve(styles),
|
|
||||||
}],
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Adds a line over text.
|
/// Adds a line over text.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #overline[A line over text.]
|
/// #overline[A line over text.]
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct OverlineElem {
|
pub struct OverlineElem {
|
||||||
/// How to [stroke] the line.
|
/// How to [stroke] the line.
|
||||||
///
|
///
|
||||||
@ -180,38 +145,13 @@ pub struct OverlineElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<OverlineElem> {
|
|
||||||
#[typst_macros::time(name = "overline", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
if styles.get(TargetElem::target).is_html() {
|
|
||||||
return Ok(HtmlElem::new(tag::span)
|
|
||||||
.with_attr(attr::style, "text-decoration: overline")
|
|
||||||
.with_body(Some(self.body.clone()))
|
|
||||||
.pack());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.body.clone().set(
|
|
||||||
TextElem::deco,
|
|
||||||
smallvec![Decoration {
|
|
||||||
line: DecoLine::Overline {
|
|
||||||
stroke: self.stroke.resolve(styles).unwrap_or_default(),
|
|
||||||
offset: self.offset.resolve(styles),
|
|
||||||
evade: self.evade.get(styles),
|
|
||||||
background: self.background.get(styles),
|
|
||||||
},
|
|
||||||
extent: self.extent.resolve(styles),
|
|
||||||
}],
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Strikes through text.
|
/// Strikes through text.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```example
|
/// ```example
|
||||||
/// This is #strike[not] relevant.
|
/// This is #strike[not] relevant.
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(title = "Strikethrough", Show)]
|
#[elem(title = "Strikethrough")]
|
||||||
pub struct StrikeElem {
|
pub struct StrikeElem {
|
||||||
/// How to [stroke] the line.
|
/// How to [stroke] the line.
|
||||||
///
|
///
|
||||||
@ -264,35 +204,13 @@ pub struct StrikeElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<StrikeElem> {
|
|
||||||
#[typst_macros::time(name = "strike", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
if styles.get(TargetElem::target).is_html() {
|
|
||||||
return Ok(HtmlElem::new(tag::s).with_body(Some(self.body.clone())).pack());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.body.clone().set(
|
|
||||||
TextElem::deco,
|
|
||||||
smallvec![Decoration {
|
|
||||||
// Note that we do not support evade option for strikethrough.
|
|
||||||
line: DecoLine::Strikethrough {
|
|
||||||
stroke: self.stroke.resolve(styles).unwrap_or_default(),
|
|
||||||
offset: self.offset.resolve(styles),
|
|
||||||
background: self.background.get(styles),
|
|
||||||
},
|
|
||||||
extent: self.extent.resolve(styles),
|
|
||||||
}],
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Highlights text with a background color.
|
/// Highlights text with a background color.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
/// ```example
|
/// ```example
|
||||||
/// This is #highlight[important].
|
/// This is #highlight[important].
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct HighlightElem {
|
pub struct HighlightElem {
|
||||||
/// The color to highlight the text with.
|
/// The color to highlight the text with.
|
||||||
///
|
///
|
||||||
@ -363,35 +281,6 @@ pub struct HighlightElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<HighlightElem> {
|
|
||||||
#[typst_macros::time(name = "highlight", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
if styles.get(TargetElem::target).is_html() {
|
|
||||||
return Ok(HtmlElem::new(tag::mark)
|
|
||||||
.with_body(Some(self.body.clone()))
|
|
||||||
.pack());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(self.body.clone().set(
|
|
||||||
TextElem::deco,
|
|
||||||
smallvec![Decoration {
|
|
||||||
line: DecoLine::Highlight {
|
|
||||||
fill: self.fill.get_cloned(styles),
|
|
||||||
stroke: self
|
|
||||||
.stroke
|
|
||||||
.resolve(styles)
|
|
||||||
.unwrap_or_default()
|
|
||||||
.map(|stroke| stroke.map(Stroke::unwrap_or_default)),
|
|
||||||
top_edge: self.top_edge.get(styles),
|
|
||||||
bottom_edge: self.bottom_edge.get(styles),
|
|
||||||
radius: self.radius.resolve(styles).unwrap_or_default(),
|
|
||||||
},
|
|
||||||
extent: self.extent.resolve(styles),
|
|
||||||
}],
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A text decoration.
|
/// A text decoration.
|
||||||
///
|
///
|
||||||
/// Can be positioned over, under, or on top of text, or highlight the text with
|
/// Can be positioned over, under, or on top of text, or highlight the text with
|
||||||
|
@ -16,14 +16,13 @@ use crate::diag::{
|
|||||||
};
|
};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, scope, Bytes, Content, Derived, NativeElement, OneOrMultiple, Packed,
|
cast, elem, scope, Bytes, Content, Derived, OneOrMultiple, Packed, PlainText,
|
||||||
PlainText, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem,
|
ShowSet, Smart, StyleChain, Styles, Synthesize,
|
||||||
};
|
};
|
||||||
use crate::html::{tag, HtmlElem};
|
use crate::layout::{Em, HAlignment};
|
||||||
use crate::layout::{BlockBody, BlockElem, Em, HAlignment};
|
|
||||||
use crate::loading::{DataSource, Load};
|
use crate::loading::{DataSource, Load};
|
||||||
use crate::model::{Figurable, ParElem};
|
use crate::model::{Figurable, ParElem};
|
||||||
use crate::text::{FontFamily, FontList, LinebreakElem, LocalName, TextElem, TextSize};
|
use crate::text::{FontFamily, FontList, LocalName, TextElem, TextSize};
|
||||||
use crate::visualize::Color;
|
use crate::visualize::Color;
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
@ -78,7 +77,6 @@ use crate::World;
|
|||||||
scope,
|
scope,
|
||||||
title = "Raw Text / Code",
|
title = "Raw Text / Code",
|
||||||
Synthesize,
|
Synthesize,
|
||||||
Show,
|
|
||||||
ShowSet,
|
ShowSet,
|
||||||
LocalName,
|
LocalName,
|
||||||
Figurable,
|
Figurable,
|
||||||
@ -429,46 +427,6 @@ impl Packed<RawElem> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<RawElem> {
|
|
||||||
#[typst_macros::time(name = "raw", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let lines = self.lines.as_deref().unwrap_or_default();
|
|
||||||
|
|
||||||
let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1));
|
|
||||||
for (i, line) in lines.iter().enumerate() {
|
|
||||||
if i != 0 {
|
|
||||||
seq.push(LinebreakElem::shared().clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
seq.push(line.clone().pack());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut realized = Content::sequence(seq);
|
|
||||||
|
|
||||||
if styles.get(TargetElem::target).is_html() {
|
|
||||||
return Ok(HtmlElem::new(if self.block.get(styles) {
|
|
||||||
tag::pre
|
|
||||||
} else {
|
|
||||||
tag::code
|
|
||||||
})
|
|
||||||
.with_body(Some(realized))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.block.get(styles) {
|
|
||||||
// Align the text before inserting it into the block.
|
|
||||||
realized = realized.aligned(self.align.get(styles).into());
|
|
||||||
realized = BlockElem::new()
|
|
||||||
.with_body(Some(BlockBody::Content(realized)))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(realized)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ShowSet for Packed<RawElem> {
|
impl ShowSet for Packed<RawElem> {
|
||||||
fn show_set(&self, styles: StyleChain) -> Styles {
|
fn show_set(&self, styles: StyleChain) -> Styles {
|
||||||
let mut out = Styles::new();
|
let mut out = Styles::new();
|
||||||
@ -498,7 +456,11 @@ impl PlainText for Packed<RawElem> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The content of the raw text.
|
/// The content of the raw text.
|
||||||
#[derive(Debug, Clone, Hash, PartialEq)]
|
#[derive(Debug, Clone, Hash)]
|
||||||
|
#[allow(
|
||||||
|
clippy::derived_hash_with_manual_eq,
|
||||||
|
reason = "https://github.com/typst/typst/pull/6560#issuecomment-3045393640"
|
||||||
|
)]
|
||||||
pub enum RawContent {
|
pub enum RawContent {
|
||||||
/// From a string.
|
/// From a string.
|
||||||
Text(EcoString),
|
Text(EcoString),
|
||||||
@ -523,6 +485,22 @@ impl RawContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for RawContent {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(RawContent::Text(a), RawContent::Text(b)) => a == b,
|
||||||
|
(lines @ RawContent::Lines(_), RawContent::Text(text))
|
||||||
|
| (RawContent::Text(text), lines @ RawContent::Lines(_)) => {
|
||||||
|
*text == lines.get()
|
||||||
|
}
|
||||||
|
(RawContent::Lines(a), RawContent::Lines(b)) => Iterator::eq(
|
||||||
|
a.iter().map(|(line, _)| line),
|
||||||
|
b.iter().map(|(line, _)| line),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
cast! {
|
cast! {
|
||||||
RawContent,
|
RawContent,
|
||||||
self => self.get().into_value(),
|
self => self.get().into_value(),
|
||||||
@ -634,7 +612,7 @@ fn format_theme_error(error: syntect::LoadingError) -> LoadError {
|
|||||||
/// It allows you to access various properties of the line, such as the line
|
/// It allows you to access various properties of the line, such as the line
|
||||||
/// number, the raw non-highlighted text, the highlighted text, and whether it
|
/// number, the raw non-highlighted text, the highlighted text, and whether it
|
||||||
/// is the first or last line of the raw block.
|
/// is the first or last line of the raw block.
|
||||||
#[elem(name = "line", title = "Raw Text / Code Line", Show, PlainText)]
|
#[elem(name = "line", title = "Raw Text / Code Line", PlainText)]
|
||||||
pub struct RawLine {
|
pub struct RawLine {
|
||||||
/// The line number of the raw line inside of the raw block, starts at 1.
|
/// The line number of the raw line inside of the raw block, starts at 1.
|
||||||
#[required]
|
#[required]
|
||||||
@ -653,13 +631,6 @@ pub struct RawLine {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<RawLine> {
|
|
||||||
#[typst_macros::time(name = "raw.line", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, _styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(self.body.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PlainText for Packed<RawLine> {
|
impl PlainText for Packed<RawLine> {
|
||||||
fn plain_text(&self, text: &mut EcoString) {
|
fn plain_text(&self, text: &mut EcoString) {
|
||||||
text.push_str(&self.text);
|
text.push_str(&self.text);
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
use crate::diag::SourceResult;
|
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
|
||||||
elem, Content, NativeElement, Packed, Show, Smart, StyleChain, TargetElem,
|
|
||||||
};
|
|
||||||
use crate::html::{tag, HtmlElem};
|
|
||||||
use crate::layout::{Em, Length};
|
|
||||||
use crate::text::{FontMetrics, TextElem, TextSize};
|
|
||||||
use ttf_parser::Tag;
|
use ttf_parser::Tag;
|
||||||
use typst_library::text::ScriptMetrics;
|
|
||||||
|
use crate::foundations::{elem, Content, Smart};
|
||||||
|
use crate::layout::{Em, Length};
|
||||||
|
use crate::text::{FontMetrics, ScriptMetrics, TextSize};
|
||||||
|
|
||||||
/// Renders text in subscript.
|
/// Renders text in subscript.
|
||||||
///
|
///
|
||||||
@ -17,7 +12,7 @@ use typst_library::text::ScriptMetrics;
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// Revenue#sub[yearly]
|
/// Revenue#sub[yearly]
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(title = "Subscript", Show)]
|
#[elem(title = "Subscript")]
|
||||||
pub struct SubElem {
|
pub struct SubElem {
|
||||||
/// Whether to create artificial subscripts by lowering and scaling down
|
/// Whether to create artificial subscripts by lowering and scaling down
|
||||||
/// regular glyphs.
|
/// regular glyphs.
|
||||||
@ -64,29 +59,6 @@ pub struct SubElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<SubElem> {
|
|
||||||
#[typst_macros::time(name = "sub", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let body = self.body.clone();
|
|
||||||
|
|
||||||
if styles.get(TargetElem::target).is_html() {
|
|
||||||
return Ok(HtmlElem::new(tag::sub)
|
|
||||||
.with_body(Some(body))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()));
|
|
||||||
}
|
|
||||||
|
|
||||||
show_script(
|
|
||||||
styles,
|
|
||||||
body,
|
|
||||||
self.typographic.get(styles),
|
|
||||||
self.baseline.get(styles),
|
|
||||||
self.size.get(styles),
|
|
||||||
ScriptKind::Sub,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Renders text in superscript.
|
/// Renders text in superscript.
|
||||||
///
|
///
|
||||||
/// The text is rendered smaller and its baseline is raised.
|
/// The text is rendered smaller and its baseline is raised.
|
||||||
@ -95,7 +67,7 @@ impl Show for Packed<SubElem> {
|
|||||||
/// ```example
|
/// ```example
|
||||||
/// 1#super[st] try!
|
/// 1#super[st] try!
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(title = "Superscript", Show)]
|
#[elem(title = "Superscript")]
|
||||||
pub struct SuperElem {
|
pub struct SuperElem {
|
||||||
/// Whether to create artificial superscripts by raising and scaling down
|
/// Whether to create artificial superscripts by raising and scaling down
|
||||||
/// regular glyphs.
|
/// regular glyphs.
|
||||||
@ -146,49 +118,6 @@ pub struct SuperElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<SuperElem> {
|
|
||||||
#[typst_macros::time(name = "super", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let body = self.body.clone();
|
|
||||||
|
|
||||||
if styles.get(TargetElem::target).is_html() {
|
|
||||||
return Ok(HtmlElem::new(tag::sup)
|
|
||||||
.with_body(Some(body))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()));
|
|
||||||
}
|
|
||||||
|
|
||||||
show_script(
|
|
||||||
styles,
|
|
||||||
body,
|
|
||||||
self.typographic.get(styles),
|
|
||||||
self.baseline.get(styles),
|
|
||||||
self.size.get(styles),
|
|
||||||
ScriptKind::Super,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_script(
|
|
||||||
styles: StyleChain,
|
|
||||||
body: Content,
|
|
||||||
typographic: bool,
|
|
||||||
baseline: Smart<Length>,
|
|
||||||
size: Smart<TextSize>,
|
|
||||||
kind: ScriptKind,
|
|
||||||
) -> SourceResult<Content> {
|
|
||||||
let font_size = styles.resolve(TextElem::size);
|
|
||||||
Ok(body.set(
|
|
||||||
TextElem::shift_settings,
|
|
||||||
Some(ShiftSettings {
|
|
||||||
typographic,
|
|
||||||
shift: baseline.map(|l| -Em::from_length(l, font_size)),
|
|
||||||
size: size.map(|t| Em::from_length(t.0, font_size)),
|
|
||||||
kind,
|
|
||||||
}),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configuration values for sub- or superscript text.
|
/// Configuration values for sub- or superscript text.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct ShiftSettings {
|
pub struct ShiftSettings {
|
||||||
|
@ -1,7 +1,4 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, Content};
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{elem, Content, Packed, Show, StyleChain};
|
|
||||||
use crate::text::TextElem;
|
|
||||||
|
|
||||||
/// Displays text in small capitals.
|
/// Displays text in small capitals.
|
||||||
///
|
///
|
||||||
@ -43,7 +40,7 @@ use crate::text::TextElem;
|
|||||||
/// = Introduction
|
/// = Introduction
|
||||||
/// #lorem(40)
|
/// #lorem(40)
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(title = "Small Capitals", Show)]
|
#[elem(title = "Small Capitals")]
|
||||||
pub struct SmallcapsElem {
|
pub struct SmallcapsElem {
|
||||||
/// Whether to turn uppercase letters into small capitals as well.
|
/// Whether to turn uppercase letters into small capitals as well.
|
||||||
///
|
///
|
||||||
@ -61,15 +58,6 @@ pub struct SmallcapsElem {
|
|||||||
pub body: Content,
|
pub body: Content,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<SmallcapsElem> {
|
|
||||||
#[typst_macros::time(name = "smallcaps", span = self.span())]
|
|
||||||
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
let sc =
|
|
||||||
if self.all.get(styles) { Smallcaps::All } else { Smallcaps::Minuscules };
|
|
||||||
Ok(self.body.clone().set(TextElem::smallcaps, Some(sc)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// What becomes small capitals.
|
/// What becomes small capitals.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Smallcaps {
|
pub enum Smallcaps {
|
||||||
|
@ -5,9 +5,10 @@ use unicode_segmentation::UnicodeSegmentation;
|
|||||||
use crate::diag::{bail, HintedStrResult, StrResult};
|
use crate::diag::{bail, HintedStrResult, StrResult};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
array, cast, dict, elem, Array, Dict, FromValue, Packed, PlainText, Smart, Str,
|
array, cast, dict, elem, Array, Dict, FromValue, Packed, PlainText, Smart, Str,
|
||||||
|
StyleChain,
|
||||||
};
|
};
|
||||||
use crate::layout::Dir;
|
use crate::layout::Dir;
|
||||||
use crate::text::{Lang, Region};
|
use crate::text::{Lang, Region, TextElem};
|
||||||
|
|
||||||
/// A language-aware quote that reacts to its context.
|
/// A language-aware quote that reacts to its context.
|
||||||
///
|
///
|
||||||
@ -200,6 +201,16 @@ pub struct SmartQuotes<'s> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'s> SmartQuotes<'s> {
|
impl<'s> SmartQuotes<'s> {
|
||||||
|
/// Retrieve the smart quotes as configured by the current styles.
|
||||||
|
pub fn get_in(styles: StyleChain<'s>) -> Self {
|
||||||
|
Self::get(
|
||||||
|
styles.get_ref(SmartQuoteElem::quotes),
|
||||||
|
styles.get(TextElem::lang),
|
||||||
|
styles.get(TextElem::region),
|
||||||
|
styles.get(SmartQuoteElem::alternative),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a new `Quotes` struct with the given quotes, optionally falling
|
/// Create a new `Quotes` struct with the given quotes, optionally falling
|
||||||
/// back to the defaults for a language and region.
|
/// back to the defaults for a language and region.
|
||||||
///
|
///
|
||||||
|
@ -2,12 +2,9 @@ use kurbo::ParamCurveExtrema;
|
|||||||
use typst_macros::{scope, Cast};
|
use typst_macros::{scope, Cast};
|
||||||
use typst_utils::Numeric;
|
use typst_utils::Numeric;
|
||||||
|
|
||||||
use crate::diag::{bail, HintedStrResult, HintedString, SourceResult};
|
use crate::diag::{bail, HintedStrResult, HintedString};
|
||||||
use crate::engine::Engine;
|
use crate::foundations::{cast, elem, Content, Packed, Smart};
|
||||||
use crate::foundations::{
|
use crate::layout::{Abs, Axes, Length, Point, Rel, Size};
|
||||||
cast, elem, Content, NativeElement, Packed, Show, Smart, StyleChain,
|
|
||||||
};
|
|
||||||
use crate::layout::{Abs, Axes, BlockElem, Length, Point, Rel, Size};
|
|
||||||
use crate::visualize::{FillRule, Paint, Stroke};
|
use crate::visualize::{FillRule, Paint, Stroke};
|
||||||
|
|
||||||
use super::FixedStroke;
|
use super::FixedStroke;
|
||||||
@ -42,7 +39,7 @@ use super::FixedStroke;
|
|||||||
/// curve.close(),
|
/// curve.close(),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(scope, Show)]
|
#[elem(scope)]
|
||||||
pub struct CurveElem {
|
pub struct CurveElem {
|
||||||
/// How to fill the curve.
|
/// How to fill the curve.
|
||||||
///
|
///
|
||||||
@ -95,14 +92,6 @@ pub struct CurveElem {
|
|||||||
pub components: Vec<CurveComponent>,
|
pub components: Vec<CurveComponent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<CurveElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_curve)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[scope]
|
#[scope]
|
||||||
impl CurveElem {
|
impl CurveElem {
|
||||||
#[elem]
|
#[elem]
|
||||||
|
@ -15,13 +15,11 @@ use ecow::EcoString;
|
|||||||
use typst_syntax::{Span, Spanned};
|
use typst_syntax::{Span, Spanned};
|
||||||
use typst_utils::LazyHash;
|
use typst_utils::LazyHash;
|
||||||
|
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::StrResult;
|
||||||
use crate::engine::Engine;
|
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Show,
|
cast, elem, func, scope, Bytes, Cast, Content, Derived, NativeElement, Packed, Smart,
|
||||||
Smart, StyleChain,
|
|
||||||
};
|
};
|
||||||
use crate::layout::{BlockElem, Length, Rel, Sizing};
|
use crate::layout::{Length, Rel, Sizing};
|
||||||
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
|
use crate::loading::{DataSource, Load, LoadSource, Loaded, Readable};
|
||||||
use crate::model::Figurable;
|
use crate::model::Figurable;
|
||||||
use crate::text::LocalName;
|
use crate::text::LocalName;
|
||||||
@ -44,7 +42,7 @@ use crate::text::LocalName;
|
|||||||
/// ],
|
/// ],
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(scope, Show, LocalName, Figurable)]
|
#[elem(scope, LocalName, Figurable)]
|
||||||
pub struct ImageElem {
|
pub struct ImageElem {
|
||||||
/// A [path]($syntax/#paths) to an image file or raw bytes making up an
|
/// A [path]($syntax/#paths) to an image file or raw bytes making up an
|
||||||
/// image in one of the supported [formats]($image.format).
|
/// image in one of the supported [formats]($image.format).
|
||||||
@ -219,16 +217,6 @@ impl ImageElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<ImageElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_image)
|
|
||||||
.with_width(self.width.get(styles))
|
|
||||||
.with_height(self.height.get(styles))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LocalName for Packed<ImageElem> {
|
impl LocalName for Packed<ImageElem> {
|
||||||
const KEY: &'static str = "figure";
|
const KEY: &'static str = "figure";
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::foundations::elem;
|
||||||
use crate::engine::Engine;
|
use crate::layout::{Abs, Angle, Axes, Length, Rel};
|
||||||
use crate::foundations::{elem, Content, NativeElement, Packed, Show, StyleChain};
|
|
||||||
use crate::layout::{Abs, Angle, Axes, BlockElem, Length, Rel};
|
|
||||||
use crate::visualize::Stroke;
|
use crate::visualize::Stroke;
|
||||||
|
|
||||||
/// A line from one point to another.
|
/// A line from one point to another.
|
||||||
@ -17,7 +15,7 @@ use crate::visualize::Stroke;
|
|||||||
/// stroke: 2pt + maroon,
|
/// stroke: 2pt + maroon,
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct LineElem {
|
pub struct LineElem {
|
||||||
/// The start point of the line.
|
/// The start point of the line.
|
||||||
///
|
///
|
||||||
@ -50,11 +48,3 @@ pub struct LineElem {
|
|||||||
#[fold]
|
#[fold]
|
||||||
pub stroke: Stroke,
|
pub stroke: Stroke,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<LineElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_line)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
use self::PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
|
use self::PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
|
||||||
use crate::diag::{bail, SourceResult};
|
use crate::diag::bail;
|
||||||
use crate::engine::Engine;
|
use crate::foundations::{array, cast, elem, Array, Reflect, Smart};
|
||||||
use crate::foundations::{
|
use crate::layout::{Axes, Length, Rel};
|
||||||
array, cast, elem, Array, Content, NativeElement, Packed, Reflect, Show, Smart,
|
|
||||||
StyleChain,
|
|
||||||
};
|
|
||||||
use crate::layout::{Axes, BlockElem, Length, Rel};
|
|
||||||
use crate::visualize::{FillRule, Paint, Stroke};
|
use crate::visualize::{FillRule, Paint, Stroke};
|
||||||
|
|
||||||
/// A path through a list of points, connected by Bézier curves.
|
/// A path through a list of points, connected by Bézier curves.
|
||||||
@ -21,7 +17,7 @@ use crate::visualize::{FillRule, Paint, Stroke};
|
|||||||
/// ((50%, 0pt), (40pt, 0pt)),
|
/// ((50%, 0pt), (40pt, 0pt)),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct PathElem {
|
pub struct PathElem {
|
||||||
/// How to fill the path.
|
/// How to fill the path.
|
||||||
///
|
///
|
||||||
@ -83,14 +79,6 @@ pub struct PathElem {
|
|||||||
pub vertices: Vec<PathVertex>,
|
pub vertices: Vec<PathVertex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<PathElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_path)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A component used for path creation.
|
/// A component used for path creation.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum PathVertex {
|
pub enum PathVertex {
|
||||||
|
@ -2,12 +2,8 @@ use std::f64::consts::PI;
|
|||||||
|
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
|
|
||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, func, scope, Content, NativeElement, Smart};
|
||||||
use crate::engine::Engine;
|
use crate::layout::{Axes, Em, Length, Rel};
|
||||||
use crate::foundations::{
|
|
||||||
elem, func, scope, Content, NativeElement, Packed, Show, Smart, StyleChain,
|
|
||||||
};
|
|
||||||
use crate::layout::{Axes, BlockElem, Em, Length, Rel};
|
|
||||||
use crate::visualize::{FillRule, Paint, Stroke};
|
use crate::visualize::{FillRule, Paint, Stroke};
|
||||||
|
|
||||||
/// A closed polygon.
|
/// A closed polygon.
|
||||||
@ -25,7 +21,7 @@ use crate::visualize::{FillRule, Paint, Stroke};
|
|||||||
/// (0%, 2cm),
|
/// (0%, 2cm),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(scope, Show)]
|
#[elem(scope)]
|
||||||
pub struct PolygonElem {
|
pub struct PolygonElem {
|
||||||
/// How to fill the polygon.
|
/// How to fill the polygon.
|
||||||
///
|
///
|
||||||
@ -124,11 +120,3 @@ impl PolygonElem {
|
|||||||
elem.pack().spanned(span)
|
elem.pack().spanned(span)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<PolygonElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, _: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_polygon)
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,9 +1,5 @@
|
|||||||
use crate::diag::SourceResult;
|
use crate::foundations::{elem, Cast, Content, Smart};
|
||||||
use crate::engine::Engine;
|
use crate::layout::{Abs, Corners, Length, Point, Rel, Sides, Size, Sizing};
|
||||||
use crate::foundations::{
|
|
||||||
elem, Cast, Content, NativeElement, Packed, Show, Smart, StyleChain,
|
|
||||||
};
|
|
||||||
use crate::layout::{Abs, BlockElem, Corners, Length, Point, Rel, Sides, Size, Sizing};
|
|
||||||
use crate::visualize::{Curve, FixedStroke, Paint, Stroke};
|
use crate::visualize::{Curve, FixedStroke, Paint, Stroke};
|
||||||
|
|
||||||
/// A rectangle with optional content.
|
/// A rectangle with optional content.
|
||||||
@ -19,7 +15,7 @@ use crate::visualize::{Curve, FixedStroke, Paint, Stroke};
|
|||||||
/// to fit the content.
|
/// to fit the content.
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(title = "Rectangle", Show)]
|
#[elem(title = "Rectangle")]
|
||||||
pub struct RectElem {
|
pub struct RectElem {
|
||||||
/// The rectangle's width, relative to its parent container.
|
/// The rectangle's width, relative to its parent container.
|
||||||
pub width: Smart<Rel<Length>>,
|
pub width: Smart<Rel<Length>>,
|
||||||
@ -122,16 +118,6 @@ pub struct RectElem {
|
|||||||
pub body: Option<Content>,
|
pub body: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<RectElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_rect)
|
|
||||||
.with_width(self.width.get(styles))
|
|
||||||
.with_height(self.height.get(styles))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A square with optional content.
|
/// A square with optional content.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -145,7 +131,7 @@ impl Show for Packed<RectElem> {
|
|||||||
/// sized to fit.
|
/// sized to fit.
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct SquareElem {
|
pub struct SquareElem {
|
||||||
/// The square's side length. This is mutually exclusive with `width` and
|
/// The square's side length. This is mutually exclusive with `width` and
|
||||||
/// `height`.
|
/// `height`.
|
||||||
@ -209,16 +195,6 @@ pub struct SquareElem {
|
|||||||
pub body: Option<Content>,
|
pub body: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<SquareElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_square)
|
|
||||||
.with_width(self.width.get(styles))
|
|
||||||
.with_height(self.height.get(styles))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An ellipse with optional content.
|
/// An ellipse with optional content.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -233,7 +209,7 @@ impl Show for Packed<SquareElem> {
|
|||||||
/// to fit the content.
|
/// to fit the content.
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct EllipseElem {
|
pub struct EllipseElem {
|
||||||
/// The ellipse's width, relative to its parent container.
|
/// The ellipse's width, relative to its parent container.
|
||||||
pub width: Smart<Rel<Length>>,
|
pub width: Smart<Rel<Length>>,
|
||||||
@ -269,16 +245,6 @@ pub struct EllipseElem {
|
|||||||
pub body: Option<Content>,
|
pub body: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<EllipseElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_ellipse)
|
|
||||||
.with_width(self.width.get(styles))
|
|
||||||
.with_height(self.height.get(styles))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A circle with optional content.
|
/// A circle with optional content.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
@ -293,7 +259,7 @@ impl Show for Packed<EllipseElem> {
|
|||||||
/// sized to fit.
|
/// sized to fit.
|
||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
#[elem(Show)]
|
#[elem]
|
||||||
pub struct CircleElem {
|
pub struct CircleElem {
|
||||||
/// The circle's radius. This is mutually exclusive with `width` and
|
/// The circle's radius. This is mutually exclusive with `width` and
|
||||||
/// `height`.
|
/// `height`.
|
||||||
@ -354,16 +320,6 @@ pub struct CircleElem {
|
|||||||
pub body: Option<Content>,
|
pub body: Option<Content>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Show for Packed<CircleElem> {
|
|
||||||
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
|
|
||||||
Ok(BlockElem::single_layouter(self.clone(), engine.routines.layout_circle)
|
|
||||||
.with_width(self.width.get(styles))
|
|
||||||
.with_height(self.height.get(styles))
|
|
||||||
.pack()
|
|
||||||
.spanned(self.span()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A geometric shape with optional fill and stroke.
|
/// A geometric shape with optional fill and stroke.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct Shape {
|
pub struct Shape {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
figure = Figur
|
figure = Figur
|
||||||
table = Tabell
|
table = Tabell
|
||||||
equation = Ekvation
|
equation = Ekvation
|
||||||
bibliography = Bibliografi
|
bibliography = Referenser
|
||||||
heading = Kapitel
|
heading = Avsnitt
|
||||||
outline = Innehåll
|
outline = Innehåll
|
||||||
raw = Listing
|
raw = Kodlistning
|
||||||
page = sida
|
page = sida
|
||||||
|
@ -14,9 +14,9 @@ use ecow::EcoString;
|
|||||||
use typst_library::diag::{bail, At, SourceResult};
|
use typst_library::diag::{bail, At, SourceResult};
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{
|
use typst_library::foundations::{
|
||||||
Content, Context, ContextElem, Element, NativeElement, Recipe, RecipeIndex, Selector,
|
Content, Context, ContextElem, Element, NativeElement, NativeShowRule, Recipe,
|
||||||
SequenceElem, Show, ShowSet, Style, StyleChain, StyledElem, Styles, SymbolElem,
|
RecipeIndex, Selector, SequenceElem, ShowSet, Style, StyleChain, StyledElem, Styles,
|
||||||
Synthesize, Transformation,
|
SymbolElem, Synthesize, TargetElem, Transformation,
|
||||||
};
|
};
|
||||||
use typst_library::html::{tag, FrameElem, HtmlElem};
|
use typst_library::html::{tag, FrameElem, HtmlElem};
|
||||||
use typst_library::introspection::{Locatable, SplitLocator, Tag, TagElem};
|
use typst_library::introspection::{Locatable, SplitLocator, Tag, TagElem};
|
||||||
@ -160,7 +160,7 @@ enum ShowStep<'a> {
|
|||||||
/// A user-defined transformational show rule.
|
/// A user-defined transformational show rule.
|
||||||
Recipe(&'a Recipe, RecipeIndex),
|
Recipe(&'a Recipe, RecipeIndex),
|
||||||
/// The built-in show rule.
|
/// The built-in show rule.
|
||||||
Builtin,
|
Builtin(NativeShowRule),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A match of a regex show rule.
|
/// A match of a regex show rule.
|
||||||
@ -382,9 +382,7 @@ fn visit_show_rules<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply a built-in show rule.
|
// Apply a built-in show rule.
|
||||||
ShowStep::Builtin => {
|
ShowStep::Builtin(rule) => rule.apply(&output, s.engine, chained),
|
||||||
output.with::<dyn Show>().unwrap().show(s.engine, chained)
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Errors in show rules don't terminate compilation immediately. We just
|
// Errors in show rules don't terminate compilation immediately. We just
|
||||||
@ -426,14 +424,14 @@ fn visit_show_rules<'a>(
|
|||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inspects a target element and the current styles and determines how to
|
/// Inspects an element and the current styles and determines how to proceed
|
||||||
/// proceed with the styling.
|
/// with the styling.
|
||||||
fn verdict<'a>(
|
fn verdict<'a>(
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
target: &'a Content,
|
elem: &'a Content,
|
||||||
styles: StyleChain<'a>,
|
styles: StyleChain<'a>,
|
||||||
) -> Option<Verdict<'a>> {
|
) -> Option<Verdict<'a>> {
|
||||||
let prepared = target.is_prepared();
|
let prepared = elem.is_prepared();
|
||||||
let mut map = Styles::new();
|
let mut map = Styles::new();
|
||||||
let mut step = None;
|
let mut step = None;
|
||||||
|
|
||||||
@ -441,20 +439,20 @@ fn verdict<'a>(
|
|||||||
// fields before real synthesis runs (during preparation). It's really
|
// fields before real synthesis runs (during preparation). It's really
|
||||||
// unfortunate that we have to do this, but otherwise
|
// unfortunate that we have to do this, but otherwise
|
||||||
// `show figure.where(kind: table)` won't work :(
|
// `show figure.where(kind: table)` won't work :(
|
||||||
let mut target = target;
|
let mut elem = elem;
|
||||||
let mut slot;
|
let mut slot;
|
||||||
if !prepared && target.can::<dyn Synthesize>() {
|
if !prepared && elem.can::<dyn Synthesize>() {
|
||||||
slot = target.clone();
|
slot = elem.clone();
|
||||||
slot.with_mut::<dyn Synthesize>()
|
slot.with_mut::<dyn Synthesize>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.synthesize(engine, styles)
|
.synthesize(engine, styles)
|
||||||
.ok();
|
.ok();
|
||||||
target = &slot;
|
elem = &slot;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lazily computes the total number of recipes in the style chain. We need
|
// Lazily computes the total number of recipes in the style chain. We need
|
||||||
// it to determine whether a particular show rule was already applied to the
|
// it to determine whether a particular show rule was already applied to the
|
||||||
// `target` previously. For this purpose, show rules are indexed from the
|
// `elem` previously. For this purpose, show rules are indexed from the
|
||||||
// top of the chain as the chain might grow to the bottom.
|
// top of the chain as the chain might grow to the bottom.
|
||||||
let depth = LazyCell::new(|| styles.recipes().count());
|
let depth = LazyCell::new(|| styles.recipes().count());
|
||||||
|
|
||||||
@ -462,7 +460,7 @@ fn verdict<'a>(
|
|||||||
// We're not interested in recipes that don't match.
|
// We're not interested in recipes that don't match.
|
||||||
if !recipe
|
if !recipe
|
||||||
.selector()
|
.selector()
|
||||||
.is_some_and(|selector| selector.matches(target, Some(styles)))
|
.is_some_and(|selector| selector.matches(elem, Some(styles)))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -480,9 +478,9 @@ fn verdict<'a>(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check whether this show rule was already applied to the target.
|
// Check whether this show rule was already applied to the element.
|
||||||
let index = RecipeIndex(*depth - r);
|
let index = RecipeIndex(*depth - r);
|
||||||
if target.is_guarded(index) {
|
if elem.is_guarded(index) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,19 +496,22 @@ fn verdict<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If we found no user-defined rule, also consider the built-in show rule.
|
// If we found no user-defined rule, also consider the built-in show rule.
|
||||||
if step.is_none() && target.can::<dyn Show>() {
|
if step.is_none() {
|
||||||
step = Some(ShowStep::Builtin);
|
let target = styles.get(TargetElem::target);
|
||||||
|
if let Some(rule) = engine.routines.rules.get(target, elem) {
|
||||||
|
step = Some(ShowStep::Builtin(rule));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's no nothing to do, there is also no verdict.
|
// If there's no nothing to do, there is also no verdict.
|
||||||
if step.is_none()
|
if step.is_none()
|
||||||
&& map.is_empty()
|
&& map.is_empty()
|
||||||
&& (prepared || {
|
&& (prepared || {
|
||||||
target.label().is_none()
|
elem.label().is_none()
|
||||||
&& target.location().is_none()
|
&& elem.location().is_none()
|
||||||
&& !target.can::<dyn ShowSet>()
|
&& !elem.can::<dyn ShowSet>()
|
||||||
&& !target.can::<dyn Locatable>()
|
&& !elem.can::<dyn Locatable>()
|
||||||
&& !target.can::<dyn Synthesize>()
|
&& !elem.can::<dyn Synthesize>()
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
return None;
|
return None;
|
||||||
@ -523,7 +524,7 @@ fn verdict<'a>(
|
|||||||
fn prepare(
|
fn prepare(
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
locator: &mut SplitLocator,
|
locator: &mut SplitLocator,
|
||||||
target: &mut Content,
|
elem: &mut Content,
|
||||||
map: &mut Styles,
|
map: &mut Styles,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Option<(Tag, Tag)>> {
|
) -> SourceResult<Option<(Tag, Tag)>> {
|
||||||
@ -533,43 +534,43 @@ fn prepare(
|
|||||||
//
|
//
|
||||||
// The element could already have a location even if it is not prepared
|
// The element could already have a location even if it is not prepared
|
||||||
// when it stems from a query.
|
// when it stems from a query.
|
||||||
let key = typst_utils::hash128(&target);
|
let key = typst_utils::hash128(&elem);
|
||||||
if target.location().is_none()
|
if elem.location().is_none()
|
||||||
&& (target.can::<dyn Locatable>() || target.label().is_some())
|
&& (elem.can::<dyn Locatable>() || elem.label().is_some())
|
||||||
{
|
{
|
||||||
let loc = locator.next_location(engine.introspector, key);
|
let loc = locator.next_location(engine.introspector, key);
|
||||||
target.set_location(loc);
|
elem.set_location(loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply built-in show-set rules. User-defined show-set rules are already
|
// Apply built-in show-set rules. User-defined show-set rules are already
|
||||||
// considered in the map built while determining the verdict.
|
// considered in the map built while determining the verdict.
|
||||||
if let Some(show_settable) = target.with::<dyn ShowSet>() {
|
if let Some(show_settable) = elem.with::<dyn ShowSet>() {
|
||||||
map.apply(show_settable.show_set(styles));
|
map.apply(show_settable.show_set(styles));
|
||||||
}
|
}
|
||||||
|
|
||||||
// If necessary, generated "synthesized" fields (which are derived from
|
// If necessary, generated "synthesized" fields (which are derived from
|
||||||
// other fields or queries). Do this after show-set so that show-set styles
|
// other fields or queries). Do this after show-set so that show-set styles
|
||||||
// are respected.
|
// are respected.
|
||||||
if let Some(synthesizable) = target.with_mut::<dyn Synthesize>() {
|
if let Some(synthesizable) = elem.with_mut::<dyn Synthesize>() {
|
||||||
synthesizable.synthesize(engine, styles.chain(map))?;
|
synthesizable.synthesize(engine, styles.chain(map))?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy style chain fields into the element itself, so that they are
|
// Copy style chain fields into the element itself, so that they are
|
||||||
// available in rules.
|
// available in rules.
|
||||||
target.materialize(styles.chain(map));
|
elem.materialize(styles.chain(map));
|
||||||
|
|
||||||
// If the element is locatable, create start and end tags to be able to find
|
// If the element is locatable, create start and end tags to be able to find
|
||||||
// the element in the frames after layout. Do this after synthesis and
|
// the element in the frames after layout. Do this after synthesis and
|
||||||
// materialization, so that it includes the synthesized fields. Do it before
|
// materialization, so that it includes the synthesized fields. Do it before
|
||||||
// marking as prepared so that show-set rules will apply to this element
|
// marking as prepared so that show-set rules will apply to this element
|
||||||
// when queried.
|
// when queried.
|
||||||
let tags = target
|
let tags = elem
|
||||||
.location()
|
.location()
|
||||||
.map(|loc| (Tag::Start(target.clone()), Tag::End(loc, key)));
|
.map(|loc| (Tag::Start(elem.clone()), Tag::End(loc, key)));
|
||||||
|
|
||||||
// Ensure that this preparation only runs once by marking the element as
|
// Ensure that this preparation only runs once by marking the element as
|
||||||
// prepared.
|
// prepared.
|
||||||
target.mark_prepared();
|
elem.mark_prepared();
|
||||||
|
|
||||||
Ok(tags)
|
Ok(tags)
|
||||||
}
|
}
|
||||||
|
@ -202,7 +202,7 @@ fn render_group(canvas: &mut sk::Pixmap, state: State, pos: Point, group: &Group
|
|||||||
mask.intersect_path(
|
mask.intersect_path(
|
||||||
&path,
|
&path,
|
||||||
sk::FillRule::default(),
|
sk::FillRule::default(),
|
||||||
false,
|
true,
|
||||||
sk::Transform::default(),
|
sk::Transform::default(),
|
||||||
);
|
);
|
||||||
storage = mask;
|
storage = mask;
|
||||||
@ -218,7 +218,7 @@ fn render_group(canvas: &mut sk::Pixmap, state: State, pos: Point, group: &Group
|
|||||||
mask.fill_path(
|
mask.fill_path(
|
||||||
&path,
|
&path,
|
||||||
sk::FillRule::default(),
|
sk::FillRule::default(),
|
||||||
false,
|
true,
|
||||||
sk::Transform::default(),
|
sk::Transform::default(),
|
||||||
);
|
);
|
||||||
storage = mask;
|
storage = mask;
|
||||||
|
@ -39,6 +39,7 @@ pub use typst_syntax as syntax;
|
|||||||
pub use typst_utils as utils;
|
pub use typst_utils as utils;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use comemo::{Track, Tracked, Validate};
|
use comemo::{Track, Tracked, Validate};
|
||||||
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
||||||
@ -46,7 +47,7 @@ use typst_library::diag::{
|
|||||||
bail, warning, FileError, SourceDiagnostic, SourceResult, Warned,
|
bail, warning, FileError, SourceDiagnostic, SourceResult, Warned,
|
||||||
};
|
};
|
||||||
use typst_library::engine::{Engine, Route, Sink, Traced};
|
use typst_library::engine::{Engine, Route, Sink, Traced};
|
||||||
use typst_library::foundations::{StyleChain, Styles, Value};
|
use typst_library::foundations::{NativeRuleMap, StyleChain, Styles, Value};
|
||||||
use typst_library::html::HtmlDocument;
|
use typst_library::html::HtmlDocument;
|
||||||
use typst_library::introspection::Introspector;
|
use typst_library::introspection::Introspector;
|
||||||
use typst_library::layout::PagedDocument;
|
use typst_library::layout::PagedDocument;
|
||||||
@ -322,37 +323,39 @@ mod sealed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Provides ways to construct a [`Library`].
|
||||||
|
pub trait LibraryExt {
|
||||||
|
/// Creates the default library.
|
||||||
|
fn default() -> Library;
|
||||||
|
|
||||||
|
/// Creates a builder for configuring a library.
|
||||||
|
fn builder() -> LibraryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl LibraryExt for Library {
|
||||||
|
fn default() -> Library {
|
||||||
|
Self::builder().build()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn builder() -> LibraryBuilder {
|
||||||
|
LibraryBuilder::from_routines(&ROUTINES)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Defines implementation of various Typst compiler routines as a table of
|
/// Defines implementation of various Typst compiler routines as a table of
|
||||||
/// function pointers.
|
/// function pointers.
|
||||||
///
|
///
|
||||||
/// This is essentially dynamic linking and done to allow for crate splitting.
|
/// This is essentially dynamic linking and done to allow for crate splitting.
|
||||||
pub static ROUTINES: Routines = Routines {
|
pub static ROUTINES: LazyLock<Routines> = LazyLock::new(|| Routines {
|
||||||
|
rules: {
|
||||||
|
let mut rules = NativeRuleMap::new();
|
||||||
|
typst_layout::register(&mut rules);
|
||||||
|
typst_html::register(&mut rules);
|
||||||
|
rules
|
||||||
|
},
|
||||||
eval_string: typst_eval::eval_string,
|
eval_string: typst_eval::eval_string,
|
||||||
eval_closure: typst_eval::eval_closure,
|
eval_closure: typst_eval::eval_closure,
|
||||||
realize: typst_realize::realize,
|
realize: typst_realize::realize,
|
||||||
layout_fragment: typst_layout::layout_fragment,
|
|
||||||
layout_frame: typst_layout::layout_frame,
|
layout_frame: typst_layout::layout_frame,
|
||||||
layout_list: typst_layout::layout_list,
|
html_module: typst_html::module,
|
||||||
layout_enum: typst_layout::layout_enum,
|
});
|
||||||
layout_grid: typst_layout::layout_grid,
|
|
||||||
layout_table: typst_layout::layout_table,
|
|
||||||
layout_stack: typst_layout::layout_stack,
|
|
||||||
layout_columns: typst_layout::layout_columns,
|
|
||||||
layout_move: typst_layout::layout_move,
|
|
||||||
layout_rotate: typst_layout::layout_rotate,
|
|
||||||
layout_scale: typst_layout::layout_scale,
|
|
||||||
layout_skew: typst_layout::layout_skew,
|
|
||||||
layout_repeat: typst_layout::layout_repeat,
|
|
||||||
layout_pad: typst_layout::layout_pad,
|
|
||||||
layout_line: typst_layout::layout_line,
|
|
||||||
layout_curve: typst_layout::layout_curve,
|
|
||||||
layout_path: typst_layout::layout_path,
|
|
||||||
layout_polygon: typst_layout::layout_polygon,
|
|
||||||
layout_rect: typst_layout::layout_rect,
|
|
||||||
layout_square: typst_layout::layout_square,
|
|
||||||
layout_ellipse: typst_layout::layout_ellipse,
|
|
||||||
layout_circle: typst_layout::layout_circle,
|
|
||||||
layout_image: typst_layout::layout_image,
|
|
||||||
layout_equation_block: typst_layout::layout_equation_block,
|
|
||||||
layout_equation_inline: typst_layout::layout_equation_inline,
|
|
||||||
};
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
title: Variants
|
title: Variants
|
||||||
category: math
|
category: math
|
||||||
path: ["math"]
|
path: ["math"]
|
||||||
filter: ["serif", "sans", "frak", "mono", "bb", "cal"]
|
filter: ["serif", "sans", "frak", "mono", "bb", "cal", "scr"]
|
||||||
details: |
|
details: |
|
||||||
Alternate typefaces within formulas.
|
Alternate typefaces within formulas.
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ use typst::foundations::{
|
|||||||
use typst::layout::{Abs, Margin, PageElem, PagedDocument};
|
use typst::layout::{Abs, Margin, PageElem, PagedDocument};
|
||||||
use typst::text::{Font, FontBook};
|
use typst::text::{Font, FontBook};
|
||||||
use typst::utils::LazyHash;
|
use typst::utils::LazyHash;
|
||||||
use typst::{Category, Feature, Library, LibraryBuilder};
|
use typst::{Category, Feature, Library, LibraryExt};
|
||||||
use unicode_math_class::MathClass;
|
use unicode_math_class::MathClass;
|
||||||
|
|
||||||
macro_rules! load {
|
macro_rules! load {
|
||||||
@ -51,7 +51,7 @@ static GROUPS: LazyLock<Vec<GroupData>> = LazyLock::new(|| {
|
|||||||
});
|
});
|
||||||
|
|
||||||
static LIBRARY: LazyLock<LazyHash<Library>> = LazyLock::new(|| {
|
static LIBRARY: LazyLock<LazyHash<Library>> = LazyLock::new(|| {
|
||||||
let mut lib = LibraryBuilder::default()
|
let mut lib = Library::builder()
|
||||||
.with_features([Feature::Html].into_iter().collect())
|
.with_features([Feature::Html].into_iter().collect())
|
||||||
.build();
|
.build();
|
||||||
let scope = lib.global.scope_mut();
|
let scope = lib.global.scope_mut();
|
||||||
|
@ -84,8 +84,9 @@ meaning in Typst. We can use `=`, `-`, `+`, and `_` to create headings, lists
|
|||||||
and emphasized text, respectively. However, having a special symbol for
|
and emphasized text, respectively. However, having a special symbol for
|
||||||
everything we want to insert into our document would soon become cryptic and
|
everything we want to insert into our document would soon become cryptic and
|
||||||
unwieldy. For this reason, Typst reserves markup symbols only for the most
|
unwieldy. For this reason, Typst reserves markup symbols only for the most
|
||||||
common things. Everything else is inserted with _functions._ For our image to
|
common things. Everything else is inserted with _functions._ For
|
||||||
show up on the page, we use Typst's [`image`] function.
|
[our image](https://github.com/typst/typst-dev-assets/blob/main/files/images/glacier.jpg)
|
||||||
|
to show up on the page, we use Typst's [`image`] function.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#image("glacier.jpg")
|
#image("glacier.jpg")
|
||||||
@ -125,19 +126,38 @@ mode. This means, you now have to remove the hash before the image function call
|
|||||||
The hash is only needed directly in markup (to disambiguate text from function
|
The hash is only needed directly in markup (to disambiguate text from function
|
||||||
calls).
|
calls).
|
||||||
|
|
||||||
The caption consists of arbitrary markup. To give markup to a function, we
|
The caption consists of arbitrary markup, and can also be a string. To give
|
||||||
enclose it in square brackets. This construct is called a _content block._
|
markup to a function, we enclose it in square brackets. This construct is called
|
||||||
|
a _content block._
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#figure(
|
#figure(
|
||||||
image("glacier.jpg", width: 70%),
|
image("glacier.jpg", width: 70%),
|
||||||
caption: [
|
caption: box[
|
||||||
_Glaciers_ form an important part
|
_Glaciers_ form an important part
|
||||||
of the earth's climate system.
|
of the earth's climate system.
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Be careful** about putting the square brackets by themselves on separate
|
||||||
|
lines. This will introduce leading and trailing space around inline text inside
|
||||||
|
the brackets, that is hard to notice. Below are several caption examples: one
|
||||||
|
with extra undesired space, and 3 correct ones.
|
||||||
|
|
||||||
|
```example
|
||||||
|
#show rect: none
|
||||||
|
#figure(rect(), caption: [
|
||||||
|
Caption text
|
||||||
|
])
|
||||||
|
#figure(rect(), caption: box[
|
||||||
|
Caption text
|
||||||
|
])
|
||||||
|
#figure(rect(), caption: [Caption
|
||||||
|
text]) // Many spaces in markup counts as one.
|
||||||
|
#figure(rect(), caption: "Caption text") // Spaces in strings are displayed verbatim.
|
||||||
|
```
|
||||||
|
|
||||||
You continue to write your report and now want to reference the figure. To do
|
You continue to write your report and now want to reference the figure. To do
|
||||||
that, first attach a label to figure. A label uniquely identifies an element in
|
that, first attach a label to figure. A label uniquely identifies an element in
|
||||||
your document. Add one after the figure by enclosing some name in angle
|
your document. Add one after the figure by enclosing some name in angle
|
||||||
|
@ -110,8 +110,8 @@ font. For the purposes of the example, we'll also set another page size.
|
|||||||
margin: (x: 1.8cm, y: 1.5cm),
|
margin: (x: 1.8cm, y: 1.5cm),
|
||||||
)
|
)
|
||||||
#set text(
|
#set text(
|
||||||
|
size: 10pt,
|
||||||
font: "New Computer Modern",
|
font: "New Computer Modern",
|
||||||
size: 10pt
|
|
||||||
)
|
)
|
||||||
#set par(
|
#set par(
|
||||||
justify: true,
|
justify: true,
|
||||||
@ -235,19 +235,27 @@ Instead, you could maybe
|
|||||||
[define a custom function]($function/#defining-functions) that always yields the
|
[define a custom function]($function/#defining-functions) that always yields the
|
||||||
logo with its image. However, there is an even easier way:
|
logo with its image. However, there is an even easier way:
|
||||||
|
|
||||||
With show rules, you can redefine how Typst displays certain elements. You
|
With show rules, you can redefine how Typst displays certain elements. You can
|
||||||
specify which elements Typst should show differently and how they should look.
|
specify which elements Typst should show differently and how they should look.
|
||||||
Show rules can be applied to instances of text, many functions, and even the
|
Show rules can be applied to instances of text, many functions, and even the
|
||||||
whole document.
|
whole document.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#show "ArtosFlow": name => box[
|
// #show "ArtosFlow": name => {
|
||||||
#box(image(
|
// let logo = box(image(
|
||||||
|
// "logo.svg",
|
||||||
|
// height: 0.7em,
|
||||||
|
// ))
|
||||||
|
// [#logo #name]
|
||||||
|
// }
|
||||||
|
#show "ArtosFlow": name => {
|
||||||
|
box(image(
|
||||||
"logo.svg",
|
"logo.svg",
|
||||||
height: 0.7em,
|
height: 0.7em,
|
||||||
))
|
))
|
||||||
#name
|
" "
|
||||||
]
|
name
|
||||||
|
}
|
||||||
|
|
||||||
This report is embedded in the
|
This report is embedded in the
|
||||||
ArtosFlow project. ArtosFlow is a
|
ArtosFlow project. ArtosFlow is a
|
||||||
@ -256,18 +264,18 @@ project of the Artos Institute.
|
|||||||
|
|
||||||
There is a lot of new syntax in this example: We write the `{show}` keyword,
|
There is a lot of new syntax in this example: We write the `{show}` keyword,
|
||||||
followed by a string of text we want to show differently and a colon. Then, we
|
followed by a string of text we want to show differently and a colon. Then, we
|
||||||
write a function that takes the content that shall be shown as an argument.
|
write a function that takes the content as an argument that shall be shown.
|
||||||
Here, we called that argument `name`. We can now use the `name` variable in the
|
Here, we called that argument `name`. We can now use the `name` variable in
|
||||||
function's body to print the ArtosFlow name. Our show rule adds the logo image
|
the function's body to display the ArtosFlow name. Our show rule adds the logo
|
||||||
in front of the name and puts the result into a box to prevent linebreaks from
|
image to the left of the name and inserts a single space between the two.
|
||||||
occurring between logo and name. The image is also put inside of a box, so that
|
The image is put inside of a `box`, so that it does not appear in its own
|
||||||
it does not appear in its own paragraph.
|
paragraph, because `image` is a block-level element.
|
||||||
|
|
||||||
The calls to the first box function and the image function did not require a
|
<!-- The calls to the first box function and the image function did not require a -->
|
||||||
leading `#` because they were not embedded directly in markup. When Typst
|
<!-- leading `#` because they were not embedded directly in markup. When Typst -->
|
||||||
expects code instead of markup, the leading `#` is not needed to access
|
<!-- expects code instead of markup, the leading `#` is not needed to access -->
|
||||||
functions, keywords, and variables. This can be observed in parameter lists,
|
<!-- functions, keywords, and variables. This can be observed in parameter lists, -->
|
||||||
function definitions, and [code blocks]($scripting).
|
<!-- function definitions, and [code blocks]($scripting). -->
|
||||||
|
|
||||||
## Review
|
## Review
|
||||||
You now know how to apply basic formatting to your Typst documents. You learned
|
You now know how to apply basic formatting to your Typst documents. You learned
|
||||||
|
@ -6,7 +6,7 @@ description: Typst's tutorial.
|
|||||||
In the previous two chapters of this tutorial, you have learned how to write a
|
In the previous two chapters of this tutorial, you have learned how to write a
|
||||||
document in Typst and how to change its formatting. The report you wrote
|
document in Typst and how to change its formatting. The report you wrote
|
||||||
throughout the last two chapters got a straight A and your supervisor wants to
|
throughout the last two chapters got a straight A and your supervisor wants to
|
||||||
base a conference paper on it! The report will of course have to comply with the
|
base a conference paper on it! The paper will of course have to comply with the
|
||||||
conference's style guide. Let's see how we can achieve that.
|
conference's style guide. Let's see how we can achieve that.
|
||||||
|
|
||||||
Before we start, let's create a team, invite your supervisor and add them to the
|
Before we start, let's create a team, invite your supervisor and add them to the
|
||||||
@ -30,12 +30,12 @@ to find other users and try teams with them!
|
|||||||
The layout guidelines are available on the conference website. Let's take a look
|
The layout guidelines are available on the conference website. Let's take a look
|
||||||
at them:
|
at them:
|
||||||
|
|
||||||
- The font should be an 11pt serif font
|
- The font should be an 11 pt serif font
|
||||||
- The title should be in 17pt and bold
|
- The title should be in 17 pt and bold
|
||||||
- The paper contains a single-column abstract and two-column main text
|
- The paper contains a single-column abstract and two-column main text
|
||||||
- The abstract should be centered
|
- The abstract should be centered
|
||||||
- The main text should be justified
|
- The main text should be justified
|
||||||
- First level section headings should be 13pt, centered, and rendered in small
|
- First level section headings should be 13 pt, centered, and rendered in small
|
||||||
capitals
|
capitals
|
||||||
- Second level headings are run-ins, italicized and have the same size as the
|
- Second level headings are run-ins, italicized and have the same size as the
|
||||||
body text
|
body text
|
||||||
@ -51,7 +51,6 @@ Let's start by writing some set rules for the document.
|
|||||||
|
|
||||||
```example
|
```example
|
||||||
#set page(
|
#set page(
|
||||||
>>> margin: auto,
|
|
||||||
paper: "us-letter",
|
paper: "us-letter",
|
||||||
header: align(right)[
|
header: align(right)[
|
||||||
A fluid dynamic model for
|
A fluid dynamic model for
|
||||||
@ -61,8 +60,8 @@ Let's start by writing some set rules for the document.
|
|||||||
)
|
)
|
||||||
#set par(justify: true)
|
#set par(justify: true)
|
||||||
#set text(
|
#set text(
|
||||||
font: "Libertinus Serif",
|
|
||||||
size: 11pt,
|
size: 11pt,
|
||||||
|
font: "Libertinus Serif",
|
||||||
)
|
)
|
||||||
|
|
||||||
#lorem(600)
|
#lorem(600)
|
||||||
@ -72,12 +71,12 @@ You are already familiar with most of what is going on here. We set the text
|
|||||||
size to `{11pt}` and the font to Libertinus Serif. We also enable paragraph
|
size to `{11pt}` and the font to Libertinus Serif. We also enable paragraph
|
||||||
justification and set the page size to US letter.
|
justification and set the page size to US letter.
|
||||||
|
|
||||||
The `header` argument is new: With it, we can provide content to fill the top
|
The `header` field is new: with it, we can provide content to fill the top
|
||||||
margin of every page. In the header, we specify our paper's title as requested
|
margin of every page. In the header, we specify our paper's title as requested
|
||||||
by the conference style guide. We use the `align` function to align the text to
|
by the conference style guide. We use the `align` function to align the text to
|
||||||
the right.
|
the right.
|
||||||
|
|
||||||
Last but not least is the `numbering` argument. Here, we can provide a
|
Last but not least is the `numbering` field. Here, we can provide a
|
||||||
[numbering pattern]($numbering) that defines how to number the pages. By
|
[numbering pattern]($numbering) that defines how to number the pages. By
|
||||||
setting it to `{"1"}`, Typst only displays the bare page number. Setting it to
|
setting it to `{"1"}`, Typst only displays the bare page number. Setting it to
|
||||||
`{"(1/1)"}` would have displayed the current page and total number of pages
|
`{"(1/1)"}` would have displayed the current page and total number of pages
|
||||||
@ -89,35 +88,65 @@ Now, let's add a title and an abstract. We'll start with the title. We center
|
|||||||
align it and increase its font weight by enclosing it in `[*stars*]`.
|
align it and increase its font weight by enclosing it in `[*stars*]`.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
>>> #set page(width: 300pt, margin: 30pt)
|
>>> #set page(
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
>>> // paper: "us-letter",
|
||||||
#align(center, text(17pt)[
|
>>> width: 300pt,
|
||||||
|
>>> margin: 30pt,
|
||||||
|
>>> header: align(right)[
|
||||||
|
>>> A fluid dynamic model for
|
||||||
|
>>> glacier flow
|
||||||
|
>>> ],
|
||||||
|
>>> // numbering: "1",
|
||||||
|
>>> )
|
||||||
|
>>> #set par(justify: true)
|
||||||
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
|
<<< ...
|
||||||
|
#align(center, block(text(
|
||||||
|
17pt,
|
||||||
|
hyphenate: false
|
||||||
|
)[
|
||||||
*A fluid dynamic model
|
*A fluid dynamic model
|
||||||
for glacier flow*
|
for glacier flow*
|
||||||
])
|
]))
|
||||||
```
|
```
|
||||||
|
|
||||||
This looks right. We used the `text` function to override the previous text
|
This looks right. We used the `text` function to override the previous text
|
||||||
set rule locally, increasing the size to 17pt for the function's argument. Let's
|
set rule locally, increasing the size to 17 pt.
|
||||||
also add the author list: Since we are writing this paper together with our
|
Add explanation about block+hyphenate, which is pretty convoluted.
|
||||||
supervisor, we'll add our own and their name.
|
Let's also add the author list: Since we are writing this paper together with
|
||||||
|
our supervisor, we'll add our own and their name.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
>>> #set page(width: 300pt, margin: 30pt)
|
>>> #set page(
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
>>> // paper: "us-letter",
|
||||||
|
>>> width: 300pt,
|
||||||
|
>>> margin: 30pt,
|
||||||
|
>>> header: align(right)[
|
||||||
|
>>> A fluid dynamic model for
|
||||||
|
>>> glacier flow
|
||||||
|
>>> ],
|
||||||
|
>>> // numbering: "1",
|
||||||
|
>>> )
|
||||||
|
>>> #set par(justify: true)
|
||||||
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
>>>
|
>>>
|
||||||
>>> #align(center, text(17pt)[
|
>>> #align(center, block(text(
|
||||||
|
>>> 17pt,
|
||||||
|
>>> hyphenate: false
|
||||||
|
>>> )[
|
||||||
>>> *A fluid dynamic model
|
>>> *A fluid dynamic model
|
||||||
>>> for glacier flow*
|
>>> for glacier flow*
|
||||||
>>> ])
|
>>> ]))
|
||||||
|
<<< ...
|
||||||
#grid(
|
#grid(
|
||||||
columns: (1fr, 1fr),
|
columns: (1fr, 1fr),
|
||||||
align(center)[
|
align: center,
|
||||||
|
[
|
||||||
Therese Tungsten \
|
Therese Tungsten \
|
||||||
Artos Institute \
|
Artos Institute \
|
||||||
#link("mailto:tung@artos.edu")
|
#link("mailto:tung@artos.edu")
|
||||||
],
|
],
|
||||||
align(center)[
|
[
|
||||||
Dr. John Doe \
|
Dr. John Doe \
|
||||||
Artos Institute \
|
Artos Institute \
|
||||||
#link("mailto:doe@artos.edu")
|
#link("mailto:doe@artos.edu")
|
||||||
@ -127,45 +156,49 @@ supervisor, we'll add our own and their name.
|
|||||||
|
|
||||||
The two author blocks are laid out next to each other. We use the [`grid`]
|
The two author blocks are laid out next to each other. We use the [`grid`]
|
||||||
function to create this layout. With a grid, we can control exactly how large
|
function to create this layout. With a grid, we can control exactly how large
|
||||||
each column is and which content goes into which cell. The `columns` argument
|
each column is and which content goes into which cell. The `columns` field
|
||||||
takes an array of [relative lengths]($relative) or [fractions]($fraction). In
|
takes the number of columns, or an array of [relative lengths]($relative) or
|
||||||
this case, we passed it two equal fractional sizes, telling it to split the
|
[fractions]($fraction). In this case, we passed it two equal fractional sizes,
|
||||||
available space into two equal columns. We then passed two content arguments to
|
telling it to split the available space into two equal columns. We then passed
|
||||||
the grid function. The first with our own details, and the second with our
|
two content arguments to the grid function --- the first with our own details,
|
||||||
supervisors'. We again use the `align` function to center the content within the
|
and the second with our supervisor's. With grid, we can avoid using `align` on
|
||||||
column. The grid takes an arbitrary number of content arguments specifying the
|
each cell content to center them, and instead use the `align` field to do this
|
||||||
cells. Rows are added automatically, but they can also be manually sized with
|
for all cells automatically. The grid takes an arbitrary number of content
|
||||||
the `rows` argument.
|
arguments specifying the cells. Rows are added automatically, but they can also
|
||||||
|
be manually sized with the `rows` field.
|
||||||
|
|
||||||
Now, let's add the abstract. Remember that the conference wants the abstract to
|
Now, let's add the abstract. Remember that the conference wants the abstract to
|
||||||
be set ragged and centered.
|
be set ragged and centered.
|
||||||
|
|
||||||
```example:0,0,612,317.5
|
```example:0,0,612,317.5
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
|
||||||
>>> #set par(justify: true)
|
|
||||||
>>> #set page(
|
>>> #set page(
|
||||||
>>> "us-letter",
|
>>> paper: "us-letter",
|
||||||
>>> margin: auto,
|
>>> header: align(right)[
|
||||||
>>> header: align(right + horizon)[
|
|
||||||
>>> A fluid dynamic model for
|
>>> A fluid dynamic model for
|
||||||
>>> glacier flow
|
>>> glacier flow
|
||||||
>>> ],
|
>>> ],
|
||||||
>>> numbering: "1",
|
>>> numbering: "1",
|
||||||
>>> )
|
>>> )
|
||||||
|
>>> #set par(justify: true)
|
||||||
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
>>>
|
>>>
|
||||||
>>> #align(center, text(17pt)[
|
>>> #align(center, block(text(
|
||||||
|
>>> 17pt,
|
||||||
|
>>> hyphenate: false
|
||||||
|
>>> )[
|
||||||
>>> *A fluid dynamic model
|
>>> *A fluid dynamic model
|
||||||
>>> for glacier flow*
|
>>> for glacier flow*
|
||||||
>>> ])
|
>>> ]))
|
||||||
>>>
|
>>>
|
||||||
>>> #grid(
|
>>> #grid(
|
||||||
>>> columns: (1fr, 1fr),
|
>>> columns: (1fr, 1fr),
|
||||||
>>> align(center)[
|
>>> align: center,
|
||||||
|
>>> [
|
||||||
>>> Therese Tungsten \
|
>>> Therese Tungsten \
|
||||||
>>> Artos Institute \
|
>>> Artos Institute \
|
||||||
>>> #link("mailto:tung@artos.edu")
|
>>> #link("mailto:tung@artos.edu")
|
||||||
>>> ],
|
>>> ],
|
||||||
>>> align(center)[
|
>>> [
|
||||||
>>> Dr. John Doe \
|
>>> Dr. John Doe \
|
||||||
>>> Artos Institute \
|
>>> Artos Institute \
|
||||||
>>> #link("mailto:doe@artos.edu")
|
>>> #link("mailto:doe@artos.edu")
|
||||||
@ -198,35 +231,33 @@ keyword:
|
|||||||
for glacier flow
|
for glacier flow
|
||||||
]
|
]
|
||||||
|
|
||||||
<<< ...
|
|
||||||
|
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
|
||||||
>>> #set par(justify: true)
|
|
||||||
#set page(
|
#set page(
|
||||||
>>> "us-letter",
|
>>> paper: "us-letter",
|
||||||
>>> margin: auto,
|
<<< ...
|
||||||
header: align(
|
header: align(right, title),
|
||||||
right + horizon,
|
|
||||||
title
|
|
||||||
),
|
|
||||||
<<< ...
|
<<< ...
|
||||||
>>> numbering: "1",
|
>>> numbering: "1",
|
||||||
)
|
)
|
||||||
|
>>> #set par(justify: true)
|
||||||
#align(center, text(17pt)[
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
*#title*
|
|
||||||
])
|
|
||||||
|
|
||||||
<<< ...
|
<<< ...
|
||||||
|
|
||||||
|
#align(center, block(text(
|
||||||
|
17pt,
|
||||||
|
hyphenate: false,
|
||||||
|
strong(title),
|
||||||
|
)))
|
||||||
|
|
||||||
|
<<< ...
|
||||||
>>> #grid(
|
>>> #grid(
|
||||||
>>> columns: (1fr, 1fr),
|
>>> columns: (1fr, 1fr),
|
||||||
>>> align(center)[
|
>>> align: center,
|
||||||
|
>>> [
|
||||||
>>> Therese Tungsten \
|
>>> Therese Tungsten \
|
||||||
>>> Artos Institute \
|
>>> Artos Institute \
|
||||||
>>> #link("mailto:tung@artos.edu")
|
>>> #link("mailto:tung@artos.edu")
|
||||||
>>> ],
|
>>> ],
|
||||||
>>> align(center)[
|
>>> [
|
||||||
>>> Dr. John Doe \
|
>>> Dr. John Doe \
|
||||||
>>> Artos Institute \
|
>>> Artos Institute \
|
||||||
>>> #link("mailto:doe@artos.edu")
|
>>> #link("mailto:doe@artos.edu")
|
||||||
@ -247,55 +278,48 @@ and also within markup (prefixed by `#`, like functions). This way, if we decide
|
|||||||
on another title, we can easily change it in one place.
|
on another title, we can easily change it in one place.
|
||||||
|
|
||||||
## Adding columns and headings { #columns-and-headings }
|
## Adding columns and headings { #columns-and-headings }
|
||||||
The paper above unfortunately looks like a wall of lead. To fix that, let's add
|
The paper above unfortunately looks like a wall of lead(?). To fix that, let's add
|
||||||
some headings and switch our paper to a two-column layout. Fortunately, that's
|
some headings and switch our paper to a two-column layout. Fortunately, that's
|
||||||
easy to do: We just need to amend our `page` set rule with the `columns`
|
easy to do: we just need to amend our `page` set rule with the `columns` field.
|
||||||
argument.
|
|
||||||
|
|
||||||
By adding `{columns: 2}` to the argument list, we have wrapped the whole
|
By adding `{columns: 2}` to the argument list, we have wrapped the whole
|
||||||
document in two columns. However, that would also affect the title and authors
|
document in two columns. However, that would also affect the title and authors
|
||||||
overview. To keep them spanning the whole page, we can wrap them in a function
|
overview. To keep them spanning the whole page, we can wrap them in a function
|
||||||
call to [`{place}`]($place). Place expects an alignment and the content it
|
call to [`{place}`]($place). The `place` expects an alignment and the content it
|
||||||
should place as positional arguments. Using the named `{scope}` argument, we can
|
should place as positional arguments. Using the named `scope` field, we can
|
||||||
decide if the items should be placed relative to the current column or its
|
decide if the items should be placed relative to the current column or its
|
||||||
parent (the page). There is one more thing to configure: If no other arguments
|
parent (the page). There is one more thing to configure: If no other arguments
|
||||||
are provided, `{place}` takes its content out of the flow of the document and
|
are provided, `place` takes its content out of the flow of the document and
|
||||||
positions it over the other content without affecting the layout of other
|
positions it over the other content without affecting the layout of other
|
||||||
content in its container:
|
content in its container:
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#place(
|
#place(top + center, rect())
|
||||||
top + center,
|
|
||||||
rect(fill: black),
|
|
||||||
)
|
|
||||||
#lorem(30)
|
#lorem(30)
|
||||||
```
|
```
|
||||||
|
|
||||||
If we hadn't used `{place}` here, the square would be in its own line, but here
|
If we hadn't used `place` here, the rectangle would be in its own line, but
|
||||||
it overlaps the few lines of text following it. Likewise, that text acts like as
|
here it overlaps the few lines of text following it. Likewise, that text acts
|
||||||
if there was no square. To change this behavior, we can pass the argument
|
like as if there was no rectangle. To change this behavior, we can pass the
|
||||||
`{float: true}` to ensure that the space taken up by the placed item at the top
|
argument `{float: true}` to ensure that the space taken up by the placed item
|
||||||
or bottom of the page is not occupied by any other content.
|
at the top or bottom of the page is not occupied by any other content.
|
||||||
|
|
||||||
```example:single
|
```example:single
|
||||||
>>> #let title = [
|
>>> #let title = [
|
||||||
>>> A fluid dynamic model
|
>>> A fluid dynamic model
|
||||||
>>> for glacier flow
|
>>> for glacier flow
|
||||||
>>> ]
|
>>> ]
|
||||||
>>>
|
<<< ...
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
|
||||||
>>> #set par(justify: true)
|
|
||||||
>>>
|
|
||||||
#set page(
|
#set page(
|
||||||
>>> margin: auto,
|
|
||||||
paper: "us-letter",
|
paper: "us-letter",
|
||||||
header: align(
|
header: align(right, title),
|
||||||
right + horizon,
|
|
||||||
title
|
|
||||||
),
|
|
||||||
numbering: "1",
|
numbering: "1",
|
||||||
columns: 2,
|
columns: 2,
|
||||||
)
|
)
|
||||||
|
>>> #set par(justify: true)
|
||||||
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
|
<<< ...
|
||||||
|
|
||||||
#place(
|
#place(
|
||||||
top + center,
|
top + center,
|
||||||
@ -303,11 +327,7 @@ or bottom of the page is not occupied by any other content.
|
|||||||
scope: "parent",
|
scope: "parent",
|
||||||
clearance: 2em,
|
clearance: 2em,
|
||||||
)[
|
)[
|
||||||
>>> #text(
|
>>> #block(text(17pt, hyphenate: false, strong(title)))
|
||||||
>>> 17pt,
|
|
||||||
>>> weight: "bold",
|
|
||||||
>>> title,
|
|
||||||
>>> )
|
|
||||||
>>>
|
>>>
|
||||||
>>> #grid(
|
>>> #grid(
|
||||||
>>> columns: (1fr, 1fr),
|
>>> columns: (1fr, 1fr),
|
||||||
@ -324,10 +344,9 @@ or bottom of the page is not occupied by any other content.
|
|||||||
>>> )
|
>>> )
|
||||||
<<< ...
|
<<< ...
|
||||||
|
|
||||||
#par(justify: false)[
|
#set par(justify: false) // Put it above to remove hyphenate?
|
||||||
*Abstract* \
|
*Abstract* \
|
||||||
#lorem(80)
|
#lorem(80)
|
||||||
]
|
|
||||||
]
|
]
|
||||||
|
|
||||||
= Introduction
|
= Introduction
|
||||||
@ -337,14 +356,14 @@ or bottom of the page is not occupied by any other content.
|
|||||||
#lorem(200)
|
#lorem(200)
|
||||||
```
|
```
|
||||||
|
|
||||||
In this example, we also used the `clearance` argument of the `{place}` function
|
In this example, we also used the `clearance` argument of the `place` function
|
||||||
to provide the space between it and the body instead of using the [`{v}`]($v)
|
to provide the space between it and the body instead of using the [`v`]
|
||||||
function. We can also remove the explicit `{align(center, ..)}` calls around the
|
function. We can also remove the explicit `{align(center, ..)}` calls around the
|
||||||
various parts since they inherit the center alignment from the placement.
|
various parts since they inherit the center alignment from the placement.
|
||||||
|
|
||||||
Now there is only one thing left to do: Style our headings. We need to make them
|
Now there is only one thing left to do: style our headings. We need to make them
|
||||||
centered and use small capitals. Because the `heading` function does not offer
|
centered and use small capitals. For centering we can use a show-set rule, but
|
||||||
a way to set any of that, we need to write our own heading show rule.
|
to use small capitals we need to write our own heading show rule.
|
||||||
|
|
||||||
```example:50,250,265,270
|
```example:50,250,265,270
|
||||||
>>> #let title = [
|
>>> #let title = [
|
||||||
@ -352,37 +371,27 @@ a way to set any of that, we need to write our own heading show rule.
|
|||||||
>>> for glacier flow
|
>>> for glacier flow
|
||||||
>>> ]
|
>>> ]
|
||||||
>>>
|
>>>
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
|
||||||
>>> #set par(justify: true)
|
|
||||||
>>> #set page(
|
>>> #set page(
|
||||||
>>> "us-letter",
|
>>> paper: "us-letter",
|
||||||
>>> margin: auto,
|
>>> header: align(right, title),
|
||||||
>>> header: align(
|
|
||||||
>>> right + horizon,
|
|
||||||
>>> title
|
|
||||||
>>> ),
|
|
||||||
>>> numbering: "1",
|
>>> numbering: "1",
|
||||||
>>> columns: 2,
|
>>> columns: 2,
|
||||||
>>> )
|
>>> )
|
||||||
#show heading: it => [
|
>>> #set par(justify: true)
|
||||||
#set align(center)
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
#set text(13pt, weight: "regular")
|
<<< ...
|
||||||
#block(smallcaps(it.body))
|
#show heading: set align(center)
|
||||||
]
|
#show heading: set text(13pt, weight: "regular")
|
||||||
|
#show heading: it => block(smallcaps(it.body))
|
||||||
|
|
||||||
<<< ...
|
<<< ...
|
||||||
>>>
|
|
||||||
>>> #place(
|
>>> #place(
|
||||||
>>> top + center,
|
>>> top + center,
|
||||||
>>> float: true,
|
>>> float: true,
|
||||||
>>> scope: "parent",
|
>>> scope: "parent",
|
||||||
>>> clearance: 2em,
|
>>> clearance: 2em,
|
||||||
>>> )[
|
>>> )[
|
||||||
>>> #text(
|
>>> #block(text(17pt, hyphenate: false, strong(title)))
|
||||||
>>> 17pt,
|
|
||||||
>>> weight: "bold",
|
|
||||||
>>> title,
|
|
||||||
>>> )
|
|
||||||
>>>
|
>>>
|
||||||
>>> #grid(
|
>>> #grid(
|
||||||
>>> columns: (1fr, 1fr),
|
>>> columns: (1fr, 1fr),
|
||||||
@ -398,10 +407,9 @@ a way to set any of that, we need to write our own heading show rule.
|
|||||||
>>> ]
|
>>> ]
|
||||||
>>> )
|
>>> )
|
||||||
>>>
|
>>>
|
||||||
>>> #par(justify: false)[
|
>>> #set par(justify: false)
|
||||||
>>> *Abstract* \
|
>>> *Abstract* \
|
||||||
>>> #lorem(80)
|
>>> #lorem(80)
|
||||||
>>> ]
|
|
||||||
>>> ]
|
>>> ]
|
||||||
>>>
|
>>>
|
||||||
>>> = Introduction
|
>>> = Introduction
|
||||||
@ -411,19 +419,26 @@ a way to set any of that, we need to write our own heading show rule.
|
|||||||
>>> #lorem(45)
|
>>> #lorem(45)
|
||||||
```
|
```
|
||||||
|
|
||||||
This looks great! We used a show rule that applies to all headings. We give it a
|
This looks great! We used a few rules that apply to all headings. First, we
|
||||||
function that gets passed the heading as a parameter. That parameter can be used
|
made headings centered, then we set font size to 13 pt and removed default
|
||||||
as content but it also has some fields like `title`, `numbers`, and `level` from
|
heading boldness by setting `weight` to `{"regular"}`. Lastly, there is a show
|
||||||
which we can compose a custom look. Here, we are center-aligning, setting the
|
rule with a closure, i.e., a callback function. We gave it a function that
|
||||||
font weight to `{"regular"}` because headings are bold by default, and use the
|
passes the heading as argument. That argument can be used as content but it
|
||||||
[`smallcaps`] function to render the heading's title in small capitals.
|
also has some fields like `title`, `numbers`, and `level`, from which we can
|
||||||
|
compose a custom look. Here, we use the [`smallcaps`] function to render the
|
||||||
|
heading's title in small capitals. Note that heading itself is wrapped in a
|
||||||
|
block by default, as it's a block-level element, and by using `{it.body}` we
|
||||||
|
are destroying the default structure of the heading. This means we strip away
|
||||||
|
not only the `block` "shell", but also any potential numbering and other
|
||||||
|
heading features. To restore it's semantic structure, we wrap
|
||||||
|
`{smallcaps(it.body)}` in a `block`. This way it will behave like usual.
|
||||||
|
|
||||||
The only remaining problem is that all headings look the same now. The
|
The only remaining problem is that all headings now look the same. The
|
||||||
"Motivation" and "Problem Statement" subsections ought to be italic run in
|
"Motivation" and "Problem Statement" subsections ought to be italic run-in
|
||||||
headers, but right now, they look indistinguishable from the section headings. We
|
headings, but right now, they look indistinguishable from the section headings.
|
||||||
can fix that by using a `where` selector on our set rule: This is a
|
We can fix that by using a `where` selector on our show rules: this is a
|
||||||
[method]($scripting/#methods) we can call on headings (and other
|
[method]($scripting/#methods) we can call on headings (and other elements) that
|
||||||
elements) that allows us to filter them by their level. We can use it to
|
allows us to filter them by their level (and other fields). We can use it to
|
||||||
differentiate between section and subsection headings:
|
differentiate between section and subsection headings:
|
||||||
|
|
||||||
```example:50,250,265,245
|
```example:50,250,265,245
|
||||||
@ -432,66 +447,49 @@ differentiate between section and subsection headings:
|
|||||||
>>> for glacier flow
|
>>> for glacier flow
|
||||||
>>> ]
|
>>> ]
|
||||||
>>>
|
>>>
|
||||||
>>> #set text(font: "Libertinus Serif", 11pt)
|
|
||||||
>>> #set par(justify: true)
|
|
||||||
>>> #set page(
|
>>> #set page(
|
||||||
>>> "us-letter",
|
>>> paper: "us-letter",
|
||||||
>>> margin: auto,
|
>>> header: align(right, title),
|
||||||
>>> header: align(
|
|
||||||
>>> right + horizon,
|
|
||||||
>>> title
|
|
||||||
>>> ),
|
|
||||||
>>> numbering: "1",
|
>>> numbering: "1",
|
||||||
>>> columns: 2,
|
>>> columns: 2,
|
||||||
>>> )
|
>>> )
|
||||||
>>>
|
>>> #set par(justify: true)
|
||||||
#show heading.where(
|
>>> #set text(11pt, font: "Libertinus Serif")
|
||||||
level: 1
|
<<< ...
|
||||||
): it => block(width: 100%)[
|
|
||||||
#set align(center)
|
|
||||||
#set text(13pt, weight: "regular")
|
|
||||||
#smallcaps(it.body)
|
|
||||||
]
|
|
||||||
|
|
||||||
#show heading.where(
|
#show heading.where(level: 1): set align(center)
|
||||||
level: 2
|
#show heading.where(level: 1): set text(13pt, weight: "regular")
|
||||||
): it => text(
|
#show heading.where(level: 1): it => block(smallcaps(it.body))
|
||||||
size: 11pt,
|
|
||||||
weight: "regular",
|
#show heading.where(level: 2): set text(11pt, weight: "regular", style: "italic")
|
||||||
style: "italic",
|
#show heading.where(level: 2): it => [#it.body.]
|
||||||
it.body + [.],
|
|
||||||
)
|
<<< ...
|
||||||
>>>
|
|
||||||
>>> #place(
|
>>> #place(
|
||||||
>>> top + center,
|
>>> top + center,
|
||||||
>>> float: true,
|
>>> float: true,
|
||||||
>>> scope: "parent",
|
>>> scope: "parent",
|
||||||
>>> clearance: 2em,
|
>>> clearance: 2em,
|
||||||
>>> )[
|
>>> )[
|
||||||
>>> #text(
|
>>> #block(text(17pt, hyphenate: false, strong(title)))
|
||||||
>>> 17pt,
|
>>>
|
||||||
>>> weight: "bold",
|
>>> #grid(
|
||||||
>>> title,
|
>>> columns: (1fr, 1fr),
|
||||||
|
>>> [
|
||||||
|
>>> Therese Tungsten \
|
||||||
|
>>> Artos Institute \
|
||||||
|
>>> #link("mailto:tung@artos.edu")
|
||||||
|
>>> ],
|
||||||
|
>>> [
|
||||||
|
>>> Dr. John Doe \
|
||||||
|
>>> Artos Institute \
|
||||||
|
>>> #link("mailto:doe@artos.edu")
|
||||||
|
>>> ]
|
||||||
>>> )
|
>>> )
|
||||||
>>>
|
>>>
|
||||||
>>> #grid(
|
>>> #set par(justify: false)
|
||||||
>>> columns: (1fr, 1fr),
|
>>> *Abstract* \
|
||||||
>>> [
|
>>> #lorem(80)
|
||||||
>>> Therese Tungsten \
|
|
||||||
>>> Artos Institute \
|
|
||||||
>>> #link("mailto:tung@artos.edu")
|
|
||||||
>>> ],
|
|
||||||
>>> [
|
|
||||||
>>> Dr. John Doe \
|
|
||||||
>>> Artos Institute \
|
|
||||||
>>> #link("mailto:doe@artos.edu")
|
|
||||||
>>> ]
|
|
||||||
>>> )
|
|
||||||
>>>
|
|
||||||
>>> #par(justify: false)[
|
|
||||||
>>> *Abstract* \
|
|
||||||
>>> #lorem(80)
|
|
||||||
>>> ]
|
|
||||||
>>> ]
|
>>> ]
|
||||||
>>>
|
>>>
|
||||||
>>> = Introduction
|
>>> = Introduction
|
||||||
@ -501,23 +499,26 @@ differentiate between section and subsection headings:
|
|||||||
>>> #lorem(45)
|
>>> #lorem(45)
|
||||||
```
|
```
|
||||||
|
|
||||||
This looks great! We wrote two show rules that each selectively apply to the
|
Excellent! We wrote several rules that selectively apply to the first and second
|
||||||
first and second level headings. We used a `where` selector to filter the
|
level headings. We used a `where` selector to filter the headings by their
|
||||||
headings by their level. We then rendered the subsection headings as run-ins. We
|
level. We then rendered the subsection headings as run-ins. We also
|
||||||
also automatically add a period to the end of the subsection headings.
|
automatically added a period to the end of the subsection headings. This time
|
||||||
|
we did not wrap result in a `block`, because we need the heading to be inline
|
||||||
|
with the following text.
|
||||||
|
|
||||||
Let's review the conference's style guide:
|
Let's review the conference's style guide:
|
||||||
- The font should be an 11pt serif font ✓
|
- The font should be an 11 pt serif font ✓
|
||||||
- The title should be in 17pt and bold ✓
|
- The title should be in 17 pt and bold ✓
|
||||||
- The paper contains a single-column abstract and two-column main text ✓
|
- The paper contains a single-column abstract and two-column main text ✓
|
||||||
- The abstract should be centered ✓
|
- The abstract should be centered ✓
|
||||||
- The main text should be justified ✓
|
- The main text should be justified ✓
|
||||||
- First level section headings should be centered, rendered in small caps and in
|
- First level section headings should be centered, rendered in small caps and in
|
||||||
13pt ✓
|
13 pt ✓
|
||||||
- Second level headings are run-ins, italicized and have the same size as the
|
- Second level headings are run-ins, italicized and have the same size as the
|
||||||
body text ✓
|
body text ✓
|
||||||
- Finally, the pages should be US letter sized, numbered in the center and the
|
- Finally, the pages should be US letter sized, numbered in the center of the
|
||||||
top right corner of each page should contain the title of the paper ✓
|
footer and the top right corner of each page should contain the title of the
|
||||||
|
paper ✓
|
||||||
|
|
||||||
We are now in compliance with all of these styles and can submit the paper to
|
We are now in compliance with all of these styles and can submit the paper to
|
||||||
the conference! The finished paper looks like this:
|
the conference! The finished paper looks like this:
|
||||||
@ -528,6 +529,62 @@ the conference! The finished paper looks like this:
|
|||||||
style="box-shadow: 0 4px 12px rgb(89 85 101 / 20%); width: 500px; max-width: 100%; display: block; margin: 24px auto;"
|
style="box-shadow: 0 4px 12px rgb(89 85 101 / 20%); width: 500px; max-width: 100%; display: block; margin: 24px auto;"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
Here is a full listing of the finished paper:
|
||||||
|
|
||||||
|
```example
|
||||||
|
#let title = [A fluid dynamic model for glacier flow]
|
||||||
|
|
||||||
|
#set page(
|
||||||
|
paper: "us-letter",
|
||||||
|
header: align(right, title),
|
||||||
|
numbering: "1",
|
||||||
|
columns: 2,
|
||||||
|
)
|
||||||
|
#set par(justify: true)
|
||||||
|
#set text(11pt, font: "Libertinus Serif")
|
||||||
|
|
||||||
|
#show heading.where(level: 1): set align(center)
|
||||||
|
#show heading.where(level: 1): set text(13pt, weight: "regular")
|
||||||
|
#show heading.where(level: 1): it => block(smallcaps(it.body))
|
||||||
|
|
||||||
|
#show heading.where(level: 2): set text(11pt, weight: "regular", style: "italic")
|
||||||
|
#show heading.where(level: 2): it => [#it.body.]
|
||||||
|
|
||||||
|
#place(top + center, float: true, scope: "parent", clearance: 2em)[
|
||||||
|
#block(text(17pt, hyphenate: false, strong(title)))
|
||||||
|
|
||||||
|
#grid(
|
||||||
|
columns: (1fr, 1fr),
|
||||||
|
[
|
||||||
|
Therese Tungsten \
|
||||||
|
Artos Institute \
|
||||||
|
#link("mailto:tung@artos.edu")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
Dr. John Doe \
|
||||||
|
Artos Institute \
|
||||||
|
#link("mailto:doe@artos.edu")
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
#set par(justify: false)
|
||||||
|
*Abstract* \
|
||||||
|
#lorem(80)
|
||||||
|
]
|
||||||
|
|
||||||
|
= Introduction
|
||||||
|
#lorem(90)
|
||||||
|
|
||||||
|
== Motivation
|
||||||
|
#lorem(140)
|
||||||
|
|
||||||
|
== Problem Statement
|
||||||
|
#lorem(50)
|
||||||
|
|
||||||
|
= Related Work
|
||||||
|
#lorem(200)
|
||||||
|
```
|
||||||
|
|
||||||
## Review
|
## Review
|
||||||
You have now learned how to create headers and footers, how to use functions and
|
You have now learned how to create headers and footers, how to use functions and
|
||||||
scopes to locally override styles, how to create more complex layouts with the
|
scopes to locally override styles, how to create more complex layouts with the
|
||||||
|
@ -25,7 +25,10 @@ You are #amazed[beautiful]!
|
|||||||
|
|
||||||
This function takes a single argument, `term`, and returns a content block with
|
This function takes a single argument, `term`, and returns a content block with
|
||||||
the `term` surrounded by sparkles. We also put the whole thing in a box so that
|
the `term` surrounded by sparkles. We also put the whole thing in a box so that
|
||||||
the term we are amazed by cannot be separated from its sparkles by a line break.
|
the term we are amazed by cannot be separated from its sparkles by a line
|
||||||
|
break. Alternatively, you can use a
|
||||||
|
[shorthand](https://typst.app/docs/reference/symbols/#shorthands)
|
||||||
|
for a no-break space and write `{[✨~#term~✨]}`.
|
||||||
|
|
||||||
Many functions that come with Typst have optional named parameters. Our
|
Many functions that come with Typst have optional named parameters. Our
|
||||||
functions can also have them. Let's add a parameter to our function that lets us
|
functions can also have them. Let's add a parameter to our function that lets us
|
||||||
@ -34,7 +37,7 @@ parameter isn't given.
|
|||||||
|
|
||||||
```example
|
```example
|
||||||
#let amazed(term, color: blue) = {
|
#let amazed(term, color: blue) = {
|
||||||
text(color, box[✨ #term ✨])
|
text(color)[✨~#term~✨]
|
||||||
}
|
}
|
||||||
|
|
||||||
You are #amazed[beautiful]!
|
You are #amazed[beautiful]!
|
||||||
@ -43,7 +46,7 @@ I am #amazed(color: purple)[amazed]!
|
|||||||
|
|
||||||
Templates now work by wrapping our whole document in a custom function like
|
Templates now work by wrapping our whole document in a custom function like
|
||||||
`amazed`. But wrapping a whole document in a giant function call would be
|
`amazed`. But wrapping a whole document in a giant function call would be
|
||||||
cumbersome! Instead, we can use an "everything" show rule to achieve the same
|
cumbersome! Instead, we can use an "global" show rule to achieve the same
|
||||||
with cleaner code. To write such a show rule, put a colon directly after the
|
with cleaner code. To write such a show rule, put a colon directly after the
|
||||||
show keyword and then provide a function. This function is given the rest of the
|
show keyword and then provide a function. This function is given the rest of the
|
||||||
document as a parameter. The function can then do anything with this content.
|
document as a parameter. The function can then do anything with this content.
|
||||||
@ -52,7 +55,7 @@ just pass it by name to the show rule. Let's try it:
|
|||||||
|
|
||||||
```example
|
```example
|
||||||
>>> #let amazed(term, color: blue) = {
|
>>> #let amazed(term, color: blue) = {
|
||||||
>>> text(color, box[✨ #term ✨])
|
>>> text(color)[✨~#term~✨]
|
||||||
>>> }
|
>>> }
|
||||||
#show: amazed
|
#show: amazed
|
||||||
I choose to focus on the good
|
I choose to focus on the good
|
||||||
@ -68,69 +71,56 @@ powerful.
|
|||||||
|
|
||||||
## Embedding set and show rules { #set-and-show-rules }
|
## Embedding set and show rules { #set-and-show-rules }
|
||||||
To apply some set and show rules to our template, we can use `set` and `show`
|
To apply some set and show rules to our template, we can use `set` and `show`
|
||||||
within a content block in our function and then insert the document into
|
within a code block in our function and then insert the document into
|
||||||
that content block.
|
that code block.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#let template(doc) = [
|
#let template(doc) = {
|
||||||
#set text(font: "Inria Serif")
|
set text(font: "Inria Serif")
|
||||||
#show "something cool": [Typst]
|
show "something cool": [Typst]
|
||||||
#doc
|
doc
|
||||||
]
|
}
|
||||||
|
|
||||||
#show: template
|
#show: template
|
||||||
I am learning something cool today.
|
I am learning something cool today.
|
||||||
It's going great so far!
|
It's going great so far!
|
||||||
```
|
```
|
||||||
|
|
||||||
Just like we already discovered in the previous chapter, set rules will apply to
|
Just like we already discovered in the previous chapter, set rules will apply
|
||||||
everything within their content block. Since the everything show rule passes our
|
to everything within their scope. Since the global show rule passes our whole
|
||||||
whole document to the `template` function, the text set rule and string show
|
document to the `template` function, the text set rule and string show rule in
|
||||||
rule in our template will apply to the whole document. Let's use this knowledge
|
our template will apply to the whole document.
|
||||||
to create a template that reproduces the body style of the paper we wrote in the
|
|
||||||
previous chapter.
|
We used a curly-braced code block instead of a content block. This way, we
|
||||||
|
don't need to prefix all set rules and function calls with a `#`. This also
|
||||||
|
removes the implicit spaces that are naturally introduced in the markup mode.
|
||||||
|
In exchange, we cannot write markup directly in the code block anymore.
|
||||||
|
|
||||||
|
Let's use this knowledge to create a template that reproduces the body style of
|
||||||
|
the paper we wrote in the previous chapter.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#let conf(title, doc) = {
|
#let conf(title, doc) = {
|
||||||
set page(
|
set page(
|
||||||
paper: "us-letter",
|
paper: "us-letter",
|
||||||
>>> margin: auto,
|
header: align(right, title),
|
||||||
header: align(
|
|
||||||
right + horizon,
|
|
||||||
title
|
|
||||||
),
|
|
||||||
columns: 2,
|
columns: 2,
|
||||||
<<< ...
|
<<< ...
|
||||||
)
|
)
|
||||||
set par(justify: true)
|
set par(justify: true)
|
||||||
set text(
|
set text(
|
||||||
|
11pt,
|
||||||
font: "Libertinus Serif",
|
font: "Libertinus Serif",
|
||||||
size: 11pt,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Heading show rules.
|
// Heading show rules.
|
||||||
<<< ...
|
<<< ...
|
||||||
>>> show heading.where(
|
>>> show heading.where(level: 1): set align(center)
|
||||||
>>> level: 1
|
>>> show heading.where(level: 1): set text(13pt, weight: "regular")
|
||||||
>>> ): it => block(
|
>>> show heading.where(level: 1): it => block(smallcaps(it.body))
|
||||||
>>> align(center,
|
>>>
|
||||||
>>> text(
|
>>> show heading.where(level: 2): set text(11pt, weight: "regular", style: "italic")
|
||||||
>>> 13pt,
|
>>> show heading.where(level: 2): it => [#it.body.]
|
||||||
>>> weight: "regular",
|
|
||||||
>>> smallcaps(it.body),
|
|
||||||
>>> )
|
|
||||||
>>> ),
|
|
||||||
>>> )
|
|
||||||
>>> show heading.where(
|
|
||||||
>>> level: 2
|
|
||||||
>>> ): it => box(
|
|
||||||
>>> text(
|
|
||||||
>>> 11pt,
|
|
||||||
>>> weight: "regular",
|
|
||||||
>>> style: "italic",
|
|
||||||
>>> it.body + [.],
|
|
||||||
>>> )
|
|
||||||
>>> )
|
|
||||||
|
|
||||||
doc
|
doc
|
||||||
}
|
}
|
||||||
@ -154,24 +144,17 @@ previous chapter.
|
|||||||
>>> #lorem(200)
|
>>> #lorem(200)
|
||||||
```
|
```
|
||||||
|
|
||||||
We copy-pasted most of that code from the previous chapter. The two differences
|
We copied most of that code from the previous chapter. However, now we wrapped
|
||||||
are this:
|
everything in the function `conf` using a global show rule. The function applies
|
||||||
|
a few set and show rules and echoes the content it has been passed at the end.
|
||||||
|
|
||||||
1. We wrapped everything in the function `conf` using an everything show rule.
|
Also note where the title comes from: we previously had it inside of a variable.
|
||||||
The function applies a few set and show rules and echoes the content it has
|
|
||||||
been passed at the end.
|
|
||||||
|
|
||||||
2. Moreover, we used a curly-braced code block instead of a content block. This
|
|
||||||
way, we don't need to prefix all set rules and function calls with a `#`. In
|
|
||||||
exchange, we cannot write markup directly in the code block anymore.
|
|
||||||
|
|
||||||
Also note where the title comes from: We previously had it inside of a variable.
|
|
||||||
Now, we are receiving it as the first parameter of the template function. To do
|
Now, we are receiving it as the first parameter of the template function. To do
|
||||||
so, we passed a closure (that's a function without a name that is used right
|
so, we passed a closure (that's a function without a name that is used right
|
||||||
away) to the everything show rule. We did that because the `conf` function
|
away) to the global show rule. We did that because the `conf` function expects
|
||||||
expects two positional arguments, the title and the body, but the show rule will
|
two positional arguments: the title and the body, but the show rule will only
|
||||||
only pass the body. Therefore, we add a new function definition that allows us
|
pass the body. Therefore, we add a new function definition that allows us to set
|
||||||
to set a paper title and use the single parameter from the show rule.
|
a paper title and use the single parameter from the show rule.
|
||||||
|
|
||||||
## Templates with named arguments { #named-arguments }
|
## Templates with named arguments { #named-arguments }
|
||||||
Our paper in the previous chapter had a title and an author list. Let's add
|
Our paper in the previous chapter had a title and an author list. Let's add
|
||||||
@ -230,6 +213,9 @@ multiple arguments for the grid. We can do that by using the
|
|||||||
[`spread` operator]($arguments). It takes an array and applies each of its items
|
[`spread` operator]($arguments). It takes an array and applies each of its items
|
||||||
as a separate argument to the function.
|
as a separate argument to the function.
|
||||||
|
|
||||||
|
Let's also include some PDF metadata. We can achieve this by using
|
||||||
|
the [`document`] function and specifying fields such as `title` and `author`.
|
||||||
|
|
||||||
The resulting template function looks like this:
|
The resulting template function looks like this:
|
||||||
|
|
||||||
```typ
|
```typ
|
||||||
@ -239,31 +225,33 @@ The resulting template function looks like this:
|
|||||||
abstract: [],
|
abstract: [],
|
||||||
doc,
|
doc,
|
||||||
) = {
|
) = {
|
||||||
|
set document(title: title, author: authors.map(author => author.name))
|
||||||
// Set and show rules from before.
|
// Set and show rules from before.
|
||||||
>>> #set page(columns: 2)
|
|
||||||
<<< ...
|
<<< ...
|
||||||
|
|
||||||
set align(center)
|
{
|
||||||
text(17pt, title)
|
set align(center)
|
||||||
|
set par(justify: false)
|
||||||
|
|
||||||
let count = authors.len()
|
block(text(17pt, strong(title)))
|
||||||
let ncols = calc.min(count, 3)
|
|
||||||
grid(
|
|
||||||
columns: (1fr,) * ncols,
|
|
||||||
row-gutter: 24pt,
|
|
||||||
..authors.map(author => [
|
|
||||||
#author.name \
|
|
||||||
#author.affiliation \
|
|
||||||
#link("mailto:" + author.email)
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
|
|
||||||
par(justify: false)[
|
let count = authors.len()
|
||||||
*Abstract* \
|
let ncols = calc.min(count, 3)
|
||||||
#abstract
|
grid(
|
||||||
]
|
columns: (1fr,) * ncols,
|
||||||
|
row-gutter: 24pt,
|
||||||
|
..authors.map(author => [
|
||||||
|
#author.name \
|
||||||
|
#author.affiliation \
|
||||||
|
#link("mailto:" + author.email)
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
strong[Abstract]
|
||||||
|
linebreak()
|
||||||
|
abstract
|
||||||
|
}
|
||||||
|
|
||||||
set align(left)
|
|
||||||
doc
|
doc
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -291,72 +279,45 @@ call.
|
|||||||
>>> abstract: [],
|
>>> abstract: [],
|
||||||
>>> doc,
|
>>> doc,
|
||||||
>>> ) = {
|
>>> ) = {
|
||||||
>>> set text(font: "Libertinus Serif", 11pt)
|
>>> set document(title: title, author: authors.map(author => author.name))
|
||||||
>>> set par(justify: true)
|
>>> set page(
|
||||||
>>> set page(
|
>>> "us-letter",
|
||||||
>>> "us-letter",
|
>>> header: align(right, title),
|
||||||
>>> margin: auto,
|
>>> numbering: "1",
|
||||||
>>> header: align(
|
>>> columns: 2,
|
||||||
>>> right + horizon,
|
>>> )
|
||||||
>>> title
|
>>> set par(justify: true)
|
||||||
>>> ),
|
>>> set text(11pt, font: "Libertinus Serif")
|
||||||
>>> numbering: "1",
|
|
||||||
>>> columns: 2,
|
|
||||||
>>> )
|
|
||||||
>>>
|
>>>
|
||||||
>>> show heading.where(
|
>>> show heading.where(level: 1): set align(center)
|
||||||
>>> level: 1
|
>>> show heading.where(level: 1): set text(13pt, weight: "regular")
|
||||||
>>> ): it => block(
|
>>> show heading.where(level: 1): it => block(smallcaps(it.body))
|
||||||
>>> align(center,
|
|
||||||
>>> text(
|
|
||||||
>>> 13pt,
|
|
||||||
>>> weight: "regular",
|
|
||||||
>>> smallcaps(it.body),
|
|
||||||
>>> )
|
|
||||||
>>> ),
|
|
||||||
>>> )
|
|
||||||
>>> show heading.where(
|
|
||||||
>>> level: 2
|
|
||||||
>>> ): it => box(
|
|
||||||
>>> text(
|
|
||||||
>>> 11pt,
|
|
||||||
>>> weight: "regular",
|
|
||||||
>>> style: "italic",
|
|
||||||
>>> it.body + [.],
|
|
||||||
>>> )
|
|
||||||
>>> )
|
|
||||||
>>>
|
>>>
|
||||||
>>> place(
|
>>> show heading.where(level: 2): set text(11pt, weight: "regular", style: "italic")
|
||||||
>>> top,
|
>>> show heading.where(level: 2): it => [#it.body.]
|
||||||
>>> float: true,
|
>>>
|
||||||
>>> scope: "parent",
|
>>> place(top + center, float: true, scope: "parent", clearance: 2em, {
|
||||||
>>> clearance: 2em,
|
>>> set par(justify: false)
|
||||||
>>> {
|
>>> block(text(17pt, title))
|
||||||
>>> set align(center)
|
>>> let count = calc.min(authors.len(), 3)
|
||||||
>>> text(17pt, title)
|
>>> grid(
|
||||||
>>> let count = calc.min(authors.len(), 3)
|
>>> columns: (1fr,) * count,
|
||||||
>>> grid(
|
>>> row-gutter: 24pt,
|
||||||
>>> columns: (1fr,) * count,
|
>>> ..authors.map(author => [
|
||||||
>>> row-gutter: 24pt,
|
>>> #author.name \
|
||||||
>>> ..authors.map(author => [
|
>>> #author.affiliation \
|
||||||
>>> #author.name \
|
>>> #link("mailto:" + author.email)
|
||||||
>>> #author.affiliation \
|
>>> ]),
|
||||||
>>> #link("mailto:" + author.email)
|
>>> )
|
||||||
>>> ]),
|
>>> strong[Abstract]
|
||||||
>>> )
|
>>> linebreak()
|
||||||
>>> par(justify: false)[
|
>>> abstract
|
||||||
>>> *Abstract* \
|
>>> })
|
||||||
>>> #abstract
|
>>> doc
|
||||||
>>> ]
|
>>> }
|
||||||
>>> },
|
|
||||||
>>> )
|
|
||||||
>>> doc
|
|
||||||
>>>}
|
|
||||||
<<< #import "conf.typ": conf
|
<<< #import "conf.typ": conf
|
||||||
#show: conf.with(
|
#show: conf.with(
|
||||||
title: [
|
title: [Towards Improved Modelling],
|
||||||
Towards Improved Modelling
|
|
||||||
],
|
|
||||||
authors: (
|
authors: (
|
||||||
(
|
(
|
||||||
name: "Theresa Tungsten",
|
name: "Theresa Tungsten",
|
||||||
@ -397,7 +358,7 @@ that define reusable document styles. You've made it far and learned a lot. You
|
|||||||
can now use Typst to write your own documents and share them with others.
|
can now use Typst to write your own documents and share them with others.
|
||||||
|
|
||||||
We are still a super young project and are looking for feedback. If you have any
|
We are still a super young project and are looking for feedback. If you have any
|
||||||
questions, suggestions or you found a bug, please let us know
|
questions, suggestions, or you found a bug, please let us know
|
||||||
in the [Forum](https://forum.typst.app/),
|
in the [Forum](https://forum.typst.app/),
|
||||||
on our [Discord server](https://discord.gg/2uDybryKPe),
|
on our [Discord server](https://discord.gg/2uDybryKPe),
|
||||||
on [GitHub](https://github.com/typst/typst/),
|
on [GitHub](https://github.com/typst/typst/),
|
||||||
|
@ -7,7 +7,7 @@ use typst::layout::PagedDocument;
|
|||||||
use typst::syntax::{FileId, Source};
|
use typst::syntax::{FileId, Source};
|
||||||
use typst::text::{Font, FontBook};
|
use typst::text::{Font, FontBook};
|
||||||
use typst::utils::LazyHash;
|
use typst::utils::LazyHash;
|
||||||
use typst::{Library, World};
|
use typst::{Library, LibraryExt, World};
|
||||||
|
|
||||||
struct FuzzWorld {
|
struct FuzzWorld {
|
||||||
library: LazyHash<Library>,
|
library: LazyHash<Library>,
|
||||||
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 908 B After Width: | Height: | Size: 965 B |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
tests/ref/issue-6267-clip-anti-alias.png
Normal file
After Width: | Height: | Size: 251 B |
BIN
tests/ref/math-style-fallback.png
Normal file
After Width: | Height: | Size: 935 B |
Before Width: | Height: | Size: 296 B After Width: | Height: | Size: 489 B |
BIN
tests/ref/math-style-script.png
Normal file
After Width: | Height: | Size: 585 B |
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 828 B After Width: | Height: | Size: 834 B |
@ -19,7 +19,7 @@ use typst::syntax::{FileId, Source, Span};
|
|||||||
use typst::text::{Font, FontBook, TextElem, TextSize};
|
use typst::text::{Font, FontBook, TextElem, TextSize};
|
||||||
use typst::utils::{singleton, LazyHash};
|
use typst::utils::{singleton, LazyHash};
|
||||||
use typst::visualize::Color;
|
use typst::visualize::Color;
|
||||||
use typst::{Feature, Library, World};
|
use typst::{Feature, Library, LibraryExt, World};
|
||||||
use typst_syntax::Lines;
|
use typst_syntax::Lines;
|
||||||
|
|
||||||
/// A world that provides access to the tests environment.
|
/// A world that provides access to the tests environment.
|
||||||
|