diff --git a/crates/typst-docs/src/lib.rs b/crates/typst-docs/src/lib.rs index 6d67ffad5..96ae460c8 100644 --- a/crates/typst-docs/src/lib.rs +++ b/crates/typst-docs/src/lib.rs @@ -167,8 +167,10 @@ fn reference_pages(resolver: &dyn Resolver) -> PageModel { /// Build the guides section. fn guides_pages(resolver: &dyn Resolver) -> PageModel { let mut page = markdown_page(resolver, "/docs/", "guides/welcome.md"); - page.children = - vec![markdown_page(resolver, "/docs/guides/", "guides/guide-for-latex-users.md")]; + page.children = vec![ + markdown_page(resolver, "/docs/guides/", "guides/guide-for-latex-users.md"), + markdown_page(resolver, "/docs/guides/", "guides/page-setup.md"), + ]; page } diff --git a/crates/typst-library/src/layout/columns.rs b/crates/typst-library/src/layout/columns.rs index d21384919..6645ba9e3 100644 --- a/crates/typst-library/src/layout/columns.rs +++ b/crates/typst-library/src/layout/columns.rs @@ -9,6 +9,9 @@ use crate::text::TextElem; /// height on the page. The columns function can break across pages if /// necessary. /// +/// If you need to insert columns across your whole document, you can use the +/// [`{page}` function's `columns` parameter]($func/page.columns) instead. +/// /// ## Example { #example } /// ```example /// = Towards Advanced Deep Learning diff --git a/crates/typst-library/src/layout/page.rs b/crates/typst-library/src/layout/page.rs index a4324b0c7..8e73c3b3a 100644 --- a/crates/typst-library/src/layout/page.rs +++ b/crates/typst-library/src/layout/page.rs @@ -12,8 +12,11 @@ use crate::text::TextElem; /// properties, it can also be used to explicitly render its argument onto /// a set of pages of its own. /// -/// Pages can be set to use `{auto}` as their width or height. In this case, -/// the pages will grow to fit their content on the respective axis. +/// Pages can be set to use `{auto}` as their width or height. In this case, the +/// pages will grow to fit their content on the respective axis. +/// +/// The [Guide for Page Setup]($guides/page-setup-guide) explains how to use +/// this and related functions to set up a document with many examples. /// /// ## Example { #example } /// ```example @@ -140,6 +143,9 @@ pub struct PageElem { /// How many columns the page has. /// + /// If you need to insert columns into a page or other container, you can + /// also use the [`columns` function]($func/columns). + /// /// ```example:single /// #set page(columns: 2, height: 4.8cm) /// Climate change is one of the most diff --git a/docs/guides/page-setup.md b/docs/guides/page-setup.md new file mode 100644 index 000000000..8efcb2cdf --- /dev/null +++ b/docs/guides/page-setup.md @@ -0,0 +1,559 @@ +--- +description: | + An in-depth guide to setting page dimensions, margins, and page numbers in + Typst. Learn how to create appealing and clear layouts and get there quickly. +--- + +# Page setup guide +Your page setup is a big part of the first impression your document gives. Line +lengths, margins, and columns influence +[appearance](https://practicaltypography.com/page-margins.html) and +[legibility](https://designregression.com/article/line-length-revisited-following-the-research) +while the right headers and footers will help your reader easily navigate your +document. This guide will help you to customize pages, margins, headers, +footers, and page numbers so that they are the right fit for your content and +you can get started with writing. + +In Typst, each page has a width, a height, and margins on all four sides. The +top and bottom margins may contain a header and footer. The set rule of the +[`{page}`]($func/page) element is where you control all of the page setup. If +you make changes with this set rule, Typst will ensure that there is a new and +conforming empty page afterward, so it may insert a page break. Therefore, it +is best to specify your [`{page}`]($func/page) set rule at the start of your +document or in your template. + +```example +#set rect( + width: 100%, + height: 100%, + inset: 4pt +) +>>> #set text(6pt) + +#set page( + "iso-b7", + header: rect[Header], + footer: rect[Footer], + number-align: top + center, +) + +#rect(fill: rgb("#565565")) +``` + +This example visualizes the dimensions for page content, headers, and footers. +The page content is the page size (ISO B7) minus each side's default margin. In +the top and the bottom margin, there are stroked rectangles visualizing the +header and footer. They do not touch the main content, instead, they are offset +by 30% of the respective margin. You can control this offset by specifying the +[`header-ascent`]($func/page.header-ascent) and +[`footer-descent`]($func/page.footer-descent) arguments. + +Below, the guide will go more into detail on how to accomplish common page setup +requirements with examples. + +## Customize page size and margins { #customize-margins } +Typst's default page size is A4 paper. Depending on your region and your +use case, you will want to change this. You can do this by using the +[`{page}`]($func/page) set rule and passing it a string argument to use a common +page size. Options include the complete ISO 216 series (e.g. `"iso-a4"`, +`"iso-c2"`), customary US formats like `"us-legal"` or `"us-letter"`, and more. +Check out the reference for the [page's paper argument]($func/page.paper) to +learn about all available options. + +```example +#set page("us-letter") + +This page likes freedom. +``` + +If you need to customize your page size to some dimensions, you can specify the +named arguments [`width`]($func/page.width) and [`height`]($func/page.height) +instead. + +```example +#set page(width: 12cm, height: 12cm) + +This page is a square. +``` + +### Change the page's margins { #change-margins } +Margins are a vital ingredient for good typography: [Typographers consider lines +that fit between 45 and 75 characters best length for +legibility](http://webtypography.net/2.1.2) and your margins and +[columns](#columns) help define line widths. By default, Typst will create +margins proportional to the page size of your document. To set custom margins, +you will use the [`margin`]($func/page.margin) argument in the +[`{page}`]($func/page) set rule. + +The `margin` argument will accept a length if you want to set all margins to the +same width. However, you often want to set different margins on each side. To do +this, you can pass a dictionary: + +```example +#set page(margin: ( + top: 3cm, + bottom: 2cm, + x: 1.5cm, +)) + +#lorem(100) +``` + +The page margin dictionary can have keys for each side (`top`, `bottom`, `left`, +`right`), but you can also control left and right together by setting the `x` +key of the margin dictionary, like in the example. Likewise, the top and bottom +margins can be adjusted together by setting the `y` key. + +If you do not specify margins for all sides in the margin dictionary, the old +margins will remain in effect for the unset sides. To prevent this and set all +remaining margins to a common size, you can use the `rest` key. For example, +`[#set page(margin: (left: 1.5in, rest: 1in))]` will set the left margin to 1.5 +inches and the remaining margins to one inch. + +### Different margins on alternating pages { #alternating-margins } +Sometimes, you'll need to alternate horizontal margins for even and odd pages, +for example, to have more room towards the spine of a book than on the outsides +of its pages. Typst keeps track of whether a page is to the left or right of the +binding. You can use this information and set the `inside` or `outside` keys of +the margin dictionary. The `inside` margin points towards the spine, and the +`outside` margin points towards the edge of the bound book. + +```typ +#set page(margin: ( + inside: 2.5cm, + outside: 2cm, + y: 1.75cm, +)) +``` + +Typst will assume that documents written in Left-to-Right scripts are bound on +the left while books written in Right-to-Left scripts are bound on the right. +However, you will need to change this in some cases: If your first page is +output by a different app, the binding is reversed from Typst's perspective. +Also, some books, like English-language Mangas are customarily bound on the +right, despite English using Left-to-Right script. To change the binding side +and explicitly set where the `inside` and `outside` are, set the +[`binding`]($func/page.binding) argument in the [`{page}`]($func/page) set rule. + +```typ +#set text(lang: "es") + +// Produce a book bound on the right, +// even though it is set in Spanish. +#set page(binding: right) +``` + +If `binding` is `left`, `inside` margins will be on the left on odd pages, and +vice versa. + +## Add headers and footers { #headers-and-footers } +Headers and footers are inserted in the top and bottom margins of every page. +You can add custom headers and footers or just insert a page number. + +In case you need more than just a page number, the best way to insert a header +and a footer are the [`header`]($func/page.header) and +[`footer`]($func/page.footer) arguments of the [`{page}`]($func/page) set rule. +You can pass any content as their values: + +```example +>>> #set page("a5", margin: (x: 2.5cm, y: 3cm)) +#set page(header: [ + _Lisa Strassner's Thesis_ + #h(1fr) + National Academy of Sciences +]) + +#lorem(150) +``` + +Headers are bottom-aligned by default so that they do not collide with the top +edge of the page. You can change this by wrapping your header in the +[`{align}`]($func/align) function. + +### Different header and footer on specific pages { #specific-pages } +You'll need different headers and footers on some pages. For example, you may +not want a header and footer on the title page. The example below shows how to +conditionally remove the header on the first page: + +```typ +>>> #set page("a5", margin: (x: 2.5cm, y: 3cm)) +#set page( + header: locate(loc => { + if counter(page).at(loc).first() > 1 [ + _Lisa Strassner's Thesis_ + #h(1fr) + National Academy of Sciences + ] + }), +) + +#lorem(150) +``` + +This example may look intimidating, but let's break it down: We are telling +Typst that the header depends on the current [location]($func/locate). The `loc` +value allows other functions to find out where on the page we currently are. We +then ask Typst if the page [counter]($func/counter) is larger than one at our +current position. The page counter starts at one, so we are skipping the header +on a single page. Counters may have multiple levels. This feature is used for +items like headings, but the page counter will always have a single level, so we +can just look at the first one. + +You can, of course, add an `else` to this example to add a different header to +the first page instead. + +### Adapt headers and footers on pages with specific elements { #specific-elements } +The technique described in the previous section can be adapted to perform more +advanced tasks using Typst's labels. For example, pages with big tables could +omit their headers to help keep clutter down. We will mark our tables with a +`` [label]($type/label) and use the [query system]($func/query) to +find out if such a label exists on the current page: + +```typ +>>> #set page("a5", margin: (x: 2.5cm, y: 3cm)) +#set page( + header: locate(loc => { + let page-counter = counter(page) + let matches = query(, loc) + let current = page-counter.at(loc) + let has-table = matches.any(m => + page-counter.at(m.location()) == current + ) + + if not has-table [ + _Lisa Strassner's Thesis_ + #h(1fr) + National Academy of Sciences + ] + }), +) + +#lorem(100) +#pagebreak() + +#table( + columns: 2 * (1fr, ) +)[A][B][C][D] +``` + +Here, we query for all instances of the `` label. We then check that +none of the tables are on the page at our current position. If so, we print the +header. This example also uses variables to be more concise. Just as above, you +could add an `else` to add another header instead of deleting it. + +## Add and customize page numbers { #page-numbers } +Page numbers help readers keep track of and reference your document more easily. +The simplest way to insert footnotes is the [`numbering`]($func/page.numbering) +argument of the [`{page}`]($func/page) set rule. You can pass a [_numbering +pattern_]($func/numbering.numbering) string that shows how you want your pages +to be numbered. + +```example +>>> #set page("iso-b6", margin: 1.75cm) +#set page(numbering: "1") + +This is a numbered page. +``` + +Above, you can check out the simplest conceivable example. It adds a single +Arabic page number at the center of the footer. You can specify other characters +than `"1"` to get other numerals. For example, `"i"` will yield lowercase Roman +numerals. Any character that is not interpreted as a number will be output +as-is. For example, put dashes around your page number by typing this: + +```example +>>> #set page("iso-b6", margin: 1.75cm) +#set page(numbering: "— 1 —") + +This is a — numbered — page. +``` + +You can add the total number of pages by entering a second number character in +the string. + +```example +>>> #set page("iso-b6", margin: 1.75cm) +#set page(numbering: "1 of 1") + +This is one of many numbered pages. +``` + +Go to the [`{numbering}` function reference]($func/numbering.numbering) to learn +more about the arguments you can pass here. + +In case you need to right- or left-align the page number, use the +[`number-align`]($func/page.number-align) argument of the [`{page}`]($func/page) +set rule. Alternating alignment between even and odd pages is not currently +supported using this property. To do this, you'll need to specify a custom +footer with your footnote and query the page counter as described in the section +on conditionally omitting headers and footers. + +### Custom footer with page numbers { #custom-footer-with-page-numbers } +Sometimes, you need to add other content than a page number to your footer. +However, once a footer is specified, the [`numbering`]($func/page.numbering) +argument of the [`{page}`]($func/page) set rule is ignored. This section shows +you how to add a custom footer with page numbers and more. + +```example +>>> #set page("iso-b6", margin: 1.75cm) +#set page( + footer: locate(loc => { + let page-num = counter(page) + .at(loc) + .first() + + let page-total = counter(page) + .final(loc) + .first() + + strong[ + American Society of Proceedings + ] + h(1fr) + [#page-num/#page-total] + }) +) + +This page has a custom footer. +``` + +The example above shows how to add a custom footer with page numbers. First, we +need to recover the page number using the page counter. For this, we are using +the [`{locate}` function]($func/locate) to check the page counter, just like in +the conditional header section. We then store the current and final page number +in variables. + +Then, we can proceed to build our footer. We add a strong label on the left, +insert all the free space on the line, and finally display the current page +number and the page total. This would work just the same in the header and with +any content. + +We can, of course, use the [`{numbering}` function]($func/numbering) to use +numbering pattern strings like before: + +```example +>>> #set page("iso-b6", margin: 1.75cm) +#set page( + footer: locate(loc => { + let page-num = counter(page) + .at(loc) + .first() + + let page-total = counter(page) + .final(loc) + .first() + + strong[ + American Society of Proceedings + ] + + h(1fr) + + numbering( + "i of I", + page-num, + page-total + ) + }) +) + +This page has a custom footer. +``` + +The `{numbering}` function accepts multiple arguments. It will use the arguments +in order for each number character. You could, for example, put the page total +in front of the page number by reversing the argument order. + +We can even use these variables to get more creative with the page number. For +example, let's insert a circle for each page. + +```example +>>> #set page("iso-b6", margin: 1.75cm) +#set page( + footer: locate(loc => { + let page-num = counter(page) + .at(loc) + .first() + + let circles = ( + box(circle( + radius: 2pt, + fill: navy, + )), + ) * page-num + + strong[Fun Typography Club] + h(1fr) + box( + inset: (bottom: 1pt), + circles.join(h(1pt)) + ) + }) +) + +This page has a custom footer. +``` + +In this example, we use the number of pages to create an array of +[circles]($func/circle). The circles are wrapped in a [box]($func/box) so they +can all appear on the same line because they are blocks and would otherwise +create paragraph breaks. The length of this [array]($type/array) depends on the +current page number. + +We then insert the circles at the right side of the footer, with 1pt of space +between them. The join method of an array will attempt to +[_join_]($scripting/#blocks) the different values of an array into a single +value, interspersed with its argument. In our case, we get a single content +value with circles and spaces between them that we can use with the align +function. Finally, we use another box to ensure that the text and the circles +can share a line and use the [`inset` argument]($func/box.inset) to raise the +circles a bit so they line up nicely with the text. + +### Reset the page number and skip pages { #skip-pages } +Do you, at some point in your document, need to reset the page number? Maybe you +want to start with the first page only after the title page. Or maybe you need +to skip a few page numbers because you will insert pages into the final printed +product. + +The right way to modify the page number is to manipulate the page +[counter]($func/counter). The simplest manipulation is to set the counter back +to 1. + +```typ +#counter(page).update(1) +``` + +This line will reset the page counter back to one. It should be placed at the +start of a page because it will otherwise create a page break. You can also +update the counter given its previous value by passing a function: + +```typ +#counter(page).update(i => i + 5) +``` + +In this example, we skip five pages. `i` is the current value of the page +counter and `i + 5` is the return value of our function. + +In case you need to retrieve the actual page number instead of the value of the +page counter, you can use the [`page`]($func/locate) method on the argument of +the `{locate}` closure: + +```example +#counter(page).update(i => i + 5) + +// This returns one even though the +// page counter was incremented by 5. +#locate(loc => {loc.page()}) +``` + +You can also obtain the page numbering pattern from the `{locate}` closure +parameter with the [`page-numbering`]($func/locate) method. + +## Add columns { #columns } +Add columns to your document to fit more on a page while maintaining legible +line lengths. Columns are vertical blocks of text which are separated by some +whitespace. This space is called the gutter. + +If all of your content needs to be laid out in columns, you can just specify the +desired number of columns in the [`{page}`]($func/page.columns) set rule: + +```example +>>> #set page(height: 120pt) +#set page(columns: 2) +#lorem(30) +``` + +If you need to adjust the gutter between the columns, refer to the method used +in the next section. + +### Use columns anywhere in your document { #columns-anywhere } +Very commonly, scientific papers have a single-column title and abstract, while +the main body is set in two-columns. To achieve this effect, Typst includes a +standalone [`{columns}` function]($func/columns) that can be used to insert +columns anywhere on a page. + +Conceptually, the `columns` function must wrap the content of the columns: + +```example:single +>>> #set page(height: 180pt) += Impacts of Odobenidae + +#set par(justify: true) +>>> #h(11pt) +#columns(2)[ + == About seals in the wild + #lorem(80) +] +``` + +However, we can use the ["everything show rule"]($styling/#show-rules) to reduce +nesting and write more legible Typst markup: + +```example:single +>>> #set page(height: 180pt) += Impacts of Odobenidae + +#set par(justify: true) +>>> #h(11pt) +#show: columns.with(2) + +== About seals in the wild +#lorem(80) +``` + +The show rule will wrap everything that comes after it in its function. The +[`with` mehtod]($type/function.with) allows us to pass arguments, in this case, +the column count, to a function without calling it. + +Another use of the `columns` function is to create columns inside of a container +like a rectangle or to customize gutter size: + +```example +#rect( + width: 6cm, + height: 3.5cm, + columns(2, gutter: 12pt)[ + In the dimly lit gas station, + a solitary taxi stood silently, + its yellow paint fading with + time. Its windows were dark, + its engine idle, and its tires + rested on the cold concrete. + ] +) +``` + +### Balanced columns { #balanced-columns } +If the columns on the last page of a document differ greatly in length, they may +create a lopsided and unappealing layout. That's why typographers will often +equalize the length of columns on the last page. This effect is called balancing +columns. Typst cannot yet balance columns automatically. However, you can +balance columns manually by placing [`[#colbreak()]`]($func/colbreak) at an +appropriate spot in your markup, creating the desired column break manually. + + +## One-off modifications { #one-off-modifications } +You do not need to override your page settings if you need to insert a single +page with a different setup. For example, you may want to insert a page that's +flipped to landscape to insert a big table or change the margin and columns for +your title page. In this case, you can call [`{page}`]($func/page) as a function +with your content as an argument and the overrides as the other arguments. This +will insert enough new pages with your overridden settings to place your content +on them. Typst will revert to the page settings from the set rule after the +call. + +```example +>>> #set page("a6") +#page(flipped: true)[ + = Multiplication table + + #table( + columns: 5 * (1fr,), + ..for x in range(1, 6) { + for y in range(1, 10) { + (str(x*y),) + } + } + ) +] +``` diff --git a/docs/guides/welcome.md b/docs/guides/welcome.md index 43ecd181d..f48fe1e5e 100644 --- a/docs/guides/welcome.md +++ b/docs/guides/welcome.md @@ -9,3 +9,4 @@ to Typst for LaTeX users. Feel free to propose other topics for guides! ## List of Guides { #list-of-guides } - [Guide for LaTeX users]($guides/guide-for-latex-users) +- [Page setup guide]($guides/page-setup-guide)