After pages created with a loop, how to place things on those pages?

Hello everyone,

After seeing my pupils before their exams, I decided to create specific paper (based on the ones used during the exams) for them to use during my classes.

Since the layout is the same for all the pages except the first one, I used a loop.

This way I can print copies looking like I want, and it trains them to respect a certain way of doing things during exams.

But I also wanted to use those during fast exams. And I usually put the problems or questions with just the right amount of lines under for them to use.

Here is what I wrote :

#set page(
  paper: "a4",
  margin: (x: 1cm, y: 1cm),
  flipped: false
)
#set text(lang:"fr", font: "Learning", size: 11pt,)
// =============================================================

#import "@preview/showybox:2.0.4": showybox
#import "@preview/cetz:0.3.4"
#import "@preview/cetz-plot:0.1.1"


// =============================================================
#show heading.where( level: 1 ): set text( font: "KG Second Chances Sketch", weight: "bold", size: 36pt,)
#show heading.where( level: 1 ): set block(above:0pt,below: 24pt)
#show heading.where( level: 2 ): set text(font: "Sketch Match", weight: "bold", size: 24pt,)  
#show heading.where( level: 2 ): set block(above:12pt,below: 6pt)
#show heading.where( level: 3): set text(font: "School Book New", weight: "bold", size: 14pt,)
#show math.equation: set text(font: "STIX Two Math")

// =============================================================


//==============================================================================
// Configuration Echelle
//==============================================================================
#let echelle = 1
//==============================================================================
// Configuration du graphique
//==============================================================================
#let config = (
  x_min: -10,
  y_min: -10,
  x_max: 10,
  y_max: 10,
  unite: 1cm,
  
  grid_stroke: (paint: gray, thickness: .4pt * echelle),
  axis_stroke: (paint: black, thickness: 1.6pt * echelle )
)
#showybox(
  
  
  frame: (
    body-color: gray.lighten(85%),
    thickness: 0.5pt,radius: (top-left: 0pt, bottom-right: 0pt, rest: 0pt), body-inset :1em
  )
)[
NOM : #box(width: 1fr, line(length: 80%, stroke: (paint: gray, thickness: 1pt, dash: "densely-dotted"))) \
PRENOM : #box(width: 1fr, line(length: 79%, stroke: (paint: gray, thickness: 1pt, dash: "densely-dotted"))) \
CLASSE : #box(width: 1fr, line(length: 79%, stroke: (paint: gray, thickness: 1pt, dash: "densely-dotted")))
#align(center)[=== Mathématiques]
#place(float:true, bottom + left,rect([
  #align(top)[Appréciation :]
  #align(bottom + right)[PPO : #h(.8cm)]
  ],stroke:.8pt,height:2cm,width:100%,radius:(top-left: 0pt,bottom-right: 0pt)))

#place(horizon + right, [
  #rect(
    stroke: 0.8pt,
    height: 2cm,
    width: 2cm,
    radius: (top-left: 0pt, bottom-right: 0pt),
    inset:0cm
  )[
    #place(clearance: 0em,
      line(
        start: (0cm, 2cm),
        end: (2cm , 0cm),
        stroke: 0.8pt
      )
    )
  ]
])
]

#cetz.canvas(
  length: 1cm,
  {
    import cetz.draw: *
    import cetz-plot: *
    
    scale(echelle)
  line((0,0),(0,-21.6),stroke:(none))
  line((3,0),(3,-21.6),stroke:(black))
  line((19,0),(19,-21.6),stroke:(black))  
  
  
  for y in range(28) {  
    line((3, -y * 0.8), (19, -y * 0.8), stroke: (paint: gray, thickness: .8pt, dash: "densely-dotted"))
  }
  
  }
)
#place(
  bottom + right,
  rect(
    fill:white,
    [
      #align(top + center)[#text(8pt,[Pages / Nombre Total de pages])]
      #align(bottom + center)[#text(18pt,[..... / .....])]
      ],
  stroke:.8pt,
  height:1.6cm,
  width:4.2cm,
  radius:(top-left: 0pt,bottom-right: 0pt)))

#place(
  top+left,
  dx:3.5cm,
  dy:6.5cm,
  [
  #rect(
    fill:white,
    stroke: (paint: gray, thickness: 1pt),
    height: 3cm,
    width: 15cm,
    [
      Essai
    ]
)])

#for i in range(3){
pagebreak()
v(.5cm)
cetz.canvas(
  length: 1cm,
  {
    import cetz.draw: *
    import cetz-plot: *
    
  scale(echelle)
  line((0,0),(0,-.8 * 34),stroke:(none))
  line((3,0),(3,-.8 * 34),stroke:(black))
  line((19,0),(19,-.8 * 34),stroke:(black)) 
  
  
  for y in range(35) {  
    line((3, -y * 0.8), (19, -y * 0.8), stroke: (paint: gray, thickness: .8pt, dash: "densely-dotted"))
  }

  }
)
place(
  bottom + right,
  rect(
    fill:white,
    [
      #align(top + center)[#text(8pt,[Pages / Nombre Total de pages])]
      #align(bottom + center)[#text(18pt,[..... / .....])]
      ],
  stroke:.8pt,
  height:1.6cm,
  width:4.2cm,
  radius:(top-left: 0pt,bottom-right: 0pt)))
}
#place(
  top+left,
  dx:3.5cm,
  dy:6.5cm,
  [
  #rect(
    fill:white,
    stroke: (paint: gray, thickness: 1pt),
    height: 3cm,
    width: 15cm,
    [
      Essai
    ]
)])

As you can see, I put a rectangle that I intend to use for my exercices on the first page, and another one, but only on the last page.

I would like to be able to place those rectangles wherever I want.

Can someone help me ?

Thank you ;)

I think, create some functions that you use later to build the structure of the document. You could insert those boxes with ‘Essai’ as it is now, just by adding them at the right iteration count in the loop, but that doesn’t make anything easier in the end.

I would want to use the following structure, three functions:

new-page()   // creates a new page 
question()   // places a question
lines()    // places lines for writing

I’ve written out a sketch with the beginning of those functions below, so that it’s more clear. These are not intended to be perfect, you can do that if you like this system. You can even call those in a loop if you want to, but if you have custom questions on each page you would not really need to, I think.

I’ve also shown how you can use pad to consistently add padding in one place to the whole page. Then you don’t copy the same absolute coordinates multiple places.

Hope it helps.

code
#set page(
  paper: "a4",
  margin: (x: 1cm, y: 1cm),
  flipped: false
)
#set text(lang:"fr", font: "Learning", size: 11pt,)
// =============================================================

#import "@preview/showybox:2.0.4": showybox
#import "@preview/cetz:0.3.4"
#import "@preview/cetz-plot:0.1.1"


// =============================================================
#show heading.where( level: 1 ): set text( font: "KG Second Chances Sketch", weight: "bold", size: 36pt,)
#show heading.where( level: 1 ): set block(above:0pt,below: 24pt)
#show heading.where( level: 2 ): set text(font: "Sketch Match", weight: "bold", size: 24pt,)  
#show heading.where( level: 2 ): set block(above:12pt,below: 6pt)
#show heading.where( level: 3): set text(font: "School Book New", weight: "bold", size: 14pt,)
#show math.equation: set text(font: "STIX Two Math")

// =============================================================


//==============================================================================
// Configuration Echelle
//==============================================================================
#let echelle = 1
//==============================================================================
// Configuration du graphique
//==============================================================================
#let config = (
  x_min: -10,
  y_min: -10,
  x_max: 10,
  y_max: 10,
  unite: 1cm,
  
  grid_stroke: (paint: gray, thickness: .4pt * echelle),
  axis_stroke: (paint: black, thickness: 1.6pt * echelle )
)
#showybox(
  
  
  frame: (
    body-color: gray.lighten(85%),
    thickness: 0.5pt,radius: (top-left: 0pt, bottom-right: 0pt, rest: 0pt), body-inset :1em
  )
)[
NOM : #box(width: 1fr, line(length: 80%, stroke: (paint: gray, thickness: 1pt, dash: "densely-dotted"))) \
PRENOM : #box(width: 1fr, line(length: 79%, stroke: (paint: gray, thickness: 1pt, dash: "densely-dotted"))) \
CLASSE : #box(width: 1fr, line(length: 79%, stroke: (paint: gray, thickness: 1pt, dash: "densely-dotted")))
#align(center)[=== Mathématiques]
#place(float:true, bottom + left,rect([
  #align(top)[Appréciation :]
  #align(bottom + right)[PPO : #h(.8cm)]
  ],stroke:.8pt,height:2cm,width:100%,radius:(top-left: 0pt,bottom-right: 0pt)))

#place(horizon + right, [
  #rect(
    stroke: 0.8pt,
    height: 2cm,
    width: 2cm,
    radius: (top-left: 0pt, bottom-right: 0pt),
    inset:0cm
  )[
    #place(clearance: 0em,
      line(
        start: (0cm, 2cm),
        end: (2cm , 0cm),
        stroke: 0.8pt
      )
    )
  ]
])
]


#let new-page(body) = {
  show: page
  {
    show: pad.with(left: 3.5cm, top: 0.5cm)

    body
  }
  
  place(
    bottom + right,
    rect(
      fill:white,
      [
        #align(top + center)[#text(8pt,[Pages / Nombre Total de pages])]
        #align(bottom + center)[#text(18pt,[..... / .....])]
        ],
    stroke:.8pt,
    height:1.6cm,
    width:4.2cm,
    radius:(top-left: 0pt,bottom-right: 0pt)))
}

#let question(body) = {
  rect(
    fill:white,
    stroke: (paint: gray, thickness: 1pt),
    height: 3cm,
    width: 15cm,
    body)
}

#let lines(lines: 25) = {
  cetz.canvas(
  length: 1cm,
  {
    import cetz.draw: *
    import cetz-plot: *
    
    scale(echelle)
    line((0,0),(0,-.8 * (lines - 1)),stroke:(none))
    line((0,0),(0,-.8 * (lines - 1)),stroke:(black))
    line((16,0),(16,-.8 * (lines - 1)),stroke:(black)) 
    
    
    for y in range(lines) {  
      line((0, -y * 0.8), (16, -y * 0.8), stroke: (paint: gray, thickness: .8pt, dash: "densely-dotted"))
    }

  }
)
}

#new-page({
  question[Q1]
  lines(lines: 10)
  question[Q2]
  lines(lines: 15)
})

#new-page({
  question[Q3]
  lines(lines: 30)
})


1 Like

Hello.

Your code sample is very big, it is very hard to understand what is going on, and which part are relevant.

You don’t take advantage of the scripting, by duplicating the same parts several times, which in this case makes source file very big and hard to modify very quickly.

I fully agree with @bluss, that stuff needs to be split into building blocks, to help the file size, readability, maintainability, etc.

It seems that some stuff looks wrong, but you didn’t say if it is, or is everything is perfect.

Here is how I understand it supposed to look like, and how smallest possible code sample can look like:

#import "@preview/showybox:2.0.4": showybox
#import "@preview/cetz:0.3.4"

#set page(margin: 1cm)
#set text(11pt, lang: "fr", font: "Learning")
#show heading.where(level: 3): set text(14pt, font: "School Book New")

#let card = showybox(
  frame: (
    body-color: gray.lighten(85%),
    thickness: 0.5pt,
    radius: 0pt,
    body-inset: 1em,
  ),
  {
    let dotted-line = box(width: 1fr, line(length: 100%, stroke: (
      paint: gray,
      dash: "densely-dotted",
    )))
    block(width: 81%)[
      NOM : #dotted-line \
      PRENOM : #dotted-line \
      CLASSE : #dotted-line \
    ]
    align(center)[=== Mathématiques]
    set line(stroke: 0.8pt)
    set square(stroke: 0.8pt)
    set rect(stroke: 0.8pt)
    let diagonal-line = line(angle: 45deg, length: 100% * calc.sqrt(2))
    place(top + right, square(size: 2cm, inset: 0cm, diagonal-line))
    v(0.5em)
    rect(width: 100%, height: 2cm, {
      align(top)[Appréciation :]
      align(bottom + right)[PPO : #h(8mm)]
    })
  },
)

#let guide-lines(n, width: 16) = {
  show: place.with(bottom + right)
  cetz.canvas(length: 1cm, {
    import cetz.draw: *
    line((0, 0), (0, -0.8 * (n - 1)))
    line((width, 0), (width, -0.8 * (n - 1)))
    let stroke = (paint: gray, thickness: 0.8pt, dash: "densely-dotted")
    for y in range(n) {
      line((0, -y * 0.8), (width, -y * 0.8), stroke: stroke)
    }
  })
}

#let page-box = {
  show: place.with(bottom + right)
  show: rect.with(fill: white, stroke: 0.8pt, height: 1.6cm, width: 4.2cm)
  set align(center)
  text(8pt)[Pages / Nombre Total de pages]
  v(1fr)
  text(18pt)[#"....." / #"....."]
}

#let a-box(dx: 3.5cm, dy: 6.5cm, width: 15cm, height: 3cm, body) = {
  show: place.with(top + left, dx: dx, dy: dy)
  show: rect.with(height: height, width: width, stroke: gray, fill: white)
  body
}

#let the-box = a-box[Essai]

#let a-page(lines: 35, first: false, ..body) = {
  if not first { pagebreak() }
  guide-lines(lines)
  page-box
  if body.pos().len() != 0 { body.pos().first() }
}

#card

#a-page(lines: 28, first: true)[
  #the-box
]

// #range(2).map(_ => a-page()).join()
#for _ in range(2) [#a-page()]

#a-page[
  #a-box(dy: 2cm)[Something else]
  #a-box(dy: 50% - 1.5cm - 5pt)[#lorem(50)]
]

1 Like

Thank you very much.

It’s true that my code is not efficient, I recently started TYPST so I try a lot of things.

I used your proposition and it’s working.
Before that I created functions with :

  1. My page (and the lines)
  2. The rectangle with the exercices.

Here it is :


//==============================================================================
// Configuration Fonction Page
//==============================================================================

#let pagebac()={
pagebreak()
v(.5cm)
cetz.canvas(
  length: 1cm,
  {
    import cetz.draw: *
    import cetz-plot: *
    
  scale(echelle)
  line((0,0),(0,-27.2),stroke:(none))
  line((3,0),(3,-27.2),stroke:(black))
  line((19,0),(19,-27.2),stroke:(black))


  for y in range(35) {
    line((3, -y * 0.8), (19, -y * 0.8), stroke: (paint: gray, thickness: .8pt, dash: "densely-dotted"))
  }

  }
)
place(
  bottom + right,
  rect(
    fill:white,
    [
      #align(top + center)[#text(8pt,[Pages / Nombre Total de pages])]
      #align(bottom + center)[#text(18pt,[..... / .....])]
      ],
  stroke:.8pt,
  height:1.6cm,
  width:4.2cm,
  radius:(top-left: 0pt,bottom-right: 0pt)))
}
//==============================================================================
// Configuration Fonction Exo
//==============================================================================

#let nouvel_exo(
  vertical:content,
  contenu:content,
)={
  place(
  top+left,
  dx:3.5cm,
  dy:vertical,
  [
  #rect(
    fill:white,
    stroke: (paint: gray, thickness: 1pt),
    height: auto,
    width: 15cm,
    [
      #contenu
    ]
)])
}

In spite of that, do you think it’s possible to access the pages created in my loop (In order to write something on them )?

The code about headings and config doesn’t do anything, therefore it’s irrelevant and shouldn’t be included.

No. You either predefine on which page what should go, and then use the loop counter to access that stuff, or a state hack, so that you can define the content at any point.

#let page-content = state("page-content", (:))
#let add-on-page(n, body) = page-content.update(dict => {
  dict += (str(n): dict.at(str(n), default: ()) + (body,))
  dict
})

#let make-page(first: false) = context {
  if not first { pagebreak() }
  block[Some page stuff]
  let content = page-content.final().at(str(here().page()), default: none)
  if content == none { return }
  for content in content {
    list.item(content)
  }
}

#make-page(first: true)

#for _ in range(2) [#make-page()]

#add-on-page(2)[this]
#add-on-page(2)[that]
#add-on-page(1)[Forgot add this one too]
1 Like