diff --git a/preview/glossy/.gitignore b/preview/glossy/.gitignore new file mode 100644 index 0000000..f1ca904 --- /dev/null +++ b/preview/glossy/.gitignore @@ -0,0 +1,2 @@ +tests/**/out/ +tests/**/diff/ diff --git a/preview/glossy/README.md b/preview/glossy/README.md index bc7b5c2..4110aec 100644 --- a/preview/glossy/README.md +++ b/preview/glossy/README.md @@ -41,7 +41,7 @@ different goals: ### Import the package ```typst -#import "@preview/glossy:0.2.0": * +#import "@preview/glossy:0.2.1": * ``` ### Defining Glossary Terms @@ -154,8 +154,9 @@ Display the glossary using the `glossary()` function: ```typst #glossary( - title: "Web Development Glossary", - theme: my-theme, + title: "Web Development Glossary", // Optional: defaults to Glossary + theme: my-theme, // Optionaal: defaults to theme-twocol + ignore-case: false, // Optional: ignore case when sorting terms groups: ("Web") // Optional: Filter to specific groups ) ``` @@ -177,52 +178,10 @@ Control how terms appear in the document by providing a custom `show-term` funct #### Included Themes -Glossy comes with several built-in themes that can be used directly or serve as examples for custom themes: +Glossy comes with several built-in themes that can be used directly or serve as +examples for custom themes: -##### theme-twocol - -A professional two-column layout ideal for technical documentation. Features: - -- Two-column layout for efficient space usage -- Dotted leaders to page numbers -- Clear hierarchy with optional group headings -- Compact but readable formatting -- Terms in regular weight with long forms and descriptions inline - -```typst -#glossary(theme: theme-twocol) -``` - -##### theme-basic - -A traditional single-column layout similar to book glossaries. Features: - -- Bold terms with indented content -- Clear separation between entries -- Hanging indentation for wrapped lines -- Parenthetical long forms -- Page numbers with "pp." prefix -- Simple, clean design - -```typst -#glossary(theme: theme-basic) -``` - -##### theme-compact - -A space-efficient layout perfect for technical documents or appendices. Features: - -- Reduced vertical spacing -- Smaller font sizes for secondary information -- Color-coded term components -- Grid-based alignment -- Minimal decorative elements -- Gray text for supplementary information -- Bullet separators between components - -```typst -#glossary(theme: theme-compact) -``` +![theme gallery image](themeshots.png) #### Custom Themes diff --git a/preview/glossy/examples/readme.pdf b/preview/glossy/examples/readme.pdf deleted file mode 100644 index bd1de40..0000000 Binary files a/preview/glossy/examples/readme.pdf and /dev/null differ diff --git a/preview/glossy/src/gloss.typ b/preview/glossy/src/gloss.typ index 092a887..c3ca8a3 100644 --- a/preview/glossy/src/gloss.typ +++ b/preview/glossy/src/gloss.typ @@ -390,7 +390,8 @@ #let glossary( title: "Glossary", theme: theme-twocol, - groups: () + ignore-case: false, + groups: (), ) = context { // Collect and organize entries by group let output = (:) @@ -437,10 +438,18 @@ // Add non-empty groups to output if current_entries.len() > 0 { group = if group == none { "" } else { group } - output.insert( - group, - current_entries.sorted(key: e => e.short) - ) + + // sort entries by case insensitivity if requested + let sorted_entries = current_entries + // 1. create array of tuples with (lower [if ignore-case], entry) + .map(e => { if ignore-case { (lower(e.short), e) } else { (e.short, e) } }) + // 2. sort the tuples (by first element then second) + .sorted(key: t => t.first()) + // 3. strip away the tuple's first element, leaving an array of entries + .map(t => t.last()) + + // add entries to this group's output map + output.insert(group, sorted_entries) } } diff --git a/preview/glossy/src/themes.typ b/preview/glossy/src/themes.typ index 7775002..8ff4163 100644 --- a/preview/glossy/src/themes.typ +++ b/preview/glossy/src/themes.typ @@ -1,236 +1,5 @@ -// Theme definition for a two-column glossary layout with hierarchical sections -// -// This theme provides formatting for three levels of glossary content: -// 1. Section: The overall glossary container -// 2. Groups: Optional categorization of related terms -// 3. Entries: Individual glossary terms and their definitions -// -#let theme-twocol = ( - // Renders the main glossary section as a two-column layout - // Parameters: - // title: The glossary section title - // body: Content containing all groups and entries - section: (title, body) => { - set par.line(numbering: none) - heading(level: 1, title) - columns(2, body) - }, - - // Renders a group of related glossary terms - // Parameters: - // name: Group name (empty string for ungrouped terms) - // index: Zero-based group index - // total: Total number of groups - // body: Content containing the group's entries - group: (name, index, total, body) => { - if name != "" and total > 1 { - heading(level: 2, name) - } - body - }, - - // Renders a single glossary entry with term, definition, and page references - // Parameters: - // entry: Dictionary containing term data: - // - short: Short form of term - // - long: Long form of term (optional) - // - description: Term description (optional) - // - label: Term's dictionary label - // - pages: Linked page numbers where term appears - // index: Zero-based entry index within group - // total: Total entries in group - entry: (entry, index, total) => { - // Format the short form and optional long form of the term - let short-display = text(weight: "regular", entry.short) - let long-display = if entry.long == none { - [] - } else { - [#h(0.25em) -- #entry.long] - } - - // Format the optional description - let description = if entry.description == none { - [] - } else { - [: #entry.description] - } - - // Render the complete entry with dotted leader line to page numbers - text( - size: 0.75em, - weight: "light", - grid( - columns: (auto,auto,auto,1fr,1em,auto), - align: (left, left, left, center, center, right), - [#short-display#entry.label], // Term with label - [#long-display], // Long form (if any) - [#description], // Description (if any) - [#repeat(h(0.25em) + "." + h(0.25em))], // Dotted leader line - [ . ], // Final dot - [#entry.pages] // Page references - ) - ) - }, -) - -// Theme definition for a basic single-column glossary layout -// -// This theme provides a traditional glossary format with: -// 1. Bold terms -// 2. Indented descriptions -// 3. Simple page references -// 4. Optional group headings -// -#let theme-basic = ( - // Renders the main glossary section as a single column - // Parameters: - // title: The glossary section title - // body: Content containing all groups and entries - section: (title, body) => { - heading(level: 1, title) - body - }, - - // Renders a group of related glossary terms - // Parameters: - // name: Group name (empty string for ungrouped terms) - // index: Zero-based group index - // total: Total number of groups - // body: Content containing the group's entries - group: (name, index, total, body) => { - if name != "" and total > 1 { - heading(level: 2, name) - } - body - }, - - // Renders a single glossary entry with term, definition, and page references - // Parameters: - // entry: Dictionary containing term data: - // - short: Short form of term - // - long: Long form of term (optional) - // - description: Term description (optional) - // - label: Term's dictionary label - // - pages: Linked page numbers where term appears - // index: Zero-based entry index within group - // total: Total entries in group - entry: (entry, index, total) => { - // Format the term parts - let term = text(weight: "bold", entry.short) - let long-form = if entry.long == none { - [] - } else { - [, #entry.long] - } - - // Format the description with proper spacing - let description = if entry.description == none { - [] - } else { - [: #entry.description] - } - - // Create the complete entry with hanging indent - block( - spacing: 0.5em, - pad( - left: 1em, - bottom: 0.5em, - block( - [#term#entry.label#long-form#description #h(1em) (pp. #entry.pages)] - ) - ) - ) - }, -) - -// Theme definition for a compact glossary layout optimized for space efficiency -// -// This theme provides a dense but readable format with: -// 1. Terms and definitions on same line -// 2. Smaller font size -// 3. Minimal vertical spacing -// 4. Left-aligned groups for quick scanning -// 5. Condensed page references -// -#let theme-compact = ( - // Renders the main glossary section with minimal spacing - // Parameters: - // title: The glossary section title - // body: Content containing all groups and entries - section: (title, body) => { - set par(leading: 0.65em) - heading(level: 1, outlined: false, text(weight: "bold", size: 1.1em, title)) - body - }, - - // Renders a group of related glossary terms - // Parameters: - // name: Group name (empty string for ungrouped terms) - // index: Zero-based group index - // total: Total number of groups - // body: Content containing the group's entries - group: (name, index, total, body) => { - if name != "" and total > 1 { - block( - spacing: 0.5em, - pad( - top: 0.5em, - text(weight: "bold", size: 0.9em, name) - ) - ) - } - body - }, - - // Renders a single glossary entry with term, definition, and page references - // Parameters: - // entry: Dictionary containing term data: - // - short: Short form of term - // - long: Long form of term (optional) - // - description: Term description (optional) - // - label: Term's dictionary label - // - pages: Linked page numbers where term appears - // index: Zero-based entry index within group - // total: Total entries in group - entry: (entry, index, total) => { - // Format term components with minimal spacing - let term = text( - size: 0.65em, - weight: "medium", - fill: gray.darken(60%), - entry.short - ) - - let long-form = if entry.long == none { - [] - } else { - text(size: 0.65em, fill: gray.darken(20%), [ (#entry.long)]) - } - - let description = if entry.description == none { - [] - } else { - text(size: 0.65em, [· #entry.description]) - } - - // Create the complete entry with tight spacing - block( - spacing: 0.4em, - grid( - columns: (auto, 1fr, auto), - gutter: 0.5em, - // Term and description column - box[#term#entry.label#long-form #description], - // Dots.... - repeat(h(0.25em) + text(fill: gray, ".") + h(0.25em)), - // Page references with smaller font - text( - size: 0.6em, - fill: gray.darken(20%), - entry.pages - ) - ) - ) - }, -) +#import "themes/academic.typ": theme-academic +#import "themes/basic.typ": theme-basic +#import "themes/chicago-index.typ": theme-chicago-index +#import "themes/compact.typ": theme-compact +#import "themes/twocol.typ": theme-twocol diff --git a/preview/glossy/src/themes/academic.typ b/preview/glossy/src/themes/academic.typ new file mode 100644 index 0000000..1093fd2 --- /dev/null +++ b/preview/glossy/src/themes/academic.typ @@ -0,0 +1,50 @@ +// Inspired by academic textbook glossaries +#let theme-academic = ( + section: (title, body) => { + set text(font: "New Computer Modern") + heading(level: 1, title) + v(1em) + body + }, + + group: (name, index, total, body) => { + if name != "" and total > 1 { + v(1.5em) + align(left, text(weight: "bold", size: 1.2em, name)) + v(0.75em) + line(length: 100%, stroke: 0.5pt) + v(0.75em) + } + body + }, + + entry: (entry, index, total) => { + let short-display = text(weight: "bold", entry.short) + let long-display = if entry.long == none { + [] + } else { + [. #entry.long] + } + + let description = if entry.description == none { + [] + } else { + [. #entry.description] + } + + block( + below: 1em, + text( + size: 0.95em, + { + grid( + columns: (1fr, auto), + gutter: 0.75em, + [#short-display#long-display#description], + text(fill: rgb("#666666"), entry.pages) + ) + } + ) + ) + }, +) diff --git a/preview/glossy/src/themes/basic.typ b/preview/glossy/src/themes/basic.typ new file mode 100644 index 0000000..b756c27 --- /dev/null +++ b/preview/glossy/src/themes/basic.typ @@ -0,0 +1,70 @@ +// Theme definition for a basic single-column glossary layout +// +// This theme provides a traditional glossary format with: +// 1. Bold terms +// 2. Indented descriptions +// 3. Simple page references +// 4. Optional group headings +// +#let theme-basic = ( + // Renders the main glossary section as a single column + // Parameters: + // title: The glossary section title + // body: Content containing all groups and entries + section: (title, body) => { + heading(level: 1, title) + body + }, + + // Renders a group of related glossary terms + // Parameters: + // name: Group name (empty string for ungrouped terms) + // index: Zero-based group index + // total: Total number of groups + // body: Content containing the group's entries + group: (name, index, total, body) => { + if name != "" and total > 1 { + heading(level: 2, name) + } + body + }, + + // Renders a single glossary entry with term, definition, and page references + // Parameters: + // entry: Dictionary containing term data: + // - short: Short form of term + // - long: Long form of term (optional) + // - description: Term description (optional) + // - label: Term's dictionary label + // - pages: Linked page numbers where term appears + // index: Zero-based entry index within group + // total: Total entries in group + entry: (entry, index, total) => { + // Format the term parts + let term = text(weight: "bold", entry.short) + let long-form = if entry.long == none { + [] + } else { + [, #entry.long] + } + + // Format the description with proper spacing + let description = if entry.description == none { + [] + } else { + [: #entry.description] + } + + // Create the complete entry with hanging indent + block( + spacing: 0.5em, + pad( + left: 1em, + bottom: 0.5em, + block( + [#term#entry.label#long-form#description #h(1em) (pp. #entry.pages)] + ) + ) + ) + }, +) diff --git a/preview/glossy/src/themes/chicago-index.typ b/preview/glossy/src/themes/chicago-index.typ new file mode 100644 index 0000000..fa3c82e --- /dev/null +++ b/preview/glossy/src/themes/chicago-index.typ @@ -0,0 +1,47 @@ +// Inspired by the Chicago Manual of Style's index format +#let theme-chicago-index = ( + section: (title, body) => { + set par(hanging-indent: 1em) + heading(level: 1, smallcaps(title)) + body + }, + + group: (name, index, total, body) => { + if name != "" and total > 1 { + v(1em) + text(weight: "medium", style: "italic", name) + v(0.5em) + } + body + }, + + entry: (entry, index, total) => { + let short-display = text(weight: "regular", entry.short) + let long-display = if entry.long == none { + [] + } else { + [ — #entry.long] // Using em-dash for Chicago style + } + + let description = if entry.description == none { + [] + } else { + text(style: "italic", [: #entry.description]) + } + + block( + below: 0.65em, + text( + size: 0.9em, + { + grid( + columns: (1fr, auto), + gutter: 1em, + [#short-display#entry.label#long-display#description], + [#entry.pages] + ) + } + ) + ) + }, +) diff --git a/preview/glossy/src/themes/compact.typ b/preview/glossy/src/themes/compact.typ new file mode 100644 index 0000000..86a40e3 --- /dev/null +++ b/preview/glossy/src/themes/compact.typ @@ -0,0 +1,91 @@ +// Theme definition for a compact glossary layout optimized for space efficiency +// +// This theme provides a dense but readable format with: +// 1. Terms and definitions on same line +// 2. Smaller font size +// 3. Minimal vertical spacing +// 4. Left-aligned groups for quick scanning +// 5. Condensed page references +// +#let theme-compact = ( + // Renders the main glossary section with minimal spacing + // Parameters: + // title: The glossary section title + // body: Content containing all groups and entries + section: (title, body) => { + set par(leading: 0.65em) + heading(level: 1, outlined: false, text(weight: "bold", size: 1.1em, title)) + body + }, + + // Renders a group of related glossary terms + // Parameters: + // name: Group name (empty string for ungrouped terms) + // index: Zero-based group index + // total: Total number of groups + // body: Content containing the group's entries + group: (name, index, total, body) => { + if name != "" and total > 1 { + block( + spacing: 0.5em, + pad( + top: 0.5em, + text(weight: "bold", size: 0.9em, name) + ) + ) + } + body + }, + + // Renders a single glossary entry with term, definition, and page references + // Parameters: + // entry: Dictionary containing term data: + // - short: Short form of term + // - long: Long form of term (optional) + // - description: Term description (optional) + // - label: Term's dictionary label + // - pages: Linked page numbers where term appears + // index: Zero-based entry index within group + // total: Total entries in group + entry: (entry, index, total) => { + // Format term components with minimal spacing + let term = text( + size: 0.65em, + weight: "medium", + fill: gray.darken(60%), + entry.short + ) + + let long-form = if entry.long == none { + [] + } else { + text(size: 0.65em, fill: gray.darken(20%), [ (#entry.long)]) + } + + let description = if entry.description == none { + [] + } else { + text(size: 0.65em, [· #entry.description]) + } + + // Create the complete entry with tight spacing + block( + spacing: 0.4em, + grid( + columns: (auto, 1fr, auto), + align: left+bottom, + gutter: 0.5em, + // Term and description column + box[#term#entry.label#long-form #description], + // Dots.... + repeat(h(0.25em) + text(fill: gray, ".") + h(0.25em)), + // Page references with smaller font + text( + size: 0.6em, + fill: gray.darken(20%), + entry.pages + ) + ) + ) + }, +) diff --git a/preview/glossy/src/themes/twocol.typ b/preview/glossy/src/themes/twocol.typ new file mode 100644 index 0000000..7183e6e --- /dev/null +++ b/preview/glossy/src/themes/twocol.typ @@ -0,0 +1,72 @@ +// Theme definition for a two-column glossary layout with hierarchical sections +// +// This theme provides formatting for three levels of glossary content: +// 1. Section: The overall glossary container +// 2. Groups: Optional categorization of related terms +// 3. Entries: Individual glossary terms and their definitions +// +#let theme-twocol = ( + // Renders the main glossary section as a two-column layout + // Parameters: + // title: The glossary section title + // body: Content containing all groups and entries + section: (title, body) => { + set par.line(numbering: none) + heading(level: 1, title) + columns(2, body) + }, + + // Renders a group of related glossary terms + // Parameters: + // name: Group name (empty string for ungrouped terms) + // index: Zero-based group index + // total: Total number of groups + // body: Content containing the group's entries + group: (name, index, total, body) => { + if name != "" and total > 1 { + heading(level: 2, name) + } + body + }, + + // Renders a single glossary entry with term, definition, and page references + // Parameters: + // entry: Dictionary containing term data: + // - short: Short form of term + // - long: Long form of term (optional) + // - description: Term description (optional) + // - label: Term's dictionary label + // - pages: Linked page numbers where term appears + // index: Zero-based entry index within group + // total: Total entries in group + entry: (entry, index, total) => { + // Format the short form and optional long form of the term + let short-display = text(weight: "regular", entry.short) + let long-display = if entry.long == none { + [] + } else { + [#h(0.25em) -- #entry.long] + } + + // Format the optional description + let description = if entry.description == none { + [] + } else { + [: #entry.description] + } + + // Render the complete entry with dotted leader line to page numbers + text( + size: 0.75em, + weight: "light", + grid( + columns: (auto,1fr,1em,auto), + align: (left, center, center, right), + [#short-display#entry.label#long-display#description], // Term with label + [#repeat(h(0.25em) + "." + h(0.25em))], // Dotted leader line + [ . ], // A 1em wide dot so we definitely get some break between term and pages + [#entry.pages] // Page references + ) + ) + }, +) diff --git a/preview/glossy/tests/demo/ref/1.png b/preview/glossy/tests/demo/ref/1.png new file mode 100644 index 0000000..9b172a6 Binary files /dev/null and b/preview/glossy/tests/demo/ref/1.png differ diff --git a/preview/glossy/examples/readme.typ b/preview/glossy/tests/demo/test.typ similarity index 98% rename from preview/glossy/examples/readme.typ rename to preview/glossy/tests/demo/test.typ index 755baea..3b2b7d9 100644 --- a/preview/glossy/examples/readme.typ +++ b/preview/glossy/tests/demo/test.typ @@ -1,6 +1,6 @@ // a minimal example, also listed in the README -#import "@preview/glossy:0.2.0": * +#import "/lib.typ": * #let myGlossary = ( html: ( diff --git a/preview/glossy/tests/sorting/ref/1.png b/preview/glossy/tests/sorting/ref/1.png new file mode 100644 index 0000000..b1ed845 Binary files /dev/null and b/preview/glossy/tests/sorting/ref/1.png differ diff --git a/preview/glossy/tests/sorting/test.typ b/preview/glossy/tests/sorting/test.typ new file mode 100644 index 0000000..fc7b1bc --- /dev/null +++ b/preview/glossy/tests/sorting/test.typ @@ -0,0 +1,38 @@ +#import "/lib.typ": * + +#let myGlossary = ( + html: ( + short: "HTML", + long: "Hypertext Markup Language", + description: "A standard language for creating web pages", + ), + iphone: ( + short: "iPhone", + long: "iPhone", + ), + css: ( + short: "CSS", + long: "Cascading Style Sheets", + description: "A language used for describing the presentation of a document", + ), + atom: ( + short: "atom", + long: "atom", + ), + tps: ( + short: "TPS", + long: "test procedure specification", + description: "A document on how to run all the test procedures" + ), +) + +#show: init-glossary.with(myGlossary, show-term: (body) => [#emph(body)]) + += Refer to our terms so they show up in the glossary +@html, @iphone:long, @css, @css:cap:pl, @atom:pl, @tps:long + += Default sorting +#glossary(theme: theme-compact) + += Case-insensitive sorting (`ignore-case: true`) +#glossary(ignore-case: true, theme: theme-compact) diff --git a/preview/glossy/tests/theme-gallery/ref/1.png b/preview/glossy/tests/theme-gallery/ref/1.png new file mode 100644 index 0000000..a1d63ee Binary files /dev/null and b/preview/glossy/tests/theme-gallery/ref/1.png differ diff --git a/preview/glossy/tests/theme-gallery/test.typ b/preview/glossy/tests/theme-gallery/test.typ new file mode 100644 index 0000000..2edee14 --- /dev/null +++ b/preview/glossy/tests/theme-gallery/test.typ @@ -0,0 +1,54 @@ +#import "/lib.typ": * + +#let myGlossary = ( + html: ( + short: "HTML", + long: "Hypertext Markup Language", + description: "A standard language for creating web pages", + ), + iphone: ( + short: "iPhone", + description: "Revolutionized phones." + ), + css: ( + short: "CSS", + long: "Cascading Style Sheets", + description: "A language used for describing the presentation of a document", + ), + atom: ( + short: "atom", + ), + tps: ( + short: "TPS", + long: "test procedure specification", + description: "A document on how to run all the test procedures" + ), +) + +#show: init-glossary.with(myGlossary, show-term: (body) => [#emph(body)]) + +#set page( + margin: 1cm, + height: auto, +) + +#text(size: 24pt, weight: "bold")[`glossy`: theme gallery] + +#text(size: 1pt, fill: rgb(0,0,0,0))[ + First, we refer to each term at least once so they'll actually show up in the + glossaries. Our terms include: @atom, @iphone, @html:cap, @css, and @tps. +] +#v(-2em) + +#import "/src/themes.typ" +#for theme in dictionary(themes) { + block( + breakable: false, + width: 100%, + spacing: 2em, + inset: 1em, + stroke: 1pt+gray, + height: if theme.first() == "theme-twocol" { 1.5in } else { auto }, + glossary(title: raw(theme.first()), theme: theme.last(), ignore-case: true) + ) +} diff --git a/preview/glossy/themeshots.png b/preview/glossy/themeshots.png new file mode 100644 index 0000000..a1d63ee Binary files /dev/null and b/preview/glossy/themeshots.png differ diff --git a/preview/glossy/typst.toml b/preview/glossy/typst.toml index c68c503..ae96f73 100644 --- a/preview/glossy/typst.toml +++ b/preview/glossy/typst.toml @@ -1,6 +1,6 @@ [package] name = "glossy" -version = "0.2.0" +version = "0.2.1" entrypoint = "lib.typ" authors = ["Stephen Waits "] license = "MIT" @@ -8,4 +8,4 @@ description = "A very simple glossary system with easily customizable output." categories = ["model"] keywords = ["glossary", "index", "dictionary", "gloss"] repository = "https://github.com/swaits/typst-collection" -exclude = ["thumbnail.png", "examples/readme.pdf"] +exclude = ["thumbnail.png", "themeshots.pjng", "tests"]