├── .editorconfig ├── .gitattributes ├── .github ├── cross-linux-riscv64 │ ├── Dockerfile │ ├── build_openssl.sh │ └── install_docker.sh └── workflows │ ├── ci.yaml │ ├── docker.yaml │ ├── gh_pages.yml │ ├── lint_pr_title.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── .prettierrc ├── CHANGELOG.md ├── CHANGELOG ├── CHANGELOG-0.1.md ├── CHANGELOG-0.2.md ├── CHANGELOG-0.3.md └── README.md ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE ├── README.md ├── assets └── fonts │ └── .gitignore ├── cli ├── Cargo.toml ├── build.rs └── src │ ├── debug_loc.rs │ ├── diag.rs │ ├── error.rs │ ├── lib.rs │ ├── main.rs │ ├── meta.rs │ ├── outline.rs │ ├── project.rs │ ├── render │ ├── html.rs │ ├── mod.rs │ ├── search.rs │ └── typst.rs │ ├── theme.rs │ ├── tui.rs │ ├── utils.rs │ └── version.rs ├── contrib └── typst │ ├── gh-ebook.typ │ ├── gh-pages.typ │ ├── theme-style.toml │ ├── tidy-book │ └── lib.typ │ └── tokyo-night.tmTheme ├── dist-workspace.toml ├── frontend ├── .gitignore ├── package.json ├── src │ ├── global.d.ts │ └── main.ts ├── tsconfig.json └── vite.config.mjs ├── github-pages ├── .gitignore └── docs │ ├── book.typ │ ├── cli │ ├── build.typ │ ├── clean.typ │ ├── completions.typ │ ├── init.typ │ ├── main.typ │ └── serve.typ │ ├── format │ ├── book-meta.typ │ ├── book.typ │ ├── build-meta.typ │ ├── main.typ │ └── theme.typ │ ├── guide │ ├── faq.typ │ ├── get-started.typ │ └── installation.typ │ ├── introduction.typ │ ├── pdf.typ │ ├── supports.typ │ └── supports │ ├── cross-ref-sample.typ │ ├── cross-ref.typ │ ├── embed-html.typ │ ├── multimedia.typ │ ├── render-test.typ │ └── sema-desc.typ ├── package.json ├── packages ├── shiroa-tests │ ├── example-shiroa-docs.typ │ ├── fixtures │ │ └── plain-text │ │ │ ├── image.svg │ │ │ └── syntax.typ │ ├── main.typ │ ├── test-no-bad-import.typ │ ├── test-plain-text.typ │ └── test-summary.typ └── shiroa │ ├── LICENSE │ ├── README.md │ ├── lib.typ │ ├── media.typ │ ├── meta-and-state.typ │ ├── summary-internal.typ │ ├── summary.typ │ ├── supports-link.typ │ ├── supports-text.typ │ ├── sys.typ │ ├── template-link.typ │ ├── template-theme.typ │ ├── templates.typ │ ├── typst.toml │ ├── utils.typ │ └── xcommand.typ ├── refs ├── .gitignore └── paged │ └── example-shiroa-docs.hash ├── scripts └── draft-release.mjs ├── tests └── .gitignore ├── themes └── mdbook │ ├── FontAwesome │ ├── css │ │ └── font-awesome.css │ └── fonts │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── LICENSE │ ├── README.md │ ├── css │ ├── chrome.css │ ├── general.css │ ├── print.css │ └── variables.css │ ├── head.hbs │ ├── header.hbs │ ├── index.hbs │ ├── index.js │ ├── typst-load-html-trampoline.hbs │ └── typst-load-trampoline.hbs ├── tools └── build-from-source │ ├── Cargo.toml │ ├── dist.toml │ └── src │ └── main.rs ├── tsconfig.json └── yarn.lock /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.ts] 12 | quote_type = single 13 | 14 | [*.md] 15 | max_line_length = off 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/cross-linux-riscv64/Dockerfile: -------------------------------------------------------------------------------- 1 | # https://github.com/wasmerio/wasmer/blob/f9ce24d504c7a14f27663741c235d80da60b1830/.github/cross-linux-riscv64/Dockerfile 2 | FROM debian:bookworm AS openssl_riscv64 3 | #FROM ghcr.io/cross-rs/riscv64gc-unknown-linux-gnu:edge AS openssl_riscv64 4 | 5 | # set CROSS_DOCKER_IN_DOCKER to inform `cross` that it is executed from within a container 6 | ENV CROSS_DOCKER_IN_DOCKER=true 7 | 8 | RUN apt-get update && \ 9 | apt-get install --assume-yes --no-install-recommends \ 10 | ca-certificates \ 11 | curl \ 12 | git \ 13 | cpio \ 14 | sharutils \ 15 | gnupg \ 16 | build-essential \ 17 | libc6-dev 18 | 19 | # Configure git 20 | RUN git config --global --add safe.directory /project 21 | 22 | # install rust tools 23 | RUN curl --proto "=https" --tlsv1.2 --retry 3 -sSfL https://sh.rustup.rs | sh -s -- -y 24 | ENV PATH="/root/.cargo/bin:${PATH}" 25 | RUN rustup -v toolchain install 1.70 26 | # add docker the manual way 27 | COPY install_docker.sh / 28 | RUN chmod +x /install_docker.sh 29 | RUN /install_docker.sh 30 | 31 | RUN apt-get update && \ 32 | apt-get install --assume-yes --no-install-recommends \ 33 | docker-ce \ 34 | docker-ce-cli \ 35 | containerd.io \ 36 | docker-buildx-plugin \ 37 | docker-compose-plugin 38 | 39 | RUN apt-get update && apt-get install -y --no-install-recommends \ 40 | gcc-riscv64-linux-gnu \ 41 | g++-riscv64-linux-gnu \ 42 | qemu-user-static \ 43 | libssl-dev \ 44 | pkg-config \ 45 | libc6-dev-riscv64-cross 46 | 47 | ENV CROSS_TOOLCHAIN_PREFIX=riscv64-linux-gnu- 48 | ENV CROSS_SYSROOT=/usr/riscv64-linux-gnu 49 | ENV CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER="$CROSS_TOOLCHAIN_PREFIX"gcc \ 50 | AR_riscv64gc_unknown_linux_gnu="$CROSS_TOOLCHAIN_PREFIX"ar \ 51 | CC_riscv64gc_unknown_linux_gnu="$CROSS_TOOLCHAIN_PREFIX"gcc \ 52 | CXX_riscv64gc_unknown_linux_gnu="$CROSS_TOOLCHAIN_PREFIX"g++ \ 53 | CFLAGS_riscv64gc_unknown_linux_gnu="-march=rv64gc -mabi=lp64d" \ 54 | BINDGEN_EXTRA_CLANG_ARGS_riscv64gc_unknown_linux_gnu="--sysroot=$CROSS_SYSROOT" \ 55 | QEMU_LD_PREFIX="$CROSS_SYSROOT" \ 56 | RUST_TEST_THREADS=1 \ 57 | PKG_CONFIG_PATH="/usr/lib/riscv64-linux-gnu/pkgconfig/:${PKG_CONFIG_PATH}" 58 | 59 | RUN rustup target add riscv64gc-unknown-linux-gnu --toolchain 1.70-x86_64-unknown-linux-gnu 60 | RUN rustup target add riscv64gc-unknown-linux-gnu 61 | 62 | #compile libssl-dev for riscv64! 63 | COPY build_openssl.sh / 64 | RUN chmod +x /build_openssl.sh 65 | RUN /build_openssl.sh 66 | ENV RISCV64GC_UNKNOWN_LINUX_GNU_OPENSSL_INCLUDE_DIR=/openssl_riscv64/include 67 | ENV RISCV64GC_UNKNOWN_LINUX_GNU_OPENSSL_LIB_DIR=/openssl_riscv64/lib 68 | -------------------------------------------------------------------------------- /.github/cross-linux-riscv64/build_openssl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -euo pipefail 4 | 5 | apt-get install wget 6 | 7 | wget https://www.openssl.org/source/openssl-3.1.1.tar.gz 8 | 9 | tar xf openssl-3.1.1.tar.gz 10 | rm openssl-3.1.1.tar.gz 11 | cd openssl-3.1.1 12 | 13 | AR=riscv64-linux-gnu-ar NM=riscv64-linux-gnu-nm CC=riscv64-linux-gnu-gcc \ 14 | ./Configure -static no-asm no-tests --prefix=/openssl_riscv64 linux64-riscv64 15 | 16 | make -j 17 | make install 18 | -------------------------------------------------------------------------------- /.github/cross-linux-riscv64/install_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -x 3 | set -euo pipefail 4 | 5 | mkdir -m 0755 -p /etc/apt/keyrings 6 | curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg 7 | 8 | echo \ 9 | "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \ 10 | "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | \ 11 | tee /etc/apt/sources.list.d/docker.list > /dev/null 12 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: shiroa::ci 2 | on: [push, pull_request] 3 | 4 | env: 5 | RUSTFLAGS: '-Dwarnings' 6 | 7 | jobs: 8 | ci: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Download Repo 12 | uses: actions/checkout@v4 13 | with: 14 | submodules: recursive 15 | - name: Download font assets 16 | run: | 17 | mkdir -p assets/fonts 18 | curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.0/charter-font-assets.tar.gz | tar -xvz -C assets/fonts/ 19 | curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.5/source-han-serif-font-assets.tar.gz | tar -xvz -C assets/fonts/ 20 | - name: Install Tinymist 21 | env: 22 | TINYMIST_VERRION: v0.13.10-rc1 23 | run: curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Myriad-Dreamin/tinymist/releases/download/${TINYMIST_VERRION}/tinymist-installer.sh | sh 24 | - name: Install Rust 25 | uses: dtolnay/rust-toolchain@stable 26 | - name: Install Artifact Cache 27 | uses: Swatinem/rust-cache@v2 28 | - name: Fmt check 29 | run: cargo fmt --check --all 30 | - name: Clippy check 31 | run: cargo clippy --workspace --all-targets 32 | - name: Documentation check 33 | run: cargo doc --workspace --no-deps 34 | - name: Build 35 | run: | 36 | cargo build --release --workspace 37 | - name: Build Book 38 | run: | 39 | cargo run --release --bin shiroa -- build --path-to-root /shiroa/ -w . github-pages/docs 40 | - name: Build Book (HTML Target) 41 | run: | 42 | cargo run --release --bin shiroa -- build --path-to-root /shiroa/ -w . github-pages/docs --mode static-html 43 | - name: Test 44 | run: cargo test --workspace --no-fail-fast 45 | - name: Test 46 | run: tinymist test packages/shiroa-tests/main.typ --root . --ignore-system-fonts --font-path ./assets/fonts/ --coverage --print-coverage=full 47 | -------------------------------------------------------------------------------- /.github/workflows/docker.yaml: -------------------------------------------------------------------------------- 1 | name: shiroa::release::docker 2 | on: 3 | release: 4 | types: [created] 5 | workflow_dispatch: 6 | 7 | env: 8 | REGISTRY: ghcr.io 9 | IMAGE_NAME: ${{ github.repository }} 10 | PLATFORMS: linux/amd64,linux/arm64 11 | 12 | jobs: 13 | build-and-publish: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | contents: read 17 | packages: write 18 | 19 | steps: 20 | - name: Checkout repository 21 | uses: actions/checkout@v3 22 | with: 23 | submodules: recursive 24 | 25 | - name: Setup Docker buildx 26 | uses: docker/setup-buildx-action@v2.5.0 27 | with: 28 | platforms: ${{ env.PLATFORMS }} 29 | 30 | - name: Log into registry ${{ env.REGISTRY }} 31 | uses: docker/login-action@v2.1.0 32 | with: 33 | registry: ${{ env.REGISTRY }} 34 | username: ${{ github.actor }} 35 | password: ${{ secrets.GITHUB_TOKEN }} 36 | 37 | - name: Extract Docker metadata 38 | id: meta 39 | uses: docker/metadata-action@v4.3.0 40 | with: 41 | images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} 42 | 43 | # https://stackoverflow.com/questions/71157844/how-can-i-copy-git-directory-to-the-container-with-github-actions 44 | - name: Build and push Docker image 45 | id: build-and-push 46 | uses: docker/build-push-action@v4.0.0 47 | with: 48 | push: true 49 | context: . 50 | tags: ${{ steps.meta.outputs.tags }} 51 | labels: ${{ steps.meta.outputs.labels }} 52 | platforms: ${{ env.PLATFORMS }} 53 | cache-from: type=gha 54 | cache-to: type=gha,mode=max 55 | -------------------------------------------------------------------------------- /.github/workflows/gh_pages.yml: -------------------------------------------------------------------------------- 1 | name: shiroa::gh_pages 2 | 3 | on: 4 | # Runs on pushes targeting the default branch 5 | push: 6 | branches: ['main'] 7 | 8 | # Allows you to run this workflow manually from the Actions tab 9 | workflow_dispatch: 10 | 11 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 12 | permissions: 13 | pages: write 14 | id-token: write 15 | contents: read 16 | 17 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 18 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 19 | concurrency: 20 | group: 'pages' 21 | cancel-in-progress: false 22 | 23 | jobs: 24 | build-gh-pages: 25 | runs-on: ubuntu-latest 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | with: 33 | submodules: recursive 34 | - name: Download font assets 35 | run: | 36 | mkdir -p assets/fonts 37 | curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.0/charter-font-assets.tar.gz | tar -xvz -C assets/fonts/ 38 | curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.5/source-han-serif-font-assets.tar.gz | tar -xvz -C assets/fonts/ 39 | - name: Install Rust 40 | uses: dtolnay/rust-toolchain@stable 41 | - name: Set Node.js 22.x 42 | uses: actions/setup-node@v3 43 | with: 44 | node-version: 22.x 45 | - name: Install Artifact Cache 46 | uses: Swatinem/rust-cache@v2 47 | - name: Build Book (Paged Target) 48 | run: | 49 | cargo run --release --bin shiroa -- build --font-path assets/fonts --path-to-root /shiroa/paged/ -w . github-pages/docs 50 | mv ./github-pages/dist/ ./github-pages/dist-paged/ 51 | mkdir -p ./github-pages/dist/ 52 | mv ./github-pages/dist-paged ./github-pages/dist/paged 53 | - name: Build Book 54 | run: | 55 | cargo run --release --bin shiroa -- build --font-path assets/fonts --path-to-root /shiroa/ -w . github-pages/docs --mode static-html 56 | - name: Setup Pages 57 | uses: actions/configure-pages@v5 58 | - name: Upload artifact 59 | uses: actions/upload-pages-artifact@v3 60 | with: 61 | # Upload `/github-pages` sub directory 62 | path: './github-pages/dist' 63 | - name: Deploy to GitHub Pages 64 | id: deployment 65 | uses: actions/deploy-pages@v4 66 | -------------------------------------------------------------------------------- /.github/workflows/lint_pr_title.yml: -------------------------------------------------------------------------------- 1 | name: shiroa::lint_pr_title 2 | on: 3 | pull_request: 4 | types: [opened, edited, synchronize] 5 | 6 | permissions: 7 | pull-requests: write 8 | 9 | jobs: 10 | main: 11 | name: Validate PR title 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: amannn/action-semantic-pull-request@v5 15 | id: lint_pr_title 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | with: 19 | # Configure which types are allowed (newline-delimited). 20 | # Default: https://github.com/commitizen/conventional-commit-types 21 | # extraType: dev: internal development 22 | types: | 23 | dev 24 | feat 25 | fix 26 | docs 27 | style 28 | refactor 29 | perf 30 | test 31 | build 32 | ci 33 | chore 34 | revert 35 | ignoreLabels: | 36 | bot 37 | ignore-semantic-pull-request 38 | - uses: marocchino/sticky-pull-request-comment@v2 39 | # When the previous steps fails, the workflow would stop. By adding this 40 | # condition you can continue the execution with the populated error message. 41 | if: always() && (steps.lint_pr_title.outputs.error_message != null) 42 | with: 43 | header: pr-title-lint-error 44 | message: | 45 | Hey there and thank you for opening this pull request! 👋🏼 46 | 47 | We require pull request titles to follow the [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/) and it looks like your proposed title needs to be adjusted. 48 | 49 | Details: 50 | 51 | ``` 52 | ${{ steps.lint_pr_title.outputs.error_message }} 53 | ``` 54 | # Delete a previous comment when the issue has been resolved 55 | - if: ${{ steps.lint_pr_title.outputs.error_message == null }} 56 | uses: marocchino/sticky-pull-request-comment@v2 57 | with: 58 | header: pr-title-lint-error 59 | delete: true 60 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_STORE 2 | .idea 3 | .vscode 4 | 5 | *.artifact.json 6 | *.pdf 7 | *.rmp 8 | *.tsbuildinfo 9 | /out.json 10 | 11 | target/ 12 | out/ 13 | node_modules/ 14 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "assets/artifacts"] 2 | path = assets/artifacts 3 | url = https://github.com/Myriad-Dreamin/typst/ 4 | branch = shiroa-v0.3.1 5 | shallow = true -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "singleQuote": true, 4 | "trailingComma": "all", 5 | "useTabs": false, 6 | "tabWidth": 2, 7 | "arrowParens": "avoid", 8 | "semi": true, 9 | "endOfLine": "auto" 10 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | [CHANGELOG/README.md](./CHANGELOG/README.md) 2 | -------------------------------------------------------------------------------- /CHANGELOG/CHANGELOG-0.1.md: -------------------------------------------------------------------------------- 1 | # v0.1.5 2 | 3 | ## Changelog since v0.1.5 4 | 5 | - Fixed documentation about building by @duskmoon314 in https://github.com/Myriad-Dreamin/typst-book/pull/67 6 | - Updated typst.ts to v0.5.0-rc4 (with canvas improvements) in https://github.com/Myriad-Dreamin/typst-book/pull/70 7 | - Supported basic theming in https://github.com/Myriad-Dreamin/typst-book/pull/71 8 | 9 | **Full Changelog**: https://github.com/Myriad-Dreamin/typst-book/compare/v0.1.4...v0.1.5 10 | 11 | # v0.1.4 12 | 13 | ## Changelog since v0.1.4 14 | 15 | **Full Changelog**: https://github.com/Myriad-Dreamin/typst-book/compare/v0.1.3...v0.1.4 16 | 17 | - (Fix) Specifying white space pre props in CSS in https://github.com/Myriad-Dreamin/typst-book/pull/54 18 | - (Fix) Improved `plain-text` by @ice1000 in https://github.com/Myriad-Dreamin/typst-book/pull/57 19 | - (Fix) Used `overflow-x: auto` by @ice1000 in https://github.com/Myriad-Dreamin/typst-book/pull/58 20 | - Performed null checking on creating labels in template in https://github.com/Myriad-Dreamin/typst-book/pull/59 21 | - You need to update your template to get this change. 22 | 23 | # v0.1.3 24 | 25 | ## Changelog since v0.1.3 26 | 27 | **Full Changelog**: https://github.com/Myriad-Dreamin/typst-book/compare/v0.1.2...v0.1.3 28 | 29 | Most efforts are internal improvements and there is no external changes since v0.1.2. 30 | 31 | - Implemented new text selection, which already works great on simple pages. 32 | - Improved performance on large pages. Note: you may still get bad performance if you set `#set page(height: auto)`, which will get improved in the future. 33 | 34 | # v0.1.2 35 | 36 | ## Changelog since v0.1.2 37 | 38 | **Full Changelog**: https://github.com/Myriad-Dreamin/typst-book/compare/v0.1.1...v0.1.2 39 | 40 | ## Feature 41 | 42 | - feat: automatically assign section number in https://github.com/Myriad-Dreamin/typst-book/pull/37 43 | - dev: enable ligature feature in https://github.com/Myriad-Dreamin/typst-book/pull/38 44 | - scripting: cross link support in https://github.com/Myriad-typstbook/pull/41 45 | - scripting: support semantic link jump in https://github.mMyriad-Dreamin/typst-book/pull/42 46 | 47 | ## Enhancement 48 | 49 | - theme: override target="\_blank" behavior in https://github.com/Myriad-Dreamin/typst-book/pull/27 and https://github.com/Myriad-Dreamin/typst-book/pull/28 50 | - scripting: improve plain text conversion in https://github.com/Myriad-Dreamin/typst-book/pull/39 51 | - This is used by conversion of typst title contents 52 | - scripting: don't justify code block in https://github.com/Myriad-Dreamin/typst-book/pull/40 53 | - You can update your template like it. 54 | - build: upgrade typst.ts to 0.4.1 in https://github.com/Myriad-Dreamin/typst-book/pull/36 55 | - It brings text selection enhancement 56 | 57 | # v0.1.1 58 | 59 | ## Changelog since v0.1.1 60 | 61 | **Full Changelog**: https://github.com/Myriad-Dreamin/typst-book/compare/v0.1.0...v0.1.1 62 | 63 | ## Enhancement 64 | 65 | - cli: correctly evict compilation cache in https://github.com/Myriad-Dreamin/typst-book/commit/149446ab63dd9ea628b1d30bc5eed7cac1582b62 66 | - this reduces memory usage slightly. 67 | 68 | ## Feature 69 | 70 | - theme: sidebar improvement in https://github.com/Myriad-Dreamin/typst-book/commit/dfff00639142d881cd11a8ae2da379aa08505b0b and https://github.com/Myriad-Dreamin/typst-book/commit/313c11d37df679670426e85c05431b687aa71056 71 | 72 | - theme: scrollbar improvement in https://github.com/Myriad-Dreamin/typst-book/commit/e274777809a6fc469f4b84509cf5522d94bc9daf 73 | 74 | - theme: support dark themes and more by @seven-mile in https://github.com/Myriad-Dreamin/typst-book/pull/23 75 | 76 | - typesetting: add repository-edit template in https://github.com/Myriad-Dreamin/typst-book/commit/9f1260c0706954faeb7bb90388143cdbf11185ab 77 | 78 | - cli: add version command in https://github.com/Myriad-Dreamin/typst-book/pull/25 79 | 80 | # v0.1.0 81 | 82 | Initial release 83 | -------------------------------------------------------------------------------- /CHANGELOG/CHANGELOG-0.2.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "shiroa" will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | - Bumped `@preview/shiroa` package to 0.1.2 by in https://github.com/Myriad-Dreamin/shiroa/pull/88 8 | 9 | ## v0.2.0 - [2024-12-29] 10 | 11 | ### Typst Scripting 12 | 13 | - (Break Change) use sys.args to control layout by in https://github.com/Myriad-Dreamin/shiroa/pull/77 14 | - (Break Change) Renamed the repository to shiroa (for publish) by in https://github.com/Myriad-Dreamin/shiroa/pull/73 15 | - Added multimedia support with sanitization by in https://github.com/Myriad-Dreamin/shiroa/pull/78 16 | - Continuing numbering sections if user changes it by in https://github.com/Myriad-Dreamin/shiroa/pull/84 17 | 18 | ### CLI Tool 19 | 20 | - Generating description metadata for SEO by in https://github.com/Myriad-Dreamin/shiroa/pull/74 and https://github.com/Myriad-Dreamin/shiroa/pull/76 21 | - Added support to infer book meta from outline by in https://github.com/Myriad-Dreamin/shiroa/pull/75 22 | 23 | ### Misc 24 | 25 | - Corrected a typo in error message by @FlorentCLMichel in https://github.com/Myriad-Dreamin/shiroa/pull/81 26 | 27 | **Full Changelog**: https://github.com/Myriad-Dreamin/shiroa/compare/v0.1.5...v0.2.0 28 | -------------------------------------------------------------------------------- /CHANGELOG/CHANGELOG-0.3.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to the "shiroa" will be documented in this file. 4 | 5 | Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how to structure this file. 6 | 7 | ## v0.3.1 - [2025-04-25] 8 | 9 | The diff between v0.3.1-rc1 and v0.3.1-rc2 is rendered at the [section](#diff-v031-rc1v031-rc2). 10 | 11 | - Bumped shiroa package to v0.2.3 in https://github.com/Myriad-Dreamin/shiroa/pull/131 and https://github.com/Myriad-Dreamin/shiroa/pull/141 12 | - Bumped zebraw to v0.5.2 in https://github.com/Myriad-Dreamin/shiroa/pull/139 13 | 14 | ### Package 15 | 16 | - (Fix) Corrected show rule for equations in https://github.com/Myriad-Dreamin/shiroa/pull/120 17 | - (Fix) Strengthened `plain-text` implementation in https://github.com/Myriad-Dreamin/shiroa/pull/122 and https://github.com/Myriad-Dreamin/shiroa/pull/138 18 | - Set up package tests in https://github.com/Myriad-Dreamin/shiroa/pull/121 19 | - Supported `cross-link` in HTML export in https://github.com/Myriad-Dreamin/shiroa/pull/130 20 | 21 | ### CLI 22 | 23 | - Pretty printing compilation in https://github.com/Myriad-Dreamin/shiroa/pull/124 24 | - Ignoring the infamous foreign object warning in https://github.com/Myriad-Dreamin/shiroa/pull/125 25 | - Fully parallelized compilation in https://github.com/Myriad-Dreamin/shiroa/pull/126 26 | - Watching compilation when serving in https://github.com/Myriad-Dreamin/shiroa/pull/127 27 | 28 | ### Misc 29 | 30 | - Updated README.md and installation.typ by @HoiGe in https://github.com/Myriad-Dreamin/shiroa/pull/119 31 | - Ensured wasm module matches frontend build by @kxxt in https://github.com/Myriad-Dreamin/shiroa/pull/101 32 | - Update doucmentation about support section and todo list in https://github.com/Myriad-Dreamin/shiroa/pull/137 33 | 34 | ### Diff v0.3.1-rc1...v0.3.1-rc2 35 | 36 | ```diff 37 | -## v0.3.1 - [2025-03-30] 38 | +## v0.3.1 - [2025-04-25] 39 | 40 | -- Bumped shiroa package to v0.2.2 in https://github.com/Myriad-Dreamin/shiroa/pull/131 41 | +- Bumped shiroa package to v0.2.3 in https://github.com/Myriad-Dreamin/shiroa/pull/131 and https://github.com/Myriad-Dreamin/shiroa/pull/141 42 | +- Bumped zebraw to v0.5.2 in https://github.com/Myriad-Dreamin/shiroa/pull/139 43 | 44 | ### Package 45 | 46 | - (Fix) Corrected show rule for equations in https://github.com/Myriad-Dreamin/shiroa/pull/120 47 | -- (Fix) Strengthened `plain-text` implementation in https://github.com/Myriad-Dreamin/shiroa/pull/122 48 | +- (Fix) Strengthened `plain-text` implementation in https://github.com/Myriad-Dreamin/shiroa/pull/122 and https://github.com/Myriad-Dreamin/shiroa/pull/138 49 | - Set up package tests in https://github.com/Myriad-Dreamin/shiroa/pull/121 50 | - Supported `cross-link` in HTML export in https://github.com/Myriad-Dreamin/shiroa/pull/130 51 | 52 | @@ -26,6 +27,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how 53 | 54 | - Updated README.md and installation.typ by @HoiGe in https://github.com/Myriad-Dreamin/shiroa/pull/119 55 | - Ensured wasm module matches frontend build by @kxxt in https://github.com/Myriad-Dreamin/shiroa/pull/101 56 | +- Update doucmentation about support section and todo list in https://github.com/Myriad-Dreamin/shiroa/pull/137 57 | 58 | **Full Changelog**: https://github.com/Myriad-Dreamin/shiroa/compare/v0.3.0...v0.3.1 59 | ``` 60 | 61 | **Full Changelog**: https://github.com/Myriad-Dreamin/shiroa/compare/v0.3.0...v0.3.1 62 | 63 | ## v0.3.0 - [2025-03-05] 64 | 65 | - Bumped `@preview/shiroa` package to 0.2.0 in https://github.com/Myriad-Dreamin/shiroa/pull/103 66 | - Bumped typst to v0.13.0 in https://github.com/Myriad-Dreamin/shiroa/pull/97 and https://github.com/Myriad-Dreamin/shiroa/pull/103 67 | 68 | ### CLI Tool 69 | 70 | - Added cross-page search component in https://github.com/Myriad-Dreamin/shiroa/pull/99 71 | - Rendering pages into static html using HTML export with `--mode=static-html` in https://github.com/Myriad-Dreamin/shiroa/pull/103 and https://github.com/Myriad-Dreamin/shiroa/pull/105 72 | 73 | ### Diff v0.3.1-rc1...v0.3.1-rc2 74 | 75 | ```diff 76 | -## v0.3.1 - [2025-03-30] 77 | +## v0.3.1 - [2025-04-25] 78 | 79 | -- Bumped shiroa package to v0.2.2 in https://github.com/Myriad-Dreamin/shiroa/pull/131 80 | +- Bumped shiroa package to v0.2.3 in https://github.com/Myriad-Dreamin/shiroa/pull/131 and https://github.com/Myriad-Dreamin/shiroa/pull/141 81 | +- Bumped zebraw to v0.5.2 in https://github.com/Myriad-Dreamin/shiroa/pull/139 82 | 83 | ### Package 84 | 85 | - (Fix) Corrected show rule for equations in https://github.com/Myriad-Dreamin/shiroa/pull/120 86 | -- (Fix) Strengthened `plain-text` implementation in https://github.com/Myriad-Dreamin/shiroa/pull/122 87 | +- (Fix) Strengthened `plain-text` implementation in https://github.com/Myriad-Dreamin/shiroa/pull/122 and https://github.com/Myriad-Dreamin/shiroa/pull/138 88 | - Set up package tests in https://github.com/Myriad-Dreamin/shiroa/pull/121 89 | - Supported `cross-link` in HTML export in https://github.com/Myriad-Dreamin/shiroa/pull/130 90 | 91 | @@ -26,6 +27,7 @@ Check [Keep a Changelog](http://keepachangelog.com/) for recommendations on how 92 | 93 | - Updated README.md and installation.typ by @HoiGe in https://github.com/Myriad-Dreamin/shiroa/pull/119 94 | - Ensured wasm module matches frontend build by @kxxt in https://github.com/Myriad-Dreamin/shiroa/pull/101 95 | +- Update doucmentation about support section and todo list in https://github.com/Myriad-Dreamin/shiroa/pull/137 96 | 97 | **Full Changelog**: https://github.com/Myriad-Dreamin/shiroa/compare/v0.3.0...v0.3.1 98 | ``` 99 | 100 | **Full Changelog**: https://github.com/Myriad-Dreamin/shiroa/compare/v0.2.0...v0.3.0 101 | -------------------------------------------------------------------------------- /CHANGELOG/README.md: -------------------------------------------------------------------------------- 1 | # CHANGELOGs 2 | 3 | - [CHANGELOG-0.3.md](./CHANGELOG-0.3.md) 4 | - [CHANGELOG-0.2.md](./CHANGELOG-0.2.md) 5 | - [CHANGELOG-0.1.md](./CHANGELOG-0.1.md) 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace.package] 2 | description = "A simple tool for creating modern online books in pure typst." 3 | authors = ["shiroa Developers"] 4 | version = "0.3.1-rc3" 5 | edition = "2021" 6 | readme = "README.md" 7 | license = "Apache-2.0" 8 | homepage = "https://github.com/Myriad-Dreamin/shiroa" 9 | repository = "https://github.com/Myriad-Dreamin/shiroa" 10 | 11 | 12 | [workspace] 13 | resolver = "2" 14 | members = ["cli", "tools/build-from-source"] 15 | 16 | [profile.release] 17 | codegen-units = 1 # Reduce number of codegen units to increase optimizations 18 | opt-level = 3 19 | panic = "abort" # Abort on panic 20 | 21 | # The profile that 'dist' will build with 22 | [profile.dist] 23 | inherits = "release" 24 | lto = "thin" 25 | 26 | [workspace.dependencies] 27 | 28 | typst = "0.13.0" 29 | typst-eval = "0.13.0" 30 | typst-assets = "0.13.0" 31 | # typst-assets = { git = "https://github.com/typst/typst-assets", rev = "8cccef9" } 32 | reflexo-typst = { version = "0.6.0", features = ["html"] } 33 | reflexo-vec2svg = { version = "0.6.0", features = [ 34 | "experimental-ligature", 35 | ] } 36 | 37 | handlebars = "6.3.0" 38 | 39 | # general 40 | anyhow = "1" 41 | comemo = "0.4" 42 | tokio = { version = "1.42", features = ["full"] } 43 | rayon = "1" 44 | 45 | # cryptography and processing 46 | serde = { version = "1" } 47 | serde_json = "1" 48 | toml = "0.8" 49 | regex = "1.8.1" 50 | 51 | # web 52 | warp = { version = "0.3", features = ["compression"] } 53 | 54 | # cli 55 | clap = { version = "4.5", features = ["derive", "env", "unicode", "wrap_help"] } 56 | clap_complete = "4.5" 57 | clap_complete_fig = "4.5" 58 | termcolor = "1" 59 | codespan-reporting = "0.11" 60 | 61 | # logging and tracing 62 | env_logger = "0.11" 63 | log = "0.4.25" 64 | 65 | # search 66 | elasticlunr-rs = "3.0.2" 67 | 68 | # misc 69 | vergen = { version = "9.0.4", features = ["build", "cargo", "rustc"] } 70 | vergen-gitcl = { version = "1.0.1" } 71 | include_dir = "0.7.3" 72 | pathdiff = "0.2.1" 73 | 74 | [patch.crates-io] 75 | typst = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0" } 76 | typst-html = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0" } 77 | typst-svg = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0" } 78 | typst-render = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0" } 79 | typst-pdf = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0" } 80 | typst-syntax = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0" } 81 | typst-eval = { git = "https://github.com/Myriad-Dreamin/typst.git", tag = "typst.ts/v0.6.0" } 82 | # typst-assets = { git = "https://github.com/typst/typst-assets", rev = "8cccef9" } 83 | 84 | # reflexo-vec2svg = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "0eee46894a09a4da5caff1610a3c99e8552bce86" } 85 | # reflexo-typst = { git = "https://github.com/Myriad-Dreamin/typst.ts", rev = "0eee46894a09a4da5caff1610a3c99e8552bce86" } 86 | 87 | # typst-shim = { git = "https://github.com/Myriad-Dreamin/tinymist.git", rev = "81cedd9819d72b77de46fac7a32d936e3d9c868d" } 88 | # tinymist-derive = { git = "https://github.com/Myriad-Dreamin/tinymist.git", rev = "81cedd9819d72b77de46fac7a32d936e3d9c868d" } 89 | # tinymist-std = { git = "https://github.com/Myriad-Dreamin/tinymist.git", rev = "81cedd9819d72b77de46fac7a32d936e3d9c868d" } 90 | # tinymist-task = { git = "https://github.com/Myriad-Dreamin/tinymist.git", rev = "81cedd9819d72b77de46fac7a32d936e3d9c868d" } 91 | # tinymist-package = { git = "https://github.com/Myriad-Dreamin/tinymist.git", rev = "81cedd9819d72b77de46fac7a32d936e3d9c868d" } 92 | # tinymist-world = { git = "https://github.com/Myriad-Dreamin/tinymist.git", rev = "81cedd9819d72b77de46fac7a32d936e3d9c868d" } 93 | # tinymist-project = { git = "https://github.com/Myriad-Dreamin/tinymist.git", rev = "81cedd9819d72b77de46fac7a32d936e3d9c868d" } 94 | 95 | # typst = { path = "../typst/crates/typst" } 96 | # typst-syntax = { path = "../typst/crates/typst-syntax" } 97 | # reflexo-vec2svg = { path = "../typst.ts/crates/conversion/vec2svg" } 98 | # reflexo-typst = { path = "../typst.ts/crates/reflexo-typst" } 99 | 100 | # comemo = { path = "../comemo" } 101 | # typst = { path = "../typst/crates/typst" } 102 | # typst-syntax = { path = "../typst/crates/typst-syntax" } 103 | # typst-render = { path = "../typst/crates/typst-render" } 104 | # typst-svg = { path = "../typst/crates/typst-svg" } 105 | # typst-pdf = { path = "../typst/crates/typst-pdf" } 106 | # typst-eval = { path = "../typst/crates/typst-eval" } 107 | # typst-html = { path = "../typst/crates/typst-html" } 108 | 109 | # fontdb = { path = "../fontdb" } 110 | 111 | # [patch."https://github.com/Myriad-Dreamin/tinymist.git"] 112 | # typst-shim = { path = "../tinymist/crates/typst-shim" } 113 | # tinymist-analysis = { path = "../tinymist/crates/tinymist-analysis" } 114 | # tinymist-std = { path = "../tinymist/crates/tinymist-std" } 115 | # tinymist-task = { path = "../tinymist/crates/tinymist-task" } 116 | # tinymist-world = { path = "../tinymist/crates/tinymist-world" } 117 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | ARG NODE_VERSION=22 3 | ARG RUST_VERSION=1.85.0 4 | 5 | FROM node:${NODE_VERSION}-alpine AS build-yarn 6 | RUN apk add --no-cache cpio findutils git 7 | ADD . /app 8 | WORKDIR /app 9 | RUN cd frontend && yarn install && yarn run build 10 | 11 | FROM rust:${RUST_VERSION}-bullseye AS build 12 | ADD . /app 13 | WORKDIR /app 14 | COPY --from=build-yarn /app/frontend /app/frontend 15 | ENV CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse 16 | RUN apt-get install -y git \ 17 | && cargo build -p shiroa --release 18 | 19 | FROM debian:11 20 | WORKDIR /root/ 21 | COPY --from=build /app/target/release/shiroa /bin 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # shiroa 2 | 3 | [_shiroa_](https://github.com/Myriad-Dreamin/shiroa) (_Shiro A_, or _The White_, or _云笺_) is a simple tool for creating modern online (cloud) books in pure typst. 4 | 5 | ## Installation (shiroa CLI) 6 | 7 | There are multiple ways to install the [shiroa](https://github.com/Myriad-Dreamin/shiroa) CLI tool. 8 | Choose any one of the methods below that best suit your needs. 9 | 10 | ### Pre-compiled binaries 11 | 12 | Executable binaries are available for download on the [GitHub Releases page](https://github.com/Myriad-Dreamin/shiroa/releases). 13 | Download the binary for your platform (Windows, macOS, or Linux) and extract the archive. 14 | The archive contains an `shiroa` executable which you can run to build your books. 15 | 16 | To make it easier to run, put the path to the binary into your `PATH`. 17 | 18 | ### Build from source using Rust 19 | 20 | To build the `shiroa` executable from source, you will first need to install Yarn, Rust, and Cargo. 21 | Follow the instructions on the [Yarn installation page](https://classic.yarnpkg.com/en/docs/install) and [Rust installation page](https://www.rust-lang.org/tools/install). 22 | shiroa currently requires at least Rust version 1.75. 23 | 24 | To build with precompiled artifacts, run the following commands: 25 | 26 | ```sh 27 | cargo install --git https://github.com/Myriad-Dreamin/shiroa --locked shiroa 28 | ``` 29 | 30 | To build from source, run the following commands: 31 | 32 | ```sh 33 | git clone https://github.com/Myriad-Dreamin/shiroa.git 34 | git submodule update --recursive --init 35 | cargo run --bin shiroa-build 36 | # optional: install it globally 37 | cargo install --path ./cli 38 | ``` 39 | 40 | With global installation, to uninstall, run the command `cargo uninstall shiroa`. 41 | 42 | Again, make sure to add the Cargo bin directory to your `PATH`. 43 | 44 | ### Get started 45 | 46 | See the [Get-started](https://myriad-dreamin.github.io/shiroa/guide/get-started.html) online documentation. 47 | 48 | ### Setup for writing your book 49 | 50 | We don't provide a watch command, but `shiroa` is designated to embracing all of the approaches to writing typst documents. It's feasible to preview your documents by following approaches (like previewing normal documents): 51 | 52 | - via [Official Web App](https://typst.app). 53 | 54 | - via VSCod(e,ium), see [Tinymist](https://marketplace.visualstudio.com/items?itemName=myriad-dreamin.tinymist) and [Typst Preview](https://marketplace.visualstudio.com/items?itemName=mgt19937.typst-preview). 55 | 56 | - via other editors. For example of neovim, see [typst.vim](https://github.com/kaarmu/typst.vim) and [Typst Preview](https://github.com/Enter-tainer/typst-preview#use-without-vscode). 57 | 58 | - via `typst-cli watch`, See [typst-cli watch](https://github.com/typst/typst#usage). 59 | 60 | ### Acknowledgement 61 | 62 | - The [mdbook theme](./themes/mdbook/) is borrowed from [mdBook](https://github.com/rust-lang/mdBook/tree/master/src/theme) project. 63 | 64 | - Compile the document with awesome [Typst](https://github.com/typst/typst). 65 | -------------------------------------------------------------------------------- /assets/fonts/.gitignore: -------------------------------------------------------------------------------- 1 | Charter* 2 | BlexMonoNerdFontMono* 3 | SourceHanSerifSC* -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "shiroa" 3 | description = "Command line tool for shiroa." 4 | authors.workspace = true 5 | version.workspace = true 6 | license.workspace = true 7 | edition.workspace = true 8 | homepage.workspace = true 9 | repository.workspace = true 10 | 11 | [[bin]] 12 | name = "shiroa" 13 | path = "src/main.rs" 14 | test = false 15 | doctest = false 16 | bench = false 17 | doc = false 18 | 19 | [dependencies] 20 | typst.workspace = true 21 | typst-assets = { workspace = true } 22 | 23 | warp.workspace = true 24 | 25 | clap.workspace = true 26 | clap_complete.workspace = true 27 | clap_complete_fig.workspace = true 28 | 29 | comemo.workspace = true 30 | tokio.workspace = true 31 | indexmap = "2" 32 | url = "2" 33 | include_dir.workspace = true 34 | 35 | serde.workspace = true 36 | serde_json.workspace = true 37 | toml.workspace = true 38 | 39 | env_logger.workspace = true 40 | log.workspace = true 41 | 42 | reflexo-typst = { workspace = true, features = [ 43 | "dynamic-layout", 44 | "svg", 45 | "system", 46 | ] } 47 | reflexo-vec2svg.workspace = true 48 | handlebars.workspace = true 49 | pathdiff.workspace = true 50 | elasticlunr-rs.workspace = true 51 | regex.workspace = true 52 | termcolor.workspace = true 53 | rayon.workspace = true 54 | codespan-reporting.workspace = true 55 | 56 | [build-dependencies] 57 | anyhow.workspace = true 58 | vergen.workspace = true 59 | vergen-gitcl.workspace = true 60 | 61 | [features] 62 | embedded-fonts = ["typst-assets/fonts"] 63 | default = ["embedded-fonts"] 64 | -------------------------------------------------------------------------------- /cli/build.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use vergen::{BuildBuilder, CargoBuilder, Emitter, RustcBuilder}; 3 | use vergen_gitcl::GitclBuilder; 4 | 5 | fn main() -> Result<()> { 6 | let build = BuildBuilder::default().build_timestamp(true).build()?; 7 | let cargo = CargoBuilder::all_cargo()?; 8 | let rustc = RustcBuilder::default() 9 | .commit_hash(true) 10 | .semver(true) 11 | .host_triple(true) 12 | .channel(true) 13 | .llvm_version(true) 14 | .build()?; 15 | let gitcl = GitclBuilder::default() 16 | .sha(false) 17 | .describe(true, true, None) 18 | .build()?; 19 | 20 | // Emit the instructions 21 | Emitter::default() 22 | .add_instructions(&build)? 23 | .add_instructions(&cargo)? 24 | .add_instructions(&rustc)? 25 | .add_instructions(&gitcl)? 26 | .emit()?; 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /cli/src/debug_loc.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use typst::layout::Position as TypstPosition; 3 | 4 | /// A serializable physical position in a document. 5 | /// 6 | /// Note that it uses [`f32`] instead of [`f64`] as same as 7 | /// [`TypstPosition`] for the coordinates to improve both performance 8 | /// of serialization and calculation. It does sacrifice the floating 9 | /// precision, but it is enough in our use cases. 10 | /// 11 | /// Also see [`TypstPosition`]. 12 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 13 | pub struct DocumentPosition { 14 | /// The page, starting at 1. 15 | pub page_no: usize, 16 | /// The exact x-coordinate on the page (from the left, as usual). 17 | pub x: f32, 18 | /// The exact y-coordinate on the page (from the top, as usual). 19 | pub y: f32, 20 | } 21 | 22 | impl From for DocumentPosition { 23 | fn from(position: TypstPosition) -> Self { 24 | Self { 25 | page_no: position.page.into(), 26 | x: position.point.x.to_pt() as f32, 27 | y: position.point.y.to_pt() as f32, 28 | } 29 | } 30 | } 31 | 32 | // /// Unevaluated source span. 33 | // /// The raw source span is unsafe to serialize and deserialize. 34 | // /// Because the real source location is only known during liveness of 35 | // /// the compiled document. 36 | // pub type SourceSpan = typst::syntax::Span; 37 | 38 | // /// Raw representation of a source span. 39 | // pub type RawSourceSpan = u64; 40 | 41 | // /// A char position represented in form of line and column. 42 | // /// The position is encoded in Utf-8 or Utf-16, and the encoding is 43 | // /// determined by usage. 44 | // /// 45 | // pub struct CharPosition { 46 | // /// The line number, starting at 0. 47 | // line: usize, 48 | // /// The column number, starting at 0. 49 | // column: usize, 50 | // } 51 | 52 | // /// A resolved source (text) location. 53 | // /// 54 | // /// See [`CharPosition`] for the definition of the position inside a file. 55 | // pub struct SourceLocation { 56 | // filepath: PathBuf, 57 | // pos: CharPosition, 58 | // } 59 | // /// A resolved source (text) range. 60 | // /// 61 | // /// See [`CharPosition`] for the definition of the position inside a file. 62 | // pub struct SourceRange { 63 | // filepath: PathBuf, 64 | // start: CharPosition, 65 | // end: CharPosition, 66 | // } 67 | -------------------------------------------------------------------------------- /cli/src/diag.rs: -------------------------------------------------------------------------------- 1 | use codespan_reporting::diagnostic::{Diagnostic, Label}; 2 | use codespan_reporting::files::Files; 3 | use codespan_reporting::term::{self, termcolor::WriteColor}; 4 | use reflexo_typst::{DiagnosticFormat, Result}; 5 | use typst::diag::{eco_format, Severity, SourceDiagnostic}; 6 | use typst::syntax::{FileId, Span}; 7 | use typst::{World, WorldExt}; 8 | 9 | /// Prints diagnostic messages to the terminal. 10 | pub fn print_diagnostics<'d, 'files, W: World + Files<'files, FileId = FileId>>( 11 | world: &'files W, 12 | diagnostics: impl Iterator, 13 | diagnostic_format: DiagnosticFormat, 14 | w: &mut dyn WriteColor, 15 | ) -> Result<(), codespan_reporting::files::Error> { 16 | let mut config = term::Config { 17 | tab_width: 2, 18 | ..Default::default() 19 | }; 20 | if diagnostic_format == DiagnosticFormat::Short { 21 | config.display_style = term::DisplayStyle::Short; 22 | } 23 | 24 | for diagnostic in diagnostics { 25 | let diag = match diagnostic.severity { 26 | Severity::Error => Diagnostic::error(), 27 | Severity::Warning => Diagnostic::warning(), 28 | } 29 | .with_message(diagnostic.message.clone()) 30 | .with_notes( 31 | diagnostic 32 | .hints 33 | .iter() 34 | .map(|e| (eco_format!("hint: {e}")).into()) 35 | .collect(), 36 | ) 37 | .with_labels(label(world, diagnostic.span).into_iter().collect()); 38 | 39 | term::emit(w, &config, world, &diag)?; 40 | 41 | // Stacktrace-like helper diagnostics. 42 | for point in &diagnostic.trace { 43 | let message = point.v.to_string(); 44 | let help = Diagnostic::help() 45 | .with_message(message) 46 | .with_labels(label(world, point.span).into_iter().collect()); 47 | 48 | term::emit(w, &config, world, &help)?; 49 | } 50 | } 51 | 52 | Ok(()) 53 | } 54 | 55 | /// Creates a label for a span. 56 | fn label<'files, W: World + Files<'files, FileId = FileId>>( 57 | world: &'files W, 58 | span: Span, 59 | ) -> Option> { 60 | Some(Label::primary(span.id()?, world.range(span)?)) 61 | } 62 | -------------------------------------------------------------------------------- /cli/src/error.rs: -------------------------------------------------------------------------------- 1 | pub use reflexo_typst::error::*; 2 | -------------------------------------------------------------------------------- /cli/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod error; 2 | pub mod meta; 3 | pub mod outline; 4 | pub mod project; 5 | pub mod render; 6 | pub mod theme; 7 | pub mod utils; 8 | pub mod version; 9 | 10 | mod debug_loc; 11 | mod diag; 12 | pub mod tui; 13 | 14 | use core::fmt; 15 | use std::path::PathBuf; 16 | 17 | use clap::{ArgAction, Parser, Subcommand, ValueEnum}; 18 | 19 | use crate::version::VersionFormat; 20 | 21 | #[derive(Debug, Parser)] 22 | #[clap(name = "shiroa", version = "0.3.1-rc3")] 23 | pub struct Opts { 24 | /// Print Version 25 | #[arg(short = 'V', long, group = "version-dump")] 26 | pub version: bool, 27 | 28 | /// Print Version in format 29 | #[arg(long = "VV", alias = "version-fmt", group = "version-dump", default_value_t = VersionFormat::None)] 30 | pub vv: VersionFormat, 31 | 32 | /// Print Verbose Log 33 | #[arg(short = 'v', long, global = true)] 34 | pub verbose: bool, 35 | 36 | #[clap(subcommand)] 37 | pub sub: Option, 38 | } 39 | 40 | #[derive(Debug, Subcommand)] 41 | #[clap( 42 | about = "The cli for shiroa.", 43 | after_help = "", 44 | next_display_order = None 45 | )] 46 | #[allow(clippy::large_enum_variant)] 47 | pub enum Subcommands { 48 | #[clap(about = "init book.")] 49 | Init(InitArgs), 50 | #[clap(about = "build book.")] 51 | Build(BuildArgs), 52 | #[clap(about = "serve book.")] 53 | Serve(ServeArgs), 54 | } 55 | 56 | /// Determine the approach to retrieving metadata of a book project. 57 | #[derive(ValueEnum, Debug, Clone, Eq, PartialEq, Default)] 58 | #[value(rename_all = "kebab-case")] 59 | pub enum MetaSource { 60 | /// Strictly retrieve the project's meta by label queries. 61 | /// + retrieve the book meta from `` 62 | /// + retrieve the build meta from `` 63 | Strict, 64 | /// Infer the project's meta from the outline of main file. 65 | /// Note: if the main file also contains `` or 66 | /// ``, the manual-set meta will be used first. 67 | #[default] 68 | Outline, 69 | } 70 | 71 | impl fmt::Display for MetaSource { 72 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 73 | f.write_str(self.to_possible_value().unwrap().get_name()) 74 | } 75 | } 76 | 77 | #[derive(ValueEnum, Debug, Clone, Eq, PartialEq, Default)] 78 | #[value(rename_all = "kebab-case")] 79 | pub enum RenderMode { 80 | #[default] 81 | DynPaged, 82 | StaticHtmlDynPaged, 83 | StaticHtml, 84 | } 85 | 86 | #[derive(Default, Debug, Clone, Parser)] 87 | #[clap(next_help_heading = "Compile options")] 88 | pub struct CompileArgs { 89 | /// Root directory for the book 90 | /// (Defaults to the current directory when omitted) 91 | #[clap(default_value = "")] 92 | pub dir: String, 93 | 94 | /// Determine the approach to retrieving metadata of the book project. 95 | #[clap(long, default_value = "strict")] 96 | pub meta_source: MetaSource, 97 | 98 | /// The mode to render typst document. 99 | /// 100 | /// + `dynamic-paged`: dynamically render as paged document. 101 | /// + `static-html-static-paged`: statically render html parts as much as 102 | /// possible, and leave frames rendered dynamically. 103 | /// + `static-html`: statically render the whole document, the embedded 104 | /// frames are not resizable. 105 | /// 106 | /// The dynamically rendering means that some elements will be rendered by a 107 | /// wasm module in the browser. 108 | #[clap(long, default_value = "dyn-paged")] 109 | pub mode: RenderMode, 110 | 111 | /// Root directory for the typst workspace, which is same as the 112 | /// `typst-cli`'s root. (Defaults to the root directory for the book 113 | /// when omitted) 114 | #[clap(long, short, default_value = "")] 115 | pub workspace: String, 116 | 117 | /// Output to directory, default in the same directory as the entry file. 118 | /// Relative paths are interpreted relative to the book's root directory. 119 | /// If omitted, shiroa uses build.build-dir from book.toml or defaults 120 | /// to `./dist`. 121 | #[clap(long, short, default_value = "")] 122 | pub dest_dir: String, 123 | 124 | /// Reset path to root in html files. 125 | #[clap(long, default_value = "/")] 126 | pub path_to_root: String, 127 | 128 | /// Specify a theme directory to copy recursively. 129 | /// 130 | /// The files will be copied to the `theme/` in the output 131 | /// directory. 132 | #[clap(long)] 133 | pub theme: Option, 134 | 135 | /// Add additional directories to search for fonts 136 | #[clap( 137 | long = "font-path", 138 | env = "TYPST_FONT_PATHS", 139 | value_name = "DIR", 140 | action = ArgAction::Append, 141 | )] 142 | pub font_paths: Vec, 143 | 144 | /// Specify a filter to only load files with a specific extension. 145 | #[clap(long, default_value = "^(player.bilibili.com)$")] 146 | pub allowed_url_source: Option, 147 | } 148 | 149 | #[derive(Default, Debug, Clone, Parser)] 150 | #[clap(next_help_heading = "Init options")] 151 | pub struct InitArgs { 152 | /// arguments for compile setting. 153 | #[clap(flatten)] 154 | pub compile: CompileArgs, 155 | } 156 | 157 | #[derive(Default, Debug, Clone, Parser)] 158 | #[clap(next_help_heading = "Build options")] 159 | pub struct BuildArgs { 160 | /// arguments for compile setting. 161 | #[clap(flatten)] 162 | pub compile: CompileArgs, 163 | } 164 | 165 | #[derive(Default, Debug, Clone, Parser)] 166 | #[clap(next_help_heading = "Compile options")] 167 | pub struct ServeArgs { 168 | /// arguments for compile setting. 169 | #[clap(flatten)] 170 | pub compile: CompileArgs, 171 | 172 | /// Do not build the book before serving. 173 | #[clap(long)] 174 | pub no_build: bool, 175 | 176 | /// Listen address. 177 | #[clap(long, default_value = "127.0.0.1:25520")] 178 | pub addr: String, 179 | } 180 | 181 | pub mod build_info { 182 | /// The version of the shiroa crate. 183 | pub static VERSION: &str = env!("CARGO_PKG_VERSION"); 184 | } 185 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::{net::SocketAddr, path::Path, process::exit}; 2 | 3 | use clap::{Args, Command, FromArgMatches}; 4 | use reflexo_typst::path::{unix_slash, PathClean}; 5 | use shiroa::{ 6 | error::prelude::*, 7 | project::Project, 8 | tui_hint, 9 | utils::{async_continue, create_dirs, make_absolute, write_file, UnwrapOrExit}, 10 | version::intercept_version, 11 | BuildArgs, InitArgs, Opts, ServeArgs, Subcommands, 12 | }; 13 | use warp::{http::Method, Filter}; 14 | 15 | fn get_cli(sub_command_required: bool) -> Command { 16 | let cli = Command::new("$").disable_version_flag(true); 17 | Opts::augment_args(cli).subcommand_required(sub_command_required) 18 | } 19 | 20 | fn help_sub_command() -> ! { 21 | Opts::from_arg_matches(&get_cli(true).get_matches()).unwrap_or_exit(); 22 | exit(0); 23 | } 24 | 25 | fn main() { 26 | let opts = Opts::from_arg_matches(&get_cli(false).get_matches()).unwrap_or_exit(); 27 | 28 | if opts.verbose { 29 | env_logger::builder() 30 | .filter_level(log::LevelFilter::Info) 31 | .filter_module("typst", log::LevelFilter::Warn) 32 | .filter_module("reflexo", log::LevelFilter::Info) 33 | .filter_module("tracing::", log::LevelFilter::Off) 34 | .init(); 35 | } else { 36 | env_logger::builder() 37 | .filter_level(log::LevelFilter::Warn) 38 | .init(); 39 | } 40 | 41 | intercept_version(opts.version, opts.vv); 42 | 43 | match opts.sub { 44 | Some(Subcommands::Init(args)) => { 45 | async_continue(async { init(args).await.unwrap_or_exit() }) 46 | } 47 | Some(Subcommands::Build(args)) => build(args).unwrap_or_exit(), 48 | Some(Subcommands::Serve(args)) => { 49 | async_continue(async { serve(args).await.unwrap_or_exit() }) 50 | } 51 | None => help_sub_command(), 52 | }; 53 | 54 | #[allow(unreachable_code)] 55 | { 56 | unreachable!("The subcommand must exit the process."); 57 | } 58 | } 59 | 60 | async fn init(args: InitArgs) -> Result<()> { 61 | let dir = make_absolute(Path::new(&args.compile.dir)).clean(); 62 | 63 | if dir.exists() { 64 | clap::Error::raw( 65 | clap::error::ErrorKind::ValueValidation, 66 | format!("the init directory already exists: {dir:?}\n"), 67 | ) 68 | .exit() 69 | } 70 | 71 | let wd = if args.compile.workspace.is_empty() { 72 | dir.clone() 73 | } else { 74 | make_absolute(Path::new(&args.compile.workspace)).clean() 75 | }; 76 | let rel = pathdiff::diff_paths(&dir, &wd).unwrap(); 77 | 78 | if rel.starts_with("..") { 79 | clap::Error::raw( 80 | clap::error::ErrorKind::ValueValidation, 81 | format!("bad workspace, which is sub-directory of book.typ's root: {dir:?} / {wd:?} -> {rel:?}"), 82 | ) 83 | .exit() 84 | } 85 | 86 | let workspace_to_root = Path::new("/").join(rel); 87 | 88 | let page_template = unix_slash(&workspace_to_root.join("templates/page.typ")); 89 | let ebook_template = unix_slash(&workspace_to_root.join("templates/ebook.typ")); 90 | let book_typ = unix_slash(&workspace_to_root.join("book.typ")); 91 | 92 | let build_meta = if args.compile.dest_dir.is_empty() { 93 | String::default() 94 | } else { 95 | format!( 96 | r##"#build-meta( 97 | dest-dir: "{}", 98 | )"##, 99 | args.compile.dest_dir 100 | ) 101 | }; 102 | 103 | create_dirs(&dir)?; 104 | create_dirs(dir.join("templates"))?; 105 | 106 | write_file( 107 | dir.join("book.typ"), 108 | format!( 109 | r##" 110 | #import "@preview/shiroa:0.2.3": * 111 | 112 | #show: book 113 | 114 | #book-meta( 115 | title: "shiroa", 116 | summary: [ 117 | #prefix-chapter("sample-page.typ")[Hello, typst] 118 | ] 119 | ) 120 | 121 | {build_meta} 122 | 123 | // re-export page template 124 | #import "{page_template}": project 125 | #let book-page = project 126 | "## 127 | ), 128 | )?; 129 | write_file( 130 | dir.join("sample-page.typ"), 131 | format!( 132 | r##"#import "{book_typ}": book-page 133 | 134 | #show: book-page.with(title: "Hello, typst") 135 | 136 | = Hello, typst 137 | 138 | Sample page 139 | "## 140 | ), 141 | )?; 142 | write_file( 143 | dir.join("ebook.typ"), 144 | format!( 145 | r##"#import "@preview/shiroa:0.2.3": * 146 | 147 | #import "{ebook_template}" 148 | 149 | #show: ebook.project.with(title: "typst-book", spec: "book.typ") 150 | 151 | // set a resolver for inclusion 152 | #ebook.resolve-inclusion(it => include it) 153 | "## 154 | ), 155 | )?; 156 | write_file( 157 | dir.join("templates/page.typ"), 158 | include_bytes!("../../contrib/typst/gh-pages.typ"), 159 | )?; 160 | write_file( 161 | dir.join("templates/ebook.typ"), 162 | std::str::from_utf8(include_bytes!("../../contrib/typst/gh-ebook.typ").as_slice()) 163 | .unwrap() 164 | .replace("/contrib/typst/gh-pages.typ", &page_template), 165 | )?; 166 | write_file( 167 | dir.join("templates/theme-style.toml"), 168 | include_bytes!("../../contrib/typst/theme-style.toml"), 169 | )?; 170 | write_file( 171 | dir.join("templates/tokyo-night.tmTheme"), 172 | include_bytes!("../../contrib/typst/tokyo-night.tmTheme"), 173 | )?; 174 | 175 | serve(ServeArgs { 176 | compile: args.compile, 177 | addr: "127.0.0.1:25520".to_owned(), 178 | ..Default::default() 179 | }) 180 | .await 181 | } 182 | 183 | fn build(args: BuildArgs) -> Result<()> { 184 | let mut proj = Project::new(args.compile)?; 185 | proj.build()?; 186 | 187 | exit(0) 188 | } 189 | 190 | pub async fn serve(args: ServeArgs) -> Result<()> { 191 | let mut proj = Project::new(args.compile)?; 192 | 193 | let http_addr: SocketAddr = args 194 | .addr 195 | .clone() 196 | .parse() 197 | .map_err(map_string_err("ParseServeAddr"))?; 198 | 199 | let dest_dir = proj.dest_dir.clone(); 200 | let server = warp::serve({ 201 | let cors = 202 | warp::cors().allow_methods(&[Method::GET, Method::POST, Method::DELETE, Method::HEAD]); 203 | 204 | let dev = warp::path("dev").and(warp::fs::dir("")); 205 | 206 | dev.or(warp::fs::dir(dest_dir)) 207 | .with(cors) 208 | .with(warp::compression::gzip()) 209 | }); 210 | 211 | let (addr, fut) = server.bind_ephemeral(http_addr); 212 | tui_hint!("Server started at http://{addr}"); 213 | 214 | // Build the book if it hasn't been built yet 215 | if !args.no_build { 216 | tokio::spawn(async move { proj.watch(Some(addr)).await }); 217 | }; 218 | 219 | fut.await; 220 | 221 | exit(0); 222 | } 223 | -------------------------------------------------------------------------------- /cli/src/meta.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | /// Typst content kind embedded in metadata nodes 4 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] 5 | #[serde(tag = "kind")] 6 | pub enum BookMetaContent { 7 | #[serde(rename = "raw")] 8 | Raw { content: serde_json::Value }, 9 | #[serde(rename = "plain-text")] 10 | PlainText { content: String }, 11 | } 12 | 13 | /// Content summary kind in summary.typ 14 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] 15 | #[serde(tag = "kind")] 16 | pub enum BookMetaElem { 17 | #[serde(rename = "part")] 18 | Part { title: BookMetaContent, level: i32 }, 19 | #[serde(rename = "chapter")] 20 | Chapter { 21 | title: BookMetaContent, 22 | link: Option, 23 | #[serde(default)] 24 | sub: Vec, 25 | section: Option, 26 | }, 27 | #[serde(rename = "separator")] 28 | Separator {}, 29 | } 30 | 31 | /// General information about your book. 32 | /// Book metadata in summary.typ 33 | #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)] 34 | pub struct BookMeta { 35 | /// The title of the book 36 | pub title: String, 37 | /// The author(s) of the book 38 | pub authors: Vec, 39 | /// A description for the book, which is added as meta information in the 40 | /// html `` of each page 41 | pub description: String, 42 | /// The github repository for the book 43 | pub repository: String, 44 | /// The github repository editing template for the book 45 | /// example: `https://github.com/Me/Book/edit/main/path/to/book/{path}` 46 | pub repository_edit: String, 47 | /// The main language of the book, which is used as a language attribute 48 | /// `` for example. 49 | pub language: String, 50 | /// Content summary of the book 51 | pub summary: Vec, 52 | } 53 | 54 | /// Build metadata in summary.typ 55 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] 56 | pub struct BuildMeta { 57 | /// The directory to put the rendered book in. By default this is book/ in 58 | /// the book's root directory. This can overridden with the --dest-dir CLI 59 | /// option. 60 | #[serde(rename = "dest-dir")] 61 | pub dest_dir: String, 62 | } 63 | 64 | /// Configuration of the search functionality of the HTML renderer. 65 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] 66 | #[serde(default, rename_all = "kebab-case")] 67 | pub struct Search { 68 | /// Enable the search feature. Default: `true`. 69 | pub enable: bool, 70 | /// Maximum number of visible results. Default: `30`. 71 | pub limit_results: u32, 72 | /// The number of words used for a search result teaser. Default: `30`. 73 | pub teaser_word_count: u32, 74 | /// Define the logical link between multiple search words. 75 | /// If true, all search words must appear in each result. Default: `false`. 76 | pub use_boolean_and: bool, 77 | /// Boost factor for the search result score if a search word appears in the 78 | /// header. Default: `2`. 79 | pub boost_title: u8, 80 | /// Boost factor for the search result score if a search word appears in the 81 | /// hierarchy. The hierarchy contains all titles of the parent documents 82 | /// and all parent headings. Default: `1`. 83 | pub boost_hierarchy: u8, 84 | /// Boost factor for the search result score if a search word appears in the 85 | /// text. Default: `1`. 86 | pub boost_paragraph: u8, 87 | /// True if the searchword `micro` should match `microwave`. Default: 88 | /// `true`. 89 | pub expand: bool, 90 | /// Documents are split into smaller parts, separated by headings. This 91 | /// defines, until which level of heading documents should be split. 92 | /// Default: `3`. (`### This is a level 3 heading`) 93 | pub heading_split_level: u8, 94 | /// Copy JavaScript files for the search functionality to the output 95 | /// directory? Default: `true`. 96 | pub copy_js: bool, 97 | } 98 | 99 | impl Default for Search { 100 | fn default() -> Search { 101 | // Please update the documentation of `Search` when changing values! 102 | Search { 103 | enable: true, 104 | limit_results: 30, 105 | teaser_word_count: 30, 106 | use_boolean_and: false, 107 | boost_title: 2, 108 | boost_hierarchy: 1, 109 | boost_paragraph: 1, 110 | expand: true, 111 | heading_split_level: 3, 112 | copy_js: true, 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /cli/src/render/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod html; 2 | pub use self::html::*; 3 | pub mod typst; 4 | pub use self::typst::*; 5 | pub mod search; 6 | pub use self::search::*; 7 | 8 | use std::collections::BTreeMap; 9 | pub type DataDict = BTreeMap; 10 | -------------------------------------------------------------------------------- /cli/src/render/search.rs: -------------------------------------------------------------------------------- 1 | use std::{borrow::Cow, path::Path, sync::Mutex}; 2 | 3 | use elasticlunr::{Index, IndexBuilder}; 4 | use reflexo_typst::{error::prelude::*, path::unix_slash}; 5 | use serde::Serialize; 6 | use typst::ecow::EcoString; 7 | 8 | use crate::meta::Search; 9 | use crate::utils::{collapse_whitespace, write_file}; 10 | 11 | const MAX_WORD_LENGTH_TO_INDEX: usize = 80; 12 | 13 | /// Tokenizes in the same way as elasticlunr-rs (for English), but also drops 14 | /// long tokens. 15 | fn tokenize(text: &str) -> Vec { 16 | text.split(|c: char| c.is_whitespace() || c == '-') 17 | .filter(|s| !s.is_empty()) 18 | .map(|s| s.trim().to_lowercase()) 19 | .filter(|s| s.len() <= MAX_WORD_LENGTH_TO_INDEX) 20 | .collect() 21 | } 22 | 23 | pub struct SearchRenderer { 24 | index: Index, 25 | doc_urls: Vec, 26 | pub config: Search, 27 | } 28 | 29 | impl Default for SearchRenderer { 30 | fn default() -> Self { 31 | Self::new() 32 | } 33 | } 34 | 35 | impl SearchRenderer { 36 | fn new() -> Self { 37 | let index = IndexBuilder::new() 38 | .add_field_with_tokenizer("title", Box::new(&tokenize)) 39 | .add_field_with_tokenizer("body", Box::new(&tokenize)) 40 | .add_field_with_tokenizer("breadcrumbs", Box::new(&tokenize)) 41 | .build(); 42 | 43 | SearchRenderer { 44 | index, 45 | doc_urls: vec![], 46 | config: Search::default(), 47 | } 48 | } 49 | 50 | /// Uses the given arguments to construct a search document, then inserts it 51 | /// to the given index. 52 | fn add_doc(&mut self, anchor_base: &str, section_id: &Option, items: &[&str]) { 53 | let url = if let Some(ref id) = *section_id { 54 | Cow::Owned(format!("{anchor_base}#{id}")) 55 | } else { 56 | Cow::Borrowed(anchor_base) 57 | }; 58 | let doc_ref = self.doc_urls.len().to_string(); 59 | self.doc_urls.push(url.into()); 60 | 61 | let items = items.iter().map(|&x| collapse_whitespace(x.trim())); 62 | self.index.add_doc(&doc_ref, items); 63 | } 64 | 65 | pub fn render_search_index(&mut self, dest_dir: &Path) -> Result<()> { 66 | let index = write_to_json(&self.index, &self.config, &self.doc_urls)?; 67 | if index.len() > 10_000_000 { 68 | log::warn!("searchindex.json is very large ({} bytes)", index.len()); 69 | } 70 | 71 | write_file(dest_dir.join("searchindex.json"), index.as_bytes())?; 72 | write_file( 73 | dest_dir.join("searchindex.js"), 74 | format!("Object.assign(window.search, {});", index).as_bytes(), 75 | )?; 76 | 77 | Ok(()) 78 | } 79 | 80 | pub fn build(&mut self, items: &[SearchItem]) -> Result<()> { 81 | for item in items { 82 | let title = item.title.as_str(); 83 | let desc = item.desc.as_str(); 84 | let dest = item.anchor_base.as_str(); 85 | 86 | // , &breadcrumbs.join(" » ") 87 | // todo: currently, breadcrumbs is title it self 88 | self.add_doc(dest, &None, &[title, desc, title]); 89 | } 90 | 91 | Ok(()) 92 | } 93 | } 94 | 95 | pub struct SearchItem { 96 | anchor_base: String, 97 | title: EcoString, 98 | desc: EcoString, 99 | } 100 | 101 | pub struct SearchCtx<'a> { 102 | pub config: &'a Search, 103 | pub items: Mutex>, 104 | } 105 | 106 | impl SearchCtx<'_> { 107 | pub fn index_search(&self, dest: &Path, title: EcoString, desc: EcoString) { 108 | let anchor_base = unix_slash(dest); 109 | 110 | self.items.lock().unwrap().push(SearchItem { 111 | anchor_base, 112 | title, 113 | desc, 114 | }); 115 | } 116 | } 117 | 118 | fn write_to_json(index: &Index, search_config: &Search, doc_urls: &Vec) -> Result { 119 | use elasticlunr::config::{SearchBool, SearchOptions, SearchOptionsField}; 120 | use std::collections::BTreeMap; 121 | 122 | #[derive(Serialize)] 123 | struct ResultsOptions { 124 | limit_results: u32, 125 | teaser_word_count: u32, 126 | } 127 | 128 | #[derive(Serialize)] 129 | struct SearchindexJson<'a> { 130 | /// The options used for displaying search results 131 | results_options: ResultsOptions, 132 | /// The searchoptions for elasticlunr.js 133 | search_options: SearchOptions, 134 | /// Used to lookup a document's URL from an integer document ref. 135 | doc_urls: &'a Vec, 136 | /// The index for elasticlunr.js 137 | index: &'a elasticlunr::Index, 138 | } 139 | 140 | let mut fields = BTreeMap::new(); 141 | let mut opt = SearchOptionsField::default(); 142 | let mut insert_boost = |key: &str, boost| { 143 | opt.boost = Some(boost); 144 | fields.insert(key.into(), opt); 145 | }; 146 | insert_boost("title", search_config.boost_title); 147 | insert_boost("body", search_config.boost_paragraph); 148 | insert_boost("breadcrumbs", search_config.boost_hierarchy); 149 | 150 | let search_options = SearchOptions { 151 | bool: if search_config.use_boolean_and { 152 | SearchBool::And 153 | } else { 154 | SearchBool::Or 155 | }, 156 | expand: search_config.expand, 157 | fields, 158 | }; 159 | 160 | let results_options = ResultsOptions { 161 | limit_results: search_config.limit_results, 162 | teaser_word_count: search_config.teaser_word_count, 163 | }; 164 | 165 | let json_contents = SearchindexJson { 166 | results_options, 167 | search_options, 168 | doc_urls, 169 | index, 170 | }; 171 | 172 | // By converting to serde_json::Value as an intermediary, we use a 173 | // BTreeMap internally and can force a stable ordering of map keys. 174 | let json_contents = 175 | serde_json::to_value(&json_contents).context("Failed to serialize search index to JSON")?; 176 | let json_contents = serde_json::to_string(&json_contents) 177 | .context("Failed to serialize search index to JSON string")?; 178 | 179 | Ok(json_contents) 180 | } 181 | -------------------------------------------------------------------------------- /cli/src/theme.rs: -------------------------------------------------------------------------------- 1 | use std::{fs::File, io::Read, path::Path}; 2 | 3 | use include_dir::include_dir; 4 | use log::warn; 5 | use reflexo_typst::{error::prelude::*, ImmutPath}; 6 | 7 | use crate::utils::{self, copy_dir_embedded, write_file}; 8 | 9 | #[derive(Debug, PartialEq)] 10 | pub enum EmbeddedThemeAsset { 11 | MdBook, 12 | } 13 | 14 | #[derive(Debug, PartialEq)] 15 | pub enum ThemeAsset { 16 | Static(EmbeddedThemeAsset), 17 | Dir(ImmutPath), 18 | } 19 | 20 | /// The `Theme` struct should be used instead of the static variables because 21 | /// the `new()` method will look if the user has a theme directory in their 22 | /// source folder and use the users theme instead of the default. 23 | /// 24 | /// You should only ever use the static variables directly if you want to 25 | /// override the user's theme with the defaults. 26 | #[derive(Debug, PartialEq)] 27 | pub struct Theme { 28 | pub index: Vec, 29 | pub head: Vec, 30 | pub header: Vec, 31 | pub typst_load_trampoline: Vec, 32 | pub typst_load_html_trampoline: Vec, 33 | 34 | asset: ThemeAsset, 35 | } 36 | 37 | impl Default for Theme { 38 | fn default() -> Self { 39 | macro_rules! default_theme_file { 40 | ($file:literal) => { 41 | include_bytes!(concat!("../../themes/mdbook/", $file)).to_vec() 42 | }; 43 | } 44 | 45 | Self { 46 | index: default_theme_file!("index.hbs"), 47 | head: default_theme_file!("head.hbs"), 48 | header: default_theme_file!("header.hbs"), 49 | typst_load_trampoline: default_theme_file!("typst-load-trampoline.hbs"), 50 | typst_load_html_trampoline: default_theme_file!("typst-load-html-trampoline.hbs"), 51 | asset: ThemeAsset::Static(EmbeddedThemeAsset::MdBook), 52 | } 53 | } 54 | } 55 | 56 | impl Theme { 57 | /// Creates a `Theme` from the given `theme_dir`. 58 | /// If a file is found in the theme dir, it will override the default 59 | /// version. 60 | pub fn new(theme_dir: &Path) -> Result { 61 | let mut theme = Self { 62 | asset: ThemeAsset::Dir(theme_dir.into()), 63 | ..Default::default() 64 | }; 65 | 66 | // If the theme directory doesn't exist there's no point continuing... 67 | if !theme_dir.exists() || !theme_dir.is_dir() { 68 | return Err(error_once!( 69 | "Theme directory doesn't exist", 70 | theme_dir: theme_dir.display(), 71 | )); 72 | } 73 | 74 | // Check for individual files, if they exist copy them across 75 | { 76 | let files = vec![ 77 | ("index.hbs", &mut theme.index), 78 | ("head.hbs", &mut theme.head), 79 | ("header.hbs", &mut theme.header), 80 | ( 81 | "typst-load-trampoline.hbs", 82 | &mut theme.typst_load_trampoline, 83 | ), 84 | ( 85 | "typst-load-html-trampoline.hbs", 86 | &mut theme.typst_load_html_trampoline, 87 | ), 88 | ]; 89 | 90 | let load_with_warn = |filename: &str, dest: &mut Vec| { 91 | let file_path = theme_dir.join(filename); 92 | if !file_path.exists() { 93 | // Don't warn if the file doesn't exist. 94 | return false; 95 | } 96 | if let Err(e) = load_file_contents(&file_path, dest) { 97 | warn!("Couldn't load custom file, {}: {}", file_path.display(), e); 98 | false 99 | } else { 100 | true 101 | } 102 | }; 103 | 104 | for (filename, dest) in files { 105 | load_with_warn(filename, dest); 106 | } 107 | } 108 | 109 | // let fonts_dir = theme_dir.join("fonts"); 110 | // ... 111 | 112 | Ok(theme) 113 | } 114 | 115 | pub fn is_static(&self) -> bool { 116 | matches!(self.asset, ThemeAsset::Static(_)) 117 | } 118 | 119 | pub fn copy_assets(&self, dest_dir: &Path) -> Result<()> { 120 | if !dest_dir.exists() { 121 | log::debug!( 122 | "{} does not exist, creating the directory", 123 | dest_dir.display() 124 | ); 125 | utils::create_dirs(dest_dir)?; 126 | } 127 | 128 | match &self.asset { 129 | ThemeAsset::Static(EmbeddedThemeAsset::MdBook) => { 130 | copy_dir_embedded( 131 | include_dir!("$CARGO_MANIFEST_DIR/../themes/mdbook/css"), 132 | dest_dir.join("css"), 133 | )?; 134 | copy_dir_embedded( 135 | include_dir!("$CARGO_MANIFEST_DIR/../themes/mdbook/FontAwesome/css"), 136 | dest_dir.join("FontAwesome/css"), 137 | )?; 138 | copy_dir_embedded( 139 | include_dir!("$CARGO_MANIFEST_DIR/../themes/mdbook/FontAwesome/fonts"), 140 | dest_dir.join("FontAwesome/fonts"), 141 | )?; 142 | write_file( 143 | dest_dir.join("index.js"), 144 | include_bytes!("../../themes/mdbook/index.js"), 145 | )?; 146 | } 147 | ThemeAsset::Dir(theme_dir) => { 148 | utils::copy_dir_all(theme_dir, dest_dir) 149 | .map_err(error_once_map!("copy_theme_directory"))?; 150 | } 151 | } 152 | 153 | Ok(()) 154 | } 155 | } 156 | 157 | /// Checks if a file exists, if so, the destination buffer will be filled with 158 | /// its contents. 159 | fn load_file_contents>(filename: P, dest: &mut Vec) -> std::io::Result<()> { 160 | let filename = filename.as_ref(); 161 | 162 | let mut buffer = Vec::new(); 163 | File::open(filename)?.read_to_end(&mut buffer)?; 164 | 165 | // We needed the buffer so we'd only overwrite the existing content if we 166 | // could successfully load the file into memory. 167 | dest.clear(); 168 | dest.append(&mut buffer); 169 | 170 | Ok(()) 171 | } 172 | -------------------------------------------------------------------------------- /cli/src/tui.rs: -------------------------------------------------------------------------------- 1 | //! Some code are from typst/typst/crates/cli 2 | 3 | #![allow(unused)] 4 | 5 | use core::fmt; 6 | use std::io::{self, IsTerminal, Write}; 7 | use std::sync::OnceLock; 8 | 9 | use termcolor::{Color, ColorChoice, ColorSpec, WriteColor}; 10 | 11 | const PREFIX_LEN: usize = 12; 12 | 13 | /// Returns a handle to the optionally colored terminal output. 14 | pub fn init_out(choice: clap::ColorChoice) -> &'static termcolor::StandardStream { 15 | static OUT: OnceLock = OnceLock::new(); 16 | 17 | OUT.get_or_init(|| { 18 | termcolor::StandardStream::stderr(match choice { 19 | clap::ColorChoice::Auto if std::io::stderr().is_terminal() => ColorChoice::Auto, 20 | clap::ColorChoice::Always => ColorChoice::Always, 21 | _ => ColorChoice::Never, 22 | }) 23 | }) 24 | } 25 | 26 | /// Returns a handle to the optionally colored terminal output. 27 | pub fn out() -> &'static termcolor::StandardStream { 28 | init_out(clap::ColorChoice::Auto) 29 | } 30 | 31 | /// Clears the entire screen. 32 | pub fn clear() -> io::Result<()> { 33 | let out = out(); 34 | 35 | // We don't want to clear anything that is not a TTY. 36 | if out.supports_color() { 37 | let mut stream = out.lock(); 38 | // Clear the screen and then move the cursor to the top left corner. 39 | write!(stream, "\x1B[2J\x1B[1;1H")?; 40 | stream.flush()?; 41 | } 42 | 43 | Ok(()) 44 | } 45 | 46 | #[macro_export] 47 | macro_rules! tui_error { 48 | (h$prefix:literal, $( $arg:tt )*) => { $crate::tui_msg!(Error, $prefix, $($arg)*) }; 49 | ($( $arg:tt )*) => { $crate::tui_msg!(Error, "Error:", $($arg)*) }; 50 | } 51 | #[macro_export] 52 | macro_rules! tui_warn { 53 | (h$prefix:literal, $( $arg:tt )*) => { $crate::tui_msg!(Warn, $prefix, $($arg)*) }; 54 | ($( $arg:tt )*) => { $crate::tui_msg!(Warn, "Warn:", $($arg)*) }; 55 | } 56 | #[macro_export] 57 | macro_rules! tui_info { 58 | (h$prefix:literal, $( $arg:tt )*) => { $crate::tui_msg!(Info, $prefix, $($arg)*) }; 59 | ($( $arg:tt )*) => { $crate::tui_msg!(Info, "Info:", $($arg)*) }; 60 | } 61 | #[macro_export] 62 | macro_rules! tui_hint { 63 | (h$prefix:literal, $( $arg:tt )*) => { $crate::tui_msg!(Hint, $prefix, $($arg)*) }; 64 | ($( $arg:tt )*) => { $crate::tui_msg!(Hint, "Hint:", $($arg)*) }; 65 | } 66 | #[macro_export] 67 | macro_rules! tui_msg { 68 | ($level:ident, $prefix:literal, $($arg:tt)*) => { $crate::tui::msg($crate::tui::Level::$level, $prefix, format_args!($($arg)*)) }; 69 | } 70 | 71 | pub enum Level { 72 | Error, 73 | Warn, 74 | Info, 75 | Hint, 76 | } 77 | 78 | pub fn msg(level: Level, prefix: &str, msg: fmt::Arguments) { 79 | let mut out = out().lock(); 80 | 81 | let header = ColorSpec::new().set_bold(true).set_intense(true).clone(); 82 | let header = match level { 83 | Level::Error => header.clone().set_fg(Some(Color::Red)).clone(), 84 | Level::Warn => header.clone().set_fg(Some(Color::Yellow)).clone(), 85 | Level::Info => header.clone().set_fg(Some(Color::Green)).clone(), 86 | Level::Hint => header.clone().set_fg(Some(Color::Cyan)).clone(), 87 | }; 88 | 89 | // todo: handle errors in these not important io. 90 | let _ = out.set_color(&header); 91 | let _ = write!(out, "{prefix:>PREFIX_LEN$}"); 92 | let _ = out.reset(); 93 | let _ = write!(out, " {msg}"); 94 | let _ = writeln!(out); 95 | } 96 | -------------------------------------------------------------------------------- /cli/src/utils.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::path::{Path, PathBuf}; 3 | use std::sync::LazyLock; 4 | use std::{fs, io}; 5 | 6 | use reflexo_typst::error::prelude::*; 7 | use reflexo_typst::TypstSystemWorld; 8 | use regex::Regex; 9 | use tokio::runtime::Builder; 10 | 11 | /// Replaces multiple consecutive whitespace characters with a single space 12 | /// character. 13 | pub fn collapse_whitespace(text: &str) -> Cow<'_, str> { 14 | static RE: LazyLock = LazyLock::new(|| Regex::new(r"\s\s+").unwrap()); 15 | RE.replace_all(text, " ") 16 | } 17 | 18 | pub fn async_continue>(f: F) -> ! { 19 | Builder::new_multi_thread() 20 | .enable_all() 21 | .build() 22 | .unwrap() 23 | .block_on(f); 24 | 25 | #[allow(unreachable_code)] 26 | { 27 | unreachable!("The async command must exit the process."); 28 | } 29 | } 30 | 31 | pub fn exit_with_error(err: E) -> ! { 32 | clap::Error::raw( 33 | clap::error::ErrorKind::ValueValidation, 34 | format!("shiroa error: {}", err), 35 | ) 36 | .exit() 37 | } 38 | 39 | pub trait UnwrapOrExit { 40 | fn unwrap_or_exit(self) -> T; 41 | } 42 | 43 | impl UnwrapOrExit for Result { 44 | fn unwrap_or_exit(self) -> T { 45 | self.map_err(exit_with_error).unwrap() 46 | } 47 | } 48 | 49 | pub fn current_dir() -> PathBuf { 50 | std::env::current_dir().unwrap_or_exit() 51 | } 52 | 53 | pub fn make_absolute_from(path: &Path, relative_to: impl FnOnce() -> PathBuf) -> PathBuf { 54 | if path.is_absolute() { 55 | path.to_owned() 56 | } else { 57 | relative_to().join(path) 58 | } 59 | } 60 | 61 | pub fn make_absolute(path: &Path) -> PathBuf { 62 | make_absolute_from(path, current_dir) 63 | } 64 | 65 | /// 66 | pub fn copy_dir_all(src: impl AsRef, dst: impl AsRef) -> io::Result<()> { 67 | fs::create_dir_all(&dst)?; 68 | for entry in fs::read_dir(src)? { 69 | let entry = entry?; 70 | let ty = entry.file_type()?; 71 | if ty.is_dir() { 72 | copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?; 73 | } else { 74 | fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?; 75 | } 76 | } 77 | Ok(()) 78 | } 79 | 80 | pub fn create_dirs>(path: P) -> Result<()> { 81 | let path = path.as_ref(); 82 | if path.exists() { 83 | return Ok(()); 84 | } 85 | 86 | fs::create_dir_all(path).map_err(error_once_map!("create_dirs")) 87 | } 88 | 89 | pub fn write_file, C: AsRef<[u8]>>(path: P, contents: C) -> Result<()> { 90 | let path = path.as_ref(); 91 | if path.exists() { 92 | if !path.is_file() { 93 | return Err(error_once!("Cannot write file: not a file at path", path: path.display())); 94 | } 95 | // todo: check mtime 96 | // check content 97 | if fs::read(path).map_err(error_once_map!("write_file: read"))? == contents.as_ref() { 98 | return Ok(()); 99 | } 100 | } 101 | 102 | fs::write(path, contents.as_ref()).map_err(error_once_map!("write_file: write")) 103 | } 104 | 105 | pub fn copy_dir_embedded(src: include_dir::Dir, dst: impl AsRef) -> Result<()> { 106 | for entry in src.files() { 107 | let t = dst.as_ref().join(entry.path()); 108 | create_dirs(t.parent().unwrap())?; 109 | write_file(t, entry.contents())?; 110 | } 111 | Ok(()) 112 | } 113 | 114 | fn release_packages_inner(world: &mut TypstSystemWorld, pkg: include_dir::Dir, no_override: bool) { 115 | fn get_string(v: &toml::Value) -> &str { 116 | match v { 117 | toml::Value::String(table) => table, 118 | _ => unreachable!(), 119 | } 120 | } 121 | 122 | let manifest = pkg.get_file("typst.toml").unwrap().contents_utf8().unwrap(); 123 | let manifest: toml::Table = toml::from_str(manifest).unwrap(); 124 | 125 | let pkg_info = match manifest.get("package").unwrap() { 126 | toml::Value::Table(table) => table, 127 | _ => unreachable!(), 128 | }; 129 | 130 | let name = get_string(pkg_info.get("name").unwrap()); 131 | let version = get_string(pkg_info.get("version").unwrap()); 132 | 133 | let pkg_dirname = format!("{}/{}", name, version); 134 | 135 | let local_path = world.registry.local_path().unwrap(); 136 | let pkg_link_target = make_absolute(&local_path.join("preview").join(&pkg_dirname)); 137 | 138 | if pkg_link_target.exists() { 139 | eprintln!("package {pkg_dirname} already exists"); 140 | if no_override { 141 | return; 142 | } 143 | } 144 | 145 | std::fs::create_dir_all(pkg_link_target.parent().unwrap()).unwrap(); 146 | copy_dir_embedded(pkg, &pkg_link_target).unwrap(); 147 | } 148 | 149 | pub fn release_packages(world: &mut TypstSystemWorld, pkg: include_dir::Dir) { 150 | release_packages_inner(world, pkg, false); 151 | } 152 | -------------------------------------------------------------------------------- /cli/src/version.rs: -------------------------------------------------------------------------------- 1 | use std::{fmt::Display, process::exit}; 2 | 3 | use crate::build_info::VERSION; 4 | use clap::ValueEnum; 5 | 6 | /// Available version formats for `$program -VV` 7 | #[derive(ValueEnum, Debug, Clone)] 8 | #[value(rename_all = "kebab-case")] 9 | pub enum VersionFormat { 10 | None, 11 | Short, 12 | Features, 13 | Full, 14 | Json, 15 | JsonPlain, 16 | } 17 | 18 | impl Display for VersionFormat { 19 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 20 | f.write_str(self.to_possible_value().unwrap().get_name()) 21 | } 22 | } 23 | 24 | /// Version information 25 | #[derive(serde::Serialize, serde::Deserialize)] 26 | struct VersionInfo { 27 | name: &'static str, 28 | version: &'static str, 29 | features: Vec<&'static str>, 30 | 31 | program_semver: &'static str, 32 | program_commit_hash: &'static str, 33 | program_target_triple: &'static str, 34 | program_opt_level: &'static str, 35 | program_build_timestamp: &'static str, 36 | 37 | rustc_semver: &'static str, 38 | rustc_commit_hash: &'static str, 39 | rustc_host_triple: &'static str, 40 | rustc_channel: &'static str, 41 | rustc_llvm_version: &'static str, 42 | } 43 | 44 | impl VersionInfo { 45 | fn new() -> Self { 46 | Self { 47 | // todo: global app name 48 | name: "shiroa", 49 | version: VERSION, 50 | features: env!("VERGEN_CARGO_FEATURES").split(',').collect::>(), 51 | 52 | program_semver: env!("VERGEN_GIT_DESCRIBE"), 53 | program_commit_hash: env!("VERGEN_GIT_SHA"), 54 | program_target_triple: env!("VERGEN_CARGO_TARGET_TRIPLE"), 55 | program_opt_level: env!("VERGEN_CARGO_OPT_LEVEL"), 56 | program_build_timestamp: env!("VERGEN_BUILD_TIMESTAMP"), 57 | 58 | rustc_semver: env!("VERGEN_RUSTC_SEMVER"), 59 | rustc_commit_hash: env!("VERGEN_RUSTC_COMMIT_HASH"), 60 | rustc_host_triple: env!("VERGEN_RUSTC_HOST_TRIPLE"), 61 | rustc_channel: env!("VERGEN_RUSTC_CHANNEL"), 62 | rustc_llvm_version: env!("VERGEN_RUSTC_LLVM_VERSION"), 63 | } 64 | } 65 | 66 | fn program_build(&self) -> String { 67 | format!( 68 | "{} with opt level({}) at {}", 69 | self.program_target_triple, self.program_opt_level, self.program_build_timestamp 70 | ) 71 | } 72 | 73 | fn rustc_build(&self) -> String { 74 | format!( 75 | "{}-{} with LLVM {}", 76 | self.rustc_host_triple, self.rustc_channel, self.rustc_llvm_version 77 | ) 78 | } 79 | } 80 | 81 | impl Default for VersionInfo { 82 | fn default() -> Self { 83 | Self::new() 84 | } 85 | } 86 | 87 | /// Print version information and exit if `-VV` is present 88 | pub fn intercept_version(v: bool, f: VersionFormat) { 89 | let f = match f { 90 | VersionFormat::None if v => VersionFormat::Short, 91 | VersionFormat::None => return, 92 | _ => f, 93 | }; 94 | let version_info = VersionInfo::new(); 95 | match f { 96 | VersionFormat::Full => print_full_version(version_info), 97 | VersionFormat::Features => println!("{}", version_info.features.join(",")), 98 | VersionFormat::Json => { 99 | println!("{}", serde_json::to_string_pretty(&version_info).unwrap()) 100 | } 101 | VersionFormat::JsonPlain => println!("{}", serde_json::to_string(&version_info).unwrap()), 102 | _ => print_short_version(version_info), 103 | } 104 | exit(0); 105 | } 106 | 107 | fn print_full_version(vi: VersionInfo) { 108 | let program_semver = vi.program_semver; 109 | let program_commit_hash = vi.program_commit_hash; 110 | let program_build = vi.program_build(); 111 | 112 | let rustc_semver = vi.rustc_semver; 113 | let rustc_commit_hash = vi.rustc_commit_hash; 114 | let rustc_build = vi.rustc_build(); 115 | 116 | print_short_version(vi); 117 | println!( 118 | r##" 119 | program-ver: {program_semver} 120 | program-rev: {program_commit_hash} 121 | program-build: {program_build} 122 | 123 | rustc-ver: {rustc_semver} 124 | rustc-rev: {rustc_commit_hash} 125 | rustc-build: {rustc_build}"## 126 | ); 127 | } 128 | 129 | fn print_short_version(vi: VersionInfo) { 130 | let name = vi.name; 131 | let version = vi.version; 132 | let features = vi 133 | .features 134 | .iter() 135 | .copied() 136 | .filter(|&s| s != "default" && !s.ends_with("_exporter")) 137 | .collect::>() 138 | .join(" "); 139 | 140 | println!( 141 | r##"{name} version {version} 142 | features: {features}"## 143 | ); 144 | } 145 | -------------------------------------------------------------------------------- /contrib/typst/gh-ebook.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/shiroa:0.2.3": * 2 | #import "/contrib/typst/gh-pages.typ": project, part-style 3 | 4 | #let _page-project = project 5 | 6 | #let _resolve-inclusion-state = state("_resolve-inclusion", none) 7 | 8 | #let resolve-inclusion(inc) = _resolve-inclusion-state.update(it => inc) 9 | 10 | #let project(title: "", authors: (), spec: "", content) = { 11 | // Set document metadata early 12 | set document( 13 | author: authors, 14 | title: title, 15 | ) 16 | 17 | // Inherit from gh-pages 18 | show: _page-project 19 | 20 | if title != "" { 21 | heading(title) 22 | } 23 | 24 | context { 25 | let inc = _resolve-inclusion-state.final() 26 | external-book(spec: inc(spec)) 27 | 28 | let mt = book-meta-state.final() 29 | let styles = (inc: inc, part: part-style, chapter: it => it) 30 | 31 | if mt != none { 32 | mt.summary.map(it => visit-summary(it, styles)).sum() 33 | } 34 | } 35 | 36 | content 37 | } 38 | -------------------------------------------------------------------------------- /contrib/typst/gh-pages.typ: -------------------------------------------------------------------------------- 1 | // This is important for shiroa to produce a responsive layout 2 | // and multiple targets. 3 | #import "@preview/shiroa:0.2.3": ( 4 | get-page-width, 5 | target, 6 | is-web-target, 7 | is-pdf-target, 8 | is-html-target, 9 | plain-text, 10 | shiroa-sys-target, 11 | templates, 12 | ) 13 | #import templates: * 14 | #import "@preview/zebraw:0.5.2": zebraw-init, zebraw 15 | 16 | // Metadata 17 | #let page-width = get-page-width() 18 | #let is-html-target = is-html-target() 19 | #let is-pdf-target = is-pdf-target() 20 | #let is-web-target = is-web-target() 21 | #let sys-is-html-target = ("target" in dictionary(std)) 22 | 23 | /// Creates an embedded block typst frame. 24 | #let div-frame(content, attrs: (:), tag: "div") = html.elem(tag, html.frame(content), attrs: attrs) 25 | #let span-frame = div-frame.with(tag: "span") 26 | #let p-frame = div-frame.with(tag: "p") 27 | 28 | // Theme (Colors) 29 | #let ( 30 | style: theme-style, 31 | is-dark: is-dark-theme, 32 | is-light: is-light-theme, 33 | main-color: main-color, 34 | dash-color: dash-color, 35 | code-extra-colors: code-extra-colors, 36 | ) = book-theme-from(toml("theme-style.toml"), xml: it => xml(it)) 37 | 38 | // Fonts 39 | #let main-font = ( 40 | "Charter", 41 | "Source Han Serif SC", 42 | // "Source Han Serif TC", 43 | // shiroa's embedded font 44 | "Libertinus Serif", 45 | ) 46 | #let code-font = ( 47 | "BlexMono Nerd Font Mono", 48 | // shiroa's embedded font 49 | "DejaVu Sans Mono", 50 | ) 51 | 52 | // Sizes 53 | #let main-size = if is-web-target { 54 | 16pt 55 | } else { 56 | 10.5pt 57 | } 58 | #let heading-sizes = if is-web-target { 59 | (2, 1.5, 1.17, 1, 0.83).map(it => it * main-size) 60 | } else { 61 | (26pt, 22pt, 14pt, 12pt, main-size) 62 | } 63 | #let list-indent = 0.5em 64 | 65 | /// The project function defines how your document looks. 66 | /// It takes your content and some metadata and formats it. 67 | /// Go ahead and customize it to your liking! 68 | #let project(title: "Typst Book", authors: (), kind: "page", body) = { 69 | // set basic document metadata 70 | set document( 71 | author: authors, 72 | title: title, 73 | ) if not is-pdf-target 74 | 75 | // set web/pdf page properties 76 | set page( 77 | numbering: none, 78 | number-align: center, 79 | width: page-width, 80 | ) if not (sys-is-html-target or is-html-target) 81 | 82 | // remove margins for web target 83 | set page( 84 | margin: ( 85 | // reserved beautiful top margin 86 | top: 20pt, 87 | // reserved for our heading style. 88 | // If you apply a different heading style, you may remove it. 89 | left: 20pt, 90 | // Typst is setting the page's bottom to the baseline of the last line of text. So bad :(. 91 | bottom: 0.5em, 92 | // remove rest margins. 93 | rest: 0pt, 94 | ), 95 | height: auto, 96 | ) if is-web-target and not is-html-target 97 | 98 | // Set main text 99 | set text( 100 | font: main-font, 101 | size: main-size, 102 | fill: main-color, 103 | lang: "en", 104 | ) 105 | 106 | // Set main spacing 107 | set enum( 108 | indent: list-indent * 0.618, 109 | body-indent: list-indent, 110 | ) 111 | set list( 112 | indent: list-indent * 0.618, 113 | body-indent: list-indent, 114 | ) 115 | set par(leading: 0.7em) 116 | set block(spacing: 0.7em * 1.5) 117 | 118 | // Set text, spacing for headings 119 | // Render a dash to hint headings instead of bolding it as well if it's for web. 120 | show heading: set text(weight: "regular") if is-web-target 121 | show heading: it => { 122 | let it = { 123 | set text(size: heading-sizes.at(it.level)) 124 | if is-web-target { 125 | heading-hash(it, hash-color: dash-color) 126 | } 127 | it 128 | } 129 | 130 | block( 131 | spacing: 0.7em * 1.5 * 1.2, 132 | below: 0.7em * 1.2, 133 | it, 134 | ) 135 | } 136 | 137 | // link setting 138 | show link: set text(fill: dash-color) 139 | 140 | // math setting 141 | show math.equation: set text(weight: 400) 142 | show math.equation.where(block: true): it => context if shiroa-sys-target() == "html" { 143 | p-frame(attrs: ("class": "block-equation"), it) 144 | } else { 145 | it 146 | } 147 | show math.equation.where(block: false): it => context if shiroa-sys-target() == "html" { 148 | span-frame(attrs: (class: "inline-equation"), it) 149 | } else { 150 | it 151 | } 152 | 153 | /// HTML code block supported by zebraw. 154 | show: if is-dark-theme { 155 | zebraw-init.with( 156 | // should vary by theme 157 | background-color: if code-extra-colors.bg != none { 158 | (code-extra-colors.bg, code-extra-colors.bg) 159 | }, 160 | highlight-color: rgb("#3d59a1"), 161 | comment-color: rgb("#394b70"), 162 | lang-color: rgb("#3d59a1"), 163 | lang: false, 164 | numbering: false, 165 | ) 166 | } else { 167 | zebraw-init.with(lang: false, numbering: false) 168 | } 169 | 170 | // code block setting 171 | set raw(theme: theme-style.code-theme) if theme-style.code-theme.len() > 0 172 | show raw: set text(font: code-font) 173 | show raw.where(block: true): it => context if shiroa-sys-target() == "paged" { 174 | rect( 175 | width: 100%, 176 | inset: (x: 4pt, y: 5pt), 177 | radius: 4pt, 178 | fill: code-extra-colors.bg, 179 | [ 180 | #set text(fill: code-extra-colors.fg) if code-extra-colors.fg != none 181 | #set par(justify: false) 182 | // #place(right, text(luma(110), it.lang)) 183 | #it 184 | ], 185 | ) 186 | } else { 187 | set text(fill: code-extra-colors.fg) if code-extra-colors.fg != none 188 | set par(justify: false) 189 | zebraw( 190 | block-width: 100%, 191 | // line-width: 100%, 192 | wrap: false, 193 | it, 194 | ) 195 | } 196 | 197 | // Put your custom CSS here. 198 | context if shiroa-sys-target() == "html" { 199 | html.elem( 200 | "style", 201 | ```css 202 | .inline-equation { 203 | display: inline-block; 204 | width: fit-content; 205 | } 206 | .block-equation { 207 | display: grid; 208 | place-items: center; 209 | overflow-x: auto; 210 | } 211 | ```.text, 212 | ) 213 | } 214 | 215 | // Main body. 216 | set par(justify: true) 217 | 218 | body 219 | } 220 | 221 | #let part-style = heading 222 | -------------------------------------------------------------------------------- /contrib/typst/theme-style.toml: -------------------------------------------------------------------------------- 1 | 2 | [light] 3 | color-scheme = "light" 4 | main-color = "#000" 5 | dash-color = "#20609f" 6 | code-theme = "" 7 | 8 | [rust] 9 | color-scheme = "light" 10 | main-color = "#262625" 11 | dash-color = "#2b79a2" 12 | code-theme = "" 13 | 14 | [coal] 15 | color-scheme = "dark" 16 | main-color = "#98a3ad" 17 | dash-color = "#2b79a2" 18 | code-theme = "tokyo-night.tmTheme" 19 | 20 | [navy] 21 | color-scheme = "dark" 22 | main-color = "#bcbdd0" 23 | dash-color = "#2b79a2" 24 | code-theme = "tokyo-night.tmTheme" 25 | 26 | [ayu] 27 | color-scheme = "dark" 28 | main-color = "#c5c5c5" 29 | dash-color = "#0096cf" 30 | code-theme = "tokyo-night.tmTheme" 31 | -------------------------------------------------------------------------------- /contrib/typst/tidy-book/lib.typ: -------------------------------------------------------------------------------- 1 | // https://github.com/Jollywatt/arrow-diagrams 2 | -------------------------------------------------------------------------------- /dist-workspace.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["cargo:."] 3 | 4 | # Config for 'dist' 5 | [dist] 6 | # The preferred dist version to use in CI (Cargo.toml SemVer syntax) 7 | cargo-dist-version = "0.28.0-tinymist.2" 8 | cargo-dist-url-override = "https://github.com/Myriad-Dreamin/cargo-dist/releases/download/v0.28.0-tinymist.2" 9 | # CI backends to support 10 | ci = "github" 11 | # The installers to generate for each app 12 | installers = ["shell", "powershell"] 13 | # The archive format to use for windows builds (defaults .zip) 14 | windows-archive = ".zip" 15 | # The archive format to use for non-windows builds (defaults .tar.xz) 16 | unix-archive = ".tar.gz" 17 | # Target platforms to build apps for (Rust target-triple syntax) 18 | # These targets are determined based on the awesome ruff project... 19 | targets = [ 20 | "aarch64-apple-darwin", 21 | "aarch64-pc-windows-msvc", 22 | "aarch64-unknown-linux-gnu", 23 | "aarch64-unknown-linux-musl", 24 | "arm-unknown-linux-musleabihf", 25 | "armv7-unknown-linux-gnueabihf", 26 | "armv7-unknown-linux-musleabihf", 27 | # "i686-pc-windows-msvc", 28 | # "i686-unknown-linux-gnu", 29 | # "i686-unknown-linux-musl", 30 | # "powerpc64-unknown-linux-gnu", 31 | # "powerpc64le-unknown-linux-gnu", 32 | "x86_64-apple-darwin", 33 | "x86_64-pc-windows-msvc", 34 | "x86_64-unknown-linux-gnu", 35 | "x86_64-unknown-linux-musl", 36 | # "riscv64gc-unknown-linux-gnu", 37 | "riscv64gc-unknown-linux-musl", 38 | "loongarch64-unknown-linux-gnu", 39 | "loongarch64-unknown-linux-musl", 40 | ] 41 | # Whether to auto-include files like READMEs, LICENSEs, and CHANGELOGs (default true) 42 | auto-includes = false 43 | # Whether dist should create a Github Release or use an existing draft 44 | create-release = false 45 | # Which actions to run on pull requests 46 | pr-run-mode = "upload" 47 | # Whether CI should trigger releases with dispatches instead of tag pushes 48 | dispatch-releases = true 49 | # Whether to install an updater program 50 | install-updater = false 51 | # Path that installers should place binaries in 52 | install-path = ["$XDG_BIN_HOME/", "$XDG_DATA_HOME/../bin", "~/.local/bin"] 53 | 54 | [dist.github-custom-runners] 55 | global = "ubuntu-22.04" 56 | aarch64-pc-windows-msvc = "windows-latest" 57 | aarch64-unknown-linux-gnu = "ubuntu-22.04" 58 | aarch64-unknown-linux-musl = "ubuntu-22.04" 59 | arm-unknown-linux-musleabihf = "ubuntu-22.04" 60 | armv7-unknown-linux-gnueabihf = "ubuntu-22.04" 61 | armv7-unknown-linux-musleabihf = "ubuntu-22.04" 62 | x86_64-unknown-linux-gnu = "ubuntu-22.04" 63 | x86_64-unknown-linux-musl = "ubuntu-22.04" 64 | # riscv64gc-unknown-linux-gnu = "ubuntu-22.04" 65 | riscv64gc-unknown-linux-musl = "ubuntu-22.04" 66 | loongarch64-unknown-linux-gnu = "ubuntu-22.04" 67 | loongarch64-unknown-linux-musl = "ubuntu-22.04" 68 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_STORE 3 | .idea 4 | .vscode 5 | 6 | dist/ 7 | node_modules/ 8 | !dist/book.mjs -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "shiroa", 3 | "version": "0.3.1-rc3", 4 | "description": "Typst Book.", 5 | "main": "dist/main.js", 6 | "repository": "https://github.com/Myriad-Dreamin/shiroa", 7 | "author": "Myriad-Dreamin", 8 | "contributors": [ 9 | "shiroa Developers" 10 | ], 11 | "scripts": { 12 | "build": "tsc && vite build", 13 | "dev": "vite build --watch", 14 | "link:local": "yarn link @myriaddreamin/typst.ts @myriaddreamin/typst-ts-renderer" 15 | }, 16 | "license": "Apache-2.0", 17 | "private": false, 18 | "dependencies": { 19 | "@myriaddreamin/typst-ts-renderer": "^0.6.0", 20 | "@myriaddreamin/typst.ts": "^0.6.0" 21 | }, 22 | "devDependencies": { 23 | "typescript": "^5.3.3", 24 | "vite": "^6.1.0", 25 | "vite-plugin-singlefile": "^2.2.0" 26 | } 27 | } -------------------------------------------------------------------------------- /frontend/src/global.d.ts: -------------------------------------------------------------------------------- 1 | import { TypstRenderer } from '@myriaddreamin/typst.ts/dist/esm/renderer'; 2 | 3 | declare global { 4 | interface Window { 5 | typstPathToRoot: string | undefined; 6 | typstGetRelatedElements: any; 7 | handleTypstLocation: any; 8 | getTypstTheme(): string; 9 | captureStack(): any; 10 | typstRerender?: (responsive?: boolean) => void; 11 | typstCheckAndRerender?: (responsive: boolean, stack?: any) => Promise; 12 | typstChangeTheme?: () => Promise; 13 | debounce(fn: T, delay = 200): T; 14 | assignSemaHash: (u: number, x: number, y: number) => void; 15 | typstProcessSvg: any; 16 | typstBookRenderHtmlPage(relPath: string, appContainer: HTMLDivElement | undefined); 17 | typstBookRenderPage( 18 | plugin: TypstSvgRenderer, 19 | relPath: string, 20 | appContainer: HTMLDivElement | undefined, 21 | ); 22 | updateHovers: (elem: Element[]) => void; 23 | typstBindSvgDom(elem: HTMLDivElement, dom: SVGSVGElement); 24 | TypstRenderModule: any; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /frontend/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "types": ["./src/global.d.ts"], 4 | "baseUrl": ".", 5 | "module": "ESNext", 6 | "target": "ES2020", 7 | "skipLibCheck": true, 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "bundler", 11 | "importHelpers": true, 12 | "isolatedModules": true, 13 | "strictNullChecks": true, 14 | "stripInternal": true, 15 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 16 | "rootDir": "src", 17 | "outDir": "dist", 18 | "declaration": true, 19 | "declarationMap": true, 20 | "inlineSources": true, 21 | "inlineSourceMap": true, 22 | "composite": true 23 | }, 24 | "include": ["src"] 25 | } 26 | -------------------------------------------------------------------------------- /frontend/vite.config.mjs: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { defineConfig } from 'vite'; 3 | import { viteSingleFile } from "vite-plugin-singlefile" 4 | 5 | export default defineConfig({ 6 | plugins: [viteSingleFile()], 7 | resolve: { 8 | preserveSymlinks: true, // this is the fix! 9 | }, 10 | build: { 11 | minify: false, 12 | cssCodeSplit: false, 13 | lib: { 14 | // Could also be a dictionary or array of multiple entry points 15 | entry: resolve(__dirname, 'src/main.ts'), 16 | name: 'TypstBook', 17 | // the proper extensions will be added 18 | fileName: 'book', 19 | }, 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /github-pages/.gitignore: -------------------------------------------------------------------------------- 1 | dist -------------------------------------------------------------------------------- /github-pages/docs/book.typ: -------------------------------------------------------------------------------- 1 | 2 | #import "@preview/shiroa:0.2.3": * 3 | 4 | #show: book 5 | 6 | #book-meta( 7 | title: "shiroa", 8 | description: "shiroa Documentation", 9 | repository: "https://github.com/Myriad-Dreamin/shiroa", 10 | repository-edit: "https://github.com/Myriad-Dreamin/shiroa/edit/main/github-pages/docs/{path}", 11 | authors: ("Myriad-Dreamin", "7mile"), 12 | language: "en", 13 | summary: [ 14 | // begin of summary 15 | #prefix-chapter("introduction.typ")[Introduction] 16 | = User Guide 17 | - #chapter("guide/installation.typ")[Installation] 18 | - #chapter("guide/get-started.typ")[Get Started] 19 | - #chapter("guide/faq.typ")[Frequently Asked Questions] 20 | - #chapter(none)[Further reading] 21 | = Reference Guide 22 | - #chapter("cli/main.typ")[Command Line Tool] 23 | - #chapter("cli/init.typ")[init] 24 | - #chapter("cli/build.typ")[build] 25 | - #chapter("cli/serve.typ")[serve] 26 | - #chapter("cli/clean.typ")[clean] 27 | - #chapter("cli/completions.typ")[completions] 28 | - #chapter("format/main.typ")[Format] 29 | - #chapter("format/book.typ")[book.typ] 30 | - #chapter("format/book-meta.typ")[Book Metadata] 31 | - #chapter(none)[Draft chapter] 32 | // - #chapter(none)[chapter with - markers] 33 | // - #chapter(none, "= Introduction") 34 | // - #chapter(none)[#text("= Introduction")] 35 | - #chapter("format/build-meta.typ")[Build Metadata] 36 | - #chapter("format/theme.typ")[Theme] 37 | - #chapter("supports.typ")[Typst Support] 38 | - #chapter("supports/cross-ref.typ")[Cross Reference] 39 | - #chapter("supports/cross-ref-sample.typ")[Cross Reference Sample] 40 | - #chapter("supports/embed-html.typ")[Embed Sanitized HTML Elements] 41 | - #chapter("supports/multimedia.typ")[Multimedia components] 42 | - #chapter("supports/sema-desc.typ")[Semantic Page Description] 43 | - #chapter("supports/render-test.typ")[Rendering Tests] 44 | - #chapter(none)[For developers] 45 | - #chapter(none)[Typst-side APIs] 46 | - #chapter(none)[shiroa CLI Internals] 47 | - #chapter(none)[Alternative Backends] 48 | // end of summary 49 | ], 50 | ) 51 | 52 | #build-meta(dest-dir: "../dist") 53 | 54 | #get-book-meta() 55 | 56 | // re-export page template 57 | #import "/contrib/typst/gh-pages.typ": project, heading-reference 58 | #let book-page = project 59 | #let cross-link = cross-link 60 | #let heading-reference = heading-reference 61 | -------------------------------------------------------------------------------- /github-pages/docs/cli/build.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | 3 | #show: book-page.with(title: "CLI Build Command") 4 | 5 | = The build command 6 | 7 | The build command is used to render your book: 8 | 9 | ```bash 10 | shiroa build 11 | ``` 12 | 13 | It will try to parse your `book.typ` file to understand the structure and metadata 14 | of your book and fetch the corresponding files. Note that files mentioned in `book.typ` 15 | but not present will be created. 16 | 17 | The rendered output will maintain the same directory structure as the source for 18 | convenience. Large books will therefore remain structured when rendered. 19 | 20 | == Specify a directory 21 | 22 | The `build` command can take a directory as an argument to use as the book's 23 | root instead of the current working directory. 24 | 25 | ```bash 26 | shiroa build path/to/book 27 | ``` 28 | 29 | === --workspace, -w 30 | 31 | *Note:* The workspace is a _typst-specific_ command. 32 | 33 | The `--workspace` option specifies the root directory of typst source files, which is like the `--root` option of `typst-cli`. It is interpreted relative to *current work directory of `shiroa` process*. 34 | 35 | For example. When a book is created with the main file `book-project1/book.typ`, and you want to access a template file with path `common/book-template.typ`, please build it with following command: 36 | 37 | ```bash 38 | shiroa build -w . book-project1 39 | ``` 40 | 41 | Then you can access the template with the absolute path in typst: 42 | 43 | ```typ 44 | #import "/common/book-template.typ": * 45 | ``` 46 | 47 | === --dest-dir, -d 48 | 49 | The `--dest-dir` (`-d`) option allows you to change the output directory for the 50 | book. Relative paths are interpreted relative to the book's root directory. If 51 | not specified it will default to the value of the `build.build-dir` key in 52 | `book.toml`, or to `./book`. 53 | 54 | === --path-to-root 55 | 56 | When your website's root is not exact serving the book, use `--path-to-root` to specify the path to the root of the book site. For example, if you own `myriad-dreamin.github.io` and have mounted the book to `/shiroa/`, you can access `https://myriad-dreamin.github.io/shiroa/cli/main.html` to get the generated content of `cli/main.typ`. 57 | 58 | ```bash 59 | shiroa build --path-to-root /shiroa/ book-project1 60 | ``` 61 | 62 | === --mode 63 | 64 | The `--mode` option allows you to specify the mode of rendering typst document. The default mode is `normal`. 65 | - (Default) `dynamic-paged`: dynamically render as paged document. 66 | - (Experimental) `static-html`: statically render the whole document, the embedded 67 | frames are not resizable. 68 | - (Todo) `static-html-static-paged`: statically render html parts as much as 69 | possible, and leave frames rendered dynamically. 70 | 71 | The dynamically rendering means that some elements will be rendered by a wasm renderer in the browser. 72 | 73 | === --theme 74 | 75 | Specify a theme directory to copy recursively. This allows you to use custom themes. 76 | 77 | The files will be copied to the `theme/` in the output directory. 78 | 79 | The default theme is located at #link("https://github.com/Myriad-Dreamin/shiroa/tree/main/themes/mdbook")[`themes/mdbook`]. You can start by copying this theme and modifying it to your needs. 80 | 81 | Currently, no interface is designed for custom themes, i.e. everything is working occasionally. If you have any questions, design or feature requests about theming, please open an issue in the repository. 82 | 83 | // todo: copy all rest files 84 | // ***Note:*** *The build command copies all files (excluding files with `.typ` extension) from the source directory into the build directory.* 85 | -------------------------------------------------------------------------------- /github-pages/docs/cli/clean.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | 3 | #show: book-page.with(title: "CLI Clean Command") 4 | 5 | = The clean command 6 | 7 | Not yet implemented. 8 | -------------------------------------------------------------------------------- /github-pages/docs/cli/completions.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | 3 | #show: book-page.with(title: "CLI Completions Command") 4 | 5 | = The completions command 6 | 7 | Not yet implemented. 8 | -------------------------------------------------------------------------------- /github-pages/docs/cli/init.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page, cross-link 2 | 3 | #show: book-page.with(title: "CLI Init Command") 4 | 5 | = The init command 6 | 7 | The `init` command will try to initialize your book to build your book successfully by default. It is hence including all of the #cross-link("/cli/build.typ")[options] from `build` command. 8 | 9 | For instance, Initialize a book to the directory `my-book`: 10 | 11 | ```bash 12 | shiroa init my-book/ 13 | shiroa build my-book/ 14 | ``` 15 | 16 | Initialize a book with specific typst workspace directory: 17 | 18 | ```bash 19 | shiroa init -w . my-book/ 20 | shiroa build -w . my-book/ 21 | ``` 22 | 23 | Initialize a book with specific `dest-dir`: 24 | 25 | ```bash 26 | shiroa init --dest-dir ../dist my-book/ 27 | shiroa build my-book/ # memoryized dest-dir 28 | ``` 29 | 30 | == Things to note 31 | 32 | The harder way, by creating the book without `init` command, your `book.typ` should at least provides a `book-meta`, as #cross-link("/guide/get-started.typ")[Get Started] shown. 33 | 34 | ```typ 35 | #import "@preview/shiroa:0.2.3": * 36 | #show: book 37 | 38 | #book-meta( 39 | title: "My Book" 40 | summary: [ 41 | = My Book 42 | ] 43 | ) 44 | ``` 45 | 46 | Your `template.typ` must import and respect the `get-page-width` and `target` variable from `@preview/shiroa:0.2.3` to this time. The two variables will be used by the tool for rendering responsive layout and multiple targets. 47 | 48 | ```typ 49 | #import "@preview/shiroa:0.2.3": get-page-width, target, is-web-target, is-pdf-target 50 | 51 | // Metadata 52 | #let page-width = get-page-width() 53 | #let is-html-target = is-html-target() // target.starts-with("html") 54 | #let is-pdf-target = is-pdf-target() // target.starts-with("pdf") 55 | #let is-web-target = is-web-target() // target.starts-with("web") or target.starts-with("html") 56 | 57 | #let project(body) = { 58 | // set web/pdf page properties 59 | set page( 60 | width: page-width, 61 | // for a website, we don't need pagination. 62 | height: auto, 63 | ) 64 | 65 | // remove margins for web target 66 | set page(margin: ( 67 | // reserved beautiful top margin 68 | top: 20pt, 69 | // Typst is setting the page's bottom to the baseline of the last line of text. So bad :(. 70 | bottom: 0.5em, 71 | // remove rest margins. 72 | rest: 0pt, 73 | )) if is-web-target; 74 | 75 | body 76 | } 77 | ``` 78 | -------------------------------------------------------------------------------- /github-pages/docs/cli/main.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page, cross-link 2 | 3 | #show: book-page.with(title: "Command Line Tool") 4 | 5 | = Command Line Tool 6 | 7 | // todo: cross link 8 | The `shiroa` command-line tool is used to create and build books. 9 | After you have #cross-link("/guide/installation.typ")[installed] `shiroa`, you can run the `shiroa help` command in your terminal to view the available commands. 10 | 11 | This following sections provide in-depth information on the different commands available. 12 | 13 | // todo: cross link 14 | - #cross-link("/cli/init.typ")[`shiroa init `] — Creates a new book with minimal boilerplate to start with. 15 | - #cross-link("/cli/build.typ")[`shiroa build`] — Renders the book. 16 | - #cross-link("/cli/serve.typ")[`shiroa serve`] — Runs a web server to view the book, and rebuilds on changes. 17 | - #cross-link("/cli/clean.typ")[`shiroa clean`] — Deletes the rendered output. 18 | - #cross-link("/cli/completions.typ")[`shiroa completions`] — Support for shell auto-completion. 19 | 20 | = Note about the missing `watch` command 21 | 22 | We suggest you to use #link("https://github.com/Enter-tainer/typst-preview")[Typst Preview plugin] for preview feature. For more details, please see #cross-link("/guide/get-started.typ")[Get Started] chapter. 23 | -------------------------------------------------------------------------------- /github-pages/docs/cli/serve.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page, cross-link 2 | 3 | #show: book-page.with(title: "CLI Serve Command") 4 | 5 | = The serve command 6 | 7 | The serve command is used to preview a book by serving it via HTTP at 8 | `localhost:25520` by default: 9 | 10 | ```bash 11 | shiroa serve 12 | ``` 13 | 14 | // The `serve` command watches the book's `src` directory for 15 | // changes, rebuilding the book and refreshing clients for each change; this includes 16 | // re-creating deleted files still mentioned in `book.typ`! A websocket 17 | // connection is used to trigger the client-side refresh. 18 | 19 | *Note:* *The `serve` command is for testing a book's HTML output, and is not 20 | intended to be a complete HTTP server for a website.* 21 | 22 | == Specify a directory 23 | 24 | The `serve` command can take a directory as an argument to use as the book's 25 | root instead of the current working directory. 26 | 27 | ```bash 28 | shiroa serve path/to/book 29 | ``` 30 | 31 | == Build options 32 | 33 | The `serve` command will build your book once before serving the content. It is hence including all of the #cross-link("/cli/build.typ")[options] from `build` command. 34 | 35 | == Server options 36 | 37 | The `serve` address defaults to `localhost:25520`. Either option can be specified on the command line: 38 | 39 | ```bash 40 | shiroa serve path/to/book --addr 8000:127.0.0.1 41 | ``` 42 | 43 | === --open 44 | 45 | When you use the `--open` flag, shiroa will open the rendered book in 46 | your default web browser after building it. 47 | 48 | // == Specify exclude patterns 49 | 50 | // The `serve` command will not automatically trigger a build for files listed in 51 | // the `.gitignore` file in the book root directory. The `.gitignore` file may 52 | // contain file patterns described in the [gitignore 53 | // documentation](https://git-scm.com/docs/gitignore). This can be useful for 54 | // ignoring temporary files created by some editors. 55 | 56 | // ***Note:*** *Only the `.gitignore` from the book root directory is used. Global 57 | // `$HOME/.gitignore` or `.gitignore` files in parent directories are not used.* 58 | -------------------------------------------------------------------------------- /github-pages/docs/format/book-meta.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | 3 | #show: book-page.with(title: "Book Metadata") 4 | 5 | = Book Metadata 6 | 7 | #let type-hint(t, required: false) = { 8 | { 9 | set text(weight: 400, size: 16pt) 10 | if required { 11 | " (required) " 12 | } 13 | } 14 | { 15 | // show "<": set text(fill: blue) 16 | // show ">": set text(fill: blue) 17 | text(fill: red, raw(t)) 18 | } 19 | } 20 | 21 | === title #type-hint("string") 22 | 23 | Specify the title of the book. 24 | 25 | ```typ 26 | #book-meta( 27 | title: "../dist", 28 | ) 29 | ``` 30 | 31 | === authors #type-hint("array") 32 | 33 | Specify the author(s) of the book. 34 | 35 | ```typ 36 | #book-meta( 37 | authors: ("Alice", "Bob"), 38 | ) 39 | ``` 40 | 41 | === summary #type-hint("content", required: true) 42 | 43 | Its formatting 44 | is very strict and must follow the structure outlined below to allow for easy 45 | parsing. Any element not specified below, be it formatting or textual, is likely 46 | to be ignored at best, or may cause an error when attempting to build the book. 47 | 48 | ```typ 49 | #book-meta( 50 | summary: [ 51 | #prefix-chapter("pre.typ")[Prefix Chapter] 52 | = User Guide 53 | - #chapter("1.typ", section: "1")[First Chapter] 54 | - #chapter("1.1.typ", section: "1.1")[First sub] 55 | - #chapter("2.typ", section: "1")[Second Chapter] 56 | #suffix-chapter("suf.typ")[Suffix Chapter] 57 | ], 58 | ) 59 | ``` 60 | 61 | + *Prefix Chapter* - Before the main numbered chapters, prefix chapters can be added 62 | that will not be numbered. This is useful for forewords, 63 | introductions, etc. There are, however, some constraints. Prefix chapters cannot be 64 | nested; they should all be on the root level. And you cannot add 65 | prefix chapters once you have added numbered chapters. 66 | ```typ 67 | #prefix-chapter("pre.typ")[Prefix Chapter] 68 | - #chapter("1.typ", section: "1")[First Chapter] 69 | ``` 70 | 71 | + *Part Title* - Headers can be used as a title for the following numbered 72 | chapters. This can be used to logically separate different sections 73 | of the book. The title is rendered as unclickable text. 74 | Titles are optional, and the numbered chapters can be broken into as many 75 | parts as desired. 76 | ```typ 77 | = My Part Title 78 | 79 | - #chapter("1.typ", section: "1")[First Chapter] 80 | ``` 81 | 82 | + *Numbered Chapter* - Numbered chapters outline the main content of the book 83 | and can be nested, resulting in a nice hierarchy 84 | (chapters, sub-chapters, etc.). 85 | ```typ 86 | = Title of Part 87 | 88 | - #chapter("first.typ", section: "1")[First Chapter] 89 | - #chapter("first-sub-chapter.typ", section: "1.1")[First sub-chapter] 90 | - #chapter("second.typ", section: "1")[Second Chapter] 91 | 92 | = Title of Another Part 93 | 94 | - #chapter("another/chapter.typ", section: "1")[Another Chapter] 95 | ``` 96 | Numbered chapters can be denoted either `-`. 97 | 98 | + *Suffix Chapter* - Like prefix chapters, suffix chapters are unnumbered, but they come after 99 | numbered chapters. 100 | ```typ 101 | = Last Part 102 | 103 | - #chapter("second.typ", section: "10")[Last Chapter] 104 | 105 | #suffix-chapter("suf.typ")[Title of Suffix Chapter] 106 | ``` 107 | 108 | + *Draft chapters* - Draft chapters are chapters without a file and thus content. 109 | The purpose of a draft chapter is to signal future chapters still to be written. 110 | Or when still laying out the structure of the book to avoid creating the files 111 | while you are still changing the structure of the book a lot. 112 | Draft chapters will be rendered in the HTML renderer as disabled links in the table 113 | of contents, as you can see for the next chapter in the table of contents on the left. 114 | Draft chapters are written like normal chapters but without writing the path to the file. 115 | ```typ 116 | #chapter(none, section: "5.2")[Draft Chapter] 117 | ``` 118 | 119 | + *Separators* - Separators can be added before, in between, and after any other element. They result 120 | in an HTML rendered line in the built table of contents. A separator is 121 | a line containing exclusively dashes and at least three of them: `---`. 122 | ```typ 123 | = My Part Title 124 | 125 | #prefix-chapter("pre.typ")[A Prefix Chapter] 126 | 127 | #divider() 128 | 129 | - #chapter("1.typ", section: "1")[First Chapter] 130 | ``` 131 | 132 | == Example 133 | 134 | Below is the summary content for the `book.typ` for this guide, with the resulting table 135 | of contents as rendered to the left. 136 | 137 | #{ 138 | let exp = read("../book.typ") 139 | let exp = exp.find(regex("// begin of summary[\s\S]*// end of summary")).split("\n") 140 | // remove first and last line (begin and end of summary) 141 | let exp = exp.slice(1, exp.len() - 2) 142 | // remove leading spaces 143 | let space = exp.at(0).position("#") 144 | let exp = exp.map(it => it.slice(space)) 145 | 146 | // filter out comments 147 | let exp = exp.filter(it => not it.starts-with(regex("\s*//"))) 148 | 149 | // render as typ raw block 150 | let exp = exp.join("\n") 151 | raw(exp, lang: "typ", block: true) 152 | } 153 | 154 | === description #type-hint("string") 155 | 156 | A description for the book, which is added as meta information in the html `` of each page. 157 | 158 | ```typ 159 | #book-meta( 160 | description: "shiroa Documentation", 161 | ) 162 | ``` 163 | 164 | === repository #type-hint("string") 165 | 166 | The github repository for the book. 167 | 168 | ```typ 169 | #book-meta( 170 | repository: "https://github.com/Myriad-Dreamin/shiroa", 171 | ) 172 | ``` 173 | 174 | === language #type-hint("string") 175 | 176 | The main language of the book, which is used as a html language attribute 177 | `` for example. 178 | 179 | ```typ 180 | #book-meta( 181 | language: "en", 182 | ) 183 | ``` 184 | -------------------------------------------------------------------------------- /github-pages/docs/format/book.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page, cross-link 2 | 3 | #show: book-page.with(title: "book.typ") 4 | 5 | = book.typ 6 | 7 | * Note: This main file must be named `book.typ`. * 8 | 9 | The `book.typ` consists of many meta sections describing your book project. If you are familiar with `mdbook`, the `book.typ` file is similar to the `book.toml` with `summary.md` file. 10 | 11 | The main file is used by `shiroa` to know what chapters to include, in what 12 | order they should appear, what their hierarchy is and where the source files 13 | are. Without this file, there is no book. 14 | 15 | Since the `book.typ` is merely a typst source file, you can import them everywhere, which could be quite useful. For example, to export project to a single PDF file, an #link("https://github.com/Myriad-Dreamin/shiroa/blob/b9fc82b0d7f7009dfcaaf405d32f8ab044960e4f/github-pages/docs/pdf.typ")[ebook] file can aggregate all source files of this project according to the imported `book-meta.summary` metadata from `book.typ`. 16 | 17 | == book-meta 18 | 19 | #let type-hint(t) = text(fill: red, raw(t)) 20 | 21 | Specify general metadata of the book project. For example: 22 | 23 | ```typ 24 | #book-meta( 25 | title: "shiroa", 26 | authors: ("Myriad-Dreamin", "7mile"), 27 | summary: [ // this field works like summary.md of mdbook 28 | #prefix-chapter("pre.typ")[Prefix Chapter] 29 | = User Guide 30 | - #chapter("1.typ", section: "1")[First Chapter] 31 | - #chapter("1.1.typ", section: "1.1")[First sub] 32 | - #chapter("2.typ", section: "1")[Second Chapter] 33 | #suffix-chapter("suf.typ")[Suffix Chapter] 34 | ] 35 | ) 36 | ``` 37 | 38 | In this example, you specify following fields for the book project: 39 | 40 | - title #type-hint("string") (optional): Specify the title of the book. 41 | - authors #type-hint("array") (optional): Specify the author(s) of the book. 42 | - summary #type-hint("content") (required): Summarize of the book. 43 | 44 | See #cross-link("/format/book-meta.typ")[Book Metadata] for more details. 45 | 46 | == build-meta 47 | 48 | Specify build metadata of the book project. For example: 49 | 50 | ```typ 51 | #build-meta( 52 | dest-dir: "../dist", 53 | ) 54 | ``` 55 | 56 | When you set `build-meta.dest-dir` to `../dist`, `shiroa` will output the generated content to `parent/to/book.typ/../../dist` or `parent/dist`. 57 | 58 | See #cross-link("/format/build-meta.typ")[Build Metadata] for more details. 59 | -------------------------------------------------------------------------------- /github-pages/docs/format/build-meta.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | 3 | #show: book-page.with(title: "Build Metadata") 4 | 5 | = Build Metadata 6 | 7 | #let type-hint(t, required: false) = { 8 | { 9 | set text(weight: 400, size: 16pt) 10 | if required { 11 | " (required) " 12 | } 13 | } 14 | { 15 | text(fill: red, raw(t)) 16 | } 17 | } 18 | 19 | === dest-dir #type-hint("string") 20 | 21 | The directory to put the rendered book in. By default this is `book/` in the book's root directory. This can be *overridden* with the `--dest-dir` CLI option. 22 | 23 | ```typ 24 | #build-meta( 25 | dest-dir: "../dist", 26 | ) 27 | ``` 28 | 29 | When you set it to `../dist`, `shiroa` will output the rendered book to `parent/to/book.typ/../../dist` or calculated `parent/dist`. 30 | -------------------------------------------------------------------------------- /github-pages/docs/format/main.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | 3 | #show: book-page.with(title: "Format") 4 | 5 | = Format 6 | 7 | In this section you will learn how to: 8 | 9 | - Structure your book correctly 10 | - Format your `book.typ` file 11 | - Configure your book using `book.typ` 12 | - Customize your theme 13 | -------------------------------------------------------------------------------- /github-pages/docs/format/theme.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page, cross-link 2 | 3 | #show: book-page.with(title: "Theme") 4 | 5 | = Theme 6 | 7 | The default renderer uses a #link("https://handlebarsjs.com")[handlebars] template to 8 | render your typst source files and comes with a default theme included in the `shiroa` 9 | binary. 10 | 11 | Currently we have no much design on theme's html part. But you can still configure your book project like a regular typst project. 12 | 13 | == Things to note 14 | 15 | #let t = cross-link("/guide/get-started.typ")[Get Started] 16 | 17 | Your `book.typ` should at least provides a `book-meta`, as #t shown. 18 | 19 | ```typ 20 | #import "@preview/shiroa:0.2.3": * 21 | #show: book 22 | 23 | #book-meta( 24 | title: "My Book" 25 | summary: [ 26 | = My Book 27 | ] 28 | ) 29 | ``` 30 | 31 | To support specialized rendering for web pages and different page layouts, Your `template.typ` can import and respect the `page-width` and `target` variable from `@preview/shiroa:0.2.3` to this time. 32 | 33 | ```typ 34 | #import "@preview/shiroa:0.2.3": page-width, target 35 | 36 | #let project(body) = { 37 | // set web/pdf page properties 38 | set page( 39 | width: page-width, 40 | // for a website, we don't need pagination. 41 | height: auto, 42 | ) 43 | 44 | // remove margins for web target 45 | set page(margin: ( 46 | // reserved beautiful top margin 47 | top: 20pt, 48 | // Typst is setting the page's bottom to the baseline of the last line of text. So bad :(. 49 | bottom: 0.5em, 50 | // remove rest margins. 51 | rest: 0pt, 52 | )) if target.starts-with("web"); 53 | 54 | body 55 | } 56 | ``` 57 | 58 | // The theme is totally customizable, you can selectively replace every file from 59 | // the theme by your own by adding a `theme` directory next to `src` folder in your 60 | // project root. Create a new file with the name of the file you want to override 61 | // and now that file will be used instead of the default file. 62 | 63 | // Here are the files you can override: 64 | 65 | // - **_index.hbs_** is the handlebars template. 66 | // - **_head.hbs_** is appended to the HTML `` section. 67 | // - **_header.hbs_** content is appended on top of every book page. 68 | // - **_css/_** contains the CSS files for styling the book. 69 | // - **_css/chrome.css_** is for UI elements. 70 | // - **_css/general.css_** is the base styles. 71 | // - **_css/print.css_** is the style for printer output. 72 | // - **_css/variables.css_** contains variables used in other CSS files. 73 | // - **_book.js_** is mostly used to add client side functionality, like hiding / 74 | // un-hiding the sidebar, changing the theme, ... 75 | // - **_highlight.js_** is the JavaScript that is used to highlight code snippets, 76 | // you should not need to modify this. 77 | // - **_highlight.css_** is the theme used for the code highlighting. 78 | // - **_favicon.svg_** and **_favicon.png_** the favicon that will be used. The SVG 79 | // version is used by [newer browsers]. 80 | // - **fonts/fonts.css** contains the definition of which fonts to load. 81 | // Custom fonts can be included in the `fonts` directory. 82 | 83 | // Generally, when you want to tweak the theme, you don't need to override all the 84 | // files. If you only need changes in the stylesheet, there is no point in 85 | // overriding all the other files. Because custom files take precedence over 86 | // built-in ones, they will not get updated with new fixes / features. 87 | 88 | // **Note:** When you override a file, it is possible that you break some 89 | // functionality. Therefore I recommend to use the file from the default theme as 90 | // template and only add / modify what you need. You can copy the default theme 91 | // into your source directory automatically by using `shiroa init --theme` and just 92 | // remove the files you don't want to override. 93 | 94 | // `shiroa init --theme` will not create every file listed above. 95 | // Some files, such as `head.hbs`, do not have built-in equivalents. 96 | // Just create the file if you need it. 97 | 98 | // If you completely replace all built-in themes, be sure to also set 99 | // [`output.html.preferred-dark-theme`] in the config, which defaults to the 100 | // built-in `navy` theme. 101 | 102 | // [`output.html.preferred-dark-theme`]: ../configuration/renderers.md#html-renderer-options 103 | // [newer browsers]: https://caniuse.com/#feat=link-icon-svg 104 | -------------------------------------------------------------------------------- /github-pages/docs/guide/faq.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | 3 | #show: book-page.with(title: "Frequently Asked Questions") 4 | 5 | We are collecting questions and answers about the `shiroa` project here. Feel free to ask questions in #link("https://github.com/Myriad-Dreamin/shiroa/issues")[Github Issues]. 6 | -------------------------------------------------------------------------------- /github-pages/docs/guide/get-started.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | #import "@preview/shiroa:0.2.3": shiroa-sys-target 3 | 4 | #show: book-page.with(title: "Get Started") 5 | 6 | = Creating a Book 7 | 8 | Once you have the `shiroa` CLI tool installed, you can use it to create and render a book. 9 | 10 | == Initializing a book 11 | 12 | The `shiroa init` command will create a new directory containing an empty book for you to get started. 13 | Give it the name of the directory that you want to create: 14 | 15 | ```sh 16 | shiroa init my-first-book 17 | ``` 18 | 19 | It will emit template files to the `my-first-book`. Then, you can change the current directory into the new book: 20 | 21 | ```sh 22 | cd my-first-book 23 | ``` 24 | 25 | There are several ways to render a book, but one of the easiest methods is to use the `serve` command, which will build your book and start a local webserver: 26 | 27 | ```sh 28 | shiroa serve 29 | ``` 30 | 31 | // The `--open` option will open your default web browser to view your new book. 32 | // You can leave the server running even while you edit the content of the book, and `shiroa` will automatically rebuild the output *and* automatically refresh your web browser. 33 | 34 | Check out the `shiroa help` for more information about other `shiroa` commands and CLI options. 35 | 36 | == Anatomy of a book 37 | 38 | A book is built from several files which define the settings and layout of the book. 39 | 40 | === `book.typ` 41 | 42 | If you are familiar with `mdbook`, the `book.typ` file is similar to the `book.toml` with `summary.md` file. 43 | 44 | The book source file is the main file located at `src/book.typ`. 45 | This file contains a list of all the chapters in the book. 46 | Before a chapter can be viewed, it must be added to this list. 47 | 48 | Here's a basic summary file with a few chapters: 49 | 50 | ```typ 51 | #import "@preview/shiroa:0.2.3": * 52 | #show: book 53 | 54 | #book-meta( // put metadata of your book like book.toml of mdbook 55 | title: "shiroa", 56 | description: "shiroa Documentation", 57 | repository: "https://github.com/Myriad-Dreamin/shiroa", 58 | authors: ("Myriad-Dreamin", "7mile"), 59 | language: "en", 60 | summary: [ // this field works like summary.md of mdbook 61 | = Introduction 62 | - #chapter("guide/installation.typ", section: "1.1")[Installation] 63 | - #chapter("guide/get-started.typ", section: "1.2")[Get Started] 64 | - #chapter(none, section: "1.2.1")[Drafting chapter] 65 | ] 66 | ) 67 | ``` 68 | 69 | Try opening up `src/book.typ` in your editor and adding a few chapters. 70 | // If any of the chapter files do not exist, `shiroa` will automatically create them for you. 71 | 72 | // For more details on other formatting options for the summary file, check out the [Summary chapter](../format/summary.typ). 73 | 74 | === Source files 75 | 76 | The content of your book is all contained in the `src` directory. 77 | Each chapter is a separate Typst file. 78 | Typically, each chapter starts with a level 1 heading with the title of the chapter. 79 | 80 | ```typ 81 | = My First Chapter 82 | 83 | Fill out your content here. 84 | ``` 85 | 86 | The precise layout of the files is up to you. 87 | The organization of the files will correspond to the HTML files generated, so keep in mind that the file layout is part of the URL of each chapter. 88 | 89 | // While the `shiroa serve` command is running, you can open any of the chapter files and start editing them. 90 | // Each time you save the file, `shiroa` will rebuild the book and refresh your web browser. 91 | 92 | // Check out the #link("https://rust-lang.github.io/myriad-dreamin/shiroa/format/typst.html")[Typst chapter] for more information on formatting the content of your chapters. 93 | 94 | All other files in the `src` directory will be included in the output. 95 | So if you have images or other static files, just include them somewhere in the `src` directory. 96 | 97 | == Publishing a book 98 | 99 | Once you've written your book, you may want to host it somewhere for others to view. 100 | The first step is to build the output of the book. 101 | This can be done with the `shiroa build` command in the same directory where the `book.toml` file is located: 102 | 103 | ```sh 104 | shiroa build 105 | ``` 106 | 107 | This will generate a directory named `book` which contains the HTML content of your book. 108 | You can then place this directory on any web server to host it. 109 | 110 | == (Experimental) Using Typst v0.13.0's HTML Export 111 | 112 | You don't have to change the default template, which already handles the HTML export. Simply set the `mode` to `static-html` to export the book as Static HTML files: 113 | 114 | ```sh 115 | shiroa build --mode static-html 116 | ``` 117 | 118 | #context if shiroa-sys-target() == "html" [ 119 | You are viewing the HTML export of the book right now :). 120 | See the #link("https://myriad-dreamin.github.io/shiroa/paged/")[Paged version of shiroa's documentation] to see the result using the old paged export. 121 | ] else [ 122 | See the #link("https://myriad-dreamin.github.io/shiroa/")[HTML version of shiroa's documentation] to see the result of the HTML export. 123 | ] 124 | 125 | 126 | There is still some known issues, for example, typst hasn't support labels in the HTML export yet. 127 | 128 | // For more information about publishing and deploying, check out the [Continuous Integration chapter](../continuous-integration.typ) for more. 129 | -------------------------------------------------------------------------------- /github-pages/docs/guide/installation.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | 3 | #show: book-page.with(title: "Installation") 4 | 5 | = Installation 6 | 7 | There are multiple ways to install the shiroa CLI tool. 8 | Choose any one of the methods below that best suit your needs. 9 | // If you are installing shiroa for automatic deployment, check out the [continuous integration] chapter for more examples on how to install. 10 | 11 | // [continuous integration]: ../continuous-integration.md 12 | 13 | == Pre-compiled binaries 14 | 15 | Executable binaries are available for download on the #link("https://github.com/Myriad-Dreamin/shiroa/releases")[GitHub Releases page]. 16 | Download the binary for your platform (Windows, macOS, or Linux) and extract the archive. 17 | The archive contains an `shiroa` executable which you can run to build your books. 18 | 19 | To make it easier to run, put the path to the binary into your `PATH`. 20 | 21 | == Build from source using Rust 22 | 23 | To build the `shiroa` executable from source, you will first need to install Yarn, Rust, and Cargo. 24 | Follow the instructions on the #link("https://classic.yarnpkg.com/en/docs/install")[Yarn installation page] and #link("https://www.rust-lang.org/tools/install")[Rust installation page]. 25 | shiroa currently requires at least Rust version 1.75. 26 | 27 | To build with precompiled artifacts, run the following commands: 28 | 29 | ```sh 30 | cargo install --git https://github.com/Myriad-Dreamin/shiroa --locked shiroa 31 | ``` 32 | 33 | To build from source, run the following commands (note: it depends on `yarn` to build frontend): 34 | 35 | ```sh 36 | git clone https://github.com/Myriad-Dreamin/shiroa.git 37 | git submodule update --recursive --init 38 | cargo run --bin shiroa-build 39 | # optional: install it globally 40 | cargo install --path ./cli 41 | ``` 42 | 43 | With global installation, to uninstall, run the command `cargo uninstall shiroa`. 44 | 45 | Again, make sure to add the Cargo bin directory to your `PATH`. 46 | -------------------------------------------------------------------------------- /github-pages/docs/introduction.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | 3 | #show: book-page.with(title: "Introduction") 4 | 5 | = Introduction 6 | 7 | *shiroa* (_Shiro A_, or _The White_, or _云笺_) is a simple tool for creating modern online (cloud) books in pure typst. It has similar use cases as #link("https://rust-lang.github.io/mdBook/index.html")[mdBook], which is ideal for creating product or API documentation, tutorials, course materials or anything that requires a clean, easily navigable and customizable presentation. 8 | 9 | *shiroa* is heavily inspired by mdBook, but it is considered to be more adapted to Typst style, hence no guarantee of compatibility with mdBook. Compared with mdBook, we utilizes typst's advantages to bring a more flexible writing experience, such as #link("https://typst.app/docs/reference/scripting/")[scripting] and #link("https://typst.app/docs/packages/")[package]. 10 | 11 | = Not yet finished project 12 | 13 | *shiroa* still have many items in todolist: 14 | 15 | - User experience, which is transparent to writers: 16 | - SEO optimization 17 | - Faster font loading 18 | - Reducing the size of theme bundle files and compiled svg artifacts 19 | - Add prev/next buttons 20 | - initialize a book project interactively 21 | - Writer experience: 22 | - Book specific helper functions 23 | - Customize Favicon 24 | 25 | Hence you may meet many problems. We are active to receive questions and bugs in #link("https://github.com/Myriad-Dreamin/shiroa/issues")[Github Issues] and please feel free to open issues. If you'd like to contribute, please consider opening a #link("https://github.com/Myriad-Dreamin/shiroa/pulls")[pull request]. 26 | 27 | = License 28 | 29 | *shiroa* source and documentation are released under the #link("https://www.apache.org/licenses/LICENSE-2.0")[Apache License v2.0]. 30 | 31 | The source and documentation in theme directory in `themes/mdbook` are released under the #link("https://www.mozilla.org/en-US/MPL/2.0/")[Mozilla Public License v2.0]. 32 | -------------------------------------------------------------------------------- /github-pages/docs/pdf.typ: -------------------------------------------------------------------------------- 1 | #import "@preview/shiroa:0.2.3": * 2 | 3 | #import "/contrib/typst/gh-ebook.typ" 4 | #let ebook = gh-ebook 5 | 6 | #show: ebook.project.with(title: "Typst book", authors: ("Myriad-Dreamin", "7mile"), spec: "book.typ") 7 | 8 | // set a resolver for inclusion 9 | #ebook.resolve-inclusion(it => include it) 10 | -------------------------------------------------------------------------------- /github-pages/docs/supports.typ: -------------------------------------------------------------------------------- 1 | #import "/github-pages/docs/book.typ": book-page 2 | 3 | #show: book-page.with(title: "Typst Supports") 4 | 5 | In this section you will learn how to: 6 | 7 | - Make a cross reference in the same page or to other pages. 8 | - Embed HTML elements into the pages: 9 | - ```typc media.iframe``` corresponds to a ```html