From 49ef6d2dfa96b5790a30fdf5148cd92e24420b86 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Thu, 4 Apr 2024 19:06:51 +0200 Subject: [PATCH 01/19] Add Ratio theme Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.md | 84 +++++ book/src/themes/gallery/ratio.typ | 120 +++++++ themes/ratio.typ | 545 ++++++++++++++++++++++++++++++ themes/themes.typ | 1 + utils/utils.typ | 172 ++++++---- 5 files changed, 859 insertions(+), 63 deletions(-) create mode 100644 book/src/themes/gallery/ratio.md create mode 100644 book/src/themes/gallery/ratio.typ create mode 100644 themes/ratio.typ diff --git a/book/src/themes/gallery/ratio.md b/book/src/themes/gallery/ratio.md new file mode 100644 index 0000000..fba3aab --- /dev/null +++ b/book/src/themes/gallery/ratio.md @@ -0,0 +1,84 @@ +# Ratio theme + +![ratio](ratio.png) + +This theme is inspired by the Singapore beamer theme. It has a more modern look, but still features Singapore's navigation sections and sub-section circles. + +Use it via + +```typ +{{#include ../../IMPORT.typ}} +#import themes.ratio: * + +#show: ratio-theme.with( + title: [Ratio theme], + abstract: [A theme about navigation and customization], + authors: (author("The Author", "Typst Community", "foo@bar.quux"),), + navigation-text: (fill: palette.secondary-200, size: 0.4em), + version: "1.0.0", + date: datetime(year: 2024, month: 4, day: 4), +) +``` + +By default it already generates a cover page and sets some document attributes based on your settings. + +`ratio` uses polylux' section handling, the regular `#outline()` will not work +properly, use `#polylux-outline` instead. + +## Options for initialization + +The theme is highly customizable! If there's something you don't like or want to tweak, you probably can. + +### The basics + +- `title`: Presentation title content +- `abstract`: An abstract or subtitle for your work. Can be none to disable. +- `authors`: An array of objects made with the custom `author(name:, affiliation:, email: )` method. +- `date`: A datetime object, defaults to today. +- `keywords`: Output document keywords to set. +- `version`: A document version to display. Something like "Draft" or "1.0". + +### The tweaks + +- `style-headings`: Enable/disable any styling applied to headings. +- `style-links`: Enable/disable any styling applied to links. +- `header`: What to display as the header ("navigation", "progress", content, or none). +- `footer`: What to display as the footer ("navigation", "progress", content, or none). +- `title-background-color`: Tweak the background color for the coming title pages. +- `title-text`: Styling to apply to the entire title page's text. +- `link-color`: The color to apply to the link anchor. +- `heading-color`: The color to apply to headings. +- `stroke-color`: The color to apply to strokes such as in tables. +- `fill-color`: The color to apply in fills such as in code blocks. +- `navigation-bar-color`: Navigation background color. +- `navigation-text`: Navigation bar text options for all text. +- `navigation-text-past`: Navigation bar text overrides for past sections. +- `navigation-text-current`: Navigation bar text overrides for the current section. +- `navigation-text-future`: Navigation bar text overrides for future sections. +- `navigation-shape-past`: Navigation bar shape for past subsections. +- `navigation-shape-current`: Navigation bar shape for current subsections. +- `navigation-shape-future`: Navigation bar shape for future subsections. +- `progress-bar-height`: Progress bar height. +- `progress-bar-color`: Progress bar background color. +- `progress-overlay-color`: Progress bar overlay color. +- `progress-text-color`: Progress bar text color. + +## Additional features + +We all know that themes and styles work all of the time 99% of the time. +Ergo, if you would like to (temporarily) turn off the "link anchor" that `ratio` creates, you can do so: + +```typ +#register-options((style-links: false)) +``` + +Any of the initialization options works this way! + +## Example code + +The image at the top is created by the following code: + +```typ +{{#include ../../IMPORT.typ}} +{{#include ratio.typ:3:}} +``` diff --git a/book/src/themes/gallery/ratio.typ b/book/src/themes/gallery/ratio.typ new file mode 100644 index 0000000..a1d623a --- /dev/null +++ b/book/src/themes/gallery/ratio.typ @@ -0,0 +1,120 @@ +#import "../../../../polylux.typ": * + +#import themes.ratio: * + +#show: ratio-theme.with( + title: [Ratio theme], + abstract: [A theme about navigation and customization], + authors: (author("Theme Author", "Typst Community", "foo@bar.quux"),), + navigation-text: (fill: palette.secondary-200, size: 0.4em), + version: "1.0.0", + date: datetime(year: 2024, month: 4, day: 4), +) + +#slide[ + #block(width: 100%, height: 100%)[ + #place(horizon + center)[= Welcome!] + ] +] + +#slide[ +== Overrides + +You can override almost any setting of the theme as you go and switch back +later. + +By default link styling is on, but let's force it. + +```typ +#register-options((style-links: true)) +``` + +#register-options((style-links: true)) +Results in: #link("https://github.com")[GitHub] + +I would like a non-styled link now... + +```typ +#register-options((style-links: false)) +``` + +#register-options((style-links: false)) +Results in: #link("https: //github.com")[GitHub] + +Feels good right! No lock in. +] + +#slide[ + = Customizations + + Some random text at the top of the page to check the margins for non-headers. + + == So much text + + #lorem(50) +] + +#slide[ + == Important subsection here! + + - Test an unordered list + + As well as some enumerations + + I'm second! + - More list + + + Followed by more enum at top level +] + +#slide[ + = Navigation + Have you noticed the navigation at the top? + + You can press the main section titles, or just one of the subsection dots. + + == Subsections become dots + + == So there's two dots for "Navigation"! + + And they're both alight since we're on this page. +] + +#slide[ + And the last subsection is still active because we haven't registered a new + section yet! +] + +#slide[ +You can also manually register a new section using: + +```typ +#utils.register-section("Hello world!") +#utils.register-subsection("With a subsec.") +``` + +#utils.register-section("Hello world!") +#utils.register-subsection("With a subsec.") +] + +#slide[ += Overrides + +We all know that layouts work all of the time 99% of the time. + +To turn off or change some of Ratio's slides on those occasions, you can use: + +```typ +// Next title slide will feature a green background. +#register-options((title-background-color: color.hsl(green))) + +// Let's add some raw content this time. We can place it anywhere! +#let raw = align(horizon + center, "raw.") +#title-slide(title: "Green", register-section: true, content: raw) +``` +] + +// Next title slide will feature a green background. +#register-options((title-background-color: color.hsl(green))) + +// Let's add some raw content this time. We can place it anywhere! +#let raw = align(horizon + center, "raw.") +#title-slide(title: "Green", register-section: true, content: raw) diff --git a/themes/ratio.typ b/themes/ratio.typ new file mode 100644 index 0000000..0356ee7 --- /dev/null +++ b/themes/ratio.typ @@ -0,0 +1,545 @@ +// Ratio theme +// +// A highly customizable theme inspired by old Beamer theme Singapore with it's section +// names at the top. + +#import "../logic.typ" +#import "../utils/utils.typ" + +#let hsl = color.hsl +#let transparent = rgb(0, 0, 0, 0) + +#let ratio-options = state("ratio-options", (:)) + +#let register-options(options) = { + ratio-options.update(s => { + s = s + options + s + }) +} + +// A default color palette to pull from. +#let palette = ( + primary-900: hsl(rgb("#1f4ac3")), + primary-800: hsl(rgb("#2c57ce")), + primary-700: hsl(rgb("#3963d9")), + primary-600: hsl(rgb("#4370ec")), + primary-500: hsl(rgb("#4d7cfe")), + primary-400: hsl(rgb("#6890fe")), + primary-300: hsl(rgb("#82a3fe")), + primary-200: hsl(rgb("#82a3fe")), + primary-100: hsl(rgb("#a6beff")), + primary-50: hsl(rgb("#eaefff")), + secondary-900: hsl(rgb("#0f0f25")), + secondary-800: hsl(rgb("#171731")), + secondary-700: hsl(rgb("#1e1e3d")), + secondary-600: hsl(rgb("#232345")), + secondary-500: hsl(rgb("#28284d")), + secondary-400: hsl(rgb("#38385b")), + secondary-300: hsl(rgb("#484868")), + secondary-200: hsl(rgb("#848499")), + secondary-100: hsl(rgb("#bfbfca")), + secondary-50: hsl(rgb("#e5e5ea")), + contrast: white, + success: hsl(rgb("#8bc34a")), + warning: hsl(rgb("#ff9800")), + danger: hsl(rgb("#f44336")), + error: hsl(rgb("#f44336")), + info: hsl(rgb("#4d7cfe")), + cat-0: hsl(rgb("#e58606")), + cat-1: hsl(rgb("#5d69b1")), + cat-2: hsl(rgb("#52bca3")), + cat-3: hsl(rgb("#99c945")), + cat-4: hsl(rgb("#cc61b0")), + cat-5: hsl(rgb("#24796c")), + cat-6: hsl(rgb("#daa51b")), + cat-7: hsl(rgb("#2f8ac4")), + cat-8: hsl(rgb("#764e9f")), + cat-9: hsl(rgb("#ed645a")), + cat-10: hsl(rgb("#a5aa99")), +) + +// Create a Ratio style author entry. +#let author(name, affiliation, email) = { (name: name, affiliation: affiliation, email: email) } + +// Ratio style title slide. +#let title-slide( + // Slide title. + title: none, + // Document authors/presenters. + authors: none, + // Presentation abstract or subtitle. + abstract: none, + // Presentation date. Set to none to hide. + date: none, + // Presentation version. Set to none to hide. + version: none, + // Raw content to show (in front of regular title page content). + content: none, + // Whether to register this slide title as a section. + register-section: false, +) = { + let content = context { + let options = ratio-options.get() + let fill = options.at("title-background-color", default: palette.secondary-800) + set text(fill: options.at("title-text-color", default: palette.contrast)) + if fill != none { + // Build the background. + place(block(width: 100%, height: 100%, fill: fill)) + // The left triangle. + place(left + top, polygon( + fill: fill.lighten(20%).transparentize(50%), + (0%, 100%), + (25%, 80%), + (0%, 70%), + )) + // The bottom triangle. + place(left + top, polygon( + fill: fill.lighten(20%).transparentize(20%), + (0%, 100%), + (25%, 80%), + (75%, 100%), + )) + // The large one on the right. + place(left + top, polygon( + fill: fill.lighten(20%).transparentize(85%), + (0%, 100%), + (100%, 100%), + (100%, 20%), + )) + } + + // The textual content. + place( + left + horizon, + block( + width: 100%, + inset: 15%, + )[ + #let v-space = v(1.5em, weak: true) + #set text( + ..options.at("title-text", default: (size: 20pt, fill: palette.contrast)), + ) + #if title != none { + text( + ..options.at("title-heading-text", default: (size: 3em, weight: "bold")), + )[#title] + if register-section { + utils.register-section(title) + } + } + + #if authors != none and authors.len() > 0 { + set text(..options.at("title-author-text", default: (:))) + for author in utils.as_array(authors) { + v-space + let name = author.at("name", default: none) + let email = author.at("email", default: none) + let affiliation = author.at("affiliation", default: none) + if email == none { + name + } else { + link("mailto:" + email)[#name] + } + if affiliation != none { + linebreak() + text( + ..options.at("title-affiliation-text", default: (size: 0.8em, weight: "light")), + affiliation, + ) + } + } + } + + #if abstract != none { + v-space + let abstract = text(..options.at("title-abstract-text", default: (:)), abstract) + par(leading: 0.78em, justify: true, linebreaks: "optimized")[#abstract] + } + + #if date != none or version != none { + v-space + set text( + ..options.at("title-version-text", default: (size: 0.8em, weight: "light")), + ) + if date != none { + text(date.display()) + } + if date != none and version != none { + h(1.6pt) + text("|") + h(1.6pt) + } + if version != none { + text(version) + } + } + ], + ) + + // Raw content if any. + if content != none { + place(left + top, block(width: 100%, height: 100%)[#content]) + } + } + logic.polylux-slide(content) +} + +// Ratio style section navigation. +#let navigation() = { + locate( + loc => { + // Get the variables at this stage or final. + let options = ratio-options.at(loc) + let page = loc.page() + let secs = utils.sections-state.final() + let subs = utils.subsections-state.final() + + set text(..options.navigation-text) + + // Precalculate when sections and subsections end. + let sec_ends = { + if secs.len() > 1 { + secs.slice(1).map(s => s.loc.page()) + } else { + () + } + } + sec_ends.push(none) + + let sub_ends = { + subs.enumerate().map(((idx, subs)) => { + let ends = if subs.len() > 1 { + subs.slice(1).map(s => s.loc.page()) + } else { + () + } + // Last one ends at next section. + ends.push(sec_ends.at(idx, default: none)) + ends + }) + } + + // The main headings. + let sec_displays = secs.zip(sec_ends).map(((sec, end)) => { + link(sec.loc)[ + #if page < sec.loc.page() { + text(..options.navigation-text-future, sec.body) + } else { + if page == sec.loc.page() or (end != none and page < end) { + text(..options.navigation-text-current, sec.body) + } else { + text(..options.navigation-text-past, sec.body) + } + } + ] + }) + + // Subsection shapes. + let columns = if subs.len() > 0 { + subs.len() + } else { + 1 + } + let sub_displays = subs.zip(sub_ends).map( + ((subs, ends)) => { + pad( + x: .5em, + grid(columns: columns, gutter: .5em, ..subs.zip(ends).map(((sub, end)) => { + link(sub.loc)[ + #if page < sub.loc.page() { + options.navigation-shape-future + } else { + if page == sub.loc.page() or (end != none and page < end) { + options.navigation-shape-current + } else { + options.navigation-shape-past + } + } + ] + })), + ) + }, + ) + + // Combine into a block that fills the header. + block(fill: options.navigation-bar-color, width: 100%, align(horizon, { + pad(x: 0.8em, y: 0.4em, grid( + columns: range(sec_displays.len()).map(_ => 1fr), + gutter: .4em, + ..sec_displays, + ..sub_displays, + )) + })) + }, + ) +} + +// Ratio style footer. +#let progress() = { + locate( + loc => { + let options = ratio-options.at(loc) + let current = loc.page() + let total = counter(page).final().first() + block( + fill: options.at("progress-bar-color", default: palette.secondary-50), + width: 100%, + height: options.at("progress-bar-height", default: 5pt), + place( + left + horizon, + utils.polylux-progress( + ratio => block( + fill: options.at("progress-overlay-color", default: palette.secondary-100), + width: ratio * 100%, + height: 100%, + ), + ), + ), + ) + }, + ) +} + +#let ratio-bar(kind) = { + if kind == "navigation" { + navigation() + } else if kind == "progress" { + progress() + } else if kind == none { + [] + } else { + kind + } +} + +#let ratio-header() = { + context ratio-bar(ratio-options.get().at("header", default: none)) +} + +#let ratio-footer() = { + context ratio-bar(ratio-options.get().at("footer", default: none)) +} + +// Ratio style slide. +#let slide(title: none, body) = { + let content = { + if title != none { + heading(level: 1, title) + } + pad(left: 1em, right: 2em, body) + } + let header = ratio-header() + let footer = ratio-footer() + logic.polylux-slide(grid( + columns: 1, + gutter: 1em, + rows: (auto, 1fr, auto), + ..(header, content, footer), + )) +} + +#let anchored(body, color: palette.primary-500) = { + body + h(0.05em) + super(box(height: 0.7em, circle(radius: 0.15em, stroke: 0.08em + color))) +} + +// The Ratio theme function that sets up all styling at once. +#let ratio-theme( + // Presentation aspect ratio. + aspect-ratio: "16-9", + // Whether to include the default cover page. + cover: true, + // Presentation title. + title: [Presentation title], + // An abstract for your work. Can be omitted if you don't have one. + abstract: lorem(30), + // Presentation authors/presenters. + authors: ( + author("Jane Doe", "Foo Ltd.", "jane.doe@foo.ltd"), + author("Foo Bar", "Quux Co.", "foo.bar@quux.co"), + ), + // Date that will be displayed on cover page. + // The value needs to be of the 'datetime' type. + // More info: https://typst.app/docs/reference/foundations/datetime/ + // Example: datetime(year: 2024, month: 03, day: 17) + date: datetime.today(), + // Document keywords to set. + keywords: (), + // The version of your work. + version: "Draft", + // Whether to apply some heading styling. + style-headings: true, + // Whether to apply the custom link style. + style-links: true, + // What to show in the header ("navigation", "progress", none). + header: "navigation", + // What to show in the footer ("navigation", "progress", none). + footer: "progress", + // Title background color. + title-background-color: palette.secondary-800, + // Title text style. + title-text: (size: 20pt, fill: palette.contrast), + // Title text heading overrides. + title-heading-text: (size: 3em, weight: "bold"), + // Title author text heading overrides. + title-author-text: (:), + // Title affiliation text overrides. + title-affiliation-text: (size: 0.8em, weight: "light"), + // Title abstract text overrides. + title-abstract-text: (:), + // Title date and version text override. + title-version-text: (size: 0.8em, weight: "light"), + // Color for external link anchors. + link-color: palette.primary-500, + // Header color. + heading-color: palette.secondary-800, + // Stroke color for tables and such. + stroke-color: palette.secondary-100, + // Fill color for code blocks and such. + fill-color: palette.secondary-50, + // Navigation background color. + navigation-bar-color: palette.secondary-50, + // Navigation text options for all text. + navigation-text: (fill: palette.secondary-200, size: 0.7em), + // Navigation text overrides for past sections. + navigation-text-past: (weight: "thin"), + // Navigation text overrides for the current section. + navigation-text-current: (:), + // Navigation text overrides for future sections. + navigation-text-future: (weight: "thin"), + // Navigation shape for past subsections. + navigation-shape-past: box(height: 3.8pt, circle( + radius: 1.7pt, + fill: palette.secondary-100, + stroke: 0.7pt + palette.secondary-100, + )), + // Navigation shape for current subsections. + navigation-shape-current: box(height: 3.8pt, circle( + radius: 1.7pt, + fill: palette.primary-500, + stroke: 0.7pt + palette.primary-500, + )), + // Navigation shape for future subsections. + navigation-shape-future: box( + height: 3.8pt, + circle(radius: 1.7pt, stroke: 0.7pt + palette.secondary-100), + ), + // Progress bar height. + progress-bar-height: 5pt, + // Progress bar background color. + progress-bar-color: palette.secondary-50, + // Progress bar overlay color. + progress-overlay-color: palette.secondary-100, + // Progress bar text color. + progress-text-color: palette.secondary-200, + // Presentation contents. + body, +) = { + // Set document properties. + set document( + title: title, + author: authors.first().name, + date: date, + keywords: keywords, + ) + + set page( + paper: "presentation-" + aspect-ratio, + margin: 0em, + header: none, + footer: none, + ) + + register-options(( + style-headings: style-headings, + style-links: style-links, + header: header, + footer: footer, + title-background-color: title-background-color, + title-text: title-text, + title-heading-text: title-heading-text, + title-author-text: title-author-text, + title-affiliation-text: title-affiliation-text, + title-abstract-text: title-abstract-text, + title-version-text: title-version-text, + link-color: link-color, + heading-color: heading-color, + stroke-color: stroke-color, + fill-color: fill-color, + navigation-bar-color: navigation-bar-color, + navigation-text: navigation-text, + navigation-text-past: navigation-text-past, + navigation-text-current: navigation-text-current, + navigation-text-future: navigation-text-future, + navigation-shape-past: navigation-shape-past, + navigation-shape-current: navigation-shape-current, + navigation-shape-future: navigation-shape-future, + progress-bar-height: progress-bar-height, + progress-bar-color: progress-bar-color, + progress-overlay-color: progress-overlay-color, + progress-text-color: progress-text-color, + )) + + // Text setup. + set par(leading: 0.7em, justify: true, linebreaks: "optimized") + show par: set block(spacing: 1.35em) + set text(font: "Cantarell", 18pt) + + // Heading setup. + show heading: it => { + // Register sections and subsections. + if it.depth == 1 { + locate(loc => { + utils.register-section(it.body) + }) + } else if it.depth == 2 { + locate(loc => { + utils.register-subsection(it.body) + }) + } + context { + let options = ratio-options.get() + if options.at("style-headings", default: false) { + let fill = options.at("heading-color", default: text.fill) + // Do not hyphenate headings. + text(fill: fill, hyphenate: false)[#it] + } else { + it + } + } + } + + // Style links if set. + show link: it => { + context { + let options = ratio-options.get() + if options.at("style-links", default: false) { + // Don't style for internal links. + if type(it.dest) == label or type(it.dest) == location { + return it + } + let color = options.at("link-color", default: palette.primary-500) + anchored(it) + } else { + it + } + } + } + + // Title slide if set. + if cover { + title-slide( + title: title, + authors: authors, + abstract: abstract, + date: date, + version: version, + register-section: false, // Don't register the cover. + ) + } + + // Presentation contents. + body +} diff --git a/themes/themes.typ b/themes/themes.typ index c3ef0ed..ef42539 100644 --- a/themes/themes.typ +++ b/themes/themes.typ @@ -3,3 +3,4 @@ #import "bipartite.typ" #import "university.typ" #import "metropolis.typ" +#import "ratio.typ" diff --git a/utils/utils.typ b/utils/utils.typ index ba2690b..2c3226b 100644 --- a/utils/utils.typ +++ b/utils/utils.typ @@ -5,14 +5,30 @@ // SECTIONS #let sections-state = state("polylux-sections", ()) -#let register-section(name) = locate( loc => { - sections-state.update(sections => { - sections.push((body: name, loc: loc)) - sections +#let subsections-state = state("polylux-subsections", ()) + +#let register-section(name) = locate(loc => { + sections-state.update(s => { + s.push((body: name, loc: loc)) + s + }) + subsections-state.update(s => { + s.push(()) + s + }) +}) + +#let register-subsection(name) = locate(loc => { + subsections-state.update(s => { + if s.len() == 0 { + s.push(()) + } + s.last().push((body: name, loc: loc)) + s }) }) -#let current-section = locate( loc => { +#let current-section = locate(loc => { let sections = sections-state.at(loc) if sections.len() > 0 { sections.last().body @@ -21,25 +37,39 @@ } }) -#let polylux-outline(enum-args: (:), padding: 0pt) = locate( loc => { +#let current-subsection = locate(loc => { + let subsections = subsections-state.at(loc) + if subsections.len() > 0 { + let arr = subsections.last() + if arr.len() > 0 { + arr.last().body + } else { + [] + } + } else { + [] + } +}) + +#let polylux-outline(enum-args: (:), padding: 0pt) = locate(loc => { let sections = sections-state.final(loc) pad(padding, enum( ..enum-args, - ..sections.map(section => link(section.loc, section.body)) + ..sections.map(section => link(section.loc, section.body)), )) }) - // PROGRESS -#let polylux-progress(ratio-to-content) = locate( loc => { - let ratio = logic.logical-slide.at(loc).first() / logic.logical-slide.final(loc).first() - ratio-to-content(ratio) -}) +#let polylux-progress(ratio-to-content) = locate( + loc => { + let ratio = logic.logical-slide.at(loc).first() / logic.logical-slide.final(loc).first() + ratio-to-content(ratio) + }, +) #let last-slide-number = locate(loc => logic.logical-slide.final(loc).first()) - // HEIGHT FITTING #let _size-to-pt(size, styles, container-dimension) = { @@ -78,65 +108,81 @@ hidden#after-label ] - locate(loc => { - let before = query(selector(before-label).before(loc), loc) - let before-pos = before.last().location().position() - let after = query(selector(after-label).before(loc), loc) - let after-pos = after.last().location().position() - - let available-height = after-pos.y - before-pos.y - - style(styles => { - layout(container-size => { - // Helper function to more easily grab absolute units - let get-pts(body, w-or-h) = { - let dim = if w-or-h == "w" {container-size.width} else {container-size.height} - _size-to-pt(body, styles, dim) - } - - // Provide a sensible initial width, which will define initial scale parameters. - // Note this is different from the post-scale width, which is a limiting factor - // on the allowable scaling ratio - let boxed-content = _limit-content-width( - width: prescale-width, body, container-size, styles - ) - - // post-scaling width - let mutable-width = width - if width == none { - mutable-width = container-size.width - } - mutable-width = get-pts(mutable-width, "w") - - let size = measure(boxed-content, styles) - let h-ratio = available-height / size.height - let w-ratio = mutable-width / size.width - let ratio = calc.min(h-ratio, w-ratio) * 100% - - let new-width = size.width * ratio - v(-available-height) - // If not boxed, the content can overflow to the next page even though it will fit. - // This is because scale doesn't update the layout information. - // Boxing in a container without clipping will inform typst that content - // will indeed fit in the remaining space - box( - width: new-width, - height: available-height, - scale(x: ratio, y: ratio, origin: top + left, boxed-content) - ) - }) - }) - }) + locate( + loc => { + let before = query(selector(before-label).before(loc), loc) + let before-pos = before.last().location().position() + let after = query(selector(after-label).before(loc), loc) + let after-pos = after.last().location().position() + + let available-height = after-pos.y - before-pos.y + + style( + styles => { + layout( + container-size => { + // Helper function to more easily grab absolute units + let get-pts(body, w-or-h) = { + let dim = if w-or-h == "w" { container-size.width } else { container-size.height } + _size-to-pt(body, styles, dim) + } + + // Provide a sensible initial width, which will define initial scale parameters. + // Note this is different from the post-scale width, which is a limiting factor + // on the allowable scaling ratio + let boxed-content = _limit-content-width(width: prescale-width, body, container-size, styles) + + // post-scaling width + let mutable-width = width + if width == none { + mutable-width = container-size.width + } + mutable-width = get-pts(mutable-width, "w") + + let size = measure(boxed-content, styles) + let h-ratio = available-height / size.height + let w-ratio = mutable-width / size.width + let ratio = calc.min(h-ratio, w-ratio) * 100% + + let new-width = size.width * ratio + v(-available-height) + // If not boxed, the content can overflow to the next page even though it will fit. + // This is because scale doesn't update the layout information. + // Boxing in a container without clipping will inform typst that content + // will indeed fit in the remaining space + box( + width: new-width, + height: available-height, + scale(x: ratio, y: ratio, origin: top + left, boxed-content), + ) + }, + ) + }, + ) + }, + ) } // SIDE BY SIDE #let side-by-side(columns: none, gutter: 1em, ..bodies) = { let bodies = bodies.pos() - let columns = if columns == none { (1fr,) * bodies.len() } else { columns } + let columns = if columns == none { (1fr,) * bodies.len() } else { columns } if columns.len() != bodies.len() { panic("number of columns must match number of content arguments") } grid(columns: columns, gutter: gutter, ..bodies) } + +// HELPERS + +// Guaranteed array helper. Users often supply a single argument to something that +// should be an array. +#let as_array(value) = { + if type(value) == array { + value + } else { + (value,) + } +} From 8728f646de8074348d2627607f887d9c3f11048b Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Thu, 4 Apr 2024 19:13:23 +0200 Subject: [PATCH 02/19] Add Ratio to summary Signed-off-by: Tiemen Schuijbroek --- book/src/SUMMARY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/book/src/SUMMARY.md b/book/src/SUMMARY.md index 8987306..de3deba 100644 --- a/book/src/SUMMARY.md +++ b/book/src/SUMMARY.md @@ -21,6 +21,7 @@ - [Metropolis](./themes/gallery/metropolis.md) - [University](./themes/gallery/university.md) - [Bipartite](./themes/gallery/bipartite.md) + - [Ratio](./themes/gallery/ratio.md) - [Build your own](./themes/your-own.md) - [Utilities](./utils/utils.md) - [Side by side](./utils/side-by-side.md) From 181e1546a9af732c5d6c9e011cbbb87228a66c12 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Thu, 4 Apr 2024 19:13:35 +0200 Subject: [PATCH 03/19] Add ratio to generate previews Signed-off-by: Tiemen Schuijbroek --- scripts/generate-previews.jl | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/generate-previews.jl b/scripts/generate-previews.jl index 51e1331..6f99479 100644 --- a/scripts/generate-previews.jl +++ b/scripts/generate-previews.jl @@ -38,14 +38,14 @@ function montage(imgs, annotation) annotation end - + a = min(max(ceil(Int, sqrt(n)), 3), n) b = ceil(Int, n / a) b, a = minmax(a, b) @assert a * b >= n idcs = CartesianIndices((1:a, 1:b)) dims = size(first(imgs)) .* (b, a) .* .6 |> reverse - + plt = plot( size = dims, background = :lightgray, @@ -147,6 +147,7 @@ generate_previews([ typ2png(path = gallery, file = "metropolis"), typ2png(path = gallery, file = "university"), typ2png(path = gallery, file = "bipartite"), + typ2png(path = gallery, file = "ratio"), typ2png(path = utils, file = "side-by-side"), typ2png(path = utils, file = "side-by-side-kwargs"), typ2png(path = utils, file = "fill-remaining"), From 13318f3ed2650db69c24d2fb9cc633daaaefe59a Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Thu, 4 Apr 2024 19:22:07 +0200 Subject: [PATCH 04/19] Slightly thicker navigation text Signed-off-by: Tiemen Schuijbroek --- themes/ratio.typ | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/themes/ratio.typ b/themes/ratio.typ index 0356ee7..bdadda1 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -373,9 +373,9 @@ style-headings: true, // Whether to apply the custom link style. style-links: true, - // What to show in the header ("navigation", "progress", none). + // What to show in the header ("navigation", "progress", content, none). header: "navigation", - // What to show in the footer ("navigation", "progress", none). + // What to show in the footer ("navigation", "progress", content, none). footer: "progress", // Title background color. title-background-color: palette.secondary-800, @@ -393,7 +393,7 @@ title-version-text: (size: 0.8em, weight: "light"), // Color for external link anchors. link-color: palette.primary-500, - // Header color. + // Heading color. heading-color: palette.secondary-800, // Stroke color for tables and such. stroke-color: palette.secondary-100, @@ -404,11 +404,11 @@ // Navigation text options for all text. navigation-text: (fill: palette.secondary-200, size: 0.7em), // Navigation text overrides for past sections. - navigation-text-past: (weight: "thin"), + navigation-text-past: (:), // Navigation text overrides for the current section. - navigation-text-current: (:), + navigation-text-current: (weight: "bold"), // Navigation text overrides for future sections. - navigation-text-future: (weight: "thin"), + navigation-text-future: (:), // Navigation shape for past subsections. navigation-shape-past: box(height: 3.8pt, circle( radius: 1.7pt, From 64975781060f20c1b0559b9e03140858ea7a90e0 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Thu, 4 Apr 2024 19:39:48 +0200 Subject: [PATCH 05/19] Add missing docs, move title arg into padded content Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.md | 28 ++++++++++++++++++++++++++-- themes/ratio.typ | 8 ++++---- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/book/src/themes/gallery/ratio.md b/book/src/themes/gallery/ratio.md index fba3aab..c1c7b36 100644 --- a/book/src/themes/gallery/ratio.md +++ b/book/src/themes/gallery/ratio.md @@ -29,14 +29,16 @@ properly, use `#polylux-outline` instead. The theme is highly customizable! If there's something you don't like or want to tweak, you probably can. -### The basics +### The essentials +- `aspect-ratio`: The aspect ratio (16-9 by default). +- `cover`: Wether to include the cover page. - `title`: Presentation title content - `abstract`: An abstract or subtitle for your work. Can be none to disable. - `authors`: An array of objects made with the custom `author(name:, affiliation:, email: )` method. - `date`: A datetime object, defaults to today. -- `keywords`: Output document keywords to set. - `version`: A document version to display. Something like "Draft" or "1.0". +- `keywords`: Output document keywords to set. ### The tweaks @@ -63,6 +65,25 @@ The theme is highly customizable! If there's something you don't like or want to - `progress-overlay-color`: Progress bar overlay color. - `progress-text-color`: Progress bar text color. +## Slide functions + +`ratio` provides the following custom slide functions: + +- `title-slide(title, authors, abstract, date, version, content, register-section)` with all keyword arguments: + + - `title`: Presentation title content. Default to none. + - `abstract`: An abstract or subtitle for your work. Defaults to none. + - `authors`: An array of objects made with the custom `author(name:, affiliation:, email: )` method. Defaults to none. + - `date`: A datetime object, defaults to none. + - `version`: A document version to display. Something like "Draft" or "1.0". Defaults to none. + - `content`: Raw content to include on the page. Useful if you don't like the default text layout. + - `register-section`: Whether the given title should be registered as a section in the outline and navigation. + +- `slide(title)[body]` with a title keyword argument and body positional. + + - `title`: Section title will be added as a heading, too. Mostly for compatibility purposes with other themes. + - `body`: Your content such that you can type `#slide[my foo is my bar]` + ## Additional features We all know that themes and styles work all of the time 99% of the time. @@ -74,6 +95,9 @@ Ergo, if you would like to (temporarily) turn off the "link anchor" that `ratio` Any of the initialization options works this way! +For your convenience, there's also a `palette` dictionary with a bunch of matching colors to choose +from. + ## Example code The image at the top is created by the following code: diff --git a/themes/ratio.typ b/themes/ratio.typ index bdadda1..002b0dc 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -323,12 +323,12 @@ // Ratio style slide. #let slide(title: none, body) = { - let content = { - if title != none { + let content = pad(left: 1em, right: 2em)[ + #if title != none { heading(level: 1, title) } - pad(left: 1em, right: 2em, body) - } + #body + ] let header = ratio-header() let footer = ratio-footer() logic.polylux-slide(grid( From 6d63225b45e0fd2ee0898ea92bb5dcff2144b3f4 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Fri, 5 Apr 2024 10:23:12 +0200 Subject: [PATCH 06/19] Update option handling Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.typ | 46 ++- themes/ratio.typ | 559 +++++++++++++++++------------- utils/utils.typ | 14 +- 3 files changed, 366 insertions(+), 253 deletions(-) diff --git a/book/src/themes/gallery/ratio.typ b/book/src/themes/gallery/ratio.typ index a1d623a..e789bfa 100644 --- a/book/src/themes/gallery/ratio.typ +++ b/book/src/themes/gallery/ratio.typ @@ -3,12 +3,14 @@ #import themes.ratio: * #show: ratio-theme.with( + aspect-ratio: "16-9", title: [Ratio theme], abstract: [A theme about navigation and customization], - authors: (author("Theme Author", "Typst Community", "foo@bar.quux"),), - navigation-text: (fill: palette.secondary-200, size: 0.4em), + authors: (ratio-author("Theme Author", "Typst Community", "foo@bar.quux"),), version: "1.0.0", date: datetime(year: 2024, month: 4, day: 4), + keywords: ("foo", "bar"), + options: (:), ) #slide[ @@ -98,23 +100,39 @@ You can also manually register a new section using: #slide[ = Overrides +#set text(size: 0.8em) We all know that layouts work all of the time 99% of the time. -To turn off or change some of Ratio's slides on those occasions, you can use: +The `update-options` function works by updating options (dictionaries) +recursively and thus only updates what you specify. + +The `register-options` function works by replacing values *completely*. + +There's a handy `palette` variable with a pre-configured color palette, but feel +free to bring your own! ```typ +// Only update the heading color, not it's size: +#update-options((title-text: (fill: palette.warning))) // <- Notice the palette! // Next title slide will feature a green background. -#register-options((title-background-color: color.hsl(green))) +#register-options((title-hero-color: color.hsl(green))) + +// Let's add some foreground and background content this time. We can place it anywhere! +#let fg = place(horizon + left, block(inset: 10%, width: 100%)[foreground.]) +#let bg = place( + horizon + left, + block(inset: 30%, width: 100%)[#text(weight: "bold")[background.]], +) +#title-slide(title: "Green", register-section: true, foreground: fg, background: bg) -// Let's add some raw content this time. We can place it anywhere! -#let raw = align(horizon + center, "raw.") -#title-slide(title: "Green", register-section: true, content: raw) +// This becomes...=> ``` ] - -// Next title slide will feature a green background. -#register-options((title-background-color: color.hsl(green))) - -// Let's add some raw content this time. We can place it anywhere! -#let raw = align(horizon + center, "raw.") -#title-slide(title: "Green", register-section: true, content: raw) +#update-options((title-text: (fill: palette.warning))) +#register-options((title-hero-color: color.hsl(green))) +#let fg = place(horizon + left, block(inset: 10%, width: 100%)[foreground.]) +#let bg = place( + horizon + left, + block(inset: 30%, width: 100%)[#text(weight: "bold")[background.]], +) +#title-slide(title: "Green", register-section: true, foreground: fg, background: bg) diff --git a/themes/ratio.typ b/themes/ratio.typ index 002b0dc..f55844c 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -1,24 +1,19 @@ // Ratio theme // -// A highly customizable theme inspired by old Beamer theme Singapore with it's section -// names at the top. +// A highly customizable theme inspired by old Beamer theme Singapore with +// the section names at the top. +// +// The theme's initialization function is at the bottom of this file. #import "../logic.typ" #import "../utils/utils.typ" +// GLOBAL HELPERS + #let hsl = color.hsl #let transparent = rgb(0, 0, 0, 0) -#let ratio-options = state("ratio-options", (:)) - -#let register-options(options) = { - ratio-options.update(s => { - s = s + options - s - }) -} - -// A default color palette to pull from. +// Ratio color palette for easy styling. #let palette = ( primary-900: hsl(rgb("#1f4ac3")), primary-800: hsl(rgb("#2c57ce")), @@ -59,10 +54,226 @@ cat-10: hsl(rgb("#a5aa99")), ) -// Create a Ratio style author entry. -#let author(name, affiliation, email) = { (name: name, affiliation: affiliation, email: email) } +// Create a Ratio theme author entry. +#let ratio-author(name, affiliation, email) = { (name: name, affiliation: affiliation, email: email) } + +// Ratio default options. +#let ratio-defaults = ( + // Presentation aspect ratio. + aspect-ratio: "16-9", + // Presentation title. + title: [Presentation title], + // An abstract for your work. Can be omitted if you don't have one. + abstract: lorem(30), + // Presentation authors/presenters. + authors: ( + ratio-author("Jane Doe", "Foo Ltd.", "jane.doe@foo.ltd"), + ratio-author("Foo Bar", "Quux Co.", "foo.bar@quux.co"), + ), + // Date that will be displayed on cover page. + // The value needs to be of the 'datetime' type. + // More info: https://typst.app/docs/reference/foundations/datetime/ + // Example: datetime(year: 2024, month: 03, day: 17) + date: datetime.today(), + // Document keywords to set. + keywords: (), + // The version of your work. + version: "Draft", + // Presentation author + // Whether to apply some heading styling. + style-headings: true, + // Whether to apply the custom link style. + style-links: true, + // Whether to apply some raw styling. + style-raw: true, + // What to show in the header ("navigation", "progress", content, none). + header: "navigation", + // What to show in the footer ("navigation", "progress", content, none). + footer: "progress", + // Title background color. + title-hero-color: palette.secondary-800, + // Title text style. + title-text: (size: 20pt, fill: palette.contrast), + // Title text heading overrides. + title-heading-text: (size: 3em, weight: "bold"), + // Title author text heading overrides. + title-author-text: (:), + // Title affiliation text overrides. + title-affiliation-text: (size: 0.8em, weight: "light"), + // Title abstract text overrides. + title-abstract-text: (:), + // Title date and version text override. + title-version-text: (size: 0.8em, weight: "light"), + // Color for external link anchors. + link-color: palette.primary-500, + // Heading color. + heading-text: (fill: palette.secondary-800), + // Stroke color for tables and such. + stroke-color: palette.secondary-100, + // Fill color for code blocks and such. + fill-color: palette.secondary-50, + // Navigation background color. + navigation-bar-color: palette.secondary-50, + // Navigation text options for all text. + navigation-text: (fill: palette.secondary-200, size: 0.7em), + // Navigation text overrides for past sections. + navigation-text-past: (:), + // Navigation text overrides for the current section. + navigation-text-current: (weight: "bold"), + // Navigation text overrides for future sections. + navigation-text-future: (:), + // Navigation shape for past subsections. + navigation-shape-past: box(height: 3.8pt, circle( + radius: 1.7pt, + fill: palette.secondary-100, + stroke: 0.7pt + palette.secondary-100, + )), + // Navigation shape for current subsections. + navigation-shape-current: box(height: 3.8pt, circle( + radius: 1.7pt, + fill: palette.primary-500, + stroke: 0.7pt + palette.primary-500, + )), + // Navigation shape for future subsections. + navigation-shape-future: box( + height: 3.8pt, + circle(radius: 1.7pt, stroke: 0.7pt + palette.secondary-100), + ), + // Progress bar height. + progress-bar-height: 5pt, + // Progress bar background color. + progress-bar-color: palette.secondary-50, + // Progress bar overlay color. + progress-overlay-color: palette.secondary-100, + // Progress bar text color. + progress-text-color: palette.secondary-200, +) + +// Variable to hold the options state. +#let ratio-options = state("ratio-options", ratio-defaults) + +// Register new options or replace existing keys. +#let register-options(options) = { + ratio-options.update(s => { + s = s + options + s + }) +} + +// Update options by replacing values and updating dictionaries. +#let update-options(options) = { + ratio-options.update(s => utils.update-dict(s, options)) +} + +// TITLE SLIDE + +// Ratio custom background image. +#let ratio-hero(fill: ratio-defaults.title-hero-color) = { + // Build the background. + place(block(width: 100%, height: 100%, fill: fill)) + // The left triangle. + place(left + top, polygon( + fill: fill.lighten(20%).transparentize(50%), + (0%, 100%), + (25%, 80%), + (0%, 70%), + )) + // The bottom triangle. + place(left + top, polygon( + fill: fill.lighten(20%).transparentize(20%), + (0%, 100%), + (25%, 80%), + (75%, 100%), + )) + // The large one on the right. + place(left + top, polygon( + fill: fill.lighten(20%).transparentize(85%), + (0%, 100%), + (100%, 100%), + (100%, 20%), + )) +} + +// Ratio title text content. Draws only from defaults, fully customizable. +#let ratio-title-content( + title: ratio-defaults.title, + authors: ratio-defaults.authors, + abstract: ratio-defaults.abstract, + date: ratio-defaults.date, + version: ratio-defaults.version, + keywords: ratio-defaults.keywords, + title-text: ratio-defaults.title-text, + heading-text: ratio-defaults.title-heading-text, + author-text: ratio-defaults.title-author-text, + affiliation-text: ratio-defaults.title-affiliation-text, + abstract-text: ratio-defaults.title-abstract-text, + version-text: ratio-defaults.title-version-text, + register-section: false, +) = { + let v-space = v(1.5em, weak: true) + set text(..title-text) + + if title != none { + text(..heading-text)[#title] + if register-section { + utils.register-section(title) + } + } + + if authors != none and authors.len() > 0 { + for author in utils.as-array(authors) { + v-space + let name = author.at("name", default: none) + let email = author.at("email", default: none) + let affiliation = author.at("affiliation", default: none) + + let author-text = text(..author-text)[#name] + if email == none { + author-text + } else { + link("mailto:" + email)[#author-text] + } + + if affiliation != none { + linebreak() + text(..affiliation-text)[#affiliation] + } + } + } + + if abstract != none { + v-space + set text(..abstract-text) + par(leading: 0.78em, justify: true, linebreaks: "optimized")[#abstract] + } + + let date-line = () + if date != none { + date-line.push(date.display()) + } + if version != none { + date-line.push(version) + } + if keywords != none { + let keywords = utils.as-array(keywords) + date-line.push(keywords.join(", ")) + } + + if date-line.len() > 0 { + v-space + let sep = [ + #h(1.6pt) + | + #h(1.6pt) + ] + set text(..version-text) + + date-line.join(sep) + } +} // Ratio style title slide. +// Draws options from context that are not in the parameter list. #let title-slide( // Slide title. title: none, @@ -74,118 +285,56 @@ date: none, // Presentation version. Set to none to hide. version: none, - // Raw content to show (in front of regular title page content). - content: none, + // Presentation keywords. Set to none to hide. + keywords: none, + // Foreground content to show (in front of regular title page content). + foreground: none, + // Background content to show (behind regular title page content). + background: none, + // Whether to draw the hero image. + hero: true, // Whether to register this slide title as a section. register-section: false, ) = { let content = context { let options = ratio-options.get() - let fill = options.at("title-background-color", default: palette.secondary-800) - set text(fill: options.at("title-text-color", default: palette.contrast)) - if fill != none { - // Build the background. - place(block(width: 100%, height: 100%, fill: fill)) - // The left triangle. - place(left + top, polygon( - fill: fill.lighten(20%).transparentize(50%), - (0%, 100%), - (25%, 80%), - (0%, 70%), - )) - // The bottom triangle. - place(left + top, polygon( - fill: fill.lighten(20%).transparentize(20%), - (0%, 100%), - (25%, 80%), - (75%, 100%), - )) - // The large one on the right. - place(left + top, polygon( - fill: fill.lighten(20%).transparentize(85%), - (0%, 100%), - (100%, 100%), - (100%, 20%), - )) - } - - // The textual content. - place( - left + horizon, - block( - width: 100%, - inset: 15%, - )[ - #let v-space = v(1.5em, weak: true) - #set text( - ..options.at("title-text", default: (size: 20pt, fill: palette.contrast)), - ) - #if title != none { - text( - ..options.at("title-heading-text", default: (size: 3em, weight: "bold")), - )[#title] - if register-section { - utils.register-section(title) - } - } - #if authors != none and authors.len() > 0 { - set text(..options.at("title-author-text", default: (:))) - for author in utils.as_array(authors) { - v-space - let name = author.at("name", default: none) - let email = author.at("email", default: none) - let affiliation = author.at("affiliation", default: none) - if email == none { - name - } else { - link("mailto:" + email)[#name] - } - if affiliation != none { - linebreak() - text( - ..options.at("title-affiliation-text", default: (size: 0.8em, weight: "light")), - affiliation, - ) - } - } - } - - #if abstract != none { - v-space - let abstract = text(..options.at("title-abstract-text", default: (:)), abstract) - par(leading: 0.78em, justify: true, linebreaks: "optimized")[#abstract] - } + if hero { + let fill = options.title-hero-color + if fill != none { + ratio-hero(fill: fill) + } + } - #if date != none or version != none { - v-space - set text( - ..options.at("title-version-text", default: (size: 0.8em, weight: "light")), - ) - if date != none { - text(date.display()) - } - if date != none and version != none { - h(1.6pt) - text("|") - h(1.6pt) - } - if version != none { - text(version) - } - } - ], - ) + if background != none { + background + } - // Raw content if any. - if content != none { - place(left + top, block(width: 100%, height: 100%)[#content]) + place(left + horizon, block(width: 100%, inset: 15%, ratio-title-content( + title: title, + authors: authors, + abstract: abstract, + date: date, + version: version, + keywords: keywords, + title-text: options.title-text, + heading-text: options.title-heading-text, + author-text: options.title-author-text, + affiliation-text: options.title-affiliation-text, + abstract-text: options.title-abstract-text, + version-text: options.title-version-text, + ))) + + if foreground != none { + foreground } } logic.polylux-slide(content) } -// Ratio style section navigation. +// CONTENT SLIDES + +// Ratio style section navigation bar. Draws everything from options. #let navigation() = { locate( loc => { @@ -275,32 +424,26 @@ ) } -// Ratio style footer. +// Ratio progress bar. Draws everything from options. #let progress() = { - locate( - loc => { - let options = ratio-options.at(loc) - let current = loc.page() - let total = counter(page).final().first() - block( - fill: options.at("progress-bar-color", default: palette.secondary-50), - width: 100%, - height: options.at("progress-bar-height", default: 5pt), - place( - left + horizon, - utils.polylux-progress( - ratio => block( - fill: options.at("progress-overlay-color", default: palette.secondary-100), - width: ratio * 100%, - height: 100%, - ), - ), - ), - ) - }, - ) + locate(loc => { + let options = ratio-options.at(loc) + let current = loc.page() + let total = counter(page).final().first() + block( + fill: options.progress-bar-color, + width: 100%, + height: options.progress-bar-height, + place(left + horizon, utils.polylux-progress(ratio => block( + fill: options.progress-overlay-color, + width: ratio * 100%, + height: 100%, + ))), + ) + }) } +// Ratio header or footer bar helper. #let ratio-bar(kind) = { if kind == "navigation" { navigation() @@ -313,10 +456,12 @@ } } +// Ratio header helper. #let ratio-header() = { context ratio-bar(ratio-options.get().at("header", default: none)) } +// Ratio footer helper. #let ratio-footer() = { context ratio-bar(ratio-options.get().at("footer", default: none)) } @@ -339,13 +484,20 @@ )) } -#let anchored(body, color: palette.primary-500) = { +// Draw a tiny anchor on the top right of the body text. +#let anchored(body, color: ratio-defaults.link-color) = { body h(0.05em) super(box(height: 0.7em, circle(radius: 0.15em, stroke: 0.08em + color))) } -// The Ratio theme function that sets up all styling at once. +// THEME + +// The Ratio theme function that sets up all styling and show rules once. +// Any provided options update the defaults by recursively updating the +// options dictionary. I.e. setting: `options: (title-text: (fill: red))` +// Only changes the title text's fill to red and maintains other options. +// You can view all defaults in the `ratio-defaults` variable. #let ratio-theme( // Presentation aspect ratio. aspect-ratio: "16-9", @@ -357,8 +509,8 @@ abstract: lorem(30), // Presentation authors/presenters. authors: ( - author("Jane Doe", "Foo Ltd.", "jane.doe@foo.ltd"), - author("Foo Bar", "Quux Co.", "foo.bar@quux.co"), + ratio-author("Jane Doe", "Foo Ltd.", "jane.doe@foo.ltd"), + ratio-author("Foo Bar", "Quux Co.", "foo.bar@quux.co"), ), // Date that will be displayed on cover page. // The value needs to be of the 'datetime' type. @@ -369,71 +521,8 @@ keywords: (), // The version of your work. version: "Draft", - // Whether to apply some heading styling. - style-headings: true, - // Whether to apply the custom link style. - style-links: true, - // What to show in the header ("navigation", "progress", content, none). - header: "navigation", - // What to show in the footer ("navigation", "progress", content, none). - footer: "progress", - // Title background color. - title-background-color: palette.secondary-800, - // Title text style. - title-text: (size: 20pt, fill: palette.contrast), - // Title text heading overrides. - title-heading-text: (size: 3em, weight: "bold"), - // Title author text heading overrides. - title-author-text: (:), - // Title affiliation text overrides. - title-affiliation-text: (size: 0.8em, weight: "light"), - // Title abstract text overrides. - title-abstract-text: (:), - // Title date and version text override. - title-version-text: (size: 0.8em, weight: "light"), - // Color for external link anchors. - link-color: palette.primary-500, - // Heading color. - heading-color: palette.secondary-800, - // Stroke color for tables and such. - stroke-color: palette.secondary-100, - // Fill color for code blocks and such. - fill-color: palette.secondary-50, - // Navigation background color. - navigation-bar-color: palette.secondary-50, - // Navigation text options for all text. - navigation-text: (fill: palette.secondary-200, size: 0.7em), - // Navigation text overrides for past sections. - navigation-text-past: (:), - // Navigation text overrides for the current section. - navigation-text-current: (weight: "bold"), - // Navigation text overrides for future sections. - navigation-text-future: (:), - // Navigation shape for past subsections. - navigation-shape-past: box(height: 3.8pt, circle( - radius: 1.7pt, - fill: palette.secondary-100, - stroke: 0.7pt + palette.secondary-100, - )), - // Navigation shape for current subsections. - navigation-shape-current: box(height: 3.8pt, circle( - radius: 1.7pt, - fill: palette.primary-500, - stroke: 0.7pt + palette.primary-500, - )), - // Navigation shape for future subsections. - navigation-shape-future: box( - height: 3.8pt, - circle(radius: 1.7pt, stroke: 0.7pt + palette.secondary-100), - ), - // Progress bar height. - progress-bar-height: 5pt, - // Progress bar background color. - progress-bar-color: palette.secondary-50, - // Progress bar overlay color. - progress-overlay-color: palette.secondary-100, - // Progress bar text color. - progress-text-color: palette.secondary-200, + // Ratio theme options. + options: (:), // Presentation contents. body, ) = { @@ -452,35 +541,17 @@ footer: none, ) - register-options(( - style-headings: style-headings, - style-links: style-links, - header: header, - footer: footer, - title-background-color: title-background-color, - title-text: title-text, - title-heading-text: title-heading-text, - title-author-text: title-author-text, - title-affiliation-text: title-affiliation-text, - title-abstract-text: title-abstract-text, - title-version-text: title-version-text, - link-color: link-color, - heading-color: heading-color, - stroke-color: stroke-color, - fill-color: fill-color, - navigation-bar-color: navigation-bar-color, - navigation-text: navigation-text, - navigation-text-past: navigation-text-past, - navigation-text-current: navigation-text-current, - navigation-text-future: navigation-text-future, - navigation-shape-past: navigation-shape-past, - navigation-shape-current: navigation-shape-current, - navigation-shape-future: navigation-shape-future, - progress-bar-height: progress-bar-height, - progress-bar-color: progress-bar-color, - progress-overlay-color: progress-overlay-color, - progress-text-color: progress-text-color, - )) + // Update all options. + let options = options + ( + aspect-ratio: aspect-ratio, + title: title, + abstract: abstract, + authors: authors, + date: date, + keywords: keywords, + version: version, + ) + update-options(options) // Text setup. set par(leading: 0.7em, justify: true, linebreaks: "optimized") @@ -501,10 +572,9 @@ } context { let options = ratio-options.get() - if options.at("style-headings", default: false) { - let fill = options.at("heading-color", default: text.fill) + if options.style-headings { // Do not hyphenate headings. - text(fill: fill, hyphenate: false)[#it] + text(hyphenate: false, ..options.heading-text)[#it] } else { it } @@ -515,7 +585,7 @@ show link: it => { context { let options = ratio-options.get() - if options.at("style-links", default: false) { + if options.style-links { // Don't style for internal links. if type(it.dest) == label or type(it.dest) == location { return it @@ -528,6 +598,18 @@ } } + // Set raw font to Fira Code if available. + show raw: it => { + context { + if ratio-options.get().style-raw { + set text(font: "Fira Code") + it + } else { + it + } + } + } + // Title slide if set. if cover { title-slide( @@ -536,6 +618,7 @@ abstract: abstract, date: date, version: version, + keywords: keywords, register-section: false, // Don't register the cover. ) } diff --git a/utils/utils.typ b/utils/utils.typ index 2c3226b..9c73b39 100644 --- a/utils/utils.typ +++ b/utils/utils.typ @@ -179,10 +179,22 @@ // Guaranteed array helper. Users often supply a single argument to something that // should be an array. -#let as_array(value) = { +#let as-array(value) = { if type(value) == array { value } else { (value,) } } + +// Recursively update a dictionary. +#let update-dict(dict, update) = { + for ((key, value)) in update.pairs() { + if type(value) == dictionary { + dict.insert(key, update-dict(dict.at(key, default: (:)), value)) + } else { + dict.insert(key, value) + } + } + dict +} From 3b3c0a57eaa01add5902f920d288c6f87de0ad4f Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Fri, 5 Apr 2024 11:12:03 +0200 Subject: [PATCH 07/19] Update option handling Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.md | 78 ++++++++++----- book/src/themes/gallery/ratio.typ | 16 ++-- themes/ratio.typ | 154 +++++++++++++++++------------- 3 files changed, 148 insertions(+), 100 deletions(-) diff --git a/book/src/themes/gallery/ratio.md b/book/src/themes/gallery/ratio.md index c1c7b36..da33253 100644 --- a/book/src/themes/gallery/ratio.md +++ b/book/src/themes/gallery/ratio.md @@ -11,16 +11,19 @@ Use it via #import themes.ratio: * #show: ratio-theme.with( + aspect-ratio: "16-9", title: [Ratio theme], abstract: [A theme about navigation and customization], - authors: (author("The Author", "Typst Community", "foo@bar.quux"),), - navigation-text: (fill: palette.secondary-200, size: 0.4em), + authors: (ratio-author("Theme Author", "Typst Community", "foo@bar.quux"),), version: "1.0.0", date: datetime(year: 2024, month: 4, day: 4), + keywords: ("foo", "bar"), + options: (:), ) ``` By default it already generates a cover page and sets some document attributes based on your settings. +You can disable this with `cover: false`. `ratio` uses polylux' section handling, the regular `#outline()` will not work properly, use `#polylux-outline` instead. @@ -31,25 +34,30 @@ The theme is highly customizable! If there's something you don't like or want to ### The essentials +The essentials are separate keyword arguments to the `ratio-theme` call. + - `aspect-ratio`: The aspect ratio (16-9 by default). - `cover`: Wether to include the cover page. - `title`: Presentation title content -- `abstract`: An abstract or subtitle for your work. Can be none to disable. +- `abstract`: An abstract or subtitle for your work. Set to `none` to disable. - `authors`: An array of objects made with the custom `author(name:, affiliation:, email: )` method. - `date`: A datetime object, defaults to today. - `version`: A document version to display. Something like "Draft" or "1.0". - `keywords`: Output document keywords to set. -### The tweaks +### The `options` tweaks + +The `options` keyword argument is special, it takes a dictionary with any of the following: - `style-headings`: Enable/disable any styling applied to headings. - `style-links`: Enable/disable any styling applied to links. -- `header`: What to display as the header ("navigation", "progress", content, or none). -- `footer`: What to display as the footer ("navigation", "progress", content, or none). +- `style-raw`: Enable/disable any styling applied to raw content. +- `header`: What to display as the header ("navigation", "progress", content, or `none`). +- `footer`: What to display as the footer ("navigation", "progress", content, or `none`). - `title-background-color`: Tweak the background color for the coming title pages. - `title-text`: Styling to apply to the entire title page's text. +- `heading-text`: The style to apply to heading text. - `link-color`: The color to apply to the link anchor. -- `heading-color`: The color to apply to headings. - `stroke-color`: The color to apply to strokes such as in tables. - `fill-color`: The color to apply in fills such as in code blocks. - `navigation-bar-color`: Navigation background color. @@ -69,34 +77,56 @@ The theme is highly customizable! If there's something you don't like or want to `ratio` provides the following custom slide functions: -- `title-slide(title, authors, abstract, date, version, content, register-section)` with all keyword arguments: - - - `title`: Presentation title content. Default to none. - - `abstract`: An abstract or subtitle for your work. Defaults to none. - - `authors`: An array of objects made with the custom `author(name:, affiliation:, email: )` method. Defaults to none. - - `date`: A datetime object, defaults to none. - - `version`: A document version to display. Something like "Draft" or "1.0". Defaults to none. - - `content`: Raw content to include on the page. Useful if you don't like the default text layout. +- `title-slide(title, authors, abstract, date, version, keywords, foreground, background, register-section)` with all keyword arguments: + - `title`: Presentation title content. Defaults to `none`. + - `abstract`: An abstract or subtitle for your work. Defaults to `none`. + - `authors`: An array of objects made with the custom `author(name:, affiliation:, email: )` method. Defaults to `none`. + - `date`: A datetime object, defaults to `none`. + - `version`: A document version to display on the date line. Something like "Draft" or "1.0". Defaults to `none`. + - `keywords`: Keywords to display on the date line. Defaults to (). + - `foreground`: Content to include in front of the default text content. + - `background`: Content to include behind the default text content. + - `hero`: Whether to draw the default hero image (uses `options.title-hero-color`). - `register-section`: Whether the given title should be registered as a section in the outline and navigation. - -- `slide(title)[body]` with a title keyword argument and body positional. - +- `slide(title, header, footer)[body]` with a title keyword argument and body positional. - `title`: Section title will be added as a heading, too. Mostly for compatibility purposes with other themes. + - `header`: Header content override. Default is `auto` which draws the header according to the theme options. Set it to `none` to disable for this slide. + - `footer`: Footer content override. Default is `auto` which draws the footer according to the theme options. Set it to `none` to disable for this slide. - `body`: Your content such that you can type `#slide[my foo is my bar]` +- `centered-slide(header, footer)[body]` + +- `bare-slide[body]`: For now just an alias of `polylux-slide` + +See the [Customization](#customization) feature for ways to change these on the fly! ## Additional features -We all know that themes and styles work all of the time 99% of the time. -Ergo, if you would like to (temporarily) turn off the "link anchor" that `ratio` creates, you can do so: +### Palette + +Do you like theme colors? Ratio includes a default palette to pull from in the `ratio-palette` variable. That should make it easier to style things! + +### Customization + +Ratio's key feature is customization. It stores all it's options in a `ratio-options` state variable. You probably won't interact with that variable directly, but its defaults are included as the `ratio-defaults` variable for you to inspect! ```typ -#register-options((style-links: false)) +// Careful, there are a lot of options! +#ratio-defaults ``` -Any of the initialization options works this way! +You can change the options in one of two ways: + +```typ +// Register: replaces the option values given in this dictionary completely. +// For example, this disables the link styling with the little anchor: +#register-options((style-links: false)) + +// Update: recursively updates any dictionaries (i.e. for `text()` related items). +// For example, this only replaces the title text's fill and leaves other options intact. +#update-options((title-text: (fill: ratio-palette.danger))) +``` -For your convenience, there's also a `palette` dictionary with a bunch of matching colors to choose -from. +You can put these overrides halfway through your document to change things on the fly! ## Example code diff --git a/book/src/themes/gallery/ratio.typ b/book/src/themes/gallery/ratio.typ index e789bfa..1a804c5 100644 --- a/book/src/themes/gallery/ratio.typ +++ b/book/src/themes/gallery/ratio.typ @@ -9,14 +9,12 @@ authors: (ratio-author("Theme Author", "Typst Community", "foo@bar.quux"),), version: "1.0.0", date: datetime(year: 2024, month: 4, day: 4), - keywords: ("foo", "bar"), + keywords: ("navigation", "customization"), options: (:), ) -#slide[ - #block(width: 100%, height: 100%)[ - #place(horizon + center)[= Welcome!] - ] +#center-slide[ + = Welcome! ] #slide[ @@ -108,12 +106,12 @@ recursively and thus only updates what you specify. The `register-options` function works by replacing values *completely*. -There's a handy `palette` variable with a pre-configured color palette, but feel -free to bring your own! +There's a handy `ratio-palette` variable with a pre-configured color palette, +but feel free to bring your own! ```typ // Only update the heading color, not it's size: -#update-options((title-text: (fill: palette.warning))) // <- Notice the palette! +#update-options((title-text: (fill: ratio-palette.warning))) // <- Notice the palette! // Next title slide will feature a green background. #register-options((title-hero-color: color.hsl(green))) @@ -128,7 +126,7 @@ free to bring your own! // This becomes...=> ``` ] -#update-options((title-text: (fill: palette.warning))) +#update-options((title-text: (fill: ratio-palette.warning))) #register-options((title-hero-color: color.hsl(green))) #let fg = place(horizon + left, block(inset: 10%, width: 100%)[foreground.]) #let bg = place( diff --git a/themes/ratio.typ b/themes/ratio.typ index f55844c..ba61401 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -10,48 +10,46 @@ // GLOBAL HELPERS -#let hsl = color.hsl -#let transparent = rgb(0, 0, 0, 0) - // Ratio color palette for easy styling. -#let palette = ( - primary-900: hsl(rgb("#1f4ac3")), - primary-800: hsl(rgb("#2c57ce")), - primary-700: hsl(rgb("#3963d9")), - primary-600: hsl(rgb("#4370ec")), - primary-500: hsl(rgb("#4d7cfe")), - primary-400: hsl(rgb("#6890fe")), - primary-300: hsl(rgb("#82a3fe")), - primary-200: hsl(rgb("#82a3fe")), - primary-100: hsl(rgb("#a6beff")), - primary-50: hsl(rgb("#eaefff")), - secondary-900: hsl(rgb("#0f0f25")), - secondary-800: hsl(rgb("#171731")), - secondary-700: hsl(rgb("#1e1e3d")), - secondary-600: hsl(rgb("#232345")), - secondary-500: hsl(rgb("#28284d")), - secondary-400: hsl(rgb("#38385b")), - secondary-300: hsl(rgb("#484868")), - secondary-200: hsl(rgb("#848499")), - secondary-100: hsl(rgb("#bfbfca")), - secondary-50: hsl(rgb("#e5e5ea")), +#let ratio-palette = ( + primary-900: color.hsl(rgb("#1f4ac3")), + primary-800: color.hsl(rgb("#2c57ce")), + primary-700: color.hsl(rgb("#3963d9")), + primary-600: color.hsl(rgb("#4370ec")), + primary-500: color.hsl(rgb("#4d7cfe")), + primary-400: color.hsl(rgb("#6890fe")), + primary-300: color.hsl(rgb("#82a3fe")), + primary-200: color.hsl(rgb("#82a3fe")), + primary-100: color.hsl(rgb("#a6beff")), + primary-50: color.hsl(rgb("#eaefff")), + secondary-900: color.hsl(rgb("#0f0f25")), + secondary-800: color.hsl(rgb("#171731")), + secondary-700: color.hsl(rgb("#1e1e3d")), + secondary-600: color.hsl(rgb("#232345")), + secondary-500: color.hsl(rgb("#28284d")), + secondary-400: color.hsl(rgb("#38385b")), + secondary-300: color.hsl(rgb("#484868")), + secondary-200: color.hsl(rgb("#848499")), + secondary-100: color.hsl(rgb("#bfbfca")), + secondary-50: color.hsl(rgb("#e5e5ea")), contrast: white, - success: hsl(rgb("#8bc34a")), - warning: hsl(rgb("#ff9800")), - danger: hsl(rgb("#f44336")), - error: hsl(rgb("#f44336")), - info: hsl(rgb("#4d7cfe")), - cat-0: hsl(rgb("#e58606")), - cat-1: hsl(rgb("#5d69b1")), - cat-2: hsl(rgb("#52bca3")), - cat-3: hsl(rgb("#99c945")), - cat-4: hsl(rgb("#cc61b0")), - cat-5: hsl(rgb("#24796c")), - cat-6: hsl(rgb("#daa51b")), - cat-7: hsl(rgb("#2f8ac4")), - cat-8: hsl(rgb("#764e9f")), - cat-9: hsl(rgb("#ed645a")), - cat-10: hsl(rgb("#a5aa99")), + success: color.hsl(rgb("#8bc34a")), + warning: color.hsl(rgb("#ff9800")), + danger: color.hsl(rgb("#f44336")), + error: color.hsl(rgb("#f44336")), + info: color.hsl(rgb("#4d7cfe")), + cat-0: color.hsl(rgb("#e58606")), + cat-1: color.hsl(rgb("#5d69b1")), + cat-2: color.hsl(rgb("#52bca3")), + cat-3: color.hsl(rgb("#99c945")), + cat-4: color.hsl(rgb("#cc61b0")), + cat-5: color.hsl(rgb("#24796c")), + cat-6: color.hsl(rgb("#daa51b")), + cat-7: color.hsl(rgb("#2f8ac4")), + cat-8: color.hsl(rgb("#764e9f")), + cat-9: color.hsl(rgb("#ed645a")), + cat-10: color.hsl(rgb("#a5aa99")), + transparent: color.hsl(rgb(0, 0, 0, 0)), ) // Create a Ratio theme author entry. @@ -91,9 +89,9 @@ // What to show in the footer ("navigation", "progress", content, none). footer: "progress", // Title background color. - title-hero-color: palette.secondary-800, + title-hero-color: ratio-palette.secondary-800, // Title text style. - title-text: (size: 20pt, fill: palette.contrast), + title-text: (size: 20pt, fill: ratio-palette.contrast), // Title text heading overrides. title-heading-text: (size: 3em, weight: "bold"), // Title author text heading overrides. @@ -105,17 +103,17 @@ // Title date and version text override. title-version-text: (size: 0.8em, weight: "light"), // Color for external link anchors. - link-color: palette.primary-500, + link-color: ratio-palette.primary-500, // Heading color. - heading-text: (fill: palette.secondary-800), + heading-text: (fill: ratio-palette.secondary-800), // Stroke color for tables and such. - stroke-color: palette.secondary-100, + stroke-color: ratio-palette.secondary-100, // Fill color for code blocks and such. - fill-color: palette.secondary-50, + fill-color: ratio-palette.secondary-50, // Navigation background color. - navigation-bar-color: palette.secondary-50, + navigation-bar-color: ratio-palette.secondary-50, // Navigation text options for all text. - navigation-text: (fill: palette.secondary-200, size: 0.7em), + navigation-text: (fill: ratio-palette.secondary-200, size: 0.5em), // Navigation text overrides for past sections. navigation-text-past: (:), // Navigation text overrides for the current section. @@ -125,28 +123,28 @@ // Navigation shape for past subsections. navigation-shape-past: box(height: 3.8pt, circle( radius: 1.7pt, - fill: palette.secondary-100, - stroke: 0.7pt + palette.secondary-100, + fill: ratio-palette.secondary-100, + stroke: 0.7pt + ratio-palette.secondary-100, )), // Navigation shape for current subsections. navigation-shape-current: box(height: 3.8pt, circle( radius: 1.7pt, - fill: palette.primary-500, - stroke: 0.7pt + palette.primary-500, + fill: ratio-palette.primary-500, + stroke: 0.7pt + ratio-palette.primary-500, )), // Navigation shape for future subsections. navigation-shape-future: box( height: 3.8pt, - circle(radius: 1.7pt, stroke: 0.7pt + palette.secondary-100), + circle(radius: 1.7pt, stroke: 0.7pt + ratio-palette.secondary-100), ), // Progress bar height. progress-bar-height: 5pt, // Progress bar background color. - progress-bar-color: palette.secondary-50, + progress-bar-color: ratio-palette.secondary-50, // Progress bar overlay color. - progress-overlay-color: palette.secondary-100, + progress-overlay-color: ratio-palette.secondary-100, // Progress bar text color. - progress-text-color: palette.secondary-200, + progress-text-color: ratio-palette.secondary-200, ) // Variable to hold the options state. @@ -165,6 +163,13 @@ ratio-options.update(s => utils.update-dict(s, options)) } +// Draw a tiny anchor on the top right of the body text. +#let ratio-anchor(body, color: ratio-defaults.link-color) = { + body + h(0.05em) + super(box(height: 0.7em, circle(radius: 0.15em, stroke: 0.08em + color))) +} + // TITLE SLIDE // Ratio custom background image. @@ -332,7 +337,7 @@ logic.polylux-slide(content) } -// CONTENT SLIDES +// CONTENT SLIDE HELPERS // Ratio style section navigation bar. Draws everything from options. #let navigation() = { @@ -466,16 +471,26 @@ context ratio-bar(ratio-options.get().at("footer", default: none)) } +// CONTENT SLIDES + // Ratio style slide. -#let slide(title: none, body) = { +#let slide(title: none, header: auto, footer: auto, body) = { let content = pad(left: 1em, right: 2em)[ #if title != none { heading(level: 1, title) } #body ] - let header = ratio-header() - let footer = ratio-footer() + let header = if header == auto { + ratio-header() + } else { + header + } + let footer = if footer == auto { + ratio-footer() + } else { + footer + } logic.polylux-slide(grid( columns: 1, gutter: 1em, @@ -484,13 +499,18 @@ )) } -// Draw a tiny anchor on the top right of the body text. -#let anchored(body, color: ratio-defaults.link-color) = { - body - h(0.05em) - super(box(height: 0.7em, circle(radius: 0.15em, stroke: 0.08em + color))) +// Ratio style centered slide. +#let center-slide(header: auto, footer: auto, body) = { + slide( + header: header, + footer: footer, + block(width: 100%, height: 100%, place(center + horizon, body)), + ) } +// Ratio style bare bones slide. +#let bare-slide = logic.polylux-slide + // THEME // The Ratio theme function that sets up all styling and show rules once. @@ -590,8 +610,8 @@ if type(it.dest) == label or type(it.dest) == location { return it } - let color = options.at("link-color", default: palette.primary-500) - anchored(it) + let color = options.at("link-color", default: ratio-palette.primary-500) + ratio-anchor(it) } else { it } From d16bcfdf89043bf5d013e6a1fd86fde183de10bb Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Fri, 5 Apr 2024 11:22:54 +0200 Subject: [PATCH 08/19] Namespace everything except slide functions Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.md | 4 ++-- book/src/themes/gallery/ratio.typ | 22 +++++++++++----------- themes/ratio.typ | 14 +++++++------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/book/src/themes/gallery/ratio.md b/book/src/themes/gallery/ratio.md index da33253..550787e 100644 --- a/book/src/themes/gallery/ratio.md +++ b/book/src/themes/gallery/ratio.md @@ -119,11 +119,11 @@ You can change the options in one of two ways: ```typ // Register: replaces the option values given in this dictionary completely. // For example, this disables the link styling with the little anchor: -#register-options((style-links: false)) +#ratio-register((style-links: false)) // Update: recursively updates any dictionaries (i.e. for `text()` related items). // For example, this only replaces the title text's fill and leaves other options intact. -#update-options((title-text: (fill: ratio-palette.danger))) +#ratio-update((title-text: (fill: ratio-palette.danger))) ``` You can put these overrides halfway through your document to change things on the fly! diff --git a/book/src/themes/gallery/ratio.typ b/book/src/themes/gallery/ratio.typ index 1a804c5..81df292 100644 --- a/book/src/themes/gallery/ratio.typ +++ b/book/src/themes/gallery/ratio.typ @@ -26,19 +26,19 @@ later. By default link styling is on, but let's force it. ```typ -#register-options((style-links: true)) +#ratio-register((style-links: true)) ``` -#register-options((style-links: true)) +#ratio-register((style-links: true)) Results in: #link("https://github.com")[GitHub] I would like a non-styled link now... ```typ -#register-options((style-links: false)) +#ratio-register((style-links: false)) ``` -#register-options((style-links: false)) +#ratio-register((style-links: false)) Results in: #link("https: //github.com")[GitHub] Feels good right! No lock in. @@ -101,19 +101,19 @@ You can also manually register a new section using: #set text(size: 0.8em) We all know that layouts work all of the time 99% of the time. -The `update-options` function works by updating options (dictionaries) -recursively and thus only updates what you specify. +The `ratio-update` function works by updating options (dictionaries) recursively +and thus only updates what you specify. -The `register-options` function works by replacing values *completely*. +The `ratio-register` function works by replacing values *completely*. There's a handy `ratio-palette` variable with a pre-configured color palette, but feel free to bring your own! ```typ // Only update the heading color, not it's size: -#update-options((title-text: (fill: ratio-palette.warning))) // <- Notice the palette! +#ratio-update((title-text: (fill: ratio-palette.warning))) // <- Notice the palette! // Next title slide will feature a green background. -#register-options((title-hero-color: color.hsl(green))) +#ratio-register((title-hero-color: color.hsl(green))) // Let's add some foreground and background content this time. We can place it anywhere! #let fg = place(horizon + left, block(inset: 10%, width: 100%)[foreground.]) @@ -126,8 +126,8 @@ but feel free to bring your own! // This becomes...=> ``` ] -#update-options((title-text: (fill: ratio-palette.warning))) -#register-options((title-hero-color: color.hsl(green))) +#ratio-update((title-text: (fill: ratio-palette.warning))) +#ratio-register((title-hero-color: color.hsl(green))) #let fg = place(horizon + left, block(inset: 10%, width: 100%)[foreground.]) #let bg = place( horizon + left, diff --git a/themes/ratio.typ b/themes/ratio.typ index ba61401..ecdf0f2 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -151,7 +151,7 @@ #let ratio-options = state("ratio-options", ratio-defaults) // Register new options or replace existing keys. -#let register-options(options) = { +#let ratio-register(options) = { ratio-options.update(s => { s = s + options s @@ -159,7 +159,7 @@ } // Update options by replacing values and updating dictionaries. -#let update-options(options) = { +#let ratio-update(options) = { ratio-options.update(s => utils.update-dict(s, options)) } @@ -340,7 +340,7 @@ // CONTENT SLIDE HELPERS // Ratio style section navigation bar. Draws everything from options. -#let navigation() = { +#let ratio-navigation() = { locate( loc => { // Get the variables at this stage or final. @@ -430,7 +430,7 @@ } // Ratio progress bar. Draws everything from options. -#let progress() = { +#let ratio-progress() = { locate(loc => { let options = ratio-options.at(loc) let current = loc.page() @@ -451,9 +451,9 @@ // Ratio header or footer bar helper. #let ratio-bar(kind) = { if kind == "navigation" { - navigation() + ratio-navigation() } else if kind == "progress" { - progress() + ratio-progress() } else if kind == none { [] } else { @@ -571,7 +571,7 @@ keywords: keywords, version: version, ) - update-options(options) + ratio-update(options) // Text setup. set par(leading: 0.7em, justify: true, linebreaks: "optimized") From 61dd501d3ccd8dcefbb68d19071a8a7374db787e Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Fri, 5 Apr 2024 11:24:49 +0200 Subject: [PATCH 09/19] Replace center-slide by centered-slide Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.typ | 2 +- themes/ratio.typ | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/book/src/themes/gallery/ratio.typ b/book/src/themes/gallery/ratio.typ index 81df292..e6f4f83 100644 --- a/book/src/themes/gallery/ratio.typ +++ b/book/src/themes/gallery/ratio.typ @@ -13,7 +13,7 @@ options: (:), ) -#center-slide[ +#centered-slide[ = Welcome! ] diff --git a/themes/ratio.typ b/themes/ratio.typ index ecdf0f2..40e74fd 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -500,7 +500,7 @@ } // Ratio style centered slide. -#let center-slide(header: auto, footer: auto, body) = { +#let centered-slide(header: auto, footer: auto, body) = { slide( header: header, footer: footer, From 75d9fc33ccff8075eeb50eff3f69f9cd7b7b0492 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Fri, 5 Apr 2024 14:45:08 +0200 Subject: [PATCH 10/19] Introduce slide alignment and padding config Signed-off-by: Tiemen Schuijbroek --- themes/ratio.typ | 74 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 59 insertions(+), 15 deletions(-) diff --git a/themes/ratio.typ b/themes/ratio.typ index 40e74fd..f70560a 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -77,13 +77,17 @@ keywords: (), // The version of your work. version: "Draft", - // Presentation author + // Default font settings. + text: (font: ("Cantarell", "Open Sans"), size: 18pt), // Whether to apply some heading styling. style-headings: true, // Whether to apply the custom link style. style-links: true, // Whether to apply some raw styling. style-raw: true, + // What to show as the "hero" or background image on title slides. `auto` means + // the theme's default background (auto, content, none). + hero: auto, // What to show in the header ("navigation", "progress", content, none). header: "navigation", // What to show in the footer ("navigation", "progress", content, none). @@ -102,10 +106,16 @@ title-abstract-text: (:), // Title date and version text override. title-version-text: (size: 0.8em, weight: "light"), + // Heading color. + heading-texts: ((fill: ratio-palette.secondary-800),), + // Heading alignments in order of heading depth. + heading-alignments: (center, left), + // Slide padding. + slide-padding: (left: 2em, right: 2.5em, top: 1em, bottom: 1em), + // Content slide alignment. + slide-align: left + horizon, // Color for external link anchors. link-color: ratio-palette.primary-500, - // Heading color. - heading-text: (fill: ratio-palette.secondary-800), // Stroke color for tables and such. stroke-color: ratio-palette.secondary-100, // Fill color for code blocks and such. @@ -296,19 +306,22 @@ foreground: none, // Background content to show (behind regular title page content). background: none, - // Whether to draw the hero image. - hero: true, + // What to show as the "hero" or background image on title slides. + // auto means the theme's default background (auto, content, none). + hero: auto, // Whether to register this slide title as a section. register-section: false, ) = { let content = context { let options = ratio-options.get() - if hero { + if hero == auto { let fill = options.title-hero-color if fill != none { ratio-hero(fill: fill) } + } else { + place(top + left, block(width: 100%, height: 100%, hero)) } if background != none { @@ -475,12 +488,14 @@ // Ratio style slide. #let slide(title: none, header: auto, footer: auto, body) = { - let content = pad(left: 1em, right: 2em)[ - #if title != none { - heading(level: 1, title) - } - #body - ] + let content = context { + pad(..ratio-options.get().slide-padding)[ + #if title != none { + heading(level: 1, title) + } + #body + ] + } let header = if header == auto { ratio-header() } else { @@ -493,7 +508,7 @@ } logic.polylux-slide(grid( columns: 1, - gutter: 1em, + gutter: 0em, rows: (auto, 1fr, auto), ..(header, content, footer), )) @@ -576,7 +591,14 @@ // Text setup. set par(leading: 0.7em, justify: true, linebreaks: "optimized") show par: set block(spacing: 1.35em) - set text(font: "Cantarell", 18pt) + + // Any text + show: it => { + context { + set text(..ratio-options.get().text) + it + } + } // Heading setup. show heading: it => { @@ -593,8 +615,28 @@ context { let options = ratio-options.get() if options.style-headings { + let depth = it.depth - 1 + + let alignments = utils.as-array(options.heading-alignments) + let value = if depth < alignments.len() { + alignments.at(depth) + } else if alignments.len() > 0 { + alignments.last() + } else { + none + } + set align(value) + + let texts = utils.as-array(options.heading-texts) + let style = if depth < texts.len() { + texts.at(depth) + } else if texts.len() > 0 { + texts.last() + } else { + (:) + } // Do not hyphenate headings. - text(hyphenate: false, ..options.heading-text)[#it] + text(hyphenate: false, ..style)[#it] } else { it } @@ -639,6 +681,8 @@ date: date, version: version, keywords: keywords, + // Pick the option without another context. + hero: options.at("hero", default: ratio-defaults.hero), register-section: false, // Don't register the cover. ) } From 01c75643f4955c2fe9d2f814a4c975a644f9ea22 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Fri, 5 Apr 2024 15:01:32 +0200 Subject: [PATCH 11/19] Update ratio theme documentation Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.md | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/book/src/themes/gallery/ratio.md b/book/src/themes/gallery/ratio.md index 550787e..8dfc8c0 100644 --- a/book/src/themes/gallery/ratio.md +++ b/book/src/themes/gallery/ratio.md @@ -49,14 +49,25 @@ The essentials are separate keyword arguments to the `ratio-theme` call. The `options` keyword argument is special, it takes a dictionary with any of the following: +- `text`: Text style for the entire document. - `style-headings`: Enable/disable any styling applied to headings. - `style-links`: Enable/disable any styling applied to links. - `style-raw`: Enable/disable any styling applied to raw content. +- `hero`: What to show as the "hero" or background image on title slides. `auto` results in the theme's default background. - `header`: What to display as the header ("navigation", "progress", content, or `none`). - `footer`: What to display as the footer ("navigation", "progress", content, or `none`). -- `title-background-color`: Tweak the background color for the coming title pages. +- `title-hero-color`: Tweak the background color for the coming title pages. - `title-text`: Styling to apply to the entire title page's text. -- `heading-text`: The style to apply to heading text. +- `title-heading-text`: Style of the title page's main title. +- `title-author-text`: Style of the title slide's author names. +- `title-affiliation-text`: Style of the title slide's affiliation entries. +- `title-abstract-text`: Style of the title slide's abstract. +- `title-version-text`: Style of the title slide's version text. +- `heading-texts`: The style to apply to heading text. + - Notice the `s`! It's an array of text styles for increasing heading depth! +- `heading-alignments`: The alignments to apply to headings. + - Notice the `s`! It's an array of alignments for increasing heading depth! +- `slide-padding`: Padding for content slides (arguments to `pad()`). - `link-color`: The color to apply to the link anchor. - `stroke-color`: The color to apply to strokes such as in tables. - `fill-color`: The color to apply in fills such as in code blocks. @@ -79,15 +90,14 @@ The `options` keyword argument is special, it takes a dictionary with any of the - `title-slide(title, authors, abstract, date, version, keywords, foreground, background, register-section)` with all keyword arguments: - `title`: Presentation title content. Defaults to `none`. - - `abstract`: An abstract or subtitle for your work. Defaults to `none`. - `authors`: An array of objects made with the custom `author(name:, affiliation:, email: )` method. Defaults to `none`. + - `abstract`: An abstract or subtitle for your work. Defaults to `none`. - `date`: A datetime object, defaults to `none`. - `version`: A document version to display on the date line. Something like "Draft" or "1.0". Defaults to `none`. - `keywords`: Keywords to display on the date line. Defaults to (). - `foreground`: Content to include in front of the default text content. - `background`: Content to include behind the default text content. - - `hero`: Whether to draw the default hero image (uses `options.title-hero-color`). - - `register-section`: Whether the given title should be registered as a section in the outline and navigation. + - `hero`: What to show as the "hero" or background image on title slides. `auto` results in the theme's default background. - `slide(title, header, footer)[body]` with a title keyword argument and body positional. - `title`: Section title will be added as a heading, too. Mostly for compatibility purposes with other themes. - `header`: Header content override. Default is `auto` which draws the header according to the theme options. Set it to `none` to disable for this slide. From 72bcb7ff41050adeb9f436a5e453fee8687a8a96 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Fri, 5 Apr 2024 16:04:45 +0200 Subject: [PATCH 12/19] Use a box+align for content Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.md | 8 +++-- book/src/themes/gallery/ratio.typ | 2 +- themes/ratio.typ | 53 ++++++++++++++++++++++++------- 3 files changed, 47 insertions(+), 16 deletions(-) diff --git a/book/src/themes/gallery/ratio.md b/book/src/themes/gallery/ratio.md index 8dfc8c0..38427ea 100644 --- a/book/src/themes/gallery/ratio.md +++ b/book/src/themes/gallery/ratio.md @@ -67,7 +67,8 @@ The `options` keyword argument is special, it takes a dictionary with any of the - Notice the `s`! It's an array of text styles for increasing heading depth! - `heading-alignments`: The alignments to apply to headings. - Notice the `s`! It's an array of alignments for increasing heading depth! -- `slide-padding`: Padding for content slides (arguments to `pad()`). +- `slide-box`: Arguments for the slide content's box container. +- `slide-align`: Slide content alignment for content slides. - `link-color`: The color to apply to the link anchor. - `stroke-color`: The color to apply to strokes such as in tables. - `fill-color`: The color to apply in fills such as in code blocks. @@ -102,9 +103,10 @@ The `options` keyword argument is special, it takes a dictionary with any of the - `title`: Section title will be added as a heading, too. Mostly for compatibility purposes with other themes. - `header`: Header content override. Default is `auto` which draws the header according to the theme options. Set it to `none` to disable for this slide. - `footer`: Footer content override. Default is `auto` which draws the footer according to the theme options. Set it to `none` to disable for this slide. + - `box`: Arguments to the slide's box. Set it to `auto` which draws from theme options, `none` to disable the use of a box or some other arguments to the `box()` function. + - `align`: Slide content alignment. Set it to `auto` to draw from theme options, `none` to disable further alignment. - `body`: Your content such that you can type `#slide[my foo is my bar]` -- `centered-slide(header, footer)[body]` - +- `centered-slide(header, footer)[body]` same as `slide` with all content `center + horizon` - `bare-slide[body]`: For now just an alias of `polylux-slide` See the [Customization](#customization) feature for ways to change these on the fly! diff --git a/book/src/themes/gallery/ratio.typ b/book/src/themes/gallery/ratio.typ index e6f4f83..3494448 100644 --- a/book/src/themes/gallery/ratio.typ +++ b/book/src/themes/gallery/ratio.typ @@ -98,7 +98,7 @@ You can also manually register a new section using: #slide[ = Overrides -#set text(size: 0.8em) +#set text(size: 15pt) We all know that layouts work all of the time 99% of the time. The `ratio-update` function works by updating options (dictionaries) recursively diff --git a/themes/ratio.typ b/themes/ratio.typ index f70560a..051ee67 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -110,8 +110,13 @@ heading-texts: ((fill: ratio-palette.secondary-800),), // Heading alignments in order of heading depth. heading-alignments: (center, left), - // Slide padding. - slide-padding: (left: 2em, right: 2.5em, top: 1em, bottom: 1em), + // Slide content box options. + slide-box: ( + width: 100%, + height: 100%, + inset: (left: 2em, right: 2.5em, top: 1em, bottom: 1em), + clip: true, + ), // Content slide alignment. slide-align: left + horizon, // Color for external link anchors. @@ -484,18 +489,39 @@ context ratio-bar(ratio-options.get().at("footer", default: none)) } +// Ratio content box helper. Wraps it in a box+align combination. +#let ratio-content(box-args: auto, align-arg: auto, body) = { + context { + let options = ratio-options.get() + let body = if align-arg == auto { + align(options.slide-align, body) + } else if align-arg == none { + body + } else { + align(align-arg, body) + } + let body = if box-args == auto { + box(..options.slide-box, body) + } else if box-args == none { + body + } else { + box(..box-args, body) + } + body + } +} + // CONTENT SLIDES // Ratio style slide. -#let slide(title: none, header: auto, footer: auto, body) = { - let content = context { - pad(..ratio-options.get().slide-padding)[ - #if title != none { - heading(level: 1, title) - } - #body - ] +#let slide(title: none, header: auto, footer: auto, box: auto, align: auto, body) = { + let inner = { + if title != none { + heading(level: 1, title) + } + body } + let content = ratio-content(box-args: box, align-arg: align, inner) let header = if header == auto { ratio-header() } else { @@ -515,11 +541,14 @@ } // Ratio style centered slide. -#let centered-slide(header: auto, footer: auto, body) = { +#let centered-slide(title: none, header: auto, footer: auto, box: auto, body) = { slide( + title: title, header: header, footer: footer, - block(width: 100%, height: 100%, place(center + horizon, body)), + box: box, + align: center + horizon, + body, ) } From 707d4cbe2a2b9c4d211b340682c668e1231c89e7 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Fri, 5 Apr 2024 16:26:01 +0200 Subject: [PATCH 13/19] Add raw styling Signed-off-by: Tiemen Schuijbroek --- themes/ratio.typ | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/themes/ratio.typ b/themes/ratio.typ index 051ee67..0677fe0 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -690,11 +690,36 @@ } // Set raw font to Fira Code if available. - show raw: it => { + show raw.where(block: true): it => { context { - if ratio-options.get().style-raw { + let options = ratio-options.get() + if options.style-raw { set text(font: "Fira Code") + block( + inset: (x: .3em), + fill: options.fill-color.lighten(25%), + outset: (y: .5em), + radius: .15em, + it, + ) + } else { it + } + } + } + + show raw.where(block: false): it => { + context { + let options = ratio-options.get() + if options.style-raw { + set text(font: "Fira Code") + box( + fill: options.fill-color, + inset: (x: .3em), + outset: (y: .3em), + radius: .15em, + it, + ) } else { it } From fe2dbb1dbfb8ec3559279abd388607e34f649c5f Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Sat, 6 Apr 2024 14:17:59 +0200 Subject: [PATCH 14/19] Fix subsection dots and add register-headings toggle Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.md | 1 + themes/ratio.typ | 36 ++++++++++++++++++-------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/book/src/themes/gallery/ratio.md b/book/src/themes/gallery/ratio.md index 38427ea..ef396a0 100644 --- a/book/src/themes/gallery/ratio.md +++ b/book/src/themes/gallery/ratio.md @@ -50,6 +50,7 @@ The essentials are separate keyword arguments to the `ratio-theme` call. The `options` keyword argument is special, it takes a dictionary with any of the following: - `text`: Text style for the entire document. +- `register-headings`: Whether to register level 1 and 2 headings automatically as sections and subsections. - `style-headings`: Enable/disable any styling applied to headings. - `style-links`: Enable/disable any styling applied to links. - `style-raw`: Enable/disable any styling applied to raw content. diff --git a/themes/ratio.typ b/themes/ratio.typ index 0677fe0..14cbc1d 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -78,7 +78,9 @@ // The version of your work. version: "Draft", // Default font settings. - text: (font: ("Cantarell", "Open Sans"), size: 18pt), + text: (font: ("Cantarell", "Noto Sans", "Open Sans"), size: 18pt), + // Whether to register headings as sections and subsections. + register-headings: true, // Whether to apply some heading styling. style-headings: true, // Whether to apply the custom link style. @@ -408,13 +410,13 @@ }) // Subsection shapes. - let columns = if subs.len() > 0 { - subs.len() - } else { - 1 - } let sub_displays = subs.zip(sub_ends).map( ((subs, ends)) => { + let columns = if subs.len() > 0 { + subs.len() + } else { + 1 + } pad( x: .5em, grid(columns: columns, gutter: .5em, ..subs.zip(ends).map(((sub, end)) => { @@ -631,18 +633,20 @@ // Heading setup. show heading: it => { - // Register sections and subsections. - if it.depth == 1 { - locate(loc => { - utils.register-section(it.body) - }) - } else if it.depth == 2 { - locate(loc => { - utils.register-subsection(it.body) - }) - } context { let options = ratio-options.get() + if options.register-headings { + // Register sections and subsections. + if it.depth == 1 { + locate(loc => { + utils.register-section(it.body) + }) + } else if it.depth == 2 { + locate(loc => { + utils.register-subsection(it.body) + }) + } + } if options.style-headings { let depth = it.depth - 1 From 83750acbb46d1f4bea648bfc7b8ab7d8efdc785d Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Mon, 8 Apr 2024 11:00:56 +0200 Subject: [PATCH 15/19] Spring-loaded grid Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.typ | 162 +++++++++++++------ themes/ratio.typ | 256 ++++++++++++++++-------------- 2 files changed, 252 insertions(+), 166 deletions(-) diff --git a/book/src/themes/gallery/ratio.typ b/book/src/themes/gallery/ratio.typ index 3494448..40c9b29 100644 --- a/book/src/themes/gallery/ratio.typ +++ b/book/src/themes/gallery/ratio.typ @@ -18,93 +18,86 @@ ] #slide[ -== Overrides - -You can override almost any setting of the theme as you go and switch back -later. - -By default link styling is on, but let's force it. +== Theme setup +The title page was the result of: ```typ -#ratio-register((style-links: true)) -``` - -#ratio-register((style-links: true)) -Results in: #link("https://github.com")[GitHub] - -I would like a non-styled link now... +#import themes.ratio: * -```typ -#ratio-register((style-links: false)) +#show: ratio-theme.with( + cover: true, + aspect-ratio: "16-9", + title: [Ratio theme], + abstract: [A theme about navigation and customization], + authors: (ratio-author("Theme Author", "Typst Community", "foo@bar.quux"),), + version: "1.0.0", + date: datetime(year: 2024, month: 4, day: 4), + keywords: ("navigation", "customization"), + options: (:), +) ``` -#ratio-register((style-links: false)) -Results in: #link("https: //github.com")[GitHub] - -Feels good right! No lock in. -] - -#slide[ - = Customizations - - Some random text at the top of the page to check the margins for non-headers. - - == So much text - - #lorem(50) -] - -#slide[ - == Important subsection here! - - - Test an unordered list - + As well as some enumerations - + I'm second! - - More list - - + Followed by more enum at top level +Which automatically generates a cover page if `cover` is set to `true`. ] #slide[ = Navigation - Have you noticed the navigation at the top? + Have you noticed the navigation bar at the top? You can press the main section titles, or just one of the subsection dots. + Also, there's a progress bar at the bottom by default. + == Subsections become dots + The current section's dot is always "alight". + == So there's two dots for "Navigation"! And they're both alight since we're on this page. + ] #slide[ - And the last subsection is still active because we haven't registered a new - section yet! + The last subsection is still active because we haven't registered a new section + yet! ] #slide[ + +#utils.register-section("Manual sections") +#utils.register-subsection("Manual subsection") + +== Adding manual subsections + You can also manually register a new section using: ```typ -#utils.register-section("Hello world!") -#utils.register-subsection("With a subsec.") +#utils.register-section("Manual sections") +#utils.register-subsection("Manual subsection") ``` -#utils.register-section("Hello world!") -#utils.register-subsection("With a subsec.") +Which is what we did at the start of this slide's code. + +== Toggle headings registration +If you don't want headings to be registered by default, you can switch it +on/off: + +```typ +#ratio-update((register-headings: false)) +``` ] #slide[ -= Overrides += Customization #set text(size: 15pt) We all know that layouts work all of the time 99% of the time. -The `ratio-update` function works by updating options (dictionaries) recursively -and thus only updates what you specify. +The `ratio-update` function updates option dictionaries *recursively* and thus +only updates what you specify. -The `ratio-register` function works by replacing values *completely*. +The `ratio-register` function replaces values *completely*. There's a handy `ratio-palette` variable with a pre-configured color palette, but feel free to bring your own! @@ -134,3 +127,70 @@ but feel free to bring your own! block(inset: 30%, width: 100%)[#text(weight: "bold")[background.]], ) #title-slide(title: "Green", register-section: true, foreground: fg, background: bg) + +#slide[ += Slide layout + +== Alignment grid + +By default Ratio uses a 5x5 `grid` wrapped in a content `box` that fills the +page's space between the header and footer. The grid has the following +specifications: + +```typ +#let slide-grid = ( + rows: (1em, 3fr, auto, 5fr, 1em), + columns: (2em, 1fr, auto, 1fr, 2.5em), + gutter: 0pt, +) +``` + +Which achieves: +- padding at the boundaries +- content in the `(auto, auto)` cell +- "spring-loaded" positioning using the `fr` rows and columns + - The defaults roughly center the content on screen and push it slightly above the + horizon. +] + +#slide[ +== Customizing the grid + +The default slide function `#slide` allows for customization of this grid using +the `grid-args` and `grid-cell` keyword arguments per slide. + +- `grid-args` fully customize the grid. + - `auto` means to use the grid settings from theme options. + - `none` means to disable the grid. + - anything else is treated as keyword arguments to `#grid` +- `grid-cell` the cell at which to put the body. + - `auto` means to put it at the cell as defined in theme options. + - `none` means to check if the input is an array: + - if it's an array, pass the array to `#grid` as the contents. + - if not, disable the grid functionality and use body as is. +] + +#ratio-register(( + slide-grid: (columns: (5em, auto, 10em), rows: (1em, auto, 2em)), + slide-grid-cell: (x: 1, y: 1), +)) + +#slide[ +== Custom grid + +If you're not satisfied with the default grid, you can tweak things in the init +function, too. + +```typ +#show: ratio-theme.with( + //.., + options: ( + slide-grid: ( + columns: (5em, auto, 10em), + rows: (1em, auto, 2em), + ), + slide-grid-cell: (x: 1, y: 1), + ), +) +``` +] diff --git a/themes/ratio.typ b/themes/ratio.typ index 14cbc1d..51a142d 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -78,7 +78,7 @@ // The version of your work. version: "Draft", // Default font settings. - text: (font: ("Cantarell", "Noto Sans", "Open Sans"), size: 18pt), + text: (font: ("Noto Sans", "Open Sans"), size: 18pt), // Whether to register headings as sections and subsections. register-headings: true, // Whether to apply some heading styling. @@ -97,7 +97,11 @@ // Title background color. title-hero-color: ratio-palette.secondary-800, // Title text style. - title-text: (size: 20pt, fill: ratio-palette.contrast), + title-text: ( + font: ("Cantarell", "Noto Sans", "Open Sans"), + size: 20pt, + fill: ratio-palette.contrast, + ), // Title text heading overrides. title-heading-text: (size: 3em, weight: "bold"), // Title author text heading overrides. @@ -108,19 +112,22 @@ title-abstract-text: (:), // Title date and version text override. title-version-text: (size: 0.8em, weight: "light"), - // Heading color. + // Common heading text style. + heading-text: (font: ("Cantarell", "Noto Sans", "Open Sans"), hyphenate: false), + // Heading text style overrides in order of heading depth. heading-texts: ((fill: ratio-palette.secondary-800),), // Heading alignments in order of heading depth. - heading-alignments: (center, left), + heading-alignments: (left,), // Slide content box options. - slide-box: ( - width: 100%, - height: 100%, - inset: (left: 2em, right: 2.5em, top: 1em, bottom: 1em), - clip: true, - ), + slide-box: (width: 100%, height: 100%, clip: true), // Content slide alignment. - slide-align: left + horizon, + slide-grid: ( + rows: (1em, 3fr, auto, 5fr, 1em), + columns: (2em, 1fr, auto, 1fr, 2.5em), + gutter: 0pt, + ), + // Slide grid cell. + slide-grid-cell: (x: 2, y: 2), // Color for external link anchors. link-color: ratio-palette.primary-500, // Stroke color for tables and such. @@ -482,26 +489,42 @@ } // Ratio header helper. -#let ratio-header() = { - context ratio-bar(ratio-options.get().at("header", default: none)) -} +#let ratio-header() = context { ratio-bar(ratio-options.get().at("header", +default: none)) } // Ratio footer helper. -#let ratio-footer() = { - context ratio-bar(ratio-options.get().at("footer", default: none)) -} +#let ratio-footer() = context { ratio-bar(ratio-options.get().at("footer", +default: none)) } -// Ratio content box helper. Wraps it in a box+align combination. -#let ratio-content(box-args: auto, align-arg: auto, body) = { +// Ratio content box helper. Wraps it in a box+grid combination. +#let ratio-content(box-args: auto, grid-args: auto, grid-cell: auto, body) = { context { let options = ratio-options.get() - let body = if align-arg == auto { - align(options.slide-align, body) - } else if align-arg == none { + + let g = if grid-args == auto { + grid.with(..options.slide-grid) + } else if grid-args == none { + none + } else { + grid.with(..grid-args) + } + + let body = if g == none { body } else { - align(align-arg, body) + if grid-cell == auto { + g(grid.cell(..options.slide-grid-cell, body)) + } else if grid-cell == none { + if type(body) == array { + g(..body) + } else { + body + } + } else { + g(grid.cell(..grid-cell, body)) + } } + let body = if box-args == auto { box(..options.slide-box, body) } else if box-args == none { @@ -516,14 +539,23 @@ // CONTENT SLIDES // Ratio style slide. -#let slide(title: none, header: auto, footer: auto, box: auto, align: auto, body) = { +#let slide( + title: none, + depth: 1, + header: auto, + footer: auto, + box-args: auto, + grid-args: auto, + grid-cell: auto, + body, +) = { let inner = { if title != none { - heading(level: 1, title) + heading(depth: depth, box(width: 100%, align(center, title))) } body } - let content = ratio-content(box-args: box, align-arg: align, inner) + let content = ratio-content(box-args: box-args, grid-args: grid-args, grid-cell: grid-cell, inner) let header = if header == auto { ratio-header() } else { @@ -536,21 +568,29 @@ } logic.polylux-slide(grid( columns: 1, - gutter: 0em, + gutter: 0pt, rows: (auto, 1fr, auto), ..(header, content, footer), )) } // Ratio style centered slide. -#let centered-slide(title: none, header: auto, footer: auto, box: auto, body) = { +#let centered-slide( + title: none, + depth: 1, + header: auto, + footer: auto, + box-args: auto, + body, +) = { slide( title: title, + depth: depth, header: header, footer: footer, - box: box, - align: center + horizon, - body, + box-args: box-args, + grid-args: none, + align(horizon + center, box(body)), ) } @@ -624,109 +664,95 @@ show par: set block(spacing: 1.35em) // Any text - show: it => { - context { - set text(..ratio-options.get().text) - it - } + show: it => context { + set text(..ratio-options.get().text) + it } // Heading setup. - show heading: it => { - context { - let options = ratio-options.get() - if options.register-headings { - // Register sections and subsections. - if it.depth == 1 { - locate(loc => { - utils.register-section(it.body) - }) - } else if it.depth == 2 { - locate(loc => { - utils.register-subsection(it.body) - }) - } + show heading: it => context { + let options = ratio-options.get() + if options.register-headings and logic.subslide.get().first() == 1 { + // Register sections and subsections. + if it.depth == 1 { + utils.register-section(it.body) + } else if it.depth == 2 { + utils.register-subsection(it.body) } - if options.style-headings { - let depth = it.depth - 1 - - let alignments = utils.as-array(options.heading-alignments) - let value = if depth < alignments.len() { - alignments.at(depth) - } else if alignments.len() > 0 { - alignments.last() - } else { - none - } - set align(value) + } + if options.style-headings { + let depth = it.depth - 1 + + let alignments = utils.as-array(options.heading-alignments) + let value = if depth < alignments.len() { + alignments.at(depth) + } else if alignments.len() > 0 { + alignments.last() + } else { + none + } + set align(value) - let texts = utils.as-array(options.heading-texts) - let style = if depth < texts.len() { - texts.at(depth) - } else if texts.len() > 0 { - texts.last() - } else { - (:) - } - // Do not hyphenate headings. - text(hyphenate: false, ..style)[#it] + let texts = utils.as-array(options.heading-texts) + let style = if depth < texts.len() { + texts.at(depth) + } else if texts.len() > 0 { + texts.last() } else { - it + (:) } + // Do not hyphenate headings. + text(..options.heading-text, ..style)[#it] + } else { + it } } // Style links if set. - show link: it => { - context { - let options = ratio-options.get() - if options.style-links { - // Don't style for internal links. - if type(it.dest) == label or type(it.dest) == location { - return it - } - let color = options.at("link-color", default: ratio-palette.primary-500) - ratio-anchor(it) - } else { - it + show link: it => context { + let options = ratio-options.get() + if options.style-links { + // Don't style for internal links. + if type(it.dest) == label or type(it.dest) == location { + return it } + let color = options.at("link-color", default: ratio-palette.primary-500) + ratio-anchor(it) + } else { + it } } // Set raw font to Fira Code if available. - show raw.where(block: true): it => { - context { - let options = ratio-options.get() - if options.style-raw { - set text(font: "Fira Code") - block( - inset: (x: .3em), - fill: options.fill-color.lighten(25%), - outset: (y: .5em), - radius: .15em, - it, - ) - } else { - it - } + show raw.where(block: true): it => context { + let options = ratio-options.get() + if options.style-raw { + set text(font: "Fira Code") + block( + inset: (x: .3em), + fill: options.fill-color.lighten(25%), + outset: (y: .5em), + radius: .15em, + it, + ) + } else { + it } } - show raw.where(block: false): it => { - context { - let options = ratio-options.get() - if options.style-raw { - set text(font: "Fira Code") - box( - fill: options.fill-color, - inset: (x: .3em), - outset: (y: .3em), - radius: .15em, - it, - ) - } else { - it - } + show raw.where(block: false): it => context { + let options = ratio-options.get() + if options.style-raw { + set text(font: "Fira Code") + box( + fill: options.fill-color, + inset: (x: .3em), + outset: (y: .3em), + radius: .15em, + it, + ) + } else { + it } } From 477d3f55757e9764ebdab8310eb7cb338c0992a6 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Mon, 8 Apr 2024 16:22:22 +0200 Subject: [PATCH 16/19] Update to 7x7 grid spec Signed-off-by: Tiemen Schuijbroek --- book/src/themes/gallery/ratio.md | 3 ++- book/src/themes/gallery/ratio.typ | 24 ++++++++++++-------- themes/ratio.typ | 37 ++++++++++++++++++++++++------- 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/book/src/themes/gallery/ratio.md b/book/src/themes/gallery/ratio.md index ef396a0..33f879f 100644 --- a/book/src/themes/gallery/ratio.md +++ b/book/src/themes/gallery/ratio.md @@ -69,7 +69,8 @@ The `options` keyword argument is special, it takes a dictionary with any of the - `heading-alignments`: The alignments to apply to headings. - Notice the `s`! It's an array of alignments for increasing heading depth! - `slide-box`: Arguments for the slide content's box container. -- `slide-align`: Slide content alignment for content slides. +- `slide-grid`: Grid specification for the grid inside the box as arguments to `#grid()`. +- `slide-grid-cell`: Grid cell to use for main body content as arguments to `#grid.cell()`. - `link-color`: The color to apply to the link anchor. - `stroke-color`: The color to apply to strokes such as in tables. - `fill-color`: The color to apply in fills such as in code blocks. diff --git a/book/src/themes/gallery/ratio.typ b/book/src/themes/gallery/ratio.typ index 40c9b29..dfe7755 100644 --- a/book/src/themes/gallery/ratio.typ +++ b/book/src/themes/gallery/ratio.typ @@ -133,27 +133,30 @@ but feel free to bring your own! == Alignment grid -By default Ratio uses a 5x5 `grid` wrapped in a content `box` that fills the -page's space between the header and footer. The grid has the following -specifications: +By default Ratio uses a 7x7 `grid` wrapped in a content `box` that fills the +page's space \ +between the header and footer. The grid has the following specifications: ```typ #let slide-grid = ( - rows: (1em, 3fr, auto, 5fr, 1em), - columns: (2em, 1fr, auto, 1fr, 2.5em), + rows: (auto, 1em, 3fr, auto, 5fr, 1em, auto), + columns: (auto, 2em, 1fr, auto, 1fr, 2.5em, auto), gutter: 0pt, ) ``` Which achieves: -- padding at the boundaries -- content in the `(auto, auto)` cell +- edge content possible using placements in the first and last columns +- followed by padding around the main content +- content in the middle `(auto, auto)` cell - "spring-loaded" positioning using the `fr` rows and columns - The defaults roughly center the content on screen and push it slightly above the horizon. ] -#slide[ +#slide( + grid-children: (grid.cell(x: 6, rowspan: 7, fill: red, align: horizon)[cell]), +)[ == Customizing the grid The default slide function `#slide` allows for customization of this grid using @@ -165,9 +168,12 @@ the `grid-args` and `grid-cell` keyword arguments per slide. - anything else is treated as keyword arguments to `#grid` - `grid-cell` the cell at which to put the body. - `auto` means to put it at the cell as defined in theme options. - - `none` means to check if the input is an array: + - `none` means to check if the body is an array: - if it's an array, pass the array to `#grid` as the contents. - if not, disable the grid functionality and use body as is. +- `grid-children` children to place on the grid. The bar on the right was achieved + with:\ + `grid-children: (grid.cell(x: 6, rowspan: 7, fill: red),)` ] #ratio-register(( diff --git a/themes/ratio.typ b/themes/ratio.typ index 51a142d..411822a 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -122,12 +122,12 @@ slide-box: (width: 100%, height: 100%, clip: true), // Content slide alignment. slide-grid: ( - rows: (1em, 3fr, auto, 5fr, 1em), - columns: (2em, 1fr, auto, 1fr, 2.5em), + rows: (auto, 1em, 3fr, auto, 5fr, 1em, auto), + columns: (auto, 2em, 1fr, auto, 1fr, 2em, auto), gutter: 0pt, ), // Slide grid cell. - slide-grid-cell: (x: 2, y: 2), + slide-grid-cell: (x: 3, y: 3), // Color for external link anchors. link-color: ratio-palette.primary-500, // Stroke color for tables and such. @@ -497,10 +497,24 @@ default: none)) } default: none)) } // Ratio content box helper. Wraps it in a box+grid combination. -#let ratio-content(box-args: auto, grid-args: auto, grid-cell: auto, body) = { +#let ratio-content( + box-args: auto, + grid-args: auto, + grid-cell: auto, + grid-children: auto, + body, +) = { context { let options = ratio-options.get() + let grid-children = if grid-children == auto { + options.grid-children + } else if grid-children == none { + () + } else { + utils.as-array(grid-children) + } + let g = if grid-args == auto { grid.with(..options.slide-grid) } else if grid-args == none { @@ -513,15 +527,15 @@ default: none)) } body } else { if grid-cell == auto { - g(grid.cell(..options.slide-grid-cell, body)) + g(grid.cell(..options.slide-grid-cell, body), ..grid-children) } else if grid-cell == none { if type(body) == array { - g(..body) + g(..body, ..grid-children) } else { body } } else { - g(grid.cell(..grid-cell, body)) + g(grid.cell(..grid-cell, body), ..grid-children) } } @@ -547,6 +561,7 @@ default: none)) } box-args: auto, grid-args: auto, grid-cell: auto, + grid-children: (), body, ) = { let inner = { @@ -555,7 +570,13 @@ default: none)) } } body } - let content = ratio-content(box-args: box-args, grid-args: grid-args, grid-cell: grid-cell, inner) + let content = ratio-content( + box-args: box-args, + grid-args: grid-args, + grid-cell: grid-cell, + grid-children: grid-children, + inner, + ) let header = if header == auto { ratio-header() } else { From d4c23062cd7284a3ff895939d56fb1dbb16ee117 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Mon, 8 Apr 2024 18:23:36 +0200 Subject: [PATCH 17/19] Wrap link anchor in a box Signed-off-by: Tiemen Schuijbroek --- themes/ratio.typ | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/themes/ratio.typ b/themes/ratio.typ index 411822a..5053293 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -189,9 +189,7 @@ // Draw a tiny anchor on the top right of the body text. #let ratio-anchor(body, color: ratio-defaults.link-color) = { - body - h(0.05em) - super(box(height: 0.7em, circle(radius: 0.15em, stroke: 0.08em + color))) + box[#body#h(0.05em)#super(box(height: 0.7em, circle(radius: 0.15em, stroke: 0.08em + color)))] } // TITLE SLIDE From 648ced7ee5ccc71fb0057e85c8b4e4adfadf1ed6 Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Mon, 8 Apr 2024 18:24:00 +0200 Subject: [PATCH 18/19] Add a fix for subsections being defined before any section Signed-off-by: Tiemen Schuijbroek --- utils/utils.typ | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/utils/utils.typ b/utils/utils.typ index 9c73b39..65c73e4 100644 --- a/utils/utils.typ +++ b/utils/utils.typ @@ -19,6 +19,12 @@ }) #let register-subsection(name) = locate(loc => { + sections-state.update(s => { + if s.len() == 0 { + s.push((body: [], loc: loc)) + } + s + }) subsections-state.update(s => { if s.len() == 0 { s.push(()) From 08c9912b7b97c440e1ef31f6f429db4d62196cfa Mon Sep 17 00:00:00 2001 From: Tiemen Schuijbroek Date: Mon, 22 Apr 2024 14:17:43 +0200 Subject: [PATCH 19/19] Grid layout title page Signed-off-by: Tiemen Schuijbroek --- themes/ratio.typ | 57 +++++++++++++++++++++++++++++++----------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/themes/ratio.typ b/themes/ratio.typ index 5053293..5ab7d4a 100644 --- a/themes/ratio.typ +++ b/themes/ratio.typ @@ -112,6 +112,8 @@ title-abstract-text: (:), // Title date and version text override. title-version-text: (size: 0.8em, weight: "light"), + // Title vertical spacing. + title-gutter: 5%, // Common heading text style. heading-text: (font: ("Cantarell", "Noto Sans", "Open Sans"), hyphenate: false), // Heading text style overrides in order of heading depth. @@ -235,13 +237,15 @@ affiliation-text: ratio-defaults.title-affiliation-text, abstract-text: ratio-defaults.title-abstract-text, version-text: ratio-defaults.title-version-text, + gutter: ratio-defaults.title-gutter, register-section: false, ) = { - let v-space = v(1.5em, weak: true) set text(..title-text) + let rows = () + if title != none { - text(..heading-text)[#title] + rows.push(text(..heading-text)[#title]) if register-section { utils.register-section(title) } @@ -249,29 +253,31 @@ if authors != none and authors.len() > 0 { for author in utils.as-array(authors) { - v-space let name = author.at("name", default: none) let email = author.at("email", default: none) let affiliation = author.at("affiliation", default: none) + let content = { + let author-text = text(..author-text)[#name] + if email == none { + author-text + } else { + link("mailto:" + email)[#author-text] + } - let author-text = text(..author-text)[#name] - if email == none { - author-text - } else { - link("mailto:" + email)[#author-text] - } - - if affiliation != none { - linebreak() - text(..affiliation-text)[#affiliation] + if affiliation != none { + v(0.3em, weak: true) + text(..affiliation-text)[#affiliation] + } } + rows.push(content) } } if abstract != none { - v-space - set text(..abstract-text) - par(leading: 0.78em, justify: true, linebreaks: "optimized")[#abstract] + rows.push([ + #set text(..abstract-text) + #abstract + ]) } let date-line = () @@ -281,22 +287,25 @@ if version != none { date-line.push(version) } - if keywords != none { + if keywords != none and keywords.len() > 0 { let keywords = utils.as-array(keywords) date-line.push(keywords.join(", ")) } if date-line.len() > 0 { - v-space let sep = [ #h(1.6pt) | #h(1.6pt) ] - set text(..version-text) - date-line.join(sep) + rows.push([ + #set text(..version-text) + #date-line.join(sep) + ]) } + + grid(columns: (auto), gutter: gutter, ..rows) } // Ratio style title slide. @@ -353,6 +362,7 @@ affiliation-text: options.title-affiliation-text, abstract-text: options.title-abstract-text, version-text: options.title-version-text, + gutter: options.title-gutter, ))) if foreground != none { @@ -651,6 +661,13 @@ default: none)) } // Presentation contents. body, ) = { + let keywords = { + if keywords == none { + () + } else { + keywords + } + } // Set document properties. set document( title: title,