[
  {
    "path": ".codex/skills/openspec-apply-change/SKILL.md",
    "content": "---\nname: openspec-apply-change\ndescription: Implement tasks from an OpenSpec change. Use when the user wants to start implementing, continue implementation, or work through tasks.\nlicense: MIT\ncompatibility: Requires openspec CLI.\nmetadata:\n  author: openspec\n  version: \"1.0\"\n  generatedBy: \"1.2.0\"\n---\n\nImplement tasks from an OpenSpec change.\n\n**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.\n\n**Steps**\n\n1. **Select the change**\n\n   If a name is provided, use it. Otherwise:\n   - Infer from conversation context if the user mentioned a change\n   - Auto-select if only one active change exists\n   - If ambiguous, run `openspec list --json` to get available changes and use the **AskUserQuestion tool** to let the user select\n\n   Always announce: \"Using change: <name>\" and how to override (e.g., `/opsx:apply <other>`).\n\n2. **Check status to understand the schema**\n   ```bash\n   openspec status --change \"<name>\" --json\n   ```\n   Parse the JSON to understand:\n   - `schemaName`: The workflow being used (e.g., \"spec-driven\")\n   - Which artifact contains the tasks (typically \"tasks\" for spec-driven, check status for others)\n\n3. **Get apply instructions**\n\n   ```bash\n   openspec instructions apply --change \"<name>\" --json\n   ```\n\n   This returns:\n   - Context file paths (varies by schema - could be proposal/specs/design/tasks or spec/tests/implementation/docs)\n   - Progress (total, complete, remaining)\n   - Task list with status\n   - Dynamic instruction based on current state\n\n   **Handle states:**\n   - If `state: \"blocked\"` (missing artifacts): show message, suggest using openspec-continue-change\n   - If `state: \"all_done\"`: congratulate, suggest archive\n   - Otherwise: proceed to implementation\n\n4. **Read context files**\n\n   Read the files listed in `contextFiles` from the apply instructions output.\n   The files depend on the schema being used:\n   - **spec-driven**: proposal, specs, design, tasks\n   - Other schemas: follow the contextFiles from CLI output\n\n5. **Show current progress**\n\n   Display:\n   - Schema being used\n   - Progress: \"N/M tasks complete\"\n   - Remaining tasks overview\n   - Dynamic instruction from CLI\n\n6. **Implement tasks (loop until done or blocked)**\n\n   For each pending task:\n   - Show which task is being worked on\n   - Make the code changes required\n   - Keep changes minimal and focused\n   - Mark task complete in the tasks file: `- [ ]` → `- [x]`\n   - Continue to next task\n\n   **Pause if:**\n   - Task is unclear → ask for clarification\n   - Implementation reveals a design issue → suggest updating artifacts\n   - Error or blocker encountered → report and wait for guidance\n   - User interrupts\n\n7. **On completion or pause, show status**\n\n   Display:\n   - Tasks completed this session\n   - Overall progress: \"N/M tasks complete\"\n   - If all done: suggest archive\n   - If paused: explain why and wait for guidance\n\n**Output During Implementation**\n\n```\n## Implementing: <change-name> (schema: <schema-name>)\n\nWorking on task 3/7: <task description>\n[...implementation happening...]\n✓ Task complete\n\nWorking on task 4/7: <task description>\n[...implementation happening...]\n✓ Task complete\n```\n\n**Output On Completion**\n\n```\n## Implementation Complete\n\n**Change:** <change-name>\n**Schema:** <schema-name>\n**Progress:** 7/7 tasks complete ✓\n\n### Completed This Session\n- [x] Task 1\n- [x] Task 2\n...\n\nAll tasks complete! Ready to archive this change.\n```\n\n**Output On Pause (Issue Encountered)**\n\n```\n## Implementation Paused\n\n**Change:** <change-name>\n**Schema:** <schema-name>\n**Progress:** 4/7 tasks complete\n\n### Issue Encountered\n<description of the issue>\n\n**Options:**\n1. <option 1>\n2. <option 2>\n3. Other approach\n\nWhat would you like to do?\n```\n\n**Guardrails**\n- Keep going through tasks until done or blocked\n- Always read context files before starting (from the apply instructions output)\n- If task is ambiguous, pause and ask before implementing\n- If implementation reveals issues, pause and suggest artifact updates\n- Keep code changes minimal and scoped to each task\n- Update task checkbox immediately after completing each task\n- Pause on errors, blockers, or unclear requirements - don't guess\n- Use contextFiles from CLI output, don't assume specific file names\n\n**Fluid Workflow Integration**\n\nThis skill supports the \"actions on a change\" model:\n\n- **Can be invoked anytime**: Before all artifacts are done (if tasks exist), after partial implementation, interleaved with other actions\n- **Allows artifact updates**: If implementation reveals design issues, suggest updating artifacts - not phase-locked, work fluidly\n"
  },
  {
    "path": ".codex/skills/openspec-archive-change/SKILL.md",
    "content": "---\nname: openspec-archive-change\ndescription: Archive a completed change in the experimental workflow. Use when the user wants to finalize and archive a change after implementation is complete.\nlicense: MIT\ncompatibility: Requires openspec CLI.\nmetadata:\n  author: openspec\n  version: \"1.0\"\n  generatedBy: \"1.2.0\"\n---\n\nArchive a completed change in the experimental workflow.\n\n**Input**: Optionally specify a change name. If omitted, check if it can be inferred from conversation context. If vague or ambiguous you MUST prompt for available changes.\n\n**Steps**\n\n1. **If no change name provided, prompt for selection**\n\n   Run `openspec list --json` to get available changes. Use the **AskUserQuestion tool** to let the user select.\n\n   Show only active changes (not already archived).\n   Include the schema used for each change if available.\n\n   **IMPORTANT**: Do NOT guess or auto-select a change. Always let the user choose.\n\n2. **Check artifact completion status**\n\n   Run `openspec status --change \"<name>\" --json` to check artifact completion.\n\n   Parse the JSON to understand:\n   - `schemaName`: The workflow being used\n   - `artifacts`: List of artifacts with their status (`done` or other)\n\n   **If any artifacts are not `done`:**\n   - Display warning listing incomplete artifacts\n   - Use **AskUserQuestion tool** to confirm user wants to proceed\n   - Proceed if user confirms\n\n3. **Check task completion status**\n\n   Read the tasks file (typically `tasks.md`) to check for incomplete tasks.\n\n   Count tasks marked with `- [ ]` (incomplete) vs `- [x]` (complete).\n\n   **If incomplete tasks found:**\n   - Display warning showing count of incomplete tasks\n   - Use **AskUserQuestion tool** to confirm user wants to proceed\n   - Proceed if user confirms\n\n   **If no tasks file exists:** Proceed without task-related warning.\n\n4. **Assess delta spec sync state**\n\n   Check for delta specs at `openspec/changes/<name>/specs/`. If none exist, proceed without sync prompt.\n\n   **If delta specs exist:**\n   - Compare each delta spec with its corresponding main spec at `openspec/specs/<capability>/spec.md`\n   - Determine what changes would be applied (adds, modifications, removals, renames)\n   - Show a combined summary before prompting\n\n   **Prompt options:**\n   - If changes needed: \"Sync now (recommended)\", \"Archive without syncing\"\n   - If already synced: \"Archive now\", \"Sync anyway\", \"Cancel\"\n\n   If user chooses sync, use Task tool (subagent_type: \"general-purpose\", prompt: \"Use Skill tool to invoke openspec-sync-specs for change '<name>'. Delta spec analysis: <include the analyzed delta spec summary>\"). Proceed to archive regardless of choice.\n\n5. **Perform the archive**\n\n   Create the archive directory if it doesn't exist:\n   ```bash\n   mkdir -p openspec/changes/archive\n   ```\n\n   Generate target name using current date: `YYYY-MM-DD-<change-name>`\n\n   **Check if target already exists:**\n   - If yes: Fail with error, suggest renaming existing archive or using different date\n   - If no: Move the change directory to archive\n\n   ```bash\n   mv openspec/changes/<name> openspec/changes/archive/YYYY-MM-DD-<name>\n   ```\n\n6. **Finalize synced spec purpose**\n\n   If specs were synced or created under `openspec/specs/`:\n   - Open each affected main spec at `openspec/specs/<capability>/spec.md`\n   - Check the `## Purpose` section\n   - If it contains the autogenerated archive placeholder (for example,\n     `TBD - created by archiving change ...`) or is still too generic, replace\n     it with a concise, capability-specific purpose statement before reporting\n     completion\n\n7. **Display summary**\n\n   Show archive completion summary including:\n   - Change name\n   - Schema that was used\n   - Archive location\n   - Whether specs were synced (if applicable)\n   - Note about any warnings (incomplete artifacts/tasks)\n\n**Output On Success**\n\n```\n## Archive Complete\n\n**Change:** <change-name>\n**Schema:** <schema-name>\n**Archived to:** openspec/changes/archive/YYYY-MM-DD-<name>/\n**Specs:** ✓ Synced to main specs (or \"No delta specs\" or \"Sync skipped\")\n\nAll artifacts complete. All tasks complete.\n```\n\n**Guardrails**\n- Always prompt for change selection if not provided\n- Use artifact graph (openspec status --json) for completion checking\n- Don't block archive on warnings - just inform and confirm\n- Preserve .openspec.yaml when moving to archive (it moves with the directory)\n- Show clear summary of what happened\n- If sync is requested, use openspec-sync-specs approach (agent-driven)\n- If delta specs exist, always run the sync assessment and show the combined summary before prompting\n- If archiving creates or updates canonical specs, do not leave autogenerated placeholder `## Purpose` text behind\n"
  },
  {
    "path": ".codex/skills/openspec-explore/SKILL.md",
    "content": "---\nname: openspec-explore\ndescription: Enter explore mode - a thinking partner for exploring ideas, investigating problems, and clarifying requirements. Use when the user wants to think through something before or during a change.\nlicense: MIT\ncompatibility: Requires openspec CLI.\nmetadata:\n  author: openspec\n  version: \"1.0\"\n  generatedBy: \"1.2.0\"\n---\n\nEnter explore mode. Think deeply. Visualize freely. Follow the conversation wherever it goes.\n\n**IMPORTANT: Explore mode is for thinking, not implementing.** You may read files, search code, and investigate the codebase, but you must NEVER write code or implement features. If the user asks you to implement something, remind them to exit explore mode first and create a change proposal. You MAY create OpenSpec artifacts (proposals, designs, specs) if the user asks—that's capturing thinking, not implementing.\n\n**This is a stance, not a workflow.** There are no fixed steps, no required sequence, no mandatory outputs. You're a thinking partner helping the user explore.\n\n---\n\n## The Stance\n\n- **Curious, not prescriptive** - Ask questions that emerge naturally, don't follow a script\n- **Open threads, not interrogations** - Surface multiple interesting directions and let the user follow what resonates. Don't funnel them through a single path of questions.\n- **Visual** - Use ASCII diagrams liberally when they'd help clarify thinking\n- **Adaptive** - Follow interesting threads, pivot when new information emerges\n- **Patient** - Don't rush to conclusions, let the shape of the problem emerge\n- **Grounded** - Explore the actual codebase when relevant, don't just theorize\n\n---\n\n## What You Might Do\n\nDepending on what the user brings, you might:\n\n**Explore the problem space**\n- Ask clarifying questions that emerge from what they said\n- Challenge assumptions\n- Reframe the problem\n- Find analogies\n\n**Investigate the codebase**\n- Map existing architecture relevant to the discussion\n- Find integration points\n- Identify patterns already in use\n- Surface hidden complexity\n\n**Compare options**\n- Brainstorm multiple approaches\n- Build comparison tables\n- Sketch tradeoffs\n- Recommend a path (if asked)\n\n**Visualize**\n```\n┌─────────────────────────────────────────┐\n│     Use ASCII diagrams liberally        │\n├─────────────────────────────────────────┤\n│                                         │\n│   ┌────────┐         ┌────────┐        │\n│   │ State  │────────▶│ State  │        │\n│   │   A    │         │   B    │        │\n│   └────────┘         └────────┘        │\n│                                         │\n│   System diagrams, state machines,      │\n│   data flows, architecture sketches,    │\n│   dependency graphs, comparison tables  │\n│                                         │\n└─────────────────────────────────────────┘\n```\n\n**Surface risks and unknowns**\n- Identify what could go wrong\n- Find gaps in understanding\n- Suggest spikes or investigations\n\n---\n\n## OpenSpec Awareness\n\nYou have full context of the OpenSpec system. Use it naturally, don't force it.\n\n### Check for context\n\nAt the start, quickly check what exists:\n```bash\nopenspec list --json\n```\n\nThis tells you:\n- If there are active changes\n- Their names, schemas, and status\n- What the user might be working on\n\n### When no change exists\n\nThink freely. When insights crystallize, you might offer:\n\n- \"This feels solid enough to start a change. Want me to create a proposal?\"\n- Or keep exploring - no pressure to formalize\n\n### When a change exists\n\nIf the user mentions a change or you detect one is relevant:\n\n1. **Read existing artifacts for context**\n   - `openspec/changes/<name>/proposal.md`\n   - `openspec/changes/<name>/design.md`\n   - `openspec/changes/<name>/tasks.md`\n   - etc.\n\n2. **Reference them naturally in conversation**\n   - \"Your design mentions using Redis, but we just realized SQLite fits better...\"\n   - \"The proposal scopes this to premium users, but we're now thinking everyone...\"\n\n3. **Offer to capture when decisions are made**\n\n   | Insight Type | Where to Capture |\n   |--------------|------------------|\n   | New requirement discovered | `specs/<capability>/spec.md` |\n   | Requirement changed | `specs/<capability>/spec.md` |\n   | Design decision made | `design.md` |\n   | Scope changed | `proposal.md` |\n   | New work identified | `tasks.md` |\n   | Assumption invalidated | Relevant artifact |\n\n   Example offers:\n   - \"That's a design decision. Capture it in design.md?\"\n   - \"This is a new requirement. Add it to specs?\"\n   - \"This changes scope. Update the proposal?\"\n\n4. **The user decides** - Offer and move on. Don't pressure. Don't auto-capture.\n\n---\n\n## What You Don't Have To Do\n\n- Follow a script\n- Ask the same questions every time\n- Produce a specific artifact\n- Reach a conclusion\n- Stay on topic if a tangent is valuable\n- Be brief (this is thinking time)\n\n---\n\n## Handling Different Entry Points\n\n**User brings a vague idea:**\n```\nUser: I'm thinking about adding real-time collaboration\n\nYou: Real-time collab is a big space. Let me think about this...\n\n      COLLABORATION SPECTRUM\n      ════════════════════════════════════════════\n\n      Awareness          Coordination         Sync\n          │                   │                 │\n          ▼                   ▼                 ▼\n      ┌────────┐         ┌────────┐        ┌────────┐\n      │Presence│         │Cursors │        │  CRDT  │\n      │  \"3    │         │ Multi  │        │Conflict│\n      │online\" │         │ select │        │  free  │\n      └────────┘         └────────┘        └────────┘\n          │                   │                 │\n       trivial            moderate           complex\n\n      Where's your head at?\n```\n\n**User brings a specific problem:**\n```\nUser: The auth system is a mess\n\nYou: [reads codebase]\n\n     ┌─────────────────────────────────────────────┐\n     │              CURRENT AUTH FLOW              │\n     └─────────────────────────────────────────────┘\n                        │\n          ┌─────────────┼─────────────┐\n          ▼             ▼             ▼\n     ┌─────────┐  ┌─────────┐  ┌─────────┐\n     │ Google  │  │  GitHub │  │  Email  │\n     │  OAuth  │  │  OAuth  │  │  Magic  │\n     └────┬────┘  └────┬────┘  └────┬────┘\n          │             │             │\n          └─────────────┼─────────────┘\n                        ▼\n                  ┌───────────┐\n                  │  Session  │\n                  └─────┬─────┘\n                        │\n                        ▼\n                  ┌───────────┐\n                  │   Perms   │\n                  └───────────┘\n\n     I see three tangles. Which one's burning?\n```\n\n**User is stuck mid-implementation:**\n```\nUser: /opsx:explore add-auth-system\n      The OAuth integration is more complex than expected\n\nYou: [reads change artifacts]\n\n     You're on task 4: \"Implement OAuth flow\"\n\n     Let me trace what's involved...\n\n     [draws diagram, explores options, suggests paths]\n\n     Want to update the design to reflect this?\n     Or add a spike task to investigate?\n```\n\n**User wants to compare options:**\n```\nUser: Should we use Postgres or SQLite?\n\nYou: Generic answer is boring. What's the context?\n\nUser: A CLI tool that tracks local dev environments\n\nYou: That changes everything.\n\n     ┌─────────────────────────────────────────────────┐\n     │         CLI TOOL DATA STORAGE                  │\n     └─────────────────────────────────────────────────┘\n\n     Key constraints:\n     • No daemon running\n     • Must work offline\n     • Single user\n\n                  SQLite          Postgres\n     Deployment   embedded ✓      needs server ✗\n     Offline      yes ✓           no ✗\n     Single file  yes ✓           no ✗\n\n     SQLite. Not even close.\n\n     Unless... is there a sync component?\n```\n\n---\n\n## Ending Discovery\n\nThere's no required ending. Discovery might:\n\n- **Flow into a proposal**: \"Ready to start? I can create a change proposal.\"\n- **Result in artifact updates**: \"Updated design.md with these decisions\"\n- **Just provide clarity**: User has what they need, moves on\n- **Continue later**: \"We can pick this up anytime\"\n\nWhen it feels like things are crystallizing, you might summarize:\n\n```\n## What We Figured Out\n\n**The problem**: [crystallized understanding]\n\n**The approach**: [if one emerged]\n\n**Open questions**: [if any remain]\n\n**Next steps** (if ready):\n- Create a change proposal\n- Keep exploring: just keep talking\n```\n\nBut this summary is optional. Sometimes the thinking IS the value.\n\n---\n\n## Guardrails\n\n- **Don't implement** - Never write code or implement features. Creating OpenSpec artifacts is fine, writing application code is not.\n- **Don't fake understanding** - If something is unclear, dig deeper\n- **Don't rush** - Discovery is thinking time, not task time\n- **Don't force structure** - Let patterns emerge naturally\n- **Don't auto-capture** - Offer to save insights, don't just do it\n- **Do visualize** - A good diagram is worth many paragraphs\n- **Do explore the codebase** - Ground discussions in reality\n- **Do question assumptions** - Including the user's and your own\n"
  },
  {
    "path": ".codex/skills/openspec-propose/SKILL.md",
    "content": "---\nname: openspec-propose\ndescription: Propose a new change with all artifacts generated in one step. Use when the user wants to quickly describe what they want to build and get a complete proposal with design, specs, and tasks ready for implementation.\nlicense: MIT\ncompatibility: Requires openspec CLI.\nmetadata:\n  author: openspec\n  version: \"1.0\"\n  generatedBy: \"1.2.0\"\n---\n\nPropose a new change - create the change and generate all artifacts in one step.\n\nI'll create a change with artifacts:\n- proposal.md (what & why)\n- design.md (how)\n- tasks.md (implementation steps)\n\nWhen ready to implement, run /opsx:apply\n\n---\n\n**Input**: The user's request should include a change name (kebab-case) OR a description of what they want to build.\n\n**Steps**\n\n1. **If no clear input provided, ask what they want to build**\n\n   Use the **AskUserQuestion tool** (open-ended, no preset options) to ask:\n   > \"What change do you want to work on? Describe what you want to build or fix.\"\n\n   From their description, derive a kebab-case name (e.g., \"add user authentication\" → `add-user-auth`).\n\n   **IMPORTANT**: Do NOT proceed without understanding what the user wants to build.\n\n2. **Create the change directory**\n   ```bash\n   openspec new change \"<name>\"\n   ```\n   This creates a scaffolded change at `openspec/changes/<name>/` with `.openspec.yaml`.\n\n3. **Get the artifact build order**\n   ```bash\n   openspec status --change \"<name>\" --json\n   ```\n   Parse the JSON to get:\n   - `applyRequires`: array of artifact IDs needed before implementation (e.g., `[\"tasks\"]`)\n   - `artifacts`: list of all artifacts with their status and dependencies\n\n4. **Create artifacts in sequence until apply-ready**\n\n   Use the **TodoWrite tool** to track progress through the artifacts.\n\n   Loop through artifacts in dependency order (artifacts with no pending dependencies first):\n\n   a. **For each artifact that is `ready` (dependencies satisfied)**:\n      - Get instructions:\n        ```bash\n        openspec instructions <artifact-id> --change \"<name>\" --json\n        ```\n      - The instructions JSON includes:\n        - `context`: Project background (constraints for you - do NOT include in output)\n        - `rules`: Artifact-specific rules (constraints for you - do NOT include in output)\n        - `template`: The structure to use for your output file\n        - `instruction`: Schema-specific guidance for this artifact type\n        - `outputPath`: Where to write the artifact\n        - `dependencies`: Completed artifacts to read for context\n      - Read any completed dependency files for context\n      - Create the artifact file using `template` as the structure\n      - Apply `context` and `rules` as constraints - but do NOT copy them into the file\n      - Show brief progress: \"Created <artifact-id>\"\n\n   b. **Continue until all `applyRequires` artifacts are complete**\n      - After creating each artifact, re-run `openspec status --change \"<name>\" --json`\n      - Check if every artifact ID in `applyRequires` has `status: \"done\"` in the artifacts array\n      - Stop when all `applyRequires` artifacts are done\n\n   c. **If an artifact requires user input** (unclear context):\n      - Use **AskUserQuestion tool** to clarify\n      - Then continue with creation\n\n5. **Show final status**\n   ```bash\n   openspec status --change \"<name>\"\n   ```\n\n**Output**\n\nAfter completing all artifacts, summarize:\n- Change name and location\n- List of artifacts created with brief descriptions\n- What's ready: \"All artifacts created! Ready for implementation.\"\n- Prompt: \"Run `/opsx:apply` or ask me to implement to start working on the tasks.\"\n\n**Artifact Creation Guidelines**\n\n- Follow the `instruction` field from `openspec instructions` for each artifact type\n- The schema defines what each artifact should contain - follow it\n- Read dependency artifacts for context before creating new ones\n- Use `template` as the structure for your output file - fill in its sections\n- **IMPORTANT**: `context` and `rules` are constraints for YOU, not content for the file\n  - Do NOT copy `<context>`, `<rules>`, `<project_context>` blocks into the artifact\n  - These guide what you write, but should never appear in the output\n\n**Guardrails**\n- Create ALL artifacts needed for implementation (as defined by schema's `apply.requires`)\n- Always read dependency artifacts before creating a new one\n- If context is critically unclear, ask the user - but prefer making reasonable decisions to keep momentum\n- If a change with that name already exists, ask if user wants to continue it or create a new one\n- Verify each artifact file exists after writing before proceeding to next\n"
  },
  {
    "path": ".codex/skills/typst-grammar-authoring/SKILL.md",
    "content": "---\nname: typst-grammar-authoring\ndescription: Use when authoring or validating Typst documents from canonical grammar examples, especially when you need compile, HTML, or SVG-based validation workflows.\nmetadata:\n  short-description: Author Typst docs from canonical grammar examples\n  from-tutorial-rev: https://github.com/typst-doc-cn/tutorial/tree/971815a6b4898c0339b620e08c62824ec03bba7f\n---\n\n# Typst Grammar Authoring\n\nUse this skill when the user wants help drafting, fixing, or validating Typst\ndocuments. Everything needed for grammar lookup lives in this file so it can be\ncopied into another repository without sibling scripts or reference files.\n\n## Workflow\n\n1. Start with the grammar lookup section in this file and pick the closest\n   existing pattern before inventing new syntax.\n2. Copy the smallest matching example, then adapt it incrementally.\n3. Run `typst compile` after each meaningful edit. Any non-zero exit code is a\n   blocking failure.\n4. After compile succeeds, use HTML output to inspect rendered text and\n   document structure when wording or content ordering matters.\n5. Use SVG output plus Playwright MCP when you need to inspect visual layout,\n   spacing, numbering, line breaks, or emphasis.\n\n## Validation\n\nCompile validation:\n\n```sh\ntypst compile --root . path/to/document.typ target/typst-grammar-authoring-check/document.pdf\n```\n\nText validation through HTML:\n\n```sh\ntypst compile --root . --features html path/to/document.typ target/typst-grammar-authoring-check/document.html\nrg \"expected text\" target/typst-grammar-authoring-check/document.html\n```\n\nVisual validation through SVG:\n\n```sh\ntypst compile --root . path/to/document.typ target/typst-grammar-authoring-check/document.svg\ntypst compile --root . path/to/document.typ target/typst-grammar-authoring-check/document-{0p}.svg\n```\n\nPlaywright inspection:\n\n- Use Playwright only after SVG generation succeeds.\n- Open the SVG directly if the MCP server supports local files.\n- Otherwise use a tiny local HTML wrapper that embeds the SVG, then capture a\n  screenshot and inspect layout, spacing, numbering, line breaks, and emphasis.\n\n## Guardrails\n\n- Keep all skill-authored prose and instructions in English.\n- Canonical syntax examples may retain non-English literals from their source\n  examples.\n- Do not treat Tinymist or editor diagnostics as the source of truth.\n- Do not assume sibling updater scripts, reference files, or repo-local\n  metadata exist when using this skill elsewhere.\n- Use `{p}` or `{0p}` in multi-page SVG output paths.\n- Treat HTML export as a validation aid, not a production contract.\n- Keep command examples platform-neutral by using forward-slash or placeholder\n  paths.\n\n## Grammar Lookup\n\nThis section is embedded on purpose so the skill stays self-contained. Examples\nare derived from the unofficial tutorial grammar samples and kept compact so the\nlookup remains usable in a single file.\n\n<!-- BEGIN GENERATED GRAMMAR LOOKUP -->\n\n### Base Elements\n\n- `paragraph`: `writing-markup`\n- `heading`: `= Heading`; `== Heading`\n- `strong`: `*Strong*`\n- `emph`: `_emphasis_`; `*_emphasis_*`\n- `list`:\n\n```typ\n+ List 1\n+ List 2\n```\n\n- `continue-list`:\n\n```typ\n4. List 1\n+ List 2\n```\n\n- `emum`:\n\n```typ\n- Enum 1\n- Enum 2\n```\n\n- `mix-list-emum`:\n\n```typ\n- Enum 1\n  + Item 1\n- Enum 2\n```\n\n- `raw`:\n\n```typ\n`code`\n```\n\n- `long-raw`:\n\n````typ\n``` code```\n````\n\n- `lang-raw`:\n\n````typ\n```rs  trait World```\n````\n\n- `blocky-raw`:\n\n````typ\n```typ\n= Heading\n```\n````\n\n- `image`: `#image(\"/assets/files/香風とうふ店.jpg\", width: 50pt)`\n- `image-stretch`: `#image(\"/assets/files/香風とうふ店.jpg\", width: 50pt, height: 50pt, fit: \"stretch\")`\n- `image-inline`: `在一段话中插入一个#box(baseline: 0.15em, image(\"/assets/files/info-icon.svg\", width: 1em))图片。`\n- `figure`:\n\n````typ\n#figure(```typ\n#image(\"/assets/files/香風とうふ店.jpg\")\n```, caption: [用于加载香風とうふ店送外卖的宝贵影像的代码])\n````\n\n- `link`: `#link(\"https://zh.wikipedia.org\")[维基百科]`\n- `http-link`: `https://zh.wikipedia.org`\n- `internal-link`:\n\n```typ\n== 某个标题 <ref-internal-link>\n#link(<ref-internal-link>)[链接到某个标题]\n```\n\n- `table`: `#table(columns: 2, [111], [2], [3])`\n- `table-align`: `#table(columns: 2, align: center, [111], [2], [3])`\n- `inline-math`: `$sum_x$`\n- `display-math`: `$ sum_x $`\n- `escape-sequences`: `>\\_<`\n- `unicode-escape-sequences`: `\\u{9999}`\n- `newline-by-space`: `A \\ B`\n- `newline`:\n\n```typ\nA \\\nB\n```\n\n- `shorthand`: `北京--上海`\n- `shorthand-space`: `A~B`\n- `inline-comment`: `// 行内注释`\n- `cross-line-comment`:\n\n```typ\n/* 行间注释\n  */\n```\n\n- `box`: `在一段话中插入一个#box(baseline: 0.15em, image(\"/assets/files/info-icon.svg\", width: 1em))图片。`\n\n### Text Styling\n\n- `highlight`: `#highlight[高亮一段内容]`\n- `underline`: `#underline[Language]`\n- `underline-evade`:\n\n```typ\n#underline(\n  evade: false)[ጿኈቼዽ]\n```\n\n- `overline`: `#overline[ጿኈቼዽ]`\n- `strike`: `#strike[ጿኈቼዽ]`\n- `subscript`: `威严满满#sub[抱头蹲防]`\n- `superscript`: `香風とうふ店#super[TM]`\n- `text-size`: `#text(size: 24pt)[一斤鸭梨]`\n- `text-fill`: `#text(fill: blue)[蓝色鸭梨]`\n- `text-font`: `#text(font: \"Microsoft YaHei\")[板正鸭梨]`\n\n### Script Declarations\n\n- `enter-script`: `#1`\n- `code-block`: `#{\"a\"; \"b\"}`\n- `content-block`: `#[内容块]`\n- `none-literal`: `#none`\n- `false-literal`: `#false`\n- `true-literal`: `#true`\n- `integer-literal`: `#(-1), #(0), #(1)`\n- `n-adecimal-literal`: `#(-0xdeadbeef), #(-0o644), #(-0b1001)`\n- `float-literal`: `#(0.001), #(.1), #(2.)`\n- `exp-repr-float`: `#(1e2), #(1.926e3), #(-1e-3)`\n- `string-literal`: `#\"Hello world!!\"`\n- `str-escape-sequences`: `#\"\\\"\"`\n- `str-unicode-escape-sequences`: `#\"\\u{9999}\"`\n- `array-literal`: `#(1, \"OvO\", [一段内容])`\n- `dict-literal`: `#(neko-mimi: 2, \"utterance\": \"喵喵喵\")`\n- `empty-array`: `#()`\n- `empty-dict`: `#(:)`\n- `paren-empty-array`: `#(())`\n- `single-member-array`: `#(1,)`\n- `var-decl`: `#let x = 1`\n- `func-decl`: `#let f(x) = x * 2`\n- `closure`: `#let f = (x, y) => x + y`\n- `named-param`: `#let g(named: none) = named`\n- `variadic-param`: `#let g(..args) = args.pos().join([、])`\n- `destruct-array`: `#let (one, hello-world) = (1, \"Hello, World\")`\n- `destruct-array-eliminate`: `#let (_, second, ..) = (1, \"Hello, World\", []); #second`\n- `destruct-dict`: `#let (neko-mimi: mimi) = (neko-mimi: 2); #mimi`\n- `array-remapping`:\n\n```typ\n#let (a, b, c) = (1, 2, 3)\n#let (b, c, a) = (a, b, c)\n#a, #b, #c\n```\n\n- `array-swap`:\n\n```typ\n#let (a, b) = (1, 2)\n#((a, b) = (b, a))\n#a, #b\n```\n\n- `placeholder`:\n\n```typ\n#let last-two(t) = {\n  let _ = t.pop()\n  t.pop()\n}\n#last-two((1, 2, 3, 4))\n```\n\n### Script Statements\n\n- `if`:\n\n```typ\n#if true { 1 },\n#if false { 1 } else { 0 }\n```\n\n- `if-if`:\n\n```typ\n#if false { 0 } else if true { 1 },\n#if false { 2 } else if false { 1 } else { 0 }\n```\n\n- `while`:\n\n```typ\n#{\n  let i = 0;\n  while i < 10 {\n    (i * 2, )\n    i += 1;\n  }\n}\n```\n\n- `for`:\n\n```typ\n#for i in range(10) {\n  (i * 2, )\n}\n```\n\n- `for-destruct`: `#for (特色, 这个) in (neko-mimi: 2) [猫猫的 #特色 是 #这个\\ ]`\n- `break`: `#for i in range(10) { (i, ); (i + 1926, ); break }`\n- `continue`:\n\n```typ\n#for i in range(10) {\n  if calc.even(i) { continue }\n  (i, )\n}\n```\n\n- `return`:\n\n```typ\n#let never(..args) = return\n#type(never(1, 2))\n```\n\n- `include`: `#include \"other-file.typ\"`\n\n### Script Styling\n\n- `set`:\n\n```typ\n#set text(size: 24pt)\n四斤鸭梨\n```\n\n- `scope`:\n\n```typ\n两只#[兔#set text(fill: rgb(\"#ffd1dc\").darken(15%))\n  #[兔白#set text(fill: orange)\n  又白]，真可爱\n]\n```\n\n- `set-if`:\n\n```typ\n#let is-dark-theme = true\n#set rect(fill: black) if is-dark-theme\n#set text(fill: white) if is-dark-theme\n#rect([wink!])\n```\n\n- `show-set`:\n\n```typ\n#show: set text(fill: blue)\nwink!\n```\n\n- `show`:\n\n````typ\n#show raw: it => it.lines.at(1)\n获取代码片段第二行内容：```typ\n#{\nset text(fill: true)\n}\n```\n````\n\n- `text-selector`:\n\n```typ\n#show \"cpp\": strong(emph(box(\"C++\")))\n在古代，cpp是一门常用语言。\n```\n\n- `regex-selector`:\n\n```typ\n#show regex(\"[”。]+\"): it => {\n  set text(font: \"KaiTi\")\n  highlight(it, fill: yellow)\n}\n“无名，万物之始也；有名，万物之母也。”\n```\n\n- `label-selector`:\n\n```typ\n#show <一整段话>: set text(fill: blue)\n#[$lambda$语言是世界上最好的语言。] <一整段话>\n\n另一段话。\n```\n\n- `selector-exp`:\n\n```typ\n#show heading.where(level: 2): set text(fill: blue)\n= 一级标题\n== 二级标题\n```\n\n- `here`: `#context here().position()`\n- `here-calc`: `#context [ 页码是偶数：#calc.even(here().page()) ]`\n- `query`: `#context query(<ref-internal-link>).at(0).body`\n- `state`: `#state(\"my-state\", 1)`\n\n### Script Expressions\n\n- `func-call`: `#calc.pow(4, 3)`\n- `content-param`: `#emph[emphasis]`\n- `member-exp`:\n\n```typ\n#`OvO`.text\n```\n\n- `method-exp`: `#\"Hello World\".split(\" \")`\n- `dict-member-exp`:\n\n```typ\n#let cat = (neko-mimi: 2)\n#cat.neko-mimi\n```\n\n- `content-member-exp`:\n\n```typ\n#`OvO`.text\n```\n\n- `repr`: `#repr[ 一段文本 ]`\n- `type`: `#type[一段文本]`\n- `eval`: `#type(eval(\"1\"))`\n- `eval-markup-mode`: `#eval(\"== 一个标题\", mode: \"markup\")`\n- `array-in`:\n\n```typ\n#let pol = (1, \"OvO\", [])\n#(1 in pol)\n```\n\n- `array-not-in`:\n\n```typ\n#let pol = (1, \"OvO\", [])\n#([另一段内容] not in pol)\n```\n\n- `dict-in`:\n\n```typ\n#let cat = (neko-mimi: 2)\n#(\"neko-mimi\" in cat)\n```\n\n- `logical-cmp-exp`:\n\n```typ\n#(1 < 0), #(1 >= 2),\n#(1 == 2), #(1 != 2)\n```\n\n- `logical-calc-exp`: `#(not false), #(false or true), #(true and false)`\n- `plus-exp`: `#(+1), #(+0), #(1), #(++1)`\n- `minus-exp`:\n\n```typ\n#(-1), #(-0), #(--1),\n#(-+-1)\n```\n\n- `arith-exp`:\n\n```typ\n#(1 + 1), #(1 + -1),\n#(1 - 1), #(1 - -1)\n```\n\n- `assign-exp`: `#let a = 1; #repr(a = 10), #a, #repr(a += 2), #a`\n- `string-concat-exp`: `#(\"a\" + \"b\")`\n- `string-mul-exp`: `#(\"a\" * 4), #(4 * \"ab\")`\n- `string-cmp-exp`: `#(\"a\" == \"b\"), #(\"a\" != \"b\"), #(\"a\" < \"ab\"), #(\"a\" >= \"a\")`\n- `int-to-float`: `#float(1), #(type(float(1)))`\n- `bool-to-int`: `#int(true), #(type(int(true)))`\n- `float-to-int`: `#int(1), #(type(int(1)))`\n- `dec-str-to-int`: `#int(\"1\"), #(type(int(\"1\")))`\n- `nadec-str-to-int`:\n\n```typ\n#let safe-to-int(x) = {\n  let res = eval(x)\n  assert(type(res) == int, message: \"should be integer\")\n  res\n}\n#safe-to-int(\"0xf\"), #(type(safe-to-int(\"0xf\"))) \\\n#safe-to-int(\"0o755\"), #(type(safe-to-int(\"0o755\"))) \\\n#safe-to-int(\"0b1011\"), #(type(safe-to-int(\"0b1011\"))) \\\n```\n\n- `num-to-str`:\n\n```typ\n#repr(str(1)),\n#repr(str(.5))\n```\n\n- `int-to-nadec-str`: `#str(501, base:16), #str(0xdeadbeef, base:36)`\n- `bool-to-str`: `#repr(false)`\n- `int-to-bool`:\n\n```typ\n#let to-bool(x) = x != 0\n#repr(to-bool(0)),\n#repr(to-bool(1))\n```\n\n<!-- END GENERATED GRAMMAR LOOKUP -->\n"
  },
  {
    "path": ".codex/skills/update-typst-grammar-authoring/SKILL.md",
    "content": "---\nname: update-typst-grammar-authoring\ndescription: Use when maintaining the portable typst-grammar-authoring skill from this repository's canonical Typst grammar source.\nmetadata:\n  short-description: Regenerate the portable Typst grammar skill\n---\n\n# Update Typst Grammar Authoring\n\nUse this skill when a maintainer needs to refresh the portable\n`typst-grammar-authoring` skill after changing the canonical grammar examples in\n`src/tutorial/reference-grammar.typ`.\n\n## Inputs And Outputs\n\n- Canonical source: `src/tutorial/reference-grammar.typ`\n- Portable skill: `.codex/skills/typst-grammar-authoring/SKILL.md`\n- Traceability artifact:\n  `.codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json`\n\n## Workflow\n\n1. Review the source edits in `src/tutorial/reference-grammar.typ` and confirm\n   they are ready to publish into the portable skill.\n2. Regenerate the portable skill body and traceability artifact:\n\n   ```sh\n   python .codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py\n   ```\n\n3. Review the generated diff for the portable skill and the traceability JSON:\n\n   ```sh\n   git diff -- .codex/skills/typst-grammar-authoring/SKILL.md .codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json\n   ```\n\n4. Verify the portable skill stayed single-file:\n\n   ```powershell\n   Get-ChildItem -Force .codex/skills/typst-grammar-authoring\n   ```\n\n5. Verify the portable skill's command examples stayed platform-neutral and did\n   not reintroduce Windows absolute paths:\n\n   ```sh\n   rg -n \"[A-Za-z]:\\\\\\\\|path\\\\\\\\to|target\\\\\\\\typst-grammar-authoring-check|\\\\.codex\\\\\\\\skills|src\\\\\\\\tutorial\" .codex/skills/typst-grammar-authoring/SKILL.md\n   ```\n\n6. If the regenerated lookup looks correct, keep the portable skill focused on\n   author guidance only. Any exact source mapping belongs in the updater-owned\n   traceability JSON, not in the portable `SKILL.md`.\n\n## Guardrails\n\n- Keep the distributed `typst-grammar-authoring` folder limited to `SKILL.md`.\n- Treat `.codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json`\n  as the source-mapping artifact for maintainers.\n- Keep maintenance-only commands, source-path references, and review steps in\n  this updater skill rather than the portable one.\n- If the generator output reveals a design issue in the portable skill, update\n  the portable template first and rerun the generator before final review.\n"
  },
  {
    "path": ".codex/skills/update-typst-grammar-authoring/agents/openai.yaml",
    "content": "interface:\n  display_name: \"Update Typst Grammar Authoring\"\n  short_description: \"Regenerate the portable Typst grammar skill\"\n  default_prompt: \"Use $update-typst-grammar-authoring to refresh the portable Typst grammar skill and its traceability artifacts from src/tutorial/reference-grammar.typ.\"\n"
  },
  {
    "path": ".codex/skills/update-typst-grammar-authoring/references/grammar-catalog.json",
    "content": "{\n  \"source_path\": \"src/tutorial/reference-grammar.typ\",\n  \"category_count\": 6,\n  \"entry_count\": 127,\n  \"categories\": [\n    {\n      \"id\": \"base-elements\",\n      \"english_heading\": \"Base Elements\",\n      \"source_heading\": \"基本元素\",\n      \"source_anchor\": \"grammar-table:base-elements\",\n      \"heading_line\": 30,\n      \"entries\": [\n        {\n          \"lookup_key\": \"paragraph\",\n          \"source_label\": \"段落\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-paragraph>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-paragraph\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 33,\n            \"entry_line_end\": 39,\n            \"code_line_start\": 37,\n            \"code_line_end\": 37\n          },\n          \"code\": \"writing-markup\"\n        },\n        {\n          \"lookup_key\": \"heading\",\n          \"source_label\": \"标题\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-heading>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-heading\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 40,\n            \"entry_line_end\": 46,\n            \"code_line_start\": 44,\n            \"code_line_end\": 44\n          },\n          \"code\": \"= Heading\"\n        },\n        {\n          \"lookup_key\": \"heading\",\n          \"source_label\": \"二级标题\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-heading>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-heading\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 47,\n            \"entry_line_end\": 53,\n            \"code_line_start\": 51,\n            \"code_line_end\": 51\n          },\n          \"code\": \"== Heading\"\n        },\n        {\n          \"lookup_key\": \"strong\",\n          \"source_label\": \"着重标记\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-strong>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-strong\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 54,\n            \"entry_line_end\": 60,\n            \"code_line_start\": 58,\n            \"code_line_end\": 58\n          },\n          \"code\": \"*Strong*\"\n        },\n        {\n          \"lookup_key\": \"emph\",\n          \"source_label\": \"强调标记\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-emph>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-emph\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 61,\n            \"entry_line_end\": 67,\n            \"code_line_start\": 65,\n            \"code_line_end\": 65\n          },\n          \"code\": \"_emphasis_\"\n        },\n        {\n          \"lookup_key\": \"emph\",\n          \"source_label\": \"着重且强调标记\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-emph>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-emph\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 68,\n            \"entry_line_end\": 74,\n            \"code_line_start\": 72,\n            \"code_line_end\": 72\n          },\n          \"code\": \"*_emphasis_*\"\n        },\n        {\n          \"lookup_key\": \"list\",\n          \"source_label\": \"有序列表\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-list>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-list\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 75,\n            \"entry_line_end\": 82,\n            \"code_line_start\": 79,\n            \"code_line_end\": 80\n          },\n          \"code\": \"+ List 1\\n+ List 2\"\n        },\n        {\n          \"lookup_key\": \"continue-list\",\n          \"source_label\": \"有序列表（重新开始标号）\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-continue-list>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-continue-list\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 83,\n            \"entry_line_end\": 90,\n            \"code_line_start\": 87,\n            \"code_line_end\": 88\n          },\n          \"code\": \"4. List 1\\n+ List 2\"\n        },\n        {\n          \"lookup_key\": \"emum\",\n          \"source_label\": \"无序列表\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-emum>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-emum\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 91,\n            \"entry_line_end\": 98,\n            \"code_line_start\": 95,\n            \"code_line_end\": 96\n          },\n          \"code\": \"- Enum 1\\n- Enum 2\"\n        },\n        {\n          \"lookup_key\": \"mix-list-emum\",\n          \"source_label\": \"交替有序与无序列表\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-mix-list-emum>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-mix-list-emum\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 99,\n            \"entry_line_end\": 107,\n            \"code_line_start\": 103,\n            \"code_line_end\": 105\n          },\n          \"code\": \"- Enum 1\\n  + Item 1\\n- Enum 2\"\n        },\n        {\n          \"lookup_key\": \"raw\",\n          \"source_label\": \"短代码片段\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-raw>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-raw\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 108,\n            \"entry_line_end\": 114,\n            \"code_line_start\": 112,\n            \"code_line_end\": 112\n          },\n          \"code\": \"`code`\"\n        },\n        {\n          \"lookup_key\": \"long-raw\",\n          \"source_label\": \"长代码片段\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-long-raw>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-long-raw\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 115,\n            \"entry_line_end\": 121,\n            \"code_line_start\": 119,\n            \"code_line_end\": 119\n          },\n          \"code\": \"``` code```\"\n        },\n        {\n          \"lookup_key\": \"lang-raw\",\n          \"source_label\": \"语法高亮\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-lang-raw>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-lang-raw\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 122,\n            \"entry_line_end\": 128,\n            \"code_line_start\": 126,\n            \"code_line_end\": 126\n          },\n          \"code\": \"```rs  trait World```\"\n        },\n        {\n          \"lookup_key\": \"blocky-raw\",\n          \"source_label\": \"块代码片段\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-blocky-raw>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-blocky-raw\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 129,\n            \"entry_line_end\": 137,\n            \"code_line_start\": 133,\n            \"code_line_end\": 135\n          },\n          \"code\": \"```typ\\n= Heading\\n```\"\n        },\n        {\n          \"lookup_key\": \"image\",\n          \"source_label\": \"图像\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-image>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-image\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 138,\n            \"entry_line_end\": 144,\n            \"code_line_start\": 142,\n            \"code_line_end\": 142\n          },\n          \"code\": \"#image(\\\"/assets/files/香風とうふ店.jpg\\\", width: 50pt)\"\n        },\n        {\n          \"lookup_key\": \"image-stretch\",\n          \"source_label\": \"拉伸图像\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-image-stretch>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-image-stretch\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 145,\n            \"entry_line_end\": 151,\n            \"code_line_start\": 149,\n            \"code_line_end\": 149\n          },\n          \"code\": \"#image(\\\"/assets/files/香風とうふ店.jpg\\\", width: 50pt, height: 50pt, fit: \\\"stretch\\\")\"\n        },\n        {\n          \"lookup_key\": \"image-inline\",\n          \"source_label\": \"内联图像（盒子法）\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-image-inline>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-image-inline\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 152,\n            \"entry_line_end\": 158,\n            \"code_line_start\": 156,\n            \"code_line_end\": 156\n          },\n          \"code\": \"在一段话中插入一个#box(baseline: 0.15em, image(\\\"/assets/files/info-icon.svg\\\", width: 1em))图片。\"\n        },\n        {\n          \"lookup_key\": \"figure\",\n          \"source_label\": \"图像标题\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-figure>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-figure\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 159,\n            \"entry_line_end\": 167,\n            \"code_line_start\": 163,\n            \"code_line_end\": 165\n          },\n          \"code\": \"#figure(```typ\\n#image(\\\"/assets/files/香風とうふ店.jpg\\\")\\n```, caption: [用于加载香風とうふ店送外卖的宝贵影像的代码])\"\n        },\n        {\n          \"lookup_key\": \"link\",\n          \"source_label\": \"链接\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-link>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-link\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 168,\n            \"entry_line_end\": 174,\n            \"code_line_start\": 172,\n            \"code_line_end\": 172\n          },\n          \"code\": \"#link(\\\"https://zh.wikipedia.org\\\")[维基百科]\"\n        },\n        {\n          \"lookup_key\": \"http-link\",\n          \"source_label\": \"HTTP(S)链接\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-http-link>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-http-link\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 175,\n            \"entry_line_end\": 181,\n            \"code_line_start\": 179,\n            \"code_line_end\": 179\n          },\n          \"code\": \"https://zh.wikipedia.org\"\n        },\n        {\n          \"lookup_key\": \"internal-link\",\n          \"source_label\": \"内部链接\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-internal-link>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-internal-link\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 182,\n            \"entry_line_end\": 189,\n            \"code_line_start\": 186,\n            \"code_line_end\": 187\n          },\n          \"code\": \"== 某个标题 <ref-internal-link>\\n#link(<ref-internal-link>)[链接到某个标题]\"\n        },\n        {\n          \"lookup_key\": \"table\",\n          \"source_label\": \"表格\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-table>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-table\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 190,\n            \"entry_line_end\": 196,\n            \"code_line_start\": 194,\n            \"code_line_end\": 194\n          },\n          \"code\": \"#table(columns: 2, [111], [2], [3])\"\n        },\n        {\n          \"lookup_key\": \"table-align\",\n          \"source_label\": \"对齐表格\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-table-align>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-table-align\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 197,\n            \"entry_line_end\": 203,\n            \"code_line_start\": 201,\n            \"code_line_end\": 201\n          },\n          \"code\": \"#table(columns: 2, align: center, [111], [2], [3])\"\n        },\n        {\n          \"lookup_key\": \"inline-math\",\n          \"source_label\": \"行内数学公式\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-inline-math>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-inline-math\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 204,\n            \"entry_line_end\": 210,\n            \"code_line_start\": 208,\n            \"code_line_end\": 208\n          },\n          \"code\": \"$sum_x$\"\n        },\n        {\n          \"lookup_key\": \"display-math\",\n          \"source_label\": \"行间数学公式\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-display-math>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-display-math\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 211,\n            \"entry_line_end\": 217,\n            \"code_line_start\": 215,\n            \"code_line_end\": 215\n          },\n          \"code\": \"$ sum_x $\"\n        },\n        {\n          \"lookup_key\": \"escape-sequences\",\n          \"source_label\": \"标记转义序列\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-escape-sequences>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-escape-sequences\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 218,\n            \"entry_line_end\": 224,\n            \"code_line_start\": 222,\n            \"code_line_end\": 222\n          },\n          \"code\": \">\\\\_<\"\n        },\n        {\n          \"lookup_key\": \"unicode-escape-sequences\",\n          \"source_label\": \"标记的Unicode转义序列\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-unicode-escape-sequences>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-unicode-escape-sequences\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 225,\n            \"entry_line_end\": 231,\n            \"code_line_start\": 229,\n            \"code_line_end\": 229\n          },\n          \"code\": \"\\\\u{9999}\"\n        },\n        {\n          \"lookup_key\": \"newline-by-space\",\n          \"source_label\": \"换行符（转义序列）\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-newline-by-space>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-newline-by-space\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 232,\n            \"entry_line_end\": 238,\n            \"code_line_start\": 236,\n            \"code_line_end\": 236\n          },\n          \"code\": \"A \\\\ B\"\n        },\n        {\n          \"lookup_key\": \"newline\",\n          \"source_label\": \"换行符情形二\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-newline>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-newline\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 239,\n            \"entry_line_end\": 246,\n            \"code_line_start\": 243,\n            \"code_line_end\": 244\n          },\n          \"code\": \"A \\\\\\nB\"\n        },\n        {\n          \"lookup_key\": \"shorthand\",\n          \"source_label\": \"速记符\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-shorthand>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-shorthand\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 247,\n            \"entry_line_end\": 253,\n            \"code_line_start\": 251,\n            \"code_line_end\": 251\n          },\n          \"code\": \"北京--上海\"\n        },\n        {\n          \"lookup_key\": \"shorthand-space\",\n          \"source_label\": \"空格（速记符）\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-shorthand-space>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-shorthand-space\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 254,\n            \"entry_line_end\": 260,\n            \"code_line_start\": 258,\n            \"code_line_end\": 258\n          },\n          \"code\": \"A~B\"\n        },\n        {\n          \"lookup_key\": \"inline-comment\",\n          \"source_label\": \"行内注释\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-inline-comment>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-inline-comment\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 261,\n            \"entry_line_end\": 267,\n            \"code_line_start\": 265,\n            \"code_line_end\": 265\n          },\n          \"code\": \"// 行内注释\"\n        },\n        {\n          \"lookup_key\": \"cross-line-comment\",\n          \"source_label\": \"行间注释\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-cross-line-comment>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-cross-line-comment\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 268,\n            \"entry_line_end\": 275,\n            \"code_line_start\": 272,\n            \"code_line_end\": 273\n          },\n          \"code\": \"/* 行间注释\\n  */\"\n        },\n        {\n          \"lookup_key\": \"box\",\n          \"source_label\": \"行内盒子\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-box>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-box\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 276,\n            \"entry_line_end\": 282,\n            \"code_line_start\": 280,\n            \"code_line_end\": 280\n          },\n          \"code\": \"在一段话中插入一个#box(baseline: 0.15em, image(\\\"/assets/files/info-icon.svg\\\", width: 1em))图片。\"\n        }\n      ]\n    },\n    {\n      \"id\": \"text-styling\",\n      \"english_heading\": \"Text Styling\",\n      \"source_heading\": \"修饰文本\",\n      \"source_anchor\": \"grammar-table:text\",\n      \"heading_line\": 285,\n      \"entries\": [\n        {\n          \"lookup_key\": \"highlight\",\n          \"source_label\": \"背景高亮\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-highlight>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-highlight\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 288,\n            \"entry_line_end\": 294,\n            \"code_line_start\": 292,\n            \"code_line_end\": 292\n          },\n          \"code\": \"#highlight[高亮一段内容]\"\n        },\n        {\n          \"lookup_key\": \"underline\",\n          \"source_label\": \"下划线\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-underline>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-underline\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 295,\n            \"entry_line_end\": 301,\n            \"code_line_start\": 299,\n            \"code_line_end\": 299\n          },\n          \"code\": \"#underline[Language]\"\n        },\n        {\n          \"lookup_key\": \"underline-evade\",\n          \"source_label\": \"无驱逐效果的下划线\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-underline-evade>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-underline-evade\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 302,\n            \"entry_line_end\": 309,\n            \"code_line_start\": 306,\n            \"code_line_end\": 307\n          },\n          \"code\": \"#underline(\\n  evade: false)[ጿኈቼዽ]\"\n        },\n        {\n          \"lookup_key\": \"overline\",\n          \"source_label\": \"上划线\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-overline>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-overline\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 310,\n            \"entry_line_end\": 316,\n            \"code_line_start\": 314,\n            \"code_line_end\": 314\n          },\n          \"code\": \"#overline[ጿኈቼዽ]\"\n        },\n        {\n          \"lookup_key\": \"strike\",\n          \"source_label\": \"中划线（删除线）\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-strike>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-strike\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 317,\n            \"entry_line_end\": 323,\n            \"code_line_start\": 321,\n            \"code_line_end\": 321\n          },\n          \"code\": \"#strike[ጿኈቼዽ]\"\n        },\n        {\n          \"lookup_key\": \"subscript\",\n          \"source_label\": \"下标\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-subscript>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-subscript\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 324,\n            \"entry_line_end\": 330,\n            \"code_line_start\": 328,\n            \"code_line_end\": 328\n          },\n          \"code\": \"威严满满#sub[抱头蹲防]\"\n        },\n        {\n          \"lookup_key\": \"superscript\",\n          \"source_label\": \"上标\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-superscript>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-superscript\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 331,\n            \"entry_line_end\": 337,\n            \"code_line_start\": 335,\n            \"code_line_end\": 335\n          },\n          \"code\": \"香風とうふ店#super[TM]\"\n        },\n        {\n          \"lookup_key\": \"text-size\",\n          \"source_label\": \"设置文本大小\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-text-size>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-text-size\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 338,\n            \"entry_line_end\": 344,\n            \"code_line_start\": 342,\n            \"code_line_end\": 342\n          },\n          \"code\": \"#text(size: 24pt)[一斤鸭梨]\"\n        },\n        {\n          \"lookup_key\": \"text-fill\",\n          \"source_label\": \"设置文本颜色\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-text-fill>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-text-fill\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 345,\n            \"entry_line_end\": 351,\n            \"code_line_start\": 349,\n            \"code_line_end\": 349\n          },\n          \"code\": \"#text(fill: blue)[蓝色鸭梨]\"\n        },\n        {\n          \"lookup_key\": \"text-font\",\n          \"source_label\": \"设置字体\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-text-font>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-text-font\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 352,\n            \"entry_line_end\": 358,\n            \"code_line_start\": 356,\n            \"code_line_end\": 356\n          },\n          \"code\": \"#text(font: \\\"Microsoft YaHei\\\")[板正鸭梨]\"\n        }\n      ]\n    },\n    {\n      \"id\": \"script-declarations\",\n      \"english_heading\": \"Script Declarations\",\n      \"source_heading\": \"脚本声明\",\n      \"source_anchor\": \"grammar-table:decl\",\n      \"heading_line\": 361,\n      \"entries\": [\n        {\n          \"lookup_key\": \"enter-script\",\n          \"source_label\": \"进入脚本模式\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-enter-script>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-enter-script\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 364,\n            \"entry_line_end\": 370,\n            \"code_line_start\": 368,\n            \"code_line_end\": 368\n          },\n          \"code\": \"#1\"\n        },\n        {\n          \"lookup_key\": \"code-block\",\n          \"source_label\": \"代码块\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-code-block>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-code-block\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 371,\n            \"entry_line_end\": 377,\n            \"code_line_start\": 375,\n            \"code_line_end\": 375\n          },\n          \"code\": \"#{\\\"a\\\"; \\\"b\\\"}\"\n        },\n        {\n          \"lookup_key\": \"content-block\",\n          \"source_label\": \"内容块\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-content-block>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-content-block\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 378,\n            \"entry_line_end\": 384,\n            \"code_line_start\": 382,\n            \"code_line_end\": 382\n          },\n          \"code\": \"#[内容块]\"\n        },\n        {\n          \"lookup_key\": \"none-literal\",\n          \"source_label\": \"空字面量\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-none-literal>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-none-literal\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 385,\n            \"entry_line_end\": 391,\n            \"code_line_start\": 389,\n            \"code_line_end\": 389\n          },\n          \"code\": \"#none\"\n        },\n        {\n          \"lookup_key\": \"false-literal\",\n          \"source_label\": \"假（布尔值）\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-false-literal>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-false-literal\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 392,\n            \"entry_line_end\": 398,\n            \"code_line_start\": 396,\n            \"code_line_end\": 396\n          },\n          \"code\": \"#false\"\n        },\n        {\n          \"lookup_key\": \"true-literal\",\n          \"source_label\": \"真（布尔值）\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-true-literal>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-true-literal\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 399,\n            \"entry_line_end\": 405,\n            \"code_line_start\": 403,\n            \"code_line_end\": 403\n          },\n          \"code\": \"#true\"\n        },\n        {\n          \"lookup_key\": \"integer-literal\",\n          \"source_label\": \"整数字面量\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-integer-literal>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-integer-literal\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 406,\n            \"entry_line_end\": 412,\n            \"code_line_start\": 410,\n            \"code_line_end\": 410\n          },\n          \"code\": \"#(-1), #(0), #(1)\"\n        },\n        {\n          \"lookup_key\": \"n-adecimal-literal\",\n          \"source_label\": \"进制数字面量\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-n-adecimal-literal>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-n-adecimal-literal\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 413,\n            \"entry_line_end\": 419,\n            \"code_line_start\": 417,\n            \"code_line_end\": 417\n          },\n          \"code\": \"#(-0xdeadbeef), #(-0o644), #(-0b1001)\"\n        },\n        {\n          \"lookup_key\": \"float-literal\",\n          \"source_label\": \"浮点数字面量\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-float-literal>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-float-literal\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 420,\n            \"entry_line_end\": 426,\n            \"code_line_start\": 424,\n            \"code_line_end\": 424\n          },\n          \"code\": \"#(0.001), #(.1), #(2.)\"\n        },\n        {\n          \"lookup_key\": \"exp-repr-float\",\n          \"source_label\": \"指数表示法\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-exp-repr-float>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-exp-repr-float\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 427,\n            \"entry_line_end\": 433,\n            \"code_line_start\": 431,\n            \"code_line_end\": 431\n          },\n          \"code\": \"#(1e2), #(1.926e3), #(-1e-3)\"\n        },\n        {\n          \"lookup_key\": \"string-literal\",\n          \"source_label\": \"字符串字面量\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-string-literal>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-string-literal\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 434,\n            \"entry_line_end\": 440,\n            \"code_line_start\": 438,\n            \"code_line_end\": 438\n          },\n          \"code\": \"#\\\"Hello world!!\\\"\"\n        },\n        {\n          \"lookup_key\": \"str-escape-sequences\",\n          \"source_label\": \"字符串转义序列\",\n          \"reference\": {\n            \"expression\": \"refs.scripting-base.with(reference: <grammar-str-escape-sequences>)\",\n            \"module\": \"scripting-base\",\n            \"anchor\": \"grammar-str-escape-sequences\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 441,\n            \"entry_line_end\": 447,\n            \"code_line_start\": 445,\n            \"code_line_end\": 445\n          },\n          \"code\": \"#\\\"\\\\\\\"\\\"\"\n        },\n        {\n          \"lookup_key\": \"str-unicode-escape-sequences\",\n          \"source_label\": \"字符串的Unicode转义序列\",\n          \"reference\": {\n            \"expression\": \"refs.scripting-base.with(reference: <grammar-str-unicode-escape-sequences>)\",\n            \"module\": \"scripting-base\",\n            \"anchor\": \"grammar-str-unicode-escape-sequences\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 448,\n            \"entry_line_end\": 454,\n            \"code_line_start\": 452,\n            \"code_line_end\": 452\n          },\n          \"code\": \"#\\\"\\\\u{9999}\\\"\"\n        },\n        {\n          \"lookup_key\": \"array-literal\",\n          \"source_label\": \"数组字面量\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-array-literal>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-array-literal\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 455,\n            \"entry_line_end\": 461,\n            \"code_line_start\": 459,\n            \"code_line_end\": 459\n          },\n          \"code\": \"#(1, \\\"OvO\\\", [一段内容])\"\n        },\n        {\n          \"lookup_key\": \"dict-literal\",\n          \"source_label\": \"字典字面量\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-dict-literal>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-dict-literal\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 462,\n            \"entry_line_end\": 468,\n            \"code_line_start\": 466,\n            \"code_line_end\": 466\n          },\n          \"code\": \"#(neko-mimi: 2, \\\"utterance\\\": \\\"喵喵喵\\\")\"\n        },\n        {\n          \"lookup_key\": \"empty-array\",\n          \"source_label\": \"空数组\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-empty-array>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-empty-array\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 469,\n            \"entry_line_end\": 475,\n            \"code_line_start\": 473,\n            \"code_line_end\": 473\n          },\n          \"code\": \"#()\"\n        },\n        {\n          \"lookup_key\": \"empty-dict\",\n          \"source_label\": \"空字典\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-empty-dict>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-empty-dict\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 476,\n            \"entry_line_end\": 482,\n            \"code_line_start\": 480,\n            \"code_line_end\": 480\n          },\n          \"code\": \"#(:)\"\n        },\n        {\n          \"lookup_key\": \"paren-empty-array\",\n          \"source_label\": \"被括号包裹的空数组\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-paren-empty-array>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-paren-empty-array\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 483,\n            \"entry_line_end\": 489,\n            \"code_line_start\": 487,\n            \"code_line_end\": 487\n          },\n          \"code\": \"#(())\"\n        },\n        {\n          \"lookup_key\": \"single-member-array\",\n          \"source_label\": \"含有一个元素的数组\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-single-member-array>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-single-member-array\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 490,\n            \"entry_line_end\": 496,\n            \"code_line_start\": 494,\n            \"code_line_end\": 494\n          },\n          \"code\": \"#(1,)\"\n        },\n        {\n          \"lookup_key\": \"var-decl\",\n          \"source_label\": \"变量声明\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-var-decl>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-var-decl\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 497,\n            \"entry_line_end\": 503,\n            \"code_line_start\": 501,\n            \"code_line_end\": 501\n          },\n          \"code\": \"#let x = 1\"\n        },\n        {\n          \"lookup_key\": \"func-decl\",\n          \"source_label\": \"函数声明\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-func-decl>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-func-decl\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 504,\n            \"entry_line_end\": 510,\n            \"code_line_start\": 508,\n            \"code_line_end\": 508\n          },\n          \"code\": \"#let f(x) = x * 2\"\n        },\n        {\n          \"lookup_key\": \"closure\",\n          \"source_label\": \"函数闭包\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-closure>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-closure\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 511,\n            \"entry_line_end\": 517,\n            \"code_line_start\": 515,\n            \"code_line_end\": 515\n          },\n          \"code\": \"#let f = (x, y) => x + y\"\n        },\n        {\n          \"lookup_key\": \"named-param\",\n          \"source_label\": \"具名参数声明\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-named-param>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-named-param\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 518,\n            \"entry_line_end\": 524,\n            \"code_line_start\": 522,\n            \"code_line_end\": 522\n          },\n          \"code\": \"#let g(named: none) = named\"\n        },\n        {\n          \"lookup_key\": \"variadic-param\",\n          \"source_label\": \"含变参函数\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-variadic-param>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-variadic-param\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 525,\n            \"entry_line_end\": 531,\n            \"code_line_start\": 529,\n            \"code_line_end\": 529\n          },\n          \"code\": \"#let g(..args) = args.pos().join([、])\"\n        },\n        {\n          \"lookup_key\": \"destruct-array\",\n          \"source_label\": \"数组解构赋值\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-destruct-array>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-destruct-array\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 532,\n            \"entry_line_end\": 538,\n            \"code_line_start\": 536,\n            \"code_line_end\": 536\n          },\n          \"code\": \"#let (one, hello-world) = (1, \\\"Hello, World\\\")\"\n        },\n        {\n          \"lookup_key\": \"destruct-array-eliminate\",\n          \"source_label\": \"数组解构赋值情形二\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-destruct-array-eliminate>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-destruct-array-eliminate\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 539,\n            \"entry_line_end\": 545,\n            \"code_line_start\": 543,\n            \"code_line_end\": 543\n          },\n          \"code\": \"#let (_, second, ..) = (1, \\\"Hello, World\\\", []); #second\"\n        },\n        {\n          \"lookup_key\": \"destruct-dict\",\n          \"source_label\": \"字典解构赋值\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-destruct-dict>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-destruct-dict\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 546,\n            \"entry_line_end\": 552,\n            \"code_line_start\": 550,\n            \"code_line_end\": 550\n          },\n          \"code\": \"#let (neko-mimi: mimi) = (neko-mimi: 2); #mimi\"\n        },\n        {\n          \"lookup_key\": \"array-remapping\",\n          \"source_label\": \"数组内容重映射\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-array-remapping>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-array-remapping\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 553,\n            \"entry_line_end\": 561,\n            \"code_line_start\": 557,\n            \"code_line_end\": 559\n          },\n          \"code\": \"#let (a, b, c) = (1, 2, 3)\\n#let (b, c, a) = (a, b, c)\\n#a, #b, #c\"\n        },\n        {\n          \"lookup_key\": \"array-swap\",\n          \"source_label\": \"数组内容交换\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-array-swap>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-array-swap\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 562,\n            \"entry_line_end\": 570,\n            \"code_line_start\": 566,\n            \"code_line_end\": 568\n          },\n          \"code\": \"#let (a, b) = (1, 2)\\n#((a, b) = (b, a))\\n#a, #b\"\n        },\n        {\n          \"lookup_key\": \"placeholder\",\n          \"source_label\": \"占位符（`let _ = ..`）\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-placeholder>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-placeholder\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 571,\n            \"entry_line_end\": 581,\n            \"code_line_start\": 575,\n            \"code_line_end\": 579\n          },\n          \"code\": \"#let last-two(t) = {\\n  let _ = t.pop()\\n  t.pop()\\n}\\n#last-two((1, 2, 3, 4))\"\n        }\n      ]\n    },\n    {\n      \"id\": \"script-statements\",\n      \"english_heading\": \"Script Statements\",\n      \"source_heading\": \"脚本语句\",\n      \"source_anchor\": null,\n      \"heading_line\": 584,\n      \"entries\": [\n        {\n          \"lookup_key\": \"if\",\n          \"source_label\": \"`if`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-if>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-if\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 587,\n            \"entry_line_end\": 594,\n            \"code_line_start\": 591,\n            \"code_line_end\": 592\n          },\n          \"code\": \"#if true { 1 },\\n#if false { 1 } else { 0 }\"\n        },\n        {\n          \"lookup_key\": \"if-if\",\n          \"source_label\": \"串联`if`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-if-if>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-if-if\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 595,\n            \"entry_line_end\": 602,\n            \"code_line_start\": 599,\n            \"code_line_end\": 600\n          },\n          \"code\": \"#if false { 0 } else if true { 1 },\\n#if false { 2 } else if false { 1 } else { 0 }\"\n        },\n        {\n          \"lookup_key\": \"while\",\n          \"source_label\": \"`while`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-while>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-while\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 603,\n            \"entry_line_end\": 615,\n            \"code_line_start\": 607,\n            \"code_line_end\": 613\n          },\n          \"code\": \"#{\\n  let i = 0;\\n  while i < 10 {\\n    (i * 2, )\\n    i += 1;\\n  }\\n}\"\n        },\n        {\n          \"lookup_key\": \"for\",\n          \"source_label\": \"`for`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-for>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-for\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 616,\n            \"entry_line_end\": 624,\n            \"code_line_start\": 620,\n            \"code_line_end\": 622\n          },\n          \"code\": \"#for i in range(10) {\\n  (i * 2, )\\n}\"\n        },\n        {\n          \"lookup_key\": \"for-destruct\",\n          \"source_label\": \"`for`语句解构赋值\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-for-destruct>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-for-destruct\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 625,\n            \"entry_line_end\": 631,\n            \"code_line_start\": 629,\n            \"code_line_end\": 629\n          },\n          \"code\": \"#for (特色, 这个) in (neko-mimi: 2) [猫猫的 #特色 是 #这个\\\\ ]\"\n        },\n        {\n          \"lookup_key\": \"break\",\n          \"source_label\": \"`break`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-break>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-break\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 632,\n            \"entry_line_end\": 638,\n            \"code_line_start\": 636,\n            \"code_line_end\": 636\n          },\n          \"code\": \"#for i in range(10) { (i, ); (i + 1926, ); break }\"\n        },\n        {\n          \"lookup_key\": \"continue\",\n          \"source_label\": \"`continue`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-continue>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-continue\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 639,\n            \"entry_line_end\": 648,\n            \"code_line_start\": 643,\n            \"code_line_end\": 646\n          },\n          \"code\": \"#for i in range(10) {\\n  if calc.even(i) { continue }\\n  (i, )\\n}\"\n        },\n        {\n          \"lookup_key\": \"return\",\n          \"source_label\": \"`return`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-return>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-return\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 649,\n            \"entry_line_end\": 656,\n            \"code_line_start\": 653,\n            \"code_line_end\": 654\n          },\n          \"code\": \"#let never(..args) = return\\n#type(never(1, 2))\"\n        },\n        {\n          \"lookup_key\": \"include\",\n          \"source_label\": \"`include`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-include>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-include\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 657,\n            \"entry_line_end\": 663,\n            \"code_line_start\": 661,\n            \"code_line_end\": 661\n          },\n          \"code\": \"#include \\\"other-file.typ\\\"\"\n        }\n      ]\n    },\n    {\n      \"id\": \"script-styling\",\n      \"english_heading\": \"Script Styling\",\n      \"source_heading\": \"脚本样式\",\n      \"source_anchor\": null,\n      \"heading_line\": 666,\n      \"entries\": [\n        {\n          \"lookup_key\": \"set\",\n          \"source_label\": \"`set`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-set>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-set\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 669,\n            \"entry_line_end\": 676,\n            \"code_line_start\": 673,\n            \"code_line_end\": 674\n          },\n          \"code\": \"#set text(size: 24pt)\\n四斤鸭梨\"\n        },\n        {\n          \"lookup_key\": \"scope\",\n          \"source_label\": \"作用域\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-scope>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-scope\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 677,\n            \"entry_line_end\": 686,\n            \"code_line_start\": 681,\n            \"code_line_end\": 684\n          },\n          \"code\": \"两只#[兔#set text(fill: rgb(\\\"#ffd1dc\\\").darken(15%))\\n  #[兔白#set text(fill: orange)\\n  又白]，真可爱\\n]\"\n        },\n        {\n          \"lookup_key\": \"set-if\",\n          \"source_label\": \"`set if`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-set-if>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-set-if\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 687,\n            \"entry_line_end\": 696,\n            \"code_line_start\": 691,\n            \"code_line_end\": 694\n          },\n          \"code\": \"#let is-dark-theme = true\\n#set rect(fill: black) if is-dark-theme\\n#set text(fill: white) if is-dark-theme\\n#rect([wink!])\"\n        },\n        {\n          \"lookup_key\": \"show-set\",\n          \"source_label\": \"`show set`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-show-set>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-show-set\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 697,\n            \"entry_line_end\": 704,\n            \"code_line_start\": 701,\n            \"code_line_end\": 702\n          },\n          \"code\": \"#show: set text(fill: blue)\\nwink!\"\n        },\n        {\n          \"lookup_key\": \"show\",\n          \"source_label\": \"`show`语句\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-show>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-show\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 705,\n            \"entry_line_end\": 716,\n            \"code_line_start\": 709,\n            \"code_line_end\": 714\n          },\n          \"code\": \"#show raw: it => it.lines.at(1)\\n获取代码片段第二行内容：```typ\\n#{\\nset text(fill: true)\\n}\\n```\"\n        },\n        {\n          \"lookup_key\": \"text-selector\",\n          \"source_label\": \"文本选择器\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-text-selector>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-text-selector\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 717,\n            \"entry_line_end\": 724,\n            \"code_line_start\": 721,\n            \"code_line_end\": 722\n          },\n          \"code\": \"#show \\\"cpp\\\": strong(emph(box(\\\"C++\\\")))\\n在古代，cpp是一门常用语言。\"\n        },\n        {\n          \"lookup_key\": \"regex-selector\",\n          \"source_label\": \"正则文本选择器\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-regex-selector>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-regex-selector\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 725,\n            \"entry_line_end\": 735,\n            \"code_line_start\": 729,\n            \"code_line_end\": 733\n          },\n          \"code\": \"#show regex(\\\"[”。]+\\\"): it => {\\n  set text(font: \\\"KaiTi\\\")\\n  highlight(it, fill: yellow)\\n}\\n“无名，万物之始也；有名，万物之母也。”\"\n        },\n        {\n          \"lookup_key\": \"label-selector\",\n          \"source_label\": \"标签选择器\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-label-selector>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-label-selector\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 736,\n            \"entry_line_end\": 745,\n            \"code_line_start\": 740,\n            \"code_line_end\": 743\n          },\n          \"code\": \"#show <一整段话>: set text(fill: blue)\\n#[$lambda$语言是世界上最好的语言。] <一整段话>\\n\\n另一段话。\"\n        },\n        {\n          \"lookup_key\": \"selector-exp\",\n          \"source_label\": \"选择器表达式\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-selector-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-selector-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 746,\n            \"entry_line_end\": 754,\n            \"code_line_start\": 750,\n            \"code_line_end\": 752\n          },\n          \"code\": \"#show heading.where(level: 2): set text(fill: blue)\\n= 一级标题\\n== 二级标题\"\n        },\n        {\n          \"lookup_key\": \"here\",\n          \"source_label\": \"获取位置\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-here>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-here\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 755,\n            \"entry_line_end\": 761,\n            \"code_line_start\": 759,\n            \"code_line_end\": 759\n          },\n          \"code\": \"#context here().position()\"\n        },\n        {\n          \"lookup_key\": \"here-calc\",\n          \"source_label\": \"检测当前页面是否为偶数页（位置相关计算）\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-here-calc>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-here-calc\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 762,\n            \"entry_line_end\": 768,\n            \"code_line_start\": 766,\n            \"code_line_end\": 766\n          },\n          \"code\": \"#context [ 页码是偶数：#calc.even(here().page()) ]\"\n        },\n        {\n          \"lookup_key\": \"query\",\n          \"source_label\": \"查询文档内容\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-query>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-query\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 769,\n            \"entry_line_end\": 775,\n            \"code_line_start\": 773,\n            \"code_line_end\": 773\n          },\n          \"code\": \"#context query(<ref-internal-link>).at(0).body\"\n        },\n        {\n          \"lookup_key\": \"state\",\n          \"source_label\": \"声明全局状态\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-state>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-state\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 776,\n            \"entry_line_end\": 782,\n            \"code_line_start\": 780,\n            \"code_line_end\": 780\n          },\n          \"code\": \"#state(\\\"my-state\\\", 1)\"\n        }\n      ]\n    },\n    {\n      \"id\": \"script-expressions\",\n      \"english_heading\": \"Script Expressions\",\n      \"source_heading\": \"脚本表达式\",\n      \"source_anchor\": null,\n      \"heading_line\": 785,\n      \"entries\": [\n        {\n          \"lookup_key\": \"func-call\",\n          \"source_label\": \"函数调用\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-func-call>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-func-call\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 788,\n            \"entry_line_end\": 794,\n            \"code_line_start\": 792,\n            \"code_line_end\": 792\n          },\n          \"code\": \"#calc.pow(4, 3)\"\n        },\n        {\n          \"lookup_key\": \"content-param\",\n          \"source_label\": \"函数调用传递内容参数\",\n          \"reference\": {\n            \"expression\": \"refs.writing-markup.with(reference: <grammar-content-param>)\",\n            \"module\": \"writing-markup\",\n            \"anchor\": \"grammar-content-param\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 795,\n            \"entry_line_end\": 801,\n            \"code_line_start\": 799,\n            \"code_line_end\": 799\n          },\n          \"code\": \"#emph[emphasis]\"\n        },\n        {\n          \"lookup_key\": \"member-exp\",\n          \"source_label\": \"成员访问\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-member-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-member-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 802,\n            \"entry_line_end\": 808,\n            \"code_line_start\": 806,\n            \"code_line_end\": 806\n          },\n          \"code\": \"#`OvO`.text\"\n        },\n        {\n          \"lookup_key\": \"method-exp\",\n          \"source_label\": \"方法调用\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-method-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-method-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 809,\n            \"entry_line_end\": 815,\n            \"code_line_start\": 813,\n            \"code_line_end\": 813\n          },\n          \"code\": \"#\\\"Hello World\\\".split(\\\" \\\")\"\n        },\n        {\n          \"lookup_key\": \"dict-member-exp\",\n          \"source_label\": \"字典成员访问\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-dict-member-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-dict-member-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 816,\n            \"entry_line_end\": 823,\n            \"code_line_start\": 820,\n            \"code_line_end\": 821\n          },\n          \"code\": \"#let cat = (neko-mimi: 2)\\n#cat.neko-mimi\"\n        },\n        {\n          \"lookup_key\": \"content-member-exp\",\n          \"source_label\": \"内容成员访问\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-content-member-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-content-member-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 824,\n            \"entry_line_end\": 830,\n            \"code_line_start\": 828,\n            \"code_line_end\": 828\n          },\n          \"code\": \"#`OvO`.text\"\n        },\n        {\n          \"lookup_key\": \"repr\",\n          \"source_label\": \"代码表示的自省函数\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-repr>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-repr\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 831,\n            \"entry_line_end\": 837,\n            \"code_line_start\": 835,\n            \"code_line_end\": 835\n          },\n          \"code\": \"#repr[ 一段文本 ]\"\n        },\n        {\n          \"lookup_key\": \"type\",\n          \"source_label\": \"类型的自省函数\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-type>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-type\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 838,\n            \"entry_line_end\": 844,\n            \"code_line_start\": 842,\n            \"code_line_end\": 842\n          },\n          \"code\": \"#type[一段文本]\"\n        },\n        {\n          \"lookup_key\": \"eval\",\n          \"source_label\": \"求值函数\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-eval>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-eval\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 845,\n            \"entry_line_end\": 851,\n            \"code_line_start\": 849,\n            \"code_line_end\": 849\n          },\n          \"code\": \"#type(eval(\\\"1\\\"))\"\n        },\n        {\n          \"lookup_key\": \"eval-markup-mode\",\n          \"source_label\": \"求值函数（标记模式）\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-eval-markup-mode>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-eval-markup-mode\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 852,\n            \"entry_line_end\": 858,\n            \"code_line_start\": 856,\n            \"code_line_end\": 856\n          },\n          \"code\": \"#eval(\\\"== 一个标题\\\", mode: \\\"markup\\\")\"\n        },\n        {\n          \"lookup_key\": \"array-in\",\n          \"source_label\": \"判断数组内容\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-array-in>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-array-in\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 859,\n            \"entry_line_end\": 866,\n            \"code_line_start\": 863,\n            \"code_line_end\": 864\n          },\n          \"code\": \"#let pol = (1, \\\"OvO\\\", [])\\n#(1 in pol)\"\n        },\n        {\n          \"lookup_key\": \"array-not-in\",\n          \"source_label\": \"判断数组内容不在\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-array-not-in>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-array-not-in\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 867,\n            \"entry_line_end\": 874,\n            \"code_line_start\": 871,\n            \"code_line_end\": 872\n          },\n          \"code\": \"#let pol = (1, \\\"OvO\\\", [])\\n#([另一段内容] not in pol)\"\n        },\n        {\n          \"lookup_key\": \"dict-in\",\n          \"source_label\": \"判断字典内容\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-dict-in>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-dict-in\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 875,\n            \"entry_line_end\": 882,\n            \"code_line_start\": 879,\n            \"code_line_end\": 880\n          },\n          \"code\": \"#let cat = (neko-mimi: 2)\\n#(\\\"neko-mimi\\\" in cat)\"\n        },\n        {\n          \"lookup_key\": \"logical-cmp-exp\",\n          \"source_label\": \"逻辑比较表达式\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-logical-cmp-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-logical-cmp-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 883,\n            \"entry_line_end\": 890,\n            \"code_line_start\": 887,\n            \"code_line_end\": 888\n          },\n          \"code\": \"#(1 < 0), #(1 >= 2),\\n#(1 == 2), #(1 != 2)\"\n        },\n        {\n          \"lookup_key\": \"logical-calc-exp\",\n          \"source_label\": \"逻辑运算表达式\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-logical-calc-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-logical-calc-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 891,\n            \"entry_line_end\": 897,\n            \"code_line_start\": 895,\n            \"code_line_end\": 895\n          },\n          \"code\": \"#(not false), #(false or true), #(true and false)\"\n        },\n        {\n          \"lookup_key\": \"plus-exp\",\n          \"source_label\": \"取正运算\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-plus-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-plus-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 898,\n            \"entry_line_end\": 904,\n            \"code_line_start\": 902,\n            \"code_line_end\": 902\n          },\n          \"code\": \"#(+1), #(+0), #(1), #(++1)\"\n        },\n        {\n          \"lookup_key\": \"minus-exp\",\n          \"source_label\": \"取负运算\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-minus-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-minus-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 905,\n            \"entry_line_end\": 912,\n            \"code_line_start\": 909,\n            \"code_line_end\": 910\n          },\n          \"code\": \"#(-1), #(-0), #(--1),\\n#(-+-1)\"\n        },\n        {\n          \"lookup_key\": \"arith-exp\",\n          \"source_label\": \"算术运算表达式\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-arith-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-arith-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 913,\n            \"entry_line_end\": 920,\n            \"code_line_start\": 917,\n            \"code_line_end\": 918\n          },\n          \"code\": \"#(1 + 1), #(1 + -1),\\n#(1 - 1), #(1 - -1)\"\n        },\n        {\n          \"lookup_key\": \"assign-exp\",\n          \"source_label\": \"赋值表达式\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-assign-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-assign-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 921,\n            \"entry_line_end\": 927,\n            \"code_line_start\": 925,\n            \"code_line_end\": 925\n          },\n          \"code\": \"#let a = 1; #repr(a = 10), #a, #repr(a += 2), #a\"\n        },\n        {\n          \"lookup_key\": \"string-concat-exp\",\n          \"source_label\": \"字符串连接\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-string-concat-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-string-concat-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 928,\n            \"entry_line_end\": 934,\n            \"code_line_start\": 932,\n            \"code_line_end\": 932\n          },\n          \"code\": \"#(\\\"a\\\" + \\\"b\\\")\"\n        },\n        {\n          \"lookup_key\": \"string-mul-exp\",\n          \"source_label\": \"重复连接字符串\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-string-mul-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-string-mul-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 935,\n            \"entry_line_end\": 941,\n            \"code_line_start\": 939,\n            \"code_line_end\": 939\n          },\n          \"code\": \"#(\\\"a\\\" * 4), #(4 * \\\"ab\\\")\"\n        },\n        {\n          \"lookup_key\": \"string-cmp-exp\",\n          \"source_label\": \"字符串比较\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-string-cmp-exp>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-string-cmp-exp\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 942,\n            \"entry_line_end\": 948,\n            \"code_line_start\": 946,\n            \"code_line_end\": 946\n          },\n          \"code\": \"#(\\\"a\\\" == \\\"b\\\"), #(\\\"a\\\" != \\\"b\\\"), #(\\\"a\\\" < \\\"ab\\\"), #(\\\"a\\\" >= \\\"a\\\")\"\n        },\n        {\n          \"lookup_key\": \"int-to-float\",\n          \"source_label\": \"整数转浮点数\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-int-to-float>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-int-to-float\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 949,\n            \"entry_line_end\": 955,\n            \"code_line_start\": 953,\n            \"code_line_end\": 953\n          },\n          \"code\": \"#float(1), #(type(float(1)))\"\n        },\n        {\n          \"lookup_key\": \"bool-to-int\",\n          \"source_label\": \"布尔值转整数\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-bool-to-int>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-bool-to-int\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 956,\n            \"entry_line_end\": 962,\n            \"code_line_start\": 960,\n            \"code_line_end\": 960\n          },\n          \"code\": \"#int(true), #(type(int(true)))\"\n        },\n        {\n          \"lookup_key\": \"float-to-int\",\n          \"source_label\": \"浮点数转整数\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-float-to-int>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-float-to-int\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 963,\n            \"entry_line_end\": 969,\n            \"code_line_start\": 967,\n            \"code_line_end\": 967\n          },\n          \"code\": \"#int(1), #(type(int(1)))\"\n        },\n        {\n          \"lookup_key\": \"dec-str-to-int\",\n          \"source_label\": \"十进制字符串转整数\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-dec-str-to-int>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-dec-str-to-int\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 970,\n            \"entry_line_end\": 976,\n            \"code_line_start\": 974,\n            \"code_line_end\": 974\n          },\n          \"code\": \"#int(\\\"1\\\"), #(type(int(\\\"1\\\")))\"\n        },\n        {\n          \"lookup_key\": \"nadec-str-to-int\",\n          \"source_label\": \"十六进制/八进制/二进制字符串转整数\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-nadec-str-to-int>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-nadec-str-to-int\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 977,\n            \"entry_line_end\": 990,\n            \"code_line_start\": 981,\n            \"code_line_end\": 988\n          },\n          \"code\": \"#let safe-to-int(x) = {\\n  let res = eval(x)\\n  assert(type(res) == int, message: \\\"should be integer\\\")\\n  res\\n}\\n#safe-to-int(\\\"0xf\\\"), #(type(safe-to-int(\\\"0xf\\\"))) \\\\\\n#safe-to-int(\\\"0o755\\\"), #(type(safe-to-int(\\\"0o755\\\"))) \\\\\\n#safe-to-int(\\\"0b1011\\\"), #(type(safe-to-int(\\\"0b1011\\\"))) \\\\\"\n        },\n        {\n          \"lookup_key\": \"num-to-str\",\n          \"source_label\": \"数字转字符串\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-num-to-str>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-num-to-str\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 991,\n            \"entry_line_end\": 998,\n            \"code_line_start\": 995,\n            \"code_line_end\": 996\n          },\n          \"code\": \"#repr(str(1)),\\n#repr(str(.5))\"\n        },\n        {\n          \"lookup_key\": \"int-to-nadec-str\",\n          \"source_label\": \"整数转十六进制字符串\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-int-to-nadec-str>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-int-to-nadec-str\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 999,\n            \"entry_line_end\": 1005,\n            \"code_line_start\": 1003,\n            \"code_line_end\": 1003\n          },\n          \"code\": \"#str(501, base:16), #str(0xdeadbeef, base:36)\"\n        },\n        {\n          \"lookup_key\": \"bool-to-str\",\n          \"source_label\": \"布尔值转字符串\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-bool-to-str>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-bool-to-str\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 1006,\n            \"entry_line_end\": 1012,\n            \"code_line_start\": 1010,\n            \"code_line_end\": 1010\n          },\n          \"code\": \"#repr(false)\"\n        },\n        {\n          \"lookup_key\": \"int-to-bool\",\n          \"source_label\": \"数字转布尔值\",\n          \"reference\": {\n            \"expression\": \"refs.content-scope-style.with(reference: <grammar-int-to-bool>)\",\n            \"module\": \"content-scope-style\",\n            \"anchor\": \"grammar-int-to-bool\"\n          },\n          \"source\": {\n            \"path\": \"src/tutorial/reference-grammar.typ\",\n            \"entry_line_start\": 1013,\n            \"entry_line_end\": 1021,\n            \"code_line_start\": 1017,\n            \"code_line_end\": 1019\n          },\n          \"code\": \"#let to-bool(x) = x != 0\\n#repr(to-bool(0)),\\n#repr(to-bool(1))\"\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": ".codex/skills/update-typst-grammar-authoring/scripts/generate_grammar_catalog.py",
    "content": "#!/usr/bin/env python3\n\"\"\"Generate Typst grammar data and sync it into the portable skill.\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport json\nimport os\nimport re\nimport sys\nimport textwrap\nfrom collections import OrderedDict\nfrom pathlib import Path\n\n\nCATEGORY_MAP = {\n    \"基本元素\": {\n        \"id\": \"base-elements\",\n        \"title\": \"Base Elements\",\n    },\n    \"修饰文本\": {\n        \"id\": \"text-styling\",\n        \"title\": \"Text Styling\",\n    },\n    \"脚本声明\": {\n        \"id\": \"script-declarations\",\n        \"title\": \"Script Declarations\",\n    },\n    \"脚本语句\": {\n        \"id\": \"script-statements\",\n        \"title\": \"Script Statements\",\n    },\n    \"脚本样式\": {\n        \"id\": \"script-styling\",\n        \"title\": \"Script Styling\",\n    },\n    \"脚本表达式\": {\n        \"id\": \"script-expressions\",\n        \"title\": \"Script Expressions\",\n    },\n}\n\nCATEGORY_PATTERN = re.compile(\n    r\"^==\\s+分类：(?P<source_heading>.+?)(?:\\s+<(?P<source_anchor>[^>]+)>)?\\s*$\",\n    re.MULTILINE,\n)\n\nENTRY_PATTERN = re.compile(\n    r\"\"\"\n    \\(\\s*\\n\n    \\s*\\[(?P<source_label>.*?)\\],\\s*\\n\n    \\s*(?P<reference_expr>refs\\.(?P<reference_module>[\\w-]+)\\.with\\(reference:\\s*<(?P<reference_anchor>[^>]+)>\\)),\\s*\\n\n    \\s*`{3,4}typ\\s*\\n\n    (?P<code>.*?)\n    \\n\\s*`{3,4},\\s*\\n\n    \\s*\\),\n    \"\"\",\n    re.DOTALL | re.VERBOSE,\n)\n\nGENERATED_BEGIN = \"<!-- BEGIN GENERATED GRAMMAR LOOKUP -->\"\nGENERATED_END = \"<!-- END GENERATED GRAMMAR LOOKUP -->\"\nINLINE_CODE_LIMIT = 88\n\n\ndef line_number(text: str, offset: int) -> int:\n    return text.count(\"\\n\", 0, offset) + 1\n\n\ndef normalize_code(raw_code: str) -> str:\n    return textwrap.dedent(raw_code).strip(\"\\n\")\n\n\ndef lookup_key(reference_anchor: str) -> str:\n    prefix = \"grammar-\"\n    if reference_anchor.startswith(prefix):\n        return reference_anchor[len(prefix) :]\n    return reference_anchor\n\n\ndef repo_relative(path: Path, repo_root: Path) -> str:\n    try:\n        return Path(os.path.relpath(path.resolve(), repo_root.resolve())).as_posix()\n    except ValueError:\n        return path.resolve().as_posix()\n\n\ndef parse_categories(\n    source_text: str, source_path: Path, repo_root: Path\n) -> list[dict]:\n    categories = []\n    category_matches = list(CATEGORY_PATTERN.finditer(source_text))\n    if not category_matches:\n        raise ValueError(f\"No categories found in {source_path}\")\n\n    for index, match in enumerate(category_matches):\n        section_start = match.end()\n        section_end = (\n            category_matches[index + 1].start()\n            if index + 1 < len(category_matches)\n            else len(source_text)\n        )\n        section_text = source_text[section_start:section_end]\n        source_heading = match.group(\"source_heading\")\n        english_heading = CATEGORY_MAP.get(source_heading, {}).get(\n            \"title\", source_heading\n        )\n        category_id = CATEGORY_MAP.get(source_heading, {}).get(\n            \"id\",\n            f\"category-{index + 1}\",\n        )\n        category = {\n            \"id\": category_id,\n            \"english_heading\": english_heading,\n            \"source_heading\": source_heading,\n            \"source_anchor\": match.group(\"source_anchor\"),\n            \"heading_line\": line_number(source_text, match.start()),\n            \"entries\": [],\n        }\n\n        for entry_match in ENTRY_PATTERN.finditer(section_text):\n            absolute_start = section_start + entry_match.start()\n            absolute_end = section_start + entry_match.end()\n            code_start = section_start + entry_match.start(\"code\")\n            code_end = section_start + entry_match.end(\"code\")\n            entry = {\n                \"lookup_key\": lookup_key(entry_match.group(\"reference_anchor\")),\n                \"source_label\": entry_match.group(\"source_label\").strip(),\n                \"reference\": {\n                    \"expression\": entry_match.group(\"reference_expr\").strip(),\n                    \"module\": entry_match.group(\"reference_module\"),\n                    \"anchor\": entry_match.group(\"reference_anchor\"),\n                },\n                \"source\": {\n                    \"path\": repo_relative(source_path, repo_root),\n                    \"entry_line_start\": line_number(source_text, absolute_start),\n                    \"entry_line_end\": line_number(source_text, absolute_end),\n                    \"code_line_start\": line_number(source_text, code_start),\n                    \"code_line_end\": line_number(source_text, code_end),\n                },\n                \"code\": normalize_code(entry_match.group(\"code\")),\n            }\n            category[\"entries\"].append(entry)\n\n        if not category[\"entries\"]:\n            raise ValueError(\n                f\"No entries parsed for category '{source_heading}' in {source_path}\"\n            )\n\n        categories.append(category)\n\n    return categories\n\n\ndef build_catalog(source_path: Path, repo_root: Path) -> dict:\n    source_text = source_path.read_text(encoding=\"utf-8\")\n    categories = parse_categories(source_text, source_path, repo_root)\n    return {\n        \"source_path\": repo_relative(source_path, repo_root),\n        \"category_count\": len(categories),\n        \"entry_count\": sum(len(category[\"entries\"]) for category in categories),\n        \"categories\": categories,\n    }\n\n\ndef is_inline_safe(code: str) -> bool:\n    return \"\\n\" not in code and \"`\" not in code and len(code) <= INLINE_CODE_LIMIT\n\n\ndef grouped_entries(entries: list[dict]) -> list[dict]:\n    groups: OrderedDict[str, list[str]] = OrderedDict()\n    for entry in entries:\n        groups.setdefault(entry[\"lookup_key\"], [])\n        if entry[\"code\"] not in groups[entry[\"lookup_key\"]]:\n            groups[entry[\"lookup_key\"]].append(entry[\"code\"])\n    return [\n        {\n            \"lookup_key\": lookup_key,\n            \"examples\": examples,\n        }\n        for lookup_key, examples in groups.items()\n    ]\n\n\ndef render_example_block(code: str) -> list[str]:\n    return [\n        \"```typ\",\n        code,\n        \"```\",\n    ]\n\n\ndef render_skill_lookup(catalog: dict) -> str:\n    lines: list[str] = []\n    for category in catalog[\"categories\"]:\n        lines.extend(\n            [\n                f\"### {category['english_heading']}\",\n                \"\",\n            ]\n        )\n        for group in grouped_entries(category[\"entries\"]):\n            inline_examples = [\n                code for code in group[\"examples\"] if is_inline_safe(code)\n            ]\n            if len(inline_examples) == len(group[\"examples\"]):\n                joined = \"; \".join(f\"`{code}`\" for code in inline_examples)\n                lines.append(f\"- `{group['lookup_key']}`: {joined}\")\n                continue\n\n            lines.append(f\"- `{group['lookup_key']}`:\")\n            lines.append(\"\")\n            for index, code in enumerate(group[\"examples\"]):\n                lines.extend(render_example_block(code))\n                if index != len(group[\"examples\"]) - 1:\n                    lines.append(\"\")\n            lines.append(\"\")\n\n    while lines and not lines[-1]:\n        lines.pop()\n    return \"\\n\".join(lines) + \"\\n\"\n\n\ndef update_skill(skill_path: Path, generated_lookup: str) -> None:\n    skill_text = skill_path.read_text(encoding=\"utf-8\")\n    pattern = re.compile(\n        rf\"{re.escape(GENERATED_BEGIN)}(?P<newline>\\r?\\n).*?{re.escape(GENERATED_END)}\",\n        re.DOTALL,\n    )\n\n    def _replacement(match: re.Match[str]) -> str:\n        newline = match.group(\"newline\")\n        return f\"{GENERATED_BEGIN}{newline}{generated_lookup}{GENERATED_END}\"\n\n    updated_text, count = pattern.subn(_replacement, skill_text, count=1)\n    if count != 1:\n        raise ValueError(f\"Could not find generated section markers in {skill_path}\")\n    skill_path.write_text(updated_text, encoding=\"utf-8\")\n\n\ndef write_outputs(\n    catalog: dict,\n    output_dir: Path,\n    skill_path: Path,\n) -> None:\n    output_dir.mkdir(parents=True, exist_ok=True)\n    json_path = output_dir / \"grammar-catalog.json\"\n    json_path.write_text(\n        json.dumps(catalog, indent=2, ensure_ascii=False) + \"\\n\",\n        encoding=\"utf-8\",\n    )\n    update_skill(skill_path, render_skill_lookup(catalog))\n    print(f\"[OK] Wrote {json_path}\")\n    print(f\"[OK] Updated {skill_path}\")\n\n\ndef default_source(script_path: Path) -> Path:\n    return script_path.parents[4] / \"src\" / \"tutorial\" / \"reference-grammar.typ\"\n\n\ndef default_skill_path(script_path: Path) -> Path:\n    return script_path.parents[2] / \"typst-grammar-authoring\" / \"SKILL.md\"\n\n\ndef main() -> int:\n    script_path = Path(__file__).resolve()\n    parser = argparse.ArgumentParser(\n        description=\"Generate Typst grammar data from reference-grammar.typ.\",\n    )\n    parser.add_argument(\n        \"--source\",\n        type=Path,\n        default=default_source(script_path),\n        help=\"Path to src/tutorial/reference-grammar.typ\",\n    )\n    parser.add_argument(\n        \"--output-dir\",\n        type=Path,\n        default=script_path.parent.parent / \"references\",\n        help=\"Directory for generated reference files\",\n    )\n    parser.add_argument(\n        \"--skill-path\",\n        type=Path,\n        default=default_skill_path(script_path),\n        help=\"Path to the portable SKILL.md file to sync\",\n    )\n    args = parser.parse_args()\n\n    try:\n        repo_root = script_path.parents[4]\n        source_path = args.source.resolve()\n        catalog = build_catalog(source_path, repo_root)\n        write_outputs(\n            catalog,\n            args.output_dir.resolve(),\n            args.skill_path.resolve(),\n        )\n    except Exception as exc:  # pragma: no cover - CLI error surface\n        print(f\"[ERROR] {exc}\", file=sys.stderr)\n        return 1\n\n    return 0\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n"
  },
  {
    "path": ".gitattributes",
    "content": ".zed/settings.json linguist-language=json5"
  },
  {
    "path": ".github/workflows/build.yml",
    "content": "name: tutorial::ci\non: push\njobs:\n  render_pdf:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: typst-community/setup-typst@v3\n      - run: git submodule update --init --recursive\n      - name: Download & install shiroa\n        run: |\n          curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.3.1-rc2/shiroa-installer.sh | sh\n      - name: Build Book\n        run: |\n          shiroa build --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ --path-to-root /tutorial/ -w . src\n      - run: typst compile --root . --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ ./src/ebook.typ\n      - uses: actions/upload-artifact@v4\n        id: artifact-upload-step\n        with:\n          name: ebook\n          path: src/ebook.pdf\n      - run: echo 'Artifact URL is ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}/artifacts/${{ steps.artifact-upload-step.outputs.artifact-id }}'\n"
  },
  {
    "path": ".github/workflows/gh_pages.yml",
    "content": "name: tutorial::gh_pages\non:\n  push:\n    branches:\n      - main\n  workflow_dispatch:\n    \npermissions:\n  pages: write\n  id-token: write\n  contents: read\n\n# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.\n# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.\nconcurrency:\n  group: 'pages'\n  cancel-in-progress: false\n\njobs:\n  build-gh-pages:\n    runs-on: ubuntu-latest\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n      - run: git submodule update --init --recursive\n      - name: Download font assets\n        # use fonts in stable releases\n        run: |\n          mkdir -p assets/fonts/\n          curl -L https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.1.2/font-assets.tar.gz | tar -xvz -C assets/fonts\n      - name: Download & install shiroa\n        run: |\n          curl --proto '=https' --tlsv1.2 -LsSf https://github.com/Myriad-Dreamin/shiroa/releases/download/v0.3.1-rc2/shiroa-installer.sh | sh\n      - name: Build Book\n        run: |\n          shiroa build --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ --path-to-root /tutorial/ -w . src \n      - name: Setup Pages\n        uses: actions/configure-pages@v5\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n        with:\n          path: \"./dist\"\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4"
  },
  {
    "path": ".gitignore",
    "content": "dist/\ntarget/\n.DS_Store\nsrc/*.pdf\n.idea\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"assets/typst-fonts\"]\n\tpath = assets/typst-fonts\n\turl = https://github.com/typst-doc-cn/fonts\n\tbranch = typst-v0.10.0\n[submodule \"assets/artifacts\"]\n\tpath = assets/artifacts\n\turl = https://github.com/typst-doc-cn/tutorial-artifacts\n\tbranch = v0.1.0\n[submodule \"assets/fonts\"]\n\tpath = assets/fonts\n\turl = https://github.com/typst-doc-cn/fonts\n\tbranch = tutorial-v0.1.0\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"tinymist.fontPaths\": [\"${workspaceFolder}/assets/fonts\"],\n  \"tinymist.exportPdf\": \"onSave\",\n  \"tinymist.outputPath\": \"$root/target/$dir/$name\",\n  \"tinymist.formatterMode\": \"typstyle\",\n  \"editor.pasteAs.preferences\": [\n    \"typst.link.uri\",\n    \"typst.link.image\",\n    \"typst.link\"\n  ],\n  \"tinymist.preview.invertColors\": \"auto\",\n  \"tinymist.lint.enabled\": true,\n  \"tinymist.lint.when\": \"onSave\",\n  \"[typst]\": {\n    \"editor.inlayHints.enabled\": \"off\"\n  },\n  \"files.watcherExclude\": {\n    \"**/target\": true\n  }\n}\n"
  },
  {
    "path": ".zed/settings.json",
    "content": "// Folder-specific settings\n//\n// For a full list of overridable settings, and general information on folder-specific settings,\n// see the documentation: https://zed.dev/docs/configuring-zed#settings-files\n{\n  \"lsp\": {\n    \"tinymist\": {\n      \"initialization_options\": {\n        \"exportPdf\": \"onSave\",\n        \"outputPath\": \"$root/target/typst/$name\"\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Cargo.toml",
    "content": "[workspace.package]\ndescription = \"The Raindrop Book - Typst中文教程\"\nauthors = [\"Myriad-Dreamin <camiyoru@gmail.com>\"]\nversion = \"0.1.0\"\nedition = \"2021\"\nreadme = \"README.md\"\nlicense = \"Apache-2.0\"\nhomepage = \"https://github.com/typst-doc-cn/tutorial\"\nrepository = \"https://github.com/typst-doc-cn/tutorial\"\n\n[workspace]\nresolver = \"2\"\nmembers = [\"crates/*\"]\n\n[workspace.dependencies]\ntypst = \"0.13.1\"\ntypst-svg = \"0.13.1\"\n\nserde_json = \"1\"\n\nreflexo-typst = { version = \"0.6.0-rc1\", default-features = false }\n\n[profile.release]\n# to satisfy stubber\nlto = false       # Enable link-time optimization\nstrip = true      # Strip symbols from binary*\nopt-level = 3     # Optimize for speed\ncodegen-units = 2 # Reduce number of codegen units to increase optimizations\npanic = 'abort'   # Abort on panic\n\n[patch.crates-io]\ntypst = { git = \"https://github.com/Myriad-Dreamin/typst.git\", tag = \"typst.ts/v0.6.0-rc1\" }\ntypst-library = { git = \"https://github.com/Myriad-Dreamin/typst.git\", tag = \"typst.ts/v0.6.0-rc1\" }\ntypst-syntax = { git = \"https://github.com/Myriad-Dreamin/typst.git\", tag = \"typst.ts/v0.6.0-rc1\" }\ntypst-utils = { git = \"https://github.com/Myriad-Dreamin/typst.git\", tag = \"typst.ts/v0.6.0-rc1\" }\ntypst-svg = { git = \"https://github.com/Myriad-Dreamin/typst.git\", tag = \"typst.ts/v0.6.0-rc1\" }\n# reflexo-typst = { git = \"https://github.com/Myriad-Dreamin/typst.ts\", tag = \"v0.5.5-rc7\" }\n# typst-ts-core = { path = \"../../rust/typst.ts/core\" }\n# typst-ts-compiler = { path = \"../../rust/typst.ts/compiler\" }\n\n[workspace.lints.rust]\nmissing_docs = \"warn\"\n\n[workspace.lints.clippy]\nuninlined_format_args = \"warn\"\nmissing_safety_doc = \"warn\"\nundocumented_unsafe_blocks = \"warn\"\n"
  },
  {
    "path": "LICENSE",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "# Tutorial\n\nTypst 中文教程\n\n[![下载最新版本](https://custom-icon-badges.demolab.com/badge/-Download-blue?style=for-the-badge&logo=download&logoColor=white \"下载最新版本\")](https://nightly.link/typst-doc-cn/tutorial/workflows/build/main/ebook.zip) **(latest 版本)**\n\n[![下载最新版本](https://custom-icon-badges.demolab.com/badge/-Download-blue?style=for-the-badge&logo=download&logoColor=white \"下载最新版本\")](https://github.com/typst-doc-cn/tutorial/releases/download/v0.1.0/Typst.Tutorial.CN.v0.1.0.pdf) **(0.1.0 版本)**\n\n## 安装字体\n\n```bash\ngit submodule update --init --recursive\n```\n\n## 托管为静态网站\n\n```bash\nshiroa serve --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ -w . ./src/\n```\n\n## 编译为静态网站\n\n```bash\nshiroa build --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ -w . ./src/\n```\n\n## 编译电子书\n\n```bash\ntypst compile --root . --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ ./src/ebook.typ\n```\n\n## 编译单独章节\n\n选择一个章节文件，比如 `第一章.typ`，然后执行：\n\n```bash\ntypst compile --root . --font-path ./assets/typst-fonts/ --font-path ./assets/fonts/ 章节文件.typ\n```\n\n## 复现 Artifacts\n\n生成`typst-docs-v0.11.0.json`：\n\n```bash\ncargo install --git https://github.com/typst/typst --locked typst-docs --features=\"cli\" --tag v0.11.0\ntypst-docs --out-file ./assets/artifacts/typst-docs-v0.11.0.json --assets-dir target/typst-docs/assets\n```\n"
  },
  {
    "path": "_typos.toml",
    "content": "[default]\nextend-ignore-identifiers-re = [\"typ\", \"typc\", \"typm\"]\n"
  },
  {
    "path": "assets/files/tokyo-night.tmTheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n  <dict>\n    <key>name</key>\n    <string>Tokyo Night</string>\n    <key>settings</key>\n    <array>\n      <dict>\n        <key>settings</key>\n        <dict>\n          <key>caret</key>\n          <string>#c0caf5</string>\n          <key>selection</key>\n          <string>#515c7e4d</string>\n          <key>lineHighlight</key>\n          <string>#1e202e</string>\n          <key>foreground</key>\n          <string>#a9b1d6</string>\n          <key>background</key>\n          <string>#1a1b26</string>\n          <key>invisibles</key>\n          <string>#363b54</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Italics - Comments, Storage, Keyword Flow, Vue attributes, Decorators</string>\n        <key>scope</key>\n        <string>comment,meta.var.expr storage.type,keyword.control.flow,keyword.control.return,meta.directive.vue punctuation.separator.key-value.html,meta.directive.vue entity.other.attribute-name.html,tag.decorator.js entity.name.tag.js,tag.decorator.js punctuation.definition.tag.js,storage.modifier</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>italic</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Fix YAML block scalar</string>\n        <key>scope</key>\n        <string>keyword.control.flow.block-scalar.literal</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string/>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Comment</string>\n        <key>scope</key>\n        <string>comment,comment.block.documentation,punctuation.definition.comment,comment.block.documentation punctuation</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#444b6a</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Comment Doc</string>\n        <key>scope</key>\n        <string>keyword.operator.assignment.jsdoc,comment.block.documentation variable,comment.block.documentation storage,comment.block.documentation keyword,comment.block.documentation support,comment.block.documentation markup,comment.block.documentation markup.inline.raw.string.markdown,meta.other.type.phpdoc.php keyword.other.type.php,meta.other.type.phpdoc.php support.other.namespace.php,meta.other.type.phpdoc.php punctuation.separator.inheritance.php,meta.other.type.phpdoc.php support.class,keyword.other.phpdoc.php,log.date</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#5a638c</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Comment Doc Emphasized</string>\n        <key>scope</key>\n        <string>meta.other.type.phpdoc.php support.class,comment.block.documentation storage.type,comment.block.documentation punctuation.definition.block.tag,comment.block.documentation entity.name.type.instance</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#646e9c</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Number, Boolean, Undefined, Null</string>\n        <key>scope</key>\n        <string>variable.other.constant,punctuation.definition.constant,constant.language,constant.numeric,support.constant</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#ff9e64</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>String, Symbols</string>\n        <key>scope</key>\n        <string>string,constant.other.symbol,constant.other.key,meta.attribute-selector</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string/>\n          <key>foreground</key>\n          <string>#9ece6a</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Colors</string>\n        <key>scope</key>\n        <string>constant.other.color,constant.other.color.rgb-value.hex punctuation.definition.constant</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9aa5ce</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Invalid</string>\n        <key>scope</key>\n        <string>invalid,invalid.illegal</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#ff5370</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Invalid deprecated</string>\n        <key>scope</key>\n        <string>invalid.deprecated</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Storage Type</string>\n        <key>scope</key>\n        <string>storage.type</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Storage - modifier, var, const, let</string>\n        <key>scope</key>\n        <string>meta.var.expr storage.type,storage.modifier</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9d7cd8</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Interpolation, PHP tags, Smarty tags</string>\n        <key>scope</key>\n        <string>punctuation.definition.template-expression,punctuation.section.embedded,meta.embedded.line.tag.smarty,support.constant.handlebars,punctuation.section.tag.twig</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7dcfff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Blade, Twig, Smarty Handlebars keywords</string>\n        <key>scope</key>\n        <string>keyword.control.smarty,keyword.control.twig,support.constant.handlebars keyword.control,keyword.operator.comparison.twig,keyword.blade,entity.name.function.blade</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#0db9d7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Spread</string>\n        <key>scope</key>\n        <string>keyword.operator.spread,keyword.operator.rest</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#f7768e</string>\n          <key>fontStyle</key>\n          <string>bold</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Operator, Misc</string>\n        <key>scope</key>\n        <string>keyword.operator,keyword.control.as,keyword.other,keyword.operator.bitwise.shift,punctuation,expression.embbeded.vue punctuation.definition.tag,text.html.twig meta.tag.inline.any.html,meta.tag.template.value.twig meta.function.arguments.twig,meta.directive.vue punctuation.separator.key-value.html,punctuation.definition.constant.markdown,punctuation.definition.string,punctuation.support.type.property-name,text.html.vue-html meta.tag,meta.attribute.directive,punctuation.definition.keyword,punctuation.terminator.rule,punctuation.definition.entity,punctuation.separator.inheritance.php,keyword.other.template,keyword.other.substitution,entity.name.operator,meta.property-list punctuation.separator.key-value,meta.at-rule.mixin punctuation.separator.key-value,meta.at-rule.function variable.parameter.url</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#89ddff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Import, Export, From, Default</string>\n        <key>scope</key>\n        <string>keyword.control.import,keyword.control.export,keyword.control.from,keyword.control.default,meta.import keyword.other</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7dcfff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Keyword</string>\n        <key>scope</key>\n        <string>keyword,keyword.control,keyword.other.important</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Keyword SQL</string>\n        <key>scope</key>\n        <string>keyword.other.DML</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7dcfff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Keyword Operator Logical, Arrow, Ternary, Comparison</string>\n        <key>scope</key>\n        <string>keyword.operator.logical,storage.type.function,keyword.operator.bitwise,keyword.operator.ternary,keyword.operator.comparison,keyword.operator.relational,keyword.operator.or.regexp</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Tag</string>\n        <key>scope</key>\n        <string>entity.name.tag</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#f7768e</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Tag - Custom</string>\n        <key>scope</key>\n        <string>entity.name.tag support.class.component,meta.tag.custom entity.name.tag,meta.tag</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#de5971</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Tag Punctuation</string>\n        <key>scope</key>\n        <string>punctuation.definition.tag</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#ba3c97</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Globals, PHP Constants, etc</string>\n        <key>scope</key>\n        <string>constant.other.php,variable.other.global.safer,variable.other.global.safer punctuation.definition.variable,variable.other.global,variable.other.global punctuation.definition.variable,constant.other</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#e0af68</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Variables</string>\n        <key>scope</key>\n        <string>variable,support.variable,string constant.other.placeholder,variable.parameter.handlebars,variable.other.object</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Variable Array Key</string>\n        <key>scope</key>\n        <string>meta.array.literal variable</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7dcfff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Object Key</string>\n        <key>scope</key>\n        <string>meta.object-literal.key,entity.name.type.hcl,string.alias.graphql,string.unquoted.graphql,string.unquoted.alias.graphql,meta.group.braces.curly constant.other.object.key.js string.unquoted.label.js,meta.field.declaration.ts variable.object.property,meta.block entity.name.label</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#73daca</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Object Property</string>\n        <key>scope</key>\n        <string>variable.other.property,support.variable.property,support.variable.property.dom,meta.function-call variable.other.object.property</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7dcfff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Object Property</string>\n        <key>scope</key>\n        <string>variable.other.object.property</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Object Literal Member lvl 3 (Vue Prop Validation)</string>\n        <key>scope</key>\n        <string>meta.objectliteral meta.object.member meta.objectliteral meta.object.member meta.objectliteral meta.object.member meta.object-literal.key</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#41a6b5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>C-related Block Level Variables</string>\n        <key>scope</key>\n        <string>source.cpp meta.block variable.other</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#f7768e</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Other Variable</string>\n        <key>scope</key>\n        <string>support.other.variable</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#f7768e</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Methods</string>\n        <key>scope</key>\n        <string>meta.class-method.js entity.name.function.js,entity.name.method.js,variable.function.constructor,keyword.other.special-method,storage.type.cs</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7aa2f7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Function Definition</string>\n        <key>scope</key>\n        <string>entity.name.function,variable.other.enummember,meta.function-call,meta.function-call entity.name.function,variable.function,meta.definition.method entity.name.function,meta.object-literal entity.name.function</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7aa2f7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Function Argument</string>\n        <key>scope</key>\n        <string>variable.parameter.function.language.special,variable.parameter,meta.function.parameters punctuation.definition.variable,meta.function.parameter variable</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#e0af68</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Constant, Tag Attribute</string>\n        <key>scope</key>\n        <string>keyword.other.type.php,storage.type.php,constant.character,constant.escape,keyword.other.unit</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Variable Definition</string>\n        <key>scope</key>\n        <string>meta.definition.variable variable.other.constant,meta.definition.variable variable.other.readwrite,variable.declaration.hcl variable.other.readwrite.hcl,meta.mapping.key.hcl variable.other.readwrite.hcl,variable.other.declaration</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Inherited Class</string>\n        <key>scope</key>\n        <string>entity.other.inherited-class</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string/>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Class, Support, DOM, etc</string>\n        <key>scope</key>\n        <string>support.class,support.type,variable.other.readwrite.alias,support.orther.namespace.use.php,meta.use.php,support.other.namespace.php,support.type.sys-types,support.variable.dom,support.constant.math,support.type.object.module,support.constant.json,entity.name.namespace,meta.import.qualifier,variable.other.constant.object</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#0db9d7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Class Name</string>\n        <key>scope</key>\n        <string>entity.name</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Support Function</string>\n        <key>scope</key>\n        <string>support.function</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#0db9d7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS Class and Support</string>\n        <key>scope</key>\n        <string>source.css support.type.property-name,source.sass support.type.property-name,source.scss support.type.property-name,source.less support.type.property-name,source.stylus support.type.property-name,source.postcss support.type.property-name,support.type.property-name.css,support.type.vendored.property-name,support.type.map.key</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7aa2f7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS Font</string>\n        <key>scope</key>\n        <string>support.constant.font-name,meta.definition.variable</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9ece6a</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS Class</string>\n        <key>scope</key>\n        <string>entity.other.attribute-name.class,meta.at-rule.mixin.scss entity.name.function.scss</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9ece6a</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS ID</string>\n        <key>scope</key>\n        <string>entity.other.attribute-name.id</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#fc7b7b</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS Tag</string>\n        <key>scope</key>\n        <string>entity.name.tag.css</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#0db9d7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS Tag Reference, Pseudo &amp; Class Punctuation</string>\n        <key>scope</key>\n        <string>entity.other.attribute-name.pseudo-class punctuation.definition.entity,entity.other.attribute-name.pseudo-element punctuation.definition.entity,entity.other.attribute-name.class punctuation.definition.entity,entity.name.tag.reference</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#e0af68</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS Punctuation</string>\n        <key>scope</key>\n        <string>meta.property-list</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9abdf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS at-rule fix</string>\n        <key>scope</key>\n        <string>meta.property-list meta.at-rule.if,meta.at-rule.return variable.parameter.url,meta.property-list meta.at-rule.else</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#ff9e64</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS Parent Selector Entity</string>\n        <key>scope</key>\n        <string>entity.other.attribute-name.parent-selector-suffix punctuation.definition.entity.css</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#73daca</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS Punctuation comma fix</string>\n        <key>scope</key>\n        <string>meta.property-list meta.property-list</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9abdf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>SCSS @</string>\n        <key>scope</key>\n        <string>meta.at-rule.mixin keyword.control.at-rule.mixin,meta.at-rule.include entity.name.function.scss,meta.at-rule.include keyword.control.at-rule.include</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>SCSS Mixins, Extends, Include Keyword</string>\n        <key>scope</key>\n        <string>keyword.control.at-rule.include punctuation.definition.keyword,keyword.control.at-rule.mixin punctuation.definition.keyword,meta.at-rule.include keyword.control.at-rule.include,keyword.control.at-rule.extend punctuation.definition.keyword,meta.at-rule.extend keyword.control.at-rule.extend,entity.other.attribute-name.placeholder.css punctuation.definition.entity.css,meta.at-rule.media keyword.control.at-rule.media,meta.at-rule.mixin keyword.control.at-rule.mixin,meta.at-rule.function keyword.control.at-rule.function,keyword.control punctuation.definition.keyword</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9d7cd8</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>SCSS Include Mixin Argument</string>\n        <key>scope</key>\n        <string>meta.property-list meta.at-rule.include</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS value</string>\n        <key>scope</key>\n        <string>support.constant.property-value</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#ff9e64</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Sub-methods</string>\n        <key>scope</key>\n        <string>entity.name.module.js,variable.import.parameter.js,variable.other.class.js</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Language methods</string>\n        <key>scope</key>\n        <string>variable.language</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#f7768e</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Variable punctuation</string>\n        <key>scope</key>\n        <string>variable.other punctuation.definition.variable</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Keyword this with Punctuation, ES7 Bind Operator</string>\n        <key>scope</key>\n        <string>source.js constant.other.object.key.js string.unquoted.label.js,variable.language.this punctuation.definition.variable,keyword.other.this</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#f7768e</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>HTML Attributes</string>\n        <key>scope</key>\n        <string>entity.other.attribute-name,text.html.basic entity.other.attribute-name.html,text.html.basic entity.other.attribute-name</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>HTML Character Entity</string>\n        <key>scope</key>\n        <string>text.html constant.character.entity</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#0DB9D7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Vue (Vetur / deprecated) Template attributes</string>\n        <key>scope</key>\n        <string>entity.other.attribute-name.id.html,meta.directive.vue entity.other.attribute-name.html</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS ID's</string>\n        <key>scope</key>\n        <string>source.sass keyword.control</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7aa2f7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS psuedo selectors</string>\n        <key>scope</key>\n        <string>entity.other.attribute-name.pseudo-class,entity.other.attribute-name.pseudo-element,entity.other.attribute-name.placeholder,meta.property-list meta.property-value</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Inserted</string>\n        <key>scope</key>\n        <string>markup.inserted</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#449dab</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Deleted</string>\n        <key>scope</key>\n        <string>markup.deleted</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#914c54</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Changed</string>\n        <key>scope</key>\n        <string>markup.changed</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#6183bb</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Regular Expressions</string>\n        <key>scope</key>\n        <string>string.regexp</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#b4f9f8</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Regular Expressions - Punctuation</string>\n        <key>scope</key>\n        <string>punctuation.definition.group</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#f7768e</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Regular Expressions - Character Class</string>\n        <key>scope</key>\n        <string>constant.other.character-class.regexp</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Regular Expressions - Character Class Set</string>\n        <key>scope</key>\n        <string>constant.other.character-class.set.regexp,punctuation.definition.character-class.regexp</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#e0af68</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Regular Expressions - Quantifier</string>\n        <key>scope</key>\n        <string>keyword.operator.quantifier.regexp</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#89ddff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Regular Expressions - Backslash</string>\n        <key>scope</key>\n        <string>constant.character.escape.backslash</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Escape Characters</string>\n        <key>scope</key>\n        <string>constant.character.escape</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#89ddff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Decorators</string>\n        <key>scope</key>\n        <string>tag.decorator.js entity.name.tag.js,tag.decorator.js punctuation.definition.tag.js</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7aa2f7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>CSS Units</string>\n        <key>scope</key>\n        <string>keyword.other.unit</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#f7768e</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>JSON Key - Level 0</string>\n        <key>scope</key>\n        <string>source.json meta.structure.dictionary.json support.type.property-name.json</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7aa2f7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>JSON Key - Level 1</string>\n        <key>scope</key>\n        <string>source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#0db9d7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>JSON Key - Level 2</string>\n        <key>scope</key>\n        <string>source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7dcfff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>JSON Key - Level 3</string>\n        <key>scope</key>\n        <string>source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>JSON Key - Level 4</string>\n        <key>scope</key>\n        <string>source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#e0af68</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>JSON Key - Level 5</string>\n        <key>scope</key>\n        <string>source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#0db9d7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>JSON Key - Level 6</string>\n        <key>scope</key>\n        <string>source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#73daca</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>JSON Key - Level 7</string>\n        <key>scope</key>\n        <string>source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#f7768e</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>JSON Key - Level 8</string>\n        <key>scope</key>\n        <string>source.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json meta.structure.dictionary.value.json meta.structure.dictionary.json support.type.property-name.json</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9ece6a</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Plain Punctuation</string>\n        <key>scope</key>\n        <string>punctuation.definition.list_item.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9abdf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Block Punctuation</string>\n        <key>scope</key>\n        <string>meta.block,meta.brace,punctuation.definition.block,punctuation.definition.use,punctuation.definition.class,punctuation.definition.begin.bracket,punctuation.definition.end.bracket,punctuation.definition.switch-expression.begin.bracket,punctuation.definition.switch-expression.end.bracket,punctuation.definition.section.switch-block.begin.bracket,punctuation.definition.section.switch-block.end.bracket,punctuation.definition.group.shell,punctuation.definition.parameters,punctuation.definition.arguments,punctuation.definition.dictionary,punctuation.definition.array,punctuation.section</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9abdf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Plain</string>\n        <key>scope</key>\n        <string>meta.jsx.children,meta.embedded.block</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>HTML text</string>\n        <key>scope</key>\n        <string>text.html,text.log</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#9aa5ce</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Markup Raw Inline</string>\n        <key>scope</key>\n        <string>text.html.markdown markup.inline.raw.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#bb9af7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Markup Raw Inline Punctuation</string>\n        <key>scope</key>\n        <string>text.html.markdown markup.inline.raw.markdown punctuation.definition.raw.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#4E5579</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Heading 1</string>\n        <key>scope</key>\n        <string>heading.1.markdown entity.name,heading.1.markdown punctuation.definition.heading.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>bold</string>\n          <key>foreground</key>\n          <string>#89ddff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Heading 2</string>\n        <key>scope</key>\n        <string>heading.2.markdown entity.name,heading.2.markdown punctuation.definition.heading.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>bold</string>\n          <key>foreground</key>\n          <string>#61bdf2</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Heading 3</string>\n        <key>scope</key>\n        <string>heading.3.markdown entity.name,heading.3.markdown punctuation.definition.heading.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>bold</string>\n          <key>foreground</key>\n          <string>#7aa2f7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Heading 4</string>\n        <key>scope</key>\n        <string>heading.4.markdown entity.name,heading.4.markdown punctuation.definition.heading.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>bold</string>\n          <key>foreground</key>\n          <string>#6d91de</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Heading 5</string>\n        <key>scope</key>\n        <string>heading.5.markdown entity.name,heading.5.markdown punctuation.definition.heading.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>bold</string>\n          <key>foreground</key>\n          <string>#9aa5ce</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Heading 6</string>\n        <key>scope</key>\n        <string>heading.6.markdown entity.name,heading.6.markdown punctuation.definition.heading.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>bold</string>\n          <key>foreground</key>\n          <string>#747ca1</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markup - Italic</string>\n        <key>scope</key>\n        <string>markup.italic,markup.italic punctuation</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>italic</string>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markup - Bold</string>\n        <key>scope</key>\n        <string>markup.bold,markup.bold punctuation</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>bold</string>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markup - Bold-Italic</string>\n        <key>scope</key>\n        <string>markup.bold markup.italic,markup.bold markup.italic punctuation</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>bold italic</string>\n          <key>foreground</key>\n          <string>#c0caf5</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markup - Underline</string>\n        <key>scope</key>\n        <string>markup.underline,markup.underline punctuation</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>underline</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Blockquote</string>\n        <key>scope</key>\n        <string>markup.quote punctuation.definition.blockquote.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#4e5579</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markup - Quote</string>\n        <key>scope</key>\n        <string>markup.quote</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>italic</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Link</string>\n        <key>scope</key>\n        <string>string.other.link,markup.underline.link,constant.other.reference.link.markdown,string.other.link.description.title.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#73daca</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Fenced Code Block</string>\n        <key>scope</key>\n        <string>markup.fenced_code.block.markdown,markup.inline.raw.string.markdown,variable.language.fenced.markdown</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#89ddff</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markdown - Separator</string>\n        <key>scope</key>\n        <string>meta.separator</string>\n        <key>settings</key>\n        <dict>\n          <key>fontStyle</key>\n          <string>bold</string>\n          <key>foreground</key>\n          <string>#444b6a</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Markup - Table</string>\n        <key>scope</key>\n        <string>markup.table</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#c0cefc</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Token - Info</string>\n        <key>scope</key>\n        <string>token.info-token</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#0db9d7</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Token - Warn</string>\n        <key>scope</key>\n        <string>token.warn-token</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#ffdb69</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Token - Error</string>\n        <key>scope</key>\n        <string>token.error-token</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#db4b4b</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Token - Debug</string>\n        <key>scope</key>\n        <string>token.debug-token</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#b267e6</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Apache Tag</string>\n        <key>scope</key>\n        <string>entity.tag.apacheconf</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#f7768e</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>Preprocessor</string>\n        <key>scope</key>\n        <string>meta.preprocessor</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#73daca</string>\n        </dict>\n      </dict>\n      <dict>\n        <key>name</key>\n        <string>ENV value</string>\n        <key>scope</key>\n        <string>source.env</string>\n        <key>settings</key>\n        <dict>\n          <key>foreground</key>\n          <string>#7aa2f7</string>\n        </dict>\n      </dict>\n    </array>\n  </dict>\n</plist>\n"
  },
  {
    "path": "crates/embedded-typst/Cargo.toml",
    "content": "[package]\nname = \"embedded-typst\"\ndescription = \"Run typst inside of typst\"\nauthors.workspace = true\nversion.workspace = true\nlicense.workspace = true\nedition.workspace = true\nhomepage.workspace = true\nrepository.workspace = true\n\n[lib]\ncrate-type = [\"cdylib\"]\n\n[dependencies]\ntypst.workspace = true\ntypst-svg.workspace = true\nreflexo-typst.workspace = true\nserde_json.workspace = true\nflate2 = \"1\"\ntar = \"0.4\"\n\nwasm-minimal-protocol = { git = \"https://github.com/astrale-sharp/wasm-minimal-protocol\", optional = true }\n\n[features]\ntypst-plugin = [\"wasm-minimal-protocol\"]\n\ndefault = [\"typst-plugin\"]\n\n[lints]\nworkspace = true\n"
  },
  {
    "path": "crates/embedded-typst/src/lib.rs",
    "content": "//! This is a WASM wrapper of the embedded Typst.\n\n#![cfg_attr(feature = \"typst-plugin\", allow(missing_docs))]\n\nuse std::{\n    collections::HashMap,\n    io::Read,\n    path::{Path, PathBuf},\n    str::FromStr,\n    sync::{Arc, Mutex, RwLock},\n    vec,\n};\n\nuse reflexo_typst::{\n    font::{pure::MemoryFontSearcher, FontResolverImpl},\n    package::{PackageRegistry, PackageSpec, RegistryPathMapper},\n    typst::prelude::EcoVec,\n    vfs::{dummy::DummyAccessModel, FileSnapshot},\n    CompilerUniverse, EntryReader, EntryState, ShadowApi, TaskInputs, TypstDocument,\n    TypstPagedDocument,\n};\nuse typst::{\n    diag::{eco_format, FileResult, PackageError},\n    foundations::Bytes,\n    Features,\n};\n\nuse wasm_minimal_protocol::*;\ninitiate_protocol!();\n\ntype StrResult<T> = Result<T, String>;\n\n/// Allocates a data reference, return the key of the data reference.\n///\n/// # Panics\n///\n/// Panics if the world is not initialized.\n///\n/// # Errors\n///\n/// Error if there is an error\n#[cfg_attr(feature = \"typst-plugin\", wasm_func)]\npub fn allocate_data(kind: &[u8], data: &[u8]) -> StrResult<Vec<u8>> {\n    allocate_data_inner(kind, data, None)\n}\n\n/// Allocates a file reference, return the key of the data reference.\n///\n/// # Panics\n///\n/// Panics if the world is not initialized.\n///\n/// # Errors\n///\n/// Error if there is an error\n#[cfg_attr(feature = \"typst-plugin\", wasm_func)]\npub fn allocate_file(kind: &[u8], path: &[u8], data: &[u8]) -> StrResult<Vec<u8>> {\n    allocate_data_inner(kind, data, Some(path))\n}\n\n/// Allocates a data reference\nfn allocate_data_inner(kind: &[u8], data: &[u8], path: Option<&[u8]>) -> StrResult<Vec<u8>> {\n    let kind = std::str::from_utf8(kind).map_err(|e| e.to_string())?;\n    let data = ImmutBytes(Bytes::new(data.to_owned()));\n\n    let data_ref = match kind {\n        \"font\" => DataRef::Font { data: Some(data) },\n        \"package\" => {\n            let path = std::str::from_utf8(path.unwrap()).map_err(|e| e.to_string())?;\n            DataRef::Package {\n                spec: path.to_string(),\n                data: Some(data),\n            }\n        }\n        \"file\" => {\n            let path = std::str::from_utf8(path.unwrap()).map_err(|e| e.to_string())?;\n            DataRef::File {\n                path: path.to_owned(),\n                data: Some(data),\n            }\n        }\n        _ => return Err(format!(\"Unknown data kind: {kind}\")),\n    };\n\n    let mut data = DATA.lock().unwrap();\n\n    data.data.push(data_ref);\n\n    Ok(vec![0])\n}\n\n/// A context that is used to resolve a world context.\npub struct Context {\n    /// The data that are used in the main file.\n    data: Vec<DataRef>,\n}\n\nstatic DATA: Mutex<Context> = Mutex::new(Context { data: vec![] });\n\n/// A bytes object that is cheap to clone.\n#[derive(Clone)]\nstruct ImmutBytes(Bytes);\n\n/// A data reference in global data storage.\n#[derive(Clone)]\nenum DataRef {\n    Font {\n        data: Option<ImmutBytes>,\n    },\n    Package {\n        spec: String,\n        data: Option<ImmutBytes>,\n    },\n    File {\n        path: String,\n        data: Option<ImmutBytes>,\n    },\n}\n\nimpl DataRef {\n    /// Gets the data of the object.\n    pub fn data(&self) -> Option<&ImmutBytes> {\n        match self {\n            Self::Font { data, .. } => data.as_ref(),\n            Self::Package { data, .. } => data.as_ref(),\n            Self::File { data, .. } => data.as_ref(),\n        }\n    }\n}\n\nstatic VERSE: RwLock<Option<WorldRepr>> = RwLock::new(None);\n\n/// Resolves a world\n#[cfg_attr(feature = \"typst-plugin\", wasm_func)]\npub fn resolve_world() -> StrResult<Vec<u8>> {\n    let resolved = create_world()?;\n    let mut world = VERSE.write().unwrap();\n    if world.is_some() {\n        return Err(\"World is already initialized\".to_string());\n    }\n\n    *world = Some(resolved);\n\n    Ok(vec![])\n}\n\n/// Creates a world\nfn create_world() -> StrResult<WorldRepr> {\n    /// Extracts a package.\n    fn extract_package(\n        data: &[u8],\n        mut cb: impl FnMut(String, &[u8], u64) -> StrResult<()>,\n    ) -> StrResult<()> {\n        let decompressed = flate2::read::GzDecoder::new(data);\n        let mut reader = tar::Archive::new(decompressed);\n        let entries = reader.entries();\n        let entries = entries.map_err(|err| {\n            let t = PackageError::MalformedArchive(Some(eco_format!(\"{err}\")));\n            format!(\"{t:?}\",)\n        })?;\n\n        let mut buf = Vec::with_capacity(1024);\n        for entry in entries {\n            // Read single entry\n            let mut entry = entry.map_err(|e| e.to_string())?;\n            let header = entry.header();\n\n            let is_file = header.entry_type().is_file();\n            if !is_file {\n                continue;\n            }\n\n            let mtime = header.mtime().unwrap_or(0);\n\n            let path = header.path().map_err(|e| e.to_string())?;\n            let path = path.to_string_lossy().as_ref().to_owned();\n\n            let size = header.size().map_err(|e| e.to_string())?;\n            buf.clear();\n            buf.reserve(size as usize);\n            entry.read_to_end(&mut buf).map_err(|e| e.to_string())?;\n\n            cb(path, &buf, mtime)?\n        }\n\n        Ok(())\n    }\n\n    /// Creates a world based on the context.\n    fn resolve_world_inner() -> StrResult<WorldRepr> {\n        let data_guard = DATA.lock().unwrap();\n\n        // Adds embedded fonts.\n        let mut fb = MemoryFontSearcher::new();\n        let mut pb = MemoryRegistry::default();\n\n        for data in &data_guard.data {\n            match data {\n                DataRef::Font { data, .. } => {\n                    fb.add_memory_font(data.clone().unwrap().0);\n                }\n                DataRef::Package { spec, .. } => {\n                    let spec = PackageSpec::from_str(spec).map_err(|e| e.to_string())?;\n                    pb.add_memory_package(spec);\n                }\n                DataRef::File { .. } => {}\n            }\n        }\n\n        let registry = Arc::new(pb);\n        let resolver = Arc::new(RegistryPathMapper::new(registry.clone()));\n        let mut vfs = reflexo_typst::vfs::Vfs::new(resolver, DummyAccessModel);\n\n        for data in &data_guard.data {\n            match data {\n                DataRef::Package { data, spec, .. } => {\n                    let spec = PackageSpec::from_str(spec).map_err(|e| e.to_string())?;\n                    let path = registry.resolve(&spec).map_err(|e| e.to_string())?;\n\n                    let data = data.clone().unwrap().0;\n                    extract_package(&data, |key, value, _mtime| {\n                        vfs.revise()\n                            .map_shadow(\n                                &path.join(key.as_str()),\n                                FileSnapshot::from(FileResult::Ok(Bytes::new(value.to_owned()))),\n                            )\n                            .map_err(|e| e.to_string())\n                    })?;\n                }\n                DataRef::Font { .. } | DataRef::File { .. } => {}\n            }\n        }\n\n        for data in EMBEDDED_FONT {\n            fb.add_memory_font(Bytes::new(data));\n        }\n\n        // Creates a world.\n        let world = WorldRepr::new_raw(\n            EntryState::new_workspace(PathBuf::from(\"/\").into()),\n            Features::default(),\n            None,\n            vfs,\n            registry,\n            Arc::new(fb.build()),\n        );\n\n        Ok(world)\n    }\n\n    // Removes files from the context as they are not used for resolving a world.\n    let mut files = Vec::new();\n    let _ = &DATA.lock().unwrap().data.retain(|f| match f {\n        DataRef::File { .. } => {\n            files.push(f.clone());\n            false\n        }\n        _ => true,\n    });\n    let mut world = resolve_world_inner()?;\n\n    for f in files.into_iter() {\n        let path = match &f {\n            DataRef::File { path, .. } => path,\n            _ => unreachable!(),\n        };\n        let path = Path::new(path);\n        let data = f.data().cloned().unwrap().0;\n        world.map_shadow(path, data).map_err(|e| e.to_string())?;\n    }\n    Ok(world)\n}\n\n/// Compile a typst file to svg.\n///\n/// # Panics\n///\n/// Panics if the world is not initialized.\n///\n/// # Errors\n///\n/// Error if there is an error\n#[cfg_attr(feature = \"typst-plugin\", wasm_func)]\npub fn svg(input: &[u8]) -> StrResult<Vec<u8>> {\n    let verse = VERSE.read().unwrap();\n    let Some(verse) = verse.as_ref() else {\n        return Err(\"World is not initialized\".to_string());\n    };\n\n    // Prepare main file.\n    let entry_file = Path::new(\"/main.typ\");\n    let entry = verse.entry_state().select_in_workspace(entry_file);\n\n    // Compile.\n    let mut world = verse.snapshot_with(Some(TaskInputs {\n        entry: Some(entry),\n        inputs: None,\n    }));\n    world\n        .map_shadow(entry_file, Bytes::new(input.to_owned()))\n        .map_err(|e| e.to_string())?;\n\n    // todo: warning\n    let doc: TypstPagedDocument =\n        typst::compile(&world)\n            .output\n            .map_err(|e: EcoVec<typst::diag::SourceDiagnostic>| {\n                let mut error_log = String::new();\n                for c in e.into_iter() {\n                    error_log.push_str(&format!(\n                        \"{:?}\\n\",\n                        reflexo_typst::error::diag_from_std(c, Some(&world))\n                    ));\n                }\n                error_log\n            })?;\n\n    let pages_data: Vec<_> = doc\n        .pages\n        .iter()\n        .map(|page| typst_svg::svg(page).into_bytes())\n        .collect();\n\n    let doc = Arc::new(doc);\n    let typ_doc = TypstDocument::Paged(doc);\n    // Query header.\n    let header = reflexo_typst::query::retrieve(&world, \"<embedded-typst>\", &typ_doc);\n    let header = match header {\n        Ok(header) => serde_json::to_vec(&header).map_err(|e| e.to_string())?,\n        Err(e) => serde_json::to_vec(&e).map_err(|e| e.to_string())?,\n    };\n\n    // Build payload.\n    Ok((Some(header).into_iter())\n        .chain(pages_data)\n        .collect::<Vec<_>>()\n        .join(&b\"\\n\\n\\n\\n\\n\\n\\n\\n\"[..]))\n}\n\n/// type trait of [`TypstWasmWorld`].\n#[derive(Debug, Clone, Copy)]\npub struct WasmCompilerFeat;\n\nimpl reflexo_typst::world::CompilerFeat for WasmCompilerFeat {\n    /// It accesses no file system.\n    type FontResolver = FontResolverImpl;\n    /// It accesses no file system.\n    type AccessModel = DummyAccessModel;\n    /// It cannot load any package.\n    type Registry = MemoryRegistry;\n}\n\ntype WorldRepr = reflexo_typst::world::CompilerUniverse<WasmCompilerFeat>;\n\n/// The compiler world in wasm environment.\n#[allow(unused)]\npub struct TypstWasmWorld(WorldRepr);\n\nimpl Default for TypstWasmWorld {\n    fn default() -> Self {\n        Self::new()\n    }\n}\n\nimpl TypstWasmWorld {\n    /// Create a new [`TypstWasmWorld`].\n    pub fn new() -> Self {\n        // Creates a virtual file system.\n        let registry = Arc::new(MemoryRegistry::default());\n        let resolver = Arc::new(RegistryPathMapper::new(registry.clone()));\n        let vfs = reflexo_typst::vfs::Vfs::new(resolver, DummyAccessModel);\n\n        // Adds embedded fonts.\n        let mut fb = MemoryFontSearcher::new();\n\n        for data in EMBEDDED_FONT {\n            fb.add_memory_font(Bytes::new(data));\n        }\n\n        // Creates a world.\n        Self(CompilerUniverse::new_raw(\n            EntryState::new_workspace(PathBuf::from(\"/\").into()),\n            Features::default(),\n            None,\n            vfs,\n            registry,\n            Arc::new(fb.build()),\n        ))\n    }\n}\n\npub static EMBEDDED_FONT: &[&[u8]] = &[];\n\n/// Creates a memory package registry from the builder.\n#[derive(Default, Debug)]\npub struct MemoryRegistry(HashMap<PackageSpec, Arc<Path>>);\n\nimpl MemoryRegistry {\n    /// Adds a memory package.\n    pub fn add_memory_package(&mut self, spec: PackageSpec) -> Arc<Path> {\n        let package_root: Arc<Path> = PathBuf::from(\"/internal-packages\")\n            .join(spec.name.as_str())\n            .join(spec.version.to_string())\n            .into();\n\n        self.0.insert(spec, package_root.clone());\n\n        package_root\n    }\n}\n\nimpl PackageRegistry for MemoryRegistry {\n    /// Resolves a package.\n    fn resolve(&self, spec: &PackageSpec) -> Result<Arc<Path>, PackageError> {\n        self.0\n            .get(spec)\n            .cloned()\n            .ok_or_else(|| PackageError::NotFound(spec.clone()))\n    }\n}\n\n/// Makes `instant::SystemTime::now` happy\npub extern \"C\" fn now() -> f64 {\n    0.0\n}\n"
  },
  {
    "path": "meta.json",
    "content": "{\n  \"contributors\": [\"Myriad-Dreamin\"]\n}\n"
  },
  {
    "path": "openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/.openspec.yaml",
    "content": "schema: spec-driven\ncreated: 2026-03-14\n"
  },
  {
    "path": "openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/design.md",
    "content": "## Context\n\nThis repository contains a detailed Typst grammar catalog in\n`src/tutorial/reference-grammar.typ`. The original change turned that tutorial\nasset into a Codex skill, but the first implementation blended portable author\nguidance with repo-local maintenance machinery. As a result,\n`typst-grammar-authoring` now includes updater scripts, traceability artifacts,\nand path examples that are not ideal for copying into another repository.\n\nThe revised direction is to separate those responsibilities:\n\n- `typst-grammar-authoring` is the portable skill that authors use.\n- `update-typst-grammar-authoring` is the repo-local maintenance skill that\n  updates the portable one from canonical tutorial examples.\n\n## Goals / Non-Goals\n\n**Goals:**\n- Keep `typst-grammar-authoring` as a self-contained, English-only,\n  distributable skill with only `SKILL.md`.\n- Keep grammar lookup and validation workflows available inside the portable\n  `SKILL.md`.\n- Move generator scripts, traceability data, and repo-specific maintenance\n  instructions into `update-typst-grammar-authoring`.\n- Eliminate Windows-specific command paths and other platform-specific path\n  examples from the distributed `SKILL.md`.\n- Preserve the ability to regenerate the portable skill from\n  `src/tutorial/reference-grammar.typ`.\n\n**Non-Goals:**\n- Translate the full Chinese tutorial into English.\n- Replace Typst's own compiler diagnostics with a custom parser or validator.\n- Depend on Tinymist, VS Code, or any specific editor integration for required\n  validation.\n- Create a package manager or external publishing workflow for distributing the\n  portable skill.\n\n## Decisions\n\n### Decision: Split authoring and maintenance into two skills\n\nThe repository will contain two skills with different responsibilities:\n\n- `typst-grammar-authoring` for portable author guidance\n- `update-typst-grammar-authoring` for repo-local regeneration and maintenance\n\nWhy this approach:\n- It keeps the distributed skill easy to copy into other repositories.\n- It lets the updater skill retain repo-specific scripts and source references\n  without polluting the authoring skill.\n- It makes it clearer which instructions are for end users versus maintainers.\n\nAlternative considered:\n- Keep one repo-local skill with embedded updater logic. Rejected because it\n  mixes portability and maintenance concerns in the same artifact.\n\n### Decision: Make the portable skill a single-file skill\n\n`typst-grammar-authoring` will consist only of `SKILL.md`. It must not depend\non sibling scripts, references, or optional UI metadata to function after being\ncopied to another repository.\n\nWhy this approach:\n- A single file is the easiest form to distribute and review.\n- It minimizes accidental coupling to repo-local layout and helper artifacts.\n- It keeps the authoring experience simple for downstream repositories.\n\nAlternative considered:\n- Keep `agents/`, `references/`, or `scripts/` alongside the distributed skill.\n  Rejected because the user explicitly wants the authoring skill to remain only\n  a Markdown file.\n\n### Decision: Put all repo-specific updater assets under `update-typst-grammar-authoring`\n\nThe updater skill will own:\n\n- regeneration scripts\n- traceability artifacts\n- repo-specific instructions that mention `src/tutorial/reference-grammar.typ`\n- any maintenance-only metadata\n\nWhy this approach:\n- It centralizes maintenance behavior in one place.\n- It keeps downstream consumers from carrying unnecessary repo-specific files.\n- It preserves exact source traceability without expanding the portable skill\n  context.\n\nAlternative considered:\n- Leave traceability JSON and generator scripts under the portable skill folder.\n  Rejected because those files are maintenance artifacts, not distributed\n  authoring guidance.\n\n### Decision: Use platform-neutral path examples in the portable skill\n\nThe distributed `SKILL.md` will use forward-slash, repo-relative, or placeholder\npaths such as `path/to/document.typ` and `target/typst-grammar-authoring-check/document.html`.\nIt must not use Windows absolute paths or Windows-style backslash-only command\nexamples.\n\nWhy this approach:\n- It keeps the skill readable across operating systems.\n- It avoids downstream users mistaking repo-local Windows commands for required\n  syntax.\n- It makes the portable skill genuinely portable instead of just copyable.\n\nAlternative considered:\n- Keep PowerShell-oriented backslash paths because the repo is currently being\n  edited on Windows. Rejected because the distributed skill should not inherit a\n  single machine's path style.\n\n### Decision: Keep the portable skill self-contained, but keep verbose traceability out of it\n\nThe portable skill will still embed a compact grammar lookup and validation\nworkflow inside `SKILL.md`, but verbose canonical references and source-line\ntraceability will live in updater-owned artifacts instead.\n\nWhy this approach:\n- It preserves low-context usability for authors.\n- It keeps the portable skill smaller than a fully traceable catalog.\n- It still gives maintainers a way to audit generated entries precisely.\n\nAlternative considered:\n- Remove all generated grammar data from the portable skill and require a sidecar\n  reference file. Rejected because the portable skill must remain single-file.\n\n## Risks / Trade-offs\n\n- A single-file portable skill is still larger than a minimal skill because it\n  embeds grammar lookup content. → Mitigation: keep examples compact and keep\n  verbose traceability in the updater skill only.\n- Splitting the skills adds another artifact to maintain. → Mitigation: make the\n  updater skill the clear owner of regeneration and validation steps.\n- Portable path examples may drift back toward platform-specific syntax during\n  updates. → Mitigation: add explicit tasks and verification for path style.\n\n## Migration Plan\n\n1. Create a repo-local `update-typst-grammar-authoring` skill under\n   `.codex/skills/`.\n2. Move generator scripts, traceability artifacts, and repo-specific update\n   instructions into the updater skill.\n3. Reduce `.codex/skills/typst-grammar-authoring/` to a single distributable\n   `SKILL.md`.\n4. Rewrite distributed validation commands and examples to use platform-neutral\n   paths.\n5. Verify the updater still regenerates the distributed skill correctly.\n\nRollback strategy:\n- Restore the previous single-skill folder layout if the split causes an\n  unacceptable maintenance burden.\n\n## Open Questions\n\n- None at proposal update time. The split between portable and maintenance\n  skills is the intended direction.\n"
  },
  {
    "path": "openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/proposal.md",
    "content": "## Why\n\nThe current `typst-grammar-authoring` implementation mixes two different jobs:\nportable Typst authoring guidance for end users and repo-local maintenance\nlogic for regenerating that guidance from `src/tutorial/reference-grammar.typ`.\nThat coupling makes the distributed skill harder to copy into another\nrepository, and it has already allowed Windows-style command paths and\nrepo-specific maintenance details to leak into `SKILL.md`.\n\nWe want to split those concerns cleanly:\n\n- `typst-grammar-authoring` should become the portable skill that users can copy\n  to another repository as a single `SKILL.md` file.\n- `update-typst-grammar-authoring` should become the repo-local maintenance\n  skill that regenerates and validates the portable skill from this tutorial's\n  canonical grammar source.\n\n## What Changes\n\n- Split the current single skill implementation into two separate skills:\n  `typst-grammar-authoring` and `update-typst-grammar-authoring`.\n- Make `typst-grammar-authoring` a self-contained, English-only, distributable\n  skill consisting only of `SKILL.md`.\n- Move generator scripts, traceability artifacts, repo-specific update\n  instructions, and any maintenance-only metadata into\n  `update-typst-grammar-authoring`.\n- Require the distributed `typst-grammar-authoring/SKILL.md` to use\n  platform-neutral path examples instead of Windows-specific command paths.\n- Keep compile, HTML, and SVG validation guidance in the portable skill while\n  keeping verbose traceability data outside its default context.\n\n## Capabilities\n\n### New Capabilities\n- `typst-document-authoring-skill`: Provide a portable, single-file Typst\n  authoring skill with embedded grammar lookup and compile-based validation\n  guidance.\n- `typst-document-authoring-skill-maintenance`: Provide a repo-local updater\n  skill that regenerates the portable Typst authoring skill from\n  `src/tutorial/reference-grammar.typ`.\n\n### Modified Capabilities\n- None.\n\n## Impact\n\n- Affects repo-local skill content under `.codex/skills/`.\n- Changes `typst-grammar-authoring` from a repo-local folder with support files\n  into a portable single-file skill.\n- Adds or updates a separate `update-typst-grammar-authoring` skill for\n  generation, validation, and traceability workflows.\n- Removes Windows-specific command path examples from the distributed authoring\n  skill.\n"
  },
  {
    "path": "openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/specs/typst-document-authoring-skill/spec.md",
    "content": "## ADDED Requirements\n\n### Requirement: Portable authoring skill is English-facing and self-contained\nThe distributed `typst-grammar-authoring` skill SHALL be complete as a\nsingle-file, English-facing `SKILL.md` so it can be copied into another\nrepository without requiring sibling support files.\n\n#### Scenario: Single-file portable distribution\n- **WHEN** `typst-grammar-authoring` is prepared for downstream use\n- **THEN** the skill folder MUST be complete with only `SKILL.md`\n- **AND** that `SKILL.md` MUST contain the authoring guidance, grammar lookup,\n  and validation workflow needed by the consuming repository\n\n### Requirement: Portable authoring skill avoids Windows-specific path examples\nThe distributed `typst-grammar-authoring/SKILL.md` SHALL use platform-neutral,\nforward-slash, or placeholder filesystem paths in commands and examples.\n\n#### Scenario: Portable command examples\n- **WHEN** `typst-grammar-authoring/SKILL.md` is authored or regenerated\n- **THEN** command examples MUST use repo-relative or placeholder paths such as\n  `path/to/document.typ`\n- **AND** the file MUST NOT include Windows absolute paths or Windows-style\n  backslash-only command paths\n\n### Requirement: Portable authoring skill embeds compact grammar lookup\nThe distributed `typst-grammar-authoring/SKILL.md` SHALL embed a compact Typst\ngrammar lookup derived from `src/tutorial/reference-grammar.typ` so it remains\nsingle-file while still teaching canonical syntax patterns.\n\n#### Scenario: Grammar lookup without sidecar references\n- **WHEN** Codex needs a Typst syntax pattern while using\n  `typst-grammar-authoring`\n- **THEN** the skill MUST provide that lookup inside `SKILL.md`\n- **AND** the lookup MUST be derived from canonical examples in\n  `src/tutorial/reference-grammar.typ`\n\n### Requirement: Updater skill owns regeneration of the portable skill\nThe repo-local `update-typst-grammar-authoring` skill SHALL own the workflow\nthat regenerates the distributed `typst-grammar-authoring/SKILL.md` from\n`src/tutorial/reference-grammar.typ`.\n\n#### Scenario: Repo-local regeneration\n- **WHEN** a maintainer updates canonical grammar examples from\n  `src/tutorial/reference-grammar.typ`\n- **THEN** `update-typst-grammar-authoring` MUST provide the update workflow\n- **AND** that workflow MUST produce the distributed\n  `typst-grammar-authoring/SKILL.md`\n\n### Requirement: Updater skill preserves verbose traceability outside portable context\nThe updater workflow SHALL preserve exact source traceability in separate\nmaintenance artifacts without injecting verbose canonical reference and\ntraceability sections into the distributed `typst-grammar-authoring/SKILL.md`.\n\n#### Scenario: Maintainer needs exact source mapping\n- **WHEN** a maintainer needs exact source mapping for a generated grammar entry\n- **THEN** `update-typst-grammar-authoring` MUST provide a separate\n  traceability artifact\n- **AND** the distributed `typst-grammar-authoring/SKILL.md` MUST omit verbose\n  canonical reference and traceability sections from its active context\n\n### Requirement: Grammar validation uses Typst compilation\nThe portable authoring skill SHALL define `typst compile` as the required\nvalidation workflow for grammar and compile-time errors in authored Typst\ndocuments.\n\n#### Scenario: Compile failure is treated as invalid grammar or document state\n- **WHEN** Codex validates a `.typ` file with the skill's required workflow\n- **THEN** a non-zero `typst compile` result MUST be treated as a blocking\n  validation failure\n\n### Requirement: Text output can be validated through HTML export\nThe portable authoring skill SHALL define a text-validation workflow based on\nTypst HTML output so Codex can inspect textual structure and rendered text\nseparately from compile success.\n\n#### Scenario: HTML validation of authored text\n- **WHEN** Codex needs to verify rendered text or document structure after a\n  successful compile\n- **THEN** the skill MUST direct Codex to use `typst compile --features html`\n  to produce HTML output for inspection\n\n### Requirement: Visual output can be validated through SVG and Playwright\nThe portable authoring skill SHALL define a visual-validation workflow based on\nTypst SVG output and Playwright MCP inspection so Codex can review rendered\nlayout after compilation.\n\n#### Scenario: SVG and Playwright visual inspection\n- **WHEN** Codex needs to inspect rendered layout or visual regressions in a\n  user Typst document\n- **THEN** the skill MUST direct Codex to compile the document to SVG\n- **AND** the skill MUST direct Codex to use Playwright MCP as the visual\n  inspection step\n"
  },
  {
    "path": "openspec/changes/archive/2026-03-15-create-english-typst-grammar-skill/tasks.md",
    "content": "## 1. Split the skill topology\n\n- [x] 1.1 Create a repo-local `update-typst-grammar-authoring` skill target under `.codex/skills/`\n- [x] 1.2 Move generator scripts, traceability artifacts, and updater-only metadata out of `typst-grammar-authoring` and into `update-typst-grammar-authoring`\n- [x] 1.3 Reduce `.codex/skills/typst-grammar-authoring/` to a distributable single-file skill that contains only `SKILL.md`\n\n## 2. Rewrite the portable authoring skill\n\n- [x] 2.1 Rewrite `typst-grammar-authoring/SKILL.md` to remove Windows-specific command paths and use platform-neutral path examples\n- [x] 2.2 Keep grammar lookup and validation guidance self-contained inside the distributed `typst-grammar-authoring/SKILL.md`\n- [x] 2.3 Remove repo-local maintenance instructions, source-path dependencies, and sidecar-file assumptions from the distributed `typst-grammar-authoring/SKILL.md`\n\n## 3. Build the updater skill workflow\n\n- [x] 3.1 Add updater instructions for regenerating `typst-grammar-authoring/SKILL.md` from `src/tutorial/reference-grammar.typ`\n- [x] 3.2 Keep exact source traceability available as updater-owned artifacts without expanding the portable skill context\n- [x] 3.3 Add updater validation guidance for refreshing, reviewing, and diffing the distributed skill output\n\n## 4. Verify the split\n\n- [x] 4.1 Verify `.codex/skills/typst-grammar-authoring/` contains only `SKILL.md`\n- [x] 4.2 Verify the distributed `SKILL.md` contains no Windows-specific or absolute repo-local paths in command examples\n- [x] 4.3 Smoke-test the updater workflow and the portable skill after the split\n"
  },
  {
    "path": "openspec/config.yaml",
    "content": "schema: spec-driven\n\ncontext: |\n  When a change is archived and syncs or creates canonical specs under\n  `openspec/specs/`, review each resulting `## Purpose` section before calling\n  the archive complete.\n  Replace any autogenerated placeholder like\n  `TBD - created by archiving change ...` with a concise, domain-specific\n  purpose statement that describes the capability.\n\nrules:\n  specs:\n    - Every spec in `openspec/specs/*/spec.md` must include a concrete `## Purpose` section.\n    - Never leave the autogenerated archive placeholder in a canonical spec purpose; replace it before reporting archive success.\n"
  },
  {
    "path": "openspec/specs/typst-document-authoring-skill/spec.md",
    "content": "# typst-document-authoring-skill Specification\n\n## Purpose\nDefine the canonical requirements for a portable, English-facing Typst authoring skill and the updater and validation workflows that maintain it.\n\nThis specification defines the `typst-document-authoring-skill` capability, which is concretely implemented by the portable `typst-grammar-authoring` skill (distributed as `typst-grammar-authoring/SKILL.md`) and its repo-local updater workflow `update-typst-grammar-authoring`.\n\n## Requirements\n### Requirement: Portable authoring skill is English-facing and self-contained\nThe distributed `typst-grammar-authoring` skill SHALL be complete as a\nsingle-file, English-facing `SKILL.md` so it can be copied into another\nrepository without requiring sibling support files.\n\n#### Scenario: Single-file portable distribution\n- **WHEN** `typst-grammar-authoring` is prepared for downstream use\n- **THEN** the skill folder MUST be complete with only `SKILL.md`\n- **AND** that `SKILL.md` MUST contain the authoring guidance, grammar lookup,\n  and validation workflow needed by the consuming repository\n\n### Requirement: Portable authoring skill avoids Windows-specific path examples\nThe distributed `typst-grammar-authoring/SKILL.md` SHALL use platform-neutral,\nforward-slash, or placeholder filesystem paths in commands and examples.\n\n#### Scenario: Portable command examples\n- **WHEN** `typst-grammar-authoring/SKILL.md` is authored or regenerated\n- **THEN** command examples MUST use repo-relative or placeholder paths such as\n  `path/to/document.typ`\n- **AND** the file MUST NOT include Windows absolute paths or Windows-style\n  backslash-only command paths\n\n### Requirement: Portable authoring skill embeds compact grammar lookup\nThe distributed `typst-grammar-authoring/SKILL.md` SHALL embed a compact Typst\ngrammar lookup derived from `src/tutorial/reference-grammar.typ` so it remains\nsingle-file while still teaching canonical syntax patterns.\n\n#### Scenario: Grammar lookup without sidecar references\n- **WHEN** Codex needs a Typst syntax pattern while using\n  `typst-grammar-authoring`\n- **THEN** the skill MUST provide that lookup inside `SKILL.md`\n- **AND** the lookup MUST be derived from canonical examples in\n  `src/tutorial/reference-grammar.typ`\n\n### Requirement: Updater skill owns regeneration of the portable skill\nThe repo-local `update-typst-grammar-authoring` skill SHALL own the workflow\nthat regenerates the distributed `typst-grammar-authoring/SKILL.md` from\n`src/tutorial/reference-grammar.typ`.\n\n#### Scenario: Repo-local regeneration\n- **WHEN** a maintainer updates canonical grammar examples from\n  `src/tutorial/reference-grammar.typ`\n- **THEN** `update-typst-grammar-authoring` MUST provide the update workflow\n- **AND** that workflow MUST produce the distributed\n  `typst-grammar-authoring/SKILL.md`\n\n### Requirement: Updater skill preserves verbose traceability outside portable context\nThe updater workflow SHALL preserve exact source traceability in separate\nmaintenance artifacts without injecting verbose canonical reference and\ntraceability sections into the distributed `typst-grammar-authoring/SKILL.md`.\n\n#### Scenario: Maintainer needs exact source mapping\n- **WHEN** a maintainer needs exact source mapping for a generated grammar entry\n- **THEN** `update-typst-grammar-authoring` MUST provide a separate\n  traceability artifact\n- **AND** the distributed `typst-grammar-authoring/SKILL.md` MUST omit verbose\n  canonical reference and traceability sections from its active context\n\n### Requirement: Grammar validation uses Typst compilation\nThe portable authoring skill SHALL define `typst compile` as the required\nvalidation workflow for grammar and compile-time errors in authored Typst\ndocuments.\n\n#### Scenario: Compile failure is treated as invalid grammar or document state\n- **WHEN** Codex validates a `.typ` file with the skill's required workflow\n- **THEN** a non-zero `typst compile` result MUST be treated as a blocking\n  validation failure\n\n### Requirement: Text output can be validated through HTML export\nThe portable authoring skill SHALL define a text-validation workflow based on\nTypst HTML output so Codex can inspect textual structure and rendered text\nseparately from compile success.\n\n#### Scenario: HTML validation of authored text\n- **WHEN** Codex needs to verify rendered text or document structure after a\n  successful compile\n- **THEN** the skill MUST direct Codex to use `typst compile --features html`\n  to produce HTML output for inspection\n\n### Requirement: Visual output can be validated through SVG and Playwright\nThe portable authoring skill SHALL define a visual-validation workflow based on\nTypst SVG output and Playwright MCP inspection so Codex can review rendered\nlayout after compilation.\n\n#### Scenario: SVG and Playwright visual inspection\n- **WHEN** Codex needs to inspect rendered layout or visual regressions in a\n  user Typst document\n- **THEN** the skill MUST direct Codex to compile the document to SVG\n- **AND** the skill MUST direct Codex to use Playwright MCP as the visual\n  inspection step\n"
  },
  {
    "path": "scripts/build.ps1",
    "content": "cargo build --release --target wasm32-unknown-unknown --manifest-path ./crates/embedded-typst/Cargo.toml --features typst-plugin\n$InstallPath = \"assets/artifacts/embedded_typst.wasm\"\nif (Test-Path $InstallPath) {\n    Remove-Item $InstallPath\n}\nMove-Item target/wasm32-unknown-unknown/release/embedded_typst.wasm $InstallPath\n"
  },
  {
    "path": "scripts/build.sh",
    "content": "#!/bin/bash\n\ncargo build --release --target wasm32-unknown-unknown\n\nINSTALL_PATH=\"assets/artifacts/embedded_typst.wasm\"\nif [ -f \"$INSTALL_PATH\" ]; then\n    rm \"$INSTALL_PATH\"\nfi\n\nmv target/wasm32-unknown-unknown/release/embedded_typst.wasm $INSTALL_PATH\n"
  },
  {
    "path": "src/book.typ",
    "content": "#import \"@preview/shiroa:0.2.3\": *\n\n#show: book\n\n#let book-info = json(\"/meta.json\")\n\n#book-meta(\n  title: \"The Raindrop-Blue Book (Typst中文教程)\",\n  description: \"Typst中文教程\",\n  repository: \"https://github.com/typst-doc-cn/tutorial\",\n  repository-edit: \"https://github.com/typst-doc-cn/tutorial/edit/main/src/{path}\",\n  authors: book-info.contributors,\n  language: \"zh-cn\",\n  summary: [\n    #prefix-chapter(\"introduction.typ\")[导引]\n    = 模式\n    - #chapter(\"tutorial/writing-markup.typ\")[标记模式]\n    - #chapter(\"tutorial/writing-scripting.typ\")[脚本模式]\n    = 脚本\n    - #chapter(\"tutorial/scripting-literal.typ\")[基本类型]\n    - #chapter(\"tutorial/scripting-variable.typ\")[变量与函数]\n    - #chapter(\"tutorial/scripting-composite.typ\")[复合类型]\n    - #chapter(\"tutorial/doc-modulize.typ\")[模块化（多文件）]\n    - #chapter(\"tutorial/scripting-block-and-expression.typ\")[表达式]\n    - #chapter(\"tutorial/scripting-control-flow.typ\")[控制流]\n    - #chapter(\"tutorial/doc-stateful.typ\")[状态化]\n    = 中文排版\n    - #chapter(\"tutorial/scripting-main.typ\")[编译流程]\n    - #chapter(\"tutorial/writing-chinese.typ\")[中文排版]\n    - #chapter(\"tutorial/scripting-style.typ\")[样式模型]\n    = 科技排版\n    - #chapter(\"tutorial/writing-math.typ\")[数学排版]\n    - #chapter(\"tutorial/scripting-shape.typ\")[图形排版]\n    - #chapter(\"tutorial/scripting-layout.typ\")[布局模型]\n    - #chapter(\"tutorial/scripting-content.typ\")[文档模型]\n    = 附录\n    // - #chapter(\"tutorial/reference-grammar.typ\")[语法示例检索表]\n    - #chapter(\"tutorial/reference-utils.typ\")[常用函数表]\n    - #chapter(\"tutorial/reference-math-symbols.typ\")[常用数学符号]\n    - #chapter(none)[特殊Unicode字符]\n    = 参考\n    - #chapter(\"tutorial/reference-typebase.typ\")[基本类型]\n    - #chapter(\"tutorial/reference-type-builtin.typ\")[内置类型]\n    - #chapter(\"tutorial/reference-date.typ\")[时间类型]\n    // - #chapter(\"tutorial/reference-visualization.typ\")[图形与几何元素]\n    // - #chapter(\"tutorial/reference-color.typ\")[颜色、色彩渐变与模式]\n    - #chapter(\"tutorial/reference-data-process.typ\")[数据读写]\n    - #chapter(\"tutorial/reference-data-process.typ\")[数据处理]\n    - #chapter(\"tutorial/reference-calculation.typ\")[数值计算]\n    - #chapter(\"tutorial/reference-math-mode.typ\")[数学模式]\n    - #chapter(\"tutorial/reference-bibliography.typ\")[参考文献]\n    - #chapter(\"tutorial/reference-wasm-plugin.typ\")[WASM插件]\n    // = 进阶教程 — 排版Ⅳ\n    // 6. 脚本Ⅱ\n    // - IO与数据处理\n    // - 内置类型\n    // - 数值计算\n    // - WASM插件\n    // 7. 排版Ⅱ\n    // - 代码高亮\n    // - 参考文献\n    // - 文档大纲\n    // 8. 科技排版\n    // - 数学模式\n    // - 数学公式和定理\n    // - 物理公式\n    // - 化学方程式\n    // 9. 图表\n    // - 立体几何\n    // - 统计图\n    // - 状态机\n    // - 电路图\n    // 10. 模板\n    // 11. 参考Ⅰ\n    // - 基本类型\n    // - 内置类型\n    // - 时间\n    // - 高级颜色\n    // - 长度单位\n    // 12. 参考Ⅱ\n    // - 图形\n    = 专题\n    - #chapter(\"topics/writing-math.typ\")[编写一篇数学文档]\n    // https://github.com/PgBiel/typst-oxifmt/blob/main/oxifmt.typ\n    // - #chapter(\"topics/format-lib.typ\")[制作一个格式化库]\n    // https://github.com/Pablo-Gonzalez-Calderon/showybox-package/blob/main/showy.typ\n    - #chapter(\"topics/writing-component-lib.typ\")[制作一个组件库]\n    - #chapter(\"topics/writing-plugin-lib.typ\")[制作一个外部插件]\n    - #chapter(\"topics/call-externals.typ\")[在Typst内执行Js、Python、Typst等]\n    // https://github.com/frugal-10191/frugal-typst\n    - #chapter(\"topics/template-book.typ\")[制作一个书籍模板]\n    // chicv\n    - #chapter(\"topics/template-cv.typ\")[制作一个CV模板]\n    // official template\n    - #chapter(\"topics/template-paper.typ\")[制作一个IEEE模板]\n    = 专题 — 公式和定理\n    - #chapter(\"science/chemical.typ\")[化学方程式]\n    - #chapter(\"science/algorithm.typ\")[伪算法]\n    - #chapter(\"science/theorem.typ\")[定理环境]\n    = 专题 — 杂项\n    - #chapter(\"misc/font-setting.typ\")[字体设置]\n    // - #chapter(\"misc/font-style.typ\")[伪粗体、伪斜体]\n    - #chapter(\"misc/code-syntax.typ\")[自定义代码高亮规则]\n    - #chapter(\"misc/code-theme.typ\")[自定义代码主题]\n    - #chapter(\"misc/text-processing.typ\")[读取外部文件和文本处理]\n    = 专题 — 绘制图表\n    - #chapter(\"graph/table.typ\")[制表]\n    - #chapter(\"graph/solid-geometry.typ\")[立体几何]\n    - #chapter(\"graph/digraph.typ\")[拓扑图]\n    - #chapter(\"graph/statistics.typ\")[统计图]\n    - #chapter(\"graph/state-machine.typ\")[状态机]\n    - #chapter(\"graph/electronics.typ\")[电路图]\n    = 专题 — 附录Ⅱ\n    - #chapter(\"tutorial/reference-grammar.typ\")[语法示例检索表Ⅱ]\n    - #chapter(\"template/slides.typ\")[演示文稿（PPT）]\n    - #chapter(\"template/paper.typ\")[论文模板]\n    - #chapter(\"template/book.typ\")[书籍模板]\n    = 专题参考\n    - #chapter(\"tutorial/reference-counter-state.typ\")[计数器和状态]\n    - #chapter(\"tutorial/reference-length.typ\")[长度单位]\n    - #chapter(\"tutorial/reference-layout.typ\")[布局函数]\n    - #chapter(\"tutorial/reference-table.typ\")[表格]\n    - #chapter(\"tutorial/reference-outline.typ\")[文档大纲]\n  ],\n)\n\n#build-meta(dest-dir: \"../dist\")\n\n// #get-book-meta()\n\n// re-export page template\n#import \"/typ/templates/page.typ\": heading-reference, project\n#let page = project\n#let ref-page = project.with(kind: \"reference-page\")\n#let cross-link = cross-link\n#let heading-reference = heading-reference\n"
  },
  {
    "path": "src/ebook.typ",
    "content": "#import \"@preview/shiroa:0.2.3\": *\n#import \"/typ/templates/ebook.typ\"\n\n#show: ebook.project.with(\n  title: \"typst-tutorial-cn\",\n  display-title: \"Typst中文教程\",\n  spec: \"book.typ\",\n  // set a resolver for inclusion\n  styles: (\n    inc: it => include it,\n  ),\n)\n"
  },
  {
    "path": "src/figures.typ",
    "content": "#import \"@preview/fletcher:0.5.7\" as fletcher: edge, node\n#import \"/typ/templates/page.typ\": is-light-theme, main-color, page-width\n#import \"mod.typ\": typst-func\n\n#let figure-typst-arch(\n  stroke-color: main-color,\n  light-theme: is-light-theme,\n) = {\n  let node = node.with(stroke: main-color + 0.5pt)\n  let xd = align.with(center)\n\n  fletcher.diagram(\n    node-outset: 2pt,\n    axes: (ltr, btt),\n    // nodes\n    node((0, 0), xd[文件解析\\ （Parsing）]),\n    node((1.5, 0), xd[表达式求值\\ （Evaluation）]),\n    node((3, 0), xd[内容排版\\ （Typesetting）]),\n    node((3, -1), xd[结构导出\\ （Exporting）]),\n    // edges\n    edge((0, 0), (1.5, 0), \"..}>\", bend: 25deg),\n    edge((1.5, 0), (0, 0), xd[`import`或\\ `include`], \"..}>\", bend: 25deg),\n    edge((1.5, 0), (3, 0), \"..}>\", bend: 25deg),\n    edge((3, 0), (1.5, 0), xd[`styled`等], \"..}>\", bend: 25deg),\n    edge((3, 0), (3, -1), \"..}>\", bend: 25deg),\n  )\n}\n\n#let figure-content-decoration(\n  stroke-color: main-color,\n  light-theme: is-light-theme,\n  width: page-width,\n) = {\n  // let node = node.with(stroke: main-color + 0.5pt)\n  let node-text = align.with(center)\n\n  // todo: 消除重复\n  if width < 500pt {\n    let node-rect-text(content) = rect(node-text(content), width: 125pt)\n    fletcher.diagram(\n      node-outset: 2pt,\n      axes: (ltr, btt),\n      // nodes\n      node((0, 0), node-rect-text[```typ 左#[一段文本]右```]),\n      node((0, -1.5), node-rect-text(```typc text(blue)```)),\n      node((0, -3), node-rect-text([左] + text(blue)[一段文本] + [右])),\n\n      node((0, -0.5 + 0), node-text[选中内容]),\n      node((0, -0.5 + -1.5), node-text[对内容块应用#typst-func(\"text\")函数]),\n      node((0, -0.5 + -3), node-text[最终效果]),\n\n      // edges\n      edge((0, -0.5 + 0), (0, -1.5), \"..}>\"),\n      edge((0, -0.5 + -1.5), (0, -3), \"..}>\"),\n    )\n  } else {\n    fletcher.diagram(\n      node-outset: 2pt,\n      axes: (ltr, btt),\n      // nodes\n      node((0, 0), node-text[```typ 左#[一段文本]右```]),\n      node((1.5, 0), node-text(```typc text(blue)```)),\n      node((3, 0), node-text([左] + text(blue)[一段文本] + [右])),\n      node((0, -0.5), node-text[选中内容]),\n      node((1.5, -0.5), node-text[对内容块应用#typst-func(\"text\")函数]),\n      node((3, -0.5), node-text[最终效果]),\n      // edges\n      edge((0, 0), (1.5, 0), \"..}>\"),\n      edge((1.5, 0), (3, 0), \"..}>\"),\n    )\n  }\n}\n"
  },
  {
    "path": "src/graph/digraph.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [拓扑图])\n"
  },
  {
    "path": "src/graph/electronics.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [电路图])\n"
  },
  {
    "path": "src/graph/mod.typ",
    "content": "#import \"/src/book.typ\"\n#import \"/typ/templates/page.typ\"\n\n#import \"../mod.typ\": code, exec-code, todo-box\n"
  },
  {
    "path": "src/graph/solid-geometry.typ",
    "content": "#import \"mod.typ\": *\n#import \"@preview/cetz:0.3.4\": *\n\n#show: book.page.with(title: \"立体几何\")\n\n== 变换「坐标系」\n\n在绘制立体图形（以及其他抽象图形）时，最重要的思路是变换「坐标系」（Viewport），以方便绘制。\n\n#code(```typ\n#import \"@preview/cetz:0.3.4\": *\n#canvas({\n  import draw: *\n  let axis-line(p) = {\n    line((0, 0), (x: 1.5), stroke: red)\n    line((0, 0), (y: 1.5), stroke: blue)\n    line((0, 0), (z: 1.5), stroke: green)\n    circle((0, 0, 0), fill: black, radius: 0.05)\n    content((-0.4, 0.1, -0.2), p)\n  }\n  set-viewport((0, 0, 0), (4, 4, -4), bounds: (4, 4, 4))\n  axis-line(\"A\")\n  set-viewport((4, 4, 4), (0, 0, 0), bounds: (4, 4, 4))\n  axis-line(\"B\")\n})\n```)\n\n== 使用「空间变换」\n\n由于「空间变换」（Transformation）的存在，你可以先绘制基本图形，再使用变换完成图形的绘制：\n\n```typ\n#let 六面体 = {\n  import draw: *\n  let neg(u) = if u == 0 { 1 } else { -1 }\n  for (p, c) in (\n    ((0, 0, 0), black), ((1, 1, 0), red),\n    ((1, 0, 1), blue), ((0, 1, 1), green),\n  ) {\n    line(vector.add(p, (0, 0, neg(p.at(2)))), p, stroke: c)\n    line(vector.add(p, (0, neg(p.at(1)), 0)), p, stroke: c)\n    line(vector.add(p, (neg(p.at(0)), 0, 0)), p, stroke: c)\n  }\n}\n```\n\n#let 六面体 = {\n  import draw: *\n  let neg(u) = if u == 0 {\n    1\n  } else {\n    -1\n  }\n  for (p, c) in (\n    ((0, 0, 0), black),\n    ((1, 1, 0), red),\n    ((1, 0, 1), blue),\n    ((0, 1, 1), green),\n  ) {\n    line(vector.add(p, (0, 0, neg(p.at(2)))), p, stroke: c)\n    line(vector.add(p, (0, neg(p.at(1)), 0)), p, stroke: c)\n    line(vector.add(p, (neg(p.at(0)), 0, 0)), p, stroke: c)\n  }\n}\n\n运行以下代码你将获得：\n\n#code(\n  ```typ\n  #import \"@preview/cetz:0.3.4\": *\n  #align(center, canvas(六面体))\n  ```,\n  scope: (六面体: 六面体),\n)\n\n#pagebreak(weak: true)\n\n== 六面体\n\n#code(\n  ```typ\n  #import \"@preview/cetz:0.3.4\": *\n  #align(center, canvas({\n    import draw: *\n    set-viewport((0, 0, 0), (4, 4, -4), bounds: (1, 1, 1))\n    group(name: \"S\", translate((0, 0, 0)) + {\n      anchor(\"O\", (0, 0, 0))\n      六面体\n    })\n    group(name: \"T\", translate((1.4, 0, 0)) + scale(x: 120%, y: 80%) + {\n      line((0, 0, 0.5), (0.5, 0, 0), stroke: black)\n      anchor(\"A\", (0, 0, 0.5))\n      anchor(\"B\", (0.5, 0, 0))\n      六面体\n    })\n    circle(\"S.O\", fill: black, radius: 0.05 / 4)\n    content((rel: (-0.08, 0.04, 0), to: \"S.O\"), [O])\n    circle(\"T.A\", fill: black, radius: 0.05 / 4)\n    content((rel: (0.02, -0.08, 0), to: \"T.A\"), [A])\n    circle(\"T.B\", fill: black, radius: 0.05 / 4)\n    content((rel: (0, 0.1, 0), to: \"T.B\"), [B])\n  }))\n  ```,\n  scope: (六面体: 六面体),\n)\n"
  },
  {
    "path": "src/graph/state-machine.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [状态机])\n"
  },
  {
    "path": "src/graph/statistics.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [统计图])\n"
  },
  {
    "path": "src/graph/table.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [制表])\n"
  },
  {
    "path": "src/intermediate/mod.typ",
    "content": "#import \"/src/book.typ\"\n#import \"../mod.typ\": code as _code, exec-code as _exec-code, pro-tip, refs, todo-box, typst-func\n#import \"/typ/templates/page.typ\": main-color\n#import \"/typ/embedded-typst/lib.typ\": default-cjk-fonts, default-fonts, svg-doc\n"
  },
  {
    "path": "src/introduction.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [导引])\n\n本书面向所有Typst用户，按三种方式讲解Typst语言。《教程》等章节循序渐进，讲述了Typst的排版方式和原理；《参考》等章节拓展广度；《专题》等则专注解决具体问题。本书希望缓解Typst相关资料严重缺失的问题，与官方文档相互补充，帮助大家入门和学习Typst。\n\n其中，《教程》的主要定位是较低的阅读门槛。即使你没有编程语言基础，也能通过《教程》上手使用Typst，在日常生活中编写各式各样的文档。\n\n《教程》的另一个作用是串联知识。有很多排版技巧和问题缺乏深度，不适合放在《教程》内。为了避免让章节冗长，这些知识会被单独列在《参考》或《专题》中，供《教程》引用。这样，擅于编程的同学也可以略读《教程》，重点阅读放在关联的章节上。\n\n同时，本书也会不时总结在使用Typst时的疑难杂症。\n\n== 为什么学习Typst？\n\n在开始之前，让我们考虑Typst的名称解释和用途。Typst首先是一种用于排版文档的标记语言，它旨在易于学习、快速且用途广泛。Typst还同时指代编译器本身。Typst编译器读取并解释带有标记的文本文件，产生适合不同终端阅读的PDF文档。Typst也支持导出其他格式，例如SVG和PNG。\n\nTypst的性能很好，是撰写长篇文本的极佳选择，例如书籍和报告。 并且，Typst 非常适合书写包含数学公式的文档，例如数学、物理和工程领域的论文。 由于其编程特性，它也适用于自动化生成一系列相同样式的文档（例如作业、丛书和发票）。\n\n== 阅读本教程前，您需要了解的知识\n\n你不需要了解任何前置知识，所需的仅仅是安装Typst，并跟着本教程的在线示例一步步学习。\n\n== 其他参考资料\n\n- #link(\"https://typst-doc-cn.github.io/docs/tutorial/\")[官方文档翻译 - 入门教程]\n- #link(\"https://typst-doc-cn.github.io/docs/chinese/\")[非官方中文用户指南]\n- #link(\"https://typst-doc-cn.github.io/docs/reference/\")[官方文档翻译 - 参考]\n- #link(\"https://sitandr.github.io/typst-examples-book/book/about.html\")[Typst Example Books]\n\n= 配置Typst运行环境\n\n== 使用官方的webapp（推荐）\n\n官方提供了*在线且免费*的多人协作编辑器。该编辑器会从远程下载WASM编译器，并在你的浏览器内运行编辑器，为你提供预览服务。你可以注册一个账户并开箱即用该编辑器。\n\n#figure(image(\"/assets/files/editor-webapp.png\"), caption: [本书作者在网页中打开webapp的瞬间])\n\n该编辑器的速度相比许多LaTeX编辑器都有显著优势。这是因为：\n- 编辑文档后，即时预览基本不会被网络请求阻塞。\n- Typst脚本皆在本地浏览器中运行。\n- Typst编译器本身性能极其优越。\n\n*注意：如果遇到无法加载的问题，你需要检查你的网络环境，如有必要请使用科学上网工具。*\n\n== 使用VSCode编辑（推荐）\n\n打开扩展界面，搜索并安装 Tinymist 插件。它会为你提供语法高亮，代码补全，代码格式化，即时预览等功能。\n\n#figure(image(\"/assets/files/editor-vscode.png\"), caption: [本书作者在VSCode中预览并编辑本文的瞬间])\n\n该编辑器的性能不如webapp。这是因为：\n- 编译器与预览器是两个不同的进程，有通信开销。\n- 用户事件和文件IO有可能增加E2E延时。\n\n但是：\n- 大部分时间下，你感受不到和webapp之间的性能差异。Typst真的非常快。\n- 你可以离线编辑Typst文档，无需任何网络连接，例如本书的部分章节是在飞机上完成的。\n- 你可以免费使用部分Typst Pro的功能。你可以在文件系统中管理你所有的源代码，实施包括但不限于使用git等源码管理软件等操作。\n\n== 使用其他编辑器编辑\n\n你可以在这些编辑器中手动安装Tinymist LSP服务或Typst相关插件。自2024年初，Neovim和Emacs已经可以与VSCode一样，已经可以有很好的Typst编辑体验。\n\n== 使用typst-cli与PDF阅读器\n\nTypst 的 CLI 可从不同的来源获得。你可以在#link(\"https://github.com/typst/typst/releases/\")[发布页面]获得最新版本的 Typst 的源代码和预构建的二进制文件。下载适合你平台的存档并将其放在“PATH”中的目录中。及时了解未来发布后，你只需运行```bash typst update```即可。\n\n你可以通过不同的包管理器安装Typst。请注意，包管理器中的版本可能落后于最新版本。\n- Linux：查看#link(\"https://repology.org/project/typst/versions\")[Typst on Repology。]\n- macOS：使用brew：\n  ```bash\n  brew install typst\n  ```\n- Windows：使用winget：\n  ```bash\n  winget install --id Typst.Typst\n  ```\n\n你还可以使用#link(\"https://rustup.rs/\")[Rust]、Nix或Docker安装Typst。更多信息请查看#link(\"https://github.com/typst/typst?tab=readme-ov-file#usage\")[官方英文说明]。\n\n安装好CLI之后，你就可以在命令行里运行Typst编译器了。以下命令将工作目录下的文件编译为`file.pdf`：\n\n```bash\ntypst compile file.typ\n```\n\n为了增量编译和预览PDF，你需要让Typst编译器运行在监视模式（watch）下：\n\n```bash\ntypst watch file.typ\n```\n\n当你启动Typst编译器后，你可以使用你喜欢的编辑器编辑Typst文档，并使用你喜欢的PDF阅读器打开编译好的PDF文件。PDF阅读器推荐使用：\n\n- 在Windows下使用#link(\"https://www.sumatrapdfreader.org/free-pdf-reader\")[Sumatra PDF]。\n\n== 各Typst运行环境的比较\n\n#{\n  set align(center)\n  table(\n    columns: if get-page-width() < 500pt {\n      (1fr, 1fr, 1fr, 1fr, 1fr, 1fr, 1fr)\n    } else {\n      (1fr, 1fr, 1fr, 5em, 5em, 5em, 10em)\n    },\n    align: horizon + center,\n    [名称], [编辑器], [编译器环境], [预览方案], [是否支持即时编译], [语言服务], [备注],\n    [WebAPP], [Code Mirror], [wasm], [渲染图片], [是], [良好], align(left)[开箱即用],\n    [VSCode], [VSCode], [native], [webview], [是], [优秀], align(left)[简单上手，定制性好],\n    [neovim], [neovim], [native], [webview], [是], [良好], align(left)[不易安装，定制性强],\n    [Emacs], [Emacs], [native], [webview], [是], [良好], align(left)[难以安装],\n    [typst-cli], [任意编辑器], [native], [任意PDF阅读器], [否], [无], align(left)[简单上手，灵活组合],\n  )\n}\n\n= 如何阅读本书\n\n本书主要分为三个部分：\n\n+ 教程：介绍Typst的语法、原理和理念，以助你深度理解Typst。\n+ 参考：完整介绍Typst中内置的函数和各种外部库，以助你广泛了解Typst的能力，用Typst实现各种排版需求。\n+ 专题等：与参考等章节的区别是，每个专题都专注解决一类问题，而不会讲解函数的用法。\n\n每个部分都包含大量例子，它们各有侧重：\n\n- 教程中的例子希望切中要点，而避免冗长的全面的展示特性。\n- 参考中的例子希望讲透元素和函数的使用，例子多选自过去的一年中大家常问的问题。\n- 专题中的例子往往有连贯性，从前往后带你完成一系列专门的问题。专题中的例子假设你已经掌握了相关知识，只以最专业的代码解决该问题。\n\n#pro-tip[\n  本书会随时夹带一些“Pro Tip”。这些“Pro Tip”由蓝色框包裹。它们告诉你一些较难理解的知识点。\n\n  你可以选择在初次阅读时*跳过*这些框，而不影响对正文的理解。但建议你在阅读完整本书后回头观看所不理解的那些“Pro Tip”。\n]\n\n以下是推荐的阅读方法：\n\n+ 首先阅读《基础教程》排版Ⅰ的两篇文章，即《初识标记模式》和《初识脚本模式》。\n\n  经过这一步，你应该可以像使用Markdown那样，编写一篇基本不设置样式的文档。同时，这一步的学习难度也与学习完整Markdown语法相当。\n\n+ 接着，阅读《基础教程》脚本Ⅰ的三篇文章。\n\n  经过这一步，你应该已经基本入门了Typst的标记和脚本。此时，你和进阶用户的唯一区别是，你还不太会使用高级的样式配置。推荐：\n  - 如果你熟练阅读英语，推荐主要参考#link(\"https://typst.app/docs/\")[官方文档]的内容。\n  - 否则，推荐继续阅读本书剩下的内容，并参考#link(\"https://typst-doc-cn.github.io/docs/\")[非官方中文文档]的内容。\n\n  有什么问题？\n  - 本书只有《基础教程》完成了校对和润色，后续部分还非常不完善。甚至《基础教程》部分还有待改进。\n  - #link(\"https://typst-doc-cn.github.io/docs/\")[非官方中文文档]是GPT机翻后润色的的结果，有可能错翻、漏翻，内容也可能有些许迟滞。\n\n+ 接着，阅读《基础教程》脚本Ⅱ和排版Ⅲ的五篇文章。\n\n  这两部分分别介绍了如何使用Typst的模块机制与状态机制。模块允许你将文档拆分为多个文件。状态则类似于其他编程语言中的全局变量的概念，可用于收集和维护数据。\n\n+ 最后，同时阅读《基础参考》和《进阶教程》。你可以根据你的需求挑选《基础参考》的部分章节阅读。即便不阅读任何《基础参考》中的内容，你也可以继续阅读《进阶教程》。\n\n  经过这一步，你应该已经完全学会了目前Typst的所有理念与内容。\n\n= 许可证\n\n*typst-tutorial-cn* 所有的源码和文档都在#link(\"https://www.apache.org/licenses/LICENSE-2.0\")[Apache License v2.0]许可证下发布.\n"
  },
  {
    "path": "src/misc/code-syntax.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [自定义代码高亮规则])\n"
  },
  {
    "path": "src/misc/code-theme.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [自定义代码主题])\n"
  },
  {
    "path": "src/misc/font-setting.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [字体设置])\n\n#let table-lnk(name, ref, it, scope: (:), res: none, ..args) = (\n  align(center + horizon, link(\"todo\", name)),\n  it,\n  align(\n    horizon,\n    {\n      set heading(bookmarked: false, outlined: false)\n      eval(it.text, mode: \"markup\", scope: scope)\n    },\n  ),\n)\n"
  },
  {
    "path": "src/misc/mod.typ",
    "content": "#import \"/src/book.typ\"\n#import \"/typ/templates/page.typ\"\n\n#import \"../mod.typ\": code, exec-code\n"
  },
  {
    "path": "src/misc/text-processing.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [读取外部文件和文本处理])\n"
  },
  {
    "path": "src/mod.typ",
    "content": "#import \"/src/book.typ\"\n#import \"/typ/templates/page.typ\"\n#import \"/typ/templates/term.typ\": _term\n#import \"/typ/templates/side-notes.typ\": side-note, side-attrs\n#import \"/typ/templates/page.typ\": main-color, get-page-width, plain-text\n#import \"/typ/templates/template-link.typ\": enable-heading-hash\n\n#import \"/typ/typst-meta/docs.typ\": typst-v11\n\n#let refs = {\n  let cl = book.cross-link\n  (\n    writing-markup: cl.with(\"/basic/writing-markup.typ\"),\n    writing-scripting: cl.with(\"/basic/writing-scripting.typ\"),\n    scripting-base: cl.with(\"/basic/scripting-literal-and-variable.typ\"),\n    scripting-complex: cl.with(\"/basic/scripting-block-and-expression.typ\"),\n    scripting-modules: cl.with(\"/intermediate/scripting-modules.typ\"),\n    content-scope-style: cl.with(\"/intermediate/content-scope-and-style.typ\"),\n    content-stateful: cl.with(\"/intermediate/content-stateful.typ\"),\n    ref-typebase: cl.with(\"/basic/reference-typebase.typ\"),\n    ref-type-builtin: cl.with(\"/basic/reference-type-builtin.typ\"),\n    ref-color: cl.with(\"/basic/reference-color.typ\"),\n    ref-visualization: cl.with(\"/basic/reference-visualization.typ\"),\n    ref-bibliography: cl.with(\"/basic/reference-bibliography.typ\"),\n    ref-datetime: cl.with(\"/basic/reference-date.typ\"),\n    ref-math-mode: cl.with(\"/basic/reference-math-mode.typ\"),\n    ref-math-symbols: cl.with(\"/basic/reference-math-symbols.typ\"),\n    ref-data-process: cl.with(\"/basic/reference-data-process.typ\"),\n    ref-wasm-plugin: cl.with(\"/basic/reference-wasm-plugin.typ\"),\n    ref-grammar: cl.with(\"/basic/reference-grammar.typ\"),\n    ref-layout: cl.with(\"/intermediate/reference-layout.typ\"),\n    ref-length: cl.with(\"/intermediate/reference-length.typ\"),\n    misc-font-setting: cl.with(\"/misc/font-setting.typ\"),\n  )\n}\n\n#let term-list = (\n  \"array literal\": \"数组字面量\",\n  \"blocky raw block\": \"块代码片段\",\n  \"boolean literal\": \"布尔字面量\",\n  \"code mode\": \"脚本模式\",\n  \"codepoint\": \"码位\",\n  \"codepoint width\": \"码位宽度\",\n  \"character\": \"字符\",\n  \"comment\": \"注释\",\n  \"content\": \"内容\",\n  \"consistency\": \"一致性\",\n  \"content block\": \"内容块\",\n  \"delimiter\": \"定界符\",\n  \"dictionary literal\": \"字典字面量\",\n  \"floating-point literal\": \"浮点数字面量\",\n  \"function identifier\": \"函数名标识符\",\n  \"emphasis semantics\": \"强调语义\",\n  \"escape sequences\": \"转义序列\",\n  \"exponential notation\": \"指数表示法\",\n  \"expression\": \"表达式\",\n  \"field\": \"域成员\",\n  \"field access\": \"访问域成员\",\n  \"function body expression\": \"函数体表达式\",\n  // https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String\n  \"grapheme cluster\": \"字素簇\",\n  \"grapheme cluster width\": \"字素簇宽度\",\n  \"initialization expression\": \"初始值表达式\",\n  \"integer literal\": \"整数字面量\",\n  \"interpret\": \"解释\",\n  \"interpreter\": \"解释器\",\n  \"interpreting mode\": \"解释模式\",\n  \"introspection function\": \"自省函数\",\n  \"lexicographical order\": \"字典序\",\n  \"line break\": \"换行符\",\n  \"literal\": \"字面量\",\n  \"markup mode\": \"标记模式\",\n  \"math mode\": \"数学模式\",\n  \"none literal\": \"空字面量\",\n  \"nearest\": \"最近\",\n  \"raw block\": \"代码片段\",\n  \"representation\": \"表示法\",\n  \"parameter identifier\": \"参数标识符\",\n  \"parser\": \"解析器\",\n  \"pattern\": \"模式串\",\n  \"placeholder\": \"占位符\",\n  \"shorthand\": \"速记符号\",\n  \"scripting\": \"脚本\",\n  \"string literal\": \"字符串字面量\",\n  \"strong semantics\": \"着重语义\",\n  \"syntactic predecessor\": \"语法前驱\",\n  \"tilde diacritical marks\": \"波浪变音符号\",\n  \"type\": \"类型\",\n  \"utf-8 encoding\": \"UTF-8编码\",\n  \"value\": \"值\",\n  \"variable identifier\": \"变量名标识符\",\n  \"whitespace\": \"空白字符\",\n)\n\n#let mark-list = (\n  \"=\": \"等于号\",\n  \"*\": \"星号\",\n  \"#\": \"井号\",\n  \"_\": \"下划线\",\n  \"`\": \"反引号\",\n  \"hyphen\": \"连字号\",\n  \"-\": \"减号\",\n  \"+\": \"加号\",\n  \"\\\\\": \"反斜杠\",\n  \"/\": \"斜杠\",\n  \"~\": \"波浪号\",\n  \".\": \"点号\",\n  \";\": \"分号\",\n)\n\n#let exec-code(cc, res: none, scope: (:), eval: eval) = {\n  rect(\n    width: 100%,\n    inset: 10pt,\n    context {\n      // Don't corrupt normal headings\n      set heading(outlined: false)\n\n      let prev = enable-heading-hash.get()\n      enable-heading-hash.update(false)\n\n      if res != none {\n        res\n      } else {\n        eval(cc.text, mode: \"markup\", scope: scope)\n      }\n\n      enable-heading-hash.update(prev)\n    },\n  )\n}\n\n#let _code-al = if get-page-width() >= 500pt {\n  left\n} else {\n  top\n}\n\n// al: alignment\n#let code(cc, code-as: none, res: none, scope: (:), eval: eval, exec-code: exec-code, al: _code-al) = {\n  let code-as = if code-as == none {\n    cc\n  } else {\n    code-as\n  }\n\n  let vv = exec-code(cc, res: res, scope: scope, eval: eval)\n  if al == left {\n    layout(lw => context {\n      let width = lw.width * 0.5 - 0.5em\n      let u = box(width: width, code-as)\n      let v = box(width: width, vv)\n\n      let u-box = measure(u)\n      let v-box = measure(v)\n\n      let height = calc.max(u-box.height, v-box.height)\n      stack(\n        dir: ltr,\n        {\n          set rect(height: height)\n          u\n        },\n        1em,\n        {\n          rect(height: height, width: width, inset: 10pt, vv.body)\n        },\n      )\n    })\n  } else {\n    code-as\n    vv\n  }\n}\n\n#let fg-blue = main-color.mix(rgb(\"#0074d9\"))\n#let pro-tip(content) = context {\n  let attr = side-attrs.get()\n  let ext = attr.width + attr.gutter\n  move(\n    dx: -ext,\n    block(\n      width: 100% + ext,\n      breakable: false,\n      inset: (x: 0.65em, y: 0.65em, left: 0.65em * 0.6),\n      radius: 4pt,\n      fill: rgb(\"#0074d920\"),\n      {\n        set text(fill: fg-blue)\n        stack(\n          dir: ltr,\n          move(dy: 0.1em, image(\"/assets/files/info-icon.svg\", width: 1em)),\n          0.2em,\n          box(width: 100% - 1.2em, v(0.2em) + content),\n        )\n      },\n    ),\n  )\n}\n\n#let fg-red = main-color.mix(red)\n#let todo-color = fg-red\n#let todo-box(content) = context {\n  let attr = side-attrs.get()\n  let ext = attr.width + attr.gutter\n  move(\n    dx: -ext,\n    block(\n      width: 100% + ext,\n      breakable: false,\n      inset: (x: 0.65em, y: 0.65em, left: 0.65em * 0.6),\n      radius: 4pt,\n      fill: fg-red.transparentize(80%),\n      {\n        set text(fill: fg-red)\n        stack(\n          dir: ltr,\n          move(dy: 0.4em, text(size: 0.5em)[todo]),\n          0.2em,\n          box(width: 100% - 1.2em, v(0.2em) + content),\n        )\n      },\n    ),\n  )\n}\n\n/// This function is to render a text string in monospace style and function\n/// color in your defining themes.\n///\n/// ## Examples\n///\n/// ```typc\n/// typst-func(\"list.item\")\n/// ```\n///\n/// Note: it doesn't check whether input is a valid function identifier or path.\n#let typst-func(it) = [\n  #raw(plain-text(it) + \"()\", lang: \"typc\") <typst-raw-func>\n]\n\n#let show-answer = false\n// #let show-answer = true\n\n/// Make an exercise item.\n///\n/// - question (content): The question to ask.\n/// - answer (content): The answer to the question.\n/// -> content\n#let exercise(question, answer) = {\n  enum.item(\n    question\n      + if show-answer {\n        parbreak() + [答：] + answer\n      },\n  )\n}\n\n/// Make a term item.\n///\n/// - term (string): The name of the term.\n/// - postfix (string): The postfix to conform typst bug.\n/// - en (bool): Whether to show the English name.\n/// -> content\n#let term(term, en: none) = _term(term-list, term, en: en)\n\n#let mark(mark) = _term(\n  mark-list,\n  mark,\n  en: if mark == \"hyphen\" {\n    raw(\"-\")\n  } else {\n    raw(mark)\n  },\n)\n\n#let ref-bookmark = side-note\n\n#let highlighter(it, k) = {\n  if k == \"method\" {\n    set text(fill: rgb(\"4b69c6\"))\n    raw(it)\n  } else if k == \"keyword\" {\n    set text(fill: rgb(\"8b41b1\"))\n    raw(it)\n  } else if k == \"var\" {\n    set text(fill: blue.mix(main-color))\n    raw(it)\n  } else {\n    raw(it)\n  }\n}\n\n#let darkify(clr) = clr.mix(main-color.negate()).saturate(30%)\n\n#let ref-ty-locs = (\n  \"int\": refs.ref-typebase,\n  \"bool\": refs.ref-typebase,\n  \"float\": refs.ref-typebase,\n  \"str\": refs.ref-typebase,\n  \"array\": refs.ref-typebase,\n  \"dict\": refs.ref-typebase,\n  \"none\": refs.ref-typebase,\n  \"ratio\": refs.ref-length,\n  \"alignment\": refs.ref-layout,\n  \"version\": refs.ref-type-builtin,\n  \"any\": refs.ref-type-builtin,\n  \"bytes\": refs.ref-type-builtin,\n  \"label\": refs.ref-type-builtin,\n  \"type\": refs.ref-type-builtin,\n  \"regex\": refs.ref-type-builtin,\n)\n\n#let show-type(ty) = {\n  h(3pt)\n  ref-ty-locs.at(ty)(\n    reference: label(\"reference-type-\" + ty),\n    box(fill: darkify(rgb(\"eff0f3\")), outset: 2pt, radius: 2pt, raw(ty)),\n  )\n  h(3pt)\n}\n\n#let ref-signature(name, kind: \"scope\") = {\n  let fn = if kind == \"scope\" {\n    typst-v11.scoped-items.at(name)\n  } else if kind == \"cons\" {\n    let ty = typst-v11.types.at(name)\n    ty.body.content.constructor\n  } else {\n    typst-v11.funcs.at(name)\n  }\n\n  context {\n    let attr = side-attrs.get()\n    let ext = attr.width + attr.gutter\n\n    move(\n      dx: -ext,\n      block(\n        fill: rgb(\"#add5a220\"),\n        radius: 2pt,\n        width: 100% + ext,\n        inset: (x: 1pt, y: 5pt),\n        {\n          set par(justify: false)\n          set text(fill: main-color.mix(rgb(\"eff0f3\").negate()))\n          highlighter(\"fn\", \"keyword\")\n          raw(\" \")\n          highlighter(name, \"method\")\n          raw(\"(\")\n          fn\n            .params\n            .map(param => {\n              highlighter(param.name, \"var\")\n              \": \"\n              param.types.map(show-type).join()\n            })\n            .join(raw(\", \"))\n          raw(\")\")\n          if fn.returns.len() > 0 {\n            raw(\" \")\n            box(raw(\"->\"))\n            raw(\" \")\n            fn.returns.map(show-type).join()\n          }\n        },\n      ),\n    )\n  }\n}\n#let ref-func-signature = ref-signature.with(kind: \"func\")\n#let ref-cons-signature = ref-signature.with(kind: \"cons\")\n#let ref-method-signature = ref-signature\n\n#let f() = { }\n"
  },
  {
    "path": "src/prefaces/acknowledgement.typ",
    "content": "\n#import \"mod.typ\": *\n\n#show: book.page.with(title: \"致谢\")\n\n#let MyriadDreamin = link(\"https://github.com/Myriad-Dreamin\")[Myriad Dreamin]\n#let OrangeX4 = link(\"https://github.com/OrangeX4\")[OrangeX4]\n#let GiggleDing = link(\"https://github.com/GiggleDing\")[GiggleDing]\n\n本文档的主要部分由#MyriadDreamin 编写。\n\n#OrangeX4 参与了基本教程部分的勘误。\n\n#GiggleDing 编写了《参考：常用数学符号》。\n\n在此表示感谢。\n"
  },
  {
    "path": "src/prefaces/license.typ",
    "content": "\n#import \"mod.typ\": *\n#page({\n  set text(size: 10.5pt)\n  set block(spacing: 1.5em)\n\n  show \"TERMS AND CONDITIONS FOR USE\": it => {\n    v(0.5em)\n    it\n  }\n  show \"APPENDIX\": it => {\n    v(1fr)\n    it\n  }\n  eval(read(\"/LICENSE\"), mode: \"markup\")\n  v(5em)\n})\n"
  },
  {
    "path": "src/prefaces/mod.typ",
    "content": "#import \"/src/book.typ\"\n\n#import \"../mod.typ\": code, exec-code\n"
  },
  {
    "path": "src/science/algorithm.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [伪算法])\n"
  },
  {
    "path": "src/science/chemical.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [化学方程式])\n"
  },
  {
    "path": "src/science/mod.typ",
    "content": "#import \"/src/book.typ\"\n#import \"/typ/templates/page.typ\"\n\n#import \"../mod.typ\": code, exec-code\n"
  },
  {
    "path": "src/science/theorem.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [定理环境])\n"
  },
  {
    "path": "src/template/book.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [书籍模板])\n"
  },
  {
    "path": "src/template/mod.typ",
    "content": "#import \"/src/book.typ\"\n#import \"/typ/templates/page.typ\"\n\n#import \"../mod.typ\": code, exec-code\n"
  },
  {
    "path": "src/template/paper.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [论文模板])\n"
  },
  {
    "path": "src/template/slides.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [演示文稿（PPT）])\n"
  },
  {
    "path": "src/topics/call-externals.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [在Typst内执行Js、Python、Typst等])\n"
  },
  {
    "path": "src/topics/mod.typ",
    "content": "#import \"/src/book.typ\"\n#import \"/typ/templates/page.typ\"\n\n#import \"../mod.typ\": code, exec-code, refs\n"
  },
  {
    "path": "src/topics/template-book.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [制作一个书籍模板])\n"
  },
  {
    "path": "src/topics/template-cv.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [制作一个CV模板])\n"
  },
  {
    "path": "src/topics/template-paper.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [制作一个IEEE模板])\n"
  },
  {
    "path": "src/topics/writing-component-lib.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [制作一个组件库])\n"
  },
  {
    "path": "src/topics/writing-math.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [编写一篇数学文档])\n"
  },
  {
    "path": "src/topics/writing-plugin-lib.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [制作一个外部插件])\n"
  },
  {
    "path": "src/tutorial/doc-modulize.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [模块化（多文件）])\n\n我们接下来学习脚本剩余的所有知识。总结而言，每个源文件对应一个模块；每个模块导出多个「变量声明」和一个「文档（内容）树」。回忆编译流程，求值按照「控制流」顺序执行表达式。「排版」（typeset）时，按照「文档树」顺序更新状态和排版结果。迭代排版多次，直到布局不再发生变化。\n\n本节首先来讲讲Typst中的模块。正如我们在《教程：脚本模式》中所说的，Typst提供了脚本语言方便排版。但事实上，写作时若能少写甚至不写脚本，这才算真正的便捷。我们总希望Typst能够允许我们以一种优雅的方式#strike[复制粘贴]引入已有代码。理想情况下，只需两行代码便可引入前辈写好的模板：\n\n#```typ\n#import \"@preview/senpai-no-awesome-template.typ:0.x.x\": *\n#show: template.with(..)\n```\n\n模块便是为复制而生。\n\n\nTypst的模块（module）机制非常简单：每个文件都是一个独立的模块。它允许你：\n+ 将一份文档分作多个文件编写。\n+ 使用本机器或网上的模板或库。\n\n由于Typst的模块机制过于简单，本节主要伴随讲解一些日常使用模块机制时所遇到的问题，而不会涉及很多需要理解的地方。\n\n== 根目录\n\n根目录是一个重要的编译参数。Typst早在v0.1.0时就禁止访问*根目录以外的内容*，以保证安全执行代码。最坏情况下，*其他人编写的Typst脚本（来自互联网）*可以访问根目录下所有的文件。\n\n你所使用的工具可能会为你自动配置*根目录*。\n\n#pro-tip[\n  + 当你使用`typst-cli`程序时，根目录默认为文档所在目录。你也可以通过`--root`命令行参数手动指定一个根目录。例如你可以指定当前工作目录的父文件夹作为编译程序的根目录：\n\n    ```bash\n    typst c --root ..\n    ```\n  + 当你使用VSCode的tinymist程序时，为了方便多文件的编写，根目录默认为你所打开的文件夹。如果你打开一个VSCode工作区，则根目录相对于离你所编辑文件最近的工作目录。\n]\n\n一个常见的错误做法是，为了共享本地代码或模板，在使用`typst-cli`的时候将根目录指定为系统的根目录。以下使用方法将有可能导致个人隐私泄露：\n\n```bash\ntypst c --root \"C:\\\\\" # Windows\ntypst c --root / # Linux或macOS\n```\n\n并访问你系统中的某处文件。\n\n```typ\n#import \"/Users/me/templates/book.typ\": book-template\n```\n\n本节将介绍另外一种创建本地「库」（package）的方式以在Typst项目之间安全地共享代码和资源。\n\n== 绝对路径与相对路径\n\n我们已经讲解过`read`函数和`include`语法，其中都有路径的概念。\n\n如果路径以字符`/`开头，则其为「绝对路径」。绝对路径相对于「根目录」解析。若设置了根目录为#text(red, `/OwO/`)，则路径`/src/chapter1.typ`对应文件系统中的#text(red, `/OwO/`)`src/chapter1.typ`。\n\n#code(\n  ```typ\n  #include \"/src/chapter1.typ\"\n  ```,\n  res: [我是#text(red, `/OwO/`)`src/chapter1.typ`文件！],\n)\n\n否则，路径不以字符`/`开头，则其「相对路径」。相对路径相对于当前文件的父文件夹解析。若我们正在编辑#text(red, `/OwO/`)#text(blue, `src/`)`main.typ`文件，则路径`chapter2.typ`对应文件系统中的#text(red, `/OwO/`)#text(blue, `src/`)`chapter2.typ`。\n\n#code(\n  ```typ\n  #include \"chapter2.typ\"\n  ```,\n  res: [我是#text(red, `/OwO/`)#text(blue, `src/`)`chapter2.typ`文件！],\n)\n\n== 「import」语法与「模块」\n\nTypst中的模块概念相当简单。你只需记住：每个文件都是一个模块。\n\n#let m1-code = raw(read(\"packages/m1.typ\").trim().replace(\"\\r\", \"\"), lang: \"typ\", block: true)\n\n假设有一个文件位于`packages/m1.typ`：\n\n#m1-code\n\n此时文件名就是所引入模块的名称，那么`packages/`#text(red, `m1`)`.typ`就对应#text(red, `m1`)模块。\n\n你可以简单通过绝对路径或相对路径引入一个文件模块：\n\n#code(```typ\n#import \"packages/m1.typ\"\n#repr(m1)\n```)\n\n你可以通过这个「模块」对象访问到文件顶层作用域的变量或函数。例如在`m1`文件中，你可以访问到其顶层作用域中的`add`或`sub`函数。\n\n#code(```typ\n#import \"packages/m1.typ\"\n#m1.add \\\n#m1.sub\n```)\n\n你可以在引入模块名的同时将其更名为你想要的名称：\n\n#code(```typ\n#{\n  import \"packages/m1.typ\" as m2\n  repr(m2)\n} \\\n// 等价于\n#{\n  import \"packages/m1.typ\"\n  let m2 = m1\n  repr(m2)\n}\n```)\n\n你可以在冒号后添加一个星号表示直接引入模块中所有的函数：\n\n#code(```typ\n// 引入所有函数\n#import \"packages/m1.typ\": *\n#type(add), #type(sub)\n```)\n\n你也可以在冒号后追加一个逗号分隔的名称列表，部分引入来自其他文件的变量或函数：\n\n#code(```typ\n// 仅仅引入`sub`函数\n#import \"packages/m1.typ\": sub\n#type(sub) \\\n// 引入`add`和`sub`函数\n#import \"packages/m1.typ\": add, sub\n#type(add), #type(sub) \\\n```)\n\n注意，本地的变量声明与`import`进来的变量声明都会覆盖Typst内置的变量声明。例如Typst内置的`sub`函数实际上为取下标函数。你可以在引入外部变量的同时更名以避免可能的名称冲突：\n\n#code(```typ\n1#sub[2]\n#import \"packages/m1.typ\": sub as subtract\n#repr(subtract(10, 1))#sub[3]\n```)\n\n== 「include」语法与「import」语法的关系\n\n在Typst内部，当解析完一个文件时，文件将被分为顶层作用域中的「内容」和「变量声明」，共同组成一个文件模块。`include`取其「内容」，`import`则取其「变量声明」。\n\n仍然以前文中的`packages/m1.typ`为例：\n\n其「内容」是除了「变量声明」以外的文件内容，连接起来为：\n\n```typ\nXXX\n// #let add(x, y) = x + y\nYYY\n// #let sub = ..\n```\n\n其导出所有在文件顶层作用域中的「变量声明」：\n\n```typ\n// XXX\n#let add(x, y) = x + y\n// YYY\n#let sub(x, y) = x - y\n```\n\n因此`include`和`import`分别得到以下结果。\n\n使用`include`得到：\n\n#code(```typ\n#repr(include \"packages/m1.typ\")\n```)\n\n使用`import`得到：\n\n#code(```typ\n#import \"packages/m1.typ\"\n#repr(m1.add), #repr(m1.sub)\n```)\n\n你可以同时使用`include`和`import`获得同一个文件的内容和变量声明。\n\n== 控制变量导出的三种方式\n\n有的时候你不希望将一些变量暴露出去，这个时候你可以让这些变量的名称以「下划线」（`_`）开头：\n\n```typ\n#let _factor = 1;\n#let add2(x, y) = _factor * (x + y)\n```\n\n这样`import`的时候就不会轻易访问到这些变量。\n\n还有一种方法是在非顶层作用域中构造闭包，这样`import`中就不会包含`factor`，因为`factor`不在顶层：\n\n```typ\n#let add2 = {\n  let factor = 1;\n  (x, y) => factor * (x + y)\n}\n```\n\n值得注意的是，`import`进来的变量也可以被重新导出，只要他们也在顶层作用域。这允许你以更优雅的第三种方式为使用者屏蔽内部变量，例如你可以在子文件夹中完成实现并重新导出：\n\n```typ\n// 仅从packages/m1/add.typ文件中重新导出`add`函数。\n#import \"m1/add.typ\": add\n// 重新导出packages/m1/sub.typ文件中所有的函数\n#import \"m1/sub.typ\": *\n```\n\n== 使用外部库\n\n\n除此之外，可以从特殊的路径从网络导入外部「模块」，即外部「库」（packages）。Typst的外部库机制可能不是世界上最精简的，但是我所见过当中最精简的。它可能更稍显简陋，但已经有上百个外部库通过官方渠道发布，并足以满足我们日常生活的使用。\n\n你只需要`import`特定的路径就能访问其内部的变量声明。例如，导入一个用于绘画自动机的外部库：\n\n#code(```typ\n#import \"@preview/fletcher:0.5.7\" as fletcher: node, edge\n\n#align(center, fletcher.diagram(\n  node((0, 0), $S_1$),\n  node((1, 0), $S_2$),\n\n  edge((1, 0), (0, 0), $g$, \"..}>\", bend: 25deg),\n  edge((0, 0), (1, 0), $f$, \"..}>\", bend: 25deg),\n))\n```)\n\n其中，以下一行代码完成了导入外部库的所有工作。我们注意到其完全为`import`语法，唯一陌生的是其中的路径格式：\n\n```typ\n#import \"@preview/fletcher:0.5.7\" as fletcher: node, edge\n```\n\n解读路径#text(red, `@preview`)`/`#text(eastern, `fletcher`)`:`#text(orange, `0.5.7`)的格式，它由三部分组成。\n\n=== 「命名空间」，#text(red, `@preview`)\n\n以`@`字符开头。目前仅允许使用两个命名空间：\n- #text(red, `@preview`)：Typst仅开放`beta`版本的包管理机制。所有`beta`版本的都在`preview`命名空间下。\n- #text(red, `@local`)：Typst建议的本地库命名空间。\n\n=== 「库名」，#text(eastern, `fletcher`)\n\n必须符合变量命名规范：\n+ 以ASCII英文字符（`a-z`或`A-Z`）开头；\n+ 跟随任意多个ASCII英文或数字字符（`a-z`或`A-Z`或`0-9`）或下划线（`_`）或中划线（`-`）。\n\n例如`OvO`、`O_O`、`O-O`都是合法的库名。但是`0v0`不是合法库名，因为其以数字零开头。\n\n=== 「版本号」，#text(orange, `0.5.7`)\n\n必须符合#link(\"https://semver.org/\")[SemVer]规范。\n\n如果你不能很好地阅读和理解#link(\"https://semver.org/\")[SemVer]规范，仅记住合法的版本号由三个递增的数字组成，并用「点号」（`.`）分隔；版本号之间可以相互比较，且比较版本时按顺序比较数字。例如`0.0.0`、`0.10.0`、`1.5.11`、`1.24.1`是合法且递增的版本号。\n+ #text(red, `1`)`.0.11`比#text(red, `0`)`.10.0`大，因为#text(red, `1`)大于#text(red, `0`)；\n+ `1.`#text(red, `24`)`.1`比`1.`#text(red, `5`)`.11`大，因为#text(red, `24`)大于#text(red, `5`)；\n+ `1.24.0`与`1.24.0`相等。\n\n== 下载外部库\n\n一般来说，使用外部库与导入本地模块一样简单。当你尝试导入一个外部库时，Typst会立即启动下载线程为你从网络下载外部库代码：\n\n```typ\n#import \"@preview/fletcher:0.5.7\" as fletcher: node, edge\n```\n\n一般情况下，从网络下载外部库的时间不会超过十秒钟，并且不需要任何额外配置。但由于不可描述的原因，你有可能需要为下载线程配置代理。当你希望通过网络代理下载外部库时，请检查全局环境变量`HTTP_PROXY`和`HTTPS_PROXY`是否成功设置。\n+ 如果你的网络代理软件包含环境变量设置，请*优先*在你的网络代理环境中配置。\n+ 否则如果你是Linux用户，请检查启动`typst-cli`或`vscode`的shell中，使用`echo $HTTP_PROXY`检查是否包含该环境变量。\n+ 否则如果你是Windows用户，请使用`Win + R`快捷键呼出运行窗口，执行`system`#sym.zws`properties`#sym.zws`advanced`，调出「环境变量」窗口，并检查是否包含该环境变量。\n  #figure(align(center, image(\"./systempropertiesadvanced.png\", width: 50%)))\n\n== 库文件夹\n\n上一小节中的「命名空间」对应本地的一个或多个文件夹。\n\n首先，Typst会感知你系统上的“数据文件夹”和“缓存文件夹”并在其中存储库代码。\n\nTypst将会检查`{data-dir}/`#text(green, `typst/packages`)中是否包含相应的库，*随后*会在`{cache-dir}/`#text(green, `typst/packages`)中检查和缓存从网络下载的库代码。其中数据文件夹（`{data-dir}`）和缓存文件夹（`{cache-dir}`）在不同的操作系统上：\n\n#{\n  set align(center)\n  table(\n    columns: 3,\n    [操作系统], [数据文件夹], [缓存文件夹],\n    [Linux], [`$XDG_DATA_HOME`或`~/.local/share`], [`$XDG_CACHE_HOME`或`~/.cache`],\n    [macOS], [`~/Library/Application Support`], [`~/Library/Caches`],\n    [Windows], [`%APPDATA%`], [`%LOCALAPPDATA%`],\n  )\n}\n\n\n#let breakable-path(..contents) = contents.pos().join([#sym.zws`/`#sym.zws])\n\n例如，当引入外部库#breakable-path(\n  text(red, `@preview`),\n  [#text(eastern, `fletcher`)`:`#text(orange, `0.5.7`)],\n)时，Typst会严格*按顺序*检查并解析路径：数据文件夹中的#breakable-path(\n  `{data-dir}`,\n  text(green, `typst/packages`),\n  text(red, `preview`),\n  text(eastern, `fletcher`),\n  text(orange, `0.5.7`),\n)和缓存文件夹中的#breakable-path(\n  `{cache-dir}`,\n  text(green, `typst/packages`),\n  text(red, `preview`),\n  text(eastern, `fletcher`),\n  text(orange, `0.5.7`),\n)。Typst会将库路径映射到你数据文件夹或缓存文件夹，优先按顺序使用已经存在的库代码，并按需从网络下载外部库。\n\n这意味着你可以拥有以下几条特性。\n\n- 若你已经下载了网络库到本地，再次访问将不会再产生网络请求和检查远程库代码的状态。\n- 你可以将本地库存储到数据文件夹。特别地，你可以在数据文件夹中的`preview`文件夹中存储库，以*覆盖*缓存文件夹中已经下载的库。这有利于你调试或临时使用即将发布的外部库。\n- 你可以在本地随意创建新的命名空间，对于#breakable-path(\n    text(red, `@my-namespace`),\n    [#text(eastern, `my-package`)`:`#text(orange, `version`)],\n  )，Typst将检查并使用你位于#breakable-path(\n    `{data-dir}`,\n    text(green, `typst/packages`),\n    text(red, `my-namespace`),\n    text(eastern, `my-package`),\n    text(orange, `version`),\n  )的库代码。尽管Typst对命名空间没有限制，但建议你使用 #text(red, `@local`)作为所有本地库的命名空间。\n\n== 构建和注册本地库\n\n本小节教你如何构建和注册本地库。\n\n=== 元数据文件\n\n在库的*根目录*下必须有一个名称为`typst.toml`的元数据文件，这是对代码库的描述。内容示例如下：\n\n```toml\n[package]\nname = \"example\"\nversion = \"0.1.0\"\nentrypoint = \"lib.typ\"\n```\n\n最低限度你仅需要填入以上三个字段，分别对应其名字、版本号和入口文件的路径。\n\n=== 入口文件\n\n一般来说，该文件对应为库的根目录下的`lib.typ`文件。你可以使用本节提及的《变量导出的三种方式》编写该文件。当导入该库时，其中的「变量声明」将提供给文档使用。\n\n=== 示例库的文件组织\n\n假设你希望构建一个`@local/example:0.1.0`的外部库供本地使用。你应该将`example`库的文件夹*拷贝或链接*到#breakable-path(\n  `{data-dir}`,\n  text(green, `typst/packages`),\n  text(red, `local`),\n  text(eastern, `example`),\n  text(orange, `0.1.0`),\n)路径。\n\n创建#breakable-path(\n  `{data-dir}`,\n  text(green, `typst/packages`),\n  text(red, `local`),\n  text(eastern, `example`),\n  text(orange, `0.1.0`),\n  `typst.toml`,\n)文件，并包含前文所述内容。\n\n创建#breakable-path(\n  `{data-dir}`,\n  text(green, `typst/packages`),\n  text(red, `local`),\n  text(eastern, `example`),\n  text(orange, `0.1.0`),\n  `lib.typ`,\n)文件，并包含以下内容：\n\n```typ\n#import \"add.typ\": add\n#import \"sub.typ\": sub\n```\n\n这样你就可以在本地的任意文档中使用库中的`add`或`sub`函数了：\n\n```typ\n#import \"@local/example:0.1.0\" as example: add\n```\n\n== 发布库到官方源\n\n略\n\n== 再谈「根目录」\n\n出于安全考虑，每个库都*默认*只能访问其专属的「根目录」，即`typst.toml`文件所在的目录。这意味着，库中的绝对路径或相对路径依其专属的「根目录」解析。请回忆《绝对路径与相对路径》小节内容。例如在文件夹`@local/example:0.1.0`的内部，设使内部文件#breakable-path(\n  `{data-dir}`,\n  text(eastern, `{example-lib}`),\n  `src/add-simd.typ`,\n)中包含这样的代码。\n\n```typ\n#read(\"/typst.toml\")\n#read(\"../typst.toml\")\n```\n\n则「根目录」被解析为#breakable-path(\n  `{data-dir}`,\n  text(eastern, `{example-lib}`),\n)，绝对路径`/typst.toml`被解析为#breakable-path(\n  `{data-dir}`,\n  text(eastern, `{example-lib}`),\n  `typst.toml`,\n)，相对路径`../typst.toml`被解析为#breakable-path(\n  `{data-dir}`,\n  text(eastern, `{example-lib}`),\n  `src`,\n  `..`,\n  `typst.toml`,\n)\n\n== 函数与闭包中的路径解析\n\n这里有一个微妙的问题。在函数或闭包中请求解析一个绝对路径或相对路径，Typst会如何做？答案是，任何路径解析都依附于路径解析代码所在文件。这句话有些晦涩，但例子却容易懂。这里举两个例子。\n\n假设函数在文档相对于根目录的#breakable-path(text(eastern, `{example-lib}`), `src`, `m1.typ`)中有这样一个函数：\n\n```typ\n#let parse-code(path) = parse-text(read(path))\n```\n\n那么无论我们在哪个文件引入了`parse-code`函数，其`read(path)`的路径解析都是固定的，其绝对路径相对于文档根目录，其相对路径相对于#breakable-path(\n  text(eastern, `{example-lib}`),\n  `src`,\n  `m1.typ`,\n)所在目录。例如调用`parse-code(\"../def.typ\")`时，其始终读取#breakable-path(\n  text(eastern, `{example-lib}`),\n  `src/../def.typ`,\n)文件。\n\n我们上一小节说过，每个库都*默认*只能访问其专属的「根目录」。这种行为在有的时候不是你期望的，但你可以同样通过传递一个来自文档的闭包绕过该限制。例如，`parse-code`改写为：\n\n```typ\n#let parse-code(path, read-file: read) = parse-text(read-file(path))\n```\n\n并且在调用时传入一个读取文件的「回调函数」\n\n```typ\n#parse-code(\"../def.typ\", read-file: (p) => read(p))\n```\n\n== 多文件文档\n\n\n尽管本书提倡你尽可能将所有文档内容放在单个文件中，本书给出构建一个多文件文档的合理方案。\n- 工作区中包含多个主文件\n- 每个主文件可以`include`多个子文件\n\n```\ntyp/packages\n└── util.typ\ntyp/templates\n└── book-template.typ\ndocuments/\n└── my-book/\n    ├── main.typ\n    ├── mod.typ\n    ├── part1/\n    │   ├── mod.typ\n    │   └── chap1.typ\n    └── part2/\n        ├── mod.typ\n        └── chapN.typ\n```\n\n=== 工作区内的模板与库\n\n使用绝对路径方便引入工作区内的模板与库：\n\n```typ\n#import \"/typ/templates/ebook.typ\": project as ebook\n```\n\n=== 主文件和主库文件\n\n`main.typ`文件中仅仅包含模板配置与`include`多个子文件。\n\n```typ\n// 在mod.typ文件中：\n// #import \"/typ/templates/ebook.typ\": project as ebook\n#import \"mod.typ\": *\n\n#show: ebook.with(title: \"My Book\")\n\n#include \"part1/chap1.typ\"\n#include \"part2/chap2.typ\"\n#include \"part2/chap3.typ\"\n```\n\n对于每个子文件，你可以都`import`一个主库文件`mod.typ`，以减少冗余。例如在`documents/my-book/part1/chap1.typ`中：\n\n```typ\n#import \"mod.typ\": *\n```\n\n=== 重导出文件\n\n示例文件结构中的`mod.typ`与编写库时的`lib.typ`很类似，都重新导出了大量函数。例如当你希望同时在`chap2.typ`和`chap3.typ`中使用相似的函数时，你可以在`mod.typ`中原地实现该函数或者从外部库中重导出对应函数：\n\n```typ\n#import \"@preview/example:0.1.0\": add\n// 或者原地实现`add`\n#let add(x, y) = x + y\n```\n\n这时你可以同时在`chap2.typ`和`chap3.typ`中直接使用该`add`函数。\n\n=== 依赖管理\n\n你可以不在`main.typ`或者`chap{N}.typ`文件中直接引入外部库，而在`my-book/mod.typ`中引入外部库。由于级联的`mod.typ`，相关函数会传递给每个`main.typ`或者`chap{N}.typ`使用。\n"
  },
  {
    "path": "src/tutorial/doc-stateful.typ",
    "content": "#import \"mod.typ\": *\n\n#import \"/typ/embedded-typst/lib.typ\": default-cjk-fonts, default-fonts, svg-doc\n\n#show: book.page.with(title: [状态化])\n\n#todo-box[本节处于校对阶段，所以可能存在不完整或错误。]\n\n在上一节中我们理解了作用域，也知道如何简单把「`show`」规则应用于文档中的部分内容。\n\n它看起来似乎已经足够强大。但还有一种可能，Typst可以给你更强大的原语。\n\n我是说有一种可能，Typst对文档内容的理解至少是二维的。这二维，有一维可以比作空间，另一维可以比作时间。你可以从文档的任意位置，\n+ 空间维度（From Space to Space）：查询文档任意部分的状态（这里的内容和那里的内容）。\n+ 时间维度（From TimeLoc to TimeLoc）：查询脚本执行到文档任意位置的状态（过去的状态和未来的状态）。\n\n这里有一个示意图，红色线段表示Typst脚本的执行方向。最后我们形成了一个由S1到S4的“时间线”。\n\n你可以选择文档中的任意位置，例如你可以在文档的某个位置（蓝色弧线的起始位置），从该处开始查询文档过去某处或未来某处（蓝色弧线的终止位置）。\n\n// I mean, Typst maintains states with at least two dimensions. The one resembles a space dimension, and the other one resembles a time dimension. You can create a state that spans:\n\n// 1. From space to space: You can locate content at here and there by selectors.\n// 2. From time to time: You can query a state in past or future by locations.\n\n// The following figure shows how a state is arranged. First, Typst executes the document with scripting from S1 to S4, as #text(fill: red, \"red lines\") shown. Then, you can locate some content in the document (at the _start position_ of #text(fill: blue, \"blue arcs\")) and query the past state or future state (at the _end position_ of #text(fill: blue, \"blue arcs\")).\n\n#import \"../tutorial/figure-time-travel.typ\": figure-time-travel\n#align(center + horizon, figure-time-travel())\n\n「状态」基本上是教程中最难的部分。它涉及教程之前所有的知识。具体而言，我们需要理解透排版Ⅱ中的「编译流程」。整个「编译流程」中，排版引擎会储存一个「上下文」（context）状态。首先，「解析器」（Parser）将源代码字符串解析为待求值的「抽象语法树」（AST），接着「表达式求值」（Evaluation）阶段将「抽象语法树」转换为「内容」,然后「排版」（typeset）阶段将「内容」转换为布局好的结果。\n\n== 「`typeset`」阶段的迭代收敛\n\n一个容易值得思考的问题是，如果我在文档的开始位置调用了#typst-func(\"state.final\")方法，那么Typst要如何做才能把文档的最终状态返回给我呢？\n\n容易推测出，原来Typst并不会只对内容执行一遍「`typeset`」。仅考虑我们使用#typst-func(\"state.final\")方法的情况。初始情况下#typst-func(\"state.final\")方法会返回状态默认值，并完成一次布局。接下来的迭代，#typst-func(\"state.final\")方法会返回上一次迭代布局完成时的。直到布局的内容不再发生变化。#typst-func(\"state.at\")会导致相似的布局迭代，只不过情况更为复杂，这里便不再展开细节。\n\n所有对文档的查询都会导致布局的迭代：`query`函数可能会导致布局的迭代；`state.at`函数可能会导致布局的迭代；`state.final`函数一定会导致布局的迭代。\n\n// 延迟执行\n\n// This section mainly talks about `selector` and `state` step by step, to teach how to locate content, create and manipulate states.\n\n// 本节教你使用选择器（selector）定位到文档的任意部分；也教你创建与查询二维文档状态（state）。\n\n== 时间维度 -- 控制流\n\n== 空间维度 -- 文档（内容）树\n\n// == 回顾其一\n\n// 针对特定的`feat`和`refactor`文本，我们使用`emph`修饰：\n\n// #frames-cjk(\n//   read(\"./stateful/s2.typ\"),\n//   code-as: ```typ\n//   #show regex(\"feat|refactor\"): emph\n//   ```,\n// )\n\n// 对于三级标题，我们将中文文本用下划线标记，同时将特定文本替换成emoji：\n\n// #frames-cjk(\n//   read(\"./stateful/s3.typ\"),\n//   code-as: ```typ\n//   #let set-heading(content) = {\n//     show heading.where(level: 3): it => {\n//       show regex(\"[\\p{hani}\\s]+\"): underline\n//       it\n//     }\n//     show heading: it => {\n//       show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n//       show regex(\"FuwaFuwa\"): box(text(\"🪄\", size: 0.5em), baseline: -50%)\n//       it\n//     }\n\n//     content\n//   }\n//   #show: set-heading\n//   ```,\n// )\n\n== 任务描述\n\n为举例说明，本节讲解的程序是如何在Typst中设置标题样式。我们的目标是设置标题为内容的页眉：\n+ 如果当前页眉有二级标题，则是当前页面的第一个二级标题。\n+ 否则是之前所有页面的最后一个二级标题。\n\n效果如下：\n\n#frames-cjk(\n  read(\"./stateful/s1.typ\"),\n  code-as: ```typ\n  #show: set-heading\n\n  == 雨滴书v0.1.2\n  === KiraKira 样式改进\n  feat: 改进了样式。\n  === FuwaFuwa 脚本改进\n  feat: 改进了脚本。\n\n  == 雨滴书v0.1.1\n  refactor: 移除了LaTeX。\n\n  feat: 删除了一个多余的文件夹。\n\n  == 雨滴书v0.1.0\n  feat: 新建了两个文件夹。\n  ```,\n)\n\n// == 制作页眉标题的两种方法\n\n// 制作页眉标题至少有两种方法。一是直接查询文档内容；二是创建状态，利用布局迭代收敛的特性获得每个页面的首标题。\n\n// 在接下来的两节中我们将分别介绍这两种方法。\n\n// 本节我们讲解制作页眉标题的第一种方法，即通过查询文档状态直接估计当前页眉应当填入的内容。\n\n// #locate(loc => query(heading, loc))\n// #locate(loc => query(heading.where(level: 2), loc))\n\n== 「here」<grammar-here>\n\n#todo-box[\n  修改以适配全面 context 化的 0.12+ 语法\n]\n\n有的时候我们会需要获取当前位置的「位置」信息。\n\n在Typst中，获取当前「位置」的唯一方法是使用「#typst-func(\"here\")」函数。\n\n我们来`repr`一下试试看：\n\n#code(```typ\n当前位置的相关信息：#context repr(here())\n```)\n\n由于「位置」太过复杂了，`repr`放弃了思考并在这里为我们放了两个点。\n\n我们来简单学习一下Typst为我们提供了哪些位置信息：\n\n#code(```typ\n当前位置的坐标：#context here().position()\n\n当前位置的页码：#context here().page()\n```)\n\n一个常见的问题是：为什么Typst提供给我的页码信息是「内容」，我无法在内容上做条件判断和计算！<grammar-here-calc>\n\n// #code(```typ\n// #repr(context here().page()) \\\n// #type(context here().page())\n// ```)\n\n// 上面输出的内容告诉我们#typst-func(\"here\")不仅是一个函数，而且更是一个元素的构造函数。#typst-func(\"here\")构造出一个`locate`内容。\n\n这其中的关系比较复杂。一个比较好理解的原因是：Typst会调用你的函数多次，因此你理应将所有使用「位置」信息的脚本放在一个上下文块中，这样Typst才能更好地合成内容。\n\n#code(```typ\n#context [ 当前位置的页码是偶数：#calc.even(here().page()) ]\n//  根据位置信息  计算得到  我们想要的内容\n```)\n\n// #pro-tip[\n//   这与Typst的缓存原理相关。由于#typst-func(\"locate\")函数接收的闭包```typc loc => ..```是一个函数，且在Typst中它被认定为*纯函数*，Typst只会针对特定的参数执行一次函数。为了强制让用户书写的函数保持纯性，Typst规定用户必须在函数内部使用「位置」信息。\n\n//   因此，例如我们希望在偶数页下让内容为“甲”，否则让内容为“乙”，应当这样书写：\n\n//   #code(```typ\n//   #context if calc.even(here().page()) [“甲”] else [“乙”]\n//   ```)\n// ]\n\n== 「query」<grammar-query>\n\n使用「#typst-func(\"query\")」函数你可以获得当前文档状态。\n\n#code(\n  ```typ\n  #context query(heading)\n  ```,\n  res: raw(\n    ```typc\n    (\n      heading(body: [雨滴书 v0.1.2], level: 2, ..),\n      ..,\n    )\n    ```.text,\n    lang: \"typc\",\n    block: false,\n  ),\n)\n\n「#typst-func(\"query\")」函数的唯一参数是「选择器」，很好理解。它接受一个选择器，并返回被选中的所有元素的列表。\n\n#code(\n  ```typ\n  #context query(heading)\n  ```,\n  res: raw(\n    ```typc\n    (\n      heading(body: [雨滴书 v0.1.2], level: 2, ..),\n      heading(body: [KiraKira 样式改进], level: 3, ..),\n      heading(body: [FuwaFuwa 脚本改进], level: 3, ..),\n      heading(body: [雨滴书 v0.1.1], level: 2, ..),\n      heading(body: [雨滴书 v0.1.0], level: 2, ..),\n    )\n    ```.text,\n    lang: \"typc\",\n    block: false,\n  ),\n)\n\n我们记得，选择器允许继续指定`where`条件过滤内容：\n\n#code(\n  ```typ\n  #context query(heading.where(level: 2))\n  ```,\n  res: raw(\n    ```typc\n    (\n      heading(body: [雨滴书 v0.1.2], level: 2, ..),\n      heading(body: [雨滴书 v0.1.1], level: 2, ..),\n      heading(body: [雨滴书 v0.1.0], level: 2, ..),\n    )\n    ```.text,\n    lang: \"typc\",\n    block: false,\n  ),\n)\n\n// 第二个参数是「位置」，就比较难以理解了。首先说明，`loc`并没有任何作用，即它是一个「哑参数」（Dummy Parameter）。\n\n// 如果你学过C++，以下两个方法分别匹配到前缀自增操作函数和后缀自增操作函数。\n\n// ```cpp\n// class Integer {\n//   Integer& operator++();   // 前缀自增操作函数\n//   Integer operator++(int); // 后缀自增操作函数\n// };\n// ```\n\n// ```cpp class Integer```类中的`int`就是一个所谓的哑参数。\n\n// 「哑参数」在实际函数执行中并未被使用，而仅仅作为标记以区分函数调用。我们知道以下两点：其一，只有#typst-func(\"locate\")函数会返回「位置」信息；其二，#typst-func(\"query\")函数需要我们传入「位置」信息。\n\n// 有了：那么Typst就是在告诉我们，#typst-func(\"query\")函数只能在#typst-func(\"locate\")函数内部调用。正如示例中的那样：\n\n// ```typ\n// #locate(loc => query(heading.where(level: 2), loc))\n// ```\n\n// 这个规则有些隐晦，并且Typst的设计者也已经注意到了这一点，所以他们正在计划改进这一点。当然在这之前，你只需要记住：#typst-func(\"query\")函数的第二个「位置」参数用于限制该函数仅在#typst-func(\"locate\")函数内部使用。\n\n// #pro-tip[\n//   这与Typst的缓存原理相关。为了加速#typst-func(\"query\")函数，Typst需要对其缓存。Typst合理做出以下假设：在文档每处的查询（`loc`），都单独缓存对应选择器的查询结果。\n\n//   更细致地描述如下：将```typc query(selector, loc)```的参数为「键」，执行结果为「值」构造一个哈希映射表。若使用`(selector, loc)`作为「键」，查询该表：\n//   + 未对应结果，则执行查询，缓存并返回结果。\n//   + 已经存在对应结果，则不会重新执行查询，而是使用表中的值作为结果。\n// ]\n\n== 通过查询内置状态制作页眉\n\n页眉的设置方法是创建一条```typc set page(header)```规则：\n\n#frames-cjk(\n  read(\"./stateful/q0.typ\"),\n  code-as: ```typ\n  #set page(header: [这是页眉])\n  ```,\n)\n\n既然如此，只需要将`[这是页眉]`替换成一个`context`表达式，就能通过#typst-func(\"query\")函数完成与「位置」相关的页眉设定：\n\n```typ\n#set page(header: context emph(get-heading-at-page()))\n```\n\n现在让我们来编写`get-heading-at-page`函数。\n\n首先，通过`query`函数查询得到整个文档的*所有二级标题*：\n\n```typc\nlet headings = query(heading).\n  filter(it => it.level == 2)\n```\n\n如果你熟记`where`方法，你可以更高效地做到这件事。以下函数调用也可以得到整个文档的*所有二级标题*：\n\n```typc\nlet headings = query(heading.where(level: 2))\n```\n\n很好，Typst文档可以很高效，但有些人写出的Typst代码天生更高效，而我们正在向他们靠近。\n\n接着，考虑构建这样一个`fold-headings`函数，它返回一个数组，数组的内容是每个页面页眉应当显示的内容，即每页的第一个标题。\n\n```typ\n#let fold-headings(headings) = {\n  ..\n}\n```\n\n我们可以对其直接调用以测试：\n\n#code(```typ\n#let fold-headings(headings) = {\n  (none, none)\n}\n#fold-headings((\n  (body:\"v2\",page:1),(body:\"v1\",page:1),(body:\"v0\",page: 2),\n))\n```)\n\n很好，这样我们就可以很方便地进行测试了。\n\n该函数首先创建一个数组，数组的长度为页面的数量。\n\n#code(```typ\n#let fold-headings(headings) = {\n  let max-page-num = calc.max(..headings.map(it => it.page))\n  (none, ) * max-page-num\n}\n#fold-headings((\n  (body:\"v2\",page:1),(body:\"v1\",page:1),(body:\"v0\",page: 2),\n))\n```)\n\n这里，```typc headings.map(it => it.page)```意即对每个标题获取对应位置的页码；```typc calc.max(..numbers)```意即取页码的最大值。\n\n由于示例中页码的最大值为`2`，`fold-headings`针对示例会返回一个长度为2的数组，数组的每一项都是`none`。\n\n接着，该函数遍历给定的`headings`，对每个页码，都首先获取第一个标题元素：\n\n#code(\n  ```typc\n  for h in headings {\n    if first-headings.at(h.page - 1) == none {\n      first-headings.at(h.page - 1) = h\n    }\n  }\n  ```,\n  res: eval(\n    ```typ\n    #let fold-headings(headings) = {\n      let max-page-num = calc.max(..headings.map(it => it.page))\n      let first-headings = (none, ) * max-page-num\n\n      for h in headings {\n        if first-headings.at(h.page - 1) == none {\n          first-headings.at(h.page - 1) = h\n        }\n      }\n\n      first-headings\n    }\n    #fold-headings((\n      (body:\"v2\",page:1),(body:\"v1\",page:1),(body:\"v0\",page: 2),\n    ))\n    ```.text,\n    mode: \"markup\",\n  ),\n)\n\n这里，```typc first-headings.at(h.page - 1)```意即获取当前页码对应在数组中的元素；`if`语句，如果对应页码对应的元素仍是```typc none```，那么就将当前的标题元素填入对应的位置中。\n\n同理，可以获得`last-headings`，存储每页的最后一个标题：\n\n#code(\n  ```typc\n  let last-headings = (none, ) * max-page-num\n  for h in headings {\n    last-headings.at(h.page - 1) = h\n  }\n  ```,\n  res: eval(\n    ```typ\n    #let fold-headings(headings) = {\n      let max-page-num = calc.max(..headings.map(it => it.page))\n      let last-headings = (none, ) * max-page-num\n\n      for h in headings {\n        last-headings.at(h.page - 1) = h\n      }\n\n      last-headings\n    }\n    #fold-headings((\n      (body:\"v2\",page:1),(body:\"v1\",page:1),(body:\"v0\",page: 2),\n    ))\n    ```.text,\n    mode: \"markup\",\n  ),\n)\n\n这里`for`语句意即：无论如何，都将当前的标题元素存入数组中。那么每页的最后一个标题总是能被存入到数组中。\n\n但是我们还没有考虑相邻情况。如果我们希望如果当前页面没有标题元素，则显示之前的标题。接下来我们来根据这个思路，组装正确的结果：\n\n#code(\n  ```typc\n  let res-headings = (none, ) * max-page-num\n  for i in range(res-headings.len()) {\n    res-headings.at(i) = if first-headings.at(i) != none {\n      first-headings.at(i)\n    } else {\n      last-headings.at(i) = last-headings.at(\n        calc.max(0, i - 1), default: none)\n      last-headings.at(i)\n    }\n  }\n  ```,\n  res: eval(\n    ```typ\n    #let fold-headings(headings) = {\n      let max-page-num = calc.max(..headings.map(it => it.page))\n      let first-headings = (none, ) * max-page-num\n      let last-headings = (none, ) * max-page-num\n\n      for h in headings {\n        if first-headings.at(h.page - 1) == none {\n          first-headings.at(h.page - 1) = h\n        }\n        last-headings.at(h.page - 1) = h\n      }\n\n      let res-headings = (none, ) * max-page-num\n      for i in range(res-headings.len()) {\n        res-headings.at(i) = if first-headings.at(i) != none {\n          first-headings.at(i)\n        } else {\n          last-headings.at(i) = last-headings.at(\n            calc.max(0, i - 1), default: none)\n          last-headings.at(i)\n        }\n      }\n\n      res-headings\n    }\n    #fold-headings((\n      (body:\"v2\",page:1),(body:\"v1\",page:1),(body:\"v0\",page: 2),\n    ))\n    ```.text,\n    mode: \"markup\",\n  ),\n)\n\n`res-headings`就是我们想要得到的结果。\n\n#let fold-headings(headings) = {\n  let max-page-num = calc.max(..headings.map(it => it.page))\n  let first-headings = (none,) * max-page-num\n  let last-headings = (none,) * max-page-num\n\n  for h in headings {\n    if first-headings.at(h.page - 1) == none {\n      first-headings.at(h.page - 1) = h\n    }\n    last-headings.at(h.page - 1) = h\n  }\n\n  let res-headings = (none,) * max-page-num\n  for i in range(res-headings.len()) {\n    res-headings.at(i) = if first-headings.at(i) != none {\n      first-headings.at(i)\n    } else {\n      last-headings.at(i) = last-headings.at(\n        calc.max(0, i - 1),\n        default: none,\n      )\n      last-headings.at(i)\n    }\n  }\n\n  res-headings\n}\n\n由于`res-headings`的计算比较复杂，我们先来一些测试用例来理解：\n\n情形一：假设文档的前段没有标题，该函数会将对应下标的结果置空：\n\n#code(\n  ```typ\n  情形一：#fold-headings((\n    (body:\"v2\",page:3),(body:\"v1\",page:3),(body:\"v0\",page: 3),\n  ))\n  ```,\n  scope: (fold-headings: fold-headings),\n)\n\n情形二：假设一页有多个标题，那么，对应下表的结果为该页面的首个标题：\n\n#code(\n  ```typ\n  情形二：#fold-headings((\n    (body:\"v2\",page:2),(body:\"v1\",page:2),(body:\"v0\",page: 3),\n  ))\n  ```,\n  scope: (fold-headings: fold-headings),\n)\n\n情形三：假设中间有页空缺，则对应下表的结果为前页的最后一个标题。\n\n#code(\n  ```typ\n  情形三：#fold-headings((\n    (body:\"v2\",page:1),(body:\"v1\",page:1),(body:\"v0\",page: 3),\n  ))\n  ```,\n  scope: (fold-headings: fold-headings),\n)\n\n其中，情形一其实是情形三的特例：假设某一页没有标题，则对应下表的结果为前页的最后一个标题。如果不存在前页包含标题，则对应下表的结果为```typc none```。\n\n于是我们可以给代码加上注释：\n\n```typc\nlet res-headings = (none, ) * max-page-num\n// 对于每一页，我们迭代下标\nfor i in range(res-headings.len()) {\n  // 让对应下标的结果等于：\n  res-headings.at(i) = {\n    // 如果该页包含标题，则其等于该页的第一个标题\n    if first-headings.at(i) != none {\n      first-headings.at(i)\n    } else {\n      // 否则，我们积累`last-headings`的结果\n      last-headings.at(i) = last-headings.at(\n        // 始终至少等于前一页的结果\n        calc.max(0, i - 1),\n        // 默认没有结果\n        default: none)\n      // 其等于前页的最后一个标题\n      last-headings.at(i)\n    }\n  }\n}\n```\n\n最后，我们将`query`与`fold-headings`结合起来，便得到了目标函数：\n\n```typ\n#let get-heading-at-page() = {\n  let headings = fold-headings(query(\n    heading.where(level: 2)))\n  headings.at(here().page() - 1)\n}\n```\n\n这里有一个问题，那便是`fold-headings`没有考虑标题的最后一页仍然存在内容的情况。例如第二页有最后一个标题，但是我们文档一共有三页。\n\n重新改造一下：\n\n#let calc-headings(headings) = {\n  let max-page-num = calc.max(..headings.map(it => it.page))\n  let first-headings = (none,) * max-page-num\n  let last-headings = (none,) * max-page-num\n\n  for h in headings {\n    if first-headings.at(h.page - 1) == none {\n      first-headings.at(h.page - 1) = h\n    }\n    last-headings.at(h.page - 1) = h\n  }\n\n  let res-headings = (none,) * max-page-num\n  for i in range(res-headings.len()) {\n    res-headings.at(i) = if first-headings.at(i) != none {\n      first-headings.at(i)\n    } else {\n      last-headings.at(i) = last-headings.at(\n        calc.max(0, i - 1),\n        default: none,\n      )\n      last-headings.at(i)\n    }\n  }\n\n  (\n    res-headings,\n    if max-page-num > 0 {\n      last-headings.at(-1)\n    },\n  )\n}\n\n```typ\n#let calc-headings(headings) = {\n  // 计算res-headings和last-headings\n  ..\n\n  // 同时返回最后一个标题\n  (res-headings, if max-page-num > 0 {\n    last-headings.at(-1)\n  })\n}\n```\n\n我们来简单测试一下：\n\n#code(\n  ```typ\n  情形一：#calc-headings((\n    (body:\"v2\",page:3),(body:\"v1\",page:3),(body:\"v0\",page: 3),\n  )).at(1) \\\n  情形二：#calc-headings((\n    (body:\"v2\",page:2),(body:\"v1\",page:2),(body:\"v0\",page: 3),\n  )).at(1) \\\n  情形三：#calc-headings((\n    (body:\"v2\",page:1),(body:\"v1\",page:1),(body:\"v0\",page: 3),\n  )).at(1)\n  ```,\n  scope: (calc-headings: calc-headings),\n)\n\n很好，这样，下面的实现就完全正确了：\n\n```typ\n#let get-heading-at-page() = {\n  let (headings, last-heading) = calc-headings(\n    query(heading.where(level: 2)))\n  headings.at(here().page() - 1, default: last-heading)\n}\n```\n\n#pro-tip[\n  将`calc-headings`与`get-heading-at-page`分离可以改进脚本性能。这是因为Typst是以函数为粒度缓存你的计算。在最后的实现：\n\n  + ```typc query(heading.where(level: 2))```会被缓存，如果二级标题的结果不变，则#typst-func(\"query\")函数不会重新执行（不会重新查询文档状态）。\n  + ```typc calc-headings(..)```会被缓存。如果查询的结果不变。则其不会重新执行。\n]\n\n最后，让我们适配`calc-headings`到真实场景，并应用到页眉规则：\n\n#frames-cjk(\n  read(\"./stateful/q1.typ\"),\n  code-as: ```typ\n  // 这里有get-heading-at-page的实现..\n\n  #set page(header: context emph(get-heading-at-page()))\n  ```,\n)\n\n== 自定义「状态」（state）<grammar-state>\n\n在法一中，我们仅靠「#typst-func(\"query\")」函数就完成制作所要求页眉的功能。\n\n思考下面函数：\n\n```typ\n#let get-heading-at-page(loc) = {\n  let (headings, last-heading) = calc-headings(\n    query(heading.where(level: 2), loc))\n  headings.at(loc.page() - 1, default: last-heading)\n}\n```\n\n对于每个页面，它都运行```typc query(heading.where(level: 2), loc)```。显然每页的「位置」信息，即`loc`对应不相同。因此：\n1. 它会*每页*都重新执行一遍`heading.where(level: 2)`查询。\n2. 同时，每次`query`都是对*全文档的线性扫描*。\n\n夸张来讲，假设你有一千页文档，文档中包含上千个二级标题；那么他将会使得你的每次标题更新都触发上百万次迭代。虽然Typst很快，但这上百万次迭代将会使得包含这种页眉的文档难以*实时预览*。\n\n那么有没有一种方法避免这种全文档扫描呢？\n\n本节将介绍法二，它基于「#typst-func(\"state\")」函数，持续维护页眉状态。\n\nTypst文档可以很高效，但有些人写出的Typst代码更高效。本节所介绍的法二，让我们变得更接近这种人。\n\n`state`接收一个名称，并创建该名称对应*全局*唯一的状态变量。\n\n#code(```typ\n#state(\"my-state\", 1)\n```)\n\n你可以使用```typc context state.get()```函数展示其「内容」：\n\n#code(```typ\n#let s1 = state(\"my-state\", 1)\ns1: #context s1.get()\n```)\n\n你可以使用```typc state.update()```方法更新其状态。`update`函数接收一个「回调函数」，该回调函数接收`state`在某时刻的状态，并返回对应下一时刻的状态：\n\n#let s = state(\"my-state\", 1)\n\n#code(```typ\n#let s1 = state(\"my-state\", 1)\ns1: #context s1.get() \\\n#s1.update(it => it + 1)\ns1: #context s1.get()\n```)\n\n#s.update(it => 1)\n\n所有的相同名称的内容将会共享更新：\n\n#code(```typ\n#let s1 = state(\"my-state\", 1)\ns1: #context s1.get() \\\n#let s2 = state(\"my-state\", 1)\ns1: #context s1.get(), s2: #context s2.get() \\\n#s2.update(it => it + 1)\ns1: #context s1.get(), s2: #context s2.get()\n```)\n\n同时，请注意状态的*全局*特性，即便处于不同文件、不同库的状态，只要字符串对应相同，那么其都会共享更新。\n\n这提示我们需要在不同的文件之间维护全局状态的名称唯一性。\n\n另一个需要注意的是，`state`允许指定一个默认值，但是一个良好的状态设置必须保持不同文件之间的默认值相同。如下所示：\n\n#s.update(it => 1)\n\n#code(```typ\n#let s1 = state(\"my-state\", 1)\ns1: #context s1.get() \\\n#let s2 = state(\"my-state\", 2)\ns1: #context s1.get(), s2: #context s2.get() \\\n#s2.update(it => it + 1)\ns1: #context s1.get(), s2: #context s2.get()\n```)\n\n尽管`s2`指定了状态的默认值为`2`，因为之前已经在文档中创建了该状态，默认值并不会应用。请注意：你不应该利用这个特性，该特性是Typst中的「未定义行为」。\n\n== 「state.update」也是「内容」\n\n#todo-box[\n  不再有 `locate` 了, 此处需要修正\n]\n一个值得注意的地方是，似乎与#typst-func(\"locate\")函数相似，#typst-func(\"state.update\")也接收一个闭包。\n\n事实上，与#typst-func(\"locate\")函数相同，#typst-func(\"state.update\")也具备延迟执行的特性。\n\n让我们检查下列脚本的输出结果：\n\n#s.update(it => 1)\n\n#code(```typ\n#let s1 = state(\"my-state\", 1)\n#((s1.update(it => it + 1), ) * 3).join()\ns1: #context s1.get()\n```)\n\n这告诉我们下面一件事情，当`eval`阶段结束时，其对应产生下面的一段内容：\n\n```typc\n(\n  state(\"my-state\", 1),\n  state(\"my-state\").update(it => it + 1),\n  state(\"my-state\").update(it => it + 1),\n  state(\"my-state\").update(it => it + 1),\n)\n```\n\n排版引擎会按照*深度优先的顺序*遍历你的内容，从文档的开始位置逐渐*积累*状态。\n\n这将帮助我们在多文件之间协助完成状态的更新与计算。\n\n假设我们有两个文件`s1.typ`和`s2.typ`，文件的内容分别是：\n```typ\n// s1.typ\n#let s1 = state(\"my-state\", (1, ))\n#s1.update(it => it + (3, ))\n// s2.typ\n#let s1 = state(\"my-state\", (2, ))\n#s1.update(it => it + (4, ))\n#s1.update(it => it + (5, ))\n```\n\n并且我们在`main.typ`中引入了上述两个文件：\n\n```typc\n#include \"s1.typ\"\n#include \"s2.typ\"\n```\n\n那么根据我们的经验，主文件内容其实对应为：\n\n```typ\n// 省略部分内容\n#({ state(\"my-state\", (1, )), .. } + { state(\"my-state\", (2, )), .. })\n```\n\n我们按照顺序执行状态更新，则状态依次变为：\n\n```typ\n#{ (1, ); (1, 3, ); }\n#{ (1, 3, ); (1, 3, 4, ); (1, 3, 4, 5, ); }\n```\n\n== 查询特定时间点的「状态」\n\n\n#todo-box[\n  `state.final` 不再需要 `loc` 了\n]\n\nTypst提供两个方法查询特定时间点的「状态」：\n\n一个方法是```typc state.at(loc)```方法，其接收一个「位置」，返回在该位置对应的状态「值」。\n\n另一个方法是```typc state.final()```方法，返回在文档结束一切排版时对应的状态「值」。\n\n熟悉的剧情再次发生了。让我们回想之前介绍#typst-func(\"query\")时讲述的知识点。\n\n\n这两个方法都只能在#typst-func(\"context\")内部调用。对于#typst-func(\"state.at\")方法，其「位置」参数是有用的/*；对于#typst-func(\"state.final\")方法，其「位置」参数仅仅作为「哑参数」*/。\n\n我们回想上一小节，由于文档的每个位置「状态」都会存有对应的值，而且当你使用状态的时候至少会指定一个默认值，我们可以知道在我们文档的任意位置使用文档的任意其他位置的状态的内容。\n\n这就是允许我们进行时光回溯的基础。\n\n== 通过自定义状态制作页眉\n\n本节使用递归的方法完成状态的构建，其更为巧妙。\n\n首先，我们声明两个与法一类似的状态，只不过这次我们将状态定义在全局。\n\n```typ\n#let first-heading = state(\"first-heading\", ())\n#let last-heading = state(\"last-heading\", ())\n```\n\n然后，我们在每个二级标题后紧接着触发一个更新：\n\n```typc\nshow heading.where(level: 2): curr-heading => {\n  curr-heading\n  context ..\n}\n```\n\n对于`last-heading`状态，我们可以非常简单地如此更新内容：\n\n```typc\ncontext last-heading.update(headings => {\n  headings.insert(str(here().page()), curr-heading.body)\n  headings\n})\n```\n\n每页的最后一个标题总能最后触发状态更新，所以`str(here().page())`总是能对应到每页的最后一个标题的内容。\n\n对于`first-heading`状态，稍微复杂但也好理解：\n\n```typc\ncontext first-heading.update(headings => {\n  let k = str(here().page())\n  if k not in headings {\n    headings.insert(k, curr-heading.body)\n  }\n  headings\n})\n```\n\n对于每页靠后的一级标题，都不能使`if`条件成立。所以`str(here().page())`总是能对应到每页的第一个一级标题的内容。\n\n接下来便是简单的查询了，我们回忆之前`get-heading-at-page`的逻辑，它首先判断是否存在本页的第一个标题，否则取前页的最后一个标题。以下函数完成了前半部分：\n\n```typc\nlet get-heading-at-page() = {\n  let page-num = here().page()\n  let first-headings = first-heading.final(here())\n\n  first-headings.at(str(page-num))\n}\n```\n\n我们为`at`函数添加`default`函数，其取前页的最后一个标题。\n\n```typc\nlet get-heading-at-page() = {\n  ..\n  let last-headings = last-heading.at(here())\n\n  first-headings.at(str(page-num), default: find-headings(last-headings, page-num))\n}\n```\n\n我们使用递归的方法实现`find-headings`：\n\n```typc\nlet find-headings(headings, page-num) = if page-num > 0 {\n  headings.at(str(page-num), default: find-headings(headings, page-num - 1))\n}\n```\n\n递归有两个分支：递归的基是，若一直找到文档最前页都找不到相应的标题，则返回`none`。否则检查`headings`中是否有对应页的标题：若有则直接返回其内容，否则继续往前页迭代。\n\n一个细节值得注意，我们对`first-heading`使用了`final`方法，但对`last-heading`使用了`at`方法。这是因为：\n+ `first-heading`需要我们支持后向查找，因此需要直接获取文档最终的状态。\n+ `last-heading`仅仅需要前向查找，因此使用`at`方法可以改进迭代效率。\n\n这个递归函数是高性能的，因为Typst会对`find-headings`缓存，并且Typst对于后一页的内容，都总是能命中前一页的缓存。\n\n与之相反，基于#typst-func(\"query\")的实现没有那么好命。它没法很好利用递归完成标题信息的构建。这是因为#typst-func(\"query\")的实现中，`calc-headings`的首次执行就被要求计算文档的所有标题。\n\n最后让我们设置页眉：\n\n#frames-cjk(\n  read(\"./stateful/s1.typ\"),\n  code-as: ```typ\n  // 这里有get-heading-at-page的实现..\n\n  #set page(header: emph(get-heading-at-page()))\n  ```,\n)\n"
  },
  {
    "path": "src/tutorial/figure-time-travel.typ",
    "content": "#import \"@preview/cetz:0.3.4\"\n#import \"/typ/templates/page.typ\": main-color, is-light-theme\n\n#let std-scale = scale\n\n#let figure-time-travel(\n  stroke-color: main-color,\n  light-theme: is-light-theme,\n) = {\n  import cetz.draw: *\n\n  let stroke-factor = if light-theme {\n    1\n  } else {\n    0.8\n  }\n\n  let line-width = 0.5pt * stroke-factor\n  let rect = rect.with(stroke: line-width + stroke-color)\n  let circle = circle.with(stroke: line-width + stroke-color)\n  let line = line.with(stroke: line-width + stroke-color)\n  let light-line = line.with(stroke: (0.3pt * stroke-factor) + stroke-color)\n  let exlight-line = line.with(stroke: (0.2pt * stroke-factor) + stroke-color)\n\n  let preview-img-fill = if light-theme {\n    green.lighten(80%)\n  } else {\n    green.darken(20%)\n  }\n\n  let preview-img-fill2 = if light-theme {\n    green.lighten(80%)\n  } else {\n    green.darken(20%)\n  }\n\n  let state-background-fill = rgb(\"#2983bb80\")\n  let preview-background-fill = rgb(\"#baccd980\")\n\n  let preview-content0 = {\n    translate((0.05, 1 - 0.2))\n    // title\n    light-line((0.15, 0), (0.55, 0))\n    translate((0, -0.08))\n    exlight-line((0.05, 0), (0.7, 0))\n    translate((0, -0.03))\n    exlight-line((0.00, 0), (0.7, 0))\n    translate((0, -0.03))\n    exlight-line((0.00, 0), (0.7, 0))\n    translate((0, -0.03))\n    exlight-line((0.00, 0), (0.4, 0))\n  }\n\n  let preview-content1 = {\n    translate((0, -0.06))\n    rect((0.05, 0), (0.65, -0.24), stroke: 0pt, fill: preview-img-fill, name: \"picture\")\n    content(\"picture\", std-scale(20%)[$lambda x = integral.double x dif x$])\n    translate((0, -0.24))\n  }\n  let doc-state(prev-name, name, checkpoint: 0) = {\n    let arrow-line(pos) = {\n      line((rel: (-0.01, 0.025), to: pos), pos)\n      line((rel: (0.01, 0.025), to: pos), pos)\n    }\n\n    rect((0, 0), (0.2, 1), stroke: 0pt, fill: state-background-fill)\n\n    if checkpoint == 0 {\n      line(prev-name + \"-S2\", name + \"-S1\", stroke: line-width + red)\n    }\n    if checkpoint == 1 {\n      line(prev-name + \"-S3\", name + \"-S2\", stroke: line-width + red)\n    }\n    if checkpoint == 2 {\n      line(prev-name + \"-S4\", name + \"-S3\", stroke: line-width + red)\n    }\n\n    circle(name + \"-S1\", fill: blue, stroke: 0pt, radius: 0.01)\n    content((rel: (-0.04, 0), to: name + \"-S1\"), std-scale(40%, [S1]))\n    if checkpoint <= 0 {\n      return\n    }\n    circle(name + \"-S2\", fill: blue, stroke: 0pt, radius: 0.01)\n    content((rel: (-0.04, -0.03), to: name + \"-S2\"), std-scale(40%, [S2]))\n    line(name + \"-S1\", name + \"-S2\")\n    arrow-line(name + \"-S2\")\n    if checkpoint <= 1 {\n      return\n    }\n    circle(name + \"-S3\", fill: blue, stroke: 0pt, radius: 0.01)\n    content((rel: (-0.04, -0.02), to: name + \"-S3\"), std-scale(40%, [S3]))\n    line(name + \"-S2\", name + \"-S3\")\n    arrow-line(name + \"-S3\")\n\n    if checkpoint == 2 {\n      circle(name + \"-R1\", fill: blue, stroke: 0pt, radius: 0.01)\n      arc-through(name + \"-R1\", (rel: (0.02, -0.03), to: name + \"-R1\"), name + \"-S3\", stroke: line-width + blue)\n      line((rel: (50deg, 0.02), to: name + \"-S3\"), name + \"-S3\", stroke: line-width + blue)\n      line((rel: (10deg, 0.02), to: name + \"-S3\"), name + \"-S3\", stroke: line-width + blue)\n    }\n\n    if checkpoint <= 2 {\n      return\n    }\n\n\n    circle(name + \"-S4\", fill: blue, stroke: 0pt, radius: 0.01)\n    content((rel: (-0.04, -0.01), to: name + \"-S4\"), std-scale(40%, [S4]))\n    line(name + \"-S3\", name + \"-S4\")\n    if checkpoint == 3 {\n      circle(name + \"-R2\", fill: blue, stroke: 0pt, radius: 0.01)\n    }\n    arrow-line(name + \"-S4\")\n\n\n    if checkpoint == 3 {\n      arc-through(name + \"-R2\", (rel: (0.05, 0.33, 0), to: name + \"-R2\"), name + \"-S2\", stroke: line-width + blue)\n      line((rel: (-110deg, 0.02), to: name + \"-S2\"), name + \"-S2\", stroke: line-width + blue)\n      line((rel: (-20deg, 0.02), to: name + \"-S2\"), name + \"-S2\", stroke: line-width + blue)\n    }\n  }\n  let doc(name, checkpoint: 0) = {\n    group(\n      name: name,\n      {\n        // line((0, 0), (0.707, 0))\n        // line((0.707, 0), (0.707, 1))\n        // line((0.707, 1), (0, 1))\n        // line((0, 0), (0, 1))\n        rect((0, 0), (0.707, 1), stroke: 0pt, fill: preview-background-fill)\n\n        let margin = 0.05\n\n        translate((margin, 1 - 0.1))\n        scale(x: (0.707 - margin * 2))\n        let lm = (-margin) / (0.707 - margin * 2)\n\n        anchor(\"title\", (0.5, 0))\n        content(\"title\", std-scale(30%)[关于吃睡玩自动机的形式化研究])\n        // content(\"title\", std-scale(30%)[#align(center, [A formal study on eat-sleep-\\ play automating cats])])\n\n        translate((0, -0.07))\n        light-line((0.1, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n\n        translate((0, -0.025))\n        if checkpoint <= 0 {\n          light-line((0.05, 0), (0.45, 0))\n          anchor(\"P\", (0.45, 0))\n          anchor(\"Q\", (lm, 0))\n          return\n        }\n\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.75, 0))\n\n        translate((0, -0.03))\n        anchor(\"P\", (0.59, -0.072))\n        anchor(\"Q\", (lm, -0.072))\n        rect((0.1, 0), (0.9, -0.10), stroke: 0pt, fill: preview-img-fill, name: \"picture\")\n        content(\"picture\", std-scale(30%)[$lambda x = integral.double x dif x$])\n        if checkpoint <= 1 {\n          return\n        }\n\n        translate((0, -0.03 - 0.10))\n\n        light-line((0.1, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.85, 0))\n\n        translate((0, -0.03))\n\n        light-line((0.1, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        if checkpoint == 2 {\n          anchor(\"R\", (0.75, 0))\n          anchor(\"R2\", (lm, 0))\n        }\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        anchor(\"P\", (0.85, 0))\n        anchor(\"Q\", (lm, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.35, 0))\n\n        if checkpoint <= 2 {\n          return\n        }\n\n        translate((0, -0.03))\n\n        light-line((0.1, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.35, 0))\n\n        translate((0, -0.03))\n\n        light-line((0.1, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        if checkpoint == 3 {\n          anchor(\"R\", (0.75, 0))\n          anchor(\"R2\", (lm, 0))\n        }\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.95, 0))\n        anchor(\"P\", (0.6, 0))\n        anchor(\"Q\", (lm, 0))\n        translate((0, -0.025))\n        light-line((0.05, 0), (0.35, 0))\n      },\n    )\n  }\n\n  cetz.canvas({\n    // 导入cetz的draw方言\n    import cetz.draw: *\n    set-viewport((0, 0, 0), (4, 4, -4), bounds: (1, 1, 1))\n\n    group(\n      name: \"doc\",\n      {\n        let x = 0.25\n        let z = 0.22\n        translate((x, 0, -z))\n        translate((x, 0, -z))\n        translate((x, 0, -z))\n        doc(\"D1\", checkpoint: 3)\n        circle(\"D1.P\", fill: red, stroke: 0pt, radius: 0.01)\n        translate((-x, 0, z))\n        doc(\"D2\", checkpoint: 2)\n        circle(\"D2.P\", fill: red, stroke: 0pt, radius: 0.01)\n        translate((-x, 0, z))\n        doc(\"D3\", checkpoint: 1)\n        circle(\"D3.P\", fill: red, stroke: 0pt, radius: 0.01)\n        translate((-x, 0, z))\n        doc(\"D4\", checkpoint: 0)\n        circle(\"D4.P\", fill: red, stroke: 0pt, radius: 0.01)\n\n        circle(\"D2.R\", fill: blue, stroke: 0pt, radius: 0.01)\n        circle(\"D1.R\", fill: blue, stroke: 0pt, radius: 0.01)\n\n        let blue-light-line = line.with(stroke: (0.3pt * stroke-factor) + blue)\n        let red-light-line = line.with(stroke: (0.3pt * stroke-factor) + red)\n        red-light-line(\"D4.P\", \"D3.P\")\n        red-light-line(\"D3.P\", \"D2.P\")\n        red-light-line(\"D2.P\", \"D1.P\")\n\n        blue-light-line(\"D2.R\", \"D2.P\")\n        blue-light-line(\"D1.R\", \"D3.P\")\n\n        let round-z-axis = ((x, y, z)) => (x, y, calc.round(z, digits: 10))\n        anchor(\"S1\", (round-z-axis, (rel: (-x * 0, 0, z * 0), to: \"D4.Q\")))\n        anchor(\"S2\", (round-z-axis, (rel: (-x * 1, 0, z * 1), to: \"D3.Q\")))\n        anchor(\"S3\", (round-z-axis, (rel: (-x * 2, 0, z * 2), to: \"D2.Q\")))\n        anchor(\"S4\", (round-z-axis, (rel: (-x * 3, 0, z * 3), to: \"D1.Q\")))\n        anchor(\"R1\", (round-z-axis, (rel: (-x * 2, 0, z * 2), to: \"D2.R2\")))\n        anchor(\"R2\", (round-z-axis, (rel: (-x * 3, 0, z * 3), to: \"D1.R2\")))\n      },\n    )\n\n    let anchor-s(prefix, p) = {\n      anchor(prefix + \"-S1\", (rel: p, to: \"doc.S1\"))\n      anchor(prefix + \"-S2\", (rel: p, to: \"doc.S2\"))\n      anchor(prefix + \"-S3\", (rel: p, to: \"doc.S3\"))\n      anchor(prefix + \"-S4\", (rel: p, to: \"doc.S4\"))\n      anchor(prefix + \"-R1\", (rel: p, to: \"doc.R1\"))\n      anchor(prefix + \"-R2\", (rel: p, to: \"doc.R2\"))\n    }\n\n    translate((-1, 0, 0))\n\n    group({\n      let x = 0.12\n      let z = 0.22\n      translate((x, 0, -z))\n      translate((x, 0, -z))\n      translate((x, 0, -z))\n      anchor-s(\"T1\", (-1 + 0.2 / 2 + x * 3, 0, -z * 3))\n      doc-state(\"T0\", \"T1\", checkpoint: 3)\n      translate((-x, 0, z))\n      anchor-s(\"T2\", (-1 + 0.2 / 2 + x * 2, 0, -z * 2))\n      doc-state(\"T1\", \"T2\", checkpoint: 2)\n      translate((-x, 0, z))\n      anchor-s(\"T3\", (-1 + 0.2 / 2 + x * 1, 0, -z * 1))\n      doc-state(\"T2\", \"T3\", checkpoint: 1)\n      translate((-x, 0, z))\n      anchor-s(\"T4\", (-1 + 0.2 / 2 + x * 0, 0, -z * 0))\n      doc-state(\"T3\", \"T4\", checkpoint: 0)\n    })\n\n    translate((-0.7, 0, 0))\n    line((0, 0), (0, 1), stroke: 0pt, fill: none)\n  })\n}\n"
  },
  {
    "path": "src/tutorial/latex-look.typ",
    "content": "#let latex-look(content) = {\n  // #set page(margin: 1.75in)\n  set par(\n    leading: 0.55em,\n    first-line-indent: 0em,\n    justify: true,\n  )\n  set text(font: \"New Computer Modern\")\n  show raw: set text(font: \"New Computer Modern Mono\")\n  show par: set par(spacing: 0.55em)\n  show heading: set block(\n    above: 1.4em,\n    below: 1em,\n  )\n\n  content\n}\n"
  },
  {
    "path": "src/tutorial/mod.typ",
    "content": "#import \"/src/book.typ\"\n#import \"/typ/templates/page.typ\": is-web-target, main-color\n#import \"/typ/embedded-typst/lib.typ\": default-cjk-fonts, default-fonts, svg-doc\n#import \"../mod.typ\": (\n  code as _code, exec-code as _exec-code, exercise, mark, pro-tip, ref-bookmark, ref-cons-signature, ref-func-signature,\n  ref-method-signature, refs, term, todo-box, todo-color, typst-func,\n)\n\n#let eval-local(it, scope, res) = if res != none {\n  res\n} else {\n  eval(it.text, mode: \"markup\", scope: scope)\n}\n#let exec-code(it, scope: (:), res: none, ..args) = _exec-code(it, res: eval-local(it, scope, res), ..args)\n/// - it (content): the body of code.\n#let code(it, scope: (:), res: none, ..args) = _code(it, res: eval-local(it, scope, res), ..args)\n\n#let frames(code, cjk-fonts: false, code-as: none, prelude: none) = {\n  if code-as != none {\n    code-as\n  } else {\n    code\n  }\n\n  if prelude != none {\n    code-as = if code-as == none {\n      code\n    }\n    code = prelude.text + \"\\n\" + code.text\n  }\n\n  let fonts = if cjk-fonts {\n    (..default-cjk-fonts(), ..default-fonts())\n  }\n\n  grid(columns: (1fr, 1fr), ..svg-doc(code, fonts: fonts).pages.map(data => image(bytes(data))).map(rect))\n}\n#let frames-cjk = frames.with(cjk-fonts: true)\n"
  },
  {
    "path": "src/tutorial/other-file.typ",
    "content": "\n一段文本\n#set text(fill: red)\n另一段文本\n"
  },
  {
    "path": "src/tutorial/packages/m1.typ",
    "content": "\nXXX\n#let add(x, y) = x + y\nYYY\n#let sub(x, y) = x - y\n"
  },
  {
    "path": "src/tutorial/reference-bibliography.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：导入和使用参考文献])\n"
  },
  {
    "path": "src/tutorial/reference-calculation.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：数值计算])\n\n#let table-lnk(name, ref, it, scope: (:), res: none, ..args) = (\n  align(center + horizon, link(\"todo\", name)),\n  it,\n  align(\n    horizon,\n    {\n      set heading(\n        bookmarked: false,\n        outlined: false,\n      )\n      eval(it.text, mode: \"markup\", scope: scope)\n    },\n  ),\n)\n\n// calc.*\n\n#todo-box[不再需要等待了]\n等待#link(\"https://github.com/typst/typst/pull/3489\")[PR: Begin migration of calc functions to methods]\n"
  },
  {
    "path": "src/tutorial/reference-color.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：颜色、色彩渐变与模式])\n\n== RGB\n\nCreate an RGB(A) color. #ref-bookmark[`color.rgb`]\n\nThe color is specified in the sRGB color space.\n\nAn RGB(A) color is represented internally by an array of four components:\n\n#code(```typ\n#square(fill: rgb(\"#b1f2eb\"))\n#square(fill: rgb(87, 127, 230))\n#square(fill: rgb(25%, 13%, 65%))\n```)\n\n== HSL\n\nCreate an HSL color. #ref-bookmark[`color.hsl`]\n\nThis color space is useful for specifying colors by hue, saturation and lightness. It is also useful for color manipulation, such as saturating while keeping perceived hue.\n\nAn HSL color is represented internally by an array of four components:\n- hue (angle)\n- saturation (ratio)\n- lightness (ratio)\n- alpha (ratio)\n\nThese components are also available using the components method.\n\n#code(```typ\n#square(\n  fill: color.hsl(30deg, 50%, 60%)\n)\n```)\n\n== CMYK\n\nCreate a CMYK color. #ref-bookmark[`color.cmyk`]\n\nThis is useful if you want to target a specific printer. The conversion to RGB for display preview might differ from how your printer reproduces the color.\n\n// todo: typo in reference\nAn CMYK color is represented internally by an array of four components:\n- cyan (ratio)\n- magenta (ratio)\n- yellow (ratio)\n- key (ratio)\n\nThese components are also available using the components method.\n\n#code(```typ\n#square(\n  fill: cmyk(27%, 0%, 3%, 5%)\n)\n```)\n\n== Luma\n\n== Oklab\n\n== Oklch\n\n== Linear RGB\n\n== HSV\n\n== 色彩空间\n\nReturns the constructor function for this color's space. #ref-bookmark[`color.space`]\n\n== 十六进制表示（RGB）\n\nReturns the color's RGB(A) hex representation. #ref-bookmark[`color.to-hex`]\n\nHex such as `#ffaa32` or `#020304fe`. The alpha component (last two digits in `#020304fe`) is omitted if it is equal to ff (255 / 100%).\n\n== 颜色计算\n\n=== lighten\n\nLightens a color by a given factor. #ref-bookmark[`color.lighten`]\n\n=== darken\n\nDarkens a color by a given factor. #ref-bookmark[`color.darken`]\n\n=== saturate\n\nIncreases the saturation of a color by a given factor. #ref-bookmark[`color.saturate`]\n\n=== negate\n\nProduces the negative of the color. #ref-bookmark[`color.negate`]\n\n=== rotate\n\nRotates the hue of the color by a given angle. #ref-bookmark[`color.rotate`]\n\n=== mix\n\nCreate a color by mixing two or more colors. #ref-bookmark[`color.mix`]\n\n== 色彩渐变\n\nA color gradient. #ref-bookmark[`gradient`]\n\nTypst supports linear gradients through the gradient.linear function, radial gradients through the gradient.radial function, and conic gradients through the gradient.conic function.\n\nA gradient can be used for the following purposes:\n\n- As a fill to paint the interior of a shape: ```typc rect(fill: gradient.linear(..))```\n- As a stroke to paint the outline of a shape: ```typc rect(stroke: 1pt + gradient.linear(..))```\n- As the fill of text: ```typc set text(fill: gradient.linear(..))```\n- As a color map you can sample from: ```typc gradient.linear(..).sample(0.5)```\n\n#code(```typ\n#stack(\n  dir: ltr,\n  spacing: 1fr,\n  square(fill: gradient.linear(\n    ..color.map.rainbow)),\n  square(fill: gradient.radial(\n    ..color.map.rainbow)),\n  square(fill: gradient.conic(\n    ..color.map.rainbow)),\n)\n```)\n\n== 填充模式\n\nA repeating pattern fill. #ref-bookmark[`tiling`]\n\nTypst supports the most common pattern type of tiled patterns, where a pattern is repeated in a grid-like fashion, covering the entire area of an element that is filled or stroked. The pattern is defined by a tile size and a body defining the content of each cell. You can also add horizontal or vertical spacing between the cells of the patterng.\n\n#code(```typ\n#let pat = tiling(size: (30pt, 30pt))[\n  #place(line(start: (0%, 0%), end: (100%, 100%)))\n  #place(line(start: (0%, 100%), end: (100%, 0%)))\n]\n\n#rect(fill: pat, width: 100%, height: 60pt, stroke: 1pt)\n```)\n"
  },
  {
    "path": "src/tutorial/reference-counter-state.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：计数器和状态])\n"
  },
  {
    "path": "src/tutorial/reference-data-process.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：数据读写与数据处理])\n\n#let table-lnk(name, ref, it, scope: (:), res: none, ..args) = (\n  align(center + horizon, link(\"todo\", name)),\n  it,\n  align(\n    horizon,\n    {\n      set heading(bookmarked: false, outlined: false)\n      eval(it.text, mode: \"markup\", scope: scope)\n    },\n  ),\n)\n\n== read\n\n== 数据格式\n\n== 字节数组\n\n== image.decode\n\n== 字符串Unicode\n\n== 正则匹配\n\n== wasm插件\n\n== metadata\n\n== typst query\n"
  },
  {
    "path": "src/tutorial/reference-date.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：时间类型])\n\n#let table-lnk(name, ref, it, scope: (:), res: none, ..args) = (\n  align(center + horizon, link(\"todo\", name)),\n  it,\n  align(\n    horizon,\n    {\n      set heading(bookmarked: false, outlined: false)\n      eval(it.text, mode: \"markup\", scope: scope)\n    },\n  ),\n)\n\n== 日期\n\n日期表示时间长河中的一个具体的时间戳。#ref-bookmark[`datetime`]\n\n#code(```typ\n一个值 #datetime(year: 2023, month: 4, day: 19).display() 偷偷混入了我们内容之中。\n```)\n\n== 时间间隔\n\n时间间隔表示两个时间戳之间的时间差。#ref-bookmark[`duration`]\n\n#code(```typ\n一个值 #duration(days: 3, hours: 10).seconds()s 偷偷混入了我们内容之中。\n```)\n\n== typst-pdf时间戳\n\n```typ\nset document(date: auto)\n```\n"
  },
  {
    "path": "src/tutorial/reference-grammar.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [参考：语法示例检索表])\n\n#let table-lnk(name, ref, it, scope: (:), res: none, ..args) = (\n  align(center + horizon, ref(name)),\n  it,\n  align(\n    horizon,\n    {\n      set heading(\n        bookmarked: false,\n        outlined: false,\n      )\n      eval(it.text, mode: \"markup\", scope: scope)\n    },\n  ),\n)\n\n#let ref-table(items) = table(\n  columns: (120pt, 6fr, 5fr),\n  align(center)[「名称/术语」],\n  align(center)[语法],\n  align(center)[渲染结果],\n  ..items.map(args => table-lnk(..args)).flatten(),\n)\n\n点击下面每行名称都可以跳转到对应章节的对应小节。\n\n== 分类：基本元素 <grammar-table:base-elements>\n\n#ref-table((\n  (\n    [段落],\n    refs.writing-markup.with(reference: <grammar-paragraph>),\n    ```typ\n    writing-markup\n    ```,\n  ),\n  (\n    [标题],\n    refs.writing-markup.with(reference: <grammar-heading>),\n    ```typ\n    = Heading\n    ```,\n  ),\n  (\n    [二级标题],\n    refs.writing-markup.with(reference: <grammar-heading>),\n    ```typ\n    == Heading\n    ```,\n  ),\n  (\n    [着重标记],\n    refs.writing-markup.with(reference: <grammar-strong>),\n    ```typ\n    *Strong*\n    ```,\n  ),\n  (\n    [强调标记],\n    refs.writing-markup.with(reference: <grammar-emph>),\n    ```typ\n    _emphasis_\n    ```,\n  ),\n  (\n    [着重且强调标记],\n    refs.writing-markup.with(reference: <grammar-emph>),\n    ```typ\n    *_emphasis_*\n    ```,\n  ),\n  (\n    [有序列表],\n    refs.writing-markup.with(reference: <grammar-list>),\n    ```typ\n    + List 1\n    + List 2\n    ```,\n  ),\n  (\n    [有序列表（重新开始标号）],\n    refs.writing-markup.with(reference: <grammar-continue-list>),\n    ```typ\n    4. List 1\n    + List 2\n    ```,\n  ),\n  (\n    [无序列表],\n    refs.writing-markup.with(reference: <grammar-emum>),\n    ```typ\n    - Enum 1\n    - Enum 2\n    ```,\n  ),\n  (\n    [交替有序与无序列表],\n    refs.writing-markup.with(reference: <grammar-mix-list-emum>),\n    ```typ\n    - Enum 1\n      + Item 1\n    - Enum 2\n    ```,\n  ),\n  (\n    [短代码片段],\n    refs.writing-markup.with(reference: <grammar-raw>),\n    ````typ\n    `code`\n    ````,\n  ),\n  (\n    [长代码片段],\n    refs.writing-markup.with(reference: <grammar-long-raw>),\n    ````typ\n    ``` code```\n    ````,\n  ),\n  (\n    [语法高亮],\n    refs.writing-markup.with(reference: <grammar-lang-raw>),\n    ````typ\n    ```rs  trait World```\n    ````,\n  ),\n  (\n    [块代码片段],\n    refs.writing-markup.with(reference: <grammar-blocky-raw>),\n    ````typ\n    ```typ\n    = Heading\n    ```\n    ````,\n  ),\n  (\n    [图像],\n    refs.writing-markup.with(reference: <grammar-image>),\n    ````typ\n    #image(\"/assets/files/香風とうふ店.jpg\", width: 50pt)\n    ````,\n  ),\n  (\n    [拉伸图像],\n    refs.writing-markup.with(reference: <grammar-image-stretch>),\n    ````typ\n    #image(\"/assets/files/香風とうふ店.jpg\", width: 50pt, height: 50pt, fit: \"stretch\")\n    ````,\n  ),\n  (\n    [内联图像（盒子法）],\n    refs.writing-markup.with(reference: <grammar-image-inline>),\n    ````typ\n    在一段话中插入一个#box(baseline: 0.15em, image(\"/assets/files/info-icon.svg\", width: 1em))图片。\n    ````,\n  ),\n  (\n    [图像标题],\n    refs.writing-markup.with(reference: <grammar-figure>),\n    ````typ\n    #figure(```typ\n    #image(\"/assets/files/香風とうふ店.jpg\")\n    ```, caption: [用于加载香風とうふ店送外卖的宝贵影像的代码])\n    ````,\n  ),\n  (\n    [链接],\n    refs.writing-markup.with(reference: <grammar-link>),\n    ````typ\n    #link(\"https://zh.wikipedia.org\")[维基百科]\n    ````,\n  ),\n  (\n    [HTTP(S)链接],\n    refs.writing-markup.with(reference: <grammar-http-link>),\n    ````typ\n    https://zh.wikipedia.org\n    ````,\n  ),\n  (\n    [内部链接],\n    refs.writing-markup.with(reference: <grammar-internal-link>),\n    ````typ\n    == 某个标题 <ref-internal-link>\n    #link(<ref-internal-link>)[链接到某个标题]\n    ````,\n  ),\n  (\n    [表格],\n    refs.writing-markup.with(reference: <grammar-table>),\n    ````typ\n    #table(columns: 2, [111], [2], [3])\n    ````,\n  ),\n  (\n    [对齐表格],\n    refs.writing-markup.with(reference: <grammar-table-align>),\n    ````typ\n    #table(columns: 2, align: center, [111], [2], [3])\n    ````,\n  ),\n  (\n    [行内数学公式],\n    refs.writing-markup.with(reference: <grammar-inline-math>),\n    ````typ\n    $sum_x$\n    ````,\n  ),\n  (\n    [行间数学公式],\n    refs.writing-markup.with(reference: <grammar-display-math>),\n    ````typ\n    $ sum_x $\n    ````,\n  ),\n  (\n    [标记转义序列],\n    refs.writing-markup.with(reference: <grammar-escape-sequences>),\n    ````typ\n    >\\_<\n    ````,\n  ),\n  (\n    [标记的Unicode转义序列],\n    refs.writing-markup.with(reference: <grammar-unicode-escape-sequences>),\n    ````typ\n    \\u{9999}\n    ````,\n  ),\n  (\n    [换行符（转义序列）],\n    refs.writing-markup.with(reference: <grammar-newline-by-space>),\n    ````typ\n    A \\ B\n    ````,\n  ),\n  (\n    [换行符情形二],\n    refs.writing-markup.with(reference: <grammar-newline>),\n    ````typ\n    A \\\n    B\n    ````,\n  ),\n  (\n    [速记符],\n    refs.writing-markup.with(reference: <grammar-shorthand>),\n    ````typ\n    北京--上海\n    ````,\n  ),\n  (\n    [空格（速记符）],\n    refs.writing-markup.with(reference: <grammar-shorthand-space>),\n    ````typ\n    A~B\n    ````,\n  ),\n  (\n    [行内注释],\n    refs.writing-markup.with(reference: <grammar-inline-comment>),\n    ````typ\n    // 行内注释\n    ````,\n  ),\n  (\n    [行间注释],\n    refs.writing-markup.with(reference: <grammar-cross-line-comment>),\n    ````typ\n    /* 行间注释\n      */\n    ````,\n  ),\n  (\n    [行内盒子],\n    refs.writing-markup.with(reference: <grammar-box>),\n    ````typ\n    在一段话中插入一个#box(baseline: 0.15em, image(\"/assets/files/info-icon.svg\", width: 1em))图片。\n    ````,\n  ),\n))\n\n== 分类：修饰文本 <grammar-table:text>\n\n#ref-table((\n  (\n    [背景高亮],\n    refs.writing-markup.with(reference: <grammar-highlight>),\n    ````typ\n    #highlight[高亮一段内容]\n    ````,\n  ),\n  (\n    [下划线],\n    refs.writing-markup.with(reference: <grammar-underline>),\n    ````typ\n    #underline[Language]\n    ````,\n  ),\n  (\n    [无驱逐效果的下划线],\n    refs.writing-markup.with(reference: <grammar-underline-evade>),\n    ````typ\n    #underline(\n      evade: false)[ጿኈቼዽ]\n    ````,\n  ),\n  (\n    [上划线],\n    refs.writing-markup.with(reference: <grammar-overline>),\n    ````typ\n    #overline[ጿኈቼዽ]\n    ````,\n  ),\n  (\n    [中划线（删除线）],\n    refs.writing-markup.with(reference: <grammar-strike>),\n    ````typ\n    #strike[ጿኈቼዽ]\n    ````,\n  ),\n  (\n    [下标],\n    refs.writing-markup.with(reference: <grammar-subscript>),\n    ````typ\n    威严满满#sub[抱头蹲防]\n    ````,\n  ),\n  (\n    [上标],\n    refs.writing-markup.with(reference: <grammar-superscript>),\n    ````typ\n    香風とうふ店#super[TM]\n    ````,\n  ),\n  (\n    [设置文本大小],\n    refs.writing-markup.with(reference: <grammar-text-size>),\n    ````typ\n    #text(size: 24pt)[一斤鸭梨]\n    ````,\n  ),\n  (\n    [设置文本颜色],\n    refs.writing-markup.with(reference: <grammar-text-fill>),\n    ````typ\n    #text(fill: blue)[蓝色鸭梨]\n    ````,\n  ),\n  (\n    [设置字体],\n    refs.writing-markup.with(reference: <grammar-text-font>),\n    ````typ\n    #text(font: \"Microsoft YaHei\")[板正鸭梨]\n    ````,\n  ),\n))\n\n== 分类：脚本声明 <grammar-table:decl>\n\n#ref-table((\n  (\n    [进入脚本模式],\n    refs.writing-markup.with(reference: <grammar-enter-script>),\n    ````typ\n    #1\n    ````,\n  ),\n  (\n    [代码块],\n    refs.writing-markup.with(reference: <grammar-code-block>),\n    ````typ\n    #{\"a\"; \"b\"}\n    ````,\n  ),\n  (\n    [内容块],\n    refs.writing-markup.with(reference: <grammar-content-block>),\n    ````typ\n    #[内容块]\n    ````,\n  ),\n  (\n    [空字面量],\n    refs.content-scope-style.with(reference: <grammar-none-literal>),\n    ````typ\n    #none\n    ````,\n  ),\n  (\n    [假（布尔值）],\n    refs.content-scope-style.with(reference: <grammar-false-literal>),\n    ````typ\n    #false\n    ````,\n  ),\n  (\n    [真（布尔值）],\n    refs.content-scope-style.with(reference: <grammar-true-literal>),\n    ````typ\n    #true\n    ````,\n  ),\n  (\n    [整数字面量],\n    refs.content-scope-style.with(reference: <grammar-integer-literal>),\n    ````typ\n    #(-1), #(0), #(1)\n    ````,\n  ),\n  (\n    [进制数字面量],\n    refs.content-scope-style.with(reference: <grammar-n-adecimal-literal>),\n    ````typ\n    #(-0xdeadbeef), #(-0o644), #(-0b1001)\n    ````,\n  ),\n  (\n    [浮点数字面量],\n    refs.content-scope-style.with(reference: <grammar-float-literal>),\n    ````typ\n    #(0.001), #(.1), #(2.)\n    ````,\n  ),\n  (\n    [指数表示法],\n    refs.content-scope-style.with(reference: <grammar-exp-repr-float>),\n    ````typ\n    #(1e2), #(1.926e3), #(-1e-3)\n    ````,\n  ),\n  (\n    [字符串字面量],\n    refs.content-scope-style.with(reference: <grammar-string-literal>),\n    ````typ\n    #\"Hello world!!\"\n    ````,\n  ),\n  (\n    [字符串转义序列],\n    refs.scripting-base.with(reference: <grammar-str-escape-sequences>),\n    ````typ\n    #\"\\\"\"\n    ````,\n  ),\n  (\n    [字符串的Unicode转义序列],\n    refs.scripting-base.with(reference: <grammar-str-unicode-escape-sequences>),\n    ````typ\n    #\"\\u{9999}\"\n    ````,\n  ),\n  (\n    [数组字面量],\n    refs.content-scope-style.with(reference: <grammar-array-literal>),\n    ````typ\n    #(1, \"OvO\", [一段内容])\n    ````,\n  ),\n  (\n    [字典字面量],\n    refs.content-scope-style.with(reference: <grammar-dict-literal>),\n    ````typ\n    #(neko-mimi: 2, \"utterance\": \"喵喵喵\")\n    ````,\n  ),\n  (\n    [空数组],\n    refs.content-scope-style.with(reference: <grammar-empty-array>),\n    ````typ\n    #()\n    ````,\n  ),\n  (\n    [空字典],\n    refs.content-scope-style.with(reference: <grammar-empty-dict>),\n    ````typ\n    #(:)\n    ````,\n  ),\n  (\n    [被括号包裹的空数组],\n    refs.content-scope-style.with(reference: <grammar-paren-empty-array>),\n    ````typ\n    #(())\n    ````,\n  ),\n  (\n    [含有一个元素的数组],\n    refs.content-scope-style.with(reference: <grammar-single-member-array>),\n    ````typ\n    #(1,)\n    ````,\n  ),\n  (\n    [变量声明],\n    refs.content-scope-style.with(reference: <grammar-var-decl>),\n    ````typ\n    #let x = 1\n    ````,\n  ),\n  (\n    [函数声明],\n    refs.content-scope-style.with(reference: <grammar-func-decl>),\n    ````typ\n    #let f(x) = x * 2\n    ````,\n  ),\n  (\n    [函数闭包],\n    refs.content-scope-style.with(reference: <grammar-closure>),\n    ````typ\n    #let f = (x, y) => x + y\n    ````,\n  ),\n  (\n    [具名参数声明],\n    refs.content-scope-style.with(reference: <grammar-named-param>),\n    ````typ\n    #let g(named: none) = named\n    ````,\n  ),\n  (\n    [含变参函数],\n    refs.content-scope-style.with(reference: <grammar-variadic-param>),\n    ````typ\n    #let g(..args) = args.pos().join([、])\n    ````,\n  ),\n  (\n    [数组解构赋值],\n    refs.content-scope-style.with(reference: <grammar-destruct-array>),\n    ````typ\n    #let (one, hello-world) = (1, \"Hello, World\")\n    ````,\n  ),\n  (\n    [数组解构赋值情形二],\n    refs.content-scope-style.with(reference: <grammar-destruct-array-eliminate>),\n    ````typ\n    #let (_, second, ..) = (1, \"Hello, World\", []); #second\n    ````,\n  ),\n  (\n    [字典解构赋值],\n    refs.content-scope-style.with(reference: <grammar-destruct-dict>),\n    ````typ\n    #let (neko-mimi: mimi) = (neko-mimi: 2); #mimi\n    ````,\n  ),\n  (\n    [数组内容重映射],\n    refs.content-scope-style.with(reference: <grammar-array-remapping>),\n    ````typ\n    #let (a, b, c) = (1, 2, 3)\n    #let (b, c, a) = (a, b, c)\n    #a, #b, #c\n    ````,\n  ),\n  (\n    [数组内容交换],\n    refs.content-scope-style.with(reference: <grammar-array-swap>),\n    ````typ\n    #let (a, b) = (1, 2)\n    #((a, b) = (b, a))\n    #a, #b\n    ````,\n  ),\n  (\n    [占位符（`let _ = ..`）],\n    refs.content-scope-style.with(reference: <grammar-placeholder>),\n    ````typ\n    #let last-two(t) = {\n      let _ = t.pop()\n      t.pop()\n    }\n    #last-two((1, 2, 3, 4))\n    ````,\n  ),\n))\n\n== 分类：脚本语句\n\n#ref-table((\n  (\n    [`if`语句],\n    refs.content-scope-style.with(reference: <grammar-if>),\n    ````typ\n    #if true { 1 },\n    #if false { 1 } else { 0 }\n    ````,\n  ),\n  (\n    [串联`if`语句],\n    refs.content-scope-style.with(reference: <grammar-if-if>),\n    ````typ\n    #if false { 0 } else if true { 1 },\n    #if false { 2 } else if false { 1 } else { 0 }\n    ````,\n  ),\n  (\n    [`while`语句],\n    refs.content-scope-style.with(reference: <grammar-while>),\n    ````typ\n    #{\n      let i = 0;\n      while i < 10 {\n        (i * 2, )\n        i += 1;\n      }\n    }\n    ````,\n  ),\n  (\n    [`for`语句],\n    refs.content-scope-style.with(reference: <grammar-for>),\n    ````typ\n    #for i in range(10) {\n      (i * 2, )\n    }\n    ````,\n  ),\n  (\n    [`for`语句解构赋值],\n    refs.content-scope-style.with(reference: <grammar-for-destruct>),\n    ````typ\n    #for (特色, 这个) in (neko-mimi: 2) [猫猫的 #特色 是 #这个\\ ]\n    ````,\n  ),\n  (\n    [`break`语句],\n    refs.content-scope-style.with(reference: <grammar-break>),\n    ````typ\n    #for i in range(10) { (i, ); (i + 1926, ); break }\n    ````,\n  ),\n  (\n    [`continue`语句],\n    refs.content-scope-style.with(reference: <grammar-continue>),\n    ````typ\n    #for i in range(10) {\n      if calc.even(i) { continue }\n      (i, )\n    }\n    ````,\n  ),\n  (\n    [`return`语句],\n    refs.content-scope-style.with(reference: <grammar-return>),\n    ````typ\n    #let never(..args) = return\n    #type(never(1, 2))\n    ````,\n  ),\n  (\n    [`include`语句],\n    refs.content-scope-style.with(reference: <grammar-include>),\n    ````typ\n    #include \"other-file.typ\"\n    ````,\n  ),\n))\n\n== 分类：脚本样式\n\n#ref-table((\n  (\n    [`set`语句],\n    refs.content-scope-style.with(reference: <grammar-set>),\n    ````typ\n    #set text(size: 24pt)\n    四斤鸭梨\n    ````,\n  ),\n  (\n    [作用域],\n    refs.content-scope-style.with(reference: <grammar-scope>),\n    ````typ\n    两只#[兔#set text(fill: rgb(\"#ffd1dc\").darken(15%))\n      #[兔白#set text(fill: orange)\n      又白]，真可爱\n    ]\n    ````,\n  ),\n  (\n    [`set if`语句],\n    refs.content-scope-style.with(reference: <grammar-set-if>),\n    ````typ\n    #let is-dark-theme = true\n    #set rect(fill: black) if is-dark-theme\n    #set text(fill: white) if is-dark-theme\n    #rect([wink!])\n    ````,\n  ),\n  (\n    [`show set`语句],\n    refs.content-scope-style.with(reference: <grammar-show-set>),\n    ````typ\n    #show: set text(fill: blue)\n    wink!\n    ````,\n  ),\n  (\n    [`show`语句],\n    refs.content-scope-style.with(reference: <grammar-show>),\n    ````typ\n    #show raw: it => it.lines.at(1)\n    获取代码片段第二行内容：```typ\n    #{\n    set text(fill: true)\n    }\n    ```\n    ````,\n  ),\n  (\n    [文本选择器],\n    refs.content-scope-style.with(reference: <grammar-text-selector>),\n    ````typ\n    #show \"cpp\": strong(emph(box(\"C++\")))\n    在古代，cpp是一门常用语言。\n    ````,\n  ),\n  (\n    [正则文本选择器],\n    refs.content-scope-style.with(reference: <grammar-regex-selector>),\n    ````typ\n    #show regex(\"[”。]+\"): it => {\n      set text(font: \"KaiTi\")\n      highlight(it, fill: yellow)\n    }\n    “无名，万物之始也；有名，万物之母也。”\n    ````,\n  ),\n  (\n    [标签选择器],\n    refs.content-scope-style.with(reference: <grammar-label-selector>),\n    ````typ\n    #show <一整段话>: set text(fill: blue)\n    #[$lambda$语言是世界上最好的语言。] <一整段话>\n\n    另一段话。\n    ````,\n  ),\n  (\n    [选择器表达式],\n    refs.content-scope-style.with(reference: <grammar-selector-exp>),\n    ````typ\n    #show heading.where(level: 2): set text(fill: blue)\n    = 一级标题\n    == 二级标题\n    ````,\n  ),\n  (\n    [获取位置],\n    refs.content-scope-style.with(reference: <grammar-here>),\n    ````typ\n    #context here().position()\n    ````,\n  ),\n  (\n    [检测当前页面是否为偶数页（位置相关计算）],\n    refs.content-scope-style.with(reference: <grammar-here-calc>),\n    ````typ\n    #context [ 页码是偶数：#calc.even(here().page()) ]\n    ````,\n  ),\n  (\n    [查询文档内容],\n    refs.content-scope-style.with(reference: <grammar-query>),\n    ````typ\n    #context query(<ref-internal-link>).at(0).body\n    ````,\n  ),\n  (\n    [声明全局状态],\n    refs.content-scope-style.with(reference: <grammar-state>),\n    ````typ\n    #state(\"my-state\", 1)\n    ````,\n  ),\n))\n\n== 分类：脚本表达式\n\n#ref-table((\n  (\n    [函数调用],\n    refs.writing-markup.with(reference: <grammar-func-call>),\n    ````typ\n    #calc.pow(4, 3)\n    ````,\n  ),\n  (\n    [函数调用传递内容参数],\n    refs.writing-markup.with(reference: <grammar-content-param>),\n    ````typ\n    #emph[emphasis]\n    ````,\n  ),\n  (\n    [成员访问],\n    refs.content-scope-style.with(reference: <grammar-member-exp>),\n    ````typ\n    #`OvO`.text\n    ````,\n  ),\n  (\n    [方法调用],\n    refs.content-scope-style.with(reference: <grammar-method-exp>),\n    ````typ\n    #\"Hello World\".split(\" \")\n    ````,\n  ),\n  (\n    [字典成员访问],\n    refs.content-scope-style.with(reference: <grammar-dict-member-exp>),\n    ````typ\n    #let cat = (neko-mimi: 2)\n    #cat.neko-mimi\n    ````,\n  ),\n  (\n    [内容成员访问],\n    refs.content-scope-style.with(reference: <grammar-content-member-exp>),\n    ````typ\n    #`OvO`.text\n    ````,\n  ),\n  (\n    [代码表示的自省函数],\n    refs.content-scope-style.with(reference: <grammar-repr>),\n    ````typ\n    #repr[ 一段文本 ]\n    ````,\n  ),\n  (\n    [类型的自省函数],\n    refs.content-scope-style.with(reference: <grammar-type>),\n    ````typ\n    #type[一段文本]\n    ````,\n  ),\n  (\n    [求值函数],\n    refs.content-scope-style.with(reference: <grammar-eval>),\n    ````typ\n    #type(eval(\"1\"))\n    ````,\n  ),\n  (\n    [求值函数（标记模式）],\n    refs.content-scope-style.with(reference: <grammar-eval-markup-mode>),\n    ````typ\n    #eval(\"== 一个标题\", mode: \"markup\")\n    ````,\n  ),\n  (\n    [判断数组内容],\n    refs.content-scope-style.with(reference: <grammar-array-in>),\n    ````typ\n    #let pol = (1, \"OvO\", [])\n    #(1 in pol)\n    ````,\n  ),\n  (\n    [判断数组内容不在],\n    refs.content-scope-style.with(reference: <grammar-array-not-in>),\n    ````typ\n    #let pol = (1, \"OvO\", [])\n    #([另一段内容] not in pol)\n    ````,\n  ),\n  (\n    [判断字典内容],\n    refs.content-scope-style.with(reference: <grammar-dict-in>),\n    ````typ\n    #let cat = (neko-mimi: 2)\n    #(\"neko-mimi\" in cat)\n    ````,\n  ),\n  (\n    [逻辑比较表达式],\n    refs.content-scope-style.with(reference: <grammar-logical-cmp-exp>),\n    ````typ\n    #(1 < 0), #(1 >= 2),\n    #(1 == 2), #(1 != 2)\n    ````,\n  ),\n  (\n    [逻辑运算表达式],\n    refs.content-scope-style.with(reference: <grammar-logical-calc-exp>),\n    ````typ\n    #(not false), #(false or true), #(true and false)\n    ````,\n  ),\n  (\n    [取正运算],\n    refs.content-scope-style.with(reference: <grammar-plus-exp>),\n    ````typ\n    #(+1), #(+0), #(1), #(++1)\n    ````,\n  ),\n  (\n    [取负运算],\n    refs.content-scope-style.with(reference: <grammar-minus-exp>),\n    ````typ\n    #(-1), #(-0), #(--1),\n    #(-+-1)\n    ````,\n  ),\n  (\n    [算术运算表达式],\n    refs.content-scope-style.with(reference: <grammar-arith-exp>),\n    ````typ\n    #(1 + 1), #(1 + -1),\n    #(1 - 1), #(1 - -1)\n    ````,\n  ),\n  (\n    [赋值表达式],\n    refs.content-scope-style.with(reference: <grammar-assign-exp>),\n    ````typ\n    #let a = 1; #repr(a = 10), #a, #repr(a += 2), #a\n    ````,\n  ),\n  (\n    [字符串连接],\n    refs.content-scope-style.with(reference: <grammar-string-concat-exp>),\n    ````typ\n    #(\"a\" + \"b\")\n    ````,\n  ),\n  (\n    [重复连接字符串],\n    refs.content-scope-style.with(reference: <grammar-string-mul-exp>),\n    ````typ\n    #(\"a\" * 4), #(4 * \"ab\")\n    ````,\n  ),\n  (\n    [字符串比较],\n    refs.content-scope-style.with(reference: <grammar-string-cmp-exp>),\n    ````typ\n    #(\"a\" == \"b\"), #(\"a\" != \"b\"), #(\"a\" < \"ab\"), #(\"a\" >= \"a\")\n    ````,\n  ),\n  (\n    [整数转浮点数],\n    refs.content-scope-style.with(reference: <grammar-int-to-float>),\n    ````typ\n    #float(1), #(type(float(1)))\n    ````,\n  ),\n  (\n    [布尔值转整数],\n    refs.content-scope-style.with(reference: <grammar-bool-to-int>),\n    ````typ\n    #int(true), #(type(int(true)))\n    ````,\n  ),\n  (\n    [浮点数转整数],\n    refs.content-scope-style.with(reference: <grammar-float-to-int>),\n    ````typ\n    #int(1), #(type(int(1)))\n    ````,\n  ),\n  (\n    [十进制字符串转整数],\n    refs.content-scope-style.with(reference: <grammar-dec-str-to-int>),\n    ````typ\n    #int(\"1\"), #(type(int(\"1\")))\n    ````,\n  ),\n  (\n    [十六进制/八进制/二进制字符串转整数],\n    refs.content-scope-style.with(reference: <grammar-nadec-str-to-int>),\n    ````typ\n    #let safe-to-int(x) = {\n      let res = eval(x)\n      assert(type(res) == int, message: \"should be integer\")\n      res\n    }\n    #safe-to-int(\"0xf\"), #(type(safe-to-int(\"0xf\"))) \\\n    #safe-to-int(\"0o755\"), #(type(safe-to-int(\"0o755\"))) \\\n    #safe-to-int(\"0b1011\"), #(type(safe-to-int(\"0b1011\"))) \\\n    ````,\n  ),\n  (\n    [数字转字符串],\n    refs.content-scope-style.with(reference: <grammar-num-to-str>),\n    ````typ\n    #repr(str(1)),\n    #repr(str(.5))\n    ````,\n  ),\n  (\n    [整数转十六进制字符串],\n    refs.content-scope-style.with(reference: <grammar-int-to-nadec-str>),\n    ````typ\n    #str(501, base:16), #str(0xdeadbeef, base:36)\n    ````,\n  ),\n  (\n    [布尔值转字符串],\n    refs.content-scope-style.with(reference: <grammar-bool-to-str>),\n    ````typ\n    #repr(false)\n    ````,\n  ),\n  (\n    [数字转布尔值],\n    refs.content-scope-style.with(reference: <grammar-int-to-bool>),\n    ````typ\n    #let to-bool(x) = x != 0\n    #repr(to-bool(0)),\n    #repr(to-bool(1))\n    ````,\n  ),\n))\n"
  },
  {
    "path": "src/tutorial/reference-layout.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：布局函数])\n\n已经移至《度量与布局》。\n"
  },
  {
    "path": "src/tutorial/reference-length.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：长度单位])\n\n已经移至《度量与布局》。\n"
  },
  {
    "path": "src/tutorial/reference-math-mode.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：数学模式])\n"
  },
  {
    "path": "src/tutorial/reference-math-symbols.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [参考：常用数学符号])\n\n#set table(\n  stroke: none,\n  align: horizon + left,\n  row-gutter: 0.45em,\n)\n\n推荐阅读：\n+ #link(\"https://github.com/johanvx/typst-undergradmath/releases/download/v1.4.0/undergradmath.pdf\")[本科生Typst数学英文版，适用于Typst 0.11.1]\n+ #link(\"https://github.com/brynne8/typst-undergradmath-zh/blob/6a1028bc13c525f29f6445e92fc6af3246b3c3cb/undergradmath.pdf\")[本科生Typst数学中文版，适用于Typst 0.9.0]\n\n以下表格列出了*数学模式*中的常用符号。\n\n#figure(\n  table(\n    columns: 8,\n    [$hat(a)$], [`hat(a)`], [$caron(a)$], [`caron(a)`], [$tilde(a)$], [`tilde(a)`], [$grave(a)$], [`grave(a)`],\n    [$dot(a)$],\n    [`dot(a)`],\n    [$dot.double(a)$],\n    [dot.double(a)],\n    [$macron(a)$],\n    [`macron(a)`],\n    [$arrow(a)$],\n    [`arrow(a)`],\n\n    [$acute(a)$], [`acute(a)`], [$breve(a)$], [`breve(a)`],\n  ),\n  caption: [数学模式重音符号],\n)\n\n#figure(\n  table(\n    columns: 8,\n    [$alpha$], [`alpha`], [$theta$], [`theta`], [$o$], [`o`], [$upsilon$], [`upsilon`],\n    [$beta$], [`beta`], [$theta.alt$], [`theta.alt`], [$pi$], [`pi`], [$phi$], [`phi`],\n    [$gamma$], [`gamma`], [$iota$], [`iota`], [$pi.alt$], [`pi.alt`], [$phi$], [`phi`],\n    [$delta$], [`delta`], [$kappa$], [`kappa`], [$rho$], [`rho`], [$chi$], [`chi`],\n    [$epsilon.alt$], [`epsilon.alt`], [$lambda$], [`lambda`], [$rho.alt$], [`rho.alt`], [$psi$], [`psi`],\n    [$epsilon$], [`epsilon`], [$mu$], [`mu`], [$sigma$], [`sigma`], [$omega$], [`omega`],\n    [$zeta$], [`zeta`], [$nu$], [`nu`], [$sigma.alt$], [`sigma.alt`], [$$], [``],\n    [$eta$], [`eta`], [$xi$], [`xi`], [$tau$], [`tau`], [$$], [``],\n    [$Gamma$], [`Gamma`], [$Lambda$], [`Lambda`], [$Sigma$], [`Sigma`], [$Psi$], [`Psi`],\n    [$Delta$], [`Delta`], [$Xi$], [`Xi`], [$Upsilon$], [`Upsilon`], [$Omega$], [`Omega`],\n    [$Theta$], [`Theta`], [$Pi$], [`Pi`], [$Phi$], [`Phi`],\n  ),\n  caption: [希腊字母],\n)\n\n#figure(\n  table(\n    columns: 6,\n    [$<$], [`<, lt`], [$>$], [`>, gt`], [$=$], [`=`],\n    [$<=$], [`<=, lt.eq`], [$>=$], [`>=, gt.eq`], [$equiv$], [`equiv`],\n    [$<<$], [`<<, lt.double`], [$>>$], [`>>, gt.double`], [$$], [``],\n    [$prec$], [`prec`], [$succ$], [`succ`], [$tilde$], [`tilde`],\n    [$prec.eq$], [`prec.eq`], [$succ.eq$], [`succ.eq`], [$tilde.eq$], [`tilde.eq`],\n    [$subset$], [`subset`], [$supset$], [`supset`], [$approx$], [`approx`],\n    [$subset.eq$], [`subset.eq`], [$supset.eq$], [`supset.eq`], [$tilde.equiv$], [`tilde.equiv`],\n    [$subset.sq$], [`subset.sq`], [$supset.sq$], [`supset.sq`], [$join$], [`join`],\n    [$subset.eq.sq$], [`subset.eq.sq`], [$supset.eq.sq$], [`supset.eq.sq`], [$$], [``],\n    [$in$], [`in`], [$in.rev$], [`in.rev`], [$prop$], [`prop`],\n    [$tack.r$], [`tack.r`], [$tack.l$], [`tack.l`], [$tack.r.double$], [`tack.r.double`],\n    [$divides$], [`divides`], [$parallel$], [`parallel`], [$tack.t$], [`tack.t`],\n    [$:$], [`:`], [$in.not$], [`in.not`], [$!=$], [`!=, eq.not`],\n  ),\n  caption: [二元关系],\n)\n\n#figure(\n  table(\n    columns: 6,\n    [$+$], [`+, plus`], [$-$], [`-, minus`], [$$], [``],\n    [$plus.minus$], [`plus.minus`], [$minus.plus$], [`minus.plus`], [$lt.tri$], [`lt.tri`],\n    [$dot$], [`dot`], [$div$], [`div`], [$gt.tri$], [`gt.tri`],\n    [$times$], [`times`], [$without$], [`without`], [$star$], [`star`],\n    [$union$], [`union`], [$inter$], [`inter`], [$*$], [`*`],\n    [$union.sq$], [`union.sq`], [$inter.sq$], [`inter.sq`], [$circle.stroked.tiny$], [`circle.stroked.tiny`],\n    [$or$], [`or`], [$and$], [`and`], [$bullet$], [`bullet`],\n    [$xor$], [`xor`], [$minus.circle$], [`minus.circle`], [$dot.circle$], [`dot.circle`],\n    [$union.plus$], [`union.plus`], [$times.circle$], [`times.circle`], [$circle.big$], [`circle.big`],\n    [$product.co$],\n    [`product.co`],\n    [$triangle.stroked.t$],\n    [`triangle.stroked.t`],\n    [$triangle.stroked.b$],\n    [`triangle.stroked.b`],\n\n    [$dagger$], [`dagger`], [$lt.tri$], [`lt.tri`], [$gt.tri$], [`gt.tri`],\n    [$dagger.double$], [`dagger.double`], [$lt.tri.eq$], [`lt.tri.eq`], [$gt.tri.eq$], [`gt.tri.eq`],\n    [$wreath$], [`wreath`],\n  ),\n  caption: [二元运算符],\n)\n\n#figure(\n  table(\n    columns: 6,\n    [$sum$], [`sum`], [$union.big$], [`union.big`], [$or.big$], [`or.big`],\n    [$product$], [`product`], [$inter.big$], [`inter.big`], [$and.big$], [`and.big`],\n    [$product.co$], [`product.co`], [$union.sq.big$], [`union.sq.big`], [$union.plus.big$], [`union.plus.big`],\n    [$integral$], [`integral`], [$integral.cont$], [`integral.cont`], [$dot.circle.big$], [`dot.circle.big`],\n    [$plus.circle.big$], [`plus.circle.big`], [$times.circle.big$], [`times.circle.big`],\n  ),\n  caption: [「大」运算符],\n)\n\n#figure(\n  table(\n    columns: 4,\n    [$arrow.l$], [`arrow.l`], [$arrow.l.long$], [`arrow.l.long`],\n    [$arrow.r$], [`arrow.r`], [$arrow.r.long$], [`arrow.r.long`],\n    [$arrow.l.r$], [`arrow.l.r`], [$arrow.l.r.long$], [`arrow.l.r.long`],\n    [$arrow.l.double$], [`arrow.l.double`], [$arrow.l.double.long$], [`arrow.l.double.long`],\n    [$arrow.r.double$], [`arrow.r.double`], [$arrow.r.double.long$], [`arrow.r.double.long`],\n    [$arrow.l.r.double$], [`arrow.l.r.double`], [$arrow.l.r.double.long$], [`arrow.l.r.double.long`],\n    [$arrow.r.bar$], [`arrow.r.bar`], [$arrow.r.long.bar$], [`arrow.r.long.bar`],\n    [$arrow.l.hook$], [`arrow.l.hook`], [$arrow.r.hook$], [`arrow.r.hook`],\n    [$harpoon.lt$], [`harpoon.lt`], [$harpoon.rt$], [`harpoon.rt`],\n    [$harpoon.lb$], [`harpoon.lb`], [$harpoon.rb$], [`harpoon.rb`],\n    [$harpoons.ltrb$], [`harpoons.ltrb`], [$arrow.t$], [`arrow.t`],\n    [$arrow.b$], [`arrow.b`], [$arrow.t.b$], [`arrow.t.b`],\n    [$arrow.t.double$], [`arrow.t.double`], [$arrow.b.double$], [`arrow.b.double`],\n    [$arrow.t.b.double$], [`arrow.t.b.double`], [$arrow.tr$], [`arrow.tr`],\n    [$arrow.br$], [`arrow.br`], [$arrow.bl$], [`arrow.bl`],\n    [$arrow.tl$], [`arrow.tl`], [$arrow.r.squiggly$], [`arrow.r.squiggly`],\n  ),\n  caption: [箭头],\n)\n\n#figure(\n  table(\n    columns: 6,\n    [$dots$], [`dots`], [$dots.c$], [`dots.c`], [$dots.v$], [`dots.v`],\n    [$dots.down$], [`dots.down`], [$planck.reduce$], [`planck.reduce`], [$dotless.i$], [`dotless.i`],\n    [$dotless.j$], [`dotless.j`], [$ell$], [`ell`], [$Re$], [`Re`],\n    [$Im$], [`Im`], [$aleph$], [`aleph`], [$forall$], [`forall`],\n    [$exists$], [`exists`], [$Omega.inv$], [`Omega.inv`], [$partial$], [`partial`],\n    [$prime$], [`', prime`], [$emptyset$], [`emptyset`], [$infinity$], [`infinity`],\n    [$nabla$], [`nabla`], [$triangle.stroked.t$], [`triangle.stroked.t`], [$square.stroked$], [`square.stroked`],\n    [$diamond.stroked$], [`diamond.stroked`], [$bot$], [`bot`], [$top$], [`top`],\n    [$angle$], [`angle`], [$suit.club$], [`suit.club`], [$suit.spade$], [`suit.spade`],\n    [$not$], [`not`],\n  ),\n  caption: [其他符号],\n)\n"
  },
  {
    "path": "src/tutorial/reference-outline.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：文档大纲])\n"
  },
  {
    "path": "src/tutorial/reference-syntax-analysis.typ",
    "content": "\n== variable identifier\n\n// `let`关键字后紧接着跟随一个#term(\"variable identifier\")。标识符是变量的“名称”。「变量声明」后续的位置都可以通过标识符引用该变量。\n\n// + 标识符以Unicode字母、Unicode数字和#mark(\"_\")开头。以下是示例的合法变量名：\n//   ```typ\n//   // 以英文字母开头，是Unicode字母\n//   #let a; #let z; #let A; #let Z;\n//   // 以汉字开头，是Unicode字母\n//   #let 这;\n//   // 以下划线开头\n//   #let _;\n//   ```\n// + 标识符后接有限个Unicode字母、Unicode数字、#mark(\"hyphen\")和#mark(\"_\")。以下是示例的合法变量名：\n//   ```typ\n//   // 纯英文变量名，带连字号\n//   #let alpha-test; #let alpha--test;\n//   // 纯中文变量名\n//   #let 这个变量; #let 这个_变量; #let 这个-变量;\n//   // 连字号、下划线在多种位置\n//   #let alpha-; // 连字号不能在变量名开头位置\n//   #let _alpha; #let alpha_;\n//   ```\n// + 特殊规则：标识符仅为#mark(\"_\")时，不允许在后续位置继续使用。\n//   #code(```typ\n//   #let _ = 1;\n//   // 不能编译：#_\n//   ```)\n//   该标识符被称为#term(\"placeholder\")。\n// + 特殊规则：标识符不允许为`let`、`set`、`show`等关键字。\n//   #code(```typ\n//   // 不能编译：\n//   // #let let = 1;\n//   ```)\n\n// 建议标识符简短且具有描述性。同时建议标识符中仅含英文与#mark(\"hyphen\")。\n\n== let statement\n\n// 「变量声明」可以没有初始值表达式：\n\n// #code(```typ\n// #let x\n// #repr(x)\n// ```)\n\n// 事实上，它等价于将`x`初始化为`none`。\n\n// #code(```typ\n// #let x = none\n// #repr(x)\n// ```)\n\n// 尽管Typst允许你不写初始值表达式，本书还是建议你让所有的「变量声明」都具有初始值表达式。因为初始值表达式还告诉阅读你代码的人这个变量可能具有什么样的类型。\n\n== expression\n\n// === 逻辑比较表达式 <grammar-logical-cmp-exp>\n\n// 数字之间可以相互（算术逻辑）比较，并产生布尔类型的表达式：\n\n// - 小于：\n//   #code(```typ\n//   #(1 < 0), #(1 < 1), #(1 < 2)\n//   ```)\n// - 小于等于：\n//   #code(```typ\n//   #(1 <= 0), #(1 <= 1), #(1 <= 2)\n//   ```)\n// - 大于：\n//   #code(```typ\n//   #(1 > 0), #(1 > 1), #(1 > 2)\n//   ```)\n// - 大于等于：\n//   #code(```typ\n//   #(1 >= 0), #(1 >= 1), #(1 >= 2)\n//   ```)\n// - 等于：\n//   #code(```typ\n//   #(1 == 0), #(1 == 1), #(1 == 2)\n//   ```)\n// - 不等于：\n//   #code(```typ\n//   #(1 != 0), #(1 != 1), #(1 != 2)\n//   ```)\n\n// 不仅整数与整数之间、浮点数与浮点数之间可以做比较，而且整数与浮点数之间也可以做比较。当整数与浮点数相互比较时，整数会转换为浮点数再参与比较。\n// #code(```typ\n// #(1 != 0.), #(1 != 1.), #(1 != 2.)\n// ```)\n\n// === 算术运算表达式 <grammar-arith-exp>\n\n// 数字之间可以做算术运算，并产生数字结果的表达式：\n\n// - 取正运算：<grammar-plus-exp>\n//   #code(```typ\n//   #(+1), #(+0), #(++1)\n//   ```)\n// - 取负运算：<grammar-minus-exp>\n//   #code(```typ\n//   #(-1), #(-0), #(--1), #(-+-1)\n//   ```)\n// - 加运算：\n//   #code(```typ\n//   #(1 + 2), #(1 + 1), #(1 + -1), #(1 + -2)\n//   ```)\n// - 减运算：\n//   #code(```typ\n//   #(1 - 2), #(1 - 1), #(1 - -1), #(1 - -2)\n//   ```)\n// - 乘运算：\n//   #code(```typ\n//   #(1 * 2), #(2 * 2), #(2 * -2)\n//   ```)\n// - 除运算：\n//   #code(```typ\n//   #(1 / 2), #(2 / 2), #(2 / -2)\n//   ```)\n\n// 值得注意的是$-2^63$在Typst中是浮点数，这可能是Typst的bug：\n\n// #code(```typ\n// #type(-9223372036854775808)\n// ```)\n\n// 为了正常使用该整数值你需要强制转换：\n\n// #code(```typ\n// #int(-9223372036854775808), #type(int(-9223372036854775808))\n// ```)\n\n// 在日常生活中，我们还常用整除，如下方法实现了整除：\n\n// #code(```typ\n// #let fdiv(x, y) = int(x / y)\n// #fdiv(3, 2), #fdiv(-12, 2), #fdiv(-12, 5) \\\n// // 或\n// #int(3 / 2), #int(-12 / 2), #int(-12 / 5)\n// ```)\n\n// #pro-tip[\n//   `calc.rem`函数帮你求解整除的余数：\n//   #code(```typ\n//   #let fdiv(x, y) = int(x / y)\n//   $ 3 div & 2 = & #fdiv(  3, 2) & ...... & #calc.rem(  3, 2). \\\n//   -12 div & 2 = & #fdiv(-12, 2) & ...... & #calc.rem(-12, 2). \\\n//   -12 div & 5 = & #fdiv(-12, 5) & ...... & #calc.rem(-12, 5). $\n//   ```)\n// ]\n\n// === 赋值表达式 <grammar-assign-exp>\n\n// 变量可以被赋予一个表达式的值，所有的赋值表达式都产生`none`值而非返回变量的值。\n\n// - 赋值及先加（减、乘或除）后赋值：\n//   #code(```typ\n//   #let a = 1\n//   #repr(a = 10), #a, #repr(a += 2), #a, #repr(a -= 2), #a, #repr(a *= 2), #a, #repr(a /= 2), #a\n//   ```)\n\n// === 字符串相关的表达式\n\n// 字符串相加表示字符串的连接：<grammar-string-concat-exp>\n\n// #code(```typ\n// #(\"a\" + \"b\")\n// ```)\n\n// 字符串与数字相乘表示将该字符串重复$n$次后再连接：<grammar-string-mul-exp>\n\n// #code(```typ\n// #(\"a\" * 4), #(4 * \"ab\")\n// ```)\n\n// 字符串之间的比较遵从#link(\"https://en.wikipedia.org/wiki/Lexicographic_order\")[#term(\"lexicographical order\")]。<grammar-string-cmp-exp>\n\n// 等于和不等于的比较，比较每个字符是否相等：\n\n// #code(```typ\n// #(\"a\" == \"a\"), #(\"a\" != \"a\") \\\n// #(\"a\" == \"b\"), #(\"a\" != \"b\") \\\n// ```)\n\n// 大于和小于的比较，从第一个字符开始依次比较，比较每个字符是否相等。直到第一个不相等的字符时作以下判断，字符的Unicode值较小的字符串则x相应更“小”：\n\n// #code(```typ\n// #(\"a\" < \"b\"), #(\"a\" > \"a\"),  \\\n// #(\"a\" < \"ba\"), #(\"ac\" < \"ba\"), #(\"aac\" < \"aba\")\n// ```)\n\n// 若一直比到了其中一个字符串的尽头，则较短的字符串更“小”：\n\n// #code(```typ\n// #(\"a\" < \"ab\")\n// ```)\n\n// 大于等于和小于等于的比较则将「相等性」纳入考虑：\n\n// #code(```typ\n// #(\"a\" >= \"a\"), #(\"a\" <= \"a\") \\\n// ```)\n"
  },
  {
    "path": "src/tutorial/reference-table.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：表格])\n"
  },
  {
    "path": "src/tutorial/reference-type-builtin.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：内置类型])\n\n== 空类型 <reference-type-none>\n\n== type <reference-type-type>\n\n== content <reference-type-content>\n\n== function <reference-type-function>\n\n== arguments <reference-type-arguments>\n\n== module <reference-type-module>\n\n== plugin <reference-type-plugin>\n\n== regex <reference-type-regex>\n\n== label <reference-type-label>\n\n== reference <reference-type-reference>\n\n== selector <reference-type-selector>\n\n== version <reference-type-version>\n"
  },
  {
    "path": "src/tutorial/reference-typebase.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：基本类型])\n\n#let glue-block = block.with(breakable: false)\n\n// === 空字面量 <grammar-none-literal>\n\n// 空字面量是纯粹抽象的概念，这意味着你在现实中很难找到对应的实体。就像是数学中的零与负数，空字面量自然产生于运算过程中。\n\n// #code(```typ\n// #repr((0, 1).find((_) => false)),\n// #repr(if false [啊？])\n// ```)\n\n// 上例第一行，当在「数组」中查找一个不存在的元素时，“没有”就是```typc none```。\n\n// 上例第二行，当条件不满足，且没有`false`分支时，“没有内容”就是```typc none```。\n\n// 这些例子都蕴含较复杂的脚本逻辑，会在下一节详细介绍。\n\n// #pro-tip[\n//   空字面量自然产生于运算过程中。以下是所有会产生```typc none```的自然场景：\n//   - 当变量未初始化时。\n//   #code(```typ\n//   #let x; #type(x)\n//   ```)\n//   - 当`if`语句条件不满足，且没有`false`（`else`）分支时，`if`语句的值为```typc none```。\n//   #code(```typ\n//   #type(if false {})\n//   ```)\n//   - 当`for`语句、`while`语句、函数没有产生任何值时，函数返回值为```typc none```。\n//   #code(```typ\n//   #let f() = {}; #type(f())\n//   ```)\n//   - 当函数显式`return`且未写明返回值时，函数返回值为```typc none```。\n//   #code(```typ\n//   #let f() = return; #type(f())\n//   ```)\n// ]\n\n// `none`不产生任何内容：\n\n// #code(```typ\n// #none\n// ```)\n\n// `none`的类型是`none`类型：\n\n// #code(```typ\n// #type(none)\n// ```)\n\n// `none`值不等于`none`类型，因为一个是值而另一个是类型：\n\n// #code(```typ\n// #(type(none) == none), #type(none), #type(type(none))\n// ```)\n\n// // 使用以下方法将`none`转化为布尔类型：\n\n// // #code(```typ\n// // #let some-x = none\n// // #(some-x == none)\n// // #((some-x != none) and (1 < 2))\n// // ```)\n\n// === 布尔字面量\n\n// 一个布尔字面量表示逻辑的确否。它要么为`false`（真）<grammar-true-literal>要么为`true`（假）<grammar-false-literal>。\n\n// #code(```typ\n// 两个值 #false 和 #true 偷偷混入了我们内容之中。\n// ```)\n\n// 一般来说，我们不直接使用布尔值。当代码做逻辑判断的时候，会自然产生布尔值。\n\n// #code(```typ\n// $1 < 2$的结果为：#(1 < 2) \\\n// $1 > 2$的结果为：#(1 > 2)\n// ```)\n\n// 布尔值的类型是布尔类型：\n\n// #code(```typ\n// #type(false), #type(true)\n// ```)\n\n// === 整数字面量 <grammar-integer-literal>\n\n// 一个整数字面量代表一个整数。相信你一定知道整数的含义。Typst中的整数默认为十进制：\n\n// #code(```typ\n// 三个值 #(-1)、#0 和 #1 偷偷混入了我们内容之中。\n// ```)\n\n// #pro-tip[\n//   有的时候Typst不支持在#mark(\"#\")后直接跟一个值。这个时候无论值有多么复杂，都可以将值用一对圆括号包裹起来，从而允许Typst轻松解析该值。例如，Typst无法处理#mark(\"#\")后直接跟随一个#mark(\"hyphen\")的情况：\n\n//   #code(```typ\n//   #(-1), #(0), #(1)\n//   ```)\n// ]\n\n// Typst允许十进制数存在前导零：\n\n// #code(```typ\n// #009009\n// ```)\n\n// 有些数字使用其他进制表示更为方便。你可以分别使用`0x`、`0o`和`0b`前缀加上进制内容表示十六进制数、八进制数和二进制数：<grammar-n-adecimal-literal>\n\n// #code(```typ\n// 十六进制数：#(0xdeadbeef)、#(-0xdeadbeef) \\\n// 八进制数：#(0o755)、#(-0o644) \\\n// 二进制数：#(0b1001)、#(-0b1001)\n// ```)\n\n// 上例中，当数字被输出到文档时，Typst将数字都转换成了十进制表示。\n\n// 以十六进制数为例介绍其与十进制数之间的关系。将一个十六进制数转换成一个十进制数，计算方式是：从十六进制数的低位（右边）开始，逐步向高位（左边），取每位的字符对应十进制数值，与 $16$ 的 $n$ 次幂相乘（这里的 $n$ 不是固定的，它等于该字符所在的位序 $-1$ ，位序指从低位到高位的排序），最后将每位的计算结果相加，即可获得对应十进制的值。\n\n// 十六进制数字与十进制数字对应关系如下：\n\n// #{\n//   set align(center)\n//   table(\n//     columns: 10,\n//     [十六进制],[...],[8],[9],[a/A],[b/B],[c/C],[d/D],[e/E],[f/F],\n//     [十进制],[...],[8],[9],[10],[11],[12],[13],[14],[15]\n//   )\n// }\n\n// 举个例子：十进制的1324相当于十六进制的0x52C。\n\n// + 计算“C”：\n//   因为“C”对应的十进制值是12，它所在的位序是1，即它是右边第1位，所以它的值为：$12 dot (16^(1-1)) = 12$\n// + 计算“2”：\n//   因为“2”对应的十进制值是2，它所在的位序是2，即它是右边第2位，所以它的值为：$2 dot (16^(2-1)) = 32$\n// + 计算“5”：\n//   因为“5”对应的十进制值是5，它所在的位序是3，即它是右边第3位，所以它的值为：$5 dot (16^(3-1)) = 1280$\n// + 将每位结果相加：\n//   $ 12 + 32 + 1280 = 1324 $\n\n// 检验：\n// #code(```typ\n// #1324, #0x52c, #(0x52c == 1324)\n// ```)\n\n// 整数的类型是整数类型：\n\n// #code(```typ\n// #type(0), #type(0x0), #type(0o0), #type(0b0)\n// ```)\n\n// 整数的有效取值范围是$[-2^63,2^63)$，其中$2^63=9223372036854775808$。\n\n// === 浮点数字面量 <grammar-float-literal>\n\n// 浮点数与整数非常类似。最常见的浮点数由至少一个整数部分或小数部分组成：\n\n// #code(```typ\n// 三个值 #(0.001)、#(.1) 和 #(2.) 偷偷混入了我们内容之中。\n// ```)\n\n// 浮点数的类型是浮点数类型：\n\n// #code(```typ\n// #type(2.)\n// ```)\n\n// 可见```typc 2.```与```typc 2```类型并不相同。\n\n// 当数字过大时，其会被隐式转换为浮点数存储：\n\n// #code(```typ\n// #type(9000000000000000000000000000000)\n// ```)\n\n// 整数相除时会被转换为浮点数：\n\n// #code(```typ\n// #(10 / 4), #type(10 / 4) \\\n// #(12 / 4), #type(12 / 4) \\\n// ```)\n\n// 为了转换类型，可以使用`int`，但有可能产生精度损失（就近取整）：\n\n// #code(```typ\n// #int(10 / 4),\n// #int(12 / 4),\n// #int(9000000000000000000000000000000)\n// ```)\n\n// 有些数字使用#term(\"exponential notation\")更为方便。你可以使用标准的#term(\"exponential notation\")创建浮点数：<grammar-exp-repr-float>\n\n// #code(```typ\n// #(1e2)、#(1.926e3)、#(-1e-3)\n// ```)\n\n// Typst还为你内置了一些特殊的数值，它们都是浮点数：\n\n// #code(```typ\n// $pi$=#calc.pi \\\n// $tau$=#calc.tau \\\n// $inf$=#calc.inf \\\n// NaN=#calc.nan \\\n// ```)\n\n// === 字符串字面量 <grammar-string-literal>\n\n// Typst中所有字符串都是`utf-8`编码的，因此使用时不存在编码问题。字符串由一对「英文双引号」定界：\n\n// #code(```typ\n// #\"Hello world!!\"\n// ```)\n\n// 字符串的类型是字符串类型：\n\n// #code(```typ\n// #type(\"Hello world!!\")\n// ```)\n\n// 有些字符无法置于双引号之内，例如双引号本身。这时候你需要嵌入字符的转义序列：\n\n// #code(```typ\n// #\"Hello \\\"world\\\"!!\"\n// ```)\n\n// 字符串中的转义序列与「标记模式」中的转义序列语法相同，但内容不同。Typst允许的字符串中的转义序列如下：\n\n// #{\n//   set align(center)\n//   table(\n//     columns: 7,\n//     [代码], [`\\\\`],[`\\\"`],[`\\n`],[`\\r`],[`\\t`],[`\\u{2665}`],\n//     [效果], \"\\\\\", \"\\\"\", [(换行)], [(回车)], [(制表)], \"\\u{2665}\",\n//   )\n// }\n\n// 与《基础文档》中的转义序列相同，你可以使用`\\u{unicode}`格式直接嵌入Unicode字符。\n\n// #code(```typ\n// #\"香辣牛肉粉好吃\\u{2665}\"\n// ```)\n\n// 除了使用简单字面量构造，可以使用以下方法从代码块获得字符串：\n\n// #code(```typ\n// #repr(`包含换行符和双引号的\n\n// \"内容\"`.text)\n// ```)\n\n== 类型转换\n\n整数转浮点数：<grammar-int-to-float>\n\n#code(```typ\n#float(1), #(type(float(1)))\n```)\n\n布尔值转整数：<grammar-bool-to-int>\n\n#code(```typ\n#int(false), #(type(int(false))) \\\n#int(true), #(type(int(true)))\n```)\n\n浮点数转整数：<grammar-float-to-int>\n\n#code(```typ\n#int(1), #(type(int(1)))\n```)\n\n该方法是就近取整，并有精度损失（根据规范，超出精度范围时，如何选择较近的值舍入是「与实现相关」）：\n\n#code(```typ\n#int(1.5), #int(1.99),\n// 超出浮点精度范围会就近舍入再转换成整数\n#int(1.9999999999999999)\n```)\n\n为了向下或向上取整，你可以同时使用`calc.floor`或`calc.ceil`函数（有精度损失）：\n\n#code(```typ\n#int(calc.floor(1.9)), #int(calc.ceil(1.9))\n```)\n\n十进制字符串转整数：<grammar-dec-str-to-int>\n\n#code(```typ\n#int(\"1\"), #(type(int(\"1\")))\n```)\n\n十六进制/八进制/二进制字符串转整数：<grammar-nadec-str-to-int>\n\n#code(```typ\n#let safe-to-int(x) = {\n  let res = eval(x)\n  assert(type(res) == int, message: \"should be integer\")\n  res\n}\n#safe-to-int(\"0xf\"), #(type(safe-to-int(\"0xf\"))) \\\n#safe-to-int(\"0o755\"), #(type(safe-to-int(\"0o755\"))) \\\n#safe-to-int(\"0b1011\"), #(type(safe-to-int(\"0b1011\"))) \\\n```)\n\n注意：`assert(type(res) == int)`是必须的，否则是不安全的。\n\n数字转字符串：<grammar-num-to-str>\n\n#code(```typ\n#repr(str(1)), #(type(str(1)))\n#repr(str(.5)), #(type(str(.5)))\n```)\n\n整数转 $N$ 进制字符串：<grammar-int-to-nadec-str>\n\n#code(```typ\n#str(501, base:16), #str(0xdeadbeef, base:36)\n```)\n\n布尔值转字符串：<grammar-bool-to-str>\n\n#code(```typ\n#repr(false), #(type(repr(false)))\n```)\n\n数字转布尔值：<grammar-int-to-bool>\n\n#code(```typ\n#let to-bool(x) = x != 0\n#repr(to-bool(0)), #(type(to-bool(0))) \\\n#repr(to-bool(1)), #(type(to-bool(1)))\n```)\n\nTypst的基本类型设计大量参考Python和Rust。Typst基本类型的特点是半纯的API设计。其基本类型的方法倾向于保持纯度，但如果影响到了使用的方便性，Typst会适当牺牲纯度。\n\n如下所示，`array.push`就是带有副作用的：\n\n#code(```typ\n#let t = (1, 2)\n#t.push(3)\n#t\n```)\n\n== 布尔类型 <reference-type-bool>\n\nTypst的布尔值是只有两个实例的类型。#ref-bookmark[`type:bool`]\n\n#code(```\n#false, #true\n```)\n\n布尔值一般用来表示逻辑的确否。\n\n#code(```\n#(1 < 2), #(1 > 2)\n```)\n\n== 整数 <reference-type-int>\n\nTypst的整数是64位宽度的有符号整数。#ref-bookmark[`type:int`]\n\n*有符号*整数的意思是，你可以使用正整数、负整数或零作为整数对象的内容。\n\n#code(```typ\n正数：#1；负数：#(-1)；零：#0。\n```)\n\n*64位宽度*整数的意思是，Typst允许你使用的整数是有限大的。正数、负数与整数均分$2^64$个数字。因此：\n- 最大的正整数是$2^63-1$。\n\n  #code(```typ\n  #int(9223372036854775807)\n  ```)\n- 最小的负整数是$-2^63$。（todo: typst v0.12.0这里有一个bug）\n\n  #code(```typ\n  #int(-9223372036854775807-1)\n  ```)\n\nTypst还允许你使用其他#term(\"representation\")表示整数。借鉴了其他编程语言，使用`0x`、`0o`和`0b`分别可以指定十六进制、八进制和二进制整数：\n\n#code(```typ\n#0xffff, #0o755, #0b101010\n```)\n\n#ref-cons-signature(\"int\")\n\n除了#term(\"literal\")构造整数，#ref-bookmark[`func:int`]你还能使用构造器#typst-func(\"int\")将其他值转换为整数。\n\n- 将布尔值转换为$0$或$1$：\n  #code(```typ\n  #int(false), #int(true)\n  ```)\n- 将浮点数向零取整为整数：\n  #code(```typ\n  #int(-1.1), #int(1.1), #int(-0.1), #int(-1.9)\n  ```)\n- 将字符串依照十进制表示转换为整数：\n  #code(```typ\n  #int(\"42\"), #int(\"0\"), #int(\"-17\")\n  ```)\n\n你可以使用各种操作符进行整数运算：\n\n#code(```typ\n#(1 + 2) \\\n#(2 - 5) \\\n#(3 + 4 < 8)\n```)\n\n详见#(refs.scripting-base)[《基本字面量、变量和简单函数》]中的表达式类型。\n\n整数类型上没有任何方法，但是Typst正计划将`calc`上的方法移动到整数类型和浮点数类型上。\n\n== 浮点数 <reference-type-float>\n\nTypst的浮点数是64位宽度的有符号浮点数。#ref-bookmark[`type:float`]\n\n*有符号*浮点数的意思是，你可以使用正数、负数或零作为浮点数数对象的内容。*64位宽度*浮点数的意思是，Typst允许你使用的浮点数是有限大的。正数、负数与浮点数均分$2^64$个浮点数。\n\nTypst的浮点数遵守#link(\"https://standards.ieee.org/ieee/754/6210/\")[IEEE-754浮点数标准]。因此：\n- 最大的正数为 $f_max = 2^1023 dot (2 - 2^(-52))$。\n- 最小的正数为 $f_min = 2^(-1022) dot 2^(-52)$。\n- 最大的负数为 $-f_min$。\n- 最小的负数为 $-f_max$。\n\nTypst还允许你使用#term(\"exponential notation\")表示浮点数。Typst允许你使用`{x}e{y}`格式表示$x dot 10^y$的浮点数：\n\n#code(```typ\n#1e1, #1e-1, #(-1e1), #(-1e-1), #(1.1e1)\n```)\n\n#ref-cons-signature(\"float\")\n\n除了#term(\"literal\")构造浮点数，#ref-bookmark[`func:float`]你还能使用构造器#typst-func(\"float\")将其他值转换为浮点数。\n\n- 将布尔值转换为$0.0$或$1.0$：\n  #code(```typ\n  #float(false), #float(true)\n  ```)\n- 将整数转换成#term(\"nearest\")的浮点数：\n  #code(```typ\n  #float(0), #float(-0), #float(10000000000)\n  ```)\n- 将百分比数字转换成#term(\"nearest\")的浮点数：\n  #code(```typ\n  #float(100%), #float(1150%), #float(1%)\n  ```)\n- 将字符串依照十进制或#term(\"exponential notation\")表示转换为浮点数：\n  #code(```typ\n  #float(\"42.2\"), #float(\"0\"), #float(\"-17\"), #float(\"1e5\")\n  ```)\n\n你可以使用各种操作符进行浮点数运算，且浮点数允许与整数混合运算：\n\n#code(```typ\n#(10 / 4) \\\n#(3.5 + 4.6 < 8)\n```)\n\n详见#(refs.scripting-base)[《基本字面量、变量和简单函数》]中的表达式类型。\n\n浮点数类型上没有任何方法，但是Typst正计划将`calc`上的方法移动到整数类型和浮点数类型上。\n\n== 字符串 <reference-type-str>\n\nTypst的字符串是#term(\"utf-8 encoding\")的字节序列。#ref-bookmark[`type:str`]\n\n#link(\"https://en.wikipedia.org/wiki/UTF-8\", \"UTF-8编码\")是一个广泛使用的变长编码规范。这意味着当你使用“abc123”字符串时，实际存储了这样的字节数据：\n\n#code(```typ\n#let f(x) = range(x.len()).map(\n  i => x.at(i))\n#f(bytes(\"abc123\"))\n```)\n\n这意味着当你使用“ふ店”字符串时，实际存储了这样的字节数据：\n\n#code(```typ\n#let f(x) = range(x.len()).map(\n  i => x.at(i))\n#f(bytes(\"ふ店\"))\n```)\n\n在绝大部分情况下，你不需要关心字符串的编码问题，因为在Typst脚本中只有#term(\"utf-8 encoding\")的字符串。\n\n但是这不意味着你不需要了解#term(\"utf-8 encoding\")。Typst的字符串API与底层编码息息相关，因此你需要尽可能多地掌握与#term(\"utf-8 encoding\")有关的知识。\n\n在编码体系中，与Typst相关的主要有三层，它们在原始字节数据视角下的边界并不一样：\n\n+ 字节表示：按字节寻址时，每个偏移量都索引到一个字节。\n+ #term(\"codepoint\")表示：#term(\"utf-8 encoding\")是变长编码。在#term(\"utf-8 encoding\")中，很多#term(\"codepoint\")都由多个字节组成。UTF-8字符串中#term(\"codepoint\")数据按照顺序存放，自然有些字节偏移量在#term(\"codepoint\")寻址中不是合法的。\n+ #term(\"grapheme cluster\")表示：#term(\"grapheme cluster\")就是人类视觉效果上的“最小字符单位”。在码位之上，Unicode规范还规定了多个码位组成一个#term(\"grapheme cluster\")的情况。在这种情况下，自然有些码位（字节）偏移量在#term(\"grapheme cluster\")寻址中不是合法的。\n\n下表说明了\\u{0061}\\u{0303}在字节表示下的合法偏移量。\n\n#let to-bytes-array(x) = range(x.len()).map(i => x.at(i))\n\n#{\n  set align(center)\n  let i = text(red, [非法])\n  table(\n    columns: (50pt, 95pt, 130pt, 80pt),\n    ..(\n      [数据],\n      ..to-bytes-array(bytes(\"\\u{0061}\\u{0303}\")).map(it => [0x#str(it, base: 16)]),\n      [字节],\n      [第1个字节],\n      [第2个字节],\n      [第3个字节],\n    ).map(it => align(center, it)),\n    [码位],\n    [英文字母a],\n    [#term(\"tilde diacritical marks\")],\n    i,\n    [字素簇],\n    [波浪变音的英文字母a],\n    i,\n    i,\n  )\n}\n\n#pro-tip[\n  关于#term(\"grapheme cluster\")的定义，请参考#link(\"https://unicode.org/reports/tr29/\")[《Unicode规范：文本分段》]。\n]\n\n字符串的字面量由双引号包裹。你可以在字符串字面量中使用与字符串相关的转义序列。\n\n#code(```typ\n#\"hello world!\" \\\n#\"\\\"hello\\n  world\\\"!\" \\\n```)\n\n详见#(refs.scripting-base)[《基本字面量、变量和简单函数》]中关于字符串类型的描述。\n\n你可以使用`in`关键字检查一个字符串是否是另一个字符串的子串：#ref-bookmark[`operator:in`\\ of  `str`]\n\n#code(```typ\n#(\"fox\" in \"lazy fox\"),\n#(\" fox\" in \"lazy fox\"),\n#(\"fox \" in \"lazy fox\"),\n#(\"dog\" in \"lazy fox\")\n```)\n\n`in`关键字左侧值还可以是一个正则表达式类型，以检查#term(\"pattern\")是否匹配字符串：\n\n#code(```typ\n#(regex(\"\\d+\") in \"ten euros\") \\\n#(regex(\"\\d+\") in \"10 euros\")\n```)\n\n`in`关键字是以#term(\"codepoint\")为粒度检查文本的：\n\n#code(```typ\n#(\"\\u{0303}\" in \"ã\")\n```)\n\n#ref-cons-signature(\"str\")\n\n除了#term(\"literal\")构造字符串，#ref-bookmark[`func:str`]你还能使用构造器#typst-func(\"str\")将其他值转换为字符串。\n\n- 将整数和浮点数转换为十进制格式字符串：\n  #code(```typ\n  #str(0), #str(199),\n  #str(0.1), #str(9.1)\n  ```)\n- 将标签转换为其名称：\n  #code(```typ\n  #str(<owo:this-label>)\n  ```)\n- 将字节数组依照#term(\"utf-8 encoding\")编码：\n  #code(```typ\n  #str(bytes((72, 0x69, 33)))\n  ```)\n\n特别地，你可以使用`base`参数，#ref-bookmark[`param:base`\\ of `str`]将整数依照$N (2 lt.slant N lt.slant 36)$进制格式转换为字符串：\n\n#code(```typ\n#str(15, base: 16),\n#str(14, base: 14),\n#str(0xdeadbeef, base: 36)\n```)\n\n借鉴Python和JavaScript，Typst不提供字符类型。相应的，仅具备单个#term(\"codepoint\")的字符串就被视作一个#term(\"character\")。也就是说，Typst认定#term(\"character\")与#term(\"codepoint\")等同。这是符合主流观念的。\n\n- “a”、“我”是字符。\n\n- “ã”不是字符。\n\n#glue-block[\n  #ref-method-signature(\"str.to-unicode\")\n\n  你可以使用#typst-func(\"str.to-unicode\")#ref-bookmark[`method:to-unicode`\\ `of str`]函数获得一个字符的#term(\"codepoint\")表示：\n\n  #code(```typ\n  #\"a\".to-unicode(),\n  #\"我\".to-unicode()\n  ```)\n\n  #pro-tip[\n    显然，不是所有的“字符”都可以应用`to-unicode`方法。\n\n    #```typ\n    #\"ã\".to-unicode() /* 不能编译 */\n    ```\n\n    这是因为视觉上为单个字符的ã是一个#term(\"grapheme cluster\")，包含多个码位。\n  ]\n]\n\n#ref-method-signature(\"str.from-unicode\")\n\n你可以使用#typst-func(\"str.from-unicode\")#ref-bookmark[`method:from-unicode`\\ `of str`]函数将一个数字的#term(\"codepoint\")表示转换成字符（串）：\n\n#code(```typ\n#str.from-unicode(97),\n#str.from-unicode(25105)\n```)\n\n#ref-method-signature(\"str.len\")\n\n你可以使用#typst-func(\"str.len\")#ref-bookmark[`method:len`\\ `of str`]函数获得一个字符串的*字节表示的长度*：\n\n#code(```typ\n#\"abc\".len(),\n#\"香風とうふ店\".len(),\n#\"ã\".len()\n```)\n\n你可能希望得到一个字符串的#term(\"codepoint width\")，这时你可以组合以下方法：\n#code(```typ\n#\"abc\".codepoints().len(),\n#\"香風とうふ店\".codepoints().len(),\n#\"ã\".codepoints().len()\n```)\n\n你可能希望得到一个字符串的#term(\"grapheme cluster width\")，这时你可以组合以下方法：\n#code(```typ\n#\"abc\".clusters().len(),\n#\"香風とうふ店\".clusters().len(),\n#\"ã\".clusters().len()\n```)\n\n#glue-block[\n  #ref-method-signature(\"str.first\")\n\n  你可以使用#typst-func(\"str.first\")#ref-bookmark[`method:first`\\ `of str`]函数获得一个字符串的第一个#term(\"grapheme cluster\")：\n\n  #code(```typ\n  #\"Wee\".first(),\n  #\"我 们 俩\".first(),\n  #\"\\u{0061}\\u{0303}\\u{0061}\".first()\n  ```)\n]\n#ref-method-signature(\"str.last\")\n\n你可以使用#typst-func(\"str.last\")#ref-bookmark[`method:last`\\ `of str`]函数获得一个字符串的最后一个#term(\"grapheme cluster\")：\n\n#code(```typ\n#\"Wee\".last(),\n#\"我 们 俩\".last(),\n#\"\\u{0061}\\u{0303}\\u{0061}\".last(),\n#\"\\u{0061}\\u{0061}\\u{0303}\".last()\n```)\n\n#ref-method-signature(\"str.at\")\n\n你可以使用#typst-func(\"str.at\")#ref-bookmark[`method:at`\\ `of str`]函数获得一个字符串位于*字节偏移量*为`offset`的#term(\"grapheme cluster\")：\n\n#code(```typ\n#\"我 们 俩\".at(0),\n#\"我 们 俩\".at(3),\n#\"我 们 俩\".at(4),\n#\"我 们 俩\".at(8)\n```)\n\n上例中，第一个空格的字节偏移量为 $3$，“俩”的字节偏移量为 $8$ 。\n\n#ref-method-signature(\"str.slice\")\n\n你可以使用#typst-func(\"str.slice\")#ref-bookmark[`method:slice`\\ `of str`]函数截取字节偏移量从`start`到`end`的子串：\n\n#code(```typ\n#repr(\"我 们 俩\".slice(0, 11)),\n#repr(\"我 们 俩\".slice(4, 8)),\n```)\n\n`end`参数可以省略：#ref-bookmark[`param:end`\\ of `str.slice`]\n\n#code(```typ\n#repr(\"我 们 俩\".slice(0)),\n#repr(\"我 们 俩\".slice(4)),\n```)\n\n#ref-method-signature(\"str.trim\")\n\n你可以使用#typst-func(\"str.trim\")#ref-bookmark[`method:trim`\\ `of str`]去除字符串的首尾空白字符：\n\n#code(```typ\n#repr(\"  A  \".trim())\n```)\n\n#typst-func(\"str.trim\")可以指定`pattern`参数。#ref-bookmark[`param:pattern`\\ of `str.trim`]\n\n#code(```typ\n#repr(\"wwAww\".trim(\"w\"))\n```)\n\n`pattern`可以是`none`、字符串或正则表达式。当不指定`pattern`时，Typst会指定一个模式*贪婪地*去除首尾空格。\n\n#code(```typ\n#repr(\"  A  \".trim()),\n#repr(\"  A  \".trim(none)),\n#repr(\"wwAww\".trim(\"w\")),\n#repr(\"abAde\".trim(regex(\"[a-z]+\"))),\n```)\n\n#pro-tip[\n  当`pattern`参数为`none`时，Typst直接调用Rust的`trim_start`或`trim_end`方法，以默认获得更高的性能。\n]\n\n`at`参数可以为`start`或`end`，#ref-bookmark[`param:at`\\ of `str.trim`]分别指定则只清除起始位置或结束位置的字符。\n\n#code(```typ\n#repr(\"  A  \".trim(\" \", at: start)),\n#repr(\"  A  \".trim(\" \", at: end))\n```)\n\n`repeat`参数为false时，#ref-bookmark[`param:repeat`\\ of `str.trim`]不会重复运行`pattern`；否则会重复运行。默认`repeat`为`true`。\n\n#code(```typ\n#repr(\"  A  \".trim(\" \")),\n#repr(\"  A  \".trim(\" \", repeat: false))\n```)\n\n`pattern`会在起始位置或结束位置执行至少一次。\n\n#code(```typ\n#repr(\"  A  \".trim(\" \", at: start, repeat: false)),\n#repr(\"abAde\".trim(\n  regex(\"[a-z]\"), repeat: false)),\n#repr(\"abAde\".trim(\n  regex(\"[a-z]+\"), repeat: false))\n```)\n\n#ref-method-signature(\"str.split\")\n\n你可以使用#typst-func(\"str.split\")#ref-bookmark[`method:split`\\ `of str`]函数将一个字符串依照空白字符拆分：\n\n#code(```typ\n#\"我 们仨\".split(),\n```)\n\n#glue-block[\n  #ref-method-signature(\"str.rev\")\n\n  你可以使用#typst-func(\"str.rev\")#ref-bookmark[`method:rev`\\ `of str`]函数将一个字符串逆转：\n\n  #code(```typ\n  #\"abcdedfg\".rev()\n  ```)\n\n  逆转时Typst会为你考虑#term(\"grapheme cluster\")。\n\n  #code(```typ\n  #\"ãb\".rev()\n  ```)\n]\n\nstr.clusters, str.codepoints\n\nstr.contains, str.starts-with, str.ends-with\n\nstr.find, str.position, str.match, str.matches, str.replace\n\n== 字典\n\n#code(```typ\n#let dict = (\n  name: \"Typst\",\n  born: 2019,\n)\n\n#dict.name \\\n#(dict.launch = 20)\n#dict.len() \\\n#dict.keys() \\\n#dict.values() \\\n#dict.at(\"born\") \\\n#dict.insert(\"city\", \"Berlin \")\n#(\"name\" in dict)\n```)\n\ndict.len, dict.at\n\ndict.insert, dict.remove\n\ndict.keys, dict.values, dict.pairs\n\n== 数组\n\n#code(```typ\n#let values = (1, 7, 4, -3, 2)\n\n#values.at(0) \\\n#(values.at(0) = 3)\n#values.at(-1) \\\n#values.find(calc.even) \\\n#values.filter(calc.odd) \\\n#values.map(calc.abs) \\\n#values.rev() \\\n#(1, (2, 3)).flatten() \\\n#((\"A\", \"B\", \"C\")\n    .join(\", \", last: \" and \"))\n```)\n\narray.range\n\narray.len\n\narray.first, array.last, array.slice\n\narray.push, array.pop, array.insert, array.remove\n\narray.contains, array.find, array.position, array.filter\n\narray.map, array.enumerate, array.zip, array.fold, array.sum, array.product, array.any, array.all, array.flatten, array.rev, array.split, array.join, array.intersperse, array.sorted, array.dedup\n"
  },
  {
    "path": "src/tutorial/reference-utils.typ",
    "content": "#import \"mod.typ\": *\n#import \"/typ/typst-meta/docs.typ\": typst-v11\n#import \"@preview/cmarker:0.1.0\": render as md\n\n#show: book.page.with(title: [参考：函数表（字典序）])\n\n#let table-lnk(name, ref, it, scope: (:), res: none, ..args) = (\n  align(center + horizon, link(\"todo\", name)),\n  it,\n  align(\n    horizon,\n    {\n      set heading(bookmarked: false, outlined: false)\n      eval(it.text, mode: \"markup\", scope: scope)\n    },\n  ),\n)\n\n#let table-item(c, mp, featured) = {\n  let item = mp.at(c)\n\n  (typst-func(c), item.title, ..featured(item))\n}\n\n#let table-items(mp, featured) = mp.keys().sorted().map(it => table-item(it, mp, featured)).flatten()\n\n#let featured-func(item) = {\n  return (md(item.body.content.oneliner),)\n}\n\n#let featured-scope-item(item) = {\n  return (md(item.oneliner),)\n}\n\n== 分类：函数\n\n#table(\n  columns: (1fr, 1fr, 2fr),\n  [函数], [名称], [描述],\n  ..table-items(typst-v11.funcs, featured-func),\n)\n\n== 分类：方法\n\n#table(\n  columns: (1fr, 1fr, 2fr),\n  [方法], [名称], [描述],\n  ..table-items(typst-v11.scoped-items, featured-scope-item),\n)\n\n#if false [\n  == `plain-text`，以及递归函数\n\n  如果我们想要实现一个函数`plain-text`，它将一段文本转换为字符串。它便可以在树上递归遍历：\n\n  ```typ\n  #let plain-text(it) = if it.has(\"text\") {\n    it.text\n  } else if it.has(\"children\") {\n    (\"\", ..it.children.map(plain-text)).join()\n  } else if it.has(\"child\") {\n    plain-text(it.child)\n  } else { ... }\n  ```\n\n  所谓递归是一种特殊的函数实现技巧：\n  - 递归总有一个不调用其自身的分支，称其为递归基。这里递归基就是返回`it.text`的分支。\n  - 函数体中包含它自身的函数调用。例如，`plain-text(it.child)`便再度调用了自身。\n\n  这个函数充分利用了内容类型的特性实现了遍历。首先它使用了`has`函数检查内容的成员。\n\n  如果一个内容有孩子，那么对其每个孩子都继续调用`plain-text`函数并组合在一起：\n\n  ```typ\n  #if it.has(\"children\") { (\"\", ..it.children.map(plain-text)).join() }\n  #if it.has(\"child\") { plain-text(it.child) }\n  ```\n\n  限于篇幅，我们没有提供`plain-text`的完整实现，你可以试着在课后完成。\n\n  == 鸭子类型\n\n  这里值得注意的是，`it.text`具有多态行为。即便没有继承，这里通过一定动态特性，允许我们同时访问「代码片段」的`text`和「文本」的text。例如：\n\n  #code(```typ\n  #let plain-mini(it) = if it.has(\"text\") { it.text }\n  #repr(plain-mini(`代码片段中的text`)) \\\n  #repr(plain-mini([文本中的text]))\n  ```)\n\n  这也便是我们在「内容类型」小节所述的鸭子类型特性。如果「内容」长得像文本（鸭子），那么它就是文本。\n]\n"
  },
  {
    "path": "src/tutorial/reference-visualization.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：图形与几何元素])\n\n== 直线\n\nA line from one point to another. #ref-bookmark[`line`]\n\n#code(```typ\n#line(length: 100%)\n#line(end: (50%, 50%))\n#line(\n  length: 4cm,\n  stroke: 2pt + maroon,\n)\n```)\n\n== 线条样式\n\nDefines how to draw a line. #ref-bookmark[`stroke`]\n\nA stroke has a paint (a solid color or gradient), a thickness, a line cap, a line join, a miter limit, and a dash pattern. All of these values are optional and have sensible defaults.\n\n#code(```typ\n#set line(length: 100%)\n#let rainbow = gradient.linear(\n  ..color.map.rainbow)\n#stack(\n  spacing: 1em,\n  line(stroke: 2pt + red),\n  line(stroke: (paint: blue, thickness: 4pt, cap: \"round\")),\n  line(stroke: (paint: blue, thickness: 1pt, dash: \"dashed\")),\n  line(stroke: 2pt + rainbow),\n)\n```)\n\n== 贝塞尔路径（曲线）\n\nA path through a list of points, connected by Bezier curves. #ref-bookmark[`path`]\n\n#code(```typ\n#path(\n  fill: blue.lighten(80%),\n  stroke: blue,\n  closed: true,\n  (0pt, 50pt),\n  (100%, 50pt),\n  ((50%, 0pt), (40pt, 0pt)),\n)\n```)\n\n== 圆形\n\nA circle with optional content. #ref-bookmark[`circle`]\n\n#code(```typ\n// Without content.\n#circle(radius: 25pt)\n\n// With content.\n#circle[\n  #set align(center + horizon)\n  Automatically \\\n  sized to fit.\n]\n```)\n\n== 椭圆\n\nAn ellipse with optional content. #ref-bookmark[`ellipse`]\n\n#code(```typ\n// Without content.\n#ellipse(width: 35%, height: 30pt)\n\n// With content.\n#ellipse[\n  #set align(center)\n  Automatically sized \\\n  to fit the content.\n]\n```)\n\n== 正方形\n\nA square with optional content. #ref-bookmark[`square`]\n\n#code(```typ\n// Without content.\n#square(size: 40pt)\n\n// With content.\n#square[\n  Automatically \\\n  sized to fit.\n]\n```)\n\n== 矩形\n\nA rectangle with optional content. #ref-bookmark[`rect`]\n\n#code(```typ\n// Without content.\n#rect(width: 35%, height: 30pt)\n\n// With content.\n#rect[\n  Automatically sized \\\n  to fit the content.\n]\n```)\n\n== 多边形\n\nA closed polygon. #ref-bookmark[`ellipse`]\n\nThe polygon is defined by its corner points and is closed automatically.\n\n#code(```typ\n#polygon(\n  fill: blue.lighten(80%),\n  stroke: blue,\n  (20%, 0pt),\n  (60%, 0pt),\n  (80%, 2cm),\n  (0%,  2cm),\n)\n```)\n\n== 图形库\n\n+ typst-fletcher\n+ typst-syntree\n+ cetz\n"
  },
  {
    "path": "src/tutorial/reference-wasm-plugin.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.ref-page.with(title: [参考：WASM插件])\n"
  },
  {
    "path": "src/tutorial/scripting-block-and-expression.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"表达式\")\n\nTypst中绝大部分语法结构都可作表达式，可以说学习完了所有表达式，则学会了Typst所有语法。当然，其吸取了一定历史经验，仅有少量的语句不是表达式。\n\n#pro-tip[\n  Typst借鉴了Rust，遵从「面向表达式编程」（expression-oriented programming）的哲学。它将所有的语句都根据可折叠规则（见后文）设计为表达式。\n  + 如果一个语句能产生值，那么该语句的结果是按*控制流*顺序所产生所有值的折叠。\n  + 否则，如果一个语句不能产生值，那么该语句的结果是```typc none```。\n  + 特别地，任意类型 $T$ 的值 $v$ 与```typc none```折叠仍然是值本身。\n]\n\n#pro-tip[\n  `show`语句和`set`语句不是表达式。\n]\n\n根据大的分类，可用的表达式可以分为5类（它们并非严格术语，而是为了方便教学而分类），他们是：\n+ 代数运算表达式。\n+ 逻辑比较表达式。\n+ 逻辑运算表达式。\n+ 赋值表达式。\n+ 项。\n\n== 代数运算表达式 <grammar-arith-exp>\n\nTypst支持对数字的算数运算，其中浮点运算遵守IEEE-754标准。整数和浮点数之间可以混合运算：\n\n#code(```typ\n//加     减        乘      除\n#(1 + 2, 1.0 - 2, 1 * 2, 1 / 2 + 1)\n```)\n\n从上可以看出，整数和整数运算尽可能*保持*整数，但是整数和浮点数运算则结果变为浮点数。除法运算的结果是浮点数。算数之间遵守四则运算规则。\n\n#pro-tip[\n  更高级的运算，例如数字的位运算和数值计算隐藏在类型的方法和`calc`标准库中：\n\n  #code(```typ\n  #(0o755.bit-and(0o644)), // 8进制位运算\n  #(calc.pow(9, 4)) // 9的4次方\n  ```)\n\n  请参考《》。\n]\n\n除了数字运算，字符串、数组等还支持加法和乘法运算。它们的加法实际上是连接操作，它们的乘法则是重复元素的累加。\n\n#code(```typ\n//乘         加\n#(\"1\"  * 2, \"4\"  + \"5\", ) \\\n#((1,) * 2, (4,) + (5,),)\n```)\n\n字典只支持加法操作。若有重复键值对，则右侧列表的键值对会覆盖左侧列表的键值对。\n\n#code(```typ\n#((a: 1) + (b: 2),\n  (a: 1) + (b: 3, a: 2) + (a: 4 , c: 5))\n```)\n\n\n== 浮点数陷阱\n\n浮点数陷阱是指由于浮点转换或浮点精度问题导致的一系列逻辑问题。\n\n// todo: 把基本字面量的运算和表达式挪过来\n\n+ 类型比较陷阱。在运行期切记同时考虑到`float`和`int`两种类型：\n\n  #code(```typ\n  #type(2), #type(2.), #(type(2.) == int)\n  ```)\n\n  可见```typc 2.```与```typc 2```类型并不相同。```typc 2.```在数学上是整数，但以浮点数存储。\n\n+ 大数类型陷阱。当数字过大时，其会被隐式转换为浮点数存储：\n\n  #code(```typ\n  #type(9000000000000000000000000000000)\n  ```)\n\n+ 整数运算陷阱。整数相除时会被转换为浮点数：\n\n  #code(```typ\n  #(10 / 4), #type(10 / 4) \\\n  #(12 / 4), #type(12 / 4) \\\n  ```)\n\n+ 浮点误差陷阱。哪怕你的运算在数学上理论是可逆的，由于浮点数精度有限，也会误判结果：\n\n  #code(```typ\n  #(1000000 / 9e21 * 9e21), #((1000000 / 9e21 * 9e21) == 1000000)\n  ```)\n\n  这提示我们，正确的浮点比较需要考虑误差。例如以上两数在允许`1e-6`误差前提下是相等的：\n\n  #code(```typ\n  #(calc.abs((1000000 / 9e21 * 9e21) - 1000000) < 1e-6)\n  ```)\n\n+ 整数转换陷阱。为了转换类型，可以使用`int`，但有可能产生精度损失（就近取整）：\n\n  #code(```typ\n  #int(10 / 4),\n  #int(12 / 4)\n  ```)\n\n  或有可能产生「截断」。（todo: typst v0.12.0已经不适用）\n\n// #code(```typ\n// #int(9000000000000000000000000000000)\n// ```)\n\n这些都是编程语言中的共通问题。凡是令数字保持有效精度，都会产生如上问题。\n\n== 逻辑比较表达式 <grammar-logical-cmp-exp>\n\n有三大简单比较关系：\n\n#code(```typ\n//大于    小于   等于\n#(1 > 2, 1 < 2, 1 == 2)\n```)\n\n基于此，可以延申出三个方便脚本编辑的比较关系。\n\n#code(```typ\n//大于或等于 小于或等于 不等于\n#(1 >= 2.0, 1 <= 1,  1 != 2)\n```)\n\n从数学角度，真正基本的关系另有其系，其中“小于或等于”是数学中的偏序关系，“等于”是数学中的等价关系。这两个关系可以衍生出其他四种关系，但是不太好理解。举例来讲，大于其实就是“小于等于”的否定；而小于则是“小于等于”但是不能“等于”。\n\n注意：不推荐将整数与浮点数相互比较。具体请参考上一节所提及的浮点数陷阱。\n\n字符串、数组和字典之间也是可以比较的，理解他们则必须从偏序关系和等价关系入手。\n\n三种项的等价关系比较容易理解，说两字符串/数组/字典相等，则是在说二者有一样多的子项，且每个子项都一一相等。\n\n#code(```typ\n#((1, 1) == (1, 1)),\n#((a: 1, c: (1, )) == (a: 1, c: (1, )))\n```)\n\n字典之间没有偏序关系，便只剩字符串和数组。偏序关系则需要指定一种排序规则。如果两字符串/数组不相等，则首先考虑它们的长度关系，长度小的一方是更小的：\n\n#code(```typ\n#(\"1\"   <= \"23\"),\n#((1, ) <= (2, 3, ))\n```)\n\n否则从前往后依次比较每一个子项，直到找到*第一个*不相等的子项，进而确定顺序。见下三例：\n\n#code(```typ\n#(\"1\" <= \"2\", \"113\" <= \"121\", \"2\" <= \"12\")\n```)\n\n对于单个字符，我们依照字典序比较，其中容易理解地是数字字符顺序按字面递增。对于前两者，我们可以看到`\"1\"`比`\"2\"`小；进由此，故\"113\"比\"121\"小；尽管如此，优先判断长度关系，故\"2\"比\"12\"小。\n\n从两种基础比较关系，其他四种比较关系也能被良好地定义，并在脚本中使用：\n\n#code(```typ\n#(\"1\" > \"2\", \"1\" != \"2\",\n  \"1\" < \"2\", \"1\" >= \"2\")\n```)\n\n== 逻辑运算表达式 <grammar-logical-calc-exp>\n\n布尔值之间可以做“且”、“或”和“非”三种逻辑运算，并产生布尔类型的表达式：\n\n#code(```typ\n#(not false), #(false or false), #(true and false)\n```)\n\n真值表如下：\n\n#{\n  set align(center)\n  table(\n    columns: (33pt * 0.6,) * 2 + (33pt,) * 3,\n    stroke: 0.5pt,\n    $p$, $q$, $not p$, $p or q$, $p and q$,\n    $0$, $0$, $1$, $0$, $0$,\n    $0$, $1$, $1$, $1$, $0$,\n    $1$, $0$, $0$, $1$, $0$,\n    $1$, $1$, $0$, $1$, $1$,\n  )\n}\n\n逻辑运算使用起来很简单，建议入门的同学找一些专题阅读，例如#link(\"https://zhuanlan.zhihu.com/p/82986019\")[数理逻辑（1）——命题逻辑的基本概念]。但一旦涉及到对复杂事物的逻辑讨论，你就可能陷入了知识的海洋。关于逻辑运算已经形成一门学科，如有兴趣建议后续找一些书籍阅读，例如#link(\"https://www.xuetangx.com/course/THU12011001060/19316572\")[逻辑学概论]。\n\n本书自然不负责教你逻辑学。\n\n== 赋值表达式 <grammar-assign-exp>\n\n变量可以被赋予一个表达式的值。事实上，`let`表达式后的语法结构就是赋值表达式。\n\n#code(```typ\n#let a = 1; #a,\n#(a = 10); #a\n```)\n\n除此之外，还有先加（减、乘或除）后赋值的变形。所有这些赋值语句都产生`none`值而非返回变量的值。\n\n#code(```typ\n#let a = 1; #a,\n#repr(a += 2), #a, #repr(a -= 2), #a, #repr(a *= 2), #a, #repr(a /= 2), #a\n```)\n\n== 代码块 <grammar-code-block>\n\n除了字面量，由于「面向表达式编程」，「代码块」也可以作为表达式的「项」。在Typst中，代码块和内容块是等同的。将「内容块」与「代码块」作比较：\n\n- 代码块：按顺序包含一系列语句，内部为#term(\"code mode\")。\n- 内容块：按顺序包含一系列内容，内部为#term(\"markup mode\")。\n\n内容块（标记模式）内部没有语句的概念，一个个内容或元素按顺序排列。相比，代码块内部则有语句概念。每个语句可以是换行分隔，也可以是#mark(\";\")分隔。\n\n#code(```typ\n#{\n  \"a\"\n  \"b\"\n} \\ // 与下表达式等同：\n#{ \"a\"; \"b\" }\n```)\n\n#pro-tip[\n  公式块只是一种特殊的内容块，只不过其内容处于「数学模式」。对比：\n  #code(```typ\n  文本优先：#[ab] v.s. $#[a]#[b]$ \\\n  数学优先：#[$lambda$$(x)$] v.s. $lambda(x)$\n  ```)\n]\n\n「代码块」和值（表达式）的关系与「控制流」息息相关。我们将会在下一节（todo：添加链接）详细介绍「控制流」。\n\n// 整数转浮点数：<grammar-int-to-float>\n\n// #code(```typ\n// #float(1), #(type(float(1)))\n// ```)\n\n// 布尔值转整数：<grammar-bool-to-int>\n\n// #code(```typ\n// #int(false), #(type(int(false))) \\\n// #int(true), #(type(int(true)))\n// ```)\n\n// 浮点数转整数：<grammar-float-to-int>\n\n// #code(```typ\n// #int(1), #(type(int(1)))\n// ```)\n\n// 数字转字符串：<grammar-num-to-str>\n\n// #code(```typ\n// #repr(str(1)), #(type(str(1)))\n// #repr(str(.5)), #(type(str(.5)))\n// ```)\n\n// 布尔值转字符串：<grammar-bool-to-str>\n\n// #code(```typ\n// #repr(false), #(type(repr(false)))\n// ```)\n\n// 数字转布尔值：<grammar-int-to-bool>\n\n// #code(```typ\n// #let to-bool(x) = x != 0\n// #repr(to-bool(0)), #(type(to-bool(0))) \\\n// #repr(to-bool(1)), #(type(to-bool(1)))\n// ```)\n\n// 表达式从感性地理解上就是检验执行一段代码是否对应产生一个值。精确来说，表达式是以下对象的集合：\n// + 前述的各种「字面量」是表达式：\n//   #code(```typ\n//   // 这些是表达式\n//   #none, #true, #1, #.2, #\"s\"\n//   ```)\n// + 「变量」是表达式：\n//   #code(```typ\n//   #let a = 1;\n//   // 这是表达式\n//   #a\n//   ```)\n// + 「括号表达式」（parenthesized expression）是表达式：\n//   #code(```typ\n//   // 这些是表达式\n//   #(0), #(1+2), #((((((((1))))))))\n//   ```)\n// + 「函数调用」是表达式：\n//   #code(```typ\n//   #let add(a, b) = a + b\n//   #let a = 1;\n//   // 这是表达式\n//   #add(a, 2)\n//   ```)\n// + 特别地，Typst中的「代码块」和「内容块」是表达式：\n//   #code(```typ\n//   // 这些是表达式\n//   #repr({ 1 }), #repr([ 1 ]),\n//   ```)\n// + 特别地，Typst中的「if语句」、「for语句」和「while语句」等都是表达式：\n//   #code(```typ\n//   // 这些是表达式\n//   #repr(if false [啊？]),\n//   #repr(for _ in range(0) {}),\n//   #repr(while false {}),\n//   #repr(let _ = 1),\n//   ```)\n//   这些将在后文中介绍。\n// + 情形1至情形7的所有对象作为项，任意项之间的「运算」也是表达式。\n\n// 其中，情形1至情形3被称为「初始表达式」（primary expression），它们就像是表达式的种子，由其他操作继续组合生成更多表达式。\n\n// 情形4至情形6本身都是经典意义上的「语句」（statement），它们由一个或多个子表达式组成，形成一个有新含义的表达式。在Typst中它们都被赋予了「值」的语义，因此它们也都是表达式。我们将会在后续文章中继续学习。\n\n// 本节主要讲解情形7。由于情形1至情形6都可以作为情形7的项，不失一般性，我们仍然可以仅以「字面量」作为项讲解所有情形7的情况。\n"
  },
  {
    "path": "src/tutorial/scripting-color.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"颜色类型\")\n"
  },
  {
    "path": "src/tutorial/scripting-composite.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"复合类型\")\n\n学会了数组和字典，我们就解锁了各种新技能。\n\n== 数组字面量 <grammar-array-literal>\n\n脚本模式中有两类核心复合字面量。一曰「数组」，一曰「字典」。\n\n「数组」是按照顺序存储的一些「值」，你可以在「数组」中存放*任意内容*而不拘泥于类型。你可以使用圆括号与一个逗号分隔的列表创建一个*数组字面量*：\n\n#code(```typ\n#(1, \"OvO\", [一段内容])\n```)\n\n== 字典字面量 <grammar-dict-literal>\n\n所谓「字典」即是「键值对」的集合，其每一项是由冒号分隔的「键值对」。如下例所示，冒号左侧，“neco-mimi”等「标识符」或「字符串」是字典的「键」，而冒号右侧分别是对应的「值」。\n\n#code(```typ\n#(neko-mimi: 2, \"utterance\": \"喵喵喵\")\n```)\n\n== 数组和字典的典型构造\n\n特别讲解一些关于数组与字典相关的典型构造：\n\n#code(```typ\n#() \\ // 是空的数组\n#(:) \\ // 是空的字典\n#(1) \\ // 被括号包裹的整数1\n#(()) \\ // 被括号包裹的空数组\n#((())) \\ // 被括号包裹的被括号包裹的空数组\n#(1,) \\ // 是含有一个元素的数组\n```)\n\n`()`是空的数组<grammar-empty-array>，不含任何值。`(:)`是空的字典<grammar-empty-dict>，不含任何键值对。\n\n如果括号内含了一个值，例如`(1)`，那么它仅仅是被括号包裹的整数1，仍然是整数1本身。\n\n类似的，`(())`是被括号包裹的空数组<grammar-paren-empty-array>，`((()))`是被括号包裹的被括号包裹的空数组。\n\n为了构建含有一个元素的数组，需要在列表末尾额外放置一个逗号以示区分，例如`(1,)`。<grammar-single-member-array>\n\n// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Trailing_commas\n这种逗号被称为尾后逗号。\n\n// todo: 改进\n构造数组字面量时，允许尾随一个多余的逗号而不造成影响。\n\n#code(```typ\n#let x = (1, \"OvO\", [一段内容] , ); #x\n//          这里有一个多余的逗号^^^\n```)\n\n构造字典字面量时，允许尾随一个多余的逗号。\n\n#code(```typ\n#let cat = (attribute: [kawaii\\~], ); #cat\n//             这里有一个尾随的小逗号^^\n```)\n\n== 数组和字典的「解构赋值」\n\n这是本节最难的内容，非程序员朋友想要灵活运用可能有点困难。\n\n除了使用字面量「构造」元素，Typst还支持「构造」的反向操作：「解构赋值」。顾名思义，你可以在左侧用相似的语法从数组<grammar-destruct-array>或字典中获取值并赋值到*对应*的变量上。<grammar-destruct-dict>\n\n#code(```typ\n#let (attr: a) = (attr: [kawaii\\~])\n#a\n```)\n\n「解构赋值」必须一一对应，但你也可以使用「占位符」（`_`）或「延展符」（`..`）以作*部分*解构：<grammar-destruct-array-eliminate>\n\n#code(```typ\n#let (first, ..) = (1, 2, 3)\n#let (.., second-last, _) = (7, 8, 9, 10)\n#first, #second-last\n```)\n\n数组的「解构赋值」有一个妙用，那就是重映射内容。<grammar-array-remapping>\n\n#code(```typ\n#let (a, b, c) = (1, 2, 3)\n#let (b, c, a) = (a, b, c); #a, #b, #c\n```)\n\n特别地，如果两个变量相互重映射，这种操作被称为「交换」：<grammar-array-swap>\n\n#code(```typ\n#let (a, b) = (1, 2)\n#((a, b) = (b, a)); #a, #b\n```)\n\n== 数组和字典的成员访问\n\n为了访问数组，你可以使用`at`方法。“at”在中文里是“在”的意思，它表示对「数组」使用「索引」操作。`at(0)`索引到第1个值，`at(n)`索引到第 $n + 1$ 个值，以此类推。如下所示：\n\n#code(```typ\n#let x = (1, \"OvO\", [一段内容])\n#x.at(0), #x.at(1), #x.at(2)\n```)\n\n至于「索引」从零开始的原因，这只是约定俗成。等你习惯了，你也会变成计数从零开始的好程序员。\n\n为了访问字典，你可以使用`at`方法。但由于「键」都是字符串，你需要使用字符串作为字典的「索引」。\n\n#code(```typ\n#let cat = (attribute: [kawaii\\~])\n#cat.at(\"attribute\")\n```)\n\n为了方便，Typst允许你直接通过成员方法访问字典对应「键」的值： <grammar-dict-member-exp>\n\n#code(```typ\n#let cat = (\"attribute\": [kawaii\\~])\n#cat.attribute\n```)\n\n== 数组和字典的「存在谓词」\n\n为了访问数组，你可以使用`contains`方法。“contain”在中文里是“包含”的意思，如下所示：\n\n#code(```typ\n#let x = (1, \"OvO\", [一段内容])\n#x.contains[一段内容]\n```)\n\n因为这太常用了，typst专门提供了`in`语法，表示判断`x`是否*存在于*某个数组中：<grammar-array-in>\n\n#code(```typ\n#([一段内容] in (1, \"OvO\", [一段内容])) \\\n#([另一段内容] in (1, \"OvO\", [一段内容]))\n```)\n\n字典也可以使用此语法，表示判断`x`是否是字典的一个「键」。特别地，你还可以前置一个`not`判断`x`是否*不在*某个数组或字典中：<grammar-dict-in>\n\n#code(```typ\n#let cat = (neko-mimi: 2)\n#(\"neko-kiki\" not in cat)\n```)\n\n注意：`x in (...)`与`\"x\" in (...)`是不同的。例如`neko-mimi in cat`将检查`neko-mimi`变量的内容是否是字典变量`cat`的一个「键」，而`\"neko-mimi\"`检查对应字符串是否在其中。\n\n// field access\n// - dictionary\n// - symbol\n// - module\n// - content\n\n== 位置参数声明 <grammar-positional-param>\n\ntypst通过数组和字典在执行时向函数传递参数。上一节我们学会了基本的函数声明，若进一步理解了数组和字典，可以定义更复杂的函数。接下来我们讲透Typst中如何声明复杂函数，以及如何调用函数。\n\n其中最简单的是位置参数声明，我们在上节已经学过。\n\n#code(```typ\n#let f(a, b) = [两个值#(a)和#(b)偷偷混入了我们内容之中。]\n#f(1, 2)\n```)\n\n在函数调用的时候，位置参数必须按照声明的顺序传递。例子中，向`f`函数传递了两个位置参数`1`和`2`，于是`a`和`b`作为变量被赋值为了`1`和`2`，接着这个函数返回一个「内容块」，其中用到了`a`和`b`变量。\n\n== 具名参数声明 <grammar-named-param>\n\n函数可以包含「具名参数」，其看起来就像字典的一项，冒号右侧则是参数的*默认值*：\n\n#code(```typ\n#let g(name: \"？\") = [你是#name]\n#g(/* 不传就是问号 */); #g(name: \"OwO。\")\n```)\n\n== 变长参数 <grammar-variadic-param>\n\n函数可以包含「变长参数」。\n\n#code(```typ\n#let g(..args) = [很多个值，#args.pos().join([、])，偷偷混入了我们内容之中。]\n#g([一个俩个], [仨个四个], [五六七八个])\n```)\n\n```typc args```的类型是`arguments`。\n+ 使用`args.pos()`得到传入的位置参数数组；使用`args.named()`得到传入的具名参数字典。\n+ 使用`args.at(i)`访问索引为整数`i`的位置参数；使用`args.at(name)`访问名称为字符串`name`的具名参数。\n\n== 参数类型 <grammar-param-literal>\n\n什么是「参数类型」（argument type）？在typst中，你完全可以先把函数参数准备好，然后直接调用函数：\n\n#code(```typ\n#let sum(..args, init: 0) = init + args.pos().sum()\n#let args = arguments(1, 2)\n#sum(..args) // 3\n```)\n\n`arguments`是一个类型，`arguments(1, 2)`是一个表达式，它返回一个`arguments`类型的值。\n\n也可以预先传递参数，然后直接调用函数：\n\n#code(```typ\n#let sum(..args, init: 0) = init + args.pos().sum()\n#let args = arguments((1,), (2,), init: ())\n#sum(..args) // (1, 2)\n```)\n\n=== 参数解构 <grammar-destructing-param>\n\n#todo-box[写完]\ntodo参数解构。\n\n#todo-box[引言]\n"
  },
  {
    "path": "src/tutorial/scripting-content.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"文档树\")\n\n#todo-box[本节处于校对阶段，所以可能存在不完整或错误。]\n\n== 「可折叠」的值（Foldable）\n\n先来看代码块。代码块其实就是一个脚本。既然是脚本，Typst就可以按照语句顺序依次执行「语句」。\n\n#pro-tip[\n  准确地来说，按照控制流顺序。\n]\n\nTypst按控制流顺序执行代码，将所有结果*折叠*成一个值。所谓折叠，就是将所有数值“连接”在一起。这样讲还是太抽象了，来看一些具体的例子。\n\n=== 字符串折叠\n\nTypst实际上不限制代码块的每个语句将会产生什么结果，只要是结果之间可以*折叠*即可。\n\n我们说字符串是可以折叠的：\n\n#code(```typ\n#{\"Hello\"; \" \"; \"World\"}\n```)\n\n实际上折叠操作基本就是#mark(\"+\")操作。那么字符串的折叠就是在做字符串连接操作：\n\n#code(```typ\n#(\"Hello\" + \" \" + \"World\")\n```)\n\n再看一个例子：\n\n#code(```typ\n#{\n  let hello = \"Hello\";\n  let space = \" \";\n  let world = \"World\";\n  hello; space; world;\n  let destroy = \", Destroy\"\n  destroy; space; world; \".\"\n}\n```)\n\n如何理解将「变量声明」与表达式混写？\n\n回忆前文。对了，「变量声明」表达式的结果为```typc none```。\n#code(```typ\n#type(let hello = \"Hello\")\n```)\n\n并且还有一个重点是，字符串与`none`相加是字符串本身，`none`加`none`还是`none`：\n\n#code(```typ\n#(\"Hello\" + none), #(none + \"Hello\"), #repr(none + none)\n```)\n\n现在可以重新体会这句话了：Typst按控制流顺序执行代码，将所有结果*折叠*成一个值。对于上例，每句话的执行结果分别是：\n\n```typc\n#{\n  none; // let hello = \"Hello\";\n  none; // let space = \" \";\n  none; // let world = \"World\";\n  \"Hello\"; \" \"; \"World\"; // hello; space; world;\n  none; // let destroy = \", Destroy\"\n  \", Destroy\"; \" \"; \"World\"; \".\" // destroy; space; world; \".\"\n}\n```\n\n将结果收集并“折叠”，得到结果：\n\n#code(```typc\n#(none + none + none + \"Hello\" + \" \" + \"World\" + none + \", Destroy\" + \" \" + \"World\" + \".\")\n```)\n\n#pro-tip[\n  还有其他可以折叠的值，例如，数组与字典也是可以折叠的：\n\n  #code(```typ\n  #for i in range(1, 5) { (i, i * 10) }\n  ```)\n\n  #code(```typ\n  #for i in range(1, 5) { let d = (:); d.insert(str(i), i * 10); d }\n  ```)\n]\n\n=== 其他基本类型的情况\n\n那么为什么说折叠操作基本就是#mark(\"+\")操作。那么就是说有的“#mark(\"+\")操作”并非是折叠操作。\n\n布尔值、整数和浮点数都不能相互折叠：\n\n```typ\n// 不能编译\n#{ false; true }; #{ 1; 2 }; #{ 1.; 2. }\n```\n\n那么是否说布尔值、整数和浮点数都不能折叠呢。答案又是否认的，它们都可以与```typc none```折叠（把下面的加号看成折叠操作）：\n\n#code(```typ\n#(1 + none)\n```)\n\n所以你可以保证一个代码块中只有一个「语句」产生布尔值、整数或浮点数结果，这样的代码块就又是能编译的了。让我们利用`let _ = `来实现这一点：\n\n#code(```typ\n#{ let _ = 1; true },\n#{ let _ = false; 2. }\n```)\n\n回忆之前所讲的特殊规则：#term(\"placeholder\")用作标识符的作用是“忽略不必要的语句结果”。\n\n=== 内容折叠\n\nTypst脚本的核心重点就在本段。\n\n内容也可以作为代码块的语句结果，这时候内容块的结果是每个语句内容的“折叠”。\n\n#code(```typ\n#{\n  [= 生活在Content树上]\n  [现代社会以海德格尔的一句“一切实践传统都已经瓦解完了”为嚆矢。]\n  [滥觞于家庭与社会传统的期望正失去它们的借鉴意义。]\n  [但面对看似无垠的未来天空，我想循卡尔维诺“树上的男爵”的生活好过过早地振翮。]\n}\n```)\n\n是不是感觉很熟悉？实际上内容块就是上述代码块的“糖”。所谓糖就是同一事物更方便书写的语法。上述代码块与下述内容块等价：\n\n```typ\n#[\n  = 生活在Content树上\n  现代社会以海德格尔的一句“一切实践传统都已经瓦解完了”为嚆矢。滥觞于家庭与社会传统的期望正失去它们的借鉴意义。但面对看似无垠的未来天空，我想循卡尔维诺“树上的男爵”的生活好过过早地振翮。\n]\n```\n\n由于Typst默认以「标记模式」开始解释你的文档，这又与省略`#[]`的写法等价：\n\n```typ\n= 生活在Content树上\n现代社会以海德格尔的一句“一切实践传统都已经瓦解完了”为嚆矢。滥觞于家庭与社会传统的期望正失去它们的借鉴意义。但面对看似无垠的未来天空，我想循卡尔维诺“树上的男爵”的生活好过过早地振翮。\n```\n\n#pro-tip[\n  实际上有区别，由于多两个换行和缩进，前后各多一个Space Element。\n]\n\n// == Hello World程序\n\n// 有的时候，我们想要访问字面量、变量与函数中存储的“信息”。例如，给定一个字符串```typc \"Hello World\"```，我们想要截取其中的第二个单词。\n\n// 单词`World`就在那里，但仅凭我们有限的脚本知识，却没有方法得到它。这是因为字符串本身是一个整体，虽然它具备单词信息，我们却缺乏了*访问*信息的方法。\n\n// Typst为我们提供了「成员」和「方法」两种概念访问这些信息。使用「方法」，可以使用以下脚本完成目标：\n\n// #code(```typ\n// #\"Hello World\".split(\" \").at(1)\n// ```)\n\n// 为了方便讲解，我们改写出6行脚本。除了第二行，每一行都输出一段内容：\n\n// #code(```typ\n// #let x = \"Hello World\"; #x \\\n// #let split = str.split\n// #split(x, \" \") \\\n// #str.split(x, \" \") \\\n// #x.split(\" \") \\\n// #x.split(\" \").at(1)\n// ```)\n\n// 从```typ #x.split(\" \").at(1)```的输出可以看出，这一行帮助我们实现了“截取其中的第二个单词”的目标。我们虽然隐隐约约能揣测出其中的意思：\n\n// ```typ\n// #(       x .split(\" \")           .at(1)          )\n// // 将字符串 根据字符串拆分  取出其中的第2个单词（字符串）\n// ```\n\n// 但至少我们对#mark(\".\")仍是一无所知。\n\n// 本节我们就来讲解Typst中较为高级的脚本语法。这些脚本语法与大部分编程语言的语法相同，但是我们假设你并不知道这些语法。\n\n== 「内容」是一棵树（Cont.）\n\n#pro-tip[\n  利用「内容」与「树」的特性，我们可以在Typst中设计出更多优雅的脚本功能。\n]\n\n=== CeTZ的「树」\n\nCeTZ利用内容树制作“内嵌的DSL”。CeTZ的`canvas`函数接收的不完全是内容，而是内容与其IR的混合。\n\n例如它的`line`函数的返回值，就完全不是一个内容，而是一个无法窥视的函数。\n\n#code(```typ\n#import \"@preview/cetz:0.3.4\"\n#repr(cetz.draw.line((0, 0), (1, 1), fill: blue))\n```)\n\n当你产生一个“混合”的内容并将其传递给`cetz.canvas`，CeTZ就会像`plain-text`一样遍历你的混合内容，并加以区分和处理。如果遇到了他自己特定的IR，例如`cetz.draw.line`，便将其以特殊的方式转换为真正的「内容」。\n\n使用混合语言，在Typst中可以很优雅地画多面体：\n\n#code.with(al: top)(```typ\n#import \"@preview/cetz:0.3.4\"\n#align(center, cetz.canvas({\n  // 导入cetz的draw方言\n  import cetz.draw: *; import cetz.vector: add\n  let neg(u) = if u == 0 { 1 } else { -1 }\n  for (p, c) in (\n    ((0, 0, 0), black), ((1, 1, 0), red), ((1, 0, 1), blue), ((0, 1, 1), green),\n  ) {\n    line(add(p, (0, 0, neg(p.at(2)))), p, stroke: c)\n    line(add(p, (0, neg(p.at(1)), 0)), p, stroke: c)\n    line(add(p, (neg(p.at(0)), 0, 0)), p, stroke: c)\n  }\n}))\n```)\n\n=== curryst的「树」\n\n我们知道「内容块」与「代码块」没有什么本质区别。\n\n如果我们可以基于「代码块」描述一棵「内容」的树，那么逻辑推理的过程也可以被描述为条件、规则、结论的树。\n\n#link(\"https://typst.app/universe/package/curryst/\")[curryst]包提供了接收条件、规则、结论参数的`rule`函数，其返回一个包含传入信息的`dict`，并且允许把`rule`函数返回的`dict`作为`rule`的部分参数。于是我们可以通过嵌套`rule`函数建立描述推理过程的树，并通过该包提供的`prooftree`函数把包含推理过程的`dict`树画出来：\n\n#code(```typ\n#import \"@preview/curryst:0.5.0\": rule, prooftree\n#let tree-dict = rule(\n  name: $R$,\n  $C_1 or C_2 or C_3$,\n  rule(\n    name: $A$,\n    $C_1 or C_2 or L$,\n    rule(\n      $C_1 or L$,\n      $Pi_1$,\n    ),\n  ),\n  rule(\n    $C_2 or overline(L)$,\n    $Pi_2$,\n  ),\n)\n`tree-dict`的类型：#type(tree-dict) \\\n`tree-dict`代表的树：#prooftree(tree-dict)\n```)\n\n== 内容类型 <content-type-feature>\n\n我们已经学过很多元素：段落、标题、代码片段等。这些元素在被创建后都会被包装成为一种被称为「内容」的值。这些值所具有的类型便被称为「内容类型」。同时「内容类型」提供了一组公共方法访问元素本身。\n\n乍一听，内容就像是一个“容器”将元素包裹。但内容又不太像是之前所学过的数组或字典那样的复合字面量，或者说这样不方便理解。事实上，每个元素都有各自的特点，但仅仅为了保持动态性，所有的元素都被硬凑在一起，共享一种类型。有两种理解这种类型的视角：从表象论，「内容类型」是一种鸭子类型；从原理论，「内容类型」提供了操控内容的公共方法，即它是一种接口，或称特征（Trait）。\n\n=== 特性一：元素包装于「内容」\n\n我们知道所有的元素语法都可以等价使用相应的函数构造。例如标题：\n\n#code(```typ\n#repr([= 123]) \\ // 语法构造\n#repr(heading(depth: 1)[123]) // 函数构造\n\n```)\n\n一个常见的误区是误认为元素继承自「内容类型」，进而使用以下方法判断一个内容是否为标题元素：\n\n#code(```typ\n标题是heading类型（伪）？#(type([= 123]) == heading)\n```)\n\n但两者类型并不一样。事实上，元素是「函数类型」，元素函数的返回值为「内容类型」。\n\n#code(```typ\n标题函数的类型：#(type(heading)) \\\n标题的类型：#type([= 123])\n```)\n\n这引出了一个重要的理念，Typst中一切皆组合。Typst中目前没有继承概念，一切功能都是组合出来的，这类似于Rust语言的概念。你可能没有学过Rust语言，但这里有一个冷知识：\n\n#align(center, [Typst $<=>$ Typ(setting Ru)st $<=>$ Typesetting Rust])\n\n即Typst是以Rust语言特性为基础设计出的一个排版（Typesetting）语言。\n\n当各式各样的元素函数接受参数时，它们会构造出「元素」，然后将元素包装成一个共同的类型：「内容类型」。`heading`是函数而不是类型。与其他语言不同，没有一个`heading`类型继承`content`。因此不能使用`type([= 123]) == heading`判断一个内容是否为标题元素。\n\n=== 特性二：内容类型的`func`方法\n\n所有内容都允许使用`func`得到构造这个内容所使用的函数。因此，可以使用以下方法判断一个内容是否为标题元素：\n\n#code(```typ\n标题所使用的构造函数：#([= 123]).func()\n\n标题的构造函数是`heading`？#(([= 123]).func() == heading)\n```)\n\n// 这一段不要了\n// === 特性二点五：内容类型的`func`方法可以直接拿来用\n\n// `func`方法返回的就是函数本身，自然也可以拿来使用：\n\n// #code(```typ\n// 重新构造标题：#(([= 123]).func())([456])\n// ```)\n\n// 这一般没什么用，但是有的时候可以用于得到一些Typst没有暴露出来的内容函数，例如`styled`。\n\n// #code(```typ\n// #let type_styled = text(fill: red, \"\").func()\n// #let st = text(fill: blue, \"\").styles\n// #text([abc], st)\n// ```)\n\n=== 特性三：内容类型的`fields`方法\n\nTypst中一切皆组合，它将所有内容打包成「内容类型」的值以完成类型上的统一，而非类型继承。\n\n但是这也有坏处，坏处是无法“透明”访问内部内容。例如，我们可能希望知道`heading`的级别。如果不提供任何方法访问标题的级别，那么我们就无法编程完成与之相关的排版。\n\n为了解决这个问题，Typst提供一个`fields`方法提供一个content的部分信息：\n\n#code(```typ\n#([= 123]).fields()\n```)\n\n`fields()`将部分信息组成字典并返回。如上图所示，我们可以通过这个字典对象进一步访问标题的内容和级别。\n\n#code(```typ\n#([= 123]).fields().at(\"depth\")\n```)\n\n#pro-tip[\n  这里的“部分信息”描述稍显模糊。具体来说，Typst只允许你直接访问元素中不受样式影响的信息，至少包含语法属性，而不允许你*直接*访问元素的样式。\n\n  // 如下：\n\n  // #code.with(al: top)(````typ\n  // #let x = [= 123]\n  // #rect([#x <the-heading>])\n  // #x.fields() \\\n  // #locate(loc => query(<the-heading>, loc))\n  // ````)\n]\n\n=== 特性四：内容类型与`fields`相关的糖 <grammar-content-member-exp>\n\n由于我们经常需要与`fields`交互，Typst提供了`has`方法帮助我们判断一个内容的`fields`是否有相关的「键」。\n\n#code(```typ\n使用`... in x.fields()`判断：#(\"text\" in `x`.fields()) \\\n等同于使用`has`方法判断：#(`x`.has(\"text\"))\n```)\n\nTypst提供了`at`方法帮助我们访问一个内容的`fields`中键对应的值。\n\n#code(```typ\n使用`x.fields().at()`获取值：#(`www`.fields().at(\"text\")) \\\n等同于使用`at`方法：#(`www`.at(\"text\"))\n```)\n\n特别地，内容的成员包含`fields`的键，我们可以直接通过成员访问相关信息：\n\n#code(```typ\n使用`at`方法：#(`www`.at(\"text\")) \\\n等同于访问`text`成员：#(`www`.text)\n```)\n"
  },
  {
    "path": "src/tutorial/scripting-control-flow.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"控制流\")\n\n== `none`类型和`if`语句 <grammar-if>\n\n默认情况下，在逻辑上，Typst按照顺序执行执行你的代码，即先执行前面的语句，再执行后面的语句。开发者如果想要控制程序执行的流程，就必须使用流程控制的语法结构，主要是条件执行和循环执行。\n\n`if`语句用于条件判断，满足条件时，就执行指定的语句。\n\n```typ\n#if expression { then-block } else { else-block }\n#if expression { then-block }\n```\n\n上面式子中，表达式`expression`为真（值为布尔值`true`）时，就执行`then-block`代码块，否则执行`else-block`代码块。特别地，`else`可以省略。\n\n如下所示：\n\n#code(```typ\n#if (1 < 2) { \"确实\" } else { \"啊？\" }\n```)\n\n因为`1 < 2`表达式为真，所以脚本仅执行了`then-block`代码块，于是最后文档的内容为“确实”。\n\n`if`语句还可以无限串联下去，你可以自行类比推理更长的`if`语句的语义：<grammar-if-if>\n\n```typ\n#if expression { .. } else if expression { .. } else { .. }\n#if expression { .. } else if expression { .. }\n#if expression { .. } else if expression { .. } else if ..\n```\n如果只写了`then`代码块，而没写`else`代码块，但偏偏表达式不为真，最终脚本会报错吗？请看：\n\n#code(```typ\n#repr(if (1 > 2) { \"啊？\" })\n```)\n\n当`if`表达式没写`else`代码块而条件为假时，结果为`none`。“none”在中文里意思是“无”，表示“什么都没有”。同时再次强调`none`在「可折叠」的值中很重要的一个性质：`none`在折叠过程中被忽略。\n\n见下程序，其根据数组所包含的值输出特定字符串：\n\n#code(```typ\n#let 查成分(成分数组) = {\n  \"是个\"\n  if \"A\" in 成分数组 or \"C\" in 成分数组 or \"G\" in 成分数组 { \"萌萌\" }\n  if \"T\" in 成分数组 { \"工具\" }\n  \"人\"\n}\n\n#查成分(()) \\\n#查成分((\"A\",\"T\",)) \\\n```)\n\n由于`if`也是表达式，你可以直接将`if`作为函数体，例如fibnacci函数的递归可以非常简单：\n\n#code(```typ\n#let fib(n) = if n <= 1 { n } else {\n  fib(n - 1) + fib(n - 2)\n}\n#fib(46)\n```)\n\n== `while`语句 <grammar-while>\n\n// if condition {..}\n// if condition [..]\n// if condition [..] else {..}\n// if condition [..] else if condition {..} else [..]\n\n`while`语句用于循环结构，满足条件时，不断执行循环体。\n\n```typ\n#while expression { cont-block }\n```\n\n上面代码中，如果表达式`expression`为真，就会执行`cont-block`代码块，然后再次判断`expression`是否为假；如果`expression`为假就跳出循环，不再执行循环体。\n\n#code(```typ\n#let i = 0;\n#while i < 10 { (i * 2, ); i += 1 }\n```)\n\n上面代码中，循环体会执行`10`次，每次将`i`增加`1`，直到等于`10`才退出循环。\n\n== `for`语句 <grammar-for>\n\n`for`语句也是常用的循环结构，它迭代访问某个对象的每一项。\n\n```typ\n#for X in A { cont-block }\n```\n\n上面代码中，对于`A`的每一项，都执行`cont-block`代码块。在执行`cont-block`时，项的内容是`X`。例如以下代码做了与之前循环相同的事情：\n\n#code(```typ\n#for i in range(10) { (i * 2, ) }\n```)\n\n其中`range(10)`创建了一个内容为`(0, 1, 2, .., 9)`一共10个值的数组。\n\n== 使用内容块替代代码块\n\n所有可以使用代码块的地方都可以使用内容块作为替代。\n\n#code(```typ\n#for i in range(4) [阿巴]......\n```)\n\n== 使用`for`遍历字典\n\n与数组相同，同理所有字典也都可以使用`for`遍历。此时，在执行`cont-block`时，Typst将每个键值对以数组的形式交给你。键值对数组的第0项是键，键值对数组的第1项是对应的值。\n\n#code(```typ\n#let cat = (neko-mimi: 2, \"utterance\": \"喵喵喵\", attribute: [kawaii\\~])\n#for i in cat {\n  [猫猫的 #i.at(0) 是 #i.at(1)\\ ]\n}\n```)\n\n你可以同时使用「解构赋值」让代码变得更容易阅读：<grammar-for-destruct>\n\n```typ\n#let cat = (neko-mimi: 2, \"utterance\": \"喵喵喵\", attribute: [kawaii\\~])\n#for (特色, 这个) in cat [猫猫的 #特色 是 #这个\\ ]\n```\n\n== `break`语句和`continue`语句 <grammar-break>\n\n无论是`while`还是`for`，都可以使用`break`跳出循环，或`continue`直接进入下一次执行。\n\n基于以下`for`循环，我们探索`break`和`continue`语句的作用。\n\n#code(```typ\n#for i in range(10) { (i, ) }\n```)\n\n在第一次执行时，如果我们直接使用`break`跳出循环，但是在break之前就已经产生了一些值，那么`for`的结果是`break`前的那些值的「折叠」。\n\n#code(```typ\n#for i in range(10) { (i, ); (i + 1926, ); break }\n```)\n\n特别地，如果我们直接使用`break`跳出循环，那么`for`的结果是*`none`*。\n\n#code(```typ\n#for i in range(10) { break }\n```)\n\n在`break`之后的那些值将会被忽略：\n\n#code(```typ\n#for i in range(10) { break; (i, ); (i + 1926, ); }\n```)\n\n以下代码将收集迭代的所有结果，直到`i >= 5`：\n#code(```typ\n#for i in range(10) {\n  if i >= 5 { break }\n  (i, )\n}\n```)\n\n// #for 方位 in (\"东\", \"南\", \"西\", \"北\", \"中\", \"间\", \"东北\", \"西北\", \"东南\", \"西南\") [鱼戏莲叶#方位，]\n\n`continue`有相似的规则，便不再赘述。我们举一个例子，以下程序输出在`range(10)`中不是偶数的数字：<grammar-continue>\n\n#code(```typ\n#let 是偶数(i) = calc.even(i)\n#for i in range(10) {\n  if 是偶数(i) { continue }\n  (i, )\n}\n```)\n\n事实上`break`语句和`continue`语句还可以在参数列表中使用，但本书非常不推荐这些写法，因此也不多做介绍：\n\n#code(```typ\n#let add(a, b, c) = a + b + c\n#while true { add(1, break, 2) }\n```)\n\n== 控制函数返回值\n\n你可以通过多种方法控制函数返回值。\n\n=== 占位符 <grammar-placeholder>\n\n早在上节我们就学习过了占位符，这在编写函数体表达式的时候尤为有用。你可以通过占位符忽略不需要的函数返回值。\n\n以下函数获取数组的倒数第二个元素：\n\n#code(```typ\n#let last-two(t) = {\n  let _ = t.pop()\n  t.pop()\n}\n#last-two((1, 2, 3, 4))\n```)\n\n=== `return`语句 <grammar-return>\n\n你可以通过`return`语句忽略表达式其余*所有语句*的结果，而使用`return`语句返回特定的值。\n\n以下函数获取数组的倒数第二个元素：\n\n#code(```typ\n#let last-two(t) = {\n  t.pop()\n  return t.pop()\n}\n#last-two((1, 2, 3, 4))\n```)\n\n\n== 「作用域」 <grammar-scope>\n\n// 内容块与代码块没有什么不同。\n\n「作用域」是一个非常抽象的概念。但是理解他也并不困难。我们需要记住一件事，那就是每个「代码块」创建了一个单独的「作用域」：\n\n#code(```typ\n两只#{\n  [兔]\n  set text(rgb(\"#ffd1dc\").darken(15%))\n  { [兔白]; set text(orange); [又白] }\n  [，真可爱]\n}\n```)\n\n从上面的染色结果来看，粉色规则可以染色到`[兔白]`、`[又白]`和`[真可爱]`，橘色规则可以染色到`[又白]`但不能染色到[，真可爱]。以内容的视角来看：\n1. `[兔]`不是相对粉色规则的后续内容，更不是相对橘色规则的后续内容，所以它默认是黑色。\n2. `[兔白]`是相对粉色规则的后续内容，所以它是粉色。\n3. `[又白]`同时被两个规则影响，但是根据「执行顺序」，橘色规则被优先使用。\n4. `[真可爱]`虽然从代码先后顺序来看在橘色规则后面，但不在橘色规则所在作用域内，不满足「`set`」影响范围的设定。\n\n我们说「`set`」的影响范围是其所在「作用域」内的后续内容，意思是：对于每个「代码块」，「`set`」规则只影响到从它自身语句开始，到该「代码块」的结束位置。\n\n接下来，我们回忆：「内容块」和「代码块」没有什么不同。上述例子还可以以「内容块」的语法改写成：\n\n#code(```typ\n两只#[兔#set text(fill: rgb(\"#ffd1dc\").darken(15%))\n  #[兔白#set text(fill: orange)\n  又白]，真可爱\n]\n```)\n\n由于断行问题，这不方便阅读，但从结果来看，它们确实是等价的。\n\n最后我们再回忆：文件本身是一个「内容块」。\n\n#code(```typ\n两小只，#set text(fill: orange)\n真可爱\n```)\n\n针对文件，我们仍重申一遍「`set`」的影响范围。其影响等价于：对于文件本身，*顶层*「`set`」规则影响到该文件的结束位置。\n\n#pro-tip[\n  也就是说，`include`文件内部的样式不会影响到外部的样式。\n]\n\n== 变量的可变性\n\n理解「作用域」对理解变量的可变性有帮助。这原本是上一节的内容，但是前置知识包含「作用域」，故在此介绍。\n\n话说Typst对内置实现的所有函数都有良好的自我管理，但总免不了用户打算写一些逆天的函数。为了保证缓存计算仍较为有效，Typst强制要求用户编写的*所有函数*都是纯函数。这允许Typst有效地缓存计算，在相当一部分文档的编译速度上，快过LaTeX等语言上百倍。\n\n你可能不知道所谓的纯函数是为何物，本书也不打算讲解什么是纯函数。关键点是，涉及函数的*纯性*，就涉及到变量的可变性。\n\n所谓变量的可变性是指，你可以任意改变一个变量的内容，也就是说一个变量默认是可变的：\n\n#code(```typ\n#let a = 1; #let b = 2;\n#((a, b) = (b, a)); #a, #b \\\n#for i in range(10) { a += i }; #a, #b\n```)\n\n但是，一个函数的函数体表达式不允许涉及到函数体外的变量修改：\n\n#code(\n  ```typ\n  #let a = 1;\n  #let f() = (a += 1);\n  #f()\n  ```,\n  res: [#text(red, [error]): variables from outside the function are read-only and cannot be modified],\n)\n\n这是因为纯函数不允许产生带有副作用的操作。\n\n同时，传递进函数的数组和字典参数都会被拷贝。这将导致对参数数组或参数字典的修改不会影响外部变量的内容：\n\n#code(```typ\n#let a = (1, ); #a \\ // 初始值\n#let add-array(a) = (a += (2, ));\n#add-array(a); #a \\ // 函数调用无法修改变量\n#(a += (2, )); #a \\ // 实际期望的效果\n```)\n\n#pro-tip[\n  准确地来说，数组和字典参数会被写时拷贝。所谓写时拷贝，即只有当你期望修改数组和字典参数时，拷贝才会随即发生。\n]\n\n为了“修改”外部变量，你必须将修改过的变量设法传出函数，并在外部更新外部变量。\n\n#code(```typ\n#let a = (1, ); #a \\ // 初始值\n#let add-array(a) = { a.push(2); a };\n#(a = add-array(a)); #a \\ // 返回值更新数组\n```)\n\n#pro-tip[\n  一个函数是纯的，如果：\n  + 对于所有相同参数，返回相同的结果。\n  + 函数没有副作用，即局部静态变量、非局部变量、可变引用参数或输入/输出流等状态不会发生变化。\n\n  本节所讲述的内容是对第二点要求的体现。\n]\n\n== 「`set if`」语法 <grammar-set-if>\n\n回到「set」语法的话题。假设我们脚本中设置了当前文档是否处于暗黑主题，并希望使用「`set`」规则感知这个设定，你可能会写：\n\n#code(```typ\n#let is-dark-theme = true\n#if is-dark-theme {\n  set rect(fill: black)\n  set text(fill: white)\n}\n\n#rect([wink!])\n```)\n\n根据我们的知识，这应该不起作用，因为`if`后的代码块创建了一个新的作用域，而「`set`」规则只能影响到该代码块内后续的代码。但是`if`的`then`和`else`一定需要创建一个新的作用域，这有点难办了。\n\n`set if`语法出手了，它允许你在当前作用域设置规则。\n\n#code(```typ\n#let is-dark-theme = true\n#set rect(fill: black) if is-dark-theme\n#set text(fill: white) if is-dark-theme\n#rect([wink!])\n```)\n\n解读`#set rect(fill: black) if is-dark-theme`。它的意思是，如果满足`is-dark-theme`条件，那么设置相关规则。这其实与下面代码“感觉”一样。\n\n#code(```typ\n#let is-dark-theme = true\n#if is-dark-theme {\n  set rect(fill: black)\n}\n#rect([wink!])\n```)\n\n区别仅仅在`set if`语法确实从语法上没有新建一个作用域。这就好像一个“规则怪谈”：如果你想要让「`set`」规则影响到对应的内容，就想方设法满足「`set`」影响范围的要求。\n"
  },
  {
    "path": "src/tutorial/scripting-layout.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"长度与布局\")\n\n#todo-box[本节处于校对阶段，所以可能存在不完整或错误。]\n\n本章我们再度回到排版专题，拓宽制作文档的能力。\n\n#let absent(content) = underline(offset: 1.5pt, underline(offset: 3pt, text(red, content)))\n#let ng(content) = underline(offset: 1.5pt, text(blue, content))\n\n== 长度类型\n\n#show quote: it => {\n  box(\n    stroke: (\n      left: 2pt + main-color,\n    ),\n    inset: (\n      left: 12pt,\n      y: 8pt,\n    ),\n    {\n      stack(\n        {\n          set text(style: \"italic\")\n          it.body\n        },\n        if \"attribution\" in it.fields() {\n          1em\n        },\n        if \"attribution\" in it.fields() {\n          align(right, [——] + it.attribution)\n        },\n      )\n    },\n  )\n}\n\nTypst有三种长度单位，它们是：绝对长度（absolute length）、相对长度（relative length）与上下文有关长度（context-sensitive length）。\n\n+ 绝对长度：人们最熟知的长度单位。例如，```typc 1cm```恰等于真实的一厘米。\n+ 相对长度：与父元素长度相关联的长度单位，例如```typc 70%```恰等于父元素高度或宽度的70%。\n+ 上下文有关长度：与样式等上下文有关的长度单位，例如```typc 1em```恰等于当前位置设定的字体长度。\n\n掌握不同种类的长度单位对构建期望的布局非常重要。它们是相辅相成的。\n\n== 绝对长度\n\n目前Typst一共提供四种绝对长度。除了公制长度单位```typc 1cm```与```typc 1mm```与英制长度单位```typc 1in```，Typst还提供排版专用的长度单位“点”，即```typc 1pt```。\n\n#quote(attribution: link(\"https://zh.wikipedia.org/wiki/%E9%BB%9E_(%E5%8D%B0%E5%88%B7)\")[维基百科：点 (印刷)])[\n  点（英语：point），pt，是印刷所使用的长度单位，用于表示字型的大小，也用于余白（字距、行距）等其他版面构成要素的长度。作为铸字行业内部的一个专用单位，1 点的长度在世界各地、各个时代曾经有过不同定义，并不统一。当代最通行的是广泛应用于桌面排版软件的 DTP 点，72 点等于 1英寸（1 point = 127⁄360 mm = 0.352777... mm）。中国传统字体排印上的字号单位是号，而后采用“点”“号”兼容的体制。\n]\n\nTypst会将你提供的任意长度单位都统一成点单位，以便进行长度运算。\n\n#{\n  set align(center)\n  let units = ((1pt, \"pt\"), (1mm, \"mm\"), (1cm, \"cm\"), (1in, \"in\"))\n  let methods = (length.pt, length.mm, length.cm, length.inches)\n  table(\n    columns: 5,\n    \"\",\n    ..(\"pt\", \"mm\", \"cm\", \"in\").map(e => raw(\"=?\" + e)),\n    ..units\n      .map(((l, u)) => {\n        (raw(\"1\" + u, lang: \"typc\"),) + methods.map(method => [#calc.round(method(l), digits: 2)])\n      })\n      .flatten(),\n  )\n}\n\n== 绝对长度的运算\n\n长度单位可以参与任意多个浮点值的运算。一个长度表达式是合法的当且仅当运算结果*保持长度量纲*。请观察下列算式，它们都可以编译：\n\n#code(```typ\n#(1cm * 3), #(1cm / 3), #(2 * 1cm * 3 / 2), #(1cm + 3in)\n```)\n\n请观察下列算式，它们都不能编译：\n\n#(\n  ```typ\n  #(1cm + 3), #(3 / 1cm), #(1cm * 1cm)\n  ```\n)\n\n所谓*保持长度量纲*，即它存在一系列判别规则：\n\n- 由于`1cm`与`3in`量纲均为长度量纲（`m`），它们之间*可以*进行*加减*运算。\n- 由于`3`无量纲，`1cm`与`3`之间*不能*进行*加减*运算。\n\n进一步，通过量纲运算，可以判断一个长度算术表达式是否合法：\n\n#{\n  set align(center)\n  table(\n    columns: 4,\n    [长度表达式],\n    [量纲运算],\n    [检查合法性],\n    [判断结果],\n    ```typc 1cm * 3```,\n    $bold(sans(m dot 1 = m))$,\n    $bold(sans(m = m))$,\n    table.cell(rowspan: 2, align: horizon)[合法],\n    ```typc 1cm / 3```,\n    $bold(sans(m op(slash) 1 = m))$,\n    $bold(sans(m = m))$,\n    ```typc 3 / 1cm```,\n    $bold(sans(1 op(slash) m = m^(-1)))$,\n    $bold(sans(m^(-1) = m))$,\n    table.cell(rowspan: 2, align: horizon)[非法],\n    ```typc 1cm * 1cm```,\n    $bold(sans(m dot m = m^2))$,\n    $bold(sans(m^2 = m))$,\n  )\n}\n\n== 绝对长度的转换\n\n你可以使用`length`类型上的「方法」实现不同单位到浮点数的转换：\n\n#code(```typ\n#1cm 是 #1cm.pt() 点 \\\n#1cm 是 #1cm.inches() 英尺\n```)\n\n你可以使用乘法实现浮点数到长度上的转换，例如```typc 28.3465 * 1pt```：\n\n#code(```typ\n#1cm 是 #(28.3465 * 1pt).cm() 厘米\n```)\n\n== 相对长度\n\n有两种相对长度（Relative Length），一是百分比（Ratio），一是分数比（Fraction）。\n\n=== 百分比 <reference-type-ratio>\n\n#let p(w, f, ..args) = box(\n  width: w,\n  height: 10pt,\n  fill: f,\n  ..args,\n)\n#let code = code.with(scope: (p: p))\n\n当「百分比」用作长度时，其实际值取决于父容器宽度：\n\n#code(```typ\n#let p(w, f, ..args) = box(\n  width: w, height: 10pt, fill: f, ..args)\n4比6：#p(100pt, blue, p(40%, red))\n```)\n\nTypst还支持以「分数比」作长度单位。当分数比作长度单位时，Typst按比例分配长度。\n\n#code(```typ\n4比6：#p(100pt, blue,\n  p(4fr, red) + p(6fr, blue))\n```)\n\n结合代码与图例理解，`N fr`代表：在总的比例中，这个元素应当占有其中`N`份长度。\n\n当同级元素既有分数比长度元素，又有其他长度单位元素时，优先将空间分配给其他长度单位元素。\n\n#code(```typ\n绿色先占60%: #p(100pt, blue, p(1fr, red) + p(2fr, blue) + p(60%, green)) \\\n绿色先占110%: #p(100pt, blue, p(1fr, red) + p(1fr, blue) + p(110%, green)) \\\n红色先占30pt: #p(100pt, blue, p(30pt, red) + p(1fr, blue) + p(110%, green))\n```)\n\n建议结合下文中grid布局关于长度的使用，加深对相对长度的理解。\n\n== 上下文有关长度\n\n目前Typst仅提供一种上下文有关长度，即当前上下文中的字体大小。历史上，定义该字体中大写字母`M`的宽度为`1em`，但是现代排版中，`1em`可以比`M`的宽度要更窄或者更宽。\n\n上下文有关长度是与相对长度相区分的。区别是上下文有关长度的取值从「样式链」获取，而相对长度相对于父元素宽度。事实上`1em`的具体值可以通过上下文有关表达式获取：\n\n#code(```typ\n#let _1em = context measure(\n  line(length: 1em)).width\n#text(size: 10pt, [1em等于] + _1em) \\\n#text(size: 20pt, [1em等于] + _1em) \\\n```)\n\n相比较，`1em`更好用一点，因为`text.size`只允许在上下文有关表达式内部使用。\n\n== 混合长度\n\n以上所介绍的各种长度可以通过「加号」任意混合成单个长度的值，其长度的值为每个分量总和：\n\n#code(```typ\n#(1pt + 1em + 100%)\n```)\n\n== 长度的内省或评估\n\n你可以通过`measure`获取一个长度在当前位置的具体值：\n\n#let length-of(l) = (measure(line(length: l)).width)\n#let code = code.with(scope: (length-of: length-of))\n\n#code(```typ\n#let length-of(l) = measure(\n  line(length: l)).width\n#context [长度等于#length-of(1em+1pt)。]\n```)\n\n但是该方式是不被推荐的，因为一个长度值中的相对长度分量会被评估为`0pt`，从而导致计算失真：\n\n#code(```typ\n长度等于 #context length-of(1em+1pt+100%)。\n```)\n\n这是因为在评估的时候，`measure`没有为内容锚定一个“父元素”。\n\n一种更为鲁棒的方法是使用`layout`函数获取`layout`位置的宽度和高度信息：\n\n#code(```typ\n长度等于#box(width: 1em+1pt+100%,\n  layout(l => l.width))\n```)\n\n但是使用`layout`会导致布局的多轮迭代，有可能*严重*降低编译性能。\n\n== 布局概览与布局模型\n\nTypst的布局引擎仍未完成，其主要#absent[缺失]或#ng[不足]的内容为：\n+ 修饰段落容器的#absent[行]或#ng[字符]的能力。\n+ 将段落容器的接口#ng[暴露]给用户的能力。\n+ #ng[浮动布局]的能力。\n+ #ng[更灵活]的Layout Splitter。目前仅支持`columns`（多栏布局）的能力。\n\nTypst的布局代码风格是用一系列容器函数包装的内容，再将整个内容交给布局引擎反复*迭代*求解。容器就是一类特殊的「元素」，它并不真正具备具体的内容，而仅仅容纳一个或多个「内容」，*以便布局*。这与JSX有些相似。\n\n针对PDF页面模型，Typst构建了完整的容器层次。Todo：用cetz绘制层次：\n#todo-box[在0.13.0之后, 情况有所改变]\n\n1. page，一个PDF有很多页。\n2. par，一个文档中有很多段。\n3. block，一页/段中有很多块。\n4. box，一个块中有很多行内元素。\n5. sequence，很多个元素可以组成一个元素序列。\n6. 各种基础元素。\n\n=== page\n\n每个`page`都单独产生一个单独的页面。并且若你希望将Typst文档导出为PDF，其恰好为PDF中单独的一页。\n\n任何内容都会至少产生新的一页：\n\n```typ\n// 第一页\nA\n```\n\n每个`pagebreak`函数（分页函数）将会产生新的一页：\n\n```typ\n// 第一页\nA\n#pagebreak()\n// 第二页\nB\n```\n\n```typ\n// 第一页\nA\n#pagebreak()\n// 第二页\n#pagebreak()\n// 第三页\nB\n```\n\n你可以使用`#pagebreak(weak: true)`产生弱的分页。这里弱的意思是：假设当前页面并没有包含任何内容，`pagebreak(weak: true)`就不会创建新的页面。你可以理解为弱的分页意即非必要不分页。\n\n```typ\n// 第一页\nA\n#pagebreak(weak: true)\n#pagebreak(weak: true)\n// 第二页\nB\n```\n\n每个`page`函数都会单列新的一页：\n\n```typ\n// 第一页\nA\n#page[\n  // 第二页\n  A\n]\n// 第三页\nB\n```\n\n如果`page`的前后已经是空页，则`page`不会导致前后产生空页。\n\n```typ\n// 这里不分页\n#page[\n  // 第一页\n  A\n] // 这里不分页\n#page[\n  // 第二页\n  A\n] // 这里不分页\n```\n\n你可以理解为`page`前后自动产生弱的分页，其前后*非必要不分页*，上例相当于：\n\n```typ\n#pagebreak(weak: true)\n// 第一页\nA\n#pagebreak(weak: true)\n#pagebreak(weak: true)\n// 第二页\nA\n#pagebreak(weak: true)\n```\n\n所以`page`与`pagebreak`是互通的，你可以随意使用你喜欢的风格创建新的页面。\n\n*注意*：设置`page`样式将导致新的弱分页：\n\n```typ\n// 第一页\nA\n#set page(..)\n// 第二页\nB\n```\n\n等价于：\n\n```typ\nA\n#pagebreak(weak: true)\n#set page(..)\n#pagebreak(weak: true)\nB\n```\n\n=== par\n\n每个`par`都单独产生一个单独的段落。\n\n`par`的特性与`page`完全相同，其前后非必要不分段。\n\n#code(```typ\n#par[\n  // 第一段\n  A\n]\n```)\n\n`parbreak`与`pagebreak`也是除了同理，除了`parbreak`一定是弱分段，并且没有可以指定的`weak`参数：\n\n#code(```typ\n// 第一段\nA\n#parbreak()\n#parbreak()\n// 第二段\nB\n```)\n\n很多元素都会在其前后自动产生新的段落，例如`figure`和`heading`。\n\n=== block\n\n`block`是布局中的块状元素。块状元素会在布局中自成一行。你几乎可以将`block`理解为某种程度上的分段：\n\n#code(```typ\n// 第一段\n#block(fill: blue)[A]\n#block(fill: blue)[B]\n```)\n\n事实上`par`本来就由`block`组成。你可以通过对`block`设置规则影响到段间距：\n\n#code(```typ\n#set block(spacing: 0.5em)\nA\n#parbreak()\n#parbreak()\nB\n```)\n\n=== box\n\n`box`是布局中的行内块元素。行内块元素会在布局中自成一行。所谓行内块即其自成一块但不会立即导致换行。如下所示：\n\n#code(```typ\n#rect(width: 100pt)[a a a #box(fill: blue)[A A A A A]a a a]\n```)\n\n`box`并不意味着不换行。`box`的宽度默认占满父元素内宽度的```typc 100%```。`box`内部的文本在其内部继续换行。如下所示，当文字太多时，将导致其在`box`内部换行：\n\n#code(```typ\n#rect(width: 100pt)[#box(fill: blue)[A A A A A A A A A A A A A]]\n```)\n\n`box`整体必须处于段落中的同一行：\n\n#code(```typ\n#rect(width: 100pt)[a a a a#box(fill: blue, width: 50%)[A A A A A A A A A]a a a]\n#rect(width: 100pt)[a a a a a#box(fill: blue, width: 50%)[A A A A A A A A A]a a a]\n```)\n\n一个妙用是，你可以将“块元素”包裹一层`box`，使得这些块元素位于段落中的同一行：\n\n#code(```typ\n我们之中混入了\n#box(rect(width: 100pt)[a a a a#box(fill: blue, width: 50%)[A A A A A A A A A]a a a])\n两个矩形\n#box(rect(width: 100pt)[a a a a a#box(fill: blue, width: 50%)[A A A A A A A A A]a a a])\n```)\n\n=== sequence\n\n`sequence`是布局中最松散的组织。事实上，它正是我们在《基础文档》中就学过的「内容块」。它起不到任何效果，仅仅容纳一系列其他内容。\n\n#code(```typ\n#rect(width: 100pt)[#[a a a a a a] #[a a a a a a a a] ]\n```)\n\n我们来温习一下。`sequence`主要的作用是可以储存一段内容供函数使用。使用`sequence`的一个好处是允许「标签」准确地选中一段内容。例如，以下代码自制了`highlight`效果：\n\n#code(```typ\n#show <fill-blue>: it => {\n  show regex(\"[\\w]\"): it => {\n    box(place(dx: -0.1em, dy: -0.15em, box(fill: blue, width: 0.85em, height: 0.95em)) + it)\n  }\n  it\n}\n#rect(width: 100pt)[#[a a a a a a] #[a a a a a a a ab] <fill-blue> ]\n```)\n\n== 「布局分割及其断点」\n\n「布局分割器」（Splitter）将所持有内容分成多段并放置于页面上。\n\n目前Typst唯一提供的分割器是`columns`，其实现了多栏布局。因为缺乏好用的分割器，有很多布局都*难以*在Typst中实现，例如浮动布局。但注意，这不代表现在的Typst不支持各种特殊布局。已经有外部库帮助你实现了首字母下沉，浮动布局等特殊布局。\n\n你可以使用`columns`在任意容器内创建多栏布局，并用`colbreak`分割每栏。\n\n#code(```typ\n#columns(2)[\n  #lorem(13)\n  #colbreak()\n  #lorem(10)\n]\n```)\n\n尽管还不是很完善，在与页面的结合中，Typst为`page`提供了一个`columns`参数。该`columns`允许你设置跨页的多栏布局。\n\n// #frames(\n//   ```typ\n//   #set page(columns: 2)\n//   #set text(size: 5pt)\n//   #lorem(130)\n//   ```,\n//   prelude: ```typ\n//   #set page(width: 120pt, height: 120pt)\n//   ```,\n// )\n\n== 「布局编排」\n\n「布局编排器」（Arranger）将多个内容组成一个新的带有布局的整体。目前，Typst提供了两种零维布局`pad`和`align`，一种一维布局`stack`，以及一种二维布局`grid`。\n\n== pad\n\n你可以使用`pad`函数为内部元素设置outer padding。`pad`接受一个内容，返回一个添加padding的新内容。默认情况下，`pad`函数不添加任何padding。\n\n#code(```typ\n#let square = rect(fill: blue, width: 15pt, height: 15pt)\n#box(fill: red, square)\n#box(fill: red, pad(square))\n```)\n\n与CSS的`padding`属性一样，你可以使用`left`、`top`、`right`、`bottom`设置四个方向的padding：\n\n#code(```typ\n#let square = rect(fill: blue, width: 15pt, height: 15pt)\n#box(fill: red, pad(left: 5pt, square))\n#box(fill: red, pad(top: 5pt, square))\n#box(fill: red, pad(right: 5pt, square))\n#box(fill: red, baseline: 5pt, pad(bottom: 5pt, square))\n#box(fill: red, pad(left: 5pt, top: 5pt, square))\n#box(fill: red, pad(left: 5pt, right: 5pt, square))\n```)\n\n特别地，你可以使用`x`同时设置`left`和`right`方向的padding，`y`同时设置`top`和`bottom`方向的padding，以及`rest`设置“其余”方向的padding。\n\n#code(```typ\n#let square = rect(fill: blue, width: 15pt, height: 15pt)\n#box(fill: red, pad(x: 5pt, square))\n#box(fill: red, baseline: 5pt, pad(y: 5pt, square))\n#box(fill: red, baseline: 2pt, pad(left: 5pt, rest: 2pt, square))\n```)\n\n== align\n\n你可以使用`align`函数为内部元素设置相对于父元素的对齐方式。默认情况下，`align`函数使用默认对齐方式（`start+top`）。\n\n#let square = rect(fill: rgb(\"c45a65c0\"), width: 15pt, height: 15pt)\n#let abox = box.with(fill: gradient.radial(..color.map.mako), width: 20pt, height: 20pt)\n\n#let align-scope = (\n  square: square,\n  abox: abox,\n  red: rgb(\"c45a65c0\"),\n)\n\n#code(\n  ```typ\n  #let square = rect(fill: red, width: 15pt, height: 15pt)\n  #let abox = box.with(fill: gradient.radial(..color.map.mako), width: 20pt, height: 20pt)\n  #abox(square)\n  #abox(align(square))\n  ```,\n  scope: align-scope,\n)\n\n与上一节介绍的`pad`类似，你可以使用`left`、`top`、`right`、`bottom`设置四个方向的alignment，并任意使用加号（`+`）组合出你想要的二维对齐方式：\n\n#code(\n  ```typ\n  #abox(align(left + top, square))\n  #abox(align(right + top, square)) \\\n  #abox(align(left + bottom, square))\n  #abox(align(right + bottom, square))\n  ```,\n  scope: align-scope,\n)\n\n除了以上四种基础对齐，Typst还额外提供两套对齐方式。第一套是居中对齐，你可以使用`center`实现水平居中对齐，以及`horizon`实现垂直居中对齐。\n\n#code(\n  ```typ\n  #abox(align(center, square))\n  #abox(align(horizon, square))\n  #abox(align(center + horizon, square))\n  ```,\n  scope: align-scope,\n)\n\n第二套与文本流相关。你可以使用`start`实现与文本流方向一致的对齐，以及`end`实现与文本流方向相反的对齐。\n\n#code(\n  ```typ\n  #let se = abox(align(start, square)) + abox(align(end, square))\n  #se \\\n  #set text(dir: rtl)\n  #se\n  ```,\n  scope: align-scope,\n)\n\n=== stack\n\n与HTML中的`flex`容器类似，`stack`可以帮你实现各种一维布局。顾名思义，`stack`将其内部元素在页面上按顺序按方向排列。\n\n#let rects = (rect(fill: red)[1], rect(fill: blue)[2], rect(fill: green)[3])\n\n#code(\n  ```typ\n  #let rects = (rect(fill: red)[1], rect(fill: blue)[2], rect(fill: green)[3])\n  #stack(dir: ltr, ..rects)\n  ```,\n  scope: (rects: rects),\n)\n\n`stack`主要只有两个可以控制的参数。\n\n其一是排列的方向，参数对应为`dir`。一共有四种方向可以选择，分别是：\n- `ltr`: 即left to right，从左至右。\n- `rtl`: 即right to left，从右至左。\n- `ttb`: 即top to bottom，从上至下。\n- `btt`: 即bottom to top，从下至上。\n\n方向默认为`ttb`。\n\n#code(\n  ```typ\n  #columns(2)[\n    `ltr`: #box(stack(dir: ltr, ..rects)) \\ `rtl`: #box(stack(dir: rtl, ..rects))\n    #colbreak()\n    `ttb`: #box(stack(/* 默认值为：dir: ttb, */ ..rects)) #h(1em) `btt`: #box(stack(dir: btt, ..rects))\n  ]\n  ```,\n  scope: (rects: rects, box: box.with(baseline: 50% - 0.25em)),\n)\n\n其二是排列的间距，参数对应为`spacing`。你可以使用各种长度为`stack`内部元素制定合适的间距。\n\n#code(\n  ```typ\n  #let stack = stack.with(dir: ltr)\n  #let example(s) = box(width: 100pt, stack(spacing: s, ..rects))\n  `5pt    `: #example(5pt    ) \\\n  `10%    `: #example(10%    ) \\\n  `5pt+10%`: #example(5pt+10%) \\\n  `1fr    `: #example(1fr    ) \\\n  ```,\n  scope: (rects: rects, box: box.with(baseline: 50% - 0.25em)),\n)\n\n你也可以直接将长度作为参数传入，为每一个元素前后微调间距。\n\n#code(\n  ```typ\n  #let stack = stack.with(dir: ltr)\n  #let r = rect(fill: blue)[ ]\n  `1fr,0pt`: #box(width: 100pt, stack(r, 1fr, r, r)) \\\n  `1fr,2fr`: #box(width: 100pt, stack(r, 1fr, r, 2fr, r)) \\\n  `10%,40%`: #box(width: 100pt, stack(r, 10%, r, 40%, r)) \\\n  ```,\n  scope: (rects: rects, box: box.with(baseline: 50% - 0.25em)),\n)\n\n当同时声明`spacing`参数和长度参数时，长度参数会覆盖`spacing`设置：\n\n#code(\n  ```typ\n  #show stack: box.with(width: 100pt, stroke: green)\n  #let stack = stack.with(dir: ltr)\n  #let r = rect(fill: blue)[ ]\n  `         `: #stack(spacing: 5pt, r, r, r, r, r) \\\n  ` 0pt     `: #stack(spacing: 5pt, r, r, r, r,  0pt, r) \\\n  `10pt     `: #stack(spacing: 5pt, r, r, r, r, 10pt, r) \\\n  `10pt+10pt`: #stack(spacing: 5pt, r, r, r, r, 10pt, 10pt, r) \\\n  ```,\n  scope: (box: box.with(baseline: 50% - 0.25em)),\n)\n\n=== stack+align/pad\n\n你可以将`stack`与`align/pad`相结合，实现更多一维布局。以下例子对应CSS相关属性：\n\n#code(\n  ```typ\n  #let box = box.with(width: 100pt, stroke: green)\n  #let stack = stack.with(dir: ltr, spacing: 1fr)\n  #let r = rect(fill: blue)[ ]\n  `space-around 5pt`: #box(stack(5pt, r, r, 5pt)) \\\n  `space-around 5pt`: #box(pad(x: 5pt, stack(r, r))) \\\n  `justify-content end`: #box(align(end, box(width: 60pt, stack(r, r)))) \\\n  `justify-content center`: #box(align(center, box(width: 60pt, stack(r, r)))) \\\n  ```,\n  scope: (box: box.with(baseline: 50% - 0.25em)),\n)\n\n上例提示我们可以组合多种布局容器实现特定的布局效果。\n\n=== grid\n\n与HTML中的`flex`容器类似，`grid`可以帮你实现各种二维布局。如下：\n\n#code(```typ\n#show grid: box.with(stroke: green)\n#let r = rect(fill: blue, width: 10pt, height: 10pt)\n#grid(columns: 3, gutter: 5pt, r, r, r, r, r)\n```)\n\n与`stack`相比，`grid`要复杂得多。\n\n其一，`grid`的前$N - 1$行可容纳的元素是有限的，而最后一行与`stack`一样容纳其余所有元素。你可以通过`columns`参数控制这一特性。\n\n#let g-scope = (\n  r: (n, width: 10pt, height: 10pt) => range(n).map(i => rect(\n    fill: color.map.rainbow.at(calc.rem(i * 16, 80)),\n    width: width,\n    height: height,\n  )),\n)\n\n`columns`要求你给定一个数组，该数组表示一行中每列的宽度。\n\n#code(\n  ```typ\n  #let r(n, width: 10pt, height: 10pt) = range(n).map(i => rect(fill: color.map.rainbow.at(calc.rem(i * 16, 80)), width: width, height: height))\n  #grid(columns: (auto, ) * 10, gutter: 5pt, ..r(10)) #h(1em)\n  #grid(columns: (auto, ) * 8, gutter: 5pt, ..r(10)) #h(1em)\n  #grid(columns: (auto, ) * 4, gutter: 5pt, ..r(10)) #h(1em)\n  #grid(columns: (auto, ) * 2, gutter: 5pt, ..r(10))\n  ```,\n  scope: (..g-scope, grid: (..args) => box(stroke: green, grid(..args))),\n)\n\n上例中，当`columns`数组的长度为`N`时，表示每行允许容纳`N`个元素，每列的*最大宽度*为`auto`。长度设定为`auto`，则表示每列宽度始终和该行该列内部元素宽度相等。\n\n每列宽度不仅可以为`auto`，而且可以指定为具体长度：\n\n#code(\n  ```typ\n  #show grid: box.with(stroke: green, width: 100pt)\n  #let grid = grid.with(gutter: 5pt, ..r(5, width: auto))\n  1: #grid(columns: (10pt, 20pt, 30pt)) #h(1em)\n  2: #grid(columns: (10%, 20%, 30%)) #h(1em)\n  3: #grid(columns: (1fr, 2fr, 3fr)) #h(1em) \\\n  4: #grid(columns: (10pt, 1fr, 1fr)) #h(1em)\n  5: #grid(columns: (10pt, 1fr, 2fr)) #h(1em)\n  ```,\n  scope: g-scope,\n)\n\nTypst允许你使用更简便的参数表示。特别地，如果`columns`数组内容恰为$N$个`auto`，那么可以直接传入一个数字。以下两种写法是等价的：\n\n#code(\n  ```typ\n  #grid(columns: 4, gutter: 5pt, ..r(10)) #h(1em)\n  #grid(columns: (auto, ) * 4, gutter: 5pt, ..r(10)) #h(1em)\n  ```,\n  scope: (..g-scope, grid: (..args) => box(stroke: green, grid(..args))),\n)\n\n特别地，如果期望`grid`只有一列，`columns`参数允许不传入数组而是单列的长度：\n\n#code(\n  ```typ\n  #show grid: box.with(stroke: green, width: 20pt)\n  #let grid = grid.with(gutter: 5pt, ..r(3, width: auto))\n  #grid(columns: (10pt, )) #h(1em)\n  #grid(columns: 10pt) #h(1em)\n  #grid(columns: 50%) #h(1em)\n  #grid(columns: 80%) #h(1em)\n  ```,\n  scope: g-scope,\n)\n\n特别地，columns的默认值可以理解为`auto`。\n\n其二，`grid`不再允许混杂内容与长度来控制某行或某列元素之间的间隔。取而代之的是三个更为复杂的参数：`gutter`、`row-gutter`和`column-gutter`。这是因为“混杂的gutter”在二维布局中有歧义。\n\n`gutter`参数事实上是`spacing`参数的加强版。它允许通过传入一个长度数组为*每行每列依次*设置间隔：\n\n#code(\n  ```typ\n  #let grid = grid.with(columns: (auto, ) * 4, ..r(15))\n  #grid(gutter: 5pt) #h(1em)\n  #grid(gutter: (5pt, 10pt, 15pt)) #h(1em)\n  ```,\n  scope: (..g-scope, grid: (..args) => box(stroke: green, grid(..args))),\n)\n\n特别地，如果`gutter`参数数组比`rows`参数数组或者`columns`参数数组更短，其余行或列的的间隔以`gutter`参数数组的最后一个长度为准：\n\n#code(\n  ```typ\n  #let grid = grid.with(columns: (auto, ) * 5, ..r(9))\n  // 等价为`5pt, 5pt, 5pt, ..`, 一直5pt下去\n  #grid(gutter: 5pt) #h(1em)\n  // 等价为`5pt, 5pt, 5pt, ..`, 一直5pt下去\n  #grid(gutter: (5pt,)) #h(1em)\n  // 等价为`5pt, 10pt, 10pt, ..`, 一直10pt下去\n  #grid(gutter: (5pt, 10pt)) #h(1em)\n  ```,\n  scope: (..g-scope, grid: (..args) => box(stroke: green, grid(..args))),\n)\n\n你可以指定`column-gutter`而非`gutter`，从而单独为*每列*设置间隔。`column-gutter`参数的优先级比`gutter`参数要高。\n\n#code(\n  ```typ\n  #let grid = grid.with(columns: (auto, ) * 4, ..r(15))\n  #grid(gutter: (5pt, 10pt, 15pt)) #h(1em)\n  #grid(gutter: 5pt, column-gutter: (5pt, 10pt, 15pt)) #h(1em)\n  #grid(column-gutter: (5pt, 10pt, 15pt)) #h(1em)\n  ```,\n  scope: (..g-scope, grid: (..args) => box(stroke: green, grid(..args))),\n)\n\n与`column-gutter`同理，你也可以指定`row-gutter`而非`gutter`，从而单独为*每行*设置间隔。\n\n与`columns`同理，你可以指定`rows`从而为每行设置高度。但是请注意，Typst对`rows`参数数组的解释方式与`columns`不同，而与`gutter`相同：\n\n#code(\n  ```typ\n  #show grid: box.with(stroke: green, height: 120pt)\n  #let grid = grid.with(columns: 2, gutter: 5pt, ..r(7, height: auto))\n  // 等价为`10pt, 10pt, 10pt, ..`, 一直10pt下去\n  1: #grid(rows: (10pt, )) #h(1em)\n  // 等价为`10pt, 20pt, 20pt, ..`, 一直20pt下去\n  2: #grid(rows: (10pt, 20pt)) #h(1em)\n  // 等价为`1fr, 2fr, 2fr, ..`, 一直2fr下去\n  3: #grid(rows: (1fr, 2fr)) #h(1em)\n  ```,\n  scope: g-scope,\n)\n\n// #grid(gutter: (5pt, 10pt)) #h(1em)\n\n== 「间距」\n\n此类函数的概念引自LaTeX，又称胶水函数。一共有两个方向的间距，分别是水平间距与竖直间距。\n\n水平间距（`h`，horizon）不包含内容，而仅仅在布局中占据一定宽度：\n\n#code(```typ\n两句话之间间隔`0pt`#h(0pt)两句话之间间隔`0pt` \\\n两句话之间间隔`5pt`#h(5pt)两句话之间间隔`5pt` \\\n两句话之间间隔`1em`#h(1em)两句话之间间隔`1em` \\\n间隔`1fr`#h(1fr)间隔`1fr` \\\n间隔`1fr`#h(1fr)间隔`...`#h(2fr)间隔`2fr` \\\n#h(1fr)夹心的`1fr``...`#h(1fr) \\\n```)\n\n同理竖直间距（`v`，vertical）不包含内容，而仅仅在布局中占据一定高度。插入竖直间距将会导致强制换行。\n\n#code(```typ\n1 2 \\\n1 \\ 2 \\\n1 #v(-1em) 2\n```)\n\n== 「空间变换」\n\n一共有三种类型的空间变换，分别是：\n- `move`：移动元素。\n- `scale`：拉伸元素。\n- `rotate`：旋转元素。\n\n它们都*不会影响布局*，且都会*导致强制换行*。\n\n`move`相对于当前位置移动一段距离。\n\n#code(```typ\n#rect(inset: 0pt, move(\n  dx: 6pt, dy: 6pt,\n  rect(\n    inset: 8pt,\n    fill: white,\n    stroke: black,\n    [Abra cadabra]\n  )\n))\n```)\n\n你可以使用`box`将其改为行内元素行为：\n\n#code(```typ\n#let TeX(width) = {\n  set text(font: \"New Computer Modern\", weight: \"regular\")\n  box(width: width, {\n    [T]\n    box(stroke: red, move(dx: -0.2em, dy: 0.22em)[E])\n    box(stroke: green, move(dx: -0.4em)[X])\n  })\n}\n#TeX(2.2em) is a digital typographical systems. \\\n#TeX(2.1em) is a digital typographical systems.\n```)\n\n`scale`可以拉伸元素，且`scale`会导致强制换行：\n\n#code(```typ\n#scale(x: 110%)[This is mirrored.]\n#box(scale(x: 100%)[This is scaled.]) 1 \\\n#box(scale(x: 115%)[This is scaled.]) 1\n```)\n\n特别地，你可以指定负长度以反转元素：\n\n#code(```typ\n#scale(x: -100%)[This is mirrored.]\n```)\n\n`rotate`可以旋转元素：\n\n#code(```typ\n#rotate(5deg)[This is rotated.]\n```)\n\n== 让「空间变换」函数影响布局\n\n虽然内置的`move`等函数不会影响布局，但是你可以通过`box`模拟子元素对父元素的布局影响：\n\n#code(```typ\n#let ac(a) = calc.abs(calc.cos(a))\n#let ax(a) = calc.abs(calc.sin(a))\n#let rot(x,y,a) = .5*(x*(ac(a)-1)+y*ax(a))\n#let rotx(body, angle) = context {\nlet sz = measure(body)\nbox(stroke: green, inset: (\n  x: rot(sz.width, sz.height, angle),\n  y: rot(sz.height, sz.width, angle)),\n  rotate(body, angle))\n}\n#lorem(7) This is a #rotx([Vertical],-90deg) word, and #rotx([this],-7deg) is #rotx([skew.],7deg) It also works with #rotx($e^(-x)$,-95deg). #lorem(20)\n```)\n\n例子使用了`measure`函数评估子元素的大小，并强行改变子元素的边界框（bounding box）。\n\n== 「绝对定位布局」\n\n你可以使用`place`完成「绝对定位布局」（absolute positioning）。`place`相对于父元素移动一段距离，其接受一个`alignment`参数，将元素放置在相对于父元素`alignment`的绝对位置。例如下例相对于父元素的右上角放置了一个矩形框：\n\n#code(```typ\n#box(height: 30pt, width: 100%)[\n  Hello, world!\n  #place(\n    top + right,\n    square(\n      width: 20pt,\n      stroke: 2pt + blue\n    ),\n  )\n]\n```)\n\n`place`会使得子元素脱离父元素布局。对比两例：\n\n#code(```typ\n#let TeX(width: 2.2em) = {\n  set text(font: \"New Computer Modern\", weight: \"regular\")\n  box(width: width, {\n    [T]\n    box(stroke: red, move(dx: -0.2em, dy: 0.22em)[E])\n    box(stroke: green, move(dx: -0.4em)[X])\n  })\n}\n#TeX() is a digital typographical systems. \\\n#let TeX = {\n  set text(font: \"New Computer Modern\", weight: \"regular\")\n  box(width: 1.7em, {\n    [T]\n    place(top, dx: 0.56em, dy: 0.22em, box(stroke: red, [E]))\n    place(top, dx: 1.1em, box(stroke: green, [X]))\n  })\n}\n#TeX is a digital typographical systems.\n```)\n\n你可以使用`place(float: true)`以创建相对于父元素的浮动布局。（todo）\n\n== 杂项\n+ hide\n+ repeat\n"
  },
  {
    "path": "src/tutorial/scripting-literal.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"基本类型\")\n\nTypst很快，并非因为它的#term(\"parser\")和#term(\"interpreter\")具有惊世的执行性能，而是语言特性本身适合缓存优化。\n\n自本节起，本教程将进入第二阶段，希望不仅让你了解如何使用脚本，还一并讲解Typst的执行原理。当然，我们不会陷入Typst的细节。所有在教程中出现的原理都是为了更好地了解语言本身。\n\n// 什么是自省\n// 程序不会自己自省，这是给你看的\n\n== 代码表示的自省函数 <grammar-repr>\n\n在开始学习之前，先学习几个与排版无关但非常实用的函数。\n\n#typst-func(\"repr\")是一个#term(\"introspection function\")，可以帮你获得任意值的代码表示，很适合用来在调试代码的时候输出内容。\n\n#code(```typ\n#[ 一段文本 ]\n\n#repr([ 一段文本 ])\n```)\n\n#pro-tip[\n  #term(\"introspection function\")是指那些为你获取解释器内部状态的函数。它们往往接受一些语言对象，而返回存储在解释器内部的相关信息。在这里，#typst-func(\"repr\")接受任意值，而返回对应的代码表示。\n]\n\n== `type`函数 <grammar-type>\n\n与#typst-func(\"repr\")类似，一个特殊的函数#typst-func(\"type\")可以获得任意值的#term(\"type\")。所谓#term(\"type\")，就是这个值归属的分类。例如：\n- `1`是整数数字，类型就对应于整数类型（integer）。\n  #code(```typ\n  #type(1)\n  ```)\n- `一段内容`是文本内容，类型就对应于内容类型（content）。\n  #code(```typ\n  #type[一段内容]\n  ```)\n\n=== 类型 <type-type>\n\n一个值只会属于一种类型，因此类型是可以比较的：\n\n#code(```typ\n#(str == str) \\\n#(type(\"X\") == type(\"Y\")) \\\n#(type(\"X\") == str) \\\n#([一段内容] == str)\n```)\n\n类型的类型是类型（它自身）：\n\n#code(```typ\n#(type(str)) \\\n#(type(type(str))) \\\n#(type(type(str)) == type) \\\n```)\n\n== `eval`函数 <grammar-eval>\n\n`eval`函数接受一个字符串，把字符串当作代码执行并求出结果：\n\n#code(```typ\n#eval(\"1\"), #type(eval(\"1\")) \\\n#eval(\"[一段内容]\"), #type(eval(\"[一段内容]\"))\n```)\n\n从```typc eval(\"[一段内容]\")```的中括号被解释为「内容块」可以得知，`eval`默认以#term(\"code mode\")解释你的代码。\n\n你可以使用`mode`参数修改`eval`的「解释模式」。`code`对应为#term(\"code mode\")，`markup`对应为#term(\"markup mode\")：<grammar-eval-markup-mode>\n\n#code(```typ\n代码模式eval：#eval(\"[一段内容]\", mode: \"code\") \\\n标记模式eval：#eval(\"[一段内容]\", mode: \"markup\")\n```)\n\n// #let x = 1;\n\n// #let f() = x\n\n// #f()\n// #(x = 2)\n// #f()\n\n== 基本字面量\n\n我们将介绍所有基本字面量，这是脚本的“一加一”。其实在上一节，我们已经见过了一部分字面量，但皆凭直觉使用：```typc 1```不就是数字吗，那么在Typst中，它就是数字。与之相对，TeX底层没有数字和字符串的概念。\n\n如果你学过Python等语言，那么这将对你来说不是问题。在Typst中，常用的字面量并不多，它们是：\n+ #term(\"none literal\")。\n+ #term(\"boolean literal\")。\n+ #term(\"integer literal\")。\n+ #term(\"floating-point literal\")。\n+ #term(\"string literal\")。\n\n=== 空字面量 <grammar-none-literal>\n\n就像是数学中的零与负数，空字面量自然产生于运算过程中。\n\n#code(```typ\n#repr((0, 1).find((_) => false)),\n#repr(if false [啊？])\n```)\n\n上例第一行，当在「数组」中查找一个不存在的元素时，“没有”就是```typc none```。第二行，当条件不满足，且没有`false`分支时，“没有内容”就是```typc none```。\n\n空字面量是纯粹抽象的概念，这意味着你在现实中很难找到对应的实体。\n\n// #pro-tip[\n//   空字面量自然产生于运算过程中。除上所述，以下是其他会产生```typc none```的自然场景：\n//   - 当变量未初始化时。\n//   #code(```typ\n//   #let x; #type(x)\n//   ```)\n//   - 当`for`语句、`while`语句、函数没有产生任何值时，函数返回值为```typc none```。\n//   #code(```typ\n//   #let f() = {}; #type(f())\n//   ```)\n//   - 当函数显式`return`且未写明返回值时，函数返回值为```typc none```。\n//   #code(```typ\n//   #let f() = return; #type(f())\n//   ```)\n// ]\n\n`none`值不会对输出文档有任何影响：\n\n#code(```typ\n#none\n```)\n\n`none`的类型是`none`类型。`none`值不等于`none`类型，因为一个是值而另一个是类型：\n\n#code(```typ\n#type(none), #(type(none) == none), #type(type(none))\n```)\n\n=== 布尔字面量\n\n一个布尔字面量表示逻辑的确否。它要么为```typc false```（真）<grammar-true-literal>要么为```typc true```（假）<grammar-false-literal>。\n\n#code(```typ\n假设 #false 那么一切为 #true。\n```)\n\n一般来说，我们不直接使用布尔值。当代码做逻辑判断的时候，会自然产生布尔值。\n\n#code(```typ\n$1 < 2$的结果为：#(1 < 2)\n```)\n\n=== 整数字面量 <grammar-integer-literal>\n\n一个整数字面量代表一个整数。Typst中的整数默认为十进制：\n\n#code(```typ\n三个值 #(-1)、#0 和 #1 偷偷混入了我们内容之中。\n```)\n\n#pro-tip[\n  有的时候Typst不支持在#mark(\"#\")后直接跟一个值。例如，Typst无法处理#mark(\"#\")后直接跟随一个#mark(\"hyphen\")的情况。这个时候无论多么复杂，都可以用括号包裹值：\n\n  #code(```typ\n  #(-1), #(0), #(1)\n  ```)\n]\n\n有些数字使用其他进制表示更为方便。你可以分别使用`0x`、`0o`和`0b`前缀加上进制内容表示十六进制数、八进制数和二进制数：<grammar-n-adecimal-literal>\n\n#code(```typ\n#(0xbeef)、#(0o755)、#(-0b1001)\n```)\n\n整数的有效取值范围是$[-2^63,2^63)$，其中$2^63=9223372036854775808$。\n\n=== 浮点数字面量 <grammar-float-literal>\n\n浮点数与整数非常类似。最常见的浮点数由至少一个整数部分或小数部分组成：\n\n#code(```typ\n浮点数#(1.001)、小数部分#(.1) 和整数部分#(2.)。\n```)\n\n有些数字使用#term(\"exponential notation\")更为方便：<grammar-exp-repr-float>\n\n#code(```typ\n#(1e2)、#(1.926e3)、#(-1e-3)\n```)\n\nTypst还为你内置了一些特殊的数值，它们都是浮点数：\n\n#code(```typ\n$inf$=#calc.inf,\n$pi$=#calc.round(calc.pi, digits: 5),\n$tau$=#calc.round(calc.tau, digits: 5)\n```)\n// NaN=#calc.nan \\\n\n=== 字符串字面量 <grammar-string-literal>\n\nTypst中所有字符串都是`utf-8`编码的，因此使用时不存在编码转换问题。字符串由一对「英文双引号」定界：\n\n#code(```typ\n#\"Hello world!!\"\n```)\n\n有些字符无法置于双引号之内，例如双引号本身。与「标记模式」中的转义序列语法类似，这时候你需要嵌入字符的转义序列：\n\n#code(```typ\n#\"Hello \\\"world\\\"!!\"\n```)\n\n字符串中的转义序列与「标记模式」中的转义序列语法相同，但有效转义的字符集不同。字符串中如下转义序列是有效的：\n\n#{\n  set align(center)\n  table(\n    columns: 7,\n    [代码], [`\\\\`], [`\\\"`], [`\\n`], [`\\r`], [`\\t`], [`\\u{2665}`],\n    [效果], \"\\\\\", \"\\\"\", [(换行)], [(回车)], [(制表)], \"\\u{2665}\",\n  )\n}\n\n你同样可以使用`\\u{unicode}`格式直接嵌入Unicode字符。\n\n#code(```typ\n#\"香辣牛肉粉好吃\\u{2665}\"\n```)\n\n除了使用简单字面量构造，可以使用以下方法从代码块获得字符串：\n\n#code(```typ\n#repr(`包含换行符和双引号的\n\n\"内容\"`.text)\n```)\n\n== 类型转换\n\n类型本身也是函数，例如`int`类型可以接受字符串，转换成整数。\n\n#code(```typ\n#int(\"11\"), #int(\"-23\")\n```)\n\n从转换的角度，`eval`可以将代码字符串转换成值。例如，你可以转换16进制数字：\n\n#code(```typ\n#eval(\"0x3f\")\n```)\n\n== 总结\n\n// Typst如何保证一个简单函数甚至是一个闭包是“纯函数”？\n\n// 答：1. 禁止修改外部变量，则捕获的变量的值是“纯的”或不可变的；2. 折叠的对象是纯的，且「折叠」操作是纯的。\n\n// Typst的多文件特性从何而来？\n\n// 答：1. import函数产生一个模块对象，而模块其实是文件顶层的scope。2. include函数即执行该文件，获得该文件对应的内容块。\n\n// 基于以上两个特性，Typst为什么快？\n\n// + Typst支持增量解析文件。\n// + Typst所有由*用户声明*的函数都是纯的，在其上的调用都是纯的。例如Typst天生支持快速计算递归实现的fibnacci函数：\n\n//   #code(```typ\n//   #let fib(n) = if n <= 1 { n } else { fib(n - 1) + fib(n - 2) }\n//   #fib(42)\n//   ```)\n// + Typst使用`include`导入其他文件的顶层「内容块」。当其他文件内容未改变时，内容块一定不变，而所有使用到对应内容块的函数的结果也一定不会因此改变。\n\n// 这意味着，如果你发现了Typst中与一般语言的不同之处，可以思考以上种种优势对用户脚本的增强或限制。\n\n#todo-box[重写总结]\n\n基于《字面量、变量和函数》掌握的知识你应该可以：\n+ 查看#(refs.ref-type-builtin)[《参考：内置类型》]，以掌握内置类型的使用方法。\n+ 查看#(refs.ref-visualization)[《参考：图形与几何元素》]，以掌握图形和几何元素的使用方法。\n+ 查看#(refs.ref-wasm-plugin)[《参考：WASM插件》]，以掌握在Typst中使用Rust、JavaScript、Python等语言编写插件库。\n+ 阅读#(refs.ref-grammar)[《参考：语法示例检索表》]，以检查自己的语法掌握程度。\n+ 查看#(refs.ref-typebase)[《参考：基本类型》]，以掌握基本类型的使用方法。\n+ 查看#(refs.ref-color)[《参考：颜色、色彩渐变与模式》]，以掌握色彩的高级管理方法。\n+ 查看#(refs.ref-data-process)[《参考：数据读写与数据处理》]，以助你从外部读取数据或将文档数据输出到文件。\n+ 查看#(refs.ref-bibliography)[《参考：导入和使用参考文献》]，以助你导入和使用参考文献。\n+ 阅读基本参考部分中的所有内容。\n\n== 习题\n\n#let q1 = ````typ\n#let a0 = 2\n#let a1 = a0 * a0\n#let a2 = a1 * a1\n#let a3 = a2 * a2\n#let a4 = a3 * a3\n#let a5 = a4 * a4\n#a5\n````\n\n#exercise[\n  使用本节所讲述的语法，计算$2^32$的值：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q1 = ````typ\n#let a = [一]\n#let b = [渔]\n#let c = [江]\n#let f(x, y) = a + x + a + y\n#let g(x, y, z, u, v) = [#f(x, y + a + z)，#f(u, v)。]\n#g([帆], [桨], [#(b)舟], [个#(b)翁], [钓钩]) \\\n#g([俯], [仰], [场笑], [#(c)明月], [#(c)秋])\n````\n\n#exercise[\n  输出下面的诗句，但你的代码中至多只能出现17个汉字：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q1 = ````typ\n#let calc-fib() = {\n  let res = range(2).map(float)\n  for i in range(2, 201) {\n    res.push(res.at(i - 1) + res.at(i - 2))\n  }\n\n  res\n}\n#let fib(n) = calc-fib().at(n)\n\n#fib(75)\n````\n\n#exercise[\n  已知斐波那契数列的递推式为$F_n = F_(n-1) + F_(n-2)$，且$F_0 = 0, F_1 = 1$。使用本节所讲述的语法，计算$F_75$的值：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q1 = ````typ\n#set align(center)\n#let matrix-fmt(..args) = table(\n  columns: args.at(0).len(),\n  ..args.pos().flatten().flatten().map(str)\n)\n#matrix-fmt(\n  (1, 2, 3),\n  (4, 5, 6),\n  (7, 8, 9),\n)\n````\n\n#exercise[\n  编写函数，使用`table`（表格）元素打印任意$N times M$矩阵，例如：\n\n  ```typ\n  #matrix-fmt(\n    (1, 2, 3),\n    (4, 5, 6),\n    (7, 8, 9),\n  )\n  ```\n\n  #rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n"
  },
  {
    "path": "src/tutorial/scripting-main.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"编译流程\")\n\n#todo-box[本节处于校对阶段，所以可能存在不完整或错误。]\n\n经过几节稍显枯燥的脚本教程，我们继续回到排版本身。\n\n在#(refs.writing-markup)[《初识标记模式》]中，我们学到了很多各式各样的内容。我们学到了段落、标题、代码片段......\n\n接着我们又花费了三节的篇幅，讲授了各式各样的脚本技巧。我们学到了字面量、变量、闭包......\n\n但是它们之间似乎隔有一层厚障壁，阻止了我们进行更高级的排版。是了，如果「内容」也是一种值，那么我们应该也可以更随心所欲地使用脚本操控它们。Typst以排版为核心，应当也对「内容类型」有着精心设计。\n\n本节主要介绍如何使用脚本排版内容。这也是Typst的核心功能，并在语法上*与很多其他语言有着不同之处*。不用担心，在我们已经学了很多Typst语言的知识的基础上，本节也仅仅更进一步，教你如何真正以脚本视角看待一篇文档。\n\n纵览Typst的编译流程，其大致分为4个阶段，解析、求值、排版和导出。\n\n// todo: 介绍Typst的多种概念\n\n// Source Code\n// Value\n// Type\n// Content\n\n// todo: 简化下面的的图片\n#import \"../figures.typ\": figure-typst-arch\n#align(center + horizon, figure-typst-arch())\n\n// ，层层有缓存\n\n为了方便排版，Typst首先使用了一个函数“解析和评估”你的代码。有趣地是，我们之前已经学过了这个函数。事实上，它就是#typst-func(\"eval\")。\n\n#code(```typ\n#repr(eval(\"#[一段内容]\", mode: \"markup\"))\n```)\n\n流程图展现了编译阶段间的关系，也包含了本节「块」与「表达式」两个概念之间的关系。\n\n- #typst-func(\"eval\")输入：在文件解析阶段，*代码字符串*被解析成一个语法结构，即「表达式」。古人云，世界是一个巨大的表达式。作为世界的一部分，Typst文档本身也是一个巨大的表达式。事实上，它就是我们在上一章提及的「内容块」。文档的本身是一个内容块，其由一个个标记串联形成。\n\n- #typst-func(\"eval\")输出：在内容排版阶段，排版引擎事实不作任何计算。用TeX黑话来说，文档被“解析和评估”完了之后，就成为了一个个「材料」（material）。排版引擎将材料。\n\n在求值阶段。「表达式」被计算成一个方便排版引擎操作的值，即「材料」。一般来说，我们所谓的表达式是诸如`1+1`的算式，而对其求值则是做算数。\n\n#code(```typ\n#eval(\"1+1\")\n```)\n\n显然，如果意图是让排版引擎输出计算结果，让排版引擎直接排版2要比排版“1+1”更简单。\n\n但是对于整个文档，要如何理解对内容块的求值？这就引入了「可折叠」的值（Foldable）的概念。「可折叠」成为块作为表达式的基础。\n\n== Typst的主函数\n\n在Typst的源代码中，有一个Rust函数直接对应整个编译流程，其内容非常简短，便是调用了两个阶段对应的函数。“求值”阶段（`eval`阶段）对应执行一个Rust函数，它的名称为`typst::eval`；“内容排版”阶段（`typeset`阶段）对应执行另一个Rust函数，它的名称为`typst::typeset`。\n\n```rs\npub fn compile(world: &dyn World) -> SourceResult<Document> {\n    // Try to evaluate the source file into a module.\n    let module = crate::eval::eval(world, &world.main())?;\n    // Typeset the module's content, relayouting until convergence.\n    typeset(world, &module.content())\n}\n```\n\n从代码逻辑上来看，它有明显的先后顺序，似乎与我们所展示的架构略有不同。其`typst::eval`的输出为一个文件模块`module`；其`typst::typeset`仅接受文件的内容`module.content()`并产生一个已经排版好的文档对象`typst::Document`。\n\n== 「`eval`阶段」与「`typeset`阶段」\n\n现在我们介绍Typst的完整架构。\n\n当Typst接受到一个编译请求时，他会使用「解析器」（Parser）从`main`文件开始解析整个项目；对于每个文件，Typst使用「评估器」（Evaluator）执行脚本并得到「内容」；对于每个「内容」，Typst使用「排版引擎」（Typesetting Engine）计算布局与合成样式。\n\n当一切布局与样式都计算好后，Typst将最终结果导出为各种格式的文件，例如PDF格式。\n\n我们回忆上一节讲过的内容，Typst大致上分为四个执行阶段。这四个执行阶段并不完全相互独立，但有明显的先后顺序：\n\n#import \"../figures.typ\": figure-typst-arch\n#align(center + horizon, figure-typst-arch())\n\n我们在上一节着重讲解了前两个阶段。这里，我们着重讲解“表达式求值”阶段与“内容排版”阶段。\n\n事实上，Typst直接在脚本中提供了对应“求值”阶段的函数，它就是我们之前已经介绍过的函数`eval`。你可以使用`eval`函数，将一个字符串对象「评估」为「内容」：\n\n#code(```typ\n以代码模式评估：#eval(\"repr(str(1 + 1))\") \\\n以标记模式评估：#eval(\"repr(str(1 + 1))\", mode: \"markup\") \\\n以标记模式评估2：#eval(\"#show: it => [c] + it + [t];a\", mode: \"markup\")\n```)\n\n由于技术原因，Typst并不提供对应“内容排版”阶段的函数，如果有的话这个函数的名称应该为`typeset`。已经有很多地方介绍了潜在的`typeset`函数：\n+ #link(\"https://github.com/andreasKroepelin/polylux\")[Polylux], #link(\"https://github.com/touying-typ/touying\")[Touying]等演示文档（PPT）框架需要将一部分内容固定为特定结果的能力。\n+ Typst的作者在其博客中提及#link(\"https://laurmaedje.github.io/posts/frozen-state/\")[Frozen State\n  ]的可能性。\n  + 他提及数学公式的编号在演示文档框架。\n  + 即便不涉及用户需求，Typst的排版引擎已经自然存在Frozen State的需求。\n+ 本文档也需要`typeset`的能力为你展示特定页面的最终结果而不影响全局状态。\n\n== 延迟执行\n\n架构图中还有两个关键的反向箭头，疑问顿生：这两个反向箭头是如何产生的？\n\n我们首先关注与本节直接相关的「样式化」内容。当`eval`阶段结束时，「`show`」语法将会对应产生一个`styled`元素，其包含了被设置样式的内容，以及设置样式的「回调」：\n\n#code(```typ\n内容是：#repr({show: set text(fill: blue); [abc]}) \\\n样式无法描述，但它在这里：#repr({show: set text(fill: blue); [abc]}.styles)\n```)\n\n也就是说`eval`并不具备任何排版能力，它只能为排版准备好各种“素材”，并把素材交给排版引擎完成排版。\n\n这里的「回调」术语很关键：它是一个计算机术语。所谓「回调函数」就是一个临时的函数，它会在后续执行过程的合适时机“回过头来被调用”。例如，我们写了一个这样的「`show`」规则：\n\n#code(```typ\n#repr({\n  show raw: content => layout(parent => if parent.width > 100pt {\n    set text(fill: red); content\n  } else {\n    content\n  })\n  `a`\n})\n```)\n\n这里`parent.width > 100pt`是说当且仅当父元素的宽度大于`100pt`时，才为该代码片段设置红色字体样式。其中，`parent.width`与排版相关。那么，自然`eval`也不知道该如何评估该条件的真正结果。*计算因此被停滞*。\n\n于是，`eval`干脆将整个`show`右侧的函数都作为“素材”交给了排版引擎。当排版引擎计算好了相关内容，才回到评估阶段，执行这一小部分“素材”函数中的脚本，得到为正确的内容。我们可以看出，`show`右侧的函数*被延后执行*可。\n\n这种被延后执行零次、一次或多次的函数便被称为「回调函数」。相关的计算方法也有对应的术语，被称为「延迟执行」。\n\n我们对每个术语咬文嚼字一番，它们都很准确：\n\n1. *「表达式求值」*阶段仅仅“评估”出*「内容排版」*阶段所需的素材.*「评估器」*并不具备排版能力。\n2. 对于依赖排版产生的内容，「表达式求值」产生包含*「回调函数」*的内容，让「排版引擎」在合适的时机“回过头来调用”。\n3. 相关的计算方法又被称为*「延迟执行」*。因为现在不具备执行条件，所以延迟到条件满足时才继续执行。\n\n现在我们可以理解两个反向箭头是如何产生的了。它们是下一阶段的回调，用于完成阶段之间复杂的协作。评估阶段可能会`import`或`include`文件，这时候会重新让解析器解析文件的字符串内容。排版阶段也可能会继续根据`styled`等元素产生复杂的内容，这时候依靠评估器执行脚本并产生或改变内容。\n\n== 模拟Typst的执行\n\n我们来模拟一遍上述示例的执行，以加深理解：\n\n#code(```typ\n#show raw: content => layout(parent => if parent.width < 100pt {\n  set text(fill: red); content\n} else {\n  content\n})\n#box(width: 50pt, `a`)\n`b`\n```)\n\n首先进行表达式求值得到：\n\n```typ\n#styled((box(width: 50pt, `a`), `b`), styles: content => ..)\n```\n\n排版引擎遇到``` `a` ```。由于``` `a` ```是`raw`元素，它「回调」了对应`show`规则右侧的函数。待执行的代码如下：\n\n```typc\nlayout(parent => if parent.width < 100pt {\n  set text(fill: red); `a`\n} else {\n  `a`\n})\n```\n\n此时`parent`即为`box(width: 50pt)`。排版引擎将这个`parent`的具体内容交给「评估器」，待执行的代码如下：\n\n```typc\nif box(width: 50pt).width < 100pt {\n  set text(fill: red); `a`\n} else {\n  `a`\n}\n```\n\n由于此时父元素（`box`元素）宽度只有`50pt`，评估器进入了`then`分支，其为代码片段设置了红色样式。内容变为：\n\n```typ\n#(box(width: 50pt, {set text(fill: red); `a`}), styled((`b`, ), styles: content => ..))\n```\n\n待执行的代码如下：\n\n```typc\nset text(fill: red); text(\"a\", font: \"monospace\")\n```\n\n排版引擎遇到``` `a` ```中的`text`元素。由于其是`text`元素，「回调」了`text`元素的「`show`」规则。记得我们之前说过`set`是一种特殊的`show`，于是排版器执行了`set text(fill: red)`。\n\n```typ\n#(box(width: 50pt, text(fill: red, \"a\", ..)), styled((`b`, ), styles: content => ..))\n```\n\n排版引擎离开了`show`规则右侧的函数，该函数调用由``` `a` ```触发。同时`set text(fill: red)`规则也被解除，因为离开了相关作用域。\n\n回到文档顶层，待执行的代码如下：\n\n```typc\n#show raw: ...\n`b`\n```\n\n排版引擎遇到``` `b` ```，再度「回调」了对应`show`规则右侧的函数。由于此时父元素（`page`元素，即整个页面）宽度有`500pt`，我们没有为代码片段设置样式。\n\n```typ\n#(box(width: 50pt, text(fill: red, \"a\", ..)), text(\"b\", ..))\n```\n\n至此，文档的内容已经准备好「导出」（Export）了。\n\n#pro-tip[\n  有时候`show`规则会原地执行，这属于一种细节上的优化，例如：\n\n  #code(```typ\n  #repr({ show: it => it; [a] }) \\\n  #repr({ show: it => [c] + it + [d]; [a] })\n  ```)\n\n  这个时候`show`规则不会对应一个`styled`元素。\n\n  这种优化告诉你前面手动描述的过程仅作理解。一旦涉及更复杂的环境，Typst的实际执行过程就会产生诸多变化。因此，你不应该依赖以上某步中排版引擎的瞬间状态。这些瞬间状态将产生「未注明特性」(undocumented details)，并随时有可能在未来被打破。\n]\n"
  },
  {
    "path": "src/tutorial/scripting-shape.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"图形排版\")\n\n#todo-box[本节处于校对阶段，所以可能存在不完整或错误。]\n\n== 颜色类型\n\nTypst只有一种颜色类型，其由两部分组成。\n\n#figure([\n  #block(\n    width: 200pt,\n    align(left)[\n      ```typ\n      #color.rgb(87, 127, 230)\n       --------- ------------\n           |           +-- 色坐标\n           +-- 色彩空间对应的构造函数\n      ```\n    ],\n  )\n  #text(todo-color)[这里有个图注解]\n])\n\n\n「色彩空间」（color space）是人们主观确定的色彩模型。Typst为不同的色彩空间提供了专门的构造函数。\n\n「色坐标」（chromaticity coordinate）是客观颜色在「色彩空间」中的坐标。给定一个色彩空间，如果某种颜色*在空间内*，那么颜色能分解到不同坐标分量上，并确定每个坐标分量上的数值。反之，选择一个构造函数，并提供坐标分量上的数值，就能构造出这个颜色。\n\n#todo-box[\n  chromaticity coordinate这个名词是对的吗，每种色彩空间中的坐标都是这个名字吗？\n]\n\n习惯上，颜色的坐标分量又称为颜色的「通道」。从物理角度，Typst使用`f32`存储颜色每通道的值，这允许你对颜色进行较复杂的计算，且计算结果仍然保证较好的误差。\n\n== 色彩空间\n\nRGB是我们使用最多的色彩空间，对应Typst的`color.rgb`函数或`rgb`函数：\n\n#code(```typ\n#box(square(fill: color.rgb(\"#b1f2eb\")))\n#box(square(fill: rgb(87, 127, 230)))\n#box(square(fill: rgb(25%, 13%, 65%)))\n```)\n\n除此之外，还支持HSL（`hsl`）、CMYK（`cmyk`）、Luma（`luma`）、Oklab（`oklab`）、Oklch（`oklch`）、Linear RGB（`color.linear-rgb`）、HSV（`color.hsv`）等色彩空间。感兴趣的可以自行搜索并使用。\n\n#pro-tip[\n  尽管你可以随意使用这些构造器，但是可能会导致PDF阅读器或浏览器的兼容性问题。它们可能不支持某些色彩空间（或色彩模式）。\n]\n\n== 预定义颜色\n\n除此之外，你还可以使用一些预定义的颜色，详见#link(\"https://typst.app/docs/reference/visualize/color/#predefined-colors\")[《Typst Docs: Predefined colors》。]\n\n#code(```typ\n#box(square(fill: red, size: 7pt))\n#box(square(fill: blue, size: 7pt))\n```)\n\n== 颜色计算\n\nTypst较LaTeX的一个有趣的特色是内置了很多方法对颜色进行计算。这允许你基于某个颜色主题（Theme）配置更丰富的颜色方案。这里给出几个常用的函数：\n\n- `lighten/darken`：增减颜色的亮度，参数为绝对的百分比。\n- `saturate/desaturate`：增减颜色的饱和度，参数为绝对的百分比。\n- `mix`：参数为两个待混合的颜色。\n\n#code(```typ\n#show square: box\n#set square(size: 15pt)\n#square(fill: red.lighten(20%))\n#square(fill: red.darken(20%)) \\\n#square(fill: red.saturate(20%))\n#square(fill: red.desaturate(20%)) \\\n#square(fill: blue.mix(green))\n```)\n\n还有一些其他不太常见的颜色计算，详见#link(\"https://typst.app/docs/reference/visualize/color/#definitions-lighten\")[《Typst Docs: Color operations》]。\n\n== 渐变色\n\n你可以以某种方式对Typst中的元素进行渐变填充。这有时候对科学作图很有帮助。\n\n有三种渐变色的构造函数，可以分别构造出线性渐变（Linear Gradient），径向渐变（Radial Gradient），锥形渐变（Conic Gradient）。他们都接受一组颜色，对元素进行颜色填充。\n\n#code(```typ\n#let sqr(f) = square(fill: f(\n    ..color.map.rainbow))\n#stack(\n  dir: ltr, spacing: 10pt,\n  sqr(gradient.linear),\n  sqr(gradient.radial),\n  sqr(gradient.conic ))\n```)\n\n从字面意思理解`color.map.rainbow`是Typst为你预定义的一个颜色数组，按顺序给出了彩虹渐变的颜色。还有一些其他预定义的颜色数组，详见#link(\"https://typst.app/docs/reference/visualize/color/#predefined-color-maps\")[《Typst Docs: Predefined color maps》]。\n\n== 填充模式\n\nTypst不仅支持颜色填充，还支持按照固定的模式将其他图形对元素进行填充。例如下面的pat定义了一个长为`61.8pt`，宽为`50pt`的图形。将其填充进一个矩形中，填充图形从左至右，从上至下布满矩形内容中。\n\n#code(```typ\n#let pat = pattern(size: (61.8pt, 50pt))[\n  #place(line(start: (0%, 0%), end: (100%, 100%)))\n  #place(line(start: (0%, 100%), end: (100%, 0%)))\n]\n\n#rect(fill: pat, width: 100%, height: 60pt, stroke: 1pt)\n```)\n\n== 线条\n\n我们学习的第一个图形元素是直线。\n\n`line`函数创建一个直线元素。这个函数接受一系列参数，包括线条的长度、起始点、终止点、颜色、粗细等。\n\n#code(```typ\n#line(length: 100%)\n#line(end: (50%, 50%))\n#line(\n  length: 30%, stroke: 2pt + maroon)\n```)\n\n除了直线，Typst还支持二次、三次贝塞尔曲线，以及它们和直线的组合。贝塞尔曲线是一种光滑的曲线，由一系列点和控制点组成。\n\n#code(```typ\n#curve(\n  stroke: blue,\n  curve.move((0pt, 20pt)),\n  curve.quad((40%, 0pt), (100%, 0pt)),\n  curve.line((100%, 20pt)),\n  curve.close()\n)\n```)\n\n== 线条样式\n\n与各类图形紧密联系的类型是「线条样式」（stroke）。线条样式可以是一个简单的「字典」，包含样式数据：\n\n#code(```typ\n#set line(length: 100%)\n#let rainbow = gradient.linear(\n  ..color.map.rainbow)\n#stack(\n  spacing: 1em,\n  line(stroke: 2pt + red),\n  line(stroke: (paint: blue, thickness: 4pt, cap: \"round\")),\n  line(stroke: (paint: blue, thickness: 1pt, dash: \"dashed\")),\n  line(stroke: 2pt + rainbow),\n)\n```)\n\n你也可以使用`stroke`函数来设置线条样式：\n\n#code(```typ\n#set line(length: 100%)\n#let blue-line-style = stroke(\n  paint: blue, thickness: 2pt)\n#line(stroke: blue-line-style)\n```)\n\n== 填充样式\n\n填充样式（fill）是另一个重要的图形属性。如果一个路径是闭合的，那么它可以被填充。\n\n#code(```typ\n#curve(\n  fill: blue.lighten(80%),\n  stroke: blue,\n  curve.move((0pt, 50pt)),\n  curve.line((100pt, 50pt)),\n  curve.cubic(none, (90pt, 0pt), (50pt, 0pt)),\n  curve.close(),\n)\n```)\n\n== 闭合图形\n\nTypst为你预定义了一些基于贝塞尔曲线的闭合图形元素。下例子中，`#circle`、`#ellipse`、`#square`、`#rect`、`#polygon`分别展示了圆、椭圆、正方形、矩形、多边形的构造方法。\n\n#code(```typ\n#box(circle(radius: 12.5pt, fill: blue))\n#box(ellipse(width: 50pt, height: 25pt))\n#box(square(size: 25pt, stroke: red))\n#box(rect(width: 50pt, height: 25pt))\n```)\n\n值得注意的是，这些图形元素都允许自适应一个内嵌的内容。例如矩形作为最常用的边框图形：\n\n#code(```typ\n#rect[\n  Automatically sized \\\n  to fit the content.\n]\n```)\n\n== 多边形\n\n多边形是仅使用直线组合而成的闭合图形。你可以使用`polygon`函数构造一个多边形。\n\n#code(```typ\n#polygon(\n  fill: blue.lighten(80%),\n  stroke: blue,\n  (20%, 0pt),\n  (60%, 0pt),\n  (80%, 2cm),\n  (0%,  2cm),\n)\n```)\n\n`polygon`不允许内嵌内容。\n\n== 外部图形库\n\n很多时候我们只需要在文档中插入分割线（直线）和文本框（带文本的矩形）。但是，若有需求，一些外部图形库可以帮助你绘制更复杂的图形：\n\n+ 树形图：#link(\"https://typst.app/universe/package/syntree\")[typst-syntree]\n+ 拓扑图：#link(\"https://typst.app/universe/package/fletcher\")[typst-fletcher]\n+ Canvas通用图形库：#link(\"https://typst.app/universe/package/cetz\")[cetz]\n\n这些库也是很好的参考资料，你可以通过查看源代码来学习如何绘制复杂的图形。\n"
  },
  {
    "path": "src/tutorial/scripting-style.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"选择器与样式\")\n\n#todo-box[本节处于校对阶段，所以可能存在不完整或错误。]\n\n== 「样式化」内容\n\n当我们有一个`repr`玩具的时候，总想着对着各种各样的对象使用`repr`。我们在上一节讲解了「`set`」和「`show`」语法。现在让我们稍微深挖一些。\n\n「`set`」是什么，`repr`一下：\n\n#code(```typ\n#repr({\n  [a]; set text(fill: blue); [b]\n})\n```)\n\n「`show`」是什么，`repr`一下：\n\n#code(```typ\n#repr({\n  [b]; show raw: set text(fill: red)\n  [a]\n})\n```)\n\n我们知道`set text(fill: blue)`是`show: set text(fill: blue)`的简写，因此「`set`」语法和「`show`」语法都可以统合到第二个例子来理解。\n\n对于第二个例子，我们发现`show`语句之后的内容都被重新包裹在`styled`元素中。虽然我们不知道`styled`做了什么事情，但是简单的事实是：\n\n#code(```typ\n该元素的类型是：#type({show: set text(fill: blue)}) \\\n该元素的构造函数是：#({show: set text(fill: blue)}).func()\n```)\n\n原来，你也是内容。从图中，我们可以看到被`show`过的内容会被封装成「样式化」内容，即图中构造函数为`styled`的内容。\n\n关于`styled`的知识便涉及到Typst的核心架构。\n\n// == 「可定位」的内容\n\n// 在过去的章节中，我们了解了评估结果的具体结构，也大致了解了排版引擎的工作方式。\n\n// 接下来，我们介绍一类内容的「可定位」（Locatable）特征。你可以与前文中的「可折叠」（Foldable）特征对照理解。\n\n// 一个内容是可定位的，如果它可以以某种方式被索引得到。\n\n// 如果一个内容在代码块中，并未被使用，那么显然这种内容是不可定位的。\n\n// ```typ\n// #{ let unused-content = [一段不可定位的内容]; }\n// ```\n\n// 理论上文档中所有内容都是可定位的，但由于*性能限制*，Typst无法允许你定位文档中的所有内容。\n\n// 我们已经学习过元素函数可以用来定位内容。如下：\n\n// #code(````typ\n// #show heading: set text(fill: blue)\n// = 蓝色标题\n// 段落中的内容保持为原色。\n// ````)\n\n// 接下来我们继续学习更多选择器。\n\n== 内容的「样式」\n\n我们接下来循着文本样式的脉络学习排版内容的语法。\n\n重点1：文本是段落的重要组成部分，与之对应的内容函数是`text`。\n\n我们知道一个函数可以有各种参数。那么我们从函数视角来看，内容的样式便由创建时参数的内容决定。例如，我们想要获得一段蓝色的文本：\n\n#code(```typ\n#text(\"一段文本\", fill: blue)\n```)\n\n`fill: blue`是函数的参数，指定了文本的样式。\n\n这个视角有助于我们更好的将对「样式」的需求转换为对函数的操控。例如，我们可以使用函数的`with`方法，获得一个固定样式的文本函数：\n\n#code(```typ\n#let warning = text.with(fill: orange)\n#warning[警告，你做个人吧]\n```)\n\n== 上下文有关表达式\n\n// contextual expression\n\n在介绍重要语法之前，我们先来一道开胃菜。\n\n#code(```typ\n#context text.size\n```)\n\n== 「`set`」语法 <grammar-set>\n\n重点2：一个段落主要是一个内容序列，其中有可能很多个文本。\n\n#code(```typ\n#repr([不止包含......一个文本！])\n```)\n\n假设我们想要让一整个段落都显示成蓝色，显然不能将文本一个个用`text.with(fill: blue)`构造好再组装起来。这个时候「`set`」语法出手了。「`set`」关键字后可以跟随一个函数调用，为影响范围内所有函数关联的对应内容设置对应参数。\n\n#code(```typ\n#set text(fill: blue)\n一段很长的话可能不止包含......一个文本！\n- 似乎，列表中也有文本。\n```)\n\n重点：「`set`」的影响范围是其所在「作用域」内的后续内容。\n\n我们紧接着来讲与之相关的，Typst中最重要的概念之一：「作用域」。\n\n== 「内容」是一棵树\n\n重点3：「内容」是一棵树，这意味着你可以“攀树而行”。\n\nTypst对代码块有着的一系列语法设计，让代码块非常适合描述内容。又由于作用域的性质，最终代码块让「内容」形成为一颗树。\n\n「内容」是一棵树。一个`main.typ`就是「内容」的一再嵌套。即便不使用任何标记语法，你也可以创建一个文档：\n\n#code.with(al: top)(```typ\n#let main-typ() = {\n  heading(\"生活在Content树上\")\n  {\n    [现代社会以海德格尔的一句]\n    [“一切实践传统都已经瓦解完了”]\n    [为嚆矢。]\n  } + parbreak()\n  [...] + parbreak()\n  [在孜孜矻矻以求生活意义的道路上，对自己的期望本就是在与家庭与社会对接中塑型的动态过程。]\n  [而我们的底料便是对不同生活方式、不同角色的觉感与体认。]\n  [...]\n}\n#main-typ()\n```)\n\n// == 「样式链」\n\n// 理解「作用域」对\n\n== 「`show`」语法 <grammar-show>\n\n「`set`」语法是「`show set`」语法的简写。因此，「`show`」语法显然可以比`set`更强大。<grammar-show-set>\n\n#code(```typ\n#show: set text(fill: blue)\nwink!\n```)\n\n我们可以看到「`show`」语法由两部分组成，由冒号分隔。\n\n`show`的右半部分是一个函数，表示选择文档的一部分以作修改。\n\n#pro-tip[\n  你可能会问，先姑且不问函数要怎么写，难道`set text(fill: blue)`也能算一个函数吗？\n\n  事实上，`set`规则是「内容类型」，它接受一个样式和一个内容，返回一个`styled`内容：\n\n  #code(```typ\n  #let x = [#set text(fill: blue)]\n  #x.func()\n  ```)\n\n  以下使用方法非常黑客，请最好不要在你的文档中包含这种代码。仅用于理解：\n\n  #code(```typ\n  #let styled = [#set text(blue)].func()\n  #let styles = text(\"\", red).styles\n  #styled([Red Text], styles)\n  ```)\n\n  1. 第一行代码，我们说`func`方法返回内容函数本身，这里便返回了一个内部的函数`styled`。\n  2. 第二行代码，这里我们从`text`内容上找到了它关于设置红色文本的样式（参数）。\n  3. 第三行代码，把一个内容及一个无论如何从某处得到了的样式传递给`styled`函数。\n  4. 最终我们构造出了一个真实的红色文本。\n\n]\n\n`show`的左半部分是选择器，表示选择文档的一部分以作修改。它作用于「作用域」内的*后续*所有*被选择器选中*的内容。\n\n如果选择器为空，则默认选择*后续所有*内容。这也是「`set`」语法对应规则的原理。如果选择器不为空，那么因为我们还没讲解选择器，所以这里不作过多讲解。\n\n但有一种选择器比较简单易懂。我们可以将内容函数作为选择器，选择相应内容作影响。\n\n以下脚本设置所有代码片段的颜色：\n\n#code(```typ\n#show raw: set text(fill: blue)\n被秀了的`代码片段`！\n```)\n\n以下脚本设置所有数学公式的颜色，但同时也修改代码片段的颜色：\n\n// todo: ugly code\n#code(```typ\n#show raw: set text(red)\n#show math.equation: set text(blue)\n#let dif2(x) = math.op(math.Delta + $x$)\n一个公式：$ sum_(f in S(x))\n  #`refl`;(f) dif2(x) $\n```)\n\n我们说，`show`的右半部分是一个函数，表示选择文档的一部分以作修改。除了直接应用`set`，应该可以有很多其他操作。现在是时候解锁Typst强大能力了。\n\n这个函数接受一个参数：参数是*未打包*（unpacked）的内容；这个函数返回一个*任意*内容。\n\n以下示例说明它接受一个*未打包*的内容。对于代码片段，我们使用「`show`」语法择区其中第二行：\n\n#code(````typ\n#show raw: it => it.lines.at(1)\n获取代码片段第二行内容：```typ\n#{\nset text(fill: true)\n}\n```\n````)\n\n在《内容类型的特性》中，我们所接触到的*已经打包*（packed）的代码片段并不包含`lines`字段。在打包后，内部大部分信息已经被屏蔽了。\n\n以下示例说明它可以返回*任意*内容。这里我们选择语言为`my-calc`的代码片段，执行并返回一个*非代码片段*：\n\n#code(````typ\n#show raw.where(lang: \"my-calc\"): it => eval(it.text)\n嵌入一个计算器语言，计算`1*2+2*(2+3)`：```my-calc 1*2+2*(2+3)```\n````)\n\n由于`show`的右半部分只要求接受内容并返回内容，我们可以有非常优雅的写法，使用一些天然满足要求的函数。\n\n以下规则将每个代码片段用方框修饰：\n\n#code(````typ\n#show raw: rect\n``` QwQ ```\n````)\n\n以下规则将每个代码片段用蓝色方框修饰：\n\n#code(````typ\n#show raw: rect.with(stroke: blue)\n``` QwQ ```\n````)\n\n// == 内容的「实例化过程」\n\n// 通过`query`我们获得同一个内容上更多的信息，即「样式」属性，即内容上的那些可选函数参数。\n\n// 根据上述例子，我们来理解为什么它只提供了语法属性。假设只看`= 123`这5个字符，显然我们从*语法*上只能获得两个信息：\n// + 它是一级标题。\n// + 它的内容是`123`。\n\n// 与之相对，当一个标题真正被放置到一个具体的「上下文」中时，才能真正关联与之相关的样式属性。例如，标题的`numbering`字段是与上下文相关的。\n\n// - location()\n\n// == import/include/styled\n\n// == 「`include`」语法 <grammar-include>\n\n// 介绍`read`，`eval(mode)`。\n\n// 路径分为相对路径和绝对路径。如果是相对路径，`read(\"other-file.typ\")`相当于在*当前*文件夹寻找对对应的文件。\n\n// `include`的本质就是`eval(read(\"other-file.typ\", mode: \"markup\"))`，获得一个「内容」，*插入到原地*。\n\n// 假设我们有一个文件：\n\n// #code(```typ\n// // 以下是other-file.typ文件的内容\n// 一段文本\n// #set text(fill: red)\n// 另一段文本\n// ```)\n\n// 那么```typ #include \"other-file.typ\"```将获得该文件的「内容」，*插入到原地*。\n\n// #code(```typ\n// #{\n//   set text(fill: blue)\n//   include \"other-file.typ\"\n// }\n// #include \"other-file.typ\"\n// ```)\n\n// `include`的文件是一个「内容块」，自带一个作用域。\n\n== 文本选择器 <grammar-text-selector>\n\n你可以使用「字符串」或「正则表达式」（`regex`）匹配文本中的特定内容，例如为`c++`文本特别设置样式：\n\n#code(````typ\n#show \"cpp\": strong(emph(box(\"C++\")))\n在古代，cpp是一门常用语言。\n````)\n\n这与使用正则表达式的效果相同：<grammar-regex-selector>\n\n#code(````typ\n#show regex(\"cp{2}\"): strong(emph(box(\"C++\")))\n在古代，cpp是一门常用语言。\n````)\n\n关于正则表达式的知识，推荐在#link(\"https://regex101.com\")[Regex 101]中继续学习。\n\n这里讲述一个关于`regex`选择器的重要知识。当文本被元素选中时，会创建一个不可见的分界，导致分界之间无法继续被正则匹配：\n\n#code(````typ\n#show \"ab\": set text(fill: blue)\n#show \"a\": set text(fill: red)\nababababababa\n````)\n\n因为`\"a\"`规则比`\"ab\"`规则更早应用，每个`a`都被单独分隔，所以`\"ab\"`规则无法匹配到任何本文。\n\n#code(````typ\n#show \"a\": set text(fill: red)\n#show \"ab\": set text(fill: blue)\nababababababa\n````)\n\n虽然每个`ab`都被单独分隔，但是`\"a\"`规则可以继续在分界内继续匹配文本。\n\n这个特征在设置文本的字体时需要特别注意：\n\n为引号单独设置字体会导致错误的排版结果。因为句号与双引号之间产生了分界，使得Typst无法应用标点挤压规则：\n\n#code(````typ\n#show \"”\": it => {\n  set text(font: \"KaiTi\")\n  highlight(it, fill: yellow)\n}\n“无名，万物之始也；有名，万物之母也。”\n````)\n\n以下正则匹配也会导致句号与双引号之间产生分界，因为没有对两个标点进行贪婪匹配：\n\n#code(````typ\n#show regex(\"[”。]\"): it => {\n  set text(font: \"KaiTi\")\n  highlight(it, fill: yellow)\n}\n“无名，万物之始也；有名，万物之母也。”\n````)\n\n以下正则匹配没有在句号与双引号之间创建分界。考虑两个标点的字体设置规则，Typst能排版出这句话的正确结果：\n\n#code(````typ\n#show regex(\"[”。]+\"): it => {\n  set text(font: \"KaiTi\")\n  highlight(it, fill: yellow)\n}\n“无名，万物之始也；有名，万物之母也。”\n````)\n\n== 标签选择器 <grammar-label-selector>\n\n基本上，任何元素都包含文本。这使得你很难对一段话针对性排版应用排版规则。「标签」有助于改善这一点。标签是「内容」，由一对「尖括号」（`<`和`>`）包裹：\n\n#code(````typ\n一句话 <some-label>\n````)\n\n「标签」可以选中恰好在它*之前*的一个内容。示例中，`<some-label>`选中了文本内容`一句话`。\n\n也就是说，「标签」无法选中在它*之前*的多个内容。以下选择器选中了`#[]`后的一句话：\n\n#code(````typ\n#show <一句话>: set text(fill: blue)\n#[一句话。]还是一句话。 <一句话>\n\n另一句话。\n````)\n\n这是因为`#[一句话。]`被分隔为了单独的内容。\n\n我们很难判断一段话中有多少个内容。因此为了可控性，我们可以使用内容块将一段话括起来，然后使用标签准确选中这一整段话：\n\n#code(````typ\n#show <一整段话>: set text(fill: blue)\n#[\n  $lambda$语言是世界上最好的语言。#[]还是一句话。\n] <一整段话>\n\n另一段话。\n````)\n\n== 选择器表达式 <grammar-selector-exp>\n\n任意「内容」可以使用「`where`」方法创建选中满足条件的选择器。\n\n例如我们可以选中二级标题：\n\n#code(````typ\n#show heading.where(level: 2): set text(fill: blue)\n= 一级标题\n== 二级标题\n````)\n\n这里`heading`是一个元素，`heading.where`创建一个选择器：\n\n#code(````typ\n选择器是：#repr(heading.where(level: 2)) \\\n类型是：#type(heading.where(level: 2))\n````)\n\n同理我们可以选中行内的代码片段而不选中代码块：\n\n#code(````typ\n#show raw.where(block:false): set text(fill: blue)\n`php`是世界上最好的语言。\n```\ntypst也是。\n```\n````)\n\n// == 「`numbering`」函数\n\n// 略\n\n== 总结\n\n本节仅以文本、代码块和内容块为例讲清楚了文件、作用域、「set」语法和「show」语法。为了拓展广度，你还需要查看《基本参考》中各种元素的用法，这样才能随心所欲排版任何「内容」。\n\n== 习题\n\n// == 字数统计\n\n// 从一个典型程序开始，这个程序基本解决我们一个需求：完成一段内容的字数统计。按照惯例，这一个程序涉及了本节所有的知识点。\n\n// ```typ\n// #let plain-text(it) = {\n//   if it.has(\"children\") {\n//     (\"\", ..it.children.map(plain-text)).join()\n//   } else if it.has(\"child\") {\n//     plain-text(it.child)\n//   } else if it.has(\"body\") {\n//     plain-text(it.body)\n//   } else if it.has(\"text\") {\n//     it.text\n//   } else if it.func() == smartquote {\n//     if it.double { \"\\\"\" } else { \"'\" }\n//   } else {\n//     \" \"\n//   }\n// }\n// ```\n\n// 以及基于其上实现一个字数统计函数：\n\n// ```typ\n// #let word-count(it) = {\n//   plain-text(it).replace(regex(\"\\p{hani}\"), \"\\1 \").split().len()\n// }\n// ```\n\n// 以下是该函数的表现：\n\n// #code.with(scope: code-scope)(```typ\n// #let show-me-the(it) = {\n//   repr(plain-text(it))\n//   [ 的字数统计为 ]\n//   repr(word-count(it))\n// }\n// #show-me-the([])\\\n// #show-me-the([一段文本]) \\\n// #show-me-the([A bc]) \\\n// #show-me-the([\n//   - 列表项1\n//   - 列表项2\n// ])\n// ```)\n\n#let plain-text(it) = {\n  if it.has(\"children\") {\n    (\"\", ..it.children.map(plain-text)).join()\n  } else if it.has(\"child\") {\n    plain-text(it.child)\n  } else if it.has(\"body\") {\n    plain-text(it.body)\n  } else if it.has(\"text\") {\n    it.text\n  } else if it.func() == smartquote {\n    if it.double {\n      \"\\\"\"\n    } else {\n      \"'\"\n    }\n  } else {\n    \" \"\n  }\n}\n#let word-count(it) = {\n  plain-text(it).replace(regex(\"\\p{hani}\"), \"\\1 \").split().len()\n}\n\n#let code-scope = (plain-text: plain-text, word-count: word-count)\n\n#let q1 = ````typ\n#let plain-text(it) = {\n  if type(it) == str {\n    it\n  } else if it.has(\"children\") {\n    (\"\", ..it.children.map(plain-text)).join()\n  } else if it.has(\"child\") {\n    plain-text(it.child)\n  } else if it.has(\"body\") {\n    plain-text(it.body)\n  } else if it.has(\"text\") {\n    it.text\n  } else if it.func() == smartquote {\n    if it.double {\n      \"\\\"\"\n    } else {\n      \"'\"\n    }\n  } else {\n    \" \"\n  }\n}\n#let main-typ() = {\n  heading(\"生活在Content树上\")\n  {\n    [现代社会以海德格尔的一句]\n    [“一切实践传统都已经瓦解完了”]\n    [为嚆矢。]\n  } + parbreak()\n  [...] + parbreak()\n  [在孜孜矻矻以求生活意义的道路上，对自己的期望本就是在与家庭与社会对接中塑型的动态过程。]\n  [而我们的底料便是对不同生活方式、不同角色的觉感与体认。]\n  [...]\n}\n#plain-text(main-typ())\n````\n\n#exercise[\n  实现内容到字符串的转换`plain-text`：对于文中出现的`main-typ()`内容，它输出：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q1 = ````typ\n#let plain-text(it) = {\n  if type(it) == str {\n    it\n  } else if it.has(\"children\") {\n    (\"\", ..it.children.map(plain-text)).join()\n  } else if it.has(\"child\") {\n    plain-text(it.child)\n  } else if it.has(\"body\") {\n    plain-text(it.body)\n  } else if it.has(\"text\") {\n    it.text\n  } else if it.func() == smartquote {\n    if it.double {\n      \"\\\"\"\n    } else {\n      \"'\"\n    }\n  } else {\n    \" \"\n  }\n}\n#let word-count(it) = {\n  plain-text(it).replace(regex(\"\\p{hani}\"), \"\\1 \").split().len()\n}\n#let main-typ() = {\n  heading(\"生活在Content树上\")\n  {\n    [现代社会以海德格尔的一句]\n    [“一切实践传统都已经瓦解完了”]\n    [为嚆矢。]\n  } + parbreak()\n  [...] + parbreak()\n  [在孜孜矻矻以求生活意义的道路上，对自己的期望本就是在与家庭与社会对接中塑型的动态过程。]\n  [而我们的底料便是对不同生活方式、不同角色的觉感与体认。]\n  [...]\n}\n#word-count(main-typ())\n````\n\n#exercise[\n  实现字数统计`word-count`：对于文中出现的`main-typ()`内容，它输出：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#exercise[\n  思考题：`plain-text`有何局限性？为什么在`show`规则影响下，`word-count`输出分别为4和5？\n\n  #code.with(scope: code-scope)(```typ\n  #let show-me-the(it) = {\n    it + [ 的字数统计为#word-count(it) ]\n  }\n  #show-me-the([#show raw: it => {\"123\"; it}; `一段文本`]) \\\n  #show-me-the([#show: it => {\"123\"; it}; 一段文本])\n  ```)\n][\n  #q1\n]\n"
  },
  {
    "path": "src/tutorial/scripting-variable.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"变量与函数\")\n\n== 变量声明 <grammar-var-decl>\n\n变量是存储“字面量”的一个个容器。它相当于为一个个字面量取名，以方便在脚本中使用。\n\n如下语法，「变量声明」表示使得`x`的内容与`\"Hello world!!\"`相等。我们对语法一一翻译：\n\n#code(```typ\n   #let    x     =  \"Hello world!!\"\n// ^^^^    ^     ^  ^^^^^^^^^^^^^^^^\n//  令    变量名  为    初始值表达式\n```)\n\n#pro-tip[\n  同时我们看见输出的文档为空，这是因为「变量声明」本身的值是`none`。\n]\n\n「变量声明」一共分为3个有效部分。\n\n+ `let`关键字：告诉Typst接下来即将开启一段「声明」。在英语中let是\"令\"的意思。\n+ #term(\"variable identifier\")、#mark(\"=\")：标识符是变量的“名称”。「变量声明」后续的位置都可以通过标识符引用该变量。\n+ #term(\"initialization expression\")：#mark(\"=\")告诉Typst变量初始等于一个表达式的值。该表达式在编译领域有一个专业术语，称为#term(\"initialization expression\")。这里，#term(\"initialization expression\")可以为任意表达式，请参阅\n\n建议标识符简短且具有描述性。尽管标识符中可以包含中文等unicode字符，但仍建议标识符中仅含英文与#mark(\"hyphen\")。\n\n// #link(<scripting-expression>)[表达式小节]。\n\n// 「变量声明」可以没有初始值表达式：\n\n// #code(```typ\n// #let x\n// #repr(x)\n// ```)\n\n// 事实上，它等价于将`x`初始化为`none`。\n\n// #code(```typ\n// #let x = none\n// #repr(x)\n// ```)\n\n// 尽管Typst允许你不写初始值表达式，本书还是建议你让所有的「变量声明」都具有初始值表达式。因为初始值表达式还告诉阅读你代码的人这个变量可能具有什么样的类型。\n\n「变量声明」后续的位置都可以继续使用该变量，取决于「作用域」。\n// + 标识符以Unicode字母、Unicode数字和#mark(\"_\")开头。以下是示例的合法变量名：\n//   ```typ\n//   // 以英文字母开头，是Unicode字母\n//   #let a; #let z; #let A; #let Z;\n//   // 以汉字开头，是Unicode字母\n//   #let 这;\n//   // 以下划线开头\n//   #let _;\n//   ```\n// + 标识符后接有限个Unicode字母、Unicode数字、#mark(\"hyphen\")和#mark(\"_\")。以下是示例的合法变量名：\n//   ```typ\n//   // 纯英文变量名，带连字号\n//   #let alpha-test; #let alpha--test;\n//   // 纯中文变量名\n//   #let 这个变量; #let 这个_变量; #let 这个-变量;\n//   // 连字号、下划线在多种位置\n//   #let alpha-; // 连字号不能在变量名开头位置\n//   #let _alpha; #let alpha_;\n//   ```\n// + 特殊规则：标识符仅为#mark(\"_\")时，不允许在后续位置继续使用。\n//   #code(```typ\n//   #let _ = 1;\n//   // 不能编译：#_\n//   ```)\n//   该标识符被称为#term(\"placeholder\")。\n// + 特殊规则：标识符不允许为`let`、`set`、`show`等关键字。\n//   #code(```typ\n//   // 不能编译：\n//   // #let let = 1;\n//   ```)\n\n#pro-tip[\n  关于作用域，你可以参考#(refs.content-scope-style)[《内容、作用域与样式》]。\n]\n\n变量可以重复输出到文档中：\n\n#code(```typ\n#let x = \"阿吧\"\n#x#x，#x#x\n```)\n\n任意时刻都可以将任意类型的值赋给一个变量。上一节所提到的「内容块」也可以赋值给一个变量。\n\n#code(```typ\n#let y = [一段文本]; #y \\\n#(y = 1) /* 重新赋值为一个整数 */ #y \\\n```)\n\n任意时刻都可以重复定义相同变量名的变量，但是之前被定义的变量将无法再被使用：\n\n#code(```typ\n#let y = [一段文本]; #y \\\n#let y = 1; /* 重新声明为一个整数 */ #y \\\n```)\n\n== 成员\n\n// ，它们是：\n// + #term(\"array literal\", postfix: \"。\")\n// + #term(\"dictionary literal\", postfix: \"。\")\n\nTypst提供了一系列「成员」和「方法」访问字面量、变量与函数中存储的“信息”。\n\n// 其实在上一节（甚至是第二节），你就已经见过了「成员」语法。<grammar-member-exp>你可以通过「点号」获得代码块的“text”（文本内容）：\n\n// #code(```typ\n// #repr(`OvO`.text), #type(`OvO`), #type(`OvO`.text)\n// ```)\n\n每个类型有哪些「成员」是由Typst决定的。你需要逐渐积累经验以知晓这些「成员」的分布，才能更快地通过访问成员快速编写出收集和处理信息的脚本。(todo: 建议阅读《参考：XXX》)\n\n当然，为防你不知道，大家不都是死记硬背的：有软件手段帮助你使用这些「成员」。许多编辑器都支持LSP（Language Server Protocol，语言服务），例如VSCode安装Tinymist LSP。当你对某个对象后接一个点号时，编辑器会自动为你做代码补全。\n\n#figure(image(\"./IDE-autocomplete.png\", width: 120pt), caption: [作者使用编辑器作代码补全的精彩瞬间。])\n\n从图中可以看出来，该代码片段「对象」上有七个「成员」。特别是“text”成员赫然立于其中，就是它了。除了「成员」列表，编辑器还会告诉你每个「成员」的作用，以及如何使用。这时候只需要选择一个「成员」作为补全结果即可。\n\n=== 函数声明 <grammar-func-decl>\n\n「函数声明」也由`let`关键字开始。如果你仔细对比，可以发现它们在语法上是一致的。\n\n如下语法，「函数声明」表示使得`f(x, y)`的内容与右侧表达式的值相等。我们对语法一一翻译：\n\n```typ\n   #let    f(x, y)      = [两个值#(x)和#(y)偷偷混入了我们内容之中。]\n// ^^^^    ^^^^^^^      ^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n//  令  函数名(参数列表)  为               一段内容\n```\n\n「函数声明」一共分为4个部分，相较于「变量声明」，多了一个参数列表。\n\n参数列表中参数的个数可以有零个，一个，至任意多个，由逗号分隔。每个参数都是一个单独的#term(\"parameter identifier\")。\n\n```typ\n// 零个参数，一个参数，两个参数，..\n    #f(),   #f(x), #f(x, y)\n```\n\n#term(\"parameter identifier\")的规则与功能与#term(\"variable identifier\")相似。为参数取名是为其能在函数体中使用。\n\n视角挪到等号右侧。#term(\"function body expression\")的规则与功能与#term(\"initialization expression\")相似。#term(\"function body expression\")可以任意使用参数列表中的“变量”，并组合出一个表达式。组合出的表达式的值就是函数调用的结果。\n\n结合例子理解函数的作用。将对应参数应用于函数可以取得对应的结果：\n\n#code(```typ\n#let f(x, y) = [两个值#(x)和#(y)偷偷混入了我们内容之中。]\n\n#let x = \"Hello world!!\"\n#let y = [一段文本]\n#f(repr(x), y)\n```)\n\n其中```typ #f(repr(x), y)```的执行过程是这样的：\n\n```typ\n#f(repr(x), y) // 转变为\n#f([\\\"Hello world!!\\\"], [一段文本])\n```\n\n注意，此时我们进入右侧表达式。\n\n```typ\n#[两个值#(x)和#(y)偷偷混入了我们内容之中。] // 转变为\n#[两个值#([\\\"Hello world!!\\\"])和#([一段文本])偷偷混入了我们内容之中。]\n```\n\n最后整理式子得到，也即是我们看到的输出：\n\n```typ\n两个值\\\"Hello world!!\\\"和一段文本偷偷混入了我们内容之中。\n```\n\n这里仅作较为基础的理解。在下一节，等我们学会了数组和字典，我们可以定义更复杂的函数。\n\n// 请体会我们在介绍「变量声明」时的特殊措辞，并与「函数声明」的语法描述相对应。\n\n// - 以下「变量声明」表示使得`x`的内容与`\"Hello world!!\"`相等。\n\n//   ```typ\n//   #let x = \"Hello world!!\"\n//   ```\n\n// - 以下「函数声明」表示使得`f(x, y)`的内容与经过计算后的`[两个值#(x)和#(y)偷偷混入了我们内容之中。]`相等。\n\n//   ```typ\n//   #let f(x, y) = [两个值#(x)和#(y)偷偷混入了我们内容之中。]\n//   ```\n\n== 函数闭包 <grammar-closure>\n\n「闭包」是一类特殊的函数，又称为匿名函数。一个简单的闭包如下：\n\n#code(```typ\n#let f = (x, y) => [#(x)和#(y)。]\n#f(\"我\", \"你\")\n```)\n\n我们可以看到其以箭头为体，两侧为参数列表和函数体。不像「函数声明」，它不需要取名。\n\n闭包以其匿名特征，很适合就地使用，让我们不必为一个暂时使用的函数起名，例如作为「回调」函数。我们将会在下一章经常使用「回调」函数。例如，Typst提供了「`show`」语法，其可以接收一个选择器和一个函数，并将其作用范围内被选择器选中的内容都使用给定函数处理：\n\n#code(```typ\n#let style(body) = text(blue, underline(body))\n#show heading.where(level: 3): style\n=== 标题\n```)\n\n但是，等价地，此时「函数声明」不如「闭包声明」优美：\n\n#code(```typ\n#show heading.where(level: 3): it => text(blue, underline(it))\n=== 标题\n```)\n\n== 方法 <grammar-method-exp>\n\n「方法」是一种特殊的「成员」。准确来说，如果一个「成员」是一个对象的函数，那么它就被称为该对象的「方法」。\n\n来看以下代码，它们输出了相同的内容，事实上，它们是*同一*「函数调用」的不同写法：\n\n#code(```typ\n#let x = \"Hello World\"\n#let str-split = str.split\n#str-split(x, \" \") \\\n#str.split(x, \" \") \\\n#x.split(\" \")\n```)\n\n第三行脚本含义对照如下。之前已经学过，这正是「函数调用」的语法：\n\n```typ\n#(        str-split(         x,  \" \"   ))\n// 调用  字符串拆分函数，参数为 变量x和空格\n```\n\n与第三行脚本相比，第四行脚本仍然是在做「函数调用」，只不过在语法上更为紧凑。\n\n第五行脚本则更加简明，此即「方法调用」。约定`str.split(x, y)`可以简写为`x.split(y)`，如果：\n+ 对象`x`是`str`类型，且方法`split`是`str`类型的「成员」。\n+ 对象`x`用作`str.split`调用的第一个参数。\n\n「方法调用」即一种特殊的「函数调用」规则（语法糖），在各编程语言中广泛存在。其大大简化了脚本。但你也可以选择不用，毕竟「函数调用」一样可以完成所有任务。\n\n#pro-tip[\n  这里有一个问题：为什么Typst要引入「方法」的概念呢？主要有以下几点考量。\n\n  其一，为了引入「方法调用」的语法，这种语法相对要更为方便和易读。对比以下两行，它们都完成了获取`\"Hello World\"`字符串的第二个单词的第一个字母的功能：\n\n  #code(\n    ```typ\n    #\"Hello World\".split(\" \").at(1).split(\"\").at(1)\n    #array.at(str.split(array.at(str.split(\"Hello World\", \" \"), 1), \"\"), 1)\n    ```,\n    al: top,\n  )\n\n  可以明显看见，第二行语句的参数已经散落在括号的里里外外，很难理解到底做了什么事情。\n\n  其二，相比「函数调用」，「方法调用」更有利于现代IDE补全脚本。你可以通过`.split`很快定位到“字符串拆分”这个函数。\n\n  其三，方便用户管理相似功能的函数。不仅仅是字符串可以拆分，似乎内容及其他许多类型也可以拆分。如果一一为它们取不同的名字，那可就太头疼了。相比，`str.split`就简单多了。要知道，很多程序员都非常头痛为不同的变量和函数取名。\n]\n\n== 总结\n\n// Typst如何保证一个简单函数甚至是一个闭包是“纯函数”？\n\n// 答：1. 禁止修改外部变量，则捕获的变量的值是“纯的”或不可变的；2. 折叠的对象是纯的，且「折叠」操作是纯的。\n\n// Typst的多文件特性从何而来？\n\n// 答：1. import函数产生一个模块对象，而模块其实是文件顶层的scope。2. include函数即执行该文件，获得该文件对应的内容块。\n\n// 基于以上两个特性，Typst为什么快？\n\n// + Typst支持增量解析文件。\n// + Typst所有由*用户声明*的函数都是纯的，在其上的调用都是纯的。例如Typst天生支持快速计算递归实现的fibnacci函数：\n\n//   #code(```typ\n//   #let fib(n) = if n <= 1 { n } else { fib(n - 1) + fib(n - 2) }\n//   #fib(42)\n//   ```)\n// + Typst使用`include`导入其他文件的顶层「内容块」。当其他文件内容未改变时，内容块一定不变，而所有使用到对应内容块的函数的结果也一定不会因此改变。\n\n// 这意味着，如果你发现了Typst中与一般语言的不同之处，可以思考以上种种优势对用户脚本的增强或限制。\n\n== 总结\n\n#todo-box[总结]\n\n== 习题\n\n#todo-box[习题]\n"
  },
  {
    "path": "src/tutorial/stateful/q0.typ",
    "content": "\n#let curr-heading = state(\"curr-heading\", ())\n\n#set text(size: 8pt)\n\n#let set-heading(content) = {\n  show heading.where(level: 3): it => {\n    show regex(\"[\\p{hani}\\s]+\"): underline\n    it\n  }\n  show heading: it => {\n    show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n    show regex(\"FuwaFuwa\"): box(\"✎\", baseline: -20%)\n    it\n  }\n\n  set page(header: {\n    set text(size: 5pt);\n    [这是页眉]\n  })\n\n  content\n}\n\n#let set-text(content) = {\n  show regex(\"feat|refactor\"): emph\n  content\n}\n\n#show: set-heading\n#show: set-text\n\n#set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt))\n\n== 雨滴书v0.1.2\n=== KiraKira 样式改进\nfeat: 改进了样式。\n=== FuwaFuwa 脚本改进\nfeat: 改进了脚本。\n\n== 雨滴书v0.1.1\nrefactor: 移除了LaTeX。\n\nfeat: 删除了一个多余的文件夹。\n\n== 雨滴书v0.1.0\nfeat: 新建了两个文件夹。\n"
  },
  {
    "path": "src/tutorial/stateful/q1.typ",
    "content": "\n#set text(size: 8pt)\n\n#let calc-headings(headings) = {\n  let max-page-num = calc.max(..headings.map(it => it.location().page()))\n  let first-headings = (none,) * max-page-num\n  let last-headings = (none,) * max-page-num\n\n  for h in headings {\n    if first-headings.at(h.location().page() - 1) == none {\n      first-headings.at(h.location().page() - 1) = h\n    }\n    last-headings.at(h.location().page() - 1) = h\n  }\n\n  let res-headings = (none,) * max-page-num\n  for i in range(res-headings.len()) {\n    res-headings.at(i) = if first-headings.at(i) != none {\n      first-headings.at(i)\n    } else {\n      last-headings.at(i) = last-headings.at(\n        calc.max(0, i - 1),\n        default: none,\n      )\n      last-headings.at(i)\n    }\n  }\n\n  (\n    res-headings,\n    if max-page-num > 0 {\n      last-headings.at(-1)\n    },\n  )\n}\n\n#let get-heading-at-page() = {\n  let (headings, last-heading) = calc-headings(query(heading.where(level: 2)))\n  headings.at(here().page() - 1, default: last-heading)\n}\n\n#let set-heading(content) = {\n  show heading.where(level: 3): it => {\n    show regex(\"[\\p{hani}\\s]+\"): underline\n    it\n  }\n  show heading: it => {\n    show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n    show regex(\"FuwaFuwa\"): box(\"✎\", baseline: -20%)\n    it\n  }\n\n  set page(\n    header: context {\n      set text(size: 5pt)\n      emph(get-heading-at-page())\n    },\n  )\n\n  content\n}\n\n#let set-text(content) = {\n  show regex(\"feat|refactor\"): emph\n  content\n}\n\n#show: set-heading\n#show: set-text\n\n#set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt))\n\n== 雨滴书v0.1.2\n=== KiraKira 样式改进\nfeat: 改进了样式。\n=== FuwaFuwa 脚本改进\nfeat: 改进了脚本。\n\n== 雨滴书v0.1.1\nrefactor: 移除了LaTeX。\n\nfeat: 删除了一个多余的文件夹。\n\n== 雨滴书v0.1.0\nfeat: 新建了两个文件夹。\n"
  },
  {
    "path": "src/tutorial/stateful/s1.typ",
    "content": "\n#let curr-heading = state(\"curr-heading\", ())\n\n#set text(size: 8pt)\n\n#let first-heading = state(\"first-heading\", (:))\n#let last-heading = state(\"last-heading\", (:))\n\n#let find-headings(headings, page-num) = if page-num > 0 {\n  headings.at(str(page-num), default: find-headings(headings, page-num - 1))\n}\n\n#let get-heading-at-page() = {\n  let first-headings = first-heading.final()\n  let last-headings = last-heading.at(here())\n  let page-num = here().page()\n\n  first-headings.at(str(page-num), default: find-headings(last-headings, page-num))\n}\n\n#let update-heading-at-page(h) = context {\n  let k = str(here().page())\n  last-heading.update(it => {\n    it.insert(k, h)\n    it\n  })\n  first-heading.update(it => {\n    if k not in it {\n      it.insert(k, h)\n    }\n    it\n  })\n}\n\n#let set-heading(content) = {\n  show heading.where(level: 2): it => {\n    it\n    update-heading-at-page(it.body)\n  }\n  show heading.where(level: 3): it => {\n    show regex(\"[\\p{hani}\\s]+\"): underline\n    it\n  }\n  show heading: it => {\n    show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n    show regex(\"FuwaFuwa\"): box(\"✎\", baseline: -20%)\n    it\n  }\n\n  set page(\n    header: context {\n      set text(size: 5pt)\n      emph(get-heading-at-page())\n    },\n  )\n\n  content\n}\n\n#let set-text(content) = {\n  show regex(\"feat|refactor\"): emph\n  content\n}\n\n#show: set-heading\n#show: set-text\n\n#set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt))\n\n== 雨滴书v0.1.2\n=== KiraKira 样式改进\nfeat: 改进了样式。\n=== FuwaFuwa 脚本改进\nfeat: 改进了脚本。\n\n== 雨滴书v0.1.1\nrefactor: 移除了LaTeX。\n\nfeat: 删除了一个多余的文件夹。\n\n== 雨滴书v0.1.0\nfeat: 新建了两个文件夹。\n\n"
  },
  {
    "path": "src/tutorial/stateful/s2.typ",
    "content": "\n#let curr-heading = state(\"curr-heading\", ())\n\n#set text(size: 8pt)\n\n#let set-heading(content) = {\n  show heading.where(level: 3): it => {\n    show regex(\"[\\p{hani}\\s]+\"): underline\n    it\n  }\n  show heading: it => {\n    show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n    show regex(\"FuwaFuwa\"): box(\"✎\", baseline: -20%)\n    it\n  }\n\n  content\n}\n\n#let set-text(content) = {\n  show regex(\"feat|refactor\"): emph\n  content\n}\n\n#show: set-text\n\n#set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt))\n\n== 雨滴书v0.1.2\n=== KiraKira 样式改进\nfeat: 改进了样式。\n=== FuwaFuwa 脚本改进\nfeat: 改进了脚本。\n\n== 雨滴书v0.1.1\nrefactor: 移除了LaTeX。\n\nfeat: 删除了一个多余的文件夹。\n\n== 雨滴书v0.1.0\nfeat: 新建了两个文件夹。\n\n"
  },
  {
    "path": "src/tutorial/stateful/s3.typ",
    "content": "\n#let curr-heading = state(\"curr-heading\", ())\n\n#set text(size: 8pt)\n\n#let set-heading(content) = {\n  show heading.where(level: 3): it => {\n    show regex(\"[\\p{hani}\\s]+\"): underline\n    it\n  }\n  show heading: it => {\n    show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n    show regex(\"FuwaFuwa\"): box(\"✎\", baseline: -20%)\n    it\n  }\n\n  content\n}\n\n#let set-text(content) = {\n  show regex(\"feat|refactor\"): emph\n  content\n}\n\n#show: set-heading\n#show: set-text\n\n#set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt))\n\n== 雨滴书v0.1.2\n=== KiraKira 样式改进\nfeat: 改进了样式。\n=== FuwaFuwa 脚本改进\nfeat: 改进了脚本。\n\n== 雨滴书v0.1.1\nrefactor: 移除了LaTeX。\n\nfeat: 删除了一个多余的文件夹。\n\n== 雨滴书v0.1.0\nfeat: 新建了两个文件夹。\n\n"
  },
  {
    "path": "src/tutorial/stateful-v0.12.0/q0.typ",
    "content": "\n#let curr-heading = state(\"curr-heading\", ())\n\n#set text(size: 8pt)\n\n#let set-heading(content) = {\n  show heading.where(level: 3): it => {\n    show regex(\"[\\p{hani}\\s]+\"): underline\n    it\n  }\n  show heading: it => {\n    show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n    show regex(\"FuwaFuwa\"): box(\"✎\", baseline: -20%)\n    it\n  }\n\n  // @typstyle off\n  set page(header: {\n    set text(size: 5pt);\n    [这是页眉]\n  })\n\n  content\n}\n\n#let set-text(content) = {\n  show regex(\"feat|refactor\"): emph\n  content\n}\n\n#show: set-heading\n#show: set-text\n\n#set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt))\n\n== 雨滴书v0.1.2\n=== KiraKira 样式改进\nfeat: 改进了样式。\n=== FuwaFuwa 脚本改进\nfeat: 改进了脚本。\n\n== 雨滴书v0.1.1\nrefactor: 移除了LaTeX。\n\nfeat: 删除了一个多余的文件夹。\n\n== 雨滴书v0.1.0\nfeat: 新建了两个文件夹。\n"
  },
  {
    "path": "src/tutorial/stateful-v0.12.0/q1.typ",
    "content": "\n#set text(size: 8pt)\n\n#let calc-headings(headings) = {\n  let max-page-num = calc.max(..headings.map(it => it.location().page()))\n  let first-headings = (none,) * max-page-num\n  let last-headings = (none,) * max-page-num\n\n  for h in headings {\n    if first-headings.at(h.location().page() - 1) == none {\n      first-headings.at(h.location().page() - 1) = h\n    }\n    last-headings.at(h.location().page() - 1) = h\n  }\n\n  let res-headings = (none,) * max-page-num\n  for i in range(res-headings.len()) {\n    res-headings.at(i) = if first-headings.at(i) != none {\n      first-headings.at(i)\n    } else {\n      last-headings.at(i) = last-headings.at(\n        calc.max(0, i - 1),\n        default: none,\n      )\n      last-headings.at(i)\n    }\n  }\n\n  (\n    res-headings,\n    if max-page-num > 0 {\n      last-headings.at(-1)\n    },\n  )\n}\n\n#let get-heading-at-page() = {\n  let (headings, last-heading) = calc-headings(query(heading.where(level: 2)))\n  headings.at(here().page() - 1, default: last-heading)\n}\n\n#let set-heading(content) = {\n  show heading.where(level: 3): it => {\n    show regex(\"[\\p{hani}\\s]+\"): underline\n    it\n  }\n  show heading: it => {\n    show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n    show regex(\"FuwaFuwa\"): box(\"✎\", baseline: -20%)\n    it\n  }\n\n  // @typstyle off\n  set page(header: context {\n    set text(size: 5pt)\n    emph(get-heading-at-page())\n  })\n\n  content\n}\n\n#let set-text(content) = {\n  show regex(\"feat|refactor\"): emph\n  content\n}\n\n#show: set-heading\n#show: set-text\n\n#set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt))\n\n== 雨滴书v0.1.2\n=== KiraKira 样式改进\nfeat: 改进了样式。\n=== FuwaFuwa 脚本改进\nfeat: 改进了脚本。\n\n== 雨滴书v0.1.1\nrefactor: 移除了LaTeX。\n\nfeat: 删除了一个多余的文件夹。\n\n== 雨滴书v0.1.0\nfeat: 新建了两个文件夹。\n"
  },
  {
    "path": "src/tutorial/stateful-v0.12.0/s1.typ",
    "content": "\n#let curr-heading = state(\"curr-heading\", ())\n\n#set text(size: 8pt)\n\n#let first-heading = state(\"first-heading\", (:))\n#let last-heading = state(\"last-heading\", (:))\n\n#let find-headings(headings, page-num) = if page-num > 0 {\n  headings.at(str(page-num), default: find-headings(headings, page-num - 1))\n}\n\n#let get-heading-at-page() = {\n  let first-headings = first-heading.final()\n  let last-headings = last-heading.get()\n  let page-num = here().page()\n\n  first-headings.at(str(page-num), default: find-headings(last-headings, page-num))\n}\n\n#let update-heading-at-page(h) = context {\n  let k = str(here().page())\n  last-heading.update(it => {\n    it.insert(k, h)\n    it\n  })\n  first-heading.update(it => {\n    if k not in it {\n      it.insert(k, h)\n    }\n    it\n  })\n}\n\n#let set-heading(content) = {\n  show heading.where(level: 2): it => {\n    it\n    update-heading-at-page(it.body)\n  }\n  show heading.where(level: 3): it => {\n    show regex(\"[\\p{hani}\\s]+\"): underline\n    it\n  }\n  show heading: it => {\n    show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n    show regex(\"FuwaFuwa\"): box(\"✎\", baseline: -20%)\n    it\n  }\n\n  set page(\n    header: context {\n      set text(size: 5pt)\n      emph(get-heading-at-page())\n    },\n  )\n\n  content\n}\n\n#let set-text(content) = {\n  show regex(\"feat|refactor\"): emph\n  content\n}\n\n#show: set-heading\n#show: set-text\n\n#set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt))\n\n== 雨滴书v0.1.2\n=== KiraKira 样式改进\nfeat: 改进了样式。\n=== FuwaFuwa 脚本改进\nfeat: 改进了脚本。\n\n== 雨滴书v0.1.1\nrefactor: 移除了LaTeX。\n\nfeat: 删除了一个多余的文件夹。\n\n== 雨滴书v0.1.0\nfeat: 新建了两个文件夹。\n\n"
  },
  {
    "path": "src/tutorial/stateful-v0.12.0/s2.typ",
    "content": "\n#let curr-heading = state(\"curr-heading\", ())\n\n#set text(size: 8pt)\n\n#let set-heading(content) = {\n  show heading.where(level: 3): it => {\n    show regex(\"[\\p{hani}\\s]+\"): underline\n    it\n  }\n  show heading: it => {\n    show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n    show regex(\"FuwaFuwa\"): box(\"✎\", baseline: -20%)\n    it\n  }\n\n  content\n}\n\n#let set-text(content) = {\n  show regex(\"feat|refactor\"): emph\n  content\n}\n\n#show: set-text\n\n#set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt))\n\n== 雨滴书v0.1.2\n=== KiraKira 样式改进\nfeat: 改进了样式。\n=== FuwaFuwa 脚本改进\nfeat: 改进了脚本。\n\n== 雨滴书v0.1.1\nrefactor: 移除了LaTeX。\n\nfeat: 删除了一个多余的文件夹。\n\n== 雨滴书v0.1.0\nfeat: 新建了两个文件夹。\n\n"
  },
  {
    "path": "src/tutorial/stateful-v0.12.0/s3.typ",
    "content": "\n#let curr-heading = state(\"curr-heading\", ())\n\n#set text(size: 8pt)\n\n#let set-heading(content) = {\n  show heading.where(level: 3): it => {\n    show regex(\"[\\p{hani}\\s]+\"): underline\n    it\n  }\n  show heading: it => {\n    show regex(\"KiraKira\"): box(\"★\", baseline: -20%)\n    show regex(\"FuwaFuwa\"): box(\"✎\", baseline: -20%)\n    it\n  }\n\n  content\n}\n\n#let set-text(content) = {\n  show regex(\"feat|refactor\"): emph\n  content\n}\n\n#show: set-heading\n#show: set-text\n\n#set page(width: 120pt, height: 120pt, margin: (top: 12pt, bottom: 10pt, x: 5pt))\n\n== 雨滴书v0.1.2\n=== KiraKira 样式改进\nfeat: 改进了样式。\n=== FuwaFuwa 脚本改进\nfeat: 改进了脚本。\n\n== 雨滴书v0.1.1\nrefactor: 移除了LaTeX。\n\nfeat: 删除了一个多余的文件夹。\n\n== 雨滴书v0.1.0\nfeat: 新建了两个文件夹。\n\n"
  },
  {
    "path": "src/tutorial/writing-chinese.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"中文排版\")\n\n建议结合#link(\"https://typst-doc-cn.github.io/docs/chinese/\")[《Typst中文文档：中文用户指南》]食用。\n\n== 中文排版 —— 字体\n\n== 设置中文字体\n\n如果中文字体不符合 typst 要求，那么它不会选择你声明的字体，例如字体的变体数量不够，参考更详细的 issue。\n\n+ typst fonts 查看系统字体，确保字体名字没有错误。\n+ typst fonts --font-path path/to/your-fonts 指定字体目录。\n+ typst fonts --variants 查看字体变体。\n+ 检查中文字体是否已经完全安装。\n\n== 设置语言和区域\n\n如果字体与 ```typc text(lang: .., region: ..)``` 不匹配，可能会导致连续标点的挤压。例如字体不是中国大陆的，标点压缩会出错；反之亦然。\n\n=== 伪粗体\n\n=== 伪斜体\n\n=== 同时设置中西字体（以宋体和Times New Roman为例）\n\n== 中文排版 —— 间距\n\n=== 首行缩进\n\n#let empty-par = par[#box()]\n#let fake-par = context empty-par + v(-measure(empty-par + empty-par).height)\n\n#let no-indent = context h(-par.first-line-indent)\n\n#let a = {\n  1\n}\n\n=== 代码片段与中文文本之间的间距\n\n=== 数学公式与中文文本之间的间距\n\n== 中文排版 —— 特殊排版\n\n=== 使用中文编号\n\n=== 为汉字和词组注音\n\n=== 为汉字添加着重号\n\n=== 竖排文本\n\n=== 使用国标文献格式\n\n== Typst v0.10.0为止的已知问题及补丁\n\n=== 源码换行导致linebreak\n\n=== 标点字体fallback\n\n== 模板参考\n\n#link(\"https://github.com/typst-doc-cn/tutorial/blob/b452e6ec436aa150a6429becb8cf046d08360f63/typ/templates/page.typ\")[本书各章节使用的模板]\n\ntodo: 内嵌和超链接可直接食用的模板\n\n== 进一步阅读\n\n+ 参考#link(\"https://typst-doc-cn.github.io/docs/chinese/\")[《Typst中文文档：中文用户指南》]，包含中文用户常见问题。\n+ 参考#(refs.scripting-modules)[《模块、外部库与多文件文档》]，在你的电脑上共享中文文档模板。\n+ 推荐使用的外部库列表\n"
  },
  {
    "path": "src/tutorial/writing-markup.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [标记模式])\n\nTypst是一门简明但强大的现代排版语言，你可以使用简洁直观的语法排版出好看的文档。\n\nTypst希望你总是尽可能少的配置样式，就获得一个排版精良的文档。多数情况下，你只需要专心撰写文档，而不需要在文档内部对排版做任何更复杂的调整。\n\n得益于此设计目标，为了使你可以用Typst编写一篇基本文档，本节仍只需涉及最基本的语法。哪怕只依靠这些语法，你已经可以编写满足很多场合需求的文档。\n\n== 段落 <grammar-paragraph>\n\n普通文本默认组成一个个段落。\n\n#code(```typ\n我是一段文本\n```)\n\n另起一行文本不会产生新的段落。为了创建新的段落，你需要空至少一行。\n\n#code(```typ\n轻轻的我走了，\n正如我轻轻的来；\n\n我轻轻的招手，\n作别西天的云彩。\n```)\n\n缩进并不会产生新的空格：\n\n#code(```typ\n  轻轻的我走了，\n      正如我轻轻的来；\n\n  我轻轻的招手，\n      作别西天的云彩。\n```)\n\n注意：如下图的蓝框高亮所示，另起一行会引入一个小的空格。该问题会在未来修复。\n\n#code(\n  ```typ\n  轻轻的我走了，#box(fill: blue, outset: (right: 0.2em), sym.space)\n  正如我轻轻的来；\n\n  轻轻的我走了，正如我轻轻的来；\n  ```,\n  code-as: ```typ\n  轻轻的我走了，\n  正如我轻轻的来；\n\n  轻轻的我走了，正如我轻轻的来；\n  ```,\n)\n\n== 标题 <grammar-heading>\n\n你可以使用一个或多个*连续*的#mark(\"=\")开启一个标题。\n\n#code(```typ\n= 一级标题\n我走了。\n== 二级标题\n我来了。\n=== 三级标题\n我走了又来了。\n```)\n\n等于号的数量恰好对应了标题的级别。一级标题由一个#mark(\"=\")开启，二级标题由两个#mark(\"=\")开启，以此类推。\n\n注意：正如你所见，标题会强制划分新的段落。\n\n#pro-tip[\n  使用show规则可以改变“标题会强制划分新的段落”这个默认规则。\n\n  #code(```typ\n  #show heading.where(level: 3): box\n  = 一级标题\n  我走了。\n\n  === 三级标题\n  我走了又来了。\n  ```)\n]\n\n== 着重和强调语义\n\n有许多与#mark(\"=\")类似的语法标记。当你以相应的语法标记文本内容时，相应的文本就被赋予了特别的语义和样式。\n\n#pro-tip[\n  与HTML一样，Typst总是希望语义先行。所谓语义先行，就是在编写文档时总是首先考虑标记语义。所有样式都是附加到语义上的。\n\n  例如在英文排版中，#typst-func(\"strong\")的样式是加粗，#typst-func(\"emph\")的样式是倾斜。你完全可以在中文排版中为它们更换样式。\n\n  #if is-web-target {\n    code(```typ\n    #show strong: content => {\n      show regex(\"\\p{Hani}\"): it => box(place(text(\"·\", size: 0.8em), dx: 0.1em, dy: 0.75em) + it)\n      content.body\n    }\n    *中文排版的着重语义用加点表示。*\n    ```)\n  } else {\n    code(```typ\n    #show strong: content => {\n      show regex(\"\\p{Hani}\"): it => box(place(text(\"·\", size: 1.3em), dx: 0.3em, dy: 0.5em) + it)\n      content.body\n    }\n    *中文排版的着重语义用加点表示。*\n    ```)\n  }\n]\n\n与许多标记语言相同，Typst中使用一系列#term(\"delimiter\")规则确定一段语义的开始和结束。为赋予语义，需要将一个#term(\"delimiter\")置于文本*之前*，表示某语义的开始；同时将另一个#term(\"delimiter\")置于文本*之后*，表示该语义的结束。\n\n例如，#mark(\"*\")作为定界符赋予所包裹的一段文本以#term(\"strong semantics\")。 <grammar-strong>\n\n#code(```typ\n着重语义：这里有一个*重点！*\n```)\n\n与#term(\"strong semantics\")类似，#mark(\"_\")作为定界符将赋予#term(\"emphasis semantics\")： <grammar-emph>\n\n#code(```typ\n强调语义：_emphasis_\n```)\n\n着重语义一般比强调语义语气更重。着重和强调语义可以相互嵌套：\n\n#code(```typ\n着重且强调：*_strong emph_* 或 _*strong emph*_\n```)\n\n注意：中文排版一般不使用斜体表示着重或强调。\n\n== （计算机）代码片段 <grammar-raw>\n\nTypst的#term(\"raw block\")标记语法与Markdown完全相同。\n\n配对的#mark(\"`\")包裹一段内容，表示内容为#term(\"raw block\")。\n\n#code(````typ\n短代码片段：`code`\n````)\n\n有时候你希望允许代码内容包含换行或#mark(\"`\")。这时候，你需要使用*至少连续*三个#mark(\"`\")组成定界符标记#term(\"raw block\")：<grammar-long-raw>\n\n#code(`````typ\n使用三个反引号包裹：``` ` ```\n\n使用四个反引号包裹：```` ``` ````\n`````)\n\n对于长代码片段，你还可以在起始定界符后*紧接着*指定该代码的语言类别，以便Typst据此完成语法高亮。<grammar-lang-raw>\n\n#code(`````typ\n一段有高亮的代码片段：```javascript function uninstallLaTeX {}```\n\n另一段有高亮的代码片段：````typst 包含反引号的长代码片段：``` ` ``` ````\n`````)\n\n除了定界符的长短，代码片段还有是否成块的区分。如果代码片段符合以下两点，那么它就是一个#term(\"blocky raw block\")： <grammar-blocky-raw>\n+ 使用*至少连续*三个#mark(\"`\")，即其需为长代码片段。\n+ 内容包含至少一个#term(\"line break\")。\n\n#code(`````typ\n非块代码片段：```rust trait World```\n\n块代码片段：```js\nfunction fibnacci(n) {\n  return n <= 1 ?: `...`;\n}\n```\n`````)\n\n// typ\n// typc\n\n== 列表\n\nTypst的列表语法与Markdown非常类似，但*不完全相同*。\n\n一行以#mark(\"-\")开头即开启一个无编号列表项： <grammar-enum>\n\n#code(```typ\n- 一级列表项1\n```)\n\n与之相对，#mark(\"+\")开启一个有编号列表项。 <grammar-list>\n\n#code(```typ\n+ 一级列表项1\n```)\n\n利用缩进控制列表项等级：\n\n#code(```typ\n- 一级列表项1\n  - 二级列表项1.1\n    - 三级列表项1.1.1\n  - 二级列表项1.2\n- 一级列表项2\n  - 二级列表项2.1\n```)\n\n有编号列表项可以与无编号列表项相混合。<grammar-mix-list-emum>\n\n#code(```typ\n+ 一级列表项1\n  - 二级列表项1.1\n    + 三级列表项1.1.1\n  - 二级列表项1.2\n+ 一级列表项2\n  - 二级列表项2.1\n```)\n\n和Markdown相同，Typst同样允许使用显式的编号`1.`开启列表。这方便对列表继续编号。<grammar-continue-list>\n\n#code(```typ\n1. 列表项1\n1. 列表项2\n```)\n\n#code(```typ\n1. 列表项1\n+  列表项2\n\n列表间插入一段描述。\n\n3. 列表项3\n+  列表项4\n+  列表项5\n```)\n\n== 转义序列 <grammar-escape-sequences>\n\n你有时希望直接展示标记符号本身。例如，你可能想直接展示一个#mark(\"_\")，而非使用强调语义。这时你需要利用#term(\"escape sequences\")语法：\n\n#code(````typ\n在段落中直接使用下划线 >\\_<！\n````)\n\n遵从许多编程语言的习惯，Typst使用#mark(\"\\\\\")转义特殊标记。下表给出了部分可以转义的字符：\n\n#let escaped-sequences = (\n  (`\\\\`, [\\\\]),\n  (`\\/`, [\\/]),\n  (`\\[`, [\\[]),\n  (`\\]`, [\\]]),\n  (`\\{`, [\\{]),\n  (`\\}`, [\\}]),\n  (`\\<`, [\\<]),\n  (`\\>`, [\\>]),\n  (`\\(`, [\\(]),\n  (`\\)`, [\\)]),\n  (`\\#`, [\\#]),\n  (`\\*`, [\\*]),\n  (`\\_`, [\\_]),\n  (`\\+`, [\\+]),\n  (`\\=`, [\\=]),\n  (`\\~`, [\\~]),\n  // @typstyle off\n  (```\\` ```, [\\`]),\n  (`\\$`, [\\$]),\n  (`\\\"`, [\\\"]),\n  (`\\'`, [\\']),\n  (`\\@`, [\\@]),\n  (`\\a`, [\\a]),\n  (`\\A`, [\\A]),\n)\n\n#let mk-tab(seq) = {\n  set align(center)\n  let u = it => raw(it.at(0).text, lang: \"typ\")\n  let ovo = it => it.at(1)\n  let w = 8\n  table(\n    columns: w + 2,\n    [代码],\n    ..seq.slice(w * 0, w * 1).map(u),\n    u((`\\u{cccc}`, [\\u{cccc}])),\n    [效果],\n    ..seq.slice(w * 0, w * 1).map(ovo),\n    [\\u{cccc}],\n    [代码],\n    ..seq.slice(w * 1, w * 2).map(u),\n    u((`\\u{cCCc}`, [\\u{cCCc}])),\n    [效果],\n    ..seq.slice(w * 1, w * 2).map(ovo),\n    [\\u{cCCc}],\n    [代码],\n    ..seq.slice(w * 2).map(u),\n    [],\n    u((`\\u{2665}`, [\\u{2665}])),\n    [效果],\n    ..seq.slice(w * 2).map(ovo),\n    [],\n    [\\u{2665}],\n  )\n}\n\n#mk-tab(escaped-sequences)\n\n以上大部分#term(\"escape sequences\")都紧跟单个字符，除了表中的最后一列。 <grammar-unicode-escape-sequences>\n\n表中的最后一列所展示的`\\u{unicode}`语法被称为Unicode转义序列，也常见于各种语言。你可以通过将`unicode`替换为#link(\"https://zh.wikipedia.org/zh-cn/%E7%A0%81%E4%BD%8D\")[Unicode码位]的值，以输出该特定字符，而无需*输入法支持*。例如，你可以这样输出一句话：\n\n#code(\n  ````typ\n  \\u{9999}\\u{8FA3}\\u{725B}\\u{8089}\\u{7C89}\\u{597D}\\u{5403}\\u{2665}\n  ````,\n  code-as: ````typ\n  \\u{9999}\\u{8FA3}\\u{725B}\\u{8089}\\u{7C89}\n  \\u{597D}\\u{5403}\\u{2665}\n  ````,\n)\n\n诸多#term(\"escape sequences\")无需死记硬背，你只需要记住：\n+ 如果其在Typst中已经被赋予含义，请尝试在字符前添加一个#mark(\"\\\\\")。\n+ 如果其不可见或难以使用输入法获得，请考虑使用`\\u{unicode}`。\n\n== 输出换行符 <grammar-newline>\n\n输出换行符是一种特殊的#term(\"escape sequences\")，它使得文档输出换行。\n\n#mark(\"\\\\\")后紧接一个任意#term(\"whitespace\")，表示在此处主动插入一个段落内的换行符： <grammar-newline-by-space>\n\n#code(````typ\n转义空格可以换行 \\ 转义回车也可以换行 \\\n换行！\n````)\n\n空白字符可以取短空格（`U+0020`）、长空格（`U+3000`）、回车（`U+000D`）等。\n\n== 速记符号 <grammar-shorthand>\n\n在#term(\"markup mode\")下，一些符号需要用特殊的符号组合打出，这种符号组合被称为#term(\"shorthand\")。它们是：\n\n空格（`U+0020`）的#term(\"shorthand\")是#mark(\"~\")： <grammar-shorthand-space>\n\n#code(```typ\nAB v.s. A~B\n```)\n\n连接号（en dash, `U+2013`）的#term(\"shorthand\")由两个连续的#mark(\"hyphen\")组成：\n\n#code(```typ\n北京--上海路线的列车正在到站。\n```)\n\n// 破折号的#term(\"shorthand\")由三个连续的#mark(\"hyphen\")组成（有问题，毋用，请直接使用em dash，`—`）：\n\n// #code(```typ\n// 你的生日------四月十八日------每年我总记得。\\\n// 你的生日——四月十八日——每年我总记得。\n// ```)\n\n省略号的#term(\"shorthand\")由三个连续的#mark(\".\")组成：\n\n#code(```typ\n真的假的......\n```)\n\n// - minusi\n// -? soft-hyphen\n\n完整的速记符号列表参考#link(\"https://typst.app/docs/reference/symbols/\")[Typst Symbols]。\n\n== 注释 <grammar-inline-comment>\n\nTypst的#term(\"comment\")直接采用C语言风格的注释语法，有两种表示方法。\n\n第一种写法是将注释内容放在两个连续的#mark(\"/\")后面，从双斜杠到行尾都属于#term(\"comment\")。\n\n#code(````typ\n// 这是一行注释\n一行文本 // 这也是注释\n````)\n\n与代码片段的情形类似，Typst也提供了另外一种可以跨行的#term(\"comment\")，形如`/*...*/`。<grammar-cross-line-comment>\n\n#code(````typ\n你没有看见/* 混入其中 */注释\n````)\n\n值得注意的是，Typst会将#term(\"comment\")从源码中剔除后再解释你的文档，因此它们对文档没有影响。\n\n以下两个段落等价：\n\n#code(````typ\n注释不会\n// 这是一行注释 // 注释内的注释还是注释\n插入换行 // 这也是注释\n\n注释不会\n插入换行\n````)\n\n以下三个段落等价：\n\n#code(````typ\n注释不会/* 混入其中 */插入空格\n\n注释不会/*\n混入其中\n*/插入空格\n\n注释不会插入空格\n````)\n\n== 总结\n\n基于《初识标记模式》掌握的知识，你应该编写一些非常简单的文档。\n\n== 习题\n\n#let q1 = ````typ\n欲穷千里目，/*\n*/更上一层楼。\n````\n\n#exercise[\n  使源码至少有两行，第一行包含“欲穷千里目，”，第二行包含“更上一层楼。”，但是输出不换行不空格：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#exercise[\n  输出一个#mark(\"*\")，期望的输出：#rect(width: 100%)[\\*]\n][\n  ```typ\n  \\*\n  ```\n]\n\n#let q1 = ````typ\n```\n`\n```\n````\n\n#exercise[\n  插入代码片段使其包含一个反引号，期望的输出：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q1 = ```typ\n\n```\n\n#let q1 = `````typ\n````\n```\n````\n`````\n\n#exercise[\n  插入代码片段使其包含三个反引号，期望的输出：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q1 = ````typ\n你可以在Typst内通过插件 ```typc plugin(\"typst.wasm\")``` 调用Typst编译器。\n````\n\n#exercise[\n  插入行内的“typc”代码，期望的输出：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q2 = ```typ\n约法五章。\n1. 其一。\n+ 其二。\n\n前两条不算。\n\n3. 其三。\n+ 其四。\n+ 其五。\n```\n\n#exercise[\n  在有序列表间插入描述，期望的输出：#rect(width: 100%, eval(q2.text, mode: \"markup\"))\n][\n  #q2\n]\n"
  },
  {
    "path": "src/tutorial/writing-math.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"数学排版\")\n\n== 数学排版\n"
  },
  {
    "path": "src/tutorial/writing-scripting.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: [脚本模式])\n\n从现在开始，示例将会逐渐开始出现脚本。不要担心，它们都仅涉及脚本的简单用法。\n\n== 内容块 <grammar-content-block>\n\n有时，文档中会出现连续大段的标记文本。\n\n#code(````typ\n*从前有座山，山会讲故事，故事讲的是*\n\n*从前有座山，山会讲故事，故事讲的是*\n\n*...*\n\n````)\n\n这可行，但稍显麻烦。如下代码则显得更为整洁，它不必为每段都打上着重标记：\n\n#code(````typ\n#strong[\n  从前有座山，山会讲故事，故事讲的是\n\n  从前有座山，山会讲故事，故事讲的是\n\n  ...\n]\n````)\n\n例中，```typ #strong[]```这个内容的语法包含三个部分：\n+ `#`使解释器进入#term(\"code mode\")。\n+ #typst-func(\"strong\")是赋予#term(\"strong semantics\")函数。\n+ `[]`作为#term(\"content block\")标记一段内容，供`strong`使用。\n\n本小节首先讲解第三点，即#term(\"content block\")语法。\n\n#term(\"content block\")的内容使用中括号包裹，如下所示：\n\n#code(```typ\n#[一段文本]#[两段文本] #[三段文本]\n```)\n\n#term(\"content block\")不会影响包裹的内容——Typst仅仅是解析内部代码作为#term(\"content block\")的内容。#term(\"content block\")也*几乎不影响内容的书写*。\n\n#term(\"content block\")的唯一作用是“界定内容”。它收集一个或多个#term(\"content\")，以待后续使用。有了#term(\"content block\")，你可以*准确指定*一段内容，并用#term(\"scripting\")加工。\n\n#import \"../figures.typ\": figure-content-decoration\n#align(center + horizon, figure-content-decoration())\n\n#v(1em)\n\n所谓#term(\"scripting\")，就是对原始内容增删查改，进而形成文档的过程描述。因为有了#term(\"scripting\")，Typst才能有远超Markdown的排版能力，在许多情况下不逊于LaTeX排版，将来有望全面超越LaTeX排版。\n\n在接下来两小节你将看到Typst作为一门*编程语言*的核心设计，也是进行更高级排版必须要掌握的知识点。由于我们的目标首先仅是*编写一篇基本文档*，我们将会尽可能减少引入更多知识点，仅仅介绍其中最简单常用的语法。\n\n== 解释模式 <grammar-enter-script>\n\n```typ #strong[]```语法第一点提及：`#`使解释器进入#term(\"code mode\")。\n\n#code(```typ\n#[一段文本]\n```)\n\n这个#mark(\"#\")不属于内容块的语法一部分，而是模式转换的标志。\n\n这涉及到Typst的编译原理。Typst是一个动态解释器，其按顺序查看并#term(\"interpret\")你的文档源码。而#mark(\"#\")就是告诉这个当前处于「标记模式」的解释器，接下来请转为「脚本模式」。\n\n实际上，相比python等语言，Typst的确有些特殊。它的解释器可以在不同#term(\"interpreting mode\")下运行。在不同模式下，解释器以不同的语法规则解释你的文档。这便是借鉴了LaTeX的文本和数学模式。Typst一共有三种#term(\"interpreting mode\")：标记模式的语法更适合你组织文本，代码模式更适合你书写脚本，而数学模式则最适合输入复杂的公式。\n\n// todo 三种解释模式的visualization\n\n=== 标记模式\n\n当解释器解释一个文件时，其默认就处于#term(\"markup mode\")，在这个模式下，你可以使用各种记号创建标题、列表、段落......在这个模式下，Typst语法几乎就和Markdown一样。这些标记，我们大多在之前的章节已经学过。\n\n=== 脚本模式\n\n在「脚本模式」下，你可以转而*主要*计算各种内容。例如，你可以计算一个算式的「内容」：\n\n#code(```typ\n#(1024*1024*8*7*17+1)是一个常见素数。\n```)\n\n当处于「脚本模式」时，解释器在*适当*的时候从「脚本模式」退回为「标记模式」。如下所示，在「脚本模式」下解析到数字```typc 2```后解释器回到了「标记模式」：\n\n#code(```typ\n#2是一个常见素数。\n```)\n\nTypst总是倾向于更快地退出脚本模式。\n\n#pro-tip[\n  具体来说，你几乎可以认为解释器至多只会解释一个*完整的表达式*，之后就会*立即*退出「脚本模式」。\n]\n\n=== 以另一个视角看待内容块\n\n你可以在「脚本模式」下使用「内容块」语法。这意味着，你还可以通过「内容块」临时又返回「标记模式」，以嵌套复杂的逻辑：\n\n#code(```typ\n#([== 脚本模式下创建一个标题] + strong[后接一段文本])\n```)\n\n如此反复，Typst就同时具备了方便文档创作与脚本编写的能力。\n\n#pro-tip[\n  能否直接像使用「星号」那样，让#term(\"markup mode\")直接将中括号包裹的一段作为内容块的内容？\n\n  这是可以的，但是存在一些问题。例如，人们也常常在正文中使用中括号等标记：\n\n  #code(```typ\n  遇事不决睡大觉 (¦3[▓▓]\n  ```)\n\n  如此，「标记模式」下默认将中括号解析为普通文本看起来更为合理。\n]\n\n== 数学模式\n\nTypst解释器一共有三种模式，其中两种我们之前已经介绍。这剩下的最后一种被称为#term(\"math mode\")。很多人认为Typst针对LaTeX的核心竞争点之一就是优美的#term(\"math mode\")。\n\nTypst的数学模式如下：<grammar-inline-math> ~ <grammar-display-math>\n\n#code(````typ\n行内数学公式：$sum_x$\n\n行间数学公式：$ sum_x $\n````)\n\n由于使用#term(\"math mode\")有很多值得注意的地方，且#term(\"math mode\")是一个较为独立的模式，本书将其单列为一章参考，可选阅读。有需要在文档中插入数学公式的同学请移步#(refs.ref-math-mode)[《参考：数学模式》]。\n\n== 函数和函数调用 <grammar-func-call>\n\n这里仅作最基础的介绍。#(refs.scripting-base)[《常量与变量》]和#(refs.scripting-complex)[《块与表达式》]中有对函数和函数调用更详细的介绍。\n\n函数与函数调用同样归属#term(\"code mode\")，所以在调用函数前，你需要先使用#mark(\"#\")让Typst先进入#term(\"code mode\")。\n\n与大部分语言相同的是，在调用Typst函数时，你可以向其传递以逗号分隔的#term(\"value\")，这些#term(\"value\")被称为函数的参数。\n\n#code(```typ\n四的三次方为#calc.pow(4, 3)。\n```)\n\n这里#typst-func(\"calc.pow\")是编译器内置的幂计算函数，其接受两个参数：\n+ 一为```typc 4```，为幂的底\n+ 一为```typc 3```，为幂的指数。\n\n你可以使用函数修饰#term(\"content block\")。例如，你可以使用着重函数#typst-func(\"strong\") 标记一整段内容：\n\n#code(```typ\n#strong([\n  And every _fair from fair_ sometime declines,\n])\n```)\n\n首先，中括号包裹的是一段内容。在之前已经学到，这是一个#term(\"content block\")。然后#term(\"content block\")在参数列表中，说明它是#typst-func(\"strong\")的参数。#typst-func(\"strong\")与幂函数调用没有什么语法上的区别，无非是接受了一个#term(\"content block\")作为参数。\n\n类似地，#typst-func(\"emph\")可以标记一整段内容为强调语义：\n\n#code(```typ\n#emph([\n  And every *fair from fair* sometime declines,\n\n  ......\n])\n```)\n\n你应该也注意到了#typst-func(\"strong\")事实上就是《初识标记模式》中讲过的着重标记，#typst-func(\"emph\")就是强调标记。\n\nTypst强调#term(\"consistency\")，因此无论是使用标记还是使用函数修饰内容，最终效果都是一样的。你可以根据实际情况选择合适的方法修饰内容。\n\n== 内容参数的糖 <grammar-content-param>\n\n在许多的语言中，所有函数参数必须包裹在函数调用参数列表的「圆括号」之内。\n\n#code(```typ\n着重语义：这里有一个#strong([重点！])\n```)\n\n但在Typst中，如果将内容块作为参数，内容块可以紧贴在参数列表的「圆括号」之后。\n\n#code(```typ\n着重语义：这里有一个#strong()[重点！]\n```)\n\n特别地，如果参数列表为空，Typst允许省略多余的参数列表。\n\n#code(```typ\n着重语义：这里有一个#strong[重点！]\n```)\n\n所以，示例也可以写为：\n\n#code(```typ\n#strong[\n  And every _fair from fair_ sometime declines,\n]\n\n#emph[\n  And every *fair from fair* sometime declines,\n]\n```)\n\n#pro-tip[\n  函数调用可以后接不止一个内容参数。例如下面的例子后接了两个内容参数：\n\n  #code(```typ\n  #let exercise(question, answer) = strong(question) + parbreak() + answer\n\n  #exercise[\n    Question: _turing complete_？\n  ][\n    Answer: Yes, Typst is.\n  ]\n  ```)\n]\n\n== 文字修饰\n\n现在你可以使用更多的文本函数来丰富你的文档效果。\n\n=== 背景高亮 <grammar-highlight>\n\n你可以使用#typst-func(`highlight`)高亮一段内容：\n\n#code(```typ\n#highlight[高亮一段内容]\n```)\n\n你可以传入`fill`参数以改变高亮颜色。\n\n#code(```typ\n#highlight(fill: orange)[高亮一段内容]\n//         ^^^^^^^^^^^^ 具名传参\n```)\n\n这种传参方式被称为#(refs.scripting-base)[「具名传参」。]冒号的左侧写`fill`，代表是令`fill`参数为冒号右边的`orange`（橘色）。\n\n=== 修饰线\n\n你可以分别使用#typst-func(\"underline\")、#typst-func(\"overline\")、或#typst-func(\"strike\")为一段内容添加下划线<grammar-underline>、上划线<grammar-overline>或中划线（删除线）<grammar-strike>：\n\n#{\n  set text(font: \"Source Han Serif SC\")\n  code(```typ\n  平地翻滚：#underline[ጿኈቼዽጿኈቼዽ] \\\n  施展轻功：#overline[ጿኈቼዽጿኈቼዽ] \\\n  泥地打滚：#strike[ጿኈቼዽጿኈቼዽ] \\\n  ```)\n}\n\n值得注意地是，线段的高度受到被修饰文本的字体影响。\n\n#code(```typ\n#set text(font: (\"Linux Libertine\", \"Source Han Serif SC\"))\n下划线效果：#underline[空格 字体不一致] \\\n#set text(font: \"Source Han Serif SC\")\n下划线效果：#underline[空格 字体一致] \\\n```)\n\n这是因为#typst-func(\"underline\")的`offset`参数默认与字体有关。这个参数决定了下划线相对于「基线」的偏移量。令其默认与字体有关是合理的，但是在一行文本混合多个字体的情况下表现不佳。\n\n#code(\n  ```typ\n  #set text(font: (\"Linux Libertine\", \"Source Han Serif SC\"))\n  #underline(offset: 1.5pt)[空格 字体的下划线offset均为1.5pt]\n  ```,\n  code-as: ```typ\n  #underline(offset: 1.5pt)[空格 字体的下划线offset均为1.5pt]\n  ```,\n)\n\n你也可以通过`offset`参数制作双下划线：\n\n#code(```typ\n#underline(offset: 1.5pt, underline(offset: 3pt, [双下划线]))\n```)\n\n如果你更喜欢连贯的下划线，你可以设置`evade`参数，以解除驱逐效果。<grammar-underline-evade>\n\n#code(```typ\n带驱逐效果：#underline[Language] \\\n不带驱逐效果：#underline(evade: false)[Language]\n```)\n\n=== 上下标\n\n你可以分别使用#typst-func(\"sub\")<grammar-subscript>或#typst-func(\"super\")<grammar-superscript>将一段文本调整至下标位置或上标位置：\n\n#code(```typ\n下标：威严满满#sub[抱头蹲防] \\\n上标：香風とうふ店#super[TM] \\\n```)\n\n你可以为上下标设置特定的字体大小：\n\n#code(```typ\n上标：香風とうふ店#super(size: 0.8em)[™] \\\n```)\n\n你可以为上下标设置相对基线的合适高度：\n\n#code(```typ\n上标：香風とうふ店#super(size: 1em, baseline: -0.1em)[™] \\\n```)\n\n== 文字属性\n\n文本本身也可以设置一些「具名参数」。与#typst-func(\"strong\")和#typst-func(\"emph\")类似，文本也有一个对应的元素函数#typst-func(\"text\")。#typst-func(\"text\")接受任意内容，返回一个影响内部文本的结果。\n\n当输入是单个文本时很好理解，返回的就是一个文本元素：\n\n#code(````typ\n#text(\"一段内容\")\n````)\n\n当输入是一段内容时，返回的是该内容本身，但是对于内容的中的每一个文本元素，都作相应文本属性的修改。下例修改了「代码片段」元素中的文本元素为红色：\n\n#code(````typ\n#text(fill: red)[```\n影响块元素的内容\n```]\n````)\n\n进一步，我们强调，其实际修改了*缺省*的文本属性。对比以下两个情形：\n\n#code(````typ\n#text[```typ #strong[一段内容] #emph[一段内容]```] \\\n#text(fill: red)[```typ #strong[一段内容] #emph[一段内容]```] \\\n````)\n\n可以看见“红色”的设置仅对代码片段中的“默认颜色”的文本生效。对于那些已经被语法高亮的文本，“红色”的设置不再生效。\n\n这说明了为什么下列情形输出了蓝色的文本：\n\n#code(````typ\n#text(fill: red, text(fill: blue, \"一段内容\"))\n````)\n\n=== 设置大小 <grammar-text-size>\n\n通过`size`参数，可以设置文本大小。\n\n#code(```typ\n#text(size: 12pt)[一斤鸭梨]\n#text(size: 24pt)[四斤鸭梨]\n```)\n\n其中`pt`是点单位。中文排版中常见的#link(\"https://ccjktype.fonts.adobe.com/2009/04/post_1.html\")[号单位]与点单位有直接换算关系：\n\n#let owo = (\n  [初号],\n  [小初],\n  [一号],\n  [小一],\n  [二号],\n  [小二],\n  [三号],\n  [小三],\n  [四号],\n  [小四],\n  [五号],\n  [小五],\n  [六号],\n  [小六],\n  [七号],\n  [八号],\n)\n#let owo2 = ([42], [36], [26], [24], [22], [18], [16], [15], [14], [12], [10.5], [9], [7.5], [6.5], [5.5], [5])\n#let owo3 = ([42], [–], [27.5], [-], [21], [–], [16], [–], [13.75], [–], [10.5], [–], [8], [–], [5.25], [4])\n#{\n  set align(center)\n  table(\n    columns: 9,\n    [字号],\n    ..owo.slice(0, 8),\n    [中国（单位：点）],\n    ..owo2.slice(0, 8),\n    [日本（单位：点）],\n    ..owo3.slice(0, 8),\n    [字号],\n    ..owo.slice(8),\n    [中国（单位：点）],\n    ..owo2.slice(8),\n    [日本（单位：点）],\n    ..owo3.slice(8),\n  )\n}\n\n另一个常见单位是`em`：\n\n#code(```typ\n#text(size: 1em)[一斤鸭梨]\n#text(size: 2em)[四斤鸭梨]\n```)\n\n```typc 2em```即是两倍于当前文字大小的长度。\n\n关于Typst中长度单位的详细介绍，可以挪步#(refs.ref-length)[《参考：长度单位》]。\n\n#pro-tip[\n  如果记不住或嫌麻烦，#link(\"https://typst.app/universe/package/pointless-size\")[pointless-size]包可帮助转换字号单位。\n\n  #code(```typ\n  #import \"@preview/pointless-size:0.1.0\": zh\n\n  #zh(1) // 一号（26pt）\n  ```)\n]\n\n=== 设置颜色 <grammar-text-fill>\n\n你可以通过`fill`参数为文字配置各种颜色：\n\n#code(```typ\n#text(fill: red)[红色鸭梨]\n#text(fill: blue)[蓝色鸭梨]\n```)\n\n你还可以通过颜色函数创建自定义颜色：\n\n#code(```typ\n#text(fill: rgb(\"ef475d\"))[茉莉红色鸭梨]\n#text(fill: color.hsl(200deg, 100%, 70%))[天依蓝色鸭梨]\n```)\n\n关于Typst中色彩系统的详细介绍，详见#(refs.ref-color)[《参考：颜色、渐变填充与模式填充》]。\n\n=== 设置字体 <grammar-text-font>\n\n你可以通过`font`参数为文字配置字体：\ntodo: CI上没有这些字体。\n\n#code(```typ\n#text(font: \"FangSong\")[北京鸭梨]\n#text(font: \"Microsoft YaHei\")[板正鸭梨]\n```)\n\n你还可以用逗号分隔的「列表」为文本同时设置多个字体。Typst优先使用列表中靠前字体。例如可以同时设置西文为Times New Roman字体，中文为仿宋字体：\n\n#code(```typ\n#text(font: (\"Times New Roman\", \"FangSong\"))[中西Pear]\n```)\n\n关于如何配置中文、西文、数学等多种字体，详见#(refs.misc-font-setting)[《字体设置》]。\n\n== 「`set`」语法\n\nTypst的元素可以与其构造函数一一对应。Typst允许你设置元素的（函数）参数，就像是设置元素的样式。这个特性由「`set`」语法实现。\n\n例如，你可以这样设置文本字体：\n\n#code(```typ\n#set text(fill: red)\n红色鸭梨\n```)\n\n`set`关键字后跟随一个函数调用语法，表示此后所有的元素都具有指定的样式。这比```typ #text(fill: red)[红色鸭梨]```要更易读。\n\n默认情况下文本元素的`fill`参数为黑色，即文本默认为黑色。经过`set`规则，其往后的文本都默认为红色。\n\n#code(```typ\n黑色鸭梨\n#set text(fill: red)\n红色鸭梨\n```)\n\n你仍然可以在创建元素的时候指定样式：\n\n#code(```typ\n#set text(fill: red)\n#text(fill: blue)[蓝色鸭梨]\n```)\n\n#pro-tip[\n  上面例子中，Typst引擎并没有立即实例化文本，而是将蓝色文本添加到「样式链」上，并继续评估内容块的内容。\n\n  ```typ\n  #set text(fill: red)\n  #{ set text(fill: blue); {// text函数调用相当于set样式。\n    \"蓝色鸭梨\" // 直到此处才获取样式并创建文本元素。\n  }}\n  ```\n\n  从这个观点，你很容易就知道鸭梨文本是蓝色的。\n]\n\n本节前面讲述的所有元素的「具名参数」都可以如是设置，例如文本大小、字体等。\n\n关于对「`set`」语法更详细的介绍，详见#(refs.content-scope-style)[《内容、作用域与样式》]。\n\n== 图像 <grammar-image>\n\n图像对应元素函数#typst-func(\"image\")。\n\n你可以通过#(refs.scripting-modules)[绝对路径或相对路径]加载一个图片文件：\n\n#{\n  show image: set align(center)\n  set image(width: 40%)\n  code(```typ\n  #image(\"/assets/files/香風とうふ店.jpg\")\n  ```)\n}\n\n#typst-func(\"image\")有一个很有用的`width`参数，用于限制图片的宽度：\n\n#{\n  show image: set align(center)\n  code(```typ\n  #image(\"/assets/files/香風とうふ店.jpg\", width: 100pt)\n  ```)\n}\n\n你还可以相对于父元素设置宽度，例如设置为父元素宽度的`50%`：\n\n#{\n  show image: set align(center)\n  code(```typ\n  #image(\"/assets/files/香風とうふ店.jpg\", width: 50%)\n  ```)\n}\n\n同理，你也可以用`height`参数限制图片的高度。\n\n#{\n  show image: set align(center)\n  code(```typ\n  #image(\"/assets/files/香風とうふ店.jpg\", height: 100pt)\n  ```)\n}\n\n当同时设置了图片的宽度和高度时，图片默认会被裁剪：\n\n#{\n  show image: set align(center)\n  code(```typ\n  #image(\"/assets/files/香風とうふ店.jpg\", width: 100pt, height: 100pt)\n  ```)\n}\n\n如果想要拉伸图片而非裁剪图片，可以同时使用`fit`参数：<grammar-image-stretch>\n\n#{\n  show image: set align(center)\n  code(```typ\n  #image(\"/assets/files/香風とうふ店.jpg\", width: 100pt, height: 100pt, fit: \"stretch\")\n  ```)\n}\n\n“stretch”在英文中是拉伸的意思。\n\n== 图形 <grammar-figure>\n\n你可以通过#typst-func(\"figure\")函数为图像设置标题：\n\n#{\n  show image: set align(center)\n  set image(width: 40%)\n  code(```typ\n  #figure(image(\"/assets/files/香風とうふ店.jpg\"), caption: [上世纪90年代，香風とうふ店送外卖的宝贵影像])\n  ```)\n}\n\n#typst-func(\"figure\")不仅仅可以接受#typst-func(\"image\")作为内容，而是可以接受任意内容：\n\n#{\n  show raw: set align(left)\n  code(````typ\n  #figure(```typ\n  #image(\"/assets/files/香風とうふ店.jpg\")\n  ```, caption: [用于加载香風とうふ店送外卖的宝贵影像的代码])\n  ````)\n}\n\n// == 标签与引用\n\n// #code(```typ\n// #set heading(numbering: \"1.\")\n// == 一个神秘标题 <myst>\n\n// @myst 讲述了一个神秘标题。\n// ```)\n\n== 行内盒子 <grammar-box>\n\ntodo：本节添加box的基础使用。<grammar-image-inline>\n\n#code(```typ\n在一段话中插入一个#box(baseline: 0.15em, image(\"/assets/files/info-icon.svg\", width: 1em))图片。\n```)\n\n== 链接 <grammar-link>\n\n链接可以分为外链与内链。最简单情况下，你只需要使用#typst-func(\"link\")函数即可创建一个链接：<grammar-http-link>\n\n#code(```typ\n#link(\"https://zh.wikipedia.org\")\n```)\n\n特别地，Typst会自动识别文中的HTTPS和HTTP链接文本并创建链接：\n\n#code(```typ\nhttps://zh.wikipedia.org\n```)\n\n无论是内链还是外链，你都可以额外传入一段*任意*内容作为链接标题：\n\n#code(```typ\n不基于比较方法，#link(\"https://zh.wikipedia.org/zh-hans/%E5%9F%BA%E6%95%B0%E6%8E%92%E5%BA%8F\")[排序]可以做到 $op(upright(O)) (n)$ 时间复杂度。\n```)\n\n请回忆，这其实等价于调用函数：\n\n#code(```typ\n#link(\"...\")[链接] 等价于 #link(\"...\", [链接])\n```)\n\n=== 内部链接 <grammar-internal-link>\n\n你可以通过创建标签，标记*任意*内容：\n\n#code(```typ\n== 一个神秘标题 <myst>\n```)\n\n上例中`myst`是该标签的名字。每个标签都会附加到恰在其之前的内容，这里内容即为该标题。\n\n#pro-tip[\n  在脚本模式中，标签无法附加到之前的内容。\n\n  #code(```typ\n  #show <awa>: set text(fill: red)\n  #{[a]; [<awa>]}\n  #[b] <awa>\n  ```)\n\n  对比上例，具体来说，标签附加到它的#term(\"syntactic predecessor\")。\n\n  这不是问题，但是易用性有可能在将来得到改善。\n]\n\n你可以通过#typst-func(\"link\")函数在文档中的任意位置链接到该内容：\n\n#code(```typ\n== 一个神秘标题 <mystery>\n\n讲述了#link(<mystery>)[一个神秘标题]。\n```)\n\n== 表格基础 <grammar-table>\n\n你可以通过#typst-func(\"table\")函数创建表格。#typst-func(\"table\")接受一系列内容，并根据参数将内容组装成一个表格。如下，通过`columns`参数设置表格为2列，Typst自动为你生成了一个2行2列的表格：\n\n#code(```typ\n#table(columns: 2, [111], [2], [3])\n```)\n\n你可以为表格设定对齐：<grammar-table-align>\n\n#code(```typ\n#table(columns: 2, align: center, [111], [2], [3])\n```)\n\n其他可选的对齐有`left`、`right`、`bottom`、`top`、`horizon`等，详见#(refs.ref-layout)[《参考：布局函数》]。\n\n== 使用其他人的模板\n\n虽然这是一篇教你写基础文档的教程，但是为什么不更进一步？有赖于Typst将样式与内容分离，如果你能找到一个朋友愿意为你分享两行神秘代码，当你粘贴到文档开头时，你的文档将会变得更为美观：\n\n#code(````typ\n#import \"latex-look.typ\": latex-look\n#show: latex-look\n\n= 这是一篇与LaTeX样式更接近的文档\n\nHey there!\n\nHere are two paragraphs. The\noutput is shown to the right.\n\nLet's get started writing this\narticle by putting insightful\nparagraphs right here!\n+ following best practices\n+ being aware of current results\n  of other researchers\n+ checking the data for biases\n\n$\n  f(x) = integral _(-oo)^oo hat(f)(xi)e^(2 pi i xi x) dif xi\n$\n````)\n\n一般来说，使用他人的模板需要做两件事：\n+ 将`latex-look.typ`放在你的文档文件夹中。\n+ 使用以下两行代码应用模板样式：\n\n  ```typ\n  #import \"latex-look.typ\": latex-look\n  #show: latex-look\n  ```\n\n== 总结\n\n基于《编写一篇基本文档》掌握的知识你应该可以：\n+ 像使用Markdown那样，编写一篇基本不设置样式的文档。\n+ 查看#(refs.ref-math-mode)[《参考：数学模式》]和#(refs.ref-math-symbols)[《参考：常用数学符号》]，以助你编写简单的数学公式。\n+ 查看#(refs.ref-datetime)[《参考：时间类型》]，以在文档中使用时间。\n\n// todo: 术语-翻译表\n// todo: 本文使用的符号-标记对照表\n\n== 习题\n\n#let q1 = ````typ\n#underline(offset: -0.4em, evade: false)[\n  吾輩は猫である。\n]\n````\n\n#exercise[\n  用#typst-func(\"underline\")实现“删除线”效果，其中删除线距离baseline距离为`40%`：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q1 = ````typ\n#text(fill: rgb(\"00000001\"))[I'm the flag]\n````\n\n#exercise[\n  攻击者有可能读取你文件系统中的内容，并将其隐藏存储在你的PDF中。请尝试将用户密码“I'm the flag”以文本形式存放在PDF中，但不可见：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q1 = ````typ\n走#text(size: 1.5em)[走#text(size: 1.5em)[走#text(size: 1.5em)[走]]]\n````\n\n#exercise[\n  请仅用`em`实现以下效果，其中后一个字是前一个字大小的1.5倍：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q1 = ````typ\n走#text(size: 1.5em)[走#text(size: 1.5em)[走]]\n走#text(size: 1.5em)[走#text(size: 1.5em)[走]]\n````\n\n#exercise[\n  请仅用`em`实现以下效果，其中后一个字是前一个字大小的1.5倍：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n\n#let q1 = ````typ\n#set text(size: 2.25em);走#set text(size: 0.666666666em);走#set text(size: 0.666666666em);走\n````\n\n#exercise[\n  请仅用`em`实现以下效果，其中前一个字是后一个字大小的1.5倍。要求代码中不允许出现中括号也不允许出现双引号：#rect(width: 100%, eval(q1.text, mode: \"markup\"))\n][\n  #q1\n]\n"
  },
  {
    "path": "src/tutorial/writing.typ",
    "content": "#import \"mod.typ\": *\n\n#show: book.page.with(title: \"编写一篇进阶文档\")\n\n// document\n// list again\n// figure again\n// footnote\n// quote\n// smallcaps\n// upper\n// lower\n// heading\n// - outlined\n// - bookmarked\n// outline\n// par\n// parbreak\n// table again\n// lorem\n// color again\n// read/query/metadata\n// # sys.version\n"
  },
  {
    "path": "typ/book/lib.typ",
    "content": "\n// #import \"@preview/typst-ts-variables:0.1.0\": page-width, target\n\n#import \"variables.typ\": page-width, target\n\n// export typst.ts variables again, don't import typst-ts-variables directly\n#let get-page-width() = page-width\n#let target = target\n#let is-web-target() = target.starts-with(\"web\")\n#let is-pdf-target() = target.starts-with(\"pdf\")\n\n#let _labeled-meta(label) = context {\n  let res = query(label)\n  if res.len() <= 0 {\n    none\n  } else if res.len() == 1 {\n    res.at(0).value\n  } else {\n    res.map(it => it.value)\n  }\n}\n\n#let book-meta-state = state(\"book-meta\", none)\n\n/// helper function to get (and print/use) the final book metadata\n#let get-book-meta() = _labeled-meta(<typst-book-book-meta>)\n\n/// helper function to get (and print/use) the final build metadata\n#let get-build-meta() = _labeled-meta(<typst-book-build-meta>)\n\n/// Book metadata in summary.typ\n///\n/// title: The title of the book\n/// authors: The author(s) of the book\n/// description: A description for the book, which is added as meta information in the\n///   html <head> of each page\n/// repository: The github repository for the book\n/// repository-edit: The github repository editing template for the book\n///   example: `https://github.com/Me/Book/edit/main/path/to/book/{path}`\n/// language: The main language of the book, which is used as a language attribute\n///   <html lang=\"en\"> for example.\n/// summary: Content summary of the book\n#let book-meta(\n  title: \"\",\n  description: \"\",\n  repository: \"\",\n  repository-edit: \"\",\n  authors: (),\n  language: \"\",\n  summary: none,\n) = [\n  #metadata((\n    kind: \"book\",\n    title: title,\n    description: description,\n    repository: repository,\n    repository_edit: repository-edit,\n    authors: authors,\n    language: language,\n    summary: summary,\n  )) <typst-book-raw-book-meta>\n]\n\n/// Build metadata in summary.typ\n/// dest-dir: The directory to put the rendered book in. By default this is book/ in\n/// the book's root directory. This can overridden with the --dest-dir CLI\n/// option.\n#let build-meta(dest-dir: \"\") = [\n  #metadata((\"dest-dir\": dest-dir)) <typst-book-build-meta>\n]\n\n#let link2page = state(\"typst-book-link2page\", (:))\n\n#let encode-url-component(s) = {\n  let prev = false\n  for (idx, c) in s.codepoints().enumerate() {\n    if c.starts-with(regex(\"[a-zA-Z]\")) {\n      if prev {\n        prev = false\n        \"-\"\n      }\n      c\n    } else {\n      prev = true\n      if idx != 0 {\n        \"-\"\n      }\n      str(c.to-unicode())\n    }\n  }\n}\n\n#let cross-link-path-label(path) = {\n  assert(path.starts-with(\"/\"), message: \"absolute positioning required\")\n  encode-url-component(path)\n}\n\n/// Cross link support\n#let cross-link(path, reference: none, content) = {\n  let path-lbl = cross-link-path-label(path)\n  if reference != none {\n    assert(type(reference) == label, message: \"invalid reference\")\n  }\n\n  assert(content != none, message: \"invalid label content\")\n  context {\n    if reference != none {\n      let result = query(reference)\n      // whether it is internal link\n      if result.len() > 0 {\n        link(reference, content)\n        return\n      }\n    }\n\n    let link-result = link2page.final()\n    if path-lbl in link-result {\n      link((page: link-result.at(path-lbl), x: 0pt, y: 0pt), content)\n      return\n    }\n    // assert(read(path) != none, message: \"no such file\")\n\n    let lnk = {\n      \"cross-link://jump?path-label=\"\n      path-lbl\n      if reference != none {\n        \"&label=\"\n        encode-url-component(str(reference))\n      }\n    }\n    link(lnk, content)\n  }\n}\n\n// Collect text content of element recursively into a single string\n// https://discord.com/channels/1054443721975922748/1088371919725793360/1138586827708702810\n#let plain-text(it) = {\n  if type(it) == str {\n    it\n  } else if it == [ ] {\n    \" \"\n  } else if it.has(\"children\") {\n    it.children.map(plain-text).filter(t => type(t) == str).join()\n  } else if it.has(\"body\") {\n    plain-text(it.body)\n  } else if it.has(\"text\") {\n    it.text\n  } else if it.func() == smartquote {\n    if it.double {\n      \"\\\"\"\n    } else {\n      \"'\"\n    }\n  } else {\n    none\n  }\n}\n\n#let _store-content(ct) = if type(ct) == \"string\" {\n  (kind: \"plain-text\", content: ct)\n} else if ct.func() == text {\n  (kind: \"plain-text\", content: ct.text)\n} else {\n  // Unreliable since v0.8.0\n  // ( kind: \"raw\", content: ct )\n  (kind: \"plain-text\", content: plain-text(ct))\n}\n\n/// Represents a chapter in the book\n/// link: path relative (from summary.typ) to the chapter\n/// title: title of the chapter\n/// section: manually specify the section number of the chapter\n///\n/// Example:\n/// ```typ\n/// #chapter(\"chapter1.typ\")[\"Chapter 1\"]\n/// #chapter(\"chapter2.typ\", section: \"1.2\")[\"Chapter 1.2\"]\n/// ```\n#let chapter(link, title, section: auto) = metadata((\n  kind: \"chapter\",\n  link: link,\n  section: section,\n  title: _store-content(title),\n))\n\n/// Represents a prefix/suffix chapter in the book\n/// Example:\n/// ```typ\n/// #prefix-chapter(\"chapter-pre.typ\")[\"Title of Prefix Chapter\"]\n/// #prefix-chapter(\"chapter-pre2.typ\")[\"Title of Prefix Chapter 2\"]\n/// // other chapters\n/// #suffix-chapter(\"chapter-suf.typ\")[\"Title of Suffix Chapter\"]\n/// ```\n#let prefix-chapter(link, title) = chapter(link, title, section: none)\n#let suffix-chapter(link, title) = chapter(link, title, section: none)\n\n/// Represents a divider in the summary sidebar\n#let divider = metadata((kind: \"divider\"))\n\n/// Internal method to convert summary content nodes\n#let _convert-summary(elem) = {\n  // The entry point of the metadata nodes\n  if metadata == elem.func() {\n    // convert any metadata elem to its value\n    let node = elem.value\n\n    // Convert the summary content inside the book elem\n    if node.at(\"kind\") == \"book\" {\n      let summary = node.at(\"summary\")\n      node.insert(\"summary\", _convert-summary(summary))\n    }\n\n    return node\n  }\n\n  // convert a heading element to a part elem\n  if heading == elem.func() {\n    return (kind: \"part\", content: elem, title: _store-content(elem.body))\n  }\n\n  // convert a (possibly nested) list to a part elem\n  if list.item == elem.func() {\n    // convert children first\n    let maybe-children = _convert-summary(elem.body)\n\n    if type(maybe-children) == \"array\" {\n      // if the list-item has children, then process subchapters\n      if maybe-children.len() <= 0 {\n        panic(\"invalid list-item, no maybe-children\")\n      }\n\n      // the first child is the chapter itself\n      let node = maybe-children.at(0)\n\n      // the rest are subchapters\n      let rest = maybe-children.slice(1)\n      node.insert(\"sub\", rest)\n\n      return node\n    } else {\n      // no children, return the list-item itself\n      return maybe-children\n    }\n  }\n\n  // convert a sequence of elements to a list of node\n  if [].func() == elem.func() {\n    return elem.children.map(_convert-summary).filter(it => it != none)\n  }\n\n  // All of rest are invalid\n  none\n}\n\n/// Internal method to number sections\n/// meta: array of summary nodes\n/// base: array of section number\n#let _numbering-sections(meta, base: ()) = {\n  // incremental section counter used in loop\n  let cnt = 1\n  for c in meta {\n    // skip non-chapter nodes or nodes without section number\n    if c.at(\"kind\") != \"chapter\" or c.at(\"section\") == none {\n      (c,)\n      continue\n    }\n\n    // default incremental section\n    let idx = cnt\n    cnt += 1\n    let num = base + (idx,)\n    // c.insert(\"auto-section\", num)\n\n    let user-specified = c.at(\"section\")\n    // c.insert(\"raw-section\", repr(user-specified))\n\n    // update section number if user specified it by str or array\n    if user-specified != none and user-specified != auto {\n      // update number\n      num = if type(user-specified) == str {\n        // e.g. \"1.2.3\" -> (1, 2, 3)\n        user-specified.split(\".\").map(int)\n      } else if type(user-specified) == array {\n        for n in user-specified {\n          assert(\n            type(n) == int,\n            message: \"invalid type of section counter specified \" + repr(user-specified) + \", want number in array\",\n          )\n        }\n\n        // e.g. (1, 2, 3)\n        user-specified\n      } else {\n        panic(\"invalid type of manual section specified \" + repr(user-specified) + \", want str or array\")\n      }\n\n      // update cnt\n      cnt = num.last() + 1\n    }\n\n    // update section number\n    let auto-num = num.map(str).join(\".\")\n    c.at(\"section\") = auto-num\n\n    // update sub chapters\n    if \"sub\" in c {\n      c.sub = _numbering-sections(c.at(\"sub\"), base: num)\n    }\n\n    (c,)\n  }\n}\n\n/// show template for a book file\n/// Example:\n/// ```typ\n/// #show: book\n/// ```\n#let book(content) = {\n  // set page(width: 300pt, margin: (left: 10pt, right: 10pt, rest: 0pt))\n  [#metadata(toml(\"typst.toml\")) <typst-book-internal-package-meta>]\n\n  context {\n    let data = query(<typst-book-raw-book-meta>).at(0)\n    let meta = _convert-summary(data)\n    meta.at(\"summary\") = _numbering-sections(meta.at(\"summary\"))\n\n    book-meta-state.update(meta)\n    [\n      #metadata(meta) <typst-book-book-meta>\n    ]\n  }\n\n  // #let sidebar-gen(node) = {\n  //   node\n  // }\n  // #sidebar-gen(converted)\n  // #get-book-meta()\n  content\n}\n\n#let external-book(spec: none) = {\n  place(\n    hide[\n      #spec\n    ],\n  )\n}\n\n#let visit-summary(x, visit) = {\n  if x.at(\"kind\") == \"chapter\" {\n    let v = none\n\n    let link = x.at(\"link\")\n    if link != none {\n      let chapter-content = visit.at(\"inc\")(link)\n\n      if chapter-content.children.len() > 0 {\n        let t = chapter-content.children.at(0)\n        if t.func() == [].func() and t.children.len() == 0 {\n          chapter-content = chapter-content.children.slice(1).sum()\n        }\n      }\n\n      if \"children\" in chapter-content.fields() and chapter-content.children.len() > 0 {\n        let t = chapter-content.children.at(0)\n        if t.func() == parbreak {\n          chapter-content = chapter-content.children.slice(1).sum()\n        }\n      }\n\n      show: it => {\n        let abs-link = cross-link-path-label(\"/\" + link)\n        context {\n          link2page.update(it => {\n            it.insert(abs-link, here().page())\n            it\n          })\n        }\n\n        it\n      }\n\n      visit.at(\"chapter\")(chapter-content)\n    }\n\n    if \"sub\" in x and x.sub != none {\n      x.sub.map(it => visit-summary(it, visit)).sum()\n    }\n  } else if x.at(\"kind\") == \"part\" {\n    // todo: more than plain text\n    visit.at(\"part\")(x.at(\"title\").at(\"content\"))\n  } else {\n    // repr(x)\n  }\n}\n"
  },
  {
    "path": "typ/book/typst.toml",
    "content": "[package]\nname = \"book\"\nversion = \"0.2.3\"\nentrypoint = \"lib.typ\"\nauthors = [\"Myriad-Dreamin\"]\nlicense = \"Apache-2.0\"\ndescription = \"A simple tool for creating modern online books in pure typst.\"\n"
  },
  {
    "path": "typ/book/variables.typ",
    "content": "\n// It is in default A4 paper size\n#let page-width = 595.28pt\n\n// default target is \"pdf\"\n#let target = \"pdf\"\n"
  },
  {
    "path": "typ/embedded-typst/example.typ",
    "content": "\n#import \"lib.typ\": *\n\n#let doc = svg-doc(```\n#set page(header: [\n  #set text(size: 20pt)\n  The book compiled by embedded typst\n])\n#set text(size: 30pt)\n\n#v(1em)\n= The first section <embedded-typst>\n#lorem(120)\n= The second section\n#lorem(120)\n```)\n\n#let query-result = doc.header.at(0)\n\nThe selected element is:\n#query-result.func#[[#[#query-result.body.text]]]\n\n#grid(columns: (1fr, 1fr), column-gutter: 0.6em, row-gutter: 1em, ..doc.pages.map(data => image(bytes(data))).map(rect))\n"
  },
  {
    "path": "typ/embedded-typst/lib.typ",
    "content": "\n#let separator = \"\\n\\n\\n\\n\\n\\n\\n\\n\"\n\n#let allocate-fonts(data) = (kind: \"font\", hash: none, data: data)\n\n#let create-font-ref(data) = {\n  let data = read(data, encoding: none)\n\n  (kind: \"font\", data: data)\n}\n\n#let default-fonts() = (\n  \"/assets/typst-fonts/LinLibertine_R.ttf\",\n  \"/assets/typst-fonts/LinLibertine_RB.ttf\",\n  \"/assets/typst-fonts/LinLibertine_RBI.ttf\",\n  \"/assets/typst-fonts/LinLibertine_RI.ttf\",\n  \"/assets/typst-fonts/NewCMMath-Book.otf\",\n  \"/assets/typst-fonts/NewCMMath-Regular.otf\",\n  \"/assets/typst-fonts/NewCM10-Regular.otf\",\n  \"/assets/typst-fonts/NewCM10-Bold.otf\",\n  \"/assets/typst-fonts/NewCM10-Italic.otf\",\n  \"/assets/typst-fonts/NewCM10-BoldItalic.otf\",\n  \"/assets/typst-fonts/DejaVuSansMono.ttf\",\n  \"/assets/typst-fonts/DejaVuSansMono-Bold.ttf\",\n  \"/assets/typst-fonts/DejaVuSansMono-Oblique.ttf\",\n  \"/assets/typst-fonts/DejaVuSansMono-BoldOblique.ttf\",\n)\n\n#let default-cjk-fonts() = (\n  \"/assets/fonts/SourceHanSerifSC-Regular.otf\",\n  \"/assets/fonts/SourceHanSerifSC-Bold.otf\",\n)\n\n#let create-world(fonts) = {\n  let base = plugin(\"/assets/artifacts/embedded_typst.wasm\")\n  let with-fonts = fonts.fold(\n    base,\n    (pre, path) => {\n      let data = read(path, encoding: none)\n      plugin.transition(pre.allocate_data, bytes(\"font\"), data)\n    },\n  )\n\n  plugin.transition(with-fonts.resolve_world)\n}\n\n#let _svg-doc(code, fonts) = {\n  let typst-with-fonts = create-world(fonts)\n  let (header, ..pages) = str(typst-with-fonts.svg(code)).split(separator)\n  (header, pages)\n}\n\n#let svg-doc(code, fonts: none) = {\n  let code = bytes(if type(code) == str {\n    code\n  } else {\n    code.text\n  })\n  if fonts == none {\n    fonts = default-fonts()\n  }\n  let (header, pages) = _svg-doc(code, fonts)\n  (header: json(bytes(header)), pages: pages)\n}\n\n"
  },
  {
    "path": "typ/templates/ebook.typ",
    "content": "#import \"@preview/shiroa:0.2.3\": *\n#import \"/typ/templates/page.typ\": project, part-style, dash-color\n#import \"/typ/templates/term.typ\": reset-term-state\n\n#let _page-project = project\n\n/// Show a title page with a full page background\n#let cover(display-title) = {\n  // #set text(fill: black, font: titleFont)\n  // #if logo != none {\n  //   place(top + center, pad(top:1cm, image(logo, width: 3cm)))\n  // }\n  stack(\n    1fr,\n    align(\n      center + horizon,\n      block(\n        width: 100%,\n        fill: dash-color.lighten(70%),\n        height: 6.2cm,\n        pad(x: 2cm, y: 1cm)[\n          // #text(size: 3em, weight: 900, project-meta.display-title)\n          #text(size: 3em, weight: 900, [The Raindrop-Blue Book])\n          #v(1cm, weak: true)\n          // #text(size: 3em, project-meta.at(\"subtitle\", default: none))\n          #text(size: 2em, display-title)\n          #v(1cm, weak: true)\n          #text(size: 1em, weight: \"bold\", \"Myriad-Dreamin等著\")\n        ],\n      ),\n    ),\n    2fr,\n  )\n}\n\n#let p = counter(\"book-part\")\n#let p-num = numbering.with(\"1\")\n#let default-styles = (\n  cover-image: \"./rm175-noon-02.jpg\",\n  cover: cover,\n  part: it => {\n    //set image(width: 100%, height: 100%)\n    page(\n      margin: 0cm,\n      header: none,\n      background: [\n        #move(dy: 3%, scale(x: -130%, y: 130%, rotate(38.2deg, image(\"./rustacean-flat-gesture.svg\", width: 130%))))\n      ],\n      {\n        p.step()\n        stack(\n          1fr,\n          align(\n            right + bottom,\n            block(\n              width: 100%,\n              fill: dash-color.lighten(70%),\n              height: 6.2cm,\n              pad(x: 1cm, y: 1cm)[\n                #set text(size: 32pt)\n                #v(1em)\n                #context {\n                  let loc = here()\n                  heading([Part.#p-num(..p.at(loc))#sym.space] + it)\n                }\n              ],\n            ),\n          ),\n          2fr,\n        )\n      },\n    )\n  },\n  chapter: it => reset-term-state + it,\n)\n\n#let project(title: \"\", display-title: none, authors: (), spec: \"\", content, styles: default-styles) = {\n  let display-title = display-title\n  if display-title == none {\n    display-title = title\n  }\n\n  // inherit styles\n  let styles = default-styles + styles\n\n  // set document metadata early\n  set document(\n    author: authors,\n    title: title,\n  )\n\n  // set web/pdf page properties\n  set page(numbering: \"1\")\n\n  // todo: abstraction\n  {\n    // inherit from page setting\n    show: _page-project.with(title: none, kind: none)\n\n    //set image(width: 100%, height: 100%)\n    set page(\n      margin: 0cm,\n      header: none,\n      background: [\n        #place({\n          set block(spacing: -0.1em)\n          image(\"./circuit-board.svg\", width: 100%)\n          image(\"./circuit-board.svg\", width: 100%)\n        })\n        #move(dy: 3%, scale(x: -130%, y: 130%, rotate(38.2deg, image(\"./rustacean-flat-gesture.svg\", width: 130%))))\n      ],\n    )\n\n    // place book meta\n    external-book(spec: (styles.inc)(spec))\n    (styles.cover)(display-title)\n  }\n\n  context {\n    let loc = here()\n    let project-meta = (title: title, display-title: display-title, book: book-meta-state.final(), styles: styles)\n\n    {\n      // inherit from page setting\n      show: _page-project.with(title: none, kind: none)\n\n      // set web/pdf page properties\n      set page(numbering: none)\n\n      include \"/src/prefaces/license.typ\"\n      include \"/src/prefaces/acknowledgement.typ\"\n\n      let outline-numbering-base = numbering.with(\"1.\")\n      let outline-numbering(a0, ..args) = if a0 > 0 {\n        h(1em * args.pos().len())\n        outline-numbering-base(a0, ..args) + [ ]\n      }\n\n      let outline-counter = counter(\"outline-counter\")\n      show outline.entry: it => {\n        let has-part = if it.body().func() != none and \"children\" in it.body().fields() {\n          for ch in it.body().children {\n            if \"text\" in ch.fields() and ch.text.contains(\"Part\") {\n              ch.text\n            }\n          }\n        }\n\n        let numbering = if has-part == none {\n          outline-counter.step(level: it.level + 1)\n          context outline-counter.display(outline-numbering)\n        } else {\n          outline-counter.step(level: 1)\n        }\n        link(it.element.location(), text(black, it.indented(numbering + it.prefix(), it.inner())))\n      }\n\n      set outline.entry(fill: repeat[.])\n      outline(depth: 1)\n    }\n\n    if project-meta.book != none {\n      project-meta.book.summary.map(it => visit-summary(it, styles)).sum()\n    }\n  }\n\n  content\n}\n"
  },
  {
    "path": "typ/templates/page.typ",
    "content": "\n// This is important for typst-book to produce a responsive layout\n// and multiple targets.\n#import \"@preview/shiroa:0.2.3\": get-page-width, is-pdf-target, is-web-target, plain-text, target, templates\n#import templates: *\n#import \"template-link.typ\": *\n#import \"/typ/templates/side-notes.typ\": side-attrs\n\n// Metadata\n#let page-width = get-page-width()\n#let is-pdf-target = is-pdf-target()\n#let is-web-target = is-web-target()\n\n// Theme (Colors)\n#let (\n  style: theme-style,\n  is-dark: is-dark-theme,\n  is-light: is-light-theme,\n  main-color: main-color,\n  dash-color: dash-color,\n  code-extra-colors: code-extra-colors,\n) = book-theme-from(toml(\"theme-style.toml\"), xml: it => xml(it))\n\n// Sizes\n#let main-size = if is-web-target {\n  16pt\n} else {\n  10.5pt\n}\n\n#let heading-sizes = if is-web-target {\n  (36pt, 26pt, 22pt, 18pt, main-size)\n} else {\n  (26pt, 22pt, 14pt, 12pt, main-size)\n}\n#assert(\n  heading-sizes.at(-1) < heading-sizes.at(-2),\n  message: \"The second smallest heading size should be larger than the paragraph size (main-size).\",\n)\n\n#let list-indent = 0.5em\n#let par-leading = if is-web-target {\n  0.8em\n} else {\n  // typst's default\n  0.65em\n}\n// 1.2, 1.5 * this parameter\n#let block-spacing = if is-web-target {\n  par-leading * 1.5\n} else {\n  0.7em\n}\n#let heading-below = block-spacing * 1.\n#let heading-spacing = heading-below * 1.5\n\n// Fonts\n#let use-fandol-fonts = false\n#let main-font-cn = {\n  if is-web-target {\n    (\"Noto Sans CJK SC\",)\n  } else if use-fandol-fonts {\n    (\"FandolSong\",)\n  }\n  (\"Source Han Serif SC\",)\n}\n\n#let code-font-cn = (\"Noto Sans CJK SC\",)\n\n#let main-font = if use-fandol-fonts {\n  (\"New Computer Modern\", ..main-font-cn)\n} else {\n  (\n    // \"Charter\",\n    // typst-book's embedded font\n    \"Libertinus Serif\",\n    ..main-font-cn,\n  )\n}\n\n#let code-font = (\n  \"BlexMono Nerd Font Mono\",\n  // typst-book's embedded font\n  \"DejaVu Sans Mono\",\n  ..code-font-cn,\n)\n\n// todo: move code theme parser to another lib file\n#let code-theme-file = theme-style.at(\"code-theme\")\n\n#let code-extra-colors = if code-theme-file.len() > 0 {\n  let data = xml(theme-style.at(\"code-theme\")).first()\n\n  let find-child(elem, tag) = {\n    elem.children.find(e => \"tag\" in e and e.tag == tag)\n  }\n\n  let find-kv(elem, key, tag) = {\n    let idx = elem.children.position(e => \"tag\" in e and e.tag == \"key\" and e.children.first() == key)\n    elem.children.slice(idx).find(e => \"tag\" in e and e.tag == tag)\n  }\n\n  let plist-dict = find-child(data, \"dict\")\n  let plist-array = find-child(plist-dict, \"array\")\n  let theme-setting = find-child(plist-array, \"dict\")\n  let theme-setting-items = find-kv(theme-setting, \"settings\", \"dict\")\n  let background-setting = find-kv(theme-setting-items, \"background\", \"string\")\n  let foreground-setting = find-kv(theme-setting-items, \"foreground\", \"string\")\n  (bg: rgb(background-setting.children.first()), fg: rgb(foreground-setting.children.first()))\n} else {\n  (bg: rgb(239, 241, 243), fg: none)\n}\n\n#let make-unique-label(it, disambiguator: 1) = label({\n  let k = plain-text(it).trim()\n  if disambiguator > 1 {\n    k + \"_d\" + str(disambiguator)\n  } else {\n    k\n  }\n})\n\n#let heading-reference(it, d: 1) = make-unique-label(it.body, disambiguator: d)\n\n// The project function defines how your document looks.\n// It takes your content and some metadata and formats it.\n// Go ahead and customize it to your liking!\n#let project(title: \"Typst中文教程\", authors: (), kind: \"page\", body) = {\n  let is-ref-page = kind == \"reference-page\"\n  let is-page = kind == \"page\"\n\n  // set basic document metadata\n  set document(\n    author: authors,\n    title: title,\n  ) if not is-pdf-target\n\n  // set web/pdf page properties\n  set page(\n    numbering: if is-pdf-target {\n      \"1\"\n    },\n  )\n\n  // set web/pdf page properties\n  set page(\n    number-align: center,\n    width: page-width,\n  )\n\n  // remove margins for web target\n  set page(\n    margin: (\n      // reserved beautiful top margin\n      top: 20pt,\n      // reserved for our heading style.\n      // If you apply a different heading style, you may remove it.\n      left: 20pt,\n      // Typst is setting the page's bottom to the baseline of the last line of text. So bad :(.\n      bottom: 0.5em,\n      // remove rest margins.\n      rest: 0pt,\n    ),\n    height: auto,\n  ) if is-web-target\n\n  // set text style\n  set text(\n    font: main-font,\n    size: main-size,\n    fill: main-color,\n    lang: \"zh\",\n    region: \"cn\",\n  )\n  set block(spacing: block-spacing)\n\n  let ld = state(\"label-disambiguator\", (:))\n  let update-ld(k) = ld.update(it => {\n    it.insert(k, it.at(k, default: 0) + 1)\n    it\n  })\n  let get-ld(loc, k) = make-unique-label(k, disambiguator: ld.at(loc).at(k))\n\n  // show regex(\"[A-Za-z]+\"): set text(font: main-font-en)\n  let cjk-markers = regex(\"[“”‘’．，。、？！：；（）｛｝［］〔〕〖〗《》〈〉「」【】『』─—＿·…\\u{30FC}]+\")\n  show cjk-markers: set text(font: main-font-cn)\n  show raw: it => {\n    show cjk-markers: set text(font: code-font-cn)\n    it\n  }\n  // show regex(\"[a-zA-Z\\s\\#\\[\\]]+\"): set text(baseline: -0.05em)\n  // show regex(\"[“”]+\"): set text(font: main-font-cn)\n\n  // Set text, spacing for headings\n  // Render a hash to hint headings instead of bolding it as well if it's for web.\n  show heading: set text(weight: \"regular\") if is-web-target\n  show heading: it => {\n    let it = {\n      set text(size: heading-sizes.at(it.level))\n      if is-web-target {\n        heading-hash(it, hash-color: dash-color)\n      }\n      it\n    }\n\n    block(\n      spacing: heading-spacing,\n      below: heading-below,\n      it,\n    )\n  }\n\n  // link setting\n  show link: set text(fill: dash-color)\n\n  // math setting\n  show math.equation: set text(weight: 400)\n\n  // code block setting\n  show raw: it => {\n    if \"block\" in it.fields() and it.block {\n      rect(\n        width: 100%,\n        inset: (x: 4pt, y: 5pt),\n        radius: 2pt,\n        stroke: none,\n        fill: code-extra-colors.at(\"bg\"),\n        [\n          #set text(font: code-font)\n          #set text(fill: code-extra-colors.at(\"fg\")) if code-extra-colors.at(\"fg\") != none\n          #set par(justify: false)\n          #it\n        ],\n      )\n    } else {\n      set text(\n        font: code-font,\n        baseline: -0.08em,\n      )\n      it\n    }\n  }\n\n  // figure setting\n  set rect(stroke: main-color)\n  set table(stroke: main-color)\n\n  show <typst-raw-func>: it => {\n    let children = it.lines.at(0).body.children\n    let rb = children.position(e => e.at(\"text\", default: none) == \"(\")\n    children.slice(0, rb).join()\n  }\n\n\n  if title != none {\n    if is-web-target {\n      // [= #title]\n    } else {\n      v(0.5em)\n      align(center, [= #title])\n      v(1em)\n    }\n  }\n\n  // Main body.\n  set par(leading: par-leading, justify: true)\n\n  if is-ref-page {\n    let side-space = 4 * main-size\n    let side-overflow = 2 * main-size\n    let gutter = 1 * main-size\n    grid(\n      columns: (side-space, 100% - side-space - gutter),\n      column-gutter: gutter,\n      place(\n        dx: -side-overflow,\n        block(\n          breakable: true,\n          width: side-space + side-overflow,\n          context {\n            let loc = here()\n            side-attrs.update(it => {\n              it.insert(\"left\", loc.position().x)\n              it.insert(\"width\", side-space + side-overflow)\n              it.insert(\"gutter\", gutter)\n              it\n            })\n          },\n        ),\n      ),\n      body,\n    )\n  } else if is-page {\n    side-attrs.update(it => {\n      it.insert(\"left\", 0pt)\n      it.insert(\"width\", 0pt)\n      it.insert(\"gutter\", 0pt)\n      it\n    })\n    body\n  } else {\n    body\n  }\n}\n\n#let part-style = heading\n"
  },
  {
    "path": "typ/templates/side-notes.typ",
    "content": "\n#let side-attrs = state(\"tuturial-side-note-attrs\", (:))\n\n#let side-note(dy: -0.65em, content) = context {\n  let loc = here()\n  let attr = side-attrs.get()\n  // side-notes.update(it => {\n  //   let p = str(loc.page())\n  //   let arr = it.at(p, default: ())\n  //   arr.push((loc.position().y, content))\n  //   it.insert(p, arr)\n  //   it\n  // })\n  box(\n    width: 0pt,\n    box(\n      width: attr.width,\n      place(\n        left,\n        dy: dy,\n        dx: attr.left - loc.position().x,\n        {\n          set text(size: 10.5pt)\n          content\n        },\n      ),\n    ),\n  )\n}\n\n"
  },
  {
    "path": "typ/templates/template-link.typ",
    "content": "\n// #import \"supports-text.typ\": plain-text\n#import \"@preview/shiroa:0.2.3\": plain-text\n\n#let make-unique-label(it, disambiguator: 1) = label({\n  let k = plain-text(it).trim()\n  if disambiguator > 1 {\n    k + \"_d\" + str(disambiguator)\n  } else {\n    k\n  }\n})\n\n#let label-disambiguator = state(\"label-disambiguator\", (:))\n#let update-label-disambiguator(k) = label-disambiguator.update(it => {\n  it.insert(k, it.at(k, default: 0) + 1)\n  it\n})\n#let get-label-disambiguator(loc, k) = make-unique-label(k, disambiguator: label-disambiguator.at(loc).at(k))\n\n#let heading-reference(it, d: 1) = make-unique-label(it.body, disambiguator: d)\n\n#let enable-heading-hash = state(\"enable-heading-hash\", true)\n\n#let heading-hash(it, hash-color: blue) = {\n  let title = plain-text(it.body)\n  if title != none {\n    let title = title.trim()\n    update-label-disambiguator(title)\n    context if enable-heading-hash.get() {\n      let loc = here()\n      let dest = get-label-disambiguator(loc, title)\n      let h = measure(it.body).height\n      place(\n        left,\n        dx: -16pt,\n        [\n          #set text(fill: hash-color)\n          #link(loc)[\\#] #dest\n        ],\n      )\n    }\n  }\n}\n"
  },
  {
    "path": "typ/templates/term.typ",
    "content": "\n#let term-state = state(\"term\", (:))\n#let reset-term-state = term-state.update(it => (:))\n\n#let _term(term-list, term, en: none) = (\n  context {\n    let s = term-state.get()\n    if term in s {\n      [「#term-list.at(term)#(\"」\")]\n    } else {\n      let en-term = term\n      if en != none {\n        en-term = en\n      }\n      [「#term-list.at(term)」（#en-term#(\"）\")]\n    }\n  }\n    + term-state.update(it => {\n      it.insert(term, \"\")\n      it\n    })\n)\n"
  },
  {
    "path": "typ/templates/theme-style.toml",
    "content": "\n[light]\ncolor-scheme = \"light\"\nmain-color = \"#000\"\ndash-color = \"#20609f\"\ncode-theme = \"\"\n\n[rust]\ncolor-scheme = \"light\"\nmain-color = \"#262625\"\ndash-color = \"#2b79a2\"\ncode-theme = \"\"\n\n[coal]\ncolor-scheme = \"dark\"\nmain-color = \"#98a3ad\"\ndash-color = \"#2b79a2\"\ncode-theme = \"/assets/files/tokyo-night.tmTheme\"\n\n[navy]\ncolor-scheme = \"dark\"\nmain-color = \"#bcbdd0\"\ndash-color = \"#2b79a2\"\ncode-theme = \"/assets/files/tokyo-night.tmTheme\"\n\n[ayu]\ncolor-scheme = \"dark\"\nmain-color = \"#c5c5c5\"\ndash-color = \"#0096cf\"\ncode-theme = \"/assets/files/tokyo-night.tmTheme\"\n"
  },
  {
    "path": "typ/typst-meta/docs.typ",
    "content": "\n\n#let iterate-scope(env, scope, belongs) = {\n  let p = (..scope.path, scope.name).join(\".\")\n  scope.insert(\"belongs\", belongs)\n  env.scoped-items.insert(p, scope)\n\n  if \"scope\" in scope {\n    let belongs = (kind: \"scope\", name: scope.name)\n    for c in scope.scope {\n      env = iterate-scope(env, c, belongs)\n    }\n  }\n\n  return env\n}\n\n#let iterate-docs(env, route, path) = {\n  if route.body.kind == \"func\" {\n    env.funcs.insert(route.body.content.name, route)\n  } else if route.body.kind == \"type\" {\n    env.types.insert(route.body.content.name, route)\n\n    // iterate-scope()\n    if \"scope\" in route.body.content {\n      let belongs = (kind: \"type\", name: route.body.content.name)\n      for c in route.body.content.scope {\n        env = iterate-scope(env, c, belongs)\n      }\n    }\n  }\n\n  for ch in route.children {\n    env = iterate-docs(env, ch, path + (route.title,))\n  }\n\n  return env\n}\n\n#let load-docs() = {\n  let m = json(\"/assets/artifacts/typst-docs-v0.11.0.json\")\n  let env = (funcs: (:), types: (:), scoped-items: (:))\n  for route in m {\n    env = iterate-docs(env, route, ())\n  }\n\n  return env\n}\n\n#let typst-v11 = load-docs()\n"
  }
]