362 lines
7.2 KiB
Typst
Executable File

/// Typst handout library, used for all documents in this repository.
/// If false, hide instructor info.
///
/// Compile with the following command to hide solutions:
/// `typst compile main.typ --input show_solutions=false`
///
/// Solutions are shown by default. This behavior
/// is less surprising than hiding content by default.
#let show_solutions = {
if "show_solutions" in sys.inputs {
// Show solutions unless they're explicitly disabled
not (
sys.inputs.show_solutions == "false" or sys.inputs.show_solutions == "no"
)
} else {
// Show solutions by default
true
}
}
// Colors
#let ored = rgb("D62121")
#let oorange = rgb("#ffaa3b")
#let ogrape = rgb("9C36B5")
#let ocyan = rgb("2288BF")
#let oteal = rgb("12B886")
#let ogreen = rgb("37B26D")
#let oblue = rgb("1C7ED6")
//
// MARK: header
//
#let make_title(
group,
quarter,
title,
subtitle,
) = {
align(
center,
block(
width: 60%,
height: auto,
breakable: false,
align(
center,
stack(
spacing: 7pt,
(
text(size: 10pt, group) + h(1fr) + text(size: 10pt, quarter)
),
line(length: 100%, stroke: 0.2mm),
(
text(size: 20pt, title) + linebreak() + text(size: 10pt, subtitle)
),
line(length: 100%, stroke: 0.2mm),
),
),
),
)
}
#let warn = {
set text(ored)
align(
center,
block(
width: 60%,
height: auto,
breakable: false,
fill: rgb(255, 255, 255),
stroke: ored + 2pt,
inset: 3mm,
(
align(center, text(weight: "bold", size: 12pt, [Instructor's Handout]))
+ parbreak()
+ align(
left,
text(
size: 10pt,
[This handout contains solutions and notes.]
+ linebreak()
+ [Recompile without solutions before distributing.],
),
)
),
),
)
}
#let preparedby(name) = (
text(
size: 10pt,
[Prepared by ]
+ name
+ [ on ]
+ datetime
.today()
.display("[month repr:long] [day padding:none], [year]"),
)
)
//
// MARK: Solutions
//
#let solution(content) = {
if show_solutions {
align(
center,
stack(
block(
width: 100%,
breakable: false,
fill: ored,
stroke: ored + 2pt,
inset: 1.5mm,
(
align(left, text(fill: white, weight: "bold", [Solution:]))
),
),
block(
width: 100%,
height: auto,
breakable: false,
fill: ored.lighten(80%).desaturate(10%),
stroke: ored + 2pt,
inset: 3mm,
align(left, content),
),
),
)
}
}
#let if_solutions(content) = {
if show_solutions { content }
}
#let if_no_solutions(content) = {
if not show_solutions { content }
}
//
// MARK: Sections
//
/// Core render code for all objects (problems, theorems, etc)
/// This should never be used directly by client code.
///
/// Args:
/// - kind: the kind of object to make ("Problem", "Definition", etc)
/// - label_name: a string. If provided, generate metadata for this object
/// under the given label. Labels must be unique, and are prefixed with `obj`
/// This label can then be used to reference this object.
///
/// For example:
/// ```
/// #problem(label: "problem1")
/// This is @obj:problem1
/// ```
#let _obj_base(kind, ..args, label_name: none) = {
counter("obj").step()
let n = context counter("obj").get().first()
// The complete title text of this object,
// like "Problem 5:" or "Theorem: "
let obj_content = if args.pos().len() == 0 {
[#kind #n:]
} else {
[#kind #n: #args.pos().at(0)]
}
// Render the object
block(
above: 8mm,
below: 2mm,
text(weight: "bold", obj_content),
)
// Generate labeled metadata for this object.
//
// This can be viewed directly with `#context query(<label>).first().value`,
// Or referenced with `@label` (we define a custom renderer for this metadata later)
if label_name != none {
let label_name = "obj:" + label_name
let meta = (
"obj_meta_ref_kind": kind,
// "obj_content": obj_content,
"label": label(label_name),
"counter": counter("obj"),
)
[ #metadata(meta) #label(label_name) ]
}
}
// `ref` implementation for object meta-references,
// see `show: ref`.
#let _ref_obj(it) = {
let magic_key = "obj_meta_ref_kind"
if not (
it.element != none
and it.element.has("value")
and type(it.element.value) == "dictionary"
and it.element.value.keys().contains(magic_key)
) {
// This label is not attached to object metadata
return none
}
let v = it.element.value
let obj_type = v.at(magic_key)
// The value of this object's counter at its label
let obj_count = v.counter.at(v.label).first()
// Produces text like "Problem 2",
// which takes you to the referenced object when clicked.
link(v.label, [#obj_type #obj_count])
}
/// Factory function for objects.
/// Provided for convenience, lets us define objects in one line.
#let _mkobj(kind) = {
let out(..args, label: none) = _obj_base(
kind,
..args,
label_name: label,
)
return out
}
#let problem = _mkobj("Problem")
#let definition = _mkobj("Definition")
#let theorem = _mkobj("Theorem")
//
// MARK: Misc
//
#let note(content, type: none) = {
if type != none {
text(fill: rgb(100, 100, 100), style: "oblique", [#type: ])
}
text(fill: rgb(100, 100, 100), content)
}
#let hint(content) = {
note(content, type: "Hint")
}
#let examplesolution(content) = {
let c = oblue
align(
center,
stack(
block(
width: 100%,
breakable: false,
fill: c,
stroke: c + 2pt,
inset: 1.5mm,
(
align(left, text(fill: white, weight: "bold", [Example solution:]))
),
),
block(
width: 100%,
height: auto,
breakable: false,
fill: c.lighten(80%).desaturate(10%),
stroke: c + 2pt,
inset: 3mm,
align(left, content),
),
),
)
}
//
// MARK: wrapper
//
#let handout(
doc,
group: none,
quarter: none,
title: none,
by: none,
subtitle: none,
) = {
set par(leading: 0.55em, first-line-indent: 0mm, justify: true)
set text(font: "New Computer Modern")
set par(spacing: 0.5em)
show list: set block(spacing: 0.5em, below: 1em)
set heading(numbering: (..nums) => nums.pos().at(0))
set page(
margin: 20mm,
width: 8in,
height: 11.5in,
footer: align(
center,
context counter(page).display(),
),
footer-descent: 5mm,
)
set list(
tight: false,
indent: 5mm,
spacing: 3mm,
)
show heading.where(level: 1): it => {
set align(center)
set text(weight: "bold")
block[
Section #counter(heading).display(): #text(it.body)
]
}
show ref: it => {
// Custom impl for object references
let x = _ref_obj(it)
if (x != none) { return x }
return it // Use default `ref` implementation
}
make_title(
group,
quarter,
title,
{
if by == none { none } else { [#preparedby(by)\ ] }
if subtitle == none { none } else { subtitle }
},
)
if show_solutions {
warn
}
doc
}