Compare commits
31 Commits
d199546f9f
...
db9a83d9fc
Author | SHA1 | Date | |
---|---|---|---|
|
db9a83d9fc | ||
|
8d3488a07d | ||
|
476c2df312 | ||
|
e0b2c32a8e | ||
|
99b7d2898e | ||
|
e1a9166e1d | ||
|
6271cdceae | ||
|
63fda9935f | ||
|
8820a00beb | ||
|
9a6ffbc7db | ||
|
bf0d45e2c0 | ||
|
d4def09962 | ||
|
66679920b2 | ||
|
cfb3b1a270 | ||
|
52f1f53973 | ||
|
d6b0d68ffa | ||
|
8f039dd614 | ||
|
2eef9e84e1 | ||
|
d11ad80dee | ||
|
bad343748b | ||
|
f31c971624 | ||
|
acd3a5b7a5 | ||
|
225e845021 | ||
|
36d83c8c09 | ||
|
3744c99b07 | ||
|
81efc82d3c | ||
|
69c3f95705 | ||
|
ebe2543264 | ||
|
56f4fa2b4d | ||
|
55bc5f4c94 | ||
|
240f238eee |
30
.github/workflows/ci.yml
vendored
@ -5,6 +5,7 @@ env:
|
|||||||
RUSTFLAGS: "-Dwarnings"
|
RUSTFLAGS: "-Dwarnings"
|
||||||
RUSTDOCFLAGS: "-Dwarnings"
|
RUSTDOCFLAGS: "-Dwarnings"
|
||||||
TYPST_TESTS_EXTENDED: true
|
TYPST_TESTS_EXTENDED: true
|
||||||
|
PKG_CONFIG_i686-unknown-linux-gnu: /usr/bin/i686-linux-gnu-pkgconf
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
# This allows us to have one branch protection rule for the full test matrix.
|
# This allows us to have one branch protection rule for the full test matrix.
|
||||||
@ -27,30 +28,43 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ubuntu-latest, windows-latest]
|
os: [ubuntu-latest, windows-latest]
|
||||||
|
bits: [64]
|
||||||
|
include:
|
||||||
|
- os: ubuntu-latest
|
||||||
|
bits: 32
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@1.83.0
|
- if: startsWith(matrix.os, 'ubuntu-') && matrix.bits == 32
|
||||||
|
run: |
|
||||||
|
sudo dpkg --add-architecture i386
|
||||||
|
sudo apt update
|
||||||
|
sudo apt install -y gcc-multilib libssl-dev:i386 pkg-config:i386
|
||||||
|
- uses: dtolnay/rust-toolchain@1.85.0
|
||||||
|
with:
|
||||||
|
targets: ${{ matrix.bits == 32 && 'i686-unknown-linux-gnu' || '' }}
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- run: cargo test --workspace --no-run
|
with:
|
||||||
- run: cargo test --workspace --no-fail-fast
|
key: ${{ matrix.bits }}
|
||||||
|
- run: cargo test --workspace --no-run ${{ matrix.bits == 32 && '--target i686-unknown-linux-gnu' || '' }}
|
||||||
|
- run: cargo test --workspace --no-fail-fast ${{ matrix.bits == 32 && '--target i686-unknown-linux-gnu' || '' }}
|
||||||
- name: Upload rendered test output
|
- name: Upload rendered test output
|
||||||
if: failure()
|
if: failure()
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: tests-rendered-${{ matrix.os }}
|
name: tests-rendered-${{ matrix.os }}-${{ matrix.bits }}
|
||||||
path: tests/store/render/**
|
path: tests/store/render/**
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
- name: Update test artifacts
|
- name: Update test artifacts
|
||||||
if: failure()
|
if: failure()
|
||||||
run: |
|
run: |
|
||||||
cargo test --workspace --test tests -- --update
|
cargo test --workspace --test tests ${{ matrix.bits == 32 && '--target i686-unknown-linux-gnu' || '' }} -- --update
|
||||||
echo 'updated_artifacts=1' >> "$GITHUB_ENV"
|
echo 'updated_artifacts=1' >> "$GITHUB_ENV"
|
||||||
- name: Upload updated reference output (for use if the test changes are desired)
|
- name: Upload updated reference output (for use if the test changes are desired)
|
||||||
if: failure() && env.updated_artifacts
|
if: failure() && env.updated_artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: tests-updated-${{ matrix.os }}
|
name: tests-updated-${{ matrix.os }}-${{ matrix.bits }}
|
||||||
path: tests/ref/**
|
path: tests/ref/**
|
||||||
retention-days: 3
|
retention-days: 3
|
||||||
|
|
||||||
@ -59,7 +73,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@1.83.0
|
- uses: dtolnay/rust-toolchain@1.85.0
|
||||||
with:
|
with:
|
||||||
components: clippy, rustfmt
|
components: clippy, rustfmt
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
@ -73,7 +87,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@1.80.0
|
- uses: dtolnay/rust-toolchain@1.83.0
|
||||||
- uses: Swatinem/rust-cache@v2
|
- uses: Swatinem/rust-cache@v2
|
||||||
- run: cargo check --workspace
|
- run: cargo check --workspace
|
||||||
|
|
||||||
|
2
.github/workflows/release.yml
vendored
@ -44,7 +44,7 @@ jobs:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: dtolnay/rust-toolchain@1.83.0
|
- uses: dtolnay/rust-toolchain@1.85.0
|
||||||
with:
|
with:
|
||||||
target: ${{ matrix.target }}
|
target: ${{ matrix.target }}
|
||||||
|
|
||||||
|
49
Cargo.lock
generated
@ -1,6 +1,6 @@
|
|||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 3
|
version = 4
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler2"
|
name = "adler2"
|
||||||
@ -2735,7 +2735,7 @@ checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst"
|
name = "typst"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comemo",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
@ -2752,12 +2752,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-assets"
|
name = "typst-assets"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
source = "git+https://github.com/typst/typst-assets?rev=fa0f8a4#fa0f8a438cc4bc2113cc0aa3304cd68cdc2bc020"
|
source = "git+https://github.com/typst/typst-assets?rev=ab1295f#ab1295ff896444e51902e03c2669955e1d73604a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-cli"
|
name = "typst-cli"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
@ -2802,12 +2802,12 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-dev-assets"
|
name = "typst-dev-assets"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
source = "git+https://github.com/typst/typst-dev-assets?rev=61aebe9#61aebe9575a5abff889f76d73c7b01dc8e17e340"
|
source = "git+https://github.com/typst/typst-dev-assets?rev=9879589#9879589f4b3247b12c5e694d0d7fa86d4d8a198e"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-docs"
|
name = "typst-docs"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"ecow",
|
"ecow",
|
||||||
@ -2830,7 +2830,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-eval"
|
name = "typst-eval"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comemo",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
@ -2848,7 +2848,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-fuzz"
|
name = "typst-fuzz"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comemo",
|
"comemo",
|
||||||
"libfuzzer-sys",
|
"libfuzzer-sys",
|
||||||
@ -2860,7 +2860,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-html"
|
name = "typst-html"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comemo",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
@ -2874,7 +2874,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-ide"
|
name = "typst-ide"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"comemo",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
@ -2891,7 +2891,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-kit"
|
name = "typst-kit"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs",
|
"dirs",
|
||||||
"ecow",
|
"ecow",
|
||||||
@ -2914,7 +2914,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-layout"
|
name = "typst-layout"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"az",
|
"az",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
@ -2944,7 +2944,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-library"
|
name = "typst-library"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"az",
|
"az",
|
||||||
"bitflags 2.8.0",
|
"bitflags 2.8.0",
|
||||||
@ -2995,6 +2995,7 @@ dependencies = [
|
|||||||
"typst-timing",
|
"typst-timing",
|
||||||
"typst-utils",
|
"typst-utils",
|
||||||
"unicode-math-class",
|
"unicode-math-class",
|
||||||
|
"unicode-normalization",
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unscanny",
|
"unscanny",
|
||||||
"usvg",
|
"usvg",
|
||||||
@ -3004,7 +3005,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-macros"
|
name = "typst-macros"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"heck",
|
"heck",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
@ -3014,7 +3015,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-pdf"
|
name = "typst-pdf"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"base64",
|
"base64",
|
||||||
@ -3040,7 +3041,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-realize"
|
name = "typst-realize"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"arrayvec",
|
"arrayvec",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
@ -3056,7 +3057,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-render"
|
name = "typst-render"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
"comemo",
|
"comemo",
|
||||||
@ -3072,7 +3073,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-svg"
|
name = "typst-svg"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64",
|
||||||
"comemo",
|
"comemo",
|
||||||
@ -3090,7 +3091,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-syntax"
|
name = "typst-syntax"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ecow",
|
"ecow",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3106,7 +3107,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-tests"
|
name = "typst-tests"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"clap",
|
"clap",
|
||||||
"comemo",
|
"comemo",
|
||||||
@ -3131,7 +3132,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-timing"
|
name = "typst-timing"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"serde",
|
"serde",
|
||||||
@ -3141,7 +3142,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-utils"
|
name = "typst-utils"
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"portable-atomic",
|
"portable-atomic",
|
||||||
|
41
Cargo.toml
@ -4,8 +4,8 @@ default-members = ["crates/typst-cli"]
|
|||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.13.0"
|
version = "0.13.1"
|
||||||
rust-version = "1.80" # also change in ci.yml
|
rust-version = "1.83" # also change in ci.yml
|
||||||
authors = ["The Typst Project Developers"]
|
authors = ["The Typst Project Developers"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
homepage = "https://typst.app"
|
homepage = "https://typst.app"
|
||||||
@ -16,24 +16,24 @@ keywords = ["typst"]
|
|||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
typst = { path = "crates/typst", version = "0.13.0" }
|
typst = { path = "crates/typst", version = "0.13.1" }
|
||||||
typst-cli = { path = "crates/typst-cli", version = "0.13.0" }
|
typst-cli = { path = "crates/typst-cli", version = "0.13.1" }
|
||||||
typst-eval = { path = "crates/typst-eval", version = "0.13.0" }
|
typst-eval = { path = "crates/typst-eval", version = "0.13.1" }
|
||||||
typst-html = { path = "crates/typst-html", version = "0.13.0" }
|
typst-html = { path = "crates/typst-html", version = "0.13.1" }
|
||||||
typst-ide = { path = "crates/typst-ide", version = "0.13.0" }
|
typst-ide = { path = "crates/typst-ide", version = "0.13.1" }
|
||||||
typst-kit = { path = "crates/typst-kit", version = "0.13.0" }
|
typst-kit = { path = "crates/typst-kit", version = "0.13.1" }
|
||||||
typst-layout = { path = "crates/typst-layout", version = "0.13.0" }
|
typst-layout = { path = "crates/typst-layout", version = "0.13.1" }
|
||||||
typst-library = { path = "crates/typst-library", version = "0.13.0" }
|
typst-library = { path = "crates/typst-library", version = "0.13.1" }
|
||||||
typst-macros = { path = "crates/typst-macros", version = "0.13.0" }
|
typst-macros = { path = "crates/typst-macros", version = "0.13.1" }
|
||||||
typst-pdf = { path = "crates/typst-pdf", version = "0.13.0" }
|
typst-pdf = { path = "crates/typst-pdf", version = "0.13.1" }
|
||||||
typst-realize = { path = "crates/typst-realize", version = "0.13.0" }
|
typst-realize = { path = "crates/typst-realize", version = "0.13.1" }
|
||||||
typst-render = { path = "crates/typst-render", version = "0.13.0" }
|
typst-render = { path = "crates/typst-render", version = "0.13.1" }
|
||||||
typst-svg = { path = "crates/typst-svg", version = "0.13.0" }
|
typst-svg = { path = "crates/typst-svg", version = "0.13.1" }
|
||||||
typst-syntax = { path = "crates/typst-syntax", version = "0.13.0" }
|
typst-syntax = { path = "crates/typst-syntax", version = "0.13.1" }
|
||||||
typst-timing = { path = "crates/typst-timing", version = "0.13.0" }
|
typst-timing = { path = "crates/typst-timing", version = "0.13.1" }
|
||||||
typst-utils = { path = "crates/typst-utils", version = "0.13.0" }
|
typst-utils = { path = "crates/typst-utils", version = "0.13.1" }
|
||||||
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "fa0f8a4" }
|
typst-assets = { git = "https://github.com/typst/typst-assets", rev = "ab1295f" }
|
||||||
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "61aebe9" }
|
typst-dev-assets = { git = "https://github.com/typst/typst-dev-assets", rev = "9879589" }
|
||||||
arrayvec = "0.7.4"
|
arrayvec = "0.7.4"
|
||||||
az = "1.2"
|
az = "1.2"
|
||||||
base64 = "0.22"
|
base64 = "0.22"
|
||||||
@ -129,6 +129,7 @@ unicode-bidi = "0.3.18"
|
|||||||
unicode-ident = "1.0"
|
unicode-ident = "1.0"
|
||||||
unicode-math-class = "0.1"
|
unicode-math-class = "0.1"
|
||||||
unicode-script = "0.5"
|
unicode-script = "0.5"
|
||||||
|
unicode-normalization = "0.1.24"
|
||||||
unicode-segmentation = "1"
|
unicode-segmentation = "1"
|
||||||
unscanny = "0.1"
|
unscanny = "0.1"
|
||||||
ureq = { version = "2", default-features = false, features = ["native-tls", "gzip", "json"] }
|
ureq = { version = "2", default-features = false, features = ["native-tls", "gzip", "json"] }
|
||||||
|
@ -113,7 +113,9 @@ Typst's CLI is available from different sources:
|
|||||||
|
|
||||||
- You can install Typst through different package managers. Note that the
|
- You can install Typst through different package managers. Note that the
|
||||||
versions in the package managers might lag behind the latest release.
|
versions in the package managers might lag behind the latest release.
|
||||||
- Linux: View [Typst on Repology][repology]
|
- Linux:
|
||||||
|
- View [Typst on Repology][repology]
|
||||||
|
- View [Typst's Snap][snap]
|
||||||
- macOS: `brew install typst`
|
- macOS: `brew install typst`
|
||||||
- Windows: `winget install --id Typst.Typst`
|
- Windows: `winget install --id Typst.Typst`
|
||||||
|
|
||||||
@ -254,3 +256,4 @@ instant preview. To achieve these goals, we follow three core design principles:
|
|||||||
[contributing]: https://github.com/typst/typst/blob/main/CONTRIBUTING.md
|
[contributing]: https://github.com/typst/typst/blob/main/CONTRIBUTING.md
|
||||||
[packages]: https://github.com/typst/packages/
|
[packages]: https://github.com/typst/packages/
|
||||||
[`comemo`]: https://github.com/typst/comemo/
|
[`comemo`]: https://github.com/typst/comemo/
|
||||||
|
[snap]: https://snapcraft.io/typst
|
||||||
|
@ -350,7 +350,7 @@ fn export_image(
|
|||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.filter(|(i, _)| {
|
.filter(|(i, _)| {
|
||||||
config.pages.as_ref().map_or(true, |exported_page_ranges| {
|
config.pages.as_ref().is_none_or(|exported_page_ranges| {
|
||||||
exported_page_ranges.includes_page_index(*i)
|
exported_page_ranges.includes_page_index(*i)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -55,11 +55,11 @@ pub fn watch(timer: &mut Timer, command: &WatchCommand) -> StrResult<()> {
|
|||||||
// Perform initial compilation.
|
// Perform initial compilation.
|
||||||
timer.record(&mut world, |world| compile_once(world, &mut config))??;
|
timer.record(&mut world, |world| compile_once(world, &mut config))??;
|
||||||
|
|
||||||
// Watch all dependencies of the initial compilation.
|
|
||||||
watcher.update(world.dependencies())?;
|
|
||||||
|
|
||||||
// Recompile whenever something relevant happens.
|
// Recompile whenever something relevant happens.
|
||||||
loop {
|
loop {
|
||||||
|
// Watch all dependencies of the most recent compilation.
|
||||||
|
watcher.update(world.dependencies())?;
|
||||||
|
|
||||||
// Wait until anything relevant happens.
|
// Wait until anything relevant happens.
|
||||||
watcher.wait()?;
|
watcher.wait()?;
|
||||||
|
|
||||||
@ -71,9 +71,6 @@ pub fn watch(timer: &mut Timer, command: &WatchCommand) -> StrResult<()> {
|
|||||||
|
|
||||||
// Evict the cache.
|
// Evict the cache.
|
||||||
comemo::evict(10);
|
comemo::evict(10);
|
||||||
|
|
||||||
// Adjust the file watching.
|
|
||||||
watcher.update(world.dependencies())?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,6 +201,10 @@ impl Watcher {
|
|||||||
let event = event
|
let event = event
|
||||||
.map_err(|err| eco_format!("failed to watch dependencies ({err})"))?;
|
.map_err(|err| eco_format!("failed to watch dependencies ({err})"))?;
|
||||||
|
|
||||||
|
if !is_relevant_event_kind(&event.kind) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Workaround for notify-rs' implicit unwatch on remove/rename
|
// Workaround for notify-rs' implicit unwatch on remove/rename
|
||||||
// (triggered by some editors when saving files) with the
|
// (triggered by some editors when saving files) with the
|
||||||
// inotify backend. By keeping track of the potentially
|
// inotify backend. By keeping track of the potentially
|
||||||
@ -224,7 +225,17 @@ impl Watcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
relevant |= self.is_event_relevant(&event);
|
// Don't recompile because the output file changed.
|
||||||
|
// FIXME: This doesn't work properly for multifile image export.
|
||||||
|
if event
|
||||||
|
.paths
|
||||||
|
.iter()
|
||||||
|
.all(|path| is_same_file(path, &self.output).unwrap_or(false))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
relevant = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we found a relevant event or if any of the missing files now
|
// If we found a relevant event or if any of the missing files now
|
||||||
@ -234,32 +245,23 @@ impl Watcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether a watch event is relevant for compilation.
|
/// Whether a kind of watch event is relevant for compilation.
|
||||||
fn is_event_relevant(&self, event: ¬ify::Event) -> bool {
|
fn is_relevant_event_kind(kind: ¬ify::EventKind) -> bool {
|
||||||
// Never recompile because the output file changed.
|
match kind {
|
||||||
if event
|
notify::EventKind::Any => true,
|
||||||
.paths
|
notify::EventKind::Access(_) => false,
|
||||||
.iter()
|
notify::EventKind::Create(_) => true,
|
||||||
.all(|path| is_same_file(path, &self.output).unwrap_or(false))
|
notify::EventKind::Modify(kind) => match kind {
|
||||||
{
|
notify::event::ModifyKind::Any => true,
|
||||||
return false;
|
notify::event::ModifyKind::Data(_) => true,
|
||||||
}
|
notify::event::ModifyKind::Metadata(_) => false,
|
||||||
|
notify::event::ModifyKind::Name(_) => true,
|
||||||
match &event.kind {
|
notify::event::ModifyKind::Other => false,
|
||||||
notify::EventKind::Any => true,
|
},
|
||||||
notify::EventKind::Access(_) => false,
|
notify::EventKind::Remove(_) => true,
|
||||||
notify::EventKind::Create(_) => true,
|
notify::EventKind::Other => false,
|
||||||
notify::EventKind::Modify(kind) => match kind {
|
|
||||||
notify::event::ModifyKind::Any => true,
|
|
||||||
notify::event::ModifyKind::Data(_) => true,
|
|
||||||
notify::event::ModifyKind::Metadata(_) => false,
|
|
||||||
notify::event::ModifyKind::Name(_) => true,
|
|
||||||
notify::event::ModifyKind::Other => false,
|
|
||||||
},
|
|
||||||
notify::EventKind::Remove(_) => true,
|
|
||||||
notify::EventKind::Other => false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -466,7 +466,7 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Code and content blocks create a scope.
|
// Code and content blocks create a scope.
|
||||||
Some(ast::Expr::Code(_) | ast::Expr::Content(_)) => {
|
Some(ast::Expr::CodeBlock(_) | ast::Expr::ContentBlock(_)) => {
|
||||||
self.internal.enter();
|
self.internal.enter();
|
||||||
for child in node.children() {
|
for child in node.children() {
|
||||||
self.visit(child);
|
self.visit(child);
|
||||||
@ -516,7 +516,7 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
|
|
||||||
// A let expression contains a binding, but that binding is only
|
// A let expression contains a binding, but that binding is only
|
||||||
// active after the body is evaluated.
|
// active after the body is evaluated.
|
||||||
Some(ast::Expr::Let(expr)) => {
|
Some(ast::Expr::LetBinding(expr)) => {
|
||||||
if let Some(init) = expr.init() {
|
if let Some(init) = expr.init() {
|
||||||
self.visit(init.to_untyped());
|
self.visit(init.to_untyped());
|
||||||
}
|
}
|
||||||
@ -529,7 +529,7 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
// A for loop contains one or two bindings in its pattern. These are
|
// A for loop contains one or two bindings in its pattern. These are
|
||||||
// active after the iterable is evaluated but before the body is
|
// active after the iterable is evaluated but before the body is
|
||||||
// evaluated.
|
// evaluated.
|
||||||
Some(ast::Expr::For(expr)) => {
|
Some(ast::Expr::ForLoop(expr)) => {
|
||||||
self.visit(expr.iterable().to_untyped());
|
self.visit(expr.iterable().to_untyped());
|
||||||
self.internal.enter();
|
self.internal.enter();
|
||||||
|
|
||||||
@ -544,7 +544,7 @@ impl<'a> CapturesVisitor<'a> {
|
|||||||
|
|
||||||
// An import contains items, but these are active only after the
|
// An import contains items, but these are active only after the
|
||||||
// path is evaluated.
|
// path is evaluated.
|
||||||
Some(ast::Expr::Import(expr)) => {
|
Some(ast::Expr::ModuleImport(expr)) => {
|
||||||
self.visit(expr.source().to_untyped());
|
self.visit(expr.source().to_untyped());
|
||||||
if let Some(ast::Imports::Items(items)) = expr.imports() {
|
if let Some(ast::Imports::Items(items)) = expr.imports() {
|
||||||
for item in items.iter() {
|
for item in items.iter() {
|
||||||
|
@ -30,7 +30,7 @@ fn eval_code<'a>(
|
|||||||
while let Some(expr) = exprs.next() {
|
while let Some(expr) = exprs.next() {
|
||||||
let span = expr.span();
|
let span = expr.span();
|
||||||
let value = match expr {
|
let value = match expr {
|
||||||
ast::Expr::Set(set) => {
|
ast::Expr::SetRule(set) => {
|
||||||
let styles = set.eval(vm)?;
|
let styles = set.eval(vm)?;
|
||||||
if vm.flow.is_some() {
|
if vm.flow.is_some() {
|
||||||
break;
|
break;
|
||||||
@ -39,7 +39,7 @@ fn eval_code<'a>(
|
|||||||
let tail = eval_code(vm, exprs)?.display();
|
let tail = eval_code(vm, exprs)?.display();
|
||||||
Value::Content(tail.styled_with_map(styles))
|
Value::Content(tail.styled_with_map(styles))
|
||||||
}
|
}
|
||||||
ast::Expr::Show(show) => {
|
ast::Expr::ShowRule(show) => {
|
||||||
let recipe = show.eval(vm)?;
|
let recipe = show.eval(vm)?;
|
||||||
if vm.flow.is_some() {
|
if vm.flow.is_some() {
|
||||||
break;
|
break;
|
||||||
@ -94,9 +94,9 @@ impl Eval for ast::Expr<'_> {
|
|||||||
Self::Label(v) => v.eval(vm),
|
Self::Label(v) => v.eval(vm),
|
||||||
Self::Ref(v) => v.eval(vm).map(Value::Content),
|
Self::Ref(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Heading(v) => v.eval(vm).map(Value::Content),
|
Self::Heading(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::List(v) => v.eval(vm).map(Value::Content),
|
Self::ListItem(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Enum(v) => v.eval(vm).map(Value::Content),
|
Self::EnumItem(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Term(v) => v.eval(vm).map(Value::Content),
|
Self::TermItem(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Equation(v) => v.eval(vm).map(Value::Content),
|
Self::Equation(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Math(v) => v.eval(vm).map(Value::Content),
|
Self::Math(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::MathText(v) => v.eval(vm).map(Value::Content),
|
Self::MathText(v) => v.eval(vm).map(Value::Content),
|
||||||
@ -116,8 +116,8 @@ impl Eval for ast::Expr<'_> {
|
|||||||
Self::Float(v) => v.eval(vm),
|
Self::Float(v) => v.eval(vm),
|
||||||
Self::Numeric(v) => v.eval(vm),
|
Self::Numeric(v) => v.eval(vm),
|
||||||
Self::Str(v) => v.eval(vm),
|
Self::Str(v) => v.eval(vm),
|
||||||
Self::Code(v) => v.eval(vm),
|
Self::CodeBlock(v) => v.eval(vm),
|
||||||
Self::Content(v) => v.eval(vm).map(Value::Content),
|
Self::ContentBlock(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Array(v) => v.eval(vm).map(Value::Array),
|
Self::Array(v) => v.eval(vm).map(Value::Array),
|
||||||
Self::Dict(v) => v.eval(vm).map(Value::Dict),
|
Self::Dict(v) => v.eval(vm).map(Value::Dict),
|
||||||
Self::Parenthesized(v) => v.eval(vm),
|
Self::Parenthesized(v) => v.eval(vm),
|
||||||
@ -126,19 +126,19 @@ impl Eval for ast::Expr<'_> {
|
|||||||
Self::Closure(v) => v.eval(vm),
|
Self::Closure(v) => v.eval(vm),
|
||||||
Self::Unary(v) => v.eval(vm),
|
Self::Unary(v) => v.eval(vm),
|
||||||
Self::Binary(v) => v.eval(vm),
|
Self::Binary(v) => v.eval(vm),
|
||||||
Self::Let(v) => v.eval(vm),
|
Self::LetBinding(v) => v.eval(vm),
|
||||||
Self::DestructAssign(v) => v.eval(vm),
|
Self::DestructAssignment(v) => v.eval(vm),
|
||||||
Self::Set(_) => bail!(forbidden("set")),
|
Self::SetRule(_) => bail!(forbidden("set")),
|
||||||
Self::Show(_) => bail!(forbidden("show")),
|
Self::ShowRule(_) => bail!(forbidden("show")),
|
||||||
Self::Contextual(v) => v.eval(vm).map(Value::Content),
|
Self::Contextual(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Conditional(v) => v.eval(vm),
|
Self::Conditional(v) => v.eval(vm),
|
||||||
Self::While(v) => v.eval(vm),
|
Self::WhileLoop(v) => v.eval(vm),
|
||||||
Self::For(v) => v.eval(vm),
|
Self::ForLoop(v) => v.eval(vm),
|
||||||
Self::Import(v) => v.eval(vm),
|
Self::ModuleImport(v) => v.eval(vm),
|
||||||
Self::Include(v) => v.eval(vm).map(Value::Content),
|
Self::ModuleInclude(v) => v.eval(vm).map(Value::Content),
|
||||||
Self::Break(v) => v.eval(vm),
|
Self::LoopBreak(v) => v.eval(vm),
|
||||||
Self::Continue(v) => v.eval(vm),
|
Self::LoopContinue(v) => v.eval(vm),
|
||||||
Self::Return(v) => v.eval(vm),
|
Self::FuncReturn(v) => v.eval(vm),
|
||||||
}?
|
}?
|
||||||
.spanned(span);
|
.spanned(span);
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ fn eval_markup<'a>(
|
|||||||
|
|
||||||
while let Some(expr) = exprs.next() {
|
while let Some(expr) = exprs.next() {
|
||||||
match expr {
|
match expr {
|
||||||
ast::Expr::Set(set) => {
|
ast::Expr::SetRule(set) => {
|
||||||
let styles = set.eval(vm)?;
|
let styles = set.eval(vm)?;
|
||||||
if vm.flow.is_some() {
|
if vm.flow.is_some() {
|
||||||
break;
|
break;
|
||||||
@ -41,7 +41,7 @@ fn eval_markup<'a>(
|
|||||||
|
|
||||||
seq.push(eval_markup(vm, exprs)?.styled_with_map(styles))
|
seq.push(eval_markup(vm, exprs)?.styled_with_map(styles))
|
||||||
}
|
}
|
||||||
ast::Expr::Show(show) => {
|
ast::Expr::ShowRule(show) => {
|
||||||
let recipe = show.eval(vm)?;
|
let recipe = show.eval(vm)?;
|
||||||
if vm.flow.is_some() {
|
if vm.flow.is_some() {
|
||||||
break;
|
break;
|
||||||
|
@ -45,7 +45,7 @@ impl Eval for ast::ShowRule<'_> {
|
|||||||
|
|
||||||
let transform = self.transform();
|
let transform = self.transform();
|
||||||
let transform = match transform {
|
let transform = match transform {
|
||||||
ast::Expr::Set(set) => Transformation::Style(set.eval(vm)?),
|
ast::Expr::SetRule(set) => Transformation::Style(set.eval(vm)?),
|
||||||
expr => expr.eval(vm)?.cast::<Transformation>().at(transform.span())?,
|
expr => expr.eval(vm)?.cast::<Transformation>().at(transform.span())?,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -83,8 +83,8 @@ fn html_document_impl(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let output = handle_list(&mut engine, &mut locator, children.iter().copied())?;
|
let output = handle_list(&mut engine, &mut locator, children.iter().copied())?;
|
||||||
|
let introspector = Introspector::html(&output);
|
||||||
let root = root_element(output, &info)?;
|
let root = root_element(output, &info)?;
|
||||||
let introspector = Introspector::html(&root);
|
|
||||||
|
|
||||||
Ok(HtmlDocument { info, root, introspector })
|
Ok(HtmlDocument { info, root, introspector })
|
||||||
}
|
}
|
||||||
@ -307,18 +307,18 @@ 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: Vec<HtmlNode>) -> SourceResult<OutputKind> {
|
||||||
let len = output.len();
|
let count = output.iter().filter(|node| !matches!(node, HtmlNode::Tag(_))).count();
|
||||||
for node in &mut output {
|
for node in &mut output {
|
||||||
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));
|
||||||
match (tag, len) {
|
match (tag, count) {
|
||||||
(tag::html, 1) => return Ok(OutputKind::Html(take())),
|
(tag::html, 1) => return Ok(OutputKind::Html(take())),
|
||||||
(tag::body, 1) => return Ok(OutputKind::Body(take())),
|
(tag::body, 1) => return Ok(OutputKind::Body(take())),
|
||||||
(tag::html | tag::body, _) => bail!(
|
(tag::html | tag::body, _) => bail!(
|
||||||
elem.span,
|
elem.span,
|
||||||
"`{}` element must be the only element in the document",
|
"`{}` element must be the only element in the document",
|
||||||
elem.tag
|
elem.tag,
|
||||||
),
|
),
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
@ -410,9 +410,17 @@ fn field_access_completions(
|
|||||||
elem.into_iter().chain(Some(ty))
|
elem.into_iter().chain(Some(ty))
|
||||||
};
|
};
|
||||||
|
|
||||||
// Autocomplete methods from the element's or type's scope.
|
// Autocomplete methods from the element's or type's scope. We only complete
|
||||||
|
// those which have a `self` parameter.
|
||||||
for (name, binding) in scopes.flat_map(|scope| scope.iter()) {
|
for (name, binding) in scopes.flat_map(|scope| scope.iter()) {
|
||||||
ctx.call_completion(name.clone(), binding.read());
|
let Ok(func) = binding.read().clone().cast::<Func>() else { continue };
|
||||||
|
if func
|
||||||
|
.params()
|
||||||
|
.and_then(|params| params.first())
|
||||||
|
.is_some_and(|param| param.name == "self")
|
||||||
|
{
|
||||||
|
ctx.call_completion(name.clone(), binding.read());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(scope) = value.scope() {
|
if let Some(scope) = value.scope() {
|
||||||
@ -509,7 +517,7 @@ fn complete_imports(ctx: &mut CompletionContext) -> bool {
|
|||||||
// "#import "path.typ": a, b, |".
|
// "#import "path.typ": a, b, |".
|
||||||
if_chain! {
|
if_chain! {
|
||||||
if let Some(prev) = ctx.leaf.prev_sibling();
|
if let Some(prev) = ctx.leaf.prev_sibling();
|
||||||
if let Some(ast::Expr::Import(import)) = prev.get().cast();
|
if let Some(ast::Expr::ModuleImport(import)) = prev.get().cast();
|
||||||
if let Some(ast::Imports::Items(items)) = import.imports();
|
if let Some(ast::Imports::Items(items)) = import.imports();
|
||||||
if let Some(source) = prev.children().find(|child| child.is::<ast::Expr>());
|
if let Some(source) = prev.children().find(|child| child.is::<ast::Expr>());
|
||||||
then {
|
then {
|
||||||
@ -528,7 +536,7 @@ fn complete_imports(ctx: &mut CompletionContext) -> bool {
|
|||||||
if let Some(grand) = parent.parent();
|
if let Some(grand) = parent.parent();
|
||||||
if grand.kind() == SyntaxKind::ImportItems;
|
if grand.kind() == SyntaxKind::ImportItems;
|
||||||
if let Some(great) = grand.parent();
|
if let Some(great) = grand.parent();
|
||||||
if let Some(ast::Expr::Import(import)) = great.get().cast();
|
if let Some(ast::Expr::ModuleImport(import)) = great.get().cast();
|
||||||
if let Some(ast::Imports::Items(items)) = import.imports();
|
if let Some(ast::Imports::Items(items)) = import.imports();
|
||||||
if let Some(source) = great.children().find(|child| child.is::<ast::Expr>());
|
if let Some(source) = great.children().find(|child| child.is::<ast::Expr>());
|
||||||
then {
|
then {
|
||||||
@ -669,10 +677,10 @@ fn complete_params(ctx: &mut CompletionContext) -> bool {
|
|||||||
if let Some(args) = parent.get().cast::<ast::Args>();
|
if let Some(args) = parent.get().cast::<ast::Args>();
|
||||||
if let Some(grand) = parent.parent();
|
if let Some(grand) = parent.parent();
|
||||||
if let Some(expr) = grand.get().cast::<ast::Expr>();
|
if let Some(expr) = grand.get().cast::<ast::Expr>();
|
||||||
let set = matches!(expr, ast::Expr::Set(_));
|
let set = matches!(expr, ast::Expr::SetRule(_));
|
||||||
if let Some(callee) = match expr {
|
if let Some(callee) = match expr {
|
||||||
ast::Expr::FuncCall(call) => Some(call.callee()),
|
ast::Expr::FuncCall(call) => Some(call.callee()),
|
||||||
ast::Expr::Set(set) => Some(set.target()),
|
ast::Expr::SetRule(set) => Some(set.target()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
then {
|
then {
|
||||||
@ -1455,7 +1463,7 @@ impl<'a> CompletionContext<'a> {
|
|||||||
let mut defined = BTreeMap::<EcoString, Option<Value>>::new();
|
let mut defined = BTreeMap::<EcoString, Option<Value>>::new();
|
||||||
named_items(self.world, self.leaf.clone(), |item| {
|
named_items(self.world, self.leaf.clone(), |item| {
|
||||||
let name = item.name();
|
let name = item.name();
|
||||||
if !name.is_empty() && item.value().as_ref().map_or(true, filter) {
|
if !name.is_empty() && item.value().as_ref().is_none_or(filter) {
|
||||||
defined.insert(name.clone(), item.value());
|
defined.insert(name.clone(), item.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1764,6 +1772,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_autocomplete_type_methods() {
|
fn test_autocomplete_type_methods() {
|
||||||
test("#\"hello\".", -1).must_include(["len", "contains"]);
|
test("#\"hello\".", -1).must_include(["len", "contains"]);
|
||||||
|
test("#table().", -1).must_exclude(["cell"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -232,7 +232,9 @@ pub fn deref_target(node: LinkedNode) -> Option<DerefTarget<'_>> {
|
|||||||
ast::Expr::FuncCall(call) => {
|
ast::Expr::FuncCall(call) => {
|
||||||
DerefTarget::Callee(expr_node.find(call.callee().span())?)
|
DerefTarget::Callee(expr_node.find(call.callee().span())?)
|
||||||
}
|
}
|
||||||
ast::Expr::Set(set) => DerefTarget::Callee(expr_node.find(set.target().span())?),
|
ast::Expr::SetRule(set) => {
|
||||||
|
DerefTarget::Callee(expr_node.find(set.target().span())?)
|
||||||
|
}
|
||||||
ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::FieldAccess(_) => {
|
ast::Expr::Ident(_) | ast::Expr::MathIdent(_) | ast::Expr::FieldAccess(_) => {
|
||||||
DerefTarget::VarAccess(expr_node)
|
DerefTarget::VarAccess(expr_node)
|
||||||
}
|
}
|
||||||
|
@ -201,7 +201,7 @@ fn named_param_tooltip(world: &dyn IdeWorld, leaf: &LinkedNode) -> Option<Toolti
|
|||||||
if let Some(expr) = grand_grand.cast::<ast::Expr>();
|
if let Some(expr) = grand_grand.cast::<ast::Expr>();
|
||||||
if let Some(ast::Expr::Ident(callee)) = match expr {
|
if let Some(ast::Expr::Ident(callee)) = match expr {
|
||||||
ast::Expr::FuncCall(call) => Some(call.callee()),
|
ast::Expr::FuncCall(call) => Some(call.callee()),
|
||||||
ast::Expr::Set(set) => Some(set.target()),
|
ast::Expr::SetRule(set) => Some(set.target()),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1377,7 +1377,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
.footer
|
.footer
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.and_then(Repeatable::as_repeated)
|
.and_then(Repeatable::as_repeated)
|
||||||
.map_or(true, |footer| footer.start != header.end)
|
.is_none_or(|footer| footer.start != header.end)
|
||||||
&& self.lrows.last().is_some_and(|row| row.index() < header.end)
|
&& self.lrows.last().is_some_and(|row| row.index() < header.end)
|
||||||
&& !in_last_with_offset(
|
&& !in_last_with_offset(
|
||||||
self.regions,
|
self.regions,
|
||||||
@ -1446,7 +1446,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
.iter_mut()
|
.iter_mut()
|
||||||
.filter(|rowspan| (rowspan.y..rowspan.y + rowspan.rowspan).contains(&y))
|
.filter(|rowspan| (rowspan.y..rowspan.y + rowspan.rowspan).contains(&y))
|
||||||
.filter(|rowspan| {
|
.filter(|rowspan| {
|
||||||
rowspan.max_resolved_row.map_or(true, |max_row| y > max_row)
|
rowspan.max_resolved_row.is_none_or(|max_row| y > max_row)
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
// If the first region wasn't defined yet, it will have the
|
// If the first region wasn't defined yet, it will have the
|
||||||
@ -1494,7 +1494,7 @@ impl<'a> GridLayouter<'a> {
|
|||||||
// laid out at the first frame of the row).
|
// laid out at the first frame of the row).
|
||||||
// Any rowspans ending before this row are laid out even
|
// Any rowspans ending before this row are laid out even
|
||||||
// on this row's first frame.
|
// on this row's first frame.
|
||||||
if laid_out_footer_start.map_or(true, |footer_start| {
|
if laid_out_footer_start.is_none_or(|footer_start| {
|
||||||
// If this is a footer row, then only lay out this rowspan
|
// If this is a footer row, then only lay out this rowspan
|
||||||
// if the rowspan is contained within the footer.
|
// if the rowspan is contained within the footer.
|
||||||
y < footer_start || rowspan.y >= footer_start
|
y < footer_start || rowspan.y >= footer_start
|
||||||
@ -1580,5 +1580,5 @@ pub(super) fn points(
|
|||||||
/// our case, headers).
|
/// our case, headers).
|
||||||
pub(super) fn in_last_with_offset(regions: Regions<'_>, offset: Abs) -> bool {
|
pub(super) fn in_last_with_offset(regions: Regions<'_>, offset: Abs) -> bool {
|
||||||
regions.backlog.is_empty()
|
regions.backlog.is_empty()
|
||||||
&& regions.last.map_or(true, |height| regions.size.y + offset == height)
|
&& regions.last.is_none_or(|height| regions.size.y + offset == height)
|
||||||
}
|
}
|
||||||
|
@ -463,7 +463,7 @@ pub fn hline_stroke_at_column(
|
|||||||
// region, we have the last index, and (as a failsafe) we don't have the
|
// region, we have the last index, and (as a failsafe) we don't have the
|
||||||
// last row of cells above us.
|
// last row of cells above us.
|
||||||
let use_bottom_border_stroke = !in_last_region
|
let use_bottom_border_stroke = !in_last_region
|
||||||
&& local_top_y.map_or(true, |top_y| top_y + 1 != grid.rows.len())
|
&& local_top_y.is_none_or(|top_y| top_y + 1 != grid.rows.len())
|
||||||
&& y == grid.rows.len();
|
&& y == grid.rows.len();
|
||||||
let bottom_y =
|
let bottom_y =
|
||||||
if use_bottom_border_stroke { grid.rows.len().saturating_sub(1) } else { y };
|
if use_bottom_border_stroke { grid.rows.len().saturating_sub(1) } else { y };
|
||||||
|
@ -588,7 +588,7 @@ impl GridLayouter<'_> {
|
|||||||
measurement_data: &CellMeasurementData<'_>,
|
measurement_data: &CellMeasurementData<'_>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
if sizes.len() <= 1
|
if sizes.len() <= 1
|
||||||
&& sizes.first().map_or(true, |&first_frame_size| {
|
&& sizes.first().is_none_or(|&first_frame_size| {
|
||||||
first_frame_size <= measurement_data.height_in_this_region
|
first_frame_size <= measurement_data.height_in_this_region
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
|
@ -154,7 +154,7 @@ pub fn line<'a>(
|
|||||||
let mut items = collect_items(engine, p, range, trim);
|
let mut items = collect_items(engine, p, range, trim);
|
||||||
|
|
||||||
// Add a hyphen at the line start, if a previous dash should be repeated.
|
// Add a hyphen at the line start, if a previous dash should be repeated.
|
||||||
if pred.map_or(false, |pred| should_repeat_hyphen(pred, full)) {
|
if pred.is_some_and(|pred| should_repeat_hyphen(pred, full)) {
|
||||||
if let Some(shaped) = items.first_text_mut() {
|
if let Some(shaped) = items.first_text_mut() {
|
||||||
shaped.prepend_hyphen(engine, p.config.fallback);
|
shaped.prepend_hyphen(engine, p.config.fallback);
|
||||||
}
|
}
|
||||||
@ -406,7 +406,7 @@ fn should_repeat_hyphen(pred_line: &Line, text: &str) -> bool {
|
|||||||
//
|
//
|
||||||
// See § 4.1.1.1.2.e on the "Ortografía de la lengua española"
|
// See § 4.1.1.1.2.e on the "Ortografía de la lengua española"
|
||||||
// https://www.rae.es/ortografía/como-signo-de-división-de-palabras-a-final-de-línea
|
// https://www.rae.es/ortografía/como-signo-de-división-de-palabras-a-final-de-línea
|
||||||
Lang::SPANISH => text.chars().next().map_or(false, |c| !c.is_uppercase()),
|
Lang::SPANISH => text.chars().next().is_some_and(|c| !c.is_uppercase()),
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
@ -290,7 +290,7 @@ fn linebreak_optimized_bounded<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If this attempt is better than what we had before, take it!
|
// If this attempt is better than what we had before, take it!
|
||||||
if best.as_ref().map_or(true, |best| best.total >= total) {
|
if best.as_ref().is_none_or(|best| best.total >= total) {
|
||||||
best = Some(Entry { pred: pred_index, total, line: attempt, end });
|
best = Some(Entry { pred: pred_index, total, line: attempt, end });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -423,7 +423,7 @@ fn linebreak_optimized_approximate(
|
|||||||
let total = pred.total + line_cost;
|
let total = pred.total + line_cost;
|
||||||
|
|
||||||
// If this attempt is better than what we had before, take it!
|
// If this attempt is better than what we had before, take it!
|
||||||
if best.as_ref().map_or(true, |best| best.total >= total) {
|
if best.as_ref().is_none_or(|best| best.total >= total) {
|
||||||
best = Some(Entry {
|
best = Some(Entry {
|
||||||
pred: pred_index,
|
pred: pred_index,
|
||||||
total,
|
total,
|
||||||
|
@ -465,7 +465,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
None
|
None
|
||||||
};
|
};
|
||||||
let mut chain = families(self.styles)
|
let mut chain = families(self.styles)
|
||||||
.filter(|family| family.covers().map_or(true, |c| c.is_match("-")))
|
.filter(|family| family.covers().is_none_or(|c| c.is_match("-")))
|
||||||
.map(|family| book.select(family.as_str(), self.variant))
|
.map(|family| book.select(family.as_str(), self.variant))
|
||||||
.chain(fallback_func.iter().map(|f| f()))
|
.chain(fallback_func.iter().map(|f| f()))
|
||||||
.flatten();
|
.flatten();
|
||||||
@ -570,7 +570,7 @@ impl<'a> ShapedText<'a> {
|
|||||||
// for the next line.
|
// for the next line.
|
||||||
let dec = if ltr { usize::checked_sub } else { usize::checked_add };
|
let dec = if ltr { usize::checked_sub } else { usize::checked_add };
|
||||||
while let Some(next) = dec(idx, 1) {
|
while let Some(next) = dec(idx, 1) {
|
||||||
if self.glyphs.get(next).map_or(true, |g| g.range.start != text_index) {
|
if self.glyphs.get(next).is_none_or(|g| g.range.start != text_index) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
idx = next;
|
idx = next;
|
||||||
@ -812,7 +812,7 @@ fn shape_segment<'a>(
|
|||||||
.nth(1)
|
.nth(1)
|
||||||
.map(|(i, _)| offset + i)
|
.map(|(i, _)| offset + i)
|
||||||
.unwrap_or(text.len());
|
.unwrap_or(text.len());
|
||||||
covers.map_or(true, |cov| cov.is_match(&text[offset..end]))
|
covers.is_none_or(|cov| cov.is_match(&text[offset..end]))
|
||||||
};
|
};
|
||||||
|
|
||||||
// Collect the shaped glyphs, doing fallback and shaping parts again with
|
// Collect the shaped glyphs, doing fallback and shaping parts again with
|
||||||
|
@ -34,7 +34,7 @@ pub fn layout_accent(
|
|||||||
|
|
||||||
// Try to replace accent glyph with flattened variant.
|
// Try to replace accent glyph with flattened variant.
|
||||||
let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height);
|
let flattened_base_height = scaled!(ctx, styles, flattened_accent_base_height);
|
||||||
if base.height() > flattened_base_height {
|
if base.ascent() > flattened_base_height {
|
||||||
glyph.make_flattened_accent_form(ctx);
|
glyph.make_flattened_accent_form(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ pub fn layout_accent(
|
|||||||
// minus the accent base height. Only if the base is very small, we need
|
// minus the accent base height. Only if the base is very small, we need
|
||||||
// a larger gap so that the accent doesn't move too low.
|
// a larger gap so that the accent doesn't move too low.
|
||||||
let accent_base_height = scaled!(ctx, styles, accent_base_height);
|
let accent_base_height = scaled!(ctx, styles, accent_base_height);
|
||||||
let gap = -accent.descent() - base.height().min(accent_base_height);
|
let gap = -accent.descent() - base.ascent().min(accent_base_height);
|
||||||
let size = Size::new(base.width(), accent.height() + gap + base.height());
|
let size = Size::new(base.width(), accent.height() + gap + base.height());
|
||||||
let accent_pos = Point::with_x(base_attach - accent_attach);
|
let accent_pos = Point::with_x(base_attach - accent_attach);
|
||||||
let base_pos = Point::with_y(accent.height() + gap);
|
let base_pos = Point::with_y(accent.height() + gap);
|
||||||
|
@ -284,6 +284,7 @@ impl<'a> CurveBuilder<'a> {
|
|||||||
self.last_point = point;
|
self.last_point = point;
|
||||||
self.last_control_from = point;
|
self.last_control_from = point;
|
||||||
self.is_started = true;
|
self.is_started = true;
|
||||||
|
self.is_empty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add a line segment.
|
/// Add a line segment.
|
||||||
|
@ -61,6 +61,7 @@ ttf-parser = { workspace = true }
|
|||||||
two-face = { workspace = true }
|
two-face = { workspace = true }
|
||||||
typed-arena = { workspace = true }
|
typed-arena = { workspace = true }
|
||||||
unicode-math-class = { workspace = true }
|
unicode-math-class = { workspace = true }
|
||||||
|
unicode-normalization = { workspace = true }
|
||||||
unicode-segmentation = { workspace = true }
|
unicode-segmentation = { workspace = true }
|
||||||
unscanny = { workspace = true }
|
unscanny = { workspace = true }
|
||||||
usvg = { workspace = true }
|
usvg = { workspace = true }
|
||||||
|
@ -312,7 +312,8 @@ impl Route<'_> {
|
|||||||
if !self.within(Route::MAX_SHOW_RULE_DEPTH) {
|
if !self.within(Route::MAX_SHOW_RULE_DEPTH) {
|
||||||
bail!(
|
bail!(
|
||||||
"maximum show rule depth exceeded";
|
"maximum show rule depth exceeded";
|
||||||
hint: "check whether the show rule matches its own output"
|
hint: "maybe a show rule matches its own output";
|
||||||
|
hint: "maybe there are too deeply nested elements"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -751,7 +751,7 @@ impl Array {
|
|||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
/// #let array = (1, 2, 3, 4, 5, 6, 7, 8)
|
/// #let array = (1, 2, 3, 4, 5, 6, 7, 8)
|
||||||
/// #array.chunks(3)
|
/// #array.chunks(3) \
|
||||||
/// #array.chunks(3, exact: true)
|
/// #array.chunks(3, exact: true)
|
||||||
/// ```
|
/// ```
|
||||||
#[func]
|
#[func]
|
||||||
|
@ -437,10 +437,10 @@ impl PartialEq for Func {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PartialEq<&NativeFuncData> for Func {
|
impl PartialEq<&'static NativeFuncData> for Func {
|
||||||
fn eq(&self, other: &&NativeFuncData) -> bool {
|
fn eq(&self, other: &&'static NativeFuncData) -> bool {
|
||||||
match &self.repr {
|
match &self.repr {
|
||||||
Repr::Native(native) => native.function == other.function,
|
Repr::Native(native) => *native == Static(*other),
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,13 @@ use comemo::Tracked;
|
|||||||
use ecow::EcoString;
|
use ecow::EcoString;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use typst_syntax::{Span, Spanned};
|
use typst_syntax::{Span, Spanned};
|
||||||
|
use unicode_normalization::UnicodeNormalization;
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
|
|
||||||
use crate::diag::{bail, At, SourceResult, StrResult};
|
use crate::diag::{bail, At, SourceResult, StrResult};
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
cast, dict, func, repr, scope, ty, Array, Bytes, Context, Decimal, Dict, Func,
|
cast, dict, func, repr, scope, ty, Array, Bytes, Cast, Context, Decimal, Dict, Func,
|
||||||
IntoValue, Label, Repr, Type, Value, Version,
|
IntoValue, Label, Repr, Type, Value, Version,
|
||||||
};
|
};
|
||||||
use crate::layout::Alignment;
|
use crate::layout::Alignment;
|
||||||
@ -286,6 +287,30 @@ impl Str {
|
|||||||
Ok(c.into())
|
Ok(c.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Normalizes the string to the given Unicode normal form.
|
||||||
|
///
|
||||||
|
/// This is useful when manipulating strings containing Unicode combining
|
||||||
|
/// characters.
|
||||||
|
///
|
||||||
|
/// ```typ
|
||||||
|
/// #assert.eq("é".normalize(form: "nfd"), "e\u{0301}")
|
||||||
|
/// #assert.eq("ſ́".normalize(form: "nfkc"), "ś")
|
||||||
|
/// ```
|
||||||
|
#[func]
|
||||||
|
pub fn normalize(
|
||||||
|
&self,
|
||||||
|
#[named]
|
||||||
|
#[default(UnicodeNormalForm::Nfc)]
|
||||||
|
form: UnicodeNormalForm,
|
||||||
|
) -> Str {
|
||||||
|
match form {
|
||||||
|
UnicodeNormalForm::Nfc => self.nfc().collect(),
|
||||||
|
UnicodeNormalForm::Nfd => self.nfd().collect(),
|
||||||
|
UnicodeNormalForm::Nfkc => self.nfkc().collect(),
|
||||||
|
UnicodeNormalForm::Nfkd => self.nfkd().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the string contains the specified pattern.
|
/// Whether the string contains the specified pattern.
|
||||||
///
|
///
|
||||||
/// This method also has dedicated syntax: You can write `{"bc" in "abcd"}`
|
/// This method also has dedicated syntax: You can write `{"bc" in "abcd"}`
|
||||||
@ -788,6 +813,25 @@ cast! {
|
|||||||
v: Str => Self::Str(v),
|
v: Str => Self::Str(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A Unicode normalization form.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||||
|
pub enum UnicodeNormalForm {
|
||||||
|
/// Canonical composition where e.g. accented letters are turned into a
|
||||||
|
/// single Unicode codepoint.
|
||||||
|
#[string("nfc")]
|
||||||
|
Nfc,
|
||||||
|
/// Canonical decomposition where e.g. accented letters are split into a
|
||||||
|
/// separate base and diacritic.
|
||||||
|
#[string("nfd")]
|
||||||
|
Nfd,
|
||||||
|
/// Like NFC, but using the Unicode compatibility decompositions.
|
||||||
|
#[string("nfkc")]
|
||||||
|
Nfkc,
|
||||||
|
/// Like NFD, but using the Unicode compatibility decompositions.
|
||||||
|
#[string("nfkd")]
|
||||||
|
Nfkd,
|
||||||
|
}
|
||||||
|
|
||||||
/// Convert an item of std's `match_indices` to a dictionary.
|
/// Convert an item of std's `match_indices` to a dictionary.
|
||||||
fn match_to_dict((start, text): (usize, &str)) -> Dict {
|
fn match_to_dict((start, text): (usize, &str)) -> Dict {
|
||||||
dict! {
|
dict! {
|
||||||
|
@ -471,7 +471,8 @@ impl Debug for Recipe {
|
|||||||
selector.fmt(f)?;
|
selector.fmt(f)?;
|
||||||
f.write_str(", ")?;
|
f.write_str(", ")?;
|
||||||
}
|
}
|
||||||
self.transform.fmt(f)
|
self.transform.fmt(f)?;
|
||||||
|
f.write_str(")")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,7 @@ use crate::foundations::{
|
|||||||
/// be accessed using [field access notation]($scripting/#fields):
|
/// be accessed using [field access notation]($scripting/#fields):
|
||||||
///
|
///
|
||||||
/// - General symbols are defined in the [`sym` module]($category/symbols/sym)
|
/// - General symbols are defined in the [`sym` module]($category/symbols/sym)
|
||||||
|
/// and are accessible without the `sym.` prefix in math mode.
|
||||||
/// - Emoji are defined in the [`emoji` module]($category/symbols/emoji)
|
/// - Emoji are defined in the [`emoji` module]($category/symbols/emoji)
|
||||||
///
|
///
|
||||||
/// Moreover, you can define custom symbols with this type's constructor
|
/// Moreover, you can define custom symbols with this type's constructor
|
||||||
@ -410,7 +411,7 @@ fn find<'a>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let score = (matching, Reverse(total));
|
let score = (matching, Reverse(total));
|
||||||
if best_score.map_or(true, |b| score > b) {
|
if best_score.is_none_or(|b| score > b) {
|
||||||
best = Some(candidate.1);
|
best = Some(candidate.1);
|
||||||
best_score = Some(score);
|
best_score = Some(score);
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ use typst_utils::NonZeroExt;
|
|||||||
|
|
||||||
use crate::diag::{bail, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::foundations::{Content, Label, Repr, Selector};
|
use crate::foundations::{Content, Label, Repr, Selector};
|
||||||
use crate::html::{HtmlElement, HtmlNode};
|
use crate::html::HtmlNode;
|
||||||
use crate::introspection::{Location, Tag};
|
use crate::introspection::{Location, Tag};
|
||||||
use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
|
use crate::layout::{Frame, FrameItem, Page, Point, Position, Transform};
|
||||||
use crate::model::Numbering;
|
use crate::model::Numbering;
|
||||||
@ -55,8 +55,8 @@ impl Introspector {
|
|||||||
|
|
||||||
/// Creates an introspector for HTML.
|
/// Creates an introspector for HTML.
|
||||||
#[typst_macros::time(name = "introspect html")]
|
#[typst_macros::time(name = "introspect html")]
|
||||||
pub fn html(root: &HtmlElement) -> Self {
|
pub fn html(output: &[HtmlNode]) -> Self {
|
||||||
IntrospectorBuilder::new().build_html(root)
|
IntrospectorBuilder::new().build_html(output)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterates over all locatable elements.
|
/// Iterates over all locatable elements.
|
||||||
@ -392,9 +392,9 @@ impl IntrospectorBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Build an introspector for an HTML document.
|
/// Build an introspector for an HTML document.
|
||||||
fn build_html(mut self, root: &HtmlElement) -> Introspector {
|
fn build_html(mut self, output: &[HtmlNode]) -> Introspector {
|
||||||
let mut elems = Vec::new();
|
let mut elems = Vec::new();
|
||||||
self.discover_in_html(&mut elems, root);
|
self.discover_in_html(&mut elems, output);
|
||||||
self.finalize(elems)
|
self.finalize(elems)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,16 +434,16 @@ impl IntrospectorBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Processes the tags in the HTML element.
|
/// Processes the tags in the HTML element.
|
||||||
fn discover_in_html(&mut self, sink: &mut Vec<Pair>, elem: &HtmlElement) {
|
fn discover_in_html(&mut self, sink: &mut Vec<Pair>, nodes: &[HtmlNode]) {
|
||||||
for child in &elem.children {
|
for node in nodes {
|
||||||
match child {
|
match node {
|
||||||
HtmlNode::Tag(tag) => self.discover_in_tag(
|
HtmlNode::Tag(tag) => self.discover_in_tag(
|
||||||
sink,
|
sink,
|
||||||
tag,
|
tag,
|
||||||
Position { page: NonZeroUsize::ONE, point: Point::zero() },
|
Position { page: NonZeroUsize::ONE, point: Point::zero() },
|
||||||
),
|
),
|
||||||
HtmlNode::Text(_, _) => {}
|
HtmlNode::Text(_, _) => {}
|
||||||
HtmlNode::Element(elem) => self.discover_in_html(sink, elem),
|
HtmlNode::Element(elem) => self.discover_in_html(sink, &elem.children),
|
||||||
HtmlNode::Frame(frame) => self.discover_in_frame(
|
HtmlNode::Frame(frame) => self.discover_in_frame(
|
||||||
sink,
|
sink,
|
||||||
frame,
|
frame,
|
||||||
|
@ -50,6 +50,42 @@ impl Dir {
|
|||||||
pub const TTB: Self = Self::TTB;
|
pub const TTB: Self = Self::TTB;
|
||||||
pub const BTT: Self = Self::BTT;
|
pub const BTT: Self = Self::BTT;
|
||||||
|
|
||||||
|
/// Returns a direction from a starting point.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// direction.from(left) \
|
||||||
|
/// direction.from(right) \
|
||||||
|
/// direction.from(top) \
|
||||||
|
/// direction.from(bottom)
|
||||||
|
/// ```
|
||||||
|
#[func]
|
||||||
|
pub const fn from(side: Side) -> Dir {
|
||||||
|
match side {
|
||||||
|
Side::Left => Self::LTR,
|
||||||
|
Side::Right => Self::RTL,
|
||||||
|
Side::Top => Self::TTB,
|
||||||
|
Side::Bottom => Self::BTT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a direction from an end point.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// direction.to(left) \
|
||||||
|
/// direction.to(right) \
|
||||||
|
/// direction.to(top) \
|
||||||
|
/// direction.to(bottom)
|
||||||
|
/// ```
|
||||||
|
#[func]
|
||||||
|
pub const fn to(side: Side) -> Dir {
|
||||||
|
match side {
|
||||||
|
Side::Right => Self::LTR,
|
||||||
|
Side::Left => Self::RTL,
|
||||||
|
Side::Bottom => Self::TTB,
|
||||||
|
Side::Top => Self::BTT,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The axis this direction belongs to, either `{"horizontal"}` or
|
/// The axis this direction belongs to, either `{"horizontal"}` or
|
||||||
/// `{"vertical"}`.
|
/// `{"vertical"}`.
|
||||||
///
|
///
|
||||||
@ -65,6 +101,22 @@ impl Dir {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The corresponding sign, for use in calculations.
|
||||||
|
///
|
||||||
|
/// ```example
|
||||||
|
/// #ltr.sign() \
|
||||||
|
/// #rtl.sign() \
|
||||||
|
/// #ttb.sign() \
|
||||||
|
/// #btt.sign()
|
||||||
|
/// ```
|
||||||
|
#[func]
|
||||||
|
pub const fn sign(self) -> i64 {
|
||||||
|
match self {
|
||||||
|
Self::LTR | Self::TTB => 1,
|
||||||
|
Self::RTL | Self::BTT => -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The start point of this direction, as an alignment.
|
/// The start point of this direction, as an alignment.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
|
@ -1387,7 +1387,7 @@ impl<'a> CellGrid<'a> {
|
|||||||
// Include the gutter right before the footer, unless there is
|
// Include the gutter right before the footer, unless there is
|
||||||
// none, or the gutter is already included in the header (no
|
// none, or the gutter is already included in the header (no
|
||||||
// rows between the header and the footer).
|
// rows between the header and the footer).
|
||||||
if header_end.map_or(true, |header_end| header_end != footer.start) {
|
if header_end != Some(footer.start) {
|
||||||
footer.start = footer.start.saturating_sub(1);
|
footer.start = footer.start.saturating_sub(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1526,11 +1526,7 @@ impl<'a> CellGrid<'a> {
|
|||||||
self.entry(x, y).map(|entry| match entry {
|
self.entry(x, y).map(|entry| match entry {
|
||||||
Entry::Cell(_) => Axes::new(x, y),
|
Entry::Cell(_) => Axes::new(x, y),
|
||||||
Entry::Merged { parent } => {
|
Entry::Merged { parent } => {
|
||||||
let c = if self.has_gutter {
|
let c = self.non_gutter_column_count();
|
||||||
1 + self.cols.len() / 2
|
|
||||||
} else {
|
|
||||||
self.cols.len()
|
|
||||||
};
|
|
||||||
let factor = if self.has_gutter { 2 } else { 1 };
|
let factor = if self.has_gutter { 2 } else { 1 };
|
||||||
Axes::new(factor * (*parent % c), factor * (*parent / c))
|
Axes::new(factor * (*parent % c), factor * (*parent / c))
|
||||||
}
|
}
|
||||||
@ -1602,6 +1598,21 @@ impl<'a> CellGrid<'a> {
|
|||||||
cell.rowspan.get()
|
cell.rowspan.get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn non_gutter_column_count(&self) -> usize {
|
||||||
|
if self.has_gutter {
|
||||||
|
// Calculation: With gutters, we have
|
||||||
|
// 'cols = 2 * (non-gutter cols) - 1', since there is a gutter
|
||||||
|
// column between each regular column. Therefore,
|
||||||
|
// 'floor(cols / 2)' will be equal to
|
||||||
|
// 'floor(non-gutter cols - 1/2) = non-gutter-cols - 1',
|
||||||
|
// so 'non-gutter cols = 1 + floor(cols / 2)'.
|
||||||
|
1 + self.cols.len() / 2
|
||||||
|
} else {
|
||||||
|
self.cols.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Given a cell's requested x and y, the vector with the resolved cell
|
/// Given a cell's requested x and y, the vector with the resolved cell
|
||||||
|
@ -34,14 +34,14 @@ use crate::loading::{DataSource, Load, Readable};
|
|||||||
/// let author = find-child(elem, "author")
|
/// let author = find-child(elem, "author")
|
||||||
/// let pars = find-child(elem, "content")
|
/// let pars = find-child(elem, "content")
|
||||||
///
|
///
|
||||||
/// heading(title.children.first())
|
/// [= #title.children.first()]
|
||||||
/// text(10pt, weight: "medium")[
|
/// text(10pt, weight: "medium")[
|
||||||
/// Published by
|
/// Published by
|
||||||
/// #author.children.first()
|
/// #author.children.first()
|
||||||
/// ]
|
/// ]
|
||||||
///
|
///
|
||||||
/// for p in pars.children {
|
/// for p in pars.children {
|
||||||
/// if (type(p) == "dictionary") {
|
/// if type(p) == dictionary {
|
||||||
/// parbreak()
|
/// parbreak()
|
||||||
/// p.children.first()
|
/// p.children.first()
|
||||||
/// }
|
/// }
|
||||||
@ -50,7 +50,7 @@ use crate::loading::{DataSource, Load, Readable};
|
|||||||
///
|
///
|
||||||
/// #let data = xml("example.xml")
|
/// #let data = xml("example.xml")
|
||||||
/// #for elem in data.first().children {
|
/// #for elem in data.first().children {
|
||||||
/// if (type(elem) == "dictionary") {
|
/// if type(elem) == dictionary {
|
||||||
/// article(elem)
|
/// article(elem)
|
||||||
/// }
|
/// }
|
||||||
/// }
|
/// }
|
||||||
|
@ -161,7 +161,7 @@ impl Show for Packed<QuoteElem> {
|
|||||||
let block = self.block(styles);
|
let block = self.block(styles);
|
||||||
let html = TargetElem::target_in(styles).is_html();
|
let html = TargetElem::target_in(styles).is_html();
|
||||||
|
|
||||||
if self.quotes(styles) == Smart::Custom(true) || !block {
|
if self.quotes(styles).unwrap_or(!block) {
|
||||||
let quotes = SmartQuotes::get(
|
let quotes = SmartQuotes::get(
|
||||||
SmartQuoteElem::quotes_in(styles),
|
SmartQuoteElem::quotes_in(styles),
|
||||||
TextElem::lang_in(styles),
|
TextElem::lang_in(styles),
|
||||||
|
@ -282,7 +282,7 @@ fn show_cell_html(tag: HtmlTag, cell: &Cell, styles: StyleChain) -> Content {
|
|||||||
|
|
||||||
fn show_cellgrid_html(grid: CellGrid, styles: StyleChain) -> Content {
|
fn show_cellgrid_html(grid: CellGrid, styles: StyleChain) -> Content {
|
||||||
let elem = |tag, body| HtmlElem::new(tag).with_body(Some(body)).pack();
|
let elem = |tag, body| HtmlElem::new(tag).with_body(Some(body)).pack();
|
||||||
let mut rows: Vec<_> = grid.entries.chunks(grid.cols.len()).collect();
|
let mut rows: Vec<_> = grid.entries.chunks(grid.non_gutter_column_count()).collect();
|
||||||
|
|
||||||
let tr = |tag, row: &[Entry]| {
|
let tr = |tag, row: &[Entry]| {
|
||||||
let row = row
|
let row = row
|
||||||
|
@ -160,7 +160,7 @@ impl FontBook {
|
|||||||
current.variant.weight.distance(variant.weight),
|
current.variant.weight.distance(variant.weight),
|
||||||
);
|
);
|
||||||
|
|
||||||
if best_key.map_or(true, |b| key < b) {
|
if best_key.is_none_or(|b| key < b) {
|
||||||
best = Some(id);
|
best = Some(id);
|
||||||
best_key = Some(key);
|
best_key = Some(key);
|
||||||
}
|
}
|
||||||
|
@ -159,7 +159,7 @@ fn is_shapable(engine: &Engine, text: &str, styles: StyleChain) -> bool {
|
|||||||
{
|
{
|
||||||
let covers = family.covers();
|
let covers = family.covers();
|
||||||
return text.chars().all(|c| {
|
return text.chars().all(|c| {
|
||||||
covers.map_or(true, |cov| cov.is_match(c.encode_utf8(&mut [0; 4])))
|
covers.is_none_or(|cov| cov.is_match(c.encode_utf8(&mut [0; 4])))
|
||||||
&& font.ttf().glyph_index(c).is_some()
|
&& font.ttf().glyph_index(c).is_some()
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@ static TO_SRGB: LazyLock<qcms::Transform> = LazyLock::new(|| {
|
|||||||
///
|
///
|
||||||
/// # Predefined color maps
|
/// # Predefined color maps
|
||||||
/// Typst also includes a number of preset color maps that can be used for
|
/// Typst also includes a number of preset color maps that can be used for
|
||||||
/// [gradients]($gradient.linear). These are simply arrays of colors defined in
|
/// [gradients]($gradient/#stops). These are simply arrays of colors defined in
|
||||||
/// the module `color.map`.
|
/// the module `color.map`.
|
||||||
///
|
///
|
||||||
/// ```example
|
/// ```example
|
||||||
|
@ -70,6 +70,9 @@ use crate::visualize::{Color, ColorSpace, WeightedColor};
|
|||||||
/// the offsets when defining a gradient. In this case, Typst will space all
|
/// the offsets when defining a gradient. In this case, Typst will space all
|
||||||
/// stops evenly.
|
/// stops evenly.
|
||||||
///
|
///
|
||||||
|
/// Typst predefines color maps that you can use as stops. See the
|
||||||
|
/// [`color`]($color/#predefined-color-maps) documentation for more details.
|
||||||
|
///
|
||||||
/// # Relativeness
|
/// # Relativeness
|
||||||
/// The location of the `{0%}` and `{100%}` stops depends on the dimensions
|
/// The location of the `{0%}` and `{100%}` stops depends on the dimensions
|
||||||
/// of a container. This container can either be the shape that it is being
|
/// of a container. This container can either be the shape that it is being
|
||||||
@ -157,10 +160,6 @@ use crate::visualize::{Color, ColorSpace, WeightedColor};
|
|||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Presets
|
|
||||||
/// Typst predefines color maps that you can use with your gradients. See the
|
|
||||||
/// [`color`]($color/#predefined-color-maps) documentation for more details.
|
|
||||||
///
|
|
||||||
/// # Note on file sizes
|
/// # Note on file sizes
|
||||||
///
|
///
|
||||||
/// Gradients can be quite large, especially if they have many stops. This is
|
/// Gradients can be quite large, especially if they have many stops. This is
|
||||||
@ -288,7 +287,7 @@ impl Gradient {
|
|||||||
/// )),
|
/// )),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[func]
|
#[func(title = "Radial Gradient")]
|
||||||
fn radial(
|
fn radial(
|
||||||
span: Span,
|
span: Span,
|
||||||
/// The color [stops](#stops) of the gradient.
|
/// The color [stops](#stops) of the gradient.
|
||||||
@ -402,7 +401,7 @@ impl Gradient {
|
|||||||
/// )),
|
/// )),
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
#[func]
|
#[func(title = "Conic Gradient")]
|
||||||
pub fn conic(
|
pub fn conic(
|
||||||
span: Span,
|
span: Span,
|
||||||
/// The color [stops](#stops) of the gradient.
|
/// The color [stops](#stops) of the gradient.
|
||||||
|
@ -70,7 +70,7 @@ pub(crate) fn write_outline(
|
|||||||
// (not exceeding whichever is the most restrictive depth limit
|
// (not exceeding whichever is the most restrictive depth limit
|
||||||
// of those two).
|
// of those two).
|
||||||
while children.last().is_some_and(|last| {
|
while children.last().is_some_and(|last| {
|
||||||
last_skipped_level.map_or(true, |l| last.level < l)
|
last_skipped_level.is_none_or(|l| last.level < l)
|
||||||
&& last.level < leaf.level
|
&& last.level < leaf.level
|
||||||
}) {
|
}) {
|
||||||
children = &mut children.last_mut().unwrap().children;
|
children = &mut children.last_mut().unwrap().children;
|
||||||
@ -83,7 +83,7 @@ pub(crate) fn write_outline(
|
|||||||
// needed, following the usual rules listed above.
|
// needed, following the usual rules listed above.
|
||||||
last_skipped_level = None;
|
last_skipped_level = None;
|
||||||
children.push(leaf);
|
children.push(leaf);
|
||||||
} else if last_skipped_level.map_or(true, |l| leaf.level < l) {
|
} else if last_skipped_level.is_none_or(|l| leaf.level < l) {
|
||||||
// Only the topmost / lowest-level skipped heading matters when you
|
// Only the topmost / lowest-level skipped heading matters when you
|
||||||
// have consecutive skipped headings (since none of them are being
|
// have consecutive skipped headings (since none of them are being
|
||||||
// added to the bookmark tree), hence the condition above.
|
// added to the bookmark tree), hence the condition above.
|
||||||
|
@ -326,7 +326,10 @@ fn visit_math_rules<'a>(
|
|||||||
// Symbols in non-math content transparently convert to `TextElem` so we
|
// Symbols in non-math content transparently convert to `TextElem` so we
|
||||||
// don't have to handle them in non-math layout.
|
// don't have to handle them in non-math layout.
|
||||||
if let Some(elem) = content.to_packed::<SymbolElem>() {
|
if let Some(elem) = content.to_packed::<SymbolElem>() {
|
||||||
let text = TextElem::packed(elem.text).spanned(elem.span());
|
let mut text = TextElem::packed(elem.text).spanned(elem.span());
|
||||||
|
if let Some(label) = elem.label() {
|
||||||
|
text.set_label(label);
|
||||||
|
}
|
||||||
visit(s, s.store(text), styles)?;
|
visit(s, s.store(text), styles)?;
|
||||||
return Ok(true);
|
return Ok(true);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
||||||
|
|
||||||
use crate::ast::AstNode;
|
|
||||||
use crate::{FileId, Span, SyntaxKind};
|
use crate::{FileId, Span, SyntaxKind};
|
||||||
|
|
||||||
/// A node in the untyped syntax tree.
|
/// A node in the untyped syntax tree.
|
||||||
@ -119,26 +118,6 @@ impl SyntaxNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Whether the node can be cast to the given AST node.
|
|
||||||
pub fn is<'a, T: AstNode<'a>>(&'a self) -> bool {
|
|
||||||
self.cast::<T>().is_some()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to convert the node to a typed AST node.
|
|
||||||
pub fn cast<'a, T: AstNode<'a>>(&'a self) -> Option<T> {
|
|
||||||
T::from_untyped(self)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cast the first child that can cast to the AST type `T`.
|
|
||||||
pub fn cast_first_match<'a, T: AstNode<'a>>(&'a self) -> Option<T> {
|
|
||||||
self.children().find_map(Self::cast)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Cast the last child that can cast to the AST type `T`.
|
|
||||||
pub fn cast_last_match<'a, T: AstNode<'a>>(&'a self) -> Option<T> {
|
|
||||||
self.children().rev().find_map(Self::cast)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Whether the node or its children contain an error.
|
/// Whether the node or its children contain an error.
|
||||||
pub fn erroneous(&self) -> bool {
|
pub fn erroneous(&self) -> bool {
|
||||||
match &self.0 {
|
match &self.0 {
|
||||||
@ -753,7 +732,7 @@ impl<'a> LinkedNode<'a> {
|
|||||||
// sibling's span number is larger than the target span's number.
|
// sibling's span number is larger than the target span's number.
|
||||||
if children
|
if children
|
||||||
.peek()
|
.peek()
|
||||||
.map_or(true, |next| next.span().number() > span.number())
|
.is_none_or(|next| next.span().number() > span.number())
|
||||||
{
|
{
|
||||||
if let Some(found) = child.find(span) {
|
if let Some(found) = child.find(span) {
|
||||||
return Some(found);
|
return Some(found);
|
||||||
|
@ -327,8 +327,8 @@ impl PackageVersion {
|
|||||||
/// missing in the bound are ignored.
|
/// missing in the bound are ignored.
|
||||||
pub fn matches_eq(&self, bound: &VersionBound) -> bool {
|
pub fn matches_eq(&self, bound: &VersionBound) -> bool {
|
||||||
self.major == bound.major
|
self.major == bound.major
|
||||||
&& bound.minor.map_or(true, |minor| self.minor == minor)
|
&& bound.minor.is_none_or(|minor| self.minor == minor)
|
||||||
&& bound.patch.map_or(true, |patch| self.patch == patch)
|
&& bound.patch.is_none_or(|patch| self.patch == patch)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a `>` match with the given version bound. The match only
|
/// Performs a `>` match with the given version bound. The match only
|
||||||
|
@ -271,10 +271,11 @@ fn math_expr_prec(p: &mut Parser, min_prec: usize, stop: SyntaxKind) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::MathShorthand => {
|
SyntaxKind::Text | SyntaxKind::MathText | SyntaxKind::MathShorthand => {
|
||||||
continuable = matches!(
|
continuable = !p.at(SyntaxKind::MathShorthand)
|
||||||
math_class(p.current_text()),
|
&& matches!(
|
||||||
None | Some(MathClass::Alphabetic)
|
math_class(p.current_text()),
|
||||||
);
|
None | Some(MathClass::Alphabetic)
|
||||||
|
);
|
||||||
if !maybe_delimited(p) {
|
if !maybe_delimited(p) {
|
||||||
p.eat();
|
p.eat();
|
||||||
}
|
}
|
||||||
|
@ -360,6 +360,21 @@ pub fn default_math_class(c: char) -> Option<MathClass> {
|
|||||||
// https://github.com/typst/typst/pull/5714
|
// https://github.com/typst/typst/pull/5714
|
||||||
'\u{22A5}' => Some(MathClass::Normal),
|
'\u{22A5}' => Some(MathClass::Normal),
|
||||||
|
|
||||||
|
// Used as a binary connector in linear logic, where it is referred to
|
||||||
|
// as "par".
|
||||||
|
// https://github.com/typst/typst/issues/5764
|
||||||
|
'⅋' => Some(MathClass::Binary),
|
||||||
|
|
||||||
|
// Those overrides should become the default in the next revision of
|
||||||
|
// MathClass.txt.
|
||||||
|
// https://github.com/typst/typst/issues/5764#issuecomment-2632435247
|
||||||
|
'⎰' | '⟅' => Some(MathClass::Opening),
|
||||||
|
'⎱' | '⟆' => Some(MathClass::Closing),
|
||||||
|
|
||||||
|
// Both ∨ and ⟑ are classified as Binary.
|
||||||
|
// https://github.com/typst/typst/issues/5764
|
||||||
|
'⟇' => Some(MathClass::Binary),
|
||||||
|
|
||||||
c => unicode_math_class::class(c),
|
c => unicode_math_class::class(c),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ impl Scalar {
|
|||||||
///
|
///
|
||||||
/// If the value is NaN, then it is set to `0.0` in the result.
|
/// If the value is NaN, then it is set to `0.0` in the result.
|
||||||
pub const fn new(x: f64) -> Self {
|
pub const fn new(x: f64) -> Self {
|
||||||
Self(if is_nan(x) { 0.0 } else { x })
|
Self(if x.is_nan() { 0.0 } else { x })
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the value of this [`Scalar`].
|
/// Gets the value of this [`Scalar`].
|
||||||
@ -37,17 +37,6 @@ impl Scalar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// We have to detect NaNs this way since `f64::is_nan` isn’t const
|
|
||||||
// on stable yet:
|
|
||||||
// ([tracking issue](https://github.com/rust-lang/rust/issues/57241))
|
|
||||||
#[allow(clippy::unusual_byte_groupings)]
|
|
||||||
const fn is_nan(x: f64) -> bool {
|
|
||||||
// Safety: all bit patterns are valid for u64, and f64 has no padding bits.
|
|
||||||
// We cannot use `f64::to_bits` because it is not const.
|
|
||||||
let x_bits = unsafe { std::mem::transmute::<f64, u64>(x) };
|
|
||||||
(x_bits << 1 >> (64 - 12 + 1)) == 0b0_111_1111_1111 && (x_bits << 12) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Numeric for Scalar {
|
impl Numeric for Scalar {
|
||||||
fn zero() -> Self {
|
fn zero() -> Self {
|
||||||
Self(0.0)
|
Self(0.0)
|
||||||
|
@ -294,20 +294,20 @@ feature flag.
|
|||||||
`errorbar.diamond.stroked`, `errorbar.diamond.filled`,
|
`errorbar.diamond.stroked`, `errorbar.diamond.filled`,
|
||||||
`errorbar.circle.stroked`, `errorbar.circle.filled`
|
`errorbar.circle.stroked`, `errorbar.circle.filled`
|
||||||
- `numero`
|
- `numero`
|
||||||
- `Omega.inv`
|
- Renamed **(Breaking change)**
|
||||||
- Renamed
|
|
||||||
- `ohm.inv` to `Omega.inv`
|
- `ohm.inv` to `Omega.inv`
|
||||||
- Changed codepoint
|
- Changed codepoint **(Breaking change)**
|
||||||
- `angle.l.double` from `《` to `⟪`
|
- `angle.l.double` from `《` to `⟪`
|
||||||
- `angle.r.double` from `》` to `⟫`
|
- `angle.r.double` from `》` to `⟫`
|
||||||
- `angstrom` from U+212B (`Å`) to U+00C5 (`Å`)
|
- `angstrom` from U+212B (`Å`) to U+00C5 (`Å`)
|
||||||
- Deprecated
|
- Deprecated
|
||||||
- `sect` and all its variants in favor of `inter`
|
- `sect` and all its variants in favor of `inter`
|
||||||
- `integral.sect` in favor of `integral.inter`
|
- `integral.sect` in favor of `integral.inter`
|
||||||
- Removed
|
- Removed **(Breaking change)**
|
||||||
- `degree.c` in favor of `°C` (`[$upright(°C)$]` or `[$upright(degree C)$]` in math)
|
- `degree.c` in favor of `°C` (`[$upright(°C)$]` or `[$upright(degree C)$]` in math)
|
||||||
- `degree.f` in favor of `°F` (`[$upright(°F)$]` or `[$upright(degree F)$]` in math)
|
- `degree.f` in favor of `°F` (`[$upright(°F)$]` or `[$upright(degree F)$]` in math)
|
||||||
- `kelvin` in favor of just K (`[$upright(K)$]` in math)
|
- `kelvin` in favor of just K (`[$upright(K)$]` in math)
|
||||||
|
- `ohm` in favor of `Omega`
|
||||||
|
|
||||||
## Deprecations
|
## Deprecations
|
||||||
- The [`path`] function in favor of the [`curve`] function
|
- The [`path`] function in favor of the [`curve`] function
|
||||||
|
29
docs/changelog/0.13.1.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
---
|
||||||
|
title: 0.13.1
|
||||||
|
description: Changes in Typst 0.13.1
|
||||||
|
---
|
||||||
|
|
||||||
|
# Version 0.13.1 (March 7, 2025)
|
||||||
|
|
||||||
|
## Command Line Interface
|
||||||
|
- Fixed high CPU usage for `typst watch` on Linux. Depending on the project
|
||||||
|
size, CPU usage would spike for varying amounts of time. This bug appeared
|
||||||
|
with 0.13.0 due to a behavioral change in the inotify file watching backend.
|
||||||
|
|
||||||
|
## HTML export
|
||||||
|
- Fixed export of tables with [gutters]($table.gutter)
|
||||||
|
- Fixed usage of `<html>` and `<body>` element within [context]
|
||||||
|
- Fixed querying of [metadata] next to `<html>` and `<body>` element
|
||||||
|
|
||||||
|
## Visualization
|
||||||
|
- Fixed [curves]($curve) with multiple non-closed components
|
||||||
|
|
||||||
|
## Introspection
|
||||||
|
- Fixed a regression where labelled [symbols]($symbol) could not be
|
||||||
|
[queried]($query) by label
|
||||||
|
|
||||||
|
## Deprecations
|
||||||
|
- Fixed false positives in deprecation warnings for type/str comparisons
|
||||||
|
|
||||||
|
## Contributors
|
||||||
|
<contributors from="v0.13.0" to="v0.13.1" />
|
@ -10,6 +10,7 @@ forward. This section documents all changes to Typst since its initial public
|
|||||||
release.
|
release.
|
||||||
|
|
||||||
## Versions
|
## Versions
|
||||||
|
- [Typst 0.13.1]($changelog/0.13.1)
|
||||||
- [Typst 0.13.0]($changelog/0.13.0)
|
- [Typst 0.13.0]($changelog/0.13.0)
|
||||||
- [Typst 0.12.0]($changelog/0.12.0)
|
- [Typst 0.12.0]($changelog/0.12.0)
|
||||||
- [Typst 0.11.1]($changelog/0.11.1)
|
- [Typst 0.11.1]($changelog/0.11.1)
|
||||||
|
@ -447,7 +447,7 @@ document.
|
|||||||
To let a function style your whole document, the show rule processes everything
|
To let a function style your whole document, the show rule processes everything
|
||||||
that comes after it and calls the function specified after the colon with the
|
that comes after it and calls the function specified after the colon with the
|
||||||
result as an argument. The `.with` part is a _method_ that takes the `conf`
|
result as an argument. The `.with` part is a _method_ that takes the `conf`
|
||||||
function and pre-configures some if its arguments before passing it on to the
|
function and pre-configures some of its arguments before passing it on to the
|
||||||
show rule.
|
show rule.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -56,7 +56,7 @@ requirements with examples.
|
|||||||
Typst's default page size is A4 paper. Depending on your region and your use
|
Typst's default page size is A4 paper. Depending on your region and your use
|
||||||
case, you will want to change this. You can do this by using the
|
case, you will want to change this. You can do this by using the
|
||||||
[`{page}`]($page) set rule and passing it a string argument to use a common page
|
[`{page}`]($page) set rule and passing it a string argument to use a common page
|
||||||
size. Options include the complete ISO 216 series (e.g. `"iso-a4"`, `"iso-c2"`),
|
size. Options include the complete ISO 216 series (e.g. `"a4"` and `"iso-c2"`),
|
||||||
customary US formats like `"us-legal"` or `"us-letter"`, and more. Check out the
|
customary US formats like `"us-legal"` or `"us-letter"`, and more. Check out the
|
||||||
reference for the [page's paper argument]($page.paper) to learn about all
|
reference for the [page's paper argument]($page.paper) to learn about all
|
||||||
available options.
|
available options.
|
||||||
|
@ -170,8 +170,8 @@
|
|||||||
category: symbols
|
category: symbols
|
||||||
path: ["emoji"]
|
path: ["emoji"]
|
||||||
details: |
|
details: |
|
||||||
Named emoji.
|
Named emojis.
|
||||||
|
|
||||||
For example, `#emoji.face` produces the 😀 emoji. If you frequently use
|
For example, `#emoji.face` produces the 😀 emoji. If you frequently use
|
||||||
certain emojis, you can also import them from the `emoji` module (`[#import
|
certain emojis, you can also import them from the `emoji` module (`[#import
|
||||||
emoji: face]`) to use them without the `#emoji.` prefix.
|
emoji: face]`) to use them without the `emoji.` prefix.
|
||||||
|
@ -188,6 +188,7 @@ fn changelog_pages(resolver: &dyn Resolver) -> PageModel {
|
|||||||
let mut page = md_page(resolver, resolver.base(), load!("changelog/welcome.md"));
|
let mut page = md_page(resolver, resolver.base(), load!("changelog/welcome.md"));
|
||||||
let base = format!("{}changelog/", resolver.base());
|
let base = format!("{}changelog/", resolver.base());
|
||||||
page.children = vec![
|
page.children = vec![
|
||||||
|
md_page(resolver, &base, load!("changelog/0.13.1.md")),
|
||||||
md_page(resolver, &base, load!("changelog/0.13.0.md")),
|
md_page(resolver, &base, load!("changelog/0.13.0.md")),
|
||||||
md_page(resolver, &base, load!("changelog/0.12.0.md")),
|
md_page(resolver, &base, load!("changelog/0.12.0.md")),
|
||||||
md_page(resolver, &base, load!("changelog/0.11.1.md")),
|
md_page(resolver, &base, load!("changelog/0.11.1.md")),
|
||||||
|
@ -13,11 +13,11 @@ your report using Typst's styling system.
|
|||||||
As we have seen in the previous chapter, Typst has functions that _insert_
|
As we have seen in the previous chapter, Typst has functions that _insert_
|
||||||
content (e.g. the [`image`] function) and others that _manipulate_ content that
|
content (e.g. the [`image`] function) and others that _manipulate_ content that
|
||||||
they received as arguments (e.g. the [`align`] function). The first impulse you
|
they received as arguments (e.g. the [`align`] function). The first impulse you
|
||||||
might have when you want, for example, to justify the report, could be to look
|
might have when you want, for example, to change the font, could be to look
|
||||||
for a function that does that and wrap the complete document in it.
|
for a function that does that and wrap the complete document in it.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#par(justify: true)[
|
#text(font: "New Computer Modern")[
|
||||||
= Background
|
= Background
|
||||||
In the case of glaciers, fluid
|
In the case of glaciers, fluid
|
||||||
dynamics principles can be used
|
dynamics principles can be used
|
||||||
@ -37,9 +37,9 @@ do in Typst, there is special syntax for it: Instead of putting the content
|
|||||||
inside of the argument list, you can write it in square brackets directly after
|
inside of the argument list, you can write it in square brackets directly after
|
||||||
the normal arguments, saving on punctuation.
|
the normal arguments, saving on punctuation.
|
||||||
|
|
||||||
As seen above, that works. The [`par`] function justifies all paragraphs within
|
As seen above, that works. With the [`text`] function, we can adjust the font
|
||||||
it. However, wrapping the document in countless functions and applying styles
|
for all text within it. However, wrapping the document in countless functions
|
||||||
selectively and in-situ can quickly become cumbersome.
|
and applying styles selectively and in-situ can quickly become cumbersome.
|
||||||
|
|
||||||
Fortunately, Typst has a more elegant solution. With _set rules,_ you can apply
|
Fortunately, Typst has a more elegant solution. With _set rules,_ you can apply
|
||||||
style properties to all occurrences of some kind of content. You write a set
|
style properties to all occurrences of some kind of content. You write a set
|
||||||
@ -47,7 +47,9 @@ rule by entering the `{set}` keyword, followed by the name of the function whose
|
|||||||
properties you want to set, and a list of arguments in parentheses.
|
properties you want to set, and a list of arguments in parentheses.
|
||||||
|
|
||||||
```example
|
```example
|
||||||
#set par(justify: true)
|
#set text(
|
||||||
|
font: "New Computer Modern"
|
||||||
|
)
|
||||||
|
|
||||||
= Background
|
= Background
|
||||||
In the case of glaciers, fluid
|
In the case of glaciers, fluid
|
||||||
|
6
flake.lock
generated
@ -112,13 +112,13 @@
|
|||||||
"rust-manifest": {
|
"rust-manifest": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"narHash": "sha256-Yqu2/i9170R7pQhvOCR1f5SyFr7PcFbO6xcMr9KWruQ=",
|
"narHash": "sha256-irgHsBXecwlFSdmP9MfGP06Cbpca2QALJdbN4cymcko=",
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"url": "https://static.rust-lang.org/dist/channel-rust-1.83.0.toml"
|
"url": "https://static.rust-lang.org/dist/channel-rust-1.85.0.toml"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"type": "file",
|
"type": "file",
|
||||||
"url": "https://static.rust-lang.org/dist/channel-rust-1.83.0.toml"
|
"url": "https://static.rust-lang.org/dist/channel-rust-1.85.0.toml"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"systems": {
|
"systems": {
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
inputs.nixpkgs.follows = "nixpkgs";
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
};
|
};
|
||||||
rust-manifest = {
|
rust-manifest = {
|
||||||
url = "https://static.rust-lang.org/dist/channel-rust-1.83.0.toml";
|
url = "https://static.rust-lang.org/dist/channel-rust-1.85.0.toml";
|
||||||
flake = false;
|
flake = false;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
BIN
tests/ref/curve-multiple-non-closed.png
Normal file
After Width: | Height: | Size: 85 B |
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
26
tests/ref/html/col-gutter-table.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>a</td>
|
||||||
|
<td>b</td>
|
||||||
|
<td>c</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>d</td>
|
||||||
|
<td>e</td>
|
||||||
|
<td>f</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>g</td>
|
||||||
|
<td>h</td>
|
||||||
|
<td>i</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
26
tests/ref/html/col-row-gutter-table.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>a</td>
|
||||||
|
<td>b</td>
|
||||||
|
<td>c</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>d</td>
|
||||||
|
<td>e</td>
|
||||||
|
<td>f</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>g</td>
|
||||||
|
<td>h</td>
|
||||||
|
<td>i</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
2
tests/ref/html/html-elem-alone-context.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html></html>
|
2
tests/ref/html/html-elem-metadata.html
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>Hi</html>
|
26
tests/ref/html/row-gutter-table.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table>
|
||||||
|
<tr>
|
||||||
|
<td>a</td>
|
||||||
|
<td>b</td>
|
||||||
|
<td>c</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>d</td>
|
||||||
|
<td>e</td>
|
||||||
|
<td>f</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>g</td>
|
||||||
|
<td>h</td>
|
||||||
|
<td>i</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
BIN
tests/ref/issue-5536-quote-inline-quotes-false.png
Normal file
After Width: | Height: | Size: 389 B |
BIN
tests/ref/issue-5930-symbol-label.png
Normal file
After Width: | Height: | Size: 243 B |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 614 B After Width: | Height: | Size: 625 B |
Before Width: | Height: | Size: 327 B After Width: | Height: | Size: 327 B |
Before Width: | Height: | Size: 1.0 KiB After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 284 B After Width: | Height: | Size: 284 B |
Before Width: | Height: | Size: 926 B After Width: | Height: | Size: 930 B |
BIN
tests/ref/math-shorthands-noncontinuable.png
Normal file
After Width: | Height: | Size: 475 B |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.3 KiB |
@ -149,7 +149,7 @@ impl Collector {
|
|||||||
for entry in walkdir::WalkDir::new(crate::SUITE_PATH).sort_by_file_name() {
|
for entry in walkdir::WalkDir::new(crate::SUITE_PATH).sort_by_file_name() {
|
||||||
let entry = entry.unwrap();
|
let entry = entry.unwrap();
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if !path.extension().is_some_and(|ext| ext == "typ") {
|
if path.extension().is_none_or(|ext| ext != "typ") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +168,7 @@ impl Collector {
|
|||||||
for entry in walkdir::WalkDir::new(crate::REF_PATH).sort_by_file_name() {
|
for entry in walkdir::WalkDir::new(crate::REF_PATH).sort_by_file_name() {
|
||||||
let entry = entry.unwrap();
|
let entry = entry.unwrap();
|
||||||
let path = entry.path();
|
let path = entry.path();
|
||||||
if !path.extension().is_some_and(|ext| ext == "png") {
|
if path.extension().is_none_or(|ext| ext != "png") {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +161,7 @@ impl<'a> Runner<'a> {
|
|||||||
|
|
||||||
// Compare against reference output if available.
|
// Compare against reference output if available.
|
||||||
// Test that is ok doesn't need to be updated.
|
// Test that is ok doesn't need to be updated.
|
||||||
if ref_data.as_ref().map_or(false, |r| D::matches(&live, r)) {
|
if ref_data.as_ref().is_ok_and(|r| D::matches(&live, r)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +86,13 @@
|
|||||||
// Error: 2-28 0x110000 is not a valid codepoint
|
// Error: 2-28 0x110000 is not a valid codepoint
|
||||||
#str.from-unicode(0x110000) // 0x10ffff is the highest valid code point
|
#str.from-unicode(0x110000) // 0x10ffff is the highest valid code point
|
||||||
|
|
||||||
|
--- str-normalize ---
|
||||||
|
// Test the `normalize` method.
|
||||||
|
#test("e\u{0301}".normalize(form: "nfc"), "é")
|
||||||
|
#test("é".normalize(form: "nfd"), "e\u{0301}")
|
||||||
|
#test("ſ\u{0301}".normalize(form: "nfkc"), "ś")
|
||||||
|
#test("ſ\u{0301}".normalize(form: "nfkd"), "s\u{0301}")
|
||||||
|
|
||||||
--- string-len ---
|
--- string-len ---
|
||||||
// Test the `len` method.
|
// Test the `len` method.
|
||||||
#test("Hello World!".len(), 12)
|
#test("Hello World!".len(), 12)
|
||||||
|
15
tests/suite/html/elem.typ
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
--- html-elem-alone-context html ---
|
||||||
|
#context html.elem("html")
|
||||||
|
|
||||||
|
--- html-elem-not-alone html ---
|
||||||
|
// Error: 2-19 `<html>` element must be the only element in the document
|
||||||
|
#html.elem("html")
|
||||||
|
Text
|
||||||
|
|
||||||
|
--- html-elem-metadata html ---
|
||||||
|
#html.elem("html", context {
|
||||||
|
let val = query(<l>).first().value
|
||||||
|
test(val, "Hi")
|
||||||
|
val
|
||||||
|
})
|
||||||
|
#metadata("Hi") <l>
|
@ -1,10 +1,35 @@
|
|||||||
|
--- dir-from ---
|
||||||
|
#test(direction.from(left), ltr)
|
||||||
|
#test(direction.from(right), rtl)
|
||||||
|
#test(direction.from(top), ttb)
|
||||||
|
#test(direction.from(bottom), btt)
|
||||||
|
|
||||||
|
--- dir-from-invalid ---
|
||||||
|
// Error: 17-23 cannot convert this alignment to a side
|
||||||
|
#direction.from(center)
|
||||||
|
|
||||||
|
--- dir-to ---
|
||||||
|
#test(direction.to(left), rtl)
|
||||||
|
#test(direction.to(right), ltr)
|
||||||
|
#test(direction.to(top), btt)
|
||||||
|
#test(direction.to(bottom), ttb)
|
||||||
|
|
||||||
|
-- dir-to-invalid ---
|
||||||
|
// Error: 15-21 cannot convert this alignment to a side
|
||||||
|
#direction.to(center)
|
||||||
|
|
||||||
--- dir-axis ---
|
--- dir-axis ---
|
||||||
// Test direction methods.
|
|
||||||
#test(ltr.axis(), "horizontal")
|
#test(ltr.axis(), "horizontal")
|
||||||
#test(rtl.axis(), "horizontal")
|
#test(rtl.axis(), "horizontal")
|
||||||
#test(ttb.axis(), "vertical")
|
#test(ttb.axis(), "vertical")
|
||||||
#test(btt.axis(), "vertical")
|
#test(btt.axis(), "vertical")
|
||||||
|
|
||||||
|
--- dir-sign ---
|
||||||
|
#test(ltr.sign(), 1)
|
||||||
|
#test(rtl.sign(), -1)
|
||||||
|
#test(ttb.sign(), 1)
|
||||||
|
#test(btt.sign(), -1)
|
||||||
|
|
||||||
--- dir-start ---
|
--- dir-start ---
|
||||||
#test(ltr.start(), left)
|
#test(ltr.start(), left)
|
||||||
#test(rtl.start(), right)
|
#test(rtl.start(), right)
|
||||||
|
@ -30,3 +30,30 @@
|
|||||||
[row],
|
[row],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
--- col-gutter-table html ---
|
||||||
|
#table(
|
||||||
|
columns: 3,
|
||||||
|
column-gutter: 3pt,
|
||||||
|
[a], [b], [c],
|
||||||
|
[d], [e], [f],
|
||||||
|
[g], [h], [i]
|
||||||
|
)
|
||||||
|
|
||||||
|
--- row-gutter-table html ---
|
||||||
|
#table(
|
||||||
|
columns: 3,
|
||||||
|
row-gutter: 3pt,
|
||||||
|
[a], [b], [c],
|
||||||
|
[d], [e], [f],
|
||||||
|
[g], [h], [i]
|
||||||
|
)
|
||||||
|
|
||||||
|
--- col-row-gutter-table html ---
|
||||||
|
#table(
|
||||||
|
columns: 3,
|
||||||
|
gutter: 3pt,
|
||||||
|
[a], [b], [c],
|
||||||
|
[d], [e], [f],
|
||||||
|
[g], [h], [i]
|
||||||
|
)
|
||||||
|
@ -13,6 +13,11 @@ $ underline(f' : NN -> RR) \
|
|||||||
1 - 0 thick &...,
|
1 - 0 thick &...,
|
||||||
) $
|
) $
|
||||||
|
|
||||||
|
--- math-shorthands-noncontinuable ---
|
||||||
|
// Test that shorthands are not continuable.
|
||||||
|
$ x >=(y) / z \
|
||||||
|
x >= (y) / z $
|
||||||
|
|
||||||
--- math-common-symbols ---
|
--- math-common-symbols ---
|
||||||
// Test common symbols.
|
// Test common symbols.
|
||||||
$ dot \ dots \ ast \ tilde \ star $
|
$ dot \ dots \ ast \ tilde \ star $
|
||||||
|
@ -49,7 +49,6 @@
|
|||||||
2000000001, "βΜκʹ, αʹ",
|
2000000001, "βΜκʹ, αʹ",
|
||||||
2000010001, "βΜκʹ, αΜαʹ, αʹ",
|
2000010001, "βΜκʹ, αΜαʹ, αʹ",
|
||||||
2056839184, "βΜκʹ, αΜ͵εχπγ, ͵θρπδ",
|
2056839184, "βΜκʹ, αΜ͵εχπγ, ͵θρπδ",
|
||||||
12312398676, "βΜρκγʹ, αΜ͵ασλθ, ͵ηχοϛ",
|
|
||||||
)
|
)
|
||||||
#t(
|
#t(
|
||||||
pat: sym.Alpha,
|
pat: sym.Alpha,
|
||||||
|
@ -118,3 +118,6 @@ An inline #quote[quote.]
|
|||||||
#quote(block: true, attribution: [The Test Author])[
|
#quote(block: true, attribution: [The Test Author])[
|
||||||
A block-level quote.
|
A block-level quote.
|
||||||
]
|
]
|
||||||
|
|
||||||
|
--- issue-5536-quote-inline-quotes-false ---
|
||||||
|
Lorem #quote(block: false, quotes: false)[dolor].
|
||||||
|
@ -44,18 +44,21 @@
|
|||||||
--- recursion-via-include-in-layout ---
|
--- recursion-via-include-in-layout ---
|
||||||
// Test cyclic imports during layout.
|
// Test cyclic imports during layout.
|
||||||
// Error: 2-38 maximum show rule depth exceeded
|
// Error: 2-38 maximum show rule depth exceeded
|
||||||
// Hint: 2-38 check whether the show rule matches its own output
|
// Hint: 2-38 maybe a show rule matches its own output
|
||||||
|
// Hint: 2-38 maybe there are too deeply nested elements
|
||||||
#layout(_ => include "recursion.typ")
|
#layout(_ => include "recursion.typ")
|
||||||
|
|
||||||
--- recursion-show-math ---
|
--- recursion-show-math ---
|
||||||
// Test recursive show rules.
|
// Test recursive show rules.
|
||||||
// Error: 22-25 maximum show rule depth exceeded
|
// Error: 22-25 maximum show rule depth exceeded
|
||||||
// Hint: 22-25 check whether the show rule matches its own output
|
// Hint: 22-25 maybe a show rule matches its own output
|
||||||
|
// Hint: 22-25 maybe there are too deeply nested elements
|
||||||
#show math.equation: $x$
|
#show math.equation: $x$
|
||||||
$ x $
|
$ x $
|
||||||
|
|
||||||
--- recursion-show-math-realize ---
|
--- recursion-show-math-realize ---
|
||||||
// Error: 22-33 maximum show rule depth exceeded
|
// Error: 22-33 maximum show rule depth exceeded
|
||||||
// Hint: 22-33 check whether the show rule matches its own output
|
// Hint: 22-33 maybe a show rule matches its own output
|
||||||
|
// Hint: 22-33 maybe there are too deeply nested elements
|
||||||
#show heading: it => heading[it]
|
#show heading: it => heading[it]
|
||||||
$ #heading[hi] $
|
$ #heading[hi] $
|
||||||
|
@ -151,3 +151,7 @@
|
|||||||
--- symbol-sect-deprecated ---
|
--- symbol-sect-deprecated ---
|
||||||
// Warning: 5-9 `sect` is deprecated, use `inter` instead
|
// Warning: 5-9 `sect` is deprecated, use `inter` instead
|
||||||
$ A sect B = A inter B $
|
$ A sect B = A inter B $
|
||||||
|
|
||||||
|
--- issue-5930-symbol-label ---
|
||||||
|
#emoji.face<lab>
|
||||||
|
#context test(query(<lab>).first().text, "😀")
|
||||||
|
@ -38,6 +38,16 @@
|
|||||||
curve.close(mode: "smooth"),
|
curve.close(mode: "smooth"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
--- curve-multiple-non-closed ---
|
||||||
|
#curve(
|
||||||
|
stroke: 2pt,
|
||||||
|
curve.line((20pt, 0pt)),
|
||||||
|
curve.move((0pt, 10pt)),
|
||||||
|
curve.line((20pt, 10pt)),
|
||||||
|
curve.move((0pt, 20pt)),
|
||||||
|
curve.line((20pt, 20pt)),
|
||||||
|
)
|
||||||
|
|
||||||
--- curve-line ---
|
--- curve-line ---
|
||||||
#curve(
|
#curve(
|
||||||
fill: purple,
|
fill: purple,
|
||||||
|