Can I have different page margins on first and subsequent pages?

I need to change margin of a page on non first appeaser of page definition. Because of some crazy formatting required by my university i need to also add a table in background, and if be more specific “big table is added to first page of outline and none after”. For this i created a function page_tab. It returns an dictionary where background returns with required table using backgrounds variable, it has an context if statement

context if big == true {
    if int(p_s) == int(counter(page).get().at(0)) {
     //tab_big
    } else { none }
  } else {
  // tab_small
  }

this statement checks if the page the function call is the first by comparing int(p_s) == int(counter(page).get().at(0)). It works with background because it expects content, but the margin expects dictionary, yet context expression produce only context what cant be transformed in dict. I don’t know how to make it work at this point.

#let page_tab(
  big: false,
  theme: [],
  author: [],
  kurathor: [],
  rec: [],
  kontrol: [],
  zatv: [],
  grup: [],
  rozdil: [],
  p_s: int,
) = {
  let backgrounds = context if big == true {
    if int(p_s) == int(counter(page).get().at(0)) {
      align(
        left + top,
        text(size: 9pt, font: "ISOCPEUR", block(
          inset: (left: 2cm, top: 1cm, right: 8mm, bottom: 5mm),
          width: auto,
          height: auto,
          grid(
            align: center + horizon,
            columns: (
              1cm,
              1.1cm,
              2.5cm,
              1.5cm,
              1cm,
              1fr,
              5mm,
              5mm,
              5mm,
              1.5cm,
              2.3cm,
            ),
            rows: (1fr, 0.5cm, 0.5cm, 0.5cm, 5mm, 5mm, 5mm, 5mm, 5mm),
            stroke: 1pt,
            grid.cell(colspan: 11)[~],
            [ ],
            [],
            [],
            [ ],
            [ ],
            grid.cell(colspan: 6, rowspan: 3, inset: (x: 5pt))[#{
              set smartquote(enabled: false)
              par(leading: 0.65em)[#text([#theme], size: 11pt)]
            }],
            [ ], [ ], [ ], [ ], [ ],
            [Зм.], [Арк.], [№ докум.], [Підис], [Дата],
            grid.cell(colspan: 2)[Розробив],
            [#author],
            [],
            [],
            grid.cell(rowspan: 5, inset: 1mm)[#text([_ЗМІСТ_], size: 20pt)],
            grid.cell(colspan: 3)[Літ.],
            [Аркуш],
            [Аркушів],
            grid.cell(colspan: 2)[Перевірив],
            [#kurathor],
            [],
            [],
            [],
            [],
            [],
            [#counter(page).display()],
            [#counter(page).final().at(0)],
            grid.cell(colspan: 2)[Т. контр.],
            [#rec],
            [],
            [],
            grid.cell(colspan: 5, rowspan: 3)[_ #grup _],
            grid.cell(colspan: 2)[Н. Контр.], [#kontrol], [], [],
            grid.cell(colspan: 2)[Затв.], [#zatv], [], [],
          ),
        )),
      )
    } else { none }
  } else {
    align(left + top, text(size: 9pt, block(
      inset: (left: 2cm, top: 1cm, right: 8mm, bottom: 4mm),
      width: auto,
      height: auto,
      grid(
        align: center + horizon,
        columns: (0.8cm, 1cm, 2.5cm, 1.5cm, 1cm, 1fr, 1.1cm),
        rows: (1fr, 0.5cm, 0.5cm, 0.5cm),
        stroke: 1pt,
        grid.cell(colspan: 7)[~],
        [ ],
        [ ],
        [ ],
        [ ],
        [ ],
        grid.cell(rowspan: 3)[#text(size: 20pt)[#rozdil]],
        [Аркуш],
        [ ],
        [ ],
        [ ],
        [ ],
        [ ],
        grid.cell(rowspan: 2)[#text(size: 14pt)[#counter(
          page,
        ).display()]],
        [Зм.], [Арк.], [№ докум.], [Підис], [Дата],
      ),
    )))
  }

  let marg = if big == true {
    if int(p_s) == int(counter(page).get().at(0)) {
      page_marg_big
    } else { page_marg }
  } else {
    page_marg
  }


  (
    paper: "a4",
    margin: marg,
    background: backgrounds,
  )
}

main.typ:

#{
  context {
    let page_st = counter(page).get().at(0)
    set page(
      ..page_tab(
        big: true,
        theme: [theme],
        author: [me],
        kurathor: [],
        kontrol: [],
        zatv: [],
        p_s: page_st,
      ),
    )
    outline()
  }
}

P.S. I need to do this like this because the outline is not breakable

Hello. I cannot help but feel like you are overcomplicating things here, even re-reading your post 3 times I have no idea why you are doing things the way you are, like why this table needs to be a background, how it relates to changing the margins of the page, or what you mean by outlines not being breakable

If you could include some additional information, like an example of what the expected output should be and some design requirements, you’d be more likely to get a good response.

gen_setings.typ:

#import "variabels.typ": *

#let style(it) = {
  set text(
    font: "Times New Roman",
    size: 14pt,
    lang: "uk",
    hyphenate: false,
    kerning: false,
    top-edge: "cap-height",
    bottom-edge: "baseline",
  )
  set heading(numbering: "1.")
  set par(
    justify: true,
    first-line-indent: (
      amount: 1.25cm,
      all: true,
    ),
    leading: spasing_l,
    spacing: spasing_l,
  )
  set align(left)
  set figure(
    numbering: num => (str(counter(heading).get().at(0)) + "." + str(num)),
    placement: none,
    supplement: [Рис.],
  )
  set table(align: center + horizon)
  set figure.caption(
    separator: [ -- ],
    position: top,
  )
  set math.equation(
    numbering: num => (
      "(" + str(counter(heading).get().at(0)) + "." + str(num) + ")"
    ),
  )
  set table.cell(breakable: false)

  // show rules
  show outline: it => {
    let sp = 4mm
    let sp_l = 3mm
    set block(above: sp)
    set par(leading: sp_l)
    show link: set text(fill: black)
    show heading.where(level: 1): its => {
      set align(center)
      set text(size: 14pt)
      set par(justify: true)
      text(weight: "bold", [#upper[#its.body]])
    }
    it
  }

  show outline.entry.where(level: 1): it => {
    text(weight: "bold", size: 14pt)[
      #link(it.element.location())[
        #grid(
          columns: (auto, 1fr, auto),
          if query(heading)
            .find(elem => elem.location() == it.element.location())
            .numbering
            == none {
            (
              upper(it.element.body)
                + box(width: 1fr, it.fill)
                + str(counter(page).at(it.element.location()).at(0))
            )
          } else {
            (
              upper([Розділ ])
                + str(counter(heading).at(it.element.location()).at(0))
                + ". "
                + upper(it.element.body)
                + box(width: 1fr, it.fill)
                + str(counter(page).at(it.element.location()).at(0))
            )
          },
        )]]
  }

  show heading.where(level: 1): it => [
    #counter(figure.where(kind: "table")).update(0)
    #counter(figure).update(0)
    #counter(math.equation).update(0)
    #set align(center)
    #set text(size: 14pt, hyphenate: false)
    #set par(justify: false)
    #text(weight: "bold", [#upper[Розділ #counter(heading).display() #it.body]])
  ]

  show heading.where(level: 1, numbering: none): it => [
    #counter(figure.where(kind: "table")).update(0)
    #counter(figure).update(0)
    #counter(math.equation).update(0)
    #set align(center)
    #set text(size: 14pt, hyphenate: false)
    #set par(justify: true)
    #text(weight: "bold", [#upper(it.body)])
  ]

  show heading.where(level: 2): it => [
    #set text(size: 14pt, hyphenate: false)
    #set par(justify: true)
    #text(weight: "bold", [#counter(heading).display() #it.body])
  ]

  show heading.where(level: 2, numbering: none): it => [
    #set text(size: 14pt, hyphenate: false)
    #set par(justify: false)
    #text(weight: "bold", [#it.body])
  ]

  show heading.where(level: 3): it => [
    #set text(size: 14pt, hyphenate: false)
    #set par(justify: true)
    #text(weight: "regular", [#it.body] + str("."))
  ]

  show table: set par(justify: false, leading: 1mm, spacing: 1mm)
  show table: set text(size: 14pt)

  show figure: set block(breakable: true)

  show figure.where(
    kind: "table",
  ): set figure.caption(position: top)

  show figure.where(
    kind: "table",
  ): set figure(supplement: [Таблиця])

  show link: set text(fill: rgb("#0000ff"))

  it
}

variabels.typ:

#let DEGC = [$degree"C"$]
#let cmq = [$"см"^3$]
#let dmq = [$"дм"^3$]
#let kuodmq = [КУО/#dmq]
#let kuocmq = [КУО/#cmq]
#let spasing_l = 6mm
#let page_marg = (
  left: 30mm,
  right: 15mm,
  bottom: 20mm,
  top: 20mm,
)
#let page_marg_big = (
  left: page_marg.left,
  right: page_marg.right,
  bottom: 50mm,
  top: page_marg.top,
)
// #let marg = if big == true {
//   if int(p_s) == int(counter(page).get().at(0)) {
//     page_marg_big
//   } else { page_marg }
// } else {
//   page_marg
// }

#let page_tab(
  big: false,
  theme: [],
  author: [],
  kurathor: [],
  rec: [],
  kontrol: [],
  zatv: [],
  grup: [],
  rozdil: [],
  p_s: int,
  p_s_1: int,
) = {
  let backgrounds = context if big == true {
    if int(p_s) == int(counter(page).get().at(0)) {
      align(
        left + top,
        text(size: 9pt, font: "ISOCPEUR", block(
          inset: (left: 2cm, top: 1cm, right: 8mm, bottom: 5mm),
          width: auto,
          height: auto,
          grid(
            align: center + horizon,
            columns: (
              1cm,
              1.1cm,
              2.5cm,
              1.5cm,
              1cm,
              1fr,
              5mm,
              5mm,
              5mm,
              1.5cm,
              2.3cm,
            ),
            rows: (1fr, 0.5cm, 0.5cm, 0.5cm, 5mm, 5mm, 5mm, 5mm, 5mm),
            stroke: 1pt,
            grid.cell(colspan: 11)[~],
            [ ],
            [],
            [],
            [ ],
            [ ],
            grid.cell(colspan: 6, rowspan: 3, inset: (x: 5pt))[#{
              set smartquote(enabled: false)
              par(leading: 0.65em)[#text([#theme], size: 11pt)]
            }],
            [ ], [ ], [ ], [ ], [ ],
            [Зм.], [Арк.], [№ докум.], [Підис], [Дата],
            grid.cell(colspan: 2)[Розробив],
            [#author],
            [],
            [],
            grid.cell(rowspan: 5, inset: 1mm)[#text([_ЗМІСТ_], size: 20pt)],
            grid.cell(colspan: 3)[Літ.],
            [Аркуш],
            [Аркушів],
            grid.cell(colspan: 2)[Перевірив],
            [#kurathor],
            [],
            [],
            [],
            [],
            [],
            [#counter(page).display()],
            [#counter(page).final().at(0)],
            grid.cell(colspan: 2)[Т. контр.],
            [#rec],
            [],
            [],
            grid.cell(colspan: 5, rowspan: 3)[_ #grup _],
            grid.cell(colspan: 2)[Н. Контр.], [#kontrol], [], [],
            grid.cell(colspan: 2)[Затв.], [#zatv], [], [],
          ),
        )),
      )
    } else { none }
  } else {
    align(left + top, text(size: 9pt, block(
      inset: (left: 2cm, top: 1cm, right: 8mm, bottom: 4mm),
      width: auto,
      height: auto,
      grid(
        align: center + horizon,
        columns: (0.8cm, 1cm, 2.5cm, 1.5cm, 1cm, 1fr, 1.1cm),
        rows: (1fr, 0.5cm, 0.5cm, 0.5cm),
        stroke: 1pt,
        grid.cell(colspan: 7)[~],
        [ ],
        [ ],
        [ ],
        [ ],
        [ ],
        grid.cell(rowspan: 3)[#text(size: 20pt)[#rozdil]],
        [Аркуш],
        [ ],
        [ ],
        [ ],
        [ ],
        [ ],
        grid.cell(rowspan: 2)[#text(size: 14pt)[#counter(
          page,
        ).display()]],
        [Зм.], [Арк.], [№ докум.], [Підис], [Дата],
      ),
    )))
  }

  let marg = if big == true {
    if int(p_s) == int(counter(page).get().at(0)) {
      page_marg_big
    } else { page_marg }
  } else {
    page_marg
  }


  (
    paper: "a4",
    margin: marg,
    background: backgrounds,
  )
}

#let is_empty(value) = {
  let empty_values = (
    array: (),
    dictionary: (:),
    str: "",
    content: [],
  )
  let t = repr(type(value))
  if t in empty_values {
    return value == empty_values.at(t)
  } else {
    return value == none
  }
}

#let table_multi_page(
  continue_header_label: [],
  last: [],
  italic: true,
  ..table_args,
) = context {
  let columns = table_args.named().at("columns", default: 1)
  let column_amount = if type(columns) == int {
    columns
  } else if type(columns) == array {
    columns.len()
  } else {
    1
  }

  // Check as show rule for appearance of a header or a footer in grid if value
  // is specified
  let label_has_content = value => (
    value.has("children") and value.children.len() > 0 or value.has("text")
  )

  // Counter of tables so we can create a unique table_part_counter for each
  // table
  let table_counter = counter("table")
  table_counter.step()

  // Counter for the amount of pages in the table
  let table_part_counter = counter(
    "table_part" + str(table_counter.get().first()),
  )

  show <table_header>: header => {
    table_part_counter.step()
    context if (
      (table_part_counter.get().first() != 1)
        and not is_empty(continue_header_label)
    ) {
      header
    }
  }

  grid(
    inset: 0mm,
    row-gutter: 2mm,
    grid.header(
      grid.cell(
        align(right + bottom)[
          #context if (
            (
              (
                table_part_counter.get().at(0) + 1
                  != table_part_counter.final().at(0)
              )
                or is_empty(last)
            )
              and italic == true
          ) {
            text(style: "italic")[
              #continue_header_label <table_header>
            ]
          } else if (
            (
              table_part_counter.get().at(0) + 1
                == table_part_counter.final().at(0)
            )
              and italic == true
          ) {
            text(style: "italic")[
              #last <table_header>
            ]
          } else if (
            (
              (
                table_part_counter.get().at(0) + 1
                  != table_part_counter.final().at(0)
              )
                or is_empty(last)
            )
              and italic == false
          ) {
            [
              #continue_header_label <table_header>
            ]
          } else if (
            (
              table_part_counter.get().at(0) + 1
                == table_part_counter.final().at(0)
            )
              and italic == false
          ) {
            [
              #last <table_header>
            ]
          }
        ],
      ),
    ),
    ..table_args,
  )
}

main.typ

#import "variabels.typ": (
  DEGC, dmq, page_marg, page_marg_big, page_tab, table_multi_page,
)
#import "gen_set.typ": style

#show: doc => style(doc)

#{
  context {
    let page_st = counter(page).get().at(0)
    set page(
      ..page_tab(
        big: true,
        theme: [_theme_],
        author: [],
        kurathor: [],
        kontrol: [],
        zatv: [],
        p_s: page_st,
      ),
      margin: page_marg_big,
    )
    outline()
  }
}

// #pagebreak()
#set page(..page_tab(big: false, rozdil: [ВСТУП]))

#for i in range(0, 90) {
  eval("= asdf", mode: "markup")
}

this is my setup. When compiled this code produce next:

on first page of outline the ‘table’ take up a lot of space from bottom so I put a bottom margin higher. But the next page don’t have this ‘table’ and yet the bottom margin stay very higher. I wona to find a way to put the bottom margin lower on every next page.

P.S. sorry for English not my native

Correct me if i am wrong, but you are looking to add a table to the bottom of the first page only? Is that the requirement?

1 Like

Unfortunately yes, and its not a table in normal context it’s in background and not interactive with anything else

Thanks for providing some code. It would really help however if you could strip it to the minimum required to demonstrate the issue. A minimum working example (MWE).

This seems to be a case of XY problem. It is not clear to me why the table needs to be in the background and what you mean be interactive with anything else. Would you consider having the table in the footer instead? I mean it can always remain in the background. This is not the issue here.

You can have different margins for the first page as seen here:

#set rect(  // For showing header and footer clearly -- remove 
  width: 100%,
  height: 100%,
  inset: 4pt,
)

#set page(
  margin: (bottom: 3cm),
  paper: "iso-b7",
  header: rect(fill: aqua)[Header],
  footer: rect(fill: aqua)[This is a large footer because of a large table. #lorem(10)],
)

= Different First Page
#lorem(10)

#set page(
  margin: (bottom: 1cm),
  footer: rect(fill: aqua)[Small Different Footer],
)
= #lorem(5)
#lorem(15)

If you want to share some code, please consider sharing a project on typst.app and remove any extra code to make it simple for us to help you.

1 Like

here is the example Typst

Also this table when put in footer is braking. Problem mostly in outline, it cant be broken in a middle to change some settings.

Thanks for providing the code example. I will try to have a look and see if we can reduce to a minimum working example.

I understand you want only the first page of the outline showing the big table at the bottom. Since Typst can’t handle dynamic margins, this is unlikely to be achieved using the footer as I incorrectly suggested above.

Minimum Non-Working Example (MNWE):

#set heading(numbering: "1.1")
#set page(
  margin: (bottom: 3cm),
  paper: "iso-b7",
  footer: rect(fill: aqua)[We want this to appear only on the first page of the outline (but it incorrectly shows on the second page as this outline has two pages)],
)

#for i in range(10) {[= #lorem(2)]}

#set page(
  margin: (bottom: 1cm),
  footer: rect(fill: aqua)[Normal Footer],
)
= #lorem(5)
#lorem(15)

I’m afraid this may be impossible to achieve as of now. See:

Can it be done through the background? Perhaps. Someone here may well come up with a solution. Perhaps using meander – Typst Universe

A bit hacky, but if the table height is constant you could insert a pagebreak in the outline. You have to manually adjust the 1 for the page where you want the cutoff to happen and find a valid cutoff position.

#set page(paper: "a8")

#show outline.entry: it => {
  it
  let cutoff = 100pt
  if it.location().page() == 1 and it.location().position().y > cutoff and it.location().position().y < cutoff+10pt {
    pagebreak()
  }
}

#outline()

= Test1
= Test2
= Test3
= Test4
= Test5
= Test6
= Test7
= Test8

2 Likes

It requires a little bit of teiking by specifying the cutof but it works :heart: even though typst through out some warnings

Actually, I think I have a cleaner solution. You can add a floating element at the bottom of the page that takes up the space of the background table:

#set page(
  background: context {
    if here().page() != 1 { return }

    // on the first page, show the table
    show: pad.with(2cm)
    table(
      rows: (1fr, 3cm),
      columns: (1fr,),
      none,
      none,
    )
  }
)

// the paced element is also on the first page, at the bottom
// `clearance: 0pt` is so that the `v` element controls the height exactly
#place(bottom, float: true, clearance: 0pt, v(2.5cm))
#outline()

// (outline longer than one page)
#for i in range(1, 15) {
  [= #numbering("A", i)]
  for j in range(1, 4) {
    [= #numbering("A.1.", i, j)]

    [...]
  }
}

The result:

I have also suggested this trick for achieving balanced columns and avoiding misplaced footnotes before. It is generally useful when you have specific pages on which you need a different top and/or bottom margin without a pagebreak. You can even decrease the page’s margin by using a negative height. It doesn’t only work on the first page, but if you need it on other pages, you have to put the place element on that page, which can require fiddling.

3 Likes