PageSourceSearch

https://touying-typ.github.io/assets/js/312a435b.56c55316.js

js touying-typ.github.io collected 2026-10-03 10:06:42 UTC 17,947 bytes, 1 lines download raw bytes

1"use strict";(self.webpackChunkdocs=self.webpackChunkdocs||[]).push([[2089],{68059:(e,n,t)=>{t.r(n),t.d(n,{assets:()=>r,contentTitle:()=>s,default:()=>h,frontMatter:()=>a,metadata:()=>l,toc:()=>c});var i=t(85893),o=t(11151);const a={sidebar_position:4},s="Page Layout",l={id:"tutorials/layout",title:"Page Layout",description:"Basic Concepts",source:"@site/docs/tutorials/layout.md",sourceDirName:"tutorials",slug:"/tutorials/layout",permalink:"/docs/tutorials/layout",draft:!1,unlisted:!1,editUrl:"https://github.com/touying-typ/touying/tree/main/docs/en/tutorials/layout.md",tags:[],version:"current",sidebarPosition:4,frontMatter:{sidebar_position:4},sidebar:"tutorialSidebar",previous:{title:"Settings and Config",permalink:"/docs/tutorials/settings"},next:{title:"Dynamic Slides",permalink:"/docs/category/dynamic-slides"}},r={},c=[{value:"Basic Concepts",id:"basic-concepts",level:2},{value:"Page Management",id:"page-management",level:2},{value:"Page Columnization",id:"page-columnization",level:2},{value:"Equalizing Column Heights with <code>lazy-v</code>
1",id:"equalizing-column-heights-with-lazy-v",level:2},{value:"How It Works",id:"how-it-works",level:3},{value:"Using <code>cols</code> (Recommended)",id:"using-cols-recommended",level:3},{value:"Using a Manual Grid",id:"using-a-manual-grid",level:3},{value:"Preventing Content Overflow",id:"preventing-content-overflow",level:2}];function d(e){const n={admonition:"admonition",code:"code",h1:"h1",h2:"h2",h3:"h3",img:"img",li:"li",ol:"ol",p:"p",pre:"pre",strong:"strong",ul:"ul",...(0,o.a)(),...e.components};return(0,i.jsxs)(i.Fragment,{children:[(0,i.jsx)(n.h1,{id:"page-layout",children:"Page Layout"}),"\n",(0,i.jsx)(n.h2,{id:"basic-concepts",children:"Basic Concepts"}),"\n",(0,i.jsx)(n.p,{children:"To create stylish slides using Typst, it's essential to understand Typst's page model correctly. If you're not concerned with customizing page styles, you can choose to skip this section. However, it's still recommended to go through it."}),"\n",(0,i.jsx)(n.p,{children:"Let's illustrate Typst's default page model through a specific example."}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:'#let container = rect.with(height: 100%, width: 100%, inset: 0pt)\n#let innerbox = rect.with(stroke: (dash: "dashed"))\n\n#set text(size: 30pt)\n#set page(\n  paper: "presentation-16-9",\n  header: container[#innerbox[Header]],\n  header-ascent: 30%,\n  footer: container[#innerbox[Footer]],\n  footer-descent: 30%,\n)\n\n#place(top + right)[Margin\u2192]\n#container[\n  #container[\n    #innerbox[Content]\n  ]\n]\n'})}),"\n",(0,i.jsx)(n.img,{alt:"Touying example output",src:t(83283).Z+"",width:"1684",height:"947"}),"\n",(0,i.jsx)(n.p,{children:"We need to distinguish the following concepts:"}),"\n",(0,i.jsxs)(n.ol,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Model:"})," Typst has a model similar to the CSS Box Model, divided into Margin, Padding, and Content. However, padding is not a property of ",(0,i.jsx)(n.code,{children:"set page(..)"})," but is obtained by manually adding ",(0,i.jsx)(n.code,{children:"#pad(..)"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Margin:"})," Margins are the edges of the page, divided into top, bottom, left, and right. They are the core of Typst's page model, and all other properties are influenced by margins, especially Header and Footer. Header and Footer are actually located within the Margin."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Header:"})," The Header is the content at the top of the page, divided into container and innerbox. We can observe that the edge of the header container and padding does not align but has some space in between, which is actually ",(0,i.jsx)(n.code,{children:"header-ascent: 30%"}),", where the percentage is relative to the margin-top. Additionally, we notice that the header innerbox is actually located at the bottom left corner of the header container, meaning innerbox defaults to ",(0,i.jsx)(n.code,{children:"#set align(left + bottom)"}),"."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Footer:"})," The Footer is the content at the bottom of the page, similar to the Header but in the opposite direction."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:"Place:"})," The ",(0,i.jsx)(n.code,{children:"place"})," function enables absolute positioning relative to the parent container without affecting other elements inside the parent container. It allows specifying ",(0,i.jsx)(n.code,{children:"alignment"}),", ",(0,i.jsx)(n.code,{children:"dx"}),", and ",(0,i.jsx)(n.code,{children:"dy"}),", making it suitable for placing decorative elements like logos."]}),"\n"]}),"\n",(0,i.jsx)(n.p,{children:"Therefore, to apply Typst to create slides, we only need to set:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:"#set page(\n  margin: (x: 4em, y: 2em),\n  header: align(top)[Header],\n  footer: align(bottom)[Footer],\n  header-ascent: 0em,\n  footer-descent: 0em,\n)\n"})}),"\n",(0,i.jsx)(n.p,{children:"However, we still need to address how the header occupies the entire page width. Here, we use negative padding to achieve this. For instance:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:'#let container = rect.with(stroke: (dash: "dashed"), height: 100%, width: 100%, inset: 0pt)\n#let innerbox = rect.with(fill: rgb("#d0d0d0"))\n#let margin = (x: 4em, y: 2em)\n\n// negative padding for header and footer\n#let negative-padding = pad.with(x: -margin.x, y: 0em)\n\n#set text(size: 30pt)\n#set page(\n  paper: "presentation-16-9",\n  margin: margin,\n  header: negative-padding[#container[#align(top)[#innerbox(width: 100%)[Header]]]],\n  header-ascent: 0em,\n  footer: negative-padding[#container[#align(bottom)[#innerbox(width: 100%)[Footer]]]],\n  footer-descent: 0em,\n)\n\n#place(top + right)[\u2191Margin\u2192]\n#container[\n  #container[\n    #innerbox[Content]\n  ]\n]\n'})}),"\n",(0,i.jsx)(n.img,{alt:"Touying example output",src:t(87219).Z+"",width:"1684",height:"947"}),"\n",(0,i.jsx)(n.h2,{id:"page-management",children:"Page Management"}),"\n",(0,i.jsxs)(n.p,{children:["In Typst, using the ",(0,i.jsx)(n.code,{children:"set page(..)"})," command to modify page parameters results in the creation of a new page, rather than modifying the current one. Therefore, Touying opts to maintain a ",(0,i.jsx)(n.code,{children:"self.page"})," member variable."]}),"\n",(0,i.jsx)(n.p,{children:"For example, the previous example can be rewritten as:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:"#show: default-theme.with(\n  config-page(\n    margin: (x: 4em, y: 2em),\n    header: align(top)[Header],\n    footer: align(bottom)[Footer],\n    header-ascent: 0em,\n    footer-descent: 0em,\n  ),\n)\n"})}),"\n",(0,i.jsxs)(n.p,{children:["Touying will automatically detect the value of ",(0,i.jsx)(n.code,{children:"margin.x"})," and determine whether to apply negative padding to the header if ",(0,i.jsx)(n.code,{children:"config-common(zero-margin-header: true)"})," is set, which is equivalent to ",(0,i.jsx)(n.code,{children:"self.zero-margin-header = true"}),"."]}),"\n",(0,i.jsx)(n.p,{children:"Similarly, if you are not satisfied with the style of the header or footer of a particular theme, you can also modify it through:"}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:"config-page(footer: [Custom Footer])\n"})}),"\n",(0,i.jsx)(n.admonition,{title:"Warning",type:"warning",children:(0,i.jsxs)(n.p,{children:["Therefore, you should not use the ",(0,i.jsx)(n.code,{children:"set page(..)"})," command yourself, as it will be reset by Touying."]})}),"\n",(0,i.jsxs)(n.p,{children:["With this approach, we can also query the current page parameters in real-time using ",(0,i.jsx)(n.code,{children:"self.page"}),", which is very useful for functions that need to obtain the page margins or the current page background color, such as ",(0,i.jsx)(n.code,{children:"transparent-cover"}),". This is somewhat equivalent to context get rule, and in practice, it is more convenient to use."]}),"\n",(0,i.jsx)(n.h2,{id:"page-columnization",children:"Page Columnization"}),"\n",(0,i.jsxs)(n.p,{children:["If you need to divide a page into two or three columns, you can use the ",(0,i.jsx)(n.code,{children:"composer"})," feature provided by the default ",(0,i.jsx)(n.code,{children:"slide"})," function in Touying. The simplest example is as follows:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:"#slide[\n  First c
1olumn.\n][\n  Second column.\n]\n"})}),"\n",(0,i.jsx)(n.img,{alt:"Touying example output",src:t(20853).Z+"",width:"1684",height:"947"}),"\n",(0,i.jsxs)(n.p,{children:["If you need to change the way columns are divided, you can modify the ",(0,i.jsx)(n.code,{children:"composer"})," parameter of ",(0,i.jsx)(n.code,{children:"slide"}),", where the default parameter is ",(0,i.jsx)(n.code,{children:"cols.with(columns: auto, gutter: 1em)"}),". If we want the left column to take up the remaining width, we can use:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:"#slide(composer: (1fr, auto))[\n  First column.\n][\n  Second column.\n]\n"})}),"\n",(0,i.jsx)(n.img,{alt:"Touying example output",src:t(93126).Z+"",width:"1684",height:"947"}),"\n",(0,i.jsxs)(n.h2,{id:"equalizing-column-heights-with-lazy-v",children:["Equalizing Column Heights with ",(0,i.jsx)(n.code,{children:"lazy-v"})]}),"\n",(0,i.jsxs)(n.p,{children:["When using multi-column layouts (via ",(0,i.jsx)(n.code,{children:"cols"})," or a manual ",(0,i.jsx)(n.code,{children:"grid"}),'), columns with different amounts of content will have different heights. If you want to push some "footer" content (e.g. a label or caption) to the bottom of each column and have it align across all columns, or simply want all columns to match the tallest one\'s height, you can use ',(0,i.jsx)(n.code,{children:"lazy-v"})," together with ",(0,i.jsx)(n.code,{children:"lazy-layout"}),"."]}),"\n",(0,i.jsx)(n.h3,{id:"how-it-works",children:"How It Works"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"lazy-v(1fr)"})})," \u2014 Place this between the main content and the footer content inside a block. It acts as a deferred vertical spacer that is invisible during height measurement."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"lazy-layout"})})," \u2014 Wraps the multi-column layout. It first measures the natural height of all columns (ignoring ",(0,i.jsx)(n.code,{children:"lazy-v"})," markers), then re-renders at that fixed height with the markers activated. This causes each column to stretch to match the tallest one, without the overall container expanding to fill the entire page."]}),"\n"]}),"\n",(0,i.jsxs)(n.h3,{id:"using-cols-recommended",children:["Using ",(0,i.jsx)(n.code,{children:"cols"})," (Recommended)"]}),"\n",(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"cols"})," does not enable ",(0,i.jsx)(n.code,{children:"lazy-layout"})," by default. Pass ",(0,i.jsx)(n.code,{children:"lazy-layout: true"})," to ",(0,i.jsx)(n.code,{children:"cols"}),", then add ",(0,i.jsx)(n.code,{children:"lazy-v(1fr)"})," inside each block:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:"#cols(lazy-layout: true)[\n  #block(fill: luma(220), inset: .5em, radius: .2em, width: 100%)[\n    #lorem(10)\n    #lazy-v(1fr)\n    Bottom left.\n  ]\n][\n  #block(fill: luma(220), inset: .5em, radius: .2em, width: 100%)[\n    #lorem(20)\n    #lazy-v(1fr)\n    Bottom right.\n  ]\n]\n"})}),"\n",(0,i.jsx)(n.img,{alt:"Touying example output",src:t(2008).Z+"",width:"1684",height:"947"}),"\n",(0,i.jsxs)(n.p,{children:['Both columns will have the same height (matching the taller one), and "Bottom left." / "Bottom right." will be aligned at the bottom. The overall layout height equals the tallest column \u2014 it does ',(0,i.jsx)(n.strong,{children:"not"})," expand to fill the entire page."]}),"\n",(0,i.jsx)(n.admonition,{title:"Note",type:"note",children:(0,i.jsxs)(n.p,{children:["This is different from using ",(0,i.jsx)(n.code,{children:"v(1fr)"})," inside ",(0,i.jsx)(n.code,{children:"#slide[][]"}),". The ",(0,i.jsx)(n.code,{children:"slide"})," composer occupies the full page height, so ",(0,i.jsx)(n.code,{children:"v(1fr)"})," works directly there. ",(0,i.jsx)(n.code,{children:"lazy-v"})," is designed for standalone ",(0,i.jsx)(n.code,{children:"cols"})," or ",(0,i.jsx)(n.code,{children:"lazy-layout"})," calls where you want height equalization without full-page expansion."]})}),"\n",(0,i.jsx)(n.h3,{id:"using-a-manual-grid",children:"Using a Manual Grid"}),"\n",(0,i.jsxs)(n.p,{children:["You can also wrap a ",(0,i.jsx)(n.code,{children:"grid"})," with ",(0,i.jsx)(n.code,{children:"lazy-layout"})," directly:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:"#lazy-layout(grid(\n  columns: (1fr, 1fr),\n  gutter: 1em,\n  block(fill: luma(220), inset: .5em, radius: .2em, width: 100%)[\n    #lorem(10)\n    #lazy-v(1fr)\n    Bottom left.\n  ],\n  block(fill: luma(220), inset: .5em, radius: .2em, width: 100%)[\n    #lorem(20)\n    #lazy-v(1fr)\n    Bottom right.\n  ],\n))\n"})}),"\n",(0,i.jsx)(n.img,{alt:"Touying example output",src:t(68169).Z+"",width:"1684",height:"947"}),"\n",(0,i.jsx)(n.admonition,{title:"Tip",type:"tip",children:(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"cols"})," defaults to ",(0,i.jsx)(n.code,{children:"lazy-layout: false"}),". Enable it only when you need height equalization."]})}),"\n",(0,i.jsx)(n.h2,{id:"preventing-content-overflow",children:"Preventing Content Overflow"}),"\n",(0,i.jsx)(n.p,{children:"By default, when slide content exceeds the page height, Touying automatically overflows the excess content to the next page. This is reasonable in most cases, but in scenarios that require str
1ict control over page mapping \u2014 such as agentic workflows where an agent needs to reason about slide boundaries \u2014 you may want to disable this behavior."}),"\n",(0,i.jsxs)(n.p,{children:["Use ",(0,i.jsx)(n.code,{children:"config-common(breakable: false)"})," to prevent content from overflowing:"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:"// Prevent overflow, panic on overflow (default behavior when breakable: false)\n#show: simple-theme.with(\n  config-common(breakable: false),\n)\n\n// Prevent overflow and visually clip overflowing content\n#show: simple-theme.with(\n  config-common(breakable: false, clip: true),\n)\n\n// Prevent overflow, disable overflow detection (performance-first)\n#show: simple-theme.with(\n  config-common(breakable: false, detect-overflow: false),\n)\n"})}),"\n",(0,i.jsx)(n.p,{children:"Related parameters:"}),"\n",(0,i.jsxs)(n.ul,{children:["\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"clip"})})," (default ",(0,i.jsx)(n.code,{children:"false"}),"): When ",(0,i.jsx)(n.code,{children:"true"}),", content that exceeds the slide height is visually truncated."]}),"\n",(0,i.jsxs)(n.li,{children:[(0,i.jsx)(n.strong,{children:(0,i.jsx)(n.code,{children:"detect-overflow"})})," (default ",(0,i.jsx)(n.code,{children:"true"}),"): When ",(0,i.jsx)(n.code,{children:"true"}),", a layout measurement is performed and ",(0,i.jsx)(n.code,{children:"panic()"})," is called if the content height exceeds the available slide height, making it easy to catch overflow early. Set to ",(0,i.jsx)(n.code,{children:"false"})," to avoid the extra layout overhead."]}),"\n"]}),"\n",(0,i.jsx)(n.admonition,{title:"Note",type:"note",children:(0,i.jsxs)(n.p,{children:[(0,i.jsx)(n.code,{children:"clip"})," and ",(0,i.jsx)(n.code,{children:"detect-overflow"})," only take effect when ",(0,i.jsx)(n.code,{children:"breakable: false"}),"."]})}),"\n",(0,i.jsxs)(n.p,{children:["You can also dynamically switch these settings mid-presentation using ",(0,i.jsx)(n.code,{children:"touying-set-config"}),":"]}),"\n",(0,i.jsx)(n.pre,{children:(0,i.jsx)(n.code,{className:"language-typst",children:"== This slide's overflow will be clipped\n\n// Enable clipping for a specific slide\n#show: touying-set-config.with(config-common(clip: true))\n\n#lorem(500)\n"})}),"\n",(0,i.jsx)(n.img,{alt:"Touying example output",src:t(76698).Z+"",width:"1684",height:"947"})]})}function h(e={}){const{wrapper:n}={...(0,o.a)(),...e.components};return n?(0,i.jsx)(n,{...e,children:(0,i.jsx)(d,{...e})}):d(e)}},68169:(e,n,t)=>{t.d(n,{Z:()=>i});const i=t.p+"assets/images/254afc1a-5395bef6482fd97f47b94933944401eb.png"},93126:(e,n,t)=>{t.d(n,{Z:()=>i});const i=t.p+"assets/images/35156899-b8b85d113cedf8703f5ccac2d65d7220.png"},20853:(e,n,t)=>{t.d(n,{Z:()=>i});const i=t.p+"assets/images/7b17e864-5912bf82e25d7deca973f7b1a99e83a3.png"},87219:(e,n,t)=>{t.d(n,{Z:()=>i});const i=t.p+"assets/images/86a23a81-5eb67baa96accc75e83649bbc6474353.png"},83283:(e,n,t)=>{t.d(n,{Z:()=>i});const i=t.p+"assets/images/af68f604-20691c05da106f1c2366c204aeac82e7.png"},2008:(e,n,t)=>{t.d(n,{Z:()=>i});const i=t.p+"assets/images/c80884e6-5395bef6482fd97f47b94933944401eb.png"},76698:(e,n,t)=>{t.d(n,{Z:()=>i});const i=t.p+"assets/images/d84c9091-bfd8e3a894927da8b6d8b1f83e58fee0.png"},11151:(e,n,t)=>{t.d(n,{Z:()=>l,a:()=>s});var i=t(67294);const o={},a=i.createContext(o);function s(e){const n=i.useContext(a);return i.useMemo((function(){return"function"==typeof e?e(n):{...n,...e}}),[n,e])}function l(e){let n;return n=e.disableParentContext?"function"==typeof e.components?e.components(o):e.components||o:s(e.components),i.createElement(a.Provider,{value:n},e.children)}}}]);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.