Merge branch 'main' into pdf-accessibility
2
.cargo/config.toml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
[alias]
|
||||||
|
testit = "test --workspace --test tests --"
|
70
Cargo.lock
generated
@ -448,42 +448,19 @@ version = "1.0.3"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "comemo"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "df6916408a724339aa77b18214233355f3eb04c42eb895e5f8909215bd8a7a91"
|
|
||||||
dependencies = [
|
|
||||||
"comemo-macros 0.4.0",
|
|
||||||
"once_cell",
|
|
||||||
"parking_lot",
|
|
||||||
"siphasher",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "comemo"
|
name = "comemo"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "649d7b2d867b569729c03c0f6968db10bc95921182a1f2b2012b1b549492f39d"
|
checksum = "649d7b2d867b569729c03c0f6968db10bc95921182a1f2b2012b1b549492f39d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comemo-macros 0.5.0",
|
"comemo-macros",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustc-hash",
|
"rustc-hash",
|
||||||
"siphasher",
|
"siphasher",
|
||||||
"slab",
|
"slab",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "comemo-macros"
|
|
||||||
version = "0.4.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c8936e42f9b4f5bdfaf23700609ac1f11cb03ad4c1ec128a4ee4fd0903e228db"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "comemo-macros"
|
name = "comemo-macros"
|
||||||
version = "0.5.0"
|
version = "0.5.0"
|
||||||
@ -1445,11 +1422,11 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "krilla"
|
name = "krilla"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
source = "git+https://github.com/LaurenzV/krilla?branch=main#1246755ed5ff18a9a8c888694e4b91f3bed1b41a"
|
source = "git+https://github.com/LaurenzV/krilla?rev=1246755#1246755ed5ff18a9a8c888694e4b91f3bed1b41a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"comemo 0.5.0",
|
"comemo",
|
||||||
"flate2",
|
"flate2",
|
||||||
"float-cmp 0.10.0",
|
"float-cmp 0.10.0",
|
||||||
"gif",
|
"gif",
|
||||||
@ -1475,7 +1452,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "krilla-svg"
|
name = "krilla-svg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
source = "git+https://github.com/LaurenzV/krilla?branch=main#1246755ed5ff18a9a8c888694e4b91f3bed1b41a"
|
source = "git+https://github.com/LaurenzV/krilla?rev=1246755#1246755ed5ff18a9a8c888694e4b91f3bed1b41a"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"flate2",
|
"flate2",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
@ -2941,8 +2918,9 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
|||||||
name = "typst"
|
name = "typst"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
|
"rustc-hash",
|
||||||
"typst-eval",
|
"typst-eval",
|
||||||
"typst-html",
|
"typst-html",
|
||||||
"typst-layout",
|
"typst-layout",
|
||||||
@ -2969,7 +2947,7 @@ dependencies = [
|
|||||||
"clap_mangen",
|
"clap_mangen",
|
||||||
"codespan-reporting",
|
"codespan-reporting",
|
||||||
"color-print",
|
"color-print",
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"dirs",
|
"dirs",
|
||||||
"ecow",
|
"ecow",
|
||||||
"fs_extra",
|
"fs_extra",
|
||||||
@ -2978,6 +2956,7 @@ dependencies = [
|
|||||||
"parking_lot",
|
"parking_lot",
|
||||||
"pathdiff",
|
"pathdiff",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"rustc-hash",
|
||||||
"same-file",
|
"same-file",
|
||||||
"self-replace",
|
"self-replace",
|
||||||
"semver",
|
"semver",
|
||||||
@ -3018,6 +2997,7 @@ dependencies = [
|
|||||||
"ecow",
|
"ecow",
|
||||||
"heck",
|
"heck",
|
||||||
"pulldown-cmark",
|
"pulldown-cmark",
|
||||||
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"serde_yaml 0.9.34+deprecated",
|
"serde_yaml 0.9.34+deprecated",
|
||||||
@ -3037,9 +3017,10 @@ dependencies = [
|
|||||||
name = "typst-eval"
|
name = "typst-eval"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"indexmap 2.7.1",
|
"indexmap 2.7.1",
|
||||||
|
"rustc-hash",
|
||||||
"stacker",
|
"stacker",
|
||||||
"toml",
|
"toml",
|
||||||
"typst-library",
|
"typst-library",
|
||||||
@ -3054,7 +3035,7 @@ dependencies = [
|
|||||||
name = "typst-fuzz"
|
name = "typst-fuzz"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"libfuzzer-sys",
|
"libfuzzer-sys",
|
||||||
"typst",
|
"typst",
|
||||||
"typst-assets",
|
"typst-assets",
|
||||||
@ -3067,9 +3048,10 @@ name = "typst-html"
|
|||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"palette",
|
"palette",
|
||||||
|
"rustc-hash",
|
||||||
"time",
|
"time",
|
||||||
"typst-assets",
|
"typst-assets",
|
||||||
"typst-library",
|
"typst-library",
|
||||||
@ -3084,10 +3066,11 @@ dependencies = [
|
|||||||
name = "typst-ide"
|
name = "typst-ide"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pathdiff",
|
"pathdiff",
|
||||||
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
"typst",
|
"typst",
|
||||||
"typst-assets",
|
"typst-assets",
|
||||||
@ -3127,7 +3110,7 @@ dependencies = [
|
|||||||
"az",
|
"az",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"codex",
|
"codex",
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"hypher",
|
"hypher",
|
||||||
"icu_properties",
|
"icu_properties",
|
||||||
@ -3137,6 +3120,7 @@ dependencies = [
|
|||||||
"icu_segmenter",
|
"icu_segmenter",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"rustc-hash",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
@ -3162,7 +3146,7 @@ dependencies = [
|
|||||||
"chinese-number",
|
"chinese-number",
|
||||||
"ciborium",
|
"ciborium",
|
||||||
"codex",
|
"codex",
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"csv",
|
"csv",
|
||||||
"ecow",
|
"ecow",
|
||||||
"flate2",
|
"flate2",
|
||||||
@ -3188,6 +3172,7 @@ dependencies = [
|
|||||||
"regex-syntax",
|
"regex-syntax",
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
|
"rustc-hash",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@ -3232,13 +3217,14 @@ version = "0.13.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"az",
|
"az",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"image",
|
"image",
|
||||||
"infer",
|
"infer",
|
||||||
"krilla",
|
"krilla",
|
||||||
"krilla-svg",
|
"krilla-svg",
|
||||||
"pretty_assertions",
|
"pretty_assertions",
|
||||||
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"typst-assets",
|
"typst-assets",
|
||||||
@ -3255,7 +3241,7 @@ version = "0.13.1"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"regex",
|
"regex",
|
||||||
"typst-library",
|
"typst-library",
|
||||||
@ -3270,7 +3256,7 @@ name = "typst-render"
|
|||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"hayro",
|
"hayro",
|
||||||
"image",
|
"image",
|
||||||
"pixglyph",
|
"pixglyph",
|
||||||
@ -3288,11 +3274,12 @@ name = "typst-svg"
|
|||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"flate2",
|
"flate2",
|
||||||
"hayro",
|
"hayro",
|
||||||
"image",
|
"image",
|
||||||
|
"rustc-hash",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
"typst-assets",
|
"typst-assets",
|
||||||
"typst-library",
|
"typst-library",
|
||||||
@ -3308,6 +3295,7 @@ name = "typst-syntax"
|
|||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ecow",
|
"ecow",
|
||||||
|
"rustc-hash",
|
||||||
"serde",
|
"serde",
|
||||||
"toml",
|
"toml",
|
||||||
"typst-timing",
|
"typst-timing",
|
||||||
@ -3324,12 +3312,13 @@ name = "typst-tests"
|
|||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"comemo 0.4.0",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"oxipng",
|
"oxipng",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rayon",
|
"rayon",
|
||||||
"regex",
|
"regex",
|
||||||
|
"rustc-hash",
|
||||||
"tiny-skia",
|
"tiny-skia",
|
||||||
"typst",
|
"typst",
|
||||||
"typst-assets",
|
"typst-assets",
|
||||||
@ -3361,6 +3350,7 @@ dependencies = [
|
|||||||
"once_cell",
|
"once_cell",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
"rustc-hash",
|
||||||
"siphasher",
|
"siphasher",
|
||||||
"thin-vec",
|
"thin-vec",
|
||||||
"unicode-math-class",
|
"unicode-math-class",
|
||||||
|
@ -49,7 +49,7 @@ clap_mangen = "0.2.10"
|
|||||||
codespan-reporting = "0.11"
|
codespan-reporting = "0.11"
|
||||||
codex = { git = "https://github.com/typst/codex", rev = "9ac86f9" }
|
codex = { git = "https://github.com/typst/codex", rev = "9ac86f9" }
|
||||||
color-print = "0.3.6"
|
color-print = "0.3.6"
|
||||||
comemo = "0.4"
|
comemo = "0.5.0"
|
||||||
csv = "1"
|
csv = "1"
|
||||||
ctrlc = "3.4.1"
|
ctrlc = "3.4.1"
|
||||||
dirs = "6"
|
dirs = "6"
|
||||||
@ -59,6 +59,7 @@ fastrand = "2.3"
|
|||||||
flate2 = "1"
|
flate2 = "1"
|
||||||
fontdb = { version = "0.23", default-features = false }
|
fontdb = { version = "0.23", default-features = false }
|
||||||
fs_extra = "1.3"
|
fs_extra = "1.3"
|
||||||
|
rustc-hash = "2.1"
|
||||||
glidesort = "0.1.2"
|
glidesort = "0.1.2"
|
||||||
hayagriva = "0.8.1"
|
hayagriva = "0.8.1"
|
||||||
hayro-syntax = { git = "https://github.com/LaurenzV/hayro", rev = "e701f95" }
|
hayro-syntax = { git = "https://github.com/LaurenzV/hayro", rev = "e701f95" }
|
||||||
@ -74,8 +75,8 @@ image = { version = "0.25.5", default-features = false, features = ["png", "jpeg
|
|||||||
indexmap = { version = "2", features = ["serde"] }
|
indexmap = { version = "2", features = ["serde"] }
|
||||||
infer = { version = "0.19.0", default-features = false }
|
infer = { version = "0.19.0", default-features = false }
|
||||||
kamadak-exif = "0.6"
|
kamadak-exif = "0.6"
|
||||||
krilla = { git = "https://github.com/LaurenzV/krilla", branch = "main", default-features = false, features = ["raster-images", "comemo", "rayon", "pdf"] }
|
krilla = { git = "https://github.com/LaurenzV/krilla", rev = "1246755", default-features = false, features = ["raster-images", "comemo", "rayon", "pdf"] }
|
||||||
krilla-svg = { git = "https://github.com/LaurenzV/krilla", branch = "main" }
|
krilla-svg = { git = "https://github.com/LaurenzV/krilla", rev = "1246755" }
|
||||||
kurbo = "0.11"
|
kurbo = "0.11"
|
||||||
libfuzzer-sys = "0.4"
|
libfuzzer-sys = "0.4"
|
||||||
lipsum = "0.9"
|
lipsum = "0.9"
|
||||||
|
@ -41,6 +41,7 @@ open = { workspace = true }
|
|||||||
parking_lot = { workspace = true }
|
parking_lot = { workspace = true }
|
||||||
pathdiff = { workspace = true }
|
pathdiff = { workspace = true }
|
||||||
rayon = { workspace = true }
|
rayon = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
same-file = { workspace = true }
|
same-file = { workspace = true }
|
||||||
self-replace = { workspace = true, optional = true }
|
self-replace = { workspace = true, optional = true }
|
||||||
semver = { workspace = true }
|
semver = { workspace = true }
|
||||||
|
@ -155,6 +155,10 @@ pub struct QueryCommand {
|
|||||||
#[clap(long)]
|
#[clap(long)]
|
||||||
pub pretty: bool,
|
pub pretty: bool,
|
||||||
|
|
||||||
|
/// The target to compile for.
|
||||||
|
#[clap(long, default_value_t)]
|
||||||
|
pub target: Target,
|
||||||
|
|
||||||
/// World arguments.
|
/// World arguments.
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub world: WorldArgs,
|
pub world: WorldArgs,
|
||||||
@ -464,6 +468,18 @@ pub enum OutputFormat {
|
|||||||
|
|
||||||
display_possible_values!(OutputFormat);
|
display_possible_values!(OutputFormat);
|
||||||
|
|
||||||
|
/// The target to compile for.
|
||||||
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)]
|
||||||
|
pub enum Target {
|
||||||
|
/// PDF and image formats.
|
||||||
|
#[default]
|
||||||
|
Paged,
|
||||||
|
/// HTML.
|
||||||
|
Html,
|
||||||
|
}
|
||||||
|
|
||||||
|
display_possible_values!(Target);
|
||||||
|
|
||||||
/// Which format to use for diagnostics.
|
/// Which format to use for diagnostics.
|
||||||
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)]
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)]
|
||||||
pub enum DiagnosticFormat {
|
pub enum DiagnosticFormat {
|
||||||
|
@ -5,11 +5,13 @@ use typst::World;
|
|||||||
use typst::diag::{HintedStrResult, StrResult, Warned, bail};
|
use typst::diag::{HintedStrResult, StrResult, Warned, bail};
|
||||||
use typst::engine::Sink;
|
use typst::engine::Sink;
|
||||||
use typst::foundations::{Content, IntoValue, LocatableSelector, Scope};
|
use typst::foundations::{Content, IntoValue, LocatableSelector, Scope};
|
||||||
|
use typst::introspection::Introspector;
|
||||||
use typst::layout::PagedDocument;
|
use typst::layout::PagedDocument;
|
||||||
use typst::syntax::{Span, SyntaxMode};
|
use typst::syntax::{Span, SyntaxMode};
|
||||||
use typst_eval::eval_string;
|
use typst_eval::eval_string;
|
||||||
|
use typst_html::HtmlDocument;
|
||||||
|
|
||||||
use crate::args::{QueryCommand, SerializationFormat};
|
use crate::args::{QueryCommand, SerializationFormat, Target};
|
||||||
use crate::compile::print_diagnostics;
|
use crate::compile::print_diagnostics;
|
||||||
use crate::set_failed;
|
use crate::set_failed;
|
||||||
use crate::world::SystemWorld;
|
use crate::world::SystemWorld;
|
||||||
@ -22,12 +24,17 @@ pub fn query(command: &QueryCommand) -> HintedStrResult<()> {
|
|||||||
world.reset();
|
world.reset();
|
||||||
world.source(world.main()).map_err(|err| err.to_string())?;
|
world.source(world.main()).map_err(|err| err.to_string())?;
|
||||||
|
|
||||||
let Warned { output, warnings } = typst::compile(&world);
|
let Warned { output, warnings } = match command.target {
|
||||||
|
Target::Paged => typst::compile::<PagedDocument>(&world)
|
||||||
|
.map(|output| output.map(|document| document.introspector)),
|
||||||
|
Target::Html => typst::compile::<HtmlDocument>(&world)
|
||||||
|
.map(|output| output.map(|document| document.introspector)),
|
||||||
|
};
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
// Retrieve and print query results.
|
// Retrieve and print query results.
|
||||||
Ok(document) => {
|
Ok(introspector) => {
|
||||||
let data = retrieve(&world, command, &document)?;
|
let data = retrieve(&world, command, &introspector)?;
|
||||||
let serialized = format(data, command)?;
|
let serialized = format(data, command)?;
|
||||||
println!("{serialized}");
|
println!("{serialized}");
|
||||||
print_diagnostics(&world, &[], &warnings, command.process.diagnostic_format)
|
print_diagnostics(&world, &[], &warnings, command.process.diagnostic_format)
|
||||||
@ -54,7 +61,7 @@ pub fn query(command: &QueryCommand) -> HintedStrResult<()> {
|
|||||||
fn retrieve(
|
fn retrieve(
|
||||||
world: &dyn World,
|
world: &dyn World,
|
||||||
command: &QueryCommand,
|
command: &QueryCommand,
|
||||||
document: &PagedDocument,
|
introspector: &Introspector,
|
||||||
) -> HintedStrResult<Vec<Content>> {
|
) -> HintedStrResult<Vec<Content>> {
|
||||||
let selector = eval_string(
|
let selector = eval_string(
|
||||||
&typst::ROUTINES,
|
&typst::ROUTINES,
|
||||||
@ -76,11 +83,7 @@ fn retrieve(
|
|||||||
})?
|
})?
|
||||||
.cast::<LocatableSelector>()?;
|
.cast::<LocatableSelector>()?;
|
||||||
|
|
||||||
Ok(document
|
Ok(introspector.query(&selector.0).into_iter().collect::<Vec<_>>())
|
||||||
.introspector
|
|
||||||
.query(&selector.0)
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<_>>())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Format the query result in the output format.
|
/// Format the query result in the output format.
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
@ -9,6 +8,7 @@ use codespan_reporting::term::termcolor::WriteColor;
|
|||||||
use codespan_reporting::term::{self, termcolor};
|
use codespan_reporting::term::{self, termcolor};
|
||||||
use ecow::eco_format;
|
use ecow::eco_format;
|
||||||
use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher as _};
|
use notify::{Event, RecommendedWatcher, RecursiveMode, Watcher as _};
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use same_file::is_same_file;
|
use same_file::is_same_file;
|
||||||
use typst::diag::{StrResult, bail, warning};
|
use typst::diag::{StrResult, bail, warning};
|
||||||
use typst::syntax::Span;
|
use typst::syntax::Span;
|
||||||
@ -91,10 +91,10 @@ struct Watcher {
|
|||||||
/// Keeps track of which paths are watched via `watcher`. The boolean is
|
/// Keeps track of which paths are watched via `watcher`. The boolean is
|
||||||
/// used during updating for mark-and-sweep garbage collection of paths we
|
/// used during updating for mark-and-sweep garbage collection of paths we
|
||||||
/// should unwatch.
|
/// should unwatch.
|
||||||
watched: HashMap<PathBuf, bool>,
|
watched: FxHashMap<PathBuf, bool>,
|
||||||
/// A set of files that should be watched, but don't exist. We manually poll
|
/// A set of files that should be watched, but don't exist. We manually poll
|
||||||
/// for those.
|
/// for those.
|
||||||
missing: HashSet<PathBuf>,
|
missing: FxHashSet<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Watcher {
|
impl Watcher {
|
||||||
@ -127,8 +127,8 @@ impl Watcher {
|
|||||||
output,
|
output,
|
||||||
rx,
|
rx,
|
||||||
watcher,
|
watcher,
|
||||||
watched: HashMap::new(),
|
watched: FxHashMap::default(),
|
||||||
missing: HashSet::new(),
|
missing: FxHashSet::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::{LazyLock, OnceLock};
|
use std::sync::{LazyLock, OnceLock};
|
||||||
@ -7,6 +6,7 @@ use std::{fmt, fs, io, mem};
|
|||||||
use chrono::{DateTime, Datelike, FixedOffset, Local, Utc};
|
use chrono::{DateTime, Datelike, FixedOffset, Local, Utc};
|
||||||
use ecow::{EcoString, eco_format};
|
use ecow::{EcoString, eco_format};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use typst::diag::{FileError, FileResult};
|
use typst::diag::{FileError, FileResult};
|
||||||
use typst::foundations::{Bytes, Datetime, Dict, IntoValue};
|
use typst::foundations::{Bytes, Datetime, Dict, IntoValue};
|
||||||
use typst::syntax::{FileId, Lines, Source, VirtualPath};
|
use typst::syntax::{FileId, Lines, Source, VirtualPath};
|
||||||
@ -41,7 +41,7 @@ pub struct SystemWorld {
|
|||||||
/// Locations of and storage for lazily loaded fonts.
|
/// Locations of and storage for lazily loaded fonts.
|
||||||
fonts: Vec<FontSlot>,
|
fonts: Vec<FontSlot>,
|
||||||
/// Maps file ids to source files and buffers.
|
/// Maps file ids to source files and buffers.
|
||||||
slots: Mutex<HashMap<FileId, FileSlot>>,
|
slots: Mutex<FxHashMap<FileId, FileSlot>>,
|
||||||
/// Holds information about where packages are stored.
|
/// Holds information about where packages are stored.
|
||||||
package_storage: PackageStorage,
|
package_storage: PackageStorage,
|
||||||
/// The current datetime if requested. This is stored here to ensure it is
|
/// The current datetime if requested. This is stored here to ensure it is
|
||||||
@ -139,7 +139,7 @@ impl SystemWorld {
|
|||||||
library: LazyHash::new(library),
|
library: LazyHash::new(library),
|
||||||
book: LazyHash::new(fonts.book),
|
book: LazyHash::new(fonts.book),
|
||||||
fonts: fonts.fonts,
|
fonts: fonts.fonts,
|
||||||
slots: Mutex::new(HashMap::new()),
|
slots: Mutex::new(FxHashMap::default()),
|
||||||
package_storage: package::storage(&world_args.package),
|
package_storage: package::storage(&world_args.package),
|
||||||
now,
|
now,
|
||||||
})
|
})
|
||||||
|
@ -21,6 +21,7 @@ typst-utils = { workspace = true }
|
|||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
unicode-segmentation = { workspace = true }
|
unicode-segmentation = { workspace = true }
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use ecow::eco_format;
|
use ecow::eco_format;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use typst_library::diag::{At, SourceDiagnostic, SourceResult, bail, error};
|
use typst_library::diag::{At, SourceDiagnostic, SourceResult, bail, error};
|
||||||
use typst_library::foundations::{Array, Dict, Value};
|
use typst_library::foundations::{Array, Dict, Value};
|
||||||
use typst_syntax::ast::{self, AstNode};
|
use typst_syntax::ast::{self, AstNode};
|
||||||
@ -137,7 +136,7 @@ where
|
|||||||
F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
|
F: Fn(&mut Vm, ast::Expr, Value) -> SourceResult<()>,
|
||||||
{
|
{
|
||||||
let mut sink = None;
|
let mut sink = None;
|
||||||
let mut used = HashSet::new();
|
let mut used = FxHashSet::default();
|
||||||
|
|
||||||
for p in destruct.items() {
|
for p in destruct.items() {
|
||||||
match p {
|
match p {
|
||||||
|
@ -246,7 +246,7 @@ impl Eval for ast::Dict<'_> {
|
|||||||
type Output = Dict;
|
type Output = Dict;
|
||||||
|
|
||||||
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
fn eval(self, vm: &mut Vm) -> SourceResult<Self::Output> {
|
||||||
let mut map = indexmap::IndexMap::new();
|
let mut map = indexmap::IndexMap::default();
|
||||||
let mut invalid_keys = eco_vec![];
|
let mut invalid_keys = eco_vec![];
|
||||||
|
|
||||||
for item in self.items() {
|
for item in self.items() {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
use ecow::eco_format;
|
use ecow::eco_format;
|
||||||
use typst_library::diag::{At, SourceResult};
|
use typst_library::diag::{At, SourceResult, warning};
|
||||||
use typst_library::foundations::{Content, NativeElement, Symbol, SymbolElem, Value};
|
use typst_library::foundations::{Content, NativeElement, Symbol, SymbolElem, Value};
|
||||||
use typst_library::math::{
|
use typst_library::math::{
|
||||||
AlignPointElem, AttachElem, FracElem, LrElem, PrimesElem, RootElem,
|
AlignPointElem, AttachElem, FracElem, LrElem, PrimesElem, RootElem,
|
||||||
@ -80,7 +80,14 @@ impl Eval for ast::MathAttach<'_> {
|
|||||||
let mut elem = AttachElem::new(base);
|
let mut elem = AttachElem::new(base);
|
||||||
|
|
||||||
if let Some(expr) = self.top() {
|
if let Some(expr) = self.top() {
|
||||||
elem.t.set(Some(expr.eval_display(vm)?));
|
let top = expr.eval(vm)?;
|
||||||
|
if let Value::Func(_) = top {
|
||||||
|
vm.engine.sink.warn(warning!(
|
||||||
|
expr.span(), "function literal used as superscript";
|
||||||
|
hint: "wrap the entire function call in parentheses",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
elem.t.set(Some(top.display().spanned(self.span())));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always attach primes in scripts style (not limits style),
|
// Always attach primes in scripts style (not limits style),
|
||||||
@ -90,7 +97,14 @@ impl Eval for ast::MathAttach<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(expr) = self.bottom() {
|
if let Some(expr) = self.bottom() {
|
||||||
elem.b.set(Some(expr.eval_display(vm)?));
|
let bottom = expr.eval(vm)?;
|
||||||
|
if let Value::Func(_) = bottom {
|
||||||
|
vm.engine.sink.warn(warning!(
|
||||||
|
expr.span(), "function literal used as subscript";
|
||||||
|
hint: "wrap the entire function call in parentheses",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
elem.b.set(Some(bottom.display().spanned(self.span())));
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(elem.pack())
|
Ok(elem.pack())
|
||||||
|
@ -24,6 +24,7 @@ bumpalo = { workspace = true }
|
|||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
palette = { workspace = true }
|
palette = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
time = { workspace = true }
|
time = { workspace = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use ecow::EcoVec;
|
||||||
use typst_library::diag::{SourceResult, warning};
|
use typst_library::diag::{SourceResult, warning};
|
||||||
use typst_library::engine::Engine;
|
use typst_library::engine::Engine;
|
||||||
use typst_library::foundations::{Content, StyleChain, Target, TargetElem};
|
use typst_library::foundations::{Content, StyleChain, Target, TargetElem};
|
||||||
@ -15,8 +16,8 @@ pub fn convert_to_nodes<'a>(
|
|||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
locator: &mut SplitLocator,
|
locator: &mut SplitLocator,
|
||||||
children: impl IntoIterator<Item = Pair<'a>>,
|
children: impl IntoIterator<Item = Pair<'a>>,
|
||||||
) -> SourceResult<Vec<HtmlNode>> {
|
) -> SourceResult<EcoVec<HtmlNode>> {
|
||||||
let mut output = Vec::new();
|
let mut output = EcoVec::new();
|
||||||
for (child, styles) in children {
|
for (child, styles) in children {
|
||||||
handle(engine, child, locator, styles, &mut output)?;
|
handle(engine, child, locator, styles, &mut output)?;
|
||||||
}
|
}
|
||||||
@ -29,12 +30,12 @@ fn handle(
|
|||||||
child: &Content,
|
child: &Content,
|
||||||
locator: &mut SplitLocator,
|
locator: &mut SplitLocator,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
output: &mut Vec<HtmlNode>,
|
output: &mut EcoVec<HtmlNode>,
|
||||||
) -> SourceResult<()> {
|
) -> SourceResult<()> {
|
||||||
if let Some(elem) = child.to_packed::<TagElem>() {
|
if let Some(elem) = child.to_packed::<TagElem>() {
|
||||||
output.push(HtmlNode::Tag(elem.tag.clone()));
|
output.push(HtmlNode::Tag(elem.tag.clone()));
|
||||||
} else if let Some(elem) = child.to_packed::<HtmlElem>() {
|
} else if let Some(elem) = child.to_packed::<HtmlElem>() {
|
||||||
let mut children = vec![];
|
let mut children = EcoVec::new();
|
||||||
if let Some(body) = elem.body.get_ref(styles) {
|
if let Some(body) = elem.body.get_ref(styles) {
|
||||||
children = html_fragment(engine, body, locator.next(&elem.span()), styles)?;
|
children = html_fragment(engine, body, locator.next(&elem.span()), styles)?;
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@ impl Properties {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a new property in builder-style.
|
/// Adds a new property in builder-style.
|
||||||
#[expect(unused)]
|
|
||||||
pub fn with(mut self, property: &str, value: impl Display) -> Self {
|
pub fn with(mut self, property: &str, value: impl Display) -> Self {
|
||||||
self.push(property, value);
|
self.push(property, value);
|
||||||
self
|
self
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use comemo::{Tracked, TrackedMut};
|
use comemo::{Tracked, TrackedMut};
|
||||||
|
use ecow::{EcoVec, eco_vec};
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use typst_library::World;
|
use typst_library::World;
|
||||||
use typst_library::diag::{SourceResult, bail};
|
use typst_library::diag::{SourceResult, bail};
|
||||||
use typst_library::engine::{Engine, Route, Sink, Traced};
|
use typst_library::engine::{Engine, Route, Sink, Traced};
|
||||||
@ -87,7 +88,7 @@ fn html_document_impl(
|
|||||||
children.iter().copied(),
|
children.iter().copied(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let mut link_targets = HashSet::new();
|
let mut link_targets = FxHashSet::default();
|
||||||
let mut introspector = introspect_html(&output, &mut link_targets);
|
let mut introspector = introspect_html(&output, &mut link_targets);
|
||||||
let mut root = root_element(output, &info)?;
|
let mut root = root_element(output, &info)?;
|
||||||
crate::link::identify_link_targets(&mut root, &mut introspector, link_targets);
|
crate::link::identify_link_targets(&mut root, &mut introspector, link_targets);
|
||||||
@ -99,12 +100,12 @@ fn html_document_impl(
|
|||||||
#[typst_macros::time(name = "introspect html")]
|
#[typst_macros::time(name = "introspect html")]
|
||||||
fn introspect_html(
|
fn introspect_html(
|
||||||
output: &[HtmlNode],
|
output: &[HtmlNode],
|
||||||
link_targets: &mut HashSet<Location>,
|
link_targets: &mut FxHashSet<Location>,
|
||||||
) -> Introspector {
|
) -> Introspector {
|
||||||
fn discover(
|
fn discover(
|
||||||
builder: &mut IntrospectorBuilder,
|
builder: &mut IntrospectorBuilder,
|
||||||
sink: &mut Vec<(Content, Position)>,
|
sink: &mut Vec<(Content, Position)>,
|
||||||
link_targets: &mut HashSet<Location>,
|
link_targets: &mut FxHashSet<Location>,
|
||||||
nodes: &[HtmlNode],
|
nodes: &[HtmlNode],
|
||||||
) {
|
) {
|
||||||
for node in nodes {
|
for node in nodes {
|
||||||
@ -141,19 +142,22 @@ fn introspect_html(
|
|||||||
|
|
||||||
/// Wrap the nodes in `<html>` and `<body>` if they are not yet rooted,
|
/// Wrap the nodes in `<html>` and `<body>` if they are not yet rooted,
|
||||||
/// supplying a suitable `<head>`.
|
/// supplying a suitable `<head>`.
|
||||||
fn root_element(output: Vec<HtmlNode>, info: &DocumentInfo) -> SourceResult<HtmlElement> {
|
fn root_element(
|
||||||
|
output: EcoVec<HtmlNode>,
|
||||||
|
info: &DocumentInfo,
|
||||||
|
) -> SourceResult<HtmlElement> {
|
||||||
let head = head_element(info);
|
let head = head_element(info);
|
||||||
let body = match classify_output(output)? {
|
let body = match classify_output(output)? {
|
||||||
OutputKind::Html(element) => return Ok(element),
|
OutputKind::Html(element) => return Ok(element),
|
||||||
OutputKind::Body(body) => body,
|
OutputKind::Body(body) => body,
|
||||||
OutputKind::Leafs(leafs) => HtmlElement::new(tag::body).with_children(leafs),
|
OutputKind::Leafs(leafs) => HtmlElement::new(tag::body).with_children(leafs),
|
||||||
};
|
};
|
||||||
Ok(HtmlElement::new(tag::html).with_children(vec![head.into(), body.into()]))
|
Ok(HtmlElement::new(tag::html).with_children(eco_vec![head.into(), body.into()]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generate a `<head>` element.
|
/// Generate a `<head>` element.
|
||||||
fn head_element(info: &DocumentInfo) -> HtmlElement {
|
fn head_element(info: &DocumentInfo) -> HtmlElement {
|
||||||
let mut children = vec![];
|
let mut children = EcoVec::new();
|
||||||
|
|
||||||
children.push(HtmlElement::new(tag::meta).with_attr(attr::charset, "utf-8").into());
|
children.push(HtmlElement::new(tag::meta).with_attr(attr::charset, "utf-8").into());
|
||||||
|
|
||||||
@ -167,7 +171,7 @@ fn head_element(info: &DocumentInfo) -> HtmlElement {
|
|||||||
if let Some(title) = &info.title {
|
if let Some(title) = &info.title {
|
||||||
children.push(
|
children.push(
|
||||||
HtmlElement::new(tag::title)
|
HtmlElement::new(tag::title)
|
||||||
.with_children(vec![HtmlNode::Text(title.clone(), Span::detached())])
|
.with_children(eco_vec![HtmlNode::Text(title.clone(), Span::detached())])
|
||||||
.into(),
|
.into(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -203,9 +207,9 @@ fn head_element(info: &DocumentInfo) -> HtmlElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Determine which kind of output the user generated.
|
/// Determine which kind of output the user generated.
|
||||||
fn classify_output(mut output: Vec<HtmlNode>) -> SourceResult<OutputKind> {
|
fn classify_output(mut output: EcoVec<HtmlNode>) -> SourceResult<OutputKind> {
|
||||||
let count = output.iter().filter(|node| !matches!(node, HtmlNode::Tag(_))).count();
|
let count = output.iter().filter(|node| !matches!(node, HtmlNode::Tag(_))).count();
|
||||||
for node in &mut output {
|
for node in output.make_mut() {
|
||||||
let HtmlNode::Element(elem) = node else { continue };
|
let HtmlNode::Element(elem) = node else { continue };
|
||||||
let tag = elem.tag;
|
let tag = elem.tag;
|
||||||
let mut take = || std::mem::replace(elem, HtmlElement::new(tag::html));
|
let mut take = || std::mem::replace(elem, HtmlElement::new(tag::html));
|
||||||
@ -232,5 +236,5 @@ enum OutputKind {
|
|||||||
/// one, but need supply the `<html>` element.
|
/// one, but need supply the `<html>` element.
|
||||||
Body(HtmlElement),
|
Body(HtmlElement),
|
||||||
/// The user generated leafs which we wrap in a `<body>` and `<html>`.
|
/// The user generated leafs which we wrap in a `<body>` and `<html>`.
|
||||||
Leafs(Vec<HtmlNode>),
|
Leafs(EcoVec<HtmlNode>),
|
||||||
}
|
}
|
||||||
|
@ -57,7 +57,7 @@ pub struct HtmlElement {
|
|||||||
/// The element's attributes.
|
/// The element's attributes.
|
||||||
pub attrs: HtmlAttrs,
|
pub attrs: HtmlAttrs,
|
||||||
/// The element's children.
|
/// The element's children.
|
||||||
pub children: Vec<HtmlNode>,
|
pub children: EcoVec<HtmlNode>,
|
||||||
/// The span from which the element originated, if any.
|
/// The span from which the element originated, if any.
|
||||||
pub span: Span,
|
pub span: Span,
|
||||||
}
|
}
|
||||||
@ -68,7 +68,7 @@ impl HtmlElement {
|
|||||||
Self {
|
Self {
|
||||||
tag,
|
tag,
|
||||||
attrs: HtmlAttrs::default(),
|
attrs: HtmlAttrs::default(),
|
||||||
children: vec![],
|
children: EcoVec::new(),
|
||||||
span: Span::detached(),
|
span: Span::detached(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,7 +76,7 @@ impl HtmlElement {
|
|||||||
/// Attach children to the element.
|
/// Attach children to the element.
|
||||||
///
|
///
|
||||||
/// Note: This overwrites potential previous children.
|
/// Note: This overwrites potential previous children.
|
||||||
pub fn with_children(mut self, children: Vec<HtmlNode>) -> Self {
|
pub fn with_children(mut self, children: EcoVec<HtmlNode>) -> Self {
|
||||||
self.children = children;
|
self.children = children;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -105,8 +105,53 @@ impl HtmlTag {
|
|||||||
bail!("tag name must not be empty");
|
bail!("tag name must not be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(c) = string.chars().find(|&c| !charsets::is_valid_in_tag_name(c)) {
|
let mut has_hyphen = false;
|
||||||
|
let mut has_uppercase = false;
|
||||||
|
|
||||||
|
for c in string.chars() {
|
||||||
|
if c == '-' {
|
||||||
|
has_hyphen = true;
|
||||||
|
} else if !charsets::is_valid_in_tag_name(c) {
|
||||||
bail!("the character {} is not valid in a tag name", c.repr());
|
bail!("the character {} is not valid in a tag name", c.repr());
|
||||||
|
} else {
|
||||||
|
has_uppercase |= c.is_ascii_uppercase();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we encounter a hyphen, we are dealing with a custom element rather
|
||||||
|
// than a standard HTML element.
|
||||||
|
//
|
||||||
|
// A valid custom element name must:
|
||||||
|
// - Contain at least one hyphen (U+002D)
|
||||||
|
// - Start with an ASCII lowercase letter (a-z)
|
||||||
|
// - Not contain any ASCII uppercase letters (A-Z)
|
||||||
|
// - Not be one of the reserved names
|
||||||
|
// - Only contain valid characters (ASCII alphanumeric and hyphens)
|
||||||
|
//
|
||||||
|
// See https://html.spec.whatwg.org/multipage/custom-elements.html#valid-custom-element-name
|
||||||
|
if has_hyphen {
|
||||||
|
if !string.starts_with(|c: char| c.is_ascii_lowercase()) {
|
||||||
|
bail!("custom element name must start with a lowercase letter");
|
||||||
|
}
|
||||||
|
if has_uppercase {
|
||||||
|
bail!("custom element name must not contain uppercase letters");
|
||||||
|
}
|
||||||
|
|
||||||
|
// These names are used in SVG and MathML. Since `html.elem` only
|
||||||
|
// supports creation of _HTML_ elements, they are forbidden.
|
||||||
|
if matches!(
|
||||||
|
string,
|
||||||
|
"annotation-xml"
|
||||||
|
| "color-profile"
|
||||||
|
| "font-face"
|
||||||
|
| "font-face-src"
|
||||||
|
| "font-face-uri"
|
||||||
|
| "font-face-format"
|
||||||
|
| "font-face-name"
|
||||||
|
| "missing-glyph"
|
||||||
|
) {
|
||||||
|
bail!("name is reserved and not valid for a custom element");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self(PicoStr::intern(string)))
|
Ok(Self(PicoStr::intern(string)))
|
||||||
@ -292,7 +337,7 @@ pub struct HtmlFrame {
|
|||||||
/// An ID to assign to the SVG itself.
|
/// An ID to assign to the SVG itself.
|
||||||
pub id: Option<EcoString>,
|
pub id: Option<EcoString>,
|
||||||
/// IDs to assign to destination jump points within the SVG.
|
/// IDs to assign to destination jump points within the SVG.
|
||||||
pub link_points: Vec<(Point, EcoString)>,
|
pub link_points: EcoVec<(Point, EcoString)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HtmlFrame {
|
impl HtmlFrame {
|
||||||
@ -302,7 +347,7 @@ impl HtmlFrame {
|
|||||||
inner,
|
inner,
|
||||||
text_size: styles.resolve(TextElem::size),
|
text_size: styles.resolve(TextElem::size),
|
||||||
id: None,
|
id: None,
|
||||||
link_points: vec![],
|
link_points: EcoVec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use comemo::{Track, Tracked, TrackedMut};
|
use comemo::{Track, Tracked, TrackedMut};
|
||||||
|
use ecow::EcoVec;
|
||||||
use typst_library::diag::{At, SourceResult};
|
use typst_library::diag::{At, SourceResult};
|
||||||
use typst_library::engine::{Engine, Route, Sink, Traced};
|
use typst_library::engine::{Engine, Route, Sink, Traced};
|
||||||
use typst_library::foundations::{Content, StyleChain};
|
use typst_library::foundations::{Content, StyleChain};
|
||||||
@ -16,7 +17,7 @@ pub fn html_fragment(
|
|||||||
content: &Content,
|
content: &Content,
|
||||||
locator: Locator,
|
locator: Locator,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<HtmlNode>> {
|
) -> SourceResult<EcoVec<HtmlNode>> {
|
||||||
html_fragment_impl(
|
html_fragment_impl(
|
||||||
engine.routines,
|
engine.routines,
|
||||||
engine.world,
|
engine.world,
|
||||||
@ -43,7 +44,7 @@ fn html_fragment_impl(
|
|||||||
content: &Content,
|
content: &Content,
|
||||||
locator: Tracked<Locator>,
|
locator: Tracked<Locator>,
|
||||||
styles: StyleChain,
|
styles: StyleChain,
|
||||||
) -> SourceResult<Vec<HtmlNode>> {
|
) -> SourceResult<EcoVec<HtmlNode>> {
|
||||||
let link = LocatorLink::new(locator);
|
let link = LocatorLink::new(locator);
|
||||||
let mut locator = Locator::link(&link).split();
|
let mut locator = Locator::link(&link).split();
|
||||||
let mut engine = Engine {
|
let mut engine = Engine {
|
||||||
|
@ -16,7 +16,7 @@ mod typed;
|
|||||||
pub use self::document::html_document;
|
pub use self::document::html_document;
|
||||||
pub use self::dom::*;
|
pub use self::dom::*;
|
||||||
pub use self::encode::html;
|
pub use self::encode::html;
|
||||||
pub use self::rules::register;
|
pub use self::rules::{html_span_filled, register};
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use typst_library::Category;
|
use typst_library::Category;
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
use comemo::Track;
|
use comemo::Track;
|
||||||
use ecow::{EcoString, eco_format};
|
use ecow::{EcoString, EcoVec, eco_format, eco_vec};
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use typst_library::foundations::{Label, NativeElement};
|
use typst_library::foundations::{Label, NativeElement};
|
||||||
use typst_library::introspection::{Introspector, Location, Tag};
|
use typst_library::introspection::{Introspector, Location, Tag};
|
||||||
use typst_library::layout::{Frame, FrameItem, Point};
|
use typst_library::layout::{Frame, FrameItem, Point};
|
||||||
@ -16,7 +17,7 @@ use crate::{HtmlElement, HtmlNode, attr, tag};
|
|||||||
/// in favor of the query in `identify_link_targets`. For the time being, some
|
/// in favor of the query in `identify_link_targets`. For the time being, some
|
||||||
/// links are created without existence of a `LinkElem`, so this is
|
/// links are created without existence of a `LinkElem`, so this is
|
||||||
/// unfortunately necessary.
|
/// unfortunately necessary.
|
||||||
pub fn introspect_frame_links(frame: &Frame, targets: &mut HashSet<Location>) {
|
pub fn introspect_frame_links(frame: &Frame, targets: &mut FxHashSet<Location>) {
|
||||||
for (_, item) in frame.items() {
|
for (_, item) in frame.items() {
|
||||||
match item {
|
match item {
|
||||||
FrameItem::Link(Destination::Location(loc), _) => {
|
FrameItem::Link(Destination::Location(loc), _) => {
|
||||||
@ -35,7 +36,7 @@ pub fn introspect_frame_links(frame: &Frame, targets: &mut HashSet<Location>) {
|
|||||||
pub fn identify_link_targets(
|
pub fn identify_link_targets(
|
||||||
root: &mut HtmlElement,
|
root: &mut HtmlElement,
|
||||||
introspector: &mut Introspector,
|
introspector: &mut Introspector,
|
||||||
mut targets: HashSet<Location>,
|
mut targets: FxHashSet<Location>,
|
||||||
) {
|
) {
|
||||||
// Query for all links with an intra-doc (i.e. `Location`) destination to
|
// Query for all links with an intra-doc (i.e. `Location`) destination to
|
||||||
// know what needs IDs.
|
// know what needs IDs.
|
||||||
@ -72,13 +73,13 @@ pub fn identify_link_targets(
|
|||||||
/// Traverses a list of nodes.
|
/// Traverses a list of nodes.
|
||||||
fn traverse(
|
fn traverse(
|
||||||
work: &mut Work,
|
work: &mut Work,
|
||||||
targets: &HashSet<Location>,
|
targets: &FxHashSet<Location>,
|
||||||
identificator: &mut Identificator<'_>,
|
identificator: &mut Identificator<'_>,
|
||||||
nodes: &mut Vec<HtmlNode>,
|
nodes: &mut EcoVec<HtmlNode>,
|
||||||
) {
|
) {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < nodes.len() {
|
while i < nodes.len() {
|
||||||
let node = &mut nodes[i];
|
let node = &mut nodes.make_mut()[i];
|
||||||
match node {
|
match node {
|
||||||
// When visiting a start tag, we check whether the element needs an
|
// When visiting a start tag, we check whether the element needs an
|
||||||
// ID and if so, add it to the queue, so that its first child node
|
// ID and if so, add it to the queue, so that its first child node
|
||||||
@ -114,7 +115,7 @@ fn traverse(
|
|||||||
HtmlNode::Text(..) => {
|
HtmlNode::Text(..) => {
|
||||||
work.drain(|label| {
|
work.drain(|label| {
|
||||||
let mut element =
|
let mut element =
|
||||||
HtmlElement::new(tag::span).with_children(vec![node.clone()]);
|
HtmlElement::new(tag::span).with_children(eco_vec![node.clone()]);
|
||||||
let id = identificator.assign(&mut element, label);
|
let id = identificator.assign(&mut element, label);
|
||||||
*node = HtmlNode::Element(element);
|
*node = HtmlNode::Element(element);
|
||||||
id
|
id
|
||||||
@ -144,10 +145,10 @@ fn traverse(
|
|||||||
/// Traverses a frame embedded in HTML.
|
/// Traverses a frame embedded in HTML.
|
||||||
fn traverse_frame(
|
fn traverse_frame(
|
||||||
work: &mut Work,
|
work: &mut Work,
|
||||||
targets: &HashSet<Location>,
|
targets: &FxHashSet<Location>,
|
||||||
identificator: &mut Identificator<'_>,
|
identificator: &mut Identificator<'_>,
|
||||||
frame: &Frame,
|
frame: &Frame,
|
||||||
link_points: &mut Vec<(Point, EcoString)>,
|
link_points: &mut EcoVec<(Point, EcoString)>,
|
||||||
) {
|
) {
|
||||||
for (_, item) in frame.items() {
|
for (_, item) in frame.items() {
|
||||||
match item {
|
match item {
|
||||||
@ -174,13 +175,13 @@ struct Work {
|
|||||||
/// now.
|
/// now.
|
||||||
queue: VecDeque<(Location, Option<Label>)>,
|
queue: VecDeque<(Location, Option<Label>)>,
|
||||||
/// The resulting mapping from element location's to HTML IDs.
|
/// The resulting mapping from element location's to HTML IDs.
|
||||||
ids: HashMap<Location, EcoString>,
|
ids: FxHashMap<Location, EcoString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Work {
|
impl Work {
|
||||||
/// Sets up.
|
/// Sets up.
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self { queue: VecDeque::new(), ids: HashMap::new() }
|
Self { queue: VecDeque::new(), ids: FxHashMap::default() }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Marks the element with the given location and label as in need of an
|
/// Marks the element with the given location and label as in need of an
|
||||||
@ -215,7 +216,7 @@ impl Work {
|
|||||||
struct Identificator<'a> {
|
struct Identificator<'a> {
|
||||||
introspector: &'a Introspector,
|
introspector: &'a Introspector,
|
||||||
loc_counter: usize,
|
loc_counter: usize,
|
||||||
label_counter: HashMap<Label, usize>,
|
label_counter: FxHashMap<Label, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Identificator<'a> {
|
impl<'a> Identificator<'a> {
|
||||||
@ -224,7 +225,7 @@ impl<'a> Identificator<'a> {
|
|||||||
Self {
|
Self {
|
||||||
introspector,
|
introspector,
|
||||||
loc_counter: 0,
|
loc_counter: 0,
|
||||||
label_counter: HashMap::new(),
|
label_counter: FxHashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,13 +11,13 @@ use typst_library::layout::{OuterVAlignment, Sizing};
|
|||||||
use typst_library::model::{
|
use typst_library::model::{
|
||||||
Attribution, CiteElem, CiteGroup, Destination, EmphElem, EnumElem, FigureCaption,
|
Attribution, CiteElem, CiteGroup, Destination, EmphElem, EnumElem, FigureCaption,
|
||||||
FigureElem, HeadingElem, LinkElem, LinkTarget, ListElem, ParbreakElem, QuoteElem,
|
FigureElem, HeadingElem, LinkElem, LinkTarget, ListElem, ParbreakElem, QuoteElem,
|
||||||
RefElem, StrongElem, TableCell, TableElem, TermsElem,
|
RefElem, StrongElem, TableCell, TableElem, TermsElem, TitleElem,
|
||||||
};
|
};
|
||||||
use typst_library::text::{
|
use typst_library::text::{
|
||||||
HighlightElem, LinebreakElem, OverlineElem, RawElem, RawLine, SmallcapsElem,
|
HighlightElem, LinebreakElem, OverlineElem, RawElem, RawLine, SmallcapsElem,
|
||||||
SpaceElem, StrikeElem, SubElem, SuperElem, UnderlineElem,
|
SpaceElem, StrikeElem, SubElem, SuperElem, UnderlineElem,
|
||||||
};
|
};
|
||||||
use typst_library::visualize::ImageElem;
|
use typst_library::visualize::{Color, ImageElem};
|
||||||
|
|
||||||
use crate::{FrameElem, HtmlAttrs, HtmlElem, HtmlTag, attr, css, tag};
|
use crate::{FrameElem, HtmlAttrs, HtmlElem, HtmlTag, attr, css, tag};
|
||||||
|
|
||||||
@ -32,6 +32,7 @@ pub fn register(rules: &mut NativeRuleMap) {
|
|||||||
rules.register(Html, ENUM_RULE);
|
rules.register(Html, ENUM_RULE);
|
||||||
rules.register(Html, TERMS_RULE);
|
rules.register(Html, TERMS_RULE);
|
||||||
rules.register(Html, LINK_RULE);
|
rules.register(Html, LINK_RULE);
|
||||||
|
rules.register(Html, TITLE_RULE);
|
||||||
rules.register(Html, HEADING_RULE);
|
rules.register(Html, HEADING_RULE);
|
||||||
rules.register(Html, FIGURE_RULE);
|
rules.register(Html, FIGURE_RULE);
|
||||||
rules.register(Html, FIGURE_CAPTION_RULE);
|
rules.register(Html, FIGURE_CAPTION_RULE);
|
||||||
@ -161,6 +162,12 @@ const LINK_RULE: ShowFn<LinkElem> = |elem, engine, _| {
|
|||||||
.pack())
|
.pack())
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TITLE_RULE: ShowFn<TitleElem> = |elem, _, styles| {
|
||||||
|
Ok(HtmlElem::new(tag::h1)
|
||||||
|
.with_body(Some(elem.resolve_body(styles).at(elem.span())?))
|
||||||
|
.pack())
|
||||||
|
};
|
||||||
|
|
||||||
const HEADING_RULE: ShowFn<HeadingElem> = |elem, engine, styles| {
|
const HEADING_RULE: ShowFn<HeadingElem> = |elem, engine, styles| {
|
||||||
let span = elem.span();
|
let span = elem.span();
|
||||||
|
|
||||||
@ -415,11 +422,36 @@ const RAW_RULE: ShowFn<RawElem> = |elem, _, styles| {
|
|||||||
seq.push(line.clone().pack());
|
seq.push(line.clone().pack());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(HtmlElem::new(if elem.block.get(styles) { tag::pre } else { tag::code })
|
let mut inline = css::Properties::new();
|
||||||
|
let block = elem.block.get(styles);
|
||||||
|
if !block {
|
||||||
|
// Without the `<pre>` tag, whitespace would be collapsed by default.
|
||||||
|
inline.push("white-space", "pre-wrap");
|
||||||
|
}
|
||||||
|
|
||||||
|
let code = HtmlElem::new(tag::code)
|
||||||
|
.with_styles(inline)
|
||||||
.with_body(Some(Content::sequence(seq)))
|
.with_body(Some(Content::sequence(seq)))
|
||||||
.pack())
|
.pack()
|
||||||
|
.spanned(elem.span());
|
||||||
|
|
||||||
|
Ok(if block { HtmlElem::new(tag::pre).with_body(Some(code)).pack() } else { code })
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// This is used by `RawElem::synthesize` through a routine.
|
||||||
|
///
|
||||||
|
/// It's a temporary workaround until `TextElem::fill` is supported in HTML
|
||||||
|
/// export.
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn html_span_filled(content: Content, color: Color) -> Content {
|
||||||
|
let span = content.span();
|
||||||
|
HtmlElem::new(tag::span)
|
||||||
|
.with_styles(css::Properties::new().with("color", css::color(color)))
|
||||||
|
.with_body(Some(content))
|
||||||
|
.pack()
|
||||||
|
.spanned(span)
|
||||||
|
}
|
||||||
|
|
||||||
const RAW_LINE_RULE: ShowFn<RawLine> = |elem, _, _| Ok(elem.body.clone());
|
const RAW_LINE_RULE: ShowFn<RawLine> = |elem, _, _| Ok(elem.body.clone());
|
||||||
|
|
||||||
const IMAGE_RULE: ShowFn<ImageElem> = |elem, engine, styles| {
|
const IMAGE_RULE: ShowFn<ImageElem> = |elem, engine, styles| {
|
||||||
|
@ -18,6 +18,7 @@ typst-eval = { workspace = true }
|
|||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
pathdiff = { workspace = true }
|
pathdiff = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
unscanny = { workspace = true }
|
unscanny = { workspace = true }
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use comemo::Track;
|
use comemo::Track;
|
||||||
use ecow::{EcoString, EcoVec, eco_vec};
|
use ecow::{EcoString, EcoVec, eco_vec};
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use typst::foundations::{Label, Styles, Value};
|
use typst::foundations::{Label, Styles, Value};
|
||||||
use typst::layout::PagedDocument;
|
use typst::layout::PagedDocument;
|
||||||
use typst::model::{BibliographyElem, FigureElem};
|
use typst::model::{BibliographyElem, FigureElem};
|
||||||
@ -76,7 +75,7 @@ pub fn analyze_labels(
|
|||||||
document: &PagedDocument,
|
document: &PagedDocument,
|
||||||
) -> (Vec<(Label, Option<EcoString>)>, usize) {
|
) -> (Vec<(Label, Option<EcoString>)>, usize) {
|
||||||
let mut output = vec![];
|
let mut output = vec![];
|
||||||
let mut seen_labels = HashSet::new();
|
let mut seen_labels = FxHashSet::default();
|
||||||
|
|
||||||
// Labels in the document.
|
// Labels in the document.
|
||||||
for elem in document.introspector.all() {
|
for elem in document.introspector.all() {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::BTreeMap;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
|
|
||||||
use ecow::{EcoString, eco_format};
|
use ecow::{EcoString, eco_format};
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use typst::foundations::{
|
use typst::foundations::{
|
||||||
AutoValue, CastInfo, Func, Label, NoneValue, ParamInfo, Repr, StyleChain, Styles,
|
AutoValue, CastInfo, Func, Label, NoneValue, ParamInfo, Repr, StyleChain, Styles,
|
||||||
@ -739,7 +740,7 @@ fn param_completions<'a>(
|
|||||||
|
|
||||||
// Determine which arguments are already present.
|
// Determine which arguments are already present.
|
||||||
let mut existing_positional = 0;
|
let mut existing_positional = 0;
|
||||||
let mut existing_named = HashSet::new();
|
let mut existing_named = FxHashSet::default();
|
||||||
for arg in args.items() {
|
for arg in args.items() {
|
||||||
match arg {
|
match arg {
|
||||||
ast::Arg::Pos(_) => {
|
ast::Arg::Pos(_) => {
|
||||||
@ -1116,7 +1117,7 @@ struct CompletionContext<'a> {
|
|||||||
explicit: bool,
|
explicit: bool,
|
||||||
from: usize,
|
from: usize,
|
||||||
completions: Vec<Completion>,
|
completions: Vec<Completion>,
|
||||||
seen_casts: HashSet<u128>,
|
seen_casts: FxHashSet<u128>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CompletionContext<'a> {
|
impl<'a> CompletionContext<'a> {
|
||||||
@ -1141,7 +1142,7 @@ impl<'a> CompletionContext<'a> {
|
|||||||
explicit,
|
explicit,
|
||||||
from: cursor,
|
from: cursor,
|
||||||
completions: vec![],
|
completions: vec![],
|
||||||
seen_casts: HashSet::new(),
|
seen_casts: FxHashSet::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use typst::diag::{FileError, FileResult};
|
use typst::diag::{FileError, FileResult};
|
||||||
use typst::foundations::{Bytes, Datetime, Smart};
|
use typst::foundations::{Bytes, Datetime, Smart};
|
||||||
use typst::layout::{Abs, Margin, PageElem};
|
use typst::layout::{Abs, Margin, PageElem};
|
||||||
@ -137,8 +137,8 @@ impl IdeWorld for TestWorld {
|
|||||||
/// Test-specific files.
|
/// Test-specific files.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
struct TestFiles {
|
struct TestFiles {
|
||||||
assets: HashMap<FileId, Bytes>,
|
assets: FxHashMap<FileId, Bytes>,
|
||||||
sources: HashMap<FileId, Source>,
|
sources: FxHashMap<FileId, Source>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Shared foundation of all test worlds.
|
/// Shared foundation of all test worlds.
|
||||||
|
@ -32,6 +32,7 @@ icu_provider_blob = { workspace = true }
|
|||||||
icu_segmenter = { workspace = true }
|
icu_segmenter = { workspace = true }
|
||||||
kurbo = { workspace = true }
|
kurbo = { workspace = true }
|
||||||
memchr = { workspace = true }
|
memchr = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
rustybuzz = { workspace = true }
|
rustybuzz = { workspace = true }
|
||||||
smallvec = { workspace = true }
|
smallvec = { workspace = true }
|
||||||
ttf-parser = { workspace = true }
|
ttf-parser = { workspace = true }
|
||||||
|
@ -7,13 +7,13 @@ mod distribute;
|
|||||||
|
|
||||||
pub(crate) use self::block::unbreakable_pod;
|
pub(crate) use self::block::unbreakable_pod;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use comemo::{Track, Tracked, TrackedMut};
|
use comemo::{Track, Tracked, TrackedMut};
|
||||||
use ecow::EcoVec;
|
use ecow::EcoVec;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use typst_library::World;
|
use typst_library::World;
|
||||||
use typst_library::diag::{At, SourceDiagnostic, SourceResult, bail};
|
use typst_library::diag::{At, SourceDiagnostic, SourceResult, bail};
|
||||||
use typst_library::engine::{Engine, Route, Sink, Traced};
|
use typst_library::engine::{Engine, Route, Sink, Traced};
|
||||||
@ -303,7 +303,7 @@ struct Work<'a, 'b> {
|
|||||||
/// Identifies floats and footnotes that can be skipped if visited because
|
/// Identifies floats and footnotes that can be skipped if visited because
|
||||||
/// they were already handled and incorporated as column or page level
|
/// they were already handled and incorporated as column or page level
|
||||||
/// insertions.
|
/// insertions.
|
||||||
skips: Rc<HashSet<Location>>,
|
skips: Rc<FxHashSet<Location>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Work<'a, 'b> {
|
impl<'a, 'b> Work<'a, 'b> {
|
||||||
@ -316,7 +316,7 @@ impl<'a, 'b> Work<'a, 'b> {
|
|||||||
footnotes: EcoVec::new(),
|
footnotes: EcoVec::new(),
|
||||||
footnote_spill: None,
|
footnote_spill: None,
|
||||||
tags: EcoVec::new(),
|
tags: EcoVec::new(),
|
||||||
skips: Rc::new(HashSet::new()),
|
skips: Rc::new(FxHashSet::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -739,7 +739,9 @@ fn assemble(
|
|||||||
}
|
}
|
||||||
|
|
||||||
advance -= max_overlap;
|
advance -= max_overlap;
|
||||||
growable += max_overlap - min_overlap;
|
// In case we have that max_overlap < min_overlap, ensure we
|
||||||
|
// don't decrease the value of growable.
|
||||||
|
growable += (max_overlap - min_overlap).max(Abs::zero());
|
||||||
}
|
}
|
||||||
|
|
||||||
full += advance;
|
full += advance;
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::collections::HashSet;
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use typst_library::foundations::StyleChain;
|
use typst_library::foundations::StyleChain;
|
||||||
use typst_library::introspection::{Locator, SplitLocator, Tag, TagElem};
|
use typst_library::introspection::{Locator, SplitLocator, Tag, TagElem};
|
||||||
use typst_library::layout::{PagebreakElem, Parity};
|
use typst_library::layout::{PagebreakElem, Parity};
|
||||||
@ -134,7 +133,7 @@ fn migrate_unterminated_tags(children: &mut [Pair], mid: usize) -> usize {
|
|||||||
|
|
||||||
// Determine the set of tag locations which we won't migrate (because they
|
// Determine the set of tag locations which we won't migrate (because they
|
||||||
// are terminated).
|
// are terminated).
|
||||||
let excluded: HashSet<_> = children[start..mid]
|
let excluded: FxHashSet<_> = children[start..mid]
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|(c, _)| match c.to_packed::<TagElem>()?.tag {
|
.filter_map(|(c, _)| match c.to_packed::<TagElem>()?.tag {
|
||||||
Tag::Start(_) => None,
|
Tag::Start(_) => None,
|
||||||
|
@ -21,7 +21,7 @@ use typst_library::model::{
|
|||||||
Attribution, BibliographyElem, CiteElem, CiteGroup, CslSource, Destination, EmphElem,
|
Attribution, BibliographyElem, CiteElem, CiteGroup, CslSource, Destination, EmphElem,
|
||||||
EnumElem, FigureCaption, FigureElem, FootnoteElem, FootnoteEntry, HeadingElem,
|
EnumElem, FigureCaption, FigureElem, FootnoteElem, FootnoteEntry, HeadingElem,
|
||||||
LinkElem, ListElem, Outlinable, OutlineElem, OutlineEntry, ParElem, ParbreakElem,
|
LinkElem, ListElem, Outlinable, OutlineElem, OutlineEntry, ParElem, ParbreakElem,
|
||||||
QuoteElem, RefElem, StrongElem, TableCell, TableElem, TermsElem, Works,
|
QuoteElem, RefElem, StrongElem, TableCell, TableElem, TermsElem, TitleElem, Works,
|
||||||
};
|
};
|
||||||
use typst_library::pdf::{ArtifactElem, EmbedElem, PdfMarkerTag};
|
use typst_library::pdf::{ArtifactElem, EmbedElem, PdfMarkerTag};
|
||||||
use typst_library::text::{
|
use typst_library::text::{
|
||||||
@ -48,6 +48,7 @@ pub fn register(rules: &mut NativeRuleMap) {
|
|||||||
rules.register(Paged, TERMS_RULE);
|
rules.register(Paged, TERMS_RULE);
|
||||||
rules.register(Paged, LINK_MARKER_RULE);
|
rules.register(Paged, LINK_MARKER_RULE);
|
||||||
rules.register(Paged, LINK_RULE);
|
rules.register(Paged, LINK_RULE);
|
||||||
|
rules.register(Paged, TITLE_RULE);
|
||||||
rules.register(Paged, HEADING_RULE);
|
rules.register(Paged, HEADING_RULE);
|
||||||
rules.register(Paged, FIGURE_RULE);
|
rules.register(Paged, FIGURE_RULE);
|
||||||
rules.register(Paged, FIGURE_CAPTION_RULE);
|
rules.register(Paged, FIGURE_CAPTION_RULE);
|
||||||
@ -231,6 +232,12 @@ const LINK_RULE: ShowFn<LinkElem> = |elem, engine, styles| {
|
|||||||
.set(LinkElem::current, Some(dest)))
|
.set(LinkElem::current, Some(dest)))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const TITLE_RULE: ShowFn<TitleElem> = |elem, _, styles| {
|
||||||
|
Ok(BlockElem::new()
|
||||||
|
.with_body(Some(BlockBody::Content(elem.resolve_body(styles).at(elem.span())?)))
|
||||||
|
.pack())
|
||||||
|
};
|
||||||
|
|
||||||
const HEADING_RULE: ShowFn<HeadingElem> = |elem, engine, styles| {
|
const HEADING_RULE: ShowFn<HeadingElem> = |elem, engine, styles| {
|
||||||
const SPACING_TO_NUMBERING: Em = Em::new(0.3);
|
const SPACING_TO_NUMBERING: Em = Em::new(0.3);
|
||||||
|
|
||||||
|
@ -50,6 +50,7 @@ regex = { workspace = true }
|
|||||||
regex-syntax = { workspace = true }
|
regex-syntax = { workspace = true }
|
||||||
roxmltree = { workspace = true }
|
roxmltree = { workspace = true }
|
||||||
rust_decimal = { workspace = true }
|
rust_decimal = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
rustybuzz = { workspace = true }
|
rustybuzz = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true }
|
serde_json = { workspace = true }
|
||||||
|
@ -151,6 +151,13 @@ pub struct Warned<T> {
|
|||||||
pub warnings: EcoVec<SourceDiagnostic>,
|
pub warnings: EcoVec<SourceDiagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Warned<T> {
|
||||||
|
/// Maps the output, keeping the same warnings.
|
||||||
|
pub fn map<R, F: FnOnce(T) -> R>(self, f: F) -> Warned<R> {
|
||||||
|
Warned { output: f(self.output), warnings: self.warnings }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// An error or warning in a source or text file.
|
/// An error or warning in a source or text file.
|
||||||
///
|
///
|
||||||
/// The contained spans will only be detached if any of the input source files
|
/// The contained spans will only be detached if any of the input source files
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
//! Definition of the central compilation context.
|
//! Definition of the central compilation context.
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
|
||||||
use comemo::{Track, Tracked, TrackedMut, Validate};
|
use comemo::{Track, Tracked, TrackedMut};
|
||||||
use ecow::EcoVec;
|
use ecow::EcoVec;
|
||||||
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
use rayon::iter::{IndexedParallelIterator, IntoParallelIterator, ParallelIterator};
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use typst_syntax::{FileId, Span};
|
use typst_syntax::{FileId, Span};
|
||||||
|
|
||||||
use crate::World;
|
use crate::World;
|
||||||
@ -135,7 +135,7 @@ pub struct Sink {
|
|||||||
/// Warnings emitted during iteration.
|
/// Warnings emitted during iteration.
|
||||||
warnings: EcoVec<SourceDiagnostic>,
|
warnings: EcoVec<SourceDiagnostic>,
|
||||||
/// Hashes of all warning's spans and messages for warning deduplication.
|
/// Hashes of all warning's spans and messages for warning deduplication.
|
||||||
warnings_set: HashSet<u128>,
|
warnings_set: FxHashSet<u128>,
|
||||||
/// A sequence of traced values for a span.
|
/// A sequence of traced values for a span.
|
||||||
values: EcoVec<(Value, Option<Styles>)>,
|
values: EcoVec<(Value, Option<Styles>)>,
|
||||||
}
|
}
|
||||||
@ -219,7 +219,7 @@ pub struct Route<'a> {
|
|||||||
// We need to override the constraint's lifetime here so that `Tracked` is
|
// We need to override the constraint's lifetime here so that `Tracked` is
|
||||||
// covariant over the constraint. If it becomes invariant, we're in for a
|
// covariant over the constraint. If it becomes invariant, we're in for a
|
||||||
// world of lifetime pain.
|
// world of lifetime pain.
|
||||||
outer: Option<Tracked<'a, Self, <Route<'static> as Validate>::Constraint>>,
|
outer: Option<Tracked<'a, Self, <Route<'static> as Track>::Call>>,
|
||||||
/// This is set if this route segment was inserted through the start of a
|
/// This is set if this route segment was inserted through the start of a
|
||||||
/// module evaluation.
|
/// module evaluation.
|
||||||
id: Option<FileId>,
|
id: Option<FileId>,
|
||||||
|
@ -5,6 +5,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use ecow::{EcoString, eco_format};
|
use ecow::{EcoString, eco_format};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
use rustc_hash::FxBuildHasher;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use typst_syntax::is_ident;
|
use typst_syntax::is_ident;
|
||||||
use typst_utils::ArcExt;
|
use typst_utils::ArcExt;
|
||||||
@ -20,7 +21,7 @@ use crate::foundations::{
|
|||||||
macro_rules! __dict {
|
macro_rules! __dict {
|
||||||
($($key:expr => $value:expr),* $(,)?) => {{
|
($($key:expr => $value:expr),* $(,)?) => {{
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut map = $crate::foundations::IndexMap::new();
|
let mut map = $crate::foundations::IndexMap::default();
|
||||||
$(map.insert($key.into(), $crate::foundations::IntoValue::into_value($value));)*
|
$(map.insert($key.into(), $crate::foundations::IntoValue::into_value($value));)*
|
||||||
$crate::foundations::Dict::from(map)
|
$crate::foundations::Dict::from(map)
|
||||||
}};
|
}};
|
||||||
@ -66,7 +67,7 @@ pub use crate::__dict as dict;
|
|||||||
/// ```
|
/// ```
|
||||||
#[ty(scope, cast, name = "dictionary")]
|
#[ty(scope, cast, name = "dictionary")]
|
||||||
#[derive(Default, Clone, PartialEq)]
|
#[derive(Default, Clone, PartialEq)]
|
||||||
pub struct Dict(Arc<IndexMap<Str, Value>>);
|
pub struct Dict(Arc<IndexMap<Str, Value, FxBuildHasher>>);
|
||||||
|
|
||||||
impl Dict {
|
impl Dict {
|
||||||
/// Create a new, empty dictionary.
|
/// Create a new, empty dictionary.
|
||||||
@ -343,7 +344,7 @@ impl<'de> Deserialize<'de> for Dict {
|
|||||||
where
|
where
|
||||||
D: Deserializer<'de>,
|
D: Deserializer<'de>,
|
||||||
{
|
{
|
||||||
Ok(IndexMap::<Str, Value>::deserialize(deserializer)?.into())
|
Ok(IndexMap::<Str, Value, FxBuildHasher>::deserialize(deserializer)?.into())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,8 +378,8 @@ impl<'a> IntoIterator for &'a Dict {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<IndexMap<Str, Value>> for Dict {
|
impl From<IndexMap<Str, Value, FxBuildHasher>> for Dict {
|
||||||
fn from(map: IndexMap<Str, Value>) -> Self {
|
fn from(map: IndexMap<Str, Value, FxBuildHasher>) -> Self {
|
||||||
Self(Arc::new(map))
|
Self(Arc::new(map))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher};
|
|||||||
use ecow::{EcoString, eco_format};
|
use ecow::{EcoString, eco_format};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use indexmap::map::Entry;
|
use indexmap::map::Entry;
|
||||||
|
use rustc_hash::FxBuildHasher;
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
|
|
||||||
use crate::diag::{DeprecationSink, HintedStrResult, HintedString, StrResult, bail};
|
use crate::diag::{DeprecationSink, HintedStrResult, HintedString, StrResult, bail};
|
||||||
@ -102,7 +103,7 @@ impl<'a> Scopes<'a> {
|
|||||||
/// A map from binding names to values.
|
/// A map from binding names to values.
|
||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct Scope {
|
pub struct Scope {
|
||||||
map: IndexMap<EcoString, Binding>,
|
map: IndexMap<EcoString, Binding, FxBuildHasher>,
|
||||||
deduplicate: bool,
|
deduplicate: bool,
|
||||||
category: Option<Category>,
|
category: Option<Category>,
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
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};
|
||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use ecow::{EcoString, EcoVec, eco_vec};
|
use ecow::{EcoString, EcoVec, eco_vec};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use typst_syntax::Span;
|
use typst_syntax::Span;
|
||||||
use typst_utils::LazyHash;
|
use typst_utils::LazyHash;
|
||||||
@ -938,7 +938,7 @@ fn block_wrong_type(func: Element, id: u8, value: &Block) -> ! {
|
|||||||
|
|
||||||
/// Holds native show rules.
|
/// Holds native show rules.
|
||||||
pub struct NativeRuleMap {
|
pub struct NativeRuleMap {
|
||||||
rules: HashMap<(Element, Target), NativeShowRule>,
|
rules: FxHashMap<(Element, Target), NativeShowRule>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The signature of a native show rule.
|
/// The signature of a native show rule.
|
||||||
@ -956,7 +956,7 @@ impl NativeRuleMap {
|
|||||||
///
|
///
|
||||||
/// Contains built-in rules for a few special elements.
|
/// Contains built-in rules for a few special elements.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
let mut rules = Self { rules: HashMap::new() };
|
let mut rules = Self { rules: FxHashMap::default() };
|
||||||
|
|
||||||
// ContextElem is as special as SequenceElem and StyledElem and could,
|
// ContextElem is as special as SequenceElem and StyledElem and could,
|
||||||
// in theory, also be special cased in realization.
|
// in theory, also be special cased in realization.
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
use std::collections::{BTreeSet, HashMap};
|
use std::collections::BTreeSet;
|
||||||
use std::fmt::{self, Debug, Display, Formatter, Write};
|
use std::fmt::{self, Debug, Display, Formatter, Write};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use codex::ModifierSet;
|
use codex::ModifierSet;
|
||||||
use ecow::{EcoString, eco_format};
|
use ecow::{EcoString, eco_format};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{Serialize, Serializer};
|
use serde::{Serialize, Serializer};
|
||||||
use typst_syntax::{Span, Spanned, is_ident};
|
use typst_syntax::{Span, Spanned, is_ident};
|
||||||
use typst_utils::hash128;
|
use typst_utils::hash128;
|
||||||
@ -221,7 +222,7 @@ impl Symbol {
|
|||||||
|
|
||||||
// Maps from canonicalized 128-bit hashes to indices of variants we've
|
// Maps from canonicalized 128-bit hashes to indices of variants we've
|
||||||
// seen before.
|
// seen before.
|
||||||
let mut seen = HashMap::<u128, usize>::new();
|
let mut seen = FxHashMap::<u128, usize>::default();
|
||||||
|
|
||||||
// A list of modifiers, cleared & reused in each iteration.
|
// A list of modifiers, cleared & reused in each iteration.
|
||||||
let mut modifiers = Vec::new();
|
let mut modifiers = Vec::new();
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
use std::collections::{BTreeSet, HashMap, HashSet};
|
use std::collections::BTreeSet;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
|
|
||||||
use ecow::{EcoString, EcoVec};
|
use ecow::{EcoString, EcoVec};
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use smallvec::SmallVec;
|
use smallvec::SmallVec;
|
||||||
use typst_utils::NonZeroExt;
|
use typst_utils::NonZeroExt;
|
||||||
|
|
||||||
@ -31,14 +32,14 @@ pub struct Introspector {
|
|||||||
keys: MultiMap<u128, Location>,
|
keys: MultiMap<u128, Location>,
|
||||||
|
|
||||||
/// Accelerates lookup of elements by location.
|
/// Accelerates lookup of elements by location.
|
||||||
locations: HashMap<Location, usize>,
|
locations: FxHashMap<Location, usize>,
|
||||||
/// Accelerates lookup of elements by label.
|
/// Accelerates lookup of elements by label.
|
||||||
labels: MultiMap<Label, usize>,
|
labels: MultiMap<Label, usize>,
|
||||||
|
|
||||||
/// Maps from element locations to assigned HTML IDs. This used to support
|
/// Maps from element locations to assigned HTML IDs. This used to support
|
||||||
/// intra-doc links in HTML export. In paged export, is is simply left
|
/// intra-doc links in HTML export. In paged export, is is simply left
|
||||||
/// empty and [`Self::html_id`] is not used.
|
/// empty and [`Self::html_id`] is not used.
|
||||||
html_ids: HashMap<Location, EcoString>,
|
html_ids: FxHashMap<Location, EcoString>,
|
||||||
|
|
||||||
/// Caches queries done on the introspector. This is important because
|
/// Caches queries done on the introspector. This is important because
|
||||||
/// even if all top-level queries are distinct, they often have shared
|
/// even if all top-level queries are distinct, they often have shared
|
||||||
@ -63,7 +64,7 @@ impl Introspector {
|
|||||||
|
|
||||||
/// Enriches an existing introspector with HTML IDs, which were assigned
|
/// Enriches an existing introspector with HTML IDs, which were assigned
|
||||||
/// to the DOM in a post-processing step.
|
/// to the DOM in a post-processing step.
|
||||||
pub fn set_html_ids(&mut self, html_ids: HashMap<Location, EcoString>) {
|
pub fn set_html_ids(&mut self, html_ids: FxHashMap<Location, EcoString>) {
|
||||||
self.html_ids = html_ids;
|
self.html_ids = html_ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +314,7 @@ impl Debug for Introspector {
|
|||||||
|
|
||||||
/// A map from one keys to multiple elements.
|
/// A map from one keys to multiple elements.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct MultiMap<K, V>(HashMap<K, SmallVec<[V; 1]>>);
|
struct MultiMap<K, V>(FxHashMap<K, SmallVec<[V; 1]>>);
|
||||||
|
|
||||||
impl<K, V> MultiMap<K, V>
|
impl<K, V> MultiMap<K, V>
|
||||||
where
|
where
|
||||||
@ -334,13 +335,13 @@ where
|
|||||||
|
|
||||||
impl<K, V> Default for MultiMap<K, V> {
|
impl<K, V> Default for MultiMap<K, V> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self(HashMap::new())
|
Self(FxHashMap::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Caches queries.
|
/// Caches queries.
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct QueryCache(RwLock<HashMap<u128, EcoVec<Content>>>);
|
struct QueryCache(RwLock<FxHashMap<u128, EcoVec<Content>>>);
|
||||||
|
|
||||||
impl QueryCache {
|
impl QueryCache {
|
||||||
fn get(&self, hash: u128) -> Option<EcoVec<Content>> {
|
fn get(&self, hash: u128) -> Option<EcoVec<Content>> {
|
||||||
@ -364,11 +365,11 @@ pub struct IntrospectorBuilder {
|
|||||||
pub pages: usize,
|
pub pages: usize,
|
||||||
pub page_numberings: Vec<Option<Numbering>>,
|
pub page_numberings: Vec<Option<Numbering>>,
|
||||||
pub page_supplements: Vec<Content>,
|
pub page_supplements: Vec<Content>,
|
||||||
pub html_ids: HashMap<Location, EcoString>,
|
pub html_ids: FxHashMap<Location, EcoString>,
|
||||||
seen: HashSet<Location>,
|
seen: FxHashSet<Location>,
|
||||||
insertions: MultiMap<Location, Vec<Pair>>,
|
insertions: MultiMap<Location, Vec<Pair>>,
|
||||||
keys: MultiMap<u128, Location>,
|
keys: MultiMap<u128, Location>,
|
||||||
locations: HashMap<Location, usize>,
|
locations: FxHashMap<Location, usize>,
|
||||||
labels: MultiMap<Label, usize>,
|
labels: MultiMap<Label, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::sync::OnceLock;
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
use comemo::{Tracked, Validate};
|
use comemo::{Track, Tracked};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use crate::introspection::{Introspector, Location};
|
use crate::introspection::{Introspector, Location};
|
||||||
|
|
||||||
@ -188,7 +188,7 @@ impl<'a> Locator<'a> {
|
|||||||
SplitLocator {
|
SplitLocator {
|
||||||
local: self.local,
|
local: self.local,
|
||||||
outer: self.outer,
|
outer: self.outer,
|
||||||
disambiguators: HashMap::new(),
|
disambiguators: FxHashMap::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,7 +244,7 @@ pub struct SplitLocator<'a> {
|
|||||||
/// for all the layers beyond the memoization boundary on-demand.
|
/// for all the layers beyond the memoization boundary on-demand.
|
||||||
outer: Option<&'a LocatorLink<'a>>,
|
outer: Option<&'a LocatorLink<'a>>,
|
||||||
/// Simply counts up the number of times we've seen each local hash.
|
/// Simply counts up the number of times we've seen each local hash.
|
||||||
disambiguators: HashMap<u128, usize>,
|
disambiguators: FxHashMap<u128, usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> SplitLocator<'a> {
|
impl<'a> SplitLocator<'a> {
|
||||||
@ -312,7 +312,7 @@ enum LinkKind<'a> {
|
|||||||
/// We need to override the constraint's lifetime here so that `Tracked` is
|
/// We need to override the constraint's lifetime here so that `Tracked` is
|
||||||
/// covariant over the constraint. If it becomes invariant, we're in for a
|
/// covariant over the constraint. If it becomes invariant, we're in for a
|
||||||
/// world of lifetime pain.
|
/// world of lifetime pain.
|
||||||
Outer(Tracked<'a, Locator<'a>, <Locator<'static> as Validate>::Constraint>),
|
Outer(Tracked<'a, Locator<'a>, <Locator<'static> as Track>::Call>),
|
||||||
/// A link which indicates that we are in measurement mode.
|
/// A link which indicates that we are in measurement mode.
|
||||||
Measure(Location),
|
Measure(Location),
|
||||||
}
|
}
|
||||||
|
@ -117,6 +117,8 @@ use crate::foundations::{Array, Context, LocatableSelector, Value, func};
|
|||||||
/// ]
|
/// ]
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// ## Retrieving a specific field
|
||||||
|
///
|
||||||
/// Frequently, you're interested in only one specific field of the resulting
|
/// Frequently, you're interested in only one specific field of the resulting
|
||||||
/// elements. In the case of the `metadata` element, the `value` field is the
|
/// elements. In the case of the `metadata` element, the `value` field is the
|
||||||
/// interesting one. You can extract just this field with the `--field`
|
/// interesting one. You can extract just this field with the `--field`
|
||||||
@ -134,6 +136,12 @@ use crate::foundations::{Array, Context, LocatableSelector, Value, func};
|
|||||||
/// $ typst query example.typ "<note>" --field value --one
|
/// $ typst query example.typ "<note>" --field value --one
|
||||||
/// "This is a note"
|
/// "This is a note"
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Querying for a specific export target
|
||||||
|
///
|
||||||
|
/// In case you need to query a document when exporting for a specific target,
|
||||||
|
/// you can use the `--target` argument. Valid values are `paged`, and `html`
|
||||||
|
/// (if the [`html`]($html) feature is enabled).
|
||||||
#[func(contextual)]
|
#[func(contextual)]
|
||||||
pub fn query(
|
pub fn query(
|
||||||
engine: &mut Engine,
|
engine: &mut Engine,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::any::TypeId;
|
use std::any::TypeId;
|
||||||
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::path::Path;
|
use std::path::Path;
|
||||||
@ -14,6 +13,7 @@ use hayagriva::{
|
|||||||
SpecificLocator, citationberg,
|
SpecificLocator, citationberg,
|
||||||
};
|
};
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
|
use rustc_hash::{FxBuildHasher, FxHashMap};
|
||||||
use smallvec::{SmallVec, smallvec};
|
use smallvec::{SmallVec, smallvec};
|
||||||
use typst_syntax::{Span, Spanned, SyntaxMode};
|
use typst_syntax::{Span, Spanned, SyntaxMode};
|
||||||
use typst_utils::{ManuallyHash, PicoStr};
|
use typst_utils::{ManuallyHash, PicoStr};
|
||||||
@ -217,7 +217,9 @@ impl LocalName for Packed<BibliographyElem> {
|
|||||||
|
|
||||||
/// A loaded bibliography.
|
/// A loaded bibliography.
|
||||||
#[derive(Clone, PartialEq, Hash)]
|
#[derive(Clone, PartialEq, Hash)]
|
||||||
pub struct Bibliography(Arc<ManuallyHash<IndexMap<Label, hayagriva::Entry>>>);
|
pub struct Bibliography(
|
||||||
|
Arc<ManuallyHash<IndexMap<Label, hayagriva::Entry, FxBuildHasher>>>,
|
||||||
|
);
|
||||||
|
|
||||||
impl Bibliography {
|
impl Bibliography {
|
||||||
/// Load a bibliography from data sources.
|
/// Load a bibliography from data sources.
|
||||||
@ -234,7 +236,7 @@ impl Bibliography {
|
|||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
#[typst_macros::time(name = "load bibliography")]
|
#[typst_macros::time(name = "load bibliography")]
|
||||||
fn decode(data: &[Loaded]) -> SourceResult<Bibliography> {
|
fn decode(data: &[Loaded]) -> SourceResult<Bibliography> {
|
||||||
let mut map = IndexMap::new();
|
let mut map = IndexMap::default();
|
||||||
let mut duplicates = Vec::<EcoString>::new();
|
let mut duplicates = Vec::<EcoString>::new();
|
||||||
|
|
||||||
// We might have multiple bib/yaml files
|
// We might have multiple bib/yaml files
|
||||||
@ -440,9 +442,35 @@ impl Reflect for CslSource {
|
|||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
fn input() -> CastInfo {
|
fn input() -> CastInfo {
|
||||||
let source = std::iter::once(DataSource::input());
|
let source = std::iter::once(DataSource::input());
|
||||||
let names = ArchivedStyle::all().iter().map(|name| {
|
|
||||||
CastInfo::Value(name.names()[0].into_value(), name.display_name())
|
/// All possible names and their short documentation for `ArchivedStyle`, including aliases.
|
||||||
|
static ARCHIVED_STYLE_NAMES: LazyLock<Vec<(&&str, &'static str)>> =
|
||||||
|
LazyLock::new(|| {
|
||||||
|
ArchivedStyle::all()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|name| {
|
||||||
|
let (main_name, aliases) = name
|
||||||
|
.names()
|
||||||
|
.split_first()
|
||||||
|
.expect("all ArchivedStyle should have at least one name");
|
||||||
|
|
||||||
|
std::iter::once((main_name, name.display_name())).chain(
|
||||||
|
aliases.iter().map(move |alias| {
|
||||||
|
// Leaking is okay here, because we are in a `LazyLock`.
|
||||||
|
let docs: &'static str = Box::leak(
|
||||||
|
format!("A short alias of `{main_name}`")
|
||||||
|
.into_boxed_str(),
|
||||||
|
);
|
||||||
|
(alias, docs)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
});
|
});
|
||||||
|
let names = ARCHIVED_STYLE_NAMES
|
||||||
|
.iter()
|
||||||
|
.map(|(value, docs)| CastInfo::Value(value.into_value(), docs));
|
||||||
|
|
||||||
CastInfo::Union(source.into_iter().chain(names).collect())
|
CastInfo::Union(source.into_iter().chain(names).collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,7 +514,7 @@ impl IntoValue for CslSource {
|
|||||||
/// citations to do it.
|
/// citations to do it.
|
||||||
pub 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: FxHashMap<Location, SourceResult<Content>>,
|
||||||
/// Lists all references in the bibliography, with optional prefix, or
|
/// Lists all references in the bibliography, with optional prefix, or
|
||||||
/// `None` if the citation style can't be used for bibliographies.
|
/// `None` if the citation style can't be used for bibliographies.
|
||||||
pub references: Option<Vec<(Option<Content>, Content)>>,
|
pub references: Option<Vec<(Option<Content>, Content)>>,
|
||||||
@ -528,7 +556,7 @@ struct Generator<'a> {
|
|||||||
/// bibliography driver and needed when processing hayagriva's output.
|
/// bibliography driver and needed when processing hayagriva's output.
|
||||||
infos: Vec<GroupInfo>,
|
infos: Vec<GroupInfo>,
|
||||||
/// Citations with unresolved keys.
|
/// Citations with unresolved keys.
|
||||||
failures: HashMap<Location, SourceResult<Content>>,
|
failures: FxHashMap<Location, SourceResult<Content>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Details about a group of merged citations. All citations are put into groups
|
/// Details about a group of merged citations. All citations are put into groups
|
||||||
@ -571,7 +599,7 @@ impl<'a> Generator<'a> {
|
|||||||
bibliography,
|
bibliography,
|
||||||
groups,
|
groups,
|
||||||
infos,
|
infos,
|
||||||
failures: HashMap::new(),
|
failures: FxHashMap::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -702,10 +730,10 @@ impl<'a> Generator<'a> {
|
|||||||
fn display_citations(
|
fn display_citations(
|
||||||
&mut self,
|
&mut self,
|
||||||
rendered: &hayagriva::Rendered,
|
rendered: &hayagriva::Rendered,
|
||||||
) -> StrResult<HashMap<Location, SourceResult<Content>>> {
|
) -> StrResult<FxHashMap<Location, SourceResult<Content>>> {
|
||||||
// Determine for each citation key where in the bibliography it is,
|
// Determine for each citation key where in the bibliography it is,
|
||||||
// so that we can link there.
|
// so that we can link there.
|
||||||
let mut links = HashMap::new();
|
let mut links = FxHashMap::default();
|
||||||
if let Some(bibliography) = &rendered.bibliography {
|
if let Some(bibliography) = &rendered.bibliography {
|
||||||
let location = self.bibliography.location().unwrap();
|
let location = self.bibliography.location().unwrap();
|
||||||
for (k, item) in bibliography.items.iter().enumerate() {
|
for (k, item) in bibliography.items.iter().enumerate() {
|
||||||
@ -760,7 +788,7 @@ impl<'a> Generator<'a> {
|
|||||||
|
|
||||||
// Determine for each citation key where it first occurred, so that we
|
// Determine for each citation key where it first occurred, so that we
|
||||||
// can link there.
|
// can link there.
|
||||||
let mut first_occurrences = HashMap::new();
|
let mut first_occurrences = FxHashMap::default();
|
||||||
for info in &self.infos {
|
for info in &self.infos {
|
||||||
for subinfo in &info.subinfos {
|
for subinfo in &info.subinfos {
|
||||||
let key = subinfo.key.resolve();
|
let key = subinfo.key.resolve();
|
||||||
@ -1056,4 +1084,28 @@ mod tests {
|
|||||||
let _ = CslStyle::from_archived(archived);
|
let _ = CslStyle::from_archived(archived);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_csl_source_cast_info_include_all_names() {
|
||||||
|
let CastInfo::Union(cast_info) = CslSource::input() else {
|
||||||
|
panic!("the cast info of CslSource should be a union");
|
||||||
|
};
|
||||||
|
|
||||||
|
let missing: Vec<_> = ArchivedStyle::all()
|
||||||
|
.iter()
|
||||||
|
.flat_map(|style| style.names())
|
||||||
|
.filter(|name| {
|
||||||
|
let found = cast_info.iter().any(|info| match info {
|
||||||
|
CastInfo::Value(Value::Str(n), _) => n.as_str() == **name,
|
||||||
|
_ => false,
|
||||||
|
});
|
||||||
|
!found
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
missing.is_empty(),
|
||||||
|
"missing style names in CslSource cast info: '{missing:?}'"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ mod reference;
|
|||||||
mod strong;
|
mod strong;
|
||||||
mod table;
|
mod table;
|
||||||
mod terms;
|
mod terms;
|
||||||
|
mod title;
|
||||||
|
|
||||||
pub use self::bibliography::*;
|
pub use self::bibliography::*;
|
||||||
pub use self::cite::*;
|
pub use self::cite::*;
|
||||||
@ -39,6 +40,7 @@ pub use self::reference::*;
|
|||||||
pub use self::strong::*;
|
pub use self::strong::*;
|
||||||
pub use self::table::*;
|
pub use self::table::*;
|
||||||
pub use self::terms::*;
|
pub use self::terms::*;
|
||||||
|
pub use self::title::*;
|
||||||
|
|
||||||
use crate::foundations::Scope;
|
use crate::foundations::Scope;
|
||||||
|
|
||||||
@ -54,6 +56,7 @@ pub fn define(global: &mut Scope) {
|
|||||||
global.define_elem::<EnumElem>();
|
global.define_elem::<EnumElem>();
|
||||||
global.define_elem::<TermsElem>();
|
global.define_elem::<TermsElem>();
|
||||||
global.define_elem::<LinkElem>();
|
global.define_elem::<LinkElem>();
|
||||||
|
global.define_elem::<TitleElem>();
|
||||||
global.define_elem::<HeadingElem>();
|
global.define_elem::<HeadingElem>();
|
||||||
global.define_elem::<FigureElem>();
|
global.define_elem::<FigureElem>();
|
||||||
global.define_elem::<QuoteElem>();
|
global.define_elem::<QuoteElem>();
|
||||||
|
77
crates/typst-library/src/model/title.rs
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
use crate::diag::{Hint, HintedStrResult};
|
||||||
|
use crate::foundations::{Content, Packed, ShowSet, Smart, StyleChain, Styles, elem};
|
||||||
|
use crate::introspection::Locatable;
|
||||||
|
use crate::layout::{BlockElem, Em};
|
||||||
|
use crate::model::DocumentElem;
|
||||||
|
use crate::text::{FontWeight, TextElem, TextSize};
|
||||||
|
|
||||||
|
/// A document title.
|
||||||
|
///
|
||||||
|
/// This should be used to display the main title of the whole document and
|
||||||
|
/// should occur only once per document. In contrast, level 1
|
||||||
|
/// [headings]($heading) are intended to be used for the top-level sections of
|
||||||
|
/// the document.
|
||||||
|
///
|
||||||
|
/// Note that additional frontmatter (like an author list) that should appear
|
||||||
|
/// together with the title does not belong in its body.
|
||||||
|
///
|
||||||
|
/// In HTML export, this shows as a `h1` element while level 1 headings show
|
||||||
|
/// as `h2` elements.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
/// ```example
|
||||||
|
/// #set document(
|
||||||
|
/// title: [Interstellar Mail Delivery]
|
||||||
|
/// )
|
||||||
|
///
|
||||||
|
/// #title()
|
||||||
|
///
|
||||||
|
/// = Introduction
|
||||||
|
/// In recent years, ...
|
||||||
|
/// ```
|
||||||
|
#[elem(Locatable, ShowSet)]
|
||||||
|
pub struct TitleElem {
|
||||||
|
/// The content of the title.
|
||||||
|
///
|
||||||
|
/// When omitted (or `{auto}`), this will default to [`document.title`]. In
|
||||||
|
/// this case, a document title must have been previously set with
|
||||||
|
/// `{set document(title: [..])}`.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #set document(title: "Course ABC, Homework 1")
|
||||||
|
/// #title[Homework 1]
|
||||||
|
///
|
||||||
|
/// ...
|
||||||
|
/// ```
|
||||||
|
#[positional]
|
||||||
|
pub body: Smart<Content>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TitleElem {
|
||||||
|
pub fn resolve_body(&self, styles: StyleChain) -> HintedStrResult<Content> {
|
||||||
|
match self.body.get_cloned(styles) {
|
||||||
|
Smart::Auto => styles
|
||||||
|
.get_cloned(DocumentElem::title)
|
||||||
|
.ok_or("document title was not set")
|
||||||
|
.hint("set the title with `set document(title: [...])`")
|
||||||
|
.hint("or provide an explicit body with `title[..]`"),
|
||||||
|
Smart::Custom(body) => Ok(body),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ShowSet for Packed<TitleElem> {
|
||||||
|
fn show_set(&self, _styles: StyleChain) -> Styles {
|
||||||
|
const SIZE: Em = Em::new(1.7);
|
||||||
|
const ABOVE: Em = Em::new(1.125);
|
||||||
|
const BELOW: Em = Em::new(0.75);
|
||||||
|
|
||||||
|
let mut out = Styles::new();
|
||||||
|
out.set(TextElem::size, TextSize(SIZE.into()));
|
||||||
|
out.set(TextElem::weight, FontWeight::BOLD);
|
||||||
|
out.set(BlockElem::above, Smart::Custom(ABOVE.into()));
|
||||||
|
out.set(BlockElem::below, Smart::Custom(BELOW.into()));
|
||||||
|
out.set(BlockElem::sticky, true);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ use crate::foundations::{
|
|||||||
use crate::introspection::{Introspector, Locator, SplitLocator};
|
use crate::introspection::{Introspector, Locator, SplitLocator};
|
||||||
use crate::layout::{Frame, Region};
|
use crate::layout::{Frame, Region};
|
||||||
use crate::model::DocumentInfo;
|
use crate::model::DocumentInfo;
|
||||||
|
use crate::visualize::Color;
|
||||||
|
|
||||||
/// Defines the `Routines` struct.
|
/// Defines the `Routines` struct.
|
||||||
macro_rules! routines {
|
macro_rules! routines {
|
||||||
@ -95,6 +96,12 @@ routines! {
|
|||||||
|
|
||||||
/// Constructs the `html` module.
|
/// Constructs the `html` module.
|
||||||
fn html_module() -> Module
|
fn html_module() -> Module
|
||||||
|
|
||||||
|
/// Wraps content in a span with a color.
|
||||||
|
///
|
||||||
|
/// This is a temporary workaround until `TextElem::fill` is supported in
|
||||||
|
/// HTML export.
|
||||||
|
fn html_span_filled(content: Content, color: Color) -> Content
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Defines what kind of realization we are performing.
|
/// Defines what kind of realization we are performing.
|
||||||
|
@ -127,13 +127,8 @@ fn draw_raster_glyph(
|
|||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Draws a glyph from the COLR table into the frame.
|
/// Convert a COLR glyph into an SVG file.
|
||||||
fn draw_colr_glyph(
|
pub fn colr_glyph_to_svg(font: &Font, glyph_id: GlyphId) -> Option<String> {
|
||||||
frame: &mut Frame,
|
|
||||||
font: &Font,
|
|
||||||
upem: Abs,
|
|
||||||
glyph_id: GlyphId,
|
|
||||||
) -> Option<()> {
|
|
||||||
let mut svg = XmlWriter::new(xmlwriter::Options::default());
|
let mut svg = XmlWriter::new(xmlwriter::Options::default());
|
||||||
|
|
||||||
let ttf = font.ttf();
|
let ttf = font.ttf();
|
||||||
@ -176,7 +171,25 @@ fn draw_colr_glyph(
|
|||||||
ttf.paint_color_glyph(glyph_id, 0, RgbaColor::new(0, 0, 0, 255), &mut glyph_painter)?;
|
ttf.paint_color_glyph(glyph_id, 0, RgbaColor::new(0, 0, 0, 255), &mut glyph_painter)?;
|
||||||
svg.end_element();
|
svg.end_element();
|
||||||
|
|
||||||
let data = Bytes::from_string(svg.end_document());
|
Some(svg.end_document())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws a glyph from the COLR table into the frame.
|
||||||
|
fn draw_colr_glyph(
|
||||||
|
frame: &mut Frame,
|
||||||
|
font: &Font,
|
||||||
|
upem: Abs,
|
||||||
|
glyph_id: GlyphId,
|
||||||
|
) -> Option<()> {
|
||||||
|
let svg_string = colr_glyph_to_svg(font, glyph_id)?;
|
||||||
|
|
||||||
|
let ttf = font.ttf();
|
||||||
|
let width = ttf.global_bounding_box().width() as f64;
|
||||||
|
let height = ttf.global_bounding_box().height() as f64;
|
||||||
|
let x_min = ttf.global_bounding_box().x_min as f64;
|
||||||
|
let y_max = ttf.global_bounding_box().y_max as f64;
|
||||||
|
|
||||||
|
let data = Bytes::from_string(svg_string);
|
||||||
let image = Image::plain(SvgImage::new(data).ok()?);
|
let image = Image::plain(SvgImage::new(data).ok()?);
|
||||||
|
|
||||||
let y_shift = Abs::pt(upem.to_pt() - y_max);
|
let y_shift = Abs::pt(upem.to_pt() - y_max);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use ecow::{EcoString, eco_format};
|
use ecow::{EcoString, eco_format};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use crate::diag::Hint;
|
use crate::diag::Hint;
|
||||||
use crate::foundations::{StyleChain, cast};
|
use crate::foundations::{StyleChain, cast};
|
||||||
@ -278,13 +278,13 @@ pub fn localized_str(lang: Lang, region: Option<Region>, key: &str) -> &'static
|
|||||||
fn parse_language_bundle(
|
fn parse_language_bundle(
|
||||||
lang: Lang,
|
lang: Lang,
|
||||||
region: Option<Region>,
|
region: Option<Region>,
|
||||||
) -> Result<HashMap<&'static str, &'static str>, &'static str> {
|
) -> Result<FxHashMap<&'static str, &'static str>, &'static str> {
|
||||||
let language_tuple = TRANSLATIONS.iter().find(|it| it.0 == lang_str(lang, region));
|
let language_tuple = TRANSLATIONS.iter().find(|it| it.0 == lang_str(lang, region));
|
||||||
let Some((_lang_name, language_file)) = language_tuple else {
|
let Some((_lang_name, language_file)) = language_tuple else {
|
||||||
return Ok(HashMap::new());
|
return Ok(FxHashMap::default());
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut bundle = HashMap::new();
|
let mut bundle = FxHashMap::default();
|
||||||
let lines = language_file.trim().lines();
|
let lines = language_file.trim().lines();
|
||||||
for line in lines {
|
for line in lines {
|
||||||
if line.trim().starts_with('#') {
|
if line.trim().starts_with('#') {
|
||||||
@ -313,9 +313,9 @@ fn lang_str(lang: Lang, region: Option<Region>) -> EcoString {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use typst_utils::option_eq;
|
use typst_utils::option_eq;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -337,7 +337,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_all_translations_included() {
|
fn test_all_translations_included() {
|
||||||
let defined_keys =
|
let defined_keys =
|
||||||
HashSet::<&str>::from_iter(TRANSLATIONS.iter().map(|(lang, _)| *lang));
|
FxHashSet::<&str>::from_iter(TRANSLATIONS.iter().map(|(lang, _)| *lang));
|
||||||
let mut checked = 0;
|
let mut checked = 0;
|
||||||
for file in translation_files_iter() {
|
for file in translation_files_iter() {
|
||||||
assert!(
|
assert!(
|
||||||
|
@ -18,12 +18,13 @@ use crate::diag::{
|
|||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
Bytes, Content, Derived, OneOrMultiple, Packed, PlainText, ShowSet, Smart,
|
Bytes, Content, Derived, OneOrMultiple, Packed, PlainText, ShowSet, Smart,
|
||||||
StyleChain, Styles, Synthesize, cast, elem, scope,
|
StyleChain, Styles, Synthesize, Target, TargetElem, cast, elem, scope,
|
||||||
};
|
};
|
||||||
use crate::introspection::Locatable;
|
use crate::introspection::Locatable;
|
||||||
use crate::layout::{Em, HAlignment};
|
use crate::layout::{Em, HAlignment};
|
||||||
use crate::loading::{DataSource, Load};
|
use crate::loading::{DataSource, Load};
|
||||||
use crate::model::{Figurable, ParElem};
|
use crate::model::{Figurable, ParElem};
|
||||||
|
use crate::routines::Routines;
|
||||||
use crate::text::{FontFamily, FontList, LocalName, TextElem, TextSize};
|
use crate::text::{FontFamily, FontList, LocalName, TextElem, TextSize};
|
||||||
use crate::visualize::Color;
|
use crate::visualize::Color;
|
||||||
|
|
||||||
@ -302,8 +303,12 @@ impl RawElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Synthesize for Packed<RawElem> {
|
impl Synthesize for Packed<RawElem> {
|
||||||
fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
|
fn synthesize(
|
||||||
let seq = self.highlight(styles);
|
&mut self,
|
||||||
|
engine: &mut Engine,
|
||||||
|
styles: StyleChain,
|
||||||
|
) -> SourceResult<()> {
|
||||||
|
let seq = self.highlight(engine.routines, styles);
|
||||||
self.lines = Some(seq);
|
self.lines = Some(seq);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -311,7 +316,7 @@ impl Synthesize for Packed<RawElem> {
|
|||||||
|
|
||||||
impl Packed<RawElem> {
|
impl Packed<RawElem> {
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
fn highlight(&self, styles: StyleChain) -> Vec<Packed<RawLine>> {
|
fn highlight(&self, routines: &Routines, styles: StyleChain) -> Vec<Packed<RawLine>> {
|
||||||
let elem = self.as_ref();
|
let elem = self.as_ref();
|
||||||
let lines = preprocess(&elem.text, styles, self.span());
|
let lines = preprocess(&elem.text, styles, self.span());
|
||||||
|
|
||||||
@ -343,6 +348,7 @@ impl Packed<RawElem> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let foreground = theme.settings.foreground.unwrap_or(synt::Color::BLACK);
|
let foreground = theme.settings.foreground.unwrap_or(synt::Color::BLACK);
|
||||||
|
let target = styles.get(TargetElem::target);
|
||||||
|
|
||||||
let mut seq = vec![];
|
let mut seq = vec![];
|
||||||
if matches!(lang.as_deref(), Some("typ" | "typst" | "typc" | "typm")) {
|
if matches!(lang.as_deref(), Some("typ" | "typst" | "typc" | "typm")) {
|
||||||
@ -365,7 +371,15 @@ impl Packed<RawElem> {
|
|||||||
let span_offset = text[..range.start]
|
let span_offset = text[..range.start]
|
||||||
.rfind('\n')
|
.rfind('\n')
|
||||||
.map_or(0, |i| range.start - (i + 1));
|
.map_or(0, |i| range.start - (i + 1));
|
||||||
styled(&text[range], foreground, style, span, span_offset)
|
styled(
|
||||||
|
routines,
|
||||||
|
target,
|
||||||
|
&text[range],
|
||||||
|
foreground,
|
||||||
|
style,
|
||||||
|
span,
|
||||||
|
span_offset,
|
||||||
|
)
|
||||||
},
|
},
|
||||||
&mut |i, range, line| {
|
&mut |i, range, line| {
|
||||||
let span = lines.get(i).map_or_else(Span::detached, |l| l.1);
|
let span = lines.get(i).map_or_else(Span::detached, |l| l.1);
|
||||||
@ -402,6 +416,8 @@ impl Packed<RawElem> {
|
|||||||
.flatten()
|
.flatten()
|
||||||
{
|
{
|
||||||
line_content.push(styled(
|
line_content.push(styled(
|
||||||
|
routines,
|
||||||
|
target,
|
||||||
piece,
|
piece,
|
||||||
foreground,
|
foreground,
|
||||||
style,
|
style,
|
||||||
@ -771,6 +787,8 @@ fn preprocess(
|
|||||||
|
|
||||||
/// Style a piece of text with a syntect style.
|
/// Style a piece of text with a syntect style.
|
||||||
fn styled(
|
fn styled(
|
||||||
|
routines: &Routines,
|
||||||
|
target: Target,
|
||||||
piece: &str,
|
piece: &str,
|
||||||
foreground: synt::Color,
|
foreground: synt::Color,
|
||||||
style: synt::Style,
|
style: synt::Style,
|
||||||
@ -784,7 +802,11 @@ fn styled(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if style.foreground != foreground {
|
if style.foreground != foreground {
|
||||||
body = body.set(TextElem::fill, to_typst(style.foreground).into());
|
let color = to_typst(style.foreground);
|
||||||
|
body = match target {
|
||||||
|
Target::Html => (routines.html_span_filled)(body, color),
|
||||||
|
Target::Paged => body.set(TextElem::fill, color.into()),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if style.font_style.contains(synt::FontStyle::BOLD) {
|
if style.font_style.contains(synt::FontStyle::BOLD) {
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use siphasher::sip128::{Hasher128, SipHasher13};
|
use siphasher::sip128::{Hasher128, SipHasher13};
|
||||||
|
|
||||||
use crate::World;
|
use crate::World;
|
||||||
@ -144,9 +144,9 @@ struct FontResolver<'a> {
|
|||||||
/// The active list of font families at the location of the SVG.
|
/// The active list of font families at the location of the SVG.
|
||||||
families: &'a [&'a str],
|
families: &'a [&'a str],
|
||||||
/// A mapping from Typst font indices to fontdb IDs.
|
/// A mapping from Typst font indices to fontdb IDs.
|
||||||
to_id: HashMap<usize, Option<fontdb::ID>>,
|
to_id: FxHashMap<usize, Option<fontdb::ID>>,
|
||||||
/// The reverse mapping.
|
/// The reverse mapping.
|
||||||
from_id: HashMap<fontdb::ID, Font>,
|
from_id: FxHashMap<fontdb::ID, Font>,
|
||||||
/// Accumulates a hash of all used fonts.
|
/// Accumulates a hash of all used fonts.
|
||||||
hasher: SipHasher13,
|
hasher: SipHasher13,
|
||||||
}
|
}
|
||||||
@ -162,8 +162,8 @@ impl<'a> FontResolver<'a> {
|
|||||||
book,
|
book,
|
||||||
world,
|
world,
|
||||||
families,
|
families,
|
||||||
to_id: HashMap::new(),
|
to_id: FxHashMap::default(),
|
||||||
from_id: HashMap::new(),
|
from_id: FxHashMap::default(),
|
||||||
hasher: SipHasher13::new(),
|
hasher: SipHasher13::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ image = { workspace = true }
|
|||||||
infer = { workspace = true }
|
infer = { workspace = true }
|
||||||
krilla = { workspace = true }
|
krilla = { workspace = true }
|
||||||
krilla-svg = { workspace = true }
|
krilla-svg = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
smallvec = { workspace = true }
|
smallvec = { workspace = true }
|
||||||
|
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
|
|
||||||
use ecow::{EcoVec, eco_format};
|
use ecow::{EcoVec, eco_format};
|
||||||
use krilla::configure::{Configuration, ValidationError, Validator};
|
use krilla::configure::{Configuration, ValidationError, Validator};
|
||||||
use krilla::destination::{NamedDestination, XyzDestination};
|
use krilla::destination::{NamedDestination, XyzDestination};
|
||||||
@ -13,6 +11,7 @@ use krilla::tagging::TagId;
|
|||||||
use krilla::tagging::fmt::Output;
|
use krilla::tagging::fmt::Output;
|
||||||
use krilla::{Document, SerializeSettings};
|
use krilla::{Document, SerializeSettings};
|
||||||
use krilla_svg::render_svg_glyph;
|
use krilla_svg::render_svg_glyph;
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use typst_library::diag::{SourceDiagnostic, SourceResult, bail, error};
|
use typst_library::diag::{SourceDiagnostic, SourceResult, bail, error};
|
||||||
use typst_library::foundations::{NativeElement, Repr};
|
use typst_library::foundations::{NativeElement, Repr};
|
||||||
use typst_library::introspection::{Location, Tag};
|
use typst_library::introspection::{Location, Tag};
|
||||||
@ -250,22 +249,22 @@ impl FrameContext {
|
|||||||
/// Globally needed context for converting a typst document.
|
/// Globally needed context for converting a typst document.
|
||||||
pub(crate) struct GlobalContext<'a> {
|
pub(crate) struct GlobalContext<'a> {
|
||||||
/// Cache the conversion between krilla and Typst fonts (forward and backward).
|
/// Cache the conversion between krilla and Typst fonts (forward and backward).
|
||||||
pub(crate) fonts_forward: HashMap<Font, krilla::text::Font>,
|
pub(crate) fonts_forward: FxHashMap<Font, krilla::text::Font>,
|
||||||
pub(crate) fonts_backward: HashMap<krilla::text::Font, Font>,
|
pub(crate) fonts_backward: FxHashMap<krilla::text::Font, Font>,
|
||||||
/// Mapping between images and their span.
|
/// Mapping between images and their span.
|
||||||
// Note: In theory, the same image can have multiple spans
|
// Note: In theory, the same image can have multiple spans
|
||||||
// if it appears in the document multiple times. We just store the
|
// if it appears in the document multiple times. We just store the
|
||||||
// first appearance, though.
|
// first appearance, though.
|
||||||
pub(crate) image_to_spans: HashMap<krilla::image::Image, Span>,
|
pub(crate) image_to_spans: FxHashMap<krilla::image::Image, Span>,
|
||||||
/// The spans of all images that appear in the document. We use this so
|
/// The spans of all images that appear in the document. We use this so
|
||||||
/// we can give more accurate error messages.
|
/// we can give more accurate error messages.
|
||||||
pub(crate) image_spans: HashSet<Span>,
|
pub(crate) image_spans: FxHashSet<Span>,
|
||||||
/// The document to convert.
|
/// The document to convert.
|
||||||
pub(crate) document: &'a PagedDocument,
|
pub(crate) document: &'a PagedDocument,
|
||||||
/// Options for PDF export.
|
/// Options for PDF export.
|
||||||
pub(crate) options: &'a PdfOptions<'a>,
|
pub(crate) options: &'a PdfOptions<'a>,
|
||||||
/// Mapping between locations in the document and named destinations.
|
/// Mapping between locations in the document and named destinations.
|
||||||
pub(crate) loc_to_names: HashMap<Location, NamedDestination>,
|
pub(crate) loc_to_names: FxHashMap<Location, NamedDestination>,
|
||||||
pub(crate) page_index_converter: PageIndexConverter,
|
pub(crate) page_index_converter: PageIndexConverter,
|
||||||
/// Tagged PDF context.
|
/// Tagged PDF context.
|
||||||
pub(crate) tags: Tags,
|
pub(crate) tags: Tags,
|
||||||
@ -275,17 +274,17 @@ impl<'a> GlobalContext<'a> {
|
|||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
document: &'a PagedDocument,
|
document: &'a PagedDocument,
|
||||||
options: &'a PdfOptions,
|
options: &'a PdfOptions,
|
||||||
loc_to_names: HashMap<Location, NamedDestination>,
|
loc_to_names: FxHashMap<Location, NamedDestination>,
|
||||||
page_index_converter: PageIndexConverter,
|
page_index_converter: PageIndexConverter,
|
||||||
) -> GlobalContext<'a> {
|
) -> GlobalContext<'a> {
|
||||||
Self {
|
Self {
|
||||||
fonts_forward: HashMap::new(),
|
fonts_forward: FxHashMap::default(),
|
||||||
fonts_backward: HashMap::new(),
|
fonts_backward: FxHashMap::default(),
|
||||||
document,
|
document,
|
||||||
options,
|
options,
|
||||||
loc_to_names,
|
loc_to_names,
|
||||||
image_to_spans: HashMap::new(),
|
image_to_spans: FxHashMap::default(),
|
||||||
image_spans: HashSet::new(),
|
image_spans: FxHashSet::default(),
|
||||||
page_index_converter,
|
page_index_converter,
|
||||||
|
|
||||||
tags: Tags::new(),
|
tags: Tags::new(),
|
||||||
@ -696,13 +695,13 @@ fn to_span(loc: Option<krilla::surface::Location>) -> Span {
|
|||||||
fn collect_named_destinations(
|
fn collect_named_destinations(
|
||||||
document: &PagedDocument,
|
document: &PagedDocument,
|
||||||
pic: &PageIndexConverter,
|
pic: &PageIndexConverter,
|
||||||
) -> HashMap<Location, NamedDestination> {
|
) -> FxHashMap<Location, NamedDestination> {
|
||||||
let mut locs_to_names = HashMap::new();
|
let mut locs_to_names = FxHashMap::default();
|
||||||
|
|
||||||
// Find all headings that have a label and are the first among other
|
// Find all headings that have a label and are the first among other
|
||||||
// headings with the same label.
|
// headings with the same label.
|
||||||
let matches: Vec<_> = {
|
let matches: Vec<_> = {
|
||||||
let mut seen = HashSet::new();
|
let mut seen = FxHashSet::default();
|
||||||
document
|
document
|
||||||
.introspector
|
.introspector
|
||||||
.query(&HeadingElem::ELEM.select())
|
.query(&HeadingElem::ELEM.select())
|
||||||
@ -737,13 +736,13 @@ fn collect_named_destinations(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct PageIndexConverter {
|
pub(crate) struct PageIndexConverter {
|
||||||
page_indices: HashMap<usize, usize>,
|
page_indices: FxHashMap<usize, usize>,
|
||||||
skipped_pages: usize,
|
skipped_pages: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PageIndexConverter {
|
impl PageIndexConverter {
|
||||||
pub fn new(document: &PagedDocument, options: &PdfOptions) -> Self {
|
pub fn new(document: &PagedDocument, options: &PdfOptions) -> Self {
|
||||||
let mut page_indices = HashMap::new();
|
let mut page_indices = FxHashMap::default();
|
||||||
let mut skipped_pages = 0;
|
let mut skipped_pages = 0;
|
||||||
|
|
||||||
for i in 0..document.pages.len() {
|
for i in 0..document.pages.len() {
|
||||||
|
@ -157,11 +157,12 @@ fn convert_gradient(
|
|||||||
RelativeTo::Parent => state.container_size(),
|
RelativeTo::Parent => state.container_size(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let angle = gradient.angle().unwrap_or_else(Angle::zero);
|
let mut angle = gradient.angle().unwrap_or_else(Angle::zero);
|
||||||
let base_transform = correct_transform(state, gradient.unwrap_relative(on_text));
|
let base_transform = correct_transform(state, gradient.unwrap_relative(on_text));
|
||||||
let stops = convert_gradient_stops(gradient);
|
let stops = convert_gradient_stops(gradient);
|
||||||
match &gradient {
|
match &gradient {
|
||||||
Gradient::Linear(_) => {
|
Gradient::Linear(_) => {
|
||||||
|
angle = Gradient::correct_aspect_ratio(angle, size.aspect_ratio());
|
||||||
let (x1, y1, x2, y2) = {
|
let (x1, y1, x2, y2) = {
|
||||||
let (mut sin, mut cos) = (angle.sin(), angle.cos());
|
let (mut sin, mut cos) = (angle.sin(), angle.cos());
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ ecow = { workspace = true }
|
|||||||
flate2 = { workspace = true }
|
flate2 = { workspace = true }
|
||||||
hayro = { workspace = true }
|
hayro = { workspace = true }
|
||||||
image = { workspace = true }
|
image = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
ttf-parser = { workspace = true }
|
ttf-parser = { workspace = true }
|
||||||
xmlparser = { workspace = true }
|
xmlparser = { workspace = true }
|
||||||
xmlwriter = { workspace = true }
|
xmlwriter = { workspace = true }
|
||||||
|
@ -6,10 +6,10 @@ mod shape;
|
|||||||
mod text;
|
mod text;
|
||||||
|
|
||||||
pub use image::{convert_image_scaling, convert_image_to_base64_url};
|
pub use image::{convert_image_scaling, convert_image_to_base64_url};
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use typst_library::introspection::Introspector;
|
use typst_library::introspection::Introspector;
|
||||||
use typst_library::model::Destination;
|
use typst_library::model::Destination;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::{self, Display, Formatter, Write};
|
use std::fmt::{self, Display, Formatter, Write};
|
||||||
|
|
||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
@ -421,12 +421,16 @@ impl<'a> SVGRenderer<'a> {
|
|||||||
struct Deduplicator<T> {
|
struct Deduplicator<T> {
|
||||||
kind: char,
|
kind: char,
|
||||||
vec: Vec<(u128, T)>,
|
vec: Vec<(u128, T)>,
|
||||||
present: HashMap<u128, Id>,
|
present: FxHashMap<u128, Id>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Deduplicator<T> {
|
impl<T> Deduplicator<T> {
|
||||||
fn new(kind: char) -> Self {
|
fn new(kind: char) -> Self {
|
||||||
Self { kind, vec: Vec::new(), present: HashMap::new() }
|
Self {
|
||||||
|
kind,
|
||||||
|
vec: Vec::new(),
|
||||||
|
present: FxHashMap::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inserts a value into the vector. If the hash is already present, returns
|
/// Inserts a value into the vector. If the hash is already present, returns
|
||||||
|
@ -5,6 +5,7 @@ use ecow::EcoString;
|
|||||||
use ttf_parser::GlyphId;
|
use ttf_parser::GlyphId;
|
||||||
use typst_library::foundations::Bytes;
|
use typst_library::foundations::Bytes;
|
||||||
use typst_library::layout::{Abs, Point, Ratio, Size, Transform};
|
use typst_library::layout::{Abs, Point, Ratio, Size, Transform};
|
||||||
|
use typst_library::text::color::colr_glyph_to_svg;
|
||||||
use typst_library::text::{Font, TextItem};
|
use typst_library::text::{Font, TextItem};
|
||||||
use typst_library::visualize::{
|
use typst_library::visualize::{
|
||||||
ExchangeFormat, FillRule, Image, Paint, RasterImage, RelativeTo,
|
ExchangeFormat, FillRule, Image, Paint, RasterImage, RelativeTo,
|
||||||
@ -31,7 +32,8 @@ impl SVGRenderer<'_> {
|
|||||||
let x_offset = x + glyph.x_offset.at(text.size).to_pt();
|
let x_offset = x + glyph.x_offset.at(text.size).to_pt();
|
||||||
let y_offset = y + glyph.y_offset.at(text.size).to_pt();
|
let y_offset = y + glyph.y_offset.at(text.size).to_pt();
|
||||||
|
|
||||||
self.render_svg_glyph(text, id, x_offset, y_offset, scale)
|
self.render_colr_glyph(text, id, x_offset, y_offset, scale)
|
||||||
|
.or_else(|| self.render_svg_glyph(text, id, x_offset, y_offset, scale))
|
||||||
.or_else(|| self.render_bitmap_glyph(text, id, x_offset, y_offset))
|
.or_else(|| self.render_bitmap_glyph(text, id, x_offset, y_offset))
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
self.render_outline_glyph(
|
self.render_outline_glyph(
|
||||||
@ -87,6 +89,42 @@ impl SVGRenderer<'_> {
|
|||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Render a glyph defined by COLR glyph descriptions.
|
||||||
|
fn render_colr_glyph(
|
||||||
|
&mut self,
|
||||||
|
text: &TextItem,
|
||||||
|
id: GlyphId,
|
||||||
|
x_offset: f64,
|
||||||
|
y_offset: f64,
|
||||||
|
scale: f64,
|
||||||
|
) -> Option<()> {
|
||||||
|
let ttf = text.font.ttf();
|
||||||
|
let converted = colr_glyph_to_svg(&text.font, id)?;
|
||||||
|
let width = ttf.global_bounding_box().width() as f64;
|
||||||
|
let height = ttf.global_bounding_box().height() as f64;
|
||||||
|
let data_url = svg_to_base64(&converted);
|
||||||
|
|
||||||
|
let x_min = ttf.global_bounding_box().x_min as f64;
|
||||||
|
let y_max = ttf.global_bounding_box().y_max as f64;
|
||||||
|
|
||||||
|
let glyph_hash = hash128(&(&text.font, id));
|
||||||
|
let id = self.glyphs.insert_with(glyph_hash, || RenderedGlyph::Image {
|
||||||
|
url: data_url,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
ts: Transform::scale(Ratio::new(scale), Ratio::new(-scale))
|
||||||
|
.pre_concat(Transform::translate(Abs::pt(x_min), -Abs::pt(y_max))),
|
||||||
|
});
|
||||||
|
|
||||||
|
self.xml.start_element("use");
|
||||||
|
self.xml.write_attribute_fmt("xlink:href", format_args!("#{id}"));
|
||||||
|
self.xml.write_attribute("x", &(x_offset));
|
||||||
|
self.xml.write_attribute("y", &(y_offset));
|
||||||
|
self.xml.end_element();
|
||||||
|
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Render a glyph defined by a bitmap.
|
/// Render a glyph defined by a bitmap.
|
||||||
fn render_bitmap_glyph(
|
fn render_bitmap_glyph(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -320,10 +358,14 @@ fn convert_svg_glyph_to_base64_url(font: &Font, id: GlyphId) -> Option<EcoString
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Some(svg_to_base64(&svg_str))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn svg_to_base64(svg_str: &str) -> EcoString {
|
||||||
let mut url: EcoString = "data:image/svg+xml;base64,".into();
|
let mut url: EcoString = "data:image/svg+xml;base64,".into();
|
||||||
let b64_encoded =
|
let b64_encoded =
|
||||||
base64::engine::general_purpose::STANDARD.encode(svg_str.as_bytes());
|
base64::engine::general_purpose::STANDARD.encode(svg_str.as_bytes());
|
||||||
url.push_str(&b64_encoded);
|
url.push_str(&b64_encoded);
|
||||||
|
|
||||||
Some(url)
|
url
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ readme = { workspace = true }
|
|||||||
typst-timing = { workspace = true }
|
typst-timing = { workspace = true }
|
||||||
typst-utils = { workspace = true }
|
typst-utils = { workspace = true }
|
||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
unicode-ident = { workspace = true }
|
unicode-ident = { workspace = true }
|
||||||
|
@ -1,21 +1,22 @@
|
|||||||
//! File and package management.
|
//! File and package management.
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::num::NonZeroU16;
|
use std::num::NonZeroU16;
|
||||||
use std::sync::{LazyLock, RwLock};
|
use std::sync::{LazyLock, RwLock};
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
use crate::VirtualPath;
|
use crate::VirtualPath;
|
||||||
use crate::package::PackageSpec;
|
use crate::package::PackageSpec;
|
||||||
|
|
||||||
/// The global package-path interner.
|
/// The global package-path interner.
|
||||||
static INTERNER: LazyLock<RwLock<Interner>> = LazyLock::new(|| {
|
static INTERNER: LazyLock<RwLock<Interner>> = LazyLock::new(|| {
|
||||||
RwLock::new(Interner { to_id: HashMap::new(), from_id: Vec::new() })
|
RwLock::new(Interner { to_id: FxHashMap::default(), from_id: Vec::new() })
|
||||||
});
|
});
|
||||||
|
|
||||||
/// A package-path interner.
|
/// A package-path interner.
|
||||||
struct Interner {
|
struct Interner {
|
||||||
to_id: HashMap<Pair, FileId>,
|
to_id: FxHashMap<Pair, FileId>,
|
||||||
from_id: Vec<Pair>,
|
from_id: Vec<Pair>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::{Index, IndexMut, Range};
|
use std::ops::{Index, IndexMut, Range};
|
||||||
|
|
||||||
use ecow::{EcoString, eco_format};
|
use ecow::{EcoString, eco_format};
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use typst_utils::default_math_class;
|
use typst_utils::default_math_class;
|
||||||
use unicode_math_class::MathClass;
|
use unicode_math_class::MathClass;
|
||||||
|
|
||||||
@ -384,10 +384,10 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
|
|||||||
fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> {
|
fn math_op(kind: SyntaxKind) -> Option<(SyntaxKind, SyntaxKind, ast::Assoc, usize)> {
|
||||||
match kind {
|
match kind {
|
||||||
SyntaxKind::Underscore => {
|
SyntaxKind::Underscore => {
|
||||||
Some((SyntaxKind::MathAttach, SyntaxKind::Hat, ast::Assoc::Right, 2))
|
Some((SyntaxKind::MathAttach, SyntaxKind::Hat, ast::Assoc::Right, 3))
|
||||||
}
|
}
|
||||||
SyntaxKind::Hat => {
|
SyntaxKind::Hat => {
|
||||||
Some((SyntaxKind::MathAttach, SyntaxKind::Underscore, ast::Assoc::Right, 2))
|
Some((SyntaxKind::MathAttach, SyntaxKind::Underscore, ast::Assoc::Right, 3))
|
||||||
}
|
}
|
||||||
SyntaxKind::Slash => {
|
SyntaxKind::Slash => {
|
||||||
Some((SyntaxKind::MathFrac, SyntaxKind::End, ast::Assoc::Left, 1))
|
Some((SyntaxKind::MathFrac, SyntaxKind::End, ast::Assoc::Left, 1))
|
||||||
@ -481,7 +481,7 @@ fn math_args(p: &mut Parser) {
|
|||||||
let mut has_arrays = false;
|
let mut has_arrays = false;
|
||||||
|
|
||||||
let mut maybe_array_start = p.marker();
|
let mut maybe_array_start = p.marker();
|
||||||
let mut seen = HashSet::new();
|
let mut seen = FxHashSet::default();
|
||||||
while !p.at_set(syntax_set!(End, Dollar, RightParen)) {
|
while !p.at_set(syntax_set!(End, Dollar, RightParen)) {
|
||||||
positional = math_arg(p, &mut seen);
|
positional = math_arg(p, &mut seen);
|
||||||
|
|
||||||
@ -522,7 +522,7 @@ fn math_args(p: &mut Parser) {
|
|||||||
/// Parses a single argument in a math argument list.
|
/// Parses a single argument in a math argument list.
|
||||||
///
|
///
|
||||||
/// Returns whether the parsed argument was positional or not.
|
/// Returns whether the parsed argument was positional or not.
|
||||||
fn math_arg<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>) -> bool {
|
fn math_arg<'s>(p: &mut Parser<'s>, seen: &mut FxHashSet<&'s str>) -> bool {
|
||||||
let m = p.marker();
|
let m = p.marker();
|
||||||
let start = p.current_start();
|
let start = p.current_start();
|
||||||
|
|
||||||
@ -831,7 +831,7 @@ fn let_binding(p: &mut Parser) {
|
|||||||
closure = true;
|
closure = true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pattern(p, false, &mut HashSet::new(), None);
|
pattern(p, false, &mut FxHashSet::default(), None);
|
||||||
other = true;
|
other = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -923,7 +923,7 @@ fn for_loop(p: &mut Parser) {
|
|||||||
let m = p.marker();
|
let m = p.marker();
|
||||||
p.assert(SyntaxKind::For);
|
p.assert(SyntaxKind::For);
|
||||||
|
|
||||||
let mut seen = HashSet::new();
|
let mut seen = FxHashSet::default();
|
||||||
pattern(p, false, &mut seen, None);
|
pattern(p, false, &mut seen, None);
|
||||||
|
|
||||||
if p.at(SyntaxKind::Comma) {
|
if p.at(SyntaxKind::Comma) {
|
||||||
@ -1084,7 +1084,7 @@ fn expr_with_paren(p: &mut Parser, atomic: bool) {
|
|||||||
} else if p.at(SyntaxKind::Eq) && kind != SyntaxKind::Parenthesized {
|
} else if p.at(SyntaxKind::Eq) && kind != SyntaxKind::Parenthesized {
|
||||||
p.restore(checkpoint);
|
p.restore(checkpoint);
|
||||||
let m = p.marker();
|
let m = p.marker();
|
||||||
destructuring_or_parenthesized(p, true, &mut HashSet::new());
|
destructuring_or_parenthesized(p, true, &mut FxHashSet::default());
|
||||||
if !p.expect(SyntaxKind::Eq) {
|
if !p.expect(SyntaxKind::Eq) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1107,7 +1107,7 @@ fn parenthesized_or_array_or_dict(p: &mut Parser) -> SyntaxKind {
|
|||||||
count: 0,
|
count: 0,
|
||||||
maybe_just_parens: true,
|
maybe_just_parens: true,
|
||||||
kind: None,
|
kind: None,
|
||||||
seen: HashSet::new(),
|
seen: FxHashSet::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// An edge case with parens is whether we can interpret a leading spread
|
// An edge case with parens is whether we can interpret a leading spread
|
||||||
@ -1169,7 +1169,7 @@ struct GroupState {
|
|||||||
/// The `SyntaxKind` to wrap as (if we've figured it out yet).
|
/// The `SyntaxKind` to wrap as (if we've figured it out yet).
|
||||||
kind: Option<SyntaxKind>,
|
kind: Option<SyntaxKind>,
|
||||||
/// Store named arguments so we can give an error if they're repeated.
|
/// Store named arguments so we can give an error if they're repeated.
|
||||||
seen: HashSet<EcoString>,
|
seen: FxHashSet<EcoString>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a single item in an array or dictionary.
|
/// Parses a single item in an array or dictionary.
|
||||||
@ -1238,7 +1238,7 @@ fn args(p: &mut Parser) {
|
|||||||
p.with_nl_mode(AtNewline::Continue, |p| {
|
p.with_nl_mode(AtNewline::Continue, |p| {
|
||||||
p.assert(SyntaxKind::LeftParen);
|
p.assert(SyntaxKind::LeftParen);
|
||||||
|
|
||||||
let mut seen = HashSet::new();
|
let mut seen = FxHashSet::default();
|
||||||
while !p.current().is_terminator() {
|
while !p.current().is_terminator() {
|
||||||
if !p.at_set(set::ARG) {
|
if !p.at_set(set::ARG) {
|
||||||
p.unexpected();
|
p.unexpected();
|
||||||
@ -1264,7 +1264,7 @@ fn args(p: &mut Parser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a single argument in an argument list.
|
/// Parses a single argument in an argument list.
|
||||||
fn arg<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>) {
|
fn arg<'s>(p: &mut Parser<'s>, seen: &mut FxHashSet<&'s str>) {
|
||||||
let m = p.marker();
|
let m = p.marker();
|
||||||
|
|
||||||
// Parses a spread argument: `..args`.
|
// Parses a spread argument: `..args`.
|
||||||
@ -1301,7 +1301,7 @@ fn params(p: &mut Parser) {
|
|||||||
p.with_nl_mode(AtNewline::Continue, |p| {
|
p.with_nl_mode(AtNewline::Continue, |p| {
|
||||||
p.assert(SyntaxKind::LeftParen);
|
p.assert(SyntaxKind::LeftParen);
|
||||||
|
|
||||||
let mut seen = HashSet::new();
|
let mut seen = FxHashSet::default();
|
||||||
let mut sink = false;
|
let mut sink = false;
|
||||||
|
|
||||||
while !p.current().is_terminator() {
|
while !p.current().is_terminator() {
|
||||||
@ -1323,7 +1323,7 @@ fn params(p: &mut Parser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a single parameter in a parameter list.
|
/// Parses a single parameter in a parameter list.
|
||||||
fn param<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>, sink: &mut bool) {
|
fn param<'s>(p: &mut Parser<'s>, seen: &mut FxHashSet<&'s str>, sink: &mut bool) {
|
||||||
let m = p.marker();
|
let m = p.marker();
|
||||||
|
|
||||||
// Parses argument sink: `..sink`.
|
// Parses argument sink: `..sink`.
|
||||||
@ -1358,7 +1358,7 @@ fn param<'s>(p: &mut Parser<'s>, seen: &mut HashSet<&'s str>, sink: &mut bool) {
|
|||||||
fn pattern<'s>(
|
fn pattern<'s>(
|
||||||
p: &mut Parser<'s>,
|
p: &mut Parser<'s>,
|
||||||
reassignment: bool,
|
reassignment: bool,
|
||||||
seen: &mut HashSet<&'s str>,
|
seen: &mut FxHashSet<&'s str>,
|
||||||
dupe: Option<&'s str>,
|
dupe: Option<&'s str>,
|
||||||
) {
|
) {
|
||||||
match p.current() {
|
match p.current() {
|
||||||
@ -1372,7 +1372,7 @@ fn pattern<'s>(
|
|||||||
fn destructuring_or_parenthesized<'s>(
|
fn destructuring_or_parenthesized<'s>(
|
||||||
p: &mut Parser<'s>,
|
p: &mut Parser<'s>,
|
||||||
reassignment: bool,
|
reassignment: bool,
|
||||||
seen: &mut HashSet<&'s str>,
|
seen: &mut FxHashSet<&'s str>,
|
||||||
) {
|
) {
|
||||||
let mut sink = false;
|
let mut sink = false;
|
||||||
let mut count = 0;
|
let mut count = 0;
|
||||||
@ -1410,7 +1410,7 @@ fn destructuring_or_parenthesized<'s>(
|
|||||||
fn destructuring_item<'s>(
|
fn destructuring_item<'s>(
|
||||||
p: &mut Parser<'s>,
|
p: &mut Parser<'s>,
|
||||||
reassignment: bool,
|
reassignment: bool,
|
||||||
seen: &mut HashSet<&'s str>,
|
seen: &mut FxHashSet<&'s str>,
|
||||||
maybe_just_parens: &mut bool,
|
maybe_just_parens: &mut bool,
|
||||||
sink: &mut bool,
|
sink: &mut bool,
|
||||||
) {
|
) {
|
||||||
@ -1457,7 +1457,7 @@ fn destructuring_item<'s>(
|
|||||||
fn pattern_leaf<'s>(
|
fn pattern_leaf<'s>(
|
||||||
p: &mut Parser<'s>,
|
p: &mut Parser<'s>,
|
||||||
reassignment: bool,
|
reassignment: bool,
|
||||||
seen: &mut HashSet<&'s str>,
|
seen: &mut FxHashSet<&'s str>,
|
||||||
dupe: Option<&'s str>,
|
dupe: Option<&'s str>,
|
||||||
) {
|
) {
|
||||||
if p.current().is_keyword() {
|
if p.current().is_keyword() {
|
||||||
@ -1920,7 +1920,7 @@ struct MemoArena {
|
|||||||
/// A map from the parser's current position to a range of previously parsed
|
/// A map from the parser's current position to a range of previously parsed
|
||||||
/// nodes in the arena and a checkpoint of the parser's state. These allow
|
/// nodes in the arena and a checkpoint of the parser's state. These allow
|
||||||
/// us to reset the parser to avoid parsing the same location again.
|
/// us to reset the parser to avoid parsing the same location again.
|
||||||
memo_map: HashMap<MemoKey, (Range<usize>, PartialState)>,
|
memo_map: FxHashMap<MemoKey, (Range<usize>, PartialState)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A type alias for the memo key so it doesn't get confused with other usizes.
|
/// A type alias for the memo key so it doesn't get confused with other usizes.
|
||||||
|
@ -71,6 +71,14 @@ pub fn enable() {
|
|||||||
ENABLED.store(true, Ordering::Relaxed);
|
ENABLED.store(true, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Disable the timer.
|
||||||
|
#[inline]
|
||||||
|
pub fn disable() {
|
||||||
|
// We only need atomicity and no synchronization of other
|
||||||
|
// operations, so `Relaxed` is fine.
|
||||||
|
ENABLED.store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the timer is enabled.
|
/// Whether the timer is enabled.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_enabled() -> bool {
|
pub fn is_enabled() -> bool {
|
||||||
|
@ -16,6 +16,7 @@ readme = { workspace = true }
|
|||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
portable-atomic = { workspace = true }
|
portable-atomic = { workspace = true }
|
||||||
rayon = { workspace = true }
|
rayon = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
siphasher = { workspace = true }
|
siphasher = { workspace = true }
|
||||||
thin-vec = { workspace = true }
|
thin-vec = { workspace = true }
|
||||||
unicode-math-class = { workspace = true }
|
unicode-math-class = { workspace = true }
|
||||||
|
@ -1,22 +1,24 @@
|
|||||||
use std::borrow::Borrow;
|
use std::borrow::Borrow;
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::{self, Debug, Display, Formatter};
|
use std::fmt::{self, Debug, Display, Formatter};
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::num::NonZeroU64;
|
use std::num::NonZeroU64;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::sync::{LazyLock, RwLock};
|
use std::sync::{LazyLock, RwLock};
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
/// Marks a number as a bitcode encoded `PicoStr``.
|
/// Marks a number as a bitcode encoded `PicoStr``.
|
||||||
const MARKER: u64 = 1 << 63;
|
const MARKER: u64 = 1 << 63;
|
||||||
|
|
||||||
/// The global runtime string interner.
|
/// The global runtime string interner.
|
||||||
static INTERNER: LazyLock<RwLock<Interner>> =
|
static INTERNER: LazyLock<RwLock<Interner>> = LazyLock::new(|| {
|
||||||
LazyLock::new(|| RwLock::new(Interner { seen: HashMap::new(), strings: Vec::new() }));
|
RwLock::new(Interner { seen: FxHashMap::default(), strings: Vec::new() })
|
||||||
|
});
|
||||||
|
|
||||||
/// A string interner.
|
/// A string interner.
|
||||||
struct Interner {
|
struct Interner {
|
||||||
seen: HashMap<&'static str, PicoStr>,
|
seen: FxHashMap<&'static str, PicoStr>,
|
||||||
strings: Vec<&'static str>,
|
strings: Vec<&'static str>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ typst-timing = { workspace = true }
|
|||||||
typst-utils = { workspace = true }
|
typst-utils = { workspace = true }
|
||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
|
|
||||||
[lints]
|
[lints]
|
||||||
workspace = true
|
workspace = true
|
||||||
|
@ -38,11 +38,11 @@ pub use typst_syntax as syntax;
|
|||||||
#[doc(inline)]
|
#[doc(inline)]
|
||||||
pub use typst_utils as utils;
|
pub use typst_utils as utils;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use comemo::{Track, Tracked, Validate};
|
use comemo::{Track, Tracked};
|
||||||
use ecow::{EcoString, EcoVec, eco_format, eco_vec};
|
use ecow::{EcoString, EcoVec, eco_format, eco_vec};
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use typst_html::HtmlDocument;
|
use typst_html::HtmlDocument;
|
||||||
use typst_library::diag::{
|
use typst_library::diag::{
|
||||||
FileError, SourceDiagnostic, SourceResult, Warned, bail, warning,
|
FileError, SourceDiagnostic, SourceResult, Warned, bail, warning,
|
||||||
@ -135,7 +135,7 @@ fn compile_impl<D: Document>(
|
|||||||
|
|
||||||
subsink = Sink::new();
|
subsink = Sink::new();
|
||||||
|
|
||||||
let constraint = <Introspector as Validate>::Constraint::new();
|
let constraint = comemo::Constraint::new();
|
||||||
let mut engine = Engine {
|
let mut engine = Engine {
|
||||||
world,
|
world,
|
||||||
introspector: introspector.track_with(&constraint),
|
introspector: introspector.track_with(&constraint),
|
||||||
@ -150,7 +150,7 @@ fn compile_impl<D: Document>(
|
|||||||
introspector = document.introspector();
|
introspector = document.introspector();
|
||||||
iter += 1;
|
iter += 1;
|
||||||
|
|
||||||
if timed!("check stabilized", introspector.validate(&constraint)) {
|
if timed!("check stabilized", constraint.validate(introspector)) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,7 +176,7 @@ fn compile_impl<D: Document>(
|
|||||||
|
|
||||||
/// Deduplicate diagnostics.
|
/// Deduplicate diagnostics.
|
||||||
fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> {
|
fn deduplicate(mut diags: EcoVec<SourceDiagnostic>) -> EcoVec<SourceDiagnostic> {
|
||||||
let mut unique = HashSet::new();
|
let mut unique = FxHashSet::default();
|
||||||
diags.retain(|diag| {
|
diags.retain(|diag| {
|
||||||
let hash = typst_utils::hash128(&(&diag.span, &diag.message));
|
let hash = typst_utils::hash128(&(&diag.span, &diag.message));
|
||||||
unique.insert(hash)
|
unique.insert(hash)
|
||||||
@ -358,4 +358,5 @@ pub static ROUTINES: LazyLock<Routines> = LazyLock::new(|| Routines {
|
|||||||
realize: typst_realize::realize,
|
realize: typst_realize::realize,
|
||||||
layout_frame: typst_layout::layout_frame,
|
layout_frame: typst_layout::layout_frame,
|
||||||
html_module: typst_html::module,
|
html_module: typst_html::module,
|
||||||
|
html_span_filled: typst_html::html_span_filled,
|
||||||
});
|
});
|
||||||
|
@ -26,6 +26,7 @@ codex = { workspace = true }
|
|||||||
ecow = { workspace = true }
|
ecow = { workspace = true }
|
||||||
heck = { workspace = true }
|
heck = { workspace = true }
|
||||||
pulldown-cmark = { workspace = true }
|
pulldown-cmark = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true, optional = true }
|
serde_json = { workspace = true, optional = true }
|
||||||
serde_yaml = { workspace = true }
|
serde_yaml = { workspace = true }
|
||||||
|
@ -477,12 +477,12 @@ and their corresponding Typst functions.
|
|||||||
|
|
||||||
Although _many_ things are built-in, not everything can be. That's why Typst has
|
Although _many_ things are built-in, not everything can be. That's why Typst has
|
||||||
its own [package ecosystem]($universe) where the community share its creations
|
its own [package ecosystem]($universe) where the community share its creations
|
||||||
and automations. Let's take, for instance, the _cetz_ package: This package
|
and automations. Let's take, for instance, the _CeTZ_ package: This package
|
||||||
allows you to create complex drawings and plots. To use cetz in your document,
|
allows you to create complex drawings and plots. To use CeTZ in your document,
|
||||||
you can just write:
|
you can just write:
|
||||||
|
|
||||||
```typ
|
```typ
|
||||||
#import "@preview/cetz:0.2.1"
|
#import "@preview/cetz:0.4.1"
|
||||||
```
|
```
|
||||||
|
|
||||||
(The `@preview` is a _namespace_ that is used while the package manager is still
|
(The `@preview` is a _namespace_ that is used while the package manager is still
|
||||||
|
@ -4,9 +4,8 @@ description: Guides for Typst.
|
|||||||
|
|
||||||
# Guides
|
# Guides
|
||||||
Welcome to the Guides section! Here, you'll find helpful material for specific
|
Welcome to the Guides section! Here, you'll find helpful material for specific
|
||||||
user groups or use cases. Currently, two guides are available: An introduction
|
user groups or use cases. Please see the list below for the available guides.
|
||||||
to Typst for LaTeX users, and a detailed look at page setup. Feel free to
|
Feel free to propose other topics for guides!
|
||||||
propose other topics for guides!
|
|
||||||
|
|
||||||
## List of Guides
|
## List of Guides
|
||||||
- [Guide for LaTeX users]($guides/guide-for-latex-users)
|
- [Guide for LaTeX users]($guides/guide-for-latex-users)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Drawing and data visualization.
|
Drawing and data visualization.
|
||||||
|
|
||||||
If you want to create more advanced drawings or plots, also have a look at the
|
If you want to create more advanced drawings or plots, also have a look at the
|
||||||
[CetZ](https://github.com/johannes-wolf/cetz) package as well as more
|
[CeTZ](https://github.com/johannes-wolf/cetz) package as well as more
|
||||||
specialized [packages]($universe) for your use case.
|
specialized [packages]($universe) for your use case.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use std::cmp::Reverse;
|
use std::cmp::Reverse;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fmt::Write;
|
use std::fmt::Write;
|
||||||
|
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::{Html, Resolver};
|
use crate::{Html, Resolver};
|
||||||
@ -12,7 +12,7 @@ pub fn contributors(resolver: &dyn Resolver, from: &str, to: &str) -> Option<Htm
|
|||||||
let bots = ["dependabot[bot]"];
|
let bots = ["dependabot[bot]"];
|
||||||
|
|
||||||
// Determine number of contributions per person.
|
// Determine number of contributions per person.
|
||||||
let mut contributors = HashMap::<String, Contributor>::new();
|
let mut contributors = FxHashMap::<String, Contributor>::default();
|
||||||
for commit in resolver.commits(from, to) {
|
for commit in resolver.commits(from, to) {
|
||||||
contributors
|
contributors
|
||||||
.entry(commit.author.login.clone())
|
.entry(commit.author.login.clone())
|
||||||
|
@ -9,10 +9,9 @@ pub use self::contribs::*;
|
|||||||
pub use self::html::*;
|
pub use self::html::*;
|
||||||
pub use self::model::*;
|
pub use self::model::*;
|
||||||
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
use ecow::{EcoString, eco_format};
|
use ecow::{EcoString, eco_format};
|
||||||
use heck::ToTitleCase;
|
use heck::ToTitleCase;
|
||||||
|
use rustc_hash::FxHashSet;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde_yaml as yaml;
|
use serde_yaml as yaml;
|
||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
@ -260,7 +259,7 @@ fn category_page(resolver: &dyn Resolver, category: Category) -> PageModel {
|
|||||||
shorthands = Some(ShorthandsModel { markup, math });
|
shorthands = Some(ShorthandsModel { markup, math });
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut skip = HashSet::new();
|
let mut skip = FxHashSet::default();
|
||||||
if category == Category::Math {
|
if category == Category::Math {
|
||||||
skip = GROUPS
|
skip = GROUPS
|
||||||
.iter()
|
.iter()
|
||||||
|
139
docs/src/link.rs
@ -1,5 +1,5 @@
|
|||||||
use typst::diag::{StrResult, bail};
|
use typst::diag::{StrResult, bail};
|
||||||
use typst::foundations::{Binding, Func};
|
use typst::foundations::{Binding, Func, Type};
|
||||||
|
|
||||||
use crate::{GROUPS, LIBRARY, get_module};
|
use crate::{GROUPS, LIBRARY, get_module};
|
||||||
|
|
||||||
@ -94,25 +94,158 @@ fn resolve_definition(head: &str, base: &str) -> StrResult<String> {
|
|||||||
let mut route = format!("{}reference/{}/{name}", base, category.name());
|
let mut route = format!("{}reference/{}/{name}", base, category.name());
|
||||||
if let Some(next) = parts.next() {
|
if let Some(next) = parts.next() {
|
||||||
if let Ok(field) = value.field(next, ()) {
|
if let Ok(field) = value.field(next, ()) {
|
||||||
|
// For top-level definitions
|
||||||
route.push_str("/#definitions-");
|
route.push_str("/#definitions-");
|
||||||
route.push_str(next);
|
route.push_str(next);
|
||||||
if let Some(next) = parts.next()
|
|
||||||
&& field.cast::<Func>().is_ok_and(|func| func.param(next).is_some())
|
let mut focus = field;
|
||||||
|
// For subsequent parameters, definitions, or definitions’ parameters
|
||||||
|
for next in parts.by_ref() {
|
||||||
|
if let Ok(field) = focus.field(next, ()) {
|
||||||
|
// For definitions
|
||||||
|
route.push_str("-definitions-");
|
||||||
|
route.push_str(next);
|
||||||
|
focus = field.clone();
|
||||||
|
} else if focus
|
||||||
|
.clone()
|
||||||
|
.cast::<Func>()
|
||||||
|
.is_ok_and(|func| func.param(next).is_some())
|
||||||
{
|
{
|
||||||
|
// For parameters
|
||||||
route.push('-');
|
route.push('-');
|
||||||
route.push_str(next);
|
route.push_str(next);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else if let Ok(ty) = value.clone().cast::<Type>()
|
||||||
|
&& let Ok(func) = ty.constructor()
|
||||||
|
&& func.param(next).is_some()
|
||||||
|
{
|
||||||
|
// For parameters of a constructor function
|
||||||
|
route.push_str("/#constructor-");
|
||||||
|
route.push_str(next);
|
||||||
} else if value
|
} else if value
|
||||||
.clone()
|
.clone()
|
||||||
.cast::<Func>()
|
.cast::<Func>()
|
||||||
.is_ok_and(|func| func.param(next).is_some())
|
.is_ok_and(|func| func.param(next).is_some())
|
||||||
{
|
{
|
||||||
|
// For parameters of a function (except for constructor functions)
|
||||||
route.push_str("/#parameters-");
|
route.push_str("/#parameters-");
|
||||||
route.push_str(next);
|
route.push_str(next);
|
||||||
} else {
|
} else {
|
||||||
bail!("field {next} not found");
|
bail!("field {next} not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(next) = parts.next() {
|
||||||
|
bail!("found redundant field {next}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(route)
|
Ok(route)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_function() {
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$figure", "/"),
|
||||||
|
Ok("/reference/model/figure".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$figure.body", "/"),
|
||||||
|
Ok("/reference/model/figure/#parameters-body".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$figure.caption", "/"),
|
||||||
|
Ok("/reference/model/figure/#definitions-caption".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$figure.caption.position", "/"),
|
||||||
|
Ok("/reference/model/figure/#definitions-caption-position".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_function_definition() {
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$outline", "/"),
|
||||||
|
Ok("/reference/model/outline".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$outline.title", "/"),
|
||||||
|
Ok("/reference/model/outline/#parameters-title".into())
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$outline.entry", "/"),
|
||||||
|
Ok("/reference/model/outline/#definitions-entry".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$outline.entry.fill", "/"),
|
||||||
|
Ok("/reference/model/outline/#definitions-entry-fill".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_function_definition_definition() {
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$outline.entry.indented", "/"),
|
||||||
|
Ok("/reference/model/outline/#definitions-entry-definitions-indented".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$outline.entry.indented.prefix", "/"),
|
||||||
|
Ok("/reference/model/outline/#definitions-entry-definitions-indented-prefix"
|
||||||
|
.into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_type() {
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$array", "/"),
|
||||||
|
Ok("/reference/foundations/array".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$array.at", "/"),
|
||||||
|
Ok("/reference/foundations/array/#definitions-at".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$array.at.index", "/"),
|
||||||
|
Ok("/reference/foundations/array/#definitions-at-index".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_type_constructor() {
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$str.base", "/"),
|
||||||
|
Ok("/reference/foundations/str/#constructor-base".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$tiling.relative", "/"),
|
||||||
|
Ok("/reference/visualize/tiling/#constructor-relative".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_group() {
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$calc.abs", "/"),
|
||||||
|
Ok("/reference/foundations/calc/#functions-abs".into())
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$calc.pow.exponent", "/"),
|
||||||
|
Ok("/reference/foundations/calc/#functions-pow-exponent".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_redundant_field() {
|
||||||
|
assert_eq!(
|
||||||
|
resolve_definition("$figure.body.anything", "/"),
|
||||||
|
Err("found redundant field anything".into())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -46,6 +46,7 @@ oxipng = { workspace = true }
|
|||||||
parking_lot = { workspace = true }
|
parking_lot = { workspace = true }
|
||||||
rayon = { workspace = true }
|
rayon = { workspace = true }
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
|
rustc-hash = { workspace = true }
|
||||||
tiny-skia = { workspace = true }
|
tiny-skia = { workspace = true }
|
||||||
unscanny = { workspace = true }
|
unscanny = { workspace = true }
|
||||||
walkdir = { workspace = true }
|
walkdir = { workspace = true }
|
||||||
|
@ -21,37 +21,37 @@ Running just the integration tests (the tests in this directory):
|
|||||||
cargo test --workspace --test tests
|
cargo test --workspace --test tests
|
||||||
```
|
```
|
||||||
|
|
||||||
You may want to [make yourself an alias](#making-an-alias) `testit` so that you can
|
The repository includes the alias `cargo testit` to make this less verbose. In
|
||||||
write shorter commands. In the examples below, we will use this alias.
|
the examples below, we will use this alias.
|
||||||
|
|
||||||
Running all tests with the given name pattern. You can use
|
Running all tests with the given name pattern. You can use
|
||||||
[regular expression](https://docs.rs/regex/latest/regex/)s.
|
[regular expression](https://docs.rs/regex/latest/regex/)s.
|
||||||
```bash
|
```bash
|
||||||
testit math # The name has "math" anywhere
|
cargo testit math # The name has "math" anywhere
|
||||||
testit math page # The name has "math" or "page" anywhere
|
cargo testit math page # The name has "math" or "page" anywhere
|
||||||
testit "^math" "^page" # The name begins with "math" or "page"
|
cargo testit "^math" "^page" # The name begins with "math" or "page"
|
||||||
testit "^(math|page)" # Same as above.
|
cargo testit "^(math|page)" # Same as above.
|
||||||
```
|
```
|
||||||
|
|
||||||
Running all tests discovered under given paths:
|
Running all tests discovered under given paths:
|
||||||
```bash
|
```bash
|
||||||
testit -p tests/suite/math/attach.typ
|
cargo testit -p tests/suite/math/attach.typ
|
||||||
testit -p tests/suite/model -p tests/suite/text
|
cargo testit -p tests/suite/model -p tests/suite/text
|
||||||
```
|
```
|
||||||
|
|
||||||
Running tests that begin with `issue` under a given path:
|
Running tests that begin with `issue` under a given path:
|
||||||
```bash
|
```bash
|
||||||
testit "^issue" -p tests/suite/model
|
cargo testit "^issue" -p tests/suite/model
|
||||||
```
|
```
|
||||||
|
|
||||||
Running a test with the exact test name `math-attach-mixed`.
|
Running a test with the exact test name `math-attach-mixed`.
|
||||||
```bash
|
```bash
|
||||||
testit --exact math-attach-mixed
|
cargo testit --exact math-attach-mixed
|
||||||
```
|
```
|
||||||
|
|
||||||
You may find more options in the help message:
|
You may find more options in the help message:
|
||||||
```bash
|
```bash
|
||||||
testit --help
|
cargo testit --help
|
||||||
```
|
```
|
||||||
|
|
||||||
To make the integration tests go faster they don't generate PDFs or SVGs by
|
To make the integration tests go faster they don't generate PDFs or SVGs by
|
||||||
@ -59,7 +59,7 @@ default. Pass the `--pdf` or `--svg` flag to generate those. Mind that PDFs and
|
|||||||
SVGs are **not** tested automatically at the moment, so you should always check
|
SVGs are **not** tested automatically at the moment, so you should always check
|
||||||
the output manually when making changes.
|
the output manually when making changes.
|
||||||
```bash
|
```bash
|
||||||
testit --pdf
|
cargo testit --pdf
|
||||||
```
|
```
|
||||||
|
|
||||||
## Writing tests
|
## Writing tests
|
||||||
@ -119,7 +119,7 @@ If you created a new test or fixed a bug in an existing test, you may need to
|
|||||||
update the reference output used for comparison. For this, you can use the
|
update the reference output used for comparison. For this, you can use the
|
||||||
`--update` flag:
|
`--update` flag:
|
||||||
```bash
|
```bash
|
||||||
testit --exact my-test-name --update
|
cargo testit --exact my-test-name --update
|
||||||
```
|
```
|
||||||
|
|
||||||
For visual tests, this will generally generate compressed reference images (to
|
For visual tests, this will generally generate compressed reference images (to
|
||||||
@ -127,24 +127,3 @@ remain within the size limit).
|
|||||||
|
|
||||||
If you use the VS Code test helper extension (see the `tools` folder), you can
|
If you use the VS Code test helper extension (see the `tools` folder), you can
|
||||||
alternatively use the save button to update the reference output.
|
alternatively use the save button to update the reference output.
|
||||||
|
|
||||||
## Making an alias
|
|
||||||
If you want to have a quicker way to run the tests, consider adding a shortcut
|
|
||||||
to your shell profile so that you can simply write something like:
|
|
||||||
```bash
|
|
||||||
testit --exact my-test-name
|
|
||||||
```
|
|
||||||
|
|
||||||
### Bash
|
|
||||||
Open your Bash configuration by executing `nano ~/.bashrc`.
|
|
||||||
```bash
|
|
||||||
alias testit="cargo test --workspace --test tests --"
|
|
||||||
```
|
|
||||||
|
|
||||||
### PowerShell
|
|
||||||
Open your PowerShell profile by executing `notepad $profile`.
|
|
||||||
```ps
|
|
||||||
function testit {
|
|
||||||
cargo test --workspace --test tests -- $args
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
8
tests/ref/html/html-elem-custom.html
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body><my-element>Hi</my-element><custom-button>Hi</custom-button><multi-word-component>Hi</multi-word-component><element->Hi</element-></body>
|
||||||
|
</html>
|
10
tests/ref/html/raw-html-inline-spaces.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>This has <code style="white-space: pre-wrap">double spaces inside</code>, which should be kept.</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
11
tests/ref/html/raw-html.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>This is <code style="white-space: pre-wrap"><strong>*</strong><strong>inline</strong><strong>*</strong></code>.</p>
|
||||||
|
<pre><code><span style="color: #d73a49">#</span><span style="color: #d73a49">set</span> <span style="color: #4b69c6">text</span>(blue)<br><strong>*</strong><strong>Hello</strong><strong>*</strong> <em>_</em><em>world</em><em>_</em>!</code></pre>
|
||||||
|
</body>
|
||||||
|
</html>
|
11
tests/ref/html/title-and-heading.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>A cool title</h1>
|
||||||
|
<h2>Some level one heading</h2>
|
||||||
|
</body>
|
||||||
|
</html>
|
10
tests/ref/html/title-basic.html
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Some Title</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
11
tests/ref/html/title-with-body.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>My title</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>My display title</h1>
|
||||||
|
</body>
|
||||||
|
</html>
|
12
tests/ref/html/title.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>My title</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>My title</h1>
|
||||||
|
<h2>A level one heading</h2>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
tests/ref/issue-6680-gradient-linear-with-aspect-correction.png
Normal file
After Width: | Height: | Size: 137 B |
BIN
tests/ref/math-attach-followed-by-func-call-complex.png
Normal file
After Width: | Height: | Size: 927 B |
BIN
tests/ref/math-attach-followed-by-func-call-warning.png
Normal file
After Width: | Height: | Size: 630 B |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 2.8 KiB |
BIN
tests/ref/math-stretch-min-overlap-exceeds-max.png
Normal file
After Width: | Height: | Size: 585 B |
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 9.3 KiB |
BIN
tests/ref/title-show-set.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
tests/ref/title-with-body-auto.png
Normal file
After Width: | Height: | Size: 615 B |
BIN
tests/ref/title-with-body.png
Normal file
After Width: | Height: | Size: 1.0 KiB |
BIN
tests/ref/title.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
@ -1,4 +1,3 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
use std::fmt::{self, Display, Formatter};
|
use std::fmt::{self, Display, Formatter};
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@ -6,6 +5,7 @@ use std::str::FromStr;
|
|||||||
use std::sync::LazyLock;
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use ecow::{EcoString, eco_format};
|
use ecow::{EcoString, eco_format};
|
||||||
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use typst_syntax::package::PackageVersion;
|
use typst_syntax::package::PackageVersion;
|
||||||
use typst_syntax::{
|
use typst_syntax::{
|
||||||
FileId, Lines, Source, VirtualPath, is_id_continue, is_ident, is_newline,
|
FileId, Lines, Source, VirtualPath, is_id_continue, is_ident, is_newline,
|
||||||
@ -123,7 +123,7 @@ impl Display for NoteKind {
|
|||||||
struct Collector {
|
struct Collector {
|
||||||
tests: Vec<Test>,
|
tests: Vec<Test>,
|
||||||
errors: Vec<TestParseError>,
|
errors: Vec<TestParseError>,
|
||||||
seen: HashMap<EcoString, (FilePos, Vec<Attr>)>,
|
seen: FxHashMap<EcoString, (FilePos, Vec<Attr>)>,
|
||||||
skipped: usize,
|
skipped: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,7 +133,7 @@ impl Collector {
|
|||||||
Self {
|
Self {
|
||||||
tests: vec![],
|
tests: vec![],
|
||||||
errors: vec![],
|
errors: vec![],
|
||||||
seen: HashMap::new(),
|
seen: FxHashMap::default(),
|
||||||
skipped: 0,
|
skipped: 0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -509,7 +509,7 @@ impl<'a> Parser<'a> {
|
|||||||
|
|
||||||
/// Whether a test is within the selected set to run.
|
/// Whether a test is within the selected set to run.
|
||||||
fn selected(name: &str, abs: PathBuf) -> bool {
|
fn selected(name: &str, abs: PathBuf) -> bool {
|
||||||
static SKIPPED: LazyLock<HashSet<&'static str>> = LazyLock::new(|| {
|
static SKIPPED: LazyLock<FxHashSet<&'static str>> = LazyLock::new(|| {
|
||||||
String::leak(std::fs::read_to_string(crate::SKIP_PATH).unwrap())
|
String::leak(std::fs::read_to_string(crate::SKIP_PATH).unwrap())
|
||||||
.lines()
|
.lines()
|
||||||
.map(|line| line.trim())
|
.map(|line| line.trim())
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@ -8,6 +7,7 @@ use std::sync::OnceLock;
|
|||||||
|
|
||||||
use comemo::Tracked;
|
use comemo::Tracked;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use rustc_hash::FxHashMap;
|
||||||
use typst::diag::{At, FileError, FileResult, SourceResult, StrResult, bail};
|
use typst::diag::{At, FileError, FileResult, SourceResult, StrResult, bail};
|
||||||
use typst::engine::Engine;
|
use typst::engine::Engine;
|
||||||
use typst::foundations::{
|
use typst::foundations::{
|
||||||
@ -108,7 +108,7 @@ struct TestBase {
|
|||||||
library: LazyHash<Library>,
|
library: LazyHash<Library>,
|
||||||
book: LazyHash<FontBook>,
|
book: LazyHash<FontBook>,
|
||||||
fonts: Vec<Font>,
|
fonts: Vec<Font>,
|
||||||
slots: Mutex<HashMap<FileId, FileSlot>>,
|
slots: Mutex<FxHashMap<FileId, FileSlot>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TestBase {
|
impl Default for TestBase {
|
||||||
@ -122,7 +122,7 @@ impl Default for TestBase {
|
|||||||
library: LazyHash::new(library()),
|
library: LazyHash::new(library()),
|
||||||
book: LazyHash::new(FontBook::from_fonts(&fonts)),
|
book: LazyHash::new(FontBook::from_fonts(&fonts)),
|
||||||
fonts,
|
fonts,
|
||||||
slots: Mutex::new(HashMap::new()),
|
slots: Mutex::new(FxHashMap::default()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,3 +13,25 @@ Text
|
|||||||
val
|
val
|
||||||
})
|
})
|
||||||
#metadata("Hi") <l>
|
#metadata("Hi") <l>
|
||||||
|
|
||||||
|
--- html-elem-custom html ---
|
||||||
|
#html.elem("my-element")[Hi]
|
||||||
|
#html.elem("custom-button")[Hi]
|
||||||
|
#html.elem("multi-word-component")[Hi]
|
||||||
|
#html.elem("element-")[Hi]
|
||||||
|
|
||||||
|
--- html-elem-invalid ---
|
||||||
|
// Error: 12-24 the character "@" is not valid in a tag name
|
||||||
|
#html.elem("my@element")
|
||||||
|
|
||||||
|
--- html-elem-custom-bad-start html ---
|
||||||
|
// Error: 12-22 custom element name must start with a lowercase letter
|
||||||
|
#html.elem("1-custom")
|
||||||
|
|
||||||
|
--- html-elem-custom-uppercase html ---
|
||||||
|
// Error: 12-21 custom element name must not contain uppercase letters
|
||||||
|
#html.elem("my-ELEM")
|
||||||
|
|
||||||
|
--- html-elem-custom-reserved html ---
|
||||||
|
// Error: 12-28 name is reserved and not valid for a custom element
|
||||||
|
#html.elem("annotation-xml")
|
||||||
|
@ -34,7 +34,7 @@ $tilde(sum), tilde(sum, size: #50%), accent(H, hat, size: #200%)$
|
|||||||
|
|
||||||
--- math-accent-sized-script ---
|
--- math-accent-sized-script ---
|
||||||
// Test accent size in script size.
|
// Test accent size in script size.
|
||||||
$tilde(U, size: #1.1em), x^tilde(U, size: #1.1em), sscript(tilde(U, size: #1.1em))$
|
$tilde(U, size: #1.1em), x^(tilde(U, size: #1.1em)), sscript(tilde(U, size: #1.1em))$
|
||||||
|
|
||||||
--- math-accent-dotless ---
|
--- math-accent-dotless ---
|
||||||
// Test dotless glyph variants.
|
// Test dotless glyph variants.
|
||||||
|
@ -42,9 +42,41 @@ attach(a, tl: u, t: t, tr: v, bl: x, b: b, br: y)
|
|||||||
$
|
$
|
||||||
|
|
||||||
--- math-attach-followed-by-func-call ---
|
--- math-attach-followed-by-func-call ---
|
||||||
// Test function call after subscript.
|
// Test function call after subscripts and superscripts.
|
||||||
$pi_1(Y), a_f(x), a^zeta (x), a^abs(b)_sqrt(c) \
|
#set page(width: auto)
|
||||||
a^subset.eq (x), a_(zeta(x)), pi_(1(Y)), a^(abs(b))_(sqrt(c))$
|
$a_1(x), a^10(x), a_f(x), a^zeta(x), a_"text"(x),
|
||||||
|
// Warning: 22-25 function literal used as subscript
|
||||||
|
// Hint: 22-25 wrap the entire function call in parentheses
|
||||||
|
// Warning: 32-36 function literal used as superscript
|
||||||
|
// Hint: 32-36 wrap the entire function call in parentheses
|
||||||
|
a_∂(x), a^cos(x), a_abs(x), a^root(2, x), a_subset.eq(x) \
|
||||||
|
a_(1(x)), a^(10(x)), a_(f(x)), a^(zeta(x)), a_("text"(x)),
|
||||||
|
a_(∂(x)), a^(cos(x)), a_(abs(x)), a^(root(2, x)), a_(subset.eq(x))$
|
||||||
|
|
||||||
|
--- math-attach-followed-by-func-call-complex ---
|
||||||
|
// Test function call with named arguments after scripts.
|
||||||
|
#let cross = $c$
|
||||||
|
// Warning: 4-10 function literal used as subscript
|
||||||
|
// Hint: 4-10 wrap the entire function call in parentheses
|
||||||
|
$a_cancel(x, cross: #true)$
|
||||||
|
|
||||||
|
--- math-attach-followed-by-func-call-error ---
|
||||||
|
// Error: 14-19 unknown variable: cross
|
||||||
|
// Hint: 14-19 if you meant to display multiple letters as is, try adding spaces between each letter: `c r o s s`
|
||||||
|
// Hint: 14-19 or if you meant to display this as text, try placing it in quotes: `"cross"`
|
||||||
|
// Warning: 4-10 function literal used as subscript
|
||||||
|
// Hint: 4-10 wrap the entire function call in parentheses
|
||||||
|
$a_cancel(x, cross: #true)$
|
||||||
|
|
||||||
|
--- math-attach-followed-by-func-call-warning ---
|
||||||
|
// Test warning for function literal as subscripts and superscripts.
|
||||||
|
#let myfunc(arg) = $arg$
|
||||||
|
// Warning: 4-10 function literal used as subscript
|
||||||
|
// Hint: 4-10 wrap the entire function call in parentheses
|
||||||
|
$a_myfunc(x) a_(myfunc(x))$
|
||||||
|
// Warning: 4-10 function literal used as subscript
|
||||||
|
// Hint: 4-10 wrap the entire function call in parentheses
|
||||||
|
$a_myfunc a_(myfunc)$
|
||||||
|
|
||||||
--- math-attach-nested ---
|
--- math-attach-nested ---
|
||||||
// Test associativity and scaling.
|
// Test associativity and scaling.
|
||||||
|
@ -91,3 +91,12 @@ $ body^"text" $
|
|||||||
}
|
}
|
||||||
$body^"long text"$
|
$body^"long text"$
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--- math-stretch-min-overlap-exceeds-max ---
|
||||||
|
// Test that glyph assembly doesn't end up with negative lengths if the max
|
||||||
|
// overlap calculated is less than the minConnectorOverlap.
|
||||||
|
#show math.equation: set text(font: "STIX Two Math")
|
||||||
|
// Warning: glyph has assembly parts with overlap less than minConnectorOverlap
|
||||||
|
// Hint: its rendering may appear broken - this is probably a font bug
|
||||||
|
// Hint: please file an issue at https://github.com/typst/typst/issues
|
||||||
|
$ stretch(->)^"Gauss-Jordan Elimination" $
|
||||||
|
24
tests/suite/model/title.typ
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
// Test title element.
|
||||||
|
|
||||||
|
--- title render html ---
|
||||||
|
#set document(title: "My title")
|
||||||
|
#title()
|
||||||
|
= A level one heading
|
||||||
|
|
||||||
|
--- title-with-body render html ---
|
||||||
|
#set document(title: "My title")
|
||||||
|
#title[My display title]
|
||||||
|
|
||||||
|
--- title-with-body-auto render ---
|
||||||
|
#set document(title: "My title")
|
||||||
|
#title(auto)
|
||||||
|
|
||||||
|
--- title-show-set ---
|
||||||
|
#show title: set text(blue)
|
||||||
|
#title[A blue title]
|
||||||
|
|
||||||
|
--- title-unset ---
|
||||||
|
// Error: 2-9 document title was not set
|
||||||
|
// Hint: 2-9 set the title with `set document(title: [...])`
|
||||||
|
// Hint: 2-9 or provide an explicit body with `title[..]`
|
||||||
|
#title()
|
@ -215,7 +215,7 @@ box[]
|
|||||||
hello.world
|
hello.world
|
||||||
hello.world()
|
hello.world()
|
||||||
hello.my.world()
|
hello.my.world()
|
||||||
f_zeta(x), f_zeta(x)/1
|
f_(zeta(x)), f_(zeta(x))/1
|
||||||
emph(hello.my.world())
|
emph(hello.my.world())
|
||||||
emph(hello.my().world)
|
emph(hello.my().world)
|
||||||
emph(hello.my().world())
|
emph(hello.my().world())
|
||||||
@ -487,6 +487,16 @@ test
|
|||||||
assert.eq(block, c.input.block, message: "in point " + c.name + ", expect " + repr(block) + ", got " + repr(c.input.block) + "")
|
assert.eq(block, c.input.block, message: "in point " + c.name + ", expect " + repr(block) + ", got " + repr(c.input.block) + "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
--- raw-html html ---
|
||||||
|
This is ```typ *inline*```.
|
||||||
|
```typ
|
||||||
|
#set text(blue)
|
||||||
|
*Hello* _world_!
|
||||||
|
```
|
||||||
|
|
||||||
|
--- raw-html-inline-spaces html ---
|
||||||
|
This has `double spaces inside`, which should be kept.
|
||||||
|
|
||||||
--- raw-line ---
|
--- raw-line ---
|
||||||
#set page(width: 200pt)
|
#set page(width: 200pt)
|
||||||
|
|
||||||
|
@ -692,3 +692,6 @@ $ A = mat(
|
|||||||
(blue, 100%),
|
(blue, 100%),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
--- issue-6680-gradient-linear-with-aspect-correction ---
|
||||||
|
#set page(width: 200pt, height: auto, margin: 10pt, fill: gradient.linear(red, blue, angle: 45deg).sharp(2))
|
||||||
|