diff --git a/preview/glossy/0.1.1/LICENSE b/preview/glossy/0.1.1/LICENSE deleted file mode 100644 index 35d39a7..0000000 --- a/preview/glossy/0.1.1/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Stephen Waits - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/preview/glossy/0.1.1/README.md b/preview/glossy/0.1.1/README.md deleted file mode 100644 index 01fe0b2..0000000 --- a/preview/glossy/0.1.1/README.md +++ /dev/null @@ -1,199 +0,0 @@ -# Glossy - -This package provides utilities to manage and render glossaries within -documents. It includes functions to define and use glossary terms, track their -usage, and generate a glossary list with references to where terms are used in -the document. - -![image of sample output](thumbnail.png) - -## Motivation - -Glossy is heavily inspired by -[glossarium](https://typst.app/universe/package/glossarium), with a few key -different goals: - -1. Provide a simple interface which allows for complete control over glossary - display. To do this, `glossy`'s `#glossary()` function accepts a theme - parameter. The goal here is to separate presentation and logic. -1. Simplify the user interface as much as possible. Glossy has exactly two - exports, `init-glossary` and `glossary`. -1. Double-down on `glossy`'s excellent `@term` reference approach, completely - eliminating the need to make any calls to `gls()` and friends. -1. Mimic established patterns and best practices. For example, `glossy`'s - `#glossary()` function is intentionally similar (in naming and parameters) to - `typst`'s built-int `#bibliography`, to the degree possible. -1. Simplify the implementation. The `glossy` code is significantly shorter and - easier to understand. - -## Features - -- Define glossary terms with short and long forms, descriptions, and grouping. -- Automatically tracks term usage in the document, making references simple - through @labels. -- Supports modifiers to adjust how terms are displayed (capitalize, pluralize, - etc.). -- Generates a formatted glossary section with backlinks to term occurrences in - the document. -- Easily customizable themes for rendering glossary sections, groups, and - entries. - -## Usage - -### Import the package - -```typst -#import "@preview/glossy:0.1.1": * - -``` - -### Defining Glossary Terms - -Use the `init-glossary` function to initialize glossary entries and process -document content: - -```typst -#let myGlossary = ( - ( - key: "html", - short: "HTML", - long: "Hypertext Markup Language", - description: "A standard language for creating web pages", - group: "Web" - ), - ( - key: "css", - short: "CSS", - long: "Cascading Style Sheets", - description: "A stylesheet language used for describing the presentation of a document", - group: "Web" - ), - ( - key: "tps", - short: "TPS", - long: "test procedure specification" - ), - // Add more entries as needed -) - -#show: init-glossary.with(myGlossary) - -// Your document content here, where glossary terms are used. -``` - -Note that you could also load glossary entries from a data file using -[#yaml()](https://typst.app/docs/reference/data-loading/yaml/), -[#json()](https://typst.app/docs/reference/data-loading/json/), or -[similar](https://typst.app/docs/reference/data-loading/). - -### Using Glossary Terms in Your Document - -Use glossary terms within your text using the `@reference` functionality in -typst. - -For example, `@html` and `@css` are used in the following text: - -```typst -In modern web development, languages like @html and @css are essential. - -Now make sure I get your @tps:short reports by 2pm! -``` - -... which should output: - -```text -In modern web development, languages like Hypertext Markup Language (HTML) and -Cascading Style Sheets (CSS) are essential. - -Now make sure I get your TPS reports by 2pm! -``` - -You can control how each term is displayed using the following modifiers: - -- **cap**: Capitalizes the term. -- **pl**: Uses the plural form of the term. -- **both**: Displays both the long and short forms (e.g., "Long Form (Short Form)"). -- **short**: Displays only the short form. -- **long**: Displays only the long form. - -Each modifier is separated by a colon (':') character. Examples: - -| **Input** | **Output** | -| ----------------------- | -------------------------------------------- | -| `@tps` (first use) | "technical procedure specification (TPS)" | -| `@tps` (subsequent use) | "TPS" | -| `@tps:short` | "TPS" | -| `@tps:long` | "technical procedure specification (TPS)" | -| `@tps:both` | "technical procedure specification (TPS)" | -| `@tps:long:cap` | "Technical procedure specification" | -| `@tps:long:pl` | "technical procedure specifications" | -| `@tps:short:pl` | "TPSes" | -| `@tps:both:pl:cap` | "Technical procedure specifications (TPSes)" | - -Combine modifiers in any way you like. But know that if you combine any of -`short`, `long`, and/or `both`, only one will be used. - -### Generating the Glossary - -To display the glossary in your document, use the `glossary` function. Customize -the title and specify a theme if desired: - -```typst -#glossary(title: "Web Development Glossary", theme: my-theme, groups: ("Web")) -``` - -### Themes - -Customize the appearance of the glossary using themes. Each theme defines how -sections, groups, and entries are displayed. - -- **section**: This is a main glossary section. It contains multiple glossary groups. -- **group**: A group within the main glossary section. Contains multiple - entries. Can be an empty string for terms with no group (`""`). -- **entry**: A single entry within a group. This is the actual term, and can - include any of the fields in entry. Contains the following fields: - - `key`: The term key. (always present and unique) - - `short`: The short form of the term. (always present) - - `long`: The long form. (can be `none`) - - `description`: The description or definition. (can be `none`) - - `pages`: Pages with links to term usage in doc. (always present) - -Example of a minimal theme: - -```typst -#let my-theme = ( - section: (title, body) => { - heading(level: 1, title) - body - }, - group: (name, i, n, body) => { - // i is the group's index, n is total number of groups - if name != "" and n > 1 { - heading(level: 2, name) - } - body - }, - entry: (entry, i, n) => { - // i is the entry's index, n is total number of entries - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) -``` - -## License - -This project is licensed under the MIT License. diff --git a/preview/glossy/0.1.1/examples/readme.pdf b/preview/glossy/0.1.1/examples/readme.pdf deleted file mode 100644 index 8b6f6ee..0000000 Binary files a/preview/glossy/0.1.1/examples/readme.pdf and /dev/null differ diff --git a/preview/glossy/0.1.1/examples/readme.typ b/preview/glossy/0.1.1/examples/readme.typ deleted file mode 100644 index a2b987d..0000000 --- a/preview/glossy/0.1.1/examples/readme.typ +++ /dev/null @@ -1,88 +0,0 @@ -// a minimal example, also listed in the README - -#import "@preview/glossy:0.1.1": * - -#let myGlossary = ( - (key: "html", short: "HTML", long: "Hypertext Markup Language", description: "A standard language for creating web pages", group: "Web"), - (key: "css", short: "CSS", long: "Cascading Style Sheets", description: "A language used for describing the presentation of a document", group: "Web"), - (key: "tps", short: "TPS", long: "test procedure specification"), - // Add more entries as needed -) - -#show: init-glossary.with(myGlossary) - -#set heading(numbering: "1.1") - -= Hello, `glossy`! - -In modern web development, languages like @html and @css are essential. - -Now make sure I get your @tps:short reports by 2pm! - -= Examples with modifiers - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - -[`@tps:short` ], [@tps:short], -[`@tps:long` ], [@tps:long], -[`@tps:both` ], [@tps:both], -[`@tps:long:cap` ], [@tps:long:cap], -[`@tps:long:pl` ], [@tps:long:pl], -[`@tps:short:pl` ], [@tps:short:pl], -[`@tps:both:pl:cap`], [@tps:both:pl:cap], -) - -== Using conflicting modifiers (short, long, both) - -These modifiers are semantically mutually exclusive, by they can be combined -syntactically. When multiple are used, `glossy` tries to make a smart choice on -which one to display. - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - -[`@tps:short:long` ], [@tps:short:long], -[`@tps:long:short` ], [@tps:long:short], -[`@tps:short:both` ], [@tps:short:both], -[`@tps:long:both` ], [@tps:long:both], -[`@tps:short:long:both` ], [@tps:short:long:both], -[`@tps:long:pl` ], [@tps:long:pl], -) - -#let my-theme = ( - section: (title, body) => { - heading(level: 1, title) - body - }, - group: (name, i, n, body) => { - if name != "" and n > 1 { - heading(level: 2, name) - } - body - }, - entry: (entry, i, n) => { - // i is the entry's index, n is total number of entries - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) - -#glossary(theme: my-theme) -#glossary(title: "Glossary with just empty group", groups: ("",), theme: my-theme) -#glossary(title: "Glossary with just Web group", groups: ("Web",), theme: my-theme) diff --git a/preview/glossy/0.1.1/lib.typ b/preview/glossy/0.1.1/lib.typ deleted file mode 100644 index f50143e..0000000 --- a/preview/glossy/0.1.1/lib.typ +++ /dev/null @@ -1 +0,0 @@ -#import "src/gloss.typ": init-glossary, glossary diff --git a/preview/glossy/0.1.1/src/gloss.typ b/preview/glossy/0.1.1/src/gloss.typ deleted file mode 100644 index 8c70355..0000000 --- a/preview/glossy/0.1.1/src/gloss.typ +++ /dev/null @@ -1,325 +0,0 @@ -#import "./themes.typ": * -#import "./utils.typ": * - -#let __gloss_entries = state("__gloss_entries", (:)) -#let __gloss_used = state("__gloss_used", (:)) - -#let __gloss_label_prefix = "__gloss:" - -// given an array of dictionaries, make sure each has all the keys we'll -// reference, using default values if needed -#let __normalize_entries(entry-list) = { - // TODO: panic if key or short missing, all others optional - let new-list = () - for entry in entry-list { - let long = entry.at("long", default: none) - let longplural = entry.at("longplural", default: none) - if long != none and longplural == none { - longplural = __pluralize(long) - } - - new-list.push(( - key: entry.key, - short: entry.short, - plural: entry.at("plural", default: __pluralize(entry.short)), - long: long, - longplural: longplural, - description: entry.at("description", default: none), - group: entry.at("group", default: ""), - )) - } - return new-list -} - -// update our state with a glossary entry -#let __add_entry(entry) = { - // make sure our final glossary state does not already have this key - if __gloss_entries.final().at(entry.key, default: false) == true { - panic("Glossary error. Duplicate key: " + entry.key) - } - - // add it to the state - __gloss_entries.update(st => { - st.insert(entry.key, entry) - return st - }) -} - -// fetch a glossary entry from our state, or panic -#let __get_entry(key) = { - let entries = __gloss_entries.final() - if key not in entries { - panic("Glossary error. Missing key: " + key) - } - - entries.at(key) -} - -// returns true if an entry with `key` is in our glossary state -#let __has_entry(key) = { - let entries = __gloss_entries.final() - key in entries -} - -// helper to prefix a key for label/reference scoping for the dictionary entry -#let __dict_label_str(key) = { - __gloss_label_prefix + key -} - -// helper to get a label with prefix for the dictionary entry -#let __dict_label(key) = { - label(__dict_label_str(key)) -} - -// helper to prefix a key for label/reference scoping for the term use in a doc -#let __term_label_str(key, index) = { - __gloss_label_prefix + key + "." + str(index) -} - -// helper to get a label with prefix for the term use in a doc -#let __term_label(key, index) = { - label(__term_label_str(key, index)) -} - -// update the usage count for a given key -#let __save_term_usage(key, count) = { - __gloss_used.update(st => { - st.insert(key, count) - return st; - }) -} - -// the main function which emits terms used in a document -// -// handles all the modifiers: -// -// - cap: capitalize the term -// - pl: pluralize the term -// - both: emit "Long form (short form)" -// - short: emit just the short form -// - long: emit just the long form -#let __gls( - key, - modifiers: array -) = { - - // Get the term - let entry = __get_entry(key) - let entry_label = __dict_label_str(key) - - // Lookup and increment the count - let entry_counter = counter(entry_label) - entry_counter.step() - - // See if this is the first use - let key_index = entry_counter.get().first() - let first = key_index == 0 - - // Count the entry as used so we can link back from glossary - __save_term_usage(entry.key, key_index + 1) - - // Helper: Apply pluralization if needed - let pluralize_term = (singular, plural) => { - if "pl" in modifiers and plural != none { plural } else { singular } - } - - // Helper: Capitalize term if "cap" modifier present - let capitalize_term = (term) => { - if "cap" in modifiers { upper(term.first()) + term.slice(1) } else { term } - } - - // Helper: Format a term with pluralization and capitalization - let format_term = (term, plural) => { - capitalize_term(pluralize_term(term, plural)) - } - - // Helper: Select and format the displayed term (long, short, or both) - let select_term = (is_long_mode, use_both) => { - // Derive pluralized and capitalized versions of the long and short forms - let short_form = format_term(entry.short, entry.plural) - let long_form = if entry.long != none { format_term(entry.long, entry.longplural) } else { none } - - // Return logic for term selection: - if use_both and long_form != none { - // 1. If "both" is requested AND we have a long form available: - // Show "Long Form (Short Form)" - [#long_form (#short_form)] - } else if is_long_mode and long_form != none { - // 2. If long mode is requested AND we have a long form available: - // Show just the long form - [#long_form] - } else { - // 3. Fallback case - use short form when: - // - "both" was requested but no long form exists - // - long mode was requested but no long form exists - // - short form was explicitly requested - // - no specific mode was requested - [#short_form] - } - } - - // Determine which form to display: "short", "long", or "both" - let is_both = "both" in modifiers - let is_long = "long" in modifiers and not is_both - let is_short = "short" in modifiers and not is_both and not is_long - - context { - // Final display logic - let display = if is_both or is_long or is_short { - // User requested specific behavior via modifiers - select_term(is_long, is_both) - } else { - // Default behavior: show "both" on first use, else "short" - select_term(false, first) - } - - // TODO: figure out how to link to __dict_label(key) if the glossary exists - // NOTE: this is a low priority, I don't think it's that important or useful. - - // Emit with labels for this instance of the term usage - [#display#metadata(display)#__term_label(key, key_index)] - } -} - -// Create all the backlinks to term uses in a doc, in the form of page numbers -// linked. Used when emitting a glossary, so its page numbers can link back to -// the uses. -#let __create_backlinks(key, count) = { - // create array of labels to link to - let labels = for i in range(count) { - (__term_label(key, i),) - } - - // create arrays of locations, and page number display text - let pages = labels - .map(l => { locate(l) }) - .map((loc) => { numbering(__default(loc.page-numbering(), "1"), loc.page()) }) - - // convert labels to links, filtering out duplicated pages - let seen = () - let links = for i in range(labels.len()) { - let l = labels.at(i) - let p = pages.at(i) - if seen.contains(p) { - (none,) - } else { - seen.push(p) - (link(l, p),) - } - }.filter(l => l != none) - - // connect links with commas and return - links.join(", ") -} - -// Main wrapper (usually used in a `#show: init-glossary`) which loads the -// entries passed in into our state. Furthermore, hooks into references so that -// we can intercept term usage in a doc and label them appropriately. -#let init-glossary(entries, body) = context { - for entry in __normalize_entries(entries) { - __add_entry(entry) - } - - // convert refs we recognize into links with labels - show ref: r => { - let (key, ..modifiers) = str(r.target).split(":") - if __has_entry(key) { - __gls(key, modifiers: modifiers) - } else { - r - } - } - - body -} - -// Used to print a glossary. Can customize title, theme, and/or specify groups. -// -// A theme is a dictionary with three attributes: -// -// #let my-theme = ( -// section: (title, body) => { -// // how to display the glossary section and its body which will contain -// // groups, each with their entries -// }, -// -// group: (name, body) => { -// // how to display a group name and its body (which will contain the -// // entries in that group) -// }, -// -// entry: (entry, i, n) => { -// // how to display a single entry, along with its index and total count -// } -// } - -#let glossary(title: "Glossary", theme: theme-2col, groups: ()) = context { - // our output is a map of group name to array of entries (each entry is a map) - let output = (:) - - // pull in entire dictionary - let all_entries = __gloss_entries.final() - - // filter down to just what we used - let all_used = __gloss_used.final() - - // TODO: what about entries with no group? Or group == "" or == none - - // get all groups - let all_groups = all_entries.values().map(e => e.at("group")).dedup().sorted() - let groups = if groups.len() == 0 { all_groups } else { groups } - - // make sure requested groups are legit - for g in groups { - if g not in all_groups { - panic("Requested group not found: " + g) - } - } - - // iterate one group at a time - for g in groups { - // collect used entries in this group - let cur = () - for (key, count) in all_used { - let e = all_entries.at(key) - if e.at("group") == g { - // this term is both in this group and was used, add to our output - let short = e.at("short") - let long = e.at("long") - let description = e.at("description") - let label = [#metadata(key)#__dict_label(key)] - let pages = __create_backlinks(key, count) - cur.push((short: short, long: long, description: description, label: label, pages: pages)) - } - } - if cur.len() > 0 { - if g == none { g = "" } - output.insert(g, cur.sorted(key: e => e.short)) - } - } - - // TODO: rendering for a) just one group, or b) the default group (ie terms - // with no group)?? -- in this case should we just not use the theme.group() - // function? - - // index for group because dictionary don't have enumerate() - let i_group = 0; - - // render it using our theme - (theme.section)( - title, - // section body (all groups) - for (group, entries) in output { - (theme.group)( - group, - i_group, - output.len(), - // group body (all entries) - for (i,e) in entries.enumerate() { - (theme.entry)(e, i, entries.len()) - }, - ) - i_group += 1 - } - ) -} diff --git a/preview/glossy/0.1.1/src/themes.typ b/preview/glossy/0.1.1/src/themes.typ deleted file mode 100644 index 2f61ee1..0000000 --- a/preview/glossy/0.1.1/src/themes.typ +++ /dev/null @@ -1,47 +0,0 @@ -#let theme-2col = ( - section: (title, body) => { - set par.line(numbering: none) - heading(level: 1, title) - columns(2, body) - }, - - group: (name, i, n, body) => { - if name != "" and n > 1 { - heading(level: 2, name) - } - body - }, - - entry: (entry, i, n) => { - // build our title, etc - let short-display = text(weight: "regular", entry.short) - let long-display = if entry.long == none { - [] - } else { - [#h(0.25em) -- #entry.long] - } - - // build our description - let description = if entry.description == none { - [] - } else { - [: #entry.description] - } - - 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], - [#long-display], - [#description], - [#repeat(h(0.25em) + "." + h(0.25em))], - [ . ], - [#entry.pages] - ) - ) - }, -) - diff --git a/preview/glossy/0.1.1/src/utils.typ b/preview/glossy/0.1.1/src/utils.typ deleted file mode 100644 index 9bc45bc..0000000 --- a/preview/glossy/0.1.1/src/utils.typ +++ /dev/null @@ -1,19 +0,0 @@ -#let __default(val, default) = if val == none { default } else { val } - -#let __pluralize(word) = { - // Define helper functions for checking endings - let ends_with = (suffix) => lower(word).ends-with(suffix) - let ends_with_any = (suffixes) => suffixes.any((suffix) => ends_with(suffix)) - - if ends_with_any(( "s", "x", "z", "sh", "ch" )) { // Handle special cases for "es" endings - word + "es" - } else if ends_with("y") and not ends_with_any(( "a", "e", "i", "o", "u" )) { // Handle "y" -> "ies" - word.slice(0, -1) + "ies" - } else if ends_with("f") { // Handle "f" or "fe" -> "ves" - word.slice(0, -1) + "ves" - } else if ends_with("fe") { - word.slice(0, -2) + "ves" - } else { // Default to adding "s" - word + "s" - } -} diff --git a/preview/glossy/0.1.1/thumbnail.png b/preview/glossy/0.1.1/thumbnail.png deleted file mode 100644 index 503272e..0000000 Binary files a/preview/glossy/0.1.1/thumbnail.png and /dev/null differ diff --git a/preview/glossy/0.1.1/typst.toml b/preview/glossy/0.1.1/typst.toml deleted file mode 100644 index 0ca295f..0000000 --- a/preview/glossy/0.1.1/typst.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "glossy" -version = "0.1.1" -entrypoint = "lib.typ" -authors = ["Stephen Waits "] -license = "MIT" -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"] diff --git a/preview/glossy/0.1.2/LICENSE b/preview/glossy/0.1.2/LICENSE deleted file mode 100644 index 35d39a7..0000000 --- a/preview/glossy/0.1.2/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Stephen Waits - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/preview/glossy/0.1.2/README.md b/preview/glossy/0.1.2/README.md deleted file mode 100644 index 0a5d378..0000000 --- a/preview/glossy/0.1.2/README.md +++ /dev/null @@ -1,245 +0,0 @@ -# Glossy - -This package provides utilities to manage and render glossaries within -documents. It includes functions to define and use glossary terms, track their -usage, and generate a glossary list with references to where terms are used in -the document. - -![image of sample output](thumbnail.png) - -## Motivation - -Glossy is heavily inspired by -[glossarium](https://typst.app/universe/package/glossarium), with a few key -different goals: - -1. Provide a simple interface which allows for complete control over glossary - display. To do this, `glossy`'s `#glossary()` function accepts a theme - parameter. The goal here is to separate presentation and logic. -1. Simplify the user interface as much as possible. Glossy has exactly two - exports, `init-glossary` and `glossary`. -1. Double-down on `glossy`'s excellent `@term` reference approach, completely - eliminating the need to make any calls to `gls()` and friends. -1. Mimic established patterns and best practices. For example, `glossy`'s - `#glossary()` function is intentionally similar (in naming and parameters) to - `typst`'s built-int `#bibliography`, to the degree possible. -1. Simplify the implementation. The `glossy` code is significantly shorter and - easier to understand. - -## Features - -- Define glossary terms with short and long forms, descriptions, and grouping. -- Automatically tracks term usage in the document, making references simple - through @labels. -- Supports modifiers to adjust how terms are displayed (capitalize, pluralize, - etc.). -- Generates a formatted glossary section with backlinks to term occurrences in - the document. -- Easily customizable themes for rendering glossary sections, groups, and - entries. - -## Usage - -### Import the package - -```typst -#import "@preview/glossy:0.1.2": * - -``` - -### Defining Glossary Terms - -Use the `init-glossary` function to initialize glossary entries and process -document content: - -```typst -#let myGlossary = ( - ( - key: "html", - short: "HTML", - long: "Hypertext Markup Language", - description: "A standard language for creating web pages", - group: "Web" - ), - ( - key: "css", - short: "CSS", - long: "Cascading Style Sheets", - description: "A stylesheet language used for describing the presentation of a document", - group: "Web" - ), - ( - key: "tps", - short: "TPS", - long: "test procedure specification" - ), - // Add more entries as needed -) - -#show: init-glossary.with(myGlossary) - -// Your document content here, where glossary terms are used. -``` - -Note that you could also load glossary entries from a data file using -[#yaml()](https://typst.app/docs/reference/data-loading/yaml/), -[#json()](https://typst.app/docs/reference/data-loading/json/), or -[similar](https://typst.app/docs/reference/data-loading/). - -For example, the above glossary could be in this YAML file: - -```yaml -- key: html, - short: HTML, - long: Hypertext Markup Language, - description: A standard language for creating web pages, - group: Web - -- key: css - short: CSS, - long: Cascading Style Sheets, - description: A stylesheet language used for describing the presentation of a document, - group: Web - -- key: tps, - short: TPS, - long: test procedure specification -``` - -And then loaded during initialization as follows: - -```typst -#show: init-glossary.with(yaml("glossary.yaml")) -``` - -### Using Glossary Terms in Your Document - -Use glossary terms within your text using the `@reference` functionality in -typst. - -For example, `@html` and `@css` are used in the following text: - -```typst -In modern web development, languages like @html and @css are essential. - -Now make sure I get your @tps:short reports by 2pm! -``` - -... which should output: - -```text -In modern web development, languages like Hypertext Markup Language (HTML) and -Cascading Style Sheets (CSS) are essential. - -Now make sure I get your TPS reports by 2pm! -``` - -You can control how each term is displayed using the following modifiers: - -- **cap**: Capitalizes the term. -- **pl**: Uses the plural form of the term. -- **both**: Displays both the long and short forms (e.g., "Long Form (Short Form)"). -- **short**: Displays only the short form. -- **long**: Displays only the long form. - -Each modifier is separated by a colon (':') character. Examples: - -| **Input** | **Output** | -| ----------------------- | -------------------------------------------- | -| `@tps` (first use) | "technical procedure specification (TPS)" | -| `@tps` (subsequent use) | "TPS" | -| `@tps:short` | "TPS" | -| `@tps:long` | "technical procedure specification (TPS)" | -| `@tps:both` | "technical procedure specification (TPS)" | -| `@tps:long:cap` | "Technical procedure specification" | -| `@tps:long:pl` | "technical procedure specifications" | -| `@tps:short:pl` | "TPSes" | -| `@tps:both:pl:cap` | "Technical procedure specifications (TPSes)" | - -Combine modifiers in any way you like. But know that if you combine any of -`short`, `long`, and/or `both`, only one will be used. - -### Generating the Glossary - -To display the glossary in your document, use the `glossary` function. Customize -the title and specify a theme if desired: - -```typst -#glossary(title: "Web Development Glossary", theme: my-theme, groups: ("Web")) -``` - -See more on theming below in "Glossary Themes." - -### Customizing the Output - -#### Term usage in documents - -You can control how terms are displayed when using the `@term` reference syntax -in your document by passing a custom function to `init-glossary` in the -`show-term` parameter. - -For example, if you wanted all terms to be italicized, you could do this: - -```typst -#let emph-term(body) = { emph(body) } - -#show: init-glossary.with(myGlossary, show-term: emph-term) - -// remainder of document follows -``` - -#### Glossary Themes - -Customize the appearance of the glossary using themes. Each theme defines how -sections, groups, and entries are displayed. - -- **section**: This is a main glossary section. It contains multiple glossary groups. -- **group**: A group within the main glossary section. Contains multiple - entries. Can be an empty string for terms with no group (`""`). -- **entry**: A single entry within a group. This is the actual term, and can - include any of the fields in entry. Contains the following fields: - - `key`: The term key. (always present and unique) - - `short`: The short form of the term. (always present) - - `long`: The long form. (can be `none`) - - `description`: The description or definition. (can be `none`) - - `pages`: Pages with links to term usage in doc. (always present) - -Example of a minimal theme: - -```typst -#let my-theme = ( - section: (title, body) => { - heading(level: 1, title) - body - }, - group: (name, i, n, body) => { - // i is the group's index, n is total number of groups - if name != "" and n > 1 { - heading(level: 2, name) - } - body - }, - entry: (entry, i, n) => { - // i is the entry's index, n is total number of entries - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) -``` - -## License - -This project is licensed under the MIT License. diff --git a/preview/glossy/0.1.2/examples/readme.pdf b/preview/glossy/0.1.2/examples/readme.pdf deleted file mode 100644 index 8703a04..0000000 Binary files a/preview/glossy/0.1.2/examples/readme.pdf and /dev/null differ diff --git a/preview/glossy/0.1.2/examples/readme.typ b/preview/glossy/0.1.2/examples/readme.typ deleted file mode 100644 index 1ae581b..0000000 --- a/preview/glossy/0.1.2/examples/readme.typ +++ /dev/null @@ -1,172 +0,0 @@ -// a minimal example, also listed in the README - -#import "@preview/glossy:0.1.2": * - -#let myGlossary = ( - (key: "html", short: "HTML", long: "Hypertext Markup Language", description: "A standard language for creating web pages", group: "Web"), - (key: "css", short: "CSS", long: "Cascading Style Sheets", description: "A language used for describing the presentation of a document", group: "Web"), - (key: "tps", short: "TPS", long: "test procedure specification"), - // Add more entries as needed -) - -#show: init-glossary.with(myGlossary, show-term: (body) => [#emph(body)]) - -#set heading(numbering: "1.1") - -= Hello, `glossy`! - -In modern web development, languages like @html and @css are essential. - -Now make sure I get your @tps:short reports by 2pm! - -= Examples with modifiers - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - - [`@tps:short` ], [@tps:short], - [`@tps:long` ], [@tps:long], - [`@tps:both` ], [@tps:both], - [`@tps:long:cap` ], [@tps:long:cap], - [`@tps:long:pl` ], [@tps:long:pl], - [`@tps:short:pl` ], [@tps:short:pl], - [`@tps:both:pl:cap`], [@tps:both:pl:cap], -) - -== Using conflicting modifiers (short, long, both) - -These modifiers are semantically mutually exclusive, by they can be combined -syntactically. When multiple are used, `glossy` tries to make a smart choice on -which one to display. - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - - [`@tps:short:long` ], [@tps:short:long], - [`@tps:short:both` ], [@tps:short:both], - [`@tps:long:both` ], [@tps:long:both], - [`@tps:short:long:both`], [@tps:short:long:both], -) - -#let my-theme = ( - section: (title, body) => { - heading(level: 1, title) - body - }, - group: (name, i, n, body) => { - if name != "" and n > 1 { - heading(level: 2, name) - } - body - }, - entry: (entry, i, n) => { - // i is the entry's index, n is total number of entries - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) - -#glossary(theme: my-theme) -#glossary(title: "Glossary with just empty group", groups: ("",), theme: my-theme) -#glossary(title: "Glossary with just Web group", groups: ("Web",), theme: my-theme) -// a minimal example, also listed in the README - -#import "@preview/glossy:0.1.2": * - -#let myGlossary = ( - (key: "html", short: "HTML", long: "Hypertext Markup Language", description: "A standard language for creating web pages", group: "Web"), - (key: "css", short: "CSS", long: "Cascading Style Sheets", description: "A language used for describing the presentation of a document", group: "Web"), - (key: "tps", short: "TPS", long: "test procedure specification"), - // Add more entries as needed -) - -#show: init-glossary.with(myGlossary) - -#set heading(numbering: "1.1") - -= Hello, `glossy`! - -In modern web development, languages like @html and @css are essential. - -Now make sure I get your @tps:short reports by 2pm! - -= Examples with modifiers - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - -[`@tps:short` ], [@tps:short], -[`@tps:long` ], [@tps:long], -[`@tps:both` ], [@tps:both], -[`@tps:long:cap` ], [@tps:long:cap], -[`@tps:long:pl` ], [@tps:long:pl], -[`@tps:short:pl` ], [@tps:short:pl], -[`@tps:both:pl:cap`], [@tps:both:pl:cap], -) - -== Using conflicting modifiers (short, long, both) - -These modifiers are semantically mutually exclusive, by they can be combined -syntactically. When multiple are used, `glossy` tries to make a smart choice on -which one to display. - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - - [`@tps:short:long` ], [@tps:short:long], - [`@tps:short:both` ], [@tps:short:both], - [`@tps:long:both` ], [@tps:long:both], - [`@tps:short:long:both` ], [@tps:short:long:both], -) - -#let my-theme = ( - section: (title, body) => { - heading(level: 1, title) - body - }, - group: (name, i, n, body) => { - if name != "" and n > 1 { - heading(level: 2, name) - } - body - }, - entry: (entry, i, n) => { - // i is the entry's index, n is total number of entries - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) - -#glossary(theme: my-theme) -#glossary(title: "Glossary with just empty group", groups: ("",), theme: my-theme) -#glossary(title: "Glossary with just Web group", groups: ("Web",), theme: my-theme) diff --git a/preview/glossy/0.1.2/lib.typ b/preview/glossy/0.1.2/lib.typ deleted file mode 100644 index f50143e..0000000 --- a/preview/glossy/0.1.2/lib.typ +++ /dev/null @@ -1 +0,0 @@ -#import "src/gloss.typ": init-glossary, glossary diff --git a/preview/glossy/0.1.2/src/gloss.typ b/preview/glossy/0.1.2/src/gloss.typ deleted file mode 100644 index f5306b6..0000000 --- a/preview/glossy/0.1.2/src/gloss.typ +++ /dev/null @@ -1,327 +0,0 @@ -#import "./themes.typ": * -#import "./utils.typ": * - -#let __gloss_entries = state("__gloss_entries", (:)) -#let __gloss_used = state("__gloss_used", (:)) - -#let __gloss_label_prefix = "__gloss:" - -// given an array of dictionaries, make sure each has all the keys we'll -// reference, using default values if needed -#let __normalize_entries(entry-list) = { - // TODO: panic if key or short missing, all others optional - let new-list = () - for entry in entry-list { - let long = entry.at("long", default: none) - let longplural = entry.at("longplural", default: none) - if long != none and longplural == none { - longplural = __pluralize(long) - } - - new-list.push(( - key: entry.key, - short: entry.short, - plural: entry.at("plural", default: __pluralize(entry.short)), - long: long, - longplural: longplural, - description: entry.at("description", default: none), - group: entry.at("group", default: ""), - )) - } - return new-list -} - -// update our state with a glossary entry -#let __add_entry(entry) = { - // make sure our final glossary state does not already have this key - if __gloss_entries.final().at(entry.key, default: false) == true { - panic("Glossary error. Duplicate key: " + entry.key) - } - - // add it to the state - __gloss_entries.update(st => { - st.insert(entry.key, entry) - return st - }) -} - -// fetch a glossary entry from our state, or panic -#let __get_entry(key) = { - let entries = __gloss_entries.final() - if key not in entries { - panic("Glossary error. Missing key: " + key) - } - - entries.at(key) -} - -// returns true if an entry with `key` is in our glossary state -#let __has_entry(key) = { - let entries = __gloss_entries.final() - key in entries -} - -// helper to prefix a key for label/reference scoping for the dictionary entry -#let __dict_label_str(key) = { - __gloss_label_prefix + key -} - -// helper to get a label with prefix for the dictionary entry -#let __dict_label(key) = { - label(__dict_label_str(key)) -} - -// helper to prefix a key for label/reference scoping for the term use in a doc -#let __term_label_str(key, index) = { - __gloss_label_prefix + key + "." + str(index) -} - -// helper to get a label with prefix for the term use in a doc -#let __term_label(key, index) = { - label(__term_label_str(key, index)) -} - -// update the usage count for a given key -#let __save_term_usage(key, count) = { - __gloss_used.update(st => { - st.insert(key, count) - return st; - }) -} - -// the main function which emits terms used in a document -// -// handles all the modifiers: -// -// - cap: capitalize the term -// - pl: pluralize the term -// - both: emit "Long form (short form)" -// - short: emit just the short form -// - long: emit just the long form -#let __gls( - key, - modifiers: array -) = { - - // Get the term - let entry = __get_entry(key) - let entry_label = __dict_label_str(key) - - // Lookup and increment the count - let entry_counter = counter(entry_label) - entry_counter.step() - - // See if this is the first use - let key_index = entry_counter.get().first() - let first = key_index == 0 - - // Count the entry as used so we can link back from glossary - __save_term_usage(entry.key, key_index + 1) - - // Helper: Apply pluralization if needed - let pluralize_term = (singular, plural) => { - if "pl" in modifiers and plural != none { plural } else { singular } - } - - // Helper: Capitalize term if "cap" modifier present - let capitalize_term = (term) => { - if "cap" in modifiers { upper(term.first()) + term.slice(1) } else { term } - } - - // Helper: Format a term with pluralization and capitalization - let format_term = (term, plural) => { - capitalize_term(pluralize_term(term, plural)) - } - - // Helper: Select and format the displayed term (long, short, or both) - let select_term = (is_long_mode, use_both) => { - // Derive pluralized and capitalized versions of the long and short forms - let short_form = format_term(entry.short, entry.plural) - let long_form = if entry.long != none { format_term(entry.long, entry.longplural) } else { none } - - // Return logic for term selection: - if use_both and long_form != none { - // 1. If "both" is requested AND we have a long form available: - // Show "Long Form (Short Form)" - [#long_form (#short_form)] - } else if is_long_mode and long_form != none { - // 2. If long mode is requested AND we have a long form available: - // Show just the long form - [#long_form] - } else { - // 3. Fallback case - use short form when: - // - "both" was requested but no long form exists - // - long mode was requested but no long form exists - // - short form was explicitly requested - // - no specific mode was requested - [#short_form] - } - } - - // Determine which form to display: "short", "long", or "both" - let is_both = "both" in modifiers - let is_long = "long" in modifiers and not is_both - let is_short = "short" in modifiers and not is_both and not is_long - - context { - // Final display logic - let display = if is_both or is_long or is_short { - // User requested specific behavior via modifiers - select_term(is_long, is_both) - } else { - // Default behavior: show "both" on first use, else "short" - select_term(false, first) - } - - // TODO: figure out how to link to __dict_label(key) if the glossary exists - // NOTE: this is a low priority, I don't think it's that important or useful. - - // Emit with labels for this instance of the term usage - [#display#metadata(display)#__term_label(key, key_index)] - } -} - -// Create all the backlinks to term uses in a doc, in the form of page numbers -// linked. Used when emitting a glossary, so its page numbers can link back to -// the uses. -#let __create_backlinks(key, count) = { - // create array of labels to link to - let labels = for i in range(count) { - (__term_label(key, i),) - } - - // create arrays of locations, and page number display text - let pages = labels - .map(l => { locate(l) }) - .map((loc) => { numbering(__default(loc.page-numbering(), "1"), loc.page()) }) - - // convert labels to links, filtering out duplicated pages - let seen = () - let links = for i in range(labels.len()) { - let l = labels.at(i) - let p = pages.at(i) - if seen.contains(p) { - (none,) - } else { - seen.push(p) - (link(l, p),) - } - }.filter(l => l != none) - - // connect links with commas and return - links.join(", ") -} - -// Main wrapper (usually used in a `#show: init-glossary`) which loads the -// entries passed in into our state. Furthermore, hooks into references so that -// we can intercept term usage in a doc and label them appropriately. -// -// Pass a function in `show-term(term-body)` to control how you display terms. -#let init-glossary(entries, show-term: (term-body) => term-body, body) = context { - for entry in __normalize_entries(entries) { - __add_entry(entry) - } - - // convert refs we recognize into links with labels - show ref: r => { - let (key, ..modifiers) = str(r.target).split(":") - if __has_entry(key) { - show-term(__gls(key, modifiers: modifiers)) - } else { - r - } - } - - body -} - -// Used to print a glossary. Can customize title, theme, and/or specify groups. -// -// A theme is a dictionary with three attributes: -// -// #let my-theme = ( -// section: (title, body) => { -// // how to display the glossary section and its body which will contain -// // groups, each with their entries -// }, -// -// group: (name, body) => { -// // how to display a group name and its body (which will contain the -// // entries in that group) -// }, -// -// entry: (entry, i, n) => { -// // how to display a single entry, along with its index and total count -// } -// } - -#let glossary(title: "Glossary", theme: theme-2col, groups: ()) = context { - // our output is a map of group name to array of entries (each entry is a map) - let output = (:) - - // pull in entire dictionary - let all_entries = __gloss_entries.final() - - // filter down to just what we used - let all_used = __gloss_used.final() - - // TODO: what about entries with no group? Or group == "" or == none - - // get all groups - let all_groups = all_entries.values().map(e => e.at("group")).dedup().sorted() - let groups = if groups.len() == 0 { all_groups } else { groups } - - // make sure requested groups are legit - for g in groups { - if g not in all_groups { - panic("Requested group not found: " + g) - } - } - - // iterate one group at a time - for g in groups { - // collect used entries in this group - let cur = () - for (key, count) in all_used { - let e = all_entries.at(key) - if e.at("group") == g { - // this term is both in this group and was used, add to our output - let short = e.at("short") - let long = e.at("long") - let description = e.at("description") - let label = [#metadata(key)#__dict_label(key)] - let pages = __create_backlinks(key, count) - cur.push((short: short, long: long, description: description, label: label, pages: pages)) - } - } - if cur.len() > 0 { - if g == none { g = "" } - output.insert(g, cur.sorted(key: e => e.short)) - } - } - - // TODO: rendering for a) just one group, or b) the default group (ie terms - // with no group)?? -- in this case should we just not use the theme.group() - // function? - - // index for group because dictionary don't have enumerate() - let i_group = 0; - - // render it using our theme - (theme.section)( - title, - // section body (all groups) - for (group, entries) in output { - (theme.group)( - group, - i_group, - output.len(), - // group body (all entries) - for (i,e) in entries.enumerate() { - (theme.entry)(e, i, entries.len()) - }, - ) - i_group += 1 - } - ) -} diff --git a/preview/glossy/0.1.2/src/themes.typ b/preview/glossy/0.1.2/src/themes.typ deleted file mode 100644 index 2f61ee1..0000000 --- a/preview/glossy/0.1.2/src/themes.typ +++ /dev/null @@ -1,47 +0,0 @@ -#let theme-2col = ( - section: (title, body) => { - set par.line(numbering: none) - heading(level: 1, title) - columns(2, body) - }, - - group: (name, i, n, body) => { - if name != "" and n > 1 { - heading(level: 2, name) - } - body - }, - - entry: (entry, i, n) => { - // build our title, etc - let short-display = text(weight: "regular", entry.short) - let long-display = if entry.long == none { - [] - } else { - [#h(0.25em) -- #entry.long] - } - - // build our description - let description = if entry.description == none { - [] - } else { - [: #entry.description] - } - - 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], - [#long-display], - [#description], - [#repeat(h(0.25em) + "." + h(0.25em))], - [ . ], - [#entry.pages] - ) - ) - }, -) - diff --git a/preview/glossy/0.1.2/src/utils.typ b/preview/glossy/0.1.2/src/utils.typ deleted file mode 100644 index 9bc45bc..0000000 --- a/preview/glossy/0.1.2/src/utils.typ +++ /dev/null @@ -1,19 +0,0 @@ -#let __default(val, default) = if val == none { default } else { val } - -#let __pluralize(word) = { - // Define helper functions for checking endings - let ends_with = (suffix) => lower(word).ends-with(suffix) - let ends_with_any = (suffixes) => suffixes.any((suffix) => ends_with(suffix)) - - if ends_with_any(( "s", "x", "z", "sh", "ch" )) { // Handle special cases for "es" endings - word + "es" - } else if ends_with("y") and not ends_with_any(( "a", "e", "i", "o", "u" )) { // Handle "y" -> "ies" - word.slice(0, -1) + "ies" - } else if ends_with("f") { // Handle "f" or "fe" -> "ves" - word.slice(0, -1) + "ves" - } else if ends_with("fe") { - word.slice(0, -2) + "ves" - } else { // Default to adding "s" - word + "s" - } -} diff --git a/preview/glossy/0.1.2/thumbnail.png b/preview/glossy/0.1.2/thumbnail.png deleted file mode 100644 index 503272e..0000000 Binary files a/preview/glossy/0.1.2/thumbnail.png and /dev/null differ diff --git a/preview/glossy/0.1.2/typst.toml b/preview/glossy/0.1.2/typst.toml deleted file mode 100644 index 73eb0dd..0000000 --- a/preview/glossy/0.1.2/typst.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "glossy" -version = "0.1.2" -entrypoint = "lib.typ" -authors = ["Stephen Waits "] -license = "MIT" -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"] diff --git a/preview/glossy/0.2.0/LICENSE b/preview/glossy/0.2.0/LICENSE deleted file mode 100644 index 35d39a7..0000000 --- a/preview/glossy/0.2.0/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Stephen Waits - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/preview/glossy/0.2.0/README.md b/preview/glossy/0.2.0/README.md deleted file mode 100644 index bc7b5c2..0000000 --- a/preview/glossy/0.2.0/README.md +++ /dev/null @@ -1,280 +0,0 @@ -# Glossy - -This package provides utilities to manage and render glossaries within -documents. It includes functions to define and use glossary terms, track their -usage, and generate a glossary list with references to where terms are used in -the document. - -![image of sample output](thumbnail.png) - -## Motivation - -Glossy is heavily inspired by -[glossarium](https://typst.app/universe/package/glossarium), with a few key -different goals: - -1. Provide a simple interface which allows for complete control over glossary - display. To do this, `glossy`'s `#glossary()` function accepts a theme - parameter. The goal here is to separate presentation and logic. -2. Simplify the user interface as much as possible. Glossy has exactly two - exports, `init-glossary` and `glossary`. -3. Double-down on `glossy`'s excellent `@term` reference approach, completely - eliminating the need to make any calls to `gls()` and friends. -4. Mimic established patterns and best practices. For example, `glossy`'s - `#glossary()` function is intentionally similar (in naming and parameters) to - `typst`'s built-in `#bibliography`, to the degree possible. -5. Simplify the implementation. The `glossy` code is significantly shorter and - easier to understand. - -## Features - -- Define glossary terms with short and long forms, descriptions, and grouping -- Automatically tracks term usage in the document through `@labels` -- Supports modifiers to adjust how terms are displayed (capitalize, pluralize, etc.) -- Generates a formatted glossary section with backlinks to term occurrences -- Customizable themes for rendering glossary sections, groups, and entries -- Automatic pluralization of terms with custom override options -- Page number references back to term usage locations - -## Usage - -### Import the package - -```typst -#import "@preview/glossy:0.2.0": * -``` - -### Defining Glossary Terms - -Use the `init-glossary` function to initialize glossary entries: - -```typst -#let myGlossary = ( - html: ( - short: "HTML", - long: "Hypertext Markup Language", - description: "A standard language for creating web pages", - group: "Web" - ), - css: ( - short: "CSS", - long: "Cascading Style Sheets", - description: "A stylesheet language used for describing the presentation of a document", - group: "Web" - ), - tps: ( - short: "TPS", - long: "test procedure specification", - description: "A formal document describing test steps and expected results", - // Optional: Override automatic pluralization - plural: "TPSes", - longplural: "test procedure specifications" - ) -) - -#show: init-glossary.with(myGlossary) -``` - -Each glossary entry supports the following fields: - -- `short` (required): Short form of the term -- `long` (optional): Long form of the term -- `description` (optional): Term description (often a definition) -- `group` (optional): Category grouping -- `plural` (optional): Override automatic pluralization of short form -- `longplural` (optional): Override automatic pluralization of long form - -You can also load glossary entries from a data file using #yaml(), #json(), or similar. - -For example, the above glossary could be in this YAML file: - -```yaml -html: - short: HTML - long: Hypertext Markup Language - description: A standard language for creating web pages - group: Web - -css: - short: CSS - long: Cascading Style Sheets - description: A stylesheet language used for describing the presentation of a document - group: Web - -tps: - short: TPS - long: test procedure specification - description: A formal document describing test steps and expected results - plural: TPSes - longplural: test procedure specifications -``` - -And then loaded during initialization as follows: - -```typst -#show: init-glossary.with(yaml("glossary.yaml")) -``` - -### Using Glossary Terms - -Reference glossary terms using Typst's `@reference` syntax: - -```typst -In modern web development, languages like @html and @css are essential. -The @tps:pl need to be submitted by Friday. -``` - -Available modifiers: - -- **cap**: Capitalizes the term -- **pl**: Uses the plural form -- **both**: Shows "Long Form (Short Form)" -- **short**: Shows only short form -- **long**: Shows only long form -- **def** or **desc**: Shows the description - -Modifiers can be combined with colons: - -| **Input** | **Output** | -| ------------------- | -------------------------------------------------------------- | -| `@tps` (first use) | "test procedure specification (TPS)" | -| `@tps` (subsequent) | "TPS" | -| `@tps:short` | "TPS" | -| `@tps:long` | "test procedure specification" | -| `@tps:both` | "test procedure specification (TPS)" | -| `@tps:long:cap` | "Test procedure specification" | -| `@tps:long:pl` | "test procedure specifications" | -| `@tps:short:pl` | "TPSes" | -| `@tps:both:pl:cap` | "Technical procedure specifications (TPSes)" | -| `@tps:def` | "A formal document describing test steps and expected results" | - -### Generating the Glossary - -Display the glossary using the `glossary()` function: - -```typst -#glossary( - title: "Web Development Glossary", - theme: my-theme, - groups: ("Web") // Optional: Filter to specific groups -) -``` - -### Customizing Term Display - -Control how terms appear in the document by providing a custom `show-term` function: - -```typst -#let emph-term(term-body) = { emph(term-body) } - -#show: init-glossary.with( - myGlossary, - show-term: emph-term -) -``` - -### Glossary Themes - -#### Included 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) -``` - -#### Custom Themes - -Customize glossary appearance by defining a theme with three functions: - -```typst -#let my-theme = ( - // Main glossary section - section: (title, body) => { - heading(level: 1, title) - body - }, - - // Group of related terms - group: (name, index, total, body) => { - // index = group index, total = total groups - if name != "" and total > 1 { - heading(level: 2, name) - } - body - }, - - // Individual glossary entry - entry: (entry, index, total) => { - // index = entry index, total = total entries in group - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) -``` - -Entry fields available to themes: - -- `short`: Short form (always present) -- `long`: Long form (can be `none`) -- `description`: Term description (can be `none`) -- `label`: Term's dictionary label -- `pages`: Linked page numbers where term appears - -## License - -This project is licensed under the MIT License. diff --git a/preview/glossy/0.2.0/examples/readme.pdf b/preview/glossy/0.2.0/examples/readme.pdf deleted file mode 100644 index bd1de40..0000000 Binary files a/preview/glossy/0.2.0/examples/readme.pdf and /dev/null differ diff --git a/preview/glossy/0.2.0/examples/readme.typ b/preview/glossy/0.2.0/examples/readme.typ deleted file mode 100644 index 755baea..0000000 --- a/preview/glossy/0.2.0/examples/readme.typ +++ /dev/null @@ -1,99 +0,0 @@ -// a minimal example, also listed in the README - -#import "@preview/glossy:0.2.0": * - -#let myGlossary = ( - html: ( - short: "HTML", - long: "Hypertext Markup Language", - description: "A standard language for creating web pages", - group: "Web"), - css: ( - short: "CSS", - long: "Cascading Style Sheets", - description: "A language used for describing the presentation of a document", - group: "Web"), - 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 heading(numbering: "1.1") - -= Hello, `glossy`! -In modern web development, languages like @html and @css are essential. - -Now make sure I get your @tps:short reports by 2pm! - -@tps:cap is defined as: @tps:def. - -= Examples with modifiers - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - - [`@tps:short` ], [@tps:short], - [`@tps:long` ], [@tps:long], - [`@tps:both` ], [@tps:both], - [`@tps:long:cap` ], [@tps:long:cap], - [`@tps:long:pl` ], [@tps:long:pl], - [`@tps:short:pl` ], [@tps:short:pl], - [`@tps:both:pl:cap`], [@tps:both:pl:cap], - [`@tps:def` ], [@tps:def], - [`@tps:desc` ], [@tps:desc], -) - -== Using conflicting modifiers (short, long, both) - -These modifiers are semantically mutually exclusive, by they can be combined -syntactically. When multiple are used, `glossy` tries to make a smart choice on -which one to display. - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - - [`@tps:short:long` ], [@tps:short:long], - [`@tps:short:both` ], [@tps:short:both], - [`@tps:long:both` ], [@tps:long:both], - [`@tps:short:long:both`], [@tps:short:long:both], -) - -#let my-theme = ( - section: (title, body) => { - heading(level: 1, title) - body - }, - group: (name, i, n, body) => { - if name != "" and n > 1 { - heading(level: 2, name) - } - body - }, - entry: (entry, i, n) => { - // i is the entry's index, n is total number of entries - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) - -#glossary(theme: my-theme) -#glossary(title: "Glossary with just empty group", groups: ("",), theme: theme-basic) -#glossary(title: "Glossary with just Web group", groups: ("Web",), theme: theme-compact) diff --git a/preview/glossy/0.2.0/lib.typ b/preview/glossy/0.2.0/lib.typ deleted file mode 100644 index eeff265..0000000 --- a/preview/glossy/0.2.0/lib.typ +++ /dev/null @@ -1,2 +0,0 @@ -#import "src/gloss.typ": init-glossary, glossary -#import "src/themes.typ": * diff --git a/preview/glossy/0.2.0/src/gloss.typ b/preview/glossy/0.2.0/src/gloss.typ deleted file mode 100644 index 092a887..0000000 --- a/preview/glossy/0.2.0/src/gloss.typ +++ /dev/null @@ -1,464 +0,0 @@ -#import "./themes.typ": * -#import "./utils.typ": * - -#let __gloss_entries = state("__gloss_entries", (:)) -#let __gloss_used = state("__gloss_used", (:)) - -#let __gloss_label_prefix = "__gloss:" - -// Normalizes a dictionary entry by ensuring all required and optional keys exist -// with appropriate default values. -// -// Parameters: -// entry (dictionary): Input dictionary containing at least 'short' and optionally -// 'long', 'plural', 'longplural', 'description', and 'group' -// -// Returns: -// dictionary: Normalized dictionary with all expected keys populated -// -// Throws: -// - If 'short' key is missing -// - If 'short' value is not a string -// - If 'short' value is an empty string -// -#let __normalize_entry(entry) = { - // Validate required fields - if not "short" in entry { - panic("Entry must contain a 'short' key") - } - if type(entry.short) != "string" { - panic("Entry 'short' must be a string") - } - if entry.short.trim() == "" { - panic("Entry 'short' cannot be empty") - } - - // Extract values with defaults, using consistent none for missing optionals - let long = entry.at("long", default: none) - - // Return the normalized entry - ( - short: entry.short, - plural: entry.at("plural", default: __pluralize(entry.short)), - long: long, - longplural: entry.at("longplural", default: __pluralize(long)), - description: entry.at("description", default: none), - group: entry.at("group", default: "") - ) -} - -// Checks if a key exists in the glossary state. -// -// Parameters: -// key (string): The key to look up in the glossary entries -// -// Returns: -// boolean: True if the key exists in the glossary, false otherwise -// -#let __has_entry(key) = { - key in __gloss_entries.final().keys() -} - -// Updates the glossary state by adding or updating an entry. -// -// Parameters: -// key (string): The key under which to store the entry -// entry (dictionary): The normalized glossary entry to store -// -// Returns: -// none: Updates state as a side effect -// -#let __add_entry(key, entry) = { - __gloss_entries.update(state => { - state.insert(key, entry) - state - }) -} - -// Retrieves a glossary entry from the state. -// -// Parameters: -// key (string): The key of the entry to retrieve -// -// Returns: -// dictionary: The glossary entry associated with the key -// -// Throws: -// - If the key does not exist in the glossary -// -#let __get_entry(key) = { - let entries = __gloss_entries.final() - if key not in entries.keys() { - panic("Glossary error: Missing key '" + key + "'") - } - - entries.at(key) -} - -// Creates a prefixed label string for dictionary entries and term usage. -// -// Parameters: -// key (string): The glossary entry key -// -// Returns: -// string: The prefixed label string for dictionary entries -// -#let __dict_label_str(key) = { - __gloss_label_prefix + key -} - -// Creates a label object for dictionary entries. -// -// Parameters: -// key (string): The glossary entry key -// -// Returns: -// label: A Typst label object for the dictionary entry -// -#let __dict_label(key) = { - label(__dict_label_str(key)) -} - -// Creates a prefixed label string for term usage in documents. -// -// Parameters: -// key (string): The glossary entry key -// index (int): The occurrence index of the term in the document -// -// Returns: -// string: The prefixed label string for term usage -// -#let __term_label_str(key, index) = { - __gloss_label_prefix + key + "." + str(index) -} - -// Creates a label object for term usage in documents. -// -// Parameters: -// key (string): The glossary entry key -// index (int): The occurrence index of the term in the document -// -// Returns: -// label: A Typst label object for the term usage -// -#let __term_label(key, index) = { - label(__term_label_str(key, index)) -} - -// Updates the term usage count in the glossary state. -// -// Parameters: -// key (string): The glossary entry key -// count (int): The number of times the term has been used -// -// Returns: -// none: Updates state as a side effect -// -#let __save_term_usage(key, count) = { - __gloss_used.update(state => { - state.insert(key, count) - state - }) -} - -// Renders a glossary term with various formatting options. -// -// This function handles the display of terms in the document, managing both first -// and subsequent uses, pluralization, capitalization, and different display forms -// (short, long, or combined forms). -// -// Parameters: -// key (string): The glossary entry key to render -// modifiers (array): Array of modifier strings that control term rendering: -// - "cap": Capitalize the first letter of the term -// - "pl": Use plural form of the term -// - "both": Show "Long form (short form)" -// - "short": Show only the short form -// - "long": Show only the long form -// - "def" or "desc": Show the term's description instead -// -// Returns: -// content: Formatted term with appropriate labels and metadata -// -// Behavior: -// - Without modifiers, first use shows "Long form (short form)", subsequent uses show short form -// - Explicit modifiers override the default first-use behavior -// - If long form is requested but unavailable, falls back to short form -// - Maintains usage counter for term references -// -#let __gls(key, modifiers: array) = { - // Fetch the entry and prepare label - let entry = __get_entry(key) - let entry_label = __dict_label_str(key) - - // Handle description mode - if "def" in modifiers or "desc" in modifiers { - return [#entry.description] - } - - // Track term usage - let entry_counter = counter(entry_label) - entry_counter.step() - let key_index = entry_counter.get().first() - let first = key_index == 0 - - __save_term_usage(key, key_index + 1) - - // Helper: Apply pluralization if requested - let pluralize_term = (singular, plural) => { - if "pl" in modifiers and plural != none { plural } else { singular } - } - - // Helper: Apply capitalization if requested - let capitalize_term = (term) => { - if "cap" in modifiers { upper(term.first()) + term.slice(1) } else { term } - } - - // Helper: Apply both pluralization and capitalization - let format_term = (term, plural) => { - capitalize_term(pluralize_term(term, plural)) - } - - // Helper: Select and format the appropriate term form - let select_term = (is_long_mode, use_both) => { - let short_form = format_term(entry.short, entry.plural) - let long_form = if entry.long != none { - format_term(entry.long, entry.longplural) - } else { - none - } - - if use_both and long_form != none { - [#long_form (#short_form)] - } else if is_long_mode and long_form != none { - [#long_form] - } else { - [#short_form] - } - } - - // Determine display mode from modifiers - let is_both = "both" in modifiers - let is_long = "long" in modifiers and not is_both - let is_short = "short" in modifiers and not is_both and not is_long - - context { - // Generate final display based on modifiers or default behavior - let display = if is_both or is_long or is_short { - select_term(is_long, is_both) - } else { - select_term(false, first) - } - - // Emit term with metadata and usage label - [#display#metadata(display)#__term_label(key, key_index)] - } -} - -// Creates page number links back to each usage of a glossary term. -// -// This function generates a comma-separated list of page numbers, where each -// number links back to a usage of the term in the document. Duplicate page -// numbers are removed to avoid redundancy. -// -// Parameters: -// key (string): The glossary entry key -// count (int): Number of times the term appears in the document -// -// Returns: -// content: Comma-separated list of linked page numbers -// -// Example output: "1, 3, 5" where each number links to the term usage on that page -// -#let __create_backlinks(key, count) = { - // Generate labels for each term usage - let labels = for i in range(count) { - (__term_label(key, i),) - } - - // Get page numbers for each usage - let pages = labels - .map(l => locate(l)) - .map(loc => numbering( - __default(loc.page-numbering(), "1"), - loc.page() - )) - - // Create links, excluding duplicate page numbers - let seen = () - let links = for i in range(labels.len()) { - let label = labels.at(i) - let page = pages.at(i) - - if seen.contains(page) { - (none,) - } else { - seen.push(page) - (link(label, page),) - } - }.filter(l => l != none) - - // Join links with commas - links.join(", ") -} - -// Initializes the glossary system and sets up term reference handling. -// -// This function is typically used with `#show: init-glossary`. It performs -// three main tasks: -// 1. Validates and loads glossary entries into the state -// 2. Sets up reference handling to intercept and format term usage -// 3. Applies custom term formatting if provided -// -// Parameters: -// entries (dictionary): Dictionary of glossary entries where: -// - keys are term identifiers -// - values are entry dictionaries containing term details -// show-term ((content) => content): Optional function to customize term rendering -// Default: Returns term content unchanged -// body (content): Document content to process -// -// Returns: -// content: Processed document content with glossary functionality enabled -// -// Throws: -// - If entries parameter is not a dictionary -// -#let init-glossary( - entries, - show-term: (term-body) => term-body, - body -) = context { - // Validate entries parameter type - if type(entries) != dictionary { - panic("entries must be a dictionary, instead got: " + repr(entries)) - } - - // Process and store each glossary entry - for (key, entry) in entries { - __add_entry(key, __normalize_entry(entry)) - } - - // Set up reference handling for glossary terms - show ref: r => { - let (key, ..modifiers) = str(r.target).split(":") - if __has_entry(key) { - show-term(__gls(key, modifiers: modifiers)) - } else { - r - } - } - - body -} - -// Renders a complete glossary with customizable formatting and grouping. -// -// The glossary displays all used terms, optionally grouped by category, with page -// references back to term usage. The appearance is controlled by a theme that -// defines how sections, groups, and entries are formatted. -// -// Parameters: -// title (string): Glossary section title (default: "Glossary") -// theme (dictionary): Controls glossary appearance with three functions: -// - section(title, body): Renders the main glossary section -// - title: The glossary title -// - body: Content containing all groups and entries -// - group(name, index, total, body): Renders a group of related terms -// - name: Group name -// - index: Zero-based group index -// - total: Total number of groups -// - body: Content containing the group's entries -// - entry(entry, index, total): Renders a single glossary entry -// - entry: Dictionary containing: -// - 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 -// groups (array): Optional list of groups to include -// If empty, includes all groups -// -// Returns: -// content: Formatted glossary content -// -// Throws: -// - If a requested group doesn't exist -// -#let glossary( - title: "Glossary", - theme: theme-twocol, - groups: () -) = context { - // Collect and organize entries by group - let output = (:) - let all_entries = __gloss_entries.final() - let all_used = __gloss_used.final() - - // Determine which groups to process - let all_groups = all_entries - .values() - .map(e => e.at("group")) - .dedup() - .sorted() - - let target_groups = if groups.len() == 0 { - all_groups - } else { - // Validate requested groups exist - for g in groups { - if g not in all_groups { - panic("Requested group not found: " + g) - } - } - groups - } - - // Process entries group by group - for group in target_groups { - let current_entries = () - - // Collect all used entries for this group - for (key, count) in all_used { - let entry = all_entries.at(key) - if entry.at("group") == group { - current_entries.push(( - short: entry.at("short"), - long: entry.at("long"), - description: entry.at("description"), - label: [#metadata(key)#__dict_label(key)], - pages: __create_backlinks(key, count) - )) - } - } - - // 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) - ) - } -} - - // Render the glossary using the theme - let group_index = 0 - - (theme.section)( - title, - for (group, entries) in output { - (theme.group)( - group, - group_index, - output.len(), - for (i, entry) in entries.enumerate() { - (theme.entry)(entry, i, entries.len()) - } - ) - group_index += 1 - } - ) -} diff --git a/preview/glossy/0.2.0/src/themes.typ b/preview/glossy/0.2.0/src/themes.typ deleted file mode 100644 index 7775002..0000000 --- a/preview/glossy/0.2.0/src/themes.typ +++ /dev/null @@ -1,236 +0,0 @@ -// 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 - ) - ) - ) - }, -) diff --git a/preview/glossy/0.2.0/src/utils.typ b/preview/glossy/0.2.0/src/utils.typ deleted file mode 100644 index 0acab16..0000000 --- a/preview/glossy/0.2.0/src/utils.typ +++ /dev/null @@ -1,38 +0,0 @@ -// Returns the provided value if it is not none, otherwise returns the default value -// Parameters: -// val: The value to check -// default: The fallback value to use if val is none -// Returns: val if not none, otherwise default -#let __default(val, default) = if val == none { default } else { val } - -// Converts an English word to its plural form following standard English pluralization rules -// Parameters: -// word: The singular word to pluralize -// Returns: The pluralized form of the word, or none if input is none -#let __pluralize(word) = { - // early exit with invalid input - if word == none { - return none - } - - // Define helper functions for checking endings - let ends_with = (suffix) => lower(word).ends-with(suffix) - let ends_with_any = (suffixes) => suffixes.any((suffix) => ends_with(suffix)) - - // Handle special cases for "es" endings (s, x, z, sh, ch) - if ends_with_any(( "s", "x", "z", "sh", "ch" )) { - word + "es" - } else if ends_with("y") and not ends_with_any(( "a", "e", "i", "o", "u" )) { - // Convert y to ies when y follows a consonant - word.slice(0, -1) + "ies" - } else if ends_with("f") { - // Convert f to ves - word.slice(0, -1) + "ves" - } else if ends_with("fe") { - // Convert fe to ves - word.slice(0, -2) + "ves" - } else { - // Default case: just add s - word + "s" - } -} diff --git a/preview/glossy/0.2.0/thumbnail.png b/preview/glossy/0.2.0/thumbnail.png deleted file mode 100644 index 7635045..0000000 Binary files a/preview/glossy/0.2.0/thumbnail.png and /dev/null differ diff --git a/preview/glossy/0.2.0/typst.toml b/preview/glossy/0.2.0/typst.toml deleted file mode 100644 index c68c503..0000000 --- a/preview/glossy/0.2.0/typst.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "glossy" -version = "0.2.0" -entrypoint = "lib.typ" -authors = ["Stephen Waits "] -license = "MIT" -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"] diff --git a/preview/glossy/0.2.1/.gitignore b/preview/glossy/0.2.1/.gitignore deleted file mode 100644 index f1ca904..0000000 --- a/preview/glossy/0.2.1/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -tests/**/out/ -tests/**/diff/ diff --git a/preview/glossy/0.2.1/LICENSE b/preview/glossy/0.2.1/LICENSE deleted file mode 100644 index 35d39a7..0000000 --- a/preview/glossy/0.2.1/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Stephen Waits - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/preview/glossy/0.2.1/README.md b/preview/glossy/0.2.1/README.md deleted file mode 100644 index 4110aec..0000000 --- a/preview/glossy/0.2.1/README.md +++ /dev/null @@ -1,239 +0,0 @@ -# Glossy - -This package provides utilities to manage and render glossaries within -documents. It includes functions to define and use glossary terms, track their -usage, and generate a glossary list with references to where terms are used in -the document. - -![image of sample output](thumbnail.png) - -## Motivation - -Glossy is heavily inspired by -[glossarium](https://typst.app/universe/package/glossarium), with a few key -different goals: - -1. Provide a simple interface which allows for complete control over glossary - display. To do this, `glossy`'s `#glossary()` function accepts a theme - parameter. The goal here is to separate presentation and logic. -2. Simplify the user interface as much as possible. Glossy has exactly two - exports, `init-glossary` and `glossary`. -3. Double-down on `glossy`'s excellent `@term` reference approach, completely - eliminating the need to make any calls to `gls()` and friends. -4. Mimic established patterns and best practices. For example, `glossy`'s - `#glossary()` function is intentionally similar (in naming and parameters) to - `typst`'s built-in `#bibliography`, to the degree possible. -5. Simplify the implementation. The `glossy` code is significantly shorter and - easier to understand. - -## Features - -- Define glossary terms with short and long forms, descriptions, and grouping -- Automatically tracks term usage in the document through `@labels` -- Supports modifiers to adjust how terms are displayed (capitalize, pluralize, etc.) -- Generates a formatted glossary section with backlinks to term occurrences -- Customizable themes for rendering glossary sections, groups, and entries -- Automatic pluralization of terms with custom override options -- Page number references back to term usage locations - -## Usage - -### Import the package - -```typst -#import "@preview/glossy:0.2.1": * -``` - -### Defining Glossary Terms - -Use the `init-glossary` function to initialize glossary entries: - -```typst -#let myGlossary = ( - html: ( - short: "HTML", - long: "Hypertext Markup Language", - description: "A standard language for creating web pages", - group: "Web" - ), - css: ( - short: "CSS", - long: "Cascading Style Sheets", - description: "A stylesheet language used for describing the presentation of a document", - group: "Web" - ), - tps: ( - short: "TPS", - long: "test procedure specification", - description: "A formal document describing test steps and expected results", - // Optional: Override automatic pluralization - plural: "TPSes", - longplural: "test procedure specifications" - ) -) - -#show: init-glossary.with(myGlossary) -``` - -Each glossary entry supports the following fields: - -- `short` (required): Short form of the term -- `long` (optional): Long form of the term -- `description` (optional): Term description (often a definition) -- `group` (optional): Category grouping -- `plural` (optional): Override automatic pluralization of short form -- `longplural` (optional): Override automatic pluralization of long form - -You can also load glossary entries from a data file using #yaml(), #json(), or similar. - -For example, the above glossary could be in this YAML file: - -```yaml -html: - short: HTML - long: Hypertext Markup Language - description: A standard language for creating web pages - group: Web - -css: - short: CSS - long: Cascading Style Sheets - description: A stylesheet language used for describing the presentation of a document - group: Web - -tps: - short: TPS - long: test procedure specification - description: A formal document describing test steps and expected results - plural: TPSes - longplural: test procedure specifications -``` - -And then loaded during initialization as follows: - -```typst -#show: init-glossary.with(yaml("glossary.yaml")) -``` - -### Using Glossary Terms - -Reference glossary terms using Typst's `@reference` syntax: - -```typst -In modern web development, languages like @html and @css are essential. -The @tps:pl need to be submitted by Friday. -``` - -Available modifiers: - -- **cap**: Capitalizes the term -- **pl**: Uses the plural form -- **both**: Shows "Long Form (Short Form)" -- **short**: Shows only short form -- **long**: Shows only long form -- **def** or **desc**: Shows the description - -Modifiers can be combined with colons: - -| **Input** | **Output** | -| ------------------- | -------------------------------------------------------------- | -| `@tps` (first use) | "test procedure specification (TPS)" | -| `@tps` (subsequent) | "TPS" | -| `@tps:short` | "TPS" | -| `@tps:long` | "test procedure specification" | -| `@tps:both` | "test procedure specification (TPS)" | -| `@tps:long:cap` | "Test procedure specification" | -| `@tps:long:pl` | "test procedure specifications" | -| `@tps:short:pl` | "TPSes" | -| `@tps:both:pl:cap` | "Technical procedure specifications (TPSes)" | -| `@tps:def` | "A formal document describing test steps and expected results" | - -### Generating the Glossary - -Display the glossary using the `glossary()` function: - -```typst -#glossary( - 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 -) -``` - -### Customizing Term Display - -Control how terms appear in the document by providing a custom `show-term` function: - -```typst -#let emph-term(term-body) = { emph(term-body) } - -#show: init-glossary.with( - myGlossary, - show-term: emph-term -) -``` - -### Glossary Themes - -#### Included Themes - -Glossy comes with several built-in themes that can be used directly or serve as -examples for custom themes: - -![theme gallery image](themeshots.png) - -#### Custom Themes - -Customize glossary appearance by defining a theme with three functions: - -```typst -#let my-theme = ( - // Main glossary section - section: (title, body) => { - heading(level: 1, title) - body - }, - - // Group of related terms - group: (name, index, total, body) => { - // index = group index, total = total groups - if name != "" and total > 1 { - heading(level: 2, name) - } - body - }, - - // Individual glossary entry - entry: (entry, index, total) => { - // index = entry index, total = total entries in group - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) -``` - -Entry fields available to themes: - -- `short`: Short form (always present) -- `long`: Long form (can be `none`) -- `description`: Term description (can be `none`) -- `label`: Term's dictionary label -- `pages`: Linked page numbers where term appears - -## License - -This project is licensed under the MIT License. diff --git a/preview/glossy/0.2.1/lib.typ b/preview/glossy/0.2.1/lib.typ deleted file mode 100644 index eeff265..0000000 --- a/preview/glossy/0.2.1/lib.typ +++ /dev/null @@ -1,2 +0,0 @@ -#import "src/gloss.typ": init-glossary, glossary -#import "src/themes.typ": * diff --git a/preview/glossy/0.2.1/src/gloss.typ b/preview/glossy/0.2.1/src/gloss.typ deleted file mode 100644 index c3ca8a3..0000000 --- a/preview/glossy/0.2.1/src/gloss.typ +++ /dev/null @@ -1,473 +0,0 @@ -#import "./themes.typ": * -#import "./utils.typ": * - -#let __gloss_entries = state("__gloss_entries", (:)) -#let __gloss_used = state("__gloss_used", (:)) - -#let __gloss_label_prefix = "__gloss:" - -// Normalizes a dictionary entry by ensuring all required and optional keys exist -// with appropriate default values. -// -// Parameters: -// entry (dictionary): Input dictionary containing at least 'short' and optionally -// 'long', 'plural', 'longplural', 'description', and 'group' -// -// Returns: -// dictionary: Normalized dictionary with all expected keys populated -// -// Throws: -// - If 'short' key is missing -// - If 'short' value is not a string -// - If 'short' value is an empty string -// -#let __normalize_entry(entry) = { - // Validate required fields - if not "short" in entry { - panic("Entry must contain a 'short' key") - } - if type(entry.short) != "string" { - panic("Entry 'short' must be a string") - } - if entry.short.trim() == "" { - panic("Entry 'short' cannot be empty") - } - - // Extract values with defaults, using consistent none for missing optionals - let long = entry.at("long", default: none) - - // Return the normalized entry - ( - short: entry.short, - plural: entry.at("plural", default: __pluralize(entry.short)), - long: long, - longplural: entry.at("longplural", default: __pluralize(long)), - description: entry.at("description", default: none), - group: entry.at("group", default: "") - ) -} - -// Checks if a key exists in the glossary state. -// -// Parameters: -// key (string): The key to look up in the glossary entries -// -// Returns: -// boolean: True if the key exists in the glossary, false otherwise -// -#let __has_entry(key) = { - key in __gloss_entries.final().keys() -} - -// Updates the glossary state by adding or updating an entry. -// -// Parameters: -// key (string): The key under which to store the entry -// entry (dictionary): The normalized glossary entry to store -// -// Returns: -// none: Updates state as a side effect -// -#let __add_entry(key, entry) = { - __gloss_entries.update(state => { - state.insert(key, entry) - state - }) -} - -// Retrieves a glossary entry from the state. -// -// Parameters: -// key (string): The key of the entry to retrieve -// -// Returns: -// dictionary: The glossary entry associated with the key -// -// Throws: -// - If the key does not exist in the glossary -// -#let __get_entry(key) = { - let entries = __gloss_entries.final() - if key not in entries.keys() { - panic("Glossary error: Missing key '" + key + "'") - } - - entries.at(key) -} - -// Creates a prefixed label string for dictionary entries and term usage. -// -// Parameters: -// key (string): The glossary entry key -// -// Returns: -// string: The prefixed label string for dictionary entries -// -#let __dict_label_str(key) = { - __gloss_label_prefix + key -} - -// Creates a label object for dictionary entries. -// -// Parameters: -// key (string): The glossary entry key -// -// Returns: -// label: A Typst label object for the dictionary entry -// -#let __dict_label(key) = { - label(__dict_label_str(key)) -} - -// Creates a prefixed label string for term usage in documents. -// -// Parameters: -// key (string): The glossary entry key -// index (int): The occurrence index of the term in the document -// -// Returns: -// string: The prefixed label string for term usage -// -#let __term_label_str(key, index) = { - __gloss_label_prefix + key + "." + str(index) -} - -// Creates a label object for term usage in documents. -// -// Parameters: -// key (string): The glossary entry key -// index (int): The occurrence index of the term in the document -// -// Returns: -// label: A Typst label object for the term usage -// -#let __term_label(key, index) = { - label(__term_label_str(key, index)) -} - -// Updates the term usage count in the glossary state. -// -// Parameters: -// key (string): The glossary entry key -// count (int): The number of times the term has been used -// -// Returns: -// none: Updates state as a side effect -// -#let __save_term_usage(key, count) = { - __gloss_used.update(state => { - state.insert(key, count) - state - }) -} - -// Renders a glossary term with various formatting options. -// -// This function handles the display of terms in the document, managing both first -// and subsequent uses, pluralization, capitalization, and different display forms -// (short, long, or combined forms). -// -// Parameters: -// key (string): The glossary entry key to render -// modifiers (array): Array of modifier strings that control term rendering: -// - "cap": Capitalize the first letter of the term -// - "pl": Use plural form of the term -// - "both": Show "Long form (short form)" -// - "short": Show only the short form -// - "long": Show only the long form -// - "def" or "desc": Show the term's description instead -// -// Returns: -// content: Formatted term with appropriate labels and metadata -// -// Behavior: -// - Without modifiers, first use shows "Long form (short form)", subsequent uses show short form -// - Explicit modifiers override the default first-use behavior -// - If long form is requested but unavailable, falls back to short form -// - Maintains usage counter for term references -// -#let __gls(key, modifiers: array) = { - // Fetch the entry and prepare label - let entry = __get_entry(key) - let entry_label = __dict_label_str(key) - - // Handle description mode - if "def" in modifiers or "desc" in modifiers { - return [#entry.description] - } - - // Track term usage - let entry_counter = counter(entry_label) - entry_counter.step() - let key_index = entry_counter.get().first() - let first = key_index == 0 - - __save_term_usage(key, key_index + 1) - - // Helper: Apply pluralization if requested - let pluralize_term = (singular, plural) => { - if "pl" in modifiers and plural != none { plural } else { singular } - } - - // Helper: Apply capitalization if requested - let capitalize_term = (term) => { - if "cap" in modifiers { upper(term.first()) + term.slice(1) } else { term } - } - - // Helper: Apply both pluralization and capitalization - let format_term = (term, plural) => { - capitalize_term(pluralize_term(term, plural)) - } - - // Helper: Select and format the appropriate term form - let select_term = (is_long_mode, use_both) => { - let short_form = format_term(entry.short, entry.plural) - let long_form = if entry.long != none { - format_term(entry.long, entry.longplural) - } else { - none - } - - if use_both and long_form != none { - [#long_form (#short_form)] - } else if is_long_mode and long_form != none { - [#long_form] - } else { - [#short_form] - } - } - - // Determine display mode from modifiers - let is_both = "both" in modifiers - let is_long = "long" in modifiers and not is_both - let is_short = "short" in modifiers and not is_both and not is_long - - context { - // Generate final display based on modifiers or default behavior - let display = if is_both or is_long or is_short { - select_term(is_long, is_both) - } else { - select_term(false, first) - } - - // Emit term with metadata and usage label - [#display#metadata(display)#__term_label(key, key_index)] - } -} - -// Creates page number links back to each usage of a glossary term. -// -// This function generates a comma-separated list of page numbers, where each -// number links back to a usage of the term in the document. Duplicate page -// numbers are removed to avoid redundancy. -// -// Parameters: -// key (string): The glossary entry key -// count (int): Number of times the term appears in the document -// -// Returns: -// content: Comma-separated list of linked page numbers -// -// Example output: "1, 3, 5" where each number links to the term usage on that page -// -#let __create_backlinks(key, count) = { - // Generate labels for each term usage - let labels = for i in range(count) { - (__term_label(key, i),) - } - - // Get page numbers for each usage - let pages = labels - .map(l => locate(l)) - .map(loc => numbering( - __default(loc.page-numbering(), "1"), - loc.page() - )) - - // Create links, excluding duplicate page numbers - let seen = () - let links = for i in range(labels.len()) { - let label = labels.at(i) - let page = pages.at(i) - - if seen.contains(page) { - (none,) - } else { - seen.push(page) - (link(label, page),) - } - }.filter(l => l != none) - - // Join links with commas - links.join(", ") -} - -// Initializes the glossary system and sets up term reference handling. -// -// This function is typically used with `#show: init-glossary`. It performs -// three main tasks: -// 1. Validates and loads glossary entries into the state -// 2. Sets up reference handling to intercept and format term usage -// 3. Applies custom term formatting if provided -// -// Parameters: -// entries (dictionary): Dictionary of glossary entries where: -// - keys are term identifiers -// - values are entry dictionaries containing term details -// show-term ((content) => content): Optional function to customize term rendering -// Default: Returns term content unchanged -// body (content): Document content to process -// -// Returns: -// content: Processed document content with glossary functionality enabled -// -// Throws: -// - If entries parameter is not a dictionary -// -#let init-glossary( - entries, - show-term: (term-body) => term-body, - body -) = context { - // Validate entries parameter type - if type(entries) != dictionary { - panic("entries must be a dictionary, instead got: " + repr(entries)) - } - - // Process and store each glossary entry - for (key, entry) in entries { - __add_entry(key, __normalize_entry(entry)) - } - - // Set up reference handling for glossary terms - show ref: r => { - let (key, ..modifiers) = str(r.target).split(":") - if __has_entry(key) { - show-term(__gls(key, modifiers: modifiers)) - } else { - r - } - } - - body -} - -// Renders a complete glossary with customizable formatting and grouping. -// -// The glossary displays all used terms, optionally grouped by category, with page -// references back to term usage. The appearance is controlled by a theme that -// defines how sections, groups, and entries are formatted. -// -// Parameters: -// title (string): Glossary section title (default: "Glossary") -// theme (dictionary): Controls glossary appearance with three functions: -// - section(title, body): Renders the main glossary section -// - title: The glossary title -// - body: Content containing all groups and entries -// - group(name, index, total, body): Renders a group of related terms -// - name: Group name -// - index: Zero-based group index -// - total: Total number of groups -// - body: Content containing the group's entries -// - entry(entry, index, total): Renders a single glossary entry -// - entry: Dictionary containing: -// - 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 -// groups (array): Optional list of groups to include -// If empty, includes all groups -// -// Returns: -// content: Formatted glossary content -// -// Throws: -// - If a requested group doesn't exist -// -#let glossary( - title: "Glossary", - theme: theme-twocol, - ignore-case: false, - groups: (), -) = context { - // Collect and organize entries by group - let output = (:) - let all_entries = __gloss_entries.final() - let all_used = __gloss_used.final() - - // Determine which groups to process - let all_groups = all_entries - .values() - .map(e => e.at("group")) - .dedup() - .sorted() - - let target_groups = if groups.len() == 0 { - all_groups - } else { - // Validate requested groups exist - for g in groups { - if g not in all_groups { - panic("Requested group not found: " + g) - } - } - groups - } - - // Process entries group by group - for group in target_groups { - let current_entries = () - - // Collect all used entries for this group - for (key, count) in all_used { - let entry = all_entries.at(key) - if entry.at("group") == group { - current_entries.push(( - short: entry.at("short"), - long: entry.at("long"), - description: entry.at("description"), - label: [#metadata(key)#__dict_label(key)], - pages: __create_backlinks(key, count) - )) - } - } - - // Add non-empty groups to output - if current_entries.len() > 0 { - group = if group == none { "" } else { group } - - // 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) - } -} - - // Render the glossary using the theme - let group_index = 0 - - (theme.section)( - title, - for (group, entries) in output { - (theme.group)( - group, - group_index, - output.len(), - for (i, entry) in entries.enumerate() { - (theme.entry)(entry, i, entries.len()) - } - ) - group_index += 1 - } - ) -} diff --git a/preview/glossy/0.2.1/src/themes.typ b/preview/glossy/0.2.1/src/themes.typ deleted file mode 100644 index 8ff4163..0000000 --- a/preview/glossy/0.2.1/src/themes.typ +++ /dev/null @@ -1,5 +0,0 @@ -#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/0.2.1/src/themes/academic.typ b/preview/glossy/0.2.1/src/themes/academic.typ deleted file mode 100644 index 1093fd2..0000000 --- a/preview/glossy/0.2.1/src/themes/academic.typ +++ /dev/null @@ -1,50 +0,0 @@ -// 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/0.2.1/src/themes/basic.typ b/preview/glossy/0.2.1/src/themes/basic.typ deleted file mode 100644 index b756c27..0000000 --- a/preview/glossy/0.2.1/src/themes/basic.typ +++ /dev/null @@ -1,70 +0,0 @@ -// 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/0.2.1/src/themes/chicago-index.typ b/preview/glossy/0.2.1/src/themes/chicago-index.typ deleted file mode 100644 index fa3c82e..0000000 --- a/preview/glossy/0.2.1/src/themes/chicago-index.typ +++ /dev/null @@ -1,47 +0,0 @@ -// 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/0.2.1/src/themes/compact.typ b/preview/glossy/0.2.1/src/themes/compact.typ deleted file mode 100644 index 86a40e3..0000000 --- a/preview/glossy/0.2.1/src/themes/compact.typ +++ /dev/null @@ -1,91 +0,0 @@ -// 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/0.2.1/src/themes/twocol.typ b/preview/glossy/0.2.1/src/themes/twocol.typ deleted file mode 100644 index 7183e6e..0000000 --- a/preview/glossy/0.2.1/src/themes/twocol.typ +++ /dev/null @@ -1,72 +0,0 @@ -// 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/0.2.1/src/utils.typ b/preview/glossy/0.2.1/src/utils.typ deleted file mode 100644 index 0acab16..0000000 --- a/preview/glossy/0.2.1/src/utils.typ +++ /dev/null @@ -1,38 +0,0 @@ -// Returns the provided value if it is not none, otherwise returns the default value -// Parameters: -// val: The value to check -// default: The fallback value to use if val is none -// Returns: val if not none, otherwise default -#let __default(val, default) = if val == none { default } else { val } - -// Converts an English word to its plural form following standard English pluralization rules -// Parameters: -// word: The singular word to pluralize -// Returns: The pluralized form of the word, or none if input is none -#let __pluralize(word) = { - // early exit with invalid input - if word == none { - return none - } - - // Define helper functions for checking endings - let ends_with = (suffix) => lower(word).ends-with(suffix) - let ends_with_any = (suffixes) => suffixes.any((suffix) => ends_with(suffix)) - - // Handle special cases for "es" endings (s, x, z, sh, ch) - if ends_with_any(( "s", "x", "z", "sh", "ch" )) { - word + "es" - } else if ends_with("y") and not ends_with_any(( "a", "e", "i", "o", "u" )) { - // Convert y to ies when y follows a consonant - word.slice(0, -1) + "ies" - } else if ends_with("f") { - // Convert f to ves - word.slice(0, -1) + "ves" - } else if ends_with("fe") { - // Convert fe to ves - word.slice(0, -2) + "ves" - } else { - // Default case: just add s - word + "s" - } -} diff --git a/preview/glossy/0.2.1/tests/demo/ref/1.png b/preview/glossy/0.2.1/tests/demo/ref/1.png deleted file mode 100644 index 9b172a6..0000000 Binary files a/preview/glossy/0.2.1/tests/demo/ref/1.png and /dev/null differ diff --git a/preview/glossy/0.2.1/tests/demo/test.typ b/preview/glossy/0.2.1/tests/demo/test.typ deleted file mode 100644 index 3b2b7d9..0000000 --- a/preview/glossy/0.2.1/tests/demo/test.typ +++ /dev/null @@ -1,99 +0,0 @@ -// a minimal example, also listed in the README - -#import "/lib.typ": * - -#let myGlossary = ( - html: ( - short: "HTML", - long: "Hypertext Markup Language", - description: "A standard language for creating web pages", - group: "Web"), - css: ( - short: "CSS", - long: "Cascading Style Sheets", - description: "A language used for describing the presentation of a document", - group: "Web"), - 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 heading(numbering: "1.1") - -= Hello, `glossy`! -In modern web development, languages like @html and @css are essential. - -Now make sure I get your @tps:short reports by 2pm! - -@tps:cap is defined as: @tps:def. - -= Examples with modifiers - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - - [`@tps:short` ], [@tps:short], - [`@tps:long` ], [@tps:long], - [`@tps:both` ], [@tps:both], - [`@tps:long:cap` ], [@tps:long:cap], - [`@tps:long:pl` ], [@tps:long:pl], - [`@tps:short:pl` ], [@tps:short:pl], - [`@tps:both:pl:cap`], [@tps:both:pl:cap], - [`@tps:def` ], [@tps:def], - [`@tps:desc` ], [@tps:desc], -) - -== Using conflicting modifiers (short, long, both) - -These modifiers are semantically mutually exclusive, by they can be combined -syntactically. When multiple are used, `glossy` tries to make a smart choice on -which one to display. - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - - [`@tps:short:long` ], [@tps:short:long], - [`@tps:short:both` ], [@tps:short:both], - [`@tps:long:both` ], [@tps:long:both], - [`@tps:short:long:both`], [@tps:short:long:both], -) - -#let my-theme = ( - section: (title, body) => { - heading(level: 1, title) - body - }, - group: (name, i, n, body) => { - if name != "" and n > 1 { - heading(level: 2, name) - } - body - }, - entry: (entry, i, n) => { - // i is the entry's index, n is total number of entries - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) - -#glossary(theme: my-theme) -#glossary(title: "Glossary with just empty group", groups: ("",), theme: theme-basic) -#glossary(title: "Glossary with just Web group", groups: ("Web",), theme: theme-compact) diff --git a/preview/glossy/0.2.1/tests/sorting/ref/1.png b/preview/glossy/0.2.1/tests/sorting/ref/1.png deleted file mode 100644 index b1ed845..0000000 Binary files a/preview/glossy/0.2.1/tests/sorting/ref/1.png and /dev/null differ diff --git a/preview/glossy/0.2.1/tests/sorting/test.typ b/preview/glossy/0.2.1/tests/sorting/test.typ deleted file mode 100644 index fc7b1bc..0000000 --- a/preview/glossy/0.2.1/tests/sorting/test.typ +++ /dev/null @@ -1,38 +0,0 @@ -#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/0.2.1/tests/theme-gallery/ref/1.png b/preview/glossy/0.2.1/tests/theme-gallery/ref/1.png deleted file mode 100644 index a1d63ee..0000000 Binary files a/preview/glossy/0.2.1/tests/theme-gallery/ref/1.png and /dev/null differ diff --git a/preview/glossy/0.2.1/tests/theme-gallery/test.typ b/preview/glossy/0.2.1/tests/theme-gallery/test.typ deleted file mode 100644 index 2edee14..0000000 --- a/preview/glossy/0.2.1/tests/theme-gallery/test.typ +++ /dev/null @@ -1,54 +0,0 @@ -#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/0.2.1/themeshots.png b/preview/glossy/0.2.1/themeshots.png deleted file mode 100644 index a1d63ee..0000000 Binary files a/preview/glossy/0.2.1/themeshots.png and /dev/null differ diff --git a/preview/glossy/0.2.1/thumbnail.png b/preview/glossy/0.2.1/thumbnail.png deleted file mode 100644 index 7635045..0000000 Binary files a/preview/glossy/0.2.1/thumbnail.png and /dev/null differ diff --git a/preview/glossy/0.2.1/typst.toml b/preview/glossy/0.2.1/typst.toml deleted file mode 100644 index ae96f73..0000000 --- a/preview/glossy/0.2.1/typst.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "glossy" -version = "0.2.1" -entrypoint = "lib.typ" -authors = ["Stephen Waits "] -license = "MIT" -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", "themeshots.pjng", "tests"] diff --git a/preview/glossy/0.2.2/.gitignore b/preview/glossy/0.2.2/.gitignore deleted file mode 100644 index f1ca904..0000000 --- a/preview/glossy/0.2.2/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -tests/**/out/ -tests/**/diff/ diff --git a/preview/glossy/0.2.2/LICENSE b/preview/glossy/0.2.2/LICENSE deleted file mode 100644 index 35d39a7..0000000 --- a/preview/glossy/0.2.2/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Stephen Waits - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/preview/glossy/0.2.2/README.md b/preview/glossy/0.2.2/README.md deleted file mode 100644 index 41b5e86..0000000 --- a/preview/glossy/0.2.2/README.md +++ /dev/null @@ -1,239 +0,0 @@ -# Glossy - -This package provides utilities to manage and render glossaries within -documents. It includes functions to define and use glossary terms, track their -usage, and generate a glossary list with references to where terms are used in -the document. - -![image of sample output](thumbnail.png) - -## Motivation - -Glossy is heavily inspired by -[glossarium](https://typst.app/universe/package/glossarium), with a few key -different goals: - -1. Provide a simple interface which allows for complete control over glossary - display. To do this, `glossy`'s `#glossary()` function accepts a theme - parameter. The goal here is to separate presentation and logic. -2. Simplify the user interface as much as possible. Glossy has exactly two - exports, `init-glossary` and `glossary`. -3. Double-down on `glossy`'s excellent `@term` reference approach, completely - eliminating the need to make any calls to `gls()` and friends. -4. Mimic established patterns and best practices. For example, `glossy`'s - `#glossary()` function is intentionally similar (in naming and parameters) to - `typst`'s built-in `#bibliography`, to the degree possible. -5. Simplify the implementation. The `glossy` code is significantly shorter and - easier to understand. - -## Features - -- Define glossary terms with short and long forms, descriptions, and grouping -- Automatically tracks term usage in the document through `@labels` -- Supports modifiers to adjust how terms are displayed (capitalize, pluralize, etc.) -- Generates a formatted glossary section with backlinks to term occurrences -- Customizable themes for rendering glossary sections, groups, and entries -- Automatic pluralization of terms with custom override options -- Page number references back to term usage locations - -## Usage - -### Import the package - -```typst -#import "@preview/glossy:0.2.2": * -``` - -### Defining Glossary Terms - -Use the `init-glossary` function to initialize glossary entries: - -```typst -#let myGlossary = ( - html: ( - short: "HTML", - long: "Hypertext Markup Language", - description: "A standard language for creating web pages", - group: "Web" - ), - css: ( - short: "CSS", - long: "Cascading Style Sheets", - description: "A stylesheet language used for describing the presentation of a document", - group: "Web" - ), - tps: ( - short: "TPS", - long: "test procedure specification", - description: "A formal document describing test steps and expected results", - // Optional: Override automatic pluralization - plural: "TPSes", - longplural: "test procedure specifications" - ) -) - -#show: init-glossary.with(myGlossary) -``` - -Each glossary entry supports the following fields: - -- `short` (required): Short form of the term -- `long` (optional): Long form of the term -- `description` (optional): Term description (often a definition) -- `group` (optional): Category grouping -- `plural` (optional): Override automatic pluralization of short form -- `longplural` (optional): Override automatic pluralization of long form - -You can also load glossary entries from a data file using #yaml(), #json(), or similar. - -For example, the above glossary could be in this YAML file: - -```yaml -html: - short: HTML - long: Hypertext Markup Language - description: A standard language for creating web pages - group: Web - -css: - short: CSS - long: Cascading Style Sheets - description: A stylesheet language used for describing the presentation of a document - group: Web - -tps: - short: TPS - long: test procedure specification - description: A formal document describing test steps and expected results - plural: TPSes - longplural: test procedure specifications -``` - -And then loaded during initialization as follows: - -```typst -#show: init-glossary.with(yaml("glossary.yaml")) -``` - -### Using Glossary Terms - -Reference glossary terms using Typst's `@reference` syntax: - -```typst -In modern web development, languages like @html and @css are essential. -The @tps:pl need to be submitted by Friday. -``` - -Available modifiers: - -- **cap**: Capitalizes the term -- **pl**: Uses the plural form -- **both**: Shows "Long Form (Short Form)" -- **short**: Shows only short form -- **long**: Shows only long form -- **def** or **desc**: Shows the description - -Modifiers can be combined with colons: - -| **Input** | **Output** | -| ------------------- | -------------------------------------------------------------- | -| `@tps` (first use) | "test procedure specification (TPS)" | -| `@tps` (subsequent) | "TPS" | -| `@tps:short` | "TPS" | -| `@tps:long` | "test procedure specification" | -| `@tps:both` | "test procedure specification (TPS)" | -| `@tps:long:cap` | "Test procedure specification" | -| `@tps:long:pl` | "test procedure specifications" | -| `@tps:short:pl` | "TPSes" | -| `@tps:both:pl:cap` | "Technical procedure specifications (TPSes)" | -| `@tps:def` | "A formal document describing test steps and expected results" | - -### Generating the Glossary - -Display the glossary using the `glossary()` function: - -```typst -#glossary( - 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 -) -``` - -### Customizing Term Display - -Control how terms appear in the document by providing a custom `show-term` function: - -```typst -#let emph-term(term-body) = { emph(term-body) } - -#show: init-glossary.with( - myGlossary, - show-term: emph-term -) -``` - -### Glossary Themes - -#### Included Themes - -Glossy comes with several built-in themes that can be used directly or serve as -examples for custom themes: - -![theme gallery image](themeshots.png) - -#### Custom Themes - -Customize glossary appearance by defining a theme with three functions: - -```typst -#let my-theme = ( - // Main glossary section - section: (title, body) => { - heading(level: 1, title) - body - }, - - // Group of related terms - group: (name, index, total, body) => { - // index = group index, total = total groups - if name != "" and total > 1 { - heading(level: 2, name) - } - body - }, - - // Individual glossary entry - entry: (entry, index, total) => { - // index = entry index, total = total entries in group - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) -``` - -Entry fields available to themes: - -- `short`: Short form (always present) -- `long`: Long form (can be `none`) -- `description`: Term description (can be `none`) -- `label`: Term's dictionary label -- `pages`: Linked page numbers where term appears - -## License - -This project is licensed under the MIT License. diff --git a/preview/glossy/0.2.2/lib.typ b/preview/glossy/0.2.2/lib.typ deleted file mode 100644 index eeff265..0000000 --- a/preview/glossy/0.2.2/lib.typ +++ /dev/null @@ -1,2 +0,0 @@ -#import "src/gloss.typ": init-glossary, glossary -#import "src/themes.typ": * diff --git a/preview/glossy/0.2.2/src/gloss.typ b/preview/glossy/0.2.2/src/gloss.typ deleted file mode 100644 index 3fd2e15..0000000 --- a/preview/glossy/0.2.2/src/gloss.typ +++ /dev/null @@ -1,466 +0,0 @@ -#import "./themes.typ": * -#import "./utils.typ": * - -#let __gloss_entries = state("__gloss_entries", (:)) -#let __gloss_used = state("__gloss_used", (:)) - -#let __gloss_label_prefix = "__gloss:" - -// Normalizes a dictionary entry by ensuring all required and optional keys exist -// with appropriate default values. -// -// Parameters: -// entry (dictionary): Input dictionary containing at least 'short' and optionally -// 'long', 'plural', 'longplural', 'description', and 'group' -// -// Returns: -// dictionary: Normalized dictionary with all expected keys populated -// -// Throws: -// - If 'short' key is missing -// - If 'short' value is not a string -// - If 'short' value is an empty string -// -#let __normalize_entry(entry) = { - // Validate required fields - if not "short" in entry { - panic("Entry must contain a 'short' key") - } - if type(entry.short) != "string" { - panic("Entry 'short' must be a string") - } - if entry.short.trim() == "" { - panic("Entry 'short' cannot be empty") - } - - // Extract values with defaults, using consistent none for missing optionals - let long = entry.at("long", default: none) - - // Return the normalized entry - ( - short: entry.short, - plural: entry.at("plural", default: __pluralize(entry.short)), - long: long, - longplural: entry.at("longplural", default: __pluralize(long)), - description: entry.at("description", default: none), - group: entry.at("group", default: "") - ) -} - -// Checks if a key exists in the glossary state. -// -// Parameters: -// key (string): The key to look up in the glossary entries -// -// Returns: -// boolean: True if the key exists in the glossary, false otherwise -// -#let __has_entry(key) = { - key in __gloss_entries.final().keys() -} - -// Updates the glossary state by adding or updating an entry. -// -// Parameters: -// key (string): The key under which to store the entry -// entry (dictionary): The normalized glossary entry to store -// -// Returns: -// none: Updates state as a side effect -// -#let __add_entry(key, entry) = { - __gloss_entries.update(state => { - state.insert(key, entry) - state - }) -} - -// Retrieves a glossary entry from the state. -// -// Parameters: -// key (string): The key of the entry to retrieve -// -// Returns: -// dictionary: The glossary entry associated with the key -// -// Throws: -// - If the key does not exist in the glossary -// -#let __get_entry(key) = { - let entries = __gloss_entries.final() - if key not in entries.keys() { - panic("Glossary error: Missing key '" + key + "'") - } - - entries.at(key) -} - -// Creates a prefixed label string for dictionary entries and term usage. -// -// Parameters: -// key (string): The glossary entry key -// -// Returns: -// string: The prefixed label string for dictionary entries -// -#let __dict_label_str(key) = { - __gloss_label_prefix + key -} - -// Creates a label object for dictionary entries. -// -// Parameters: -// key (string): The glossary entry key -// -// Returns: -// label: A Typst label object for the dictionary entry -// -#let __dict_label(key) = { - label(__dict_label_str(key)) -} - -// Creates a prefixed label string for term usage in documents. -// -// Parameters: -// key (string): The glossary entry key -// index (int): The occurrence index of the term in the document -// -// Returns: -// string: The prefixed label string for term usage -// -#let __term_label_str(key, index) = { - __gloss_label_prefix + key + "." + str(index) -} - -// Creates a label object for term usage in documents. -// -// Parameters: -// key (string): The glossary entry key -// index (int): The occurrence index of the term in the document -// -// Returns: -// label: A Typst label object for the term usage -// -#let __term_label(key, index) = { - label(__term_label_str(key, index)) -} - -// Updates the term usage count in the glossary state. -// -// Parameters: -// key (string): The glossary entry key -// count (int): The number of times the term has been used -// -// Returns: -// none: Updates state as a side effect -// -#let __save_term_usage(key, count) = { - __gloss_used.update(state => { - state.insert(key, count) - state - }) -} - -// Renders a glossary term with various formatting options. -// -// This function handles the display of terms in the document, managing both first -// and subsequent uses, pluralization, capitalization, and different display forms -// (short, long, or combined forms). -// -// Parameters: -// key (string): The glossary entry key to render -// modifiers (array): Array of modifier strings that control term rendering: -// - "cap": Capitalize the first letter of the term -// - "pl": Use plural form of the term -// - "both": Show "Long form (short form)" -// - "short": Show only the short form -// - "long": Show only the long form -// - "def" or "desc": Show the term's description instead -// -// Returns: -// content: Formatted term with appropriate labels and metadata -// -// Behavior: -// - Without modifiers, first use shows "Long form (short form)", subsequent uses show short form -// - Explicit modifiers override the default first-use behavior -// - If long form is requested but unavailable, falls back to short form -// - Maintains usage counter for term references -// -#let __gls(key, modifiers: array) = { - // Fetch the entry and prepare label - let entry = __get_entry(key) - let entry_label = __dict_label_str(key) - - // Handle description mode - if "def" in modifiers or "desc" in modifiers { - return [#entry.description] - } - - // Track term usage - let entry_counter = counter(entry_label) - entry_counter.step() - let key_index = entry_counter.get().first() - let first = key_index == 0 - - __save_term_usage(key, key_index + 1) - - // Helper: Apply pluralization if requested - let pluralize_term = (singular, plural) => { - if "pl" in modifiers and plural != none { plural } else { singular } - } - - // Helper: Apply capitalization if requested - let capitalize_term = (term) => { - if "cap" in modifiers { upper(term.first()) + term.slice(1) } else { term } - } - - // Helper: Apply both pluralization and capitalization - let format_term = (term, plural) => { - capitalize_term(pluralize_term(term, plural)) - } - - // Helper: Select and format the appropriate term form - let select_term = (is_long_mode, use_both) => { - let short_form = format_term(entry.short, entry.plural) - let long_form = if entry.long != none { - format_term(entry.long, entry.longplural) - } else { - none - } - - if use_both and long_form != none { - [#long_form (#short_form)] - } else if is_long_mode and long_form != none { - [#long_form] - } else { - [#short_form] - } - } - - // Determine display mode from modifiers - let is_both = "both" in modifiers - let is_long = "long" in modifiers and not is_both - let is_short = "short" in modifiers and not is_both and not is_long - - context { - // Generate final display based on modifiers or default behavior - let display = if is_both or is_long or is_short { - select_term(is_long, is_both) - } else { - select_term(false, first) - } - - // Emit term with metadata and usage label - [#display#metadata(display)#__term_label(key, key_index)] - } -} - -// Creates page number links back to each usage of a glossary term. -// -// This function generates a comma-separated list of page numbers, where each -// number links back to a usage of the term in the document. Duplicate page -// numbers are removed to avoid redundancy. -// -// Parameters: -// key (string): The glossary entry key -// count (int): Number of times the term appears in the document -// -// Returns: -// content: Comma-separated list of linked page numbers -// -// Example output: "1, 3, 5" where each number links to the term usage on that page -// -#let __create_backlinks(key, count) = { - // Convert term (key + count) to labels with their displayed page numbers - let page_labels = range(count).map(i => { - let label = __term_label(key, i) - let loc = locate(label) - ( - label, - numbering(__default(loc.page-numbering(), "1"), ..counter(page).at(loc)) - ) - }) - - // Filter out repeated page numbers using a regular loop - let seen = (:) - let singulated = () - for (label, page) in page_labels { - if not seen.keys().contains(page) { - seen.insert(page, true) - singulated.push((label, page)) - } - } - - // Convert resulting set to array of links - singulated.map(((label, page)) => link(label, page)).join(", ") -} - -// Initializes the glossary system and sets up term reference handling. -// -// This function is typically used with `#show: init-glossary`. It performs -// three main tasks: -// 1. Validates and loads glossary entries into the state -// 2. Sets up reference handling to intercept and format term usage -// 3. Applies custom term formatting if provided -// -// Parameters: -// entries (dictionary): Dictionary of glossary entries where: -// - keys are term identifiers -// - values are entry dictionaries containing term details -// show-term ((content) => content): Optional function to customize term rendering -// Default: Returns term content unchanged -// body (content): Document content to process -// -// Returns: -// content: Processed document content with glossary functionality enabled -// -// Throws: -// - If entries parameter is not a dictionary -// -#let init-glossary( - entries, - show-term: (term-body) => term-body, - body -) = context { - // Validate entries parameter type - if type(entries) != dictionary { - panic("entries must be a dictionary, instead got: " + repr(entries)) - } - - // Process and store each glossary entry - for (key, entry) in entries { - __add_entry(key, __normalize_entry(entry)) - } - - // Set up reference handling for glossary terms - show ref: r => { - let (key, ..modifiers) = str(r.target).split(":") - if __has_entry(key) { - show-term(__gls(key, modifiers: modifiers)) - } else { - r - } - } - - body -} - -// Renders a complete glossary with customizable formatting and grouping. -// -// The glossary displays all used terms, optionally grouped by category, with page -// references back to term usage. The appearance is controlled by a theme that -// defines how sections, groups, and entries are formatted. -// -// Parameters: -// title (string): Glossary section title (default: "Glossary") -// theme (dictionary): Controls glossary appearance with three functions: -// - section(title, body): Renders the main glossary section -// - title: The glossary title -// - body: Content containing all groups and entries -// - group(name, index, total, body): Renders a group of related terms -// - name: Group name -// - index: Zero-based group index -// - total: Total number of groups -// - body: Content containing the group's entries -// - entry(entry, index, total): Renders a single glossary entry -// - entry: Dictionary containing: -// - 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 -// groups (array): Optional list of groups to include -// If empty, includes all groups -// -// Returns: -// content: Formatted glossary content -// -// Throws: -// - If a requested group doesn't exist -// -#let glossary( - title: "Glossary", - theme: theme-twocol, - ignore-case: false, - groups: (), -) = context { - // Collect and organize entries by group - let output = (:) - let all_entries = __gloss_entries.final() - let all_used = __gloss_used.final() - - // Determine which groups to process - let all_groups = all_entries - .values() - .map(e => e.at("group")) - .dedup() - .sorted() - - let target_groups = if groups.len() == 0 { - all_groups - } else { - // Validate requested groups exist - for g in groups { - if g not in all_groups { - panic("Requested group not found: " + g) - } - } - groups - } - - // Process entries group by group - for group in target_groups { - let current_entries = () - - // Collect all used entries for this group - for (key, count) in all_used { - let entry = all_entries.at(key) - if entry.at("group") == group { - current_entries.push(( - short: entry.at("short"), - long: entry.at("long"), - description: entry.at("description"), - label: [#metadata(key)#__dict_label(key)], - pages: __create_backlinks(key, count) - )) - } - } - - // Add non-empty groups to output - if current_entries.len() > 0 { - group = if group == none { "" } else { group } - - // 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) - } -} - - // Render the glossary using the theme - let group_index = 0 - - (theme.section)( - title, - for (group, entries) in output { - (theme.group)( - group, - group_index, - output.len(), - for (i, entry) in entries.enumerate() { - (theme.entry)(entry, i, entries.len()) - } - ) - group_index += 1 - } - ) -} diff --git a/preview/glossy/0.2.2/src/themes.typ b/preview/glossy/0.2.2/src/themes.typ deleted file mode 100644 index dc3f4b8..0000000 --- a/preview/glossy/0.2.2/src/themes.typ +++ /dev/null @@ -1,6 +0,0 @@ -#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/table.typ": theme-table -#import "themes/twocol.typ": theme-twocol diff --git a/preview/glossy/0.2.2/src/themes/academic.typ b/preview/glossy/0.2.2/src/themes/academic.typ deleted file mode 100644 index 1093fd2..0000000 --- a/preview/glossy/0.2.2/src/themes/academic.typ +++ /dev/null @@ -1,50 +0,0 @@ -// 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/0.2.2/src/themes/basic.typ b/preview/glossy/0.2.2/src/themes/basic.typ deleted file mode 100644 index b756c27..0000000 --- a/preview/glossy/0.2.2/src/themes/basic.typ +++ /dev/null @@ -1,70 +0,0 @@ -// 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/0.2.2/src/themes/chicago-index.typ b/preview/glossy/0.2.2/src/themes/chicago-index.typ deleted file mode 100644 index fa3c82e..0000000 --- a/preview/glossy/0.2.2/src/themes/chicago-index.typ +++ /dev/null @@ -1,47 +0,0 @@ -// 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/0.2.2/src/themes/compact.typ b/preview/glossy/0.2.2/src/themes/compact.typ deleted file mode 100644 index 86a40e3..0000000 --- a/preview/glossy/0.2.2/src/themes/compact.typ +++ /dev/null @@ -1,91 +0,0 @@ -// 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/0.2.2/src/themes/table.typ b/preview/glossy/0.2.2/src/themes/table.typ deleted file mode 100644 index 79ec375..0000000 --- a/preview/glossy/0.2.2/src/themes/table.typ +++ /dev/null @@ -1,32 +0,0 @@ -// A tabular theme courtesy of [@Fevol](https://github.com/Fevol) -#let theme-table = ( - section: (title, body) => { - heading(level: 1, title) - body - }, - - group: (name, index, total, body) => { - if name != "" and total > 1 { - heading(level: 2, name) - } - - table(columns: 4, - stroke: none, - inset: (x, y) => { - if (x == 0) { - (left: 0pt, rest: 5pt) - } else if (x == 3) { - (right: 0pt, rest: 5pt) - } else { - 5pt - } - }, - table.header([*Abbreviation*], [*Full Name*], [*Description*], [*Pages*]), - ..body - ) - }, - - entry: (entry, index, total) => { - (entry.short, entry.long, entry.description, entry.pages) - } -) diff --git a/preview/glossy/0.2.2/src/themes/twocol.typ b/preview/glossy/0.2.2/src/themes/twocol.typ deleted file mode 100644 index 7183e6e..0000000 --- a/preview/glossy/0.2.2/src/themes/twocol.typ +++ /dev/null @@ -1,72 +0,0 @@ -// 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/0.2.2/src/utils.typ b/preview/glossy/0.2.2/src/utils.typ deleted file mode 100644 index 0acab16..0000000 --- a/preview/glossy/0.2.2/src/utils.typ +++ /dev/null @@ -1,38 +0,0 @@ -// Returns the provided value if it is not none, otherwise returns the default value -// Parameters: -// val: The value to check -// default: The fallback value to use if val is none -// Returns: val if not none, otherwise default -#let __default(val, default) = if val == none { default } else { val } - -// Converts an English word to its plural form following standard English pluralization rules -// Parameters: -// word: The singular word to pluralize -// Returns: The pluralized form of the word, or none if input is none -#let __pluralize(word) = { - // early exit with invalid input - if word == none { - return none - } - - // Define helper functions for checking endings - let ends_with = (suffix) => lower(word).ends-with(suffix) - let ends_with_any = (suffixes) => suffixes.any((suffix) => ends_with(suffix)) - - // Handle special cases for "es" endings (s, x, z, sh, ch) - if ends_with_any(( "s", "x", "z", "sh", "ch" )) { - word + "es" - } else if ends_with("y") and not ends_with_any(( "a", "e", "i", "o", "u" )) { - // Convert y to ies when y follows a consonant - word.slice(0, -1) + "ies" - } else if ends_with("f") { - // Convert f to ves - word.slice(0, -1) + "ves" - } else if ends_with("fe") { - // Convert fe to ves - word.slice(0, -2) + "ves" - } else { - // Default case: just add s - word + "s" - } -} diff --git a/preview/glossy/0.2.2/tests/demo/ref/1.png b/preview/glossy/0.2.2/tests/demo/ref/1.png deleted file mode 100644 index 84a3c89..0000000 Binary files a/preview/glossy/0.2.2/tests/demo/ref/1.png and /dev/null differ diff --git a/preview/glossy/0.2.2/tests/demo/test.typ b/preview/glossy/0.2.2/tests/demo/test.typ deleted file mode 100644 index b347f4f..0000000 --- a/preview/glossy/0.2.2/tests/demo/test.typ +++ /dev/null @@ -1,100 +0,0 @@ -// a minimal example, also listed in the README - -#import "/lib.typ": * - -#let myGlossary = ( - html: ( - short: "HTML", - long: "Hypertext Markup Language", - description: "A standard language for creating web pages", - group: "Web"), - css: ( - short: "CSS", - long: "Cascading Style Sheets", - description: "A language used for describing the presentation of a document", - group: "Web"), - 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 heading(numbering: "1.1") -#set page(numbering: "1") - -= Hello, `glossy`! -In modern web development, languages like @html and @css are essential. - -Now make sure I get your @tps:short reports by 2pm! - -@tps:cap is defined as: @tps:def. - -= Examples with modifiers - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - - [`@tps:short` ], [@tps:short], - [`@tps:long` ], [@tps:long], - [`@tps:both` ], [@tps:both], - [`@tps:long:cap` ], [@tps:long:cap], - [`@tps:long:pl` ], [@tps:long:pl], - [`@tps:short:pl` ], [@tps:short:pl], - [`@tps:both:pl:cap`], [@tps:both:pl:cap], - [`@tps:def` ], [@tps:def], - [`@tps:desc` ], [@tps:desc], -) - -== Using conflicting modifiers (short, long, both) - -These modifiers are semantically mutually exclusive, by they can be combined -syntactically. When multiple are used, `glossy` tries to make a smart choice on -which one to display. - -#table( - columns: 2, - table.header([*Input*], [*Output*]), - - [`@tps:short:long` ], [@tps:short:long], - [`@tps:short:both` ], [@tps:short:both], - [`@tps:long:both` ], [@tps:long:both], - [`@tps:short:long:both`], [@tps:short:long:both], -) - -#let my-theme = ( - section: (title, body) => { - heading(level: 1, title) - body - }, - group: (name, i, n, body) => { - if name != "" and n > 1 { - heading(level: 2, name) - } - body - }, - entry: (entry, i, n) => { - // i is the entry's index, n is total number of entries - let output = [#entry.short] - if entry.long != none { - output = [#output -- #entry.long] - } - if entry.description != none { - output = [#output: #entry.description] - } - block( - grid( - columns: (auto, 1fr, auto), - output, - repeat([#h(0.25em) . #h(0.25em)]), - entry.pages, - ) - ) - } -) - -#glossary(theme: my-theme) -#glossary(title: "Glossary with just empty group", groups: ("",), theme: theme-basic) -#glossary(title: "Glossary with just Web group", groups: ("Web",), theme: theme-compact) diff --git a/preview/glossy/0.2.2/tests/page-numbers/ref/1.png b/preview/glossy/0.2.2/tests/page-numbers/ref/1.png deleted file mode 100644 index 0658e21..0000000 Binary files a/preview/glossy/0.2.2/tests/page-numbers/ref/1.png and /dev/null differ diff --git a/preview/glossy/0.2.2/tests/page-numbers/ref/2.png b/preview/glossy/0.2.2/tests/page-numbers/ref/2.png deleted file mode 100644 index 65d36eb..0000000 Binary files a/preview/glossy/0.2.2/tests/page-numbers/ref/2.png and /dev/null differ diff --git a/preview/glossy/0.2.2/tests/page-numbers/ref/3.png b/preview/glossy/0.2.2/tests/page-numbers/ref/3.png deleted file mode 100644 index 48b31dc..0000000 Binary files a/preview/glossy/0.2.2/tests/page-numbers/ref/3.png and /dev/null differ diff --git a/preview/glossy/0.2.2/tests/page-numbers/ref/4.png b/preview/glossy/0.2.2/tests/page-numbers/ref/4.png deleted file mode 100644 index 5a79626..0000000 Binary files a/preview/glossy/0.2.2/tests/page-numbers/ref/4.png and /dev/null differ diff --git a/preview/glossy/0.2.2/tests/page-numbers/test.pdf b/preview/glossy/0.2.2/tests/page-numbers/test.pdf deleted file mode 100644 index 7c416df..0000000 Binary files a/preview/glossy/0.2.2/tests/page-numbers/test.pdf and /dev/null differ diff --git a/preview/glossy/0.2.2/tests/page-numbers/test.typ b/preview/glossy/0.2.2/tests/page-numbers/test.typ deleted file mode 100644 index 706d873..0000000 --- a/preview/glossy/0.2.2/tests/page-numbers/test.typ +++ /dev/null @@ -1,51 +0,0 @@ -#import "/lib.typ": * - -#let myGlossary = ( - html: ( - short: "HTML", - long: "Hypertext Markup Language", - description: "A standard language for creating web pages", - ), - iphone: ( - short: "iPhone", - ), - 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) - -#set page(numbering: "i") - -@html, @iphone, @css - -#pagebreak() - -#set page(numbering: "1") - -@html, @iphone, @css, @atom - -#pagebreak() - -#counter(page).update(1) -#set page(numbering: "A") - -@html, @iphone, @css, @tps - -#pagebreak() - -#set page(numbering: "I") -@css, @iphone, @tps - -#glossary(theme: theme-chicago-index, ignore-case: true) diff --git a/preview/glossy/0.2.2/tests/sorting/ref/1.png b/preview/glossy/0.2.2/tests/sorting/ref/1.png deleted file mode 100644 index 9cbd27f..0000000 Binary files a/preview/glossy/0.2.2/tests/sorting/ref/1.png and /dev/null differ diff --git a/preview/glossy/0.2.2/tests/sorting/test.typ b/preview/glossy/0.2.2/tests/sorting/test.typ deleted file mode 100644 index 6754610..0000000 --- a/preview/glossy/0.2.2/tests/sorting/test.typ +++ /dev/null @@ -1,40 +0,0 @@ -#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)]) - -#set page(numbering: "1") - -= 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/0.2.2/tests/theme-gallery/ref/1.png b/preview/glossy/0.2.2/tests/theme-gallery/ref/1.png deleted file mode 100644 index 74748a3..0000000 Binary files a/preview/glossy/0.2.2/tests/theme-gallery/ref/1.png and /dev/null differ diff --git a/preview/glossy/0.2.2/tests/theme-gallery/test.typ b/preview/glossy/0.2.2/tests/theme-gallery/test.typ deleted file mode 100644 index ce447b7..0000000 --- a/preview/glossy/0.2.2/tests/theme-gallery/test.typ +++ /dev/null @@ -1,55 +0,0 @@ -#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, - numbering: "1", -) - -#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/0.2.2/themeshots.png b/preview/glossy/0.2.2/themeshots.png deleted file mode 100644 index 74748a3..0000000 Binary files a/preview/glossy/0.2.2/themeshots.png and /dev/null differ diff --git a/preview/glossy/0.2.2/thumbnail.png b/preview/glossy/0.2.2/thumbnail.png deleted file mode 100644 index 7635045..0000000 Binary files a/preview/glossy/0.2.2/thumbnail.png and /dev/null differ diff --git a/preview/glossy/0.2.2/typst.toml b/preview/glossy/0.2.2/typst.toml deleted file mode 100644 index 4d87082..0000000 --- a/preview/glossy/0.2.2/typst.toml +++ /dev/null @@ -1,11 +0,0 @@ -[package] -name = "glossy" -version = "0.2.2" -entrypoint = "lib.typ" -authors = ["Stephen Waits "] -license = "MIT" -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", "themeshots.pjng", "tests"] diff --git a/preview/glossy/0.1.0/LICENSE b/preview/glossy/LICENSE similarity index 100% rename from preview/glossy/0.1.0/LICENSE rename to preview/glossy/LICENSE diff --git a/preview/glossy/0.1.0/README.md b/preview/glossy/README.md similarity index 100% rename from preview/glossy/0.1.0/README.md rename to preview/glossy/README.md diff --git a/preview/glossy/0.1.0/examples/readme.pdf b/preview/glossy/examples/readme.pdf similarity index 100% rename from preview/glossy/0.1.0/examples/readme.pdf rename to preview/glossy/examples/readme.pdf diff --git a/preview/glossy/0.1.0/examples/readme.typ b/preview/glossy/examples/readme.typ similarity index 100% rename from preview/glossy/0.1.0/examples/readme.typ rename to preview/glossy/examples/readme.typ diff --git a/preview/glossy/0.1.0/lib.typ b/preview/glossy/lib.typ similarity index 100% rename from preview/glossy/0.1.0/lib.typ rename to preview/glossy/lib.typ diff --git a/preview/glossy/0.1.0/src/gloss.typ b/preview/glossy/src/gloss.typ similarity index 100% rename from preview/glossy/0.1.0/src/gloss.typ rename to preview/glossy/src/gloss.typ diff --git a/preview/glossy/0.1.0/src/themes.typ b/preview/glossy/src/themes.typ similarity index 100% rename from preview/glossy/0.1.0/src/themes.typ rename to preview/glossy/src/themes.typ diff --git a/preview/glossy/0.1.0/src/utils.typ b/preview/glossy/src/utils.typ similarity index 100% rename from preview/glossy/0.1.0/src/utils.typ rename to preview/glossy/src/utils.typ diff --git a/preview/glossy/0.1.0/thumbnail.png b/preview/glossy/thumbnail.png similarity index 100% rename from preview/glossy/0.1.0/thumbnail.png rename to preview/glossy/thumbnail.png diff --git a/preview/glossy/0.1.0/typst.toml b/preview/glossy/typst.toml similarity index 100% rename from preview/glossy/0.1.0/typst.toml rename to preview/glossy/typst.toml