How to define the page paper format and define margins using set rules?

Hi all,

I am trying to implement a function that adapts the page margins given the paper format provided by the user in the template. Initially, I use the following implementation:

#let paper-format = "a5"
#let adapt-margins(margins) = context {
  let paper-width = page.width
  let paper-height = page.height
  let a4-width = 210mm
  let a4-height = 297mm

  let width = (paper-width - a4-width) / 2
  let height = (paper-height - a4-height) / 2
  
  (
    left: width + margins.left,
    right: width + margins.right,
    top: height + margins.top,
    bottom: height + margins.bottom,
  )
}

In the template:

#let my-template(paper: "a4", args..., body) = context {
   // It is an example
   let my-margins = (left: 1cm, right: 1cm, top: 1cm, bottom: 1cm)

   show: page.with(paper: paper)
   let margins = adapt-margins(my-margins)

   set page(margin: margins)
}

The previous function doesn’t work because margins is a context and not a dictionary. If I code the function directly in the template definition, that is:

#let my-template(paper: "a4", args..., body) = context {
   // It is an example
   let my-margins = (left: 1cm, right: 1cm, top: 1cm, bottom: 1cm)

   show: page.with(paper: paper)
   let paper-width = page.width
  let paper-height = page.height
  let a4-width = 210mm
  let a4-height = 297mm

  let width = (paper-width - a4-width) / 2
  let height = (paper-height - a4-height) / 2

   set page(
         margin: (
           left: width + margins.left,
           right: width + margins.right,
           top: height + margins.top,
           bottom: height + margins.bottom,
       )
   )
}

it works, but an extra page is added, which is quite logical.

Is there a way to do this without redefining all the paper widths and heights (which is my current solution)?

Thank you

You don’t actually need the function adapt-margins to be contextual if you are calling it with context. In your case my-template is already contextual so you can remove the context in adapt-margins.

this is because your template sets parameters for page twice. combining the two gets rid of the issue:

#let my-template(paper: "a4", ..args, body) = context {
   // It is an example
   let my-margins = (left: 1cm, right: 1cm, top: 1cm, bottom: 1cm)

   let margins = adapt-margins(my-margins)

   set page(paper: paper, margin: margins)

   body
}

Also, it could just be that your example is purposefully omitting details but it seems to me that trying to hardcode margins (using a4-width = 210mm , a4-height = 297mm) for all paper sizes is error-prone and tedious to implement. If it’s possible for your project, I would try using relative lengths for margins:

#set page(margin: (x: 7%, y: 4%))

Thank you for your reply. Your last solution is a solution I tried. However, combining both paper and margin doesn’t seem to work since the margins are not calculated using the paper defined by the user.

Another way to rephrase the question is: Is it possible to adapt/update the margins of the document pages after the definition of the paper format without adding a blank page?

Finally, I have hardcoded the width and height of the paper to rescale the margins given the paper format. For this, I have checked the dimensions defined in the source code of Typst.

What do you mean? Testing with a margin of x: 7% the combined absolute sizes of the left + right margins were 83.34pt, 58.63pt, 41.67pt for a4, a5, a6 respectively. Compare this to x: 1cm, which was 56.69pt for all sizes

Size calculation
#set page(margin: (x: 7%, y: 4%))
#set page(paper: "a4")

#context block(width: 100%, layout(bl => {
  page.width - bl.width
}))

#set page(paper: "a5")


#context block(width: 100%, layout(bl => {
  page.width - bl.width
}))


#set page(paper: "a6")


#context block(width: 100%, layout(bl => {
  page.width - bl.width
}))


#set page(margin: (x: 1cm, y: 1cm))

#set page(paper: "a4")

#context block(width: 100%, layout(bl => {
  page.width - bl.width
}))

#set page(paper: "a5")


#context block(width: 100%, layout(bl => {
  page.width - bl.width
}))


#set page(paper: "a6")


#context block(width: 100%, layout(bl => {
  page.width - bl.width
}))

It should work fine if both are defined using set rules and there is no content (i.e. anything that would appear on the screen, code is fine) between the two rules:

#set page(paper: "a4")
#let my-variable = "test"
// some code here
#set page(margin: (x: 7%, y: 4%))

Yes, I just meant that if you intent to do this for all 100 or so paper sizes defined by typst it might take some time. If you intend the template to only work for a subset of the paper sizes and are happy with the results, it’s probably fine :smile:

1 Like