#let path = sys.inputs.at("path", default: "menu.json") #let all-combos = sys.inputs.at("all-combos", default: "false") == "true" #let categories = sys.inputs.at("categories", default: "E,D,C,V").split(",") #let dark = sys.inputs.at("dark", default: "false") == "true" #let days = json(path) #let nbsp = sym.space.nobreak #let colors = if dark {( bg: rgb("#1f1f24"), text: white, categories: rgb("#70c947"), menu-sep: rgb("#ababab"), title-underline: rgb("#eb2324"), day-sep: rgb("#a3cf40") )} else {( bg: white, text: black, categories: rgb("#577F25"), menu-sep: rgb("#ababab"), title-underline: rgb("#e34243"), day-sep: rgb("#82BC00") )} #let width = if days.len() > 1 {20cm} else {10cm} #set page( margin: 0cm, height: auto, width: width, fill: colors.bg ) #set text( fill: colors.text ) #let parse-date(date-txt) = { let (year, month, day) = date-txt.split("-") return datetime(year: int(year), month: int(month), day: int(day)) } #let fmt-number( number, decimals: 2, decimal-sep: ".", thousands-sep: "'" ) = { let integer = calc.trunc(number) let decimal = calc.fract(number) let res = str(integer).clusters() .rev() .chunks(3) .map(c => c.join("")) .join(thousands-sep) .rev() if decimals != 0 { res += decimal-sep decimal = decimal * calc.pow(10, decimals) decimal = calc.round(decimal) decimal = str(decimal) + ("0" * decimals) res += decimal.slice(0, decimals) } return res } #let day-names = ( "Lundi", "Mardi", "Mercredi", "Jeudi", "Vendredi", "Samedi", "Dimanche" ) #let display-menus( days, categories, day-sep-stroke: colors.day-sep + 1pt, title-underline-stroke: colors.title-underline + 2pt, menu-sep-stroke: colors.menu-sep + 1pt, price-categ-color: colors.categories ) = { set text(font: "Liberation Sans", hyphenate: true) let cols = calc.min(2, days.len()) let cells = () for day in days { let date = parse-date(day.date) let weekday = date.weekday() - 1 let day-name = day-names.at(weekday) let menus = () for menu in day.menus { let price-cell = grid.cell(rowspan: 2)[] let prices = menu.at( "prices", default: (:) ) if menu.keys().contains("price") { prices = ("": menu.price) } let pairs = prices.pairs() .filter(p => p.first() in categories) .sorted(key: p => p.last()) let prices = pairs.map(p => { let categ = emph( text( fill: price-categ-color, p.first() ) ) let price = fmt-number(p.last()) categ + ":" + nbsp + "CHF" + nbsp + price }) price-cell = grid.cell( rowspan: menu.extra.len() + 1, stack(dir: ttb, spacing: .4em, ..prices) ) //let price = "CHF" + sym.space.nobreak + str(menu.price) menus.push( grid( columns: (1fr, auto), column-gutter: .4em, row-gutter: .4em, [*#menu.name*], price-cell, ..menu.extra.map(l => text(10pt, l)) ) ) } let title = align( center, stack( dir: ttb, spacing: .5em, text(size: 14pt, weight: "bold", day-name), text(size: 10pt, date.display("[day].[month]")) ) ) let title-box = box( width: 50%, inset: .6em, stroke: (bottom: title-underline-stroke), title ) let title-cell = grid.cell(align: center, title-box) cells.push(box(width: 100%, grid( columns: (100%), inset: (x, y) => ( top: if y == 0 {0pt} else {.8em}, bottom: if y == 0 {0pt} else {2em} ), stroke: (_, y) => (top: if y < 2 {none} else {menu-sep-stroke}), title-cell, ..menus ))) } if calc.rem(cells.len(), 2) == 1 { cells.last() = grid.cell(colspan: calc.min(2, cols), cells.last()) } grid( columns: (100% / cols,) * cols, inset: (x: 1.5em, y: .8em), stroke: (x, y) => { let borders = (:) if x != 0 { borders.insert("left", day-sep-stroke) } if y != 0 { borders.insert("top", day-sep-stroke) } return borders }, ..cells ) } #{ if all-combos { let n-combos = calc.pow(2, categories.len()) for i in range(n-combos) { let filtered-categories = categories.enumerate().filter(p => { let (j, categ) = p return i.bit-rshift(j).bit-and(1) != 0 }).map(p => p.last()) display-menus(days, filtered-categories) pagebreak(weak: true) } } else { display-menus(days, categories) } }