Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/preview/progressive-layout/1.0.0/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.DS_Store
*.pdf
21 changes: 21 additions & 0 deletions packages/preview/progressive-layout/1.0.0/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 David Hajage

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
68 changes: 68 additions & 0 deletions packages/preview/progressive-layout/1.0.0/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# progressive-outline

This package provides a Typst function, `progressive-outline`, to generate a dynamic table of contents, similar to what is found in some LaTeX's Beamer presentations.

## Context and Ultimate Goal

This function was developed to meet a need within the Typst community for displaying partial or progressive outlines, especially in presentations. It is inspired by the following discussions:

- [GitHub Issue (touying)](https://github.com/touying-typ/touying/issues/137)
- [Typst Forum](https://forum.typst.app/t/how-to-display-partial-outlines-with-touying/5526)

The ultimate goal would be to integrate this functionality directly into a presentation package like **[`polylux`](https://typst.app/universe/package/polylux/)**, **[`touying`](https://typst.app/universe/package/touying/)**, or **[`presentate`](https://typst.app/universe/package/presentate/)**. This would allow for the creation of "Beamer-like" presentations where the outline unfolds flexibly as you progress through the slides. Feel free to draw inspiration from this code to contribute to these projects!

## Function Usage

To use the function, import the package and call `progressive-outline` where you want the outline to appear.

```typst
#import "@preview/progressive-layout:1.0.0": progressive-outline

#progressive-outline(
h1-style: "all",
h2-style: "current-and-grayed",
h3-style: "current",
show-numbering: true,
)
```

## Example: The `progressive-layout` Template

This package also contains a simple template, `progressive-layout`, which shows an example of how to use the function. Here is a minimal code example to test it:

```typst
// Import the template
#import "@preview/progressive-layout:1.0.0": progressive-layout, progressive-outline

// Apply the template to your document
#show: doc => progressive-layout(doc)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is more idiomatic to write #show doc: progressive-layout, can you please update the example here (and other example files, using .with(...) in case you need to supply other arguments)?


// Your headings...
= Chapter 1
== Section 1.1
= Chapter 2
```

## Examples

This package includes two files to demonstrate its usage:

- [`example.typ`](https://github.com/eusebe/progressive-layout/blob/main/example.typ): This file shows how the `progressive-outline` function works with different options.
- [`demo.typ`](https://github.com/eusebe/progressive-layout/blob/main/demo.typ): This file provides an example of how to use the `progressive-layout` template.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would either:

  • link to the files in this repository (i.e. [\example.typ`](example.typ)`)
  • delete the copy of the files in this repository, and link to a pinned commit or tag in your own repository (by replacing main in the URL with the commit hash or tag name)
    This was, you can be sure that the example files will always be in sync with the version of the package the user is reading the README for. Currently, if you push a breaking API change, the examples won't work anymore for people still reading the old README.


## `progressive-outline` Parameters

| Parameter | Description | Options | Default |
| :--- | :--- | :--- | :--- |
| `h1-style` | Controls the display of level 1 headings. | `"all"`, `"current"`, `"current-and-grayed"`, `"none"` | `"all"` |
| `h2-style` | Controls the display of level 2 headings. | `"all"`, `"current"`, `"current-and-grayed"`, `"none"` | `"all"` |
| `h3-style` | Controls the display of level 3 headings. | `"all"`, `"current"`, `"current-and-grayed"`, `"none"` | `"all"` |
| `scope-h2` | Restricts the display of level 2 headings. | `"all"`, `"current-h1"` | `"current-h1"` |
| `scope-h3` | Restricts the display of level 3 headings. | `"all"`, `"current-h2"` | `"current-h2"` |
| `show-numbering` | Shows or hides the numbering of headings. | `true`, `false` | `true` |

---

## Note on Code Generation

This package was initially generated with the help of a Large Language Model (LLM). It is a demonstration of what I would like to see one day included in a typst package dedicated to creating presentations, but I am unable to integrate such a feature into the packages I mentioned above in the readme. While functional, it may contain errors, inefficiencies, or programming practices that could be improved. Please use it with discretion and feel free to suggest improvements!
42 changes: 42 additions & 0 deletions packages/preview/progressive-layout/1.0.0/demo.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Import the layout function from the local template
#import "@preview/progressive-layout:1.0.0": progressive-layout, progressive-outline
// #import "lib.typ": progressive-layout, progressive-outline

// Apply the layout to the entire document
#show: doc => progressive-layout(doc, show-numbering: true)

= First Section : General Introduction

== Project Context

#lorem(20)

== Objectives

#lorem(20)

= Second Section : Detailed Analysis

== Analysis of User's Need

#lorem(20)

== Proposed Solutions

=== Solution A

#lorem(20)

=== Solution B

#lorem(20)

= Third Section : Conclusion

== Appraisal

#lorem(20)

== Perspectives

#lorem(20)
95 changes: 95 additions & 0 deletions packages/preview/progressive-layout/1.0.0/example.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
#import "@preview/progressive-layout:1.0.0": progressive-outline
// #import "lib.typ": progressive-outline

#progressive-outline(
h1-style: "all",
h2-style: "all",
h3-style: "all",
show-numbering: false
)

= Title 1

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

#progressive-outline(
h1-style: "current-and-grayed",
h2-style: "none",
h3-style: "none",
show-numbering: false
)

== Subtitle 1.1

Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

#progressive-outline(
h1-style: "none",
h2-style: "current-and-grayed",
h3-style: "none",
show-numbering: false
)

=== Sub-subtitle 1.1.1

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

#progressive-outline(
h1-style: "none",
h2-style: "none",
h3-style: "current-and-grayed",
show-numbering: false
)

=== Sub-subtitle 1.1.2

Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.

#progressive-outline(
h1-style: "none",
h2-style: "current",
h3-style: "current-and-grayed",
show-numbering: false
)

== Subtitle 1.2

Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.

= Title 2

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

#progressive-outline(
h1-style: "current-and-grayed",
h2-style: "none",
h3-style: "none",
show-numbering: false
)

== Subtitle 2.1

Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

#progressive-outline(
h1-style: "current",
h2-style: "current-and-grayed",
h3-style: "none",
show-numbering: false
)

== Subtitle 2.2

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.

= Title 3

Lorem ipsum dolor sit amet, consectetur adipiscing elit.

== Subtitle 3.1

Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

== Subtitle 3.2

Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
138 changes: 138 additions & 0 deletions packages/preview/progressive-layout/1.0.0/lib.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
#let progressive-outline(
h1-style: "all",
h2-style: "all",
h3-style: "all",
scope-h2: "current-h1",
scope-h3: "current-h2",
show-numbering: true,
) = {
context {
let loc = here()
let all-headings = query(heading.where(outlined: true))

let is-before(loc1, loc2) = {
if loc1.page() < loc2.page() {
return true
}
if loc1.page() == loc2.page() and loc1.position().y < loc2.position().y {
return true
}
return false
}

let current-h1 = if query(heading.where(outlined: true, level: 1).before(loc)).len() > 0 {
query(heading.where(outlined: true, level: 1).before(loc)).last()
} else { none }

let current-h2 = if query(heading.where(outlined: true, level: 2).before(loc)).len() > 0 {
query(heading.where(outlined: true, level: 2).before(loc)).last()
} else { none }

let current-h3 = if query(heading.where(outlined: true, level: 3).before(loc)).len() > 0 {
query(heading.where(outlined: true, level: 3).before(loc)).last()
} else { none }

let style-heading(heading, style, current) = {
let content = if show-numbering {
numbering("1.1.1", ..counter(heading.func()).at(heading.location())) + " " + heading.body
} else {
heading.body
}
if style == "all" {
return content
} else if style == "current" {
if heading == current {
return content
} else {
return none
}
} else if style == "current-and-grayed" {
if heading == current {
return content
} else {
return text(fill: gray, content)
}
} else if style == "none" {
return none
} else {
return content
}
}

for heading in all-headings {
let styled-heading = if heading.level == 1 {
style-heading(heading, h1-style, current-h1)
} else if heading.level == 2 {
if scope-h2 == "current-h1" and current-h1 != none {
let parent-h1 = all-headings.rev().find(h => h.level == 1 and is-before(h.location(), heading.location()))
if parent-h1 != current-h1 {
continue
}
}
style-heading(heading, h2-style, current-h2)
} else if heading.level == 3 {
if scope-h3 == "current-h2" and current-h2 != none {
let parent-h2 = all-headings.rev().find(h => h.level == 2 and is-before(h.location(), heading.location()))
if parent-h2 != current-h2 {
continue
}
}
style-heading(heading, h3-style, current-h3)
} else {
heading.body
}

if styled-heading != none {
let indent = 2em * (heading.level - 1)
block(pad(left: indent, styled-heading))
}
}
}
}

/// Applies dynamic layout with outlines.
#let progressive-layout(doc, show-numbering: true) = {
// --- Base styles ---
set text(font: "Arial", size: 22pt)
set page(
paper: "presentation-16-9",
margin: (top: 2cm, bottom: 2cm, x: 2cm),
)
set heading(numbering: "1.1.1")

// --- Display logic ---

// At the beginning of the document, display the full table of contents.
outline()

// For each level 1 heading, display the list of level 2 sub-sections.
show heading.where(level: 1): it => {
pagebreak(weak: true) // Each section 1 starts on a new page
// Display the progressive outline for the current H1, without the heading text itself.
set text(size: 22pt) // Homogenize font size for the outline
progressive-outline(h1-style: "current", h2-style: "all", h3-style: "none", show-numbering: show-numbering)
pagebreak(weak: true) // Start the actual content on a new page
}

// For each level 2 heading, display the list of *all* H2s,
// with the current H2 highlighted.
show heading.where(level: 2): it => {
pagebreak(weak: true) // Each H2 starts on a new page
// Display the progressive outline for the current H2, without the heading text itself.
set text(size: 22pt) // Homogenize font size for the outline
progressive-outline(h1-style: "current", h2-style: "current-and-grayed", h3-style: "none", show-numbering: show-numbering)
pagebreak(weak: true) // Start the actual content on a new page
}
// For each level 3 heading, display the list of *all* H3s,
// with the current H3 highlighted.
show heading.where(level: 3): it => {
pagebreak(weak: true) // Each H3 starts on a new page
// Display the progressive outline for the current H3, without the heading text itself.
set text(size: 22pt) // Homogenize font size for the outline
progressive-outline(h1-style: "current", h2-style: "current-and-grayed", h3-style: "current-and-grayed", show-numbering: show-numbering)
pagebreak(weak: true) // Start the actual content on a new page
}

// --- Document content ---
doc
}
12 changes: 12 additions & 0 deletions packages/preview/progressive-layout/1.0.0/typst.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[package]
name = "progressive-layout"
version = "1.0.0"
entrypoint = "lib.typ"
authors = ["David Hajage <[email protected]>"]
description = "A Typst function to display a dynamic table of contents, inspired by LaTeX's Beamer class."
license = "MIT"
keywords = ["outline", "table of contents", "presentation", "dynamic"]
compiler = "0.11.0"
exclude = ["example.typ", "demo.typ"]
repository = "https://github.com/eusebe/progressive-layout"