diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000000..a253042ba3 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,23 @@ +[target.x86_64-apple-darwin] +rustflags = ["-C", "link-args=-rdynamic"] + +[target.x86_64-unknown-linux-gnu] +rustflags = ["-C", "link-args=-rdynamic"] + +# Since autotools sets these and we are *not* forcing them here, this will not +# affect release builds. It will affect `cargo run`, `cargo clippy` and others +# making it easier to test locally since the Lua loader path and other +# resources will be relative to the current sources. +[env] +SILE_PATH = { value = "", relative = true } +CONFIGURE_DATADIR = { value = "", relative = true } + +[target.'cfg(all())'] +rustflags = [ + # CLIPPY LINT SETTINGS + # This is a workaround to configure lints for the entire workspace, pending the ability to configure this via TOML. + # See: `https://github.com/rust-lang/cargo/issues/5034` + # `https://github.com/EmbarkStudios/rust-ecosystem/issues/22#issuecomment-947011395` + "-Asuspicious_double_ref_op", + "-Aclippy::ptr_arg", +] diff --git a/.cirrus.yml b/.cirrus.yml index dfc8998788..9e15024552 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -23,29 +23,28 @@ task: folder: /usr/local/lib/lua fingerprint_script: cat sile.rockspec.in dependencies_script: - - pkg install -y autoconf automake fontconfig GentiumPlus git gmake harfbuzz libtool pkgconf png - - pkg install -y lua54 lua54-luaexpat lua54-lpeg lua54-luafilesystem lua54-luarocks lua54-luasec lua54-luasocket - - luarocks54 install cassowary - - luarocks54 install cldr - - luarocks54 install compat53 - - luarocks54 install cosmo - - luarocks54 install fluent - - luarocks54 install linenoise - - luarocks54 install loadkit - - luarocks54 install lua-zlib - - luarocks54 install lua_cliargs - - luarocks54 install luaepnf - - luarocks54 install luarepl - - luarocks54 install luautf8 - - luarocks54 install penlight - - luarocks54 install vstruct + - pkg install -y autoconf automake fontconfig GentiumPlus git gmake harfbuzz jq libtool pkgconf png rust + - pkg install -y luajit lua51-luaexpat lua51-lpeg lua51-luafilesystem lua51-luarocks lua51-luasec lua51-luasocket lua51-lzlib + - luarocks51 install cassowary + - luarocks51 install cldr + - luarocks51 install compat53 + - luarocks51 install fluent + - luarocks51 install linenoise + - luarocks51 install loadkit + - luarocks51 install lua_cliargs + - luarocks51 install luaepnf + - luarocks51 install luarepl + - luarocks51 install luautf8 + - luarocks51 install penlight + - luarocks51 install vstruct bootstrap_script: - git fetch --prune --tags ||: - ./bootstrap.sh configure_script: | ./configure MAKE=gmake \ - --enable-developer LUAROCKS=false LUACHECK=false BUSTED=false PDFINFO=false NIX=false \ + --enable-developer-mode LDOC=false LUAROCKS=false LUACHECK=false BUSTED=false DELTA=cat PDFINFO=false NIX=false NPM=false DOCKER=false \ --disable-font-variations \ + --with-system-lua-sources \ --with-system-luarocks \ --without-manual make_script: diff --git a/.commitlintrc.yml b/.commitlintrc.yml index 12a25a8fbe..50eba6a573 100644 --- a/.commitlintrc.yml +++ b/.commitlintrc.yml @@ -39,6 +39,7 @@ rules: - inputters - installation - languages + - api - manpage - manual - math @@ -53,6 +54,7 @@ rules: - settings - shapers - tooling + - types - typesetters - utilities help: | diff --git a/.editorconfig b/.editorconfig index f935e92c56..b877137e05 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,15 +6,28 @@ insert_final_newline = true charset = utf-8 trim_trailing_whitespace = true -[{*.lua,*.lua.in,sile.in,*rockspec,.busted,.luacheckrc}] +[{Makefile*,*.mk,*.mk.in}] +indent_style = tab +indent_size = 4 + +[*.m4] indent_style = space -indent_size = 2 +indent_size = 8 + +[*.md] +trim_trailing_whitespace = false + +[{*.lua,*.lua.in,sile.in,*rockspec,.busted,.luacheckrc,config.ld}] +indent_style = space +indent_size = 3 max_line_length = 120 +[*rockspec] +max_line_length = 190 + [*.pl] indent_style = space -indent_size = 4 +indent_size = 2 -[makefile*] -indent_style = tab +[*.rs] indent_size = 4 diff --git a/.envrc b/.envrc new file mode 100644 index 0000000000..3550a30f2d --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 20ebf6b663..075c78d427 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -9,7 +9,15 @@ concurrency: jobs: build-ubuntu: + strategy: + matrix: + configuration: + - [ 'dynamic', '' ] + - [ 'system', '--with-system-lua-sources' ] + - [ 'embedded', '--enable-embedded-resources' ] + - [ 'static', '--enable-embedded-resources --disable-shared --enable-static' ] runs-on: ubuntu-22.04 + name: Build Ubuntu ${{ matrix.configuration[0] }} steps: - name: Checkout uses: actions/checkout@v4 @@ -28,43 +36,64 @@ jobs: path: | lua_modules key: luarocks-${{ hashFiles('Makefile-luarocks', 'sile.rockspec.in') }} + - name: Cache Rust + uses: Swatinem/rust-cache@v2 - name: Install system dependencies run: | sudo apt-get update - sudo apt-get install fonts-sil-gentiumplus ghostscript graphviz libarchive-tools libfontconfig1-dev libharfbuzz-dev libicu-dev liblua5.3-dev libpng-dev lua5.3 lua-sec lua-socket lua-zlib-dev luarocks poppler-utils + sudo apt-get install fonts-sil-gentiumplus ghostscript graphviz jq libarchive-tools libfontconfig1-dev libharfbuzz-dev libicu-dev libluajit-5.1-dev libpng-dev luajit lua-sec lua-socket lua-zlib-dev luarocks poppler-utils + - name: Setup ‘cargo’ + uses: actions-rs/toolchain@v1 - name: Configure run: | ./bootstrap.sh ./configure \ + --enable-developer-mode \ + BUSTED=false DELTA=false LDOC=false LUACHECK=false NIX=false \ --disable-font-variations \ - --without-system-luarocks \ - --with-manual + --with-manual \ + ${{ matrix.configuration[1] }} echo "VERSION=$(./build-aux/git-version-gen .tarball-version)" >> $GITHUB_ENV echo "MAKEFLAGS=-j$(nproc) -Otarget" >> $GITHUB_ENV + echo "CARCH=$(uname -m)" >> $GITHUB_ENV - name: Make run: | make - name: Package run: | make dist - - name: Upload artifacts - if: ${{ !contains(github.ref, 'refs/tags/v') }} + - name: Upload source dist artifact + if: ${{ matrix.configuration[0] == 'dynamic' && !contains(github.ref, 'refs/tags/v') }} uses: actions/upload-artifact@v4 with: name: sile-${{ env.VERSION }} path: sile-${{ env.VERSION }}.zip + - name: Append architecture to static binary + if: ${{ matrix.configuration[0] == 'static' }} + run: | + cp sile sile-${{ env.CARCH }} + sha256sum sile-${{ env.CARCH }} | tee -a sile-${{ env.VERSION }}.sha256.txt + - name: Upload static binary artifact + if: ${{ matrix.configuration[0] == 'static' && !contains(github.ref, 'refs/tags/v') }} + uses: actions/upload-artifact@v4 + with: + name: sile-${{ env.CARCH }} + path: sile-${{ env.CARCH }} - name: Release uses: softprops/action-gh-release@v2 - if: github.repository == 'sile-typesetter/sile' && startsWith(github.ref, 'refs/tags/v') + if: matrix.configuration[0] == 'static' && github.repository == 'sile-typesetter/sile' && startsWith(github.ref, 'refs/tags/v') with: body_path: sile-${{ env.VERSION }}.md files: | sile-${{ env.VERSION }}.pdf sile-${{ env.VERSION }}.zip sile-${{ env.VERSION }}.tar.zst + sile-${{ env.CARCH }} + sile-${{ env.VERSION }}.sha256.txt build-nix: runs-on: ubuntu-22.04 + name: Build Nix steps: - name: Checkout uses: actions/checkout@v4 @@ -86,6 +115,6 @@ jobs: nix develop --command ./bootstrap.sh nix develop --configure nix develop --command make - - name: Run HB6+ only regressions + - name: Run regression tests for which Ubuntu can't provide deps run: | - nix develop --command make regressions TESTSRCS=tests/variations-axis.sil + nix develop --command make regressions TESTSRCS='tests/variations-axis.sil tests/feat-unicode-softhyphen.sil' diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8a984ed480..94c52599df 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -60,10 +60,8 @@ jobs: run: | ./bootstrap.sh ./configure \ - --enable-developer LUACHECK=false NIX=false \ + --enable-developer-mode LDOC=false LUACHECK=false NIX=false DELTA=cat \ --disable-font-variations \ - --without-system-luarocks \ - --with-luajit \ --without-manual - name: Make run: | diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index cd11234a80..5e0f0d8c2f 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - develop - rel* tags: - latest @@ -20,11 +21,27 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 + - name: Install system dependencies + run: | + sudo apt-get update + sudo apt-get install libluajit-5.1-dev luajit luarocks - name: Configure run: | echo "REF=${GITHUB_REF##refs/*/}" >> $GITHUB_ENV ./bootstrap.sh - ./configure --disable-dependency-checks --enable-developer + ./configure \ + --enable-developer-mode \ + --without-harfbuzz \ + --disable-font-variations \ + BSDTAR=false \ + BUSTED=false \ + DELTA=false \ + FCMATCH=true \ + LDOC=false \ + LUACHECK=false \ + LUAROCKS=false \ + NIX=false \ + PDFINFO=false - name: Publish Docker Image to GH Container Registry run: | make docker-build-push diff --git a/.github/workflows/rust_lint.yml b/.github/workflows/rust_lint.yml new file mode 100644 index 0000000000..8c315d67f0 --- /dev/null +++ b/.github/workflows/rust_lint.yml @@ -0,0 +1,42 @@ +name: Rust Lint + +on: [ push, pull_request ] + +jobs: + + rustfmt: + strategy: + fail-fast: false + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + components: rustfmt + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + - name: Run rustfmt + run: | + git ls-files '*.rs' '*.rs.in' | xargs rustfmt --check --config skip_children=true + + clippy: + strategy: + fail-fast: false + runs-on: ubuntu-22.04 + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + components: clippy + - name: Cache Rust + uses: Swatinem/rust-cache@v2 + - uses: actions-rs/clippy-check@v1 + with: + token: ${{ github.token }} + args: --features luajit,vendored -- -D warnings diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4977c663ae..28b9d1cd43 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,6 +39,8 @@ jobs: path: | lua_modules key: luarocks-${{ matrix.luaVersion[0] }}-${{ hashFiles('Makefile-luarocks', 'sile.rockspec.in') }} + - name: Cache Rust + uses: Swatinem/rust-cache@v2 - name: Install system dependencies run: | sudo apt-get update @@ -48,6 +50,8 @@ jobs: with: luaVersion: ${{ matrix.luaVersion[0] }} luaCompileFlags: ${{ matrix.luaVersion[1] }} + - name: Setup ‘cargo’ + uses: actions-rs/toolchain@v1 - name: Setup ‘luarocks’ uses: leafo/gh-actions-luarocks@v4 - name: Prep system Lua for use @@ -66,10 +70,11 @@ jobs: run: | ./bootstrap.sh ./configure \ - --enable-developer LUACHECK=false NIX=false \ + ${{ matrix.luaVersion[1] }} \ + --enable-developer-mode LDOC=false LUACHECK=false NIX=false DELTA=cat \ --disable-font-variations \ - --without-system-luarocks \ --with${{ !startsWith(matrix.luaVersion[0], 'luajit') && 'out' || '' }}-luajit \ + --without-system-luarocks \ --without-manual - name: Make run: | @@ -86,6 +91,10 @@ jobs: timeout-minutes: ${{ runner.debug && 20 || 2 }} run: | make regressions + - name: Test Cargo + timeout-minutes: ${{ runner.debug && 20 || 2 }} + run: | + make cargo-test - name: Upload artifacts uses: actions/upload-artifact@v4 with: diff --git a/.gitignore b/.gitignore index 76a9c3b235..47a5e18a60 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ Makefile.in /autom4te.cache /config.status /configure +/aminclude.am build-aux/compile build-aux/config.guess build-aux/config.sub @@ -21,6 +22,7 @@ build-aux/ltversion.m4 build-aux/lt~obsolete.m4 build-aux/missing build-aux/list-dist-files.sh +build-aux/rust_boilerplate.mk tests/regressions.pl # Other autojunk @@ -65,7 +67,6 @@ node_modules lua_modules lua_modules_dist package-lock.json -yarn.lock # Editor entrails .vscode/ @@ -79,16 +80,26 @@ gource.webm *.rockspec .fonts/* .sources/* -/sile +sile sile.1 -/.version -/.version-prev -/.tarball-version -/.built-subdirs +sile-lua +sile-lua.1 +.version +.version-prev +.tarball-version +.built-subdirs *.so +*.o core/version.lua core/features.lua +target/ +completions/ core/pathsetup.lua +src/embed.rs +src/embed-includes.rs # Nix symlink to builds -/result +result +result-man +result-doc +result-dev diff --git a/.husky/.gitignore b/.husky/.gitignore deleted file mode 100644 index 31354ec138..0000000000 --- a/.husky/.gitignore +++ /dev/null @@ -1 +0,0 @@ -_ diff --git a/.husky/commit-msg b/.husky/commit-msg index d71a03b9f3..b56767669e 100755 --- a/.husky/commit-msg +++ b/.husky/commit-msg @@ -1,4 +1,4 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" -yarn commitlint --edit $1 +npx --no -- commitlint --edit "$1" diff --git a/.husky/pre-commit b/.husky/pre-commit deleted file mode 100755 index 6700f51282..0000000000 --- a/.husky/pre-commit +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/sh -. "$(dirname "$0")/_/husky.sh" diff --git a/.luacheckrc b/.luacheckrc index cfc81951d7..2fa32aa67f 100644 --- a/.luacheckrc +++ b/.luacheckrc @@ -27,10 +27,8 @@ globals = { "luautf8", "pl", "fluent", - "extendSilePath", "executablePath", - "SYSTEM_SILE_PATH", - "SHARED_LIB_EXT" + "extendSilePath" } max_line_length = false ignore = { diff --git a/.luarc.json b/.luarc.json index 16515bea9e..776a3289c9 100644 --- a/.luarc.json +++ b/.luarc.json @@ -7,10 +7,14 @@ "luautf8", "pl", "fluent", - "extendSilePath", "executablePath", - "SYSTEM_SILE_PATH", - "SHARED_LIB_EXT" + "extendSilePath" + ], + "Lua.workspace.ignoreDir": [ + "languages", + "lua-libraries", + "spec", + "tests" ], "Lua.workspace.preloadFileSize": 5120, "Lua.workspace.checkThirdParty": false diff --git a/CMakeLists.txt b/CMakeLists.txt index 90373dcb7f..1f362b3ba2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,7 +136,7 @@ if (WIN32) endif() -add_library(justenoughlibtexpdf SHARED src/justenoughlibtexpdf.c src/imagebbox.c) +add_library(justenoughlibtexpdf SHARED justenough/justenoughlibtexpdf.c justenough/imagebbox.c) add_dependencies(justenoughlibtexpdf libtexpdf lua) target_include_directories(justenoughlibtexpdf PUBLIC "${CMAKE_SOURCE_DIR}" @@ -149,7 +149,7 @@ target_link_libraries(justenoughlibtexpdf PUBLIC libtexpdf lua51.lib) target_link_options(justenoughlibtexpdf PUBLIC /EXPORT:luaopen_justenoughlibtexpdf) -add_library(justenoughharfbuzz SHARED src/justenoughharfbuzz.c src/hb-utils.c src/hb-utils.h) +add_library(justenoughharfbuzz SHARED justenough/justenoughharfbuzz.c justenough/hb-utils.c justenough/hb-utils.h) add_dependencies(justenoughharfbuzz harfbuzz lua) target_include_directories(justenoughharfbuzz PUBLIC "${TMP_INSTALL_DIR}/include" @@ -162,7 +162,7 @@ target_compile_definitions(justenoughharfbuzz PUBLIC HAVE_HARFBUZZ_SUBSET) target_link_libraries(justenoughharfbuzz PUBLIC harfbuzz.lib harfbuzz-subset.lib lua51.lib) target_link_options(justenoughharfbuzz PUBLIC /EXPORT:luaopen_justenoughharfbuzz) -add_library(justenoughicu SHARED src/justenoughicu.c) +add_library(justenoughicu SHARED justenough/justenoughicu.c) add_dependencies(justenoughicu icu lua) target_include_directories(justenoughicu PUBLIC "${TMP_INSTALL_DIR}/include" @@ -173,7 +173,7 @@ target_link_directories(justenoughicu PUBLIC target_link_libraries(justenoughicu PUBLIC icuio.lib icuin.lib icuuc.lib icudt.lib lua51.lib) target_link_options(justenoughicu PUBLIC /EXPORT:luaopen_justenoughicu) -add_library(justenoughfontconfig SHARED src/justenoughfontconfig.c) +add_library(justenoughfontconfig SHARED justenough/justenoughfontconfig.c) add_dependencies(justenoughfontconfig fontconfig) target_include_directories(justenoughfontconfig PUBLIC "${TMP_INSTALL_DIR}/include" @@ -184,7 +184,7 @@ target_link_directories(justenoughfontconfig PUBLIC target_link_libraries(justenoughfontconfig PUBLIC fontconfig-static.lib lua51.lib expat.lib freetype.lib libpng16_static.lib zlibstatic.lib) target_link_options(justenoughfontconfig PUBLIC /EXPORT:luaopen_justenoughfontconfig) -add_library(fontmetrics SHARED src/fontmetrics.c src/hb-utils.c src/hb-utils.h) +add_library(fontmetrics SHARED justenough/fontmetrics.c justenough/hb-utils.c justenough/hb-utils.h) add_dependencies(fontmetrics harfbuzz lua) target_include_directories(fontmetrics PUBLIC "${TMP_INSTALL_DIR}/include" @@ -196,7 +196,7 @@ target_link_directories(fontmetrics PUBLIC target_link_libraries(fontmetrics PUBLIC harfbuzz.lib lua51.lib) target_link_options(fontmetrics PUBLIC /EXPORT:luaopen_fontmetrics) -add_library(svg SHARED src/svg.c) +add_library(svg SHARED justenough/svg.c) add_dependencies(svg lua) target_include_directories(svg PUBLIC "${TMP_INSTALL_DIR}/include" diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000000..7e711da169 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1863 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.6.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" + +[[package]] +name = "anstyle-parse" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +dependencies = [ + "anstyle", + "windows-sys 0.52.0", +] + +[[package]] +name = "anyhow" +version = "1.0.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad32ce52e4161730f7098c077cd2ed6229b5804ccf99e5366be1ab72a98b4e1" + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "regex-automata", + "serde", +] + +[[package]] +name = "btoi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd6407f73a9b8b6162d8a2ef999fe6afd7cc15902ebf42c5cd296addf17e0ad" +dependencies = [ + "num-traits", +] + +[[package]] +name = "camino" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "694c8807f2ae16faecc43dc17d74b3eb042482789fd0eb64b39a2e04e087053f" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cc" +version = "1.0.88" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02f341c093d19155a6e41631ce5971aac4e9a868262212153124c15fa22d1cdc" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c918d541ef2913577a0f9566e9ce27cb35b6df072075769e0b26cb5a554520da" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f3e7391dad68afb0c2ede1bf619f579a3dc9c2ec67f089baa397123a2f3d1eb" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", + "terminal_size", +] + +[[package]] +name = "clap_complete" +version = "4.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "885e4d7d5af40bfb99ae6f9433e292feac98d452dcb3ec3d25dfe7552b77da8c" +dependencies = [ + "clap", +] + +[[package]] +name = "clap_derive" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "307bc0538d5f0f83b8248db3087aa92fe504e4691294d0c96c0eabc33f47ba47" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "clap_lex" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" + +[[package]] +name = "clap_mangen" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1dd95b5ebb5c1c54581dd6346f3ed6a79a3eef95dd372fc2ac13d535535300e" +dependencies = [ + "clap", + "roff", +] + +[[package]] +name = "clru" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8191fa7302e03607ff0e237d4246cc043ff5b3cb9409d995172ba3bea16b807" + +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "libc", +] + +[[package]] +name = "core-text" +version = "19.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dunce" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" + +[[package]] +name = "either" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" + +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "faster-hex" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2a2b11eda1d40935b26cf18f6833c526845ae8c41e58d09af6adeb6f0269183" +dependencies = [ + "serde", +] + +[[package]] +name = "fastrand" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" + +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + +[[package]] +name = "flate2" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "freetype" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efc8599a3078adf8edeb86c71e9f8fa7d88af5ca31e806a867756081f90f5d83" +dependencies = [ + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66ee28c39a43d89fbed8b4798fb4ba56722cfd2b5af81f9326c27614ba88ecd5" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gix" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd025382892c7b500a9ce1582cd803f9c2ebfe44aff52e9c7f86feee7ced75e" +dependencies = [ + "gix-actor", + "gix-commitgraph", + "gix-config", + "gix-date", + "gix-diff", + "gix-discover", + "gix-features", + "gix-fs", + "gix-glob", + "gix-hash", + "gix-hashtable", + "gix-index", + "gix-lock", + "gix-macros", + "gix-object", + "gix-odb", + "gix-pack", + "gix-path", + "gix-ref", + "gix-refspec", + "gix-revision", + "gix-revwalk", + "gix-sec", + "gix-tempfile", + "gix-trace", + "gix-traverse", + "gix-url", + "gix-utils", + "gix-validate", + "once_cell", + "parking_lot", + "signal-hook", + "smallvec", + "thiserror", + "unicode-normalization", +] + +[[package]] +name = "gix-actor" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da27b5ab4ab5c75ff891dccd48409f8cc53c28a79480f1efdd33184b2dc1d958" +dependencies = [ + "bstr", + "btoi", + "gix-date", + "itoa", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-bitmap" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b6cd0f246180034ddafac9b00a112f19178135b21eb031b3f79355891f7325" +dependencies = [ + "thiserror", +] + +[[package]] +name = "gix-chunk" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "003ec6deacf68076a0c157271a127e0bb2c031c1a41f7168cbe5d248d9b85c78" +dependencies = [ + "thiserror", +] + +[[package]] +name = "gix-commitgraph" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8dcbf434951fa477063e05fea59722615af70dc2567377e58c2f7853b010fc" +dependencies = [ + "bstr", + "gix-chunk", + "gix-features", + "gix-hash", + "memmap2", + "thiserror", +] + +[[package]] +name = "gix-config" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "367304855b369cadcac4ee5fb5a3a20da9378dd7905106141070b79f85241079" +dependencies = [ + "bstr", + "gix-config-value", + "gix-features", + "gix-glob", + "gix-path", + "gix-ref", + "gix-sec", + "memchr", + "once_cell", + "smallvec", + "thiserror", + "unicode-bom", + "winnow", +] + +[[package]] +name = "gix-config-value" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74ab5d22bc21840f4be0ba2e78df947ba14d8ba6999ea798f86b5bdb999edd0c" +dependencies = [ + "bitflags 2.4.2", + "bstr", + "gix-path", + "libc", + "thiserror", +] + +[[package]] +name = "gix-date" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17077f0870ac12b55d2eed9cb3f56549e40def514c8a783a0a79177a8a76b7c5" +dependencies = [ + "bstr", + "itoa", + "thiserror", + "time", +] + +[[package]] +name = "gix-diff" +version = "0.39.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6a0454f8c42d686f17e7f084057c717c082b7dbb8209729e4e8f26749eb93a" +dependencies = [ + "bstr", + "gix-hash", + "gix-object", + "thiserror", +] + +[[package]] +name = "gix-discover" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8d7b2896edc3d899d28a646ccc6df729827a6600e546570b2783466404a42d6" +dependencies = [ + "bstr", + "dunce", + "gix-hash", + "gix-path", + "gix-ref", + "gix-sec", + "thiserror", +] + +[[package]] +name = "gix-features" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d50270e8dcc665f30ba0735b17984b9535bdf1e646c76e638e007846164d57af" +dependencies = [ + "crc32fast", + "flate2", + "gix-hash", + "gix-trace", + "libc", + "once_cell", + "prodash", + "sha1_smol", + "thiserror", + "walkdir", +] + +[[package]] +name = "gix-fs" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7555c23a005537434bbfcb8939694e18cad42602961d0de617f8477cc2adecdd" +dependencies = [ + "gix-features", +] + +[[package]] +name = "gix-glob" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae6232f18b262770e343dcdd461c0011c9b9ae27f0c805e115012aa2b902c1b8" +dependencies = [ + "bitflags 2.4.2", + "bstr", + "gix-features", + "gix-path", +] + +[[package]] +name = "gix-hash" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0ed89cdc1dce26685c80271c4287077901de3c3dd90234d5fa47c22b2268653" +dependencies = [ + "faster-hex", + "thiserror", +] + +[[package]] +name = "gix-hashtable" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe47d8c0887f82355e2e9e16b6cecaa4d5e5346a7a474ca78ff94de1db35a5b" +dependencies = [ + "gix-hash", + "hashbrown", + "parking_lot", +] + +[[package]] +name = "gix-index" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e50e63df6c8d4137f7fb882f27643b3a9756c468a1a2cdbe1ce443010ca8778" +dependencies = [ + "bitflags 2.4.2", + "bstr", + "btoi", + "filetime", + "gix-bitmap", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-traverse", + "itoa", + "libc", + "memmap2", + "rustix", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-lock" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40a439397f1e230b54cf85d52af87e5ea44cc1e7748379785d3f6d03d802b00" +dependencies = [ + "gix-tempfile", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d75e7ab728059f595f6ddc1ad8771b8d6a231971ae493d9d5948ecad366ee8bb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "gix-object" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c89402e8faa41b49fde348665a8f38589e461036475af43b6b70615a6a313a2" +dependencies = [ + "bstr", + "btoi", + "gix-actor", + "gix-date", + "gix-features", + "gix-hash", + "gix-validate", + "itoa", + "smallvec", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-odb" +version = "0.56.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46ae6da873de41c6c2b73570e82c571b69df5154dcd8f46dfafc6687767c33b1" +dependencies = [ + "arc-swap", + "gix-date", + "gix-features", + "gix-hash", + "gix-object", + "gix-pack", + "gix-path", + "gix-quote", + "parking_lot", + "tempfile", + "thiserror", +] + +[[package]] +name = "gix-pack" +version = "0.46.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "782b4d42790a14072d5c400deda9851f5765f50fe72bca6dece0da1cd6f05a9a" +dependencies = [ + "clru", + "gix-chunk", + "gix-features", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-path", + "gix-tempfile", + "memmap2", + "parking_lot", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-path" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69e0b521a5c345b7cd6a81e3e6f634407360a038c8b74ba14c621124304251b8" +dependencies = [ + "bstr", + "gix-trace", + "home", + "once_cell", + "thiserror", +] + +[[package]] +name = "gix-quote" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d1b102957d975c6eb56c2b7ad9ac7f26d117299b910812b2e9bf086ec43496d" +dependencies = [ + "bstr", + "gix-utils", + "thiserror", +] + +[[package]] +name = "gix-ref" +version = "0.40.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64d9bd1984638d8f3511a2fcbe84fcedb8a5b5d64df677353620572383f42649" +dependencies = [ + "gix-actor", + "gix-date", + "gix-features", + "gix-fs", + "gix-hash", + "gix-lock", + "gix-object", + "gix-path", + "gix-tempfile", + "gix-validate", + "memmap2", + "thiserror", + "winnow", +] + +[[package]] +name = "gix-refspec" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be219df5092c1735abb2a53eccdf775e945eea6986ee1b6e7a5896dccc0be704" +dependencies = [ + "bstr", + "gix-hash", + "gix-revision", + "gix-validate", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-revision" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa78e1df3633bc937d4db15f8dca2abdb1300ca971c0fabcf9fa97e38cf4cd9f" +dependencies = [ + "bstr", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "gix-trace", + "thiserror", +] + +[[package]] +name = "gix-revwalk" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "702de5fe5c2bbdde80219f3a8b9723eb927466e7ecd187cfd1b45d986408e45f" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-sec" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "022592a0334bdf77c18c06e12a7c0eaff28845c37e73c51a3e37d56dd495fb35" +dependencies = [ + "bitflags 2.4.2", + "gix-path", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "gix-tempfile" +version = "12.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8ef376d718b1f5f119b458e21b00fbf576bc9d4e26f8f383d29f5ffe3ba3eaa" +dependencies = [ + "gix-fs", + "libc", + "once_cell", + "parking_lot", + "signal-hook", + "signal-hook-registry", + "tempfile", +] + +[[package]] +name = "gix-trace" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b202d766a7fefc596e2cc6a89cda8ad8ad733aed82da635ac120691112a9b1" + +[[package]] +name = "gix-traverse" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65109e445ba7a409b48f34f570a4d7db72eade1dc1bcff81990a490e86c07161" +dependencies = [ + "gix-commitgraph", + "gix-date", + "gix-hash", + "gix-hashtable", + "gix-object", + "gix-revwalk", + "smallvec", + "thiserror", +] + +[[package]] +name = "gix-url" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f0f17cceb7552a231d1fec690bc2740c346554e3be6f5d2c41dfa809594dc44" +dependencies = [ + "bstr", + "gix-features", + "gix-path", + "home", + "thiserror", + "url", +] + +[[package]] +name = "gix-utils" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60157a15b9f14b11af1c6817ad7a93b10b50b4e5136d98a127c46a37ff16eeb6" +dependencies = [ + "fastrand", + "unicode-normalization", +] + +[[package]] +name = "gix-validate" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac7cc36f496bd5d96cdca0f9289bb684480725d40db60f48194aa7723b883854" +dependencies = [ + "bstr", + "thiserror", +] + +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "harfbuzz-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf8c27ca13930dc4ffe474880040fe9e0f03c2121600dc9c95423624cab3e467" +dependencies = [ + "cc", + "core-graphics", + "core-text", + "foreign-types", + "freetype", + "pkg-config", +] + +[[package]] +name = "hashbrown" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + +[[package]] +name = "libc" +version = "0.2.153" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" + +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + +[[package]] +name = "lock_api" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "lua-src" +version = "546.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da0daa7eee611a4c30c8f5ee31af55266e26e573971ba9336d2993e2da129b2" +dependencies = [ + "cc", +] + +[[package]] +name = "luajit-src" +version = "210.5.6+9cc2e42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b365d859c9ffc187f48bb3e25ec80c3b40cf3f68f53544f4adeaee70554157" +dependencies = [ + "cc", + "which", +] + +[[package]] +name = "memchr" +version = "2.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" + +[[package]] +name = "memmap2" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +dependencies = [ + "libc", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +dependencies = [ + "adler", +] + +[[package]] +name = "mlua" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "868d02cb5eb97761bbf6bd6922c1c7a88b8ea252bbf43bd8350a0bf8497a1fc0" +dependencies = [ + "bstr", + "mlua-sys", + "mlua_derive", + "num-traits", + "once_cell", + "rustc-hash", +] + +[[package]] +name = "mlua-sys" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2847b42764435201d8cbee1f517edb79c4cca4181877b90047587c89e1b7bce4" +dependencies = [ + "cc", + "cfg-if", + "lua-src", + "luajit-src", + "pkg-config", +] + +[[package]] +name = "mlua_derive" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaade5f94e5829db58791664ba98f35fea6a3ffebc783becb51dc97c7a21abee" +dependencies = [ + "itertools", + "once_cell", + "proc-macro-error", + "proc-macro2", + "quote", + "regex", + "syn 2.0.52", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_threads" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" +dependencies = [ + "libc", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.48.5", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2422ad645d89c99f8f3e6b88a9fdeca7fabeac836b1002371c4367c8f984aae" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "prodash" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79" + +[[package]] +name = "quote" +version = "1.0.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "regex" +version = "1.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" + +[[package]] +name = "roff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b833d8d034ea094b1ea68aa6d5c740e0d04bad9d16568d08ba6f76823a114316" + +[[package]] +name = "rust-embed" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb78f46d0066053d16d4ca7b898e9343bc3530f71c61d5ad84cd404ada068745" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91ac2a3c6c0520a3fb3dd89321177c3c692937c4eb21893378219da10c44fc8" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn 2.0.52", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86f69089032567ffff4eada41c573fc43ff466c7db7c5688b2e7969584345581" +dependencies = [ + "globset", + "sha2", + "walkdir", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustix" +version = "0.38.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea3e1a662af26cd7a3ba09c0297a31af215563ecf42817c98df621387f4e949" +dependencies = [ + "bitflags 2.4.2", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustversion" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4" + +[[package]] +name = "ryu" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.197" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "serde_json" +version = "1.0.114" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5f09b1bd632ef549eaa9f60a1f8de742bdbc698e6cee2095fc84dde5f549ae0" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1_smol" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "sile" +version = "0.14.16" +dependencies = [ + "anyhow", + "clap", + "clap_complete", + "clap_mangen", + "harfbuzz-sys", + "mlua", + "rust-embed", + "vergen", +] + +[[package]] +name = "smallvec" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7" + +[[package]] +name = "strsim" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "terminal_size" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" +dependencies = [ + "rustix", + "windows-sys 0.48.0", +] + +[[package]] +name = "thiserror" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e45bcbe8ed29775f228095caf2cd67af7a4ccf756ebff23a306bf3e8b47b24b" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a953cb265bef375dae3de6663da4d3804eee9682ea80d8e2542529b73c531c81" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.52", +] + +[[package]] +name = "time" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +dependencies = [ + "deranged", + "itoa", + "libc", + "num-conv", + "num_threads", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-bom" +version = "2.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7eec5d1121208364f6793f7d2e222bf75a915c19557537745b195b253dd64217" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "vergen" +version = "8.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e27d6bdd219887a9eadd19e1c34f32e47fa332301184935c6d9bca26f3cca525" +dependencies = [ + "anyhow", + "cargo_metadata", + "cfg-if", + "gix", + "regex", + "rustversion", + "time", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "which" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fa5e0c10bf77f44aac573e498d1a82d5fbd5e91f6fc0a99e7be4b38e85e101c" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.4", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm 0.52.4", + "windows_aarch64_msvc 0.52.4", + "windows_i686_gnu 0.52.4", + "windows_i686_msvc 0.52.4", + "windows_x86_64_gnu 0.52.4", + "windows_x86_64_gnullvm 0.52.4", + "windows_x86_64_msvc 0.52.4", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000000..3a6ea2280d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,87 @@ +[package] +name = "sile" +version = "0.14.16" +edition = "2021" +rust-version = "1.71.0" +description = "Simon’s Improved Layout Engine" +authors = [ + "Simon Cozens", + "Caleb Maclennan ", + "Olivier Nicole", + "Didier Willis" +] +readme = "README.md" +homepage = "https://sile-typesetter.org" +repository = "https://github.com/sile-typesetter/sile" +license = "MIT" +build = "build-aux/build.rs" +# links = "svg" + +[[bin]] +name = "sile" +required-features = [ "cli" ] + +[features] +default = [ "cli", "bash", "elvish", "fish", "manpage", "powershell", "zsh" ] +lua54 = [ "mlua/lua54" ] +lua53 = [ "mlua/lua53" ] +lua52 = [ "mlua/lua52" ] +lua51 = [ "mlua/lua51" ] +luajit = [ "mlua/luajit" ] +vendored = [ "mlua/vendored" ] +static = [ "rust-embed" ] +variations = [] +completions = [ "cli", "clap_complete" ] +cli = [ "clap" ] +bash = [ "completions" ] +elvish = [ "completions" ] +fish = [ "completions" ] +manpage = [ "clap_mangen" ] +powershell = [ "completions" ] +zsh = [ "completions" ] + +[profile.release] +lto = true + +[dependencies] + + [dependencies.anyhow] + version = "1.0" + + [dependencies.clap] + version = "4.4" + optional = true + features = [ "derive", "string", "wrap_help" ] + + [dependencies.mlua] + version = "0.9" + features = [ "macros" ] + + [dependencies.rust-embed] + version = "8.0" + optional = true + features = [ "include-exclude" ] + + [dependencies.harfbuzz-sys] + version = "0.5" + optional = true + +[build-dependencies] + + [build-dependencies.clap_complete] + version = "4.4" + optional = true + + [build-dependencies.clap_mangen] + version = "0.2" + optional = true + + [build-dependencies.clap] + version = "4.4" + optional = true + features = [ "derive" ] + + [build-dependencies.vergen] + version = "8.2" + default-features = false + features = [ "build", "cargo", "git", "gitoxide" ] diff --git a/Dockerfile b/Dockerfile index cab044aabd..c17bad642e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -26,9 +26,8 @@ WORKDIR /src RUN build-aux/docker-bootstrap.sh RUN ./bootstrap.sh -RUN ./configure --without-manual +RUN ./configure --with-system-lua-sources --without-manual RUN make -RUN make check RUN make install DESTDIR=/pkgdir # Work around BuiltKit / buildx bug, they can’t copy to symlinks only dirs diff --git a/LICENSE b/LICENSE deleted file mode 100644 index c1a6832f12..0000000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2012-2022 Simon Cozens, Caleb Maclennan. - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000..2b0c94e73a --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,25 @@ +The MIT License (MIT) +===================== + +Copyright © `2012-2023` `Simon Cozens, Caleb Maclennan` + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the “Software”), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. diff --git a/Makefile-fonts b/Makefile-fonts index dcb7c15c61..59b35676ba 100644 --- a/Makefile-fonts +++ b/Makefile-fonts @@ -1,3 +1,5 @@ +.PHONY: fonttooling + if FONT_DOWNLOAD_TOOLS .fonts: fonttooling @@ -6,7 +8,6 @@ if FONT_DOWNLOAD_TOOLS .sources: fonttooling [ -h .sources ] || mkdir -p $@ -.PHONY: fonttooling fonttooling: $(if $(BSDTAR),,$(error Please set BSDTAR with path or `./configure --enable-developer`)) $(if $(CURL),,$(error Please set CURL with path or `./configure --enable-developer`)) @@ -33,6 +34,7 @@ TESTFONTFILES = $(DOCSFONTFILES) TESTFONTFILES += Amiri-Regular.ttf TESTFONTFILES += AmiriQuran.ttf TESTFONTFILES += AwamiNastaliq-Regular.ttf +TESTFONTFILES += FRBTaiwaneseKana.otf TESTFONTFILES += LibertinusSans-Bold.otf TESTFONTFILES += NotoNaskhArabic-Regular.ttf TESTFONTFILES += NotoSansKannada-Regular.ttf @@ -72,6 +74,10 @@ notobase = $(shell echo $(notdir $1) | sed -e 's/-.*//') .fonts/TwemojiMozilla.ttf: | .fonts $(CURL) -fsSL https://github.com/mozilla/twemoji-colr/releases/download/v0.5.1/$(notdir $@) -o $@ +.fonts/FRBTaiwaneseKana.otf: | .fonts + : $(CURL) -fsSL https://github.com/ctrlcctrlv/FRBTaiwaneseKana/releases/download/v1.1/$(notdir $@) -o $@ + $(CURL) -fsSL https://raw.githubusercontent.com/ctrlcctrlv/FRBTaiwaneseKana/5c367e9ee5aefd54b5c9c9e996705f0561fe3d15/$(notdir $@) -o $@ + # Tell make how to download font file bundles (when not downloadable individually) .sources/AwamiNastaliq-2.200.zip: | .sources diff --git a/Makefile-luarocks b/Makefile-luarocks index 54387731dd..6042815d90 100644 --- a/Makefile-luarocks +++ b/Makefile-luarocks @@ -1,4 +1,5 @@ .PHONY: installrocks + LUAMODSPEC := sile-dev-1.rockspec if !SYSTEM_LUAROCKS LUAMODLOCK := sile-dev-1.rockslock diff --git a/Makefile.am b/Makefile.am index 894c0f64b8..3f82a98ade 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1,18 +1,19 @@ ACLOCAL_AMFLAGS = -I build-aux +AM_DISTCHECK_CONFIGURE_FLAGS = --enable-developer-mode .ONESHELL: .SECONDARY: .SECONDEXPANSION: .DELETE_ON_ERROR: -if SYSTEM_LIBTEXPDF -SUBDIRS = src -else -SUBDIRS = libtexpdf src +SUBDIRS = +if !SYSTEM_LIBTEXPDF +SUBDIRS += libtexpdf endif +SUBDIRS += justenough . -licensedir = $(datarootdir)/licenses/$(TRANSFORMED_PACKAGE_NAME) docdir = $(datarootdir)/doc/$(TRANSFORMED_PACKAGE_NAME) +licensedir = $(datarootdir)/licenses/$(TRANSFORMED_PACKAGE_NAME) datadir = $(datarootdir)/$(TRANSFORMED_PACKAGE_NAME) pkgdatadir = $(datadir) @@ -45,7 +46,7 @@ include $(wildcard Makefile-distfiles) FIGURES = documentation/fig-input-to-output.pdf MANUAL := documentation/sile.pdf -SILE := $(PACKAGE_NAME) +SILELUA := $(PACKAGE_NAME)-lua if MANUAL _MANUAL = $(MANUAL) @@ -54,45 +55,108 @@ endif $(MANUAL): $(FIGURES) +BUILT_LUA_SOURCES = core/features.lua core/pathsetup.lua core/version.lua + +bin_PROGRAMS = sile +bin_SCRIPTS = sile-lua +dist_man_MANS = sile-lua.1 +sile_SOURCES = src/bin/sile.rs src/lib.rs src/cli.rs +EXTRA_sile_SOURCES = +if !EMBEDDED_RESOURCES nobase_dist_pkgdata_DATA = $(SILEDATA) $(LUALIBRARIES) -nobase_nodist_pkgdata_DATA = core/features.lua core/pathsetup.lua core/version.lua $(LUAMODULES) -dist_man_MANS = sile.1 +nobase_nodist_pkgdata_DATA = $(BUILT_LUA_SOURCES) $(LUAMODULES) +endif dist_doc_DATA = README.md CHANGELOG.md dist_pdf_DATA = $(_MANUAL) -dist_license_DATA = LICENSE lua-libraries/LICENSE-lunamark -bin_SCRIPTS = sile +dist_license_DATA = LICENSE.md EXTRA_DIST = spec tests documentation sile-dev-1.rockspec fontconfig.conf EXTRA_DIST += Makefile-distfiles -EXTRA_DIST += build-aux/action-updater.js build-aux/decore-automake.sh build-aux/git-version-gen build-aux/list-dist-files.sh +EXTRA_DIST += build-aux/action-updater.js build-aux/cargo-updater.js build-aux/config.ld build-aux/decore-automake.sh build-aux/git-version-gen build-aux/list-dist-files.sh EXTRA_DIST += Dockerfile build-aux/docker-bootstrap.sh build-aux/docker-fontconfig.conf hooks/build -EXTRA_DIST += default.nix flake.nix flake.lock shell.nix +EXTRA_DIST += default.nix flake.nix flake.lock shell.nix build-aux/pkg.nix EXTRA_DIST += package.json # imported by both Nix and Docker EXTRA_DIST += $(MANUAL) $(FIGURES) +EXTRA_DIST += src/embed.rs.in +EXTRA_DIST += sil.abnf + +BUILT_SOURCES = $(BUILT_LUA_SOURCES) Makefile-distfiles -BUILT_SOURCES = .version core/features.lua core/pathsetup.lua core/version.lua Makefile-distfiles +CLEANFILES = $(MANUAL) -CLEANFILES = $(bin_SCRIPTS) $(dist_man_MANS) $(BUILT_SOURCES) $(DEPFILES) $(ACTUALS) $(TESTPDFS) $(MANUAL) $(_BUILT_SUBDIRS) .version-prev +DISTCLEANFILES = @AMINCLUDE@ + +# A classical use of the autoconf-archive include macro would expand +# INC_AMINCLUDE here, but the perl script that inlines include statements +# runs before the automake that organizes logic and performs substitution. +# Consequentially with a substitution here it becomes impossible to use +# automake conditionals and substitutions in the included Makefile fragments. +# By entering the expanded value directly we are ready in time for the inlining +# functionality and hence can use conditionals in included makefile fragments. +include $(top_srcdir)/aminclude.am Makefile-distfiles: $(wildcard .version .tarball-version) | $(LUAMODLOCK) $(SHELL) build-aux/list-dist-files.sh > $@ -_BRANCH_REF != $(AWK) '{print ".git/" $$2}' .git/HEAD 2>/dev/null ||: - -.version: $(_BRANCH_REF) - @if [ -e "$(srcdir)/.tarball-version" ]; then \ - printf "$(VERSION)" > $@; \ - else \ - touch "$@-prev"; \ - if [ -e "$@" ]; then \ - cp "$@" "$@-prev"; \ - fi; \ - ./build-aux/git-version-gen "$(srcdir)/.tarball-version" > $@; \ - cmp -s "$@" "$@-prev" || ( autoreconf configure.ac --force && build-aux/decore-automake.sh ); \ - fi +if EMBEDDED_RESOURCES +_EMBEDDED_SOURCES = src/embed.rs src/embed-includes.rs +nodist_sile_SOURCES = $(_EMBEDDED_SOURCES) +BUILT_SOURCES += $(_EMBEDDED_SOURCES) +CLEANFILES += $(_EMBEDDED_SOURCES) +$(CARGO_BIN): justenough/.libs/fontmetrics.a +$(CARGO_BIN): justenough/.libs/justenoughfontconfig.a +$(CARGO_BIN): justenough/.libs/justenoughharfbuzz.a +$(CARGO_BIN): justenough/.libs/justenoughicu.a +$(CARGO_BIN): justenough/.libs/justenoughlibtexpdf.a +$(CARGO_BIN): justenough/.libs/svg.a +$(CARGO_BIN): libtexpdf/.libs/libtexpdf.a + +src/embed-includes.rs: Makefile-distfiles + { + echo $(BUILT_LUA_SOURCES) + $(GREP) -E '^(SILEDATA|LUALIBRARIES|LUAMODULES) = ' $< + } | + $(SED) -E -e 's/^.* = //;s/ /\n/g' | + while read file; do + echo "#[include = \"$${file}\"]" + done > $@ + +src/embed.rs: src/embed.rs.in src/embed-includes.rs + $(SED) \ + -e '/@INCLUDE_EMDED_INCLUDES@/r $(word 2,$^)' \ + -e '/@INCLUDE_EMDED_INCLUDES@/d' \ + $< > $@ +endif EMBEDDED_RESOURCES + +if LUAJIT +MLUAVER = luajit +else +MLUAVER = lua$(LUA_SHORT_VERSION) +endif +CARGO_FEATURE_ARGS = --features $(MLUAVER) + +if !SYSTEM_LUA_SOURCES +CARGO_FEATURE_ARGS += --features vendored +endif -dist-hook: $(MANUAL) +if EMBEDDED_RESOURCES +CARGO_FEATURE_ARGS += --features static +endif + +if FONT_VARIATIONS +CARGO_FEATURE_ARGS += --features variations +endif + +DEPDIR := .deps +LOCALFONTS := FONTCONFIG_FILE=$(PWD)/fontconfig.conf +LOCALPATHS := SILE_PATH="$(PWD);libtexpdf/.libs;justenough/.libs" +SILEFLAGS ?= -m $(DEPDIR)/$(basename $@).d -d versions -f fontconfig +DRAFT ?= false + +dist-hook: $(MANUAL) dist-hook-distfiles + +.PHONY: dist-hook-distfiles +dist-hook-distfiles: cd $(distdir) - printf "$(VERSION)" > .tarball-version $(SED) -i -e '/^LUAMODULES =/s/=.*/=/' Makefile-distfiles $(top_srcdir)/build-aux/decore-automake.sh $(SED) -i -e '/^LUAMODULES/d;/^\tlua_modules/d' Makefile.in @@ -100,76 +164,49 @@ dist-hook: $(MANUAL) # Whether to force tests to run from scratch CLEAN ?= -RELTYPE ?= - -.PHONY: tagrelease -tagrelease: - test -z $$($(GIT) tag --points-at HEAD) || exit 0 # end if we are already on a release tag - $(GIT) diff-index --quiet --cached HEAD || exit 1 # die if anything staged but not committed - $(GIT) diff-files --quiet || exit 1 # die if any tracked files have unstagged changes - npm run release -- $(and $(RELTYPE),--release-as $(RELTYPE)) - -.PHONY: prerelease -prerelease: test docs update_libtexpdf - -.PHONY: release-preview -release-preview: - npm run release -- --dry-run $(and $(RELTYPE),--release-as $(RELTYPE)) - -.PHONY: release -release: tagrelease - dist: sile-$(VERSION).pdf sile-$(VERSION).md sile-$(VERSION).pdf: $(MANUAL) - cp $(MANUAL) $@ + $(INSTALL) $(MANUAL) $@ sile-%.md: CHANGELOG.md $(SED) -e '/\.\.\.v$*/,/\.\.\.v/!d' CHANGELOG.md | \ $(SED) -e '1,3d;N;$$!P;$$!D;$$d' > $@ -.PHONY: update_libtexpdf -update_libtexpdf: - $(GIT) diff-index --quiet --cached HEAD || exit 1 # die if anything already staged - $(GIT) submodule update --init --remote -- libtexpdf - $(NIX) flake lock --override-input libtexpdf-src github:sile-typesetter/libtexpdf/$(shell $(GIT) submodule status -- libtexpdf | awk '{print $$1}') - $(GIT) add -- libtexpdf flake.lock - $(GIT) diff-index --quiet --cached HEAD || $(GIT) commit -m "chore(build): Pin latest libtexpdf library submodule" - -DEPDIR := .deps -REGRESSIONSCRIPT := ./tests/regressions.pl -LOCALTESTFONTS := FONTCONFIG_FILE=$(PWD)/fontconfig.conf -SILEFLAGS ?= -m $(DEPDIR)/$(basename $@).d -d versions -f fontconfig -BUSTEDFLAGS ?= $(and $(SILE_COVERAGE),-c) - -TESTPDFS = $(addsuffix .pdf,$(basename $(TESTSRCS))) -EXPECTEDS ?= $(filter $(addsuffix .expected,$(basename $(TESTSRCS))),$(TESTEXPECTS)) -ACTUALS = $(addsuffix .actual,$(basename $(EXPECTEDS))) - check: selfcheck .PHONY: selfcheck -selfcheck: | $(_BUILT_SUBDIRS) +selfcheck: | $(bin_PROGRAMS) $(_BUILT_SUBDIRS) output=$$(mktemp -t selfcheck-XXXXXX.pdf) - trap 'rm -f $$output' EXIT HUP TERM - echo "foo" | ./$(SILE) -o $$output - + trap '$(RM) $$output' EXIT HUP TERM + echo "foo" | $(LOCALPATHS) ./$(bin_PROGRAMS) -o $$output - $(PDFINFO) $$output | $(GREP) "SILE v$(VERSION)" .PHONY: docs -docs: $(MANUAL) +docs: $(_MANUAL) lua-api-docs + +.PHONY: lua-api-docs +lua-api-docs: lua-api-docs/index.html + +lua-api-docs/index.html: build-aux/config.ld + $(LDOC) -c build-aux/config.ld . + +.PHONY: docs-figures +docs-figures: $(FIGURES) # This is a monkey patch to figure out how many passes we have to to to # garantee the TOC is up to date, simplify when #230 is fixed. hastoc = [ -f $(subst .pdf,.toc,$@) ] && echo true || echo false pages = $(PDFINFO) $@ | $(AWK) '$$1 == "Pages:" {print $$2}' || echo 0 -silepass = $(LOCALTESTFONTS) ./$(SILE) $(SILEFLAGS) $< -o $@ && pg0=$${pg} pg=$$($(pages)) || false +localsile = $(LOCALFONTS) $(LOCALPATHS) ./$(bin_PROGRAMS) $(SILEFLAGS) +silepass = $(localsile) $< -o $@ && pg0=$${pg} pg=$$($(pages)) || false define runsile = set -e pg=$$($(pages)) hadtoc=$$($(hastoc)) - mkdir -p $(DEPDIR)/$$(dirname $@) + $(MKDIR_P) $(DEPDIR)/$$(dirname $@) $(silepass) export -n SILE_COVERAGE - if $(hastoc); then + if $$($(hastoc)) && ! $(DRAFT); then $${hadtoc} || $(silepass) [ "$${pg}" = "$${pg0}" ] || $(silepass) fi @@ -182,15 +219,37 @@ _DOCS_DEPS = $(and $$(filter documentation/%,$@),$(addprefix .fonts/,$(DOCSFONTF # TODO: remove _BUILT_SUBDIRS hack and replace it with something sensible when # these subdirs don't do crazy things like copying files outside of their own trees! _BUILT_SUBDIRS = .built-subdirs -_SUBDIR_TELLS = justenoughfontconfig.so justenoughharfbuzz.so justenoughicu.so justenoughlibtexpdf.so libtexpdf/.libs/libtexpdf.so.0.0.0 +_SUBDIR_TELLS = + +if SHARED +_SUBDIR_TELLS += justenough/.libs/fontmetrics.$(SHARED_LIB_EXT) \ + justenough/.libs/justenoughfontconfig.$(SHARED_LIB_EXT) \ + justenough/.libs/justenoughharfbuzz.$(SHARED_LIB_EXT) \ + justenough/.libs/justenoughicu.$(SHARED_LIB_EXT) \ + justenough/.libs/justenoughlibtexpdf.$(SHARED_LIB_EXT) \ + justenough/.libs/svg.$(SHARED_LIB_EXT) \ + libtexpdf/.libs/libtexpdf.$(SHARED_LIB_EXT).0.0.0 +endif + +if STATIC +_SUBDIR_TELLS += justenough/.libs/fontmetrics.a \ + justenough/.libs/justenoughfontconfig.a \ + justenough/.libs/justenoughharfbuzz.a \ + justenough/.libs/justenoughicu.a \ + justenough/.libs/justenoughlibtexpdf.a \ + justenough/.libs/svg.a \ + libtexpdf/.libs/libtexpdf.a +endif + $(_BUILT_SUBDIRS): $(_SUBDIR_TELLS) touch $@ +CLEANFILES += $(_BUILT_SUBDIRS) + $(_SUBDIR_TELLS): $(MAKE) $(AM_MAKEFLAGS) all-recursive -# $(error Running `make install`, `make dist`, or other end-game targets before `make all` unspported.) -patterndeps = $(_FORCED) $(_TEST_DEPS) $(_DOCS_DEPS) | $(DEPDIRS) $(LUAMODLOCK) $(_BUILT_SUBDIRS) +patterndeps = $(_FORCED) $(_TEST_DEPS) $(_DOCS_DEPS) | $(bin_PROGRAMS) $(DEPDIRS) $(LUAMODLOCK) $(_BUILT_SUBDIRS) %.pdf: %.sil $$(patterndeps) $(runsile) @@ -208,13 +267,46 @@ patterndeps = $(_FORCED) $(_TEST_DEPS) $(_DOCS_DEPS) | $(DEPDIRS) $(LUAMODLOCK) .PHONY: force force: ; -PHONY_DEVELOPER_TARGETS = regressions test lint luarocks-lint luacheck busted coverage benchmark compare update_expecteds regression_previews docker docker-dep-check docker-ghcr-to-hub docker-build-push gource.webm +PHONY_DEVELOPER_TARGETS = busted compare coverage \ + docker-dep-check docker-ghcr-to-hub gource.webm lint luacheck luarocks-lint \ + prerelease regression_previews regressions release release-preview tagrelease \ + test update_expecteds update_libtexpdf .PHONY: $(PHONY_DEVELOPER_TARGETS) -if DEVELOPER +if DEVELOPER_MODE + +RELTYPE ?= + +tagrelease: + test -z $$($(GIT) tag --points-at HEAD) || exit 0 # end if we are already on a release tag + $(GIT) diff-index --quiet --cached HEAD || exit 1 # die if anything staged but not committed + $(GIT) diff-files --quiet || exit 1 # die if any tracked files have unstagged changes + $(NPM) run release -- $(and $(RELTYPE),--release-as $(RELTYPE)) + +prerelease: test docs update_libtexpdf + +release-preview: + $(NPM) run release -- --dry-run $(and $(RELTYPE),--release-as $(RELTYPE)) + +release: tagrelease + +update_libtexpdf: + $(GIT) diff-index --quiet --cached HEAD || exit 1 # die if anything already staged + $(GIT) submodule update --init --remote -- libtexpdf + $(NIX) flake lock --override-input libtexpdf-src github:sile-typesetter/libtexpdf/$(shell $(GIT) submodule status -- libtexpdf | awk '{print $$1}') + $(GIT) add -- libtexpdf flake.lock + $(GIT) diff-index --quiet --cached HEAD || $(GIT) commit -m "chore(build): Pin latest libtexpdf library submodule" + +TESTPDFS = $(addsuffix .pdf,$(basename $(TESTSRCS))) +EXPECTEDS ?= $(filter $(addsuffix .expected,$(basename $(TESTSRCS))),$(TESTEXPECTS)) +ACTUALS = $(addsuffix .actual,$(basename $(EXPECTEDS))) +CLEANFILES += $(TESTPDFS) $(ACTUALS) + +REGRESSIONSCRIPT := ./tests/regressions.pl +BUSTEDFLAGS ?= regressions: $(TESTSRCS) $(ACTUALS) - $(LOCALTESTFONTS) $(REGRESSIONSCRIPT) $(TESTSRCS) + $(LOCALFONTS) $(REGRESSIONSCRIPT) $(TESTSRCS) test: regressions busted @@ -226,7 +318,7 @@ luarocks-lint: $(LUAMODSPEC) luacheck: $(LUACHECK) -j$(shell nproc) -q . -busted: $(SILE) $(addprefix .fonts/,$(TESTFONTFILES)) $(BUSTEDSPECS) +busted: $(SILELUA) $(addprefix .fonts/,$(TESTFONTFILES)) $(BUSTEDSPECS) set -f; IFS=';' packagepath=(./{,lua-libraries/}?{,/init}.lua) packagecpath=(./{,core/,{libtexpdf,justenough}/.libs/}?.$(SHARED_LIB_EXT)) @@ -238,7 +330,7 @@ endif $(LOCALFONTS) $(BUSTED) --lua=$(LUA) --lpath="'$${packagepath[*]};;'" --cpath="'$${packagecpath[*]};;'" $(BUSTEDFLAGS) . coverage: export SILE_COVERAGE=1 -coverage: BUSTEDFLAGS = -c +coverage: BUSTEDFLAGS += -c coverage: regression_previews busted HEADSHA ?= HEAD @@ -249,47 +341,45 @@ _BASESHA ?= $(shell test -e .git && $(GIT) rev-parse --short=7 $(BASESHA)) clean-recursive: clean-tests clean-tests: - rm -rf tests/*.actual - rm -rf $(DEPDIR)/tests/* + $(RM) -r tests/*.actual + $(RM) -r $(DEPDIR)/tests/* clean-recursive: clean-deps clean-deps: - rm -rf $(DEPDIR) - -time-%.json: benchmark-%/time.json - cp $< $@ + $(RM) -r $(DEPDIR) update_expecteds: $(EXPECTEDS) tests/%.expected: tests/%.sil $$(patterndeps) - $(LOCALTESTFONTS) ./$(SILE) $(SILEFLAGS) -b debug $< -o $@ + $(localsile) -b debug $< -o $@ tests/%.expected: tests/%.xml $$(patterndeps) - $(LOCALTESTFONTS) ./$(SILE) $(SILEFLAGS) -b debug $< -o $@ + $(localsile) -b debug $< -o $@ tests/%.expected: tests/%.nil $$(patterndeps) - $(LOCALTESTFONTS) ./$(SILE) $(SILEFLAGS) -b debug $< -o $@ + $(localsile) -b debug $< -o $@ regression_previews: $(TESTPREVIEWS) tests/%.actual: tests/%.sil $$(patterndeps) - -$(if $(CLEAN),rm -f $@,:) - $(LOCALTESTFONTS) ./$(SILE) $(SILEFLAGS) -b debug $< -o $@ + -$(if $(CLEAN),$(RM) $@,:) + $(localsile) -b debug $< -o $@ tests/%.actual: tests/%.xml $$(patterndeps) - -$(if $(CLEAN),rm -f $@,:) - $(LOCALTESTFONTS) ./$(SILE) $(SILEFLAGS) -b debug $< -o $@ + -$(if $(CLEAN),$(RM) $@,:) + $(localsile) -b debug $< -o $@ tests/%.actual: tests/%.nil $$(patterndeps) - -$(if $(CLEAN),rm -f $@,:) - $(LOCALTESTFONTS) ./$(SILE) $(SILEFLAGS) -b debug $< -o $@ + -$(if $(CLEAN),$(RM) $@,:) + $(localsile) -b debug $< -o $@ DEPFILES = $(addsuffix .d,$(addprefix $(DEPDIR)/,$(basename $(TESTSRCS) $(MANUAL)))) DEPDIRS = $(sort $(dir $(DEPFILES))) +CLEANFILES += $(DEPFILES) $(DEPDIRS): | Makefile-distfiles - mkdir -p $@ + $(MKDIR_P) $@ $(DEPFILES): | $(DEPDIRS) @@ -302,62 +392,44 @@ export DOCKER_REGISTRY ?= docker.io export DOCKER_REPO ?= siletypesetter/$(TRANSFORMED_PACKAGE_NAME) export DOCKER_TAG ?= HEAD -docker: Dockerfile hooks/build .version - ./hooks/build $(VERSION) - docker-dep-check: .docker_deps .aur_deps - diff -u $^ + $(DIFF) -u $^ -CLEANFILES += .docker_deps .docker_deps: hooks/build $(SHELL) -c 'source <($(SED) -nE "/^(RUN|'"'"')/{s/.*=/echo /;p}" $<)' | \ - tr ' ' '\n' | \ - sort > $@ + $(TR) ' ' '\n' | \ + $(SORT) > $@ + +CLEANFILES += .docker_deps -CLEANFILES += .aur_deps .aur_deps: - curl -Ls 'https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO?h=sile-git' | \ + $(CURL) -Ls 'https://aur.archlinux.org/cgit/aur.git/plain/.SRCINFO?h=sile-git' | \ $(SED) -nE '/\bdepends =/{s/.*= //;p}' | \ $(GREP) -vxE '(lua-.*|.*\.so|git|glibc)' | \ - sort > $@ - -define docker_push = - test -z "$(DOCKER_PAT)" || \ - docker login https://$(DOCKER_REGISTRY) -u $(DOCKER_USERNAME) -p $(DOCKER_PAT) - docker push $(DOCKER_REGISTRY)/$(DOCKER_REPO):$(DOCKER_TAG) - if [[ "$(DOCKER_TAG)" == v*.*.* ]]; then \ - tag=$(DOCKER_TAG) ; \ - docker tag $(DOCKER_REPO):$(DOCKER_TAG) $(DOCKER_REGISTRY)/$(DOCKER_REPO):latest ; \ - docker tag $(DOCKER_REPO):$(DOCKER_TAG) $(DOCKER_REGISTRY)/$(DOCKER_REPO):$${tag//.*} ; \ - docker push $(DOCKER_REGISTRY)/$(DOCKER_REPO):latest ; \ - docker push $(DOCKER_REGISTRY)/$(DOCKER_REPO):$${tag//.*} ; \ - fi -endef + $(SORT) > $@ -docker-ghcr-to-hub: - docker pull $(GHCR_REGISTRY)/$(GHCR_REPO):$(DOCKER_TAG) - docker tag $(GHCR_REGISTRY)/$(GHCR_REPO):$(DOCKER_TAG) $(DOCKER_REGISTRY)/$(DOCKER_REPO):$(DOCKER_TAG) - $(docker_push) +CLEANFILES += .aur_deps -docker-build-push: docker - docker tag $(DOCKER_REPO):$(DOCKER_TAG) $(DOCKER_REGISTRY)/$(DOCKER_REPO):$(DOCKER_TAG) +docker-ghcr-to-hub: + $(DOCKER) pull $(GHCR_REGISTRY)/$(GHCR_REPO):$(DOCKER_TAG) + $(DOCKER) tag $(GHCR_REGISTRY)/$(GHCR_REPO):$(DOCKER_TAG) $(DOCKER_REGISTRY)/$(DOCKER_REPO):$(DOCKER_TAG) $(docker_push) gource.webm: - mkdir -p /tmp/gravatars + $(MKDIR_P) /tmp/gravatars magick documentation/sile-logo.pdf[0] -density 300 -colorspace RGB -negate -resize 50% /tmp/sile-logo.jpg $(GIT) log --pretty=format:"%an—%ae" | \ - sort -u | \ + $(SORT) -u | \ while IFS=— read name email; do \ test -f "/tmp/gravatars/$$name.jpg" || \ - curl -S "https://www.gravatar.com/avatar/$$(echo -n $$email | md5sum | cut -d\ -f1)?d=identicon&s=256" -o "/tmp/gravatars/$$name.jpg" ; \ + $(CURL) -S "https://www.gravatar.com/avatar/$$(echo -n $$email | md5sum | cut -d\ -f1)?d=identicon&s=256" -o "/tmp/gravatars/$$name.jpg" ; \ done ;\ gource -a 0.2 -s 0.2 -i 0 --logo /tmp/sile-logo.jpg -b 000000 --max-file-lag 5 --hide filenames --date-format '%Y-%m-%d' --user-image-dir /tmp/gravatars --user-filter simoncozens --key -1920x1080 -o - | \ ffmpeg -y -r 60 -f image2pipe -vcodec ppm -i - -vcodec libvpx -b 10000K $@ -else +else !DEVELOPER_MODE $(PHONY_DEVELOPER_TARGETS): @: $(error "Please reconfigure using --enable-developer to use developer tooling") -endif +endif !DEVELOPER_MODE diff --git a/README.md b/README.md index 40abe9b95e..e25401e5b5 100644 --- a/README.md +++ b/README.md @@ -234,7 +234,7 @@ $ ./bootstrap.sh If you just plan on installing and using SILE, the default configure options (plus any Lua related options discussed above) should be fine. If you plan on developing SILE itself (whether to just tinker with it for your own use or contribute upstream) there is one particularly useful configuration option. -You can add `--enable-developer` will set the 'installed data' directory to the source location which will enable the compiled binary to run directly from the source directory without being installed at all. +You can add `--enable-developer-mode` will set the 'installed data' directory to the source location which will enable the compiled binary to run directly from the source directory without being installed at all. Additionally it will enable checks for tooling we expect SILE contributors to have such as tools used for testing. Using this options also enables a number of targets that wouldn’t normally be needed by end users such as `make regressions`. @@ -245,7 +245,7 @@ $ ./configure $ make ``` -If you just want to mess with SILE locally you can stop here (especially if you used `--enable-developer`). +If you just want to mess with SILE locally you can stop here (especially if you used `--enable-developer-mode`). However to actually install, you will need to run the installation command with system permissions. ```console diff --git a/bootstrap.sh b/bootstrap.sh index 3e2165b3e8..dc49b2827f 100755 --- a/bootstrap.sh +++ b/bootstrap.sh @@ -35,6 +35,16 @@ else ./build-aux/git-version-gen .tarball-version > .version fi +# Autoreconf uses a perl script to inline includes from Makefile.am into +# Makefile.in before ./configure is ever run even once ... which typically means +# AX_AUTOMAKE_MACROS forfeit access to substitutions or conditional logic +# because they enter the picture after those steps. We're intentially using the +# expanded value of @INC_AMINCLUDE@ directly so the include will be inlined. To +# bootstrap we must pre-seed an empty file to avoid a 'file not found' error on +# first run. Subsequently running ./configure will generate the correct content +# based on the configuration flags and also get re-inlined into Makefile.in. +touch aminclude.am + autoreconf --install # See discussion in https://github.com/sile-typesetter/sile/issues/82 and diff --git a/build-aux/adl_recursive_eval.m4 b/build-aux/adl_recursive_eval.m4 index 71f2aeb247..4f9a17a54e 100644 --- a/build-aux/adl_recursive_eval.m4 +++ b/build-aux/adl_recursive_eval.m4 @@ -3,13 +3,16 @@ dnl ================================= dnl Interpolate the VALUE in loop until it doesn't change, dnl and set the result to $RESULT. dnl WARNING: It's easy to get an infinite loop with some unsane input. -AC_DEFUN([adl_RECURSIVE_EVAL], -[_lcl_receval="$1" -$2=`(test "x$prefix" = xNONE && prefix="$ac_default_prefix" - test "x$exec_prefix" = xNONE && exec_prefix="${prefix}" - _lcl_receval_old='' - while test "[$]_lcl_receval_old" != "[$]_lcl_receval"; do - _lcl_receval_old="[$]_lcl_receval" - eval _lcl_receval="\"[$]_lcl_receval\"" - done - echo "[$]_lcl_receval")`]) \ No newline at end of file +AC_DEFUN([adl_RECURSIVE_EVAL], [ + _lcl_receval="$1" + $2=`( + test "x$prefix" = xNONE && prefix="$ac_default_prefix" + test "x$exec_prefix" = xNONE && exec_prefix="${prefix}" + _lcl_receval_old='' + while test "[$]_lcl_receval_old" != "[$]_lcl_receval"; do + _lcl_receval_old="[$]_lcl_receval" + eval _lcl_receval="\"[$]_lcl_receval\"" + done + echo "[$]_lcl_receval" + )` +]) diff --git a/build-aux/ax_add_am_macro.m4 b/build-aux/ax_add_am_macro.m4 new file mode 100644 index 0000000000..3962002bf0 --- /dev/null +++ b/build-aux/ax_add_am_macro.m4 @@ -0,0 +1,29 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_add_am_macro.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_ADD_AM_MACRO([RULE]) +# +# DESCRIPTION +# +# Adds the specified rule to $AMINCLUDE. This macro will only work +# properly with implementations of Make which allow include statements. +# See also AX_ADD_AM_MACRO_STATIC. +# +# LICENSE +# +# Copyright (c) 2009 Tom Howard +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 10 + +AC_DEFUN([AX_ADD_AM_MACRO],[ + AC_REQUIRE([AX_AM_MACROS]) + AX_APPEND_TO_FILE([$AMINCLUDE],[$1]) +]) diff --git a/build-aux/ax_am_macros.m4 b/build-aux/ax_am_macros.m4 new file mode 100644 index 0000000000..36c3ab6a27 --- /dev/null +++ b/build-aux/ax_am_macros.m4 @@ -0,0 +1,44 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_am_macros.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_AM_MACROS +# +# DESCRIPTION +# +# Adds support for macros that create Make rules. You must manually add +# the following line +# +# @INC_AMINCLUDE@ +# +# to your Makefile.in (or Makefile.am if you use Automake) files. +# +# LICENSE +# +# Copyright (c) 2009 Tom Howard +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 11 + +AC_DEFUN([AX_AM_MACROS], +[ +AC_MSG_NOTICE([adding automake macro support]) +AMINCLUDE="aminclude.am" +AC_SUBST(AMINCLUDE) +AC_MSG_NOTICE([creating $AMINCLUDE]) +AMINCLUDE_TIME=`LC_ALL=C date` +AX_PRINT_TO_FILE([$AMINCLUDE],[[ +# generated automatically by configure from AX_AUTOMAKE_MACROS +# on $AMINCLUDE_TIME + +]]) + +INC_AMINCLUDE="include \$(top_builddir)/$AMINCLUDE" +AC_SUBST(INC_AMINCLUDE) +]) diff --git a/build-aux/ax_append_to_file.m4 b/build-aux/ax_append_to_file.m4 new file mode 100644 index 0000000000..fca5708372 --- /dev/null +++ b/build-aux/ax_append_to_file.m4 @@ -0,0 +1,27 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_append_to_file.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_APPEND_TO_FILE([FILE],[DATA]) +# +# DESCRIPTION +# +# Appends the specified data to the specified file. +# +# LICENSE +# +# Copyright (c) 2008 Tom Howard +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 9 + +AC_DEFUN([AX_APPEND_TO_FILE],[ +AC_REQUIRE([AX_FILE_ESCAPES]) +printf "%s" "$2" >> "$1" +]) diff --git a/build-aux/ax_build_date_epoch.m4 b/build-aux/ax_build_date_epoch.m4 index dbecb067a8..81ac87c7b4 100644 --- a/build-aux/ax_build_date_epoch.m4 +++ b/build-aux/ax_build_date_epoch.m4 @@ -53,18 +53,15 @@ #serial 2 -AC_DEFUN([AX_BUILD_DATE_EPOCH], -[dnl -AC_MSG_CHECKING([for build time]) -ax_date_fmt="m4_default($2,%s)" -AS_IF([test x"$SOURCE_DATE_EPOCH" = x], - [$1=`date "+$ax_date_fmt"`], - [ax_build_date=`date -u -d "@$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null \ - || date -u -r "$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null` - AS_IF([test x"$ax_build_date" = x], - [m4_ifval([$3], - [$3], - [AC_MSG_ERROR([malformed SOURCE_DATE_EPOCH])])], - [$1=$ax_build_date])]) -AC_MSG_RESULT([$$1]) +AC_DEFUN([AX_BUILD_DATE_EPOCH], [dnl + AC_MSG_CHECKING([for build time]) + ax_date_fmt="m4_default($2,%s)" + AS_IF([test x"$SOURCE_DATE_EPOCH" = x], + [$1=`date "+$ax_date_fmt"`], + [ax_build_date=`date -u -d "@$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null \ + || date -u -r "$SOURCE_DATE_EPOCH" "+$ax_date_fmt" 2>/dev/null` + AS_IF([test x"$ax_build_date" = x], + [m4_ifval([$3], [$3], [AC_MSG_ERROR([malformed SOURCE_DATE_EPOCH])])], + [$1=$ax_build_date])]) + AC_MSG_RESULT([$$1]) ])dnl AX_BUILD_DATE_EPOCH diff --git a/build-aux/ax_file_escapes.m4 b/build-aux/ax_file_escapes.m4 new file mode 100644 index 0000000000..a86fdc326b --- /dev/null +++ b/build-aux/ax_file_escapes.m4 @@ -0,0 +1,30 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_file_escapes.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_FILE_ESCAPES +# +# DESCRIPTION +# +# Writes the specified data to the specified file. +# +# LICENSE +# +# Copyright (c) 2008 Tom Howard +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 8 + +AC_DEFUN([AX_FILE_ESCAPES],[ +AX_DOLLAR="\$" +AX_SRB="\\135" +AX_SLB="\\133" +AX_BS="\\\\" +AX_DQ="\"" +]) diff --git a/build-aux/ax_lua.m4 b/build-aux/ax_lua.m4 index 085dc49ad2..12ce133e64 100644 --- a/build-aux/ax_lua.m4 +++ b/build-aux/ax_lua.m4 @@ -1,30 +1,230 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_lua.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PROG_LUA[([MINIMUM-VERSION], [TOO-BIG-VERSION], [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_HEADERS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_LIBS[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# AX_LUA_READLINE[([ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND])] +# +# DESCRIPTION +# +# Detect a Lua interpreter, optionally specifying a minimum and maximum +# version number. Set up important Lua paths, such as the directories in +# which to install scripts and modules (shared libraries). +# +# Also detect Lua headers and libraries. The Lua version contained in the +# header is checked to match the Lua interpreter version exactly. When +# searching for Lua libraries, the version number is used as a suffix. +# This is done with the goal of supporting multiple Lua installs (5.1, +# 5.2, 5.3, and 5.4 side-by-side). +# +# A note on compatibility with previous versions: This file has been +# mostly rewritten for serial 18. Most developers should be able to use +# these macros without needing to modify configure.ac. Care has been taken +# to preserve each macro's behavior, but there are some differences: +# +# 1) AX_WITH_LUA is deprecated; it now expands to the exact same thing as +# AX_PROG_LUA with no arguments. +# +# 2) AX_LUA_HEADERS now checks that the version number defined in lua.h +# matches the interpreter version. AX_LUA_HEADERS_VERSION is therefore +# unnecessary, so it is deprecated and does not expand to anything. +# +# 3) The configure flag --with-lua-suffix no longer exists; the user +# should instead specify the LUA precious variable on the command line. +# See the AX_PROG_LUA description for details. +# +# Please read the macro descriptions below for more information. +# +# This file was inspired by Andrew Dalke's and James Henstridge's +# python.m4 and Tom Payne's, Matthieu Moy's, and Reuben Thomas's ax_lua.m4 +# (serial 17). Basically, this file is a mash-up of those two files. I +# like to think it combines the best of the two! +# +# AX_PROG_LUA: Search for the Lua interpreter, and set up important Lua +# paths. Adds precious variable LUA, which may contain the path of the Lua +# interpreter. If LUA is blank, the user's path is searched for an +# suitable interpreter. +# +# Optionally a LUAJIT option may be set ahead of time to look for and +# validate a LuaJIT install instead of PUC Lua. Usage might look like: +# +# AC_ARG_WITH(luajit, [AS_HELP_STRING([--with-luajit], +# [Prefer LuaJIT over PUC Lua, even if the latter is newer. Default: no]) +# ]) +# AM_CONDITIONAL([LUAJIT], [test "x$with_luajit" != 'xno']) +# +# If MINIMUM-VERSION is supplied, then only Lua interpreters with a +# version number greater or equal to MINIMUM-VERSION will be accepted. If +# TOO-BIG-VERSION is also supplied, then only Lua interpreters with a +# version number greater or equal to MINIMUM-VERSION and less than +# TOO-BIG-VERSION will be accepted. +# +# The Lua version number, LUA_VERSION, is found from the interpreter, and +# substituted. LUA_PLATFORM is also found, but not currently supported (no +# standard representation). +# +# Finally, the macro finds four paths: +# +# luadir Directory to install Lua scripts. +# pkgluadir $luadir/$PACKAGE +# luaexecdir Directory to install Lua modules. +# pkgluaexecdir $luaexecdir/$PACKAGE +# +# These paths are found based on $prefix, $exec_prefix, Lua's +# package.path, and package.cpath. The first path of package.path +# beginning with $prefix is selected as luadir. The first path of +# package.cpath beginning with $exec_prefix is used as luaexecdir. This +# should work on all reasonable Lua installations. If a path cannot be +# determined, a default path is used. Of course, the user can override +# these later when invoking make. +# +# luadir Default: $prefix/share/lua/$LUA_VERSION +# luaexecdir Default: $exec_prefix/lib/lua/$LUA_VERSION +# +# These directories can be used by Automake as install destinations. The +# variable name minus 'dir' needs to be used as a prefix to the +# appropriate Automake primary, e.g. lua_SCRIPS or luaexec_LIBRARIES. +# +# If an acceptable Lua interpreter is found, then ACTION-IF-FOUND is +# performed, otherwise ACTION-IF-NOT-FOUND is preformed. If ACTION-IF-NOT- +# FOUND is blank, then it will default to printing an error. To prevent +# the default behavior, give ':' as an action. +# +# AX_LUA_HEADERS: Search for Lua headers. Requires that AX_PROG_LUA be +# expanded before this macro. Adds precious variable LUA_INCLUDE, which +# may contain Lua specific include flags, e.g. -I/usr/include/lua5.1. If +# LUA_INCLUDE is blank, then this macro will attempt to find suitable +# flags. +# +# LUA_INCLUDE can be used by Automake to compile Lua modules or +# executables with embedded interpreters. The *_CPPFLAGS variables should +# be used for this purpose, e.g. myprog_CPPFLAGS = $(LUA_INCLUDE). +# +# This macro searches for the header lua.h (and others). The search is +# performed with a combination of CPPFLAGS, CPATH, etc, and LUA_INCLUDE. +# If the search is unsuccessful, then some common directories are tried. +# If the headers are then found, then LUA_INCLUDE is set accordingly. +# +# The paths automatically searched are: +# +# * /usr/include/luaX.Y +# * /usr/include/lua/X.Y +# * /usr/include/luaXY +# * /usr/local/include/luaX.Y +# * /usr/local/include/lua-X.Y +# * /usr/local/include/lua/X.Y +# * /usr/local/include/luaXY +# +# (Where X.Y is the Lua version number, e.g. 5.1.) +# +# The Lua version number found in the headers is always checked to match +# the Lua interpreter's version number. Lua headers with mismatched +# version numbers are not accepted. +# +# If headers are found, then ACTION-IF-FOUND is performed, otherwise +# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then +# it will default to printing an error. To prevent the default behavior, +# set the action to ':'. +# +# AX_LUA_LIBS: Search for Lua libraries. Requires that AX_PROG_LUA be +# expanded before this macro. Adds precious variable LUA_LIB, which may +# contain Lua specific linker flags, e.g. -llua5.1. If LUA_LIB is blank, +# then this macro will attempt to find suitable flags. +# +# LUA_LIB can be used by Automake to link Lua modules or executables with +# embedded interpreters. The *_LIBADD and *_LDADD variables should be used +# for this purpose, e.g. mymod_LIBADD = $(LUA_LIB). +# +# This macro searches for the Lua library. More technically, it searches +# for a library containing the function lua_load. The search is performed +# with a combination of LIBS, LIBRARY_PATH, and LUA_LIB. +# +# If the search determines that some linker flags are missing, then those +# flags will be added to LUA_LIB. +# +# If libraries are found, then ACTION-IF-FOUND is performed, otherwise +# ACTION-IF-NOT-FOUND is performed. If ACTION-IF-NOT-FOUND is blank, then +# it will default to printing an error. To prevent the default behavior, +# set the action to ':'. +# +# AX_LUA_READLINE: Search for readline headers and libraries. Requires the +# AX_LIB_READLINE macro, which is provided by ax_lib_readline.m4 from the +# Autoconf Archive. +# +# If a readline compatible library is found, then ACTION-IF-FOUND is +# performed, otherwise ACTION-IF-NOT-FOUND is performed. +# +# LICENSE +# +# Copyright (c) 2023 Caleb Maclennan +# Copyright (c) 2015 Reuben Thomas +# Copyright (c) 2014 Tim Perkins +# +# This program is free software: you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation, either version 3 of the License, or (at your +# option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General +# Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program. If not, see . +# +# As a special exception, the respective Autoconf Macro's copyright owner +# gives unlimited permission to copy, distribute and modify the configure +# scripts that are the output of Autoconf when processing the Macro. You +# need not follow the terms of the GNU General Public License when using +# or distributing such scripts, even though portions of the text of the +# Macro appear in them. The GNU General Public License (GPL) does govern +# all other use of the material that constitutes the Autoconf Macro. +# +# This special exception to the GPL applies to versions of the Autoconf +# Macro released by the Autoconf Archive. When you make and distribute a +# modified version of the Autoconf Macro, you may extend this special +# exception to the GPL to apply to your modified version as well. + +#serial 45 + dnl ========================================================================= dnl AX_PROG_LUA([MINIMUM-VERSION], [TOO-BIG-VERSION], dnl [ACTION-IF-FOUND], [ACTION-IF-NOT-FOUND]) dnl ========================================================================= AC_DEFUN([AX_PROG_LUA], [ + dnl Check for required tools. + AC_REQUIRE([AC_PROG_GREP]) + AC_REQUIRE([AC_PROG_SED]) + dnl Make LUA a precious variable. AC_ARG_VAR([LUA], [The Lua interpreter, e.g. /usr/bin/lua5.1]) dnl Find a Lua interpreter. AM_COND_IF([LUAJIT], - [_ax_lua_interpreter_list="luajit luajit-2.1.0-beta3 luajit-2.0.5 luajit-2.0.4 luajit-2.0.3"], - [_ax_lua_interpreter_list="lua lua5.4 lua54 lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua5.0 lua50"]) + [_ax_lua_interpreter_list='luajit luajit-2.1.0-beta3 luajit-2.0.5 luajit-2.0.4 luajit-2.0.3'], + [_ax_lua_interpreter_list='lua lua5.4 lua54 lua5.3 lua53 lua5.2 lua52 lua5.1 lua51 lua5.0 lua50']) m4_if([$1], [], [ dnl No version check is needed. Find any Lua interpreter. AS_IF([test "x$LUA" = 'x'], - [AC_PATH_PROGS([LUA], [_ax_lua_interpreter_list], [:])]) + [AC_PATH_PROGS([LUA], [$_ax_lua_interpreter_list], [:])]) ax_display_LUA='lua' - dnl At least check if this is a Lua interpreter. + AS_IF([test "x$LUA" != 'x:'], + [ dnl At least check if this is a Lua interpreter. AC_MSG_CHECKING([if $LUA is a Lua interpreter]) _AX_LUA_CHK_IS_INTRP([$LUA], [AC_MSG_RESULT([yes])], [ AC_MSG_RESULT([no]) AC_MSG_ERROR([not a Lua interpreter]) ]) + ]) ], [ dnl A version check is needed. AS_IF([test "x$LUA" != 'x'], @@ -71,8 +271,15 @@ AC_DEFUN([AX_PROG_LUA], m4_default([$4], [AC_MSG_ERROR([cannot find suitable Lua interpreter])]) ], [ dnl Query Lua for its version number. - AC_CACHE_CHECK([for $ax_display_LUA version], [ax_cv_lua_version], - [ ax_cv_lua_version=`$LUA -e 'print(_VERSION:match "(%d+%.%d+)")'` ]) + AC_CACHE_CHECK([for $ax_display_LUA version], + [ax_cv_lua_version], + [ dnl Get the interpreter version in X.Y format. This should work for + dnl interpreters version 5.0 and beyond. + ax_cv_lua_version=[`$LUA -e ' + -- return a version number in X.Y format + local _, _, ver = string.find(_VERSION, "^Lua (%d+%.%d+)") + print(ver or "")'`] + ]) AS_IF([test "x$ax_cv_lua_version" = 'x'], [AC_MSG_ERROR([invalid Lua version number])]) AC_SUBST([LUA_VERSION], [$ax_cv_lua_version]) @@ -80,18 +287,22 @@ AC_DEFUN([AX_PROG_LUA], AM_COND_IF([LUAJIT], [ AC_CACHE_CHECK([for $ax_display_LUA jit version], [ax_cv_luajit_version], - [ ax_cv_luajit_version=`$LUA -e 'print(jit and jit.version:match "(%d+%..+)")'` ]) + [ ax_cv_luajit_version=[`$LUA -e ' + local _, _, ver = string.find(jit and jit.version, "(%d+%..+)") + print(ver or "")'`] + ]) AS_IF([test "x$ax_cv_luajit_version" = 'x'], - [AC_MSG_ERROR([invalid Lua version number])]) + [AC_MSG_ERROR([invalid Lua jit version number])]) AC_SUBST([LUAJIT_VERSION], [$ax_cv_luajit_version]) - AC_SUBST([LUAJIT_SHORT_VERSION], [`echo "$LUAJIT_VERSION" | $SED 's|\.|§|;s|\..*||;s|§|.|'`]) + AC_SUBST([LUAJIT_SHORT_VERSION], [$(echo "$LUAJIT_VERSION" | $SED 's|\.|§|;s|\..*||;s|§|.|')]) ]) dnl The following check is not supported: dnl At times (like when building shared libraries) you may want to know dnl which OS platform Lua thinks this is. - AC_CACHE_CHECK([for $ax_display_LUA platform], [ax_cv_lua_platform], - [ax_cv_lua_platform=`$LUA -e "print('unknown')"`]) + AC_CACHE_CHECK([for $ax_display_LUA platform], + [ax_cv_lua_platform], + [ax_cv_lua_platform=[`$LUA -e 'print("unknown")'`]]) AC_SUBST([LUA_PLATFORM], [$ax_cv_lua_platform]) dnl Use the values of $prefix and $exec_prefix for the corresponding @@ -116,12 +327,12 @@ AC_DEFUN([AX_PROG_LUA], ax_cv_lua_luadir="$LUA_PREFIX/share/lua/$LUA_VERSION" dnl Try to find a path with the prefix. - _AX_LUA_FND_PRFX_PTH([$LUA], [$ax_lua_prefix], [package.path]) + _AX_LUA_FND_PRFX_PTH([$LUA], [$ax_lua_prefix], [script]) AS_IF([test "x$ax_lua_prefixed_path" != 'x'], [ dnl Fix the prefix. _ax_strip_prefix=`echo "$ax_lua_prefix" | $SED 's|.|.|g'` ax_cv_lua_luadir=`echo "$ax_lua_prefixed_path" | \ - $SED "s,^$_ax_strip_prefix,$LUA_PREFIX,"` + $SED "s|^$_ax_strip_prefix|$LUA_PREFIX|"` ]) ]) AC_SUBST([luadir], [$ax_cv_lua_luadir]) @@ -143,12 +354,12 @@ AC_DEFUN([AX_PROG_LUA], dnl Try to find a path with the prefix. _AX_LUA_FND_PRFX_PTH([$LUA], - [$ax_lua_exec_prefix], [package.cpath]) + [$ax_lua_exec_prefix], [module]) AS_IF([test "x$ax_lua_prefixed_path" != 'x'], [ dnl Fix the prefix. _ax_strip_prefix=`echo "$ax_lua_exec_prefix" | $SED 's|.|.|g'` ax_cv_lua_luaexecdir=`echo "$ax_lua_prefixed_path" | \ - $SED "s,^$_ax_strip_prefix,$LUA_EXEC_PREFIX,"` + $SED "s|^$_ax_strip_prefix|$LUA_EXEC_PREFIX|"` ]) ]) AC_SUBST([luaexecdir], [$ax_cv_lua_luaexecdir]) @@ -162,7 +373,7 @@ AC_DEFUN([AX_PROG_LUA], dnl AX_WITH_LUA is now the same thing as AX_PROG_LUA. AC_DEFUN([AX_WITH_LUA], [ - AC_MSG_WARN([[$0 is deprecated, please use AX_PROG_LUA]]) + AC_MSG_WARN([[$0 is deprecated, please use AX_PROG_LUA instead]]) AX_PROG_LUA ]) @@ -172,8 +383,19 @@ dnl _AX_LUA_CHK_IS_INTRP(PROG, [ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl ========================================================================= AC_DEFUN([_AX_LUA_CHK_IS_INTRP], [ - dnl Just print _VERSION because all Lua interpreters have this global. - AS_IF([$1 -e "print('Hello ' .. _VERSION .. '!')"], + dnl A minimal Lua factorial to prove this is an interpreter. This should work + dnl for Lua interpreters version 5.0 and beyond. + _ax_lua_factorial=[`$1 2>/dev/null -e ' + -- a simple factorial + function fact (n) + if n == 0 then + return 1 + else + return n * fact(n-1) + end + end + print("fact(5) is " .. fact(5))'`] + AS_IF([test "$_ax_lua_factorial" = 'fact(5) is 120'], [$2], [$3]) ]) @@ -184,13 +406,28 @@ dnl [ACTION-IF-TRUE], [ACTION-IF-FALSE]) dnl ========================================================================= AC_DEFUN([_AX_LUA_CHK_VER], [ - AS_IF([$1 2>/dev/null -e ' - function norm (v) - i,j=v:match "(%d+)%.(%d+)" if i then return 100 * i + j end - end - v, toobig=norm (_VERSION), norm "$3" or math.huge - os.exit ((v >= norm ("$2") and v < toobig) and 0 or 1)'], - [$4], [$5]) + dnl Check that the Lua version is within the bounds. Only the major and minor + dnl version numbers are considered. This should work for Lua interpreters + dnl version 5.0 and beyond. + _ax_lua_good_version=[`$1 -e ' + -- a script to compare versions + function verstr2num(verstr) + local _, _, majorver, minorver = string.find(verstr, "^(%d+)%.(%d+)") + if majorver and minorver then + return tonumber(majorver) * 100 + tonumber(minorver) + end + end + local minver = verstr2num("$2") + local _, _, trimver = string.find(_VERSION, "^Lua (.*)") + local ver = verstr2num(trimver) + local maxver = verstr2num("$3") or 1e9 + if minver <= ver and ver < maxver then + print("yes") + else + print("no") + end'`] + AS_IF([test "x$_ax_lua_good_version" = "xyes"], + [$4], [$5]) ]) AC_DEFUN([_AX_LUAJIT_CHK_VER], @@ -206,25 +443,44 @@ AC_DEFUN([_AX_LUAJIT_CHK_VER], dnl ========================================================================= -dnl _AX_LUA_FND_PRFX_PTH(PROG, PREFIX, LUA-PATH-VARIABLE) +dnl _AX_LUA_FND_PRFX_PTH(PROG, PREFIX, SCRIPT-OR-MODULE-DIR) dnl ========================================================================= AC_DEFUN([_AX_LUA_FND_PRFX_PTH], [ - dnl Invokes the Lua interpreter PROG to print the path variable - dnl LUA-PATH-VARIABLE, usually package.path or package.cpath. Paths are - dnl then matched against PREFIX. Then ax_lua_prefixed_path is set to the - dnl shortest sub path beginning with PREFIX up to the last directory - dnl that does not contain a '?', if any. - - ax_lua_prefixed_path=`$1 2>/dev/null -e ' - $3:gsub ("(@<:@^;@:>@+)", - function (p) - p = p:gsub ("%?.*$", ""):gsub ("/@<:@^/@:>@*$", "") - if p:match ("^$2") and (not shortest or #shortest > #p) then - shortest = p + dnl Get the script or module directory by querying the Lua interpreter, + dnl filtering on the given prefix, and selecting the shallowest path. If no + dnl path is found matching the prefix, the result will be an empty string. + dnl The third argument determines the type of search, it can be 'script' or + dnl 'module'. Supplying 'script' will perform the search with package.path + dnl and LUA_PATH, and supplying 'module' will search with package.cpath and + dnl LUA_CPATH. This is done for compatibility with Lua 5.0. + + ax_lua_prefixed_path=[`$1 -e ' + -- get the path based on search type + local searchtype = "$3" + local paths = "" + if searchtype == "script" then + paths = (package and package.path) or LUA_PATH + elseif searchtype == "module" then + paths = (package and package.cpath) or LUA_CPATH + end + -- search for the prefix + local prefix = "'$2'" + local minpath = "" + local mindepth = 1e9 + string.gsub(paths, "(@<:@^;@:>@+)", + function (path) + path = string.gsub(path, "%?.*$", "") + path = string.gsub(path, "/@<:@^/@:>@*$", "") + if string.find(path, prefix) then + local depth = string.len(string.gsub(path, "@<:@^/@:>@", "")) + if depth < mindepth then + minpath = path + mindepth = depth + end end end) - print (shortest or "")'` + print(minpath)'`] ]) @@ -241,13 +497,35 @@ AC_DEFUN([AX_LUA_HEADERS], AC_MSG_ERROR([cannot check Lua headers without knowing LUA_VERSION]) ]) + AM_COND_IF([LUAJIT],[ + dnl Check for LUAJIT_VERSION. + AC_MSG_CHECKING([if LUAJIT_VERSION is defined]) + AS_IF([test "x$LUAJIT_VERSION" != 'x'], + [AC_MSG_RESULT([yes])], + [ AC_MSG_RESULT([no]) + AC_MSG_ERROR([cannot check Lua jit headers without knowing LUAJIT_VERSION]) + ]) + ]) + dnl Make LUA_INCLUDE a precious variable. AC_ARG_VAR([LUA_INCLUDE], [The Lua includes, e.g. -I/usr/include/lua5.1]) dnl Some default directories to search. AM_COND_IF([LUAJIT], - [_ax_lua_include_list="/usr/include/luajit-$LUAJIT_VERSION /usr/include/luajit-$LUAJIT_SHORT_VERSION /usr/local/include/luajit-$LUAJIT_VERSION /usr/local/include/luajit-$LUAJIT_SHORT_VERSION"], - [_ax_lua_include_list="/usr/include/lua$LUA_VERSION /usr/include/lua/$LUA_VERSION /usr/include/lua$LUA_SHORT_VERSION /usr/local/include/lua$LUA_VERSION /usr/local/include/lua-$LUA_VERSION /usr/local/include/lua/$LUA_VERSION /usr/local/include/lua$LUA_SHORT_VERSION"]) + [_ax_lua_include_list=" + /usr/include/luajit-$LUAJIT_VERSION + /usr/include/luajit-$LUAJIT_SHORT_VERSION + /usr/local/include/luajit-$LUAJIT_VERSION + /usr/local/include/luajit-$LUAJIT_SHORT_VERSION"], + [_ax_lua_include_list=" + /usr/include/lua$LUA_VERSION + /usr/include/lua-$LUA_VERSION + /usr/include/lua/$LUA_VERSION + /usr/include/lua$LUA_SHORT_VERSION + /usr/local/include/lua$LUA_VERSION + /usr/local/include/lua-$LUA_VERSION + /usr/local/include/lua/$LUA_VERSION + /usr/local/include/lua$LUA_SHORT_VERSION"]) dnl Try to find the headers. _ax_lua_saved_cppflags=$CPPFLAGS @@ -258,9 +536,9 @@ AC_DEFUN([AX_LUA_HEADERS], dnl Try some other directories if LUA_INCLUDE was not set. AS_IF([test "x$LUA_INCLUDE" = 'x' && - test "x$ac_cv_header_lua_h" != "xyes" || - test "x$with_luajit" = "xyes" && - test "x$ac_cv_header_luajit_h" != 'xyes'], + test "x$ac_cv_header_lua_h" != 'xyes' || + test "x$with_luajit" != 'xno' && + test "x$ac_cv_header_luajit_h" != 'xyes'], [ dnl Try some common include paths. for _ax_include_path in $_ax_lua_include_list; do test ! -d "$_ax_include_path" && continue @@ -269,10 +547,10 @@ AC_DEFUN([AX_LUA_HEADERS], AC_MSG_RESULT([$_ax_include_path]) AS_UNSET([ac_cv_header_lua_h]) - AS_UNSET([ac_cv_header_luajit_h]) AS_UNSET([ac_cv_header_lualib_h]) AS_UNSET([ac_cv_header_lauxlib_h]) AS_UNSET([ac_cv_header_luaconf_h]) + AS_UNSET([ac_cv_header_luajit_h]) _ax_lua_saved_cppflags=$CPPFLAGS CPPFLAGS="$CPPFLAGS -I$_ax_include_path" @@ -287,46 +565,44 @@ AC_DEFUN([AX_LUA_HEADERS], done ]) - AS_IF([test "x$ac_cv_header_lua_h" = 'xyes' && test "x$cross_compiling" != 'xyes'], + AS_IF([test "x$ac_cv_header_lua_h" = 'xyes'], [ dnl Make a program to print LUA_VERSION defined in the header. - dnl TODO This probably shouldn't be a runtime test. - - AC_CACHE_CHECK([for Lua header version], - [ax_cv_lua_header_version], - [ _ax_lua_saved_cppflags=$CPPFLAGS - CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" - AC_RUN_IFELSE( - [ AC_LANG_SOURCE([[ + dnl TODO It would be really nice if we could do this without compiling a + dnl program, then it would work when cross compiling. But I'm not sure how + dnl to do this reliably. For now, assume versions match when cross compiling. + + AS_IF([test "x$cross_compiling" != 'xyes'], + [ AC_CACHE_CHECK([for Lua header version], + [ax_cv_lua_header_version], + [ _ax_lua_saved_cppflags=$CPPFLAGS + CPPFLAGS="$CPPFLAGS $LUA_INCLUDE" + AC_COMPUTE_INT(ax_cv_lua_header_version_major,[LUA_VERSION_NUM/100],[AC_INCLUDES_DEFAULT #include -#include -#include -int main(int argc, char ** argv) -{ - if(argc > 1) printf("%s", LUA_VERSION); - exit(EXIT_SUCCESS); -} -]]) - ], - [ ax_cv_lua_header_version=`./conftest$EXEEXT p | \ - $SED "s|^Lua \(.*\)|\1|" | \ - $GREP -E -o "^@<:@0-9@:>@+\.@<:@0-9@:>@+"` +],[ax_cv_lua_header_version_major=unknown]) + AC_COMPUTE_INT(ax_cv_lua_header_version_minor,[LUA_VERSION_NUM%100],[AC_INCLUDES_DEFAULT +#include +],[ax_cv_lua_header_version_minor=unknown]) + AS_IF([test "x$ax_cv_lua_header_version_major" = xunknown || test "x$ax_cv_lua_header_version_minor" = xunknown],[ + ax_cv_lua_header_version=unknown + ],[ + ax_cv_lua_header_version="$ax_cv_lua_header_version_major.$ax_cv_lua_header_version_minor" + ]) + CPPFLAGS=$_ax_lua_saved_cppflags + ]) + + dnl Compare this to the previously found LUA_VERSION. + AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION]) + AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"], + [ AC_MSG_RESULT([yes]) + ax_header_version_match='yes' ], - [ax_cv_lua_header_version='unknown']) - CPPFLAGS=$_ax_lua_saved_cppflags - ]) - - dnl Compare this to the previously found LUA_VERSION. - AC_MSG_CHECKING([if Lua header version matches $LUA_VERSION]) - AS_IF([test "x$ax_cv_lua_header_version" = "x$LUA_VERSION"], - [ AC_MSG_RESULT([yes]) - ax_header_version_match='yes' + [ AC_MSG_RESULT([no]) + ax_header_version_match='no' + ]) ], - [ AC_MSG_RESULT([no]) - ax_header_version_match='no' + [ AC_MSG_WARN([cross compiling so assuming header version number matches]) + ax_header_version_match='yes' ]) - ], - [ - ax_header_version_match='yes' ]) dnl Was LUA_INCLUDE specified? @@ -342,7 +618,7 @@ int main(int argc, char ** argv) dnl AX_LUA_HEADERS_VERSION no longer exists, use AX_LUA_HEADERS. AC_DEFUN([AX_LUA_HEADERS_VERSION], [ - AC_MSG_WARN([[$0 is deprecated, please use AX_LUA_HEADERS]]) + AC_MSG_WARN([[$0 is deprecated, please use AX_LUA_HEADERS instead]]) ]) @@ -399,12 +675,21 @@ AC_DEFUN([AX_LUA_LIBS], LIBS="$LIBS $LUA_LIB" AM_COND_IF([LUAJIT], [AC_SEARCH_LIBS([lua_load], - [luajit$LUA_VERSION luajit$LUA_SHORT_VERSION luajit-$LUA_VERSION luajit-$LUA_SHORT_VERSION luajit], + [ luajit$LUA_VERSION \ + luajit$LUA_SHORT_VERSION \ + luajit-$LUA_VERSION \ + luajit-$LUA_SHORT_VERSION \ + luajit], [_ax_found_lua_libs='yes'], [_ax_found_lua_libs='no'], [$_ax_lua_extra_libs])], [AC_SEARCH_LIBS([lua_load], - [lua$LUA_VERSION lua$LUA_SHORT_VERSION lua-$LUA_VERSION lua-$LUA_SHORT_VERSION lua], + [ lua$LUA_VERSION \ + lua$LUA_SHORT_VERSION \ + lua-$LUA_VERSION \ + lua-$LUA_SHORT_VERSION \ + lua \ + ], [_ax_found_lua_libs='yes'], [_ax_found_lua_libs='no'], [$_ax_lua_extra_libs])]) diff --git a/build-aux/ax_print_to_file.m4 b/build-aux/ax_print_to_file.m4 new file mode 100644 index 0000000000..8aa71120d1 --- /dev/null +++ b/build-aux/ax_print_to_file.m4 @@ -0,0 +1,27 @@ +# =========================================================================== +# https://www.gnu.org/software/autoconf-archive/ax_print_to_file.html +# =========================================================================== +# +# SYNOPSIS +# +# AX_PRINT_TO_FILE([FILE],[DATA]) +# +# DESCRIPTION +# +# Writes the specified data to the specified file. +# +# LICENSE +# +# Copyright (c) 2008 Tom Howard +# +# Copying and distribution of this file, with or without modification, are +# permitted in any medium without royalty provided the copyright notice +# and this notice are preserved. This file is offered as-is, without any +# warranty. + +#serial 8 + +AC_DEFUN([AX_PRINT_TO_FILE],[ +AC_REQUIRE([AX_FILE_ESCAPES]) +printf "$2" > "$1" +]) diff --git a/build-aux/build.rs b/build-aux/build.rs new file mode 100644 index 0000000000..2f90b107fc --- /dev/null +++ b/build-aux/build.rs @@ -0,0 +1,134 @@ +#[cfg(feature = "manpage")] +use clap_mangen::Man; +#[cfg(any(feature = "static", feature = "completions"))] +use std::path::Path; +use std::{collections, env}; +use vergen::EmitBuilder; +#[cfg(feature = "completions")] +use { + clap::CommandFactory, + clap_complete::generator::generate_to, + clap_complete::shells::{Bash, Elvish, Fish, PowerShell, Zsh}, + std::fs, +}; + +#[cfg(feature = "completions")] +include!("../src/cli.rs"); + +fn main() { + if let Ok(val) = env::var("AUTOTOOLS_DEPENDENCIES") { + for dependency in val.split(' ') { + println!("cargo:rerun-if-changed={dependency}"); + } + } + let mut builder = EmitBuilder::builder(); + // If passed a version from automake, use that instead of vergen's formatting + if let Ok(val) = env::var("VERSION_FROM_AUTOTOOLS") { + println!("cargo:rustc-env=VERGEN_GIT_DESCRIBE={val}") + } else { + builder = *builder.git_describe(true, true, None); + }; + builder.emit().expect("Unable to generate the cargo keys!"); + pass_on_configure_details(); + #[cfg(feature = "manpage")] + generate_manpage(); + #[cfg(feature = "completions")] + generate_shell_completions(); + #[cfg(feature = "static")] + { + let dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + println!( + "cargo:rustc-link-search=native={}", + Path::new(&dir).join("justenough").join(".libs").display() + ); + println!( + "cargo:rustc-link-search=native={}", + Path::new(&dir).join("libtexpdf").join(".libs").display() + ); + println!("cargo:rustc-link-arg=-l:fontmetrics.a"); + println!("cargo:rustc-link-arg=-l:justenoughfontconfig.a"); + println!("cargo:rustc-link-arg=-l:justenoughharfbuzz.a"); + println!("cargo:rustc-link-arg=-l:justenoughicu.a"); + println!("cargo:rustc-link-arg=-l:justenoughlibtexpdf.a"); + println!("cargo:rustc-link-arg=-l:svg.a"); + println!("cargo:rustc-link-arg=-l:libtexpdf.a"); + + // These are automatically linked by Cargo, but sadly *previous* to our links above + println!("cargo:rustc-link-arg=-lharfbuzz"); // needed by justenoughharfbuzz + #[cfg(feature = "variations")] + println!("cargo:rustc-link-arg=-lharfbuzz-subset"); // needed by justenoughharfbuzz + println!("cargo:rustc-link-arg=-lfontconfig"); // needed by justenoughfontconfig + println!("cargo:rustc-link-arg=-licui18n"); // needed by justenoughicu + println!("cargo:rustc-link-arg=-licuuc"); // needed by justenoughicu + println!("cargo:rustc-link-arg=-lm"); // needed by svg + println!("cargo:rustc-link-arg=-lz"); // needed by libtexpdf + println!("cargo:rustc-link-arg=-lpng"); // needed by libtexpdf + } +} + +/// Generate man page +#[cfg(feature = "manpage")] +fn generate_manpage() { + let out_dir = match env::var_os("OUT_DIR") { + None => return, + Some(out_dir) => out_dir, + }; + let manpage_dir = Path::new(&out_dir); + fs::create_dir_all(manpage_dir).expect("Unable to create directory for generated manpages"); + let app = Cli::command(); + let bin_name: &str = app + .get_bin_name() + .expect("Could not retrieve bin-name from generated Clap app"); + let app = Cli::command(); + let man = Man::new(app); + let mut buffer: Vec = Default::default(); + man.render(&mut buffer) + .expect("Unable to render man page to UTF-8 string"); + fs::write(manpage_dir.join(format!("{bin_name}.1")), buffer) + .expect("Unable to write manepage to file"); +} + +/// Generate shell completion files from CLI interface +#[cfg(feature = "completions")] +fn generate_shell_completions() { + let out_dir = match env::var_os("OUT_DIR") { + None => return, + Some(out_dir) => out_dir, + }; + let completions_dir = Path::new(&out_dir).join("completions"); + fs::create_dir_all(&completions_dir) + .expect("Could not create directory in which to place completions"); + let app = Cli::command(); + let bin_name: &str = app + .get_bin_name() + .expect("Could not retrieve bin-name from generated Clap app"); + let mut app = Cli::command(); + #[cfg(feature = "bash")] + generate_to(Bash, &mut app, bin_name, &completions_dir) + .expect("Unable to generate bash completions"); + #[cfg(feature = "elvish")] + generate_to(Elvish, &mut app, bin_name, &completions_dir) + .expect("Unable to generate elvish completions"); + #[cfg(feature = "fish")] + generate_to(Fish, &mut app, bin_name, &completions_dir) + .expect("Unable to generate fish completions"); + #[cfg(feature = "powershell")] + generate_to(PowerShell, &mut app, bin_name, &completions_dir) + .expect("Unable to generate powershell completions"); + #[cfg(feature = "zsh")] + generate_to(Zsh, &mut app, bin_name, &completions_dir) + .expect("Unable to generate zsh completions"); +} + +/// Pass through some variables set by autoconf/automake about where we're installed to cargo for +/// use in finding resources at runtime +fn pass_on_configure_details() { + let mut autoconf_vars = collections::HashMap::new(); + autoconf_vars.insert("CONFIGURE_PREFIX", String::from("./")); + autoconf_vars.insert("CONFIGURE_BINDIR", String::from("./")); + autoconf_vars.insert("CONFIGURE_DATADIR", String::from("./")); + for (var, default) in autoconf_vars { + let val = env::var(var).unwrap_or(default); + println!("cargo:rustc-env={var}={val}"); + } +} diff --git a/build-aux/cargo-updater.js b/build-aux/cargo-updater.js new file mode 100644 index 0000000000..1b64fda447 --- /dev/null +++ b/build-aux/cargo-updater.js @@ -0,0 +1,12 @@ +const TOML = require('@iarna/toml') + +module.exports.readVersion = function (contents) { + const data = TOML.parse(contents) + return data.package.version +} + +module.exports.writeVersion = function (contents, version) { + const data = TOML.parse(contents) + data.package.version = version + return TOML.stringify(data) +} diff --git a/build-aux/config.ld b/build-aux/config.ld new file mode 100644 index 0000000000..2c2f41be21 --- /dev/null +++ b/build-aux/config.ld @@ -0,0 +1,26 @@ +project = "SILE" +description = "The SILE Typesetter" +readme = "../README.md" +dir = "../lua-api-docs" +format = "discount" +file = { + "../sile-lua", + "../core/", + "../classes/", + "../inputters/", + "../languages/", + "../outputters/", + "../packages/", + "../pagebuilders/", + "../shapers/", + "../types/", + "../typesetters/", +} +merge = true +no_space_before_args = true +sort_modules = true +kind_names = { module = "Library" } +new_type("interfaces", "Interfaces", true) +new_type("types", "Types", true) +new_type("use", "Modules", true) +-- vim: ft=lua diff --git a/build-aux/git-version-gen b/build-aux/git-version-gen index 2a79f1f675..b971f41c26 100755 --- a/build-aux/git-version-gen +++ b/build-aux/git-version-gen @@ -1,4 +1,5 @@ -#!/bin/sh +#!/usr/bin/env sh + # Print a version string. scriptversion=2012-03-18.17; # UTC @@ -165,8 +166,8 @@ then # Newer: v6.10-77-g0f8faeb # Older: v6.10-g0f8faeb case $v in - *-*-*) : git describe is okay three part flavor ;; - *-*) + *-*-g*) : git describe is okay three part flavor ;; + *-g*) : git describe is older two part flavor # Recreate the number of commits and rewrite such that the # result is the same as if we were using the newer version @@ -181,7 +182,7 @@ then ;; esac - v=`echo "$v" | sed 's/-/.r/'`; + v=`echo "$v" | sed 's/-\([0-9]\)/.r\1/'`; v_from_git=1 else v=UNKNOWN diff --git a/build-aux/list-dist-files.sh.in b/build-aux/list-dist-files.sh.in index c007dcb009..4c7af2f6cb 100755 --- a/build-aux/list-dist-files.sh.in +++ b/build-aux/list-dist-files.sh.in @@ -8,7 +8,7 @@ finder () { printf '%s' "SILEDATA =" finder core -name '*.lua' -not -name '*_spec.lua' -not -name 'version.lua' -not -name 'features.lua' -not -name 'pathsetup.lua' -finder classes inputters languages outputters packages shapers typesetters pagebuilders -name '*.lua' -not -name '*_spec.lua' +finder classes inputters languages outputters packages shapers types typesetters pagebuilders -name '*.lua' -not -name '*_spec.lua' finder classes i18n packages -name '*.ftl' finder packages -name '*.svg' diff --git a/build-aux/pkg.nix b/build-aux/pkg.nix new file mode 100644 index 0000000000..ee993b7a9f --- /dev/null +++ b/build-aux/pkg.nix @@ -0,0 +1,177 @@ +# NOTE: This file is supposed to be similar to what is in Nixpkgs, except for +# the `version`, `src` and `libtexpdf-src` attributes that are given by the +# `flake.nix`. In Nixpkgs, we don't need `libtexpdf-src` because we use +# `fetchFromGitHub` with fetchSubmodules = true;`. +{ lib +, stdenv +, version, src, libtexpdf-src +, autoreconfHook +, gitMinimal +, pkg-config +, jq +, cargo +, rustc +, rustPlatform +, makeWrapper +, poppler_utils +, harfbuzz +, icu +, fontconfig +, lua +, libiconv +, darwin +, makeFontsConf +, gentium +, runCommand +}: + +let + luaEnv = lua.withPackages(ps: with ps; [ + cassowary + cldr + fluent + linenoise + loadkit + lpeg + lua-zlib + lua_cliargs + luaepnf + luaexpat + luafilesystem + luarepl + luasec + luasocket + luautf8 + penlight + vstruct + # lua packages needed for testing + busted + luacheck + # packages needed for building api docs + ldoc + # NOTE: Add lua packages here, to change the luaEnv also read by `flake.nix` + ] ++ lib.optionals (lib.versionOlder lua.luaversion "5.2") [ + bit32 + ] ++ lib.optionals (lib.versionOlder lua.luaversion "5.3") [ + compat53 + ]); +in stdenv.mkDerivation (finalAttrs: { + pname = "sile"; + inherit version src; + + preAutoreconf = '' + # Add the libtexpdf src instead of the git submodule. (From some reason + # without --no-preserve=mode flag, libtexpdf/ is unwriteable). As explained + # before, in Nixpkgs, we won't need to run these commands. + rm -rf ./libtexpdf + cp --no-preserve=mode -r ${libtexpdf-src} ./libtexpdf/ + # pretend to be a tarball release so sile --version will not say `vUNKNOWN`. + echo ${finalAttrs.version} > .tarball-version + ''; + + nativeBuildInputs = [ + autoreconfHook + gitMinimal + pkg-config + jq + cargo + rustc + rustPlatform.cargoSetupHook + poppler_utils + makeWrapper + ]; + # In Nixpkgs, we don't copy the Cargo.lock file from the repo to Nixpkgs' + # repo, but we inherit src, and specify a hash (it is a fixed output + # derivation). `nix-update` and `nixpkgs-update` should be able to catch that + # hash and update it as well when performing updates. + cargoDeps = rustPlatform.importCargoLock { + lockFile = ../Cargo.lock; + }; + + buildInputs = [ + luaEnv + harfbuzz + icu + fontconfig + libiconv + ] ++ lib.optionals stdenv.isDarwin [ + darwin.apple_sdk.frameworks.AppKit + ]; + + configureFlags = [ + # Nix will supply all the Lua dependencies, so stop the build system from + # bundling vendored copies of them. + "--with-system-lua-sources" + "--with-system-luarocks" + # The automake check target uses pdfinfo to confirm the output of a test + # run, and uses autotools to discover it. This flake build eschews that + # test because it is run from the source directory but the binary is + # already built with system paths, so it can't be checked under Nix until + # after install. After install the Makefile isn't available of course, so + # we have our own copy of it with a hard coded path to `pdfinfo`. By + # specifying some binary here we skip the configure time test for + # `pdfinfo`, by using `false` we make sure that if it is expected during + # build time we would fail to build since we only provide it at test time. + "PDFINFO=false" + #"--with-manual" In Nixpkgs we add this flag, here its not important enough + ] ++ lib.optionals (!lua.pkgs.isLuaJIT) [ + "--without-luajit" + ]; + + postPatch = '' + patchShebangs build-aux/*.sh build-aux/git-version-gen + ''; + + NIX_LDFLAGS = lib.optionalString stdenv.isDarwin "-framework AppKit"; + + FONTCONFIG_FILE = makeFontsConf { + fontDirectories = [ + gentium + ]; + }; + + enableParallelBuilding = true; + + # See commentary in bootstrap.sh; we're getting AMINCCLUDE stuff inlined + # instead of included but need to avoid a file not found error on first run. + postUnpack = '' + touch source/aminclude.am + ''; + + passthru = { + # So it will be easier to inspect this environment, in comparison to others + inherit luaEnv; + # Copied from Makefile.am + tests.test = lib.optionalAttrs (!(stdenv.isDarwin && stdenv.isAarch64)) ( + runCommand "${finalAttrs.pname}-test" { + nativeBuildInputs = [ poppler_utils finalAttrs.finalPackage ]; + inherit (finalAttrs) FONTCONFIG_FILE; + } '' + output=$(mktemp -t selfcheck-XXXXXX.pdf) + echo "foo" | sile -o $output - + pdfinfo $output | grep "SILE v${finalAttrs.version}" > $out + ''); + }; + + outputs = [ "out" "doc" "man" "dev" ]; + + meta = { + description = "A typesetting system"; + longDescription = '' + SILE is a typesetting system; its job is to produce beautiful + printed documents. Conceptually, SILE is similar to TeX—from + which it borrows some concepts and even syntax and + algorithms—but the similarities end there. Rather than being a + derivative of the TeX family SILE is a new typesetting and + layout engine written from the ground up using modern + technologies and borrowing some ideas from graphical systems + such as InDesign. + ''; + homepage = "https://sile-typesetter.org"; + # In nixpkgs we use a version specific URL for CHANGELOG.md + changelog = "https://github.com/sile-typesetter/sile/raw/master/CHANGELOG.md"; + platforms = lib.platforms.unix; + maintainers = with lib.maintainers; [ doronbehar alerque ]; + license = lib.licenses.mit; + }; +}) diff --git a/build-aux/que_developer_mode.m4 b/build-aux/que_developer_mode.m4 new file mode 100644 index 0000000000..18bd29fe8f --- /dev/null +++ b/build-aux/que_developer_mode.m4 @@ -0,0 +1,19 @@ +# Like AM_MAINTAINER_MODE, but doesn't touch automake internals and so +# can be used freely to control access to project specific developer +# tooling without breaking autotools if disabled. +AC_DEFUN([QUE_DEVELOPER_MODE], [ + m4_case(m4_default([$1], [disable]), + [enable], [m4_define([_que_developer_def], [disable])], + [disable], [m4_define([_que_developer_def], [enable])], + [m4_define([_que_developer_def], [enable]) + m4_warn([syntax], [unexpected argument to AM@&t@_DEVELOPER_MODE: $1])]) + AC_MSG_CHECKING([whether to enable developer-specific portions of Makefiles]) + AC_ARG_ENABLE([developer-mode], + [AS_HELP_STRING([--]_que_developer_def[-developer-mode], + _que_developer_def[ dependencies and make targets only useful for developers])], + [USE_DEVELOPER_MODE=$enableval], + [USE_DEVELOPER_MODE=]m4_if(_que_developer_def, [enable], [no], [yes])) + AC_MSG_RESULT([$USE_DEVELOPER_MODE]) + AM_CONDITIONAL([DEVELOPER_MODE], [test $USE_DEVELOPER_MODE = yes]) + +]) diff --git a/build-aux/que_dist_checksums.am b/build-aux/que_dist_checksums.am new file mode 100644 index 0000000000..ffaecc6692 --- /dev/null +++ b/build-aux/que_dist_checksums.am @@ -0,0 +1,21 @@ +# Output both a file that can be attatched to releases and also write STDOUT +# for the sake of CI build logs so they can be audited as matching what is +# eventually posted. The list of files checksummed is a glob (even though we +# know an exact pattern) to avoid errors for formats not generated. +checksum_dist = \ + shopt -s nullglob ; \ + $(SHA256SUM) $(distdir)*.{tar.{gz,bz2,lz,xz,zst},zip} |\ + $(TEE) $(distdir).sha256.txt + +# Since the checksums file isn't an artifact produced by the default source dist +# creation process, we have to clean it up ourselves so distcheck can see that +# everything round-tripped cleanly. +distclean-local: distclean-local-checksums + +distclean-local-checksums: + rm -f $(distdir).sha256.txt + +# Append checksum operation to function that runs after compressing dist archives +am__post_remove_distdir = $(am__remove_distdir); $(checksum_dist) + +# vim: ft=automake diff --git a/build-aux/que_dist_checksums.m4 b/build-aux/que_dist_checksums.m4 new file mode 100644 index 0000000000..7ace93afda --- /dev/null +++ b/build-aux/que_dist_checksums.m4 @@ -0,0 +1,20 @@ +AC_DEFUN_ONCE([QUE_DIST_CHECKSUMS], [ + + QUE_TRANSFORM_PACKAGE_NAME + + AC_REQUIRE([AX_AM_MACROS]) + AX_ADD_AM_MACRO([dnl +EXTRA_DIST += build-aux/que_dist_checksums.am +])dnl + + AM_COND_IF([DEVELOPER_MODE], [ + + QUE_PROGVAR([sha256sum]) + QUE_PROGVAR([tee]) + + AX_ADD_AM_MACRO([dnl +$(cat build-aux/que_dist_checksums.am) +])dnl + + ]) +]) diff --git a/build-aux/que_docker_boilerplate.am b/build-aux/que_docker_boilerplate.am new file mode 100644 index 0000000000..cec6ca3e1a --- /dev/null +++ b/build-aux/que_docker_boilerplate.am @@ -0,0 +1,28 @@ +export VERSION_FROM_AUTOTOOLS = v$(VERSION) + +DOCKER_DEVELOPER_TARGETS = docker +.PHONY: $(DOCKER_DEVELOPER_TARGETS) + +export DOCKER_REGISTRY ?= ghcr.io +export DOCKER_REPO ?= alerque/$(TRANSFORMED_PACKAGE_NAME) +export DOCKER_TAG ?= HEAD + +docker: Dockerfile hooks/build .version + ./hooks/build $(VERSION) + +docker-build-push: docker + docker tag $(DOCKER_REPO):$(DOCKER_TAG) $(DOCKER_REGISTRY)/$(DOCKER_REPO):$(DOCKER_TAG) + $(docker_push) + +define docker_push = + test -z "$(DOCKER_PAT)" || \ + docker login https://$(DOCKER_REGISTRY) -u $(DOCKER_USERNAME) -p $(DOCKER_PAT) + docker push $(DOCKER_REGISTRY)/$(DOCKER_REPO):$(DOCKER_TAG) + if [[ "$(DOCKER_TAG)" == v*.*.* ]]; then \ + tag=$(DOCKER_TAG) ; \ + docker tag $(DOCKER_REPO):$(DOCKER_TAG) $(DOCKER_REGISTRY)/$(DOCKER_REPO):latest ; \ + docker tag $(DOCKER_REPO):$(DOCKER_TAG) $(DOCKER_REGISTRY)/$(DOCKER_REPO):$${tag//.*} ; \ + docker push $(DOCKER_REGISTRY)/$(DOCKER_REPO):latest ; \ + docker push $(DOCKER_REGISTRY)/$(DOCKER_REPO):$${tag//.*} ; \ + fi +endef diff --git a/build-aux/que_docker_boilerplate.m4 b/build-aux/que_docker_boilerplate.m4 new file mode 100644 index 0000000000..066bf3e13e --- /dev/null +++ b/build-aux/que_docker_boilerplate.m4 @@ -0,0 +1,18 @@ +AC_DEFUN_ONCE([QUE_DOCKER_BOILERPLATE], [ + + QUE_TRANSFORM_PACKAGE_NAME + + AC_MSG_NOTICE([checking for tools used by automake to build Docker projects]) + AC_PROG_INSTALL + AM_COND_IF([DEVELOPER_MODE], [ + QUE_PROGVAR([docker]) + ]) + + AC_REQUIRE([AX_AM_MACROS]) + AX_ADD_AM_MACRO([dnl +EXTRA_DIST += build-aux/que_docker_boilerplate.am + +$($SED -E "s/@PACKAGE_VAR@/$PACKAGE_VAR/g" build-aux/que_docker_boilerplate.am) +])dnl + +]) diff --git a/build-aux/que_font.m4 b/build-aux/que_font.m4 index 63c8d0f2f5..57db8a4fe4 100644 --- a/build-aux/que_font.m4 +++ b/build-aux/que_font.m4 @@ -1,21 +1,21 @@ AC_DEFUN([QUE_FONT], [ - AC_PROG_GREP - if test -z "$FCMATCH"; then - AC_PATH_PROG(FCMATCH, fc-match) + AC_PROG_GREP if test -z "$FCMATCH"; then - AC_MSG_ERROR([can't find fc-match]) + AC_PATH_PROG(FCMATCH, fc-match) + if test -z "$FCMATCH"; then + AC_MSG_ERROR([can't find fc-match]) + fi fi - fi - pushdef([FONT],$1) - AC_MSG_CHECKING(whether font family FONT is available) - AS_IF([test "$FCMATCH" = "true"],[ - AC_MSG_RESULT(skip) - ],[ - AS_IF([$FCMATCH "FONT" family | $GREP -qx "FONT"],[ - AC_MSG_RESULT(yes) + pushdef([FONT],$1) + AC_MSG_CHECKING(whether font family FONT is available) + AS_IF([test "$FCMATCH" = "true"],[ + AC_MSG_RESULT(skip) ],[ - AC_MSG_FAILURE([font family FONT not found]) + AS_IF([$FCMATCH "FONT" family | $GREP -qx "FONT"],[ + AC_MSG_RESULT(yes) + ],[ + AC_MSG_FAILURE([font family FONT not found]) + ]) ]) - ]) - popdef([FONT]) + popdef([FONT]) ])dnl diff --git a/build-aux/que_git_version.am b/build-aux/que_git_version.am new file mode 100644 index 0000000000..6b9e8098c8 --- /dev/null +++ b/build-aux/que_git_version.am @@ -0,0 +1,36 @@ +.SECONDEXPANSION: + +EXTRA_DIST += build-aux/git-version-gen +BUILT_SOURCES += .version +CLEANFILES += .version .version-prev + +_BRANCH_REF != $(AWK) '{print ".git/" $$2}' .git/HEAD 2>/dev/null ||: +.version: $(_BRANCH_REF) + @if [ -e "$(srcdir)/.tarball-version" ]; then \ + printf "$(VERSION)" > $@; \ + else \ + touch "$@-prev"; \ + if [ -e "$@" ]; then \ + cp "$@" "$@-prev"; \ + fi; \ + ./build-aux/git-version-gen "$(srcdir)/.tarball-version" > $@; \ + $(CMP) -s "$@" "$@-prev" || autoreconf configure.ac --force; \ + fi + +check-version: check-git-version + +.PHONY: check-git-version +check-git-version: $(PACKAGE_NAME)$(EXEEXT) | .version + $(GREP) -Fx '$(VERSION)' $| + ./$< --version | $(GREP) -Ff $| + +installcheck-local-version: + ./$(TRANSFORMED_PACKAGE_NAME)$(EXEEXT) --version + +dist-hook: dist-tarball-version + +.PHONY: dist-tarball-version +dist-tarball-version: + printf "$(VERSION)" > "$(distdir)/.tarball-version" + +# vim: ft=automake diff --git a/build-aux/que_git_version.m4 b/build-aux/que_git_version.m4 new file mode 100644 index 0000000000..f91ca11b2b --- /dev/null +++ b/build-aux/que_git_version.m4 @@ -0,0 +1,26 @@ +AC_DEFUN_ONCE([QUE_GIT_VERSION], [ + + AM_CONDITIONAL([SOURCE_IS_GIT], + [test -d .git]) + + AM_CONDITIONAL([SOURCE_IS_DIST], + [test -f .tarball-version]) + + AM_CONDITIONAL([SOURCE_IS_ARCHIVE], + [test ! -d .git -a ! -f .tarball-version]) + + AC_PROG_AWK + AC_PROG_GREP + + QUE_TRANSFORM_PACKAGE_NAME + + AM_COND_IF([SOURCE_IS_DIST], [], [QUE_PROGVAR([cmp])]) + + AC_REQUIRE([AX_AM_MACROS]) + AX_ADD_AM_MACRO([dnl +EXTRA_DIST += build-aux/que_git_version.am + +$(cat build-aux/que_git_version.am) +])dnl + +]) diff --git a/build-aux/que_rust_boilerplate.am b/build-aux/que_rust_boilerplate.am new file mode 100644 index 0000000000..2f7b2dd481 --- /dev/null +++ b/build-aux/que_rust_boilerplate.am @@ -0,0 +1,93 @@ +export VERSION_FROM_AUTOTOOLS = v$(VERSION) + +@PACKAGE_VAR@_SOURCES += Cargo.toml build-aux/build.rs +EXTRA_@PACKAGE_VAR@_SOURCES += Cargo.lock .version +dist_man_MANS += @PACKAGE_NAME@.1 + +CLEANFILES += $(bin_PROGRAMS) $(BUILT_SOURCES) $(dist_man_MANS) + +CARGO_RELEASE_ARGS = +if !DEBUG_RELEASE +CARGO_RELEASE_ARGS += --release --locked +endif + +CARGO_ENV = CARGO_TARGET_DIR=@builddir@/target +CARGO_BIN = @builddir@/target/@RUST_TARGET_SUBDIR@/$(PACKAGE_NAME) +_RUST_OUT = @builddir@/target/@RUST_TARGET_SUBDIR@/.cargo_out_dir + +distclean-local: distclean-local-rust + +distclean-local-rust: + -rm -rf @builddir@/target + +@PACKAGE_NAME@$(EXEEXT): $(CARGO_BIN) + $(INSTALL) $< $@ + +# Leave some tips for cargo to use so CLI knows where it is +export CONFIGURE_PREFIX = $(prefix)/ +export CONFIGURE_DATADIR = $(datadir)/ +export CONFIGURE_BINDIR = $(bindir)/ + +CARGO_VERBOSE = $(cargo_verbose_$(V)) +cargo_verbose_ = $(cargo_verbose_$(AM_DEFAULT_VERBOSITY)) +cargo_verbose_0 = +cargo_verbose_1 = --verbose + +$(COMPLETIONS_OUT_DIR): + $(MKDIR_P) $@ + +$(PACKAGE_NAME).1: $(CARGO_BIN) + $(INSTALL) -m644 $$(cat $(_RUST_OUT))/$@ $@ + +$(COMPLETIONS_OUT_DIR)/$(TRANSFORMED_PACKAGE_NAME): $(CARGO_BIN) | $(COMPLETIONS_OUT_DIR) + $(INSTALL) -m755 $$(cat $(_RUST_OUT))/$(COMPLETIONS_OUT_DIR)/$(PACKAGE_NAME).bash $@ + +$(COMPLETIONS_OUT_DIR)/$(TRANSFORMED_PACKAGE_NAME).elv: $(CARGO_BIN) | $(COMPLETIONS_OUT_DIR) + $(INSTALL) -m755 $$(cat $(_RUST_OUT))/$(COMPLETIONS_OUT_DIR)/$(PACKAGE_NAME).elv $@ + +$(COMPLETIONS_OUT_DIR)/$(TRANSFORMED_PACKAGE_NAME).fish: $(CARGO_BIN) | $(COMPLETIONS_OUT_DIR) + $(INSTALL) -m755 $$(cat $(_RUST_OUT))/$(COMPLETIONS_OUT_DIR)/$(PACKAGE_NAME).fish $@ + +$(COMPLETIONS_OUT_DIR)/_$(TRANSFORMED_PACKAGE_NAME).ps1: $(CARGO_BIN) | $(COMPLETIONS_OUT_DIR) + $(INSTALL) -m755 $$(cat $(_RUST_OUT))/$(COMPLETIONS_OUT_DIR)/_$(PACKAGE_NAME).ps1 $@ + +$(COMPLETIONS_OUT_DIR)/_$(TRANSFORMED_PACKAGE_NAME): $(CARGO_BIN) | $(COMPLETIONS_OUT_DIR) + $(INSTALL) -m755 $$(cat $(_RUST_OUT))/$(COMPLETIONS_OUT_DIR)/_$(PACKAGE_NAME) $@ + +$(_RUST_OUT) $(CARGO_BIN): $(@PACKAGE_VAR@_SOURCES) $(EXTRA_@PACKAGE_VAR@_SOURCES) + set -e + export AUTOTOOLS_DEPENDENCIES="$^" + $(CARGO_ENV) $(CARGO) build $(CARGO_VERBOSE) $(CARGO_FEATURE_ARGS) $(CARGO_RELEASE_ARGS) + $(CARGO_ENV) $(CARGO) build --quiet --message-format=json $(CARGO_FEATURE_ARGS) $(CARGO_RELEASE_ARGS) | \ + $(JQ) -sr 'map(select(.reason == "build-script-executed")) | last | .out_dir' > $(_RUST_OUT) + +RUST_DEVELOPER_TARGETS = cargo-test clippy rustfmt +.PHONY: $(RUST_DEVELOPER_TARGETS) + +if DEVELOPER_MODE + +test: cargo-test +lint: rustfmt clippy +clean-local: clean-cargo +check-local: cargo-test + +rustfmt: + $(GIT) ls-files '*.rs' '*.rs.in' | $(XARGS) $(RUSTFMT) --check --config skip_children=true + +clippy: + $(CARGO_ENV) $(CARGO) $(CARGO_VERBOSE) clippy $(CARGO_FEATURE_ARGS) -- -D warnings + +clean-cargo: + $(CARGO_ENV) $(CARGO) $(CARGO_VERBOSE) clean + +cargo-test: $(PACKAGE_NAME)$(EXEEXT) + $(CARGO_ENV) $(CARGO) $(CARGO_VERBOSE) test $(CARGO_FEATURE_ARGS) --locked + +else !DEVELOPER_MODE + +$(RUST_DEVELOPER_TARGETS): + @: $(error "Please reconfigure using --enable-developer-mode to use developer tooling") + +endif !DEVELOPER_MODE + +# vim: ft=automake diff --git a/build-aux/que_rust_boilerplate.m4 b/build-aux/que_rust_boilerplate.m4 new file mode 100644 index 0000000000..36fa0923d4 --- /dev/null +++ b/build-aux/que_rust_boilerplate.m4 @@ -0,0 +1,42 @@ +AC_DEFUN_ONCE([QUE_RUST_BOILERPLATE], [ + + QUE_TRANSFORM_PACKAGE_NAME + QUE_DEVELOPER_MODE + QUE_SHELL_COMPLETION_DIRS + + AC_ARG_ENABLE(debug, + AS_HELP_STRING([--enable-debug], + [Build Rust code with debugging information])) + AM_CONDITIONAL([DEBUG_RELEASE], [test "x$debug_release" = "xyes"]) + + AC_MSG_NOTICE([checking for tools used by automake to build Rust projects]) + AC_PROG_INSTALL + AC_PROG_SED + QUE_PROGVAR([cargo]) + QUE_PROGVAR([jq]) + QUE_PROGVAR([rustc]) + QUE_PROGVAR([cmp]) + QUE_PROGVAR([xargs]) + AM_COND_IF([DEVELOPER_MODE], [ + QUE_PROGVAR([git]) + QUE_PROGVAR([rustfmt]) + ]) + + AC_MSG_CHECKING([whether to build Rust code with debugging information]) + AM_COND_IF([DEBUG_RELEASE], [ + AC_MSG_RESULT(yes) + RUST_TARGET_SUBDIR=debug + ], [ + AC_MSG_RESULT(no) + RUST_TARGET_SUBDIR=release + ]) + AC_SUBST([RUST_TARGET_SUBDIR]) + + AC_REQUIRE([AX_AM_MACROS]) + AX_ADD_AM_MACRO([dnl +EXTRA_DIST += build-aux/que_rust_boilerplate.am + +$($SED -E "s/@PACKAGE_VAR@/$PACKAGE_VAR/g;s/@PACKAGE_NAME@/$PACKAGE_NAME/g" build-aux/que_rust_boilerplate.am) +])dnl + +]) diff --git a/build-aux/que_shell_completion_dirs.am b/build-aux/que_shell_completion_dirs.am new file mode 100644 index 0000000000..5e29abbe74 --- /dev/null +++ b/build-aux/que_shell_completion_dirs.am @@ -0,0 +1,21 @@ +COMPLETIONS_OUT_DIR = completions + +if ENABLE_BASH_COMPLETION +bashcompletiondir = $(BASH_COMPLETION_DIR) +nodist_bashcompletion_DATA = $(COMPLETIONS_OUT_DIR)/$(TRANSFORMED_PACKAGE_NAME) +CLEANFILES += $(nodist_bashcompletion_DATA) +endif + +if ENABLE_FISH_COMPLETION +fishcompletiondir = $(FISH_COMPLETION_DIR) +nodist_fishcompletion_DATA = $(COMPLETIONS_OUT_DIR)/$(TRANSFORMED_PACKAGE_NAME).fish +CLEANFILES += $(nodist_fishcompletion_DATA) +endif + +if ENABLE_ZSH_COMPLETION +zshcompletiondir = $(ZSH_COMPLETION_DIR) +nodist_zshcompletion_DATA = $(COMPLETIONS_OUT_DIR)/_$(TRANSFORMED_PACKAGE_NAME) +CLEANFILES += $(nodist_zshcompletion_DATA) +endif + +# vim: ft=automake diff --git a/build-aux/que_shell_completion_dirs.m4 b/build-aux/que_shell_completion_dirs.m4 new file mode 100644 index 0000000000..380ff53d4b --- /dev/null +++ b/build-aux/que_shell_completion_dirs.m4 @@ -0,0 +1,54 @@ +AC_DEFUN_ONCE([QUE_SHELL_COMPLETION_DIRS], [ + + QUE_TRANSFORM_PACKAGE_NAME + + AC_ARG_WITH([bash-completion-dir], + AS_HELP_STRING([--with-bash-completion-dir[=PATH]], + [Install the bash auto-completion script in this directory. @<:@default=yes@:>@]), + [], + [with_bash_completion_dir=yes]) + AM_CONDITIONAL([ENABLE_BASH_COMPLETION], + [test "x$with_bash_completion_dir" != "xno"]) + + AM_COND_IF([ENABLE_BASH_COMPLETION], + [PKG_CHECK_MODULES([BASH_COMPLETION], [bash-completion >= 2.0], + [BASH_COMPLETION_DIR="$(pkg-config --define-variable=datadir=$datadir --variable=completionsdir bash-completion)"], + [BASH_COMPLETION_DIR="$datadir/bash-completion/completions"])], + [BASH_COMPLETION_DIR="$with_bash_completion_dir"]) + AC_SUBST([BASH_COMPLETION_DIR]) + + AC_ARG_WITH([fish-completion-dir], + AS_HELP_STRING([--with-fish-completion-dir[=PATH]], + [Install the fish auto-completion script in this directory. @<:@default=yes@:>@]), + [], + [with_fish_completion_dir=yes]) + AM_CONDITIONAL([ENABLE_FISH_COMPLETION],[test "x$with_fish_completion_dir" != "xno"]) + + AM_COND_IF([ENABLE_FISH_COMPLETION], + [PKG_CHECK_MODULES([FISH_COMPLETION], [fish >= 3.0], + [FISH_COMPLETION_DIR="$(pkg-config --define-variable=datadir=$datadir --variable=completionsdir fish)"], + [FISH_COMPLETION_DIR="$datadir/fish/vendor_completions.d"])], + [FISH_COMPLETION_DIR="$with_fish_completion_dir"]) + AC_SUBST([FISH_COMPLETION_DIR]) + + AC_ARG_WITH([zsh-completion-dir], + AS_HELP_STRING([--with-zsh-completion-dir[=PATH]], + [Install the zsh auto-completion script in this directory. @<:@default=yes@:>@]), + [], + [with_zsh_completion_dir=yes]) + AM_CONDITIONAL([ENABLE_ZSH_COMPLETION], + [test "x$with_zsh_completion_dir" != "xno"]) + + AM_COND_IF([ENABLE_ZSH_COMPLETION], + [ZSH_COMPLETION_DIR="$datadir/zsh/site-functions"], + [ZSH_COMPLETION_DIR="$with_zsh_completion_dir"]) + AC_SUBST([ZSH_COMPLETION_DIR]) + + AC_REQUIRE([AX_AM_MACROS]) + AX_ADD_AM_MACRO([dnl +EXTRA_DIST += build-aux/que_shell_completion_dirs.am + +$(cat build-aux/que_shell_completion_dirs.am) +])dnl + +]) diff --git a/build-aux/que_subst_man_date.m4 b/build-aux/que_subst_man_date.m4 index ffe6df89f3..b016b2356a 100644 --- a/build-aux/que_subst_man_date.m4 +++ b/build-aux/que_subst_man_date.m4 @@ -1,13 +1,13 @@ AC_DEFUN([QUE_SUBST_MAN_DATE], [ - ax_date_fmt="m4_default($1,%d %B %Y)" - ax_src_file="m4_default($2,*.1.in)" - AS_IF([test ! -e .gitignore], - [ - QUE_PROGVAR([date]) - AX_BUILD_DATE_EPOCH(MAN_DATE, "$ax_date_fmt") - ], [ - QUE_PROGVAR([git]) - MAN_DATE=$($GIT log -1 --format="%cd" --date=format:"$ax_date_fmt" -- $ax_src_file) - ]) - AC_SUBST([MAN_DATE]) + ax_date_fmt="m4_default($1,%d %B %Y)" + ax_src_file="m4_default($2,*.1.in)" + AS_IF([test ! -e .gitignore], + [ + QUE_PROGVAR([date]) + AX_BUILD_DATE_EPOCH(MAN_DATE, "$ax_date_fmt") + ], [ + QUE_PROGVAR([git]) + MAN_DATE=$($GIT log -1 --format="%cd" --date=format:"$ax_date_fmt" -- $ax_src_file) + ]) + AC_SUBST([MAN_DATE]) ]) diff --git a/build-aux/sile_dist_checksums.m4 b/build-aux/sile_dist_checksums.m4 new file mode 100644 index 0000000000..79d8906442 --- /dev/null +++ b/build-aux/sile_dist_checksums.m4 @@ -0,0 +1,15 @@ +AC_DEFUN_ONCE([SILE_DIST_CHECKSUMS], [ + + QUE_DIST_CHECKSUMS + + AM_COND_IF([DEVELOPER_MODE], [ + + AX_ADD_AM_MACRO([dnl +checksum_dist += \ + ; \$(SHA256SUM) sile-\$(VERSION).pdf |\ + \$(TEE) -a \$(distdir).sha256.txt +])dnl + + ]) +]) + diff --git a/classes/base.lua b/classes/base.lua index 8dea6857b9..b9ea72778d 100644 --- a/classes/base.lua +++ b/classes/base.lua @@ -1,3 +1,6 @@ +--- SILE document class interface. +-- @interfaces classes + local class = pl.class() class.type = "class" class._name = "base" @@ -17,7 +20,7 @@ class.options = setmetatable({}, { elseif type(value) == "function" then opts[key] = value elseif type(key) == "number" then - return nil + return else SU.error("Attempted to set an undeclared class option '" .. key .. "'") end @@ -80,11 +83,15 @@ end function class:setOptions (options) options = options or {} -- Classes that add options with dependencies should explicitly handle them, then exempt them from furthur processing. - -- The landscape option is handled explicitly before papersize, then the "rest" of options that are not interdependent. + -- The landscape and crop related options are handled explicitly before papersize, then the "rest" of options that are not interdependent. self.options.landscape = SU.boolean(options.landscape, false) options.landscape = nil self.options.papersize = options.papersize or "a4" options.papersize = nil + self.options.bleed = options.bleed or "0" + options.bleed = nil + self.options.sheetsize = options.sheetsize or nil + options.sheetsize = nil for option, value in pairs(options) do self.options[option] = value end @@ -127,6 +134,33 @@ function class:declareOptions () end return self.papersize end) + self:declareOption("sheetsize", function (_, size) + if size then + self.sheetsize = size + SILE.documentState.sheetSize = SILE.papersize(size, self.options.landscape) + if SILE.documentState.sheetSize[1] < SILE.documentState.paperSize[1] + or SILE.documentState.sheetSize[2] < SILE.documentState.paperSize[2] then + SU.error("Sheet size shall not be smaller than the paper size") + end + if SILE.documentState.sheetSize[1] < SILE.documentState.paperSize[1] + SILE.documentState.bleed then + SU.debug("frames", "Sheet size width augmented to take page bleed into account") + SILE.documentState.sheetSize[1] = SILE.documentState.paperSize[1] + SILE.documentState.bleed + end + if SILE.documentState.sheetSize[2] < SILE.documentState.paperSize[2] + SILE.documentState.bleed then + SU.debug("frames", "Sheet size height augmented to take page bleed into account") + SILE.documentState.sheetSize[2] = SILE.documentState.paperSize[2] + SILE.documentState.bleed + end + else + return self.sheetsize + end + end) + self:declareOption("bleed", function (_, dimen) + if dimen then + self.bleed = dimen + SILE.documentState.bleed = SU.cast("measurement", dimen):tonumber() + end + return self.bleed + end) end function class.declareSettings (_) @@ -150,15 +184,44 @@ function class.declareSettings (_) }) end -function class:loadPackage (packname, options) - local pack = require(("packages.%s"):format(packname)) - if type(pack) == "table" and pack.type == "package" then -- new package - self.packages[pack._name] = pack(options) +function class:loadPackage (packname, options, reload) + local pack + -- Allow loading by injecting whole packages as-is, otherwise try to load it with the usual packages path. + if type(packname) == "table" then + pack, packname = packname, packname._name + elseif type(packname) == "nil" or packname == "nil" or pl.stringx.strip(packname) == "" then + SU.error(("Attempted to load package with an invalid packname '%s'"):format(packname)) + else + pack = require(("packages.%s"):format(packname)) + if pack._name ~= packname then + SU.error(("Loaded module name '%s' does not match requested name '%s'"):format(pack._name, packname)) + end + end + SILE.packages[packname] = pack + if type(pack) == "table" and pack.type == "package" then -- current package api + if self.packages[packname] then + -- If the same package name has been loaded before, we might be loading a modified version of the same package or + -- we might be re-loading the same package, or we might just be doubling up work because somebody called us twice. + -- The package itself should take care of the difference between load and reload based on the reload flag here, + -- but in addition to that we also want to avoid creating a new instance. We want to run the intitializer from the + -- (possibly different) new module, but not create a new instance ID and loose any connections it made. + -- To do this we add a create function that returns the current instance. This brings along the _initialized flag + -- and of course anything else already setup and running. + local current_instance = self.packages[packname] + pack._create = function () return current_instance end + pack(options, true) + else + self.packages[packname] = pack(options, reload) + end else -- legacy package self:initPackage(pack, options) end end +function class:reloadPackage (packname, options) + return self:loadPackage(packname, options, true) +end + function class:initPackage (pack, options) SU.deprecated("class:initPackage(options)", "package(options)", "0.14.0", "0.16.0", [[ This package appears to be a legacy format package. It returns a table @@ -214,11 +277,23 @@ end function class:runHooks (category, options) for _, func in ipairs(self.hooks[category]) do - SU.debug("classhooks", "Running hook from", category, options and "with options " .. #options) + SU.debug("classhooks", "Running hook from", category, options and "with options #" .. #options) func(self, options) end end +--- Register a function as a SILE command. +-- Takes any Lua function and registers it for use as a SILE command (which will in turn be used to process any content +-- nodes identified with the command name. +-- +-- Note that this should only be used to register commands supplied directly by a document class. A similar method is +-- available for packages, `packages:registerCommand`. +-- @tparam string name Name of cammand to register. +-- @tparam function func Callback function to use as command handler. +-- @tparam[opt] nil|string help User friendly short usage string for use in error messages, documentation, etc. +-- @tparam[opt] nil|string pack Information identifying the module registering the command for use in error and usage +-- messages. Usually auto-detected. +-- @see SILE.packages:registerCommand function class.registerCommand (_, name, func, help, pack) SILE.Commands[name] = func if not pack then @@ -330,10 +405,35 @@ function class:registerCommands () self:registerCommand("script", function (options, content) local packopts = packOptions(options) - if SU.hasContent(content) then + local function _deprecated (original, suggested) + SU.deprecated("\\script", "\\lua or \\use", "0.15.0", "0.16.0", ([[ + The \script function has been deprecated. It was overloaded to mean + too many different things and more targeted tools were introduced in + SILE v0.14.0. To load 3rd party modules designed for use with SILE, + replace \script[src=...] with \use[module=...]. To run arbitrary Lua + code inline use \lua{}, optionally with a require= parameter to load + a (non-SILE) Lua module using the Lua module path or src= to load a + file by file path. + + For this use case consider replacing: + + %s + + with: + + %s + ]]):format(original, suggested)) + end + if SU.ast.hasContent(content) then + _deprecated("\\script{...}", "\\lua{...}") return SILE.processString(content[1], options.format or "lua", nil, packopts) elseif options.src then - return SILE.require(options.src) + local module = options.src:gsub("%/", ".") + local original = (("\\script[src=%s]"):format(options.src)) + local result = SILE.require(options.src) + local suggested = (result._name and "\\use[module=%s]" or "\\lua[require=%s]"):format(module) + _deprecated(original, suggested) + return result else SU.error("\\script function requires inline content or a src file path") return SILE.processString(content[1], options.format or "lua", nil, packopts) @@ -342,8 +442,8 @@ function class:registerCommands () self:registerCommand("include", function (options, content) local packopts = packOptions(options) - if SU.hasContent(content) then - local doc = SU.contentToString(content) + if SU.ast.hasContent(content) then + local doc = SU.ast.contentToString(content) return SILE.processString(doc, options.format, nil, packopts) elseif options.src then return SILE.processFile(options.src, options.format, packopts) @@ -354,8 +454,8 @@ function class:registerCommands () self:registerCommand("lua", function (options, content) local packopts = packOptions(options) - if SU.hasContent(content) then - local doc = SU.contentToString(content) + if SU.ast.hasContent(content) then + local doc = SU.ast.contentToString(content) return SILE.processString(doc, "lua", nil, packopts) elseif options.src then return SILE.processFile(options.src, "lua", packopts) @@ -369,8 +469,8 @@ function class:registerCommands () self:registerCommand("sil", function (options, content) local packopts = packOptions(options) - if SU.hasContent(content) then - local doc = SU.contentToString(content) + if SU.ast.hasContent(content) then + local doc = SU.ast.contentToString(content) return SILE.processString(doc, "sil") elseif options.src then return SILE.processFile(options.src, "sil", packopts) @@ -381,8 +481,8 @@ function class:registerCommands () self:registerCommand("xml", function (options, content) local packopts = packOptions(options) - if SU.hasContent(content) then - local doc = SU.contentToString(content) + if SU.ast.hasContent(content) then + local doc = SU.ast.contentToString(content) return SILE.processString(doc, "xml", nil, packopts) elseif options.src then return SILE.processFile(options.src, "xml", packopts) @@ -394,7 +494,7 @@ function class:registerCommands () self:registerCommand("use", function (options, content) local packopts = packOptions(options) if content[1] and string.len(content[1]) > 0 then - local doc = SU.contentToString(content) + local doc = SU.ast.contentToString(content) SILE.processString(doc, "lua", nil, packopts) else if options.src then @@ -439,7 +539,7 @@ function class:registerCommands () end, "Inserts a penalty node. Option is penalty= for the size of the penalty.") self:registerCommand("discretionary", function (options, _) - local discretionary = SILE.nodefactory.discretionary({}) + local discretionary = SILE.types.node.discretionary({}) if options.prebreak then local hbox = SILE.typesetter:makeHbox({ options.prebreak }) discretionary.prebreak = { hbox } @@ -462,12 +562,12 @@ function class:registerCommands () self:registerCommand("kern", function (options, _) local width = SU.cast("length", options.width):absolute() - SILE.typesetter:pushHorizontal(SILE.nodefactory.kern(width)) + SILE.typesetter:pushHorizontal(SILE.types.node.kern(width)) end, "Inserts a glue node. The width option denotes the glue dimension.") self:registerCommand("skip", function (options, _) options.discardable = SU.boolean(options.discardable, false) - options.height = SILE.length(options.height):absolute() + options.height = SILE.types.length(options.height):absolute() SILE.typesetter:leaveHmode() if options.discardable then SILE.typesetter:pushVglue(options) diff --git a/classes/bible.lua b/classes/bible.lua index 0e572d9e1e..82ec301da1 100644 --- a/classes/bible.lua +++ b/classes/bible.lua @@ -1,3 +1,6 @@ +--- bible document class. +-- @use classes.bible + local plain = require("classes.plain") local class = pl.class(plain) @@ -192,19 +195,19 @@ end function class:endPage () if (self:oddPage() and SILE.scratch.headers.right) then SILE.typesetNaturally(SILE.getFrame("runningHead"), function () - SILE.settings:set("current.parindent", SILE.nodefactory.glue()) - SILE.settings:set("document.lskip", SILE.nodefactory.glue()) - SILE.settings:set("document.rskip", SILE.nodefactory.glue()) - -- SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) + SILE.settings:set("document.lskip", SILE.types.node.glue()) + SILE.settings:set("document.rskip", SILE.types.node.glue()) + -- SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) SILE.process(SILE.scratch.headers.right) SILE.call("par") end) elseif (not(self:oddPage()) and SILE.scratch.headers.left) then SILE.typesetNaturally(SILE.getFrame("runningHead"), function () - SILE.settings:set("current.parindent", SILE.nodefactory.glue()) - SILE.settings:set("document.lskip", SILE.nodefactory.glue()) - SILE.settings:set("document.rskip", SILE.nodefactory.glue()) - -- SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) + SILE.settings:set("document.lskip", SILE.types.node.glue()) + SILE.settings:set("document.rskip", SILE.types.node.glue()) + -- SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) SILE.process(SILE.scratch.headers.left) SILE.call("par") end) @@ -261,9 +264,9 @@ function class:registerCommands () SILE.call("save-verse-number", options, content) SILE.call("left-running-head", {}, function () SILE.settings:temporarily(function () - SILE.settings:set("document.lskip", SILE.nodefactory.glue()) - SILE.settings:set("document.rskip", SILE.nodefactory.glue()) - -- SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue()) + SILE.settings:set("document.lskip", SILE.types.node.glue()) + SILE.settings:set("document.rskip", SILE.types.node.glue()) + -- SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) SILE.call("font", { size = "10pt", family = "Gentium" }, function () SILE.call("first-reference") SILE.call("hfill") @@ -274,9 +277,9 @@ function class:registerCommands () end) SILE.call("right-running-head", {}, function () SILE.settings:temporarily(function () - SILE.settings:set("document.lskip", SILE.nodefactory.glue()) - SILE.settings:set("document.rskip", SILE.nodefactory.glue()) - SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue()) + SILE.settings:set("document.lskip", SILE.types.node.glue()) + SILE.settings:set("document.rskip", SILE.types.node.glue()) + SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) SILE.call("font", { size = "10pt", family = "Gentium" }, function () -- SILE.call("font", { style = "italic" }, SILE.scratch.theChapter) SILE.call("hfill") diff --git a/classes/book.lua b/classes/book.lua index 20a0290d24..286d479d2a 100644 --- a/classes/book.lua +++ b/classes/book.lua @@ -1,3 +1,6 @@ +--- book document class. +-- @use classes.book + local plain = require("classes.plain") local class = pl.class(plain) @@ -55,20 +58,20 @@ function class:endPage () if self:oddPage() and SILE.scratch.headers.right then SILE.typesetNaturally(SILE.getFrame("runningHead"), function () SILE.settings:toplevelState() - SILE.settings:set("current.parindent", SILE.nodefactory.glue()) - SILE.settings:set("document.lskip", SILE.nodefactory.glue()) - SILE.settings:set("document.rskip", SILE.nodefactory.glue()) - -- SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) + SILE.settings:set("document.lskip", SILE.types.node.glue()) + SILE.settings:set("document.rskip", SILE.types.node.glue()) + -- SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) SILE.process(SILE.scratch.headers.right) SILE.call("par") end) elseif not self:oddPage() and SILE.scratch.headers.left then SILE.typesetNaturally(SILE.getFrame("runningHead"), function () SILE.settings:toplevelState() - SILE.settings:set("current.parindent", SILE.nodefactory.glue()) - SILE.settings:set("document.lskip", SILE.nodefactory.glue()) - SILE.settings:set("document.rskip", SILE.nodefactory.glue()) - -- SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) + SILE.settings:set("document.lskip", SILE.types.node.glue()) + SILE.settings:set("document.rskip", SILE.types.node.glue()) + -- SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) SILE.process(SILE.scratch.headers.left) SILE.call("par") end) @@ -105,7 +108,7 @@ function class:registerCommands () number = self.packages.counters:formatMultilevelCounter(self:getMultilevelCounter("sectioning")) end if SU.boolean(options.toc, true) then - SILE.call("tocentry", { level = level, number = number }, SU.subContent(content)) + SILE.call("tocentry", { level = level, number = number }, SU.ast.subContent(content)) end if SU.boolean(options.numbering, true) then if options.msg then diff --git a/classes/diglot.lua b/classes/diglot.lua index 402679a4b1..acfc6f9b62 100644 --- a/classes/diglot.lua +++ b/classes/diglot.lua @@ -1,3 +1,6 @@ +--- diglot document class. +-- @use classes.diglot + local plain = require("classes.plain") local class = pl.class(plain) diff --git a/classes/docbook.lua b/classes/docbook.lua index 4c967f5f8e..6081a5c1a9 100644 --- a/classes/docbook.lua +++ b/classes/docbook.lua @@ -1,3 +1,6 @@ +--- docbook document class. +-- @use classes.docbook + local plain = require("classes.plain") local class = pl.class(plain) @@ -22,7 +25,7 @@ function class:_init (options) -- SILE sensibly does not define a pixels unit because it has no meaning in its frame of reference. However the -- Docbook standard requires them and even defaults to them for bare numbers, even while warning against their use. -- Here we define a px arbitrarily to be the equivalent point unit if output was 300 DPI. - SILE.units.px = { + SILE.types.unit.px = { definition = "0.24pt" } end diff --git a/classes/jbook.lua b/classes/jbook.lua index 7cd2e5f535..6c98fa16f5 100644 --- a/classes/jbook.lua +++ b/classes/jbook.lua @@ -1,3 +1,6 @@ +--- jbook document class. +-- @use classes.jbook + local tbook = require("classes.tbook") local class = pl.class(tbook) diff --git a/classes/jplain.lua b/classes/jplain.lua index b5350ef601..2ddd37a0bd 100644 --- a/classes/jplain.lua +++ b/classes/jplain.lua @@ -1,3 +1,6 @@ +--- jplain document class. +-- @use classes.jplain + -- Basic! Transitional! In development! Not very good! Don't use it! local tplain = require("classes.tplain") diff --git a/classes/letter.lua b/classes/letter.lua index d812da98e9..5a95f5bd45 100644 --- a/classes/letter.lua +++ b/classes/letter.lua @@ -1,3 +1,6 @@ +--- letter document class. +-- @use classes.letter + local plain = require("classes.plain") local class = pl.class(plain) @@ -27,8 +30,8 @@ function class:registerCommands () plain.registerCommands(self) self:registerCommand("letter", function (_, content) - SILE.settings:set("current.parindent", SILE.nodefactory.glue()) - SILE.settings:set("document.parindent", SILE.nodefactory.glue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) + SILE.settings:set("document.parindent", SILE.types.node.glue()) SILE.call("raggedright", {}, function () SILE.call("letter:format:date") SILE.call("bigskip") diff --git a/classes/markdown.lua b/classes/markdown.lua deleted file mode 100644 index e2a997e73b..0000000000 --- a/classes/markdown.lua +++ /dev/null @@ -1,70 +0,0 @@ --- You will need my lunamark fork from https://github.com/simoncozens/lunamark --- for the AST writer. - -local book = require("classes.book") -local class = pl.class(book) -class._name = "markdown" - -SILE.inputs.markdown = { - order = 2, - appropriate = function (fn, _) - return fn:match("md$") or fn:match("markdown$") - end, - process = function (data) - local lunamark = require("lunamark") - local reader = lunamark.reader.markdown - local writer = lunamark.writer.ast.new() - local parse = reader.new(writer) - local t = parse(data) - t = { [1] = t, id = "document", options = { class = "markdown" }} - -- SILE.inputs.common.init(fn, t) - SILE.process(t[1]) - end -} - -function class:_init (options) - book._init(self, options) - self:loadPackage("url") - self:loadPackage("image") -end - -function class:registerCommands () - - book.registerCommands(self) - - self:registerCommand("sect1", function (options, content) - SILE.call("chapter", options, content) - end) - - self:registerCommand("sect2", function (options, content) - SILE.call("section", options, content) - end) - - self:registerCommand("sect3", function (options, content) - SILE.call("subsection", options, content) - end) - - self:registerCommand("emphasis", function (options, content) - SILE.call("em", options, content) - end) - - self:registerCommand("paragraph", function (_, content) - SILE.process(content) - SILE.call("par") - end) - - self:registerCommand("bulletlist", function (_, content) - SILE.process(content) - end) - - self:registerCommand("link", function (_, content) - SILE.call("verbatim:font", {}, content) - end) - - self:registerCommand("image", function (_, content) - SILE.call("img", {src=content.src}) - end) - -end - -return class diff --git a/classes/pecha.lua b/classes/pecha.lua index 8138650e63..9e7a088329 100644 --- a/classes/pecha.lua +++ b/classes/pecha.lua @@ -1,3 +1,6 @@ +--- tbook document class. +-- @use classes.tbook + local plain = require("classes.plain") local class = pl.class(plain) @@ -41,9 +44,9 @@ function class:_init(options) self:loadPackage("rotate") self:registerPostinit(function () SILE.call("language", { main = "bo" }) - SILE.settings:set("document.lskip", SILE.nodefactory.hfillglue()) - SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue()) - SILE.settings:set("document.parindent", SILE.nodefactory.glue()) + SILE.settings:set("document.lskip", SILE.types.node.hfillglue()) + SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) + SILE.settings:set("document.parindent", SILE.types.node.glue()) end) end diff --git a/classes/plain.lua b/classes/plain.lua index 12ef08f951..cd5b18b846 100644 --- a/classes/plain.lua +++ b/classes/plain.lua @@ -1,3 +1,6 @@ +--- plain document class. +-- @use classes.plain + local base = require("classes.base") local class = pl.class(base) @@ -64,7 +67,7 @@ function class:declareSettings () SILE.settings:declare({ parameter = "plain." .. k .. "skipamount", type = "vglue", - default = SILE.nodefactory.vglue(v), + default = SILE.types.node.vglue(v), help = "The amount of a \\" .. k .. "skip" }) end @@ -78,13 +81,13 @@ function class:registerCommands () if #SILE.typesetter.state.nodes ~= 0 then SU.warn("\\noindent called after nodes already received in a paragraph, the setting will have no effect because the parindent (if any) has already been output") end - SILE.settings:set("current.parindent", SILE.nodefactory.glue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) SILE.process(content) end, "Do not add an indent to the start of this paragraph") self:registerCommand("neverindent", function (_, content) - SILE.settings:set("current.parindent", SILE.nodefactory.glue()) - SILE.settings:set("document.parindent", SILE.nodefactory.glue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) + SILE.settings:set("document.parindent", SILE.types.node.glue()) SILE.process(content) end, "Turn off all indentation") @@ -101,25 +104,25 @@ function class:registerCommands () end self:registerCommand("hfill", function (_, _) - SILE.typesetter:pushExplicitGlue(SILE.nodefactory.hfillglue()) + SILE.typesetter:pushExplicitGlue(SILE.types.node.hfillglue()) end, "Add a huge horizontal glue") self:registerCommand("vfill", function (_, _) SILE.typesetter:leaveHmode() - SILE.typesetter:pushExplicitVglue(SILE.nodefactory.vfillglue()) + SILE.typesetter:pushExplicitVglue(SILE.types.node.vfillglue()) end, "Add huge vertical glue") self:registerCommand("hss", function (_, _) SILE.typesetter:initline() - SILE.typesetter:pushGlue(SILE.nodefactory.hssglue()) - table.insert(SILE.typesetter.state.nodes, SILE.nodefactory.zerohbox()) + SILE.typesetter:pushGlue(SILE.types.node.hssglue()) + table.insert(SILE.typesetter.state.nodes, SILE.types.node.zerohbox()) end, "Add glue which stretches and shrinks horizontally (good for centering)") self:registerCommand("vss", function (_, _) - SILE.typesetter:pushExplicitVglue(SILE.nodefactory.vssglue()) + SILE.typesetter:pushExplicitVglue(SILE.types.node.vssglue()) end, "Add glue which stretches and shrinks vertically") - local _thinspacewidth = SILE.measurement(0.16667, "em") + local _thinspacewidth = SILE.types.measurement(0.16667, "em") self:registerCommand("thinspace", function (_, _) SILE.call("glue", { width = _thinspacewidth }) @@ -130,7 +133,7 @@ function class:registerCommands () end) self:registerCommand("enspace", function (_, _) - SILE.call("glue", { width = SILE.measurement(1, "en") }) + SILE.call("glue", { width = SILE.types.measurement(1, "en") }) end) self:registerCommand("relax", function (_, _) @@ -140,7 +143,7 @@ function class:registerCommands () SILE.call("enspace") end) - local _quadwidth = SILE.measurement(1, "em") + local _quadwidth = SILE.types.measurement(1, "em") self:registerCommand("quad", function (_, _) SILE.call("glue", { width = _quadwidth }) @@ -221,27 +224,15 @@ function class:registerCommands () SILE.call("penalty", { penalty = -20000 }) end, "Fills the page with stretchable vglue and then requests a non-negotiable page break") - self:registerCommand("justified", function (_, content) - SILE.settings:set("document.rskip", nil) - SILE.settings:set("document.spaceskip", nil) - SILE.process(content) - SILE.call("par") - end) - - self:registerCommand("rightalign", function (_, content) - SILE.call("raggedleft", {}, function () - SILE.process(content) - SILE.call("par") - end) - end) - self:registerCommand("em", function (_, content) - SILE.call("font", { style = "Italic" }, content) - end) + local style = SILE.settings:get("font.style") + local toggle = (style and style:lower() == "italic") and "Regular" or "Italic" + SILE.call("font", { style = toggle }, content) + end, "Emphasizes its contents by switching the font style to italic (or back to regular if already italic)") self:registerCommand("strong", function (_, content) SILE.call("font", { weight = 700 }, content) - end) + end, "Sets the font weight to bold (700)") self:registerCommand("code", function (options, content) -- IMPLEMENTATION NOTE: @@ -260,27 +251,118 @@ function class:registerCommands () SILE.call("font", { language = "und" }, content) end) + self:registerCommand("center", function (_, content) + if #SILE.typesetter.state.nodes ~= 0 then + SU.warn("\\center environment started after other nodes in a paragraph, may not center as expected") + end + SILE.settings:temporarily(function () + local lskip = SILE.settings:get("document.lskip") or SILE.types.node.glue() + local rskip = SILE.settings:get("document.rskip") or SILE.types.node.glue() + SILE.settings:set("document.parindent", SILE.types.node.glue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) + SILE.settings:set("document.lskip", SILE.types.node.hfillglue(lskip.width.length)) + SILE.settings:set("document.rskip", SILE.types.node.hfillglue(rskip.width.length)) + SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) + SILE.settings:set("document.spaceskip", SILE.types.length("1spc", 0, 0)) + SILE.process(content) + SILE.call("par") + end) + end, "Typeset its contents in a centered block (keeping margins).") + self:registerCommand("raggedright", function (_, content) - SILE.call("ragged", { right = true }, content) - end) + SILE.settings:temporarily(function () + local lskip = SILE.settings:get("document.lskip") or SILE.types.node.glue() + local rskip = SILE.settings:get("document.rskip") or SILE.types.node.glue() + SILE.settings:set("document.lskip", SILE.types.node.glue(lskip.width.length)) + SILE.settings:set("document.rskip", SILE.types.node.hfillglue(rskip.width.length)) + SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) + SILE.settings:set("document.spaceskip", SILE.types.length("1spc", 0, 0)) + SILE.process(content) + SILE.call("par") + end) + end, "Typeset its contents in a left aligned block (keeping margins).") self:registerCommand("raggedleft", function (_, content) - SILE.call("ragged", { left = true }, content) + SILE.settings:temporarily(function () + local lskip = SILE.settings:get("document.lskip") or SILE.types.node.glue() + local rskip = SILE.settings:get("document.rskip") or SILE.types.node.glue() + SILE.settings:set("document.lskip", SILE.types.node.hfillglue(lskip.width.length)) + SILE.settings:set("document.rskip", SILE.types.node.glue(rskip.width.length)) + SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) + SILE.settings:set("document.spaceskip", SILE.types.length("1spc", 0, 0)) + SILE.process(content) + SILE.call("par") + end) + end, "Typeset its contents in a right aligned block (keeping margins).") + + self:registerCommand("justified", function (_, content) + SILE.settings:temporarily(function () + local lskip = SILE.settings:get("document.lskip") or SILE.types.node.glue() + local rskip = SILE.settings:get("document.rskip") or SILE.types.node.glue() + -- Keep the fixed part of the margins for nesting but remove the stretchability. + SILE.settings:set("document.lskip", SILE.types.node.glue(lskip.width.length)) + SILE.settings:set("document.rskip", SILE.types.node.glue(rskip.width.length)) + -- Reset parfillskip to its default value, in case the surrounding context + -- is ragged and cancelled it. + SILE.settings:set("typesetter.parfillskip", nil, false, true) + SILE.settings:set("document.spaceskip", nil) + SILE.process(content) + SILE.call("par") + end) + end, "Typeset its contents in a justified block (keeping margins).") + + self:registerCommand("ragged", function (options, content) + -- Fairly dubious command for compatibility + local l = SU.boolean(options.left, false) + local r = SU.boolean(options.right, false) + if l and r then + SILE.call("center", {}, content) + elseif r then + SILE.call("raggedleft", {}, content) + elseif l then + SILE.call("raggedright", {}, content) + else + SILE.call("justified", {}, content) + end end) + self:registerCommand("rightalign", function (_, content) + SU.deprecated("\\rightalign", "\\raggedleft", "0.15.0", "0.17.0") + SILE.call("raggedleft", {}, content) + end) + + self:registerCommand("blockquote", function (_, content) + SILE.call("smallskip") + SILE.typesetter:leaveHmode() + SILE.settings:temporarily(function () + local indent = SILE.types.measurement("2em"):absolute() + local lskip = SILE.settings:get("document.lskip") or SILE.types.node.glue() + local rskip = SILE.settings:get("document.rskip") or SILE.types.node.glue() + -- We keep the stretcheability of the lskip and rskip: honoring text alignment + -- from the parent context. + SILE.settings:set("document.lskip", SILE.types.node.glue(lskip.width + indent)) + SILE.settings:set("document.rskip", SILE.types.node.glue(rskip.width + indent)) + SILE.settings:set("font.size", SILE.settings:get("font.size") * 0.95) + SILE.process(content) + SILE.typesetter:leaveHmode() + end) + SILE.call("smallskip") + end, "A blockquote environment") + self:registerCommand("quote", function (_, content) - SU.deprecated("\\quote", "\\pullquote", "0.14.5", "0.16.0", [[ + SU.deprecated("\\quote", "\\pullquote or \\blockquote", "0.14.5", "0.16.0", [[ The \quote command has *such* bad output it is being completely - deprecated as unsuitable for general purpose use. The pullquote - package (\use[module=packages.pullquote]) provides one alternative, - but you can also copy and adapt the original source from the plain + deprecated as unsuitable for general purpose use. + The pullquote package (\use[module=packages.pullquote]) provides one + alternative, and the blockquote environment provides another. + But you can also copy and adapt the original source from the plain class if you need to maintain exact output past SILE v0.16.0.]]) SILE.call("smallskip") SILE.call("par") - local margin = SILE.measurement(2.5, "em") + local margin = SILE.types.measurement(2.5, "em") SILE.settings:set("document.lskip", margin) SILE.settings:set("document.lskip", margin) - SILE.call("font", { size = SILE.measurement(0.8, "em") }, function () + SILE.call("font", { size = SILE.types.measurement(0.8, "em") }, function () SILE.call("noindent") SILE.process(content) end) @@ -310,29 +392,6 @@ function class:registerCommands () SILE.settings:set("linebreak.tolerance", 10000) end) - self:registerCommand("center", function (_, content) - if #SILE.typesetter.state.nodes ~= 0 then - SU.warn("\\center environment started after other nodes in a paragraph, may not center as expected") - end - SILE.settings:temporarily(function() - SILE.settings:set("current.parindent", 0) - SILE.settings:set("document.parindent", 0) - SILE.call("ragged", { left = true, right = true }, content) - end) - end) - - self:registerCommand("ragged", function (options, content) - SILE.settings:temporarily(function () - if SU.boolean(options.left, false) then SILE.settings:set("document.lskip", SILE.nodefactory.hfillglue()) end - if SU.boolean(options.right, false) then SILE.settings:set("document.rskip", SILE.nodefactory.hfillglue()) end - SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue()) - SILE.settings:set("document.parindent", SILE.nodefactory.glue()) - SILE.settings:set("document.spaceskip", SILE.length("1spc", 0, 0)) - SILE.process(content) - SILE.call("par") - end) - end) - self:registerCommand("hbox", function (_, content) local hbox, hlist = SILE.typesetter:makeHbox(content) SILE.typesetter:pushHbox(hbox) @@ -351,7 +410,7 @@ function class:registerCommands () self:registerCommand("vbox", function (options, content) local vbox SILE.settings:temporarily(function () - if options.width then SILE.settings:set("typesetter.breakwidth", SILE.length(options.width)) end + if options.width then SILE.settings:set("typesetter.breakwidth", SILE.types.length(options.width)) end SILE.typesetter:pushState() SILE.process(content) SILE.typesetter:leaveHmode(1) diff --git a/classes/tbook.lua b/classes/tbook.lua index 11c2e87217..aad8c1620c 100644 --- a/classes/tbook.lua +++ b/classes/tbook.lua @@ -1,3 +1,6 @@ +--- tbook document class. +-- @use classes.tbook + local book = require("classes.book") local tplain = require("classes.tplain") diff --git a/classes/tplain.lua b/classes/tplain.lua index f1c6ec189a..9981044f99 100644 --- a/classes/tplain.lua +++ b/classes/tplain.lua @@ -1,3 +1,6 @@ +--- tplain document class. +-- @use classes.tplain + -- Basic! Transitional! In development! Not very good! Don't use it! local plain = require("classes.plain") @@ -24,7 +27,7 @@ function class:_t_common () end) self.defaultFrameset.content.tate = self.options.layout == "tate" self:declareHanmenFrame("content", self.defaultFrameset.content) - SILE.settings:set("document.parindent", SILE.nodefactory.glue("10pt")) + SILE.settings:set("document.parindent", SILE.types.node.glue("10pt")) end function class:_init (options) diff --git a/classes/triglot.lua b/classes/triglot.lua index eaf1bc4be5..4af034b86f 100644 --- a/classes/triglot.lua +++ b/classes/triglot.lua @@ -1,3 +1,6 @@ +--- triglot document class. +-- @use classes.triglot + local book = require("classes.book") local class = pl.class(book) @@ -44,7 +47,7 @@ function class:_init (options) }) SILE.settings:set("linebreak.tolerance", 5000) - SILE.settings:set("document.parindent", SILE.nodefactory.glue()) + SILE.settings:set("document.parindent", SILE.types.node.glue()) end diff --git a/configure.ac b/configure.ac index 641a78f731..a7da9b54c1 100644 --- a/configure.ac +++ b/configure.ac @@ -1,11 +1,15 @@ AC_PREREQ([2.69]) -AC_INIT([sile], [m4_esyscmd(build-aux/git-version-gen .tarball-version)], [simon@simon-cozens.org]) +AC_INIT([sile], [m4_esyscmd(build-aux/git-version-gen .tarball-version)], [caleb@alerque.com]) AC_CONFIG_AUX_DIR([build-aux]) AC_CONFIG_MACRO_DIR([build-aux]) AM_INIT_AUTOMAKE([foreign tar-pax dist-zstd dist-zip no-dist-gzip color-tests subdir-objects]) AM_SILENT_RULES([yes]) -AM_CONDITIONAL([IS_SDIST], [test ! -e .gitignore]) +QUE_GIT_VERSION +QUE_TRANSFORM_PACKAGE_NAME +QUE_DEVELOPER_MODE +QUE_DIST_CHECKSUMS +SILE_DIST_CHECKSUMS # Checks for programs. AC_PROG_CC @@ -24,17 +28,16 @@ LT_INIT([dlopen]) AC_CANONICAL_HOST -QUE_TRANSFORM_PACKAGE_NAME +QUE_RUST_BOILERPLATE +QUE_DOCKER_BOILERPLATE -AC_ARG_ENABLE([dependency-checks], - AS_HELP_STRING([--disable-dependency-checks], - [Disable dependency checks])) -AM_CONDITIONAL([DEPENDENCY_CHECKS], [test "x$enable_dependency_checks" != "xno"]) +AM_CONDITIONAL([SHARED], [test "x$enable_shared" = "xyes"]) +AM_CONDITIONAL([STATIC], [test "x$enable_static" = "xyes"]) -AC_ARG_ENABLE([developer], - AS_HELP_STRING([--enable-developer], - [Check for and enable tooling required only for developers])) -AM_CONDITIONAL([DEVELOPER], [test "x$enable_developer" = "xyes"]) +AC_ARG_ENABLE([embedded], + AS_HELP_STRING([--enable-embedded-resources], + [Compile resources such as Lua module files directly into the Rust CLI binary])) +AM_CONDITIONAL([EMBEDDED_RESOURCES], [test "x$enable_embedded_resources" = "xyes"]) AC_ARG_ENABLE([font-variations], AS_HELP_STRING([--disable-font-variations], @@ -68,140 +71,153 @@ AC_ARG_WITH([system-luarocks], AM_CONDITIONAL([SYSTEM_LUAROCKS], [test "x$with_system_luarocks" = "xyes"]) AC_SUBST([SYSTEM_LUAROCKS]) +AC_ARG_WITH([system-lua-sources], + AS_HELP_STRING([--with-system-lua-sources], + [Don’t compile against vendored Lua sources, use system headers])) +AM_CONDITIONAL([SYSTEM_LUA_SOURCES], [test "x$with_system_lua_sources" = "xyes"]) +AC_SUBST([SYSTEM_LUA_SOURCES]) + AC_ARG_WITH([luajit], - AS_HELP_STRING([--with-luajit], - [Run under LuaJIT instead of Lua])) -AM_CONDITIONAL([LUAJIT], [test "x$with_luajit" = "xyes"]) + AS_HELP_STRING([--without-luajit], + [Prefer LuaJIT over PUC Lua, even if the latter is newer])) +AM_CONDITIONAL([LUAJIT], [test "x$with_luajit" != "xno"]) AC_ARG_WITH([manual], AS_HELP_STRING([--with-manual], [Rebuild manual and install to system’s PDF documentation directory])) AM_CONDITIONAL([MANUAL], [test "x$with_manual" = "xyes"]) -AM_CONDITIONAL([FONT_DOWNLOAD_TOOLS], [test -z ${DEVELOPER_TRUE} || (test -z ${MANUAL_TRUE} && test -z ${IS_SDIST_FALSE})]) +AM_CONDITIONAL([FONT_DOWNLOAD_TOOLS], [test -z ${DEVELOPER_MODE_TRUE} || (test -z ${SOURCE_IS_DIST_TRUE} && test -z ${MANUAL_TRUE})]) AC_SUBST([FONT_DOWNLOAD_TOOLS]) -AM_COND_IF([DEPENDENCY_CHECKS], [ +AC_MSG_NOTICE([checking for SILE specific build dependencies]) - QUE_FONT(Gentium Plus) +QUE_FONT(Gentium Plus) - AM_COND_IF([MANUAL], [ - AC_PATH_PROG([DOT], [dot]) - AC_PATH_PROG([GS], [gs]) - ]) - AC_PATH_PROG([PDFINFO], [pdfinfo]) +QUE_PROGVAR([pdfinfo]) +QUE_PROGVAR([jq]) +QUE_PROGVAR([xargs]) +AM_COND_IF([MANUAL], [ + QUE_PROGVAR([dot]) + QUE_PROGVAR([gs]) + ]) - AC_MSG_CHECKING([for OS X]) - have_appkit=no - case $host_os in - darwin*) +AC_MSG_CHECKING([for OS X]) +have_appkit=no +case $host_os in + darwin*) + AC_MSG_RESULT([yes]) + AC_MSG_CHECKING([for AppKit works]) + save_LIBS="$LIBS" + LIBS="$LIBS -framework AppKit -fmodules" + AC_LANG_PUSH([Objective C]) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[@import AppKit;]],[[]])], + [ + have_appkit=yes AC_MSG_RESULT([yes]) - AC_MSG_CHECKING([for AppKit works]) - save_LIBS="$LIBS" - LIBS="$LIBS -framework AppKit -fmodules" - AC_LANG_PUSH([Objective C]) - AC_LINK_IFELSE([AC_LANG_PROGRAM([[@import AppKit;]],[[]])], - [ - have_appkit=yes - AC_MSG_RESULT([yes]) - ], [ - have_appkit=no - AC_MSG_RESULT([no]) - ]) - AC_LANG_POP([Objective C]) - LIBS="$save_LIBS" - ;; - *) + ], [ + have_appkit=no AC_MSG_RESULT([no]) - ;; - esac - - AM_COND_IF([FONTCONFIG], - [PKG_CHECK_MODULES(FONTCONFIG, [fontconfig], - [], - [AC_MSG_FAILURE([Fontconfig package not found])])]) - - AM_COND_IF([HARFBUZZ], - [PKG_CHECK_MODULES(HARFBUZZ, [harfbuzz >= 2.7.4], - [], - [AC_MSG_FAILURE([--with-harfbuzz was given, but harfbuzz not found])]) ]) - - AM_COND_IF([FONT_VARIATIONS], - [PKG_CHECK_MODULES(HARFBUZZ_SUBSET, [harfbuzz-subset >= 6.0.0], - [AC_DEFINE(HAVE_HARFBUZZ_SUBSET, [1], [Have harfbuzz-subset library])], - [AC_MSG_FAILURE([--enable-font-variations was given, but harfbuzz version not new enough to include required subset library functions])])]) - - AM_COND_IF([SYSTEM_LIBTEXPDF], - [AC_CHECK_LIB([texpdf], [texpdf_doc_set_verbose], - [], - [AC_MSG_FAILURE([--with-system-libtexpdf was given, but test for libtexpdf failed])])], - [AC_CONFIG_SUBDIRS([libtexpdf])]) - - PKG_CHECK_MODULES(ICU, icu-uc icu-io, [ + ]) + AC_LANG_POP([Objective C]) + LIBS="$save_LIBS" + ;; + *) + AC_MSG_RESULT([no]) + ;; +esac + +AM_COND_IF([FONTCONFIG], + [PKG_CHECK_MODULES(FONTCONFIG, [fontconfig], + [], + [AC_MSG_FAILURE([Fontconfig package not found])])]) + +AM_COND_IF([HARFBUZZ], + [PKG_CHECK_MODULES(HARFBUZZ, [harfbuzz >= 2.7.4], + [], + [AC_MSG_FAILURE([--with-harfbuzz was given, but harfbuzz not found])]) ]) + +AM_COND_IF([FONT_VARIATIONS], + [PKG_CHECK_MODULES(HARFBUZZ_SUBSET, [harfbuzz-subset >= 6.0.0], + [AC_DEFINE(HAVE_HARFBUZZ_SUBSET, [1], [Have harfbuzz-subset library])], + [AC_MSG_FAILURE([--enable-font-variations was given, but harfbuzz version not new enough to include required subset library functions])])]) + +AM_COND_IF([SYSTEM_LIBTEXPDF], + [AC_CHECK_LIB([texpdf], [texpdf_doc_set_verbose], + [], + [AC_MSG_FAILURE([--with-system-libtexpdf was given, but test for libtexpdf failed])])], + [AC_CONFIG_SUBDIRS([libtexpdf])]) + +PKG_CHECK_MODULES(ICU, icu-uc icu-io, [ + with_icu=yes +],[ + AC_CHECK_TOOL(ICU_CONFIG, icu-config, no) + AC_MSG_CHECKING([for ICU by using icu-config fallback]) + if test "$ICU_CONFIG" != "no" && "$ICU_CONFIG" --version >/dev/null; then + ICU_LIBS=`icu-config --ldflags-libsonly --ldflags-icuio` + ICU_CFLAGS=`icu-config --cppflags` with_icu=yes - ],[ - AC_CHECK_TOOL(ICU_CONFIG, icu-config, no) - AC_MSG_CHECKING([for ICU by using icu-config fallback]) - if test "$ICU_CONFIG" != "no" && "$ICU_CONFIG" --version >/dev/null; then - ICU_LIBS=`icu-config --ldflags-libsonly --ldflags-icuio` - ICU_CFLAGS=`icu-config --cppflags` - with_icu=yes - AC_MSG_RESULT([yes]) - else - AC_MSG_FAILURE([Required ICU library not found]) - fi - ]) + AC_MSG_RESULT([yes]) + else + AC_MSG_FAILURE([Required ICU library not found]) + fi +]) - AX_PROG_LUA([5.1]) - AX_LUA_HEADERS - AX_LUA_LIBS +AX_PROG_LUA([5.1]) +AX_LUA_HEADERS +AX_LUA_LIBS - AM_COND_IF([SYSTEM_LUAROCKS], [ - AS_IF([test "$LUA_SHORT_VERSION" -lt 52], [ - AM_COND_IF([LUAJIT], [], [ - AX_LUA_MODULE([bit32], [bit32]) - ]) +AM_COND_IF([SYSTEM_LUAROCKS], [ + AS_IF([test "$LUA_SHORT_VERSION" -lt 52], [ + AM_COND_IF([LUAJIT], [], [ + AX_LUA_MODULE([bit32], [bit32]) ]) - AX_LUA_MODULE([cassowary], [cassowary]) - AS_IF([test "$LUA_SHORT_VERSION" -lt 53], - AX_LUA_MODULE([compat53], [compat53]) - ) - AX_LUA_MODULE([cosmo], [cosmo]) - AX_LUA_MODULE([cldr], [cldr]) - AX_LUA_MODULE([fluent], [fluent]) - AX_LUA_MODULE([linenoise], [linenoise]) - AX_LUA_MODULE([loadkit], [loadkit]) - AX_LUA_MODULE([lpeg], [lpeg]) - AX_LUA_MODULE([zlib], [lua-zlib]) - AX_LUA_MODULE([cliargs], [lua_cliargs]) - AX_LUA_MODULE([epnf], [luaepnf]) - AX_LUA_MODULE([lxp], [luaexpat]) - AX_LUA_MODULE([lfs], [luafilesystem]) - AX_LUA_MODULE([repl], [luarepl]) - AX_LUA_MODULE([ssl], [luasec]) - AX_LUA_MODULE([socket], [luasocket]) - AX_LUA_MODULE([lua-utf8], [luautf8]) - AX_LUA_MODULE([pl], [penlight]) - AX_LUA_MODULE([vstruct], [vstruct]) - ], [ - QUE_PROGVAR([luarocks]) - ]) - - # Required for downloading fonts for the manual and for tests - # Since the source tarball includes a prebuilt manual we only need this for Git source builds - AM_COND_IF([FONT_DOWNLOAD_TOOLS], [ - QUE_PROGVAR([curl]) - QUE_PROGVAR([bsdtar]) ]) + AX_LUA_MODULE([cassowary], [cassowary]) + AS_IF([test "$LUA_SHORT_VERSION" -lt 53], + AX_LUA_MODULE([compat53], [compat53]) + ) + AX_LUA_MODULE([cldr], [cldr]) + AX_LUA_MODULE([fluent], [fluent]) + AX_LUA_MODULE([linenoise], [linenoise]) + AX_LUA_MODULE([loadkit], [loadkit]) + AX_LUA_MODULE([lpeg], [lpeg]) + AX_LUA_MODULE([zlib], [lua-zlib]) + AX_LUA_MODULE([cliargs], [lua_cliargs]) + AX_LUA_MODULE([epnf], [luaepnf]) + AX_LUA_MODULE([lxp], [luaexpat]) + AX_LUA_MODULE([lfs], [luafilesystem]) + AX_LUA_MODULE([repl], [luarepl]) + AX_LUA_MODULE([ssl], [luasec]) + AX_LUA_MODULE([socket], [luasocket]) + AX_LUA_MODULE([lua-utf8], [luautf8]) + AX_LUA_MODULE([pl], [penlight]) + AX_LUA_MODULE([vstruct], [vstruct]) +], [ + QUE_PROGVAR([luarocks]) +]) - AM_COND_IF([DEVELOPER], [ - QUE_PROGVAR([busted]) - QUE_PROGVAR([luacheck]) - QUE_PROGVAR([luarocks]) - QUE_PROGVAR([nix]) - QUE_PROGVAR([perl]) - ]) +# Required for downloading fonts for the manual and for tests +# Since the source tarball includes a prebuilt manual we only need this for Git source builds +AM_COND_IF([FONT_DOWNLOAD_TOOLS], [ + QUE_PROGVAR([curl]) + QUE_PROGVAR([bsdtar]) +]) +AM_COND_IF([DEVELOPER_MODE], [ + QUE_PROGVAR([busted]) + QUE_PROGVAR([curl]) + QUE_PROGVAR([delta]) + QUE_PROGVAR([diff]) + QUE_PROGVAR([head]) + QUE_PROGVAR([ldoc]) + QUE_PROGVAR([luacheck]) + QUE_PROGVAR([luarocks]) + QUE_PROGVAR([nix]) + QUE_PROGVAR([npm]) + QUE_PROGVAR([perl]) + QUE_PROGVAR([sort]) + QUE_PROGVAR([tr]) ]) AM_CONDITIONAL([APPKIT], [test "x$have_appkit" = "xyes"]) @@ -236,17 +252,27 @@ AC_SUBST([LUAROCKSARGS]) # Avoid need for `--datarootdir=$(cd ..; pwd)` hack to run locally for # tests/manual build when developer mode is enabled -AM_COND_IF([DEVELOPER], [ +AM_COND_IF([DEVELOPER_MODE], [ adl_RECURSIVE_EVAL(["$(pwd)"], [SILE_PATH]) datarootdir="$(cd ..; pwd)" ],[ adl_RECURSIVE_EVAL(["${datadir}/${TRANSFORMED_PACKAGE_NAME}"], [SILE_PATH]) ]) -AC_DEFINE_UNQUOTED([SILE_PATH],["${SILE_PATH}"],[Path for SILE packages and classes]) +AC_DEFINE_UNQUOTED([SILE_PATH], ["${SILE_PATH}"], [Path for SILE packages and classes]) AC_SUBST([SILE_PATH]) +# In order for our Rust CLI binary to use the same default package.path as the system Lua, +# we test the system Lua (required only at build not run time) for its current package.path. +adl_RECURSIVE_EVAL(["$(${LUA} -e 'print(package.path)')"], [LUA_PATH]) +AC_DEFINE_UNQUOTED([LUA_PATH], ["${LUA_PATH}"],[System Lua package path]) +AC_SUBST([LUA_PATH]) + +adl_RECURSIVE_EVAL(["$(${LUA} -e 'print(package.cpath)')"], [LUA_CPATH]) +AC_DEFINE_UNQUOTED([LUA_CPATH], ["${LUA_CPATH}"], [System Lua package cpath]) +AC_SUBST([LUA_CPATH]) + adl_RECURSIVE_EVAL(["${libdir}/${TRANSFORMED_PACKAGE_NAME}"], [SILE_LIB_PATH]) -AC_DEFINE_UNQUOTED([SILE_LIB_PATH],["${SILE_LIB_PATH}"],[Path for SILE libraries]) +AC_DEFINE_UNQUOTED([SILE_LIB_PATH],["${SILE_LIB_PATH}"], [Path for SILE libraries]) AC_SUBST([SILE_LIB_PATH]) AC_SUBST([ROCKSPECWARNING], ["DO NOT EDIT! Modify template sile.rockspec.in"]) @@ -255,8 +281,9 @@ AC_SUBST([ROCKREV], [1]) QUE_SUBST_MAN_DATE AC_CONFIG_FILES([build-aux/list-dist-files.sh], [chmod +x build-aux/list-dist-files.sh]) -AC_CONFIG_FILES([Makefile src/Makefile sile.1 core/features.lua core/pathsetup.lua core/version.lua]) -AC_CONFIG_FILES([sile tests/regressions.pl], [chmod +x sile tests/regressions.pl]) +AC_CONFIG_FILES([Makefile justenough/Makefile sile-lua.1 core/features.lua core/pathsetup.lua core/version.lua]) +AC_CONFIG_FILES([sile-lua:sile.in], [chmod +x sile-lua]) +AC_CONFIG_FILES([tests/regressions.pl], [chmod +x tests/regressions.pl]) AC_CONFIG_FILES([sile-dev-1.rockspec:sile.rockspec.in]) AC_OUTPUT diff --git a/core/break.lua b/core/break.lua index 1f041885a9..35b9afb8e7 100644 --- a/core/break.lua +++ b/core/break.lua @@ -51,12 +51,12 @@ local debugging = false function lineBreak:init() self:trimGlue() -- 842 -- 849 - self.activeWidth = SILE.length() - self.curActiveWidth = SILE.length() - self.breakWidth = SILE.length() + self.activeWidth = SILE.types.length() + self.curActiveWidth = SILE.types.length() + self.breakWidth = SILE.types.length() -- 853 - local rskip = (SILE.settings:get("document.rskip") or SILE.nodefactory.glue()).width:absolute() - local lskip = (SILE.settings:get("document.lskip") or SILE.nodefactory.glue()).width:absolute() + local rskip = (SILE.settings:get("document.rskip") or SILE.types.node.glue()).width:absolute() + local lskip = (SILE.settings:get("document.lskip") or SILE.types.node.glue()).width:absolute() self.background = rskip + lskip -- 860 self.bestInClass = {} @@ -72,7 +72,7 @@ end function lineBreak:trimGlue() -- 842 local nodes = self.nodes if nodes[#nodes].is_glue then nodes[#nodes] = nil end - nodes[#nodes+1] = SILE.nodefactory.penalty(inf_bad) + nodes[#nodes+1] = SILE.types.node.penalty(inf_bad) end -- NOTE FOR DEVELOPERS: this method is called when the linebreak.parShape @@ -100,9 +100,9 @@ end local parShapeCache = {} local grantLeftoverWidth = function (hsize, l, w, r) - local width = SILE.measurement(w or hsize) - if not w and l then width = width - SILE.measurement(l) end - if not w and r then width = width - SILE.measurement(r) end + local width = SILE.types.measurement(w or hsize) + if not w and l then width = width - SILE.types.measurement(l) end + if not w and r then width = width - SILE.types.measurement(r) end local remaining = hsize:tonumber() - width:tonumber() local left = SU.cast("number", l or (r and (remaining - SU.cast("number", r))) or 0) local right = SU.cast("number", r or (l and (remaining - SU.cast("number", l))) or remaining) @@ -156,13 +156,13 @@ function lineBreak:tryBreak() -- 855 if not node then pi = ejectPenalty; breakType = "hyphenated" elseif node.is_discretionary then breakType = "hyphenated"; pi = param("hyphenPenalty") else breakType = "unhyphenated"; pi = node.penalty or 0 end - if debugging then SU.debug("break", "Trying a ", breakType, "break p =", pi) end + if debugging then SU.debug("break", "Trying a", breakType, "break p =", pi) end self.no_break_yet = true -- We have to store all this state crap in the object, or it's global variables all the way self.prev_prev_r = nil self.prev_r = self.activeListHead self.old_l = 0 self.r = nil - self.curActiveWidth = SILE.length(self.activeWidth) + self.curActiveWidth = SILE.types.length(self.activeWidth) while true do while true do -- allows "break" to function as "continue" self.r = self.prev_r.next @@ -176,7 +176,7 @@ function lineBreak:tryBreak() -- 855 end -- 861 if self.r.lineNumber > self.old_l then - if debugging then SU.debug("break", "Minimum demerits = " .. self.minimumDemerits) end + if debugging then SU.debug("break", "Minimum demerits =", self.minimumDemerits) end if self.minimumDemerits < awful_bad and (self.old_l ~= self.easy_line or self.r == self.activeListHead) then self:createNewActiveNodes(breakType) end @@ -199,16 +199,16 @@ function lineBreak:tryBreak() -- 855 self.lineWidth = self.firstWidth end end - if debugging then SU.debug("break", "line width = " .. tostring(self.lineWidth)) end + if debugging then SU.debug("break", "line width =", self.lineWidth) end end if debugging then - SU.debug("break", " ---> (2) cuaw is " .. tostring(self.curActiveWidth)) - SU.debug("break", " ---> aw is " .. tostring(self.activeWidth)) + SU.debug("break", " ---> (2) cuaw is", self.curActiveWidth) + SU.debug("break", " ---> aw is", self.activeWidth) end self:considerDemerits(pi, breakType) if debugging then - SU.debug("break", " <--- cuaw is " .. tostring(self.curActiveWidth)) - SU.debug("break", " <--- aw is " .. tostring(self.activeWidth)) + SU.debug("break", " <--- cuaw is", self.curActiveWidth) + SU.debug("break", " <--- aw is ", self.activeWidth) end end end @@ -269,7 +269,7 @@ function lineBreak:tryAlternatives(from, to) local ss = shortfall - addWidth -- Warning, assumes abosolute local badness = SU.rateBadness(inf_bad, ss.length.amount, self.curActiveWidth[ss > 0 and "stretch" or "shrink"].length.amount) - if debugging then SU.debug("break", " badness of " .. ss .. " (" .. self.curActiveWidth .. ") is " .. badness) end + if debugging then SU.debug("break", " badness of", ss, "(", self.curActiveWidth, ") is", badness) end if badness < localMinimum then self.r.alternates = alternates self.r.altSelections = combination @@ -287,13 +287,12 @@ function lineBreak:considerDemerits(pi, breakType) -- 877 self.artificialDemerits = false local nodeStaysActive = false -- self:dumpActiveRing() - local shortfall = self.lineWidth - self.curActiveWidth if self.seenAlternatives then - self:tryAlternatives(self.r.prevBreak and self.r.prevBreak.curBreak or 1, self.r.curBreak and self.r.curBreak or 1, shortfall) + self:tryAlternatives(self.r.prevBreak and self.r.prevBreak.curBreak or 1, self.r.curBreak and self.r.curBreak or 1) end - shortfall = self.lineWidth - self.curActiveWidth + local shortfall = self.lineWidth - self.curActiveWidth self.badness, self.fitClass = fitclass(self, shortfall) - if debugging then SU.debug("break", self.badness .. " " .. self.fitClass) end + if debugging then SU.debug("break", self.badness, self.fitClass) end if (self.badness > inf_bad or pi == ejectPenalty) then if self.finalpass and self.minimumDemerits == awful_bad and self.r.next == self.activeListHead and self.prev_r == self.activeListHead then self.artificialDemerits = true @@ -328,7 +327,7 @@ function lineBreak:deactivateR() -- 886 self.r = self.activeListHead.next if self.r.type == "delta" then self.activeWidth:___add(self.r.width) - self.curActiveWidth = SILE.length(self.activeWidth) + self.curActiveWidth = SILE.types.length(self.activeWidth) self.activeListHead.next = self.r.next end if debugging then SU.debug("break", " Deactivate, branch 1"); end @@ -382,11 +381,11 @@ function lineBreak:recordFeasible(pi, breakType) -- 881 local demerit = lineBreak:computeDemerits(pi, breakType) if debugging then if self.nodes[self.place] then - SU.debug("break", "@" .. self.nodes[self.place] .. " via @@" .. (self.r.serial or "0") .. " badness=" .. self.badness .. " demerit=".. demerit) -- 882 + SU.debug("break", "@", self.nodes[self.place], "via @@", (self.r.serial or "0"), "badness =", self.badness, "demerit =", demerit) -- 882 else SU.debug("break", "@ \\par via @@") end - SU.debug("break", " fit class = "..self.fitClass) + SU.debug("break", " fit class =", self.fitClass) end demerit = demerit + self.r.totalDemerits if demerit <= self.bestInClass[self.fitClass].minimalDemerits then @@ -405,7 +404,7 @@ function lineBreak:createNewActiveNodes(breakType) -- 862 if self.no_break_yet then -- 863 self.no_break_yet = false - self.breakWidth = SILE.length(self.background) + self.breakWidth = SILE.types.length(self.background) local place = self.place local node = self.nodes[place] if node and node.is_discretionary then -- 866 @@ -422,17 +421,17 @@ function lineBreak:createNewActiveNodes(breakType) -- 862 end place = place + 1 end - if debugging then SU.debug("break", "Value of breakWidth = " .. tostring(self.breakWidth)) end + if debugging then SU.debug("break", "Value of breakWidth =", self.breakWidth) end end -- 869 (Add a new delta node) if self.prev_r.type == "delta" then self.prev_r.width:___sub(self.curActiveWidth) self.prev_r.width:___add(self.breakWidth) elseif self.prev_r == self.activeListHead then - self.activeWidth = SILE.length(self.breakWidth) + self.activeWidth = SILE.types.length(self.breakWidth) else local newDelta = { next = self.r, type = "delta", width = self.breakWidth - self.curActiveWidth } - if debugging then SU.debug("break", "Added new delta node = " .. tostring(newDelta.width)) end + if debugging then SU.debug("break", "Added new delta node =", newDelta.width) end self.prev_r.next = newDelta self.prev_prev_r = self.prev_r self.prev_r = newDelta @@ -447,7 +446,7 @@ function lineBreak:createNewActiveNodes(breakType) -- 862 local class = classes[i] local best = self.bestInClass[class] local value = best.minimalDemerits - if debugging then SU.debug("break", "Class is "..class.." Best value here is " .. value) end + if debugging then SU.debug("break", "Class is", class, "Best value here is", value) end if value <= self.minimumDemerits then -- 871: this is what creates new active notes @@ -489,7 +488,7 @@ function lineBreak.dumpBreakNode(_, node) end function lineBreak:describeBreakNode(node) - --print("@@" .. b.serial .. ": line " .. (b.lineNumber -1) .. "." .. b.fitness .. " " .. b.type .. " t=".. b.totalDemerits .. " -> @@ " .. (b.prevBreak and b.prevBreak.serial or "0") ) + --SU.debug("break", "@@", b.serial, ": line", b.lineNumber - 1, ".", b.fitness, b.type, "t=", b.totalDemerits, "-> @@", b.prevBreak and b.prevBreak.serial or "0") if node.sentinel then return node.sentinel end if node.type == "delta" then return "delta "..node.width.."pt" end local before = self.nodes[node.curBreak-1] @@ -582,7 +581,7 @@ function lineBreak:doBreak (nodes, hsize, sideways) end -- 889 while 1 do - if debugging then SU.debug("break", "@" .. self.pass .. "pass") end + if debugging then SU.debug("break", "@", self.pass, "pass") end if self.threshold > inf_bad then self.threshold = inf_bad end if self.pass == "second" then self.nodes = SILE.hyphenate(self.nodes) @@ -605,7 +604,7 @@ function lineBreak:doBreak (nodes, hsize, sideways) } -- Not doing 1630 - self.activeWidth = SILE.length(self.background) + self.activeWidth = SILE.types.length(self.background) self.place = 1 while self.nodes[self.place] and self.activeListHead.next ~= self.activeListHead do diff --git a/core/cli.lua b/core/cli.lua index 23ffae8cfc..a6298164da 100644 --- a/core/cli.lua +++ b/core/cli.lua @@ -2,37 +2,55 @@ local cli = pl.class() cli.parseArguments = function () local cliargs = require("cliargs") - local print_version = function() - print(SILE.full_version) + local print_version = function(flag) + print(flag == "V" and "SILE " .. SILE.version or SILE.full_version) os.exit(0) end cliargs:set_colsz(0, 120) cliargs:set_name("sile") cliargs:set_description([[ - The SILE typesetter reads a single input file, by default in either SIL or XML format, - and processes it to generate a single output file, by default in PDF format. The - output file will be written to the same name as the input file with the extension - changed to .pdf. Additional input or output formats can be handled by requiring a - module that adds support for them first. + The SILE typesetter reads an input file(s), by default in either SIL or XML format, and + processes them to generate an output file, by default in PDF format. The output will be written + to a file with the same name as the first input file with the extension changed to .pdf unless + the `--output` argument is used. Additional input or output formats can be handled by loading + a module with the `--use` argument to add support for them first. ]]) - cliargs:splat("INPUTS", "input document(s), by default in SIL or XML format", nil, 999) - cliargs:option("-b, --backend=VALUE", "choose an alternative output backend") - cliargs:option("-c, --class=VALUE", "override default document class") - cliargs:option("-d, --debug=VALUE", "show debug information for tagged aspects of SILE’s operation", {}) - cliargs:option("-e, --evaluate=VALUE", "evaluate Lua expression before processing input", {}) - cliargs:option("-E, --evaluate-after=VALUE", "evaluate Lua expression after processing input", {}) - cliargs:option("-f, --fontmanager=VALUE", "choose an alternative font manager") - cliargs:option("-I, --include=FILE", "deprecated, see --use, --preamble, or --postamble", {}) - cliargs:option("-m, --makedeps=FILE", "generate a list of dependencies in Makefile format") - cliargs:option("-o, --output=FILE", "explicitly set output file name") - cliargs:option("-O, --options=PARAMETER=VALUE[,PARAMETER=VALUE]", "set document class options", {}) - cliargs:option("-p, --preamble=FILE", "process SIL, XML, or other content before the input document", {}) - cliargs:option("-P, --postamble=FILE", "process SIL, XML, or other content after the input document", {}) - cliargs:option("-u, --use=MODULE[[PARAMETER=VALUE][,PARAMETER=VALUE]]", "load and initialize a module before processing input", {}) - cliargs:flag("-q, --quiet", "suppress warnings and informational messages during processing") - cliargs:flag("-t, --traceback", "display detailed location trace on errors and warnings") - cliargs:flag("-h, --help", "display this help, then exit") - cliargs:flag("-v, --version", "display version information, then exit", print_version) + cliargs:splat("INPUTS", + "Input document filename(s), by default in SIL, XML, or Lua formats.", nil, 999) + cliargs:option("-b, --backend=VALUE", + "Specify the output backend") + cliargs:option("-c, --class=VALUE", + "Override the default or specified document class") + cliargs:option("-d, --debug=VALUE", + "Show debug information for tagged aspects of SILE’s operation", {}) + cliargs:option("-e, --evaluate=VALUE", + "Evaluate Lua expression before processing input", {}) + cliargs:option("-E, --evaluate-after=VALUE", + "Evaluate Lua expression after processing input", {}) + cliargs:option("-f, --fontmanager=VALUE", + "Specify which font manager to use") + cliargs:option("-I, --include=FILE", + "Deprecated, see --use, --preamble, --postamble, or multiple input files", {}) + cliargs:option("-m, --makedeps=FILE", + "Generate a Makefile format list of dependencies and white them to a file") + cliargs:option("-o, --output=FILE", + "Explicitly set output file name") + cliargs:option("-O, --options=PARAMETER=VALUE[,PARAMETER=VALUE]", + "Set or override document class options", {}) + cliargs:option("-p, --preamble=FILE", + "Include the contents of a SIL, XML, or other resource file before the input document content", {}) + cliargs:option("-P, --postamble=FILE", + "Include the contents of a SIL, XML, or other resource file after the input document content", {}) + cliargs:option("-u, --use=MODULE[[PARAMETER=VALUE][,PARAMETER=VALUE]]", + "Load and initialize a class, inputter, shaper, or other module before processing the main input", {}) + cliargs:flag("-q, --quiet", + "Suppress warnings and informational messages during processing") + cliargs:flag("-t, --traceback", + "Display detailed location trace on errors and warnings") + cliargs:flag("-h, --help", + "Display this help, then exit") + cliargs:flag("-V, --version", + "Print version", print_version) -- Work around cliargs not processing - as an alias for STDIO streams: -- https://github.com/amireh/lua_cliargs/issues/67 local _arg = pl.tablex.imap(luautf8.gsub, _G.arg, "^-$", "STDIO") @@ -101,9 +119,8 @@ cli.parseArguments = function () for _, path in ipairs(opts.postamble) do table.insert(SILE.input.postambles, path) end - for _, path in ipairs(opts.include) do + if #opts.include > 0 then SU.deprecated("-I/--include", "-u/--use or -p/--preamble", "0.14.0", "0.15.0") - table.insert(SILE.input.includes, path) end -- http://lua-users.org/wiki/VarargTheSecondClassCitizen local summary = function (...) diff --git a/core/deprecations.lua b/core/deprecations.lua index 937b8ca6e7..4bddc87cae 100644 --- a/core/deprecations.lua +++ b/core/deprecations.lua @@ -27,16 +27,8 @@ local fluentglobal = function () fluent_once = true end SILE.fluent = setmetatable({}, { - __call = function (_, ...) - fluentglobal() - SILE.fluent = fluent - return fluent(pl.utils.unpack({...}, 1, select("#", ...))) - end, - __index = function (_, key) - fluentglobal() - SILE.fluent = fluent - return fluent[key] - end + __call = fluentglobal, + __index = fluentglobal, }) local nobaseclass = function () @@ -49,21 +41,20 @@ SILE.baseClass = setmetatable({}, { __index = nobaseclass }) -SILE.defaultTypesetter = function (frame) +SILE.defaultTypesetter = function () SU.deprecated("SILE.defaultTypesetter", "SILE.typesetters.base", "0.14.6", "0.15.0") - return SILE.typesetters.base(frame) end SILE.toPoints = function (_, _) - SU.deprecated("SILE.toPoints", "SILE.measurement():tonumber", "0.10.0", "0.13.1") + SU.deprecated("SILE.toPoints", "SILE.types.measurement():tonumber", "0.10.0", "0.13.1") end SILE.toMeasurement = function (_, _) - SU.deprecated("SILE.toMeasurement", "SILE.measurement", "0.10.0", "0.13.1") + SU.deprecated("SILE.toMeasurement", "SILE.types.measurement", "0.10.0", "0.13.1") end SILE.toAbsoluteMeasurement = function (_, _) - SU.deprecated("SILE.toAbsoluteMeasurement", "SILE.measurement():absolute", "0.10.0", "0.13.1") + SU.deprecated("SILE.toAbsoluteMeasurement", "SILE.types.measurement():absolute", "0.10.0", "0.13.1") end SILE.readFile = function (filename) @@ -71,10 +62,57 @@ SILE.readFile = function (filename) return SILE.processFile(filename) end +local usetypes = function (type) + SU.deprecated(("SILE.%s"):format(type), ("SILE.types.%s"):format(type), "0.15.0", "0.16.0", ([[ + In order to keep things tidy internally, more easily allow 3rd party + packages to override core functions, and substitute some slow bits + with Rust modules, internal types have been moved from the top level + SILE global to a types namespace. + + Please substitute 'SILE.%s()' with 'SILE.types.%s()'. + ]]):format(type, type)) + return SILE.types[type] +end + +SILE.color = setmetatable({}, { + __call = function (_, ...) return usetypes("color")(...) end, + __index = function () return usetypes("color") end, + }) + +SILE.measurement = setmetatable({}, { + __call = function (_, ...) return usetypes("measurement")(...) end, + __index = function () return usetypes("measurement") end, + }) + +SILE.length = setmetatable({}, { + __call = function (_, ...) return usetypes("length")(...) end, + __index = function () return usetypes("length") end, + }) + +local usetypes2 = function (old, new, type) + SU.deprecated(("SILE.%s.%s"):format(old, type), ("SILE.types.%s.%s"):format(new, type), "0.15.0", "0.16.0", ([[ + In order to keep things tidy internally, more easily allow 3rd party + packages to override core functions, and substitute some slow bits + with Rust modules, internal types have been moved from the top level + SILE global to a types namespace. + + Please substitute 'SILE.%s.%s()' with 'SILE.types.%s.%s()'. + ]]):format(old, type, new, type)) + return SILE.types[new][type] +end + +SILE.nodefactory = setmetatable({}, { + __index = function (_, type) return usetypes2("nodefactory", "node", type) end, + }) + +SILE.units = setmetatable({}, { + __index = function (_, type) return usetypes2("units", "unit", type) end, + }) + SILE.colorparser = function (input) - SU.deprecated("SILE.colorparser", "SILE.color", "0.14.0", "0.16.0", + SU.deprecated("SILE.colorparser", "SILE.types.color", "0.14.0", "0.16.0", [[Color results are now color objects, not just tables with relevant values.]]) - return SILE.color(input) + return SILE.types.color(input) end function SILE.doTexlike (doc) @@ -82,3 +120,33 @@ function SILE.doTexlike (doc) [[Add format argument "sil" to skip content detection and assume SIL input]]) return SILE.processString(doc, "sil") end + +local nopackagemanager = function () + SU.deprecated("SILE.PackageManager", nil, "0.13.2", "0.15.0", [[ + The built in SILE package manager has been completely deprecated. In its place + SILE can now load classes, packages, and other resources installed via + LuaRocks. Any SILE package may be published on LuaRocks.org or any private + repository. Rocks may be installed to the host system root filesystem, a user + directory, or a custom location. Please see the SILE manual for usage + instructions. Package authors especially can review the template repository + on GitHub for how to create a package. + ]]) +end + +SILE.PackageManager = {} +setmetatable(SILE.PackageManager, { + __index = nopackagemanager +}) + +SU.utf8char = function () + SU.deprecated("SU.utf8char", "luautf8.char", "0.11.0", "0.12.0") +end + +SU.utf8codes = function () + SU.deprecated("SU.utf8codes", "luautf8.codes", "0.11.0", "0.12.0") +end + +-- luacheck: ignore updatePackage +-- luacheck: ignore installPackage +updatePackage = nopackagemanager +installPackage = nopackagemanager diff --git a/core/font.lua b/core/font.lua index eb822817ab..f7704fd07b 100644 --- a/core/font.lua +++ b/core/font.lua @@ -1,9 +1,11 @@ +--- font +-- @module SILE.font local icu = require("justenoughicu") local lastshaper SILE.registerCommand("font", function (options, content) - if SU.hasContent(content) then SILE.settings:pushState() end + if SU.ast.hasContent(content) then SILE.settings:pushState() end if options.filename then SILE.settings:set("font.filename", options.filename) end if options.family then SILE.settings:set("font.family", options.family) @@ -49,7 +51,7 @@ SILE.registerCommand("font", function (options, content) -- that the post-load hook might want to do. SILE.font.cache(SILE.font.loadDefaults({}), SILE.shaper.getFace) - if SU.hasContent(content) then + if SU.ast.hasContent(content) then SILE.process(content) SILE.settings:popState() if SILE.shaper._name == "harfbuzzWithColor" and lastshaper then @@ -77,7 +79,7 @@ SILE.fontCache = {} local _key = function (options) return table.concat({ options.family, - ("%g"):format(SILE.measurement(options.size):tonumber()), + ("%g"):format(SILE.types.measurement(options.size):tonumber()), ("%d"):format(options.weight or 0), options.style, options.variant, diff --git a/core/frame.lua b/core/frame.lua index ce7c6a3387..41c6971601 100644 --- a/core/frame.lua +++ b/core/frame.lua @@ -35,7 +35,7 @@ SILE.framePrototype = pl.class({ self.variables[method] = cassowary.Variable({ name = spec.id .. "_" .. method }) self[method] = function (instance_self) instance_self:solve() - return SILE.measurement(instance_self.variables[method].value) + return SILE.types.measurement(instance_self.variables[method].value) end end -- Add definitions of width and height @@ -49,7 +49,7 @@ SILE.framePrototype = pl.class({ -- This gets called by us in typesetter before we start to use the frame init = function (self, typesetter) - self.state = { totals = { height = SILE.measurement(0) } } + self.state = { totals = { height = SILE.types.measurement(0) } } self:enter(typesetter) self:newLine(typesetter) if self:pageAdvanceDirection() == "TTB" then diff --git a/core/frameparser.lua b/core/frameparser.lua index 962378ade3..287991c4e8 100644 --- a/core/frameparser.lua +++ b/core/frameparser.lua @@ -4,7 +4,7 @@ local cassowary = require("cassowary") local P, C, V = lpeg.P, lpeg.C, lpeg.V local function resolveMeasurement (str) - return SILE.measurement(str):tonumber() + return SILE.types.measurement(str):tonumber() end local functionOfFrame = function (dim, id) diff --git a/core/globals.lua b/core/globals.lua new file mode 100644 index 0000000000..041de291be --- /dev/null +++ b/core/globals.lua @@ -0,0 +1,26 @@ +--- Global library provisions. +-- @module globals +-- @alias _G + +--- Penlight od-demand loader. +-- The Lua language adopts a "no batteries included" philosophy by providing a minimal standard library. Penlight is +-- a widely used set libraries for making it easier to work with common tasks. Loading SILE implies that the PEnlight +-- on-demand module loader is available, allowing any Penlight functions to be accessed using the `pl` prefix. Consult +-- the [Penlight documentation](https://lunarmodules.github.io/Penlight/) for specifics of the utilities available. +_G.pl = require("pl.import_into")() + +--- UTF-8 string library. +-- LuaJIT 5.1 and 5.2's `string` module only handle strings as bytes. Lua 5.3+ has a UTF-8 safe `string` module, but its +-- feature set is somewhat underwhelming. This module includes more functions and levels the playing field no matter +-- which Lua VM is being used. See [luautf8 docs](https://github.com/starwing/luautf8) for more details. +_G.luautf8 = require("lua-utf8") + +--- Fluent localization library. +-- For handling messages in various languages SILE provides an implementation of [Project +-- Fluent](https://projectfluent.org/)'s localization system (originally developed by Mozilla for use in Firefox). This +-- global is an instantiated interface to [fluent-lua](https://github.com/alerque/fluent-lua) pre-loaded with resources +-- for all the langugaes and regions SILE has support for. +_G.fluent = require("fluent")() + +-- For developer testing only, usually in CI +if os.getenv("SILE_COVERAGE") then require("luacov") end diff --git a/core/hyphenator-liang.lua b/core/hyphenator-liang.lua index a6bd30b652..709ec3322e 100644 --- a/core/hyphenator-liang.lua +++ b/core/hyphenator-liang.lua @@ -55,12 +55,13 @@ local function loadPatterns(hyphenator, language) end SILE._hyphenate = function (self, text) - if string.len(text) < self.minWord then return { text } end - local points = self.exceptions[text:lower()] + if luautf8.len(text) < self.minWord then return { text } end + local lowertext = luautf8.lower(text) + local points = self.exceptions[lowertext] local word = SU.splitUtf8(text) if not points then - points = SU.map(function ()return 0 end, word) - local work = SU.map(string.lower, word) + points = SU.map(function () return 0 end, word) + local work = SU.map(luautf8.lower, word) table.insert(work, ".") table.insert(work, 1, ".") table.insert(points, 1, 0) @@ -97,7 +98,7 @@ SILE._hyphenators = {} local function defaultHyphenateSegments (node, segments, _) local hyphen = SILE.shaper:createNnodes(SILE.settings:get("font.hyphenchar"), node.options) - return SILE.nodefactory.discretionary({ prebreak = hyphen }), segments + return SILE.types.node.discretionary({ prebreak = hyphen }), segments end local initHyphenator = function (lang) diff --git a/core/languages.lua b/core/languages.lua index fd084fdd1b..67c93471ee 100644 --- a/core/languages.lua +++ b/core/languages.lua @@ -1,3 +1,6 @@ +--- SILE language class. +-- @interfaces languages + local loadkit = require("loadkit") local cldr = require("cldr") @@ -81,7 +84,7 @@ SILE.registerCommand("ftl", function (options, content) fluent:set_locale(locale) if options.src then fluent:load_file(options.src, locale) - elseif SU.hasContent(content) then + elseif SU.ast.hasContent(content) then local input = content[1] fluent:add_messages(input, locale) end diff --git a/core/length.lua b/core/length.lua deleted file mode 100644 index ede6dcb4b1..0000000000 --- a/core/length.lua +++ /dev/null @@ -1,168 +0,0 @@ -local function _error_if_not_number (a) - if type(a) ~= "number" then - SU.error("We tried to do impossible arithmetic on a " .. SU.type(a) .. ". (That's a bug)", true) - end -end - -return pl.class({ - type = "length", - length = nil, - stretch = nil, - shrink = nil, - - _init = function (self, spec, stretch, shrink) - if stretch or shrink then - self.length = SILE.measurement(spec or 0) - self.stretch = SILE.measurement(stretch or 0) - self.shrink = SILE.measurement(shrink or 0) - elseif type(spec) == "number" then - self.length = SILE.measurement(spec) - elseif SU.type(spec) == "measurement" then - self.length = spec - elseif SU.type(spec) == "glue" then - self.length = SILE.measurement(spec.width.length or 0) - self.stretch = SILE.measurement(spec.width.stretch or 0) - self.shrink = SILE.measurement(spec.width.shrink or 0) - elseif type(spec) == "table" then - self.length = SILE.measurement(spec.length or 0) - self.stretch = SILE.measurement(spec.stretch or 0) - self.shrink = SILE.measurement(spec.shrink or 0) - elseif type(spec) == "string" then - local amount = tonumber(spec) - if type(amount) == "number" then - self:_init(amount) - else - local parsed = SILE.parserBits.length:match(spec) - if not parsed then SU.error("Could not parse length '"..spec.."'") end - self:_init(parsed) - end - end - if not self.length then self.length = SILE.measurement() end - if not self.stretch then self.stretch = SILE.measurement() end - if not self.shrink then self.shrink = SILE.measurement() end - end, - - absolute = function (self) - return SILE.length(self.length:tonumber(), self.stretch:tonumber(), self.shrink:tonumber()) - end, - - negate = function (self) - return self:__unm() - end, - - tostring = function (self) - return self:__tostring() - end, - - tonumber = function (self) - return self.length:tonumber() - end, - - new = function (_) - SU.deprecated("SILE.length.new", "SILE.length", "0.10.0") - end, - - make = function (_) - SU.deprecated("SILE.length.make", "SILE.length", "0.10.0") - end, - - parse = function (_) - SU.deprecated("SILE.length.parse", "SILE.length", "0.10.0") - end, - - fromLengthOrNumber = function (_, _) - SU.deprecated("SILE.length.fromLengthOrNumber", "SILE.length", "0.10.0") - end, - - __index = function (_, key) - SU.deprecated("SILE.length." .. key, "SILE.length", "0.10.0") - end, - - __tostring = function (self) - local str = tostring(self.length) - if self.stretch.amount ~= 0 then str = str .. " plus " .. tostring(self.stretch) end - if self.shrink.amount ~= 0 then str = str .. " minus " .. tostring(self.shrink) end - return str - end, - - __add = function (self, other) - if type(self) == "number" then self, other = other, self end - other = SU.cast("length", other) - return SILE.length(self.length + other.length, - self.stretch + other.stretch, - self.shrink + other.shrink) - end, - - -- See usage comments on SILE.measurement:___add() - ___add = function (self, other) - if SU.type(other) ~= "length" then - self.length:___add(other) - else - self.length:___add(other.length) - self.stretch:___add(other.stretch) - self.shrink:___add(other.shrink) - end - return nil - end, - - __sub = function (self, other) - local result = SILE.length(self) - other = SU.cast("length", other) - result.length = result.length - other.length - result.stretch = result.stretch - other.stretch - result.shrink = result.shrink - other.shrink - return result - end, - - -- See usage comments on SILE.measurement:___add() - ___sub = function (self, other) - self.length:___sub(other.length) - self.stretch:___sub(other.stretch) - self.shrink:___sub(other.shrink) - return nil - end, - - __mul = function (self, other) - if type(self) == "number" then self, other = other, self end - _error_if_not_number(other) - local result = SILE.length(self) - result.length = result.length * other - result.stretch = result.stretch * other - result.shrink = result.shrink * other - return result - end, - - __div = function (self, other) - local result = SILE.length(self) - _error_if_not_number(other) - result.length = result.length / other - result.stretch = result.stretch / other - result.shrink = result.shrink / other - return result - end, - - __unm = function (self) - local result = SILE.length(self) - result.length = result.length:__unm() - return result - end, - - __lt = function (self, other) - local a = SU.cast("number", self) - local b = SU.cast("number", other) - return a - b < 0 - end, - - __le = function (self, other) - local a = SU.cast("number", self) - local b = SU.cast("number", other) - return a - b <= 0 - end, - - __eq = function (self, other) - local a = SU.cast("length", self) - local b = SU.cast("length", other) - return a.length == b.length and a.stretch == b.stretch and a.shrink == b.shrink - end - - }) diff --git a/core/measurement.lua b/core/measurement.lua deleted file mode 100644 index 3254affe53..0000000000 --- a/core/measurement.lua +++ /dev/null @@ -1,189 +0,0 @@ -local function _tonumber (amount) - return SU.cast("number", amount) -end - -local function _similarunit (a, b) - if type(b) == "number" or type(a) == "number" then - return true - else - return a.unit == b.unit - end -end - -local function _hardnumber (a, b) - if type(b) == "number" or type(a) == "number" then - return true - else - return false - end -end - -local function _unit (a, b) - return type(a) == "table" and a.unit or b.unit -end - -local function _amount (input) - return type(input) == "number" and input or input.amount -end - -local function _pt_amount (input) - return type(input) == "number" and input or not input and 0 or input._mutable and input.amount or input:tonumber() -end - -local function _error_if_immutable (input) - if type(input) == "table" and not input._mutable then - SU.error("Not so fast, we can't do mutating arithmetic except on 'pt' unit measurements!", true) - end -end - -local function _error_if_relative (a, b) - if type(a) == "table" and a.relative or type(b) == "table" and b.relative then - SU.error("Cannot do arithmetic on a relative measurement without explicitly absolutizing it.", true) - end -end - -local measurement = pl.class({ - type = "measurement", - amount = 0, - unit = "pt", - relative = false, - _mutable = false, - - _init = function (self, amount, unit) - if unit then self.unit = unit end - if SU.type(amount) == "length" then - self.amount = amount.length.amount - self.unit = amount.length.unit - elseif type(amount) == "table" then - self.amount = amount.amount - self.unit = amount.unit - elseif type(tonumber(amount)) == "number" then - self.amount = tonumber(amount) - elseif type(amount) == "string" then - local parsed = SILE.parserBits.measurement:match(amount) - if not parsed then SU.error("Could not parse measurement '"..amount.."'") end - self.amount, self.unit = parsed.amount, parsed.unit - end - local _su = SILE.units[self.unit] - if not _su then SU.error("Unknown unit: " .. unit) end - self.relative = _su.relative - if self.unit == "pt" then self._mutable = true end - end, - - absolute = function (self) - return SILE.measurement(self:tonumber()) - end, - - tostring = function (self) - return self:__tostring() - end, - - tonumber = function (self) - local def = SILE.units[self.unit] - local amount = def.converter and def.converter(self.amount) or (self.amount * def.value) - return amount - end, - - __tostring = function (self) - return self.amount .. self.unit - end, - - __concat = function (a, b) - return tostring(a) .. tostring(b) - end, - - __add = function (self, other) - if _similarunit(self, other) then - return SILE.measurement(_amount(self) + _amount(other), _unit(self, other)) - else - _error_if_relative(self, other) - return SILE.measurement(_tonumber(self) + _tonumber(other)) - end - end, - - -- Note all private math (_ + __func()) functions: - -- * Are much faster than regular math operations - -- * Are **not** intended for use outside of the most performance sensitive loops - -- * Modify the lhs input in-place, never instantiating new objects - -- * Always assume absolute lhs input and absolutize the rhs values at runtime - -- * Assmue the inputs are sane with much less error checking than regular math funcs - -- * Are not composable using chained methods since they return nil for safety - ___add = function (self, other) - _error_if_immutable(self) - self.amount = self.amount + _pt_amount(other) - return nil - end, - - __sub = function (self, other) - if _similarunit(self, other) then - return SILE.measurement(_amount(self) - _amount(other), _unit(self, other)) - else - _error_if_relative(self, other) - return SILE.measurement(_tonumber(self) - _tonumber(other)) - end - end, - - -- See usage comments on SILE.measurement:___add() - ___sub = function (self, other) - _error_if_immutable(self) - self.amount = self.amount - _pt_amount(other) - return nil - end, - - __mul = function (self, other) - if _hardnumber(self, other) then - return SILE.measurement(_amount(self) * _amount(other), _unit(self, other)) - else - _error_if_relative(self, other) - return SILE.measurement(_tonumber(self) * _tonumber(other)) - end - end, - - __pow = function (self, other) - if _hardnumber(self, other) then - return SILE.measurement(_amount(self) ^ _amount(other), self.unit) - else - _error_if_relative(self, other) - return SILE.measurement(_tonumber(self) ^ _tonumber(other)) - end - end, - - __div = function (self, other) - if _hardnumber(self, other) then - return SILE.measurement(_amount(self) / _amount(other), self.unit) - else - _error_if_relative(self, other) - return SILE.measurement(_tonumber(self) / _tonumber(other)) - end - end, - - __mod = function (self, other) - if _hardnumber(self, other) then - return SILE.measurement(_amount(self) % _amount(other), self.unit) - else - _error_if_relative(self, other) - return SILE.measurement(_tonumber(self) % _tonumber(other)) - end - end, - - __unm = function (self) - local ret = SILE.measurement(self) - ret.amount = self.amount * -1 - return ret - end, - - __eq = function (self, other) - return _tonumber(self) == _tonumber(other) - end, - - __lt = function (self, other) - return _tonumber(self) < _tonumber(other) - end, - - __le = function (self, other) - return _tonumber(self) <= _tonumber(other) - end - - }) - -return measurement diff --git a/core/packagemanager.lua b/core/packagemanager.lua deleted file mode 100644 index a87ebc5ee9..0000000000 --- a/core/packagemanager.lua +++ /dev/null @@ -1,212 +0,0 @@ -local lfs = require("lfs") - -local catalogueURL = "https://raw.githubusercontent.com/sile-typesetter/sile-packages/master/packages.lua" -local packageHome = tostring(SYSTEM_SILE_PATH) .. "/packagemanager/" -local catalogueHome = packageHome .. "catalogue.lua" -local installedCatalogue = packageHome .. "installed.lua" - -local http = require("ssl.https") -local recentlyUpdated = false -local recentlyReloaded = false -local origcpath = package.cpath -- for idempotence -local origpath = package.path - -SILE.PackageManager = { - installed = {}, - Catalogue = {} -} - -local _deprecated = function () - SU.deprecated("SILE.PackageManager", nil, "0.13.2", "0.15.0", [[ - The built in SILE package manager has been completely deprecated. In its place - SILE can now load classes, packages, and other resources installed via - LuaRocks. Any SILE package may be published on LuaRocks.org or any private - repository. Rocks may be installed to the host system root filesystem, a user - directory, or a custom location. Please see the SILE manual for usage - instructions. Package authors especially can review the template repository - on GitHub for how to create a package. - ]]) -end - -local function loadInSandbox(untrusted_code) - _deprecated() - -- luacheck: ignore _ENV - if _ENV then -- simple Lua 5.2 version check - local env = {} - local untrusted_function, message = load(untrusted_code, nil, 't', env) - if not untrusted_function then return nil, message end - return pcall(untrusted_function) - else - if untrusted_code:byte(1) == 27 then return nil, "binary bytecode prohibited" end - local untrusted_function, message = load(untrusted_code) - if not untrusted_function then return nil, message end - -- luacheck: globals setfenv env - -- (At least there is in Lua 5.1) - setfenv(untrusted_function, env) - return pcall(untrusted_function) - end -end - -local function dumpTable(tbl) - _deprecated() - if type(tbl) == 'table' then - local str = '{ ' - for k, v in pairs(tbl) do - if type(k) ~= 'number' then k = '"'..k..'"' end - str = str .. '['..k..'] = ' .. dumpTable(v) .. ',' - end - return str .. '} ' - else - -- This only works because we are only storing strings! - return '"' .. tostring(tbl) .. '"' - end -end - -local function fixupPaths() - local paths = "" - local cpaths = "" - for pkg, _ in pairs(SILE.PackageManager.installed) do - _deprecated() - paths = paths .. packageHome .. pkg .. '/?.lua;' - cpaths = cpaths .. packageHome .. pkg .. "/?."..SHARED_LIB_EXT.. ";" - end - if paths:len() >= 1 then package.path = paths .. ";" .. origpath end - if cpaths:len() >= 1 then package.cpath = cpaths .. ";" .. origcpath end -end - -local function saveInstalled() - _deprecated() - local dump = dumpTable(SILE.PackageManager.installed) - local file, err = io.open(installedCatalogue, "w") - if err then - SU.error("Could not write installed package list at"..installedCatalogue..": "..err) - end - file:write("return "..dump) - file:close() - fixupPaths() -end - -local function updateCatalogue () - if not lfs.attributes(packageHome) then - if not lfs.mkdir(packageHome) then - SU.error("Error making package manager home directory: "..packageHome) - end - end - print("Loading catalogue from "..catalogueURL) - local result, statuscode, _ = http.request(catalogueURL) - if statuscode ~= 200 then - SU.error("Could not load catalogue from "..catalogueURL..": "..statuscode) - end - local file, err = io.open(catalogueHome, "w") - if err then - SU.error("Could not write package catalogue at"..catalogueHome..": "..err) - end - print("Writing "..(#result).." bytes to "..catalogueHome) - file:write(result) - file:close() - recentlyUpdated = true - recentlyReloaded = false -end - -local function loadInstalledCatalogue() - local file = io.open(installedCatalogue, "r") - if file ~= nil then - local contents = file:read("*all") - local success, res = loadInSandbox(contents) - if not success then - SU.error("Error loading installed package list: "..res) - end - SILE.PackageManager.installed = res - end -end - -local function reloadCatalogue() - local file = io.open(catalogueHome, "r") - if file ~= nil then - local contents = file:read("*all") - local success, res = loadInSandbox(contents) - if not success then - SU.error("Error loading package catalogue: "..res) - end - SILE.PackageManager.Catalogue = res - end - loadInstalledCatalogue() - print("Package catalogue reloaded") - recentlyReloaded = true -end - --- These functions are global so they can be used from the REPL --- luacheck: ignore updatePackage --- luacheck: ignore installPackage - -function updatePackage(packageName, branch) - _deprecated() - local target = packageHome .. packageName - -- Are we already there? - if SILE.PackageManager.installed[packageName] == branch and branch ~= "master" then - print("Nothing to do!") - return true - end - local cwd = lfs.currentdir() - local _, err = lfs.chdir(target) - if err then - SU.warn("Package directory "..target.." went away! Trying again...") - SILE.PackageManager.installed[packageName] = nil - saveInstalled() - installPackage(packageName) - end - - local ret = os.execute("git pull") - if not ret then - SU.error("Error updating repository for package "..packageName..": "..ret) - end - ret = os.execute("git checkout "..branch) - if not ret then - SU.error("Error updating repository for package "..packageName..": "..ret) - end - lfs.chdir(cwd) - SILE.PackageManager.installed[packageName] = branch - saveInstalled() -end - -function installPackage(packageName) - _deprecated() - if not recentlyUpdated then updateCatalogue() end - if not recentlyReloaded then reloadCatalogue() end - if not SILE.PackageManager.Catalogue[packageName] then - -- Add support for URL-based package names later. - SU.error("Can't install "..packageName..": package not known") - end - - local metadata = SILE.PackageManager.Catalogue[packageName] - - -- Check dependencies - if metadata.depends then - for _, pkg in ipairs(metadata.depends) do - if not SILE.PackageManager.installed[pkg] then - print(packageName.." requires "..pkg..", installing that...") - installPackage(pkg) - end - end - end - - -- Clone repo in temp directory - if metadata.repository then - local branch = metadata.version or "master" - local target = packageHome .. packageName - if lfs.attributes(target) then - updatePackage(packageName, branch) - else - local ret = os.execute("git clone -c advice.detachedHead=false -b "..branch.." "..metadata.repository.." "..target) - if not ret then -- This should return status code but it's returning true for me... - SU.error("Error cloning repository for package "..packageName..": "..ret) - end - end - SILE.PackageManager.installed[packageName] = branch - saveInstalled() - end -end - --- Set up the world -loadInstalledCatalogue() -fixupPaths() diff --git a/core/pagebuilder.lua b/core/pagebuilder.lua index 36b68f159d..c0e904fc34 100644 --- a/core/pagebuilder.lua +++ b/core/pagebuilder.lua @@ -1,3 +1 @@ SU.deprecated("core.pagebuilder", "pagebuilder.base", "0.14.6", "0.15.0") - -return require("pagebuilders.base") diff --git a/core/papersize.lua b/core/papersize.lua index f1b9c4a04a..499b88888d 100644 --- a/core/papersize.lua +++ b/core/papersize.lua @@ -67,7 +67,7 @@ setmetatable(papersize, { local geometry local _, _, x, y = string.find(size, "(.+)%s+x%s+(.+)") if x and y then - geometry = { SILE.measurement(x):tonumber(), SILE.measurement(y):tonumber() } + geometry = { SILE.types.measurement(x):tonumber(), SILE.types.measurement(y):tonumber() } else local preset_name = string.lower(size:gsub("[-%s]+", "")) geometry = self[preset_name] diff --git a/core/parserbits.lua b/core/parserbits.lua index f77a0ebe0f..f41e7373f1 100644 --- a/core/parserbits.lua +++ b/core/parserbits.lua @@ -5,8 +5,9 @@ local C, Cf, Cg, Ct, Cmt = lpeg.C, lpeg.Cf, lpeg.Cg, lpeg.Ct, lpeg.Cmt local function isaunit (_, _, unit) -- TODO: fix race condition so we can validate units - if not SILE or not SILE.units then return true end - return SILE.units[unit] and true or false + local factory = rawget(SILE.types, "unit") + if not SILE or not factory then return true end + return factory[unit] and true or false end local function inferpoints (number) @@ -61,15 +62,16 @@ local shrink = bits.ws * P"minus" * bits.ws * Cg(amount, "shrink") bits.length = Ct(length * stretch^-1 * shrink^-1) bits.utf8char = utf8char -local pairsep = S",;" * bits.ws +local pairsep = S",;" local quote = P'"' local escaped_quote = B(P"\\") * quote local unescapeQuote = function (str) local a = str:gsub('\\"', '"'); return a end -local quotedString = quote * C((1 - quote + escaped_quote)^1 / unescapeQuote) * quote -local value = quotedString + (1-S",;]")^1 +local quotedValueString = quote * C((1 - quote + escaped_quote)^0 / unescapeQuote) * quote +local valueString = (1-pairsep-quote-S"]")^0 / pl.stringx.strip +local value = quotedValueString + valueString + P"" local ID = C(bits.letter * (bits.letter + bits.digit)^0) bits.silidentifier = (ID + S":-")^1 -local pair = Cg(C(bits.silidentifier) * bits.ws * "=" * bits.ws * C(value)) * pairsep^-1 / unwrapper +local pair = Cg(bits.ws * C(bits.silidentifier) * bits.ws * "=" * bits.ws * C(value) * bits.ws) * pairsep^-1 / unwrapper bits.parameters = Cf(Ct"" * pair^0, rawset) local wrapper = function (a) return type(a)=="table" and a or {} end diff --git a/core/pathsetup.lua.in b/core/pathsetup.lua.in index ea9ad466f7..3a5f482bbe 100644 --- a/core/pathsetup.lua.in +++ b/core/pathsetup.lua.in @@ -1,43 +1,66 @@ +-- Allow autoconf to setup system lua paths at compile time, not run time (only used in developer mode) +if "@LUA_PATH@" ~= "" then + package.path = "@LUA_PATH@" + package.cpath = "@LUA_CPATH@" +end + local executable = debug.getinfo(3, "S").source local luaversion = _VERSION:match("%d+%.%d+") -- Normalize possibly dirty Lua path formatting shortcut: /./ → / --- Even leafo/gh-actions-luarocks takes this shortcut which inhibits duplicate cleanup +-- Even leafo/gh-actions-luarocks takes this shortcut which inhibits duplicate cleanup. package.path = package.path:gsub("/%./", "/") package.cpath = package.cpath:gsub("/%./", "/") +-- Utility function so that last-added paths take precidence and are not duplicated. local function prepend_and_dedup (segment, path) local escaped = segment:gsub('[%-%.%+%[%]%(%)%$%^%%%?%*]','%%%1') -- copied from pl.utils.escape() which we can't load yet local striped = path:gsub(("^%s"):format(escaped), ""):gsub((";%s"):format(escaped), "") return ("%s;%s"):format(segment, striped) end +-- Prepend paths specifically for Lua module.s local function prependPath (path) package.path = prepend_and_dedup(path .. "/?/init.lua", package.path) package.path = prepend_and_dedup(path .. "/?.lua", package.path) end +-- Prepand paths specifically for C modules. local function prependCPath (path) package.cpath = prepend_and_dedup(path .. "/?.@SHARED_LIB_EXT@", package.cpath) end -local function extendPaths (path, ours) +-- Take a given path and iterate over permutations of paths that LuaRocks might have installed a rock to that are +-- specific to a given Lua version version +local function extendPathsRocks (path) + prependCPath(path .. "/lib/lua/" .. luaversion) + prependCPath(path .. "/lib/lua/" .. luaversion .. "/sile") + prependPath(path .. "/share/lua/" .. luaversion) + prependPath(path .. "/share/lua/" .. luaversion .. "/sile") +end + +-- Take a given path and iterate over the permutations of subdirectories we expect to finde SILE/Lua/C modules under. +-- The second argument enables extra paths that we *only* expect to find in SILE source checkouts, and should not be +-- found in system, user, toolkit, or project level paths. +local function extendPaths (path, silesourcedir) + extendPathsRocks(path .. "/lua_modules") prependCPath(path) prependPath(path) - if ours then + if silesourcedir then prependPath(path .. "/lua-libraries") - if "@SYSTEM_LUAROCKS_FALSE@" == "" then -- see ./configure --with[out]-system-luarocks - prependCPath(path .. "/lua_modules/lib/lua/" .. luaversion) - prependPath(path .. "/lua_modules/share/lua/" .. luaversion) - end else prependCPath(path .. "/sile") prependPath(path .. "/sile") end + -- These paths are *only* used in developer mode for build testing + if "@DEVELOPER_FALSE@" ~= "" then -- see ./configure --(en|dis)able-developer + prependCPath(path .. "/libtexpdf/.libs") + prependCPath(path .. "/justenough/.libs") + end end --- Facilitate loading SILE classes & packages from system LuaRocks --- Also weed out CWD relative paths, we add them in a different order later +-- Facilitate loading SILE classes & packages from system LuaRocks by adding variants of the default Lua paths with sile +-- appended, stashed to be prepended later. Also weed out CWD relative paths, we add them in a different order later. local luapath = {} local extpath = {} for path in package.path:gmatch("[^;]+") do @@ -46,27 +69,48 @@ for path in package.path:gmatch("[^;]+") do end package.path = table.concat(luapath, ";") +-- This path is set by autoconf at configure time, and could be the full path to the source directory if in developer +-- mode or the expeted system istalation location otherwise. extendPaths("@SILE_PATH@", true) extendPaths("@SILE_LIB_PATH@", true) +-- If the configure time option to use system luarocks is disabled, use ones local to the source (again could be the +-- development mode source directory or expected system instalation location). +if "@SYSTEM_LUAROCKS_FALSE@" == "" then -- see ./configure --with[out]-system-luarocks + extendPathsRocks("@SILE_PATH@/lua_modules") +end + +-- Stuff the variants of system Lua Rocks paths with sile suffixes added back at higher priority that regular paths. package.path = table.concat(extpath, ";") .. ";" .. package.path +-- Deal with the *run time* variant of SILE_PATH, which may be more than one path. This could be references to a source +-- tree for development work, a fork of some SILE core libraries, or just a way to stuff toolkits into the path besides +-- the default project local or system paths without exporting Lua environment variables. local pathvar = os.getenv("SILE_PATH") if pathvar then for path in string.gmatch(pathvar, "[^;]+") do if not path:match("^./") and path:len() >= 1 then - extendPaths(path) + extendPaths(path, true) end end end -local cwd = executable:gsub("(.*)(/.*)", "%1") -if cwd:match("^@") then -- Consider "ours" for the sake of Nix Flake - extendPaths(".", true) +-- Add the current working directory, presumably a local project, as one of the highest priority paths. +local executable_dir = executable:gsub("(.*)(/.*)", "%1") +if executable_dir:match("^@") then + -- Running from a nix flake reports this, but we don't want anything special to get added. + extendPaths(".") else - if cwd ~= "./" then extendPaths(cwd) end + -- If executable_dir is just an alternate name of PWD, we don't need to duplicate it. + -- Also ignore Rust binary thinking its executable_dir is in its source directory. + if executable_dir ~= "./" and executable_dir ~= "src" then + extendPaths(executable_dir) + end + extendPathsRocks("./lua_modules") extendPaths(".") end +-- Stuff internal utility features into the global namespace so they could be manipulated externally (undocumented). _G.extendSilePath = extendPaths +_G.extendSilePathRocks = extendPathsRocks _G.executablePath = executable diff --git a/core/settings.lua b/core/settings.lua index 4db053fdcf..7b17405352 100644 --- a/core/settings.lua +++ b/core/settings.lua @@ -1,8 +1,11 @@ +--- core settings instance +--- @module SILE.settings + local deprecator = function () SU.deprecated("SILE.settings.*", "SILE.settings:*", "0.13.0", "0.15.0") - return SILE.settings end +--- @type settings local settings = pl.class() function settings:_init() @@ -11,6 +14,7 @@ function settings:_init() self.declarations = {} self.stateQueue = {} self.defaults = {} + self.hooks = {} self:declare({ parameter = "document.language", @@ -22,28 +26,28 @@ function settings:_init() self:declare({ parameter = "document.parindent", type = "glue", - default = SILE.nodefactory.glue("20pt"), + default = SILE.types.node.glue("1bs"), help = "Glue at start of paragraph" }) self:declare({ parameter = "document.baselineskip", type = "vglue", - default = SILE.nodefactory.vglue("1.2em plus 1pt"), + default = SILE.types.node.vglue("1.2em plus 1pt"), help = "Leading" }) self:declare({ parameter = "document.lineskip", type = "vglue", - default = SILE.nodefactory.vglue("1pt"), + default = SILE.types.node.vglue("1pt"), help = "Leading" }) self:declare({ parameter = "document.parskip", type = "vglue", - default = SILE.nodefactory.vglue("0pt plus 1pt"), + default = SILE.types.node.vglue("0pt plus 1pt"), help = "Leading" }) @@ -76,7 +80,6 @@ function settings:_init() }) SILE.registerCommand("set", function(options, content) - local parameter = SU.required(options, "parameter", "\\set command") local makedefault = SU.boolean(options.makedefault, false) local reset = SU.boolean(options.reset, false) local value = options.value @@ -85,53 +88,73 @@ function settings:_init() SU.warn("Are you sure meant to set default settings *and* pass content to ostensibly apply them to temporarily?") end self:temporarily(function() - self:set(parameter, value, makedefault, reset) + if options.parameter then + local parameter = SU.required(options, "parameter", "\\set command") + self:set(parameter, value, makedefault, reset) + end SILE.process(content) end) else + local parameter = SU.required(options, "parameter", "\\set command") self:set(parameter, value, makedefault, reset) end end, "Set a SILE parameter to value (restoring the value afterwards if is provided)", nil, true) end +--- Stash the current values of all settings in a stack to be returned to later function settings:pushState () - if not self then self = deprecator() end + if not self then return deprecator() end table.insert(self.stateQueue, self.state) self.state = pl.tablex.copy(self.state) end +--- Return the most recently pushed set of values in the setting stack function settings:popState () - if not self then self = deprecator() end + if not self then return deprecator() end + local previous = self.state self.state = table.remove(self.stateQueue) + for parameter, oldvalue in pairs(previous) do + if self.hooks[parameter] then + local newvalue = self.state[parameter] + if oldvalue ~= newvalue then + self:runHooks(parameter, newvalue) + end + end + end end +--- Declare a new setting +--- @tparam table specs { parameter, type, default, help, hook, ... } declaration specification function settings:declare (spec) - if not spec then self, spec = deprecator(), self end + if not spec then return deprecator() end if spec.name then SU.deprecated("'name' argument of SILE.settings:declare", "'parameter' argument of SILE.settings:declare", "0.10.10", "0.11.0") end if self.declarations[spec.parameter] then - SU.debug("settings", "Attempt to re-declare setting: " .. spec.parameter) + SU.debug("settings", "Attempt to re-declare setting:", spec.parameter) return end self.declarations[spec.parameter] = spec + self.hooks[spec.parameter] = {} + if spec.hook then + self:registerHook(spec.parameter, spec.hook) + end self:set(spec.parameter, spec.default, true) end ---- Reset all settings to their default value. +--- Reset all settings to their registered default values. function settings:reset () - if not self then self = deprecator() end + if not self then return deprecator() end for k,_ in pairs(self.state) do self:set(k, self.defaults[k]) end end --- Restore all settings to the value they had in the top-level state, --- that is at the head of the settings stack (normally the document --- level). +-- that is at the tap of the settings stack (normally the document level). function settings:toplevelState () - if not self then self = deprecator() end + if not self then return deprecator() end if #self.stateQueue ~= 0 then for parameter, _ in pairs(self.state) do -- Bypass self:set() as the latter performs some tests and a cast, @@ -142,13 +165,16 @@ function settings:toplevelState () end end +--- Get the value of a setting +-- @tparam string parameter The full name of the setting to fetch. +-- @return Value of setting function settings:get (parameter) -- HACK FIXME https://github.com/sile-typesetter/sile/issues/1699 -- See comment on set() below. if parameter == "current.parindent" then return SILE.typesetter and SILE.typesetter.state.parindent end - if not parameter then self, parameter = deprecator(), self end + if not parameter then return deprecator() end if not self.declarations[parameter] then SU.error("Undefined setting '"..parameter.."'") end @@ -159,6 +185,11 @@ function settings:get (parameter) end end +--- Set the value of a setting +-- @tparam string parameter The full name of the setting to change. +-- @param value The new value to change it to. +-- @tparam[opt=false] boolean makedefault Whether to make this the new default value. +-- @tparam[opt=false] boolean reset Whether to reset the value to the current default value. function settings:set (parameter, value, makedefault, reset) -- HACK FIXME https://github.com/sile-typesetter/sile/issues/1699 -- Anything dubbed current.xxx should likely NOT be a "setting" (subject @@ -184,7 +215,7 @@ function settings:set (parameter, value, makedefault, reset) end return end - if type(self) ~= "table" then self, parameter, value, makedefault, reset = deprecator(), self, parameter, value, makedefault end + if type(self) ~= "table" then return deprecator() end if not self.declarations[parameter] then SU.error("Undefined setting '"..parameter.."'") end @@ -200,17 +231,43 @@ function settings:set (parameter, value, makedefault, reset) if makedefault then self.defaults[parameter] = value end + self:runHooks(parameter, value) +end + +--- Register a callback hook to be run when a setting changes. +-- @tparam string parameter Name of the setting to add a hook to. +-- @tparam function func Callback function accepting one argument (the new value). +function settings:registerHook (parameter, func) + table.insert(self.hooks[parameter], func) +end + +--- Trigger execution of callback hooks for a given setting. +-- @tparam string parameter The name of the parameter changes. +-- @param value The new value of the setting, passed as the first argument to the hook function. +function settings:runHooks (parameter, value) + if self.hooks[parameter] then + for _, func in ipairs(self.hooks[parameter]) do + SU.debug("classhooks", "Running seting hook for", parameter) + func(value) + end + end end +--- Isolate a block of processing so that setting changes made during the block don't last past the block. +-- (Under the hood this just uses `:pushState()`, the processes the function, then runs `:popState()`) +-- @tparam function func A function wrapping the actions to take without affecting settings for future use. function settings:temporarily (func) - if not func then self, func = deprecator(), self end + if not func then return deprecator() end self:pushState() func() self:popState() end -function settings:wrap () -- Returns a closure which applies the current state, later - if not self then self = deprecator() end +--- Create a settings wrapper function that applies current settings to later content processing. +--- @treturn function a closure fuction accepting one argument (content) to process using +--- typesetter settings as they are at the time of closure creation. +function settings:wrap () + if not self then return deprecator() end local clSettings = pl.tablex.copy(self.state) return function(content) table.insert(self.stateQueue, self.state) diff --git a/core/settings_spec.lua b/core/settings_spec.lua new file mode 100644 index 0000000000..0b5bd5df95 --- /dev/null +++ b/core/settings_spec.lua @@ -0,0 +1,42 @@ +SILE = require("core.sile") + +describe("The settings handler", function() + + it("should trigger callbacks on set", function() + local private = "foo" + local function callback (value) + private = value .. " hooked" + end + SILE.settings:declare({ + parameter = "test.callback", + type = "string", + default = "bar", + hook = callback + }) + SILE.settings:set("test.callback", "baz") + assert.is.equal("baz hooked", private) + end) + + it("should trigger callbacks on push/pop events", function() + local mystate = "foo" + local function callback2 (value) + mystate = value .. " stack" + end + SILE.settings:declare({ + parameter = "test.callback2", + type = "string", + default = "bar", + hook = callback2 + }) + SILE.settings:pushState() + SILE.settings:set("test.callback2", "baz1") + SILE.settings:pushState() + SILE.settings:set("test.callback2", "baz2") + assert.is.equal("baz2 stack", mystate) + SILE.settings:popState() + assert.is.equal("baz1 stack", mystate) + SILE.settings:popState() + assert.is.equal("bar stack", mystate) + end) + +end) diff --git a/core/sile.lua b/core/sile.lua index 48626bc5e4..5d2a8e889b 100644 --- a/core/sile.lua +++ b/core/sile.lua @@ -1,44 +1,72 @@ --- Initialize SILE internals +--- The core SILE library. +-- Depending on how SILE was loaded, everything in here will probably be available under a top level `SILE` global. Note +-- that an additional global `SU` is typically available as an alias to `SILE.utilities`. Also some 3rd party Lua +-- libraries are always made available in the global scope, see `globals`. +-- @module SILE + +-- Placeholder for 3rd party Lua libraries SILE always provides as globals +require("core.globals") + +-- Reserve scope placeholder for profiler (developer tooling) +local ProFi + +-- Placeholder for SILE internals table SILE = {} +--- Fields +-- @section fields + +--- Machine friendly short-form version. +-- Semver, prefixed with "v", possible postfixed with ".r" followed by VCS version information. +-- @string version SILE.version = require("core.version") + +--- Status information about what options SILE was compiled with. +-- @table SILE.features +-- @tfield boolean appkit +-- @tfield boolean font_variations +-- @tfield boolean fontconfig +-- @tfield boolean harfbuzz +-- @tfield boolean icu SILE.features = require("core.features") -- Initialize Lua environment and global utilities + +--- ABI version of Lua VM. +-- For example may be `"5.1"` or `"5.4"` or others. Note that the ABI version for most LuaJIT implementations is 5.1. +-- @string lua_version SILE.lua_version = _VERSION:sub(-3) + + +--- Whether or not Lua VM is a JIT compiler. +-- @boolean lua_isjit -- luacheck: ignore jit SILE.lua_isjit = type(jit) == "table" + +--- User friendly long-form version string. +-- For example may be "SILE v0.14.17 (Lua 5.4)". +-- @string full_version SILE.full_version = string.format("SILE %s (%s)", SILE.version, SILE.lua_isjit and jit.version or _VERSION) -- Backport of lots of Lua 5.3 features to Lua 5.[12] if not SILE.lua_isjit and SILE.lua_version < "5.3" then require("compat53") end --- Penlight on-demand module loader, provided for SILE and document usage -pl = require("pl.import_into")() - --- For developer testing only, usually in CI -if os.getenv("SILE_COVERAGE") then require("luacov") end - --- Lua 5.3+ has a UTF-8 safe string function module but it is somewhat --- underwhelming. This module includes more functions and supports older Lua --- versions. Docs: https://github.com/starwing/luautf8 -luautf8 = require("lua-utf8") - --- Localization library, provided as global -fluent = require("fluent")() - --- Includes for _this_ scope -local lfs = require("lfs") - --- Developer tooling profiler -local ProFi +--- Modules +-- @section modules +--- Utilities module, typically accessed via `SU` alias. +-- @see SU SILE.utilities = require("core.utilities") SU = SILE.utilities -- regrettable global alias +-- For warnings and shims scheduled for removal that are easier to keep track +-- of when they are not spread across so many locations... +-- Loaded early to make it easier to manage migrations in core code. +require("core/deprecations") + -- On demand loader, allows modules to be loaded into a specific scope but -- only when/if accessed. -local core_loader = function (scope) +local function core_loader (scope) return setmetatable({}, { __index = function (self, key) -- local var = rawget(self, key) @@ -49,28 +77,78 @@ local core_loader = function (scope) }) end +--- Data tables +--- @section data + +--- Stash of all Lua functions used to power typesetter commands. +-- @table Commands SILE.Commands = {} + +--- Short usage messages corresponding to typesetter commands. +-- @table Help SILE.Help = {} + +--- List of currently enabled debug flags. +-- E.g. `{ typesetter = true, frames, true }`. +-- @table debugFlags SILE.debugFlags = {} + SILE.nodeMakers = {} SILE.tokenizers = {} SILE.status = {} + +--- The wild-west of stash stuff. +-- No rules, just right (or usually wrong). Everything in here *should* be somewhere else, but lots of early SILE code +-- relied on this as a global key/value store for various class, document, and package values. Since v0.14.0 with many +-- core SILE components being instances of classes –and especially with each package having it's own variable namespace– +-- there are almost always better places for things. This scratch space will eventually be completely deprecated, so +-- don't put anything new in here and only use things in it if there are no other current alternatives. +-- @table scratch SILE.scratch = {} + +--- Data storage for typesetter, frame, and class information. +-- Current document class instances, node queues, and other "hot" data can be found here. As with `SILE.scratch` +-- everything in here probably belongs elsewhere, but for now it is what it is. +-- @table documentState +-- @tfield table documentClass The instantiated document processing class. +-- @tfield table thisPageTemplate The frameset used for the current page. +-- @tfield table paperSize The current paper size. +-- @tfield table orgPaperSize The original paper size if the current one is modified via packages. SILE.documentState = {} + +--- Callback functions for handling types of raw content. +-- All registered handlers for raw content blocks have an entry in this table with the type as the key and the +-- processing function as the value. +-- @ table rawHandlers SILE.rawHandlers = {} +--- User input +-- @section input + +--- All user-provided input collected before beginning document processing. -- User input values, currently from CLI options, potentially all the inuts -- needed for a user to use a SILE-as-a-library version to produce documents -- programmatically. +-- @table input +-- @tfield table filenames Path names of file(s) intended for processing. Files are processed in the order provided. +-- File types may be mixed of any formaat for which SILE has an inputter module. +-- @tfield table evaluates List of strings to be evaluated as Lua code snippets *before* processing inputs. +-- @tfield table evaluteAfters List of strings to be evaluated as Lua code snippets *after* processing inputs. +-- @tfield table uses List of strings specifying module names (and optionally optionns) for modules to load *before* +-- processing inputs. For example this accomodates loading inputter modules before any input of that type is encountered. +-- Additionally it can be used to process a document using a document class *other than* the one specified in the +-- document itself. Document class modules loaded here are instantiated after load, meaning the document will not be +-- queried for a class at all. +-- @tfield table options Extra document class options to set or override in addition to ones found in the first input +-- document. SILE.input = { filenames = {}, evaluates = {}, evaluateAfters = {}, - includes = {}, uses = {}, options = {}, - preambles = {}, - postambles = {}, + preambles = {}, -- deprecated, undocumented + postambles = {}, -- deprecated, undocumented } -- Internal libraries that are idempotent and return classes that need instantiation @@ -81,20 +159,13 @@ SILE.classes = core_loader("classes") SILE.packages = core_loader("packages") SILE.typesetters = core_loader("typesetters") SILE.pagebuilders = core_loader("pagebuilders") +SILE.types = core_loader("types") --- Internal libraries that don't make assumptions on load, only use -SILE.traceStack = require("core.tracestack")() +-- Internal libraries that don't try to use anything on load, only provide something SILE.parserBits = require("core.parserbits") SILE.frameParser = require("core.frameparser") -SILE.color = require("core.color") -SILE.units = require("core.units") SILE.fontManager = require("core.fontmanager") - --- Internal libraries that assume globals, may be picky about load order -SILE.measurement = require("core.measurement") -SILE.length = require("core.length") SILE.papersize = require("core.papersize") -SILE.nodefactory = require("core.nodefactory") -- NOTE: -- See remainaing internal libraries loaded at the end of this file because @@ -114,7 +185,21 @@ local function runEvals (evals, arg) end end -SILE.init = function () +--- Core functions +-- @section functions + +--- Initialize a SILE instance. +-- Presumes CLI args have already been processed and/or library inputs are set. +-- +-- 1. If no backend has been loaded already (e.g. via `--use`) then assumes *libtexpdf*. +-- 2. Loads and instantiates a shaper and outputter module appropriate for the chosen backend. +-- 3. Instantiates a pagebuilder. +-- 4. Starts a Lua profiler if the profile debug flag is set. +-- 5. Instantiates a dependency tracker if we've been asked to write make dependencies. +-- 6. Runs any code snippents passed with `--eval`. +-- +-- Does not move on to processing input document(s). +function SILE.init () if not SILE.backend then SILE.backend = "libtexpdf" end @@ -171,11 +256,18 @@ local function suggest_luarocks (module) SILE.lua_version, guessed_module_name, SILE.lua_version, - pl.stringx.join(" ", _G.arg) + pl.stringx.join(" ", _G.arg or {}) ) end -SILE.use = function (module, options) +--- Multi-purpose loader to load and initialize modules. +-- This is used to load and intitialize core parts of SILE and also 3rd party modules. +-- Module types supported bay be an *inputter*, *outputer*, *shaper*, *typesetter*, *pagebuilder*, or *package*. +-- @tparam string|table module The module spec name to load (dot-separated, e.g. `"packages.lorem"`) or a table with +-- a module that has already been loaded. +-- @tparam[opt] table options Startup options as key/value pairs passed to the module when initialized. +-- @tparam[opt=false] boolean reload whether or not to reload a module that has been loaded and initialized before. +function SILE.use (module, options, reload) local status, pack if type(module) == "string" then status, pack = pcall(require, module) @@ -212,16 +304,19 @@ SILE.use = function (module, options) SILE.pagebuilders[name] = pack SILE.pagebuilder = pack(options) elseif pack.type == "package" then - SILE.packages[name] = pack + SILE.packages[pack._name] = pack if class then - pack(options) + class:loadPackage(pack, options, reload) else table.insert(SILE.input.preambles, { pack = pack, options = options }) end end end -SILE.require = function (dependency, pathprefix, deprecation_ack) +-- --- Content loader like Lua's `require()` but whith special path handling for loading SILE resource files. +-- -- Used for example by commands that load data via a `src=file.name` option. +-- -- @tparam string dependency Lua spec +function SILE.require (dependency, pathprefix, deprecation_ack) if pathprefix and not deprecation_ack then local notice = string.format([[ Please don't use the path prefix mechanism; it was intended to provide @@ -264,7 +359,11 @@ SILE.require = function (dependency, pathprefix, deprecation_ack) return lib end -SILE.process = function (ast) +--- Process content. +-- This is the main 'action' SILE does. Once input files are parsed into an abstract syntax tree, then we recursively +-- iterate through the tree handling each item in the order encountered. +-- @tparam table ast SILE content in abstract syntax tree format (a table of strings, functions, or more AST trees). +function SILE.process (ast) if not ast then return end if SU.debugging("ast") then SU.debugAST(ast, 0) @@ -277,9 +376,9 @@ SILE.process = function (ast) content() elseif SILE.Commands[content.command] then SILE.call(content.command, content.options, content) - elseif content.id == "texlike_stuff" + elseif content.id == "content" or (not content.command and not content.id) then - local pId = SILE.traceStack:pushContent(content, "texlike_stuff") + local pId = SILE.traceStack:pushContent(content, "content") SILE.process(content) SILE.traceStack:pop(pId) elseif type(content) ~= "nil" then @@ -316,6 +415,12 @@ local function detectFormat (doc, filename) SU.error(("Unable to pick inputter to process input from '%s'"):format(filename)) end +--- Process an input string. +-- First converts the string to an AST, then runs `process` on it. +-- @tparam string doc Input string to be coverted to SILE content. +-- @tparam[opt] nil|string format The name of the formatter. If nil, defaults to using each intputter's auto detection. +-- @tparam[opt] nil|string filename Pseudo filename to identify the content with, useful for error messages stack traces. +-- @tparam[opt] nil|table options Options to pass to the inputter instance when instantiated. function SILE.processString (doc, format, filename, options) local cpf if not filename then @@ -347,7 +452,14 @@ function SILE.processString (doc, format, filename, options) if cpf then SILE.currentlyProcessingFile = cpf end end +--- Process an input file +-- Opens a file, converts the contents to an AST, then runs `process` on it. +-- Roughly equivalent to listing the file as an input, but easier to embed in code. +-- @tparam string filename Path of file to open string to be coverted to SILE content. +-- @tparam[opt] nil|string format The name of the formatter. If nil, defaults to using each intputter's auto detection. +-- @tparam[opt] nil|table options Options to pass to the inputter instance when instantiated. function SILE.processFile (filename, format, options) + local lfs = require("lfs") local doc if filename == "-" then filename = "STDIN" @@ -363,6 +475,7 @@ function SILE.processFile (filename, format, options) end if SILE.masterDir and SILE.masterDir:len() >= 1 then _G.extendSilePath(SILE.masterDir) + _G.extendSilePathRocks(SILE.masterDir .. "/lua_modules") end filename = SILE.resolveFile(filename) or SU.error("Could not find file") local mode = lfs.attributes(filename).mode @@ -390,7 +503,7 @@ end -- TODO: this probably needs deprecating, moved here just to get out of the way so -- typesetters classing works as expected -SILE.typesetNaturally = function (frame, func) +function SILE.typesetNaturally (frame, func) local saveTypesetter = SILE.typesetter if SILE.typesetter.frame then SILE.typesetter.frame:leave(SILE.typesetter) end SILE.typesetter = SILE.typesetters.base(frame) @@ -402,7 +515,10 @@ SILE.typesetNaturally = function (frame, func) if SILE.typesetter.frame then SILE.typesetter.frame:enter(SILE.typesetter) end end --- Sort through possible places files could be +--- Resolve relative file paths to identify absolute resources locations. +-- Makes it possible to load resources from relative paths, relative to a document or project or SILE itself. +-- @tparam string filename Name of file to find using the same order of precidence logic in `require()`. +-- @tparam[opt] nil|string pathprefix Optional prefix in which to look for if the file isn't found otherwise. function SILE.resolveFile (filename, pathprefix) local candidates = {} -- Start with the raw file name as given prefixed with a path if requested @@ -430,6 +546,12 @@ function SILE.resolveFile (filename, pathprefix) return resolved end +--- Execute a registered SILE command. +-- Uses a function previously registered by any modules explicitly loaded by the user at runtime via `--use`, the SILE +-- core, the document class, or any loaded package. +-- @tparam string command Command name. +-- @tparam[opt={}] nil|table options Options to pass to the command. +-- @tparam[opt] nil|table content Any valid AST node to be processed by the command. function SILE.call (command, options, content) options = options or {} content = content or {} @@ -446,6 +568,19 @@ function SILE.call (command, options, content) return result end +--- (Deprecated) Register a function as a SILE command. +-- Takes any Lua function and registers it for use as a SILE command (which will in turn be used to process any content +-- nodes identified with the command name. +-- +-- Note that alternative versions of this action are available as methods on document classes and packages. Those +-- interfaces should be prefered to this global one. +-- @tparam string name Name of cammand to register. +-- @tparam function func Callback function to use as command handler. +-- @tparam[opt] nil|string help User friendly short usage string for use in error messages, documentation, etc. +-- @tparam[opt] nil|string pack Information identifying the module registering the command for use in error and usage +-- messages. Usually auto-detected. +-- @see SILE.classes:registerCommand +-- @see SILE.packages:registerCommand function SILE.registerCommand (name, func, help, pack, cheat) local class = SILE.documentState.documentClass if not cheat then @@ -459,36 +594,52 @@ function SILE.registerCommand (name, func, help, pack, cheat) return class:registerCommand(name, func, help, pack) end -function SILE.setCommandDefaults (command, defaults) +--- Wrap an existing command with new default options. +-- Modifies an already registered SILE command with a new table of options to be used as default values any time it is +-- called. Calling options still take precidence. +-- @tparam string command Name of command to overwride. +-- @tparam table options Options to set as updated defaults. +function SILE.setCommandDefaults (command, options) local oldCommand = SILE.Commands[command] - SILE.Commands[command] = function (options, content) - for k, v in pairs(defaults) do - options[k] = options[k] or v + SILE.Commands[command] = function (defaults, content) + for k, v in pairs(options) do + defaults[k] = defaults[k] or v end - return oldCommand(options, content) + return oldCommand(defaults, content) end end +-- TODO: Move to new table entry handler in types.unit function SILE.registerUnit (unit, spec) -- If a unit exists already, clear it first so we get fresh meta table entries, see #1607 - if SILE.units[unit] then - SILE.units[unit] = nil + if SILE.types.unit[unit] then + SILE.types.unit[unit] = nil end - SILE.units[unit] = spec + SILE.types.unit[unit] = spec end function SILE.paperSizeParser (size) - -- SU.deprecated("SILE.paperSizeParser", "SILE.papersize", "0.10.0", nil) + SU.deprecated("SILE.paperSizeParser", "SILE.papersize", "0.15.0", "0.16.0") return SILE.papersize(size) end +--- Finalize document processing +-- Signals that all the `SILE.process()` calls have been made and SILE should move on to finish up the output +-- +-- 1. Tells the document class to run its `:finish()` method. This method is typically responsible for calling the +-- `:finish()` method of the outputter module in the appropriate sequence. +-- 2. Closes out anything in active memory we don't need like font instances. +-- 3. Evaluate any snippets in SILE.input.evalAfter table. +-- 4. Stops logging dependecies and writes them to a makedepends file if requested. +-- 5. Close out the Lua profiler if it was running. +-- 6. Output version information if versions debug flag is set. function SILE.finish () - if SILE.makeDeps then - SILE.makeDeps:write() - end SILE.documentState.documentClass:finish() SILE.font.finish() runEvals(SILE.input.evaluateAfters, "evaluate-after") + if SILE.makeDeps then + SILE.makeDeps:write() + end if not SILE.quiet then io.stderr:write("\n") end @@ -501,19 +652,15 @@ function SILE.finish () end end --- Internal libraries that run core SILE functions on load +-- Internal libraries that return classes, but we only ever use one instantiation +SILE.traceStack = require("core.tracestack")() SILE.settings = require("core.settings")() + +-- Internal libraries that run core SILE functions on load require("core.hyphenator-liang") require("core.languages") -require("core.packagemanager") SILE.linebreak = require("core.break") require("core.frame") -SILE.cli = require("core.cli") -SILE.repl = require("core.repl") SILE.font = require("core.font") --- For warnings and shims scheduled for removal that are easier to keep track --- of when they are not spead across so many locations... -require("core/deprecations") - return SILE diff --git a/core/typesetter.lua b/core/typesetter.lua index cf7ca1a524..d2d35c79af 100644 --- a/core/typesetter.lua +++ b/core/typesetter.lua @@ -1,3 +1 @@ SU.deprecated("core.typesetter", "typesetters.base", "0.14.0", "0.15.0") - -return require("typesetters.base") diff --git a/core/utilities.lua b/core/utilities.lua deleted file mode 100644 index 92752ce85e..0000000000 --- a/core/utilities.lua +++ /dev/null @@ -1,633 +0,0 @@ -local bitshim = require("bitshim") -local luautf8 = require("lua-utf8") -local semver = require("semver") - -local utilities = {} - -local epsilon = 1E-12 - -utilities.required = function (options, name, context, required_type) - if not options[name] then utilities.error(context.." needs a "..name.." parameter") end - if required_type then - return utilities.cast(required_type, options[name]) - end - return options[name] -end - -local function preferbool () - utilities.warn("Please use boolean values or strings such as 'true' and 'false' instead of 'yes' and 'no'.") -end - -utilities.boolean = function (value, default) - if value == false then return false end - if value == true then return true end - if value == "false" then return false end - if value == "true" then return true end - if value == "no" then preferbool(); return false end - if value == "yes" then preferbool(); return true end - if value == nil then return default end - SU.error("Expecting a boolean value but got '" .. value .. "'") - return default -end - -local _skip_traceback_levels = 2 - -utilities.error = function (message, isbug) - _skip_traceback_levels = 3 - utilities.warn(message, isbug) - _skip_traceback_levels = 2 - io.stderr:flush() - SILE.outputter:finish() -- Only really useful from the REPL but no harm in trying - SILE.scratch.caughterror = true - error("", 2) -end - -utilities.warn = function (message, isbug) - if SILE.quiet then return end - io.stderr:write("\n! " .. message) - if SILE.traceback or isbug then - io.stderr:write(" at:\n" .. SILE.traceStack:locationTrace()) - if _skip_traceback_levels == 2 then - io.stderr:write(debug.traceback("", _skip_traceback_levels) or "\t! debug.traceback() did not identify code location") - end - else - io.stderr:write(" at " .. SILE.traceStack:locationHead()) - end - io.stderr:write("\n") -end - -utilities.msg = function (message) - if SILE.quiet then return end - io.stderr:write("\n! " .. message .. "\n") -end - -utilities.debugging = function (category) - return SILE.debugFlags.all and category ~= "profile" or SILE.debugFlags[category] -end - -utilities.feq = function (lhs, rhs) -- Float point equal - lhs = SU.cast("number", lhs) - rhs = SU.cast("number", rhs) - local abs = math.abs - return abs(lhs - rhs) <= epsilon * (abs(lhs) + abs(rhs)) -end - -utilities.gtoke = function (string, pattern) - string = string and tostring(string) or '' - pattern = pattern and tostring(pattern) or "%s+" - local length = #string - return coroutine.wrap(function() - local index = 1 - repeat - local first, last = string:find(pattern, index) - if last then - if index < first then coroutine.yield({ string = string:sub(index, first - 1) }) end - coroutine.yield({ separator = string:sub(first, last) }) - index = last + 1 - else - if index <= length then - coroutine.yield({ string = string:sub(index) }) - end - break - end - until index > length - end) -end - -utilities.deprecated = function (old, new, warnat, errorat, extra) - warnat, errorat = semver(warnat or 0), semver(errorat or 0) - local current = SILE.version and semver(SILE.version:match("v([0-9]*.[0-9]*.[0-9]*)")) or warnat - -- SILE.version is defined *after* most of SILE loads. It’s available at - -- runtime but not useful if we encounter deprecated code in core code. Users - -- will never encounter this failure, but as a developer it’s hard to test a - -- deprecation when core code refactoring is an all-or-nothing proposition. - -- Hence we fake it ‘till we make it, all deprecations internally are warnings. - local brackets = old:sub(1,1) == '\\' and "" or "()" - local _new = new and "Please use " .. (new .. brackets) .. " instead." or "Plase don't use it." - local msg = (old .. brackets) .. " was deprecated in SILE v" .. tostring(warnat) .. ". " .. _new .. (extra and "\n" .. extra .. "\n\n" or "") - if errorat and current >= errorat then - SU.error(msg) - elseif warnat and current >= warnat then - SU.warn(msg) - end -end - -utilities.debug = function (category, ...) - if SILE.quiet then return end - if utilities.debugging(category) then - local inputs = pl.utils.pack(...) - for i, input in ipairs(inputs) do - if type(input) == "function" then - local status, output = pcall(input) - inputs[i] = status and output or SU.warn(("Output of %s debug function was an error: %s"):format(category, output)) - end - end - local message = utilities.concat(inputs, " ") - if message then io.stderr:write(("\n[%s] %s"):format(category, message)) end - end -end - -utilities.debugAST = function (ast, level) - if not ast then - SU.error("debugAST called with nil", true) - end - local out = string.rep(" ", 1+level) - if level == 0 then - SU.debug("ast", function () - return "[" .. SILE.currentlyProcessingFile - end) - end - if type(ast) == "function" then - SU.debug("ast", function () - return out .. tostring(ast) - end) - elseif type(ast) == "table" then - for _, content in ipairs(ast) do - if type(content) == "string" then - SU.debug("ast", function () - return out .. "[" .. content .. "]" - end) - elseif type(content) == "table" then - if SILE.Commands[content.command] then - SU.debug("ast", function () - return out .. "\\" .. content.command .. " " .. pl.pretty.write(content.options, "") - end) - if (#content>=1) then utilities.debugAST(content, level+1) end - elseif content.id == "texlike_stuff" or (not content.command and not content.id) then - utilities.debugAST(content, level+1) - else - SU.debug("ast", function () - return out .. "?\\" .. (content.command or content.id) - end) - end - end - end - end - if level == 0 then SU.debug("ast", "]") end -end - -utilities.dump = function (...) - local arg = { ... } -- Avoid things that Lua stuffs in arg like args to self() - pl.pretty.dump(#arg == 1 and arg[1] or arg, "/dev/stderr") -end - -utilities.concat = function (array, separator) - return table.concat(utilities.map(tostring, array), separator) -end - -utilities.inherit = function (orig, spec) - local new = pl.tablex.deepcopy(orig) - if spec then - for k,v in pairs(spec) do new[k] = v end - end - if new.init then new:init() end - return new -end - -utilities.map = function (func, array) - local new_array = {} - local last = #array - for i = 1, last do - new_array[i] = func(array[i]) - end - return new_array -end - -utilities.sortedpairs = function (input) - local keys = {} - for k, _ in pairs(input) do - keys[#keys+1] = k - end - table.sort(keys, function(a, b) - if type(a) == type(b) then return a < b - elseif type(a) == "number" then return true - else return false - end - end) - return coroutine.wrap(function() - for i = 1, #keys do - coroutine.yield(keys[i], input[keys[i]]) - end - end) -end - -utilities.splice = function (array, start, stop, replacement) - local ptr = start - local room = stop - start + 1 - local last = replacement and #replacement or 0 - for i = 1, last do - if room > 0 then - room = room - 1 - array[ptr] = replacement[i] - else - table.insert(array, ptr, replacement[i]) - end - ptr = ptr + 1 - end - - for _ = 1, room do - table.remove(array, ptr) - end - return array -end - -utilities.sum = function (array) - local total = 0 - local last = #array - for i = 1, last do - total = total + array[i] - end - return total -end - --- Lua <= 5.2 can't handle objects in math functions -utilities.max = function (...) - local input = pl.utils.pack(...) - local max = table.remove(input, 1) - for _, val in ipairs(input) do - if val > max then max = val end - end - return max -end - -utilities.min = function (...) - local input = pl.utils.pack(...) - local min = input[1] - for _, val in ipairs(input) do - if val < min then min = val end - end - return min -end - -utilities.compress = function (items) - local rv = {} - local max = math.max(pl.utils.unpack(pl.tablex.keys(items))) - for i = 1, max do if items[i] then rv[#rv+1] = items[i] end end - return rv -end - -utilities.flip_in_place = function (tbl) - local tmp, j - for i = 1, math.floor(#tbl / 2) do - tmp = tbl[i] - j = #tbl - i + 1 - tbl[i] = tbl[j] - tbl[j] = tmp - end -end - -utilities.allCombinations = function (options) - local count = 1 - for i=1,#options do count = count * options[i] end - return coroutine.wrap(function() - for i=0,count-1 do - local this = i - local rv = {} - for j = 1,#options do - local base = options[j] - rv[#rv+1] = this % base + 1 - this = (this - this % base )/ base - end - coroutine.yield(rv) - end - end) -end - -utilities.type = function(value) - if type(value) == "number" then - return math.floor(value) == value and "integer" or "number" - elseif type(value) == "table" and value.prototype then - return value:prototype() - elseif type(value) == "table" and value.is_a then - return value.type - else - return type(value) - end -end - -utilities.cast = function (wantedType, value) - local actualType = SU.type(value) - wantedType = string.lower(wantedType) - if wantedType:match(actualType) then return value - elseif actualType == "nil" and wantedType:match("nil") then return nil - elseif wantedType:match("length") then return SILE.length(value) - elseif wantedType:match("measurement") then return SILE.measurement(value) - elseif wantedType:match("vglue") then return SILE.nodefactory.vglue(value) - elseif wantedType:match("glue") then return SILE.nodefactory.glue(value) - elseif wantedType:match("kern") then return SILE.nodefactory.kern(value) - elseif actualType == "nil" then SU.error("Cannot cast nil to " .. wantedType) - elseif wantedType:match("boolean") then return SU.boolean(value) - elseif wantedType:match("string") then return tostring(value) - elseif wantedType:match("number") then - if type(value) == "table" and type(value.tonumber) == "function" then - return value:tonumber() - end - local num = tonumber(value) - if not num then SU.error("Cannot cast '" .. value .. "'' to " .. wantedType) end - return num - elseif wantedType:match("integer") then - local num - if type(value) == "table" and type(value.tonumber) == "function" then - num = value:tonumber() - else - num = tonumber(value) - end - if not num then SU.error("Cannot cast '" .. value .. "'' to " .. wantedType) end - if not wantedType:match("number") and num % 1 ~= 0 then - -- Could be an error but since it wasn't checked before, let's just warn: - -- Some packages might have wrongly typed settings, for instance. - SU.warn("Casting an integer but got a float number " .. num) - end - return num - else SU.error("Cannot cast to unrecognized type " .. wantedType) - end -end - -utilities.hasContent = function(content) - return type(content) == "function" or type(content) == "table" and #content > 0 -end - --- Flatten content trees into just the string components (allows passing --- objects with complex structures to functions that need plain strings) -utilities.contentToString = function (content) - local string = "" - for i = 1, #content do - if type(content[i]) == "table" and type(content[i][1]) == "string" then - string = string .. content[i][1] - elseif type(content[i]) == "string" then - -- Work around PEG parser returning env tags as content - -- TODO: refactor capture groups in PEG parser - if content.command == content[i] and content[i] == content[i+1] then - break - end - string = string .. content[i] - end - end - return string -end - --- Strip the top level command off a content object and keep only the child --- items — assuming that the current command is taking care of itself -utilities.subContent = function (content) - local out = { id="stuff" } - for key, val in utilities.sortedpairs(content) do - if type(key) == "number" then - out[#out+1] = val - end - end - return out -end - --- Call `action` on each content AST node, recursively, including `content` itself. --- Not called on leaves, i.e. strings. -utilities.walkContent = function (content, action) - if type(content) ~= "table" then - return - end - action(content) - for i = 1, #content do - utilities.walkContent(content[i], action) - end -end - ---- Strip position, line and column recursively from a content tree. --- This can be used to remove position details where we do not want them, --- e.g. in table of contents entries (referring to the original content, --- regardless where it was exactly, for the purpose of checking whether --- the table of contents changed.) --- -utilities.stripContentPos = function (content) - if type(content) ~= "table" then - return content - end - local stripped = {} - for k, v in pairs(content) do - if type(v) == "table" then - v = SU.stripContentPos(v) - end - stripped[k] = v - end - if content.id or content.command then - stripped.pos, stripped.col, stripped.lno = nil, nil, nil - end - return stripped -end - -utilities.rateBadness = function(inf_bad, shortfall, spring) - if spring == 0 then return inf_bad end - local bad = math.floor(100 * math.abs(shortfall / spring) ^ 3) - return math.min(inf_bad, bad) -end - -utilities.rationWidth = function (target, width, ratio) - if ratio < 0 and width.shrink:tonumber() > 0 then - target:___add(width.shrink:tonumber() * ratio) - elseif ratio > 0 and width.stretch:tonumber() > 0 then - target:___add(width.stretch:tonumber() * ratio) - end - return target -end - --- Unicode-related utilities -utilities.utf8char = function (c) - utilities.deprecated("SU.utf8char", "luautf8.char", "0.11.0", "0.12.0") - return luautf8.char(c) -end - -utilities.codepoint = function (uchar) - local seq = 0 - local val = -1 - for i = 1, #uchar do - local c = string.byte(uchar, i) - if seq == 0 then - if val > -1 then return val end - seq = c < 0x80 and 1 or c < 0xE0 and 2 or c < 0xF0 and 3 or - c < 0xF8 and 4 or --c < 0xFC and 5 or c < 0xFE and 6 or - error("invalid UTF-8 character sequence") - val = bitshim.band(c, 2^(8-seq) - 1) - else - val = bitshim.bor(bitshim.lshift(val, 6), bitshim.band(c, 0x3F)) - end - seq = seq - 1 - end - return val -end - -utilities.utf8charfromcodepoint = function (codepoint) - local val = codepoint - local cp = val - local hex = (cp:match("[Uu]%+(%x+)") or cp:match("0[xX](%x+)")) - if hex then - cp = tonumber("0x"..hex) - elseif tonumber(cp) then - cp = tonumber(cp) - end - - if type(cp) == "number" then - val = luautf8.char(cp) - end - return val -end - -utilities.utf8codes = function (ustr) - utilities.deprecated("SU.utf8codes", "luautf8.codes", "0.11.0", "0.12.0") - return luautf8.codes(ustr) -end - -utilities.utf16codes = function (ustr, endian) - local pos = 1 - return function() - if pos > #ustr then - return nil - else - local c1, c2, c3, c4, wchar, lowchar - c1 = string.byte(ustr, pos, pos+1) - pos = pos + 1 - c2 = string.byte(ustr, pos, pos+1) - pos = pos + 1 - if endian == "be" then - wchar = c1 * 256 + c2 - else - wchar = c2 * 256 + c1 - end - if not (wchar >= 0xD800 and wchar <= 0xDBFF) then - return wchar - end - c3 = string.byte(ustr, pos, pos+1) - pos = pos + 1 - c4 = string.byte(ustr, pos, pos+1) - pos = pos + 1 - if endian == "be" then - lowchar = c3 * 256 + c4 - else - lowchar = c4 * 256 + c3 - end - return 0x10000 + bitshim.lshift(bitshim.band(wchar, 0x03FF), 10) + bitshim.band(lowchar, 0x03FF) - end - end -end - -utilities.splitUtf8 = function (str) -- Return an array of UTF8 strings each representing a Unicode char - local rv = {} - for _, cp in luautf8.next, str do - table.insert(rv, luautf8.char(cp)) - end - return rv -end - -utilities.lastChar = function (str) - local chars = utilities.splitUtf8(str) - return chars[#chars] -end - -utilities.firstChar = function (str) - local chars = utilities.splitUtf8(str) - return chars[1] -end - -local byte, floor, reverse = string.byte, math.floor, string.reverse - -utilities.utf8charat = function (str, index) - return str:sub(index):match("([%z\1-\127\194-\244][\128-\191]*)") -end - -local utf16bom = function(endianness) - return endianness == "be" and "\254\255" or endianness == "le" and "\255\254" or SU.error("Unrecognized endianness") -end - -utilities.hexencoded = function (str) - local ustr = "" - for i = 1, #str do - ustr = ustr..string.format("%02x", byte(str, i, i+1)) - end - return ustr -end - -utilities.hexdecoded = function (str) - if #str % 2 == 1 then SU.error("Cannot decode hex string with odd len") end - local ustr = "" - for i = 1, #str, 2 do - ustr = ustr..string.char(tonumber(string.sub(str, i, i+1), 16)) - end - return ustr -end - -local uchr_to_surrogate_pair = function(uchr, endianness) - local hi, lo = floor((uchr - 0x10000) / 0x400) + 0xd800, (uchr - 0x10000) % 0x400 + 0xdc00 - local s_hi, s_lo = string.char(floor(hi / 256)) .. string.char(hi % 256), string.char(floor(lo / 256)) .. string.char(lo % 256) - return endianness == "le" and (reverse(s_hi) .. reverse(s_lo)) or s_hi .. s_lo -end - -local uchr_to_utf16_double_byte = function(uchr, endianness) - local ustr = string.char(floor(uchr / 256)) .. string.char(uchr % 256) - return endianness == "le" and reverse(ustr) or ustr -end - -local utf8_to_utf16 = function(str, endianness) - local ustr = utf16bom(endianness) - for _, uchr in luautf8.codes(str) do - ustr = ustr..(uchr < 0x10000 and uchr_to_utf16_double_byte(uchr, endianness) - or uchr_to_surrogate_pair(uchr, endianness)) - end - return ustr -end - -utilities.utf8_to_utf16be = function (str) return utf8_to_utf16(str, "be") end -utilities.utf8_to_utf16le = function (str) return utf8_to_utf16(str, "le") end -utilities.utf8_to_utf16be_hexencoded = function (str) return utilities.hexencoded(utilities.utf8_to_utf16be(str)) end -utilities.utf8_to_utf16le_hexencoded = function (str) return utilities.hexencoded(utilities.utf8_to_utf16le(str)) end - -local utf16_to_utf8 = function (str, endianness) - local bom = utf16bom(endianness) - - if str:find(bom) == 1 then str = string.sub(str, 3, #str) end - local ustr = "" - for uchr in utilities.utf16codes(str, endianness) do - ustr = ustr..luautf8.char(uchr) - end - return ustr -end - -utilities.utf16be_to_utf8 = function (str) return utf16_to_utf8(str, "be") end -utilities.utf16le_to_utf8 = function (str) return utf16_to_utf8(str, "le") end - -utilities.breadcrumbs = function () - local breadcrumbs = {} - - setmetatable (breadcrumbs, { - __index = function(_, key) - local frame = SILE.traceStack[key] - return frame and frame.command or nil - end, - __len = function(_) - return #SILE.traceStack - end, - __tostring = function (self) - return "B»" .. table.concat(self, "»") - end - }) - - function breadcrumbs:dump () - SU.dump(self) - end - - function breadcrumbs:parent (count) - -- Note LuaJIT does not support __len, so this has to work even when that metamethod doesn't fire... - return self[#SILE.traceStack-(count or 1)] - end - - function breadcrumbs:contains (needle) - for i, command in ipairs(self) do - if command == needle then return true, #self - i end - end - return false, -1 - end - - return breadcrumbs -end - -utilities.formatNumber = require("core.utilities-numbers") - -utilities.collatedSort = require("core.utilities-sorting") - -return utilities diff --git a/core/utilities/ast.lua b/core/utilities/ast.lua new file mode 100644 index 0000000000..650d3469c8 --- /dev/null +++ b/core/utilities/ast.lua @@ -0,0 +1,258 @@ +--- AST utilities. +-- Functions for working with SILE's Abstract Syntax Trees. +-- @module SU.ast + +-- @type SU.ast +local ast = {} + +--- Output developer friendly debugging view of an AST. +-- @tparam table tree Abstract Syntax Tree. +-- @tparam integer level Starting level to review. +function ast.debug (tree, level) + if not tree then + SU.error("debugAST called with nil", true) + end + local out = string.rep(" ", 1+level) + if level == 0 then + SU.debug("ast", function () + return "[" .. SILE.currentlyProcessingFile + end) + end + if type(tree) == "function" then + SU.debug("ast", function () + return out .. tostring(tree) + end) + elseif type(tree) == "table" then + for _, content in ipairs(tree) do + if type(content) == "string" then + SU.debug("ast", function () + return out .. "[" .. content .. "]" + end) + elseif type(content) == "table" then + if SILE.Commands[content.command] then + SU.debug("ast", function () + return out .. "\\" .. content.command .. " " .. pl.pretty.write(content.options, "") + end) + if (#content>=1) then ast.debug(content, level+1) end + elseif content.id == "content" or (not content.command and not content.id) then + ast.debug(content, level+1) + else + SU.debug("ast", function () + return out .. "?\\" .. (content.command or content.id) + end) + end + end + end + end + if level == 0 then SU.debug("ast", "]") end +end + +--- Find a command node in a SILE AST tree, +-- looking only at the first level. +-- (We're not reimplementing XPath here.) +-- @tparam table tree AST tree +-- @tparam string command command name +-- @treturn table|nil AST command node +function ast.findInTree (tree, command) + for i=1, #tree do + if type(tree[i]) == "table" and tree[i].command == command then + return tree[i] + end + end +end + +--- Find and extract (remove) a command node in a SILE AST tree, +-- looking only at the first level. +-- @tparam table tree AST tree +-- @tparam string command command name +-- @treturn table|nil AST command node +function ast.removeFromTree (tree, command) + for i=1, #tree do + if type(tree[i]) == "table" and tree[i].command == command then + return table.remove(tree, i) + end + end +end + +--- Create a command from a simple content tree. +-- It encapsulates the content in a command node. +-- @tparam string command command name +-- @tparam table options command options +-- @tparam table content child AST tree +-- @tparam table position position in source (or parent AST command node) +-- @treturn table AST command node +function ast.createCommand (command, options, content, position) + local result = { content } + result.options = options or {} + result.command = command + result.id = "command" + if position then + result.col = position.col or 0 + result.lno = position.lno or 0 + result.pos = position.pos or 0 + else + result.col = 0 + result.lno = 0 + result.pos = 0 + end + return result +end + +--- Create a command from a structured content tree. +-- The content is normally a table of an already prepared content list. +-- @tparam string command command name +-- @tparam table options command options +-- @tparam table content child AST tree +-- @tparam table position position in source (or parent AST command node) +-- @treturn table AST command node +function ast.createStructuredCommand (command, options, content, position) + local result = type(content) == "table" and content or { content } + result.options = options or {} + result.command = command + result.id = "command" + if position then + result.col = position.col or 0 + result.lno = position.lno or 0 + result.pos = position.pos or 0 + else + result.col = 0 + result.lno = 0 + result.pos = 0 + end + return result +end + +--- Extract the sub-content tree from a (command) node, +-- that is the child nodes of the (command) node. +-- @tparam table content AST tree +-- @treturn table AST tree +function ast.subContent (content) + local out = {} + for _, val in ipairs(content) do + out[#out+1] = val + end + return out +end + +-- String trimming +local function trimLeft (str) + return str:gsub("^%s*", "") +end +local function trimRight (str) + return str:gsub("%s*$", "") +end + +--- Content tree trimming: remove leading and trailing spaces, but from +--- a content tree i.e. possibly containing several elements. +-- @tparam table content AST tree +-- @treturn table AST tree +function ast.trimSubContent (content) + if #content == 0 then + return + end + if type(content[1]) == "string" then + content[1] = trimLeft(content[1]) + if content[1] == "" then + table.remove(content, 1) + end + end + if type(content[#content]) == "string" then + content[#content] = trimRight(content[#content]) + if content[#content] == "" then + table.remove(content, #content) + end + end + return content +end + +--- Process the AST walking through content nodes as a "structure": +-- Text nodes are ignored (e.g. usually just spaces due to indentation) +-- Command options are enriched with their "true" node position, so we can later +-- refer to it (as with an XPath pos()). +-- @tparam table content AST tree +function ast.processAsStructure (content) + local iElem = 0 + local nElem = 0 + for i = 1, #content do + if type(content[i]) == "table" then + nElem = nElem + 1 + end + end + for i = 1, #content do + if type(content[i]) == "table" then + iElem = iElem + 1 + content[i].options._pos_ = iElem + content[i].options._last_ = iElem == nElem + SILE.process({ content[i] }) + end + -- All text nodes in ignored in structure tags. + end +end + +--- Call `action` on each content AST node, recursively, including `content` itself. +-- Not called on leaves, i.e. strings. +-- @tparam table content AST tree +-- @tparam function action A function to call on each node +function ast.walkContent (content, action) + if type(content) ~= "table" then + return + end + action(content) + for i = 1, #content do + ast.walkContent(content[i], action) + end +end + +--- Strip position, line and column recursively from a content tree. +-- This can be used to remove position details where we do not want them, +-- e.g. in table of contents entries (referring to the original content, +-- regardless where it was exactly, for the purpose of checking whether +-- the table of contents changed.) +-- @param table content AST tree +-- @treturn table AST tree +function ast.stripContentPos (content) + if type(content) ~= "table" then + return content + end + local stripped = {} + for k, v in pairs(content) do + if type(v) == "table" then + v = ast.stripContentPos(v) + end + stripped[k] = v + end + if content.id or content.command then + stripped.pos, stripped.col, stripped.lno = nil, nil, nil + end + return stripped +end + +--- Flatten content trees into just the string components (allows passing +-- objects with complex structures to functions that need plain strings) +-- @tparam table content AST tree +-- @treturn string A string representation of content +function ast.contentToString (content) + local string = "" + for i = 1, #content do + if type(content[i]) == "table" and type(content[i][1]) == "string" then + string = string .. content[i][1] + elseif type(content[i]) == "string" then + -- Work around PEG parser returning env tags as content + -- TODO: refactor capture groups in PEG parser + if content.command == content[i] and content[i] == content[i+1] then + break + end + string = string .. content[i] + end + end + return string +end + +--- Check whether a content AST tree is empty. +-- @tparam table content AST tree +-- @treturn boolean true if content is not empty +function ast.hasContent (content) + return type(content) == "function" or type(content) == "table" and #content > 0 +end + +return ast diff --git a/core/utilities/init.lua b/core/utilities/init.lua new file mode 100644 index 0000000000..eb274cc47b --- /dev/null +++ b/core/utilities/init.lua @@ -0,0 +1,749 @@ +--- SILE.utilities (aliased as SU) +-- @module SU +-- @alias utilities + +local bitshim = require("bitshim") +local luautf8 = require("lua-utf8") +local semver = require("semver") + +local utilities = {} + +local epsilon = 1E-12 + +--- Generic +-- @section generic + +--- Concatenate values from a table using a given separator. +-- Differs from `table.concat` in that all values are explicitly cast to strings, allowing debugging of tables that +-- include functions, other tables, data types, etc. +-- @tparam table array Input. +-- @tparam[opt=" "] string separator Separator. +function utilities.concat (array, separator) + return table.concat(utilities.map(tostring, array), separator) +end + +--- Execute a callback function on each value in a table. +-- @tparam function func Function to run on each value. +-- @tparam table array Input list-like table. +function utilities.map (func, array) + local new_array = {} + local last = #array + for i = 1, last do + new_array[i] = func(array[i]) + end + return new_array +end + +--- Require that an option table contains a specific value, otherwise raise an error. +-- @param options Input table of options. +-- @param name Name of the required option. +-- @param context User friendly name of the function or calling context. +-- @param required_type The name of a data type that the option must sucessfully cast to. +function utilities.required (options, name, context, required_type) + if not options[name] then utilities.error(context.." needs a "..name.." parameter") end + if required_type then + return utilities.cast(required_type, options[name]) + end + return options[name] +end + +--- Iterate over key/value pairs in sequence of the sorted keys. +-- Table iteration order with `pairs` is non-deterministic. This function returns an iterator that can be used in plais +-- of `pairs` that will iterate through the values in the order of their *sorted* keys. +-- @tparam table input Input table. +-- @usage for val in SU.sortedpairs({ b: "runs second", a: "runs first" ) do print(val) end +function utilities.sortedpairs (input) + local keys = {} + for k, _ in pairs(input) do + keys[#keys+1] = k + end + table.sort(keys, function(a, b) + if type(a) == type(b) then return a < b + elseif type(a) == "number" then return true + else return false + end + end) + return coroutine.wrap(function() + for i = 1, #keys do + coroutine.yield(keys[i], input[keys[i]]) + end + end) +end + +--- Substitute a range of value(s) in one table with values from another. +-- @tparam table array Table to modify. +-- @tparam integer start First key to replace. +-- @tparam integer stop Last key to replace. +-- @tparam table replacement Table from which to pull key/values plairs to inject in array. +-- @treturn table array First input array modified with values from replacement. +function utilities.splice (array, start, stop, replacement) + local ptr = start + local room = stop - start + 1 + local last = replacement and #replacement or 0 + for i = 1, last do + if room > 0 then + room = room - 1 + array[ptr] = replacement[i] + else + table.insert(array, ptr, replacement[i]) + end + ptr = ptr + 1 + end + + for _ = 1, room do + table.remove(array, ptr) + end + return array +end + +-- TODO: Unused, now deprecated? +function utilities.inherit (orig, spec) + local new = pl.tablex.deepcopy(orig) + if spec then + for k,v in pairs(spec) do new[k] = v end + end + if new.init then new:init() end + return new +end + +--- Type handling +-- @section types + +local function preferbool () + utilities.warn("Please use boolean values or strings such as 'true' and 'false' instead of 'yes' and 'no'.") +end + +--- Cast user intput into a boolean type. +-- User input content such as options typed into documents will return string values such as "true" or "false rather +-- than true or false types. This evaluates those strings or other inputs ane returns a consistent boolean type in +-- return. +-- @tparam nil|bool|string value Input value such as a string to evaluate for thruthyness. +-- @tparam[opt=false] boolean default Whether to assume inputs that don't specifically evaluate to something should be true or false. +-- @treturn boolean +function utilities.boolean (value, default) + if value == false then return false end + if value == true then return true end + if value == "false" then return false end + if value == "true" then return true end + if value == "no" then preferbool(); return false end + if value == "yes" then preferbool(); return true end + if value == nil then return default == true end + if value == "" then return default == true end + SU.error("Expecting a boolean value but got '" .. value .. "'") + return default == true +end + +--- Cast user intput to an expected type. +-- If possible, converts input from one type to another. Not all types can be cast. For example "four" can't be cast to +-- a number, but "4" or 4 can. Likewise "6pt" or 6 can be cast to a SILE.types.measurement, SILE.types.length, or even +-- a SILE.types.node.glue, but not a SILE.types.color. +-- @tparam string wantedType Expected type. +-- @return A value of the type wantedType. +function utilities.cast (wantedType, value) + local actualType = SU.type(value) + wantedType = string.lower(wantedType) + if wantedType:match(actualType) then return value + elseif actualType == "nil" and wantedType:match("nil") then return nil + elseif wantedType:match("length") then return SILE.types.length(value) + elseif wantedType:match("measurement") then return SILE.types.measurement(value) + elseif wantedType:match("vglue") then return SILE.types.node.vglue(value) + elseif wantedType:match("glue") then return SILE.types.node.glue(value) + elseif wantedType:match("kern") then return SILE.types.node.kern(value) + elseif actualType == "nil" then SU.error("Cannot cast nil to " .. wantedType) + elseif wantedType:match("boolean") then return SU.boolean(value) + elseif wantedType:match("string") then return tostring(value) + elseif wantedType:match("number") then + if type(value) == "table" and type(value.tonumber) == "function" then + return value:tonumber() + end + local num = tonumber(value) + if not num then SU.error("Cannot cast '" .. value .. "'' to " .. wantedType) end + return num + elseif wantedType:match("integer") then + local num + if type(value) == "table" and type(value.tonumber) == "function" then + num = value:tonumber() + else + num = tonumber(value) + end + if not num then SU.error("Cannot cast '" .. value .. "'' to " .. wantedType) end + if not wantedType:match("number") and num % 1 ~= 0 then + -- Could be an error but since it wasn't checked before, let's just warn: + -- Some packages might have wrongly typed settings, for instance. + SU.warn("Casting an integer but got a float number " .. num) + end + return num + else SU.error("Cannot cast to unrecognized type " .. wantedType) + end +end + +--- Return the type of an object +-- Like Lua's `type`, but also handles various SILE user data types. +-- @tparam any value Any input value. If a table is one of SILE's classes or types, report on it's internal type. +-- Otherwise use the output of `type`. +-- @treturn string +function utilities.type (value) + if type(value) == "number" then + return math.floor(value) == value and "integer" or "number" + elseif type(value) == "table" and value.prototype then + return value:prototype() + elseif type(value) == "table" and value.is_a then + return value.type + else + return type(value) + end +end + +--- Errors and debugging +-- @section errors + +--- Output a debug message only if debugging for a specific category is enabled. +-- Importantly passing siries of strings, functions, or tables is more effecient than trying to formulate a full message +-- using concatentation and tostring() methods in the original code because it doesn't have to even run if the relevant +-- debug flag is not enabled. +-- @tparam text category Category flag for which this message should be output. +-- @tparam string|function|table ... Each argument will be returned separated by spaces, strings directly, functions by +-- evaluating them and assuming the return value is a string, and tables by using their internal :__tostring() methods. +-- @usage +-- > glue = SILE.types.node.glue("6em") +-- > SU.debug("foo", "A glue node", glue) +-- [foo] A glue node G<6em> +function utilities.debug (category, ...) + if SILE.quiet then return end + if utilities.debugging(category) then + local inputs = pl.utils.pack(...) + for i, input in ipairs(inputs) do + if type(input) == "function" then + local status, output = pcall(input) + inputs[i] = status and output or SU.warn(("Output of %s debug function was an error: %s"):format(category, output)) + elseif type(input) ~= "string" then + inputs[i] = tostring(input) + end + end + local message = utilities.concat(inputs, " ") + if message then io.stderr:write(("\n[%s] %s"):format(category, message)) end + end +end + +--- Determine if a specific debug flag is set. +-- @tparam string category Name of the flag status to check, e.g. "frames". +-- @treturn boolean +function utilities.debugging (category) + return SILE.debugFlags.all and category ~= "profile" or SILE.debugFlags[category] +end + +--- Warn about use of a deprecated feature. +-- Checks the current version and decides whether to warn or error, then oatputs a message with as much useful +-- information as possible to make it easy for end users to update their usage. +-- @tparam string old The name of the deprecated interface. +-- @tparam string new A name of a suggested replacement interface. +-- @tparam string warnat The first release where the interface is considered deprecated, at which point their might be +-- a shim. +-- @tparam string errorat The first release where the interface is no longer functional even with a shim. +-- @tparam string extra Longer-form help to include in output separate from the expected one-liner of warning messages. +function utilities.deprecated (old, new, warnat, errorat, extra) + warnat, errorat = semver(warnat or 0), semver(errorat or 0) + local current = SILE.version and semver(SILE.version:match("v([0-9]*.[0-9]*.[0-9]*)")) or warnat + -- SILE.version is defined *after* most of SILE loads. It’s available at + -- runtime but not useful if we encounter deprecated code in core code. Users + -- will never encounter this failure, but as a developer it’s hard to test a + -- deprecation when core code refactoring is an all-or-nothing proposition. + -- Hence we fake it ‘till we make it, all deprecations internally are warnings. + local brackets = old:sub(1,1) == '\\' and "" or "()" + local _new = new and "Please use " .. (new .. brackets) .. " instead." or "Plase don't use it." + local msg = (old .. brackets) .. " was deprecated in SILE v" .. tostring(warnat) .. ". " .. _new .. (extra and ("\n\n" .. extra .. "\n") or "") + if errorat and current >= errorat then + SU.error(msg) + elseif warnat and current >= warnat then + SU.warn(msg) + end +end + +--- Dump the contents of a any Lua type. +-- For quick debugging, can be used on any number of any type of Lua value. Pretty-prints tables. +-- @tparam any ... Any number of values +function utilities.dump (...) + local arg = { ... } -- Avoid things that Lua stuffs in arg like args to self() + pl.pretty.dump(#arg == 1 and arg[1] or arg, "/dev/stderr") +end + +local _skip_traceback_levels = 2 + +--- Raise an error and exit. +-- Outputs a warning message via `warn`, then finishes up anything it can without processing more content, then exits. +-- @tparam string message The error message to give. +-- @tparam boolean isbug Whether or not hitting this error is expected to be a code bug (as opposed to misakes in user input). +function utilities.error (message, isbug) + _skip_traceback_levels = 3 + utilities.warn(message, isbug) + _skip_traceback_levels = 2 + io.stderr:flush() + SILE.outputter:finish() -- Only really useful from the REPL but no harm in trying + SILE.scratch.caughterror = true + error("", 2) +end + +--- Output an information message. +-- Basically like `warn`, except to source tracing information is added. +-- @tparam string message +function utilities.msg (message) + if SILE.quiet then return end + io.stderr:write("\n! " .. message .. "\n") +end + +--- Output a warning. +-- Outputs a warning message including identifying where in the processing SILE is at when the warning is given. +-- @tparam string message The error message to give. +-- @tparam boolean isbug Whether or not hitting this warning is expected to be a code bug (as opposed to misakes in user input). +function utilities.warn (message, isbug) + utilities.msg(message) + if SILE.traceback or isbug then + io.stderr:write(" at:\n" .. SILE.traceStack:locationTrace()) + if _skip_traceback_levels == 2 then + io.stderr:write(debug.traceback("", _skip_traceback_levels) or "\t! debug.traceback() did not identify code location") + end + else + io.stderr:write(" at " .. SILE.traceStack:locationHead()) + end + io.stderr:write("\n") +end + +--- Math +-- @section math + +--- Check equality of floating point values. +-- Comparing floating point numbers using math functions in Lua may give different and unexpected answers depending on +-- the Lua VM and other environmental factors. This normalizes them using our standard internal epsilon value and +-- compares the absolute intereger value to avoid floating point number wierdness. +-- @tparam float lhs +-- @tparam float rhs +-- @treturn boolean +function utilities.feq (lhs, rhs) + lhs = SU.cast("number", lhs) + rhs = SU.cast("number", rhs) + local abs = math.abs + return abs(lhs - rhs) <= epsilon * (abs(lhs) + abs(rhs)) +end + +--- Add up all the values in a table. +-- @tparam table array Input list-like table. +-- @treturn number Sum of all values. +function utilities.sum (array) + local total = 0 + local last = #array + for i = 1, last do + total = total + array[i] + end + return total +end + +--- Return maximum value of inputs. +-- `math.max`, but works on SILE types such as SILE.types.measurement. +-- Lua <= 5.2 can't handle math operators on objects. +function utilities.max (...) + local input = pl.utils.pack(...) + local max = table.remove(input, 1) + for _, val in ipairs(input) do + if val > max then max = val end + end + return max +end + +--- Return minimum value of inputs. +-- `math.min`, but works on SILE types such as SILE.types.measurement. +-- Lua <= 5.2 can't handle math operators on objects. +function utilities.min (...) + local input = pl.utils.pack(...) + local min = input[1] + for _, val in ipairs(input) do + if val < min then min = val end + end + return min +end + +--- Round and normalize a number for debugging. +-- LuaJIT 2.1 betas (and inheritors such as OpenResty and Moonjit) are biased +-- towards rounding 0.5 up to 1, all other Lua interpreters are biased +-- towards rounding such floating point numbers down. This hack shaves off +-- just enough to fix the bias so our test suite works across interpreters. +-- Note that even a true rounding function here will fail because the bias is +-- inherent to the floating point type. Also note we are erroring in favor of +-- the *less* common option because the LuaJIT VMS are hopelessly broken +-- whereas normal LUA VMs can be cooerced. +-- @tparam number input Input value. +-- @treturn string Four-digit precision foating point. +function utilities.debug_round (input) + if input > 0 then input = input + .00000000000001 end + if input < 0 then input = input - .00000000000001 end + return string.format("%.4f", input) +end + +--- Remove empty spaces from list-like tables +-- Iterating list-like tables is hard if some values have been removed. This converts { 1 = "a", 3 = "b" } into +-- { 1 = "a", 2 = "b" } which can be iterated using `ipairs` without stopping after 1. +-- @tparam table items List-like table potentially with holes. +-- @treturn table List like table without holes. +function utilities.compress (items) + local rv = {} + local max = math.max(pl.utils.unpack(pl.tablex.keys(items))) + for i = 1, max do if items[i] then rv[#rv+1] = items[i] end end + return rv +end + +--- Reverse the order of a list-like table. +-- @tparam table tbl Input list-like table. +function utilities.flip_in_place (tbl) + local tmp, j + for i = 1, math.floor(#tbl / 2) do + tmp = tbl[i] + j = #tbl - i + 1 + tbl[i] = tbl[j] + tbl[j] = tmp + end +end + +-- TODO: Before documenting, consider whether this should be private to the one existing usage. +function utilities.allCombinations (options) + local count = 1 + for i=1,#options do count = count * options[i] end + return coroutine.wrap(function() + for i=0,count-1 do + local this = i + local rv = {} + for j = 1,#options do + local base = options[j] + rv[#rv+1] = this % base + 1 + this = (this - this % base )/ base + end + coroutine.yield(rv) + end + end) +end + + +function utilities.rateBadness (inf_bad, shortfall, spring) + if spring == 0 then return inf_bad end + local bad = math.floor(100 * math.abs(shortfall / spring) ^ 3) + return math.min(inf_bad, bad) +end + +function utilities.rationWidth (target, width, ratio) + if ratio < 0 and width.shrink:tonumber() > 0 then + target:___add(width.shrink:tonumber() * ratio) + elseif ratio > 0 and width.stretch:tonumber() > 0 then + target:___add(width.stretch:tonumber() * ratio) + end + return target +end + +--- Text handling +-- @section text + +--- Iterate over a string split into tokens via a pattern. +-- @tparam string string Input string. +-- @tparam string pattern Pattern on which to split the input. +-- @treturn function An iterator function +-- @usage for str in SU.gtoke("foo-bar-baz", "-") do print(str) end +function utilities.gtoke (string, pattern) + string = string and tostring(string) or '' + pattern = pattern and tostring(pattern) or "%s+" + local length = #string + return coroutine.wrap(function() + local index = 1 + repeat + local first, last = string:find(pattern, index) + if last then + if index < first then coroutine.yield({ string = string:sub(index, first - 1) }) end + coroutine.yield({ separator = string:sub(first, last) }) + index = last + 1 + else + if index <= length then + coroutine.yield({ string = string:sub(index) }) + end + break + end + until index > length + end) +end + +--- Convert a Unicode character to its corresponding codepoint. +-- @tparam string uchar A single inicode character. +-- @return number The Unicode code point where uchar is encoded. +function utilities.codepoint (uchar) + local seq = 0 + local val = -1 + for i = 1, #uchar do + local c = string.byte(uchar, i) + if seq == 0 then + if val > -1 then return val end + seq = c < 0x80 and 1 or c < 0xE0 and 2 or c < 0xF0 and 3 or + c < 0xF8 and 4 or --c < 0xFC and 5 or c < 0xFE and 6 or + error("invalid UTF-8 character sequence") + val = bitshim.band(c, 2^(8-seq) - 1) + else + val = bitshim.bor(bitshim.lshift(val, 6), bitshim.band(c, 0x3F)) + end + seq = seq - 1 + end + return val +end + +--- Covert a code point to a Unicode character. +-- @tparam number|string codepoint Input code point value, either as a number or a string representing the decimal value "U+NNNN" or hex value "0xFFFF". +-- @treturn string The character replestened by a codepoint descriptions. +function utilities.utf8charfromcodepoint (codepoint) + local val = codepoint + local cp = val + local hex = (cp:match("[Uu]%+(%x+)") or cp:match("0[xX](%x+)")) + if hex then + cp = tonumber("0x"..hex) + elseif tonumber(cp) then + cp = tonumber(cp) + end + + if type(cp) == "number" then + val = luautf8.char(cp) + end + return val +end + +--- Convert a UTF-16 encoded string to a series of code points. +-- Like `luautf8.codes`, but for UTF-16 strings. +-- @tparam string ustr Input string. +-- @tparam string endian Either "le" or "be" depending on the enconding endedness. +-- @treturn string Serious of hex encoded code points. +function utilities.utf16codes (ustr, endian) + local pos = 1 + return function() + if pos > #ustr then + return nil + else + local c1, c2, c3, c4, wchar, lowchar + c1 = string.byte(ustr, pos, pos+1) + pos = pos + 1 + c2 = string.byte(ustr, pos, pos+1) + pos = pos + 1 + if endian == "be" then + wchar = c1 * 256 + c2 + else + wchar = c2 * 256 + c1 + end + if not (wchar >= 0xD800 and wchar <= 0xDBFF) then + return wchar + end + c3 = string.byte(ustr, pos, pos+1) + pos = pos + 1 + c4 = string.byte(ustr, pos, pos+1) + pos = pos + 1 + if endian == "be" then + lowchar = c3 * 256 + c4 + else + lowchar = c4 * 256 + c3 + end + return 0x10000 + bitshim.lshift(bitshim.band(wchar, 0x03FF), 10) + bitshim.band(lowchar, 0x03FF) + end + end +end + +--- Split a UTF-8 string into characters. +-- Lua's `string.split` will only explode a string by bytes. For text processing purposes it is usually more desirable +-- to split it into 1, 2, 3, or 4 byte grups matching the UTF-8 encoding. +-- @tparam string str Input UTF-8 encoded string. +-- @treturn table A list-like table of UTF8 strings each representing a Unicode char from the input string. +function utilities.splitUtf8 (str) + local rv = {} + for _, cp in luautf8.next, str do + table.insert(rv, luautf8.char(cp)) + end + return rv +end + +--- The last Unicode character in a UTF-8 encoded string. +-- Uses `SU.splitUtf8` to break an string into segments represtenting encoded characters, returns the last one. May be +-- more than one byte. +-- @tparam string str Input string. +-- @treturn string A single Unicode character. +function utilities.lastChar (str) + local chars = utilities.splitUtf8(str) + return chars[#chars] +end + +--- The first Unicode character in a UTF-8 encoded string. +-- Uses `SU.splitUtf8` to break an string into segments represtenting encoded characters, returns the first one. May be +-- more than one byte. +-- @tparam string str Input string. +-- @treturn string A single Unicode character. +function utilities.firstChar (str) + local chars = utilities.splitUtf8(str) + return chars[1] +end + +local byte, floor, reverse = string.byte, math.floor, string.reverse + +--- The Unicode character in a UTF-8 encoded string at a specifi position +-- Uses `SU.splitUtf8` to break an string into segments represtenting encoded characters, returns the Nth one. May be +-- more than one byte. +-- @tparam string str Input string. +-- @tparam number index Index of character to return. +-- @treturn string A single Unicode character. +function utilities.utf8charat (str, index) + return str:sub(index):match("([%z\1-\127\194-\244][\128-\191]*)") +end + +local utf16bom = function(endianness) + return endianness == "be" and "\254\255" or endianness == "le" and "\255\254" or SU.error("Unrecognized endianness") +end + +--- Encode a string to a hexidecimal replesentation. +-- @tparam string str Input UTF-8 string +-- @treturn string Hexidecimal replesentation of str. +function utilities.hexencoded (str) + local ustr = "" + for i = 1, #str do + ustr = ustr..string.format("%02x", byte(str, i, i+1)) + end + return ustr +end + +--- Decode a hexidecimal replesentation into a string. +-- @tparam string str Input hexidecimal encoded string. +-- @treturn string UTF-8 string. +function utilities.hexdecoded (str) + if #str % 2 == 1 then SU.error("Cannot decode hex string with odd len") end + local ustr = "" + for i = 1, #str, 2 do + ustr = ustr..string.char(tonumber(string.sub(str, i, i+1), 16)) + end + return ustr +end + +local uchr_to_surrogate_pair = function(uchr, endianness) + local hi, lo = floor((uchr - 0x10000) / 0x400) + 0xd800, (uchr - 0x10000) % 0x400 + 0xdc00 + local s_hi, s_lo = string.char(floor(hi / 256)) .. string.char(hi % 256), string.char(floor(lo / 256)) .. string.char(lo % 256) + return endianness == "le" and (reverse(s_hi) .. reverse(s_lo)) or s_hi .. s_lo +end + +local uchr_to_utf16_double_byte = function(uchr, endianness) + local ustr = string.char(floor(uchr / 256)) .. string.char(uchr % 256) + return endianness == "le" and reverse(ustr) or ustr +end + +local utf8_to_utf16 = function(str, endianness) + local ustr = utf16bom(endianness) + for _, uchr in luautf8.codes(str) do + ustr = ustr..(uchr < 0x10000 and uchr_to_utf16_double_byte(uchr, endianness) + or uchr_to_surrogate_pair(uchr, endianness)) + end + return ustr +end + +--- Convert a UTF-8 string to big-endian UTF-16. +-- @tparam string str UTF-8 encoded string. +-- @treturn string Big-endian UTF-16 encoded string. +function utilities.utf8_to_utf16be (str) return utf8_to_utf16(str, "be") end + +--- Convert a UTF-8 string to little-endian UTF-16. +-- @tparam string str UTF-8 encoded string. +-- @treturn string Little-endian UTF-16 encoded string. +function utilities.utf8_to_utf16le (str) return utf8_to_utf16(str, "le") end + +--- Convert a UTF-8 string to big-endian UTF-16, then encode in hex. +-- @tparam string str UTF-8 encoded string. +-- @treturn string Hexidecimal representation of a big-endian UTF-16 encoded string. +function utilities.utf8_to_utf16be_hexencoded (str) return utilities.hexencoded(utilities.utf8_to_utf16be(str)) end + +--- Convert a UTF-8 string to little-endian UTF-16, then encode in hex. +-- @tparam string str UTF-8 encoded string. +-- @treturn string Hexidecimal representation of a little-endian UTF-16 encoded string. +function utilities.utf8_to_utf16le_hexencoded (str) return utilities.hexencoded(utilities.utf8_to_utf16le(str)) end + +local utf16_to_utf8 = function (str, endianness) + local bom = utf16bom(endianness) + + if str:find(bom) == 1 then str = string.sub(str, 3, #str) end + local ustr = "" + for uchr in utilities.utf16codes(str, endianness) do + ustr = ustr..luautf8.char(uchr) + end + return ustr +end + +--- Convert a big-endian UTF-16 string to UTF-8. +-- @tparam string str Big-endian UTF-16 encoded string. +-- @treturn string UTF-8 encoded string. +function utilities.utf16be_to_utf8 (str) return utf16_to_utf8(str, "be") end + +--- Convert a little-endian UTF-16 string to UTF-8. +-- @tparam string str Little-endian UTF-16 encoded string. +-- @treturn string UTF-8 encoded string. +function utilities.utf16le_to_utf8 (str) return utf16_to_utf8(str, "le") end + +function utilities.breadcrumbs () + local breadcrumbs = {} + + setmetatable (breadcrumbs, { + __index = function(_, key) + local frame = SILE.traceStack[key] + return frame and frame.command or nil + end, + __len = function(_) + return #SILE.traceStack + end, + __tostring = function (self) + return "B»" .. table.concat(self, "»") + end + }) + + function breadcrumbs:dump () + SU.dump(self) + end + + function breadcrumbs:parent (count) + -- Note LuaJIT does not support __len, so this has to work even when that metamethod doesn't fire... + return self[#SILE.traceStack-(count or 1)] + end + + function breadcrumbs:contains (needle) + for i, command in ipairs(self) do + if command == needle then return true, #self - i end + end + return false, -1 + end + + return breadcrumbs +end + +utilities.formatNumber = require("core.utilities.numbers") + +utilities.collatedSort = require("core.utilities.sorting") + +utilities.ast = require("core.utilities.ast") +utilities.debugAST = utilities.ast.debug + +function utilities.subContent (content) + SU.deprecated("SU.subContent", "SU.ast.subContent", "0.15.0", "0.17.0", [[ + Note that the new implementation no longer introduces an id="stuff" key.]]) + return utilities.ast.subContent(content) +end + +function utilities.hasContent (content) + SU.deprecated("SU.hasContent", "SU.ast.hasContent", "0.15.0", "0.17.0") + return SU.ast.hasContent(content) +end + +function utilities.contentToString (content) + SU.deprecated("SU.contentToString", "SU.ast.contentToString", "0.15.0", "0.17.0") + return SU.ast.contentToString(content) +end + +function utilities.walkContent (content, action) + SU.deprecated("SU.walkContent", "SU.ast.walkContent", "0.15.0", "0.17.0") + SU.ast.walkContent(content, action) +end + +function utilities.stripContentPos (content) + SU.deprecated("SU.stripContentPos", "SU.ast.stripContentPos", "0.15.0", "0.17.0") + return SU.ast.stripContentPos(content) +end + +return utilities diff --git a/core/utilities-numbers.lua b/core/utilities/numbers.lua similarity index 86% rename from core/utilities-numbers.lua rename to core/utilities/numbers.lua index 4eef0ea43f..6bd6f3552c 100644 --- a/core/utilities-numbers.lua +++ b/core/utilities/numbers.lua @@ -1,9 +1,9 @@ --- --- Number formatting utilities --- MIT License (c) 2022 SILE organization --- +--- Number formatting utilities. +--- @module SU.numbers + local icu = require("justenoughicu") +--- @type formatNumber -- Language-specific number formatters add functions to this table, -- see e.g. languages/eo.lua local formatNumber = { @@ -20,8 +20,29 @@ local formatNumber = { num = (num - num % 26) / 26 until num < 1 return out + end, + -- Greek is another special case: + -- There are books where one wants to number items with Greek letters in + -- sequence, e.g. annotations in biblical material etc. + -- as in "α β γ δ ε ζ η θ ι κ λ μ ν ξ ο π ρ σ τ υ φ χ ψ ω". + -- We can't use ICU "grek" or "greklow" numbering systems because they are + -- non-arithmetical, e.g. 6 is a digamma (ϝ´), 11 is iota alpha (ια´), etc. + -- and they are also all followed by a numeric marker ("keraia"). + greek = function(num) + local out = "" + local a = SU.codepoint("α") -- alpha + if num < 18 then + -- alpha to rho + out = luautf8.char(num + a - 1) + elseif num < 25 then + -- sigma to omega (unicode has two sigmas here, we skip one) + out = luautf8.char(num + a) + else + -- Don't try to be too clever + SU.error("Greek numbering is only supported up to 24") + end + return out end - } } @@ -113,7 +134,7 @@ setmetatable (formatNumber, { New syntax is SU.formatNumber(num, options[, case]) with an options table, possibly containing: - system: a numbering system string, e.g. "latn" (= "arabic"), "roman", "arab", etc. - With the addition of "alpha". + With the addition of "alpha" and "greek". Casing is taken into account (e.g. roman, Roman, ROMAN) unless specified - style: a format style string, i.e. "default", "decimal", "ordinal", "string") E.g. in English and latin script: 1234 1,234 1,124th one thousand... @@ -176,7 +197,7 @@ setmetatable (formatNumber, { -- Global specific hooks exists: use them... result = self.und[system](num, options) elseif system and type(self["und"][system]) == "function" then - -- TRICK: Notably, special case for "alpha" + -- TRICK: Notably, special case for "alpha" and "greek" result = system and self.und[system](num, options) else --- Otherwise, rely on ICU... diff --git a/core/utilities-sorting.lua b/core/utilities/sorting.lua similarity index 90% rename from core/utilities-sorting.lua rename to core/utilities/sorting.lua index 38a350e625..88a0402d2f 100644 --- a/core/utilities-sorting.lua +++ b/core/utilities/sorting.lua @@ -1,7 +1,6 @@ --- --- Table sorting with language-dependent collation --- MIT License (c) 2022 SILE organization --- +--- Table sorting with language-dependent collation. +-- @module SU.sorting + local icu = require("justenoughicu") local collatedSort = { diff --git a/documentation/c02-gettingstarted.sil b/documentation/c02-gettingstarted.sil index 255c7d4367..699754d3d6 100644 --- a/documentation/c02-gettingstarted.sil +++ b/documentation/c02-gettingstarted.sil @@ -228,7 +228,7 @@ Otherwise to go with default of bundling them, just run: \begin{autodoc:note} If you plan on developing SILE itself (whether just to hack on it for your own use or contribute upstream) there are two particularly useful configuration options. First, you can add \code{--datarootdir=$(cd ..;pwd)} which will enable the compiled binary to run directly from the source directory without being installed at all. -Second, you can add \code{--enable-developer} to also check for tooling we expect SILE developers to have, such as tools used for testing. +Second, you can add \code{--enable-developer-mode} to also check for tooling we expect SILE developers to have, such as tools used for testing. Using this option also enables a number of targets that wouldn't normally be needed by end-users, such as \code{make regressions}. \end{autodoc:note} diff --git a/documentation/c03-input.sil b/documentation/c03-input.sil index 3151e7f945..525a74c2cb 100644 --- a/documentation/c03-input.sil +++ b/documentation/c03-input.sil @@ -69,6 +69,27 @@ The orientation of the page is defined as "portrait" by default, but if you want \begin[landscape=true]{document} \end{raw} +\subsection{Full bleed printing} + +When preparing a book for press printing, you may be asked by the professional printer to output the document on a larger sheet than your target paper, and to reserve a trim area around it. +This trick is often called “full bleed printing”. +Your document will be printed on an oversized sheet that will then be mechanically cut down to the target size. +You can specify the expected “trim” (or “bleed”) dimension, to be distributed evenly on all sides +of the document: + +\code{papersize=\em{}, bleed=\em{}}. + +For instance, a US trade book with an extra 0.125 inch bleed area can be specified by \code{papersize=6in x 9in, bleed=0.25in}. +The output paper size is then 6.25 per 9.25 inches, with the actual 6 per 9 inches inner content centered. + +Some packages, such as \autodoc:package{background} and \autodoc:package{cropmarks}, ensure their content extends over the trim area and thus indeed “bleeds” off the sides of the page, so that artifacts such as blank lines are avoided when the sheets are cut, would they be trimmed slightly differently for some assembling or technical reasons. + +Finally, there is also the case when the actual paper sheets available to you are larger than your target paper size, and yet you would want the output document to show properly centered: + +\code{papersize=\em{}, sheetsize=\em{}}. + +For instance, \code{papersize=6in x 9in, sheetsize=a4} produces an A4-dimensioned document, but with you content formatted as a 6 per 9 inches US trade book. You may, obviously, combine these options and also specify a bleed area. + \section{Ordinary text} On the whole, ordinary text isn’t particularly interesting—it’s just typeset. @@ -165,7 +186,7 @@ The \code{\\begin} command at the start of the document is an example of this.% \end{raw} The parameters to a command are enclosed in square brackets and take the form \code{\em{key}=\em{value}}; - multiple parameters are separated by commas or semicolons, as in \code{[key1=value1,key2=value2,…]}. + multiple parameters are separated by commas, as in \code{[key1=value1,key2=value2,…]}. Spaces around the keys are not significant; we could equally write that as \code{[key1 = value1; key2 = value2; …]}. If you need to include a comma or semicolon within the value to a parameter, you can enclose the value in quotes: @@ -191,7 +212,7 @@ Here are a few more examples of SILE commands: \end{raw}% \section{Environments} -Commands like \autodoc:command{\chapter} (to start a chapter) and \autodoc:command{\em} (to emphasise text by making it italic) are normally used to enclose a relatively small piece of text—a few lines at most. +Commands like \autodoc:command{\chapter} (to start a chapter) and \autodoc:command{\em} (to emphasise text) are normally used to enclose a relatively small piece of text—a few lines at most. Where you want to enclose a larger piece of the document, you can use an \em{environment}. An environment begins with \code{\\begin\{\em{name}\}} and encloses all the text up until the corresponding \code{\\end\{\em{name}\}}. We’ve already seen an example: the \autodoc:environment[check=false]{document} environment, which must enclose the \em{entire} document. @@ -209,6 +230,16 @@ Hi there! However, in some cases the environment form of the command will be easier to read and will help you to be clearer on where the command begins and ends. +\section{SIL flavor grammar specifications} + +The official grammar for the SIL flavor is the LPEG reference implementation. +That being said the reference implementation has some idiosyncrasies and is not the easiest to read. +For convenience an ABNF grammar is also provided in the source tree, see \code{sil.abnf}. +This grammar does not completely express the language as it cannot express the way SIL can embed other syntaxes, but it is a decent approximation. + +The intent behind many of the syntax choices is to make it easy to have parity with XML flavors. +This means limiting commands to valid XML identifiers (e.g. starting with an ASCII letter, not a digit or special character), requiring a single top level command as the document, and so forth. + \section{The XML flavor} As mentioned at the start of the chapter, SILE can actually process its input in a completely different file format. diff --git a/documentation/c04-useful.sil b/documentation/c04-useful.sil index c4ffacd3c7..b304726613 100644 --- a/documentation/c04-useful.sil +++ b/documentation/c04-useful.sil @@ -83,7 +83,7 @@ The full list of attributes to the \autodoc:command{\font} command are: It’s quite fiddly to be always changing font specifications manually; later we’ll see some ways to automate the process. -SILE’s \autodoc:class{plain} class notably provides the \autodoc:command{\em{…}} command as a shortcut for \autodoc:command{\font[style=italic]{…}}, and the \autodoc:command{\strong{…}} command as a a shortcut for \autodoc:command{\font[weight=700]{…}}. +SILE’s \autodoc:class{plain} class notably provides the \autodoc:command{\strong{…}} command as a a shortcut for \autodoc:command{\font[weight=700]{…}}, and the \autodoc:command{\em{…}} to emphasize text (switching between italic or regular style as needed). Note for parameters that accept multiple values, values may be separated with commas. Be sure to wrap the value in quotes so the commas don't get parsed as new parameters. @@ -268,41 +268,53 @@ To Sherlock Holmes she is always \em{the woman}. This is because every file is required to contain a valid XML tree, which wouldn’t be the case without a common root. SILE is written in the Lua programming language, and the Lua interpreter is available at runtime. -Just as one can run Javascript code from within a HTML document using a \code{ - -]===] - - return DZSlides -end - -return M diff --git a/lua-libraries/lunamark/writer/generic.lua b/lua-libraries/lunamark/writer/generic.lua deleted file mode 100644 index 92133e4737..0000000000 --- a/lua-libraries/lunamark/writer/generic.lua +++ /dev/null @@ -1,294 +0,0 @@ --- (c) 2009-2011 John MacFarlane. Released under MIT license. --- See the file LICENSE in the source for details. - ---- Generic writer for lunamark. --- This serves as generic documentation for lunamark writers, --- which all export a table with the same functions defined. --- --- New writers can simply modify the generic writer: for example, --- --- local Xml = generic.new(options) --- --- Xml.linebreak = "" --- --- local escaped = { --- ["<" ] = "<", --- [">" ] = ">", --- ["&" ] = "&", --- ["\"" ] = """, --- ["'" ] = "'" --- } --- --- function Xml.string(s) --- return s:gsub(".",escaped) --- end - -local util = require("lunamark.util") -local M = {} -local W = {} - -local meta = {} -meta.__index = - function(_, key) - io.stderr:write(string.format("WARNING: Undefined writer function '%s'\n",key)) - return (function(...) return table.concat({...}," ") end) - end -setmetatable(W, meta) - -local rope_to_string = util.rope_to_string - ---- Returns a table with functions defining a generic lunamark writer, --- which outputs plain text with no formatting. `options` is an optional --- table with the following fields: --- --- `layout` --- : `minimize` (no space between blocks) --- : `compact` (no extra blank lines between blocks) --- : `default` (blank line between blocks) -function M.new(options) - ---- The table contains the following fields: - - options = options or {} - local metadata = {} - - --- Set metadata field `key` to `val`. - function W.set_metadata(key, val) - metadata[key] = val - return "" - end - - --- Add `val` to an array in metadata field `key`. - function W.add_metadata(key, val) - local cur = metadata[key] - if type(cur) == "table" then - table.insert(cur,val) - elseif cur then - metadata[key] = {cur, val} - else - metadata[key] = {val} - end - end - - --- Return metadata table. - function W.get_metadata() - return metadata - end - - -- Turn list of output into final result. - function W.merge(result) - return rope_to_string(result) - end - - --- A space (string). - W.space = " " - - --- Setup tasks at beginning of document. - function W.start_document() - return "" - end - - --- Finalization tasks at end of document. - function W.stop_document() - return "" - end - - --- Plain text block (not formatted as a pragraph). - function W.plain(s) - return s - end - - --- A line break (string). - W.linebreak = "\n" - - --- Line breaks to use between block elements. - W.interblocksep = "\n\n" - - --- Line breaks to use between a container (like a `
` - -- tag) and the adjacent block element. - W.containersep = "\n" - - if options.layout == "minimize" then - W.interblocksep = "" - W.containersep = "" - elseif options.layout == "compact" then - W.interblocksep = "\n" - W.containersep = "\n" - end - - --- Ellipsis (string). - W.ellipsis = "…" - - --- Em dash (string). - W.mdash = "—" - - --- En dash (string). - W.ndash = "–" - - --- Non-breaking space. - W.nbsp = " " - - --- String in curly single quotes. - function W.singlequoted(s) - return {"‘", s, "’"} - end - - --- String in curly double quotes. - function W.doublequoted(s) - return {"“", s, "”"} - end - - --- String, escaped as needed for the output format. - function W.string(s) - return s - end - - --- Inline (verbatim) code. - function W.code(s) - return s - end - - --- A link with link text `label`, uri `uri`, - -- and title `title`. - function W.link(label, uri, title) - return label - end - - --- An image link with alt text `label`, - -- source `src`, and title `title`. - function W.image(label, src, title) - return label - end - - --- A paragraph. - function W.paragraph(s) - return s - end - - --- A bullet list with contents `items` (an array). If - -- `tight` is true, returns a "tight" list (with - -- minimal space between items). - function W.bulletlist(items,tight) - return util.intersperse(items,W.interblocksep) - end - - --- An ordered list with contents `items` (an array). If - -- `tight` is true, returns a "tight" list (with - -- minimal space between items). If optional - -- number `startnum` is present, use it as the - -- number of the first list item. - function W.orderedlist(items,tight,startnum) - return util.intersperse(items,W.interblocksep) - end - - --- Inline HTML. - function W.inline_html(s) - return "" - end - - --- Display HTML (HTML block). - function W.display_html(s) - return "" - end - - --- Emphasized text. - function W.emphasis(s) - return s - end - - --- Strongly emphasized text. - function W.strong(s) - return s - end - - --- Block quotation. - function W.blockquote(s) - return s - end - - --- Verbatim block. - function W.verbatim(s) - return s - end - - --- Fenced code block, with infostring `i`. - function W.fenced_code(s, i) - return s - end - - --- Header level `level`, with text `s`. - function W.header(s, level) - return s - end - - --- Horizontal rule. - W.hrule = "" - - --- A string of one or more citations. `text_cites` is a boolean, true if the - -- citations are in-text citations. `cites` - is an array of tables, each of - -- the form `{ prenote = q, name = n, postnote = e, suppress_author = s }`, - -- where: - -- - `q` is a nil or a rope that should be inserted before the citation, - -- - `e` is a nil or a rope that should be inserted after the citation, - -- - `n` is a string with the citation name, and - -- - `s` is a boolean, true if the author should be omitted from the - -- citation. - function W.citations(text_cites, cites) - local buffer = {} - local opened_brackets = false - for i, cite in ipairs(cites) do - if i == 1 then -- Opening in-text citation - if text_cites then - buffer[#buffer + 1] = {cite.suppress_author and "-" or "", "@", - cite.name} - if cite.postnote then - opened_brackets = true - buffer[#buffer + 1] = {" [", cite.postnote} - end - else -- Opening regular citation - opened_brackets = true - buffer[#buffer + 1] = {"[", cite.prenote and {cite.prenote, " "} or "", - cite.suppress_author and "-" or "", "@", cite.name, cite.postnote and - {" ", cite.postnote}} - end - else -- Continuation citation - buffer[#buffer + 1] = {"; ", cite.prenote and {cite.prenote, " "} or "", - cite.suppress_author and "-" or "", "@", cite.name, cite.postnote and - {" ", cite.postnote}} - end - end - if opened_brackets then - buffer[#buffer + 1] = "]" - end - return buffer - end - - --- A footnote or endnote. - function W.note(contents) - return contents - end - - --- A definition list. `items` is an array of tables, - -- each of the form `{ term = t, definitions = defs, tight = tight }`, - -- where `t` is a string and `defs` is an array of strings. - -- `tight` is a boolean, true if it is a tight list. - function W.definitionlist(items, tight) - local buffer = {} - for _,item in ipairs(items) do - buffer[#buffer + 1] = item.t - buffer[#buffer + 1] = util.intersperse(item.definitions, W.interblocksep) - end - return util.intersperse(buffer,W.interblocksep) - end - - --- A cosmo template to be used in producing a standalone document. - -- `$body` is replaced with the document body, `$title` with the - -- title, and so on. - W.template = [[ -$body -]] - - return util.table_copy(W) -end - -return M diff --git a/lua-libraries/lunamark/writer/groff.lua b/lua-libraries/lunamark/writer/groff.lua deleted file mode 100644 index d722fea921..0000000000 --- a/lua-libraries/lunamark/writer/groff.lua +++ /dev/null @@ -1,81 +0,0 @@ --- (c) 2009-2011 John MacFarlane. Released under MIT license. --- See the file LICENSE in the source for details. - ---- Generic groff writer for lunamark. --- This is currently used as the basis for [lunamark.writer.man]. --- In principle other groff-based writers could also extend it. - -local M = {} - -local util = require("lunamark.util") -local generic = require("lunamark.writer.generic") - ---- Returns a new Groff writer. --- For a list of all fields, see [lunamark.writer.generic]. -function M.new(options) - options = options or {} - local Groff = generic.new(options) - - Groff.interblocksep = "\n\n" -- insensitive to layout - - Groff.containersep = "\n" - - Groff.linebreak = ".br\n" - - Groff.ellipsis = "\\&..." - - Groff.mdash = "\\[em]" - - Groff.ndash = "\\[en]" - - Groff.nbsp = "\\~" - - function Groff.singlequoted(s) - return {"`",s,"'"} - end - - function Groff.doublequoted(s) - return {"\\[lq]",s,"\\[rq]"} - end - - Groff.escaped = { - ["@"] = "\\@", - ["\\"] = "\\\\", - } - - local escaped_utf8_triplet = { - ["\226\128\156"] = "\\[lq]", - ["\226\128\157"] = "\\[rq]", - ["\226\128\152"] = "`", - ["\226\128\153"] = "'", - ["\226\128\148"] = "\\[em]", - ["\226\128\147"] = "\\[en]", - ["\194\160"] = "\\ ", - } - - local escape = util.escaper(Groff.escaped, escaped_utf8_triplet) - - Groff.string = escape - - function Groff.inline_html(s) - end - - function Groff.display_html(s) - end - - function Groff.code(s) - return {"\\f[C]",s,"\\f[]"} - end - - function Groff.emphasis(s) - return {"\\f[I]",s,"\\f[]"} - end - - function Groff.strong(s) - return {"\\f[B]",s,"\\f[]"} - end - - return Groff -end - -return M diff --git a/lua-libraries/lunamark/writer/html.lua b/lua-libraries/lunamark/writer/html.lua deleted file mode 100644 index eba6e8eb36..0000000000 --- a/lua-libraries/lunamark/writer/html.lua +++ /dev/null @@ -1,191 +0,0 @@ --- (c) 2009-2011 John MacFarlane. Released under MIT license. --- See the file LICENSE in the source for details. - ---- HTML writer for lunamark. --- Extends [lunamark.writer.xml]. - -local M = {} - -local xml = require("lunamark.writer.xml") -local util = require("lunamark.util") -local flatten, intersperse, map = util.flatten, util.intersperse, util.map - ---- Return a new HTML writer. --- For a list of all fields in the writer, see [lunamark.writer.generic]. --- ---`options` is a table that can contain the following fields: --- --- `containers` --- : Put sections in `
` tags. --- --- `slides` --- : Do not allow containers to nest; when a subsection begins, --- close the section's container and start a new one. --- --- `layout` --- : `minimize` removes semantically insignificant white space. --- : `compact` removes unneeded blank lines. --- : `default` puts blank lines between block elements. -function M.new(options) - options = options or {} - local Html = xml.new(options) - - local endnotes = {} - local containersep = Html.containersep - local interblocksep = Html.interblocksep - - Html.container = "div" - Html.linebreak = "
" - Html.nbsp = " " - - function Html.code(s) - return {"", Html.string(s), ""} - end - - function Html.link(lab,src,tit) - local titattr - if type(tit) == "string" and #tit > 0 - then titattr = " title=\"" .. Html.string(tit) .. "\"" - else titattr = "" - end - return {"", lab, ""} - end - - function Html.image(lab,src,tit) - local titattr - if type(tit) == "string" and #tit > 0 - then titattr = " title=\"" .. Html.string(tit) .. "\"" - else titattr = "" - end - return {"\"","} - end - - function Html.paragraph(s) - return {"

", s, "

"} - end - - local function listitem(s) - return {"
  • ", s, "
  • "} - end - - function Html.bulletlist(items,tight) - return {"
      ", containersep, intersperse(map(items, listitem), containersep), containersep, "
    "} - end - - function Html.orderedlist(items,tight,startnum) - local start = "" - if startnum and startnum ~= 1 then - start = " start=\"" .. startnum .. "\"" - end - return {"", containersep, intersperse(map(items, listitem), containersep), containersep, ""} - end - - function Html.inline_html(s) - return s - end - - function Html.display_html(s) - return s - end - - function Html.emphasis(s) - return {"", s, ""} - end - - function Html.strong(s) - return {"", s, ""} - end - - function Html.blockquote(s) - return {"
    ", containersep, s, containersep, "
    "} - end - - function Html.verbatim(s) - return {"
    ", Html.string(s), "
    "} - end - - function Html.fenced_code(s,i) - if i ~= "" then - return {'
    ', Html.string(s), "
    "} - else - return Html.verbatim(s) - end - end - - function Html.header(s,level) - local sep = "" - if options.slides or options.containers then - local lev = (options.slides and 1) or level - local stop = Html.stop_section(lev) - if stop ~= "" then - stop = stop .. Html.interblocksep - end - sep = stop .. Html.start_section(lev) .. Html.containersep - end - return {sep, "", s, ""} - end - - Html.hrule = "
    " - - function Html.note(contents) - local num = #endnotes + 1 - local backref = ' ' - local contentsf = flatten(contents) - if contentsf[#contentsf] == "

    " then - table.insert(contentsf, #contentsf, backref) - else - contentsf[#contentsf + 1] = backref - end - endnotes[num] = {'
  • ', contentsf, '
  • '} - return {'', num, ''} - end - - function Html.start_document() - endnotes = {} - return "" - end - - function Html.stop_document() - return function() - local stop = Html.stop_section(1) -- close section containers - if stop ~= "" then stop = Html.containersep .. stop end - if #endnotes == 0 then - return stop - else - return {stop, interblocksep, '
    ', interblocksep, '
      ', - containersep, intersperse(endnotes, interblocksep), containersep, '
    '} - end - end - end - - function Html.definitionlist(items, tight) - local buffer = {} - local sep - if tight then sep = "" else sep = Html.containersep end - for _,item in ipairs(items) do - local defs = {} - for _,def in ipairs(item.definitions) do - defs[#defs + 1] = {"
    ", sep, def, sep, "
    "} - end - buffer[#buffer + 1] = {"
    ", item.term, "
    ", containersep, intersperse(defs, containersep)} - end - return {"
    ", containersep, intersperse(buffer, containersep), containersep, "
    "} - end - - Html.template = [[ - - - -$title - - -$body - - -]] - - return Html -end - -return M diff --git a/lua-libraries/lunamark/writer/html5.lua b/lua-libraries/lunamark/writer/html5.lua deleted file mode 100644 index cfb1db937d..0000000000 --- a/lua-libraries/lunamark/writer/html5.lua +++ /dev/null @@ -1,37 +0,0 @@ --- (c) 2009-2011 John MacFarlane. Released under MIT license. --- See the file LICENSE in the source for details. - ---- HTML 5 writer for lunamark. --- Extends [lunamark.writer.html], but uses `
    ` tags for sections --- if `options.containers` is true. - -local M = {} - -local html = require("lunamark.writer.html") - ---- Returns a new HTML 5 writer. --- `options` is as in `lunamark.writer.html`. --- For a list of fields, see [lunamark.writer.generic]. -function M.new(options) - options = options or {} - local Html5 = html.new(options) - - Html5.container = "section" - - Html5.template = [[ - - - - -$title - - -$body - - -]] - - return Html5 -end - -return M diff --git a/lua-libraries/lunamark/writer/latex.lua b/lua-libraries/lunamark/writer/latex.lua deleted file mode 100644 index 3b9f7e3c3a..0000000000 --- a/lua-libraries/lunamark/writer/latex.lua +++ /dev/null @@ -1,260 +0,0 @@ --- (c) 2009-2011 John MacFarlane. Released under MIT license. --- See the file LICENSE in the source for details. - ---- LaTeX writer for lunamark. --- Extends the [lunamark.writer.tex]. - -local M = {} - -local tex = require("lunamark.writer.tex") -local util = require("lunamark.util") -local format = string.format - ---- Returns a new LaTeX writer. --- --- * `options` is a table with parsing options. --- The following fields are significant: --- --- `citations` --- : Enable citations as in pandoc. Either a boolean or one of --- the following strings should be specified: --- --- - `latex` -- produce basic LaTeX2e citations, --- - `natbib` -- produce citations for the Natbib package, or --- - `biblatex` -- produce citations for the BibLaTeX package. --- --- For a list of fields in the writer, see [lunamark.writer.generic]. -function M.new(options) - options = options or {} - local LaTeX = tex.new(options) - - function LaTeX.code(s) - return {"\\texttt{",LaTeX.string(s),"}"} - end - - function LaTeX.link(lab,src,tit) - return {"\\href{",LaTeX.string(src),"}{",lab,"}"} - end - - function LaTeX.image(lab,src,tit) - return {"\\includegraphics{",LaTeX.string(src),"}"} - end - - local function listitem(s) - return {"\\item ",s} - end - - function LaTeX.bulletlist(items) - local buffer = {} - for _,item in ipairs(items) do - buffer[#buffer + 1] = listitem(item) - end - local contents = util.intersperse(buffer,"\n") - return {"\\begin{itemize}\n",contents,"\n\\end{itemize}"} - end - - function LaTeX.orderedlist(items) - local buffer = {} - for _,item in ipairs(items) do - buffer[#buffer + 1] = listitem(item) - end - local contents = util.intersperse(buffer,"\n") - return {"\\begin{enumerate}\n",contents,"\n\\end{enumerate}"} - end - - function LaTeX.emphasis(s) - return {"\\emph{",s,"}"} - end - - function LaTeX.strong(s) - return {"\\textbf{",s,"}"} - end - - function LaTeX.blockquote(s) - return {"\\begin{quote}\n",s,"\n\\end{quote}"} - end - - function LaTeX.verbatim(s) - return {"\\begin{verbatim}\n",s,"\\end{verbatim}"} - end - - LaTeX.fenced_code = LaTeX.verbatim - - function LaTeX.header(s,level) - local cmd - if level == 1 then - cmd = "\\section" - elseif level == 2 then - cmd = "\\subsection" - elseif level == 3 then - cmd = "\\subsubsection" - elseif level == 4 then - cmd = "\\paragraph" - elseif level == 5 then - cmd = "\\subparagraph" - else - cmd = "" - end - return {cmd,"{",s,"}"} - end - - LaTeX.hrule = "\\hspace{\\fill}\\rule{.6\\linewidth}{0.4pt}\\hspace{\\fill}" - - function LaTeX.note(contents) - return {"\\footnote{",contents,"}"} - end - - local function citation_optargs(cite) - if cite.prenote and cite.postnote then - return {"[", cite.prenote, "][", cite.postnote, "]"} - elseif cite.prenote and not cite.postnote then - return {"[", cite.prenote, "][]"} - elseif not cite.prenote and cite.postnote then - return {"[", cite.postnote, "]"} - else - return "" - end - end - - if options.citations == true or options.citations == "latex" then - --- Basic LaTeX2e citations - function LaTeX.citations(_, cites) - local buffer = {} - local opened_braces = false - for i, cite in ipairs(cites) do - if cite.prenote or cite.postnote then -- A separate complex citation - buffer[#buffer + 1] = {opened_braces and "}" or "", - cite.prenote and {i == 1 and "" or " ", cite.prenote, "~"} or - "", {(i == 1 or cite.prenote) and "" or " ", "\\cite"}, - cite.postnote and {"[", cite.postnote, "]"} or "", "{", cite.name, - cite_postnote and {"~", cite_postnote} or "", "}"} - opened_braces = false - else -- A string of simple citations - buffer[#buffer + 1] = {opened_braces and ", " or {i == 1 and "" or - " ", "\\cite{"}, cite.name} - opened_braces = true - end - end - if opened_braces then - buffer[#buffer + 1] = "}" - end - return buffer - end - elseif options.citations == "natbib" then - --- NatBib citations - function LaTeX.citations(text_cites, cites) - if #cites == 1 then -- A single citation - local cite = cites[1] - if text_cites then - return {"\\citet", citation_optargs(cite), "{", cite.name, "}"} - else - return {cite.suppress_author and "\\citeyearpar" or "\\citep", - citation_optargs(cite), "{", cite.name, "}"} - end - else -- A string of citations - local complex = false - local last_suppressed = nil - for _, cite in ipairs(cites) do - if cite.prenote or cite.postnote or - cite.suppress_author == not last_suppressed then - complex = true - break - end - last_suppressed = cite.suppress_author - end - if complex then -- A string of complex citations - local buffer = {"\\citetext{"} - for i, cite in ipairs(cites) do - buffer[#buffer + 1] = {i ~= 1 and "; " or "", cite.suppress_author - and "\\citeyear" or (text_cites and "\\citealt" or "\\citealp"), - citation_optargs(cite), "{", cite.name, "}"} - end - buffer[#buffer + 1] = "}" - return buffer - else -- A string of simple citations - local buffer = {} - for i, cite in ipairs(cites) do - buffer[#buffer + 1] = {i == 1 and (text_cites and "\\citet{" or - "\\citep{") or ", ", cite.name} - end - buffer[#buffer + 1] = "}" - return buffer - end - end - end - elseif options.citations == "biblatex" then - --- BibLaTeX citations - function LaTeX.citations(text_cites, cites) - if #cites == 1 then -- A single citation - local cite = cites[1] - if text_cites then - return {"\\textcite", citation_optargs(cite), "{", cite.name, "}"} - else - return {"\\autocite", cite.suppress_author and "*" or "", - citation_optargs(cite), "{", cite.name, "}"} - end - else -- A string of citations - local buffer = {text_cites and "\\textcites" or "\\autocites"} - for _, cite in ipairs(cites) do - buffer[#buffer + 1] = {citation_optargs(cite), "{", cite.name, "}"} - end - return buffer - end - end - end - - function LaTeX.definitionlist(items) - local buffer = {} - for _,item in ipairs(items) do - buffer[#buffer + 1] = format("\\item[%s]\n%s", - item.term, util.intersperse(item.definitions, LaTeX.interblocksep)) - end - local contents = util.intersperse(buffer, LaTeX.containersep) - return {"\\begin{description}\n",contents,"\n\\end{description}"} - end - - LaTeX.template = [===[ -\documentclass{article} -\usepackage{amssymb,amsmath} -\usepackage{ifxetex,ifluatex} -\ifxetex - \usepackage{fontspec,xltxtra,xunicode} - \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase} -\else - \ifluatex - \usepackage{fontspec} - \defaultfontfeatures{Mapping=tex-text,Scale=MatchLowercase} - \else - \usepackage[utf8]{inputenc} - \fi -\fi -\ifxetex - \usepackage[setpagesize=false, % page size defined by xetex - unicode=false, % unicode breaks when used with xetex - xetex]{hyperref} -\else - \usepackage[unicode=true]{hyperref} -\fi -\hypersetup{breaklinks=true, pdfborder={0 0 0}} -\setlength{\parindent}{0pt} -\setlength{\parskip}{6pt plus 2pt minus 1pt} -\setlength{\emergencystretch}{3em} % prevent overfull lines -\setcounter{secnumdepth}{0} - -\title{$title} -\author{$sepby{author}[=[$it]=][=[ \and ]=]} -\date{$date} - -\begin{document} - -$if{ title }[[\maketitle -]] -$body - -\end{document} -]===] - - return LaTeX -end - -return M diff --git a/lua-libraries/lunamark/writer/man.lua b/lua-libraries/lunamark/writer/man.lua deleted file mode 100644 index db4992a64f..0000000000 --- a/lua-libraries/lunamark/writer/man.lua +++ /dev/null @@ -1,130 +0,0 @@ --- (c) 2009-2011 John MacFarlane. Released under MIT license. --- See the file LICENSE in the source for details. - ---- Groff man writer for lunamark. --- Extends [lunamark.writer.groff]. --- --- Note: continuation paragraphs in lists are not --- handled properly. - -local M = {} - -local groff = require("lunamark.writer.groff") -local util = require("lunamark.util") -local format = string.format - ---- Returns a new groff writer. --- For a list of fields, see [lunamark.writer.generic]. -function M.new(options) - options = options or {} - local Man = groff.new(options) - - local endnotes = {} - - function Man.link(lab,src,tit) - return {lab," (",src,")"} - end - - function Man.image(lab,src,tit) - return {"[IMAGE (",lab,")]"} - end - - -- TODO handle continuations properly. - -- pandoc does this: - -- .IP \[bu] 2 - -- one - -- .RS 2 - -- .PP - -- cont - -- .RE - - function Man.paragraph(contents) - return {".PP\n",contents} - end - - function Man.bulletlist(items,tight) - local buffer = {} - for _,item in ipairs(items) do - local revitem = item - -- we don't want to have .IP then .PP - if revitem[1][1] == ".PP\n" then revitem[1][1] = "" end - buffer[#buffer + 1] = {".IP \\[bu] 2\n",item} - end - return util.intersperse(buffer, Man.containersep) - end - - function Man.orderedlist(items,tight,startnum) - local buffer = {} - local num = startnum or 1 - for _,item in ipairs(items) do - local revitem = item - -- we don't want to have .IP then .PP - if revitem[1][1] == ".PP\n" then revitem[1][1] = "" end - buffer[#buffer + 1] = {format(".IP \"%d.\" 4\n",num),item} - num = num + 1 - end - return util.intersperse(buffer, Man.containersep) - end - - function Man.blockquote(s) - return {".RS\n",s,"\n.RE"} - end - - function Man.verbatim(s) - return {".IP\n.nf\n\\f[C]\n",s,".fi"} - end - - Man.fenced_code = Man.verbatim - - function Man.header(s,level) - local hcode = ".SS" - if level == 1 then hcode = ".SH" end - return {hcode," ",s} - end - - Man.hrule = ".PP\n * * * * *" - - function Man.note(contents) - local num = #endnotes + 1 - endnotes[num] = {format(".SS [%d]\n",num),contents} - return format('[%d]', num) - end - - function Man.definitionlist(items,tight) - local buffer = {} - local ds - for _,item in ipairs(items) do - if tight then - ds = util.intersperse(item.definitions,"\n.RS\n.RE\n") - buffer[#buffer + 1] = {".TP\n.B ",item.term,"\n",ds,"\n.RS\n.RE"} - else - ds = util.intersperse(item.definitions,"\n.RS\n.RE\n") - buffer[#buffer + 1] = {".TP\n.B ",item.term,"\n.RS\n",ds,"\n.RE"} - end - end - local contents = util.intersperse(buffer,"\n") - return contents - end - - function Man.start_document() - endnotes = {} - return "" - end - - function Man.stop_document() - if #endnotes == 0 then - return "" - else - return {"\n.SH NOTES\n", util.intersperse(endnotes, "\n")} - end - end - - Man.template = [===[ -.TH "$title" "$section" "$date" "$left_footer" "$center_header" -$body -]===] - - return Man -end - -return M diff --git a/lua-libraries/lunamark/writer/tex.lua b/lua-libraries/lunamark/writer/tex.lua deleted file mode 100644 index 09cb24e086..0000000000 --- a/lua-libraries/lunamark/writer/tex.lua +++ /dev/null @@ -1,89 +0,0 @@ --- (c) 2009-2011 John MacFarlane. Released under MIT license. --- See the file LICENSE in the source for details. - ---- Generic TeX writer for lunamark. --- It extends [lunamark.writer.generic] and is extended by --- [lunamark.writer.latex] and [lunamark.writer.context]. - -local M = {} - -local util = require("lunamark.util") -local generic = require("lunamark.writer.generic") -local format = string.format - ---- Returns a new TeX writer. --- For a list ofy fields, see [lunamark.writer.generic]. -function M.new(options) - options = options or {} - local TeX = generic.new(options) - - TeX.interblocksep = "\n\n" -- insensitive to layout - - TeX.containersep = "\n" - - TeX.linebreak = "\\\\" - - TeX.ellipsis = "\\ldots{}" - - TeX.mdash = "---" - - TeX.ndash = "--" - - TeX.nbsp = "~" - - function TeX.singlequoted(s) - return format("`%s'",s) - end - - function TeX.doublequoted(s) - return format("``%s''",s) - end - - TeX.escaped = { - ["{"] = "\\{", - ["}"] = "\\}", - ["$"] = "\\$", - ["%"] = "\\%", - ["&"] = "\\&", - ["_"] = "\\_", - ["#"] = "\\#", - ["^"] = "\\^{}", - ["\\"] = "\\char92{}", - ["~"] = "\\char126{}", - ["|"] = "\\char124{}", - ["<"] = "\\char60{}", - [">"] = "\\char62{}", - ["["] = "{[}", -- to avoid interpretation as optional argument - ["]"] = "{]}", - } - - local str_escaped = { - ["\226\128\156"] = "``", - ["\226\128\157"] = "''", - ["\226\128\152"] = "`", - ["\226\128\153"] = "'", - ["\226\128\148"] = "---", - ["\226\128\147"] = "--", - ["\194\160"] = "~", - } - - local escaper = util.escaper(TeX.escaped, str_escaped) - - TeX.string = escaper - - function TeX.inline_html(s) - return "" - end - - function TeX.display_html(s) - return "" - end - - function TeX.paragraph(s) - return s - end - - return TeX -end - -return M diff --git a/lua-libraries/lunamark/writer/xml.lua b/lua-libraries/lunamark/writer/xml.lua deleted file mode 100644 index 8d02ec1fa0..0000000000 --- a/lua-libraries/lunamark/writer/xml.lua +++ /dev/null @@ -1,60 +0,0 @@ --- (c) 2009-2011 John MacFarlane. Released under MIT license. --- See the file LICENSE in the source for details. - ---- Generic XML writer for lunamark. --- It extends [lunamark.writer.generic] and is extended by --- [lunamark.writer.html] and [lunamark.writer.docbook]. - -local M = {} - -local generic = require("lunamark.writer.generic") -local util = require("lunamark.util") - ---- Returns a new XML writer. --- For a list of fields, see [lunamark.writer.generic]. -function M.new(options) - options = options or {} - local Xml = generic.new(options) - - Xml.container = "section" - -- {1,2} means: a second level header inside a first-level - local header_level_stack = {} - - function Xml.start_section(level) - header_level_stack[#header_level_stack + 1] = level - return "<" .. Xml.container .. ">" - end - - function Xml.stop_section(level) - local len = #header_level_stack - if len == 0 then - return "" - else - local last = header_level_stack[len] - local res = {} - while last >= level do - header_level_stack[len] = nil - table.insert(res, "") - len = len - 1 - last = (len > 0 and header_level_stack[len]) or 0 - end - return table.concat(res, Xml.containersep) - end - end - - Xml.linebreak = "" - - local escape = util.escaper { - ["<" ] = "<", - [">" ] = ">", - ["&" ] = "&", - ["\"" ] = """, - ["'" ] = "'" - } - - Xml.string = escape - - return Xml -end - -return M diff --git a/outputters/base.lua b/outputters/base.lua index a5ce0aacdb..51cc36e59a 100644 --- a/outputters/base.lua +++ b/outputters/base.lua @@ -1,12 +1,33 @@ +--- SILE outputter class. +-- @interfaces outputters + local outputter = pl.class() outputter.type = "outputter" outputter._name = "base" -function outputter._init () end +function outputter:_init () + self.hooks = {} + return self +end + +function outputter:registerHook (category, func) + if not self.hooks[category] then self.hooks[category] = {} end + table.insert(self.hooks[category], func) +end + +function outputter:runHooks (category, data) + if not self.hooks[category] then return nil end + for _, func in ipairs(self.hooks[category]) do + data = func(self, data) + end + return data +end function outputter.newPage () end -function outputter.finish () end +function outputter:finish () + self:runHooks("prefinish") +end function outputter.getCursor () end @@ -34,6 +55,18 @@ function outputter.debugFrame (_, _, _) end function outputter.debugHbox (_, _, _) end +function outputter.setLinkAnchor (_, _, _) end -- Unstable API + +function outputter.beginLink (_, _, _) end -- Unstable API + +function outputter.endLink (_, _, _, _, _, _, _) end -- Unstable API + +function outputter.setMetadata (_, _, _) end + +function outputter.setBookmark (_, _, _) end + +function outputter.drawRaw (_) end + function outputter:getOutputFilename () local fname if SILE.outputFilename then diff --git a/outputters/cairo.lua b/outputters/cairo.lua index a394b9b98f..f5d8a5efc8 100644 --- a/outputters/cairo.lua +++ b/outputters/cairo.lua @@ -22,6 +22,7 @@ outputter._name = "cairo" outputter.extension = "pdf" function outputter:_init () + base._init(self) local fname = self:getOutputFilename() local surface = cairolgi.PdfSurface.create(fname == "-" and "/dev/stdout" or fname, SILE.documentState.paperSize[1], SILE.documentState.paperSize[2]) cr = cairolgi.Context.create(surface) @@ -119,4 +120,9 @@ function outputter:debugHbox (hbox, scaledWidth) cr:move_to(x, y) end +-- untested +function outputter.drawRaw (_, literal) + cr:show_text(literal) +end + return outputter diff --git a/outputters/debug.lua b/outputters/debug.lua index 472de5ecca..dc9df6b593 100644 --- a/outputters/debug.lua +++ b/outputters/debug.lua @@ -8,19 +8,7 @@ local started = false local lastFont local outfile -local function _round (input) - -- LuaJIT 2.1 betas (and inheritors such as OpenResty and Moonjit) are biased - -- towards rounding 0.5 up to 1, all other Lua interpreters are biased - -- towards rounding such floating point numbers down. This hack shaves off - -- just enough to fix the bias so our test suite works across interpreters. - -- Note that even a true rounding function here will fail because the bias is - -- inherent to the floating point type. Also note we are erroring in favor of - -- the *less* common option because the LuaJIT VMS are hopelessly broken - -- whereas normal LUA VMs can be cooerced. - if input > 0 then input = input + .00000000000001 end - if input < 0 then input = input - .00000000000001 end - return string.format("%.4f", input) -end +local _round = SU.debug_round local outputter = pl.class(base) outputter._name = "debug" @@ -59,6 +47,7 @@ end function outputter:finish () if SILE.status.unsupported then self:_writeline("UNSUPPORTED") end self:_writeline("End page") + self:runHooks("prefinish") self:_writeline("Finish") outfile:close() end @@ -80,21 +69,21 @@ end function outputter:setColor (color) if color.r then - self:_writeline("Set color", _round(color.r), _round(color.g), _round(color.b)) + self:_writeline("Set color", tostring(color)) elseif color.c then - self:_writeline("Set color", _round(color.c), _round(color.m), _round(color.y), _round(color.k)) + self:_writeline("Set color", tostring(color)) elseif color.l then - self:_writeline("Set color", _round(color.l)) + self:_writeline("Set color", tostring(color)) end end function outputter:pushColor (color) if color.r then - self:_writeline("Push color", _round(color.r), _round(color.g), _round(color.b)) + self:_writeline("Push color", tostring(color)) elseif color.c then - self:_writeline("Push color (CMYK)", _round(color.c), _round(color.m), _round(color.y), _round(color.k)) + self:_writeline("Push color (CMYK)", tostring(color)) elseif color.l then - self:_writeline("Push color (grayscale)", _round(color.l)) + self:_writeline("Push color (grayscale)", tostring(color)) end end @@ -162,4 +151,28 @@ function outputter:drawRule (x, y, width, depth) self:_writeline("Draw line", _round(x), _round(y), _round(width), _round(depth)) end +function outputter:setLinkAnchor (name, x, y) + self:_writeline("Setting link anchor", name, x, y) +end + +function outputter:beginLink (dest, opts) + self:_writeline("Begining a link", dest, opts) +end + +function outputter:endLink(dest, opts, x0, y0, x1, y1) + self:_writeline("Ending a link", dest, opts, x0, y0, x1, y1) +end + +function outputter:setBookmark (dest, title, level) + self:_writeline("Setting bookmark", dest, title, level) +end + +function outputter:setMetadata (key, value) + self:_writeline("Set metadata", key, value) +end + +function outputter:drawRaw (literal) + self:_writeline("Draw raw", literal) +end + return outputter diff --git a/outputters/libtexpdf.lua b/outputters/libtexpdf.lua index 9e6ab8e036..a752cacb22 100644 --- a/outputters/libtexpdf.lua +++ b/outputters/libtexpdf.lua @@ -22,14 +22,36 @@ local outputter = pl.class(base) outputter._name = "libtexpdf" outputter.extension = "pdf" +-- N.B. Sometimes setCoord is called before the outputter has ensured initialization. +-- This ok for coordinates manipulation, at these points we know the page size. +local deltaX +local deltaY +local function trueXCoord (x) + if not deltaX then + local sheetSize = SILE.documentState.sheetSize or SILE.documentState.paperSize + deltaX = (sheetSize[1] - SILE.documentState.paperSize[1]) / 2 + end + return x + deltaX +end +local function trueYCoord (y) + if not deltaY then + local sheetSize = SILE.documentState.sheetSize or SILE.documentState.paperSize + deltaY = (sheetSize[2] - SILE.documentState.paperSize[2]) / 2 + end + return y + deltaY +end + -- The outputter init can't actually initialize output (as logical as it might -- have seemed) because that requires a page size which we don't know yet. -- function outputter:_init () end function outputter:_ensureInit () if not started then - local w, h = SILE.documentState.paperSize[1], SILE.documentState.paperSize[2] + local sheetSize = SILE.documentState.sheetSize or SILE.documentState.paperSize + local w, h = sheetSize[1], sheetSize[2] local fname = self:getOutputFilename() + -- Ideally we could want to set the PDF CropBox, BleedBox, TrimBox... + -- Our wrapper only manages the MediaBox at this point. pdf.init(fname == "-" and "/dev/stdout" or fname, w, h, SILE.full_version) pdf.beginpage() started = true @@ -49,7 +71,7 @@ end function outputter:finish () self:_ensureInit() pdf.endpage() - self:_endHook() + self:runHooks("prefinish") pdf.finish() started = false lastkey = nil @@ -90,7 +112,7 @@ function outputter:_drawString (str, width, x_offset, y_offset) local x, y = self:getCursor() pdf.colorpush_rgb(0,0,0) pdf.colorpop() - pdf.setstring(x+x_offset, y+y_offset, str, string.len(str), _font, width) + pdf.setstring(trueXCoord(x+x_offset), trueYCoord(y+y_offset), str, string.len(str), _font, width) end function outputter:drawHbox (value, width) @@ -157,7 +179,7 @@ function outputter:drawImage (src, x, y, width, height, pageno) width = SU.cast("number", width) height = SU.cast("number", height) self:_ensureInit() - pdf.drawimage(src, x, y, width, height, pageno or 1) + pdf.drawimage(src, trueXCoord(x), trueYCoord(y), width, height, pageno or 1) end function outputter:getImageSize (src, pageno) @@ -174,8 +196,9 @@ function outputter:drawSVG (figure, x, y, _, height, scalefactor) pdf.add_content("q") self:setCursor(x, y) x, y = self:getCursor() - local newy = y - SILE.documentState.paperSize[2] + height - pdf.add_content(table.concat({ scalefactor, 0, 0, -scalefactor, x, newy, "cm" }, " ")) + local sheetSize = SILE.documentState.sheetSize or SILE.documentState.paperSize + local newy = y - SILE.documentState.paperSize[2] / 2 + height - sheetSize[2] / 2 + pdf.add_content(table.concat({ scalefactor, 0, 0, -scalefactor, trueXCoord(x), newy, "cm" }, " ")) pdf.add_content(figure) pdf.add_content("Q") end @@ -187,12 +210,12 @@ function outputter:drawRule (x, y, width, height) height = SU.cast("number", height) self:_ensureInit() local paperY = SILE.documentState.paperSize[2] - pdf.setrule(x, paperY - y - height, width, height) + pdf.setrule(trueXCoord(x), trueYCoord(paperY - y - height), width, height) end function outputter:debugFrame (frame) self:_ensureInit() - self:pushColor({ r = 0.8, g = 0, b = 0 }) + self:pushColor(SILE.types.color({ r = 0.8, g = 0, b = 0 })) self:drawRule(frame:left()-_dl/2, frame:top()-_dl/2, frame:width()+_dl, _dl) self:drawRule(frame:left()-_dl/2, frame:top()-_dl/2, _dl, frame:height()+_dl) self:drawRule(frame:right()-_dl/2, frame:top()-_dl/2, _dl, frame:height()+_dl) @@ -214,7 +237,7 @@ end function outputter:debugHbox (hbox, scaledWidth) self:_ensureInit() - self:pushColor({ r = 0.8, g = 0.3, b = 0.3 }) + self:pushColor(SILE.types.color({ r = 0.8, g = 0.3, b = 0.3 })) local paperY = SILE.documentState.paperSize[2] local x, y = self:getCursor() y = paperY - y @@ -222,10 +245,151 @@ function outputter:debugHbox (hbox, scaledWidth) self:drawRule(x-_dl/2, y-hbox.height-_dl/2, _dl, hbox.height+hbox.depth+_dl) self:drawRule(x-_dl/2, y-_dl/2, scaledWidth+_dl, _dl) self:drawRule(x+scaledWidth-_dl/2, y-hbox.height-_dl/2, _dl, hbox.height+hbox.depth+_dl) - if hbox.depth > SILE.length(0) then + if hbox.depth > SILE.types.length(0) then self:drawRule(x-_dl/2, y+hbox.depth-_dl/2, scaledWidth+_dl, _dl) end self:popColor() end +-- The methods below are only implemented on outputters supporting these features. +-- In PDF, it relies on transformation matrices, but other backends may call +-- for a different strategy. +-- ! The API is unstable and subject to change. ! + +function outputter:scaleFn (xorigin, yorigin, xratio, yratio, callback) + xorigin = SU.cast("number", xorigin) + yorigin = SU.cast("number", yorigin) + local x0 = trueXCoord(xorigin) + local y0 = -trueYCoord(yorigin) + self:_ensureInit() + pdf:gsave() + pdf.setmatrix(1, 0, 0, 1, x0, y0) + pdf.setmatrix(xratio, 0, 0, yratio, 0, 0) + pdf.setmatrix(1, 0, 0, 1, -x0, -y0) + callback() + pdf:grestore() +end + +function outputter:rotateFn (xorigin, yorigin, theta, callback) + xorigin = SU.cast("number", xorigin) + yorigin = SU.cast("number", yorigin) + local x0 = trueXCoord(xorigin) + local y0 = -trueYCoord(yorigin) + self:_ensureInit() + pdf:gsave() + pdf.setmatrix(1, 0, 0, 1, x0, y0) + pdf.setmatrix(math.cos(theta), math.sin(theta), -math.sin(theta), math.cos(theta), 0, 0) + pdf.setmatrix(1, 0, 0, 1, -x0, -y0) + callback() + pdf:grestore() +end + +-- Other rotation unstable APIs + +function outputter:enterFrameRotate (xa, xb, y, theta) -- Unstable API see rotate package + xa = SU.cast("number", xa) + xb = SU.cast("number", xb) + y = SU.cast("number", y) + -- Keep center point the same? + local cx0 = trueXCoord(xa) + local cx1 = trueXCoord(xb) + local cy = -trueYCoord(y) + self:_ensureInit() + pdf:gsave() + pdf.setmatrix(1, 0, 0, 1, cx1, cy) + pdf.setmatrix(math.cos(theta), math.sin(theta), -math.sin(theta), math.cos(theta), 0, 0) + pdf.setmatrix(1, 0, 0, 1, -cx0, -cy) +end + +function outputter.leaveFrameRotate (_) + pdf:grestore() +end + +-- Unstable link APIs + +function outputter:setLinkAnchor (name, x, y) + x = SU.cast("number", x) + y = SU.cast("number", y) + self:_ensureInit() + pdf.destination(name, trueXCoord(x), trueYCoord(y)) +end + +local function borderColor (color) + if color then + if color.r then return "/C [" .. color.r .. " " .. color.g .. " " .. color.b .. "]" end + if color.c then return "/C [" .. color.c .. " " .. color.m .. " " .. color.y .. " " .. color.k .. "]" end + if color.l then return "/C [" .. color.l .. "]" end + end + return "" +end +local function borderStyle (style, width) + if style == "underline" then return "/BS<>" end + if style == "dashed" then return "/BS<>" end + return "/Border[0 0 " .. width .. "]" +end + +function outputter:startLink (_, _) -- destination, options as argument + self:_ensureInit() + -- HACK: + -- Looking at the code, pdf.begin_annotation does nothing, and Simon wrote a comment + -- about tracking boxes. Unsure what he implied with this obscure statement. + -- Sure thing is that some backends may need the destination here, e.g. an HTML backend + -- would generate a , as well as the options possibly for styling + -- on the link opening? + pdf.begin_annotation() +end + +function outputter.endLink (_, dest, opts, x0, y0, x1, y1) + local bordercolor = borderColor(opts.bordercolor) + local borderwidth = SU.cast("integer", opts.borderwidth) + local borderstyle = borderStyle(opts.borderstyle, borderwidth) + local target = opts.external and "/Type/Action/S/URI/URI" or "/S/GoTo/D" + local d = "<>>>" + pdf.end_annotation(d, + trueXCoord(x0) , trueYCoord(y0 - opts.borderoffset), + trueXCoord(x1), trueYCoord(y1 + opts.borderoffset)) +end + +-- Bookmarks and metadata + +local function validate_date (date) + return string.match(date, [[^D:%d+%s*-%s*%d%d%s*'%s*%d%d%s*'?$]]) ~= nil +end + +function outputter:setMetadata (key, value) + if key == "Trapped" then + SU.warn("Skipping special metadata key \\Trapped") + return + end + + if key == "ModDate" or key == "CreationDate" then + if not validate_date(value) then + SU.warn("Invalid date: " .. value) + return + end + else + -- see comment in on bookmark + value = SU.utf8_to_utf16be(value) + end + self:_ensureInit() + pdf.metadata(key, value) +end + +function outputter:setBookmark (dest, title, level) + -- For annotations and bookmarks, text strings must be encoded using + -- either PDFDocEncoding or UTF16-BE with a leading byte-order marker. + -- As PDFDocEncoding supports only limited character repertoire for + -- European languages, we use UTF-16BE for internationalization. + local ustr = SU.utf8_to_utf16be_hexencoded(title) + local d = "</A<>>>" + self:_ensureInit() + pdf.bookmark(d, level) +end + +-- Assumes the caller known what they want to stuff in raw PDF format +function outputter:drawRaw (literal) + self:_ensureInit() + pdf.add_content(literal) +end + return outputter diff --git a/outputters/podofo.lua b/outputters/podofo.lua index 61a47f4517..70d12114d3 100644 --- a/outputters/podofo.lua +++ b/outputters/podofo.lua @@ -21,7 +21,8 @@ local outputter = pl.class(base) outputter._name = "podofo" outputter.extension = "pdf" -function outputter._init (_) +function outputter:_init (_) + base._init(self) document = pdf.PdfMemDocument() pagesize = pdf.PdfRect() pagesize:SetWidth(SILE.documentState.paperSize[1]) @@ -39,6 +40,7 @@ end function outputter:finish () painter:FinishPage() + self:runHooks("prefinish") local fname = self:getOutputFilename() document:Write(fname == "-" and "/dev/stdout" or fname) end @@ -104,4 +106,10 @@ function outputter:debugHbox (hbox, scaledWidth) --cr:move_to(x, y) end +-- untested +function outputter:drawRaw (literal) + local x, y = self:getCursor() + painter:DrawText(x, y, literal, string.len(literal)) +end + return outputter diff --git a/outputters/text.lua b/outputters/text.lua index 38fff3ed04..863e4e9e41 100644 --- a/outputters/text.lua +++ b/outputters/text.lua @@ -36,6 +36,7 @@ end function outputter:finish () self:_ensureInit() + self:runHooks("prefinish") outfile:close() end @@ -45,8 +46,8 @@ end function outputter:setCursor (x, y, relative) self:_ensureInit() - local bs = SILE.measurement("0.8bs"):tonumber() - local spc = SILE.measurement("0.8spc"):tonumber() + local bs = SILE.types.measurement("0.8bs"):tonumber() + local spc = SILE.types.measurement("0.8spc"):tonumber() local offset = relative and { x = cursorX, y = cursorY } or { x = 0, y = 0 } local newx, newy = offset.x + x, offset.y - y if started then @@ -81,4 +82,9 @@ function outputter:drawHbox (value, width) end end +function outputter:drawRaw (literal) + self:_ensureInit() + outfile:write(literal) +end + return outputter diff --git a/package.json b/package.json index 679897538e..bb5b0a9768 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,8 @@ "@commitlint/config-conventional": "^19.1", "@commitlint/prompt": "^19.0", "commitizen": "^4.3", - "conventional-changelog-cli": "^4.0", + "conventional-changelog-cli": "^4.1", + "husky": "^8.0", "husky": "^9.0", "standard-version": "^9.5", "yaml": "^2.3" @@ -42,6 +43,10 @@ { "filename": "package.json", "type": "json" + }, + { + "filename": "Cargo.toml", + "updater": "build-aux/cargo-updater.js" } ], "infile": "CHANGELOG.md", diff --git a/packages/autodoc/init.lua b/packages/autodoc/init.lua index 167bba5f21..eea8f678d6 100644 --- a/packages/autodoc/init.lua +++ b/packages/autodoc/init.lua @@ -89,10 +89,10 @@ local function typesetAST (options, content) else seenCommandWithoutArg = true end - elseif ast.id == "texlike_stuff" or (not ast.command and not ast.id) then + elseif ast.id == "content" or (not ast.command and not ast.id) then -- Due to the way it is implemented, the SILE-inputter may generate such -- nodes in the AST. It's poorly documented, so it's not clear why they - -- are even kept there (esp. the "texlike_stuff" nodes), but anyhow, as + -- are even kept there (esp. the "content" nodes), but anyhow, as -- far as autodoc is concerned for presentation purposes, just -- recurse into them. typesetAST(options, ast) @@ -126,7 +126,7 @@ end function package:registerRawHandlers () self:registerRawHandler("autodoc:codeblock", function(options, content) - SILE.call("autodoc:codeblock", options, { content[1] }) -- Still issues with SU.contentToString() witb raw content + SILE.call("autodoc:codeblock", options, { content[1] }) -- Still issues with SU.ast.contentToString() witb raw content end) end @@ -323,14 +323,14 @@ function package:registerCommands () -- (and try to better enforce novbreak points of insertion) SILE.call("verbatim:font") -- Rather than absolutizing 4 different values, just do it once and cache it - local ex = SILE.measurement("1ex"):absolute() + local ex = SILE.types.measurement("1ex"):absolute() SILE.typesetter:leaveHmode() SILE.settings:set("typesetter.parseppattern", "\n") SILE.settings:set("typesetter.obeyspaces", true) - SILE.settings:set("document.parindent", SILE.nodefactory.glue()) - SILE.settings:set("document.parskip", SILE.nodefactory.vglue(0.3*ex)) - SILE.settings:set("document.baselineskip", SILE.nodefactory.glue(2.3*ex)) - SILE.settings:set("document.spaceskip", SILE.length("1spc")) + SILE.settings:set("document.parindent", SILE.types.node.glue()) + SILE.settings:set("document.parskip", SILE.types.node.vglue(0.3*ex)) + SILE.settings:set("document.baselineskip", SILE.types.node.glue(2.3*ex)) + SILE.settings:set("document.spaceskip", SILE.types.length("1spc")) SILE.settings:set("shaper.variablespaces", false) SILE.settings:set("document.language", "und") SILE.typesetter:leaveHmode() @@ -365,12 +365,12 @@ function package:registerCommands () self:registerCommand("autodoc:note", function(_, content) -- Replacing the \note command from the original SILE manual... - local linedimen = SILE.length("0.75em") - local linethickness = SILE.length("0.3pt") - local ls = SILE.settings:get("document.lskip") or SILE.nodefactory.glue() + local linedimen = SILE.types.length("0.75em") + local linethickness = SILE.types.length("0.3pt") + local ls = SILE.settings:get("document.lskip") or SILE.types.node.glue() local p = SILE.settings:get("document.parindent") local leftindent = (p.width:absolute() + ls.width:absolute()).length -- fixed part - local innerindent = SILE.measurement("1em"):absolute() + local innerindent = SILE.types.measurement("1em"):absolute() SILE.settings:temporarily(function () SILE.settings:set("document.lskip", leftindent) SILE.settings:set("document.rskip", leftindent) @@ -386,8 +386,8 @@ function package:registerCommands () SILE.call("noindent") SILE.call("novbreak") SILE.settings:temporarily(function () - SILE.settings:set("document.lskip", SILE.nodefactory.glue(leftindent + innerindent)) - SILE.settings:set("document.rskip", SILE.nodefactory.glue(leftindent + innerindent)) + SILE.settings:set("document.lskip", SILE.types.node.glue(leftindent + innerindent)) + SILE.settings:set("document.rskip", SILE.types.node.glue(leftindent + innerindent)) SILE.call("font", { size = "0.95em", style = "italic "}, content) SILE.call("novbreak") end) diff --git a/packages/background/init.lua b/packages/background/init.lua index 02e2fbd6ae..d601b53763 100644 --- a/packages/background/init.lua +++ b/packages/background/init.lua @@ -3,48 +3,77 @@ local base = require("packages.base") local package = pl.class(base) package._name = "background" -local outputBackground = function (color) - local page = SILE.getFrame("page") - local backgroundColor = SILE.color(color) - SILE.outputter:pushColor(backgroundColor) - SILE.outputter:drawRule(page:left(), page:top(), page:right(), page:bottom()) - SILE.outputter:popColor() +local background = {} + +local outputBackground = function () + local pagea = SILE.getFrame("page") + local offset = SILE.documentState.bleed / 2 + if type(background.bg) == "string" then + SILE.outputter:drawImage(background.bg, + pagea:left() - offset, pagea:top() - offset, + pagea:width() + 2 * offset, pagea:height() + 2 * offset) + elseif background.bg then + SILE.outputter:pushColor(background.bg) + SILE.outputter:drawRule( + pagea:left() - offset, pagea:top() - offset, + pagea:width() + 2 * offset, pagea:height() + 2 * offset) + SILE.outputter:popColor() + end + if not background.allpages then + background.bg = nil + end end function package:_init () base._init(self) - self:loadPackage("color") + self.class:registerHook("newpage", outputBackground) end function package:registerCommands () self:registerCommand("background", function (options, _) - options.color = options.color or "white" - options.allpages = options.allpages or true - outputBackground(options.color) - if options.allpages and options.allpages ~= "false" then - local oldNewPage = SILE.documentState.documentClass.newPage - SILE.documentState.documentClass.newPage = function (self_) - local page = oldNewPage(self_) - outputBackground(options.color) - return page - end + if SU.boolean(options.disable, false) then + -- This option is certainly better than enforcing a white color. + background.bg = nil + return + end + + local allpages = SU.boolean(options.allpages, true) + background.allpages = allpages + local color = options.color and SILE.types.color(options.color) + local src = options.src + if src then + background.bg = src and SILE.resolveFile(src) or SU.error("Couldn't find file "..src) + elseif color then + background.bg = color + else + SU.error("background requires a color or an image src parameter") end - end, "Draws a solid background color on pages after initialization.") + outputBackground() + end, "Output a solid background color or an image on pages after initialization.") end package.documentation = [[ \begin{document} \use[module=packages.background] -The \autodoc:package{background} package allows you to set the color of the canvas background (by drawing a solid color block the full size of the page on page initialization). -The package provides a \autodoc:command{\background} command which requires at least one parameter, \autodoc:parameter{color=}, and sets the background of the current and all following pages to that color. -If you want to set only the current page background different from the default, use the parameter \autodoc:parameter{allpages=false}. -The color specification in the same as specified in the \autodoc:package{color} package. +As its name implies, the \autodoc:package{background} package allows you to set the color of the page canvas background or to use a background image extending to the full page width and height. + +The package provides a \autodoc:command{\background} command which requires one of the following parameters: +\begin{itemize} +\item{\autodoc:parameter{color=} sets the background of the current and all following pages to that color. The color specification has the same syntax as specified in the \autodoc:package{color} package.} +\item{\autodoc:parameter{src=} sets the backgound of the current and all following pages to the specified image. The latter will be scaled to the target dimension.} +\end{itemize} + +The background extends to the page trim area (“page bleed”) if the latter is defined. +This is to ensure that it indeed “bleeds” off the sides of the page, so as to avoid thin white lignes on an otherwise full color page when the paper sheet is cut to dimension but some pages are trimmed slightly more than others. +If setting only the current page background different from the default is desired, an extra parameter \autodoc:parameter{allpages=false} can be passed. \background[color=#e9d8ba,allpages=false] So, for example, \autodoc:command{\background[color=#e9d8ba,allpages=false]} will set a sepia tone background on the current page. +The \autodoc:parameter{disable=true} parameter allows disabling the background on the following pages. +It may be useful when \autodoc:parameter{allpages} is active from a previous invocation. \end{document} ]] diff --git a/packages/balanced-frames/init.lua b/packages/balanced-frames/init.lua index 97619d38a6..2a9a703944 100644 --- a/packages/balanced-frames/init.lua +++ b/packages/balanced-frames/init.lua @@ -12,7 +12,7 @@ local function buildPage (typesetter, independent) if not (frame.balanced == true) then return unbalanced_buildPage(typesetter, independent) end local colCount = 0 - local target = SILE.length() + local target = SILE.types.length() while frame and frame.balanced == true do target = target + frame:height() colCount = colCount + 1 @@ -25,7 +25,7 @@ local function buildPage (typesetter, independent) -- of frame space on the page, and there are no magic requests to balance the -- columns, then we have a full page. Just send it out normally. local q = typesetter.state.outputQueue - local totalHeight = SILE.length() + local totalHeight = SILE.types.length() local mustBalance = 0 for i = 1, #q do totalHeight = totalHeight + q[i].height + q[i].depth @@ -55,7 +55,7 @@ local function buildPage (typesetter, independent) end typesetter.state.lastPenalty = 0 local oldPageBuilder = SILE.pagebuilder - SILE.pagebuilder = require("core.pagebuilder")() + SILE.pagebuilder = SILE.pagebuilders.base() while typesetter.frame and typesetter.frame.balanced do unbalanced_buildPage(typesetter, true) if typesetter.frame.next and SILE.getFrame(typesetter.frame.next).balanced == true then @@ -72,8 +72,8 @@ local function buildPage (typesetter, independent) return true end -function package:_init (class) - base._init(self, class) +function package:_init (options) + base._init(self, options) self.class:registerPostinit(function(_) if not unbalanced_buildPage then unbalanced_buildPage = SILE.typesetter.buildPage diff --git a/packages/base.lua b/packages/base.lua index 17d711bd02..26754d8f18 100644 --- a/packages/base.lua +++ b/packages/base.lua @@ -1,3 +1,6 @@ +--- SILE package class. +-- @interfaces packages + local package = pl.class() package.type = "package" package._name = "base" @@ -14,15 +17,32 @@ local function script_path () return base end -function package:_init (_) +local settingDeclarations = { } +local rawhandlerRegistrations = {} +local commandRegistrations = { } + +function package:_init (_, reload) self.class = SILE.scratch.half_initialized_class or SILE.documentState.documentClass if not self.class then SU.error("Attempted to initialize package before class, should have been queued in the preamble", true) end self.basedir = script_path() - self:declareSettings() - self:registerRawHandlers() - self:registerCommands() + -- Note string.format(%p) would be nicer than tostring() but only LuaJIT and Lua 5.4 support it + local settingsDeclarator = tostring(self.declareSettings) + if reload or not settingDeclarations[settingsDeclarator] then + settingDeclarations[settingsDeclarator] = true + self:declareSettings() + end + local rawhandlerRegistrator = tostring(self.registerRawHandlers) + if reload or not rawhandlerRegistrations[rawhandlerRegistrator] then + rawhandlerRegistrations[rawhandlerRegistrator] = true + self:registerRawHandlers() + end + local commandRegistrator = tostring(self.registerCommands) + if reload or not commandRegistrations[commandRegistrator] then + commandRegistrations[commandRegistrator] = true + self:registerCommands() + end end function package:_post_init () @@ -33,14 +53,30 @@ function package.declareSettings (_) end function package.registerRawHandlers (_) end -function package:loadPackage (packname) - return self.class:loadPackage(packname) +function package:loadPackage (packname, options, reload) + return self.class:loadPackage(packname, options, reload) +end + +function package:reloadPackage (packname, options) + return self.class:reloadPackage(packname, options) end function package.registerCommands (_) end -- This gives us a hook to match commands with the packages that registered -- them as opposed to core commands or class-provided commands + +--- Register a function as a SILE command. +-- Takes any Lua function and registers it for use as a SILE command (which will in turn be used to process any content +-- nodes identified with the command name. +-- +-- A similar method is available for classes, `classes:registerCommand`. +-- @tparam string name Name of cammand to register. +-- @tparam function func Callback function to use as command handler. +-- @tparam[opt] nil|string help User friendly short usage string for use in error messages, documentation, etc. +-- @tparam[opt] nil|string pack Information identifying the module registering the command for use in error and usage +-- messages. Usually auto-detected. +-- @see SILE.classes:registerCommand function package:registerCommand (name, func, help, pack) self.class:registerCommand(name, func, help, pack) end diff --git a/packages/bibtex/bibliography.lua b/packages/bibtex/bibliography.lua index b55104d63a..0572390d12 100644 --- a/packages/bibtex/bibliography.lua +++ b/packages/bibtex/bibliography.lua @@ -9,18 +9,18 @@ local function find_outside_braces(str, pat, i) local j, k = string.find(str, pat, i) if not j then return j, k end local jb, kb = string.find(str, '%b{}', i) - while jb and jb < j do --- scan past braces - --- braces come first, so we search again after close brace + while jb and jb < j do -- scan past braces + -- braces come first, so we search again after close brace local i2 = kb + 1 j, k = string.find(str, pat, i2) if not j then return j, k end jb, kb = string.find(str, '%b{}', i2) end -- either pat precedes braces or there are no braces - return string.find(str, pat, j) --- 2nd call needed to get captures + return string.find(str, pat, j) -- 2nd call needed to get captures end -local function split(str, pat, find) --- return list of substrings separated by pat +local function split(str, pat, find) -- return list of substrings separated by pat find = find or string.find -- could be find_outside_braces -- @Omikhelia: I added this check here to avoid breaking on error, -- but probably in could have been done earlier... @@ -43,7 +43,7 @@ local function split(str, pat, find) --- return list of substrings separated by return t end -local function splitters(str, pat, find) --- return list of separators +local function splitters(str, pat, find) -- return list of separators find = find or string.find -- could be find_outside_braces local t = { } local insert = table.insert @@ -130,7 +130,7 @@ do trailers[i] = string.sub(trailer, 1, 1) end end - local commas = { } --- maps each comma to index of token the follows it + local commas = { } -- maps each comma to index of token the follows it for i, t in ipairs(trailers) do string.gsub(t, ',', function() table.insert(commas, i+1) end) end @@ -263,7 +263,7 @@ do end end ---- Thanks, Norman, for the above functions! +-- Thanks, Norman, for the above functions! local Bibliography Bibliography = { diff --git a/packages/bibtex/init.lua b/packages/bibtex/init.lua index 134ee3301d..33829d3302 100644 --- a/packages/bibtex/init.lua +++ b/packages/bibtex/init.lua @@ -79,18 +79,12 @@ function package:registerCommands () SILE.scratch.bibtex.bib = parseBibtex(file) -- Later we'll do multiple bibliogs, but not now end) - self:registerCommand("bibstyle", function (_, content) + self:registerCommand("bibstyle", function (_, _) SU.deprecated("\\bibstyle", '\\set[parameter=bibtex.style]', "0.13.2", "0.14.0") - if type(content) == "table" then - content = content[1] - end - if type(content) == "string" then - SILE.settings:set("bibtex.style", content) - end end) self:registerCommand("cite", function (options, content) - if not options.key then options.key = SU.contentToString(content) end + if not options.key then options.key = SU.ast.contentToString(content) end local style = SILE.settings:get("bibtex.style") local bibstyle = require("packages.bibtex.styles." .. style) local cite = Bibliography.produceCitation(options, SILE.scratch.bibtex.bib, bibstyle) @@ -102,7 +96,7 @@ function package:registerCommands () end) self:registerCommand("reference", function (options, content) - if not options.key then options.key = SU.contentToString(content) end + if not options.key then options.key = SU.ast.contentToString(content) end local style = SILE.settings:get("bibtex.style") local bibstyle = require("packages.bibtex.styles." .. style) local cite, err = Bibliography.produceReference(options, SILE.scratch.bibtex.bib, bibstyle) diff --git a/packages/bidi/init.lua b/packages/bidi/init.lua index b9eab110a2..9bd2756070 100644 --- a/packages/bidi/init.lua +++ b/packages/bidi/init.lua @@ -82,8 +82,8 @@ end local splitNodeAtPos = function (n, splitstart, p) if n.is_unshaped then local utf8chars = SU.splitUtf8(n.text) - local n2 = SILE.nodefactory.unshaped({ text = "", options = pl.tablex.copy(n.options) }) - local n1 = SILE.nodefactory.unshaped({ text = "", options = pl.tablex.copy(n.options) }) + local n2 = SILE.types.node.unshaped({ text = "", options = pl.tablex.copy(n.options) }) + local n1 = SILE.types.node.unshaped({ text = "", options = pl.tablex.copy(n.options) }) for i = splitstart, #utf8chars do if i <= p then n1.text = n1.text .. utf8chars[i] else n2.text = n2.text .. utf8chars[i] diff --git a/packages/boustrophedon/init.lua b/packages/boustrophedon/init.lua index ce81f8fd1b..9dbe953961 100644 --- a/packages/boustrophedon/init.lua +++ b/packages/boustrophedon/init.lua @@ -3,8 +3,8 @@ local base = require("packages.base") local package = pl.class(base) package._name = "boustrophedon" -function package:_init (class) - base._init(self, class) +function package:_init (options) + base._init(self, options) SILE.hyphenator.languages.grc = { patterns={} } SILE.nodeMakers.grc = pl.class(SILE.nodeMakers.unicode) function SILE.nodeMakers.grc.iterator (node, items) @@ -13,7 +13,7 @@ function package:_init (class) node:addToken(items[i].text, items[i]) node:makeToken() node:makePenalty() - coroutine.yield(SILE.nodefactory.glue("0pt plus 2pt")) + coroutine.yield(SILE.types.node.glue("0pt plus 2pt")) end end) end @@ -44,7 +44,7 @@ function package:registerCommands () end end if startdir == dir then - local restore = SILE.nodefactory.vbox({}) + local restore = SILE.types.node.vbox({}) restore.outputYourself = function (_, typesetter, _) typesetter.frame.direction = startdir typesetter.frame:newLine() diff --git a/packages/chordmode/init.lua b/packages/chordmode/init.lua index 60f71ac14f..613c6ae4f2 100644 --- a/packages/chordmode/init.lua +++ b/packages/chordmode/init.lua @@ -14,7 +14,7 @@ function package.declareSettings (_) SILE.settings:declare({ parameter = "chordmode.offset", type = "length", - default = SILE.length("2ex"), + default = SILE.types.length("2ex"), help = "Vertical offset between the chord name and the text." }) @@ -27,7 +27,7 @@ function package:registerCommands () SILE.call("chordmode:chordfont", {}, { options.name }) end) local origWidth = chordBox.width - chordBox.width = SILE.length() + chordBox.width = SILE.types.length() SILE.call("raise", { height = SILE.settings:get("chordmode.offset") }, function () SILE.typesetter:pushHbox(chordBox) @@ -35,7 +35,7 @@ function package:registerCommands () local lyricBox = SILE.call("hbox", {}, content) if lyricBox.width < origWidth then - lyricBox.width = origWidth + SILE.length("0.5em"):absolute() + lyricBox.width = origWidth + SILE.types.length("0.5em"):absolute() end local chordLineHeight = chordBox.height + SILE.settings:get("chordmode.offset"):absolute() if chordLineHeight > lyricBox.height then diff --git a/packages/color-fonts/init.lua b/packages/color-fonts/init.lua index d6955bab75..3484462e0e 100644 --- a/packages/color-fonts/init.lua +++ b/packages/color-fonts/init.lua @@ -79,15 +79,16 @@ function package:_init () for i=1, #run do options = pl.tablex.deepcopy(options) if run[i].color then - nodes[#nodes+1] = SILE.nodefactory.hbox({ - outputYourself = function () SILE.outputter:pushColor(run[i].color) end + local color = SILE.types.color(run[i].color) + nodes[#nodes+1] = SILE.types.node.hbox({ + outputYourself = function () SILE.outputter:pushColor(color) end }) end for node in nodeMaker(options):iterator(run[i].slice, run[i].chunk) do nodes[#nodes+1] = node end if run[i].color then - nodes[#nodes+1] = SILE.nodefactory.hbox({ + nodes[#nodes+1] = SILE.types.node.hbox({ outputYourself = function () SILE.outputter:popColor() end }) end diff --git a/packages/color/init.lua b/packages/color/init.lua index 2ee86b4941..8034ad43cb 100644 --- a/packages/color/init.lua +++ b/packages/color/init.lua @@ -6,14 +6,17 @@ package._name = "color" function package:registerCommands () self:registerCommand("color", function (options, content) - local color = SILE.color(options.color or "black") - SILE.typesetter:pushHbox({ - outputYourself = function () SILE.outputter:pushColor(color) end - }) - SILE.process(content) - SILE.typesetter:pushHbox({ - outputYourself = function () SILE.outputter:popColor() end - }) + local color = SILE.types.color(options.color or "black") + -- This is a bit of a hack to use a liner. + -- (Due to how the color stack is currently handled) + -- If the content spans multiple lines, and a page break occurs in between, + -- this avoids the color being propagated to other page content. + -- (folio, footnotes, etc.) + SILE.typesetter:liner("color", content, function (box, typesetter, line) + SILE.outputter:pushColor(color) + box:outputContent(typesetter, line) + SILE.outputter:popColor() + end) end, "Changes the active ink color to the color .") end diff --git a/packages/complex-spaces/init.lua b/packages/complex-spaces/init.lua index 81a4208eac..92bda3beec 100644 --- a/packages/complex-spaces/init.lua +++ b/packages/complex-spaces/init.lua @@ -21,7 +21,7 @@ function package:_init () local myoptions = pl.tablex.deepcopy(options) myoptions.language = "x-spaces-are-nodes" local nnodes = SILE.shaper:createNnodes( " ", myoptions) - return SILE.nodefactory.discretionary({ replacement=nnodes }) + return SILE.types.node.discretionary({ replacement=nnodes }) end return SILE.shaper.noncomplex_SpaceNode(_, options, item) end diff --git a/packages/counters/init.lua b/packages/counters/init.lua index 7df646d244..a01a518d51 100644 --- a/packages/counters/init.lua +++ b/packages/counters/init.lua @@ -3,14 +3,12 @@ local base = require("packages.base") local package = pl.class(base) package._name = "counters" -SILE.formatCounter = function (counter) +SILE.formatCounter = function () SU.deprecated("SILE.formatCounter", "class:formatCounter", "0.13.0", "0.15.0") - return package.formatCounter(nil, counter) end -SILE.formatMultilevelCounter = function (counter, options) +SILE.formatMultilevelCounter = function () SU.deprecated("SILE.formatMultilevelCounter", "class:formatMultilevelCounter", "0.13.0", "0.15.0") - return package.formatMultilevelCounter(nil, counter, options) end local function getCounter (_, id) @@ -51,7 +49,7 @@ function package:formatMultilevelCounter (counter, options) options = options or {} local maxlevel = options.level and SU.min(SU.cast("integer", options.level), #counter.value) or #counter.value -- Option minlevel is undocumented and should perhaps be deprecated: is there a real use case for it? - local minlevel = options.minlevel and SU.min(SU.cast("integer", options.minlevel, #counter.value)) or 1 + local minlevel = options.minlevel and SU.min(SU.cast("integer", options.minlevel), #counter.value) or 1 local out = {} if SU.boolean(options.noleadingzeros, false) then while counter.value[minlevel] == 0 do minlevel = minlevel + 1 end -- skip leading zeros @@ -219,7 +217,8 @@ The available built-in display types are: \item{\code{alpha}, for lower-case alphabetic counting} \item{\code{Alpha}, for upper-case alphabetic counting} \item{\code{roman}, for lower-case Roman numerals} -\item{\code{ROMAN} for upper-case Roman numerals} +\item{\code{ROMAN}, for upper-case Roman numerals} +\item{\code{greek}, for Greek letters in alphabetical order (not Greek numerals)} \end{itemize} The ICU library also provides ways of formatting numbers in global (non-Latin) scripts. diff --git a/packages/cropmarks/init.lua b/packages/cropmarks/init.lua index a3c5af14ff..2fd529f56c 100644 --- a/packages/cropmarks/init.lua +++ b/packages/cropmarks/init.lua @@ -7,51 +7,45 @@ local outcounter = 1 local function outputMarks () local page = SILE.getFrame("page") - SILE.outputter:drawRule(page:left() - 10, page:top(), -10, 0.5) - SILE.outputter:drawRule(page:left(), page:top() - 10, 0.5, -10) - SILE.outputter:drawRule(page:right() + 10, page:top(), 10, 0.5) - SILE.outputter:drawRule(page:right(), page:top() - 10, 0.5, -10) - SILE.outputter:drawRule(page:left() - 10, page:bottom(), -10, 0.5) - SILE.outputter:drawRule(page:left(), page:bottom() + 10, 0.5, 10) - SILE.outputter:drawRule(page:right() + 10, page:bottom(), 10, 0.5) - SILE.outputter:drawRule(page:right(), page:bottom() + 10, 0.5, 10) + -- Length of crop mark bars + local cropsz = 20 + -- Ensure the crop marks stay outside the bleed area + local offset = math.max(10, SILE.documentState.bleed / 2) + + SILE.outputter:drawRule(page:left() - offset, page:top(), -cropsz, 0.5) + SILE.outputter:drawRule(page:left(), page:top() - offset, 0.5, -cropsz) + SILE.outputter:drawRule(page:right() + offset, page:top(), cropsz, 0.5) + SILE.outputter:drawRule(page:right(), page:top() - offset, 0.5, -cropsz) + SILE.outputter:drawRule(page:left() - offset, page:bottom(), -cropsz, 0.5) + SILE.outputter:drawRule(page:left() , page:bottom() + offset, 0.5, cropsz) + SILE.outputter:drawRule(page:right() + offset, page:bottom(), cropsz, 0.5) + SILE.outputter:drawRule(page:right(), page:bottom() + offset, 0.5, cropsz) local hbox, hlist = SILE.typesetter:makeHbox(function () SILE.settings:temporarily(function () SILE.call("noindent") SILE.call("font", { size="6pt" }) - SILE.call("crop:header") + if SILE.Commands["crop:header"] then + -- Deprecation shim: + -- If user redefined this command, still use it with a warning... + SU.deprecated("crop:header", "cropmarks:header", "0.15.0", "0.16.0") + SILE.call("crop:header") + else + SILE.call("cropmarks:header") + end end) end) if #hlist > 0 then - SU.error("Forbidden migrating content in crop header") + SU.error("Migrating content is forbidden in crop header") end - SILE.typesetter.frame.state.cursorX = page:left() + 10 - SILE.typesetter.frame.state.cursorY = page:top() - 13 + SILE.typesetter.frame.state.cursorX = page:left() + offset + SILE.typesetter.frame.state.cursorY = page:top() - offset - 4 outcounter = outcounter + 1 if hbox then - for i = 1, #(hbox.value) do hbox.value[i]:outputYourself(SILE.typesetter, { ratio = 1 }) end - end -end - -local function reconstrainFrameset (fs) - for n, f in pairs(fs) do - if n ~= "page" then - if f:isAbsoluteConstraint("right") then - f.constraints.right = "left(page) + (" .. f.constraints.right .. ")" - end - if f:isAbsoluteConstraint("left") then - f.constraints.left = "left(page) + (" .. f.constraints.left .. ")" - end - if f:isAbsoluteConstraint("top") then - f.constraints.top = "top(page) + (" .. f.constraints.top .. ")" - end - if f:isAbsoluteConstraint("bottom") then - f.constraints.bottom = "top(page) + (" .. f.constraints.bottom .. ")" - end - f:invalidate() + for i = 1, #(hbox.value) do + hbox.value[i]:outputYourself(SILE.typesetter, { ratio = 1 }) end end end @@ -63,53 +57,36 @@ end function package:registerCommands () - self:registerCommand("crop:header", function (_, _) - local info = SILE.input.filenames[1] .. " - " .. self.class:date("%x %X") .. " - " .. outcounter + self:registerCommand("cropmarks:header", function (_, _) + local info = SILE.input.filenames[1] + .. " - " + .. self.class.packages.date:date({ format = "%x %X" }) + .. " - " .. outcounter SILE.typesetter:typeset(info) end) - self:registerCommand("crop:setup", function (options, _) - local papersize = SU.required(options, "papersize", "setting up crop marks") - local landscape = SU.boolean(options.landscape, self.class.options.landscape) - local size = SILE.papersize(papersize, landscape) - local oldsize = SILE.documentState.paperSize - SILE.documentState.paperSize = size - local offsetx = ( SILE.documentState.paperSize[1] - oldsize[1] ) /2 - local offsety = ( SILE.documentState.paperSize[2] - oldsize[2] ) /2 - local page = SILE.getFrame("page") - page:constrain("right", page:right() + offsetx) - page:constrain("left", offsetx) - page:constrain("bottom", page:bottom() + offsety) - page:constrain("top", offsety) - if SILE.scratch.masters then - for _, v in pairs(SILE.scratch.masters) do - reconstrainFrameset(v.frames) - end - else - reconstrainFrameset(SILE.documentState.documentClass.pageTemplate.frames) - end - if SILE.typesetter.frame then SILE.typesetter.frame:init() end - local oldEndPage = SILE.documentState.documentClass.endPage - SILE.documentState.documentClass.endPage = function (self_) - oldEndPage(self_) - outputMarks() - end + self:registerCommand("cropmarks:setup", function (_, _) + self.class:registerHook("endpage", outputMarks) end) + self:registerCommand("crop:setup", function (_, _) + SU.deprecated("crop:setup", "cropmarks:setup", "0.15.10", "0.17.0") + SILE.call("cropmarks:setup") + end) end package.documentation = [[ \begin{document} -When preparing a document for printing, you may be asked by the printer to add crop marks. -This means that you need to output the document on a slightly larger page size than your target paper and add printer’s crop marks to show where the paper should be trimmed down to the correct size. -(This is to ensure that pages where the content “bleeds” off the side of the page are correctly cut.) +When preparing a document for printing, you may be asked by the printer add crop marks. +This means that you need to output the document on a slightly larger page size than your target paper and add crop marks to show where the paper sheet should be trimmed down to the correct size. -This package provides the \autodoc:command{\crop:setup} command which should be run early in your document file. -It takes one argument, \autodoc:parameter{papersize}, which is the true target paper size. -It place cropmarks around the true page content. +Actual paper size, true page content area and bleed/trim area can all be set via class options. +This package provides the \autodoc:command{\cropmarks:setup} command which should be run early in your document file. +It places crop marks around the true page content. +The crop marks are guaranteed to stay outside the bleed/trim area, when defined. It also adds a header at the top of the page with the filename, date and output sheet number. -You can customize this header by redefining \autodoc:command{\crop:header}. +You can customize this header by redefining \autodoc:command{\cropmarks:header}. \end{document} ]] diff --git a/packages/dropcaps/init.lua b/packages/dropcaps/init.lua index ac7b6bccd9..7ce03d86dd 100644 --- a/packages/dropcaps/init.lua +++ b/packages/dropcaps/init.lua @@ -58,7 +58,7 @@ local function getToleranceDepth () bsratio = computeBaselineRatio() SU.debug("dropcaps", "Using computed descender baseline ratio", bsratio) end - return bsratio * SILE.measurement("1bs"):tonumber() + return bsratio * SILE.types.measurement("1bs"):tonumber() end function package:registerCommands () @@ -97,7 +97,7 @@ function package:registerCommands () -- Note this only works for the default linespace mechanism. -- We determine the height of the first line by measuring the size the initial content *would have* been. local tmpHbox = shapeHbox(options, content) - local extraHeight = SILE.measurement((lines - 1).."bs"):tonumber() + local extraHeight = SILE.types.measurement((lines - 1).."bs"):tonumber() local curHeight = tmpHbox.height:tonumber() + depthAdjustment local targetHeight = (curHeight - depthAdjustment) * scale + extraHeight if strict then @@ -108,7 +108,7 @@ function package:registerCommands () -- Now we need to figure out how to scale the dropcap font to get an initial of targetHeight. -- From that we can also figure out the width it will be at that height. - local curSize = SILE.measurement(SILE.settings:get("font.size")):tonumber() + local curSize = SILE.types.measurement(SILE.settings:get("font.size")):tonumber() local curWidth = tmpHbox.width:tonumber() options.size = size and size:tonumber() or (targetHeight / curHeight * curSize) local targetWidth = curWidth / curSize * options.size @@ -129,7 +129,7 @@ function package:registerCommands () local toleranceDepth = getToleranceDepth() if extraDepth > toleranceDepth then SU.debug("dropcaps", "Extra depth", extraDepth, "> tolerance", toleranceDepth) - local extraLines = math.ceil((extraDepth - toleranceDepth) / SILE.measurement("1bs"):tonumber()) + local extraLines = math.ceil((extraDepth - toleranceDepth) / SILE.types.measurement("1bs"):tonumber()) lines = lines + extraLines SU.debug("dropcaps", "Extra lines needed to fit", extraLines) else diff --git a/packages/footnotes/init.lua b/packages/footnotes/init.lua index 4adb978885..3e40ce87da 100644 --- a/packages/footnotes/init.lua +++ b/packages/footnotes/init.lua @@ -15,9 +15,9 @@ function package:_init (options) self.class:initInsertionClass("footnote", { insertInto = options.insertInto or "footnotes", stealFrom = options.stealFrom or { "content" }, - maxHeight = SILE.length("75%ph"), - topBox = SILE.nodefactory.vglue("2ex"), - interInsertionSkip = SILE.length("1ex"), + maxHeight = SILE.types.length("75%ph"), + topBox = SILE.types.node.vglue("2ex"), + interInsertionSkip = SILE.types.length("1ex"), }) end @@ -41,10 +41,10 @@ function package:registerCommands () self:registerCommand("footnote:options", function (options, _) if options["maxHeight"] then - SILE.scratch.insertions.classes.footnote.maxHeight = SILE.length(options["maxHeight"]) + SILE.scratch.insertions.classes.footnote.maxHeight = SILE.types.length(options["maxHeight"]) end if options["interInsertionSkip"] then - SILE.scratch.insertions.classes.footnote.interInsertionSkip = SILE.length(options["interInsertionSkip"]) + SILE.scratch.insertions.classes.footnote.interInsertionSkip = SILE.types.length(options["interInsertionSkip"]) end end) @@ -54,7 +54,7 @@ function package:registerCommands () local frame = opts.insertInto and SILE.getFrame(opts.insertInto.frame) local oldGetTargetLength = SILE.typesetter.getTargetLength local oldFrame = SILE.typesetter.frame - SILE.typesetter.getTargetLength = function () return SILE.length(0xFFFFFF) end + SILE.typesetter.getTargetLength = function () return SILE.types.length(0xFFFFFF) end SILE.settings:pushState() -- Restore the settings to the top of the queue, which should be the document #986 SILE.settings:toplevelState() diff --git a/packages/frametricks/init.lua b/packages/frametricks/init.lua index f9fe1123b8..349726c92a 100644 --- a/packages/frametricks/init.lua +++ b/packages/frametricks/init.lua @@ -5,7 +5,7 @@ package._name = "frametricks" local breakFrameHorizontalAt = function (offset) local cFrame = SILE.typesetter.frame - if not offset or not (offset > SILE.length(0)) then + if not offset or not (offset > SILE.types.length(0)) then SILE.typesetter:chuck() offset = SILE.typesetter.frame.state.cursorX end @@ -32,10 +32,10 @@ end local shiftframeedge = function (frame, options) if options.left then - frame:constrain("left", frame:left() + SILE.length(options.left)) + frame:constrain("left", frame:left() + SILE.types.length(options.left)) end if options.right then - frame:constrain("right", frame:right() + SILE.length(options.right)) + frame:constrain("right", frame:right() + SILE.types.length(options.right)) end end @@ -104,7 +104,7 @@ function package.breakFrameVertical (_, after) if after then totalHeight = after else - totalHeight = SILE.length(0) + totalHeight = SILE.types.length(0) SILE.typesetter:leaveHmode(1) local queue = SILE.typesetter.state.outputQueue for i = 1, #queue do @@ -210,22 +210,22 @@ function package:registerCommands () SILE.typesetter:leaveHmode() local hbox = SILE.typesetter:makeHbox(content) -- HACK What about migrating nodes here? local heightOfPageSoFar = SILE.pagebuilder:collateVboxes(SILE.typesetter.state.outputQueue).height - local overshoot = SILE.length(heightOfPageSoFar + hbox.height - SILE.typesetter:getTargetLength()) - if overshoot > SILE.length(0) then + local overshoot = SILE.types.length(heightOfPageSoFar + hbox.height - SILE.typesetter:getTargetLength()) + if overshoot > SILE.types.length(0) then SILE.call("eject") SILE.typesetter:leaveHmode() end self:breakFrameVertical() - local boundary = hbox.width + SILE.length(options.rightboundary):absolute() + local boundary = hbox.width + SILE.types.length(options.rightboundary):absolute() breakFrameHorizontalAt(boundary) SILE.typesetNaturally(SILE.typesetter.frame.previous, function () table.insert(SILE.typesetter.state.nodes, hbox) end) - -- SILE.settings:set("document.baselineskip", SILE.length("1ex") - SILE.settings:get("document.baselineskip").height) + -- SILE.settings:set("document.baselineskip", SILE.types.length("1ex") - SILE.settings:get("document.baselineskip").height) -- undoSkip.stretch = hbox.height -- SILE.typesetter:pushHbox({ value = {} }) -- SILE.typesetter:pushVglue({ height = undoSkip }) - self:breakFrameVertical(hbox.height + SILE.length(options.bottomboundary):absolute()) + self:breakFrameVertical(hbox.height + SILE.types.length(options.bottomboundary):absolute()) shiftframeedge(SILE.getFrame(SILE.typesetter.frame.next), { left = -boundary }) --SILE.outputter:debugFrame(SILE.typesetter.frame) end, "Sets the given content in its own frame, flowing the remaining content around it") @@ -244,7 +244,7 @@ function package:registerCommands () SU.error("Can't find frame "..options.frame.." to fit") end local frame = SILE.frames[options.frame] - local height = SILE.length() + local height = SILE.types.length() SILE.typesetNaturally(frame, function () SILE.typesetter:leaveHmode() for i = 1, #SILE.typesetter.state.outputQueue do diff --git a/packages/grid/init.lua b/packages/grid/init.lua index 9df14d28cc..efed7a0793 100644 --- a/packages/grid/init.lua +++ b/packages/grid/init.lua @@ -8,7 +8,7 @@ local oldPagebuilderType, oldTypesetterType local function startGridInFrame (typesetter) if not SILE.typesetter.state.grid then return end -- Ensure the frame hook isn't effective when grid is off local queue = typesetter.state.outputQueue - typesetter.frame.state.totals.gridCursor = SILE.measurement(0) + typesetter.frame.state.totals.gridCursor = SILE.types.measurement(0) if #queue == 0 then typesetter.state.previousVbox = typesetter:pushVbox() return @@ -17,7 +17,7 @@ local function startGridInFrame (typesetter) table.remove(queue, 1) end if queue[1] then - table.insert(queue, 1, SILE.nodefactory.vbox()) + table.insert(queue, 1, SILE.types.node.vbox()) table.insert(queue, 2, SILE.typesetter:leadingFor(queue[2], queue[1])) end end diff --git a/packages/gutenberg/init.lua b/packages/gutenberg/init.lua index a48944485b..82aab67c47 100644 --- a/packages/gutenberg/init.lua +++ b/packages/gutenberg/init.lua @@ -18,7 +18,7 @@ function package:registerCommands () end table.insert(alts, hbox) end - local alternative = SILE.nodefactory.alternative({ + local alternative = SILE.types.node.alternative({ options = alts, selected = 1 }) diff --git a/packages/hanmenkyoshi/init.lua b/packages/hanmenkyoshi/init.lua index cf21bdb086..648fefa599 100644 --- a/packages/hanmenkyoshi/init.lua +++ b/packages/hanmenkyoshi/init.lua @@ -58,8 +58,8 @@ local declareHanmenFrame = function (class, id, spec) spec.hanmen.linegap * ( spec.hanmen.linecount -1 ) end local skip = spec.hanmen.linegap + spec.hanmen.gridsize - SILE.settings:set("document.baselineskip", SILE.nodefactory.vglue(skip)) - SILE.settings:set("document.parskip", SILE.nodefactory.vglue()) + SILE.settings:set("document.baselineskip", SILE.types.node.vglue(skip)) + SILE.settings:set("document.parskip", SILE.types.node.vglue()) local frame = SILE.newFrame(spec, spec.tate and SILE.tateFramePrototype or SILE.framePrototype) if spec.id then class.pageTemplate.frames[spec.id] = frame @@ -79,7 +79,8 @@ function package:registerCommands () if not frame.hanmen then SU.error("show-hanmen called on a frame with no hanmen") end - SILE.outputter:pushColor({r = 1, g= 0.9, b = 0.9 }) + local color = SILE.types.color({r = 1, g= 0.9, b = 0.9 }) + SILE.outputter:pushColor(color) if frame:writingDirection() == "TTB" then showHanmenTate(frame) else diff --git a/packages/indexer/init.lua b/packages/indexer/init.lua index 3254e87a83..12d938d1cb 100644 --- a/packages/indexer/init.lua +++ b/packages/indexer/init.lua @@ -68,8 +68,8 @@ function package:registerCommands () self:registerCommand("index:item", function (options, content) SILE.settings:temporarily(function () - SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue()) - SILE.settings:set("current.parindent", SILE.nodefactory.glue()) + SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) SILE.call("code", {}, content) -- Ideally, leaders SILE.call("hss") diff --git a/packages/infonode/init.lua b/packages/infonode/init.lua index c53fce99f6..2d8b2599e3 100644 --- a/packages/infonode/init.lua +++ b/packages/infonode/init.lua @@ -9,7 +9,7 @@ package._name = "infonode" -- Check out SILE.scratch.info.thispage in your end-of-page routine and see what nodes -- are there. -local _info = pl.class(SILE.nodefactory.zerohbox) +local _info = pl.class(SILE.types.node.zerohbox) _info.type = "info" function _info:__tostring () @@ -43,9 +43,8 @@ function package:_init () SILE.scratch.info = { thispage = {} } end self.class:registerHook("newpage", newPageInfo) - self:deprecatedExport("newPageInfo", function (class) + self:deprecatedExport("newPageInfo", function () SU.deprecated("class:newPageInfo", nil, "0.13.0", "0.15.0", _deprecate) - return class:newPageInfo() end) end diff --git a/packages/inputfilter/init.lua b/packages/inputfilter/init.lua index 13fff8e07e..448e5739d4 100644 --- a/packages/inputfilter/init.lua +++ b/packages/inputfilter/init.lua @@ -25,14 +25,8 @@ function package:transformContent (content, transformFunction, extraArgs) end function package.createCommand (_, pos, col, lno, command, options, content) - local result = { content } - result.col = col - result.lno = lno - result.pos = pos - result.options = options - result.command = command - result.id = "command" - return result + local position = { lno = lno, col = col, pos = pos } + return SU.ast.createCommand(command, options, content, position) end function package:_init () diff --git a/packages/insertions/init.lua b/packages/insertions/init.lua index 14fe0273f5..cc40a37437 100644 --- a/packages/insertions/init.lua +++ b/packages/insertions/init.lua @@ -63,7 +63,7 @@ local initInsertionClass = function (_, classname, options) options.insertInto = { frame = options.insertInto, ratio = 1 } end - options.maxHeight = SILE.length(options.maxHeight) + options.maxHeight = SILE.types.length(options.maxHeight) SILE.scratch.insertions.classes[classname] = options end @@ -77,21 +77,21 @@ typesetter and frame. --]] local insertionsThisPage = {} -SILE.nodefactory.insertionlist = pl.class(SILE.nodefactory.vbox) +SILE.types.node.insertionlist = pl.class(SILE.types.node.vbox) -SILE.nodefactory.insertionlist.type = "insertionlist" -SILE.nodefactory.insertionlist.frame = nil +SILE.types.node.insertionlist.type = "insertionlist" +SILE.types.node.insertionlist.frame = nil -function SILE.nodefactory.insertionlist:_init (spec) - SILE.nodefactory.vbox._init(self, spec) +function SILE.types.node.insertionlist:_init (spec) + SILE.types.node.vbox._init(self, spec) self.typesetter = SILE.typesetters.base() end -function SILE.nodefactory.insertionlist:__tostring () +function SILE.types.node.insertionlist:__tostring () return "PI<" .. self.nodes .. ">" end -function SILE.nodefactory.insertionlist:outputYourself () +function SILE.types.node.insertionlist:outputYourself () self.typesetter:initFrame(SILE.getFrame(self.frame)) for _, node in ipairs(self.nodes) do node:outputYourself(self.typesetter, node) @@ -100,7 +100,7 @@ end local thisPageInsertionBoxForClass = function (class) if not insertionsThisPage[class] then - insertionsThisPage[class] = SILE.nodefactory.insertionlist({ + insertionsThisPage[class] = SILE.types.node.insertionlist({ frame = SILE.scratch.insertions.classes[class].insertInto.frame }) end @@ -116,28 +116,28 @@ So we stick the material into an insertion vbox, and when the pagebuilder sees this, magic will happen. --]] -SILE.nodefactory.insertion = pl.class(SILE.nodefactory.vbox) +SILE.types.node.insertion = pl.class(SILE.types.node.vbox) -SILE.nodefactory.insertion.discardable = true -SILE.nodefactory.insertion.type = "insertion" -SILE.nodefactory.insertion.seen = false +SILE.types.node.insertion.discardable = true +SILE.types.node.insertion.type = "insertion" +SILE.types.node.insertion.seen = false -function SILE.nodefactory.insertion:__tostring () +function SILE.types.node.insertion:__tostring () return "I<"..self.nodes[1].."...>" end -function SILE.nodefactory.insertion.outputYourself (_) +function SILE.types.node.insertion.outputYourself (_) end -- And some utility methods to make the insertion processing code -- easier to read. -function SILE.nodefactory.insertion:dropDiscardables () +function SILE.types.node.insertion:dropDiscardables () while #self.nodes > 1 and self.nodes[#self.nodes].discardable do self.nodes[#self.nodes] = nil end end -function SILE.nodefactory.insertion:split (materialToSplit, maxsize) +function SILE.types.node.insertion:split (materialToSplit, maxsize) local firstpage = SILE.pagebuilder:findBestBreak({ vboxlist = materialToSplit, target = maxsize, @@ -149,8 +149,8 @@ function SILE.nodefactory.insertion:split (materialToSplit, maxsize) self:append(materialToSplit) self.contentHeight = self.height self.contentDepth = self.depth - self.depth = SILE.length(0) - self.height = SILE.length(0) + self.depth = SILE.types.length(0) + self.height = SILE.types.length(0) return SILE.pagebuilder:collateVboxes(firstpage) end end @@ -168,7 +168,7 @@ entered yet. local initShrinkage = function (frame) if not frame.state or not frame.state.totals then frame:init() end - if not frame.state.totals.shrinkage then frame.state.totals.shrinkage = SILE.measurement(0) end + if not frame.state.totals.shrinkage then frame.state.totals.shrinkage = SILE.types.measurement(0) end end local nextInterInsertionSkip = function (class) @@ -178,12 +178,12 @@ local nextInterInsertionSkip = function (class) if options["topBox"] then return options["topBox"]:absolute() elseif options["topSkip"] then - return SILE.nodefactory.vglue(options["topSkip"]:tonumber()) + return SILE.types.node.vglue(options["topSkip"]:tonumber()) end else local skipSize = options["interInsertionSkip"]:tonumber() skipSize = skipSize - stuffSoFar.nodes[#stuffSoFar.nodes].depth:tonumber() - return SILE.nodefactory.vglue(skipSize) + return SILE.types.node.vglue(skipSize) end end @@ -204,10 +204,10 @@ local insert = function (_, classname, vbox) local insertion = SILE.scratch.insertions.classes[classname] if not insertion then SU.error("Uninitialized insertion class " .. classname) end SILE.typesetter:pushMigratingMaterial({ - SILE.nodefactory.penalty(SILE.settings:get("insertion.penalty")) + SILE.types.node.penalty(SILE.settings:get("insertion.penalty")) }) SILE.typesetter:pushMigratingMaterial({ - SILE.nodefactory.insertion({ + SILE.types.node.insertion({ class = classname, nodes = vbox.nodes, -- actual height and depth must remain zero for page glue calculations @@ -256,7 +256,7 @@ function package:_init () if stealPosition == "bottom" then frame:relax("bottom") else frame:relax("top") end SU.debug("insertions", "Constraining height of", fName, "by", frame.state.totals.shrinkage, "to", newHeight) frame:constrain("height", newHeight) - frame.state.totals.shrinkage = SILE.measurement(0) + frame.state.totals.shrinkage = SILE.types.measurement(0) end end end @@ -306,7 +306,7 @@ function package:_init () -- We look into the page's insertion box and choose the appropriate skip, -- so we know how high the whole insertion is. local topBox = nextInterInsertionSkip(ins.class) - local insertionsHeight = SILE.length() + local insertionsHeight = SILE.types.length() insertionsHeight:___add(ins.contentHeight) insertionsHeight:___add(topBox.height) insertionsHeight:___add(topBox.depth) @@ -322,7 +322,7 @@ function package:_init () local effectOnThisFrame = options.stealFrom[SILE.typesetter.frame.id] if effectOnThisFrame then effectOnThisFrame = insertionsHeight * effectOnThisFrame - else effectOnThisFrame = SILE.measurement(0) end + else effectOnThisFrame = SILE.types.measurement(0) end local newTarget = target - effectOnThisFrame @@ -374,7 +374,7 @@ function package:_init () the penalty (and break the page) and then consider the rest of the insertion. --]] - table.insert(vboxlist, i, SILE.nodefactory.penalty(-20000)) + table.insert(vboxlist, i, SILE.types.node.penalty(-20000)) return target -- Who cares? The penalty is going to cause a split. end @@ -409,7 +409,7 @@ function package:_init () local lastbox = i while not vboxlist[lastbox].is_vbox do lastbox = lastbox - 1 end while not (vboxlist[i].is_penalty and vboxlist[i].penalty == -20000) do - table.insert(vboxlist, lastbox, SILE.nodefactory.penalty(-20000)) + table.insert(vboxlist, lastbox, SILE.types.node.penalty(-20000)) end return target end diff --git a/packages/leaders/init.lua b/packages/leaders/init.lua index 7fe989982e..963b29bc2d 100644 --- a/packages/leaders/init.lua +++ b/packages/leaders/init.lua @@ -23,7 +23,7 @@ local widthToFrameEdge = function (frame) return w end -local leader = pl.class(SILE.nodefactory.glue) +local leader = pl.class(SILE.types.node.glue) function leader:outputYourself (typesetter, line) local outputWidth = SU.rationWidth(self.width, self.width, line.ratio):tonumber() @@ -86,7 +86,7 @@ end function package:registerCommands () self:registerCommand("leaders", function(options, content) - local width = options.width and SU.cast("glue", options.width) or SILE.nodefactory.hfillglue() + local width = options.width and SU.cast("glue", options.width) or SILE.types.node.hfillglue() local hbox, hlist = SILE.typesetter:makeHbox(content) if #hlist > 0 then SU.error("Forbidden migrating content in leaders") @@ -102,10 +102,10 @@ function package:registerCommands () -- though in older times it was sometimes up to 1em and could be distributed -- differently. Anyhow, it is also the approach taken by LaTeX, with a -- \@dotsep space of 4.5mu (where 18mu = 1em, so indeed leading to 0.25em). - SILE.call("leaders", { width = SILE.nodefactory.hfillglue() }, function() - SILE.call("kern", { width = SILE.length("0.25em") }) + SILE.call("leaders", { width = SILE.types.node.hfillglue() }, function() + SILE.call("kern", { width = SILE.types.length("0.25em") }) SILE.typesetter:typeset(".") - SILE.call("kern", {width = SILE.length("0.25em") }) + SILE.call("kern", {width = SILE.types.length("0.25em") }) end) end) diff --git a/packages/linespacing/init.lua b/packages/linespacing/init.lua index 0af66972ff..a9a5d13b79 100644 --- a/packages/linespacing/init.lua +++ b/packages/linespacing/init.lua @@ -8,7 +8,7 @@ local metrics = require("fontmetrics") local metricscache = {} local getLineMetrics = function (l) - local linemetrics = { ascender = 0, descender = 0, lineheight = SILE.length() } + local linemetrics = { ascender = 0, descender = 0, lineheight = SILE.types.length() } if not l or not l.nodes then return linemetrics end for i = 1, #(l.nodes) do local node = l.nodes[i] @@ -39,8 +39,8 @@ local linespacingLeading = function (_, vbox, previous) local firstline = SILE.settings:get("linespacing.minimumfirstlineposition"):absolute() if not previous then if firstline.length:tonumber() > 0 then - local toAdd = SILE.length(firstline.length - vbox.height) - return SILE.nodefactory.vkern(toAdd) + local toAdd = SILE.types.length(firstline.length - vbox.height) + return SILE.types.node.vkern(toAdd) else return nil end @@ -52,14 +52,14 @@ local linespacingLeading = function (_, vbox, previous) if method == "fit-glyph" then local extra = SILE.settings:get("linespacing.fit-glyph.extra-space"):absolute() - local toAdd = SILE.length(extra) - return SILE.nodefactory.vglue(toAdd) + local toAdd = SILE.types.length(extra) + return SILE.types.node.vglue(toAdd) end if method == "fixed" then local btob = SILE.settings:get("linespacing.fixed.baselinedistance"):absolute() - local toAdd = SILE.length(btob.length - (vbox.height + previous.depth), btob.stretch, btob.shrink) - return SILE.nodefactory.vglue(toAdd) + local toAdd = SILE.types.length(btob.length - (vbox.height + previous.depth), btob.stretch, btob.shrink) + return SILE.types.node.vglue(toAdd) end -- For these methods, we need to read the font metrics @@ -75,7 +75,7 @@ local linespacingLeading = function (_, vbox, previous) local extra = SILE.settings:get("linespacing.fit-font.extra-space"):absolute() local btob = prevmetrics.descender + thismetrics.ascender + extra local toAdd = btob - (vbox.height + (previous and previous.depth or 0)) - return SILE.nodefactory.vglue(toAdd) + return SILE.types.node.vglue(toAdd) end if method == "css" then @@ -85,7 +85,7 @@ local linespacingLeading = function (_, vbox, previous) previous.height = previous.height + leading / 2 previous.depth = previous.depth + leading / 2 end - return SILE.nodefactory.vglue() + return SILE.types.node.vglue() end @@ -110,32 +110,32 @@ function package.declareSettings (_) SILE.settings:declare({ parameter = "linespacing.fixed.baselinedistance", - default = SILE.length("1.2em"), + default = SILE.types.length("1.2em"), type = "length", help = "Distance from baseline to baseline in the case of fixed line spacing" }) SILE.settings:declare({ parameter = "linespacing.minimumfirstlineposition", - default = SILE.length(0), + default = SILE.types.length(0), type = "length" }) SILE.settings:declare({ parameter = "linespacing.fit-glyph.extra-space", - default = SILE.length(0), + default = SILE.types.length(0), type = "length" }) SILE.settings:declare({ parameter = "linespacing.fit-font.extra-space", - default = SILE.length(0), + default = SILE.types.length(0), type = "length" }) SILE.settings:declare({ parameter = "linespacing.css.line-height", - default = SILE.length("1.2em"), + default = SILE.types.length("1.2em"), type = "length" }) diff --git a/packages/lists/init.lua b/packages/lists/init.lua index b2082af0fe..cf3e9ecad2 100644 --- a/packages/lists/init.lua +++ b/packages/lists/init.lua @@ -72,8 +72,8 @@ local trim = function (str) end local enforceListType = function (cmd) - if cmd ~= "enumerate" and cmd ~= "itemize" then - SU.error("Only 'enumerate', 'itemize' or 'item' are accepted in lists, found '"..cmd.."'") + if cmd ~= "enumerate" and cmd ~= "itemize" and cmd ~= "BulletedList" and cmd ~= "OrderedList" then + SU.error("Only items or lists are allowed as content in lists, found '"..cmd.."'") end end @@ -121,7 +121,7 @@ function package:doItem (options, content) -- appearing twice in output... but we can avoid it: -- reboxing an hbox was dumb anyway. We just need to fix its width before -- reinserting it in the text flow. - mark.width = SILE.length(stepback) + mark.width = SILE.types.length(stepback) SILE.typesetter:pushHbox(mark) SILE.process(content) end @@ -147,7 +147,7 @@ function package.doNestedList (_, listType, options, content) end -- indent - local baseIndent = (depth == 1) and SILE.settings:get("document.parindent").width:absolute() or SILE.measurement("0pt") + local baseIndent = (depth == 1) and SILE.settings:get("document.parindent").width:absolute() or SILE.types.measurement("0pt") local listIndent = SILE.settings:get("lists."..listType..".leftmargin"):absolute() -- processing @@ -156,16 +156,16 @@ function package.doNestedList (_, listType, options, content) end SILE.settings:temporarily(function () SILE.settings:set("lists.current."..listType..".depth", depth) - SILE.settings:set("current.parindent", SILE.nodefactory.glue()) - SILE.settings:set("document.parindent", SILE.nodefactory.glue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) + SILE.settings:set("document.parindent", SILE.types.node.glue()) SILE.settings:set("document.parskip", SILE.settings:get("lists.parskip")) - local lskip = SILE.settings:get("document.lskip") or SILE.nodefactory.glue() - SILE.settings:set("document.lskip", SILE.nodefactory.glue(lskip.width + (baseIndent + listIndent))) + local lskip = SILE.settings:get("document.lskip") or SILE.types.node.glue() + SILE.settings:set("document.lskip", SILE.types.node.glue(lskip.width + (baseIndent + listIndent))) local counter = options.start and (SU.cast("integer", options.start) - 1) or 0 for i = 1, #content do - if type(content[i]) == "table" then - if content[i].command == "item" then + if type(content[i]) == "table" and #content[i] > 0 then + if content[i].command == "item" or content[i].command == "ListItem" then counter = counter + 1 -- Enrich the node with internal properties content[i]._lists_ = { @@ -182,6 +182,10 @@ function package.doNestedList (_, listType, options, content) else SILE.typesetter:leaveHmode() end + -- Whitespace left around comment nodes is fine too + elseif type(content[i]) == "table" and #content[i] == 0 then + -- ignore whitespace leaking in from in front of indented comments + assert(true) elseif type(content[i]) == "string" then -- All text nodes are ignored in structure tags, but just warn -- if there do not just consist in spaces. @@ -230,28 +234,28 @@ function package.declareSettings (_) SILE.settings:declare({ parameter = "lists.enumerate.leftmargin", type = "measurement", - default = SILE.measurement("2em"), + default = SILE.types.measurement("2em"), help = "Left margin (indentation) for enumerations" }) SILE.settings:declare({ parameter = "lists.enumerate.labelindent", type = "measurement", - default = SILE.measurement("0.5em"), + default = SILE.types.measurement("0.5em"), help = "Label indentation for enumerations" }) SILE.settings:declare({ parameter = "lists.itemize.leftmargin", type = "measurement", - default = SILE.measurement("1.5em"), + default = SILE.types.measurement("1.5em"), help = "Left margin (indentation) for bullet lists (itemize)" }) SILE.settings:declare({ parameter = "lists.parskip", type = "vglue", - default = SILE.nodefactory.vglue("0pt plus 1pt"), + default = SILE.types.node.vglue("0pt plus 1pt"), help = "Leading between paragraphs and items in a list" }) diff --git a/packages/masters/init.lua b/packages/masters/init.lua index 664c7b3faf..90419bfa58 100644 --- a/packages/masters/init.lua +++ b/packages/masters/init.lua @@ -5,20 +5,20 @@ package._name = "masters" local _currentMaster -local function defineMaster (_, args) - SU.required(args, "id", "defining master") - SU.required(args, "frames", "defining master") - SU.required(args, "firstContentFrame", "defining master") - SILE.scratch.masters[args.id] = { frames = {}, firstContentFrame = nil } - for frame, spec in pairs(args.frames) do +local function defineMaster (_, options) + SU.required(options, "id", "defining master") + SU.required(options, "frames", "defining master") + SU.required(options, "firstContentFrame", "defining master") + SILE.scratch.masters[options.id] = { frames = {}, firstContentFrame = nil } + for frame, spec in pairs(options.frames) do spec.id = frame if spec.solve then - SILE.scratch.masters[args.id].frames[frame] = spec + SILE.scratch.masters[options.id].frames[frame] = spec else - SILE.scratch.masters[args.id].frames[frame] = SILE.newFrame(spec) + SILE.scratch.masters[options.id].frames[frame] = SILE.newFrame(spec) end end - SILE.scratch.masters[args.id].firstContentFrame = SILE.scratch.masters[args.id].frames[args.firstContentFrame] + SILE.scratch.masters[options.id].firstContentFrame = SILE.scratch.masters[options.id].frames[options.firstContentFrame] end local function defineMasters (class, list) @@ -37,9 +37,8 @@ local function doswitch (frames) end end -local function switchMasterOnePage (class, id) +local function switchMasterOnePage (_, id) if not id then - id = class SU.deprecated("class.switchMasterOnePage", "class:switchMasterOnePage", "0.13.0", "0.15.0") end if not SILE.scratch.masters[id] then @@ -53,7 +52,6 @@ end local function switchMaster (class, id) if not id then - id, class = class, SILE.documentState.documentClass SU.deprecated("class.switchMaster", "class:switchMaster", "0.13.0", "0.15.0") end _currentMaster = id diff --git a/packages/math/base-elements.lua b/packages/math/base-elements.lua index 2a57d3367f..fc0f517f79 100644 --- a/packages/math/base-elements.lua +++ b/packages/math/base-elements.lua @@ -1,4 +1,4 @@ -local nodefactory = require("core.nodefactory") +local nodefactory = require("types.node") local hb = require("justenoughharfbuzz") local ot = require("core.opentype-parser") local syms = require("packages.math.unicode-symbols") @@ -192,7 +192,7 @@ local function getRightMostGlyphId(node) end end --- Compares two SILE length, without considering shrink or stretch values, and +-- Compares two SILE.types.length, without considering shrink or stretch values, and -- returns the biggest. local function maxLength(...) local arg = {...} @@ -237,8 +237,8 @@ function elements.mbox:_init () nodefactory.hbox._init(self) self.font = {} self.children = {} -- The child nodes - self.relX = SILE.length(0) -- x position relative to its parent box - self.relY = SILE.length(0) -- y position relative to its parent box + self.relX = SILE.types.length(0) -- x position relative to its parent box + self.relY = SILE.types.length(0) -- y position relative to its parent box self.value = {} self.mode = mathMode.display self.atom = atomType.ordinary @@ -437,11 +437,11 @@ function elements.stackbox:shape () -- 1. set self.width to max element width -- 2. set self.height -- And finally set children's relative coordinates - self.height = SILE.length(0) - self.depth = SILE.length(0) + self.height = SILE.types.length(0) + self.depth = SILE.types.length(0) if self.direction == "H" then for i, n in ipairs(self.children) do - n.relY = SILE.length(0) + n.relY = SILE.types.length(0) self.height = i == 1 and n.height or maxLength(self.height, n.height) self.depth = i == 1 and n.depth or maxLength(self.depth, n.depth) end @@ -453,14 +453,14 @@ function elements.stackbox:shape () end end -- Set self.width - self.width = SILE.length(0) + self.width = SILE.types.length(0) for i, n in ipairs(self.children) do n.relX = self.width self.width = i == 1 and n.width or self.width + n.width end else -- self.direction == "V" for i, n in ipairs(self.children) do - n.relX = SILE.length(0) + n.relX = SILE.types.length(0) self.width = i == 1 and n.width or maxLength(self.width, n.width) end -- Set self.height and self.depth @@ -531,12 +531,12 @@ function elements.subscript:shape () local constants = mathMetrics.constants local scaleDown = self:getScaleDown() if self.base then - self.base.relX = SILE.length(0) - self.base.relY = SILE.length(0) + self.base.relX = SILE.types.length(0) + self.base.relY = SILE.types.length(0) -- Use widthForSubscript of base, if available self.width = self.base.widthForSubscript or self.base.width else - self.width = SILE.length(0) + self.width = SILE.types.length(0) end local itCorr = self:calculateItalicsCorrection() * scaleDown local subShift @@ -550,7 +550,7 @@ function elements.subscript:shape () subShift = 0 end self.sub.relX = self.width + subShift - self.sub.relY = SILE.length(math.max( + self.sub.relY = SILE.types.length(math.max( constants.subscriptShiftDown * scaleDown, --self.base.depth + constants.subscriptBaselineDropMin * scaleDown, (self.sub.height - constants.subscriptTopMax * scaleDown):tonumber() @@ -570,7 +570,7 @@ function elements.subscript:shape () supShift = itCorr end self.sup.relX = self.width + supShift - self.sup.relY = SILE.length(math.max( + self.sup.relY = SILE.types.length(math.max( isCrampedMode(self.mode) and constants.superscriptShiftUpCramped * scaleDown or constants.superscriptShiftUp * scaleDown, -- or cramped @@ -601,18 +601,18 @@ function elements.subscript:shape () end end self.width = self.width + maxLength( - self.sub and self.sub.width + subShift or SILE.length(0), - self.sup and self.sup.width + supShift or SILE.length(0) + self.sub and self.sub.width + subShift or SILE.types.length(0), + self.sup and self.sup.width + supShift or SILE.types.length(0) ) + constants.spaceAfterScript * scaleDown self.height = maxLength( - self.base and self.base.height or SILE.length(0), - self.sub and (self.sub.height - self.sub.relY) or SILE.length(0), - self.sup and (self.sup.height - self.sup.relY) or SILE.length(0) + self.base and self.base.height or SILE.types.length(0), + self.sub and (self.sub.height - self.sub.relY) or SILE.types.length(0), + self.sup and (self.sup.height - self.sup.relY) or SILE.types.length(0) ) self.depth = maxLength( - self.base and self.base.depth or SILE.length(0), - self.sub and (self.sub.depth + self.sub.relY) or SILE.length(0), - self.sup and (self.sup.depth + self.sup.relY) or SILE.length(0) + self.base and self.base.depth or SILE.types.length(0), + self.sub and (self.sub.depth + self.sub.relY) or SILE.types.length(0), + self.sup and (self.sup.depth + self.sup.relY) or SILE.types.length(0) ) end @@ -656,15 +656,15 @@ function elements.underOver:shape () local scaleDown = self:getScaleDown() -- Determine relative Ys if self.base then - self.base.relY = SILE.length(0) + self.base.relY = SILE.types.length(0) end if self.sub then - self.sub.relY = self.base.depth + SILE.length(math.max( + self.sub.relY = self.base.depth + SILE.types.length(math.max( (self.sub.height + constants.lowerLimitGapMin * scaleDown):tonumber(), constants.lowerLimitBaselineDropMin * scaleDown)) end if self.sup then - self.sup.relY = 0 - self.base.height - SILE.length(math.max( + self.sup.relY = 0 - self.base.height - SILE.types.length(math.max( (constants.upperLimitGapMin * scaleDown + self.sup.depth):tonumber(), constants.upperLimitBaselineRiseMin * scaleDown)) end @@ -699,7 +699,7 @@ function elements.underOver:shape () b = nil end end - widest.relX = SILE.length(0) + widest.relX = SILE.types.length(0) local c = widest.width / 2 if a then a.relX = c - a.width / 2 end if b then b.relX = c - b.width / 2 end @@ -708,9 +708,9 @@ function elements.underOver:shape () if self.sub then self.sub.relX = self.sub.relX - itCorr / 2 end -- Determine width and height self.width = maxLength( - self.base and self.base.width or SILE.length(0), - self.sub and self.sub.width or SILE.length(0), - self.sup and self.sup.width or SILE.length(0) + self.base and self.base.width or SILE.types.length(0), + self.sub and self.sub.width or SILE.types.length(0), + self.sup and self.sup.width or SILE.types.length(0) ) if self.sup then self.height = 0 - self.sup.relY + self.sup.height @@ -777,14 +777,14 @@ local function getStandardLength (value) direction = -1 end if value == "thin" then - return SILE.length("3mu") * direction + return SILE.types.length("3mu") * direction elseif value == "med" then - return SILE.length("4mu plus 2mu minus 4mu") * direction + return SILE.types.length("4mu plus 2mu minus 4mu") * direction elseif value == "thick" then - return SILE.length("5mu plus 5mu") * direction + return SILE.types.length("5mu plus 5mu") * direction end end - return SILE.length(value) + return SILE.types.length(value) end function elements.space:_init (width, height, depth) @@ -900,8 +900,8 @@ function elements.text:shape () for i = 1, #glyphs do table.insert(self.value.glyphString, glyphs[i].gid) end - self.width = SILE.length(0) - self.widthForSubscript = SILE.length(0) + self.width = SILE.types.length(0) + self.widthForSubscript = SILE.types.length(0) for i = #glyphs, 1, -1 do self.width = self.width + glyphs[i].glyphAdvance end @@ -912,13 +912,13 @@ function elements.text:shape () self.width = self.width + itCorr * self:getScaleDown() end for i = 1, #glyphs do - self.height = i == 1 and SILE.length(glyphs[i].height) or SILE.length(math.max(self.height:tonumber(), glyphs[i].height)) - self.depth = i == 1 and SILE.length(glyphs[i].depth) or SILE.length(math.max(self.depth:tonumber(), glyphs[i].depth)) + self.height = i == 1 and SILE.types.length(glyphs[i].height) or SILE.types.length(math.max(self.height:tonumber(), glyphs[i].height)) + self.depth = i == 1 and SILE.types.length(glyphs[i].depth) or SILE.types.length(math.max(self.depth:tonumber(), glyphs[i].depth)) end else - self.width = SILE.length(0) - self.height = SILE.length(0) - self.depth = SILE.length(0) + self.width = SILE.types.length(0) + self.height = SILE.types.length(0) + self.depth = SILE.types.length(0) end end @@ -955,7 +955,7 @@ function elements.text:stretchyReshape (depth, height) m = diff end end - SU.debug("math", "stretch: closestI =", tostring(closestI)) + SU.debug("math", "stretch: closestI =", closestI) if closest then -- Now we have to re-shape the glyph chain. We will assume there -- is only one glyph. @@ -970,9 +970,9 @@ function elements.text:stretchyReshape (depth, height) glyphs[1].height = dimen.height glyphs[1].depth = dimen.depth glyphs[1].glyphAdvance = dimen.glyphAdvance - self.width = SILE.length(dimen.glyphAdvance) - self.depth = SILE.length(dimen.depth) - self.height = SILE.length(dimen.height) + self.width = SILE.types.length(dimen.glyphAdvance) + self.depth = SILE.types.length(dimen.depth) + self.height = SILE.types.length(dimen.height) SILE.shaper:preAddNodes(glyphs, self.value) self.value.items = glyphs self.value.glyphString = {glyphs[1].gid} @@ -986,7 +986,7 @@ function elements.text:output (x, y, line) if isDisplayMode(self.mode) and self.atom == atomType.bigOperator and self.value.items[1].fontDepth then - compensatedY = SILE.length(y.length + self.value.items[1].depth - self.value.items[1].fontDepth) + compensatedY = SILE.types.length(y.length + self.value.items[1].depth - self.value.items[1].fontDepth) else compensatedY = y end @@ -1027,7 +1027,7 @@ function elements.fraction:shape () else widest, other = self.numerator, self.denominator end - widest.relX = SILE.length(0) + widest.relX = SILE.types.length(0) other.relX = (widest.width - other.width) / 2 self.width = widest.width -- Determine relative ordinates and height @@ -1036,24 +1036,24 @@ function elements.fraction:shape () self.axisHeight = constants.axisHeight * scaleDown self.ruleThickness = constants.fractionRuleThickness * scaleDown if isDisplayMode(self.mode) then - self.numerator.relY = -self.axisHeight - self.ruleThickness/2 - SILE.length(math.max( + self.numerator.relY = -self.axisHeight - self.ruleThickness/2 - SILE.types.length(math.max( (constants.fractionNumDisplayStyleGapMin*scaleDown + self.numerator.depth):tonumber(), constants.fractionNumeratorDisplayStyleShiftUp * scaleDown - self.axisHeight - self.ruleThickness/2)) else - self.numerator.relY = -self.axisHeight - self.ruleThickness/2 - SILE.length(math.max( + self.numerator.relY = -self.axisHeight - self.ruleThickness/2 - SILE.types.length(math.max( (constants.fractionNumeratorGapMin*scaleDown + self.numerator.depth):tonumber(), constants.fractionNumeratorShiftUp * scaleDown - self.axisHeight - self.ruleThickness/2)) end if isDisplayMode(self.mode) then - self.denominator.relY = -self.axisHeight + self.ruleThickness/2 + SILE.length(math.max( + self.denominator.relY = -self.axisHeight + self.ruleThickness/2 + SILE.types.length(math.max( (constants.fractionDenomDisplayStyleGapMin * scaleDown + self.denominator.height):tonumber(), constants.fractionDenominatorDisplayStyleShiftDown * scaleDown + self.axisHeight - self.ruleThickness/2)) else - self.denominator.relY = -self.axisHeight + self.ruleThickness/2 + SILE.length(math.max( + self.denominator.relY = -self.axisHeight + self.ruleThickness/2 + SILE.types.length(math.max( (constants.fractionDenominatorGapMin * scaleDown + self.denominator.height):tonumber(), constants.fractionDenominatorShiftDown * scaleDown @@ -1115,10 +1115,10 @@ function elements.table:_init (children, options) self.ncols = math.max(pl.utils.unpack(mapList(function(_, row) return #row.children end, self.children))) SU.debug("math", "self.ncols =", self.ncols) - self.rowspacing = self.options.rowspacing and SILE.length(self.options.rowspacing) - or SILE.length("7pt") - self.columnspacing = self.options.columnspacing and SILE.length(self.options.columnspacing) - or SILE.length("6pt") + self.rowspacing = self.options.rowspacing and SILE.types.length(self.options.rowspacing) + or SILE.types.length("7pt") + self.columnspacing = self.options.columnspacing and SILE.types.length(self.options.columnspacing) + or SILE.types.length("6pt") -- Pad rows that do not have enough cells by adding cells to the -- right. for i,row in ipairs(self.children) do @@ -1167,30 +1167,30 @@ function elements.table:shape () -- height (resp. depth) among its elements. Then we only need to add it to -- the table's height and center every cell vertically. for _,row in ipairs(self.children) do - row.height = SILE.length(0) - row.depth = SILE.length(0) + row.height = SILE.types.length(0) + row.depth = SILE.types.length(0) for _,cell in ipairs(row.children) do row.height = maxLength(row.height, cell.height) row.depth = maxLength(row.depth, cell.depth) end end - self.vertSize = SILE.length(0) + self.vertSize = SILE.types.length(0) for i, row in ipairs(self.children) do self.vertSize = self.vertSize + row.height + row.depth + - (i == self.nrows and SILE.length(0) or self.rowspacing) -- Spacing + (i == self.nrows and SILE.types.length(0) or self.rowspacing) -- Spacing end - local rowHeightSoFar = SILE.length(0) + local rowHeightSoFar = SILE.types.length(0) for i, row in ipairs(self.children) do row.relY = rowHeightSoFar + row.height - self.vertSize rowHeightSoFar = rowHeightSoFar + row.height + row.depth + - (i == self.nrows and SILE.length(0) or self.rowspacing) -- Spacing + (i == self.nrows and SILE.types.length(0) or self.rowspacing) -- Spacing end - self.width = SILE.length(0) - local thisColRelX = SILE.length(0) + self.width = SILE.types.length(0) + local thisColRelX = SILE.types.length(0) -- For every column... for i = 1,self.ncols do -- Determine its width - local columnWidth = SILE.length(0) + local columnWidth = SILE.types.length(0) for j = 1,self.nrows do if self.children[j].children[i].width > columnWidth then columnWidth = self.children[j].children[i].width @@ -1210,7 +1210,7 @@ function elements.table:shape () end end thisColRelX = thisColRelX + columnWidth + - (i == self.ncols and SILE.length(0) or self.columnspacing) -- Spacing + (i == self.ncols and SILE.types.length(0) or self.columnspacing) -- Spacing end self.width = thisColRelX -- Center myself vertically around the axis, and update relative Ys of rows accordingly diff --git a/packages/math/init.lua b/packages/math/init.lua index 8e7229fa2f..3509dcd97f 100644 --- a/packages/math/init.lua +++ b/packages/math/init.lua @@ -54,7 +54,7 @@ function package.declareSettings (_) SILE.settings:declare({ parameter = "math.displayskip", type = "VGlue", - default = SILE.nodefactory.vglue("2ex plus 1pt") + default = SILE.types.node.vglue("2ex plus 1pt") }) end @@ -65,7 +65,7 @@ function package:registerCommands () local mode = (options and options.mode) and options.mode or 'text' local mbox xpcall(function() - mbox = self:ConvertMathML(content, mbox) + mbox = self:ConvertMathML(content) end, function(err) print(err); print(debug.traceback()) end) self:handleMath(mbox, mode) end) diff --git a/packages/math/texlike.lua b/packages/math/texlike.lua index 04224f4872..c57a091cd9 100644 --- a/packages/math/texlike.lua +++ b/packages/math/texlike.lua @@ -86,8 +86,8 @@ local mathGrammar = function (_ENV) return pl.utils.unpack(t) end - START "texlike_math" - texlike_math = V"mathlist" * EOF"Unexpected character at end of math code" + START "math" + math = V"mathlist" * EOF"Unexpected character at end of math code" mathlist = (comment + (WS * _) + element)^0 supsub = element_no_infix * _ * P"^" * _ * element_no_infix * _ * P"_" * _ * element_no_infix @@ -254,7 +254,7 @@ local function compileToMathML_aux (_, arg_env, tree) return accumulator end tree = fold_pairs(compile_and_insert, tree) - if tree.id == "texlike_math" then + if tree.id == "math" then tree.command = "math" -- If the outermost `mrow` contains only other `mrow`s, remove it -- (allowing vertical stacking). diff --git a/packages/pagebuilder-bestfit/init.lua b/packages/pagebuilder-bestfit/init.lua index 7819065dca..917c675435 100644 --- a/packages/pagebuilder-bestfit/init.lua +++ b/packages/pagebuilder-bestfit/init.lua @@ -11,7 +11,7 @@ function package:_init () -- Find last penalty local q = typesetter.state.outputQueue local lastpenalty = -1 - local cHeight = SILE.length() + local cHeight = SILE.types.length() for j = #q,1,-1 do if q[j].is_penalty and lastpenalty == -1 then lastpenalty = q[j].penalty diff --git a/packages/pandoc/init.lua b/packages/pandoc/init.lua index c3fc868d47..f157a1db49 100644 --- a/packages/pandoc/init.lua +++ b/packages/pandoc/init.lua @@ -3,10 +3,9 @@ local base = require("packages.base") local package = pl.class(base) package._name = "pandoc" --- Process arguments that might not actually have that much to do with their --- immediate function but affect the document in other ways, such as setting --- bookmarks on anything tagged with an ID attribute. -local handlePandocArgs = function (options) +-- Process command options that are not actually intended to be options for a specific function but affect the document +-- in other ways, such as setting bookmarks on anything tagged with an ID attribute. +local handlePandocOptions = function (options) local wrapper = SILE.process if options.id then SU.debug("pandoc", "Set ID on tag") @@ -49,6 +48,7 @@ function package:_init () base._init(self) self:loadPackage("footnotes") self:loadPackage("image") + self:loadPackage("lists") self:loadPackage("pdf") self:loadPackage("raiselower") self:loadPackage("rules") @@ -67,21 +67,17 @@ function package:registerCommands () SILE.typesetter:leaveHmode() end) - self:registerCommand("BulletList", function (_, content) - -- luacheck: ignore pandocListType - local pandocListType = "bullet" - SILE.settings:temporarily(function () - SILE.settings:set("document.rskip","10pt") - SILE.settings:set("document.lskip","20pt") - SILE.process(content) + self:registerCommand("BulletList", function (options, content) + local wrapper, options_ = handlePandocOptions(options) + wrapper(function () + SILE.call("itemize", options_, content) end) - SILE.typesetter:leaveHmode() end) self:registerCommand("CodeBlock", function (options, content) - local wrapper, args = handlePandocArgs(options) + local wrapper, options_ = handlePandocOptions(options) wrapper(function () - SILE.call("verbatim", args, content) + SILE.call("verbatim", options_, content) end) SILE.typesetter:leaveHmode() end) @@ -92,17 +88,17 @@ function package:registerCommands () end) self:registerCommand("Div", function (options, content) - handlePandocArgs(options)(content) + handlePandocOptions(options)(content) SILE.typesetter:leaveHmode() end, "Generic block wrapper") self:registerCommand("Header", function (options, content) local analog = options.type options.level, options.type = nil, nil - local wrapper, args = handlePandocArgs(options) + local wrapper, options_ = handlePandocOptions(options) wrapper(function () if analog and SILE.Commands[analog] then - SILE.call(analog, args, content) + SILE.call(analog, options_, content) else SILE.process(content) end @@ -128,14 +124,11 @@ function package:registerCommands () SILE.typesetter:leaveHmode() end) - self:registerCommand("OrderedList", function (_, content) - -- TODO: handle listAttributes - SILE.settings:temporarily(function () - SILE.settings:set("document.rskip","10pt") - SILE.settings:set("document.lskip","20pt") - SILE.process(content) + self:registerCommand("OrderedList", function (options, content) + local wrapper, options_ = handlePandocOptions(options) + wrapper(function () + SILE.call("enumerate", options_, content) end) - SILE.typesetter:leaveHmode() end) self:registerCommand("Para", function (_, content) @@ -175,9 +168,9 @@ function package:registerCommands () end, "Creates a Cite inline element") self:registerCommand("Code", function (options, content) - local wrapper, args = handlePandocArgs(options) + local wrapper, options_ = handlePandocOptions(options) wrapper(function () - SILE.call("code", args, content) + SILE.call("code", options_, content) end) end, "Creates a Code inline element") @@ -186,9 +179,9 @@ function package:registerCommands () end, "Creates an inline element representing emphasised text.") self:registerCommand("Image", function (options, _) - local wrapper, args = handlePandocArgs(options) + local wrapper, options_ = handlePandocOptions(options) wrapper(function () - SILE.call("img", args) + SILE.call("img", options_) end) end, "Creates a Image inline element") @@ -197,9 +190,9 @@ function package:registerCommands () end, "Create a LineBreak inline element") self:registerCommand("Link", function (options, content) - local wrapper, args = handlePandocArgs(options) + local wrapper, options_ = handlePandocOptions(options) wrapper(function () - SILE.call("url", args, content) + SILE.call("url", options_, content) end) end, "Creates a link inline element, usually a hyperlink.") @@ -235,7 +228,7 @@ function package:registerCommands () end, "Creates text rendered in small caps") self:registerCommand("Span", function (options, content) - handlePandocArgs(options)(content) + handlePandocOptions(options)(content) end, "Creates a Span inline element") self:registerCommand("Strikeout", function (_, content) @@ -277,20 +270,11 @@ function package:registerCommands () -- Non native types - self:registerCommand("ListItem", function (_, content) - SILE.call("smallskip") - SILE.call("glue", { width = "-1em"}) - SILE.call("rebox", { width = "1em" }, function () - -- Note: Relies on Lua scope shadowing to find immediate parent list type - -- luacheck: ignore pandocListType - if pandocListType == "bullet" then - SILE.typesetter:typeset("•") - else - SILE.typesetter:typeset("-") - end + self:registerCommand("ListItem", function (options, content) + local wrapper, options_ = handlePandocOptions(options) + wrapper(function () + SILE.call("item", options_, content) end) - SILE.process(content) - SILE.call("smallskip") end) self:registerCommand("ListItemTerm", function (_, content) diff --git a/packages/parallel/init.lua b/packages/parallel/init.lua index 70bc7f6d3e..55c9ff605e 100644 --- a/packages/parallel/init.lua +++ b/packages/parallel/init.lua @@ -76,7 +76,7 @@ from another package, responsible for correct initialization.]]) end -- Fixed leading here is obviously a bug, but n-way leading calculations -- get very complicated... - -- typesetterPool[frame].leadingFor = function() return SILE.nodefactory.vglue(SILE.settings:get("document.lineskip")) end + -- typesetterPool[frame].leadingFor = function() return SILE.types.node.vglue(SILE.settings:get("document.lineskip")) end local fontcommand = frame .. ":font" self:registerCommand(frame, function (_, _) -- \left ... SILE.typesetter = typesetterPool[frame] @@ -113,7 +113,7 @@ function package:registerCommands () self:registerCommand("sync", function (_, _) local anybreak = false - local maxheight = SILE.length() + local maxheight = SILE.types.length() SU.debug("parallel", "Trying a sync") allTypesetters(function (_, typesetter) SU.debug("parallel", "Leaving hmode on", typesetter.id) @@ -133,7 +133,7 @@ function package:registerCommands () end allTypesetters(function (frame, typesetter) - calculations[frame].heightOfNewMaterial = SILE.length() + calculations[frame].heightOfNewMaterial = SILE.types.length() for i = calculations[frame].mark + 1, #typesetter.state.outputQueue do local thisHeight = typesetter.state.outputQueue[i].height + typesetter.state.outputQueue[i].depth calculations[frame].heightOfNewMaterial = calculations[frame].heightOfNewMaterial + thisHeight diff --git a/packages/pdf/init.lua b/packages/pdf/init.lua index f6622a3bce..93b92f845a 100644 --- a/packages/pdf/init.lua +++ b/packages/pdf/init.lua @@ -1,44 +1,19 @@ +--- pdf package +--- @use packages.pdf + +-- This package and its commands are perhaps ill-named: +-- Exception made of the pdf:literal command below, the concepts of links +-- (anchor, target), bookmarks, and metadata are not specific to PDF. +-- local base = require("packages.base") local package = pl.class(base) package._name = "pdf" -local pdf - -local function borderColor (color) - if color then - if color.r then return "/C [" .. color.r .. " " .. color.g .. " " .. color.b .. "]" end - if color.c then return "/C [" .. color.c .. " " .. color.m .. " " .. color.y .. " " .. color.k .. "]" end - if color.l then return "/C [" .. color.l .. "]" end - end - return "" -end - -local function borderStyle (style, width) - if style == "underline" then return "/BS<>" end - if style == "dashed" then return "/BS<>" end - return "/Border[0 0 " .. width .. "]" -end - -local function validate_date (date) - return string.match(date, [[^D:%d+%s*-%s*%d%d%s*'%s*%d%d%s*'?$]]) ~= nil -end - -function package:_init () - base._init(self) - pdf = require("justenoughlibtexpdf") - if SILE.outputter._name ~= "libtexpdf" then - SU.error("pdf package requires libtexpdf backend") - end -end - function package:registerCommands () self:registerCommand("pdf:destination", function (options, _) local name = SU.required(options, "name", "pdf:destination") - if type(SILE.outputter._ensureInit) == "function" then - SILE.outputter:_ensureInit() - end SILE.typesetter:pushHbox({ outputYourself = function (_, typesetter, line) local state = typesetter.frame.state @@ -46,7 +21,7 @@ function package:registerCommands () local x, y = state.cursorX, state.cursorY typesetter.frame:advancePageDirection(line.height) local _y = SILE.documentState.paperSize[2] - y - pdf.destination(name, x:tonumber(), _y:tonumber()) + SILE.outputter:setLinkAnchor(name, x, _y) end }) end) @@ -54,82 +29,50 @@ function package:registerCommands () self:registerCommand("pdf:bookmark", function (options, _) local dest = SU.required(options, "dest", "pdf:bookmark") local title = SU.required(options, "title", "pdf:bookmark") - local level = options.level or 1 - -- Added UTF8 to UTF16-BE conversion - -- For annotations and bookmarks, text strings must be encoded using - -- either PDFDocEncoding or UTF16-BE with a leading byte-order marker. - -- As PDFDocEncoding supports only limited character repertoire for - -- European languages, we use UTF-16BE for internationalization. - local ustr = SU.utf8_to_utf16be_hexencoded(title) - if type(SILE.outputter._ensureInit) == "function" then - SILE.outputter:_ensureInit() - end + local level = SU.cast("integer", options.level or 1) SILE.typesetter:pushHbox({ value = nil, - height = SILE.measurement(0), - width = SILE.measurement(0), - depth = SILE.measurement(0), + height = SILE.types.measurement(0), + width = SILE.types.measurement(0), + depth = SILE.types.measurement(0), outputYourself = function () - local d = "</A<>>>" - pdf.bookmark(d, level) - end - }) - end) - - self:registerCommand("pdf:literal", function (_, content) - if type(SILE.outputter._ensureInit) == "function" then - SILE.outputter:_ensureInit() - end - SILE.typesetter:pushHbox({ - value = nil, - height = SILE.measurement(0), - width = SILE.measurement(0), - depth = SILE.measurement(0), - outputYourself = function (_, _, _) - pdf.add_content(content[1]) + SILE.outputter:setBookmark(dest, title, level) end }) end) + -- TODO: Shim to pdfannotations package + -- self:registerCommand("pdf:literal", function (_, content) self:registerCommand("pdf:link", function (options, content) local dest = SU.required(options, "dest", "pdf:link") - local target = options.external and "/Type/Action/S/URI/URI" or "/S/GoTo/D" + local external = SU.boolean(options.external, false) local borderwidth = options.borderwidth and SU.cast("measurement", options.borderwidth):tonumber() or 0 - local bordercolor = borderColor(SILE.color(options.bordercolor or "blue")) + local bordercolor = SILE.types.color(options.bordercolor or "blue") local borderoffset = SU.cast("measurement", options.borderoffset or "1pt"):tonumber() - local borderstyle = borderStyle(options.borderstyle, borderwidth) - local llx, lly - if type(SILE.outputter._ensureInit) == "function" then - SILE.outputter:_ensureInit() - end - SILE.typesetter:pushHbox({ - value = nil, - height = SILE.measurement(0), - width = SILE.measurement(0), - depth = SILE.measurement(0), - outputYourself = function (_, typesetter, _) - llx = typesetter.frame.state.cursorX:tonumber() - lly = (SILE.documentState.paperSize[2] - typesetter.frame.state.cursorY):tonumber() - pdf.begin_annotation() + local opts = { + external = external, + borderstyle = options.borderstyle, + bordercolor = bordercolor, + borderwidth = borderwidth, + borderoffset = borderoffset + } + + SILE.typesetter:liner("pdf:link", content, + function (box, typesetter, line) + local x0 = typesetter.frame.state.cursorX:tonumber() + local y0 = (SILE.documentState.paperSize[2] - typesetter.frame.state.cursorY):tonumber() + SILE.outputter:beginLink(dest, opts) + + -- Build the content. + -- Cursor will be moved by the actual definitive size. + box:outputContent(typesetter, line) + local x1 = typesetter.frame.state.cursorX:tonumber() + local y1 = (SILE.documentState.paperSize[2] - typesetter.frame.state.cursorY + box.height):tonumber() + + SILE.outputter:endLink(dest, opts, x0, y0, x1, y1) -- Unstable API end - }) - - local hbox, hlist = SILE.typesetter:makeHbox(content) -- hack - SILE.typesetter:pushHbox(hbox) - SILE.typesetter:pushHlist(hlist) + ) - SILE.typesetter:pushHbox({ - value = nil, - height = SILE.measurement(0), - width = SILE.measurement(0), - depth = SILE.measurement(0), - outputYourself = function (_, typesetter, _) - local d = "<>>>" - local x = typesetter.frame.state.cursorX:tonumber() - local y = (SILE.documentState.paperSize[2] - typesetter.frame.state.cursorY + hbox.height):tonumber() - pdf.end_annotation(d, llx , lly - borderoffset, x, y + borderoffset) - end - }) end) self:registerCommand("pdf:metadata", function (options, _) @@ -139,32 +82,7 @@ function package:registerCommands () end local value = SU.required(options, "value", "pdf:metadata") - if key == "Trapped" then - SU.warn("Skipping special metadata key \\Trapped") - return - end - - if key == "ModDate" or key == "CreationDate" then - if not validate_date(value) then - SU.warn("Invalid date: " .. value) - return - end - else - -- see comment in pdf:bookmark - value = SU.utf8_to_utf16be(value) - end - if type(SILE.outputter._ensureInit) == "function" then - SILE.outputter:_ensureInit() - end - SILE.typesetter:pushHbox({ - value = nil, - height = SILE.measurement(0), - width = SILE.measurement(0), - depth = SILE.measurement(0), - outputYourself = function (_, _, _) - pdf.metadata(key, value) - end - }) + SILE.outputter:setMetadata(key, value) end) end @@ -174,15 +92,22 @@ package.documentation = [[ The \autodoc:package{pdf} package enables basic support for PDF links and table-of-contents entries. It provides the four commands \autodoc:command{\pdf:destination}, \autodoc:command{\pdf:link}, \autodoc:command{\pdf:bookmark}, and \autodoc:command{\pdf:metadata}. -The \autodoc:command{\pdf:destination} parameter creates a link target; it expects a parameter called \autodoc:parameter{name} to uniquely identify the target. +The \autodoc:command{\pdf:destination} parameter creates a link target; + it expects a parameter called \autodoc:parameter{name} to uniquely identify the target. To create a link to that location in the document, use \autodoc:command{\pdf:link[dest=]{}}. -The \autodoc:command{\pdf:link} command accepts several options defining its border style: a \autodoc:parameter{borderwidth} length setting the border width (defaults to \code{0}, meaning no border), a \autodoc:parameter{borderstyle} string (can be set to \code{underline} or \code{dashed}, otherwise a solid box), a \autodoc:parameter{bordercolor} color specification for this border (defaults to \code{blue}), and finally a \autodoc:parameter{borderoffset} length for adjusting the border with some vertical space above the content and below the baseline (defaults to \code{1pt}). +The \autodoc:command{\pdf:link} command accepts several options defining its border style: + a \autodoc:parameter{borderwidth} length setting the border width (defaults to \code{0}, meaning no border), + a \autodoc:parameter{borderstyle} string (can be set to \code{underline} or \code{dashed}, otherwise a solid box), + a \autodoc:parameter{bordercolor} color specification for this border (defaults to \code{blue}), + and finally a \autodoc:parameter{borderoffset} length for adjusting the border with some vertical space above the content and below the baseline (defaults to \code{1pt}). Note that PDF renderers may vary on how they honor these border styling features on link annotations. It also has an \autodoc:parameter{external} option for URL links, which is not intended to be used directly—refer to the \autodoc:package{url} package for more flexibility typesetting external links. -To set arbitrary key-value metadata, use something like \autodoc:command{\pdf:metadata[key=Author, value=J. Smith]}. The PDF metadata field names are case-sensitive. Common keys include \code{Title}, \code{Author}, \code{Subject}, \code{Keywords}, \code{CreationDate}, and \code{ModDate}. +To set arbitrary key-value metadata, use something like \autodoc:command{\pdf:metadata[key=Author, value=J. Smith]}. +The PDF metadata field names are case-sensitive. +Common keys include \code{Title}, \code{Author}, \code{Subject}, \code{Keywords}, \code{CreationDate}, and \code{ModDate}. \end{document} ]] diff --git a/packages/pdfstructure/init.lua b/packages/pdfstructure/init.lua index 4dbfbc7c45..517517f406 100644 --- a/packages/pdfstructure/init.lua +++ b/packages/pdfstructure/init.lua @@ -79,7 +79,7 @@ function package:_init () local stRoot = stNode("Document") stPointer = stRoot self:loadPackage("pdf") - function SILE.outputters.libtexpdf._endHook (_) + SILE.outputter:registerHook("prefinish", function() local catalog = pdf.get_dictionary("Catalog") local structureTree = pdf.parse("<< /Type /StructTreeRoot >>") pdf.add_dict(catalog, pdf.parse("/StructTreeRoot"), pdf.reference(structureTree)) @@ -88,7 +88,7 @@ function package:_init () pdf.add_dict(structureTree, pdf.parse("/K"), dumpTree(stRoot)) if structureNumberTree then pdf.release(structureNumberTree) end if structureTree then pdf.release(structureTree) end - end + end) end function package:registerCommands () @@ -119,6 +119,24 @@ function package:registerCommands () stPointer = oldstPointer end) + self:registerCommand("pdf:literal", function (_, content) + -- NOTE: This method is used by the pdfstructure package and should + -- probably be moved elsewhere, so there's no attempt here to delegate + -- the low-level libtexpdf call to te outputter. + if SILE.outputter._name ~= "libtexpdf" then + SU.error("pdf package requires libtexpdf backend") + end + SILE.typesetter:pushHbox({ + value = nil, + height = SILE.types.measurement(0), + width = SILE.types.measurement(0), + depth = SILE.types.measurement(0), + outputYourself = function (_, _, _) + SILE.outputter:drawRaw (content[1]) + end + }) + end) + end package.documentation = [[ @@ -127,7 +145,8 @@ package.documentation = [[ \pdf:structure[type=P]{% For PDF documents to be considered accessible, they must contain a description of the PDF’s document structure. This package allows structure trees to be created and saved to the PDF file. -Currently this provides a low-level interface to creating nodes in the tree; classes which require PDF accessibility should use the \autodoc:command{\pdf:structure} command in their sectioning implementation to declare the document structure. +Currently this provides a low-level interface to creating nodes in the tree; + classes which require PDF accessibility should use the \autodoc:command{\pdf:structure} command in their sectioning implementation to declare the document structure. } \pdf:structure[type=P]{% diff --git a/packages/pullquote/init.lua b/packages/pullquote/init.lua index 251b1c0eec..fa82f5f2c1 100644 --- a/packages/pullquote/init.lua +++ b/packages/pullquote/init.lua @@ -13,7 +13,7 @@ local typesetMark = function (open, setback, scale, color, mark) SILE.typesetter:pushGlue({ width = -setback }) SILE.call("rebox", { width = setback, height = 0 }, { mark }) else - SILE.typesetter:pushGlue(SILE.nodefactory.hfillglue()) + SILE.typesetter:pushGlue(SILE.types.node.hfillglue()) local hbox = SILE.typesetter:makeHbox({ mark }) -- for measuring SILE.typesetter:pushGlue({ width = setback - hbox.width }) SILE.call("rebox", { width = hbox.width, height = 0 }, { mark }) @@ -52,8 +52,8 @@ function package:registerCommands () SILE.settings:temporarily(function () SILE.call("pullquote:font") local setback = SU.cast("length", options.setback or "2em"):absolute() - SILE.settings:set("document.rskip", SILE.nodefactory.glue(setback)) - SILE.settings:set("document.lskip", SILE.nodefactory.glue(setback)) + SILE.settings:set("document.rskip", SILE.types.node.glue(setback)) + SILE.settings:set("document.lskip", SILE.types.node.glue(setback)) SILE.call("noindent") typesetMark(true, setback, scale, color, "“") SILE.call("indent") diff --git a/packages/rebox/init.lua b/packages/rebox/init.lua index ce75c7086d..eba0548343 100644 --- a/packages/rebox/init.lua +++ b/packages/rebox/init.lua @@ -7,9 +7,9 @@ function package:registerCommands () self:registerCommand("rebox", function (options, content) local hbox, hlist = SILE.typesetter:makeHbox(content) - if options.width then hbox.width = SILE.length(options.width) end - if options.height then hbox.height = SILE.length(options.height) end - if options.depth then hbox.depth = SILE.length(options.depth) end + if options.width then hbox.width = SILE.types.length(options.width) end + if options.height then hbox.height = SILE.types.length(options.height) end + if options.depth then hbox.depth = SILE.types.length(options.depth) end if options.phantom then hbox.outputYourself = function (node, typesetter, line) typesetter.frame:advanceWritingDirection(node:scaledWidth(line)) diff --git a/packages/retrograde/init.lua b/packages/retrograde/init.lua new file mode 100644 index 0000000000..c678f36d5e --- /dev/null +++ b/packages/retrograde/init.lua @@ -0,0 +1,187 @@ +local base = require("packages.base") + +local package = pl.class(base) +package._name = "retrograde" + +local semver = require("semver") + +local semver_descending = function (a, b) + a, b = semver(a), semver(b) + return a > b +end + +-- Default settings that have gone out of fashion +package.default_settings = { + ["0.15.0"] = { + ["shaper.spaceenlargementfactor"] = 1.2, + ["document.parindent"] = "20pt", + }, + ["0.9.5"] = { + ["font.family"] = "Gentium Basic", + }, +} + +local function _v14_aligns (content) + SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) + SILE.settings:set("document.parindent", SILE.types.node.glue()) + SILE.settings:set("document.spaceskip", SILE.types.length("1spc", 0, 0)) + SILE.process(content) + SILE.call("par") +end + +package.shim_commands = { + ["0.15.0"] = { + ["center"] = function (_) + return function (_, content) + if #SILE.typesetter.state.nodes ~= 0 then + SU.warn("\\center environment started after other nodes in a paragraph, may not center as expected") + end + SILE.settings:temporarily(function () + SILE.settings:set("document.rskip", SILE.types.node.hfillglue()) + SILE.settings:set("document.lskip", SILE.types.node.hfillglue()) + _v14_aligns(content) + end) + end + end, + ["raggedright"] = function (_) + return function (_, content) + SILE.settings:temporarily(function () + SILE.settings:set("document.rskip", SILE.types.node.hfillglue()) + _v14_aligns(content) + end) + end + end, + ["raggedleft"] = function (_) + return function (_, content) + SILE.settings:temporarily(function () + SILE.settings:set("document.lskip", SILE.types.node.hfillglue()) + _v14_aligns(content) + end) + end + end, + } +} + +function package:_init (options) + base._init(self, options) + self:recede(options.target) +end + +function package:recede (target) + self:recede_defaults(target) + self:recede_commands(target) +end + +function package._prep (_, target, type) + target = semver(target and target or SILE.version) + SU.debug("retrograde", ("Targeting %s changes back as far as the release of SILE v%s."):format(type, target)) + local terminal = function (version) + SU.debug("retrograde", ("The next set of %s changes is from the release of SILE v%s, stopping."):format(type, version)) + end + return target, terminal +end + +function package:recede_defaults (target) + local semvertarget, terminal = self:_prep(target, "default") + local target_hit = false + for version, settings in pl.tablex.sort(self.default_settings, semver_descending) do + version = semver(version) + if target_hit then + terminal() + break + end + for parameter, value in pairs(settings) do + SU.debug("retrograde", ("Resetting '%s' to '%s' as it was prior to v%s."):format(parameter, tostring(value), version)) + SILE.settings:set(parameter, value, true) + end + if version <= semvertarget then target_hit = true end + end +end + +function package:recede_commands (target) + local semvertarget, terminal = self:_prep(target, "command") + local currents = {} + local target_hit = false + for version, commands in pl.tablex.sort(self.shim_commands, semver_descending) do + version = semver(version) + if target_hit then + terminal() + break + end + for command, get_function in pairs(commands) do + SU.debug("retrograde", ("Shimming command '%s' to behavior similar to prior to v%s."):format(command, version)) + local current = SILE.Commands[command] + currents[command] = current + SILE.Commands[command] = get_function(current) + end + if version <= semvertarget then target_hit = true end + end + local function reverter () + for command, current in pairs(currents) do + SILE.Commands[command] = current + end + end + return reverter +end + +function package:registerCommands () + + self:registerCommand("recede", function (options, content) + SILE.call("recede-defaults", options, content) + end) + + self:registerCommand("recede-defaults", function (options, content) + if content then + SILE.settings:temporarily(function () + self:recede_defaults(options.target) + SILE.process(content) + end) + else + self:recede_defaults(options.target) + end + end) + + self:registerCommand("recede-commands", function (options, content) + if content then + local reverter = self:recede_commands(options.target) + SILE.process(content) + reverter() + else + self:recede_commands(options.target) + end + end) + +end + +local doctarget = "v" .. tostring(semver(SILE.version)) +package.documentation = ([[ +\begin{document} + +From time to time, the default behavior of a function or value of a setting in SILE might change with a new release. +If these changes are expected to cause document reflows they will be noted in release notes as breaking changes. +That generally means old documents will have to be updated to keep rending the same way. +On a best-effort basis (not a guarantee) this package tries to restore earlier default behaviors and settings. + +For settings this is relatively simple. +You just set the old default value explicitly in your document or project. +But first, knowing what those are requires a careful reading of the release notes. +Then you have to chase down the incantations to set the old values. +This package tries to restore as many previous setting values as possible to make old documents render like they would have in previous releases without changing the documents themselves (beyond loading this package). + +For functions things are a little more complex, but for as many cases as possible we'll try to allow swapping old versions of code. + +None of this is a guarantee that your old document will be stable in new versions of SILE. +All of this is a danger zone. + +From inside a document, use \autodoc:command{\use[module=packages.retrograde,target=%s]} to load features from SILE %s. + +This can also be triggered from the command line with no changes to a document: + +\begin{autodoc:codeblock} +$ sile -u 'packages.retrograde[target=%s]' +\end{autodoc:codeblock} + +\end{document} +]]):format(doctarget, doctarget, doctarget) + +return package diff --git a/packages/rotate/init.lua b/packages/rotate/init.lua index 5982f0c2c8..50abd5931d 100644 --- a/packages/rotate/init.lua +++ b/packages/rotate/init.lua @@ -3,23 +3,25 @@ local base = require("packages.base") local package = pl.class(base) package._name = "rotate" -local pdf = require("justenoughlibtexpdf") - local enter = function (self, _) + -- Probably broken, see: + -- https://github.com/sile-typesetter/sile/issues/427 if not self.rotate then return end - local x = -math.rad(self.rotate) + if not SILE.outputter.enterFrameRotate then + return SU.warn("Frame '".. self.id "' will not be rotated: backend '" .. SILE.outputter._name .. "' does not support rotation") + end + local theta = -math.rad(self.rotate) -- Keep center point the same - pdf:gsave() - local cx = self:left():tonumber() - local cy = -self:bottom():tonumber() - pdf.setmatrix(1, 0, 0, 1, cx + math.sin(x) * self:height():tonumber(), cy) - pdf.setmatrix(math.cos(x), math.sin(x), -math.sin(x), math.cos(x), 0, 0) - pdf.setmatrix(1, 0, 0, 1, -cx, -cy) + local x0 = self:left():tonumber() + local x1 = x0 + math.sin(theta) * self:height():tonumber() + local y0 = self:bottom():tonumber() + SILE.outputter:enterFrameRotate(x0, x1, y0, theta) -- Unstable API end -local leave = function(self, _) +local leave = function(self, _) if not self.rotate then return end - pdf:grestore() + if not SILE.outputter.enterFrameRotate then return end -- no enter no leave. + SILE.outputter:leaveFrameRotate() end -- What is the width, depth and height of a rectangle width w and height h rotated by angle theta? @@ -35,20 +37,20 @@ end local outputRotatedHbox = function (self, typesetter, line) local origbox = self.value.orig - local x = self.value.theta + local theta = self.value.theta + -- Find origin of untransformed hbox - local save = typesetter.frame.state.cursorX - typesetter.frame.state.cursorX = typesetter.frame.state.cursorX - (origbox.width.length-self.width)/2 - - local horigin = (typesetter.frame.state.cursorX + origbox.width.length / 2):tonumber() - local vorigin = -(typesetter.frame.state.cursorY - (origbox.height - origbox.depth) / 2):tonumber() - pdf:gsave() - pdf.setmatrix(1, 0, 0, 1, horigin, vorigin) - pdf.setmatrix(math.cos(x), math.sin(x), -math.sin(x), math.cos(x), 0, 0) - pdf.setmatrix(1, 0, 0, 1, -horigin, -vorigin) - origbox:outputYourself(typesetter, line) - pdf:grestore() - typesetter.frame.state.cursorX = save + local X = typesetter.frame.state.cursorX + local Y = typesetter.frame.state.cursorY + typesetter.frame.state.cursorX = X - (origbox.width.length-self.width)/2 + local horigin = X + origbox.width.length / 2 + local vorigin = Y - (origbox.height - origbox.depth) / 2 + + SILE.outputter:rotateFn(horigin, vorigin, theta, function () + origbox:outputYourself(typesetter, line) + end) + typesetter.frame.state.cursorX = X + typesetter.frame.state.cursorY = Y typesetter.frame:advanceWritingDirection(self.width) end @@ -65,6 +67,10 @@ end function package:registerCommands () self:registerCommand("rotate", function(options, content) + if not SILE.outputter.rotateFn then + SU.warn("Output will not be rotated: backend '" .. SILE.outputter._name .. "' does not support rotation") + return SILE.process(content) + end local angle = SU.required(options, "angle", "rotate command") local theta = -math.rad(angle) local origbox, hlist = SILE.typesetter:makeHbox(content) @@ -91,8 +97,7 @@ function package:registerCommands () depth = 0.5*(h-h*ct-w*st) end depth = -depth - if depth < SILE.length(0) then depth = SILE.length(0) end - SILE.outputter:_ensureInit() + if depth < SILE.types.length(0) then depth = SILE.types.length(0) end SILE.typesetter:pushHbox({ value = { orig = origbox, theta = theta}, height = height, diff --git a/packages/ruby/init.lua b/packages/ruby/init.lua index 0da12ec660..1598e4bc61 100644 --- a/packages/ruby/init.lua +++ b/packages/ruby/init.lua @@ -40,23 +40,34 @@ function package.declareSettings (_) SILE.settings:declare({ parameter = "ruby.height", type = "measurement", - default = SILE.measurement("1zw"), + default = SILE.types.measurement("1zw"), help = "Vertical offset between the ruby and the main text" }) SILE.settings:declare({ parameter = "ruby.latinspacer", type = "glue", - default = SILE.nodefactory.glue("0.25em"), + default = SILE.types.node.glue("0.25em"), help = "Glue added between consecutive Latin ruby" }) + SILE.settings:declare({ + parameter = "ruby.opentype", + type = "boolean", + default = true, + help = "Use OpenType tate feature instead of of a bold weight" + }) + end function package:registerCommands () self:registerCommand("ruby:font", function (_, _) - SILE.call("font", { size = "0.6zw", weight = 800 }) + if SILE.settings:get("ruby.opentype") then + SILE.call("font", { size = "0.6zw", features = "+ruby" }) + else + SILE.call("font", { size = "0.6zw", weight = 700 }) + end end) self:registerCommand("ruby", function (options, content) @@ -92,17 +103,17 @@ function package:registerCommands () if cbox:lineContribution() > rubybox:lineContribution() then SU.debug("ruby", "Base is longer, offsetting ruby to fit") -- This is actually the offset against the base - rubybox.width = SILE.length(cbox:lineContribution() - rubybox:lineContribution())/2 + rubybox.width = SILE.types.length(cbox:lineContribution() - rubybox:lineContribution())/2 else local diff = rubybox:lineContribution() - cbox:lineContribution() - local to_insert = SILE.length(diff / 2) + local to_insert = SILE.types.length(diff / 2) SU.debug("ruby", "Ruby is longer, inserting", to_insert, "either side of base") cbox.width = rubybox:lineContribution() rubybox.height = 0 rubybox.width = 0 -- add spaces at beginning and end - table.insert(cbox.value, 1, SILE.nodefactory.glue(to_insert)) - table.insert(cbox.value, SILE.nodefactory.glue(to_insert)) + table.insert(cbox.value, 1, SILE.types.node.glue(to_insert)) + table.insert(cbox.value, SILE.types.node.glue(to_insert)) end SILE.scratch.lastRubyBox = rubybox SILE.scratch.lastRubyText = reading diff --git a/packages/rules/init.lua b/packages/rules/init.lua index 22f68851bb..229840f57f 100644 --- a/packages/rules/init.lua +++ b/packages/rules/init.lua @@ -27,9 +27,9 @@ end -- \hfill (from the "plain" class) and \leaders (from the "leaders" package) use glues, -- so we behave the same for hrulefill. -local hrulefillglue = pl.class(SILE.nodefactory.hfillglue) -hrulefillglue.raise = SILE.measurement() -hrulefillglue.thickness = SILE.measurement("0.2pt") +local hrulefillglue = pl.class(SILE.types.node.hfillglue) +hrulefillglue.raise = SILE.types.measurement() +hrulefillglue.thickness = SILE.types.measurement("0.2pt") function hrulefillglue:outputYourself (typesetter, line) local outputWidth = SU.rationWidth(self.width, self.width, line.ratio):tonumber() @@ -99,7 +99,7 @@ function package:registerCommands () SILE.typesetter:pushExplicitGlue(hrulefillglue({ raise = raise, - thickness = thickness or SILE.measurement("0.2pt"), + thickness = thickness or SILE.types.measurement("0.2pt"), })) end, "Add a huge horizontal hrule glue") @@ -107,38 +107,15 @@ function package:registerCommands () local thickness = SU.cast("measurement", options.thickness or "0.2pt") local raise = SU.cast("measurement", options.raise or "0.5em") - -- BEGIN DEPRECATION COMPATIBILITY if options.height then SU.deprecated("\\fullrule[…, height=…]", "\\fullrule[…, thickness=…]", "0.13.1", "0.15.0") - thickness = SU.cast("measurement", options.height) end if not SILE.typesetter:vmode() then SU.deprecated("\\fullrule in horizontal mode", "\\hrule or \\hrulefill", "0.13.1", "0.15.0") - if options.width then - SU.deprecated("\\fullrule with width", "\\hrule and \\raise", "0.13.1", "0.15.0") - SILE.call("raise", { height = raise }, function () - SILE.call("hrule", { - height = thickness, - width = options.width - }) - end) - else - -- This was very broken anyway, as it was overflowing the line. - -- At least we try better... - SILE.call("hrulefill", { raise = raise, thickness = thickness }) - end - return end if options.width then SU.deprecated("\\fullrule with width", "\\hrule and \\raise", "0.13.1 ", "0.15.0") - SILE.call("raise", { height = raise }, function () - SILE.call("hrule", { - height = thickness, - width = options.width - }) - end) end - -- END DEPRECATION COMPATIBILITY SILE.typesetter:leaveHmode() SILE.call("noindent") @@ -149,22 +126,14 @@ function package:registerCommands () self:registerCommand("underline", function (_, content) local underlinePosition, underlineThickness = getUnderlineParameters() - local hbox, hlist = SILE.typesetter:makeHbox(content) - -- Re-wrap the hbox in another hbox responsible for boxing it at output - -- time, when we will know the line contribution and can compute the scaled width - -- of the box, taking into account possible stretching and shrinking. - SILE.typesetter:pushHbox({ - inner = hbox, - width = hbox.width, - height = hbox.height, - depth = hbox.depth, - outputYourself = function (node, typesetter, line) + SILE.typesetter:liner("underline", content, + function (box, typesetter, line) local oldX = typesetter.frame.state.cursorX local Y = typesetter.frame.state.cursorY - -- Build the original hbox. + -- Build the content. -- Cursor will be moved by the actual definitive size. - node.inner:outputYourself(SILE.typesetter, line) + box:outputContent(typesetter, line) local newX = typesetter.frame.state.cursorX -- Output a line. @@ -173,36 +142,28 @@ function package:registerCommands () -- should expand downwards SILE.outputter:drawRule(oldX, Y - underlinePosition, newX - oldX, underlineThickness) end - }) - SILE.typesetter:pushHlist(hlist) + ) end, "Underlines some content") self:registerCommand("strikethrough", function (_, content) local yStrikeoutPosition, yStrikeoutSize = getStrikethroughParameters() - local hbox, hlist = SILE.typesetter:makeHbox(content) - -- Re-wrap the hbox in another hbox responsible for boxing it at output - -- time, when we will know the line contribution and can compute the scaled width - -- of the box, taking into account possible stretching and shrinking. - SILE.typesetter:pushHbox({ - inner = hbox, - width = hbox.width, - height = hbox.height, - depth = hbox.depth, - outputYourself = function (node, typesetter, line) + SILE.typesetter:liner("strikethrough", content, + function (box, typesetter, line) local oldX = typesetter.frame.state.cursorX local Y = typesetter.frame.state.cursorY - -- Build the original hbox. + + -- Build the content. -- Cursor will be moved by the actual definitive size. - node.inner:outputYourself(SILE.typesetter, line) + box:outputContent(typesetter, line) local newX = typesetter.frame.state.cursorX + -- Output a line. -- NOTE: The OpenType spec is not explicit regarding how the size -- (thickness) affects the position. We opt to distribute evenly SILE.outputter:drawRule(oldX, Y - yStrikeoutPosition - yStrikeoutSize / 2, newX - oldX, yStrikeoutSize) end - }) - SILE.typesetter:pushHlist(hlist) + ) end, "Strikes out some content") self:registerCommand("boxaround", function (_, content) @@ -260,6 +221,8 @@ The \autodoc:command{\underline} command \underline{underlines} its content. The \autodoc:command{\strikethrough} command \strikethrough{strikes} its content. +Both commands support paragraph content spanning multiple lines. + \autodoc:note{The position and thickness of the underlines and strikethroughs are based on the metrics of the current font, honoring the values defined by the type designer.} The \autodoc:command{\hrulefill} inserts an infinite horizontal rubber, similar to an \autodoc:command{\hfill}, but—as its name implies—filled with a rule (that is, a solid line). diff --git a/packages/scalebox/init.lua b/packages/scalebox/init.lua index f4d8375c2e..8ab331fbb2 100644 --- a/packages/scalebox/init.lua +++ b/packages/scalebox/init.lua @@ -6,12 +6,10 @@ package._name = "scalebox" function package:registerCommands () self:registerCommand("scalebox", function(options, content) - if SILE.outputter._name ~= "libtexpdf" then - SU.warn("Output will not be scaled: \\scalebox only works with the libtexpdf backend") + if not SILE.outputter.scaleFn then + SU.warn("Output will not be scaled: backend '" .. SILE.outputter._name .. "' does not support scaling") return SILE.process(content) end - SILE.outputter:_ensureInit() - local pdf = require("justenoughlibtexpdf") local hbox, hlist = SILE.typesetter:makeHbox(content) local xratio, yratio = SU.cast("number", options.xratio or 1), SU.cast("number", options.yratio or 1) @@ -37,18 +35,13 @@ function package:registerCommands () local outputWidth = SU.rationWidth(node.width, node.width, line.ratio) local X = typesetter.frame.state.cursorX local Y = typesetter.frame.state.cursorY - local x0 = X:tonumber() - local y0 = -Y:tonumber() if xratio < 0 then typesetter.frame:advanceWritingDirection(-outputWidth) end - pdf:gsave() - pdf.setmatrix(1, 0, 0, 1, x0, y0) - pdf.setmatrix(xratio, 0, 0, yratio, 0, 0) - pdf.setmatrix(1, 0, 0, 1, -x0, -y0) - hbox.outputYourself(hbox, typesetter, line) - pdf:grestore() + SILE.outputter:scaleFn(X, Y, xratio, yratio, function () + hbox:outputYourself(typesetter, line) + end) typesetter.frame.state.cursorX = X typesetter.frame.state.cursorY = Y typesetter.frame:advanceWritingDirection(outputWidth) diff --git a/packages/simpletable/init.lua b/packages/simpletable/init.lua index f762ca7aca..f92af0892e 100644 --- a/packages/simpletable/init.lua +++ b/packages/simpletable/init.lua @@ -52,7 +52,7 @@ function package:_init (options) local tbl = {} table.insert(SILE.scratch.simpletable.tables, tbl) SILE.settings:temporarily(function () - SILE.settings:set("document.parindent", SILE.nodefactory.glue()) + SILE.settings:set("document.parindent", SILE.types.node.glue()) SILE.process(content) end) SILE.typesetter:leaveHmode() @@ -75,7 +75,7 @@ function package:_init (options) until not stuffInThisColumn -- Now set each row at the given column width SILE.settings:temporarily(function () - SILE.settings:set("document.parindent", SILE.nodefactory.glue()) + SILE.settings:set("document.parindent", SILE.types.node.glue()) for row = 1, #tbl do for colno = 1, #(tbl[row]) do local hbox = tbl[row][colno].hbox diff --git a/packages/specimen/init.lua b/packages/specimen/init.lua index 83f631ac6c..a9203f2fbf 100644 --- a/packages/specimen/init.lua +++ b/packages/specimen/init.lua @@ -16,8 +16,8 @@ function package:registerCommands () for i = 1 , maxg - 1 do local wd = metrics.glyphwidth(i, face) SILE.typesetter:pushHbox({ - height= SILE.length(1.2 * fontoptions.size), - width= SILE.length(wd * fontoptions.size), + height= SILE.types.length(1.2 * fontoptions.size), + width= SILE.types.length(wd * fontoptions.size), depth= 0, value= { options = fontoptions, glyphString = { i } }, }) diff --git a/packages/svg/init.lua b/packages/svg/init.lua index 617c033a19..91a162ee43 100644 --- a/packages/svg/init.lua +++ b/packages/svg/init.lua @@ -18,8 +18,8 @@ local _drawSVG = function (svgdata, width, height, density, drop) elseif height then scalefactor = height:tonumber() / svgheight end - width = SILE.measurement(svgwidth * scalefactor) - height = SILE.measurement(svgheight * scalefactor) + width = SILE.types.measurement(svgwidth * scalefactor) + height = SILE.types.measurement(svgheight * scalefactor) scalefactor = scalefactor * density / 72 SILE.typesetter:pushHbox({ value = nil, diff --git a/packages/tableofcontents/init.lua b/packages/tableofcontents/init.lua index b4036660de..38f4ac7b0b 100644 --- a/packages/tableofcontents/init.lua +++ b/packages/tableofcontents/init.lua @@ -57,7 +57,7 @@ local function _linkWrapper (dest, func) end -- Flatten a node list into just its string representation. --- (Similar to SU.contentToString(), but allows passing typeset +-- (Similar to SU.ast.contentToString(), but allows passing typeset -- objects to functions that need plain strings). local function _nodesToText (nodes) -- A real interword space width depends on several settings (depending on variable @@ -140,7 +140,7 @@ function package:registerCommands () self:registerCommand("tableofcontents:item", function (options, content) SILE.settings:temporarily(function () - SILE.settings:set("typesetter.parfillskip", SILE.nodefactory.glue()) + SILE.settings:set("typesetter.parfillskip", SILE.types.node.glue()) SILE.call("tableofcontents:level" .. options.level .. "item", { }, _linkWrapper(options.link, function () @@ -174,7 +174,7 @@ function package:registerCommands () SILE.call("info", { category = "toc", value = { - label = SU.stripContentPos(content), + label = SU.ast.stripContentPos(content), level = (options.level or 1), number = options.number, link = dest diff --git a/packages/tate/init.lua b/packages/tate/init.lua index cae802fe50..eb86050396 100644 --- a/packages/tate/init.lua +++ b/packages/tate/init.lua @@ -25,21 +25,21 @@ end local outputLatinInTate = function (self, typesetter, line) -- My baseline moved - typesetter.frame:advanceWritingDirection(SILE.measurement("-0.5zw")) - typesetter.frame:advancePageDirection(SILE.measurement("0.25zw")) + typesetter.frame:advanceWritingDirection(SILE.types.measurement("-0.5zw")) + typesetter.frame:advancePageDirection(SILE.types.measurement("0.25zw")) local vorigin = -typesetter.frame.state.cursorY self:oldOutputYourself(typesetter,line) typesetter.frame.state.cursorY = -vorigin typesetter.frame:advanceWritingDirection(self:lineContribution()) -- My baseline moved - typesetter.frame:advanceWritingDirection(SILE.measurement("0.5zw") ) - typesetter.frame:advancePageDirection(-SILE.measurement("0.25zw")) + typesetter.frame:advanceWritingDirection(SILE.types.measurement("0.5zw") ) + typesetter.frame:advancePageDirection(-SILE.types.measurement("0.25zw")) end local outputTateChuYoko = function (self, typesetter, line) -- My baseline moved - local em = SILE.measurement("1zw") + local em = SILE.types.measurement("1zw") typesetter.frame:advanceWritingDirection(-em + em/4 - self:lineContribution()/2) typesetter.frame:advancePageDirection(2*self.height - self.width/2) self:oldOutputYourself(typesetter,line) @@ -83,7 +83,7 @@ function package:registerCommands () end) SILE.typesetter = oldT SILE.typesetter:pushGlue({ - width = SILE.length("0.5zw", "0.25zw", "0.25zw"):absolute() + width = SILE.types.length("0.5zw", "0.25zw", "0.25zw"):absolute() }) for i = 1, #nodes do if SILE.typesetter.frame:writingDirection() ~= "TTB" or nodes[i].is_glue then @@ -107,7 +107,7 @@ function package:registerCommands () self:registerCommand("tate-chu-yoko", function (_, content) if SILE.typesetter.frame:writingDirection() ~= "TTB" then return SILE.process(content) end -- SILE.typesetter:pushGlue({ - -- width = SILE.length.new({length = SILE.toPoints("0.5zw"), + -- width = SILE.types.length.new({length = SILE.toPoints("0.5zw"), -- stretch = SILE.toPoints("0.25zw"), -- shrink = SILE.toPoints("0.25zw") -- }) @@ -124,7 +124,7 @@ function package:registerCommands () end) -- SILE.typesetter:pushGlue({ - -- width = SILE.length.new({length = SILE.toPoints("0.5zw"), + -- width = SILE.types.length.new({length = SILE.toPoints("0.5zw"), -- stretch = SILE.toPoints("0.25zw"), -- shrink = SILE.toPoints("0.25zw") -- }) diff --git a/packages/twoside/init.lua b/packages/twoside/init.lua index 4f90ceb758..4e81ef16b4 100644 --- a/packages/twoside/init.lua +++ b/packages/twoside/init.lua @@ -57,9 +57,8 @@ function package:_init (options) end self:export("oddPage", self.oddPage) self:export("mirrorMaster", mirrorMaster) - self:export("switchPage", function (class) + self:export("switchPage", function () SU.deprecated("class:switchPage", nil, "0.13.0", "0.15.0", _deprecate) - return class:switchPage() end) self.class.oddPageMaster = options.oddPageMaster self.class.evenPageMaster = options.evenPageMaster diff --git a/packages/url/init.lua b/packages/url/init.lua index f30259445e..b754595cc7 100644 --- a/packages/url/init.lua +++ b/packages/url/init.lua @@ -3,8 +3,6 @@ local base = require("packages.base") local package = pl.class(base) package._name = "url" -local pdf - -- URL escape sequence, URL fragment: local preferBreakBefore = "%#" -- URL path elements, URL query arguments, acceptable extras: @@ -23,8 +21,7 @@ function package:_init () base._init(self) self:loadPackage("verbatim") self:loadPackage("inputfilter") - pdf = SILE.outputter._name == "libtexpdf" - if pdf then self:loadPackage("pdf") end + self:loadPackage("pdf") end function package.declareSettings (_) @@ -48,15 +45,6 @@ end function package:registerCommands () self:registerCommand("href", function (options, content) - if not pdf then - if options.src then - SILE.process(content) - else - SILE.call("url", { language = options.language }, content) - end - return -- DONE. - end - if options.src then SILE.call("pdf:link", { dest = options.src, external = true, borderwidth = options.borderwidth, diff --git a/packages/verbatim/init.lua b/packages/verbatim/init.lua index 19c630df3e..1090b2f2ca 100644 --- a/packages/verbatim/init.lua +++ b/packages/verbatim/init.lua @@ -17,12 +17,12 @@ function package:registerCommands () SILE.settings:temporarily(function() SILE.settings:set("typesetter.parseppattern", "\n") SILE.settings:set("typesetter.obeyspaces", true) - SILE.settings:set("document.rskip", SILE.nodefactory.glue("0 plus 10000pt")) - SILE.settings:set("document.parindent", SILE.nodefactory.glue("0")) - SILE.settings:set("document.baselineskip", SILE.nodefactory.vglue("0")) - SILE.settings:set("document.lineskip", SILE.nodefactory.vglue("2pt")) + SILE.settings:set("document.rskip", SILE.types.node.glue("0 plus 10000pt")) + SILE.settings:set("document.parindent", SILE.types.node.glue("0")) + SILE.settings:set("document.baselineskip", SILE.types.node.vglue("0")) + SILE.settings:set("document.lineskip", SILE.types.node.vglue("2pt")) SILE.call("verbatim:font") - SILE.settings:set("document.spaceskip", SILE.length("1spc")) + SILE.settings:set("document.spaceskip", SILE.types.length("1spc")) SILE.settings:set("shaper.variablespaces", false) SILE.settings:set("document.language", "und") SILE.process(content) diff --git a/pagebuilders/base.lua b/pagebuilders/base.lua index 65dc083d4f..2eef602f53 100644 --- a/pagebuilders/base.lua +++ b/pagebuilders/base.lua @@ -1,3 +1,6 @@ +--- SILE pagebuilder class. +-- @interfaces pagebuilders + local pagebuilder = pl.class() pagebuilder.type = "pagebuilder" pagebuilder._name = "base" @@ -10,7 +13,7 @@ function pagebuilder:_init () end function pagebuilder.collateVboxes (_, vboxlist) - local output = SILE.nodefactory.vbox() + local output = SILE.types.node.vbox() output:append(vboxlist) return output end @@ -27,7 +30,7 @@ function pagebuilder:findBestBreak (options) local restart = options.restart or false local force = options.force or false local i = 0 - local totalHeight = SILE.length() + local totalHeight = SILE.types.length() local bestBreak = nil local started = false if restart and restart.target == target then diff --git a/pagebuilders/grid.lua b/pagebuilders/grid.lua index 1f038949db..89bbcf8036 100644 --- a/pagebuilders/grid.lua +++ b/pagebuilders/grid.lua @@ -11,12 +11,12 @@ function pagebuilder.findBestBreak (_, options) local vboxlist = SU.required(options, "vboxlist", "in findBestBreak") local target = SU.required(options, "target", "in findBestBreak") local i = 0 - local totalHeight = SILE.length() + local totalHeight = SILE.types.length() local bestBreak = 0 SU.debug("pagebuilder", "Page builder for frame", SILE.typesetter.frame.id, "called with", #vboxlist, "nodes,", target) if SU.debugging("vboxes") then for j, box in ipairs(vboxlist) do - SU.debug("vboxes", (j == i and " >" or " ") .. j .. ": " .. box) + SU.debug("vboxes", function () return (j == i and " >" or " ") .. j .. ": " .. box end) end end while i < #vboxlist do diff --git a/rust-toolchain b/rust-toolchain new file mode 100644 index 0000000000..2bf5ad0447 --- /dev/null +++ b/rust-toolchain @@ -0,0 +1 @@ +stable diff --git a/shapers/base.lua b/shapers/base.lua index 330dea1907..ec4ddfb365 100644 --- a/shapers/base.lua +++ b/shapers/base.lua @@ -1,3 +1,6 @@ +--- SILE shaper class. +-- @interfaces shapers + -- local smallTokenSize = 20 -- Small words will be cached -- local shapeCache = {} -- local _key = function (options) @@ -5,7 +8,7 @@ -- end SILE.settings:declare({ parameter = "shaper.variablespaces", type = "boolean", default = true }) -SILE.settings:declare({ parameter = "shaper.spaceenlargementfactor", type = "number or integer", default = 1.2 }) +SILE.settings:declare({ parameter = "shaper.spaceenlargementfactor", type = "number or integer", default = 1 }) SILE.settings:declare({ parameter = "shaper.spacestretchfactor", type = "number or integer", default = 1/2 }) SILE.settings:declare({ parameter = "shaper.spaceshrinkfactor", type = "number or integer", default = 1/3 }) @@ -30,7 +33,7 @@ local function shapespace (spacewidth) local length = spacewidth * SILE.settings:get("shaper.spaceenlargementfactor") local stretch = absoluteSpaceWidth * SILE.settings:get("shaper.spacestretchfactor") local shrink = absoluteSpaceWidth * SILE.settings:get("shaper.spaceshrinkfactor") - return SILE.length(length, stretch, shrink) + return SILE.types.length(length, stretch, shrink) end local shaper = pl.class() @@ -55,7 +58,7 @@ function shaper:measureSpace (options) local items, width = self:shapeToken(" ", options) if not width and not items[1] then SU.warn("Could not measure the width of a space") - return SILE.length() + return SILE.types.length() end return shapespace(width and width.length or items[1].width) end @@ -130,14 +133,14 @@ function shaper:formNnode (contents, token, options) end self:addShapedGlyphToNnodeValue(nnodeValue, glyph) end - table.insert(nnodeContents, SILE.nodefactory.hbox({ + table.insert(nnodeContents, SILE.types.node.hbox({ depth = totalDepth, height = totalHeight, misfit = misfit, - width = SILE.length(totalWidth), + width = SILE.types.length(totalWidth), value = nnodeValue })) - return SILE.nodefactory.nnode({ + return SILE.types.node.nnode({ nodes = nnodeContents, text = token, misfit = misfit, @@ -153,7 +156,7 @@ function shaper.makeSpaceNode (_, options, item) else width = SILE.shaper:measureSpace(options) end - return SILE.nodefactory.glue(width) + return SILE.types.node.glue(width) end function shaper.debugVersions (_) end diff --git a/shapers/fallback.lua b/shapers/fallback.lua index 9c9e4bf84b..be0a430172 100644 --- a/shapers/fallback.lua +++ b/shapers/fallback.lua @@ -57,7 +57,7 @@ function fallbackQueue:addRun (offset, start) end) local options = self:nextFallback() if not options then return false end - options.size = SILE.measurement(options.size):tonumber() + options.size = SILE.types.measurement(options.size):tonumber() self.pending = { options = options, offset = offset, diff --git a/shapers/harfbuzz.lua b/shapers/harfbuzz.lua index ac00227074..ca6d783141 100644 --- a/shapers/harfbuzz.lua +++ b/shapers/harfbuzz.lua @@ -82,7 +82,7 @@ function shaper.getFace (opts) if not face or not face.filename then SU.error("Couldn't find face '"..opts.family.."'") end if SILE.makeDeps then SILE.makeDeps:add(face.filename) end face.variations = opts.variations or "" - face.pointsize = ("%g"):format(SILE.measurement(opts.size):tonumber()) + face.pointsize = ("%g"):format(SILE.types.measurement(opts.size):tonumber()) face.weight = ("%d"):format(opts.weight or 0) -- Try instanciating the font, hb.instanciate() will return nil if it is not @@ -98,12 +98,15 @@ function shaper.getFace (opts) SU.debug("fonts", "Instanciated", _pretty_varitions(face), "as", face.tempfilename) elseif (face.variations ~= "") or (bitshim.rshift(face.index, 16) ~= 0) then if not SILE.features.font_variations then - SU.warn([[This build of SILE was compiled with font variations support disabled, + SU.warn([[ + This build of SILE was compiled with font variations support disabled, likely due to not having the subsetter library included in HarfBuzz >= 6. This document specifies font variations which cannot be correctly rendered. Please rebuild SILE with the necessary library support. Alternatively to procede anyway *incorrectly* render this document run: + sile -e 'SILE.features.font_variations = true' .... + Or modify the document to remove variations options from font commands.]]) end SU.error("Failed to instanciate: " .. _pretty_varitions(face)) @@ -121,11 +124,12 @@ function shaper.preAddNodes (_, items, nnodeValue) -- Check for complex nodes end function shaper.addShapedGlyphToNnodeValue (_, nnodevalue, shapedglyph) - if nnodevalue.complex then + -- Note: previously we stored the shaped items only for "complex" nodes + -- (nodevalue.comple). We now always do it, so as to have them at hand for + -- italic correction. + if not nnodevalue.items then nnodevalue.items = {} end + nnodevalue.items[#nnodevalue.items+1] = shapedglyph - if not nnodevalue.items then nnodevalue.items = {} end - nnodevalue.items[#nnodevalue.items+1] = shapedglyph - end if not nnodevalue.glyphString then nnodevalue.glyphString = {} end if not nnodevalue.glyphNames then nnodevalue.glyphNames = {} end table.insert(nnodevalue.glyphString, shapedglyph.gid) diff --git a/shapers/pango.lua b/shapers/pango.lua index 8c207d06cc..6120b2ab4c 100644 --- a/shapers/pango.lua +++ b/shapers/pango.lua @@ -53,7 +53,7 @@ function shaper:shapeToken (text, options) local pal = SILE.font.cache(options, self.getFace) local rv = {} local items = pangolgi.itemize(pango_context, text, 0, string.len(text), pal, nil) - local twidth = SILE.length() + local twidth = SILE.types.length() for i = 1,#items do local item = items[i] local pgs = _shape(text, item) -- local text = string.sub(text,1+items[i].offset, items[i].length) diff --git a/sil.abnf b/sil.abnf new file mode 100644 index 0000000000..8d02f400c4 --- /dev/null +++ b/sil.abnf @@ -0,0 +1,124 @@ +; Formal grammar specification for SIL (SILE Input Language) files +; +; Uses RFC 5234 (Augmented BNF for Syntax Specifications: ABNF) +; Uses RFC 7405 (Case-Sensitive String Support in ABNF) + +; IMPORTANT CAVEAT: +; Backus-Naur Form grammars (like ABNF and EBNF) do not have a way to +; express matching opening and closing tags. The grammar below does +; not express SILE's ability to skip over passthrough content until +; it hits the matching closing tag for environments. + +; A master document can only have one top level content item, but we allow +; loading of fragments as well which can have any number of top level content +; items, hence valid grammar can be any number of content items. +document = *content + +; Top level content can be any sequence of these things +content = environment +content =/ comment +content =/ text +content =/ braced-content +content =/ command + +; Environments come in two flavors, passthrough (raw) and regular. The +; difference is what is allowed to terminate them and what escapes are needed +; for the content in the middle. +environment = %s"\begin" [ options ] "{" passthrough-command-id "}" + env-passthrough-text + %s"\end{" passthrough-command-id "}" + ; ^^^^^^^^^^^^^^^^^^^^^^ + ; End command must match id used in begin, see caveat at top +environment =/ %s"\begin" [ options ] "{" command-id "}" + content + %s"\end{" command-id "}" + ; ^^^^^^^^^^ + ; End command must match id used in begin, see caveat at top + +; Passthrough (raw) environments can have any valid UTF-8 except the closing +; delimiter matching the opening, per the environment rule. +env-passthrough-text = *utf8-char + +; Nothing to see here. +; But potentially important because it eats newlines! +comment = "%" *utf8-char CRLF + +; Input strings that are not special +text = *text-char + +; Input content wrapped in braces can be attached to a command or used to +; manually isolate chunks of content (e.g. to hinder ligatures). +braced-content = "{" content "}" + +; As with environments, the content format may be passthrough (raw) or more SIL +; content depending on the command. +command = "\" passthrough-command-id [ options ] [ braced-passthrough-text ] +command =/ "\" command-id [ options ] [ braced-content ] + +; Passthrough (raw) command text can have any valid UTF-8 except an unbalanced +; closing delimiter +braced-passthrough-text = "{" + *( braced-passthrough-text / braced-passthrough-char ) + "}" + +braced-passthrough-char = %x00-7A ; omit { +braced-passthrough-char =/ %x7C ; omit } +braced-passthrough-char =/ %x7E-7F ; end of utf8-1 +braced-passthrough-char =/ utf8-2 +braced-passthrough-char =/ utf8-3 +braced-passthrough-char =/ utf8-4 + +options = "[" parameter *( "," parameter ) "]" +parameter = *WSP identifier *WSP "=" *WSP ( quoted-value / value ) *WSP + +quoted-value = DQUOTE *quoted-value-char DQUOTE +quoted-value-char = "\" %x22 +quoted-value-char =/ %x00-21 ; omit " +quoted-value-char =/ %x23-7F ; end of utf8-1 +quoted-value-char =/ utf8-2 +quoted-value-char =/ utf8-3 +quoted-value-char =/ utf8-4 + +value = *value-char +value-char = %x00-21 ; omit " +value-char =/ %x23-2B ; omit , +value-char =/ %x3C-5C ; omit ] +value-char =/ %x3E-7F ; end of utf8-1 +value-char =/ utf8-2 +value-char =/ utf8-3 +value-char =/ utf8-4 + +text-char = "\" ( %x5C / %x25 / %x7B / %x7D ) +text-char =/ %x00-24 ; omit % +text-char =/ %x26-5B ; omit \ +text-char =/ %x5D-7A ; omit { +text-char =/ %x7C ; omit } +text-char =/ %x7E-7F ; end of utf8-1 +text-char =/ utf8-2 +text-char =/ utf8-3 +text-char =/ utf8-4 + +letter = ALPHA / "_" / ":" +identifier = letter *( letter / DIGIT / "-" / "." ) +passthrough-command-id = %s"ftl" + / %s"lua" + / %s"math" + / %s"raw" + / %s"script" + / %s"sil" + / %s"use" + / %s"xml" +command-id = identifier + +; ASCII isn't good enough for us. +utf8-char = utf8-1 / utf8-2 / utf8-3 / utf8-4 +utf8-1 = %x00-7F +utf8-2 = %xC2-DF utf8-tail +utf8-3 = %xE0 %xA0-BF utf8-tail + / %xE1-EC 2utf8-tail + / %xED %x80-9F utf8-tail + / %xEE-EF 2utf8-tail +utf8-4 = %xF0 %x90-BF 2utf8-tail + / %xF1-F3 3utf8-tail + / %xF4 %x80-8F 2utf8-tail +utf8-tail = %x80-BF diff --git a/sile.1.in b/sile-lua.1.in similarity index 52% rename from sile.1.in rename to sile-lua.1.in index 3fdc465416..b5375705a7 100644 --- a/sile.1.in +++ b/sile-lua.1.in @@ -1,94 +1,118 @@ .TH @TRANSFORMED_PACKAGE_NAME@ 1 "@MAN_DATE@" "version v@VERSION@" .SH NAME -@TRANSFORMED_PACKAGE_NAME@ \- Simon's Improved Layout Engine +@TRANSFORMED_PACKAGE_NAME@ \- Simon’s Improved Layout Engine .SH SYNOPSIS -.B @TRANSFORMED_PACKAGE_NAME@ [ -.I options -.B ] [ -.I filename.sil -.B | -.I filename.xml -.B ] +.B @TRANSFORMED_PACKAGE_NAME@ +.B [\fIoptions\fR] +.B [\fIINPUT\fR] .SH DESCRIPTION -The SILE typesetter reads a single input file, by default in either SIL or XML format, and processes it to generate a single output file, by default in PDF format. -The output will be written to the same name as the input file with the extension changed to .pdf unless the \fB\-\-output\fR flag is used. -Additional input or output formats can be handled by loading a module with \fB\-\-use\fR to add support for them first. +The SILE typesetter reads an input file(s), by default in either SIL or XML format, and processes them to generate an output file, by default in PDF format. +The output will be written to a file with the same name as the first input file with the extension changed to .pdf unless the \fB\-\-output\fR argument is used. +Additional input or output formats can be handled by loading a module with the \fB\-\-use\fR argument to add support for them first. .SH OPTIONS -.B @TRANSFORMED_PACKAGE_NAME@ accepts the following feature flags: -.TP -.BR \-t ", " \-\-traceback -Display detailed location trace on errors and warnings. -.TP -.BR \-h ", " \-\-help -Print help message and exit. -.TP -.BR \-v ", " \-\-version -Print version information and exit. -.TP -.B @TRANSFORMED_PACKAGE_NAME@ accepts the following options with values: .TP .BR \-b ", " \-\-backend= \fIvalue\fR -Choose an alternative output backend. -The default backend for producing PDF files is \fIlibtexpdf\fR. -Other available backends include \fIcairo\fR, \fIdebug\fR, \fItext\fR, and \fIdummy\fR. +Specify the output backend. +.IP +The default is \fIlibtexpdf\fR and suitible for most PDF output. +Alternatives supported out of the box include \fItext\fR, \fIdebug\fR, \fIdummy\fR, \fIcairo\fR, and \fIpodofo\fR. +Other outputters may be enabled via \fI--use\fR. .TP .BR \-c ", " \-\-class= \fIvalue\fR -Set the document class. -The default for documents that do not specify is \fIplain\fR. -Can be used to either change the default class or to override the class specified in a document. -Other default classes include \fIbase\fR, \fIbible\fR, \fIbook\fR, \fIdiglot\fR, \fIdocbook\fR, \fIdocbook.sil \fIjbook\fR, \fIjplain\fR, \fIletter\fR, \fImarkdown\fR, \fIpecha\fR, \fItbook\fR, \fItplain\fR, and \fItriglot\fR. +Override the default or specified document class. +.IP +The default class for documents that do not specify one in the root tag is \fIplain\fR. +This can be used to either change the default class or to override the class actually specified in a document. +Other bundled classes include \fIbase\fR, \fIbible\fR, \fIbook\fR, \fIdiglot\fR, \fIdocbook\fR, \fIdocbook\fR, \fIjbook\fR, \fIjplain\fR, \fIletter\fR, \fIpecha\fR, \fItbook\fR, \fItplain\fR, and \fItriglot\fR. +Others will be loaded dynamically from the module path. .TP .BR \-d ", " \-\-debug= \fIvalue\fR[,\fIvalue\fR] -Debug SILE's operation. +Show debug information for tagged aspects of SILE’s operation. +.IP Multiple debug flags may be given as a comma separated list. While packages may define their own debug flags, the most commonly used ones are \fItypesetter\fR, \fIpagebuilder\fR, \fIvboxes\fR, \fIbreak\fR, \fIframes\fR, \fIprofile\fR, and \fIversions\fR. May be specified more than once. .TP .BR \-e ", " \-\-evaluate= \fIvalue\fR Evaluate Lua expression before processing input. +.IP May be specified more than once. .TP .BR \-E ", " \-\-evaluate-after= \fIvalue\fR Evaluate Lua expression after processing input. +.IP May be specified more than once. .TP .BR \-f ", " \-\-fontmanager= \fIvalue\fR -Choose an alternative font manager. +Specify which font manager to use. +.IP The font manager is responsible for discovering the locations on font files on the system given a font name. The default font manager is \fIfontconfig\fR on non-macOS systems and \fImacfonts\fR on macOS. .TP .BR \-m ", " \-\-makedeps \fIfile\fR Generate a list of dependencies in Makefile format. +.IP +This tracks all the files (input files, Lua libraries, fonts, images, etc.) use during the typesetting process. +After completion, the list is written to FILE in the format of a dependency list for a target in a Makefile. +This can be used later to determine if a PDF needs re-rendering based on whether any inputs have changed. .TP .BR \-o ", " \-\-output= \fIfile\fR Explicitly set the output file name. +.IP +By default the basename of the first input file will be used as the output filename. +An extension will be chosen based on the output backend, typically .pdf. +With this option any arbitrary name and path can be given. +Additionally \fI-\fR can be used to write the output to STDOUT. .TP .BR \-O ", " \-\-options= \fIparameter=value\fR[,\fIparameter=value\fR] -Set document class options. +Set or override document class options. +.IP Can be used to change default options or override the ones specified in a document. -For example setting \fB\-\-options papersize=letter\fR would override both the default \fIpapersize\fR of A4 and any specific one set in the document's options. +For example setting \fB\-\-options papersize=letter\fR would override both the default \fIpapersize\fR of A4 and any specific one set in the document’s options. May be specified more than once. .TP .BR \-I ", " \-\-include= \fIfilename\fR -Deprecated, will be removed. -Please use \-\-use, \-\-preamble, or \-\-postamble. +Deprecated, use \-\-use, \-\-preamble, \-\-postamble, or multiple input files. .TP .BR \-p ", " \-\-preamble= \fIfilename\fR -Include an SIL, XML, or other content resource before the input document. +Include the contents of a SIL, XML, or other resource file before the input document content. +.IP The value should be a full filename with a path relative to PWD or an absolute path. May be specified more than once. .TP .BR \-P ", " \-\-postamble= \fIfilename\fR -Include an SIL, XML, or other content resource after the input document. +Include the contents of a SIL, XML, or other resource file after the input document content. +.IP The value should be a full filename with a path relative to PWD or an absolute path. May be specified more than once. .TP .BR \-u ", " \-\-use= \fImodule\fR [[\fIparameter=value[,parameter=value]]]\fR Load and initialize a class, inputter, shaper, or other module before processing the main input. -The value should be a loadable module name (with no extension, using \fI'.'\fR as a path separator) and will be loaded using SILE's module search path. +.IP +The value should be a loadable module name (with no extension, using \fI'.'\fR as a path separator) and will be loaded using SILE’s module search path. Options may be passed to the module by enclosing \fIkey=value\fR pairs in square brackets following the module name. This is particularly useful when the input document is not SIL or a natively recognized XML scheme and SILE needs to be taught new tricks before even trying to read the input file. If the module is a document class, it will replace \fIplain\fR as the default class for processing documents that do not specifically identify one to use. Because this executes before loading the document, it may even add an input parser or modify an existing one to support new file formats. Package modules will be added to the preamble to be loaded after the class is initialized. May be specified more than once. +.TP +.BR \-q ", " \-\-quiet +Suppress warnings and informational messages during processing. +.TP +.BR \-t ", " \-\-traceback +Display detailed location trace on errors and warnings. +.TP +.BR [INPUT] +Input document filename(s), by default in SIL, XML, or Lua formats. +.IP +One or more input files from which to process content. +The first listed file is considered the master document, others are procced in sequence. +Other inputter formats may be enabled via \fI--use\fR. +Use \fI-\fR to read a content stream from STDIN. +.TP +.BR \-h ", " \-\-help +Print help message and exit. +.TP +.BR \-v ", " \-\-version +Print version. diff --git a/sile.in b/sile.in index 6606f7c393..940bb076d8 100755 --- a/sile.in +++ b/sile.in @@ -1,9 +1,5 @@ #!@LUA@ --- globals used only in core/packagemanager, soon to be deprecated -SYSTEM_SILE_PATH = "@SILE_PATH@" -SHARED_LIB_EXT = "@SHARED_LIB_EXT@" - -- At this point at run time we may or may not have anything useful in package.path, -- so require() isn't something we can rely on. local status @@ -24,8 +20,10 @@ end -- This global is set here and *also* in the core library, since this -- effectively passes the same table they are interchangeable (for now). SILE = require("core.sile") +SILE.full_version = SILE.full_version .. " [Lua]" -SILE.cli:parseArguments() +local cli = require("core.cli") +cli:parseArguments() if not os.getenv 'LUA_REPL_RLWRAP' and not SILE.quiet then io.stderr:write(SILE.full_version .. '\n') @@ -35,19 +33,6 @@ SILE.init() if SILE.input.filenames and #SILE.input.filenames >= 1 then - -- Deprecated, notice given in core.cli when argument used - for _, path in ipairs(SILE.input.includes) do - if not SILE.quiet then - io.stderr:write("Loading "..path.."\n") - end - local c = SILE.resolveFile(path, "classes") - if c then - SILE.processFile(c) - else - SILE.require(path, "classes") - end - end - for _, spec in ipairs(SILE.input.uses) do SILE.use(spec.module, spec.options) end @@ -68,7 +53,8 @@ if SILE.input.filenames and #SILE.input.filenames >= 1 then SILE.finish() else - SILE.repl:enter() + local repl = require("core.repl") + repl:enter() end -- vim: ft=lua diff --git a/sile.rockspec.in b/sile.rockspec.in index 24f2baf4d9..9cf849739c 100644 --- a/sile.rockspec.in +++ b/sile.rockspec.in @@ -5,7 +5,7 @@ version = "dev-@ROCKREV@" source = { url = "git+https://github.com/sile-typesetter/sile.git", - branch = "master" + branch = "develop" } description = { @@ -29,20 +29,19 @@ dependencies = { "cassowary == 2.3.2-1", "cldr == 0.3.0-0", "compat53 == 0.8-1", -- only required on Lua < 5.3 - "cosmo == 16.06.04-1", "fluent == 0.2.0-0", "linenoise == 0.9-1", "loadkit == 1.1.0-1", - "lpeg == 1.0.2-1", + "lpeg == 1.1.0-1", "lua-zlib == 1.2-2", "lua_cliargs == 3.0-2", "luaepnf == 0.3-2", "luaexpat == 1.5.1-1", "luafilesystem == 1.8.0-1", "luarepl == 0.10-1", - "luasec == 1.3.1-1", + "luasec == 1.3.2-1", "luasocket == 3.1.0-1", - "luautf8 == 0.1.5-1", + "luautf8 == 0.1.5-2", "penlight == 1.13.1-1", "vstruct == 2.1.1-1" } diff --git a/spec/break_spec.lua b/spec/break_spec.lua index b86d4ddb46..9efd227290 100644 --- a/spec/break_spec.lua +++ b/spec/break_spec.lua @@ -7,68 +7,68 @@ describe("SILE.linebreak", function() -- This is a list of boxes, with their dimensions, extracted from a specially hacked version of TeX. local hlist = {} - local function nnode(spec) table.insert(hlist, SILE.nodefactory.nnode(spec)) end - local function glue(spec) table.insert(hlist, SILE.nodefactory.glue(spec)) end + local function nnode(spec) table.insert(hlist, SILE.types.node.nnode(spec)) end + local function glue(spec) table.insert(hlist, SILE.types.node.glue(spec)) end nnode({ text ="To", height = 6.15234, depth = 0.14647, width = 10.14648 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="Sherlock", height = 7.56836, depth = 0.14647, width = 35.82031 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="Holmes", height = 7.56836, depth = 0.14647, width = 30.79102 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="she", height = 7.56836, depth = 0.14647, width = 13.99902 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="is", height = 6.6211, depth = 0.14647, width = 6.57227 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="always", height = 7.56836, depth = 2.44139, width = 27.59766 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="the", height = 7.56836, depth = 0.14647, width = 13.5791 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="woman", height = 4.6875, depth = 0.1953, width = 32.37305 }) - glue({ width = SILE.length({ length = 2.93619, stretch = 3.30322, shrink = 0.24467 }) }) + glue({ width = SILE.types.length({ length = 2.93619, stretch = 3.30322, shrink = 0.24467 }) }) nnode({ text ="I", height = 6.15234, depth = 0.0, width = 2.97852 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.09996, shrink = 0.73477 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.09996, shrink = 0.73477 }) }) nnode({ text ="have", height = 7.56836, depth = 0.14647, width = 19.26758 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="seldom", height = 7.56836, depth = 0.14647, width = 29.45313 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="heard", height = 7.56836, depth = 0.14647, width = 23.78906 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="him", height = 7.56836, depth = 0.0, width = 16.25977 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="mention", height = 6.6211, depth = 0.14647, width = 34.86816 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="her", height = 7.56836, depth = 0.14647, width = 14.09668 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="under", height = 7.56836, depth = 0.14647, width = 24.59473 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="any", height = 4.6875, depth = 2.44139, width = 15.03906 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="other", height = 7.56836, depth = 0.14647, width = 22.56836 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="name", height = 4.6875, depth = 0.1953, width = 25.04883 }) - glue({ width = SILE.length({ length = 2.93619, stretch = 3.30322, shrink = 0.24467 }) }) + glue({ width = SILE.types.length({ length = 2.93619, stretch = 3.30322, shrink = 0.24467 }) }) nnode({ text ="In", height = 6.15234, depth = 0.0, width = 8.4961 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="his", height = 7.56836, depth = 0.14647, width = 12.08984 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="eyes", height = 4.6875, depth = 2.44139, width = 17.83691 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="she", height = 7.56836, depth = 0.14647, width = 13.99902 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="eclipses", height = 7.56836, depth = 2.34373, width = 31.9043 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="and", height = 7.56836, depth = 0.14647, width = 15.30762 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="predominates", height = 7.56836, depth = 2.34373, width = 56.7334 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="the", height = 7.56836, depth = 0.14647, width = 13.5791 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="whole", height = 7.56836, depth = 0.14647, width = 24.93652 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="of", height = 7.56836, depth = 0.14647, width = 8.13965 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="her", height = 7.56836, depth = 0.14647, width = 14.09668 }) - glue({ width = SILE.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) + glue({ width = SILE.types.length({ length = 2.20215, stretch = 1.10107, shrink = 0.73404 }) }) nnode({ text ="sex.", height = 4.6875, depth = 0.1953, width = 15.6543 }) it("should sleuth the right break point", function() diff --git a/spec/hyphenator_spec.lua b/spec/hyphenator_spec.lua new file mode 100644 index 0000000000..73ec7f3de5 --- /dev/null +++ b/spec/hyphenator_spec.lua @@ -0,0 +1,51 @@ +SILE = require("core.sile") +-- Using French below requires the shaper to be initialized +SILE.backend = "debug" +SILE.init() + +describe("Hyphenation module", function () + local hyphenate = SILE.showHyphenationPoints + + describe("minWord with UTF8 in input text", function () + + SILE.languageSupport.loadLanguage("fr") + fluent:set_locale("fr") + -- Trigger the initialization of the hyphenator + -- so SILE._hyphenators["fr"] is created + hyphenate("série", "fr") + + -- Current lefthyphenmin and righthyphenmin values + -- for this test (whether changed or not for the language) + SILE._hyphenators["fr"].leftmin = 2 + SILE._hyphenators["fr"].rightmin = 2 + + it("should hyphenate", function () + SILE._hyphenators["fr"].minWord = 5 -- (Default) + assert.is.equal("sé-rie", hyphenate("série", "fr")) + assert.is.equal("Lé-gè-re-ment", hyphenate("Légèrement", "fr")) + end) + + it("should not hyphenate", function () + SILE._hyphenators["fr"].minWord = 6 + -- 5 characters but 6 bytes + assert.is.equal("série", hyphenate("série", "fr")) + SILE._hyphenators["fr"].minWord = 5 -- back to default + end) + + end) + + describe("exceptions with UTF8 in input text", function () + + SILE.languageSupport.loadLanguage("fr") + fluent:set_locale("fr") + + SILE.call("hyphenator:add-exceptions", { lang = "fr" }, { "légè-rement" }) + + it("should hyphenate with exception rule", function() + assert.is.equal("légè-rement", hyphenate("légèrement", "fr")) + assert.is.equal("Légè-rement", hyphenate("Légèrement", "fr")) + end) + + end) + +end) diff --git a/spec/measurements_spec.lua b/spec/measurements_spec.lua index e4b9eec167..e0341827be 100644 --- a/spec/measurements_spec.lua +++ b/spec/measurements_spec.lua @@ -31,7 +31,7 @@ describe("The measurement convertor", function() it("should exist", function() assert.is.truthy(SILE.toPoints) end) - it("should work for points, units explicit", function () assert.is.equal(20, SILE.measurement(20, "pt"):tonumber()) end) - it("should work for points, units implicit", function () assert.is.equal(20, SILE.measurement("20pt"):tonumber()) end) - it("should work for inches, units implicit", function () assert.is.equal(14.4, SILE.measurement("0.2in"):tonumber()) end) + it("should work for points, units explicit", function () assert.is.equal(20, SILE.types.measurement(20, "pt"):tonumber()) end) + it("should work for points, units implicit", function () assert.is.equal(20, SILE.types.measurement("20pt"):tonumber()) end) + it("should work for inches, units implicit", function () assert.is.equal(14.4, SILE.types.measurement("0.2in"):tonumber()) end) end) diff --git a/spec/shaper_spec.lua b/spec/shaper_spec.lua index bfb1211edb..22b981a4d7 100644 --- a/spec/shaper_spec.lua +++ b/spec/shaper_spec.lua @@ -8,10 +8,10 @@ describe("SILE.shapers.base", function() SILE.settings:set("shaper.variablespaces", true) SILE.settings:set("shaper.spacestretchfactor", 2) SILE.settings:set("shaper.spaceshrinkfactor", 2) - local negative_glue = SILE.nodefactory.glue("-4pt") + local negative_glue = SILE.types.node.glue("-4pt") local space = SILE.shaper:makeSpaceNode({}, negative_glue) - assert.is.truthy(space.width.stretch > SILE.measurement(0)) - assert.is.truthy(space.width.shrink > SILE.measurement(0)) + assert.is.truthy(space.width.stretch > SILE.types.measurement(0)) + assert.is.truthy(space.width.shrink > SILE.types.measurement(0)) end) end) diff --git a/spec/utilities_spec.lua b/spec/utilities_spec.lua index 8c3747286e..387222db66 100644 --- a/spec/utilities_spec.lua +++ b/spec/utilities_spec.lua @@ -149,25 +149,34 @@ describe("SILE.utilities", function() assert.is.equal("2", SU.formatNumber(2, { system = "arabic" })) end) - it("should format roman number", function () + it("should format roman numbers", function () assert.is.equal("mcmlxxxiv", SU.formatNumber(1984, { system = "roman" })) end) - it("should format ROMAN number", function () + it("should format ROMAN numbers", function () assert.is.equal("MCMLXXXIV", SU.formatNumber(1984, { system = "ROMAN" })) end) - it("should format alpha number", function () + it("should format alpha numbers", function () assert.is.equal("b", SU.formatNumber(2, { system = "alpha" })) end) - it("should format ALPHA number", function () + it("should format ALPHA numbers", function () assert.is.equal("B", SU.formatNumber(2, { system = "Alpha" })) end) it("should format 'arab' numbers", function () assert.is.equal("٢", SU.formatNumber(2, { system = "arab" })) end) + + it("should format greek numbers", function () + assert.is.equal("β", SU.formatNumber(2, { system = "greek" })) + end) + + it("should format GREEK numbers", function () + assert.is.equal("Β", SU.formatNumber(2, { system = "Greek" })) + end) + end) end) diff --git a/src/Makefile.am b/src/Makefile.am deleted file mode 100644 index 70f72913c1..0000000000 --- a/src/Makefile.am +++ /dev/null @@ -1,67 +0,0 @@ -ACLOCAL_AMFLAGS = -I ../build-aux - -if LINKLUA -MY_LUA_LIB=$(LUA_LIB) -UNDEFINED=-no-undefined -else -MY_LUA_LIB= -UNDEFINED= -endif - -if SYSTEM_LIBTEXPDF -LIBTEXPDF_LIB=-ltexpdf -else -LIBTEXPDF_LIB=../libtexpdf/libtexpdf.la -endif - -pkglib_LTLIBRARIES = justenoughharfbuzz.la justenoughlibtexpdf.la justenoughfontconfig.la fontmetrics.la svg.la -justenoughharfbuzz_la_SOURCES = justenoughharfbuzz.c hb-utils.c hb-utils.h -justenoughharfbuzz_la_LDFLAGS = -module -avoid-version -shared $(UNDEFINED) -justenoughharfbuzz_la_CFLAGS = $(HARFBUZZ_CFLAGS) $(HARFBUZZ_SUBSET_CFLAGS) $(LUA_INCLUDE) -justenoughharfbuzz_la_LIBADD = $(HARFBUZZ_LIBS) $(HARFBUZZ_SUBSET_LIBS) $(MY_LUA_LIB) -to_copy = .libs/justenoughharfbuzz.@SHARED_LIB_EXT@ - -justenoughfontconfig_la_SOURCES = justenoughfontconfig.c silewin32.h -justenoughfontconfig_la_LDFLAGS = -module -avoid-version -shared $(UNDEFINED) -justenoughfontconfig_la_CFLAGS = $(FONTCONFIG_CFLAGS) $(LUA_INCLUDE) -justenoughfontconfig_la_LIBADD = $(FONTCONFIG_LIBS) $(MY_LUA_LIB) -to_copy += .libs/justenoughfontconfig.@SHARED_LIB_EXT@ - -if APPKIT -pkglib_LTLIBRARIES += macfonts.la -macfonts_la_SOURCES = macfonts.m -macfonts_la_LDFLAGS = -module -avoid-version -shared $(UNDEFINED) -macfonts_la_OBJCFLAGS = -U VERSION $(HARFBUZZ_CFLAGS) $(LUA_INCLUDE) -fmodules -macfonts_la_LIBADD = $(HARFBUZZ_LIBS) $(MY_LUA_LIB) -to_copy += .libs/macfonts.@SHARED_LIB_EXT@ -endif - -justenoughlibtexpdf_la_SOURCES = justenoughlibtexpdf.c imagebbox.c -justenoughlibtexpdf_la_LDFLAGS = -module -avoid-version -shared $(UNDEFINED) -justenoughlibtexpdf_la_CFLAGS = -I.. $(LIBPNG_INCLUDES) $(ZLIB_INCLUDES) $(LIBPAPER_INCLUDES) $(LUA_INCLUDE) -justenoughlibtexpdf_la_LIBADD = $(LIBTEXPDF_LIB) $(LIBPNG_LIBS) $(ZLIB_LIBS) $(LIBPAPER_LIBS) $(MY_LUA_LIB) -to_copy += .libs/justenoughlibtexpdf.@SHARED_LIB_EXT@ - -fontmetrics_la_SOURCES = fontmetrics.c hb-utils.c hb-utils.h -fontmetrics_la_LDFLAGS = -module -avoid-version -shared $(UNDEFINED) -fontmetrics_la_CFLAGS = $(LUA_INCLUDE) $(HARFBUZZ_CFLAGS) -fontmetrics_la_LIBADD = $(MY_LUA_LIB) $(HARFBUZZ_LIBS) -to_copy += .libs/fontmetrics.@SHARED_LIB_EXT@ - -svg_la_SOURCES = svg.c nanosvg.h -svg_la_LDFLAGS = -module -avoid-version -shared $(UNDEFINED) -svg_la_CFLAGS = $(LUA_INCLUDE) -svg_la_LIBADD = $(MY_LUA_LIB) -to_copy += .libs/svg.@SHARED_LIB_EXT@ - -if ICU -pkglib_LTLIBRARIES += justenoughicu.la -justenoughicu_la_SOURCES = justenoughicu.c -justenoughicu_la_LDFLAGS = -module -avoid-version -shared $(UNDEFINED) -justenoughicu_la_CFLAGS = -I.. $(ICU_CFLAGS) $(LUA_INCLUDE) -justenoughicu_la_LIBADD = $(ICU_LIBS) $(MY_LUA_LIB) -to_copy += .libs/justenoughicu.@SHARED_LIB_EXT@ -endif - -all-local: $(pkglib_LTLIBRARIES) - cp $(to_copy) .. diff --git a/src/bin/sile.rs b/src/bin/sile.rs new file mode 100644 index 0000000000..146f4e7cc5 --- /dev/null +++ b/src/bin/sile.rs @@ -0,0 +1,35 @@ +use clap::{CommandFactory, FromArgMatches}; + +use sile::cli::Cli; +use sile::Result; + +fn main() -> Result<()> { + let version = option_env!("VERGEN_GIT_DESCRIBE").unwrap_or_else(|| env!("CARGO_PKG_VERSION")); + let version = version.replacen('-', ".r", 1); + let long_version = sile::version()? + .strip_prefix("SILE ") + .unwrap_or("") + .to_string(); + let app = Cli::command().version(version).long_version(long_version); + #[allow(unused_variables)] + let matches = app.get_matches(); + let args = Cli::from_arg_matches(&matches).expect("Unable to parse arguments"); + sile::run( + args.input, + args.backend, + args.class, + args.debug, + args.evaluate, + args.evaluate_after, + args.fontmanager, + args.makedeps, + args.output, + args.option, + args.preamble, + args.postamble, + args.r#use, + args.quiet, + args.traceback, + )?; + Ok(()) +} diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000000..9af8b71426 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,132 @@ +use clap::Parser; +use std::path::PathBuf; + +/// The SILE typesetter reads an input file(s), by default in either SIL or XML format, and +/// processes them to generate an output file, by default in PDF format. The output will be written +/// to a file with the same name as the first input file with the extension changed to .pdf unless +/// the `--output` argument is used. Additional input or output formats can be handled by loading +/// a module with the `--use` argument to add support for them first. +#[derive(Parser, Debug)] +#[clap(author, name = "SILE", bin_name = "sile")] +pub struct Cli { + /// Input document filename(s), by default in SIL, XML, or Lua formats. + /// + /// One or more input files from which to process content. + /// The first listed file is considered the master document, others are procced in sequence. + /// Other inputter formats may be enabled via`--use`. + /// Use `-` to read a content stream from STDIN. + pub input: Option>, + + /// Specify the output backend. + /// + /// The default is `libtexpdf` and suitible for most PDF output. + /// Alternatives supported out of the box include `text`, `debug`, `dummy`, `cairo`, and `podofo`. + /// Other outputters may be enabled via `--use`. + #[clap(short, long, value_name = "BACKEND")] + pub backend: Option, + + /// Override the default or specified document class. + /// + /// The default class for documents that do not specify one in the root tag is `plain`. + /// This can be used to either change the default class or to override the class actually specified in a document. + /// Other bundled classes include `base`, `bible`, `book`, `diglot`, `docbook`, `docbook`, `jbook`, `jplain`, `letter`, `pecha`, `tbook`, `tplain`, and `triglot`. + /// Others will be loaded dynamically from the module path. + #[clap(short, long)] + pub class: Option, + + /// Show debug information for tagged aspects of SILE’s operation. + /// + /// Multiple debug flags may be given as a comma separated list. + /// While packages may define their own debug flags, the most commonly used ones are `typesetter`, `pagebuilder`, `vboxes`, `break`, `frames`, `profile`, and `versions`. + /// May be specified more than once. + #[clap(short, long, value_name = "DEBUGFLAG[,DEBUGFLAG]")] + // TODO: switch to num_args(0..) to allow space separated inputs + pub debug: Option>, + + /// Evaluate Lua expression before processing input. + /// + /// May be specified more than once. + #[clap(short, long, value_name = "EXRPESION")] + pub evaluate: Option>, + + /// Evaluate Lua expression after processing input. + /// + /// May be specified more than once. + #[clap(short = 'E', long, value_name = "EXRPESION")] + pub evaluate_after: Option>, + + /// Specify which font manager to use. + /// + /// The font manager is responsible for discovering the locations on font files on the system given a font name. + /// The default font manager is `fontconfig` on non-macOS systems and `macfonts` on macOS. + #[clap(short, long, value_name = "FONTMANAGER")] + pub fontmanager: Option, + + /// Generate a Makefile format list of dependencies and white them to a file. + /// + /// This tracks all the files (input files, Lua libraries, fonts, images, etc.) use during the + /// typesetting process. + /// After completion, the list is written to FILE in the format of a dependency list for + /// a target in a Makefile. + /// This can be used later to determine if a PDF needs re-rendering based on whether any inputs + /// have changed. + #[clap(short, long, value_name = "FILE")] + pub makedeps: Option, + + /// Explicitly set the output file name. + /// + /// By default the basename of the first input file will be used as the output filename. + /// An extension will be chosen based on the output backend, typically .pdf. + /// With this option any arbitrary name and path can be given. + /// Additionally `-` can be used to write the output to STDOUT. + #[clap(short, long, value_name = "FILE")] + pub output: Option, + + /// Set or override document class options. + /// + /// Can be used to change default options or override the ones specified in a document. + /// For example setting `--options papersize=letter` would override both the default `papersize` of A4 and any specific one set in the document’s options. + /// May be specified more than once. + #[clap(short = 'O', long)] + pub option: Option>, + + /// Include the contents of a SIL, XML, or other resource file before the input document content. + /// + /// The value should be a full filename with a path relative to PWD or an absolute path. + /// May be specified more than once. + #[clap(short, long, value_name = "FILE")] + pub preamble: Option>, + + /// Include the contents of a SIL, XML, or other resource file after the input document content. + /// + /// The value should be a full filename with a path relative to PWD or an absolute path. + /// May be specified more than once. + #[clap(short = 'P', long, value_name = "FILE")] + pub postamble: Option>, + + /// Load and initialize a class, inputter, shaper, or other module before processing the main input. + /// + /// The value should be a loadable module name (with no extension, using `.` as a path separator) and will be loaded using SILE’s module search path. + /// Options may be passed to the module by enclosing `key=value` pairs in square brackets following the module name. + /// This is particularly useful when the input document is not SIL or a natively recognized XML scheme + /// and SILE needs to be taught new tricks before even trying to read the input file. + /// If the module is a document class, it will replace `plain` as the default class for processing + /// documents that do not specifically identify one to use. + /// Because this executes before loading the document, it may even add an input parser or modify an existing one to support new file formats. + /// Package modules will be added to the preamble to be loaded after the class is initialized. + /// May be specified more than once. + #[clap( + short, + long, + value_name = "MODULE[[PARAMETER=VALUE[,PARAMETER=VALUE]]]" + )] + pub r#use: Option>, + + /// Suppress warnings and informational messages during processing. + #[clap(short, long)] + pub quiet: bool, + + /// Display detailed location trace on errors and warnings. + #[clap(short, long)] + pub traceback: bool, +} diff --git a/src/embed.rs.in b/src/embed.rs.in new file mode 100644 index 0000000000..f9b26cdbf5 --- /dev/null +++ b/src/embed.rs.in @@ -0,0 +1,136 @@ +use mlua::chunk; +use mlua::prelude::*; +use rust_embed::{EmbeddedFile, RustEmbed}; +use std::str; + +/// Embed everything that would otherwise be installed to datadir +#[derive(RustEmbed)] +#[folder = "."] +#[exclude = ".*"] +#[exclude = "*~"] +#[exclude = "*.in"] +#[exclude = "Make*"] +#[exclude = "autom4te.cache/*"] +#[exclude = "build-aux/*"] +#[exclude = "cmake/*"] +#[exclude = "completions/*"] +#[exclude = "documentation/*"] +#[exclude = "justenough/*"] +#[exclude = "libtexpdf/*"] +#[exclude = "libtexpdf/*"] +#[exclude = "libtool"] +#[exclude = "node_modules/*"] +#[exclude = "rust-toolchain"] +#[exclude = "sile*"] +#[exclude = "src/*"] +#[exclude = "target/*"] +#[exclude = "tests/*"] +// @INCLUDE_EMDED_INCLUDES@ -- this marker line gets replaced by a list of includes +pub struct SileModules; + +// Link Lua loader functions from C modules that Lua would otherwise be loading externally that +// we've linked into the CLI binary. Linking happens in build-aux/build.rs. +extern "C-unwind" { + fn luaopen_fontmetrics(lua: *mut mlua::lua_State) -> i32; + fn luaopen_justenoughfontconfig(lua: *mut mlua::lua_State) -> i32; + fn luaopen_justenoughharfbuzz(lua: *mut mlua::lua_State) -> i32; + fn luaopen_justenoughicu(lua: *mut mlua::lua_State) -> i32; + fn luaopen_justenoughlibtexpdf(lua: *mut mlua::lua_State) -> i32; + fn luaopen_svg(lua: *mut mlua::lua_State) -> i32; +} + +/// Register a Lua function in the loaders/searchers table to return C modules linked into the CLI +/// binary and another to return embedded Lua resources as Lua modules. See discussion in mlua: +/// https://github.com/khvzak/mlua/discussions/322 +pub fn inject_embedded_loader(lua: &Lua) { + let package: LuaTable = lua.globals().get("package").unwrap(); + let loaders: LuaTable = match package.get("loaders").unwrap() { + LuaValue::Table(loaders) => loaders, + LuaValue::Nil => package.get("searchers").unwrap(), + _ => panic!("Unable to find approprate interface to inject embedded loader"), + }; + loaders + .push(LuaFunction::wrap(|lua, module: String| unsafe { + match module.as_str() { + "fontmetrics" => lua + .create_c_function(luaopen_fontmetrics) + .map(LuaValue::Function), + "justenoughfontconfig" => lua + .create_c_function(luaopen_justenoughfontconfig) + .map(LuaValue::Function), + "justenoughharfbuzz" => lua + .create_c_function(luaopen_justenoughharfbuzz) + .map(LuaValue::Function), + "justenoughicu" => lua + .create_c_function(luaopen_justenoughicu) + .map(LuaValue::Function), + "justenoughlibtexpdf" => lua + .create_c_function(luaopen_justenoughlibtexpdf) + .map(LuaValue::Function), + "svg" => lua.create_c_function(luaopen_svg).map(LuaValue::Function), + _ => format!("C Module '{module}' is not linked in Rust binary").into_lua(lua), + } + })) + .unwrap(); + loaders + .push(LuaFunction::wrap(|lua, module: String| { + let module_path = module.replace('.', "/"); + let luaversion: LuaString = lua + .load(chunk! { + return _VERSION:match("%d+%.%d+") + }) + .eval() + .unwrap(); + let luaversion: &str = luaversion.to_str().unwrap(); + let mut package_epath: Vec<&str> = vec!["?/init.lua", "?.lua", "lua-libraries/?.lua"]; + let path = format!("lua_modules/lib/lua/{}/?/init.lua", luaversion); + package_epath.push(&path); + let path = format!("lua_modules/lib/lua/{}/?.lua", luaversion); + package_epath.push(&path); + let path = format!("lua_modules/share/lua/{}/?/init.lua", luaversion); + package_epath.push(&path); + let path = format!("lua_modules/share/lua/{}/?.lua", luaversion); + package_epath.push(&path); + let mut resource_option: Option = None; + for pattern in &package_epath { + let path = pattern.replace('?', &module_path); + let embedded = SileModules::get(&path); + if embedded.is_some() { + resource_option = embedded; + break; + } + } + match resource_option { + Some(module) => { + return LuaFunction::wrap(move |lua, ()| { + let data = str::from_utf8(module.data.as_ref()) + .expect("Embedded content is not valid UTF-8"); + lua.load(data).call::<_, LuaValue>(()) + }) + .into_lua(lua) + } + None => format!("Module '{module}' is not embedded in Rust binary").into_lua(lua), + } + })) + .unwrap(); + loaders + .push(LuaFunction::wrap(|lua, module: String| { + let module_path = module.replace('.', "/"); + let pattern = "?.ftl"; + let path = pattern.replace('?', &module_path); + match SileModules::get(&path) { + Some(module) => LuaFunction::wrap(move |lua, ()| { + let data = str::from_utf8(module.data.as_ref()) + .expect("Embedded content is not valid UTF-8"); + lua.load(chunk! { + return assert(fluent:add_messages($data)) + }) + .call::<_, LuaValue>(()) + }) + .into_lua(lua), + None => format!("FTL resource '{module_path}' is not embedded in Rust binary") + .into_lua(lua), + } + })) + .unwrap(); +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000000..ae6a911ed8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,202 @@ +// rust-embed include attributes have issues with lots of matches... +#![recursion_limit = "2048"] + +use mlua::chunk; +use mlua::prelude::*; +#[cfg(not(feature = "static"))] +use std::env; +use std::path::PathBuf; +#[cfg(feature = "cli")] +pub mod cli; + +#[cfg(feature = "static")] +pub mod embed; + +pub type Result = anyhow::Result; + +pub fn start_luavm() -> crate::Result { + let lua = unsafe { Lua::unsafe_new() }; + #[cfg(feature = "static")] + crate::embed::inject_embedded_loader(&lua); + inject_paths(&lua); + load_sile(&lua); + inject_version(&lua); + Ok(lua) +} + +pub fn inject_paths(lua: &Lua) { + #[cfg(feature = "static")] + lua.load(r#"require("core.pathsetup")"#).exec().unwrap(); + #[cfg(not(feature = "static"))] + { + let datadir = env!("CONFIGURE_DATADIR").to_string(); + let sile_path = match env::var("SILE_PATH") { + Ok(val) => format!("{datadir};{val}"), + Err(_) => datadir, + }; + let sile_path: LuaString = lua.create_string(&sile_path).unwrap(); + lua.load(chunk! { + local status + for path in string.gmatch($sile_path, "[^;]+") do + status = pcall(dofile, path .. "/core/pathsetup.lua") + if status then break end + end + if not status then + dofile("./core/pathsetup.lua") + end + }) + .exec() + .unwrap(); + } +} + +pub fn inject_version(lua: &Lua) { + let sile: LuaTable = lua.globals().get("SILE").unwrap(); + let mut full_version: String = sile.get("full_version").unwrap(); + full_version.push_str(" [Rust]"); + sile.set("full_version", full_version).unwrap(); +} + +pub fn load_sile(lua: &Lua) { + let entry: LuaString = lua.create_string("core.sile").unwrap(); + let r#require: LuaFunction = lua.globals().get("require").unwrap(); + r#require.call::(entry).unwrap(); +} + +pub fn version() -> crate::Result { + let lua = start_luavm()?; + let sile: LuaTable = lua.globals().get("SILE").unwrap(); + let full_version: String = sile.get("full_version").unwrap(); + Ok(full_version) +} + +// Yes I know this should be taking a struct, probably 1 with what ends up being SILE.input and one +// with other stuff the CLI may inject, but I'm playing with what a minimum/maximum set of +// parameters would look like here while maintaining compatiblitiy with the Lua CLI. +#[allow(clippy::too_many_arguments)] +pub fn run( + inputs: Option>, + backend: Option, + class: Option, + debugs: Option>, + evaluates: Option>, + evaluate_afters: Option>, + fontmanager: Option, + makedeps: Option, + output: Option, + options: Option>, + preambles: Option>, + postambles: Option>, + uses: Option>, + quiet: bool, + traceback: bool, +) -> crate::Result<()> { + let lua = start_luavm()?; + let sile: LuaTable = lua.globals().get("SILE")?; + sile.set("traceback", traceback)?; + sile.set("quiet", quiet)?; + let mut has_input_filename = false; + if let Some(flags) = debugs { + let debug_flags: LuaTable = sile.get("debugFlags")?; + for flag in flags { + debug_flags.set(flag, true)?; + } + } + let full_version: String = sile.get("full_version")?; + let sile_input: LuaTable = sile.get("input")?; + if let Some(expressions) = evaluates { + sile_input.set("evaluates", expressions)?; + } + if let Some(expressions) = evaluate_afters { + sile_input.set("evaluateAfters", expressions)?; + } + if let Some(backend) = backend { + sile.set("backend", backend)?; + } + if let Some(fontmanager) = fontmanager { + sile.set("fontmanager", fontmanager)?; + } + if let Some(class) = class { + sile_input.set("class", class)?; + } + if let Some(paths) = preambles { + sile_input.set("preambles", paths_to_strings(paths))?; + } + if let Some(paths) = postambles { + sile_input.set("postambles", paths_to_strings(paths))?; + } + if let Some(path) = makedeps { + sile_input.set("makedeps", path_to_string(&path))?; + } + if let Some(path) = output { + sile.set("outputFilename", path_to_string(&path))?; + has_input_filename = true; + } + if let Some(options) = options { + sile_input.set("options", options)?; + } + if let Some(modules) = uses { + // let parser_bits: LuaTable = sile.get("parserBits")?; + // let cliuse: LuaAnyUserData = parser_bits.get("cliuse")?; + // sile_input.get("uses")?; + for module in modules.iter() { + let module = lua.create_string(module)?; + lua.load(chunk! { + local spec = SILE.parserBits.cliuse:match($module); + table.insert(SILE.input.uses, spec) + }) + .eval()?; + // let spec = cliuse.call_function::<_, _, _>("match", module); + } + } + if !quiet { + eprintln!("{full_version}"); + } + let init: LuaFunction = sile.get("init")?; + init.call::<_, _>(())?; + if let Some(inputs) = inputs { + let input_filenames: LuaTable = lua.create_table()?; + for input in inputs.iter() { + let path = &path_to_string(input); + if !has_input_filename && path != "-" { + has_input_filename = true; + } + input_filenames.push(lua.create_string(path)?)?; + } + if !has_input_filename { + panic!( + "\nUnable to derive an output filename (perhaps because input is a STDIO stream)\nPlease use --output to set one explicitly." + ); + } + sile_input.set("filenames", input_filenames)?; + let input_uses: LuaTable = sile_input.get("uses")?; + let r#use: LuaFunction = sile.get("use")?; + for spec in input_uses.sequence_values::() { + let spec = spec?; + let module: LuaString = spec.get("module")?; + let options: LuaTable = spec.get("options")?; + r#use.call::<(LuaString, LuaTable), _>((module, options))?; + } + let input_filenames: LuaTable = sile_input.get("filenames")?; + let process_file: LuaFunction = sile.get("processFile")?; + for file in input_filenames.sequence_values::() { + process_file.call::(file?)?; + } + let finish: LuaFunction = sile.get("finish")?; + finish.call::<_, _>(())?; + } else { + let repl_module: LuaString = lua.create_string("core.repl")?; + let r#require: LuaFunction = lua.globals().get("require")?; + let repl: LuaTable = r#require.call::(repl_module)?; + repl.call_method::<_, _>("enter", ())?; + } + Ok(()) +} + +fn path_to_string(path: &PathBuf) -> String { + path.clone().into_os_string().into_string().unwrap() +} + +fn paths_to_strings(paths: Vec) -> Vec { + paths.iter().map(path_to_string).collect() +} diff --git a/tests/absmin.expected b/tests/absmin.expected index 71163b2a51..59c4dc8ce1 100644 --- a/tests/absmin.expected +++ b/tests/absmin.expected @@ -1,6 +1,6 @@ Set paper size 595.275597 841.8897729 Begin page -Mx 49.7638 +Mx 41.7638 My 48.3445 Set font Gentium Plus;10;400;;normal;;;LTR T 87 72 86 87 w=15.3662 (test) diff --git a/tests/alignment.expected b/tests/alignment.expected index e8edf7e082..be4fe4129b 100644 --- a/tests/alignment.expected +++ b/tests/alignment.expected @@ -1,6 +1,6 @@ Set paper size 297.6377985 419.5275636 Begin page -Mx 29.9059 +Mx 33.9059 My 27.9564 Set font Libertinus Serif;10;400;Italic;normal;;;LTR Mx 5.8700 @@ -10,9 +10,9 @@ Mx 5.0000 Mx 4.5400 Mx 5.0600 T 51 a=5.8700 66 a=4.5700 72 a=5.0000 72 a=5.0000 70 a=4.4700 69 a=5.0600 (Ragged) -Mx 62.9459 +Mx 66.4459 T 77 70 2385 w=13.0700 (left) -Mx 79.0159 +Mx 82.0159 Set font Libertinus Serif;10;400;;normal;;;LTR Mx 2.6400 Mx 5.0400 @@ -20,31 +20,31 @@ Mx 3.6400 Mx 4.4700 Mx 7.9000 T 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 78 a=7.9000 (lorem) -Mx 105.7059 +Mx 108.2059 T 74 81 84 86 78 w=25.0100 (ipsum) -Mx 133.7159 +Mx 135.7159 T 69 80 77 80 83 w=21.5000 (dolor) -Mx 158.2159 +Mx 159.7159 T 84 74 85 w=9.7700 (sit) -Mx 170.9859 +Mx 171.9859 T 66 78 70 85 w=20.1000 (amet) -Mx 194.0859 +Mx 194.5859 T 68 80 79 84 70 85 70 85 86 83 w=42.9300 (consetetur) Mx 240.0159 T 84 66 69 74 81 84 68 74 79 72 w=42.7400 (sadipscing) -Mx 36.3059 +Mx 14.8819 My 39.9564 T 70 77 74 85 83 w=16.7000 (elitr) -Mx 56.0059 +Mx 33.8353 Mx 3.9000 Mx 4.5400 Mx 5.0600 T 84 a=3.9000 70 a=4.4700 69 a=5.0600 (sed) -Mx 72.5059 +Mx 49.5887 T 69 74 66 78 w=20.2400 (diam) -Mx 95.7459 +Mx 72.0821 T 79 80 79 86 78 90 w=34.2400 (nonumy) -Mx 132.9859 +Mx 108.5755 Mx 4.4700 Mx 2.7100 Mx 3.7200 @@ -52,7 +52,7 @@ Mx 7.9000 Mx 5.1100 Mx 5.0600 T 70 a=4.4700 74 a=2.7100 83 a=3.7200 78 a=7.9000 80 a=5.0400 69 a=5.0600 (eirmod) -Mx 164.9559 +Mx 139.7989 Mx 3.1600 Mx 4.4700 Mx 7.9000 @@ -60,11 +60,11 @@ Mx 5.2600 Mx 5.0400 Mx 3.7200 T 85 a=3.1600 70 a=4.4700 78 a=7.9000 81 a=5.1900 80 a=5.0400 83 a=3.7200 (tempor) -Mx 197.5059 +Mx 171.6023 T 74 79 87 74 69 86 79 85 w=34.7600 (invidunt) -Mx 235.2659 +Mx 208.6157 T 86 85 w=8.4700 (ut) -Mx 246.7359 +Mx 219.3391 Mx 2.6400 Mx 4.5700 Mx 5.0300 @@ -72,10 +72,9 @@ Mx 5.0400 Mx 3.6400 Mx 4.4700 T 77 a=2.6400 66 a=4.5700 67 a=4.9300 80 a=5.0400 83 a=3.7200 70 a=4.4700 (labore) -Mx 275.1259 +Mx 246.9825 T 70 85 w=7.6300 (et) -Mx 28.8759 -My 51.9564 +Mx 256.8659 Mx 5.0600 Mx 5.0400 Mx 2.6400 @@ -83,20 +82,21 @@ Mx 5.0400 Mx 3.6400 Mx 4.4700 T 69 a=5.0600 80 a=5.0400 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 (dolore) -Mx 57.7659 +Mx 14.8819 +My 51.9564 T 78 66 72 79 66 w=27.4600 (magna) -Mx 88.2259 +Mx 44.8368 T 66 77 74 82 86 90 66 78 w=37.8800 (aliquyam) -Mx 129.1059 +Mx 85.2117 T 70 83 66 85 w=15.9200 (erat) -Mx 148.0259 +Mx 103.6266 Mx 3.9000 Mx 4.5400 Mx 5.0600 T 84 a=3.9000 70 a=4.4700 69 a=5.0600 (sed) -Mx 164.5259 +Mx 119.6215 T 69 74 66 78 w=20.2400 (diam) -Mx 187.7659 +Mx 142.3564 Mx 4.8900 Mx 5.0400 Mx 2.6400 @@ -106,31 +106,31 @@ Mx 3.1600 Mx 5.3100 Mx 4.5700 T 87 a=4.9700 80 a=5.0400 77 a=2.6400 86 a=5.3100 81 a=5.1900 85 a=3.1600 86 a=5.3100 66 a=4.5700 (voluptua) -Mx 226.8759 +Mx 180.9614 T 66 85 w=7.7300 (at) -Mx 237.6059 +Mx 191.1863 Mx 4.8900 Mx 4.4700 Mx 3.6400 Mx 5.0400 T 87 a=4.9700 70 a=4.4700 83 a=3.7200 80 a=5.0400 (vero) -Mx 258.6459 +Mx 211.7212 Mx 4.5400 Mx 5.0400 Mx 3.9000 T 70 a=4.4700 80 a=5.0400 84 a=3.9000 (eos) -Mx 275.1259 +Mx 227.6961 T 70 85 w=7.6300 (et) -Mx 51.3659 -My 63.9564 +Mx 237.8210 T 66 68 68 86 84 66 78 w=34.8100 (accusam) -Mx 89.1759 +Mx 275.1259 T 70 85 w=7.6300 (et) -Mx 99.8059 +Mx 59.2459 +My 63.9564 T 75 86 84 85 80 w=20.1300 (justo) -Mx 122.9359 +Mx 81.8759 T 69 86 80 w=15.4100 (duo) -Mx 141.3459 +Mx 99.7859 Mx 5.0600 Mx 5.0400 Mx 2.6400 @@ -139,25 +139,24 @@ Mx 3.6400 Mx 4.4700 Mx 3.9000 T 69 a=5.0600 80 a=5.0400 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 84 a=3.9000 (dolores) -Mx 174.1359 +Mx 132.0759 T 70 85 w=7.6300 (et) -Mx 184.7659 +Mx 142.2059 T 70 66 w=9.0400 (ea) -Mx 196.8059 +Mx 153.7459 Mx 3.6400 Mx 4.4700 Mx 4.9300 Mx 5.3100 Mx 7.9000 T 83 a=3.7200 70 a=4.4700 67 a=4.9300 86 a=5.3100 78 a=7.9000 (rebum) -Mx 226.0559 +Mx 182.4959 T 84 85 70 85 w=14.6900 (stet) -Mx 243.7459 +Mx 199.6859 T 68 77 74 85 66 w=17.3600 (clita) -Mx 264.1059 +Mx 219.5459 T 76 66 84 69 w=18.6500 (kasd) Mx 240.6959 -My 75.9564 Mx 5.0000 Mx 5.3100 Mx 5.0300 @@ -168,8 +167,8 @@ Mx 3.6400 Mx 4.4700 Mx 5.4200 T 72 a=5.0000 86 a=5.3100 67 a=4.9300 70 a=4.4700 83 a=3.7200 72 a=5.0000 83 a=3.7200 70 a=4.4700 79 a=5.4200 (gubergren) -Mx 14.8819 -My 107.9564 +Mx 26.8819 +My 95.9564 Set font Libertinus Serif;10;400;Italic;normal;;;LTR Mx 5.8700 Mx 4.5700 @@ -178,9 +177,9 @@ Mx 5.0000 Mx 4.5400 Mx 5.0600 T 51 a=5.8700 66 a=4.5700 72 a=5.0000 72 a=5.0000 70 a=4.4700 69 a=5.0600 (Ragged) -Mx 47.9219 +Mx 59.4219 T 83 74 72 73 85 w=19.9700 (right) -Mx 70.8919 +Mx 81.8919 Set font Libertinus Serif;10;400;;normal;;;LTR Mx 2.6400 Mx 5.0400 @@ -188,31 +187,31 @@ Mx 3.6400 Mx 4.4700 Mx 7.9000 T 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 78 a=7.9000 (lorem) -Mx 97.5819 +Mx 108.0819 T 74 81 84 86 78 w=25.0100 (ipsum) -Mx 125.5919 +Mx 135.5919 T 69 80 77 80 83 w=21.5000 (dolor) -Mx 150.0919 +Mx 159.5919 T 84 74 85 w=9.7700 (sit) -Mx 162.8619 +Mx 171.8619 T 66 78 70 85 w=20.1000 (amet) -Mx 185.9619 +Mx 194.4619 T 68 80 79 84 70 85 70 85 86 83 w=42.9300 (consetetur) -Mx 231.8919 +Mx 239.8919 T 84 66 69 74 81 84 68 74 79 72 w=42.7400 (sadipscing) Mx 14.8819 -My 119.9564 +My 107.9564 T 70 77 74 85 83 w=16.7000 (elitr) -Mx 34.5819 +Mx 33.8353 Mx 3.9000 Mx 4.5400 Mx 5.0600 T 84 a=3.9000 70 a=4.4700 69 a=5.0600 (sed) -Mx 51.0819 +Mx 49.5887 T 69 74 66 78 w=20.2400 (diam) -Mx 74.3219 +Mx 72.0821 T 79 80 79 86 78 90 w=34.2400 (nonumy) -Mx 111.5619 +Mx 108.5755 Mx 4.4700 Mx 2.7100 Mx 3.7200 @@ -220,7 +219,7 @@ Mx 7.9000 Mx 5.1100 Mx 5.0600 T 70 a=4.4700 74 a=2.7100 83 a=3.7200 78 a=7.9000 80 a=5.0400 69 a=5.0600 (eirmod) -Mx 143.5319 +Mx 139.7989 Mx 3.1600 Mx 4.4700 Mx 7.9000 @@ -228,11 +227,11 @@ Mx 5.2600 Mx 5.0400 Mx 3.7200 T 85 a=3.1600 70 a=4.4700 78 a=7.9000 81 a=5.1900 80 a=5.0400 83 a=3.7200 (tempor) -Mx 176.0819 +Mx 171.6023 T 74 79 87 74 69 86 79 85 w=34.7600 (invidunt) -Mx 213.8419 +Mx 208.6157 T 86 85 w=8.4700 (ut) -Mx 225.3119 +Mx 219.3391 Mx 2.6400 Mx 4.5700 Mx 5.0300 @@ -240,10 +239,9 @@ Mx 5.0400 Mx 3.6400 Mx 4.4700 T 77 a=2.6400 66 a=4.5700 67 a=4.9300 80 a=5.0400 83 a=3.7200 70 a=4.4700 (labore) -Mx 253.7019 +Mx 246.9825 T 70 85 w=7.6300 (et) -Mx 14.8819 -My 131.9564 +Mx 256.8659 Mx 5.0600 Mx 5.0400 Mx 2.6400 @@ -251,20 +249,21 @@ Mx 5.0400 Mx 3.6400 Mx 4.4700 T 69 a=5.0600 80 a=5.0400 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 (dolore) -Mx 43.7719 +Mx 14.8819 +My 119.9564 T 78 66 72 79 66 w=27.4600 (magna) -Mx 74.2319 +Mx 44.8368 T 66 77 74 82 86 90 66 78 w=37.8800 (aliquyam) -Mx 115.1119 +Mx 85.2117 T 70 83 66 85 w=15.9200 (erat) -Mx 134.0319 +Mx 103.6266 Mx 3.9000 Mx 4.5400 Mx 5.0600 T 84 a=3.9000 70 a=4.4700 69 a=5.0600 (sed) -Mx 150.5319 +Mx 119.6215 T 69 74 66 78 w=20.2400 (diam) -Mx 173.7719 +Mx 142.3564 Mx 4.8900 Mx 5.0400 Mx 2.6400 @@ -274,31 +273,31 @@ Mx 3.1600 Mx 5.3100 Mx 4.5700 T 87 a=4.9700 80 a=5.0400 77 a=2.6400 86 a=5.3100 81 a=5.1900 85 a=3.1600 86 a=5.3100 66 a=4.5700 (voluptua) -Mx 212.8819 +Mx 180.9614 T 66 85 w=7.7300 (at) -Mx 223.6119 +Mx 191.1863 Mx 4.8900 Mx 4.4700 Mx 3.6400 Mx 5.0400 T 87 a=4.9700 70 a=4.4700 83 a=3.7200 80 a=5.0400 (vero) -Mx 244.6519 +Mx 211.7212 Mx 4.5400 Mx 5.0400 Mx 3.9000 T 70 a=4.4700 80 a=5.0400 84 a=3.9000 (eos) -Mx 261.1319 +Mx 227.6961 T 70 85 w=7.6300 (et) -Mx 14.8819 -My 143.9564 +Mx 237.8210 T 66 68 68 86 84 66 78 w=34.8100 (accusam) -Mx 52.6919 +Mx 275.1259 T 70 85 w=7.6300 (et) -Mx 63.3219 +Mx 14.8819 +My 131.9564 T 75 86 84 85 80 w=20.1300 (justo) -Mx 86.4519 +Mx 37.5119 T 69 86 80 w=15.4100 (duo) -Mx 104.8619 +Mx 55.4219 Mx 5.0600 Mx 5.0400 Mx 2.6400 @@ -307,25 +306,24 @@ Mx 3.6400 Mx 4.4700 Mx 3.9000 T 69 a=5.0600 80 a=5.0400 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 84 a=3.9000 (dolores) -Mx 137.6519 +Mx 87.7119 T 70 85 w=7.6300 (et) -Mx 148.2819 +Mx 97.8419 T 70 66 w=9.0400 (ea) -Mx 160.3219 +Mx 109.3819 Mx 3.6400 Mx 4.4700 Mx 4.9300 Mx 5.3100 Mx 7.9000 T 83 a=3.7200 70 a=4.4700 67 a=4.9300 86 a=5.3100 78 a=7.9000 (rebum) -Mx 189.5719 +Mx 138.1319 T 84 85 70 85 w=14.6900 (stet) -Mx 207.2619 +Mx 155.3219 T 68 77 74 85 66 w=17.3600 (clita) -Mx 227.6219 +Mx 175.1819 T 76 66 84 69 w=18.6500 (kasd) -Mx 14.8819 -My 155.9564 +Mx 196.3319 Mx 5.0000 Mx 5.3100 Mx 5.0300 @@ -336,8 +334,8 @@ Mx 3.6400 Mx 4.4700 Mx 5.4200 T 72 a=5.0000 86 a=5.3100 67 a=4.9300 70 a=4.4700 83 a=3.7200 72 a=5.0000 83 a=3.7200 70 a=4.4700 79 a=5.4200 (gubergren) -Mx 16.9889 -My 187.9564 +Mx 18.9889 +My 163.9564 Set font Libertinus Serif;10;400;Italic;normal;;;LTR Mx 6.4600 Mx 4.4700 @@ -348,7 +346,7 @@ Mx 3.6400 Mx 4.5400 Mx 5.0600 T 36 a=6.4600 70 a=4.4700 79 a=5.4200 85 a=3.1600 70 a=4.4700 83 a=3.7200 70 a=4.4700 69 a=5.0600 (Centered) -Mx 57.2089 +Mx 58.7089 Set font Libertinus Serif;10;400;;normal;;;LTR Mx 2.6400 Mx 5.0400 @@ -356,31 +354,31 @@ Mx 3.6400 Mx 4.4700 Mx 7.9000 T 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 78 a=7.9000 (lorem) -Mx 83.8989 +Mx 84.8989 T 74 81 84 86 78 w=25.0100 (ipsum) -Mx 111.9089 +Mx 112.4089 T 69 80 77 80 83 w=21.5000 (dolor) Mx 136.4089 T 84 74 85 w=9.7700 (sit) -Mx 149.1789 +Mx 148.6789 T 66 78 70 85 w=20.1000 (amet) -Mx 172.2789 +Mx 171.2789 T 68 80 79 84 70 85 70 85 86 83 w=42.9300 (consetetur) -Mx 218.2089 +Mx 216.7089 T 84 66 69 74 81 84 68 74 79 72 w=42.7400 (sadipscing) -Mx 263.9489 +Mx 261.9489 T 70 77 74 85 83 w=16.7000 (elitr) -Mx 20.9989 -My 199.9564 +Mx 23.2489 +My 175.9564 Mx 3.9000 Mx 4.5400 Mx 5.0600 T 84 a=3.9000 70 a=4.4700 69 a=5.0600 (sed) -Mx 37.4989 +Mx 39.2489 T 69 74 66 78 w=20.2400 (diam) -Mx 60.7389 +Mx 61.9889 T 79 80 79 86 78 90 w=34.2400 (nonumy) -Mx 97.9789 +Mx 98.7289 Mx 4.4700 Mx 2.7100 Mx 3.7200 @@ -388,7 +386,7 @@ Mx 7.9000 Mx 5.1100 Mx 5.0600 T 70 a=4.4700 74 a=2.7100 83 a=3.7200 78 a=7.9000 80 a=5.0400 69 a=5.0600 (eirmod) -Mx 129.9489 +Mx 130.1989 Mx 3.1600 Mx 4.4700 Mx 7.9000 @@ -396,11 +394,11 @@ Mx 5.2600 Mx 5.0400 Mx 3.7200 T 85 a=3.1600 70 a=4.4700 78 a=7.9000 81 a=5.1900 80 a=5.0400 83 a=3.7200 (tempor) -Mx 162.4989 +Mx 162.2489 T 74 79 87 74 69 86 79 85 w=34.7600 (invidunt) -Mx 200.2589 +Mx 199.5089 T 86 85 w=8.4700 (ut) -Mx 211.7289 +Mx 210.4789 Mx 2.6400 Mx 4.5700 Mx 5.0300 @@ -408,9 +406,9 @@ Mx 5.0400 Mx 3.6400 Mx 4.4700 T 77 a=2.6400 66 a=4.5700 67 a=4.9300 80 a=5.0400 83 a=3.7200 70 a=4.4700 (labore) -Mx 240.1189 +Mx 238.3689 T 70 85 w=7.6300 (et) -Mx 250.7489 +Mx 248.4989 Mx 5.0600 Mx 5.0400 Mx 2.6400 @@ -418,21 +416,21 @@ Mx 5.0400 Mx 3.6400 Mx 4.4700 T 69 a=5.0600 80 a=5.0400 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 (dolore) -Mx 17.4189 -My 211.9564 +Mx 14.8819 +My 187.9564 T 78 66 72 79 66 w=27.4600 (magna) -Mx 47.8789 +Mx 44.8368 T 66 77 74 82 86 90 66 78 w=37.8800 (aliquyam) -Mx 88.7589 +Mx 85.2117 T 70 83 66 85 w=15.9200 (erat) -Mx 107.6789 +Mx 103.6266 Mx 3.9000 Mx 4.5400 Mx 5.0600 T 84 a=3.9000 70 a=4.4700 69 a=5.0600 (sed) -Mx 124.1789 +Mx 119.6215 T 69 74 66 78 w=20.2400 (diam) -Mx 147.4189 +Mx 142.3564 Mx 4.8900 Mx 5.0400 Mx 2.6400 @@ -442,31 +440,31 @@ Mx 3.1600 Mx 5.3100 Mx 4.5700 T 87 a=4.9700 80 a=5.0400 77 a=2.6400 86 a=5.3100 81 a=5.1900 85 a=3.1600 86 a=5.3100 66 a=4.5700 (voluptua) -Mx 186.5289 +Mx 180.9614 T 66 85 w=7.7300 (at) -Mx 197.2589 +Mx 191.1863 Mx 4.8900 Mx 4.4700 Mx 3.6400 Mx 5.0400 T 87 a=4.9700 70 a=4.4700 83 a=3.7200 80 a=5.0400 (vero) -Mx 218.2989 +Mx 211.7212 Mx 4.5400 Mx 5.0400 Mx 3.9000 T 70 a=4.4700 80 a=5.0400 84 a=3.9000 (eos) -Mx 234.7789 +Mx 227.6961 T 70 85 w=7.6300 (et) -Mx 245.4089 +Mx 237.8210 T 66 68 68 86 84 66 78 w=34.8100 (accusam) -Mx 29.4989 -My 223.9564 +Mx 275.1259 T 70 85 w=7.6300 (et) -Mx 40.1289 +Mx 37.0639 +My 199.9564 T 75 86 84 85 80 w=20.1300 (justo) -Mx 63.2589 +Mx 59.6939 T 69 86 80 w=15.4100 (duo) -Mx 81.6689 +Mx 77.6039 Mx 5.0600 Mx 5.0400 Mx 2.6400 @@ -475,24 +473,24 @@ Mx 3.6400 Mx 4.4700 Mx 3.9000 T 69 a=5.0600 80 a=5.0400 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 84 a=3.9000 (dolores) -Mx 114.4589 +Mx 109.8939 T 70 85 w=7.6300 (et) -Mx 125.0889 +Mx 120.0239 T 70 66 w=9.0400 (ea) -Mx 137.1289 +Mx 131.5639 Mx 3.6400 Mx 4.4700 Mx 4.9300 Mx 5.3100 Mx 7.9000 T 83 a=3.7200 70 a=4.4700 67 a=4.9300 86 a=5.3100 78 a=7.9000 (rebum) -Mx 166.3789 +Mx 160.3139 T 84 85 70 85 w=14.6900 (stet) -Mx 184.0689 +Mx 177.5039 T 68 77 74 85 66 w=17.3600 (clita) -Mx 204.4289 +Mx 197.3639 T 76 66 84 69 w=18.6500 (kasd) -Mx 226.0789 +Mx 218.5139 Mx 5.0000 Mx 5.3100 Mx 5.0300 @@ -503,8 +501,8 @@ Mx 3.6400 Mx 4.4700 Mx 5.4200 T 72 a=5.0000 86 a=5.3100 67 a=4.9300 70 a=4.4700 83 a=3.7200 72 a=5.0000 83 a=3.7200 70 a=4.4700 79 a=5.4200 (gubergren) -Mx 34.8819 -My 255.9564 +Mx 26.8819 +My 231.9564 Set font Libertinus Serif;10;400;Italic;normal;;;LTR Mx 3.2200 Mx 5.3100 @@ -515,7 +513,7 @@ Mx 5.6000 Mx 4.5400 Mx 5.0600 T 43 a=3.2200 86 a=5.3100 84 a=3.9000 85 a=3.1600 74 a=2.7100 2361 a=5.6000 70 a=4.4700 69 a=5.0600 (Justified) -Mx 72.4725 +Mx 62.8736 Set font Libertinus Serif;10;400;;normal;;;LTR Mx 2.6400 Mx 5.0400 @@ -523,31 +521,31 @@ Mx 3.6400 Mx 4.4700 Mx 7.9000 T 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 78 a=7.9000 (lorem) -Mx 100.2530 +Mx 89.0554 T 74 81 84 86 78 w=25.0100 (ipsum) -Mx 129.3536 +Mx 116.5571 T 69 80 77 80 83 w=21.5000 (dolor) -Mx 154.9442 +Mx 140.5489 T 84 74 85 w=9.7700 (sit) -Mx 168.8048 +Mx 152.8107 T 66 78 70 85 w=20.1000 (amet) -Mx 192.9953 +Mx 175.4024 T 68 80 79 84 70 85 70 85 86 83 w=42.9300 (consetetur) -Mx 240.0159 +Mx 220.8242 T 84 66 69 74 81 84 68 74 79 72 w=42.7400 (sadipscing) -Mx 14.8819 -My 267.9564 +Mx 266.0559 T 70 77 74 85 83 w=16.7000 (elitr) -Mx 33.8353 +Mx 14.8819 +My 243.9564 Mx 3.9000 Mx 4.5400 Mx 5.0600 T 84 a=3.9000 70 a=4.4700 69 a=5.0600 (sed) -Mx 49.5887 +Mx 30.7203 T 69 74 66 78 w=20.2400 (diam) -Mx 72.0821 +Mx 53.2987 T 79 80 79 86 78 90 w=34.2400 (nonumy) -Mx 108.5755 +Mx 89.8771 Mx 4.4700 Mx 2.7100 Mx 3.7200 @@ -555,7 +553,7 @@ Mx 7.9000 Mx 5.1100 Mx 5.0600 T 70 a=4.4700 74 a=2.7100 83 a=3.7200 78 a=7.9000 80 a=5.0400 69 a=5.0600 (eirmod) -Mx 139.7989 +Mx 121.1855 Mx 3.1600 Mx 4.4700 Mx 7.9000 @@ -563,11 +561,11 @@ Mx 5.2600 Mx 5.0400 Mx 3.7200 T 85 a=3.1600 70 a=4.4700 78 a=7.9000 81 a=5.1900 80 a=5.0400 83 a=3.7200 (tempor) -Mx 171.6023 +Mx 153.0739 T 74 79 87 74 69 86 79 85 w=34.7600 (invidunt) -Mx 208.6157 +Mx 190.1723 T 86 85 w=8.4700 (ut) -Mx 219.3391 +Mx 200.9807 Mx 2.6400 Mx 4.5700 Mx 5.0300 @@ -575,9 +573,9 @@ Mx 5.0400 Mx 3.6400 Mx 4.4700 T 77 a=2.6400 66 a=4.5700 67 a=4.9300 80 a=5.0400 83 a=3.7200 70 a=4.4700 (labore) -Mx 246.9825 +Mx 228.7091 T 70 85 w=7.6300 (et) -Mx 256.8659 +Mx 238.6775 Mx 5.0600 Mx 5.0400 Mx 2.6400 @@ -585,21 +583,25 @@ Mx 5.0400 Mx 3.6400 Mx 4.4700 T 69 a=5.0600 80 a=5.0400 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 (dolore) +Mx 266.9059 +T 78 66 w=12.4700 (ma) +Mx 279.3759 +T 14 w=3.3800 (-) Mx 14.8819 -My 279.9564 -T 78 66 72 79 66 w=27.4600 (magna) -Mx 45.8493 +My 255.9564 +T 72 79 66 w=14.9900 (gna) +Mx 33.5004 T 66 77 74 82 86 90 66 78 w=37.8800 (aliquyam) -Mx 87.2367 +Mx 75.0090 T 70 83 66 85 w=15.9200 (erat) -Mx 106.6641 +Mx 94.5575 Mx 3.9000 Mx 4.5400 Mx 5.0600 T 84 a=3.9000 70 a=4.4700 69 a=5.0600 (sed) -Mx 123.6715 +Mx 111.6861 T 69 74 66 78 w=20.2400 (diam) -Mx 147.4189 +Mx 135.5546 Mx 4.8900 Mx 5.0400 Mx 2.6400 @@ -609,31 +611,31 @@ Mx 3.1600 Mx 5.3100 Mx 4.5700 T 87 a=4.9700 80 a=5.0400 77 a=2.6400 86 a=5.3100 81 a=5.1900 85 a=3.1600 86 a=5.3100 66 a=4.5700 (voluptua) -Mx 187.0363 +Mx 175.2932 T 66 85 w=7.7300 (at) -Mx 198.2737 +Mx 186.6517 Mx 4.8900 Mx 4.4700 Mx 3.6400 Mx 5.0400 T 87 a=4.9700 70 a=4.4700 83 a=3.7200 80 a=5.0400 (vero) -Mx 219.8211 +Mx 208.3203 Mx 4.5400 Mx 5.0400 Mx 3.9000 T 70 a=4.4700 80 a=5.0400 84 a=3.9000 (eos) -Mx 236.8085 +Mx 225.4288 T 70 85 w=7.6300 (et) -Mx 247.9459 +Mx 236.6874 T 66 68 68 86 84 66 78 w=34.8100 (accusam) -Mx 14.8819 -My 291.9564 +Mx 275.1259 T 70 85 w=7.6300 (et) -Mx 25.5155 +Mx 14.8819 +My 267.9564 T 75 86 84 85 80 w=20.1300 (justo) -Mx 48.6492 +Mx 37.5174 T 69 86 80 w=15.4100 (duo) -Mx 67.0628 +Mx 55.4330 Mx 5.0600 Mx 5.0400 Mx 2.6400 @@ -642,24 +644,24 @@ Mx 3.6400 Mx 4.4700 Mx 3.9000 T 69 a=5.0600 80 a=5.0400 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 84 a=3.9000 (dolores) -Mx 99.8565 +Mx 87.7285 T 70 85 w=7.6300 (et) -Mx 110.4901 +Mx 97.8640 T 70 66 w=9.0400 (ea) -Mx 122.5338 +Mx 109.4096 Mx 3.6400 Mx 4.4700 Mx 4.9300 Mx 5.3100 Mx 7.9000 T 83 a=3.7200 70 a=4.4700 67 a=4.9300 86 a=5.3100 78 a=7.9000 (rebum) -Mx 151.7874 +Mx 138.1651 T 84 85 70 85 w=14.6900 (stet) -Mx 169.4811 +Mx 155.3607 T 68 77 74 85 66 w=17.3600 (clita) -Mx 189.8447 +Mx 175.2262 T 76 66 84 69 w=18.6500 (kasd) -Mx 211.4984 +Mx 196.3817 Mx 5.0000 Mx 5.3100 Mx 5.0300 @@ -670,11 +672,11 @@ Mx 3.6400 Mx 4.4700 Mx 5.4200 T 72 a=5.0000 86 a=5.3100 67 a=4.9300 70 a=4.4700 83 a=3.7200 72 a=5.0000 83 a=3.7200 70 a=4.4700 79 a=5.4200 (gubergren) -Mx 135.7259 -My 323.9564 +Mx 138.2259 +My 299.9564 Set font Libertinus Serif;10;400;Italic;normal;;;LTR T 51 74 72 73 85 w=22.1200 (Right) -Mx 160.8459 +Mx 162.8459 Mx 4.5700 Mx 2.6400 Mx 2.7100 @@ -683,7 +685,7 @@ Mx 5.4200 Mx 4.5400 Mx 5.0600 T 66 a=4.5700 77 a=2.6400 74 a=2.7100 72 a=5.0000 79 a=5.4200 70 a=4.4700 69 a=5.0600 (aligned) -Mx 193.7859 +Mx 195.2859 Set font Libertinus Serif;10;400;;normal;;;LTR Mx 2.6400 Mx 5.0400 @@ -691,9 +693,9 @@ Mx 3.6400 Mx 4.4700 Mx 7.9000 T 77 a=2.6400 80 a=5.0400 83 a=3.7200 70 a=4.4700 78 a=7.9000 (lorem) -Mx 220.4759 +Mx 221.4759 T 74 81 84 86 78 w=25.0100 (ipsum) -Mx 248.4859 +Mx 248.9859 T 69 80 77 80 83 w=21.5000 (dolor) Mx 272.9859 T 84 74 85 w=9.7700 (sit) diff --git a/tests/amharic.sil b/tests/amharic.sil index b22d154f83..247d2d62e3 100644 --- a/tests/amharic.sil +++ b/tests/amharic.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \font[family=Noto Sans Ethiopic,language=am] የሰው፡ልጅ፡ሁሉ፡ሲወለድ፡ነጻና፡በክብርና፡በመብትም፡እኩልነት፡ያለው፡ነው።፡የተፈጥሮ፡ማስተዋልና፡ሕሊና፡ስላለው፡አንዱ፡ሌላውን፡በወንድማማችነት፡መንፈስ፡መመልከት፡ይገባዋል። diff --git a/tests/arabic-scripts.sil b/tests/arabic-scripts.sil index 10ec342c18..bf939b49df 100644 --- a/tests/arabic-scripts.sil +++ b/tests/arabic-scripts.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.bidi] \font[family=LateefGR,size=16pt,language=en] diff --git a/tests/bibtex.sil b/tests/bibtex.sil index d50221a8a0..06adcc5c4e 100644 --- a/tests/bibtex.sil +++ b/tests/bibtex.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \use[module=packages.bibtex] diff --git a/tests/bidi.sil b/tests/bidi.sil index 1e26ebff01..6f4a8a900a 100644 --- a/tests/bidi.sil +++ b/tests/bidi.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \font[family=SBL Hebrew,language=he] \font[size=25pt] \set[parameter=document.baselineskip,value=29pt] diff --git a/tests/bug-1003.expected b/tests/bug-1003.expected index ad865893c3..586dcfbf31 100644 --- a/tests/bug-1003.expected +++ b/tests/bug-1003.expected @@ -1,6 +1,6 @@ Set paper size 297.6377985 419.5275636 Begin page -Mx 142.2344 +Mx 14.8819 My 28.5447 Set font Gentium Plus;10;400;;normal;;;LTR T 73 82 82 w=13.1689 (foo) diff --git a/tests/bug-1044.sil b/tests/bug-1044.sil index 26d156816f..f9962bf15c 100644 --- a/tests/bug-1044.sil +++ b/tests/bug-1044.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \font[family=Libertinus Serif]{Ta of} diff --git a/tests/bug-1047.expected b/tests/bug-1047.expected new file mode 100644 index 0000000000..1328ef9908 --- /dev/null +++ b/tests/bug-1047.expected @@ -0,0 +1,17 @@ +Set paper size 297.6377985 419.5275636 +Begin page +Mx 24.7039 +My 42.3452 +Set font FRB Taiwanese Kana;24;400;;normal;+ruby;;LTR +T 172 w=24.0000 (タ) +Mx 48.7039 +T 145 w=24.0000 (イ) +Mx 72.7039 +T 122 w=12.0000 (𚿳) +Mx 24.7039 +My 82.3452 +Mx 34.7039 +Set font Noto Serif CJK TC;40;400;;normal;;;LTR +T 34119 w=40.0000 (臺) +End page +Finish diff --git a/tests/bug-1047.sil b/tests/bug-1047.sil new file mode 100644 index 0000000000..dd07fbbaa7 --- /dev/null +++ b/tests/bug-1047.sil @@ -0,0 +1,10 @@ +\begin[class=jplain,papersize=a6]{document} +\nofolios +\neverindent +\language[main=zh] +\use[module=packages.ruby] +\font:remove-fallback +\font:add-fallback[family=Noto Serif CJK TC] +\font[family=FRB Taiwanese Kana,size=40pt] +\ruby[reading=タイ𚿳]{臺} +\end{document} diff --git a/tests/bug-1101.sil b/tests/bug-1101.sil index ddc7e0a01a..d2b2f6102f 100644 --- a/tests/bug-1101.sil +++ b/tests/bug-1101.sil @@ -1,4 +1,5 @@ \begin[class=jplain,layout=tate,papersize=a4]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios diff --git a/tests/bug-1142.expected b/tests/bug-1142.expected index c276b649c6..f37cbc8e37 100644 --- a/tests/bug-1142.expected +++ b/tests/bug-1142.expected @@ -4,14 +4,14 @@ Mx 14.8819 My 28.9764 Set font ;10;400;;normal;;;LTR;tests/TestCLR-Regular.ttf T 3 w=10.0000 (a) -Push color 1.0000 0.8784 0.5412 +Push color C Mx 24.8819 T 7 a=10.0000 () Pop color -Push color 0.8980 0.6078 0.1451 +Push color C T 8 a=10.0000 () Pop color -Push color 0.5176 0.2196 0.0510 +Push color C T 9 w=10.0000 (☺) Pop color Mx 34.8819 diff --git a/tests/bug-117.sil b/tests/bug-117.sil index ed044f8f1a..60250d6f35 100644 --- a/tests/bug-117.sil +++ b/tests/bug-117.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] %nice script from SILE book \begin{script} for i=1,10 do diff --git a/tests/bug-1171.expected b/tests/bug-1171.expected index 7100a6dbf4..0a30d6b1f1 100644 --- a/tests/bug-1171.expected +++ b/tests/bug-1171.expected @@ -1,29 +1,29 @@ Set paper size 595.275597 841.8897729 Begin page -Push color 1.0000 0.8000 0.3020 +Push color C Mx 542.4079 My 97.6592 Set font Twemoji Mozilla;10;400;;normal;;;TTB T 7876 a=10.0000 () Pop color -Push color 0.4000 0.2706 0.0000 +Push color C T 7893 a=10.0000 () T 7894 w=10.0000 (😉) Pop color -Push color 1.0000 0.7961 0.2980 +Push color C My 107.6592 T 9105 a=10.0000 () Pop color -Push color 0.3961 0.2784 0.1059 +Push color C T 9106 a=10.0000 () Pop color -Push color 1.0000 1.0000 1.0000 +Push color C T 9107 a=10.0000 () Pop color -Push color 0.3961 0.2784 0.1059 +Push color C T 9108 a=10.0000 () Pop color -Push color 0.3922 0.6667 0.8667 +Push color C T 9109 w=10.0000 (🤣) Pop color End page diff --git a/tests/bug-1244-fallback-after-color.expected b/tests/bug-1244-fallback-after-color.expected index 802612b9ea..08180bdf5e 100644 --- a/tests/bug-1244-fallback-after-color.expected +++ b/tests/bug-1244-fallback-after-color.expected @@ -7,16 +7,16 @@ T 1542 w=10.0000 (ん) Mx 24.8819 Set font Gentium Plus;10;400;;normal;;;LTR T 68 w=4.5898 (a) -Push color 1.0000 0.8784 0.5412 +Push color C Mx 14.8819 My 40.6664 Set font ;10;400;;normal;;;LTR;tests/TestCLR-Regular.ttf T 7 a=10.0000 () Pop color -Push color 0.8980 0.6078 0.1451 +Push color C T 8 a=10.0000 () Pop color -Push color 0.5176 0.2196 0.0510 +Push color C T 9 w=10.0000 (☺) Pop color Mx 24.8819 @@ -28,16 +28,16 @@ T 1542 w=10.0000 (ん) Mx 24.8819 Set font Gentium Plus;10;400;;normal;;;LTR T 68 w=4.5898 (a) -Push color 1.0000 0.8784 0.5412 +Push color C Mx 14.8819 My 64.6664 Set font ;10;400;;normal;;;LTR;tests/TestCLR-Regular.ttf T 7 a=10.0000 () Pop color -Push color 0.8980 0.6078 0.1451 +Push color C T 8 a=10.0000 () Pop color -Push color 0.5176 0.2196 0.0510 +Push color C T 9 w=10.0000 (☺) Pop color Mx 24.8819 diff --git a/tests/bug-1259.sil b/tests/bug-1259.sil index 55f47dfa14..6f1a5dc474 100644 --- a/tests/bug-1259.sil +++ b/tests/bug-1259.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \use[module=packages.rules] diff --git a/tests/bug-1280.expected b/tests/bug-1280.expected index fe96924919..2c0992b8a1 100644 --- a/tests/bug-1280.expected +++ b/tests/bug-1280.expected @@ -1,5 +1,10 @@ Set paper size 425.196855 170.078742 Begin page +Set metadata Author Erwin Schrödinger +Set metadata Title Erwin Schrödinger’s 猫🐈 +Set metadata CreationDate D:19990209153925 - 08 ' 00 ' +Set metadata ModDate should fail +Set metadata Trapped should be skipped Mx 21.2598 My 16.5739 Set font ;10;400;;normal;;;LTR;.fonts/NotoSerifCJK-Regular.ttc diff --git a/tests/bug-1280.sil b/tests/bug-1280.sil index e70e280f52..00c3dd58f3 100644 --- a/tests/bug-1280.sil +++ b/tests/bug-1280.sil @@ -1,4 +1,6 @@ \begin[papersize=15cm x 6cm]{document} +\use[module=packages.retrograde,target=v0.15.0] +\use[module=packages.pdf] \font[filename=.fonts/NotoSerifCJK-Regular.ttc] \nofolios \neverindent @@ -43,11 +45,6 @@ for i = 1, fchecks do check() end - if SILE.backend ~= "debug" then - SILE.use("packages.pdf") - else - SILE.registerCommand("pdf:metadata", function (_, _) end) - end \end{script} \pdf:metadata[key=Author, value=Erwin Schrödinger] \pdf:metadata[key=Title, value=Erwin Schrödinger’s 猫🐈] diff --git a/tests/bug-1297-hyph-fr-ca.sil b/tests/bug-1297-hyph-fr-ca.sil index 77416554c4..03fe2fac18 100644 --- a/tests/bug-1297-hyph-fr-ca.sil +++ b/tests/bug-1297-hyph-fr-ca.sil @@ -1,4 +1,5 @@ \begin[papersize=6in x 9in]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \language[main=fr] diff --git a/tests/bug-1317.sil b/tests/bug-1317.sil index 57413de072..bff19d3f4a 100644 --- a/tests/bug-1317.sil +++ b/tests/bug-1317.sil @@ -1,4 +1,5 @@ \begin[direction=RTL,papersize=a7]{document} +\use[module=packages.retrograde,target=v0.15.0] \font[family=Amiri,size=30pt] \nofolios \neverindent diff --git a/tests/bug-1320.sil b/tests/bug-1320.sil index 285d12f93c..169645c681 100644 --- a/tests/bug-1320.sil +++ b/tests/bug-1320.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \use[module=packages.rules] diff --git a/tests/bug-1343-dotfill-stretch.sil b/tests/bug-1343-dotfill-stretch.sil index 265b76f811..cb4d6d87e5 100644 --- a/tests/bug-1343-dotfill-stretch.sil +++ b/tests/bug-1343-dotfill-stretch.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.leaders] \use[module=packages.lorem] diff --git a/tests/bug-1359.sil b/tests/bug-1359.sil index fc2205d0ea..118a25641e 100644 --- a/tests/bug-1359.sil +++ b/tests/bug-1359.sil @@ -1,4 +1,5 @@ \begin[papersize=6in x 9in]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \language[main=fr] diff --git a/tests/bug-1362.sil b/tests/bug-1362.sil index 5feb574097..8854f72bd5 100644 --- a/tests/bug-1362.sil +++ b/tests/bug-1362.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \language[main=en] diff --git a/tests/bug-1430.sil b/tests/bug-1430.sil index 96788b9f4a..3cd767d5cc 100644 --- a/tests/bug-1430.sil +++ b/tests/bug-1430.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \use[module=packages.grid,spacing=50pt] diff --git a/tests/bug-1442-fallback-font-options.sil b/tests/bug-1442-fallback-font-options.sil index cbaa8f4d8a..3286f03e0e 100644 --- a/tests/bug-1442-fallback-font-options.sil +++ b/tests/bug-1442-fallback-font-options.sil @@ -1,7 +1,7 @@ \begin[papersize=a6]{document} \nofolios \neverindent -\script[src=packages.font-fallback] +\use[module=packages.font-fallback] \font[family=Libertinus Serif,size=50pt] \font:add-fallback[family=Libertinus Sans,size=6pt] \font:add-fallback[family=Symbola,size=.2en] diff --git a/tests/bug-1495-inline-math-layout.sil b/tests/bug-1495-inline-math-layout.sil index ca96b12723..56e2975d69 100644 --- a/tests/bug-1495-inline-math-layout.sil +++ b/tests/bug-1495-inline-math-layout.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \use[module=packages.math] diff --git a/tests/bug-1580.sil b/tests/bug-1580.sil index 3c0d6a3b46..753dd51f45 100644 --- a/tests/bug-1580.sil +++ b/tests/bug-1580.sil @@ -1,4 +1,5 @@ \begin[class=plain,papersize=a7]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \use[module=packages.masters] diff --git a/tests/bug-162.sil b/tests/bug-162.sil index acad9ffa65..87d6d6db18 100644 --- a/tests/bug-162.sil +++ b/tests/bug-162.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] Both lines below should end in a period. \font[language=cs] diff --git a/tests/bug-1647.sil b/tests/bug-1647.sil index 82e70794c7..09789db287 100644 --- a/tests/bug-1647.sil +++ b/tests/bug-1647.sil @@ -1,4 +1,5 @@ \begin[papersize=a7]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \set[parameter=document.parskip, value=50pt] diff --git a/tests/bug-1674.sil b/tests/bug-1674.sil index 209af1e546..e8cb0a0d42 100644 --- a/tests/bug-1674.sil +++ b/tests/bug-1674.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \script{SILE.processString("Foo \\em{bar} \\em{baz}", "sil")} diff --git a/tests/bug-1699.sil b/tests/bug-1699.sil index 32f3f4474c..6a59383a8c 100644 --- a/tests/bug-1699.sil +++ b/tests/bug-1699.sil @@ -1,4 +1,5 @@ \begin[class=plain,papersize=a7]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios % Both "bar" entries should have parindent applied \noindent diff --git a/tests/bug-1708.sil b/tests/bug-1708.sil index cf6d14b1d0..a1c498af0b 100644 --- a/tests/bug-1708.sil +++ b/tests/bug-1708.sil @@ -1,4 +1,5 @@ \begin[papersize=a7,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \set[parameter=document.letterspaceglue, value=5pt] @@ -9,4 +10,4 @@ Lorem ipsum\footnote{test} Lorem ipsum% Should still have letterspacing at 5pt -\end{document} \ No newline at end of file +\end{document} diff --git a/tests/bug-1715.expected b/tests/bug-1715.expected new file mode 100644 index 0000000000..b54b230a4f --- /dev/null +++ b/tests/bug-1715.expected @@ -0,0 +1,16 @@ +Set paper size 209.7637818 297.6377985 +Begin page +Mx 22.4882 +My 22.4502 +Set font Gentium Plus;10;400;;normal;;;LTR +T 37 72 73 82 85 72 w=26.8359 (Before) +Mx 51.5371 +Set font Gentium Plus;10;400;Italic;normal;;;LTR +T 71 88 85 76 81 74 w=25.1221 (during) +Mx 78.8722 +Set font Gentium Plus;10;400;;normal;;;LTR +T 68 73 87 72 85 w=19.7217 (after) +Mx 98.5938 +T 17 w=2.2900 (.) +End page +Finish diff --git a/tests/bug-1715.sil b/tests/bug-1715.sil new file mode 100644 index 0000000000..757fd6f414 --- /dev/null +++ b/tests/bug-1715.sil @@ -0,0 +1,4 @@ +\begin[papersize=a7]{document} +\nofolios +Before \set{\set[parameter=font.style,value=Italic]during} after. +\end{document} diff --git a/tests/bug-182.expected b/tests/bug-182.expected index 468f70b61f..60c444d897 100644 --- a/tests/bug-182.expected +++ b/tests/bug-182.expected @@ -1,6 +1,6 @@ Set paper size 595.275597 841.8897729 Begin page -Mx 256.5354 +Mx 268.5354 My 48.5252 Set font Gentium Plus;10;400;;normal;;;LTR T 36 37 38 w=16.8115 (ABC) diff --git a/tests/bug-192.sil b/tests/bug-192.sil index f0a46b6a52..be773d1210 100644 --- a/tests/bug-192.sil +++ b/tests/bug-192.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[family=Noto Naskh Arabic,language=urd,size=18pt] \hbox\skip[height=95%fh] diff --git a/tests/bug-200.sil b/tests/bug-200.sil index ec9682e676..162b89f92e 100644 --- a/tests/bug-200.sil +++ b/tests/bug-200.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[family=Gentium Plus,size=16pt,language=en] \begin{raggedright} diff --git a/tests/bug-226.sil b/tests/bug-226.sil index 8a4876a907..35212e48c8 100644 --- a/tests/bug-226.sil +++ b/tests/bug-226.sil @@ -1,4 +1,5 @@ \begin{document} +\use[module=packages.retrograde,target=v0.15.0] \hfill Filler content here: Par 1 (One) \hfill Par 2 (Two) @@ -8,4 +9,4 @@ \hfill Out, \hfill Caleb Maclennan -\end{document} \ No newline at end of file +\end{document} diff --git a/tests/bug-241.sil b/tests/bug-241.sil index 4d0d0a810b..4ca7d2ccff 100644 --- a/tests/bug-241.sil +++ b/tests/bug-241.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[family=Gentium Plus,size=9pt] \center{TITILE\break{}Then vfill to logo\vfill{}\par\break} diff --git a/tests/bug-243.sil b/tests/bug-243.sil index bf1377f400..9083807b87 100644 --- a/tests/bug-243.sil +++ b/tests/bug-243.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.leaders] \font[size=50pt] diff --git a/tests/bug-244.expected b/tests/bug-244.expected index 987b52a0e7..364c72c24e 100644 --- a/tests/bug-244.expected +++ b/tests/bug-244.expected @@ -1,12 +1,12 @@ Set paper size 595.275597 841.8897729 Begin page Mx 541.9479 -My 117.7637 +My 117.7638 Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 41 70 77 77 80 w=24.5800 (Hello) -My 145.0785 +My 144.6306 T 88 80 83 77 69 w=26.9700 (world) -My 172.0485 +My 171.6006 T 15 w=2.7800 (.) Mx 298.1329 My 781.8786 diff --git a/tests/bug-252.sil b/tests/bug-252.sil index 3c1f8e81f5..6fed341b70 100644 --- a/tests/bug-252.sil +++ b/tests/bug-252.sil @@ -1,4 +1,5 @@ \begin[papersize=a7,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.frametricks] \use[module=packages.lorem] This is a test\footnote{What happens to frames?}. diff --git a/tests/bug-252a.sil b/tests/bug-252a.sil index b9a4c88407..0e8bd45931 100644 --- a/tests/bug-252a.sil +++ b/tests/bug-252a.sil @@ -1,7 +1,8 @@ \begin[papersize=a7,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lorem] \use[module=packages.rebox] \footnote:separator{\skip[height=5pt]} \define[command=deepbox]{\rebox[depth=60pt,height=10pt]{x}} This is a test\footnote{\deepbox}. -\end{document} \ No newline at end of file +\end{document} diff --git a/tests/bug-254.sil b/tests/bug-254.sil index f9956afbf3..6aa33e71ab 100644 --- a/tests/bug-254.sil +++ b/tests/bug-254.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \raggedright{ \font[weight=600]{This} is a sample of bogus\break extra space. @@ -6,4 +7,4 @@ extra space. \font[weight=600]{This} does not have\break {}extra space. } -\end{document} \ No newline at end of file +\end{document} diff --git a/tests/bug-255.sil b/tests/bug-255.sil index bf9a2d0194..66703138f8 100644 --- a/tests/bug-255.sil +++ b/tests/bug-255.sil @@ -1,10 +1,11 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.frametricks] \script{ SILE.registerCommand("donothing", function (options, content) SILE.settings:pushState() SILE.settings:temporarily(function () - SILE.settings:set("document.lskip", SILE.nodefactory.glue("0pt")) + SILE.settings:set("document.lskip", SILE.types.node.glue("0pt")) SILE.process(content) SILE.call("par") end) diff --git a/tests/bug-255b.sil b/tests/bug-255b.sil index 6c0293fe14..6c0ff4af81 100644 --- a/tests/bug-255b.sil +++ b/tests/bug-255b.sil @@ -1,11 +1,12 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.frametricks] \script{ SILE.registerCommand("donothing", function(o,c) SILE.settings:pushState() SILE.settings:temporarily(function() - SILE.settings:set("document.lskip", SILE.nodefactory.glue("20pt")) - SILE.settings:set("document.rskip", SILE.nodefactory.glue("20pt")) + SILE.settings:set("document.lskip", SILE.types.node.glue("20pt")) + SILE.settings:set("document.rskip", SILE.types.node.glue("20pt")) SILE.process(c) SILE.call("par") end) diff --git a/tests/bug-262.sil b/tests/bug-262.sil index 091d21bbde..96881e8809 100644 --- a/tests/bug-262.sil +++ b/tests/bug-262.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.lorem] \hbox diff --git a/tests/bug-264.sil b/tests/bug-264.sil index c29bd8d56f..10072fe709 100644 --- a/tests/bug-264.sil +++ b/tests/bug-264.sil @@ -1,4 +1,5 @@ \begin[papersize=a7,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios Footnote\skip[height=170pt plus 50pt] marker.\footnote{Same page\hfill\break as marker?} diff --git a/tests/bug-264b.sil b/tests/bug-264b.sil index ecaef722e9..71afaea96f 100644 --- a/tests/bug-264b.sil +++ b/tests/bug-264b.sil @@ -1,6 +1,7 @@ \begin[papersize=a5,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] % Note: The failure case for this involves an infinite loop when it -% erroniously matched penalty types that arent actually page breaks +% erroneously matched penalty types that aren't actually page breaks \use[module=packages.linespacing] \set[parameter=linespacing.method,value=fit-font] \set[parameter=linespacing.fit-font.extra-space,value=15pt plus 0.5pt minus 0.5pt] diff --git a/tests/bug-266.sil b/tests/bug-266.sil index 2294ba3678..593cf29ad6 100644 --- a/tests/bug-266.sil +++ b/tests/bug-266.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios Word\glue[width=1ex]to the wise diff --git a/tests/bug-269.sil b/tests/bug-269.sil index 091780cde6..ec4a9a2fd0 100644 --- a/tests/bug-269.sil +++ b/tests/bug-269.sil @@ -1,4 +1,5 @@ \begin[papersize=a6,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[family=Gentium Plus,size=10.1pt,language=tr] \use[module=packages.footnotes] diff --git a/tests/bug-283.sil b/tests/bug-283.sil index 97117dc9b3..881143e73b 100644 --- a/tests/bug-283.sil +++ b/tests/bug-283.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] % I tried extensively to get this test to *fail* again prior to v0.14.15 % release but failed. Even reverting the "fixing" commit 9d6691f and a whole % bunch of subsequent stuff I couldn't make it fail again. Apparently not only diff --git a/tests/bug-284.sil b/tests/bug-284.sil index e87ced8c79..6c34b098cd 100644 --- a/tests/bug-284.sil +++ b/tests/bug-284.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \set[parameter=document.lskip,value=15pt] \set[parameter=document.rskip,value=10pt] diff --git a/tests/bug-301.expected b/tests/bug-301.expected index 677ced152e..e9c59861ef 100644 --- a/tests/bug-301.expected +++ b/tests/bug-301.expected @@ -108,7 +108,7 @@ T 88 81 71 72 85 86 76 74 81 72 71 w=48.8127 (undersigned) Mx 190.1779 T 15 w=2.1755 (,) Mx 138.1542 -My 79.7247 +My 78.7247 T 45 82 75 81 w=18.1604 (John) Mx 158.8251 T 43 68 81 70 82 70 78 w=33.5283 (Hancock) diff --git a/tests/bug-301.sil b/tests/bug-301.sil index 379286497f..98dcb2f80e 100644 --- a/tests/bug-301.sil +++ b/tests/bug-301.sil @@ -1,4 +1,5 @@ \begin[papersize=a7,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.linespacing] \nofolios \font[family=Gentium Plus,size=9.5pt] diff --git a/tests/bug-303.sil b/tests/bug-303.sil index c9ca7ec518..f52ba7f430 100644 --- a/tests/bug-303.sil +++ b/tests/bug-303.sil @@ -4,10 +4,10 @@ SILE.registerCommand("poetry", function(options, content) SILE.settings:temporarily(function() SILE.typesetter:leaveHmode() - SILE.settings:set("document.lskip", SILE.nodefactory.glue("1em")) - SILE.settings:set("document.rskip", SILE.nodefactory.hfillglue()) - SILE.settings:set("current.parindent", SILE.nodefactory.glue()) - SILE.settings:set("document.parindent", SILE.nodefactory.glue()) + SILE.settings:set("document.lskip", SILE.types.node.glue("1em")) + SILE.settings:set("document.rskip", SILE.types.node.hfillglue()) + SILE.settings:set("current.parindent", SILE.types.node.glue()) + SILE.settings:set("document.parindent", SILE.types.node.glue()) SILE.process(content) SILE.call("par") end) diff --git a/tests/bug-309.sil b/tests/bug-309.sil index 6499873e8b..7e7b3c38f2 100644 --- a/tests/bug-309.sil +++ b/tests/bug-309.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \set[parameter=document.parskip,value=2em plus 1em] Test par diff --git a/tests/bug-317.sil b/tests/bug-317.sil index dc0a729c26..ca86f4cb52 100644 --- a/tests/bug-317.sil +++ b/tests/bug-317.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.verbatim] \define[command=verbatim:font]{\set[parameter=font.family,value=Hack]} diff --git a/tests/bug-337.expected b/tests/bug-337.expected index a21541759e..ed8b905951 100644 --- a/tests/bug-337.expected +++ b/tests/bug-337.expected @@ -1,69 +1,68 @@ -Set paper size 297.6377985 419.5275636 +Set paper size 209.7637818 297.6377985 Begin page -Mx 43.9370 -My 67.1949 +My 6.2500 Set font Gentium Plus;10;400;;normal;;;LTR T 21 20 19 83 87 w=22.8174 (210pt) -Mx 69.4140 +Mx 25.4770 T 2258 w=4.1016 (×) -Mx 76.1752 +Mx 32.2382 T 21 28 27 83 87 w=22.8174 (298pt) -Draw line 33.9370 60.9449 -10.0000 0.5000 -Draw line 43.9370 50.9449 0.5000 -10.0000 -Draw line 263.7008 60.9449 10.0000 0.5000 -Draw line 253.7008 50.9449 0.5000 -10.0000 -Draw line 33.9370 358.5827 -10.0000 0.5000 -Draw line 43.9370 368.5827 0.5000 10.0000 -Draw line 263.7008 358.5827 10.0000 0.5000 -Draw line 253.7008 368.5827 0.5000 10.0000 -Mx 53.9370 -My 47.9449 +Draw line -10.0000 0.0000 -20.0000 0.5000 +Draw line 0.0000 -10.0000 0.5000 -20.0000 +Draw line 219.7638 0.0000 20.0000 0.5000 +Draw line 209.7638 -10.0000 0.5000 -20.0000 +Draw line -10.0000 297.6378 -20.0000 0.5000 +Draw line 0.0000 307.6378 0.5000 20.0000 +Draw line 219.7638 297.6378 20.0000 0.5000 +Draw line 209.7638 307.6378 0.5000 20.0000 +Mx 10.0000 +My -14.0000 Set font Gentium Plus;6;400;;normal;;;LTR T 87 72 86 87 86 w=11.5371 (tests) -Mx 65.4741 +Mx 21.5371 T 18 w=2.8154 (/) -Mx 68.2895 +Mx 24.3525 T 69 88 74 w=9.1523 (bug) -Mx 77.4419 +Mx 33.5049 T 16 w=2.0215 (-) -Mx 79.4634 +Mx 35.5264 T 22 22 26 w=8.4463 (337) -Mx 87.9097 +Mx 43.9727 T 17 w=1.3740 (.) -Mx 89.2837 +Mx 45.3467 T 86 76 79 w=5.5693 (sil) New page -Mx 43.9370 -My 67.1949 +Mx 0.0000 +My 6.2500 Set font Gentium Plus;10;400;;normal;;;LTR T 21 20 19 83 87 w=22.8174 (210pt) -Mx 69.4140 +Mx 25.4770 T 2258 w=4.1016 (×) -Mx 76.1752 +Mx 32.2382 T 21 28 27 83 87 w=22.8174 (298pt) -Draw line 33.9370 60.9449 -10.0000 0.5000 -Draw line 43.9370 50.9449 0.5000 -10.0000 -Draw line 263.7008 60.9449 10.0000 0.5000 -Draw line 253.7008 50.9449 0.5000 -10.0000 -Draw line 33.9370 358.5827 -10.0000 0.5000 -Draw line 43.9370 368.5827 0.5000 10.0000 -Draw line 263.7008 358.5827 10.0000 0.5000 -Draw line 253.7008 368.5827 0.5000 10.0000 -Mx 53.9370 -My 47.9449 +Draw line -10.0000 0.0000 -20.0000 0.5000 +Draw line 0.0000 -10.0000 0.5000 -20.0000 +Draw line 219.7638 0.0000 20.0000 0.5000 +Draw line 209.7638 -10.0000 0.5000 -20.0000 +Draw line -10.0000 297.6378 -20.0000 0.5000 +Draw line 0.0000 307.6378 0.5000 20.0000 +Draw line 219.7638 297.6378 20.0000 0.5000 +Draw line 209.7638 307.6378 0.5000 20.0000 +Mx 10.0000 +My -14.0000 Set font Gentium Plus;6;400;;normal;;;LTR T 87 72 86 87 86 w=11.5371 (tests) -Mx 65.4741 +Mx 21.5371 T 18 w=2.8154 (/) -Mx 68.2895 +Mx 24.3525 T 69 88 74 w=9.1523 (bug) -Mx 77.4419 +Mx 33.5049 T 16 w=2.0215 (-) -Mx 79.4634 +Mx 35.5264 T 22 22 26 w=8.4463 (337) -Mx 87.9097 +Mx 43.9727 T 17 w=1.3740 (.) -Mx 89.2837 +Mx 45.3467 T 86 76 79 w=5.5693 (sil) End page Finish diff --git a/tests/bug-337.sil b/tests/bug-337.sil index 66a746aa14..8c702824c2 100644 --- a/tests/bug-337.sil +++ b/tests/bug-337.sil @@ -1,7 +1,9 @@ -\begin[papersize=a7,class=book]{document} -\script[src=inc.bug-337] -\define[command=crop:header]{tests/bug-337.sil} -\crop:setup[papersize=a6] +\begin[papersize=a7,class=book,sheetsize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] +\use[module=packages.color] +\use[module=inc.bug-337] +\define[command=cropmarks:header]{tests/bug-337.sil} +\cropmarks:setup \nofolios \set[parameter=document.parindent,value=0] diff --git a/tests/bug-344.sil b/tests/bug-344.sil index 22a5388ae4..43485b9fed 100644 --- a/tests/bug-344.sil +++ b/tests/bug-344.sil @@ -1,4 +1,5 @@ \begin[papersize=a5,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \define[command=mwe]{One, two. Buckle my shoe.\smallskip} diff --git a/tests/bug-353.expected b/tests/bug-353.expected index 47f50f2ca8..9041381f99 100644 --- a/tests/bug-353.expected +++ b/tests/bug-353.expected @@ -1,9 +1,9 @@ Set paper size 419.5275636 595.275597 Begin page -Push color 0.9137 0.8471 0.7294 +Push color C Draw line 0.0000 0.0000 419.5276 595.2756 Pop color -Push color 0.3529 0.2549 0.1608 +Push color C Mx 40.9764 My 37.3321 Set font Gentium Plus;10;400;;normal;;;LTR diff --git a/tests/bug-353.sil b/tests/bug-353.sil index 636cb60e55..448668c471 100644 --- a/tests/bug-353.sil +++ b/tests/bug-353.sil @@ -1,5 +1,7 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.background] +\use[module=packages.color] \nofolios \background[color=#e9d8ba] \color[color=#5a4129]{Sepia baby.} diff --git a/tests/bug-355-tr-hyphenation.sil b/tests/bug-355-tr-hyphenation.sil index c8e8e2c68a..8735fe895c 100644 --- a/tests/bug-355-tr-hyphenation.sil +++ b/tests/bug-355-tr-hyphenation.sil @@ -1,4 +1,5 @@ \begin[papersize=a7]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \set[parameter=shaper.variablespaces, value=false] diff --git a/tests/bug-39.sil b/tests/bug-39.sil index 87f1c6697c..9d6127c4ba 100644 --- a/tests/bug-39.sil +++ b/tests/bug-39.sil @@ -1,4 +1,5 @@ \begin[papersize=a5,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.frametricks] \right-running-head{First Line of Header\skip[height=8mm] Second Line of Header} \showframe[id=all] diff --git a/tests/bug-465-a.sil b/tests/bug-465-a.sil index 34651f9c21..a57bb8fda8 100644 --- a/tests/bug-465-a.sil +++ b/tests/bug-465-a.sil @@ -1,3 +1,4 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] Hello \include[src=bug-465-b.sil]. -\end{document} \ No newline at end of file +\end{document} diff --git a/tests/bug-522.sil b/tests/bug-522.sil index e1c40bd7b2..19139597aa 100644 --- a/tests/bug-522.sil +++ b/tests/bug-522.sil @@ -1,4 +1,5 @@ \begin[papersize=a5,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \show-multilevel-counter[id=foo] \increment-multilevel-counter[id=foo,level=3] @@ -10,4 +11,4 @@ \increment-multilevel-counter[id=foo,level=2] \show-multilevel-counter[id=foo] -\end{document} \ No newline at end of file +\end{document} diff --git a/tests/bug-524.expected b/tests/bug-524.expected index 73ff2a2cd4..b329db3289 100644 --- a/tests/bug-524.expected +++ b/tests/bug-524.expected @@ -1,6 +1,6 @@ Set paper size 595.275597 841.8897729 Begin page -Push color 1.0000 0.9000 0.9000 +Push color C Draw line 49.4079 97.4092 500.0000 0.5000 Draw line 49.1579 107.4092 0.5000 -10.0000 Draw line 59.1579 107.4092 0.5000 -10.0000 @@ -1605,7 +1605,7 @@ Mx 59.5278 T 1506 w=10.0000 (は) Mx 51.7739 My 129.9592 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR T 56 w=5.2680 (W) Mx 49.4079 My 139.9592 diff --git a/tests/bug-524.sil b/tests/bug-524.sil index ed2381bc60..f851dca04a 100644 --- a/tests/bug-524.sil +++ b/tests/bug-524.sil @@ -3,6 +3,7 @@ \neverindent \show-hanmen \use[module=packages.ruby] +\set[parameter=ruby.opentype,value=false] 私は {}私は diff --git a/tests/bug-525.expected b/tests/bug-525.expected index a0b19a1d68..edba4e9c8d 100644 --- a/tests/bug-525.expected +++ b/tests/bug-525.expected @@ -2,7 +2,7 @@ Set paper size 297.6377985 419.5275636 Begin page Mx 24.7039 My 46.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR T 20220 w=6.0000 (日) Mx 30.8207 T 20758 w=6.0000 (本) @@ -15,7 +15,7 @@ Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 29078 w=10.0000 (私) Mx 42.9375 My 46.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR T 45 w=3.2580 (L) Mx 46.3123 Mx 3.2940 @@ -33,7 +33,7 @@ Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 29078 w=10.0000 (私) Mx 24.7039 My 63.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR T 45 w=3.2580 (L) Mx 28.0787 Mx 3.2940 @@ -51,7 +51,7 @@ Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 29078 w=10.0000 (私) Mx 39.3011 My 63.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR T 20220 w=6.0000 (日) Mx 45.4179 T 20758 w=6.0000 (本) @@ -64,7 +64,7 @@ Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 29078 w=10.0000 (私) Mx 24.7039 My 80.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR T 45 w=3.2580 (L) Mx 28.0790 Mx 3.2940 @@ -82,7 +82,7 @@ Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 29078 w=10.0000 (私) Mx 41.8022 My 80.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR T 45 w=3.2580 (L) Mx 45.1773 Mx 3.2940 diff --git a/tests/bug-525.sil b/tests/bug-525.sil index 600f37cf15..3a0ee4c1cf 100644 --- a/tests/bug-525.sil +++ b/tests/bug-525.sil @@ -2,6 +2,7 @@ \nofolios \neverindent \use[module=packages.ruby] +\set[parameter=ruby.opentype,value=false] \ruby[reading=日本語]{私}\ruby[reading=Latin]{私} \ruby[reading=Latin]{私}\ruby[reading=日本語]{私} diff --git a/tests/bug-526.sil b/tests/bug-526.sil index 63395abe4b..52edb662eb 100644 --- a/tests/bug-526.sil +++ b/tests/bug-526.sil @@ -1,4 +1,5 @@ \begin[class=plain,papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.frametricks] \use[module=packages.lorem] diff --git a/tests/bug-530.sil b/tests/bug-530.sil index 40ab0f8161..d9fff30489 100644 --- a/tests/bug-530.sil +++ b/tests/bug-530.sil @@ -1,4 +1,5 @@ \begin[direction=RTL]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lorem] \lorem @@ -7,4 +8,4 @@ \lorem -\end{document} \ No newline at end of file +\end{document} diff --git a/tests/bug-54.sil b/tests/bug-54.sil index 5807b4dc5d..160a951cb7 100644 --- a/tests/bug-54.sil +++ b/tests/bug-54.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[family=Gentium Plus,size=12pt] \set[parameter=document.parindent,value=10mm] diff --git a/tests/bug-583.sil b/tests/bug-583.sil index e843838d5d..f8e33b50c6 100644 --- a/tests/bug-583.sil +++ b/tests/bug-583.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \define[command=ah]{\discretionary[prebreak=-, replacement=’]} diff --git a/tests/bug-61.sil b/tests/bug-61.sil index 8a4efd80a9..95218e40da 100644 --- a/tests/bug-61.sil +++ b/tests/bug-61.sil @@ -1,4 +1,5 @@ \begin[papersize=a6,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.frametricks] \begin[first-content-frame=frame1]{pagetemplate} \frame[id=frame1,left=left(content),right=right(content),height=18pt,top=top(content),next=frame2] diff --git a/tests/bug-62.sil b/tests/bug-62.sil index 6d87df13da..f1b91a0f4a 100644 --- a/tests/bug-62.sil +++ b/tests/bug-62.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] First paragraph %comment diff --git a/tests/bug-621.sil b/tests/bug-621.sil index 9fd878b1d3..ba5b5056ff 100644 --- a/tests/bug-621.sil +++ b/tests/bug-621.sil @@ -1,4 +1,5 @@ \begin{document} +\use[module=packages.retrograde,target=v0.15.0] \font[language=fr, size=15pt] La peine de mort est nécessaire, disent les partisans de l’antique et barbare routine ; sans elle il n’est point de frein assez puissant pour le crime. Qui vous l’a dit ? Avez-vous calculé tous les ressorts par lesquels les lois pénales peuvent agir sur la sensibilité humaine ? Hélas ! avant la mort, combien de douleurs physiques et morales l’homme ne peut-il pas endurer ! diff --git a/tests/bug-702.sil b/tests/bug-702.sil index 0c6b9b9c78..a101a6898e 100644 --- a/tests/bug-702.sil +++ b/tests/bug-702.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \language[main=fr] Il était une fois, dans un pays très lointain (dont le nom périlleux ne doit pas être prononcé), vivait Bernadette, qui nous disait « Rien d’important! » As-tu entendu? \end{document} diff --git a/tests/bug-704.sil b/tests/bug-704.sil index 2205eba4ff..18e4b9479c 100644 --- a/tests/bug-704.sil +++ b/tests/bug-704.sil @@ -1,4 +1,5 @@ \begin[papersize=6in x 9in]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \language[main=fr] @@ -10,7 +11,7 @@ Cinq : « Six ‹ Sept › `Huit » ! Treize ; Quatorze… ! -Quinze (?), ?!, [!], …? etc. +Quinze (?), ?!, [!], …? etc. Un! Deux? Trois!? Quatre!!! @@ -21,6 +22,6 @@ Cinq: «Six ‹Sept› `Huit»! Treize; Quatorze…! -Quinze ( ?), ? !, [ !], … ? etc. +Quinze ( ?), ? !, [ !], … ? etc. \end{document} diff --git a/tests/bug-76.sil b/tests/bug-76.sil index dc7bdd0bcd..6e06e48dad 100644 --- a/tests/bug-76.sil +++ b/tests/bug-76.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.bidi] \thisframeRTL diff --git a/tests/bug-79.sil b/tests/bug-79.sil index 3178b8df42..28b72c3122 100644 --- a/tests/bug-79.sil +++ b/tests/bug-79.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \begin[first-content-frame=frame]{pagetemplate} \frame[id=frame, left=2cm, width=5.8cm, top=2cm, bottom=10cm] diff --git a/tests/bug-80.sil b/tests/bug-80.sil index 90889a0741..063a92cb84 100644 --- a/tests/bug-80.sil +++ b/tests/bug-80.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \define[command=item]{\quad – \process} diff --git a/tests/bug-865.expected b/tests/bug-865.expected index 016f0de20c..ec70b85ed0 100644 --- a/tests/bug-865.expected +++ b/tests/bug-865.expected @@ -27,7 +27,7 @@ Mx 38.0763 T 81 82 81 88 80 92 w=34.3262 (nonumy) Mx 75.0647 T 72 76 85 80 82 71 w=29.5508 (eirmod) -Push color 0.6000 0.6000 0.6000 +Push color C Mx 14.8819 My 69.7847 Set font Libertinus Serif;30;400;;normal;;;LTR @@ -112,7 +112,7 @@ Mx 94.4649 T 81 82 81 88 80 92 w=34.3262 (nonumy) Mx 131.4336 T 72 76 85 80 82 71 w=29.5508 (eirmod) -Push color 0.6000 0.6000 0.6000 +Push color C Mx 271.5059 My 125.4747 Set font Libertinus Serif;30;400;;normal;;;LTR diff --git a/tests/bug-865.sil b/tests/bug-865.sil index 8b2bb30124..b82a36eb5d 100644 --- a/tests/bug-865.sil +++ b/tests/bug-865.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.pullquote] \use[module=packages.lorem] diff --git a/tests/bug-892.sil b/tests/bug-892.sil index 2cf46ace51..078c03b3dc 100644 --- a/tests/bug-892.sil +++ b/tests/bug-892.sil @@ -1,4 +1,5 @@ \begin{document} +\use[module=packages.retrograde,target=v0.15.0] \define[command=micro]{} \define[command=macro]{\process} \begin{script} diff --git a/tests/bug-926.expected b/tests/bug-926.expected index 9d7210deee..4c51251640 100644 --- a/tests/bug-926.expected +++ b/tests/bug-926.expected @@ -2,7 +2,7 @@ Set paper size 297.6377985 419.5275636 Begin page Mx 24.7039 My 46.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR T 20220 w=6.0000 (日) Mx 30.7039 T 20758 w=6.0000 (本) @@ -15,7 +15,7 @@ Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 29078 w=10.0000 (私) Mx 42.7039 My 46.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR Mx 3.2580 Mx 3.2940 Mx 2.2620 @@ -29,7 +29,7 @@ Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 29078 w=10.0000 (私) Mx 24.7039 My 63.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR Mx 3.2580 Mx 3.2940 Mx 2.2620 @@ -43,7 +43,7 @@ Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 29078 w=10.0000 (私) Mx 38.8339 My 63.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR T 20220 w=6.0000 (日) Mx 44.8339 T 20758 w=6.0000 (本) @@ -56,7 +56,7 @@ Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 29078 w=10.0000 (私) Mx 24.7039 My 80.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR Mx 3.2580 Mx 3.2940 Mx 2.2620 @@ -70,7 +70,7 @@ Set font Noto Sans CJK JP;10;400;;normal;;;LTR T 29078 w=10.0000 (私) Mx 41.3339 My 80.9652 -Set font Noto Sans CJK JP;6;800;;normal;;;LTR +Set font Noto Sans CJK JP;6;700;;normal;;;LTR Mx 3.2580 Mx 3.2940 Mx 2.2620 diff --git a/tests/bug-926.sil b/tests/bug-926.sil index 15b355e3de..5d85584f92 100644 --- a/tests/bug-926.sil +++ b/tests/bug-926.sil @@ -3,6 +3,7 @@ \neverindent \language[main=en] \use[module=packages.ruby] +\set[parameter=ruby.opentype,value=false] \ruby[reading=日本語]{私}\ruby[reading=Latin]{私} \ruby[reading=Latin]{私}\ruby[reading=日本語]{私} diff --git a/tests/bug-990.expected b/tests/bug-990.expected index cdf325b11b..d00c5114d0 100644 --- a/tests/bug-990.expected +++ b/tests/bug-990.expected @@ -15,42 +15,42 @@ Mx 24.7039 My 124.9953 Set font Noto Sans CJK JP;22;800;;normal;;;LTR T 29804 w=22.0000 (第) -Mx 46.7338 +Mx 46.7477 T 19 w=12.2100 (2) -Mx 58.9737 +Mx 59.0014 T 29653 w=22.0000 (章) Mx 24.7039 My 151.1793 Mx 14.0360 T 44 a=14.2120 (K) -Mx 38.7475 +Mx 38.7510 T 80 w=13.3320 (o) -Mx 52.0871 +Mx 52.0941 T 79 w=13.4420 (n) -Mx 65.5366 +Mx 65.5471 T 79 w=13.4420 (n) -Mx 78.9862 +Mx 79.0002 T 74 w=6.0500 (i) -Mx 85.0437 +Mx 85.0613 T 68 w=11.1980 (c) -Mx 96.2493 +Mx 96.2703 T 73 w=13.3540 (h) -Mx 109.6109 +Mx 109.6354 T 74 w=6.0500 (i) -Mx 121.5929 +Mx 121.6209 T 41 w=16.0160 (H) -Mx 137.6165 +Mx 137.6480 T 66 w=12.4080 (a) -Mx 155.9566 +Mx 155.9916 T 52 w=13.1340 (S) -Mx 169.0981 +Mx 169.1366 T 70 w=12.1880 (e) -Mx 181.2937 +Mx 181.3357 Mx 11.9020 T 76 a=12.1440 (k) -Mx 193.2033 +Mx 193.2488 T 66 w=12.4080 (a) -Mx 205.6188 +Mx 205.6678 T 74 w=6.0500 (i) Mx 24.7039 My 181.1157 diff --git a/tests/bug-990.sil b/tests/bug-990.sil index 0ef9a521a3..c12087fb22 100644 --- a/tests/bug-990.sil +++ b/tests/bug-990.sil @@ -1,4 +1,5 @@ \begin[papersize=a6,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \define[command=open-spread]{} \nofolios diff --git a/tests/bug-liner-width.expected b/tests/bug-liner-width.expected new file mode 100644 index 0000000000..6d21f07f17 --- /dev/null +++ b/tests/bug-liner-width.expected @@ -0,0 +1,35 @@ +Set paper size 209.7637818 297.6377985 +Begin page +Push color C +Draw line 10.4882 14.8819 188.7874 34.5868 +Pop color +Push color C +Draw line 10.4882 53.2819 188.7874 34.5868 +Pop color +Push color C +Draw line 10.4882 91.6819 116.2969 34.5868 +Pop color +Mx 10.4882 +My 154.3006 +Set font Gentium Plus;32;400;;normal;;;LTR +T 79 82 74 76 16 w=59.8281 (logi-) +Mx 70.3163 +T 93 82 87 92 w=56.4688 (zoty) +Mx 139.4475 +T 79 82 74 76 16 w=59.8281 (logi-) +Mx 10.4882 +My 192.7006 +T 16 w=10.7813 (-) +Mx 21.2694 +T 93 82 87 92 w=56.4688 (zoty) +Mx 82.9787 +T 79 82 74 76 16 w=59.8281 (logi-) +Mx 142.8068 +T 93 82 87 92 w=56.4688 (zoty) +Mx 10.4882 +My 231.1006 +T 79 82 74 76 16 w=59.8281 (logi-) +Mx 70.3163 +T 93 82 87 92 w=56.4688 (zoty) +End page +Finish diff --git a/tests/bug-liner-width.sil b/tests/bug-liner-width.sil new file mode 100644 index 0000000000..618d2ab3d1 --- /dev/null +++ b/tests/bug-liner-width.sil @@ -0,0 +1,31 @@ +\begin[papersize=a7]{document} +\nofolios +\neverindent +\font[size=32pt,language=pl] +\begin{lua} +-- MVE: Use the box width to advance position, rather than rely on internal content to move it. +SILE.documentState.documentClass:registerCommand("advance-box-width", function (options, content) + local bs = SILE.measurement("0.9bs"):tonumber() + local bsratio = 0.3 + SILE.typesetter:liner("advance-box-width", content, + function (box, typesetter, line) + local outputWidth = SU.rationWidth(box.width, box.width, line.ratio) + local height = SU.max(box.height:tonumber(), (1 - bsratio) * bs) + local depth = SU.max(box.depth:tonumber(), bsratio * bs) + local cx, cy = typesetter.frame.state.cursorX, typesetter.frame.state.cursorY + SILE.outputter:pushColor(SILE.types.color("gray")) + SILE.outputter:drawRule(cx, cy - height, outputWidth, height + depth) + SILE.outputter:popColor() + typesetter.frame:advanceWritingDirection(outputWidth) + end + ) +end) +\end{lua} + +% In liner +\advance-box-width{logi-zoty logi-zoty logi-zoty logi-zoty} + +% Vs. normal +logi-zoty logi-zoty logi-zoty logi-zoty + +\end{document} diff --git a/tests/centering.sil b/tests/centering.sil index 702321fff0..b0ae8b6874 100644 --- a/tests/centering.sil +++ b/tests/centering.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lorem] \ragged[right=true,left=true]{\lorem[words=100]} diff --git a/tests/chapterverse.sil b/tests/chapterverse.sil index d309b318a5..ded8997a1f 100644 --- a/tests/chapterverse.sil +++ b/tests/chapterverse.sil @@ -1,4 +1,5 @@ \begin[papersize=a7,class=bible]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[family=Gentium Plus] \begin{script} diff --git a/tests/classes/testsidenote.lua b/tests/classes/testsidenote.lua index bcc923680a..cc9bdb81e8 100644 --- a/tests/classes/testsidenote.lua +++ b/tests/classes/testsidenote.lua @@ -13,9 +13,9 @@ function testsidenote:_init (options) self:loadPackage("insertions") self:loadPackage("footnotes") self:initInsertionClass("footnote", { - maxHeight = SILE.length("75%ph"):absolute(), - topBox = SILE.nodefactory.zerovglue(), - interInsertionSkip = SILE.length("1ex"), + maxHeight = SILE.types.length("75%ph"):absolute(), + topBox = SILE.types.node.zerovglue(), + interInsertionSkip = SILE.types.length("1ex"), insertInto = { frame = "sidenotes", ratio = 0 }, stealFrom = { }, }) diff --git a/tests/combining.sil b/tests/combining.sil index 89c079f754..7ee1c4c6b5 100644 --- a/tests/combining.sil +++ b/tests/combining.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[family=Gentium Plus,size=24pt] Ầ Ầ diff --git a/tests/counters.sil b/tests/counters.sil index 6d581dc95e..d6599a40ed 100644 --- a/tests/counters.sil +++ b/tests/counters.sil @@ -1,5 +1,6 @@ \begin[papersize=a6]{document} \nofolios +\use[module=packages.retrograde,target=v0.15.0] \set[parameter=document.parindent,value=0pt] \font[family=Libertinus Serif] \use[module=packages.counters] diff --git a/tests/crash.sil b/tests/crash.sil index 74a7f0c95d..249988786c 100644 --- a/tests/crash.sil +++ b/tests/crash.sil @@ -1,3 +1,4 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] verylongwordverylongwordverylongwordverylongwordveryverylongwordveryverylongwordveryverylongwordveryverylongwordveryverylongwordveryverylongwordveryverylongwordverylongwordverylongwordverylongwordverylongwordverylongwordverylongwordverylongwordverylongwordverylongwordvery \end{document} diff --git a/tests/cursive-shaping.sil b/tests/cursive-shaping.sil index a700234142..57d3bd45d1 100644 --- a/tests/cursive-shaping.sil +++ b/tests/cursive-shaping.sil @@ -1,4 +1,5 @@ \begin[papersize=a6,direction=RTL]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \language[main=ar] diff --git a/tests/disappearing-skip.sil b/tests/disappearing-skip.sil index 5300b852ee..34cf537f2b 100644 --- a/tests/disappearing-skip.sil +++ b/tests/disappearing-skip.sil @@ -1,4 +1,5 @@ \begin[papersize=129mm x 198mm]{document} +\use[module=packages.retrograde,target=v0.15.0] Para 1. \skip[height=155mm] diff --git a/tests/dropcaps-descenders.sil b/tests/dropcaps-descenders.sil index cecf0d5e28..347d848e1d 100644 --- a/tests/dropcaps-descenders.sil +++ b/tests/dropcaps-descenders.sil @@ -1,7 +1,8 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent -\script[src=packages/dropcaps] +\use[module=packages.dropcaps] \set[parameter=document.baselineskip, value=1.2em] \font[family=Cormorant Infant, size=11pt] % Normal I diff --git a/tests/dropcaps.sil b/tests/dropcaps.sil index 0515ff92e5..7b72c20861 100644 --- a/tests/dropcaps.sil +++ b/tests/dropcaps.sil @@ -1,6 +1,6 @@ \begin[papersize=a6]{document} \nofolios -\script[src=packages/dropcaps] +\use[module=packages.dropcaps] \font[size=25pt] \dropcap[lines=2]{T}esting one two three, don't collide with me. Four five six, no crossing sticks. diff --git a/tests/eject.sil b/tests/eject.sil index 970473ed32..03b28a974c 100644 --- a/tests/eject.sil +++ b/tests/eject.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lorem] \pagetemplate[first-content-frame=a]{ \frame[id=a,top=5%ph,bottom=95%ph,next=b,left=5%pw,right=28%pw] diff --git a/tests/feat-1092-raw.sil b/tests/feat-1092-raw.sil index cc36d2ef0e..1e4cb69138 100644 --- a/tests/feat-1092-raw.sil +++ b/tests/feat-1092-raw.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \use[module=packages.svg] @@ -18,4 +19,4 @@ This is a plain text inline. A bit like a true verbatim. \lets[do]{weird things}% Yeah! } -\end{document} \ No newline at end of file +\end{document} diff --git a/tests/feat-1365-lists-alternate.sil b/tests/feat-1365-lists-alternate.sil index 87709ad6cf..07bb13c746 100644 --- a/tests/feat-1365-lists-alternate.sil +++ b/tests/feat-1365-lists-alternate.sil @@ -1,4 +1,5 @@ \begin[papersize=a6, class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lists] \font[family=Libertinus Serif]% Default Gentium lacks the white circle. \nofolios diff --git a/tests/feat-1365-lists-compact.sil b/tests/feat-1365-lists-compact.sil index 3040fae456..10fac5d94f 100644 --- a/tests/feat-1365-lists-compact.sil +++ b/tests/feat-1365-lists-compact.sil @@ -1,4 +1,5 @@ \begin[papersize=a6, class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lists] \font[family=Libertinus Serif]% Default Gentium lacks the white circle. \nofolios diff --git a/tests/feat-1365-lists-footnote.sil b/tests/feat-1365-lists-footnote.sil index 5418482a75..1f44d43874 100644 --- a/tests/feat-1365-lists-footnote.sil +++ b/tests/feat-1365-lists-footnote.sil @@ -1,4 +1,5 @@ \begin[papersize=a6, class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lists] \font[family=Libertinus Serif]% Default Gentium lacks the white circle. \nofolios @@ -19,7 +20,7 @@ \item{Level 2 Ft} \end{itemize} \item{Level 1 Ft} -\end{itemize} +\end{itemize} }} \begin{itemize} \item{Level 3} diff --git a/tests/feat-875-dotfill-alignment.sil b/tests/feat-875-dotfill-alignment.sil index cb3c3d8a6e..add5c385e0 100644 --- a/tests/feat-875-dotfill-alignment.sil +++ b/tests/feat-875-dotfill-alignment.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.leaders] \nofolios \neverindent diff --git a/tests/feat-875-leaders-alignment.sil b/tests/feat-875-leaders-alignment.sil index fda2cdc09c..10cd9ca15b 100644 --- a/tests/feat-875-leaders-alignment.sil +++ b/tests/feat-875-leaders-alignment.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.leaders] \nofolios \neverindent diff --git a/tests/feat-875-leaders-fixed-alignment.sil b/tests/feat-875-leaders-fixed-alignment.sil index 6878f0acb8..cf8fea7852 100644 --- a/tests/feat-875-leaders-fixed-alignment.sil +++ b/tests/feat-875-leaders-fixed-alignment.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.leaders] \nofolios \neverindent diff --git a/tests/feat-emdash-dialogue.sil b/tests/feat-emdash-dialogue.sil index c872927ced..aadfbd4e8c 100644 --- a/tests/feat-emdash-dialogue.sil +++ b/tests/feat-emdash-dialogue.sil @@ -1,4 +1,5 @@ \begin[papersize=a7]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \font[size=22pt] diff --git a/tests/feat-fullrule-hfillrule.sil b/tests/feat-fullrule-hfillrule.sil index 2ba7971631..3b58f75535 100644 --- a/tests/feat-fullrule-hfillrule.sil +++ b/tests/feat-fullrule-hfillrule.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \use[module=packages.rules] diff --git a/tests/feat-italic-correction.expected b/tests/feat-italic-correction.expected new file mode 100644 index 0000000000..9056935f1e --- /dev/null +++ b/tests/feat-italic-correction.expected @@ -0,0 +1,567 @@ +Set paper size 297.6377985 419.5275636 +Begin page +Mx 14.8819 +My 32.6951 +Set font Gentium Plus;15;400;;normal;;;LTR +T 2149 w=6.1523 (−) +Mx 25.0090 +T 11 w=4.7534 (() +Mx 29.7624 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 53.0534 +Set font Gentium Plus;15;400;;normal;;;LTR +T 12 w=4.7534 ()) +Mx 61.7816 +T 163 w=4.0796 (¡) +Mx 65.8612 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 73 68 81 70 92 73 88 79 w=45.9668 (fancyful) +Mx 115.4414 +T 83 85 82 82 73 w=29.1431 (proof) +Mx 144.5845 +Set font Gentium Plus;15;400;;normal;;;LTR +T 4 w=4.0796 (!) +Mx 152.6388 +T 62 w=4.6143 ([) +Mx 157.2531 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 83 88 747 w=22.7417 (puff) +Mx 179.9948 +Set font Gentium Plus;15;400;;normal;;;LTR +T 64 w=4.6143 (]) +Mx 188.5838 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 211.8748 +Set font Gentium Plus;15;400;;normal;;;LTR +T 2011 w=4.9292 (²) +Mx 14.8819 +My 50.6951 +T 14 w=6.1523 (+) +Mx 25.0071 +T 11 w=4.7534 (() +Mx 30.2362 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 56.0468 +Set font Gentium Plus;15;400;;normal;;;LTR +T 12 w=4.7534 ()) +Mx 64.7730 +T 163 w=4.0796 (¡) +Mx 69.5851 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 73 68 81 70 92 73 88 79 w=45.9668 (fancyful) +Mx 119.1635 +T 83 85 82 82 73 w=29.1431 (proof) +Mx 150.7598 +Set font Gentium Plus;15;400;;normal;;;LTR +T 4 w=4.0796 (!) +Mx 158.8123 +T 62 w=4.6143 ([) +Mx 163.4560 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 83 88 747 w=22.7417 (puff) +Mx 188.7172 +Set font Gentium Plus;15;400;;normal;;;LTR +T 64 w=4.6143 (]) +Mx 197.8102 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 223.3572 +Set font Gentium Plus;15;400;;normal;;;LTR +T 2011 w=4.9292 (²) +Mx 14.8819 +My 68.6951 +T 2149 w=6.1523 (−) +Mx 25.0131 +T 95 w=3.6621 (|) +Mx 28.6752 +T 11 w=4.7534 (() +Mx 33.4286 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 56.7196 +Set font Gentium Plus;15;400;;normal;;;LTR +T 12 w=4.7534 ()) +Mx 65.4519 +T 163 w=4.0796 (¡) +Mx 69.5315 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 73 68 81 70 92 73 88 79 w=45.9668 (fancyful) +Mx 119.1155 +T 83 85 82 82 73 w=29.1431 (proof) +Mx 148.2585 +Set font Gentium Plus;15;400;;normal;;;LTR +T 4 w=4.0796 (!) +Mx 156.3170 +T 62 w=4.6143 ([) +Mx 160.9312 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 83 88 747 w=22.7417 (puff) +Mx 183.6729 +Set font Gentium Plus;15;400;;normal;;;LTR +T 64 w=4.6143 (]) +Mx 188.2872 +T 95 w=3.6621 (|) +Mx 14.8819 +My 86.6951 +T 14 w=6.1523 (+) +Mx 25.0117 +T 95 w=3.6621 (|) +Mx 28.6738 +T 11 w=4.7534 (() +Mx 33.9029 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 59.7134 +Set font Gentium Plus;15;400;;normal;;;LTR +T 12 w=4.7534 ()) +Mx 68.4443 +T 163 w=4.0796 (¡) +Mx 73.2563 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 73 68 81 70 92 73 88 79 w=45.9668 (fancyful) +Mx 122.8389 +T 83 85 82 82 73 w=29.1431 (proof) +Mx 154.4352 +Set font Gentium Plus;15;400;;normal;;;LTR +T 4 w=4.0796 (!) +Mx 162.4922 +T 62 w=4.6143 ([) +Mx 167.1359 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 83 88 747 w=22.7417 (puff) +Mx 192.3972 +Set font Gentium Plus;15;400;;normal;;;LTR +T 64 w=4.6143 (]) +Mx 197.0114 +T 95 w=3.6621 (|) +Mx 14.8819 +My 104.6951 +T 2149 w=6.1523 (−) +Mx 25.0143 +T 169 w=7.2803 («) +Mx 35.4705 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 61.9375 +Set font Gentium Plus;15;400;;normal;;;LTR +T 170 w=7.2803 (») +Mx 73.1978 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 73 85 72 81 70 75 w=35.9619 (french) +Mx 112.7779 +T 83 85 82 82 73 w=29.1431 (proof) +Mx 143.9029 +Set font Gentium Plus;15;400;;normal;;;LTR +T 4 w=4.0796 (!) +Mx 151.9626 +T 11 w=4.7534 (() +Mx 156.7160 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 180.0070 +Set font Gentium Plus;15;400;;normal;;;LTR +T 12 w=4.7534 ()) +Mx 14.8819 +My 122.6951 +T 14 w=6.1523 (+) +Mx 25.0138 +T 169 w=7.2803 («) +Mx 35.4699 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 61.9367 +Set font Gentium Plus;15;400;;normal;;;LTR +T 170 w=7.2803 (») +Mx 73.1709 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 73 85 72 81 70 75 w=35.9619 (french) +Mx 112.7506 +T 83 85 82 82 73 w=29.1431 (proof) +Mx 143.8756 +Set font Gentium Plus;15;400;;normal;;;LTR +T 4 w=4.0796 (!) +Mx 151.9347 +T 11 w=4.7534 (() +Mx 157.1638 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 182.9744 +Set font Gentium Plus;15;400;;normal;;;LTR +T 12 w=4.7534 ()) +Mx 14.8819 +My 140.6951 +T 2149 w=6.1523 (−) +Mx 27.4228 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 50.7138 +Set font Gentium Plus;15;400;;normal;;;LTR +T 4 w=4.0796 (!) +Mx 61.1819 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 90.8614 +Set font Gentium Plus;15;400;;normal;;;LTR +T 739 88 747 w=26.0083 (fluff) +Mx 123.2583 +T 68 68 68 68 68 68 68 w=48.1934 (aaaaaaa) +Mx 177.8402 +T 68 68 68 68 w=27.5391 (aaaa) +Mx 211.7678 +T 68 68 68 68 68 68 w=41.3086 (aaaaaa) +Mx 259.4649 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 14.8819 +My 158.6951 +Set font Gentium Plus;15;400;;normal;;;LTR +T 739 88 747 w=26.0083 (fluff) +My 176.6951 +T 14 w=6.1523 (+) +Mx 26.4319 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 52.1762 +Set font Gentium Plus;15;400;;normal;;;LTR +T 4 w=4.0796 (!) +Mx 62.0234 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 93.5412 +Set font Gentium Plus;15;400;;normal;;;LTR +T 739 88 747 w=26.0083 (fluff) +Mx 125.2568 +T 68 68 68 68 68 68 68 w=48.1934 (aaaaaaa) +Mx 179.1574 +T 68 68 68 68 w=27.5391 (aaaa) +Mx 212.4037 +T 68 68 68 68 68 68 w=41.3086 (aaaaaa) +Mx 259.4649 +Set font Gentium Plus;15;400;Italic;normal;;;LTR +T 739 88 747 w=23.2910 (fluff) +Mx 14.8819 +My 194.6951 +Set font Gentium Plus;15;400;;normal;;;LTR +T 739 88 747 w=26.0083 (fluff) +My 212.6951 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2531 w=5.9700 (−) +Mx 25.0773 +T 2403 w=4.6350 (() +Mx 29.7123 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 51.9573 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2404 w=4.6350 ()) +Mx 60.8177 +T 2372 w=3.7050 (¡) +Mx 64.5227 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +Mx 3.6450 +Mx 6.5100 +Mx 7.0650 +Mx 5.1750 +Mx 6.7500 +Mx 3.9300 +Mx 6.9750 +Mx 3.5550 +T 680 a=3.6450 457 a=6.5100 567 a=6.9750 486 a=5.1750 665 a=6.5400 517 a=3.9300 634 a=6.9750 555 a=3.5550 (fancyful) +Mx 112.3532 +Mx 6.1050 +Mx 4.8300 +Mx 5.9700 +Mx 5.9700 +Mx 3.9300 +T 604 a=6.1050 607 a=5.1000 577 a=5.9700 577 a=5.9700 517 a=3.9300 (proof) +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2371 w=3.7050 (!) +Mx 147.0886 +T 2407 w=4.1100 ([) +Mx 151.1986 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 604 634 816 w=20.7150 (puff) +Mx 171.9136 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2408 w=4.1100 (]) +Mx 180.2490 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 202.4940 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2328 w=3.7650 (²) +Mx 14.8819 +My 230.6951 +T 2530 w=5.9700 (+) +Mx 25.0754 +T 2403 w=4.6350 (() +Mx 30.3927 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 54.6027 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2404 w=4.6350 ()) +Mx 63.4612 +T 2372 w=3.7050 (¡) +Mx 68.7562 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +Mx 3.6450 +Mx 6.5100 +Mx 7.0650 +Mx 5.1750 +Mx 6.7500 +Mx 3.9300 +Mx 6.9750 +Mx 3.5550 +T 680 a=3.6450 457 a=6.5100 567 a=6.9750 486 a=5.1750 665 a=6.5400 517 a=3.9300 634 a=6.9750 555 a=3.5550 (fancyful) +Mx 116.5847 +Mx 6.1050 +Mx 4.8300 +Mx 5.9700 +Mx 5.9700 +Mx 3.9300 +T 604 a=6.1050 607 a=5.1000 577 a=5.9700 577 a=5.9700 517 a=3.9300 (proof) +Mx 145.0921 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2371 w=3.7050 (!) +Mx 153.0206 +T 2407 w=4.1100 ([) +Mx 157.5312 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 604 634 816 w=20.7150 (puff) +Mx 180.2085 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2408 w=4.1100 (]) +Mx 189.2185 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 213.4285 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2328 w=3.7650 (²) +Mx 14.8819 +My 248.6951 +T 2531 w=5.9700 (−) +Mx 25.0817 +T 2627 w=2.5200 (|) +Mx 27.6017 +T 2403 w=4.6350 (() +Mx 32.2367 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 54.4817 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2404 w=4.6350 ()) +Mx 63.3466 +T 2372 w=3.7050 (¡) +Mx 67.0516 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +Mx 3.6450 +Mx 6.5100 +Mx 7.0650 +Mx 5.1750 +Mx 6.7500 +Mx 3.9300 +Mx 6.9750 +Mx 3.5550 +T 680 a=3.6450 457 a=6.5100 567 a=6.9750 486 a=5.1750 665 a=6.5400 517 a=3.9300 634 a=6.9750 555 a=3.5550 (fancyful) +Mx 114.8864 +Mx 6.1050 +Mx 4.8300 +Mx 5.9700 +Mx 5.9700 +Mx 3.9300 +T 604 a=6.1050 607 a=5.1000 577 a=5.9700 577 a=5.9700 517 a=3.9300 (proof) +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2371 w=3.7050 (!) +Mx 149.6263 +T 2407 w=4.1100 ([) +Mx 153.7363 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 604 634 816 w=20.7150 (puff) +Mx 174.4513 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2408 w=4.1100 (]) +Mx 178.5613 +T 2627 w=2.5200 (|) +Mx 14.8819 +My 266.6951 +T 2530 w=5.9700 (+) +Mx 25.0803 +T 2627 w=2.5200 (|) +Mx 27.6003 +T 2403 w=4.6350 (() +Mx 32.9175 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 57.1275 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2404 w=4.6350 ()) +Mx 65.9909 +T 2372 w=3.7050 (¡) +Mx 71.2859 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +Mx 3.6450 +Mx 6.5100 +Mx 7.0650 +Mx 5.1750 +Mx 6.7500 +Mx 3.9300 +Mx 6.9750 +Mx 3.5550 +T 680 a=3.6450 457 a=6.5100 567 a=6.9750 486 a=5.1750 665 a=6.5400 517 a=3.9300 634 a=6.9750 555 a=3.5550 (fancyful) +Mx 119.1193 +Mx 6.1050 +Mx 4.8300 +Mx 5.9700 +Mx 5.9700 +Mx 3.9300 +T 604 a=6.1050 607 a=5.1000 577 a=5.9700 577 a=5.9700 517 a=3.9300 (proof) +Mx 147.6268 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2371 w=3.7050 (!) +Mx 155.5602 +T 2407 w=4.1100 ([) +Mx 160.0708 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 604 634 816 w=20.7150 (puff) +Mx 182.7480 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2408 w=4.1100 (]) +Mx 186.8580 +T 2627 w=2.5200 (|) +Mx 14.8819 +My 284.6951 +T 2531 w=5.9700 (−) +Mx 25.0824 +T 2441 w=6.8400 («) +Mx 35.2975 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 60.9177 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2442 w=6.8400 (») +Mx 71.9882 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +Mx 3.9300 +Mx 4.8300 +Mx 5.1450 +Mx 7.0650 +Mx 5.1750 +Mx 6.6750 +T 517 a=3.9300 607 a=5.1000 498 a=5.1450 567 a=6.9750 486 a=5.1750 527 a=6.6750 (french) +Mx 109.0387 +Mx 6.1050 +Mx 4.8300 +Mx 5.9700 +Mx 5.9700 +Mx 3.9300 +T 604 a=6.1050 607 a=5.1000 577 a=5.9700 577 a=5.9700 517 a=3.9300 (proof) +Mx 137.9497 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2371 w=3.7050 (!) +Mx 145.8851 +T 2403 w=4.6350 (() +Mx 150.5201 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 172.7651 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2404 w=4.6350 ()) +Mx 14.8819 +My 302.6951 +T 2530 w=5.9700 (+) +Mx 25.0820 +T 2441 w=6.8400 («) +Mx 35.2970 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 60.9170 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2442 w=6.8400 (») +Mx 71.7616 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +Mx 3.9300 +Mx 4.8300 +Mx 5.1450 +Mx 7.0650 +Mx 5.1750 +Mx 6.6750 +T 517 a=3.9300 607 a=5.1000 498 a=5.1450 567 a=6.9750 486 a=5.1750 527 a=6.6750 (french) +Mx 108.8116 +Mx 6.1050 +Mx 4.8300 +Mx 5.9700 +Mx 5.9700 +Mx 3.9300 +T 604 a=6.1050 607 a=5.1000 577 a=5.9700 577 a=5.9700 517 a=3.9300 (proof) +Mx 137.7226 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2371 w=3.7050 (!) +Mx 145.6577 +T 2403 w=4.6350 (() +Mx 150.9750 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 175.1850 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2404 w=4.6350 ()) +Mx 14.8819 +My 320.6951 +T 2531 w=5.9700 (−) +Mx 26.7967 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 49.0417 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2371 w=3.7050 (!) +Mx 58.6916 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 86.8815 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 952 699 936 w=24.9000 (fluff) +Mx 117.7263 +T 522 522 522 522 522 522 522 w=51.4500 (aaaaaaa) +Mx 175.1212 +T 522 522 522 522 w=29.4000 (aaaa) +Mx 210.4660 +T 522 522 522 522 522 522 w=44.1000 (aaaaaa) +Mx 260.5109 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 14.8819 +My 338.6951 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 952 699 936 w=24.9000 (fluff) +My 356.6951 +T 2530 w=5.9700 (+) +Mx 26.1564 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 50.1038 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 2371 w=3.7050 (!) +Mx 59.2925 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 88.9226 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 952 699 936 w=24.9000 (fluff) +Mx 119.2428 +T 522 522 522 522 522 522 522 w=51.4500 (aaaaaaa) +Mx 176.1129 +T 522 522 522 522 w=29.4000 (aaaa) +Mx 210.9330 +T 522 522 522 522 522 522 w=44.1000 (aaaaaa) +Mx 260.5109 +Set font Cormorant Infant;15;400;Italic;normal;;;LTR +T 718 555 634 816 w=22.2450 (fluff) +Mx 14.8819 +My 374.6951 +Set font Cormorant Infant;15;400;;normal;;;LTR +T 952 699 936 w=24.9000 (fluff) +End page +Finish diff --git a/tests/feat-italic-correction.sil b/tests/feat-italic-correction.sil new file mode 100644 index 0000000000..7f20775493 --- /dev/null +++ b/tests/feat-italic-correction.sil @@ -0,0 +1,77 @@ +\begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] +\nofolios +\neverindent +\font[size=15pt] +\use[module=packages.math] +% Italic correction. +% The setting plays at paragraph level +% Disabled: +\set[parameter=typesetter.italicCorrection, value=false] +− (\em{fluff}) ¡\em{fancyful proof}! [\em{puff}] \em{fluff}² + +% Enabled: +\set[parameter=typesetter.italicCorrection, value=true] ++ (\em{fluff}) ¡\em{fancyful proof}! [\em{puff}] \em{fluff}² + +% The setting also works in hbox-built content. +% We want to be sure the hbox width is still properly computed +% Hence the | marks. +% Disabled: +\set[parameter=typesetter.italicCorrection, value=false] +− |\hbox{(\em{fluff}) ¡\em{fancyful proof}! [\em{puff}]}| + +% Enabled: +\set[parameter=typesetter.italicCorrection, value=true] ++ |\hbox{(\em{fluff}) ¡\em{fancyful proof}! [\em{puff}]}| + +% Extra check with French exceptions. +\language[main=fr]{% +% Disabled: +\set[parameter=typesetter.italicCorrection, value=false] +− «\em{fluff}» \em{french proof}! (\em{fluff}) + +% Enabled: +\set[parameter=typesetter.italicCorrection, value=true] ++ «\em{fluff}» \em{french proof}! (\em{fluff}) +} + +% We want to insert the correction as a kern or glue depending on context, +% so that line breaking may still occur where it should. +% Disabled +\set[parameter=typesetter.italicCorrection, value=false] +− \em{fluff}! \em{fluff} fluff aaaaaaa aaaa aaaaaa \em{fluff} fluff + +% Enabled +\set[parameter=typesetter.italicCorrection, value=true] ++ \em{fluff}! \em{fluff} fluff aaaaaaa aaaa aaaaaa \em{fluff} fluff + +% Repeated with another font +\font[family=Cormorant Infant] +\set[parameter=typesetter.italicCorrection, value=false] +− (\em{fluff}) ¡\em{fancyful proof}! [\em{puff}] \em{fluff}² + +\set[parameter=typesetter.italicCorrection, value=true] ++ (\em{fluff}) ¡\em{fancyful proof}! [\em{puff}] \em{fluff}² + +\set[parameter=typesetter.italicCorrection, value=false] +− |\hbox{(\em{fluff}) ¡\em{fancyful proof}! [\em{puff}]}| + +\set[parameter=typesetter.italicCorrection, value=true] ++ |\hbox{(\em{fluff}) ¡\em{fancyful proof}! [\em{puff}]}| + +\language[main=fr]{% +\set[parameter=typesetter.italicCorrection, value=false] +− «\em{fluff}» \em{french proof}! (\em{fluff}) + +\set[parameter=typesetter.italicCorrection, value=true] ++ «\em{fluff}» \em{french proof}! (\em{fluff}) +} + +\set[parameter=typesetter.italicCorrection, value=false] +− \em{fluff}! \em{fluff} fluff aaaaaaa aaaa aaaaaa \em{fluff} fluff + +\set[parameter=typesetter.italicCorrection, value=true] ++ \em{fluff}! \em{fluff} fluff aaaaaaa aaaa aaaaaa \em{fluff} fluff + +\end{document} diff --git a/tests/feat-liner-spanning.expected b/tests/feat-liner-spanning.expected new file mode 100644 index 0000000000..6bab893f57 --- /dev/null +++ b/tests/feat-liner-spanning.expected @@ -0,0 +1,29 @@ +Set paper size 297.6377985 419.5275636 +Begin page +Mx 14.8819 +My 31.5721 +Set font Gentium Plus;14;400;;normal;;;LTR +T 86 87 85 76 78 72 w=32.8604 (strike) +Mx 52.5440 +T 86 87 85 76 78 72 w=32.8604 (strike) +Mx 90.2061 +T 86 87 85 76 78 72 w=32.8604 (strike) +Mx 127.8682 +T 86 87 85 76 78 72 w=32.8604 (strike) +Mx 165.5303 +T 88 81 71 72 85 79 76 81 72 w=56.2119 (underline) +Mx 226.5440 +T 88 81 71 72 85 79 76 81 72 w=56.2119 (underline) +Draw line 165.5303 33.2811 117.2256 0.6836 +Draw line 14.8819 27.8123 267.8740 0.6836 +Mx 14.8819 +My 48.3721 +T 88 81 71 72 85 79 76 81 72 w=56.2119 (underline) +Draw line 14.8819 50.0811 56.2119 0.6836 +Mx 74.2034 +T 86 87 85 76 78 72 w=32.8604 (strike) +Mx 107.0638 +T 17 w=3.2061 (.) +Draw line 14.8819 44.6123 95.3879 0.6836 +End page +Finish diff --git a/tests/feat-liner-spanning.sil b/tests/feat-liner-spanning.sil new file mode 100644 index 0000000000..7a2cf008f4 --- /dev/null +++ b/tests/feat-liner-spanning.sil @@ -0,0 +1,13 @@ +\begin[papersize=a6]{document} +\neverindent +\nofolios +\set[parameter=shaper.spaceenlargementfactor, value=1] +\use[module=packages.rules] +\font[size=14pt] +% strikethrough and underline can span multiple lines +% Here line-breaking (paragraphing) occurs in the underlined sequence. +\strikethrough{strike strike strike strike +\underline{underline underline underline} strike.} + +\end{document} + diff --git a/tests/feat-unicode-nbsp.sil b/tests/feat-unicode-nbsp.sil index f4ce0f5c2a..b28a557db4 100644 --- a/tests/feat-unicode-nbsp.sil +++ b/tests/feat-unicode-nbsp.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \language[main=und] diff --git a/tests/feat-unicode-softhyphen.sil b/tests/feat-unicode-softhyphen.sil index 1a34c76f63..8eb691855a 100644 --- a/tests/feat-unicode-softhyphen.sil +++ b/tests/feat-unicode-softhyphen.sil @@ -1,4 +1,12 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] +\begin{lua} + -- Recent versions of ICU report different information about chunking around + -- soft hyphens. Our test expectations are wired for 74, but our IC has 70. + local icu = require("justenoughicu") + local icu70minus = tostring(icu.version()) <= "70.0" + if icu70minus then SILE.status.unsupported = true end +\end{lua} \nofolios \neverindent % Language without hyphenation patterns diff --git a/tests/fluent.sil b/tests/fluent.sil index f78935f533..dbee08d7b8 100644 --- a/tests/fluent.sil +++ b/tests/fluent.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios % Output a message with variable replacement diff --git a/tests/font-features-cvXX.sil b/tests/font-features-cvXX.sil index de0b9826d4..e266d20389 100644 --- a/tests/font-features-cvXX.sil +++ b/tests/font-features-cvXX.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \use[module=packages.features] diff --git a/tests/footnote-skip.sil b/tests/footnote-skip.sil index 104cee520c..3d50961c67 100644 --- a/tests/footnote-skip.sil +++ b/tests/footnote-skip.sil @@ -1,9 +1,10 @@ \begin[papersize=a5,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lorem] \use[module=packages.rules] \begin{script} -SILE.scratch.insertions.classes.footnote.topSkip = SILE.length("5pt") -SILE.scratch.insertions.classes.footnote.interInsertionSkip = SILE.length("24pt") +SILE.scratch.insertions.classes.footnote.topSkip = SILE.types.length("5pt") +SILE.scratch.insertions.classes.footnote.interInsertionSkip = SILE.types.length("24pt") \end{script} \lorem[words=50]\footnote{\hrule[width=2pt, height=19.82421875pt]} diff --git a/tests/footnote.sil b/tests/footnote.sil index 53432aec3b..f925d5e1cf 100644 --- a/tests/footnote.sil +++ b/tests/footnote.sil @@ -1,4 +1,5 @@ \begin[papersize=a6,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.lorem] \font[family=Gentium Plus,size=10pt] diff --git a/tests/frametricks.sil b/tests/frametricks.sil index 9415b10eb1..7ec8628af0 100644 --- a/tests/frametricks.sil +++ b/tests/frametricks.sil @@ -4,11 +4,11 @@ \makecolumns[columns=2] \font[size=15pt] \set[parameter=document.baselineskip,value=3ex] -\script{SU.debug("float", "Frame Pre:", SILE.length("100%fw"):absolute())} +\script{SU.debug("float", "Frame Pre:", SILE.types.length("100%fw"):absolute())} \begin{raggedright} \float[rightboundary=5pt,bottomboundary=1ex]{\font[size=54pt]{T}}o Sherlock Holmes she is always \em{the woman}. I have seldom heard him mention her under any other name. In his eyes she eclipses and predominates the whole of her sex. It was not that he felt any emotion akin to love for Irene Adler. All emotions, and that one particularly, were abhorrent to his cold, precise but admirably balanced mind. He was, I take it, the most perfect reasoning and observing machine that the world has seen, but as a lover he would have placed himself in a false position. -\script{SU.debug("float", "Frame Post:", SILE.length("100%fw"):absolute())} +\script{SU.debug("float", "Frame Post:", SILE.types.length("100%fw"):absolute())} He never spoke of the softer passions, save with a gibe and a sneer. They were admirable things for the observer -- excellent for drawing the veil from men's motives and actions. But for the trained teasoner to admit such intru \end{raggedright} diff --git a/tests/grid.sil b/tests/grid.sil index b5bdbd28ff..3d7f7c303d 100644 --- a/tests/grid.sil +++ b/tests/grid.sil @@ -1,4 +1,6 @@ \begin[papersize=a5,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lorem] \use[module=packages.grid] \use[module=packages.frametricks] diff --git a/tests/hangafter.sil b/tests/hangafter.sil index 72e36f0578..658afcc060 100644 --- a/tests/hangafter.sil +++ b/tests/hangafter.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.lorem] \set[parameter=document.parskip,value=1ex] diff --git a/tests/hyph-except.sil b/tests/hyph-except.sil index e1b7766c99..3674a487d1 100644 --- a/tests/hyph-except.sil +++ b/tests/hyph-except.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \language[main=tr] \script{SILE.typesetter:typeset(SILE.showHyphenationPoints("rab'bin", "tr"))} diff --git a/tests/image.sil b/tests/image.sil index ea50fbd0f5..ef03f44ab5 100644 --- a/tests/image.sil +++ b/tests/image.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.image] One two \img[src=documentation/fig1.png,width=50] three diff --git a/tests/inc/bug-337.lua b/tests/inc/bug-337.lua index f6624cd7ee..f15d10d41e 100644 --- a/tests/inc/bug-337.lua +++ b/tests/inc/bug-337.lua @@ -4,11 +4,11 @@ local package = pl.class(base) package._name = "bug-337" -function package:_init (class) +function package:_init (options) - base._init(self, class) + base._init(self, options) - class:defineMaster({ + self.class:defineMaster({ id = "right", firstContentFrame = "content", frames = { @@ -26,23 +26,23 @@ function package:_init (class) } } }) - class:defineMaster({ + self.class:defineMaster({ id = "left", firstContentFrame = "content", frames = {} }) - class:mirrorMaster("right", "left") - class:switchMasterOnePage("right") + self.class:mirrorMaster("right", "left") + self.class:switchMasterOnePage("right") - class:loadPackage("cropmarks") + self.class:loadPackage("cropmarks") end function package:registerCommands () self.class:registerCommand("printPageInPoints", function() - local w = SILE.measurement("100%pw"):tonumber() - local h = SILE.measurement("100%ph"):tonumber() + local w = SILE.types.measurement("100%pw"):tonumber() + local h = SILE.types.measurement("100%ph"):tonumber() SILE.typesetter:typeset(("%.0fpt × %.0fpt"):format(w, h)) end) diff --git a/tests/inline-lua.sil b/tests/inline-lua.sil index 174335f2c3..9cecda9bf4 100644 --- a/tests/inline-lua.sil +++ b/tests/inline-lua.sil @@ -1,4 +1,5 @@ \begin[papersize=a5,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] % A real comment \begin{script} answer = { output = tostring(3 * 3 % 5) } diff --git a/tests/italic.sil b/tests/italic.sil index 1e65204740..409f367d59 100644 --- a/tests/italic.sil +++ b/tests/italic.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[style=italic]{Italic?} \end{document} diff --git a/tests/kannada.sil b/tests/kannada.sil index 8d0e49d2e3..8f9d232f97 100644 --- a/tests/kannada.sil +++ b/tests/kannada.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \noindent \font[family=Libertinus Serif,size=16pt] diff --git a/tests/letterspacing.sil b/tests/letterspacing.sil index 9d44e08524..9a319ab2cf 100644 --- a/tests/letterspacing.sil +++ b/tests/letterspacing.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] No letterspacing. diff --git a/tests/linespacing.sil b/tests/linespacing.sil index e90724b750..90f2d22eb1 100644 --- a/tests/linespacing.sil +++ b/tests/linespacing.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lorem] \nofolios \font[family=Libertinus Serif,size=12pt] diff --git a/tests/linespacing2.sil b/tests/linespacing2.sil index a56580c73f..c753e17fb1 100644 --- a/tests/linespacing2.sil +++ b/tests/linespacing2.sil @@ -1,4 +1,5 @@ \begin{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[family=Libertinus Serif,size=11pt] \use[module=packages.linespacing] diff --git a/tests/masters.sil b/tests/masters.sil index f09a2ed95b..a86b68e35a 100644 --- a/tests/masters.sil +++ b/tests/masters.sil @@ -1,4 +1,5 @@ \begin{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.masters] \use[module=packages.lorem] \use[module=packages.frametricks] diff --git a/tests/math-bigops.xml b/tests/math-bigops.xml index b10e6805e7..a4d7144230 100644 --- a/tests/math-bigops.xml +++ b/tests/math-bigops.xml @@ -1,4 +1,5 @@ + Big operators, text, MathML: diff --git a/tests/math-fractions.xml b/tests/math-fractions.xml index 64e6e347da..46125a2e3e 100644 --- a/tests/math-fractions.xml +++ b/tests/math-fractions.xml @@ -1,4 +1,5 @@ + Fractions, text, MathML: diff --git a/tests/math-ops.sil b/tests/math-ops.sil index 200c00ae1c..56c5a7fbfe 100644 --- a/tests/math-ops.sil +++ b/tests/math-ops.sil @@ -1,6 +1,7 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] -\script[src=packages/math] +\use[module=packages.math] Custom atom types: diff --git a/tests/math-spaces.sil b/tests/math-spaces.sil index db1ca80fca..a8ef4bd59f 100644 --- a/tests/math-spaces.sil +++ b/tests/math-spaces.sil @@ -1,6 +1,7 @@ \begin[class=plain]{document} +\use[module=packages.retrograde,target=v0.15.0] -\script[src=packages/math] +\use[module=packages.math] Positive space commands: diff --git a/tests/math-stretchy.xml b/tests/math-stretchy.xml index 2dbb47547c..a3c8ba33fc 100644 --- a/tests/math-stretchy.xml +++ b/tests/math-stretchy.xml @@ -1,4 +1,5 @@ + Stretchy parentheses, text, MathML: diff --git a/tests/math-subsup.xml b/tests/math-subsup.xml index 0b5bb76632..dec3159050 100644 --- a/tests/math-subsup.xml +++ b/tests/math-subsup.xml @@ -1,4 +1,5 @@ + Sub-superscripts, text, MathML: diff --git a/tests/math-tables-mathml.xml b/tests/math-tables-mathml.xml index 79ecabb100..7ebdfe5db2 100644 --- a/tests/math-tables-mathml.xml +++ b/tests/math-tables-mathml.xml @@ -1,4 +1,5 @@ + Number matrix, text, MathML: diff --git a/tests/math-tables-tex.sil b/tests/math-tables-tex.sil index e22730bad0..924bfb3275 100644 --- a/tests/math-tables-tex.sil +++ b/tests/math-tables-tex.sil @@ -1,4 +1,5 @@ \begin[class=plain]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.math] diff --git a/tests/math-variants.xml b/tests/math-variants.xml index 0e08d9ad4d..5850deabe9 100644 --- a/tests/math-variants.xml +++ b/tests/math-variants.xml @@ -1,4 +1,5 @@ + Math variants, text, MathML: diff --git a/tests/mini-arabic.sil b/tests/mini-arabic.sil index 039b647457..206cf89aaf 100644 --- a/tests/mini-arabic.sil +++ b/tests/mini-arabic.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.bidi] \thisframeRTL diff --git a/tests/mlcounter.sil b/tests/mlcounter.sil index f0c62a1ead..617c86dc77 100644 --- a/tests/mlcounter.sil +++ b/tests/mlcounter.sil @@ -1,4 +1,5 @@ \begin[papersize=a5]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.counters] \increment-multilevel-counter[id=test] diff --git a/tests/negative-spaces-in-line.sil b/tests/negative-spaces-in-line.sil index 4838acb3a9..cd44cdcd42 100644 --- a/tests/negative-spaces-in-line.sil +++ b/tests/negative-spaces-in-line.sil @@ -1,4 +1,5 @@ \begin[papersize=a6,direction=RTL-TTB]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \font[family=Awami Nastaliq,size=22] diff --git a/tests/nondeterminism.sil b/tests/nondeterminism.sil index 07412c8a10..c003469dd1 100644 --- a/tests/nondeterminism.sil +++ b/tests/nondeterminism.sil @@ -1,6 +1,7 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[size=11pt,family=Gentium Plus] -\noindent{}Now that we understand some of what SILE is about and what it seeks to do, +\noindent{}Now that we understand some of what SILE is about and what it seeks to do, let’s dive into SILE itself. \end{document} diff --git a/tests/parallel.sil b/tests/parallel.sil index 7476c5a0dd..8c3fb1182b 100644 --- a/tests/parallel.sil +++ b/tests/parallel.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \begin{script} local class = SILE.documentState.documentClass diff --git a/tests/parshaping-simple.sil b/tests/parshaping-simple.sil index 1f7e8ebfc8..d695f4b60e 100644 --- a/tests/parshaping-simple.sil +++ b/tests/parshaping-simple.sil @@ -1,4 +1,5 @@ \begin[papersize=a7]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \use[module=packages.lorem] \begin{script} diff --git a/tests/regressions.pl.in b/tests/regressions.pl.in index 65198c357f..0c6a7e58b0 100644 --- a/tests/regressions.pl.in +++ b/tests/regressions.pl.in @@ -10,6 +10,9 @@ my (@failed, @passed, @unsupported, @knownbad, @knownbadbutpassing, @missing); my @specifics = @ARGV; my @DISABLEDSRCS = split(/ +/, $DISABLEDSRCS); +my $highlighter = -t STDOUT ? "| @DELTA@" : ""; +my $diffcontext = -t STDOUT ? "2" : "0"; + my $exit = 0; for (@specifics ? @specifics : ) { my $expectation = $_; $expectation =~ s/\.(sil|xml)$/\.expected/; @@ -23,14 +26,14 @@ for (@specifics ? @specifics : ) { next; } # Run but don't fail on tests that exist but are known to fail - if (!system("head -n1 $_ | grep -q KNOWNBAD")) { + if (!system("@HEAD@ -n1 $_ | @GREP@ -q KNOWNBAD")) { $knownbad = 1; } if (! -f $actual and ! $knownbad) { push @failed, $_; - } elsif (!system("grep -qx 'UNSUPPORTED' $actual")) { + } elsif (!system("@GREP@ -qx 'UNSUPPORTED' $actual")) { $unsupported = 1; - } elsif (!system("diff -".($knownbad?"q":"")."U0 $expectation $actual")) { + } elsif (!system("@CMP@ -s $expectation $actual")) { if ($knownbad) { push @knownbadbutpassing, $_; } else { push @passed, $_; } } elsif ($knownbad) { @@ -38,6 +41,7 @@ for (@specifics ? @specifics : ) { } elsif ($unsupported) { push @unsupported, $_; } else { + system("@DIFF@ -".($knownbad?"q":"")."U$diffcontext $expectation $actual $highlighter"); push @failed, $_; } } else { diff --git a/tests/rskip.sil b/tests/rskip.sil index 1c2e1b3935..97cba4ef43 100644 --- a/tests/rskip.sil +++ b/tests/rskip.sil @@ -1,4 +1,5 @@ \begin[papersize=a5,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lorem] \set[parameter=document.rskip,value=20mm] \lorem[words=50] diff --git a/tests/settings.sil b/tests/settings.sil index 03e13b2cbb..e352a2fbfb 100644 --- a/tests/settings.sil +++ b/tests/settings.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent foo diff --git a/tests/split-footnote.sil b/tests/split-footnote.sil index 9c52014169..5afe6e1eeb 100644 --- a/tests/split-footnote.sil +++ b/tests/split-footnote.sil @@ -1,4 +1,5 @@ \begin[papersize=a6,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \use[module=packages.lorem] \nofolios Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod diff --git a/tests/sura-2.sil b/tests/sura-2.sil index 154bafb938..2d1c3d80aa 100644 --- a/tests/sura-2.sil +++ b/tests/sura-2.sil @@ -1,4 +1,5 @@ \begin[papersize=a6,direction=RTL]{document} +\use[module=packages.retrograde,target=v0.15.0] \begin{lua} -- ICU 74 introduced a change that affects where SILE sees font -- property changes in this text. Both outputs are fine, but we diff --git a/tests/tracking.sil b/tests/tracking.sil index 6b730039be..ce5c707f9b 100644 --- a/tests/tracking.sil +++ b/tests/tracking.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \font[size=20pt] \set[parameter=document.parindent,value=0] diff --git a/tests/turkish-hypenation-options.sil b/tests/turkish-hypenation-options.sil index 2214eb13a3..0b2a5a9e0d 100644 --- a/tests/turkish-hypenation-options.sil +++ b/tests/turkish-hypenation-options.sil @@ -1,4 +1,5 @@ \begin[papersize=a7]{document} +\use[module=packages.retrograde,target=v0.15.0] \nofolios \neverindent \font[size=32pt,language=tr] diff --git a/tests/twoside.sil b/tests/twoside.sil index 45cac8390c..837879defc 100644 --- a/tests/twoside.sil +++ b/tests/twoside.sil @@ -1,4 +1,5 @@ \begin[papersize=a8,class=book]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent % Note folio suppression on some pages are part of test \begin[command=stuff]{define} diff --git a/tests/variations-axis.sil b/tests/variations-axis.sil index bc43280d5c..4103ead911 100644 --- a/tests/variations-axis.sil +++ b/tests/variations-axis.sil @@ -1,4 +1,5 @@ \begin[papersize=a6]{document} +\use[module=packages.retrograde,target=v0.15.0] \neverindent \nofolios \font[family=Tourney,size=48pt] diff --git a/tests/vertical.expected b/tests/vertical.expected index 47aa08f8c3..55b5609f23 100644 --- a/tests/vertical.expected +++ b/tests/vertical.expected @@ -4,7 +4,7 @@ Mx 528.4079 My 107.6592 Set font Noto Sans CJK JP;24;400;;normal;;;TTB T 20220 w=24.0000 (日) -My 131.9203 +My 131.9210 T 20758 w=24.0000 (本) End page Finish diff --git a/core/color.lua b/types/color.lua similarity index 94% rename from core/color.lua rename to types/color.lua index 1e28c0f652..e436915b64 100644 --- a/core/color.lua +++ b/types/color.lua @@ -1,3 +1,6 @@ +--- SILE color type. +-- @types color + local colornames = { aliceblue = { 240, 248, 255 }, antiquewhite = { 250, 235, 215 }, @@ -163,13 +166,23 @@ local color = pl.class({ }) function color:_init (input) - local c = self:parse(input) + local c = type(input) == "string" and self:parse(input) or input for k, v in pairs(c) do self[k] = v end return self end +function color:__tostring() + local p = {} + for _, k in pairs({"r", "g", "b", "c", "m", "y", "k", "l"}) do + if self[k] then + table.insert(p, k .. SU.debug_round(self[k])) + end + end + return ("C<%s>"):format(pl.pretty.write(p, ""):gsub('[{}"]', "")) +end + function color.parse (_, input) local r, g, b, c, m, y, k, l if not input or type(input) ~= "string" then diff --git a/core/color_spec.lua b/types/color_spec.lua similarity index 97% rename from core/color_spec.lua rename to types/color_spec.lua index ff7b29ee85..74591c7fcb 100644 --- a/core/color_spec.lua +++ b/types/color_spec.lua @@ -2,7 +2,7 @@ SILE = require("core.sile") SU.warn = function () end describe("The color parser", function() - local parse = SILE.color + local parse = SILE.types.color local rebecca = { b = 0.6, g = 0.2, diff --git a/types/length.lua b/types/length.lua new file mode 100644 index 0000000000..3a091b0d42 --- /dev/null +++ b/types/length.lua @@ -0,0 +1,165 @@ +--- SILE length type. +-- Lengths are composed of 3 `measurement`s: a length, a stretch, and a shrink. Each part internally is just +-- a measurement, but combined describe a flexible length that is allowed to grow up to the amout defined by stretch or +-- compress up to the amount defined by shrink. +-- @types length + +local function _error_if_not_number (a) + if type(a) ~= "number" then + SU.error("We tried to do impossible arithmetic on a " .. SU.type(a) .. ". (That's a bug)", true) + end +end + +--- @type length +local length = pl.class() +length.type = "length" + +length.length = nil +length.stretch = nil +length.shrink = nil + +--- Constructor. +-- @tparam measurement spec A measurement or value that can be cast to a measurement. +-- @tparam[opt=0] measurement stretch A measurement describing how much the length is allowed to grow. +-- @tparam[opt=0] measurement shrink A measurement describing how much the length is allowed to grow. +-- @treturn length +-- @usage +-- SILE.types.length("6em", "4pt", "2pt") +-- SILE.types.length("6em plus 4pt minus 2pt") +-- SILE.types.length(30, 4, 2) +function length:_init (spec, stretch, shrink) + if stretch or shrink then + self.length = SILE.types.measurement(spec or 0) + self.stretch = SILE.types.measurement(stretch or 0) + self.shrink = SILE.types.measurement(shrink or 0) + elseif type(spec) == "number" then + self.length = SILE.types.measurement(spec) + elseif SU.type(spec) == "measurement" then + self.length = spec + elseif SU.type(spec) == "glue" then + self.length = SILE.types.measurement(spec.width.length or 0) + self.stretch = SILE.types.measurement(spec.width.stretch or 0) + self.shrink = SILE.types.measurement(spec.width.shrink or 0) + elseif type(spec) == "table" then + self.length = SILE.types.measurement(spec.length or 0) + self.stretch = SILE.types.measurement(spec.stretch or 0) + self.shrink = SILE.types.measurement(spec.shrink or 0) + elseif type(spec) == "string" then + local amount = tonumber(spec) + if type(amount) == "number" then + self:_init(amount) + else + local parsed = SILE.parserBits.length:match(spec) + if not parsed then SU.error("Could not parse length '"..spec.."'") end + self:_init(parsed) + end + end + if not self.length then self.length = SILE.types.measurement() end + if not self.stretch then self.stretch = SILE.types.measurement() end + if not self.shrink then self.shrink = SILE.types.measurement() end +end + +function length:absolute () + return SILE.types.length(self.length:tonumber(), self.stretch:tonumber(), self.shrink:tonumber()) +end + +function length:negate () + return self:__unm() +end + +function length:tostring () + return self:__tostring() +end + +function length:tonumber () + return self.length:tonumber() +end + +function length:__tostring () + local str = tostring(self.length) + if self.stretch.amount ~= 0 then str = str .. " plus " .. tostring(self.stretch) end + if self.shrink.amount ~= 0 then str = str .. " minus " .. tostring(self.shrink) end + return str +end + +function length:__add (other) + if type(self) == "number" then self, other = other, self end + other = SU.cast("length", other) + return SILE.types.length(self.length + other.length, + self.stretch + other.stretch, + self.shrink + other.shrink) +end + +-- See usage comments on SILE.types.measurement:___add() +function length:___add (other) + if SU.type(other) ~= "length" then + self.length:___add(other) + else + self.length:___add(other.length) + self.stretch:___add(other.stretch) + self.shrink:___add(other.shrink) + end + return nil +end + +function length:__sub (other) + local result = SILE.types.length(self) + other = SU.cast("length", other) + result.length = result.length - other.length + result.stretch = result.stretch - other.stretch + result.shrink = result.shrink - other.shrink + return result +end + +-- See usage comments on SILE.types.measurement:___add() +function length:___sub (other) + self.length:___sub(other.length) + self.stretch:___sub(other.stretch) + self.shrink:___sub(other.shrink) + return nil +end + +function length:__mul (other) + if type(self) == "number" then self, other = other, self end + _error_if_not_number(other) + local result = SILE.types.length(self) + result.length = result.length * other + result.stretch = result.stretch * other + result.shrink = result.shrink * other + return result +end + +function length:__div (other) + local result = SILE.types.length(self) + _error_if_not_number(other) + result.length = result.length / other + result.stretch = result.stretch / other + result.shrink = result.shrink / other + return result +end + +function length:__unm () + local result = SILE.types.length(self) + result.length = result.length:__unm() + return result +end + +function length:__lt (other) + local a = SU.cast("number", self) + local b = SU.cast("number", other) + return a - b < 0 +end + +function length:__le (other) + local a = SU.cast("number", self) + local b = SU.cast("number", other) + return a - b <= 0 +end + +function length:__eq (other) + local a = SU.cast("length", self) + local b = SU.cast("length", other) + return a.length == b.length and a.stretch == b.stretch and a.shrink == b.shrink +end + +return length diff --git a/types/measurement.lua b/types/measurement.lua new file mode 100644 index 0000000000..05134ac637 --- /dev/null +++ b/types/measurement.lua @@ -0,0 +1,223 @@ +--- SILE measurement type. +-- Measurements consist of an amount and a unit. Any registered `types.unit` may be used. Some units are relative and +-- their value may depend on the context where they are evaluated. Others are absolute. Unlike `types.length` +-- measurements have no stretch or shrink parameters. +-- @types measurement + +local function _tonumber (amount) + return SU.cast("number", amount) +end + +local function _similarunit (a, b) + if type(b) == "number" or type(a) == "number" then + return true + else + return a.unit == b.unit + end +end + +local function _hardnumber (a, b) + if type(b) == "number" or type(a) == "number" then + return true + else + return false + end +end + +local function _unit (a, b) + return type(a) == "table" and a.unit or b.unit +end + +local function _amount (input) + return type(input) == "number" and input or input.amount +end + +local function _pt_amount (input) + return type(input) == "number" and input or not input and 0 or input._mutable and input.amount or input:tonumber() +end + +local function _error_if_immutable (input) + if type(input) == "table" and not input._mutable then + SU.error("Not so fast, we can't do mutating arithmetic except on 'pt' unit measurements!", true) + end +end + +local function _error_if_relative (a, b) + if type(a) == "table" and a.relative or type(b) == "table" and b.relative then + SU.error("Cannot do arithmetic on a relative measurement without explicitly absolutizing it.", true) + end +end + +--- @type measurement +local measurement = pl.class() +measurement.type = "measurement" + +measurement.amount = 0 +measurement.unit = "pt" +measurement.relative = false +measurement._mutable = false + +--- Constructor. +-- @tparam number|length|measurement|string amount Amount of units or a string with the amount and unit. +-- @tparam[opt=pt] string unit Name of unit. +-- @treturn measurement +-- @usage +-- SILE.types.measurement(3, "em") +-- SILE.types.measurement("2%fw") +-- SILE.types.measurement(6) +function measurement:_init (amount, unit) + if unit then self.unit = unit end + if SU.type(amount) == "length" then + self.amount = amount.length.amount + self.unit = amount.length.unit + elseif type(amount) == "table" then + self.amount = amount.amount + self.unit = amount.unit + elseif type(tonumber(amount)) == "number" then + self.amount = tonumber(amount) + elseif type(amount) == "string" then + local parsed = SILE.parserBits.measurement:match(amount) + if not parsed then SU.error("Could not parse measurement '"..amount.."'") end + self.amount, self.unit = parsed.amount, parsed.unit + end + local _su = SILE.types.unit[self.unit] + if not _su then SU.error("Unknown unit: " .. unit) end + self.relative = _su.relative + if self.unit == "pt" then self._mutable = true end +end + +--- Convert relative measurements to absolute values and return a measurement. +-- Resolves relative measurements (like em relevant to the current font size) into absolute measurements. +-- @treturn measurement A new measurement in pt with any relative values resolved. +-- @usage +-- > a = SILE.types.measurement("1.2em") +-- > print(a:absolute()) +-- 12pt +function measurement:absolute () + return SILE.types.measurement(self:tonumber()) +end + +function measurement:tostring () + return self:__tostring() +end + +--- Convert relative measurements to absolute values and return a number. +-- Similar to `measurement:absolute` but returns a number instead of a new measurement type. +-- @treturn number A number (corresponding to pts) for the amount with any relative values resolved. +function measurement:tonumber () + local def = SILE.types.unit[self.unit] + local amount = def.converter and def.converter(self.amount) or (self.amount * def.value) + return amount +end + +function measurement:__tostring () + return self.amount .. self.unit +end + +function measurement:__concat (other) + return tostring(self) .. tostring(other) +end + +--- Addition meta-method. +-- Assuming matching relative units or absolute units, allows two measurements to be combined into one. +-- @tparam measurement other +-- @treturn measuremnet A new measurement of the same unit type as `self` with the value of `other` added. +-- @usage +-- > a = SILE.types.measurement(6, "em") +-- > b = SILE.types.measurement("2em") +-- > c = a + b +-- > print(c) +-- 8em +function measurement:__add (other) + if _similarunit(self, other) then + return SILE.types.measurement(_amount(self) + _amount(other), _unit(self, other)) + else + _error_if_relative(self, other) + return SILE.types.measurement(_tonumber(self) + _tonumber(other)) + end +end + +-- Note all private math (_ + __func()) functions: +-- * Are much faster than regular math operations +-- * Are **not** intended for use outside of the most performance sensitive loops +-- * Modify the lhs input in-place, never instantiating new objects +-- * Always assume absolute lhs input and absolutize the rhs values at runtime +-- * Assmue the inputs are sane with much less error checking than regular math funcs +-- * Are not composable using chained methods since they return nil for safety +function measurement:___add (other) + _error_if_immutable(self) + self.amount = self.amount + _pt_amount(other) + return nil +end + +function measurement:__sub (other) + if _similarunit(self, other) then + return SILE.types.measurement(_amount(self) - _amount(other), _unit(self, other)) + else + _error_if_relative(self, other) + return SILE.types.measurement(_tonumber(self) - _tonumber(other)) + end +end + +-- See usage comments on SILE.types.measurement:___add() +function measurement:___sub (other) + _error_if_immutable(self) + self.amount = self.amount - _pt_amount(other) + return nil +end + +function measurement:__mul (other) + if _hardnumber(self, other) then + return SILE.types.measurement(_amount(self) * _amount(other), _unit(self, other)) + else + _error_if_relative(self, other) + return SILE.types.measurement(_tonumber(self) * _tonumber(other)) + end +end + +function measurement:__pow (other) + if _hardnumber(self, other) then + return SILE.types.measurement(_amount(self) ^ _amount(other), self.unit) + else + _error_if_relative(self, other) + return SILE.types.measurement(_tonumber(self) ^ _tonumber(other)) + end +end + +function measurement:__div (other) + if _hardnumber(self, other) then + return SILE.types.measurement(_amount(self) / _amount(other), self.unit) + else + _error_if_relative(self, other) + return SILE.types.measurement(_tonumber(self) / _tonumber(other)) + end +end + +function measurement:__mod (other) + if _hardnumber(self, other) then + return SILE.types.measurement(_amount(self) % _amount(other), self.unit) + else + _error_if_relative(self, other) + return SILE.types.measurement(_tonumber(self) % _tonumber(other)) + end +end + +function measurement:__unm () + local ret = SILE.types.measurement(self) + ret.amount = self.amount * -1 + return ret +end + +function measurement:__eq (other) + return _tonumber(self) == _tonumber(other) +end + +function measurement:__lt (other) + return _tonumber(self) < _tonumber(other) +end + +function measurement:__le (other) + return _tonumber(self) <= _tonumber(other) +end + +return measurement diff --git a/core/nodefactory.lua b/types/node.lua similarity index 58% rename from core/nodefactory.lua rename to types/node.lua index 9d986e0079..c8ddeadd78 100644 --- a/core/nodefactory.lua +++ b/types/node.lua @@ -1,8 +1,11 @@ -local nodefactory = {} +--- SILE node type. +-- @types node + +local nodetypes = {} -- This infinity needs to be smaller than an actual infinity but bigger than the infinite stretch -- added by the typesetter. See https://github.com/sile-typesetter/sile/issues/227 -local infinity = SILE.measurement(1e13) +local infinity = SILE.types.measurement(1e13) local function _maxnode (nodes, dim) local dims = SU.map(function (node) @@ -11,24 +14,24 @@ local function _maxnode (nodes, dim) -- return node[dim] return SU.cast("length", node[dim]) end, nodes) - return SU.max(SILE.length(0), pl.utils.unpack(dims)) + return SU.max(SILE.types.length(0), pl.utils.unpack(dims)) end local _dims = pl.Set { "width", "height", "depth" } -nodefactory.box = pl.class() -nodefactory.box.type = "special" +nodetypes.box = pl.class() +nodetypes.box.type = "special" -nodefactory.box.height = nil -nodefactory.box.depth = nil -nodefactory.box.width = nil -nodefactory.box.misfit = false -nodefactory.box.explicit = false -nodefactory.box.discardable = false -nodefactory.box.value = nil -nodefactory.box._default_length = "width" +nodetypes.box.height = nil +nodetypes.box.depth = nil +nodetypes.box.width = nil +nodetypes.box.misfit = false +nodetypes.box.explicit = false +nodetypes.box.discardable = false +nodetypes.box.value = nil +nodetypes.box._default_length = "width" -function nodefactory.box:_init (spec) +function nodetypes.box:_init (spec) if type(spec) == "string" or type(spec) == "number" or SU.type(spec) == "measurement" @@ -43,7 +46,7 @@ function nodefactory.box:_init (spec) SU.error("Unimplemented, creating " .. self.type .. " node from " .. SU.type(spec), 1) end for dim in pairs(_dims) do - if not self[dim] then self[dim] = SILE.length() end + if not self[dim] then self[dim] = SILE.types.length() end end self["is_"..self.type] = true self.is_box = self.is_hbox or self.is_vbox or self.is_zerohbox or self.is_alternative or self.is_nnode @@ -52,24 +55,24 @@ function nodefactory.box:_init (spec) end -- De-init instances by shallow copying properties and removing meta table -function nodefactory.box:_tospec () +function nodetypes.box:_tospec () return pl.tablex.copy(self) end -function nodefactory.box:tostring () +function nodetypes.box:tostring () return self:__tostring() end -function nodefactory.box:__tostring () +function nodetypes.box:__tostring () return self.type end -function nodefactory.box.__concat (a, b) +function nodetypes.box.__concat (a, b) return tostring(a) .. tostring(b) end -function nodefactory.box:absolute () - local clone = nodefactory[self.type](self:_tospec()) +function nodetypes.box:absolute () + local clone = nodetypes[self.type](self:_tospec()) for dim in pairs(_dims) do clone[dim] = self[dim]:absolute() end @@ -79,101 +82,101 @@ function nodefactory.box:absolute () return clone end -function nodefactory.box:lineContribution () +function nodetypes.box:lineContribution () -- Regardless of the orientations, "width" is always in the -- writingDirection, and "height" is always in the "pageDirection" return self.misfit and self.height or self.width end -function nodefactory.box:outputYourself () +function nodetypes.box:outputYourself () SU.error(self.type.." with no output routine") end -function nodefactory.box:toText () +function nodetypes.box:toText () return self.type end -function nodefactory.box:isBox () +function nodetypes.box:isBox () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "hbox" or self.type == "zerohbox" or self.type == "alternative" or self.type == "nnode" or self.type == "vbox" end -function nodefactory.box:isNnode () +function nodetypes.box:isNnode () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type=="nnode" end -function nodefactory.box:isGlue () +function nodetypes.box:isGlue () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "glue" end -function nodefactory.box:isVglue () +function nodetypes.box:isVglue () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "vglue" end -function nodefactory.box:isZero () +function nodetypes.box:isZero () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "zerohbox" or self.type == "zerovglue" end -function nodefactory.box:isUnshaped () +function nodetypes.box:isUnshaped () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "unshaped" end -function nodefactory.box:isAlternative () +function nodetypes.box:isAlternative () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "alternative" end -function nodefactory.box:isVbox () +function nodetypes.box:isVbox () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "vbox" end -function nodefactory.box:isInsertion () +function nodetypes.box:isInsertion () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "insertion" end -function nodefactory.box:isMigrating () +function nodetypes.box:isMigrating () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.migrating end -function nodefactory.box:isPenalty () +function nodetypes.box:isPenalty () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "penalty" end -function nodefactory.box:isDiscretionary () +function nodetypes.box:isDiscretionary () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "discretionary" end -function nodefactory.box:isKern () +function nodetypes.box:isKern () SU.warn("Deprecated function, please use boolean is_ property to check types", true) return self.type == "kern" end -nodefactory.hbox = pl.class(nodefactory.box) -nodefactory.hbox.type = "hbox" +nodetypes.hbox = pl.class(nodetypes.box) +nodetypes.hbox.type = "hbox" -function nodefactory.hbox:_init (spec) - nodefactory.box._init(self, spec) +function nodetypes.hbox:_init (spec) + nodetypes.box._init(self, spec) end -function nodefactory.hbox:__tostring () +function nodetypes.hbox:__tostring () return "H<" .. tostring(self.width) .. ">^" .. tostring(self.height) .. "-" .. tostring(self.depth) .. "v" end -function nodefactory.hbox:scaledWidth (line) +function nodetypes.hbox:scaledWidth (line) return SU.rationWidth(self:lineContribution(), self.width, line.ratio) end -function nodefactory.hbox:outputYourself (typesetter, line) +function nodetypes.hbox:outputYourself (typesetter, line) local outputWidth = self:scaledWidth(line) if not self.value.glyphString then return end if typesetter.frame:writingDirection() == "RTL" then @@ -187,32 +190,32 @@ function nodefactory.hbox:outputYourself (typesetter, line) end end -nodefactory.zerohbox = pl.class(nodefactory.hbox) -nodefactory.zerohbox.type = "zerohbox" -nodefactory.zerohbox.value = { glyph = 0 } +nodetypes.zerohbox = pl.class(nodetypes.hbox) +nodetypes.zerohbox.type = "zerohbox" +nodetypes.zerohbox.value = { glyph = 0 } -nodefactory.nnode = pl.class(nodefactory.hbox) -nodefactory.nnode.type = "nnode" -nodefactory.nnode.language = "" -nodefactory.nnode.pal = nil -nodefactory.nnode.nodes = {} +nodetypes.nnode = pl.class(nodetypes.hbox) +nodetypes.nnode.type = "nnode" +nodetypes.nnode.language = "" +nodetypes.nnode.pal = nil +nodetypes.nnode.nodes = {} -function nodefactory.nnode:_init (spec) +function nodetypes.nnode:_init (spec) self:super(spec) if 0 == self.depth:tonumber() then self.depth = _maxnode(self.nodes, "depth") end if 0 == self.height:tonumber() then self.height = _maxnode(self.nodes, "height") end if 0 == self.width:tonumber() then self.width = SU.sum(SU.map(function (node) return node.width end, self.nodes)) end end -function nodefactory.nnode:__tostring () +function nodetypes.nnode:__tostring () return "N<" .. tostring(self.width) .. ">^" .. tostring(self.height) .. "-" .. tostring(self.depth) .. "v(" .. self:toText() .. ")" end -function nodefactory.nnode:absolute () +function nodetypes.nnode:absolute () return self end -function nodefactory.nnode:outputYourself (typesetter, line) +function nodetypes.nnode:outputYourself (typesetter, line) -- See typesetter:computeLineRatio() which implements the currently rather messy -- and probably slightly dubious 'hyphenated' logic. -- Example: consider the word "out-put". @@ -236,30 +239,30 @@ function nodefactory.nnode:outputYourself (typesetter, line) end end -function nodefactory.nnode:toText () +function nodetypes.nnode:toText () return self.text end -nodefactory.unshaped = pl.class(nodefactory.nnode) -nodefactory.unshaped.type = "unshaped" +nodetypes.unshaped = pl.class(nodetypes.nnode) +nodetypes.unshaped.type = "unshaped" -function nodefactory.unshaped:_init (spec) +function nodetypes.unshaped:_init (spec) self:super(spec) self.width = nil end -function nodefactory.unshaped:__tostring () +function nodetypes.unshaped:__tostring () return "U(" .. self:toText() .. ")"; end -getmetatable(nodefactory.unshaped).__index = function (_, _) +getmetatable(nodetypes.unshaped).__index = function (_, _) -- if k == "width" then SU.error("Can't get width of unshaped node", true) end -- TODO: No idea why porting to proper Penlight classes this ^^^^^^ started -- killing everything. Perhaps because this function started working and would -- actually need to return rawget(self, k) or something? end -function nodefactory.unshaped:shape () +function nodetypes.unshaped:shape () local node = SILE.shaper:createNnodes(self.text, self.options) for i=1, #node do node[i].parent = self.parent @@ -267,27 +270,49 @@ function nodefactory.unshaped:shape () return node end -function nodefactory.unshaped.outputYourself (_) +function nodetypes.unshaped.outputYourself (_) SU.error("An unshaped node made it to output", true) end -nodefactory.discretionary = pl.class(nodefactory.hbox) +nodetypes.discretionary = pl.class(nodetypes.hbox) -nodefactory.discretionary.type = "discretionary" -nodefactory.discretionary.prebreak = {} -nodefactory.discretionary.postbreak = {} -nodefactory.discretionary.replacement = {} -nodefactory.discretionary.used = false +nodetypes.discretionary.type = "discretionary" +nodetypes.discretionary.prebreak = {} +nodetypes.discretionary.postbreak = {} +nodetypes.discretionary.replacement = {} +nodetypes.discretionary.used = false -function nodefactory.discretionary:__tostring () +function nodetypes.discretionary:__tostring () return "D(" .. SU.concat(self.prebreak, "") .. "|" .. SU.concat(self.postbreak, "") .."|" .. SU.concat(self.replacement, "") .. ")"; end -function nodefactory.discretionary:toText () +function nodetypes.discretionary:toText () return self.used and "-" or "_" end -function nodefactory.discretionary:outputYourself (typesetter, line) +function nodetypes.discretionary:markAsPrebreak () + self.used = true + if self.parent then + self.parent.hyphenated = true + end + self.is_prebreak = true +end + +function nodetypes.discretionary:cloneAsPostbreak () + if not self.used then + SU.error("Cannot clone a non-used discretionary (previously marked as prebreak)") + end + return SILE.types.node.discretionary({ + prebreak = self.prebreak, + postbreak = self.postbreak, + replacement = self.replacement, + parent = self.parent, + used = true, + is_prebreak = false, + }) +end + +function nodetypes.discretionary:outputYourself (typesetter, line) -- See typesetter:computeLineRatio() which implements the currently rather -- messy hyphenated checks. -- Example: consider the word "out-put-ter". @@ -300,22 +325,13 @@ function nodefactory.discretionary:outputYourself (typesetter, line) -- It's possible not to have a parent (e.g. on a discretionary directly -- added in the queue and not coming from the hyphenator logic). - -- Eiher that, or we have a hyphenate parent. + -- Eiher that, or we have a hyphenated parent. if self.used then -- This is the actual hyphenation point. - -- Skip margin glue and zero boxes. - -- If we then reach our discretionary, it means its the first in the line, - -- i.e. a postbreak. Otherwise, its a prebreak (near the end of the line, - -- notwithstanding glues etc.) - local i = 1 - while (line.nodes[i].is_glue and line.nodes[i].value == "margin") - or line.nodes[i].type == "zerohbox" do - i = i + 1 - end - if (line.nodes[i] == self) then - for _, node in ipairs(self.postbreak) do node:outputYourself(typesetter, line) end - else + if self.is_prebreak then for _, node in ipairs(self.prebreak) do node:outputYourself(typesetter, line) end + else + for _, node in ipairs(self.postbreak) do node:outputYourself(typesetter, line) end end else -- This is not the hyphenation point (but another discretionary in the queue) @@ -326,229 +342,229 @@ function nodefactory.discretionary:outputYourself (typesetter, line) end end -function nodefactory.discretionary:prebreakWidth () +function nodetypes.discretionary:prebreakWidth () if self.prebw then return self.prebw end - self.prebw = SILE.length() + self.prebw = SILE.types.length() for _, node in ipairs(self.prebreak) do self.prebw:___add(node.width) end return self.prebw end -function nodefactory.discretionary:postbreakWidth () +function nodetypes.discretionary:postbreakWidth () if self.postbw then return self.postbw end - self.postbw = SILE.length() + self.postbw = SILE.types.length() for _, node in ipairs(self.postbreak) do self.postbw:___add(node.width) end return self.postbw end -function nodefactory.discretionary:replacementWidth () +function nodetypes.discretionary:replacementWidth () if self.replacew then return self.replacew end - self.replacew = SILE.length() + self.replacew = SILE.types.length() for _, node in ipairs(self.replacement) do self.replacew:___add(node.width) end return self.replacew end -function nodefactory.discretionary:prebreakHeight () +function nodetypes.discretionary:prebreakHeight () if self.prebh then return self.prebh end self.prebh = _maxnode(self.prebreak, "height") return self.prebh end -function nodefactory.discretionary:postbreakHeight () +function nodetypes.discretionary:postbreakHeight () if self.postbh then return self.postbh end self.postbh = _maxnode(self.postbreak, "height") return self.postbh end -function nodefactory.discretionary:replacementHeight () +function nodetypes.discretionary:replacementHeight () if self.replaceh then return self.replaceh end self.replaceh = _maxnode(self.replacement, "height") return self.replaceh end -function nodefactory.discretionary:replacementDepth () +function nodetypes.discretionary:replacementDepth () if self.replaced then return self.replaced end self.replaced = _maxnode(self.replacement, "depth") return self.replaced end -nodefactory.alternative = pl.class(nodefactory.hbox) +nodetypes.alternative = pl.class(nodetypes.hbox) -nodefactory.alternative.type = "alternative" -nodefactory.alternative.options = {} -nodefactory.alternative.selected = nil +nodetypes.alternative.type = "alternative" +nodetypes.alternative.options = {} +nodetypes.alternative.selected = nil -function nodefactory.alternative:__tostring () +function nodetypes.alternative:__tostring () return "A(" .. SU.concat(self.options, " / ") .. ")" end -function nodefactory.alternative:minWidth () +function nodetypes.alternative:minWidth () local minW = function (a, b) return SU.min(a.width, b.width) end return pl.tablex.reduce(minW, self.options) end -function nodefactory.alternative:deltas () +function nodetypes.alternative:deltas () local minWidth = self:minWidth() local rv = {} for i = 1, #self.options do rv[#rv+1] = self.options[i].width - minWidth end return rv end -function nodefactory.alternative:outputYourself (typesetter, line) +function nodetypes.alternative:outputYourself (typesetter, line) if self.selected then self.options[self.selected]:outputYourself(typesetter, line) end end -nodefactory.glue = pl.class(nodefactory.box) -nodefactory.glue.type = "glue" -nodefactory.glue.discardable = true +nodetypes.glue = pl.class(nodetypes.box) +nodetypes.glue.type = "glue" +nodetypes.glue.discardable = true -function nodefactory.glue:__tostring () +function nodetypes.glue:__tostring () return (self.explicit and "E:" or "") .. "G<" .. tostring(self.width) .. ">" end -function nodefactory.glue.toText (_) +function nodetypes.glue.toText (_) return " " end -function nodefactory.glue:outputYourself (typesetter, line) +function nodetypes.glue:outputYourself (typesetter, line) local outputWidth = SU.rationWidth(self.width:absolute(), self.width:absolute(), line.ratio) typesetter.frame:advanceWritingDirection(outputWidth) end -- A hfillglue is just a glue with infinite stretch. -- (Convenience so callers do not have to know what infinity is.) -nodefactory.hfillglue = pl.class(nodefactory.glue) -function nodefactory.hfillglue:_init (spec) +nodetypes.hfillglue = pl.class(nodetypes.glue) +function nodetypes.hfillglue:_init (spec) self:super(spec) - self.width = SILE.length(self.width.length, infinity, self.width.shrink) + self.width = SILE.types.length(self.width.length, infinity, self.width.shrink) end -- A hssglue is just a glue with infinite stretch and shrink. -- (Convenience so callers do not have to know what infinity is.) -nodefactory.hssglue = pl.class(nodefactory.glue) -function nodefactory.hssglue:_init (spec) +nodetypes.hssglue = pl.class(nodetypes.glue) +function nodetypes.hssglue:_init (spec) self:super(spec) - self.width = SILE.length(self.width.length, infinity, infinity) + self.width = SILE.types.length(self.width.length, infinity, infinity) end -nodefactory.kern = pl.class(nodefactory.glue) -nodefactory.kern.type = "kern" -- Perhaps some smell here, see comment on vkern -nodefactory.kern.discardable = false +nodetypes.kern = pl.class(nodetypes.glue) +nodetypes.kern.type = "kern" -- Perhaps some smell here, see comment on vkern +nodetypes.kern.discardable = false -function nodefactory.kern:__tostring () +function nodetypes.kern:__tostring () return "K<" .. tostring(self.width) .. ">" end -nodefactory.vglue = pl.class(nodefactory.box) -nodefactory.vglue.type = "vglue" -nodefactory.vglue.discardable = true -nodefactory.vglue._default_length = "height" -nodefactory.vglue.adjustment = nil +nodetypes.vglue = pl.class(nodetypes.box) +nodetypes.vglue.type = "vglue" +nodetypes.vglue.discardable = true +nodetypes.vglue._default_length = "height" +nodetypes.vglue.adjustment = nil -function nodefactory.vglue:_init (spec) +function nodetypes.vglue:_init (spec) self.adjustment = SILE.measurement() self:super(spec) end -function nodefactory.vglue:__tostring () +function nodetypes.vglue:__tostring () return (self.explicit and "E:" or "") .. "VG<" .. tostring(self.height) .. ">"; end -function nodefactory.vglue:adjustGlue (adjustment) +function nodetypes.vglue:adjustGlue (adjustment) self.adjustment = adjustment end -function nodefactory.vglue:outputYourself (typesetter, line) +function nodetypes.vglue:outputYourself (typesetter, line) typesetter.frame:advancePageDirection(line.height:absolute() + line.depth:absolute() + self.adjustment) end -function nodefactory.vglue:unbox () +function nodetypes.vglue:unbox () return { self } end -- A vfillglue is just a vglue with infinite stretch. -- (Convenience so callers do not have to know what infinity is.) -nodefactory.vfillglue = pl.class(nodefactory.vglue) -function nodefactory.vfillglue:_init (spec) +nodetypes.vfillglue = pl.class(nodetypes.vglue) +function nodetypes.vfillglue:_init (spec) self:super(spec) - self.height = SILE.length(self.width.length, infinity, self.width.shrink) + self.height = SILE.types.length(self.width.length, infinity, self.width.shrink) end -- A vssglue is just a vglue with infinite stretch and shrink. -- (Convenience so callers do not have to know what infinity is.) -nodefactory.vssglue = pl.class(nodefactory.vglue) -function nodefactory.vssglue:_init (spec) +nodetypes.vssglue = pl.class(nodetypes.vglue) +function nodetypes.vssglue:_init (spec) self:super(spec) - self.height = SILE.length(self.width.length, infinity, infinity) + self.height = SILE.types.length(self.width.length, infinity, infinity) end -nodefactory.zerovglue = pl.class(nodefactory.vglue) +nodetypes.zerovglue = pl.class(nodetypes.vglue) -nodefactory.vkern = pl.class(nodefactory.vglue) +nodetypes.vkern = pl.class(nodetypes.vglue) -- FIXME TODO -- Here we cannot do: --- nodefactory.vkern.type = "vkern" +-- nodetypes.vkern.type = "vkern" -- It cannot be typed as "vkern" as the pagebuilder doesn't check is_vkern. -- So it's just a vglue currrenty, marked as not discardable... --- But on the other hand, nodefactory.kern is typed "kern" and is not a glue... +-- But on the other hand, nodetypes.kern is typed "kern" and is not a glue... -- Frankly, the discardable/explicit flags and the types are too -- entangled and point towards a more general design issue. -- N.B. this vkern node is only used in the linespacing package so far. -nodefactory.vkern.discardable = false +nodetypes.vkern.discardable = false -function nodefactory.vkern:__tostring () +function nodetypes.vkern:__tostring () return "VK<" .. tostring(self.height) .. ">" end -nodefactory.penalty = pl.class(nodefactory.box) -nodefactory.penalty.type = "penalty" -nodefactory.penalty.discardable = true -nodefactory.penalty.penalty = 0 +nodetypes.penalty = pl.class(nodetypes.box) +nodetypes.penalty.type = "penalty" +nodetypes.penalty.discardable = true +nodetypes.penalty.penalty = 0 -function nodefactory.penalty:_init (spec) +function nodetypes.penalty:_init (spec) self:super(spec) if type(spec) ~= "table" then self.penalty = SU.cast("number", spec) end end -function nodefactory.penalty:__tostring () +function nodetypes.penalty:__tostring () return "P(" .. tostring(self.penalty) .. ")"; end -function nodefactory.penalty.outputYourself (_) +function nodetypes.penalty.outputYourself (_) end -function nodefactory.penalty.toText (_) +function nodetypes.penalty.toText (_) return "(!)" end -function nodefactory.penalty:unbox () +function nodetypes.penalty:unbox () return { self } end -nodefactory.vbox = pl.class(nodefactory.box) -nodefactory.vbox.type = "vbox" -nodefactory.vbox.nodes = {} -nodefactory.vbox._default_length = "height" +nodetypes.vbox = pl.class(nodetypes.box) +nodetypes.vbox.type = "vbox" +nodetypes.vbox.nodes = {} +nodetypes.vbox._default_length = "height" -function nodefactory.vbox:_init (spec) +function nodetypes.vbox:_init (spec) self.nodes = {} self:super(spec) self.depth = _maxnode(self.nodes, "depth") self.height = _maxnode(self.nodes, "height") end -function nodefactory.vbox:__tostring () +function nodetypes.vbox:__tostring () return "VB<" .. tostring(self.height) .. "|" .. self:toText() .. "v".. tostring(self.depth) ..")"; end -function nodefactory.vbox:toText () +function nodetypes.vbox:toText () return "VB[" .. SU.concat(SU.map(function (node) return node:toText() end, self.nodes), "") .. "]" end -function nodefactory.vbox:outputYourself (typesetter, line) +function nodetypes.vbox:outputYourself (typesetter, line) typesetter.frame:advancePageDirection(self.height) local initial = true for _, node in ipairs(self.nodes) do @@ -561,14 +577,14 @@ function nodefactory.vbox:outputYourself (typesetter, line) typesetter.frame:newLine() end -function nodefactory.vbox:unbox () +function nodetypes.vbox:unbox () for i = 1, #self.nodes do if self.nodes[i].is_vbox or self.nodes[i].is_vglue then return self.nodes end end return {self} end -function nodefactory.vbox:append (box) +function nodetypes.vbox:append (box) local nodes = box if not box then SU.error("nil box given", true) end if nodes.type then @@ -576,7 +592,7 @@ function nodefactory.vbox:append (box) end self.height = self.height:absolute() self.height:___add(self.depth) - local lastdepth = SILE.length() + local lastdepth = SILE.types.length() for i = 1, #nodes do table.insert(self.nodes, nodes[i]) self.height:___add(nodes[i].height) @@ -588,112 +604,50 @@ function nodefactory.vbox:append (box) self.depth = lastdepth end -nodefactory.migrating = pl.class(nodefactory.hbox) -nodefactory.migrating.type = "migrating" -nodefactory.migrating.material = {} -nodefactory.migrating.value = {} -nodefactory.migrating.nodes = {} +nodetypes.migrating = pl.class(nodetypes.hbox) +nodetypes.migrating.type = "migrating" +nodetypes.migrating.material = {} +nodetypes.migrating.value = {} +nodetypes.migrating.nodes = {} -function nodefactory.migrating:__tostring () +function nodetypes.migrating:__tostring () return "" end -local _deprecated_nodefactory = {} - -_deprecated_nodefactory.newHbox = function (spec) - return nodefactory.hbox(spec) -end - -_deprecated_nodefactory.newNnode = function (spec) - return nodefactory.nnode(spec) -end - -_deprecated_nodefactory.newUnshaped = function (spec) - return nodefactory.unshaped(spec) -end - -_deprecated_nodefactory.newDisc = function (spec) - return nodefactory.discretionary(spec) -end - -_deprecated_nodefactory.disc = function (spec) - return nodefactory.discretionary(spec) -end - -_deprecated_nodefactory.newAlternative = function (spec) - return nodefactory.alternative(spec) -end - -_deprecated_nodefactory.newGlue = function (spec) - return nodefactory.glue(spec) -end - -_deprecated_nodefactory.newKern = function (spec) - return nodefactory.kern(spec) -end - -_deprecated_nodefactory.newVglue = function (spec) - return nodefactory.vglue(spec) -end - -_deprecated_nodefactory.newVKern = function (spec) - return nodefactory.vkern(spec) -end - -_deprecated_nodefactory.newPenalty = function (spec) - return nodefactory.penalty(spec) -end - -_deprecated_nodefactory.newDiscretionary = function (spec) - return nodefactory.discretionary(spec) -end - -_deprecated_nodefactory.newVbox = function (spec) - return nodefactory.vbox(spec) -end - -_deprecated_nodefactory.newMigrating = function (spec) - return nodefactory.migrating(spec) -end - -_deprecated_nodefactory.zeroGlue = function () - return nodefactory.glue() -end - -_deprecated_nodefactory.hfillGlue = function () - return nodefactory.hfillglue() -end - -_deprecated_nodefactory.vfillGlue = function () - return nodefactory.vfillglue() -end - -_deprecated_nodefactory.hssGlue = function () - return nodefactory.hssglue() -end - -_deprecated_nodefactory.vssGlue = function () - return nodefactory.vssglue() -end - -_deprecated_nodefactory.zeroHbox = function () - return nodefactory.zerohbox() -end - -_deprecated_nodefactory.zeroVglue = function () - return nodefactory.zerovglue() -end - -setmetatable(nodefactory, { +local _deprecated_nodefactory = { + newHbox = true, + newNnode = true, + newUnshaped = true, + newDisc = true, + disc = true, + newAlternative = true, + newGlue = true, + newKern = true, + newVglue = true, + newVKern = true, + newPenalty = true, + newDiscretionary = true, + newVbox = true, + newMigrating = true, + zeroGlue = true, + hfillGlue = true, + vfillGlue = true, + hssGlue = true, + vssGlue = true, + zeroHbox = true, + zeroVglue = true, +} + +setmetatable(nodetypes, { __index = function (_, prop) if _deprecated_nodefactory[prop] then - SU.deprecated("SILE.nodefactory." .. prop, "SILE.nodefactory." .. prop:match("n?e?w?(.*)"):lower(), "0.10.0", "0.14.0") + SU.deprecated("SILE.types.node." .. prop, "SILE.types.node." .. prop:match("n?e?w?(.*)"):lower(), "0.10.0", "0.14.0") elseif type(prop) == "number" then -- luacheck: ignore 542 - -- Likely at attempt to iterate (or dump) the table, sort of safe to ignore + -- Likely an attempt to iterate, inspect, or dump the table, sort of safe to ignore else - SU.error("Attempt to access non-existent SILE.nodefactory." .. prop) + SU.error("Attempt to access non-existent SILE.types.node." .. prop) end end }) -return nodefactory +return nodetypes diff --git a/spec/nodefactory_spec.lua b/types/node_spec.lua similarity index 71% rename from spec/nodefactory_spec.lua rename to types/node_spec.lua index a78ca3475f..0fefc54267 100644 --- a/spec/nodefactory_spec.lua +++ b/types/node_spec.lua @@ -1,10 +1,10 @@ SILE = require("core.sile") describe("The node factory", function() - it("should exist", function() assert.is.truthy(SILE.nodefactory) end) + it("should exist", function() assert.is.truthy(SILE.types.node) end) describe("hboxes", function() - local hbox = SILE.nodefactory.hbox({ width = 20, height = 30, depth = 3 }) + local hbox = SILE.types.node.hbox({ width = 20, height = 30, depth = 3 }) it("should have width", function () assert.is.equal(20, hbox.width:tonumber()) end) it("should have height", function () assert.is.equal(30, hbox.height:tonumber()) end) it("should have depth", function () assert.is.equal(3, hbox.depth:tonumber()) end) @@ -15,9 +15,9 @@ describe("The node factory", function() end) describe("vboxes", function() - local hbox1 = SILE.nodefactory.hbox({ width = 10, height = 5, depth = 2 }) - local hbox2 = SILE.nodefactory.hbox({ width = 11, height = 6, depth = 3 }) - local vbox = SILE.nodefactory.vbox({ height = 4, depth = 3, nodes = { hbox1, hbox2 } }) + local hbox1 = SILE.types.node.hbox({ width = 10, height = 5, depth = 2 }) + local hbox2 = SILE.types.node.hbox({ width = 11, height = 6, depth = 3 }) + local vbox = SILE.types.node.vbox({ height = 4, depth = 3, nodes = { hbox1, hbox2 } }) it("should have nodes", function () assert.is.equal(2, #vbox.nodes) end) it("should have height", function () assert.is.equal(6, vbox.height:tonumber()) end) it("should have depth", function () assert.is.equal(3, vbox.depth:tonumber()) end) @@ -28,9 +28,9 @@ describe("The node factory", function() end) describe("nnodes", function () - local hbox1 = SILE.nodefactory.hbox({ width = 10, height = 5, depth = 3 }) - local hbox2 = SILE.nodefactory.hbox({ width = 20, height = 10, depth = 5 }) - local nnode = SILE.nodefactory.nnode({ text = "test", nodes = { hbox1, hbox2 } }) + local hbox1 = SILE.types.node.hbox({ width = 10, height = 5, depth = 3 }) + local hbox2 = SILE.types.node.hbox({ width = 20, height = 10, depth = 5 }) + local nnode = SILE.types.node.nnode({ text = "test", nodes = { hbox1, hbox2 } }) it("should have width", function () assert.is.equal(30, nnode.width:tonumber()) end) it("should have depth", function () assert.is.equal(5, nnode.depth:tonumber()) end) it("should have height", function () assert.is.equal(10, nnode.height:tonumber()) end) @@ -40,15 +40,15 @@ describe("The node factory", function() end) describe("discs", function () - local nnode1 = SILE.nodefactory.nnode({ width = 20, height = 30, depth = 3, text = "pre" }) - local nnode2 = SILE.nodefactory.nnode({ width = 20, height = 30, depth = 3, text = "break" }) - local nnode3 = SILE.nodefactory.nnode({ width = 20, height = 30, depth = 3, text = "post" }) - local discretionary = SILE.nodefactory.discretionary({ prebreak = { nnode1, nnode2 }, postbreak = { nnode3, nnode2 } }) + local nnode1 = SILE.types.node.nnode({ width = 20, height = 30, depth = 3, text = "pre" }) + local nnode2 = SILE.types.node.nnode({ width = 20, height = 30, depth = 3, text = "break" }) + local nnode3 = SILE.types.node.nnode({ width = 20, height = 30, depth = 3, text = "post" }) + local discretionary = SILE.types.node.discretionary({ prebreak = { nnode1, nnode2 }, postbreak = { nnode3, nnode2 } }) it("should stringify", function() assert.is.equal("D(N<20pt>^30pt-3ptv(pre)N<20pt>^30pt-3ptv(break)|N<20pt>^30pt-3ptv(post)N<20pt>^30pt-3ptv(break)|)", tostring(discretionary)) end) end) describe("glues", function () - local glue = SILE.nodefactory.glue({ width = SILE.length({ length = 3, stretch = 2, shrink = 2 }) }) + local glue = SILE.nodefactory.glue({ width = SILE.types.length({ length = 3, stretch = 2, shrink = 2 }) }) it("should have width", function () assert.is.equal(3, glue.width:tonumber()) end) it("should be discardable", function () assert.is.truthy(glue.discardable) end) it("should stringify", function() assert.is.equal("G<3pt plus 2pt minus 2pt>", tostring(glue)) end) @@ -56,7 +56,7 @@ describe("The node factory", function() describe("vboxs", function () local nnode1 = SILE.nodefactory.nnode({ width = 20, height = 30, depth = 3, text = "one" }) - local glue = SILE.nodefactory.glue({ width = SILE.length({ length = 3, stretch = 2, shrink = 2}) }) + local glue = SILE.nodefactory.glue({ width = SILE.types.length({ length = 3, stretch = 2, shrink = 2}) }) local nnode2 = SILE.nodefactory.nnode({ width = 20, height = 30, depth = 7, text = "two" }) local nnode3 = SILE.nodefactory.nnode({ width = 20, height = 30, depth = 2, text = "three" }) local vbox = SILE.nodefactory.vbox({ nodes = { nnode1, glue, nnode2, glue, nnode3 } }) diff --git a/core/units.lua b/types/unit.lua similarity index 88% rename from core/units.lua rename to types/unit.lua index 909c5ed5ac..d5d43c2557 100644 --- a/core/units.lua +++ b/types/unit.lua @@ -1,16 +1,21 @@ -local units = { +--- SILE unit type. +-- @types unit + +local bits = require("core.parserbits") + +local unittypes = { pt = { relative = false, value = 1 } } -setmetatable(units, { +setmetatable(unittypes, { __newindex = function (self, unit, spec) local def = SU.required(spec, "definition", "registering unit " .. unit) local relative = SU.boolean(spec.relative, false) if type(def) == "string" then - local parsed = SILE.parserBits.measurement:match(def) + local parsed = bits.measurement:match(def) if not parsed then SU.error("Could not parse unit definition '"..def.."'") end if not self[parsed.unit] then SU.error("Unit " .. unit .. " defined in terms of unknown unit " .. parsed.unit) @@ -36,36 +41,36 @@ setmetatable(units, { end }) -units["twip"] = { +unittypes["twip"] = { definition = "0.05pt" } -units["mm"] = { +unittypes["mm"] = { definition = "2.8346457pt" } -units["cm"] = { +unittypes["cm"] = { definition = "10mm" } -units["m"] = { +unittypes["m"] = { definition = "100cm" } -units["hm"] = { +unittypes["hm"] = { definition = "0.01mm" } -units["in"] = { +unittypes["in"] = { definition = "72pt" } -units["ft"] = { +unittypes["ft"] = { definition = "12in" } -- Picas are 1/6 inch, used in Docbook images -units["pc"] = { +unittypes["pc"] = { definition = "0.166666667in" } @@ -81,7 +86,7 @@ local checkFrameDefined = function () end end -units["%pw"] = { +unittypes["%pw"] = { relative = true, definition = function (value) checkPaperDefined() @@ -89,7 +94,7 @@ units["%pw"] = { end } -units["%ph"] = { +unittypes["%ph"] = { relative = true, definition = function (value) checkPaperDefined() @@ -97,7 +102,7 @@ units["%ph"] = { end } -units["%pmin"] = { +unittypes["%pmin"] = { relative = true, definition = function (value) checkPaperDefined() @@ -105,7 +110,7 @@ units["%pmin"] = { end } -units["%pmax"] = { +unittypes["%pmax"] = { relative = true, definition = function (value) checkPaperDefined() @@ -113,7 +118,7 @@ units["%pmax"] = { end } -units["%fw"] = { +unittypes["%fw"] = { relative = true, definition = function (value) checkFrameDefined() @@ -121,7 +126,7 @@ units["%fw"] = { end } -units["%fh"] = { +unittypes["%fh"] = { relative = true, definition = function (value) checkFrameDefined() @@ -129,7 +134,7 @@ units["%fh"] = { end } -units["%fmin"] = { +unittypes["%fmin"] = { relative = true, definition = function (value) checkFrameDefined() @@ -137,7 +142,7 @@ units["%fmin"] = { end } -units["%fmax"] = { +unittypes["%fmax"] = { relative = true, definition = function (value) checkFrameDefined() @@ -145,7 +150,7 @@ units["%fmax"] = { end } -units["%lw"] = { +unittypes["%lw"] = { relative = true, definition = function (value) local lskip = SILE.settings:get("document.lskip") @@ -157,7 +162,7 @@ units["%lw"] = { end } -units["ps"] = { +unittypes["ps"] = { relative = true, definition = function (value) local ps = SILE.settings:get("document.parskip") @@ -166,7 +171,7 @@ units["ps"] = { end } -units["bs"] = { +unittypes["bs"] = { relative = true, definition = function (value) local bs = SILE.settings:get("document.baselineskip") @@ -175,28 +180,28 @@ units["bs"] = { end } -units["em"] = { +unittypes["em"] = { relative = true, definition = function (value) return value * SILE.settings:get("font.size") end } -units["ex"] = { +unittypes["ex"] = { relative = true, definition = function (value) return value * SILE.shaper:measureChar("x").height end } -units["spc"] = { +unittypes["spc"] = { relative = true, definition = function (value) return value * SILE.shaper:measureChar(" ").width end } -units["en"] = { +unittypes["en"] = { relative = true, definition = "0.5em" } @@ -205,7 +210,7 @@ units["en"] = { -- width of a full-width character. In SILE terms it isn't: measuring an "m" in -- a 10pt Japanese font gets you 5 points. So we measure a full-width character -- and use that as a unit. We call it zw following ptex (zenkaku width) -units["zw"] = { +unittypes["zw"] = { relative = true, definition = function (v) local zenkakuchar = SILE.settings:get("document.zenkakuchar") @@ -220,4 +225,4 @@ units["zw"] = { end } -return units +return unittypes diff --git a/typesetters/base.lua b/typesetters/base.lua index bfd369f238..6572ca4f0f 100644 --- a/typesetters/base.lua +++ b/typesetters/base.lua @@ -1,12 +1,9 @@ ---- SILE typesetter (default/base) class. --- --- @copyright License: MIT --- @module typesetters.base --- - --- Typesetter base class +--- SILE typesetter class. +-- @interfaces typesetters +--- @type typesetter local typesetter = pl.class() + typesetter.type = "typesetter" typesetter._name = "base" @@ -19,8 +16,8 @@ local supereject_penalty = 2 * -inf_bad -- Local helper class to compare pairs of margins local _margins = pl.class({ - lskip = SILE.nodefactory.glue(), - rskip = SILE.nodefactory.glue(), + lskip = SILE.types.node.glue(), + rskip = SILE.types.node.glue(), _init = function (self, lskip, rskip) self.lskip, self.rskip = lskip, rskip @@ -46,6 +43,8 @@ function typesetter:init (frame) self:_init(frame) end +--- Constructor +-- @param frame A initial frame to attach the typesetter to. function typesetter:_init (frame) self:declareSettings() self.hooks = {} @@ -60,6 +59,7 @@ function typesetter:_init (frame) return self end +--- Declare new setting types function typesetter.declareSettings(_) -- Settings common to any typesetter instance. @@ -100,7 +100,7 @@ function typesetter.declareSettings(_) SILE.settings:declare({ parameter = "typesetter.parfillskip", type = "glue", - default = SILE.nodefactory.glue("0pt plus 10000pt"), + default = SILE.types.node.glue("0pt plus 10000pt"), help = "Glue added at the end of a paragraph" }) @@ -114,14 +114,14 @@ function typesetter.declareSettings(_) SILE.settings:declare({ parameter = "typesetter.underfulltolerance", type = "length or nil", - default = SILE.length("1em"), + default = SILE.types.length("1em"), help = "Amount a page can be underfull without warning" }) SILE.settings:declare({ parameter = "typesetter.overfulltolerance", type = "length or nil", - default = SILE.length("5pt"), + default = SILE.types.length("5pt"), help = "Amount a page can be overfull without warning" }) @@ -132,6 +132,13 @@ function typesetter.declareSettings(_) help = "Width to break lines at" }) + SILE.settings:declare({ + parameter = "typesetter.italicCorrection", + type = "boolean", + default = false, + help = "Whether italic correction is activated or not" + }) + SILE.settings:declare({ parameter = "typesetter.softHyphen", type = "boolean", @@ -159,6 +166,7 @@ function typesetter:initState () nodes = {}, outputQueue = {}, lastBadness = awful_bad, + liners = {}, } end @@ -226,25 +234,25 @@ end function typesetter:pushHbox (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushHorizontal() to pass a premade node instead of a spec") end local ntype = SU.type(spec) - local node = (ntype == "hbox" or ntype == "zerohbox") and spec or SILE.nodefactory.hbox(spec) + local node = (ntype == "hbox" or ntype == "zerohbox") and spec or SILE.types.node.hbox(spec) return self:pushHorizontal(node) end function typesetter:pushUnshaped (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushHorizontal() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "unshaped" and spec or SILE.nodefactory.unshaped(spec) + local node = SU.type(spec) == "unshaped" and spec or SILE.types.node.unshaped(spec) return self:pushHorizontal(node) end function typesetter:pushGlue (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushHorizontal() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "glue" and spec or SILE.nodefactory.glue(spec) + local node = SU.type(spec) == "glue" and spec or SILE.types.node.glue(spec) return self:pushHorizontal(node) end function typesetter:pushExplicitGlue (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushHorizontal() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "glue" and spec or SILE.nodefactory.glue(spec) + local node = SU.type(spec) == "glue" and spec or SILE.types.node.glue(spec) node.explicit = true node.discardable = false return self:pushHorizontal(node) @@ -252,30 +260,30 @@ end function typesetter:pushPenalty (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushHorizontal() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "penalty" and spec or SILE.nodefactory.penalty(spec) + local node = SU.type(spec) == "penalty" and spec or SILE.types.node.penalty(spec) return self:pushHorizontal(node) end function typesetter:pushMigratingMaterial (material) - local node = SILE.nodefactory.migrating({ material = material }) + local node = SILE.types.node.migrating({ material = material }) return self:pushHorizontal(node) end function typesetter:pushVbox (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "vbox" and spec or SILE.nodefactory.vbox(spec) + local node = SU.type(spec) == "vbox" and spec or SILE.types.node.vbox(spec) return self:pushVertical(node) end function typesetter:pushVglue (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "vglue" and spec or SILE.nodefactory.vglue(spec) + local node = SU.type(spec) == "vglue" and spec or SILE.types.node.vglue(spec) return self:pushVertical(node) end function typesetter:pushExplicitVglue (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "vglue" and spec or SILE.nodefactory.vglue(spec) + local node = SU.type(spec) == "vglue" and spec or SILE.types.node.vglue(spec) node.explicit = true node.discardable = false return self:pushVertical(node) @@ -283,7 +291,7 @@ end function typesetter:pushVpenalty (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "penalty" and spec or SILE.nodefactory.penalty(spec) + local node = SU.type(spec) == "penalty" and spec or SILE.types.node.penalty(spec) return self:pushVertical(node) end @@ -300,7 +308,7 @@ function typesetter:typeset (text) local warnedshy = false for token2 in SU.gtoke(token.string, luautf8.char(0x00AD)) do if token2.separator then -- soft hyphen support - local discretionary = SILE.nodefactory.discretionary({}) + local discretionary = SILE.types.node.discretionary({}) local hbox = SILE.typesetter:makeHbox({ SILE.settings:get("font.hyphenchar") }) discretionary.prebreak = { hbox } table.insert(SILE.typesetter.state.nodes, discretionary) @@ -327,7 +335,7 @@ end function typesetter:initline () if self.state.hmodeOnly then return end -- https://github.com/sile-typesetter/sile/issues/1718 if (#self.state.nodes == 0) then - self.state.nodes[#self.state.nodes+1] = SILE.nodefactory.zerohbox() + self.state.nodes[#self.state.nodes+1] = SILE.types.node.zerohbox() SILE.documentState.documentClass.newPar(self) end end @@ -346,13 +354,13 @@ local speakerChangeReplacement = luautf8.char(0x2014) .. " " -- Special unshaped node subclass to handle space after a speaker change in dialogues -- introduced by an em-dash. -local speakerChangeNode = pl.class(SILE.nodefactory.unshaped) +local speakerChangeNode = pl.class(SILE.types.node.unshaped) function speakerChangeNode:shape() local node = self._base.shape(self) local spc = node[2] if spc and spc.is_glue then -- Switch the variable space glue to a fixed kern - node[2] = SILE.nodefactory.kern({ width = spc.width.length }) + node[2] = SILE.types.node.kern({ width = spc.width.length }) node[2].parent = self.parent else -- Should not occur: @@ -395,18 +403,160 @@ function typesetter:breakIntoLines (nodelist, breakWidth) return self:breakpointsToLines(breakpoints) end -function typesetter.shapeAllNodes (_, nodelist) - local newNl = {} - for i = 1, #nodelist do - if nodelist[i].is_unshaped then - pl.tablex.insertvalues(newNl, nodelist[i]:shape()) +local function getLastShape(nodelist) + local hasGlue + local last + if nodelist then + -- The node list may contain nnodes, penalties, kern and glue + -- We skip the latter, and retrieve the last shaped item. + for i = #nodelist, 1, -1 do + local n = nodelist[i] + if n.is_nnode then + local items = n.nodes[#n.nodes].value.items + last = items[#items] + break + end + if n.is_kern and n.subtype == "punctspace" then + -- Some languages such as French insert a special space around + -- punctuations. In those case, we should not need italic correction. + break + end + if n.is_glue then hasGlue = true end + end + end + return last, hasGlue +end +local function getFirstShape(nodelist) + local first + local hasGlue + if nodelist then + -- The node list may contain nnodes, penalties, kern and glue + -- We skip the latter, and retrieve the first shaped item. + for i = 1, #nodelist do + local n = nodelist[i] + if n.is_nnode then + local items = n.nodes[1].value.items + first = items[1] + break + end + if n.is_kern and n.subtype == "punctspace" then + -- Some languages such as French insert a special space around + -- punctuations. In those case, we should not need italic correction. + break + end + if n.is_glue then hasGlue = true end + end + end + return first, hasGlue +end + +local function fromItalicCorrection (precShape, curShape) + local xOffset + if not curShape or not precShape then + xOffset = 0 + else + -- Computing italic correction is at best heuristics. + -- The strong assumption is that italic is slanted to the right. + -- Thus, the part of the character that goes beyond its width is usually + -- maximal at the top of the glyph. + -- E.g. consider a "f", that would be the top hook extent. + -- Pathological cases exist, such as fonts with a Q with a long tail, + -- but these will rarely occur in usual languages. For instance, Klingon's + -- "QaQ" might be an issue, but there's not much we can do... + -- Another assumption is that we can distribute that extent in proportion + -- with the next character's height. + -- This might not work that well with non-Latin scripts. + local d = precShape.glyphWidth + precShape.x_bearing + local delta = d > precShape.width and d - precShape.width or 0 + xOffset = precShape.height <= curShape.height + and delta + or delta * curShape.height / precShape.height + end + return xOffset +end + +local function toItalicCorrection (precShape, curShape) + if not SILE.settings:get("typesetter.italicCorrection") then return end + local xOffset + if not curShape or not precShape then + xOffset = 0 + else + -- Same assumptions as fromItalicCorrection(), but on the starting side of + -- the glyph. + local d = curShape.x_bearing + local delta = d < 0 and -d or 0 + xOffset = precShape.depth >= curShape.depth + and delta + or delta * precShape.depth / curShape.depth + end + return xOffset +end + +local function isItalicLike(nnode) + -- We could do... + -- return nnode and string.lower(nnode.options.style) == "italic" + -- But it's probably more robust to use the italic angle, so that + -- thin italic, oblique or slanted fonts etc. may work too. + local ot = require("core.opentype-parser") + local face = SILE.font.cache(nnode.options, SILE.shaper.getFace) + local font = ot.parseFont(face) + return font.post.italicAngle ~= 0 +end + +function typesetter.shapeAllNodes (_, nodelist, inplace) + inplace = SU.boolean(inplace, true) -- Compatibility with earlier versions + local newNodelist = {} + local prec + local precShapedNodes + for _, current in ipairs(nodelist) do + if current.is_unshaped then + local shapedNodes = current:shape() + + if SILE.settings:get("typesetter.italicCorrection") and prec then + local itCorrOffset + local isGlue + if isItalicLike(prec) and not isItalicLike(current) then + local precShape, precHasGlue = getLastShape(precShapedNodes) + local curShape, curHasGlue = getFirstShape(shapedNodes) + isGlue = precHasGlue or curHasGlue + itCorrOffset = fromItalicCorrection(precShape, curShape) + elseif not isItalicLike(prec) and isItalicLike(current) then + local precShape, precHasGlue = getLastShape(precShapedNodes) + local curShape, curHasGlue = getFirstShape(shapedNodes) + isGlue = precHasGlue or curHasGlue + itCorrOffset = toItalicCorrection(precShape, curShape) + end + if itCorrOffset and itCorrOffset ~= 0 then + -- If one of the node contains a glue (e.g. "a \em{proof} is..."), + -- line breaking may occur between them, so our correction shall be + -- a glue too. + -- Otherwise, the font change is considered to occur at a non-breaking + -- point (e.g. "\em{proof}!") and the correction shall be a kern. + local makeItCorrNode = isGlue and SILE.types.node.glue or SILE.types.node.kern + newNodelist[#newNodelist+1] = makeItCorrNode({ + width = SILE.types.length(itCorrOffset), + subtype = "itcorr" + }) + end + end + + pl.tablex.insertvalues(newNodelist, shapedNodes) + + prec = current + precShapedNodes = shapedNodes else - newNl[#newNl+1] = nodelist[i] + prec = nil + newNodelist[#newNodelist+1] = current end end - for i =1, #newNl do nodelist[i]=newNl[i] end - if #nodelist > #newNl then - for i=#newNl+1, #nodelist do nodelist[i]=nil end + + if not inplace then + return newNodelist + end + + for i =1, #newNodelist do nodelist[i] = newNodelist[i] end + if #nodelist > #newNodelist then + for i= #newNodelist + 1, #nodelist do nodelist[i] = nil end end end @@ -432,7 +582,7 @@ function typesetter:boxUpNodes () self:pushGlue(parfillskip) self:pushPenalty(-inf_bad) SU.debug("typesetter", function () - return "Boxed up "..(#nodelist > 500 and (#nodelist).." nodes" or SU.contentToString(nodelist)) + return "Boxed up "..(#nodelist > 500 and (#nodelist).." nodes" or SU.ast.contentToString(nodelist)) end) local breakWidth = SILE.settings:get("typesetter.breakwidth") or self.frame:getLineWidth() local lines = self:breakIntoLines(nodelist, breakWidth) @@ -450,7 +600,7 @@ function typesetter:boxUpNodes () nodes[#nodes+1] = node end end - local vbox = SILE.nodefactory.vbox({ nodes = nodes, ratio = line.ratio }) + local vbox = SILE.types.node.vbox({ nodes = nodes, ratio = line.ratio }) local pageBreakPenalty = 0 if (#lines > 1 and index == 1) then pageBreakPenalty = SILE.settings:get("typesetter.widowpenalty") @@ -463,7 +613,7 @@ function typesetter:boxUpNodes () self.state.previousVbox = vbox if pageBreakPenalty > 0 then SU.debug("typesetter", "adding penalty of", pageBreakPenalty, "after", vbox) - vboxes[#vboxes+1] = SILE.nodefactory.penalty(pageBreakPenalty) + vboxes[#vboxes+1] = SILE.types.node.penalty(pageBreakPenalty) end end return vboxes @@ -528,8 +678,8 @@ end function typesetter:setVerticalGlue (pageNodeList, target) local glues = {} - local gTotal = SILE.length() - local totalHeight = SILE.length() + local gTotal = SILE.types.length() + local totalHeight = SILE.types.length() local pastTop = false for _, node in ipairs(pageNodeList) do @@ -655,7 +805,7 @@ function typesetter:pushBack () local discardedFistInitLine = false if (#self.state.nodes == 0) then -- Setup queue but avoid calling newPar - self.state.nodes[#self.state.nodes+1] = SILE.nodefactory.zerohbox() + self.state.nodes[#self.state.nodes+1] = SILE.types.node.zerohbox() end for i, node in ipairs(vbox.nodes) do if node.is_glue and not node.discardable then @@ -721,12 +871,7 @@ end function typesetter:leaveHmode (independent) if self.state.hmodeOnly then - -- HACK HBOX - -- This should likely be an error, but may break existing uses - -- (although these are probably already defective). - -- See also comment HACK HBOX in typesetter:makeHbox(). - SU.warn([[Building paragraphs in this context may have unpredictable results. -It will likely break in future versions]]) + SU.error([[Paragraphs are forbidden in restricted horizontal mode.]]) end SU.debug("typesetter", "Leaving hmode") local margins = self:getMargins() @@ -752,7 +897,7 @@ function typesetter.leadingFor (_, vbox, previous) SU.debug("typesetter", " Considering leading between two lines:") SU.debug("typesetter", " 1)", previous) SU.debug("typesetter", " 2)", vbox) - if not previous then return SILE.nodefactory.vglue() end + if not previous then return SILE.types.node.vglue() end local prevDepth = previous.depth SU.debug("typesetter", " Depth of previous line was", prevDepth) local bls = SILE.settings:get("document.baselineskip") @@ -762,32 +907,215 @@ function typesetter.leadingFor (_, vbox, previous) -- the lineskip setting is a vglue, but we need a version absolutized at this point, see #526 local lead = SILE.settings:get("document.lineskip").height:absolute() if depth > lead then - return SILE.nodefactory.vglue(SILE.length(depth.length, bls.height.stretch, bls.height.shrink)) + return SILE.types.node.vglue(SILE.types.length(depth.length, bls.height.stretch, bls.height.shrink)) else - return SILE.nodefactory.vglue(lead) + return SILE.types.node.vglue(lead) end end +-- Beggining of liner logic (contructs spanning over several lines) + +-- These two special nodes are used to track the current liner entry and exit. +-- As Sith Lords, they are always two: they are local here, so no one can +-- use one alone and break the balance of the Force. +local linerEnterNode = pl.class(SILE.nodefactory.hbox) +function linerEnterNode:_init(name, outputMethod) + SILE.nodefactory.hbox._init(self) + self.outputMethod = outputMethod + self.name = name + self.is_enter = true +end +function linerEnterNode:clone() + return linerEnterNode(self.name, self.outputMethod) +end +function linerEnterNode:outputYourself () + SU.error("A liner enter node " .. tostring(self) .. "'' made it to output", true) +end +function linerEnterNode:__tostring () + return "+L[" .. self.name .. "]" +end +local linerLeaveNode = pl.class(SILE.nodefactory.hbox) +function linerLeaveNode:_init(name) + SILE.nodefactory.hbox._init(self) + self.name = name + self.is_leave = true +end +function linerLeaveNode:clone() + return linerLeaveNode(self.name) +end +function linerLeaveNode:outputYourself () + SU.error("A liner leave node " .. tostring(self) .. "'' made it to output", true) +end +function linerLeaveNode:__tostring () + return "-L[" .. self.name .. "]" +end + +local linerBox = pl.class(SILE.nodefactory.hbox) +function linerBox:_init (name, outputMethod) + SILE.nodefactory.hbox._init(self) + self.width = SILE.length() + self.height = SILE.length() + self.depth = SILE.length() + self.name = name + self.inner = {} + self.outputYourself = outputMethod +end +function linerBox:append (node) + self.inner[#self.inner+1] = node + if node.is_discretionary then + -- Discretionary nodes don't have a width of their own. + if node.used then + if node.is_prebreak then + self.width:___add(node:prebreakWidth()) + else + self.width:___add(node:postbreakWidth()) + end + else + self.width:___add(node:replacementWidth()) + end + else + self.width:___add(node.width:absolute()) + end + self.height = SU.max(self.height, node.height) + self.depth = SU.max(self.depth, node.depth) +end +function linerBox:count () + return #self.inner +end +function linerBox:outputContent (tsetter, line) + for _, node in ipairs(self.inner) do + node.outputYourself(node, tsetter, line) + end +end +function linerBox:__tostring () + return "*L[" .. self.name .. "]H<" .. tostring(self.width) .. ">^" .. tostring(self.height) .. "-" .. tostring(self.depth) .. "v" +end + +--- Any unclosed liner is reopened on the current line, so we clone and repeat it. +-- An assumption is that the inserts are done after the current slice content, +-- supposed to be just before meaningful (visible) content. +-- @tparam slice slice +-- @treturn boolean Whether a liner was reopened +function typesetter:_repeatEnterLiners (slice) + local m = self.state.liners + if #m > 0 then + for i = 1, #m do + local n = m[i]:clone() + slice[#slice+1] = n + SU.debug("typesetter.liner", "Reopening liner", n) + end + return true + end + return false +end + +--- All pairs of liners are rebuilt as hboxes wrapping their content. +-- Migrating content, however, must be kept outside the hboxes at top slice level. +-- @tparam table slice Flat nodes from current line +-- @treturn table New reboxed slice +function typesetter._reboxLiners (_, slice) + local outSlice = {} + local migratingList = {} + local lboxStack = {} + for i = 1, #slice do + local node = slice[i] + if node.is_enter then + SU.debug("typesetter.liner", "Start reboxing", node) + local n = linerBox(node.name, node.outputMethod) + lboxStack[#lboxStack+1] = n + elseif node.is_leave then + if #lboxStack == 0 then + SU.error("Multiliner box stacking mismatch" .. node) + elseif #lboxStack == 1 then + SU.debug("typesetter.liner", "End reboxing", node, + "(toplevel) =", lboxStack[1]:count(), "nodes") + if lboxStack[1]:count() > 0 then + outSlice[#outSlice+1] = lboxStack[1] + end + else + SU.debug("typesetter.liner", "End reboxing", node, + "(sublevel) =", lboxStack[#lboxStack]:count(), "nodes") + if lboxStack[#lboxStack]:count() > 0 then + local hbox = lboxStack[#lboxStack - 1] + hbox:append(lboxStack[#lboxStack]) + end + end + lboxStack[#lboxStack] = nil + pl.tablex.insertvalues(outSlice, migratingList) + migratingList = {} + else + if #lboxStack > 0 then + if not node.is_migrating then + local lbox = lboxStack[#lboxStack] + lbox:append(node) + else + migratingList[#migratingList+1] = node + end + else + outSlice[#outSlice+1] = node + end + end + end + return outSlice -- new reboxed slice +end + +--- Check if a node is a liner, and process it if so, in a stack. +-- @tparam table node Current node (any type) +-- @treturn boolean Whether a liner was opened +function typesetter:_processIfLiner(node) + local entered = false + if node.is_enter then + SU.debug("typesetter.liner", "Enter liner", node) + self.state.liners[#self.state.liners+1] = node + entered = true + elseif node.is_leave then + SU.debug("typesetter.liner", "Leave liner", node) + if #self.state.liners == 0 then + SU.error("Multiliner stack mismatch" .. node) + elseif self.state.liners[#self.state.liners].name == node.name then + self.state.liners[#self.state.liners].link = node -- for consistency check + self.state.liners[#self.state.liners] = nil + else + SU.error("Multiliner stack inconsistency" + .. self.state.liners[#self.state.liners] .. "vs. " .. node) + end + end + return entered +end + +function typesetter:_repeatLeaveLiners(slice, insertIndex) + for _, v in ipairs(self.state.liners) do + if not v.link then + local n = linerLeaveNode(v.name) + SU.debug("typesetter.liner", "Closing liner", n) + table.insert(slice, insertIndex, n) + else + SU.error("Multiliner stack inconsistency" .. v) + end + end +end +-- End of liner logic + function typesetter:addrlskip (slice, margins, hangLeft, hangRight) local LTR = self.frame:writingDirection() == "LTR" local rskip = margins[LTR and "rskip" or "lskip"] - if not rskip then rskip = SILE.nodefactory.glue(0) end + if not rskip then rskip = SILE.types.node.glue(0) end if hangRight and hangRight > 0 then - rskip = SILE.nodefactory.glue({ width = rskip.width:tonumber() + hangRight }) + rskip = SILE.types.node.glue({ width = rskip.width:tonumber() + hangRight }) end rskip.value = "margin" -- while slice[#slice].discardable do table.remove(slice, #slice) end table.insert(slice, rskip) - table.insert(slice, SILE.nodefactory.zerohbox()) + table.insert(slice, SILE.types.node.zerohbox()) local lskip = margins[LTR and "lskip" or "rskip"] - if not lskip then lskip = SILE.nodefactory.glue(0) end + if not lskip then lskip = SILE.types.node.glue(0) end if hangLeft and hangLeft > 0 then - lskip = SILE.nodefactory.glue({ width = lskip.width:tonumber() + hangLeft }) + lskip = SILE.types.node.glue({ width = lskip.width:tonumber() + hangLeft }) end lskip.value = "margin" while slice[1].discardable do table.remove(slice, 1) end table.insert(slice, 1, lskip) - table.insert(slice, 1, SILE.nodefactory.zerohbox()) + table.insert(slice, 1, SILE.types.node.zerohbox()) end function typesetter:breakpointsToLines (breakpoints) @@ -800,12 +1128,34 @@ function typesetter:breakpointsToLines (breakpoints) if point.position ~= 0 then local slice = {} local seenNonDiscardable = false + local seenLiner = false + local lastContentNodeIndex + for j = linestart, point.position do - slice[#slice+1] = nodes[j] - if nodes[j] then - if not nodes[j].discardable then + local currentNode = nodes[j] + if not currentNode.discardable + and not (currentNode.is_glue and not currentNode.explicit) + and not currentNode.is_zero then + -- actual visible content starts here + lastContentNodeIndex = #slice + 1 + end + if not seenLiner and lastContentNodeIndex then + -- Any stacked liner (unclosed from a previous line) is reopened on + -- the current line. + seenLiner = self:_repeatEnterLiners(slice) + lastContentNodeIndex = #slice + 1 + end + if currentNode.is_discretionary and currentNode.used then + -- This is the used (prebreak) discretionary from a previous line, + -- repeated. Replace it with a clone, changed to a postbreak. + currentNode = currentNode:cloneAsPostbreak() + end + slice[#slice+1] = currentNode + if currentNode then + if not currentNode.discardable then seenNonDiscardable = true end + seenLiner = self:_processIfLiner(currentNode) or seenLiner end end if not seenNonDiscardable then @@ -813,20 +1163,33 @@ function typesetter:breakpointsToLines (breakpoints) SU.debug("typesetter", "Skipping a line containing only discardable nodes") linestart = point.position + 1 else - -- If the line ends with a discretionary, repeat it on the next line, - -- so as to account for a potential postbreak. if slice[#slice].is_discretionary then + -- The line ends, with a discretionary: + -- repeat it on the next line, so as to account for a potential postbreak. linestart = point.position + -- And mark it as used as prebreak for now. + slice[#slice]:markAsPrebreak() else linestart = point.position + 1 end + -- Any unclosed liner is closed on the next line in reverse order. + if lastContentNodeIndex then + self:_repeatLeaveLiners(slice, lastContentNodeIndex + 1) + end + -- Then only we can add some extra margin glue... local mrg = self:getMargins() self:addrlskip(slice, mrg, point.left, point.right) -- And compute the line... local ratio = self:computeLineRatio(point.width, slice) + + -- Re-shuffle liners, if any, into their own boxes. + if seenLiner then + slice = self:_reboxLiners(slice) + end + local thisLine = { ratio = ratio, nodes = slice } lines[#lines+1] = thisLine end @@ -841,49 +1204,31 @@ function typesetter:breakpointsToLines (breakpoints) end function typesetter.computeLineRatio (_, breakwidth, slice) - -- This somewhat wrong, see #1362 and #1528 - -- This is a somewhat partial workaround, at least made consistent with - -- the nnode and discretionary outputYourself routines - -- (which are somewhat wrong too, or to put it otherwise, the whole - -- logic here, marking nodes without removing/replacing them, likely makes - -- things more complex than they should). - -- TODO Possibly consider a full rewrite/refactor. - local naturalTotals = SILE.length() - - -- From the line end, check if the line is hyphenated (to account for a prebreak) - -- or contains extraneous glues (e.g. to account for spaces to ignore). - local n = #slice - while n > 1 do - if slice[n].is_glue or slice[n].is_zero then - -- Skip margin glues (they'll be accounted for in the loop below) and - -- zero boxes, so as to reach actual content... - if slice[n].value ~= "margin" then - -- ... but any other glue than a margin, at the end of a line, is actually - -- extraneous. It will however also be accounted for below, so subtract - -- them to cancel their width. Typically, if a line break occurred at - -- a space, the latter is then at the end of the line now, and must be - -- ignored. - naturalTotals:___sub(slice[n].width) + local naturalTotals = SILE.types.length() + + -- From the line end, account for the margin but skip any trailing + -- glues (spaces to ignore) and zero boxes until we reach actual content. + local npos = #slice + while npos > 1 do + if slice[npos].is_glue or slice[npos].is_zero then + if slice[npos].value == "margin" then + naturalTotals:___add(slice[npos].width) end - elseif slice[n].is_discretionary then - -- Stop as we reached an hyphenation, and account for the prebreak. - slice[n].used = true - if slice[n].parent then - slice[n].parent.hyphenated = true - end - naturalTotals:___add(slice[n]:prebreakWidth()) - slice[n].height = slice[n]:prebreakHeight() - break else - -- Stop as we reached actual content. break end - n = n - 1 + npos = npos - 1 end + -- Due to discretionaries, keep track of seen parent nodes local seenNodes = {} + -- CODE SMELL: Not sure which node types were supposed to be skipped + -- at initial positions in the line! local skipping = true - for i, node in ipairs(slice) do + + -- Until end of actual content + for i = 1, npos do + local node = slice[i] if node.is_box then skipping = false if node.parent and not node.parent.hyphenated then @@ -899,29 +1244,25 @@ function typesetter.computeLineRatio (_, breakwidth, slice) elseif node.is_discretionary then skipping = false local seen = node.parent and seenNodes[node.parent] - if not seen and not node.used then - naturalTotals:___add(node:replacementWidth():absolute()) - slice[i].height = slice[i]:replacementHeight():absolute() + if not seen then + if node.used then + if node.is_prebreak then + naturalTotals:___add(node:prebreakWidth()) + node.height = node:prebreakHeight() + else + naturalTotals:___add(node:postbreakWidth()) + node.height = node:postbreakHeight() + end + else + naturalTotals:___add(node:replacementWidth():absolute()) + node.height = node:replacementHeight():absolute() + end end elseif not skipping then naturalTotals:___add(node.width) end end - -- From the line start, skip glues and margins, and check if it then starts - -- with a used discretionary. If so, account for a postbreak. - n = 1 - while n < #slice do - if slice[n].is_discretionary and slice[n].used then - naturalTotals:___add(slice[n]:postbreakWidth()) - slice[n].height = slice[n]:postbreakHeight() - break - elseif not (slice[n].is_glue or slice[n].is_zero) then - break - end - n = n + 1 - end - local _left = breakwidth:tonumber() - naturalTotals:tonumber() local ratio = _left / naturalTotals[_left < 0 and "shrink" or "stretch"]:tonumber() ratio = math.max(ratio, -1) @@ -957,33 +1298,21 @@ function typesetter:makeHbox (content) local recentContribution = {} local migratingNodes = {} - -- HACK HBOX - -- This is from the original implementation. - -- It would be somewhat cleaner to use a temporary typesetter state - -- (pushState/popState) rather than using the current one, removing - -- the processed nodes from it afterwards. However, as long - -- as leaving horizontal mode is not strictly forbidden here, it would - -- lead to a possibly different result (the output queue being skipped). - -- See also HACK HBOX comment in typesetter:leaveHmode(). - local index = #(self.state.nodes)+1 + self:pushState() self.state.hmodeOnly = true SILE.process(content) - self.state.hmodeOnly = false -- Wouldn't be needed in a temporary state - local l = SILE.length() - local h, d = SILE.length(), SILE.length() - for i = index, #(self.state.nodes) do - local node = self.state.nodes[i] + -- We must do a first pass for shaping the nnodes: + -- This is also where italic correction may occur. + local nodes = self:shapeAllNodes(self.state.nodes, false) + + -- Then we can process and measure the nodes. + local l = SILE.types.length() + local h, d = SILE.types.length(), SILE.types.length() + for i = 1, #nodes do + local node = nodes[i] if node.is_migrating then migratingNodes[#migratingNodes+1] = node - elseif node.is_unshaped then - local shape = node:shape() - for _, attr in ipairs(shape) do - recentContribution[#recentContribution+1] = attr - h = attr.height > h and attr.height or h - d = attr.depth > d and attr.depth or d - l = l + attr:lineContribution():absolute() - end elseif node.is_discretionary then -- HACK https://github.com/sile-typesetter/sile/issues/583 -- Discretionary nodes have a null line contribution... @@ -1007,10 +1336,10 @@ function typesetter:makeHbox (content) h = node.height > h and node.height or h d = node.depth > d and node.depth or d end - self.state.nodes[i] = nil -- wouldn't be needed in a temporary state end + self:popState() - local hbox = SILE.nodefactory.hbox({ + local hbox = SILE.types.node.hbox({ height = h, width = l, depth = d, @@ -1020,16 +1349,18 @@ function typesetter:makeHbox (content) local ox = atypesetter.frame.state.cursorX local oy = atypesetter.frame.state.cursorY SILE.outputter:setCursor(atypesetter.frame.state.cursorX, atypesetter.frame.state.cursorY) + SU.debug("hboxes", function () + -- setCursor is also invoked by the internal (wrapped) hboxes etc. + -- so we must show our debug box before outputting its content. + SILE.outputter:debugHbox(box, box:scaledWidth(line)) + return "Drew debug outline around hbox" + end) for _, node in ipairs(box.value) do node:outputYourself(atypesetter, line) end atypesetter.frame.state.cursorX = ox atypesetter.frame.state.cursorY = oy _post() - SU.debug("hboxes", function () - SILE.outputter:debugHbox(box, box:scaledWidth(line)) - return "Drew debug outline around hbox" - end) end }) return hbox, migratingNodes @@ -1041,4 +1372,38 @@ function typesetter:pushHlist (hlist) end end +--- A liner is a construct that may span multiple lines. +-- This is the user-facing method for creating such liners in packages. +-- The content may be line-broken, and each bit on each line will be wrapped +-- into a box. +-- These boxes will be formatted according to some output logic. +-- The output method has the same signature as the outputYourself method +-- of a box, and is responsible for outputting the liner inner content with the +-- outputContent(typesetter, line) method, possibly surrounded by some additional +-- effects. +-- If we are already in horizontal-restricted mode, the liner is processed +-- immediately, since line breaking won't occur then. +-- @tparam string name Name of the liner (usefull for debugging) +-- @tparam table content SILE AST to process +-- @tparam function outputYourself Output method for wrapped boxes +function typesetter:liner (name, content, outputYourself) + if self.state.hmodeOnly then + SU.debug("typesetter.liner", "Applying liner in horizontal-restricted mode") + local hbox, hlist = self:makeHbox(content) + local lbox = linerBox(name, outputYourself) + lbox:append(hbox) + self:pushHorizontal(lbox) + self:pushHlist(hlist) + else + self.state.linerCount = (self.state.linerCount or 0) + 1 + local uname = name .. "_" .. self.state.linerCount + SU.debug("typesetter.liner", "Applying liner in standard mode") + local enter = linerEnterNode(uname, outputYourself) + local leave = linerLeaveNode(uname) + self:pushHorizontal(enter) + SILE.process(content) + self:pushHorizontal(leave) + end +end + return typesetter diff --git a/typesetters/firstfit.lua b/typesetters/firstfit.lua index 0eae7af57a..62fc7a3822 100644 --- a/typesetters/firstfit.lua +++ b/typesetters/firstfit.lua @@ -5,24 +5,24 @@ typesetter._name = "firstfit" function typesetter:breakIntoLines (nl, breakWidth) local breaks = {} - local length = SILE.length() + local length = SILE.types.length() for i = 1,#nl do local n = nl[i] if n.is_box then - SU.debug("break", n .. " " .. tostring(n:lineContribution())) + SU.debug("break", n, function () return n:lineContribution() end) length = length + n:lineContribution() - SU.debug("break", " Length now " .. tostring(length) .. " breakwidth ".. tostring(breakWidth)) + SU.debug("break", " Length now ", length, "breakwidth", breakWidth) end if not n.is_box or n.isHangable then SU.debug("break", n ) if n.is_glue then length = length + n.width:absolute() end - SU.debug("break", " Length now " .. tostring(length) .. " breakwidth " .. tostring(breakWidth)) + SU.debug("break", " Length now ", length, " breakwidth ", breakWidth) -- Can we break? if length:tonumber() >= breakWidth:tonumber() then SU.debug("break", "Breaking!") breaks[#breaks+1] = { position = i, width = breakWidth} - length = SILE.length() + length = SILE.types.length() end end end diff --git a/typesetters/grid.lua b/typesetters/grid.lua index 6e6efbf44b..6683c79f73 100644 --- a/typesetters/grid.lua +++ b/typesetters/grid.lua @@ -4,25 +4,25 @@ local typesetter = pl.class(base) typesetter._name = "grid" local function makeUp (spacing, totals) - local toadd = (spacing - SILE.measurement(totals.gridCursor)) % spacing + local toadd = (spacing - SILE.types.measurement(totals.gridCursor)) % spacing totals.gridCursor = totals.gridCursor + toadd SU.debug("typesetter", "Makeup height =", toadd) - return SILE.nodefactory.vglue({ discardable = false, gridleading = true, height = toadd }) + return SILE.types.node.vglue({ discardable = false, gridleading = true, height = toadd }) end function typesetter:_init(frame) base._init(self, frame) - self.options = { spacing = SILE.measurement("1bs") } + self.options = { spacing = SILE.types.measurement("1bs") } end function typesetter:leadingFor (vbox, previous) SU.debug("typesetter", " Considering leading between two lines (grid mode):") SU.debug("typesetter", " 1)", previous) SU.debug("typesetter", " 2)", vbox) - if not previous then return SILE.nodefactory.vglue() end + if not previous then return SILE.types.node.vglue() end SU.debug("typesetter", " Depth of previous line was", previous.depth) local totals = self.frame.state.totals - local oldCursor = SILE.measurement(totals.gridCursor) + local oldCursor = SILE.types.measurement(totals.gridCursor) totals.gridCursor = oldCursor + vbox.height:absolute() + previous.depth SU.debug("typesetter", " Cursor change =", totals.gridCursor - oldCursor) return makeUp(self.options.spacing, self.frame.state.totals) @@ -30,11 +30,11 @@ end function typesetter:pushVglue (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "vglue" and spec or SILE.nodefactory.vglue(spec) - node.height.stretch = SILE.measurement() - node.height.shrink = SILE.measurement() + local node = SU.type(spec) == "vglue" and spec or SILE.types.node.vglue(spec) + node.height.stretch = SILE.types.measurement() + node.height.shrink = SILE.types.measurement() local totals = self.frame.state.totals - totals.gridCursor = totals.gridCursor + SILE.measurement(node.height):absolute() + totals.gridCursor = totals.gridCursor + SILE.types.measurement(node.height):absolute() self:pushVertical(node) self:pushVertical(makeUp(self.options.spacing, self.frame.state.totals)) return node @@ -42,13 +42,13 @@ end function typesetter:pushExplicitVglue (spec) -- if SU.type(spec) ~= "table" then SU.warn("Please use pushVertical() to pass a premade node instead of a spec") end - local node = SU.type(spec) == "vglue" and spec or SILE.nodefactory.vglue(spec) + local node = SU.type(spec) == "vglue" and spec or SILE.types.node.vglue(spec) node.explicit = true node.discardable = false - node.height.stretch = SILE.measurement() - node.height.shrink = SILE.measurement() + node.height.stretch = SILE.types.measurement() + node.height.shrink = SILE.types.measurement() local totals = self.frame.state.totals - totals.gridCursor = totals.gridCursor + SILE.measurement(node.height):absolute() + totals.gridCursor = totals.gridCursor + SILE.types.measurement(node.height):absolute() self:pushVertical(node) self:pushVertical(makeUp(self.options.spacing, self.frame.state.totals)) return node diff --git a/typesetters/tate.lua b/typesetters/tate.lua index a6725e3599..203c03b0ff 100644 --- a/typesetters/tate.lua +++ b/typesetters/tate.lua @@ -4,11 +4,11 @@ local typesetter = pl.class(base) typesetter._name = "tate" function typesetter.leadingFor (_, v) - v.height = SILE.length("1zw"):absolute() + v.height = SILE.types.length("1zw"):absolute() local bls = SILE.settings:get("document.baselineskip") local d = bls.height:absolute() - v.height - local len = SILE.length(d.length, bls.height.stretch, bls.height.shrink) - return SILE.nodefactory.vglue({ height = len }) + local len = SILE.types.length(d.length, bls.height.stretch, bls.height.shrink) + return SILE.types.node.vglue({ height = len }) end return typesetter