From 4ada8ea17dea41e1642514ffe85394dfddce403a Mon Sep 17 00:00:00 2001 From: "Guilherme D. Garcia" Date: Sat, 17 Oct 2026 23:38:01 -0400 Subject: [PATCH] phonokit:0.6.1 --- packages/preview/phonokit/0.6.1/LICENSE | 21 + packages/preview/phonokit/0.6.1/README.md | 193 ++ packages/preview/phonokit/0.6.1/_config.typ | 8 + .../preview/phonokit/0.6.1/autosegmental.typ | 275 ++ .../preview/phonokit/0.6.1/consonants.typ | 989 +++++++ packages/preview/phonokit/0.6.1/ex.typ | 408 +++ packages/preview/phonokit/0.6.1/extras.typ | 39 + packages/preview/phonokit/0.6.1/features.typ | 2140 +++++++++++++++ .../0.6.1/gallery/autoseg_example_1.png | Bin 0 -> 5687 bytes .../0.6.1/gallery/autoseg_example_1.typ | 11 + .../0.6.1/gallery/autoseg_example_2.png | Bin 0 -> 11421 bytes .../0.6.1/gallery/autoseg_example_2.typ | 22 + .../0.6.1/gallery/autoseg_example_3.png | Bin 0 -> 58139 bytes .../0.6.1/gallery/autoseg_example_3.typ | 47 + .../0.6.1/gallery/consonants_example.png | Bin 0 -> 78637 bytes .../0.6.1/gallery/consonants_example.typ | 8 + .../phonokit/0.6.1/gallery/feat_geom.png | Bin 0 -> 45515 bytes .../phonokit/0.6.1/gallery/feat_geom.typ | 9 + .../0.6.1/gallery/features_example.png | Bin 0 -> 127933 bytes .../0.6.1/gallery/features_example.typ | 5 + .../phonokit/0.6.1/gallery/grid_example.png | Bin 0 -> 5493 bytes .../phonokit/0.6.1/gallery/grid_example.typ | 4 + .../phonokit/0.6.1/gallery/maxent_example.png | Bin 0 -> 132255 bytes .../phonokit/0.6.1/gallery/maxent_example.typ | 41 + .../0.6.1/gallery/multi-tier_example.png | Bin 0 -> 18041 bytes .../0.6.1/gallery/multi-tier_example.typ | 18 + .../phonokit/0.6.1/gallery/ot_example.png | Bin 0 -> 23071 bytes .../phonokit/0.6.1/gallery/ot_example.typ | 17 + .../0.6.1/gallery/syllable_example.png | Bin 0 -> 9388 bytes .../0.6.1/gallery/syllable_example.typ | 6 + .../0.6.1/gallery/vowels3d_example.png | Bin 0 -> 26986 bytes .../0.6.1/gallery/vowels3d_example.typ | 4 + .../phonokit/0.6.1/gallery/vowels_example.png | Bin 0 -> 25610 bytes .../phonokit/0.6.1/gallery/vowels_example.typ | 17 + .../phonokit/0.6.1/gallery/word_example.png | Bin 0 -> 23435 bytes .../phonokit/0.6.1/gallery/word_example.typ | 5 + packages/preview/phonokit/0.6.1/geom.typ | 2028 ++++++++++++++ packages/preview/phonokit/0.6.1/grids.typ | 114 + packages/preview/phonokit/0.6.1/hasse.typ | 444 ++++ .../preview/phonokit/0.6.1/intonational.typ | 80 + packages/preview/phonokit/0.6.1/ipa.typ | 507 ++++ packages/preview/phonokit/0.6.1/lib.typ | 1234 +++++++++ .../preview/phonokit/0.6.1/multi-tier.typ | 510 ++++ packages/preview/phonokit/0.6.1/ot.typ | 1262 +++++++++ packages/preview/phonokit/0.6.1/phonetics.typ | 1017 +++++++ packages/preview/phonokit/0.6.1/prosody.typ | 2343 +++++++++++++++++ packages/preview/phonokit/0.6.1/sonority.typ | 279 ++ .../preview/phonokit/0.6.1/sound-shift.typ | 261 ++ packages/preview/phonokit/0.6.1/typst.toml | 23 + packages/preview/phonokit/0.6.1/ui-lang.typ | 369 +++ packages/preview/phonokit/0.6.1/vowels-3d.typ | 212 ++ packages/preview/phonokit/0.6.1/vowels.typ | 605 +++++ 52 files changed, 15575 insertions(+) create mode 100644 packages/preview/phonokit/0.6.1/LICENSE create mode 100644 packages/preview/phonokit/0.6.1/README.md create mode 100644 packages/preview/phonokit/0.6.1/_config.typ create mode 100644 packages/preview/phonokit/0.6.1/autosegmental.typ create mode 100644 packages/preview/phonokit/0.6.1/consonants.typ create mode 100644 packages/preview/phonokit/0.6.1/ex.typ create mode 100644 packages/preview/phonokit/0.6.1/extras.typ create mode 100644 packages/preview/phonokit/0.6.1/features.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/autoseg_example_1.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/autoseg_example_1.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/autoseg_example_2.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/autoseg_example_2.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/autoseg_example_3.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/autoseg_example_3.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/consonants_example.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/consonants_example.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/feat_geom.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/feat_geom.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/features_example.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/features_example.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/grid_example.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/grid_example.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/maxent_example.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/maxent_example.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/multi-tier_example.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/multi-tier_example.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/ot_example.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/ot_example.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/syllable_example.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/syllable_example.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/vowels3d_example.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/vowels3d_example.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/vowels_example.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/vowels_example.typ create mode 100644 packages/preview/phonokit/0.6.1/gallery/word_example.png create mode 100644 packages/preview/phonokit/0.6.1/gallery/word_example.typ create mode 100644 packages/preview/phonokit/0.6.1/geom.typ create mode 100644 packages/preview/phonokit/0.6.1/grids.typ create mode 100644 packages/preview/phonokit/0.6.1/hasse.typ create mode 100644 packages/preview/phonokit/0.6.1/intonational.typ create mode 100644 packages/preview/phonokit/0.6.1/ipa.typ create mode 100644 packages/preview/phonokit/0.6.1/lib.typ create mode 100644 packages/preview/phonokit/0.6.1/multi-tier.typ create mode 100644 packages/preview/phonokit/0.6.1/ot.typ create mode 100644 packages/preview/phonokit/0.6.1/phonetics.typ create mode 100644 packages/preview/phonokit/0.6.1/prosody.typ create mode 100644 packages/preview/phonokit/0.6.1/sonority.typ create mode 100644 packages/preview/phonokit/0.6.1/sound-shift.typ create mode 100644 packages/preview/phonokit/0.6.1/typst.toml create mode 100644 packages/preview/phonokit/0.6.1/ui-lang.typ create mode 100644 packages/preview/phonokit/0.6.1/vowels-3d.typ create mode 100644 packages/preview/phonokit/0.6.1/vowels.typ diff --git a/packages/preview/phonokit/0.6.1/LICENSE b/packages/preview/phonokit/0.6.1/LICENSE new file mode 100644 index 0000000000..e116f4b73a --- /dev/null +++ b/packages/preview/phonokit/0.6.1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2025 Guilherme D. Garcia + +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/packages/preview/phonokit/0.6.1/README.md b/packages/preview/phonokit/0.6.1/README.md new file mode 100644 index 0000000000..8b43262e45 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/README.md @@ -0,0 +1,193 @@ +
+ + + + phonokit logo + +
+ +
+ +[![Typst Package](https://img.shields.io/badge/dynamic/toml?url=https%3A%2F%2Fraw.githubusercontent.com%2Fguilhermegarcia%2Fphonokit%2Fmain%2Ftypst.toml&query=%24.package.version&prefix=v&logo=typst&label=package&color=239DAD)](https://typst.app/universe/package/phonokit) +[![MIT License](https://img.shields.io/badge/license-MIT-blue)](LICENSE) +[![User Manual](https://img.shields.io/badge/manual-.pdf-purple)](https://doi.org/10.5281/zenodo.18260076) + +
+ +**Charis font is needed** for this package to work exactly as intended, but you can also use your own font with `#phonokit-init(font: "...")`. New Computer Modern is used for arrows. + +## 📝 Some examples + + + + + + + + + + + + + + + + + + + + + + +
+ Consonant inventory table +
Consonant inventories (with pre-defined languages, simplified) +
+ Vowel trapezoid chart +
Vowel trapezoids (with pre-defined languages and arrows) +
+ 3D vowel space +
3D vowel space (with adjustable rotation) +
+ Multi-tier phonological representation +
Multi-tier representations +
+ Syllable structure tree +
Syllable structure (onset-rhyme and moraic) +
+ Prosodic word tree +
Prosodic word (with metrical parsing) +
+ Metrical grid +
Metrical grids with IPA support +
+ Autosegmental feature spreading +
Autosegmental phonology: features +
+ Autosegmental tone representation +
Autosegmental phonology: tones +
+ Feature geometry tree +
Feature geometry +
+ OT tableau with shading +
OT tableaux with automatic shading +
+ MaxEnt tableau with probability bars +
MaxEnt tableaux with automatic calculation +
+ +Click on any image to view its source code. + +## 🔍 Manual + +See [**manual**](https://doi.org/10.5281/zenodo.18260076) for a comprehensive demonstration of available functions and their usage. + +### IPA Module + +- **tipa-style input**: Use familiar LaTeX tipa notation instead of hunting for Unicode symbols +- **Comprehensive symbol support**: most common IPA consonants, vowels, and diacritics +- **Vowel charts**: Plot vowels on the IPA vowel trapezoid with accurate positioning +- **3D vowel space**: Plot vowels in a rotatable prism with rounding as depth (Ladefoged & Maddieson 1996), with adjustable rotation and tilt +- **Consonant tables**: Display consonants in the pulmonic IPA consonant table +- **Scalable charts**: Adjust size to fit your document layout (scaling includes text as expected) + +`vowels()` and `consonants()` check built-in preset names before interpreting +custom IPA inventories. Empty or whitespace-only input (or no argument) renders +nothing while the user is getting started, unless a `lang:` is supplied. For +example, `vowels("english")` uses a preset, while +`vowels("aeiou")` plots a custom inventory. An unknown name such as +`vowels("indonesian")` or `consonants("indonesian")` displays an error instead of a chart when it +contains unsupported inventory symbols, listing available presets and guidance for custom inventories. Unsupported symbols are never silently filtered +from custom inventories. Supported tipa notation, nasal vowels, and braced +consonant sequences remain accepted, as does whitespace between symbols. + +Use `lang:` when you intend a language preset, for example +`vowels(lang: "english")`. Unknown `lang:` values also display an error instead of a chart; a +positional string made entirely of supported inventory symbols is interpreted +as a custom inventory if it does not match a preset. Braced affricates and +aspirated consonants still require their existing display flags. + +### Phonetics Module + +- **Sound shifts**: Create schematic chain shifts, mergers, and splits by placing symbols freely in two-dimensional space and connecting them with arrows +- **Voice Onset Time**: Generate pedagogical VOT timelines showing closure, release, aspiration, and voicing onset, with support for segment labels and localized interface labels +- **Vowel dispersion**: Plot illustrative F1/F2 vowel distributions with optional jitter, ellipses, and support for reading real vowel data from `csv` files + +### Prosody Module + +- **Prosodic structure visualization**: Draw syllable structures (onset-rhyme and moraic representations) as well as feet and prosodic words with simple and intuitive syntax. You can also define which symbols you prefer to use for different prosodic domains +- **Multi-tier representations**: Create complex non-linear representations +- **Metrical grids**: Inputs as strings or tuples +- **Sonority profile**: Visualize the sonority of a string +- **ToBI labels**: Add intonational labels to running text with `#int()`, with optional vertical lines for association + +### Autosegmental Module + +- **Features and tones**: Create autosegmental representation for both features and tones +- **Support for common processes**: Easily add linking, delinking, floating tones, one-to-many relationships and highlighting. Additional options for spacing and annotation also available + +### Feature Geometry Module + +- **Create complex feature geometry representations**: Pre-defined phonemes are available for quick representations +- **Intuitive arguments based on typical nodes**: Create custom representations with intuitive argument structure +- **Support for arrows and delinking**: Represent processes with arrows, which may or may not curve. Obstacle avoidance is attempted when arrows curve, but you can also customize arrow path. +- **Highlight nodes**: Easily highlight nodes with the `highlight` argument, which dims the whole representation *except* the nodes you need highlighted. + +### SPE module + +- **Feature matrices**: Create SPE-style feature matrices with `#feat()` and segment-based distinctive-feature matrices with `#feat-matrix()` + +### Optimality Theory Module + +- **OT tableaux**: Create publication-ready Optimality Theory tableaux with automatic formatting +- **Automatic shading**: Cells are automatically grayed out after fatal violations +- **Winner indication**: Optimal candidates automatically marked with ☞ (pointing finger) +- **IPA support**: Input and candidate forms can use tipa-style IPA notation +- **Hasse diagrams**: Generate Hasse diagrams to visualize constraint rankings + +### Maximum Entropy Module + +- **MaxEnt tableaux**: Generate Maximum Entropy grammar tableaux with probability calculations +- **Automatic calculations**: Computes harmony scores H(x), unnormalized probabilities P*(x), and normalized probabilities P(x) +- **Visual probability bars**: Optional graphical representation of candidate probabilities +- **IPA support**: Input and candidate forms can use tipa-style IPA notation + +### Harmonic Grammar Module + +- **HG tableaux**: Generate HG tableaux with automatic calculation of harmony given constraint weights and violations +- **Noisy HG**: Generate NHG tableaux with automatic calculation of harmony and probabilities derived from simulated noise and multiple evaluations + +### General Module + +- **Numbered examples**: Create examples and sub-examples with labels and correct alignment +- **Shortcuts**: Quick commands to add a range of arrows, angle brackets for extrametricality, and SPE-style underlines for context + +*** + +## 📦 Package Repository + +- `https://github.com/guilhermegarcia/phonokit` [(most up-to-date version)](http://github.com/guilhermegarcia/phonokit) +- `https://typst.app/universe/package/phonokit` [(published on Typst)](https://typst.app/universe/package/phonokit) + +## Package website + +For the most up-to-date information about the package, vignettes and demos, visit . + +## License + +MIT + +## Author + +**Guilherme D. Garcia** \ +Email: \ +Website: + +## 📚 Citation + +If you use this package in your research, please visit its GitHub repository and cite it using the metadata from the `CITATION.cff` file or click the "Cite this repository" button in the GitHub sidebar. + +## Contributing + +Contributions are welcome! Please feel free to submit issues or pull requests. diff --git a/packages/preview/phonokit/0.6.1/_config.typ b/packages/preview/phonokit/0.6.1/_config.typ new file mode 100644 index 0000000000..6ccb1944da --- /dev/null +++ b/packages/preview/phonokit/0.6.1/_config.typ @@ -0,0 +1,8 @@ +// Shared configuration state for phonokit +// This module provides a global font setting that all modules can access. + +#let phonokit-font = state("phonokit-font", "Charis") + +#let phonokit-init(font: "Charis") = { + phonokit-font.update(font) +} diff --git a/packages/preview/phonokit/0.6.1/autosegmental.typ b/packages/preview/phonokit/0.6.1/autosegmental.typ new file mode 100644 index 0000000000..275f933d0e --- /dev/null +++ b/packages/preview/phonokit/0.6.1/autosegmental.typ @@ -0,0 +1,275 @@ +#import "@preview/cetz:0.5.2" +#import "ipa.typ": ipa +#import "_config.typ": phonokit-font + +#let autoseg( + segments, + features: (), + links: (), + delinks: (), + spacing: 1.5, + arrow: false, + tone: false, + highlight: (), + float: (), + multilinks: (), + baseline: 40%, + gloss: "", + dash: "dashed", +) = { + box(inset: 1.2em, baseline: baseline, cetz.canvas({ + import cetz.draw: * + + // Coordinate positions depend on whether we're drawing tones or features + let seg_y = if tone { 0 } else { 0.8 } + let feat_y = if tone { 0.8 } else { 0 } + let gloss_y = if tone { -0.8 } else { 1.6 } + let seg_anchor_dir = if tone { "north" } else { "south" } + let feat_anchor_dir = if tone { "south" } else { "north" } + let gloss_anchor_dir = if tone { "north" } else { "south" } + + for (i, seg) in segments.enumerate() { + let feat = features.at(i, default: "") + let x = i * spacing + + // Check if this position is part of a multilink (skip normal drawing if so) + let is_multilinked = multilinks.any(entry => { + let tone_spec = entry.at(0) + let tone_pos = if type(tone_spec) == array { tone_spec.at(0) } else { tone_spec } + tone_pos == i + }) + + // Handle multiple tones/features as an array (for branching) + let feat_array = if type(feat) == array { feat } else { (feat,) } + let num_feats = feat_array.len() + + group(name: "n" + str(i), { + // 3. Labels (segment) + // Use horizon alignment to ensure consistent baseline across all segments + content((x, seg_y), padding: 0.1, anchor: seg_anchor_dir, box(height: 1em, align(horizon, context text( + font: phonokit-font.get(), + ipa(seg), + )))) + + // Process each tone/feature (creates branching for multiple tones) + // Skip if this position is multilinked (will be drawn by multilink code) + for (j, f) in feat_array.enumerate() { + if f != "" and not is_multilinked { + // Calculate horizontal offset for multiple tones + let x_offset = if num_feats > 1 { (j - (num_feats - 1) / 2) * 0.6 } else { 0 } + let feat_x = x + x_offset + + // 1. Draw the vertical stem if not floating + if i not in float { + line((feat_x, feat_y), (x, seg_y), stroke: 0.05em, name: "stem" + str(j)) + } + + // 2. Feature/tone label with optional circle highlight + // Check if this specific tone should be highlighted + let should_highlight = tone and (i in highlight or (i, j) in highlight) + let box_stroke = if should_highlight { 0.05em + black } else { none } + content((feat_x, feat_y), padding: 0.1, anchor: feat_anchor_dir, box( + stroke: box_stroke, + inset: 0.15em, + radius: 100%, + width: 1.2em, + height: 1.2em, + align(center + horizon, context text(font: phonokit-font.get(), f)), + )) + + // Create anchor for this specific tone (for sub-indexing in links) + anchor("feat" + str(j), (feat_x, feat_y)) + } + } + + // 3. Anchors for association lines (center position and segment) + anchor("seg", (x, seg_y)) + anchor("feat", (x, feat_y)) + }) + } + + // 4. Draw the spreading links + for (src, dest) in links { + // Parse src and dest - can be int or (pos, sub) tuple for targeting specific tones + let parse_index = idx => { + if type(idx) == array { + (pos: idx.at(0), sub: idx.at(1)) + } else { + (pos: idx, sub: none) + } + } + + let src_parsed = parse_index(src) + let dest_parsed = parse_index(dest) + + // Build anchor names with optional sub-index + let get_anchor = (parsed, tier) => { + let base = "n" + str(parsed.pos) + if tier == "feat" and parsed.sub != none { + base + ".feat" + str(parsed.sub) + } else if tier == "feat" { + base + ".feat" + } else { + base + ".seg" + } + } + + // Both modes: link from feature/tone[src] to segment[dest] + let start_anchor = get_anchor(src_parsed, "feat") + let end_anchor = get_anchor(dest_parsed, "seg") + + // Draw the association line + let link-stroke = if dash == "solid" { (thickness: 0.05em) } else { (dash: dash, thickness: 0.05em) } + line(start_anchor, end_anchor, stroke: link-stroke) + + if arrow { + // Arrow points from feature/tone toward segment + line(start_anchor, end_anchor, stroke: (thickness: 0em), mark: (end: "stealth"), fill: black) + } + } + + // 5. Draw multi-linked tones (shared tones positioned between segments) + for entry in multilinks { + let (tone_spec, seg_positions) = entry + + // Parse tone specification (can be index or (pos, sub)) + let tone_parsed = if type(tone_spec) == array { + (pos: tone_spec.at(0), sub: tone_spec.at(1)) + } else { + (pos: tone_spec, sub: none) + } + + // Calculate midpoint position for the tone + let min_seg = calc.min(..seg_positions) + let max_seg = calc.max(..seg_positions) + let mid_x = ((min_seg + max_seg) / 2) * spacing + + // Get the tone text + let tone_feat = features.at(tone_parsed.pos, default: "") + let tone_array = if type(tone_feat) == array { tone_feat } else { (tone_feat,) } + let tone_text = if tone_parsed.sub != none { + tone_array.at(tone_parsed.sub) + } else { + if type(tone_feat) == array { tone_feat.join(" ") } else { tone_feat } + } + + // Draw the tone at midpoint + let box_stroke = if tone and (tone_parsed.pos in highlight or (tone_parsed.pos, tone_parsed.sub) in highlight) { + 0.05em + black + } else { + none + } + content((mid_x, feat_y), padding: 0.1, anchor: feat_anchor_dir, box( + stroke: box_stroke, + inset: 0.15em, + radius: 100%, + width: 1.2em, + height: 1.2em, + align(center + horizon, context text(font: phonokit-font.get(), tone_text)), + )) + + // Draw solid lines to each segment (no arrows, no dashes) + for seg_pos in seg_positions { + let seg_x = seg_pos * spacing + line((mid_x, feat_y), (seg_x, seg_y), stroke: 0.05em) + } + } + + // 6. Draw delinks on association lines (drawn after multilinks to be on top) + for (src, dest) in delinks { + // Parse src and dest - can be int or (pos, sub) tuple + let parse_index = idx => { + if type(idx) == array { + (pos: idx.at(0), sub: idx.at(1)) + } else { + (pos: idx, sub: none) + } + } + + let src_parsed = parse_index(src) + let dest_parsed = parse_index(dest) + + // Calculate dest position + let dest_x = dest_parsed.pos * spacing + + // Check if source is multilinked + let is_multilinked = multilinks.any(entry => { + let tone_spec = entry.at(0) + let tone_pos = if type(tone_spec) == array { tone_spec.at(0) } else { tone_spec } + tone_pos == src_parsed.pos + }) + + let src_feat_x = if is_multilinked { + // Find the multilink entry for this source + let multilink_entry = multilinks.find(entry => { + let tone_spec = entry.at(0) + let tone_pos = if type(tone_spec) == array { tone_spec.at(0) } else { tone_spec } + tone_pos == src_parsed.pos + }) + let seg_positions = multilink_entry.at(1) + let min_seg = calc.min(..seg_positions) + let max_seg = calc.max(..seg_positions) + ((min_seg + max_seg) / 2) * spacing + } else { + // Regular tone or branching tone + let src_x = src_parsed.pos * spacing + let src_feat = features.at(src_parsed.pos, default: "") + let src_feat_array = if type(src_feat) == array { src_feat } else { (src_feat,) } + let num_src_feats = src_feat_array.len() + + let src_x_offset = if num_src_feats > 1 and src_parsed.sub != none { + (src_parsed.sub - (num_src_feats - 1) / 2) * 0.6 + } else { + 0 + } + src_x + src_x_offset + } + + // Calculate midpoint for delink marks + let mid_x = (src_feat_x + dest_x) / 2 + let mid_y = (seg_y + feat_y) / 2 + + // Draw the delink marks (two parallel lines perpendicular to the association line) + // Calculate the direction vector and its perpendicular + let dx = dest_x - src_feat_x + let dy = seg_y - feat_y + let length = calc.sqrt(dx * dx + dy * dy) + + // Normalized direction + let dir_x = dx / length + let dir_y = dy / length + + // Perpendicular direction (rotate 90 degrees) + let perp_x = -dir_y + let perp_y = dir_x + + let offset = 0.15 + let spacing_offset = 0.06 + + // First delink line (closer to tone) + let p1_start = ( + mid_x - offset * perp_x - spacing_offset * dir_x, + mid_y - offset * perp_y - spacing_offset * dir_y, + ) + let p1_end = (mid_x + offset * perp_x - spacing_offset * dir_x, mid_y + offset * perp_y - spacing_offset * dir_y) + + // Second delink line (closer to segment) + let p2_start = ( + mid_x - offset * perp_x + spacing_offset * dir_x, + mid_y - offset * perp_y + spacing_offset * dir_y, + ) + let p2_end = (mid_x + offset * perp_x + spacing_offset * dir_x, mid_y + offset * perp_y + spacing_offset * dir_y) + + line(p1_start, p1_end, stroke: 0.05em) + line(p2_start, p2_end, stroke: 0.05em) + } + + // 7. Draw gloss (if provided) + if gloss != "" and gloss != () { + // Calculate center of the entire representation + let center_x = ((segments.len() - 1) / 2) * spacing + // Use fixed-height box to ensure consistent baseline alignment across autoseg instances + content((center_x, gloss_y), padding: 0.1, anchor: gloss_anchor_dir, box(height: 1em, align(horizon, context text(size: 0.9em, font: phonokit-font.get(), gloss)))) + } + })) +} diff --git a/packages/preview/phonokit/0.6.1/consonants.typ b/packages/preview/phonokit/0.6.1/consonants.typ new file mode 100644 index 0000000000..bf7ede9f7d --- /dev/null +++ b/packages/preview/phonokit/0.6.1/consonants.typ @@ -0,0 +1,989 @@ +#import "@preview/cetz:0.5.2": canvas, draw +#import "ipa.typ": ipa-to-unicode +#import "_config.typ": phonokit-font +#import "ui-lang.typ": resolve-ui-lang, ui-lang-error, ui-consonant-labels + +// Consonant data with place, manner, and voicing +// place: 0=bilabial, 1=labiodental, 2=dental, 3=alveolar, 4=postalveolar +// 5=retroflex, 6=palatal, 7=velar, 8=uvular, 9=pharyngeal, 10=glottal +// manner: 0=plosive, 1=nasal, 2=trill, 3=tap/flap, 4=fricative, +// 5=lateral fricative, 6=approximant, 7=lateral approximant +#let consonant-data = ( + // Plosives (row 0) + "p": (place: 0, manner: 0, voicing: false), + "b": (place: 0, manner: 0, voicing: true), + "t": (place: 3, manner: 0, voicing: false), + "d": (place: 3, manner: 0, voicing: true), + "ʈ": (place: 5, manner: 0, voicing: false), + "ɖ": (place: 5, manner: 0, voicing: true), + "c": (place: 6, manner: 0, voicing: false), + "ɟ": (place: 6, manner: 0, voicing: true), + "k": (place: 7, manner: 0, voicing: false), + "ɡ": (place: 7, manner: 0, voicing: true), + "g": (place: 7, manner: 0, voicing: true), + "q": (place: 8, manner: 0, voicing: false), + "ɢ": (place: 8, manner: 0, voicing: true), + "ʔ": (place: 10, manner: 0, voicing: false), + // Nasals (row 1) + "m": (place: 0, manner: 1, voicing: true), + "ɱ": (place: 1, manner: 1, voicing: true), + "n": (place: 3, manner: 1, voicing: true), + "ɳ": (place: 5, manner: 1, voicing: true), + "ɲ": (place: 6, manner: 1, voicing: true), + "ŋ": (place: 7, manner: 1, voicing: true), + "ɴ": (place: 8, manner: 1, voicing: true), + // Trills (row 2) + "ʙ": (place: 0, manner: 2, voicing: true), + "r": (place: 3, manner: 2, voicing: true), + "ʀ": (place: 8, manner: 2, voicing: true), + // Tap or Flap (row 3) + "ⱱ": (place: 1, manner: 3, voicing: true), + "ɾ": (place: 3, manner: 3, voicing: true), + "ɽ": (place: 5, manner: 3, voicing: true), + // Fricatives (row 4) + "ɸ": (place: 0, manner: 4, voicing: false), + "β": (place: 0, manner: 4, voicing: true), + "f": (place: 1, manner: 4, voicing: false), + "v": (place: 1, manner: 4, voicing: true), + "θ": (place: 2, manner: 4, voicing: false), + "ð": (place: 2, manner: 4, voicing: true), + "s": (place: 3, manner: 4, voicing: false), + "z": (place: 3, manner: 4, voicing: true), + "ʃ": (place: 4, manner: 4, voicing: false), + "ʒ": (place: 4, manner: 4, voicing: true), + "ʂ": (place: 5, manner: 4, voicing: false), + "ʐ": (place: 5, manner: 4, voicing: true), + "ç": (place: 6, manner: 4, voicing: false), + "ʝ": (place: 6, manner: 4, voicing: true), + "x": (place: 7, manner: 4, voicing: false), + "ɣ": (place: 7, manner: 4, voicing: true), + "χ": (place: 8, manner: 4, voicing: false), + "ʁ": (place: 8, manner: 4, voicing: true), + "ħ": (place: 9, manner: 4, voicing: false), + "ʕ": (place: 9, manner: 4, voicing: true), + "h": (place: 10, manner: 4, voicing: false), + "ɦ": (place: 10, manner: 4, voicing: true), + // Lateral fricatives (row 5) + "ɬ": (place: 3, manner: 5, voicing: false), + "ɮ": (place: 3, manner: 5, voicing: true), + // Approximants (row 6) + "w": (place: 0, manner: 6, voicing: true), // labiovelar, shown under bilabial + "ʋ": (place: 1, manner: 6, voicing: true), + "ɹ": (place: 3, manner: 6, voicing: true), + "ɻ": (place: 5, manner: 6, voicing: true), + "j": (place: 6, manner: 6, voicing: true), + "ɰ": (place: 7, manner: 6, voicing: true), + // Lateral approximants (row 7) + "l": (place: 3, manner: 7, voicing: true), + "ɭ": (place: 5, manner: 7, voicing: true), + "ʎ": (place: 6, manner: 7, voicing: true), + "ʟ": (place: 7, manner: 7, voicing: true), +) + +// Aspirated plosive data (shown in separate row when aspirated: true) +#let aspirated-plosive-data = ( + // Aspirated plosives (voiceless only - aspiration is contrastive with plain voiceless) + "pʰ": (place: 0, voicing: false), + "tʰ": (place: 3, voicing: false), + "ʈʰ": (place: 5, voicing: false), + "cʰ": (place: 6, voicing: false), + "kʰ": (place: 7, voicing: false), + "qʰ": (place: 8, voicing: false), +) + +// Affricate data (shown in separate row when affricates: true) +// These appear after fricatives in the chart +// Note: Displayed without tie bars since the row label makes it clear they're affricates +#let affricate-data = ( + // Labiodental affricates + "pf": (place: 1, voicing: false), + "bv": (place: 1, voicing: true), + // Alveolar affricates + "ts": (place: 3, voicing: false), + "dz": (place: 3, voicing: true), + // Postalveolar affricates + "tʃ": (place: 4, voicing: false), + "dʒ": (place: 4, voicing: true), + // Retroflex affricates + "ʈʂ": (place: 5, voicing: false), + "ɖʐ": (place: 5, voicing: true), + // Alveolo-palatal affricates + "tɕ": (place: 4, voicing: false), // Use postalveolar column + "dʑ": (place: 4, voicing: true), +) + +// Aspirated affricate data (shown when both affricates: true AND aspirated: true) +#let aspirated-affricate-data = ( + // Aspirated affricates (voiceless only) + "tsʰ": (place: 3, voicing: false), + "tʃʰ": (place: 4, voicing: false), + "ʈʂʰ": (place: 5, voicing: false), + "tɕʰ": (place: 4, voicing: false), // Alveolo-palatal +) + +// Column labels (places of articulation) +#let places = ( + "Bilabial", + "Labiodental", + "Dental", + "Alveolar", + "Postalveolar", + "Retroflex", + "Palatal", + "Velar", + "Uvular", + "Pharyngeal", + "Glottal", +) + +#let places-short = ( + "Bilab", + "Labdent", + "Dent", + "Alv", + "Postalv", + "Retro", + "Pal", + "Vel", + "Uvu", + "Phar", + "Glot", +) + +// Row labels (manners of articulation) +#let manners = ( + "Plosive", + "Nasal", + "Trill", + "Tap or Flap", + "Fricative", + "Lateral fricative", + "Approximant", + "Lateral approximant", +) + +#let manners-short = ( + "Plos", + "Nas", + "Trill", + "Tap/Flap", + "Fric", + "Lat fric", + "Approx", + "Lat approx", +) + +// Language consonant inventories +#let language-consonants = ( + "all": "pbtdʈɖcɟkɡqɢʔmɱnɳɲŋɴʙrʀⱱɾɽɸβfvθðszʃʒʂʐçʝxɣχʁħʕhɦɬɮʋɹɻjɰwlɭʎʟ", + "english": "pbmnŋtdkɡfvθðszʃʒhlɹwj", + "spanish": "pbmnɲtdkɡfθsxlrɾj", + "french": "pbmnɲtdkɡfrvszʃʒljw", + "german": "pbmntdkɡfvszʃʒçxhʁlj", + "italian": "pbmnɲtdkɡfvszʃʎlrj", + "japanese": "pbmnɲtdkɡçɸsʃzʒhɾj", + "portuguese": "pbmnɲtdkɡfvszʃʒʎlrɾjw", + "russian": "pbmntdkɡfvszʃʒxlrj", + "arabic": "btdkqʔmnfwðszʃxɣħʕhlrj", +) + +// Language affricate inventories (used when affricates: true) +// Note: No tie bars needed - the "Affricate" row label makes it clear +// Note: Only one affricate pair per place - "all" uses more common variants +#let language-affricates = ( + "all": "pfbvtsdztʃdʒʈʂɖʐ", // tʃ/dʒ chosen over tɕ/dʑ (more common) + "english": "tʃdʒ", + "spanish": "tʃ", + "french": "", // No native affricates + "german": "pfts", + "italian": "tsdztʃdʒ", + "japanese": "", // Do not include allophones + "portuguese": "", // [tʃ dʒ] are allophones of /t d/ before [i] + "russian": "tstʃ", + "arabic": "", // No native affricates +) + +// Language aspirated plosive inventories (used when aspirated: true) +#let language-aspirated-plosives = ( + "all": "pʰtʰʈʰcʰkʰqʰ", + "english": "", // English aspiration is allophonic, not phonemic + "spanish": "", + "french": "", + "german": "", + "italian": "", + "japanese": "", + "portuguese": "", + "russian": "", + "arabic": "", +) + +// Language aspirated affricate inventories (used when both affricates: true AND aspirated: true) +#let language-aspirated-affricates = ( + "all": "tsʰtʃʰʈʂʰtɕʰ", + "english": "", + "spanish": "", + "french": "", + "german": "", + "italian": "", + "japanese": "", + "portuguese": "", + "russian": "", + "arabic": "", +) + +// Helper function to extract braced content {phoneme} from input +// Braced content can be affricates, aspirated consonants, etc. +#let extract-braced-content(input) = { + let braced-items = () + let cleaned = "" + let in-braces = false + let current-item = "" + let rejected = () + + for char in input.clusters() { + if char == "{" { + if in-braces { rejected.push("{") } + in-braces = true + current-item = "" + } else if char == "}" { + if in-braces and current-item != "" { + braced-items.push(current-item) + } else { + rejected.push("}") + } + in-braces = false + current-item = "" + } else if in-braces { + current-item += char + } else { + cleaned += char + } + } + if in-braces { rejected.push("{" + current-item) } + + (braced-items: braced-items, cleaned: cleaned, rejected: rejected) +} + +// Helper function to categorize braced items into affricates, aspirated plosives, and aspirated affricates +#let categorize-braced-items(braced-items) = { + let affricates = () + let aspirated-plosives = () + let aspirated-affricates = () + let rejected = () + + for item in braced-items { + // Convert IPA notation to Unicode (spaces are required for diacritics like \h) + let converted = ipa-to-unicode(item) + + // Check which category this belongs to + if converted in aspirated-affricate-data { + aspirated-affricates.push(converted) + } else if converted in aspirated-plosive-data { + aspirated-plosives.push(converted) + } else if converted in affricate-data { + affricates.push(converted) + } else { + rejected.push("{" + item + "}") + } + } + + ( + affricates: affricates, + aspirated-plosives: aspirated-plosives, + aspirated-affricates: aspirated-affricates, + rejected: rejected, + ) +} + +// Main consonants function +#let consonants( + ..args, // Optional positional consonant-string (read below) — allows `lang`-only calls + lang: none, + ui-lang: "en", + affricates: false, + aspirated: false, + abbreviate: false, + simplify: false, + delete-cols: (), + delete-rows: (), + cell-width: 1.8, + cell-height: 0.9, + label-width: 3.5, + label-height: 1.2, + scale: 0.7, + phoneme-colors: (:), +) = { + // Read the optional positional argument: consonant symbols (tipa-style IPA, + // optionally with braced affricates/aspirated items) or a built-in language + // name. Optional (via the `..args` sink) so that `lang`-only calls like + // `consonants(lang: "spanish")` work — a parameter with a default value would + // be named-only in Typst and could not be passed positionally. + assert(args.pos().len() <= 1, + message: "consonants: expected at most one positional argument (the consonant string)") + assert(args.named().len() == 0, + message: "consonants: unexpected named argument(s): " + args.named().keys().join(", ")) + let consonant-string = args.pos().at(0, default: none) + // An unfinished inventory should stay quiet while the user is typing. + if lang == none and (consonant-string == none or consonant-string.trim() == "") { + return none + } + + // Determine which consonants to plot + let consonants-to-plot = "" + let custom-affricates-string = "" + let custom-aspirated-plosives-string = "" + let custom-aspirated-affricates-string = "" + let error-msg = none + let inventory-error = [ + *Error:* Available presets: #(language-consonants.keys().map(raw).join(", ")). + Use a listed preset or provide only consonant symbols. + ] + let ui-locale = resolve-ui-lang(ui-lang) + + if ui-locale == none { + return ui-lang-error(ui-lang) + } + let labels = ui-consonant-labels(ui-locale) + + // Check if consonant-string is actually a language name + if consonant-string != none and consonant-string in language-consonants { + consonants-to-plot = language-consonants.at(consonant-string) + } else if lang != none { + if lang in language-consonants { + consonants-to-plot = language-consonants.at(lang) + } else { + return inventory-error + } + } else if consonant-string != none and consonant-string != "" { + // Use as manual consonant specification + // Extract braced content first (affricates, aspirated consonants, etc.) + let extracted = extract-braced-content(consonant-string.replace(regex("\\s+"), " ")) + + // Convert IPA notation to Unicode for consonants (excluding braced items) + consonants-to-plot = ipa-to-unicode(extracted.cleaned) + + // Categorize braced items into their respective types + let categorized = categorize-braced-items(extracted.braced-items) + let rejected = (extracted.rejected + categorized.rejected + + consonants-to-plot.clusters().filter(c => c not in consonant-data) + ) + if rejected.dedup().len() > 0 { + return inventory-error + } + if consonants-to-plot == "" and extracted.braced-items.len() == 0 { + return inventory-error + } + // Note: .join("") returns none for empty arrays in Typst, so we check length first + if categorized.affricates.len() > 0 { + custom-affricates-string = categorized.affricates.join("") + } + if categorized.aspirated-plosives.len() > 0 { + custom-aspirated-plosives-string = categorized.aspirated-plosives.join("") + } + if categorized.aspirated-affricates.len() > 0 { + custom-aspirated-affricates-string = categorized.aspirated-affricates.join("") + } + } else { + error-msg = inventory-error + } + + // If there's an error, display it and return + if error-msg != none { + return error-msg + } + + // Determine which affricates to plot (if affricates: true) + let affricates-to-plot = "" + if affricates { + // Check if we used a language name + if consonant-string != none and consonant-string in language-affricates { + affricates-to-plot = language-affricates.at(consonant-string) + } else if lang != none and lang in language-affricates { + affricates-to-plot = language-affricates.at(lang) + } else { + // For custom input, use only the extracted affricates from braces + affricates-to-plot = custom-affricates-string + } + } + + // Determine which aspirated consonants to plot (if aspirated: true) + let aspirated-plosives-to-plot = "" + let aspirated-affricates-to-plot = "" + if aspirated { + // Aspirated plosives + if consonant-string != none and consonant-string in language-aspirated-plosives { + aspirated-plosives-to-plot = language-aspirated-plosives.at(consonant-string) + } else if lang != none and lang in language-aspirated-plosives { + aspirated-plosives-to-plot = language-aspirated-plosives.at(lang) + } else { + // For custom input, use aspirated plosives extracted from braces + aspirated-plosives-to-plot = custom-aspirated-plosives-string + } + + // Aspirated affricates (only if affricates is also true) + if affricates { + if consonant-string != none and consonant-string in language-aspirated-affricates { + aspirated-affricates-to-plot = language-aspirated-affricates.at(consonant-string) + } else if lang != none and lang in language-aspirated-affricates { + aspirated-affricates-to-plot = language-aspirated-affricates.at(lang) + } else { + // For custom input, use aspirated affricates extracted from braces + aspirated-affricates-to-plot = custom-aspirated-affricates-string + } + } + } + + // No affricates to plot (e.g., Portuguese, French): don't add an empty row + let affricates = ( + affricates and (affricates-to-plot.replace("͡", "") != "" or aspirated-affricates-to-plot != "") + ) + + // If simplify is true, auto-delete empty columns and rows + if simplify { + // Collect all used places and manners from the data to plot + let used-places = () + let used-manners = () + + // Scan main consonants + for consonant in consonants-to-plot.clusters() { + if consonant in consonant-data { + let info = consonant-data.at(consonant) + if info.place not in used-places { used-places.push(info.place) } + if info.manner not in used-manners { used-manners.push(info.manner) } + } + } + + // Special /w/ handling: also occupies velar column if /ɰ/ is absent + if consonants-to-plot.contains("w") and not consonants-to-plot.contains("ɰ") { + if 7 not in used-places { used-places.push(7) } + } + + // Scan aspirated plosives + if aspirated { + for asp-plosive in aspirated-plosive-data.keys() { + if aspirated-plosives-to-plot.contains(asp-plosive) { + let info = aspirated-plosive-data.at(asp-plosive) + if info.place not in used-places { used-places.push(info.place) } + // Aspirated plosives have their own inserted row, no base manner to add + } + } + } + + // Scan affricates + if affricates { + let affricates-cleaned = affricates-to-plot.replace("͡", "") + for affricate in affricate-data.keys() { + if affricates-cleaned.contains(affricate) { + let info = affricate-data.at(affricate) + if info.place not in used-places { used-places.push(info.place) } + // Affricates have their own inserted row, no base manner to add + } + } + } + + // Scan aspirated affricates + if aspirated and affricates { + for asp-affricate in aspirated-affricate-data.keys() { + if aspirated-affricates-to-plot.contains(asp-affricate) { + let info = aspirated-affricate-data.at(asp-affricate) + if info.place not in used-places { used-places.push(info.place) } + } + } + } + + // Merge: add unused places/manners to delete lists + for i in range(places.len()) { + if i not in used-places and i not in delete-cols { + delete-cols.push(i) + } + } + for i in range(manners.len()) { + if i not in used-manners and i not in delete-rows { + delete-rows.push(i) + } + } + } + + // Select label sets based on abbreviation setting + let place-labels = if abbreviate { labels.places_short } else { labels.places } + let manner-labels = if abbreviate { labels.manners_short } else { labels.manners } + + // Filter deleted columns and build column remapping + let kept-cols = range(places.len()).filter(i => i not in delete-cols) + let display-places = kept-cols.map(i => place-labels.at(i)) + let col-remap = (:) + for (new-i, orig-i) in kept-cols.enumerate() { + col-remap.insert(str(orig-i), new-i) + } + + // Filter deleted rows and build display-manners with optional row insertions + // Inserted rows are independent of base rows: deleting Plosive or Fricative + // must not drop aspirated plosives or affricates. With simplify, inserted + // rows are shown only if they have content. + let show-aspirated-plosive-row = aspirated and (not simplify or aspirated-plosives-to-plot != "") + let show-affricate-row = affricates and (not simplify or affricates-to-plot.replace("͡", "") != "") + let show-aspirated-affricate-row = ( + aspirated and affricates and (not simplify or aspirated-affricates-to-plot != "") + ) + let display-manners = () + let manner-to-row = (:) + let aspirated-plosive-row = -1 + let affricate-row = -1 + let aspirated-affricate-row = -1 + let current-row = 0 + + for base-row in range(manners.len()) { + if base-row not in delete-rows { + display-manners.push(manner-labels.at(base-row)) + manner-to-row.insert(str(base-row), current-row) + current-row += 1 + } + + // Insert aspirated plosive row after Plosive (base 0) + if base-row == 0 and show-aspirated-plosive-row { + let asp-label = if abbreviate { labels.aspirated_plosive_short } else { labels.aspirated_plosive } + display-manners.push(asp-label) + aspirated-plosive-row = current-row + current-row += 1 + } + + // Insert affricate rows after Fricative (base 4) + if base-row == 4 { + if show-affricate-row { + let affr-label = if abbreviate { labels.affricate_short } else { labels.affricate } + display-manners.push(affr-label) + affricate-row = current-row + current-row += 1 + } + + if show-aspirated-affricate-row { + let asp-affr-label = if abbreviate { labels.aspirated_affricate_short } else { labels.aspirated_affricate } + display-manners.push(asp-affr-label) + aspirated-affricate-row = current-row + current-row += 1 + } + } + } + + // Calculate scaled dimensions + let scaled-cell-width = cell-width * scale + let scaled-cell-height = cell-height * scale + let scaled-label-width = label-width * scale + let scaled-label-height = label-height * scale + let scaled-font-size = 18 * scale + let scaled-label-font-size = 9 * scale + let scaled-circle-radius = 0.3 * scale + let scaled-line-thickness = 0.8 * scale + let resolved-phoneme-colors = (:) + for (phoneme, color) in phoneme-colors { + resolved-phoneme-colors.insert(ipa-to-unicode(phoneme), color) + } + let phoneme-text = phoneme => { + if phoneme in resolved-phoneme-colors { + text(size: scaled-font-size * 1pt, font: phonokit-font.get(), fill: resolved-phoneme-colors.at(phoneme), top-edge: "x-height", bottom-edge: "baseline", phoneme) + } else { + text(size: scaled-font-size * 1pt, font: phonokit-font.get(), top-edge: "x-height", bottom-edge: "baseline", phoneme) + } + } + + let num-cols = display-places.len() + let num-rows = display-manners.len() + + context { + let label-padding = 0.35 * scale + let column-widths = display-places.map(place => calc.max( + scaled-cell-width, + measure(text(size: scaled-label-font-size * 1pt, font: phonokit-font.get(), place)).width / 1cm + label-padding, + )) + let resolved-label-width = calc.max( + scaled-label-width, + calc.max(..display-manners.map(manner => measure(text(size: scaled-label-font-size * 1pt, font: phonokit-font.get(), manner)).width / 1cm)) + 0.35 * scale, + ) + + let col-starts = () + let x-cursor = resolved-label-width + for width in column-widths { + col-starts.push(x-cursor) + x-cursor += width + } + + canvas({ + import draw: * + + // Calculate total dimensions + let total-width = resolved-label-width + column-widths.sum() + let total-height = scaled-label-height + (num-rows * scaled-cell-height) + + // Draw column headers (places of articulation) + for (i, place) in display-places.enumerate() { + let col-width = column-widths.at(i) + let x = col-starts.at(i) + (col-width / 2) + let y = total-height / 2 - (scaled-label-height * 0.65) + + content((x, y), text(size: scaled-label-font-size * 1pt, font: phonokit-font.get(), top-edge: "cap-height", bottom-edge: "baseline", place), anchor: "center") + } + + // Draw row headers (manners of articulation) + for (i, manner) in display-manners.enumerate() { + let x = resolved-label-width - 0.2 + let y = total-height / 2 - scaled-label-height - (i * scaled-cell-height) - (scaled-cell-height / 2) + + content((x, y), text(size: scaled-label-font-size * 1pt, font: phonokit-font.get(), manner), anchor: "east") + } + + // Row indices for grid drawing (from the remapping built above) + let fricative-display-row = manner-to-row.at("4", default: -1) + + // Draw grid lines + // Vertical lines + for i in range(num-cols + 1) { + let x = if i == num-cols { total-width } else { col-starts.at(i) } + let y1 = total-height / 2 - scaled-label-height + let y2 = -total-height / 2 + + // Check if this vertical line is at a dental-alveolar or alveolar-postalveolar boundary + let is-special = false + if i > 0 and i < num-cols { + let left-orig = kept-cols.at(i - 1) + let right-orig = kept-cols.at(i) + if (left-orig == 2 and right-orig == 3) or (left-orig == 3 and right-orig == 4) { + is-special = true + } + } + + if is-special { + // Draw line segments only for fricative and affricate rows + for row in range(num-rows) { + if row == fricative-display-row or row == affricate-row or row == aspirated-affricate-row { + let row-y1 = total-height / 2 - scaled-label-height - (row * scaled-cell-height) + let row-y2 = row-y1 - scaled-cell-height + line((x, row-y1), (x, row-y2), stroke: (paint: gray.lighten(20%), thickness: scaled-line-thickness * 1pt)) + } + } + } else { + // Draw normal full-height vertical line for other columns + line((x, y1), (x, y2), stroke: (paint: gray.lighten(20%), thickness: scaled-line-thickness * 1pt)) + } + } + + // Horizontal lines + for i in range(num-rows + 1) { + let y = total-height / 2 - scaled-label-height - (i * scaled-cell-height) + let x1 = resolved-label-width + let x2 = total-width + line((x1, y), (x2, y), stroke: (paint: gray.lighten(20%), thickness: scaled-line-thickness * 1pt)) + } + + // Gray out impossible consonant cells + // Format: (display-row, orig-col, half) where half can be "full", "voiced", "voiceless" + // Row positions come from manner-to-row mapping + let plosive-row = manner-to-row.at("0", default: -1) + let nasal-row = manner-to-row.at("1", default: -1) + let trill-row = manner-to-row.at("2", default: -1) + let tap-row = manner-to-row.at("3", default: -1) + let lat-fric-row = manner-to-row.at("5", default: -1) + let approx-row = manner-to-row.at("6", default: -1) + let lat-approx-row = manner-to-row.at("7", default: -1) + + let impossible-cells = ( + // Lateral fricative + (lat-fric-row, 0, "full"), + (lat-fric-row, 1, "full"), + (lat-fric-row, 9, "full"), + (lat-fric-row, 10, "full"), + // Lateral approximant + (lat-approx-row, 0, "full"), + (lat-approx-row, 1, "full"), + (lat-approx-row, 9, "full"), + (lat-approx-row, 10, "full"), + // Trill + (trill-row, 7, "full"), + (trill-row, 10, "full"), + // Tap or flap + (tap-row, 7, "full"), + (tap-row, 10, "full"), + // Approximant + (approx-row, 10, "full"), + // Plosive - voiced side only + (plosive-row, 9, "voiced"), + (plosive-row, 10, "voiced"), + // Nasal + (nasal-row, 9, "full"), + (nasal-row, 10, "full"), + ) + + // Add aspirated plosive impossible cells if that row exists + if aspirated-plosive-row >= 0 { + impossible-cells = ( + impossible-cells + + ( + (aspirated-plosive-row, 9, "full"), + (aspirated-plosive-row, 10, "full"), + ) + ) + } + + for (row, orig-col, half) in impossible-cells { + if row < 0 or str(orig-col) not in col-remap { + // Row or column was deleted, skip + } else { + let col = col-remap.at(str(orig-col)) + let col-width = column-widths.at(col) + let cell-x = col-starts.at(col) + let cell-y = total-height / 2 - scaled-label-height - (row * scaled-cell-height) + + if half == "full" { + // Fill entire cell + rect( + (cell-x, cell-y), + (cell-x + col-width, cell-y - scaled-cell-height), + fill: gray.lighten(70%), + stroke: (paint: gray.lighten(20%), thickness: scaled-line-thickness * 1pt), + ) + } else if half == "voiced" { + // Fill right half of cell (voiced side) + let mid-x = cell-x + (col-width / 2) + rect( + (mid-x, cell-y), + (cell-x + col-width, cell-y - scaled-cell-height), + fill: gray.lighten(70%), + stroke: (paint: gray.lighten(20%), thickness: scaled-line-thickness * 1pt), + ) + } else if half == "voiceless" { + // Fill left half of cell (voiceless side) + let mid-x = cell-x + (col-width / 2) + rect( + (cell-x, cell-y), + (mid-x, cell-y - scaled-cell-height), + fill: gray.lighten(70%), + stroke: (paint: gray.lighten(20%), thickness: scaled-line-thickness * 1pt), + ) + } + } + } + + // Collect consonants by cell + let cell-consonants = (:) + for consonant in consonants-to-plot.clusters() { + if consonant in consonant-data { + let info = consonant-data.at(consonant) + let key = str(info.place) + "-" + str(info.manner) + + if key not in cell-consonants { + cell-consonants.insert(key, (voiceless: none, voiced: none)) + } + + if info.voicing { + cell-consonants.at(key).voiced = consonant + } else { + cell-consonants.at(key).voiceless = consonant + } + } + } + + // Special handling for /w/ (labiovelar approximant) + // If /w/ is present but /ɰ/ (velar approximant) is not, show /w/ in both bilabial and velar columns + if consonants-to-plot.contains("w") and not consonants-to-plot.contains("ɰ") { + let velar-approx-key = "7-6" // place 7 (velar), manner 6 (approximant) + if velar-approx-key not in cell-consonants { + cell-consonants.insert(velar-approx-key, (voiceless: none, voiced: none)) + } + cell-consonants.at(velar-approx-key).voiced = "w" + } + + // Collect aspirated plosives if enabled + let cell-aspirated-plosives = (:) + if aspirated { + for asp-plosive in aspirated-plosive-data.keys() { + if aspirated-plosives-to-plot.contains(asp-plosive) { + let info = aspirated-plosive-data.at(asp-plosive) + let key = str(info.place) + + if key not in cell-aspirated-plosives { + cell-aspirated-plosives.insert(key, (voiceless: none, voiced: none)) + } + + // Aspirated plosives are always voiceless + cell-aspirated-plosives.at(key).voiceless = asp-plosive + } + } + } + + // Collect affricates if enabled + let cell-affricates = (:) + if affricates { + // Strip tie bars from input (user might input t͡s, we display as ts) + let affricates-cleaned = affricates-to-plot.replace("͡", "") + + for affricate in affricate-data.keys() { + if affricates-cleaned.contains(affricate) { + let info = affricate-data.at(affricate) + let key = str(info.place) + + if key not in cell-affricates { + cell-affricates.insert(key, (voiceless: none, voiced: none)) + } + + if info.voicing { + cell-affricates.at(key).voiced = affricate + } else { + cell-affricates.at(key).voiceless = affricate + } + } + } + } + + // Collect aspirated affricates if both enabled + let cell-aspirated-affricates = (:) + if aspirated and affricates { + for asp-affricate in aspirated-affricate-data.keys() { + if aspirated-affricates-to-plot.contains(asp-affricate) { + let info = aspirated-affricate-data.at(asp-affricate) + let key = str(info.place) + + if key not in cell-aspirated-affricates { + cell-aspirated-affricates.insert(key, (voiceless: none, voiced: none)) + } + + // Aspirated affricates are always voiceless + cell-aspirated-affricates.at(key).voiceless = asp-affricate + } + } + } + + // Draw consonants in cells + for (key, pair) in cell-consonants { + let parts = key.split("-") + let orig-col = int(parts.at(0)) + let orig-manner = int(parts.at(1)) + + // Skip if column or row was deleted + if str(orig-col) in col-remap and str(orig-manner) in manner-to-row { + let col = col-remap.at(str(orig-col)) + let row = manner-to-row.at(str(orig-manner)) + + let col-width = column-widths.at(col) + let cell-x = col-starts.at(col) + let cell-y = total-height / 2 - scaled-label-height - (row * scaled-cell-height) + let cell-center-x = cell-x + (col-width / 2) + let cell-center-y = cell-y - (scaled-cell-height / 2) + + // Check if this is a sonorant manner (not contrastive for voicing, should be centered) + // Manners: 1=Nasal, 2=Trill, 3=Tap/Flap, 6=Approximant, 7=Lateral Approximant + let is-sonorant = orig-manner in (1, 2, 3, 6, 7) + + if is-sonorant { + // Center the consonant (sonorants are always voiced in typical inventories) + if pair.voiced != none { + let pos = (cell-center-x, cell-center-y) + circle(pos, radius: scaled-circle-radius, fill: white, stroke: none) + content(pos, context phoneme-text(pair.voiced), anchor: "center") + } + // In rare cases where voiceless sonorants exist, also center them + if pair.voiceless != none { + let pos = (cell-center-x, cell-center-y) + circle(pos, radius: scaled-circle-radius, fill: white, stroke: none) + content(pos, context phoneme-text(pair.voiceless), anchor: "center") + } + } else { + // Obstruents: use left/right positioning for voicing contrast + let offset = col-width * 0.25 + + if pair.voiceless != none { + let pos = (cell-center-x - offset, cell-center-y) + circle(pos, radius: scaled-circle-radius, fill: white, stroke: none) + content(pos, context phoneme-text(pair.voiceless), anchor: "center") + } + + if pair.voiced != none { + let pos = (cell-center-x + offset, cell-center-y) + circle(pos, radius: scaled-circle-radius, fill: white, stroke: none) + content(pos, context phoneme-text(pair.voiced), anchor: "center") + } + } + } + } + + // Draw aspirated plosives + if aspirated and aspirated-plosive-row >= 0 { + for (key, pair) in cell-aspirated-plosives { + let orig-col = int(key) + if str(orig-col) in col-remap { + let col = col-remap.at(str(orig-col)) + let row = aspirated-plosive-row + + let col-width = column-widths.at(col) + let cell-x = col-starts.at(col) + let cell-y = total-height / 2 - scaled-label-height - (row * scaled-cell-height) + let cell-center-x = cell-x + (col-width / 2) + let cell-center-y = cell-y - (scaled-cell-height / 2) + + let offset = col-width * 0.25 + + if pair.voiceless != none { + let pos = (cell-center-x - offset, cell-center-y) + circle(pos, radius: scaled-circle-radius, fill: white, stroke: none) + content(pos, context phoneme-text(pair.voiceless), anchor: "center") + } + } + } + } + + // Draw affricates + if affricates and affricate-row >= 0 { + for (key, pair) in cell-affricates { + let orig-col = int(key) + if str(orig-col) in col-remap { + let col = col-remap.at(str(orig-col)) + let row = affricate-row + + let col-width = column-widths.at(col) + let cell-x = col-starts.at(col) + let cell-y = total-height / 2 - scaled-label-height - (row * scaled-cell-height) + let cell-center-x = cell-x + (col-width / 2) + let cell-center-y = cell-y - (scaled-cell-height / 2) + + let offset = col-width * 0.25 + + if pair.voiceless != none { + let pos = (cell-center-x - offset, cell-center-y) + circle(pos, radius: scaled-circle-radius, fill: white, stroke: none) + content(pos, context phoneme-text(pair.voiceless), anchor: "center") + } + + if pair.voiced != none { + let pos = (cell-center-x + offset, cell-center-y) + circle(pos, radius: scaled-circle-radius, fill: white, stroke: none) + content(pos, context phoneme-text(pair.voiced), anchor: "center") + } + } + } + } + + // Draw aspirated affricates + if aspirated and affricates and aspirated-affricate-row >= 0 { + for (key, pair) in cell-aspirated-affricates { + let orig-col = int(key) + if str(orig-col) in col-remap { + let col = col-remap.at(str(orig-col)) + let row = aspirated-affricate-row + + let col-width = column-widths.at(col) + let cell-x = col-starts.at(col) + let cell-y = total-height / 2 - scaled-label-height - (row * scaled-cell-height) + let cell-center-x = cell-x + (col-width / 2) + let cell-center-y = cell-y - (scaled-cell-height / 2) + + let offset = col-width * 0.25 + + if pair.voiceless != none { + let pos = (cell-center-x - offset, cell-center-y) + circle(pos, radius: scaled-circle-radius, fill: white, stroke: none) + content(pos, context phoneme-text(pair.voiceless), anchor: "center") + } + } + } + } + }) + } +} diff --git a/packages/preview/phonokit/0.6.1/ex.typ b/packages/preview/phonokit/0.6.1/ex.typ new file mode 100644 index 0000000000..eefddd5d06 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/ex.typ @@ -0,0 +1,408 @@ +// Linguistic example environment with automatic numbering +// Similar to linguex's \ex. command in LaTeX +// +// Create a numbered linguistic example +// +// Generates numbered examples (1), (2), etc. similar to linguex in LaTeX. +// +// Arguments: +// - body (content): The example content +// - number-dy (length): Vertical offset for the number (optional; default: 0.4em) +// - caption (string): Caption for outline (hidden in document; optional) +// - title (content): Optional title shown on the same line as the number +// - labels (array): Optional labels for sub-examples in list mode (e.g., (, )) +// - columns (array): Optional column widths for tabular cells (data columns only) +// +// Returns: Numbered example that can be labeled and referenced +// +// Smart modes — detected automatically from body content: +// +// Single item (auto-numbered): +// #ex[Some example content] +// +// Single item with tabular cells (& separator): +// #ex[#ipa("/anba/") & #a-r & #ipa("[amba]")] +// +// Sub-examples with list syntax (auto-lettered): +// #ex[ +// - First example +// - Second example +// ] +// +// Sub-examples with tabular cells (& separator): +// #ex(labels: (, ), columns: (5em, 2em, 5em))[ +// - #ipa("/anba/") & #a-r & #ipa("[amba]") +// - #ipa("/anka/") & #a-r & #ipa("[aNka]") +// ] +// +// Legacy table mode (when body is a table — backward compatible): +// #ex()[ +// #table( +// columns: (2em, 2em, 5em, 2em, 5em), +// stroke: none, align: left, +// [#ex-num-label()], [#subex-label()], [#ipa("/anba/")], [#a-r], [#ipa("[amba]")], +// [], [#subex-label()], [#ipa("/anka/")], [#a-r], [#ipa("[aNka]")], +// ) +// ] + +// Counters +#let example-counter = counter("linguistic-example") +#let subex-counter = counter("linguistic-subexample") + +// Alphabet for sub-example lettering (a, b, c...) +#let letters = "abcdefghijklmnopqrstuvwxyz" + +// Split content at & characters into an array of cell contents. +// Walks the content tree, accumulating children into cells. +// Trims leading/trailing space nodes from each cell. +#let _split-cells(body) = { + let children = if body.has("children") { body.children } else { (body,) } + let cells = () + let current = () + for child in children { + if child.has("text") and child.text.contains("&") { + // Found separator — finalize current cell + cells.push(current) + current = () + } else { + current.push(child) + } + } + cells.push(current) // last cell + // Trim leading/trailing spaces from each cell and join into content + cells.map(cell => { + let trimmed = cell + // Trim leading spaces + while trimmed.len() > 0 and trimmed.first().func() == [ ].func() and trimmed.first().has("text") and trimmed.first().text.trim() == "" { + trimmed = trimmed.slice(1) + } + // Trim trailing spaces + while trimmed.len() > 0 and trimmed.last().func() == [ ].func() and trimmed.last().has("text") and trimmed.last().text.trim() == "" { + trimmed = trimmed.slice(0, trimmed.len() - 1) + } + trimmed.join() + }) +} + +// Check if content contains an & text node +#let _has-ampersand(body) = { + let children = if body.has("children") { body.children } else { (body,) } + children.any(c => c.has("text") and c.text.contains("&")) +} + +// Classify body content to determine rendering mode +// Typst's `- item` syntax creates list.item elements in a sequence (not wrapped in a list) +#let _classify-body(body) = { + if body.func() == list.item { return "list" } + if body.func() == table { return "legacy" } + if body.has("children") { + for child in body.children { + if child.func() == list.item { return "list" } + if child.func() == table { return "legacy" } + } + } + "single" +} + +// Extract list items from body (may be nested in a sequence) +#let _extract-items(body) = { + if body.func() == list.item { return (body,) } + if body.has("children") { + return body.children.filter(c => c.func() == list.item) + } + () +} + +// Build a grid from list items with auto numbering and lettering +// Supports & cell separator for tabular data +#let _build-subex-grid(items, labels, columns, numbered: true) = { + // Check if any item has tabular cells + let has-tabular = items.any(item => _has-ampersand(item.body)) + + let cells = () + let n-data-cols = 1 // default: single content column + + for (i, item) in items.enumerate() { + // Column 1: example number on first row only (skip when title handles numbering) + if numbered { + let num-cell = if i == 0 { + context { + subex-counter.update(0) + example-counter.step() + [(#(example-counter.get().first() + 1))] + } + } else { [] } + cells.push(num-cell) + } + + // Column 2: sub-example letter (a., b., c.) + let letter-fig = figure( + box(baseline: 0pt, context { + set par(first-line-indent: 0em) + subex-counter.step() + let n = subex-counter.get().first() + [#letters.at(n).] + }), + kind: "linguistic-subexample", + supplement: none, + numbering: none, + ) + // Attach label if provided (label must immediately follow figure, no whitespace) + let letter-cell = if labels != () and i < labels.len() { + [#letter-fig#labels.at(i)] + } else { + letter-fig + } + + cells.push(letter-cell) + + if has-tabular { + let data-cells = _split-cells(item.body) + n-data-cols = calc.max(n-data-cols, data-cells.len()) + for cell in data-cells { + cells.push(cell) + } + // Pad with empty cells if this row has fewer columns + let pad = n-data-cols - data-cells.len() + for _ in range(pad) { cells.push([]) } + } else { + cells.push(item.body) + } + } + + // Build column spec + let data-col-spec = if columns != () { + columns + } else if has-tabular { + range(n-data-cols).map(_ => auto) + } else { + (1fr,) + } + + let col-spec = if numbered { + (2em, 2em, ..data-col-spec) + } else { + (2em, ..data-col-spec) + } + + grid( + columns: col-spec, + row-gutter: 1em, + column-gutter: 0.5em, + align: left + bottom, + ..cells, + ) +} + +// Main example function +#let ex( + number-dy: 0.4em, + caption: none, + title: none, + labels: (), + columns: (), + body, +) = { + // Build the smart body content (list, single, or legacy) + // numbered: false when title branch handles the example number + let _build-smart-body(body, labels, columns, numbered: true) = { + let mode = _classify-body(body) + if mode == "list" { + let items = _extract-items(body) + _build-subex-grid(items, labels, columns, numbered: numbered) + } else if mode == "single" and _has-ampersand(body) { + let data-cells = _split-cells(body) + let data-col-spec = if columns != () { columns } else { + range(data-cells.len()).map(_ => auto) + } + grid( + columns: data-col-spec, + column-gutter: 0.5em, + align: left + bottom, + ..data-cells, + ) + } else { + body + } + } + + let content = if title != none { + // Title case: (num | title) / ([] | smart body) + let num = context { + subex-counter.update(0) + example-counter.step() + [(#(example-counter.get().first() + 1))] + } + let smart-body = _build-smart-body(body, labels, columns, numbered: false) + grid( + columns: (auto, 1fr), + column-gutter: 0.75em, + row-gutter: 0.3em, + align: (left + top, left + top), + num, title, + [], smart-body, + ) + } else { + let mode = _classify-body(body) + if mode == "list" { + // List mode: auto number + auto letter sub-examples (with optional & cells) + let items = _extract-items(body) + _build-subex-grid(items, labels, columns) + } else if mode == "single" { + // Single-item mode: auto number to the left + let num = context { + subex-counter.update(0) + example-counter.step() + [(#(example-counter.get().first() + 1))] + } + // Check for tabular cells in single-item mode + if _has-ampersand(body) { + let data-cells = _split-cells(body) + let data-col-spec = if columns != () { columns } else { + range(data-cells.len()).map(_ => auto) + } + grid( + columns: (2em, ..data-col-spec), + column-gutter: 0.5em, + align: left + bottom, + num, ..data-cells, + ) + } else { + grid( + columns: (auto, 1fr), + column-gutter: 0.75em, + align: (left + bottom, left + bottom), + num, body, + ) + } + } else { + // Legacy table mode: user manages numbering via ex-num-label() / subex-label() + let step = context { + subex-counter.update(0) + example-counter.step() + [] + } + grid( + columns: (auto, 1fr), + column-gutter: 0pt, + align: (left + top, left + top), + step, body, + ) + } + } + figure( + align(left, content), + caption: if caption != none { caption } else { none }, + outlined: caption != none, + kind: "linguistic-example", + supplement: none, + numbering: "(1)", + placement: none, + gap: 0pt, + ) +} + +// Display the current example number inside an ex() body. +// +// Use as the first-column cell of a 3-column table (num | sub-label | content) +// when no title is provided. Because it lives in the same table, it can share +// bottom alignment with the sub-example labels and the sentence text. +// +// Example: +// #ex(caption: "Example")[ +// #table( +// columns: 3, +// stroke: none, +// align: (left + bottom, left + bottom, left + top), +// [#ex-num-label()], [#subex-label()], [sentence a], +// [], [#subex-label()], [sentence b], +// ) +// ] +#let ex-num-label() = { + // No figure wrapper — a plain box behaves consistently with subex-label() + // in table cells without requiring explicit bottom alignment. + box(baseline: 0pt, context { + set par(first-line-indent: 0em) + let n = example-counter.get().first() + [(#n)] + }) +} + +// Create a sub-example label for use in tables +// +// Generates automatic lettering (a., b., c., ...) for table rows. +// Place in the first column of each row and attach a label after it. +// +// Returns: Labelable letter marker (a., b., c., ...) +// +// Example: +// #ex(caption: "A phonology example")[ +// #table( +// columns: 4, +// stroke: none, +// align: left, +// [#subex-label()], [#ipa("/anba/")], [#a-r], [#ipa("[amba]")], +// [#subex-label()], [#ipa("/anka/")], [#a-r], [#ipa("[aNka]")], +// ) +// ] +// +// See @ex-phon2, @ex-anba, and @ex-anka. +#let subex-label() = { + // Figure must be outermost so labels attach to it (not to context) + // Box with baseline ensures proper vertical alignment in table cells + // Reset first-line-indent to avoid misalignment in documents with paragraph indentation + figure( + box(baseline: 0pt, context { + set par(first-line-indent: 0em) + subex-counter.step() + let n = subex-counter.get().first() + // get() returns value BEFORE step, so n=0,1,2... gives a,b,c... + [#letters.at(n).] + }), + kind: "linguistic-subexample", + supplement: none, + numbering: none, + ) +} + +// Show rules for linguistic examples +// +// Apply this to enable proper reference formatting for ex() and subex-label(). +// References render as (1), (1a), (1b), etc. +// +// Usage: #show: ex-rules +#let ex-rules(doc) = { + show ref: it => { + let el = it.element + if el != none and el.func() == figure { + if el.kind == "linguistic-example" { + // Reference to main example: (1) + // at() returns value before step, so add 1 + link(el.location(), context { + let loc = el.location() + let num = example-counter.at(loc).first() + 1 + [(#num)] + }) + } else if el.kind == "linguistic-subexample" { + // Reference to sub-example: (1a) + // Subex is inside parent ex, so example-counter already stepped (no +1) + // Subex counter: at() returns value before step (0,1,2...) + link(el.location(), context { + let loc = el.location() + let parent-num = example-counter.at(loc).first() + let letter-num = subex-counter.at(loc).first() + let letter = letters.at(letter-num) + [(#parent-num#letter)] + }) + } else { + it + } + } else { + it + } + } + // Hide captions in document (they still appear in outline) + show figure.where(kind: "linguistic-example"): it => it.body + show figure.where(kind: "linguistic-subexample"): it => it.body + doc +} diff --git a/packages/preview/phonokit/0.6.1/extras.typ b/packages/preview/phonokit/0.6.1/extras.typ new file mode 100644 index 0000000000..bd2e0a88e8 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/extras.typ @@ -0,0 +1,39 @@ + +// NOTE: -- A collection of arrows +#let a-r-large = text(font: "New Computer Modern", size: 1.5em)[#h(1em)#sym.arrow.r#h(1em)] +#let a-r = text(font: "New Computer Modern", size: 1em)[#sym.arrow.r] +#let a-l = text(font: "New Computer Modern")[#sym.arrow.l] +#let a-u = text(font: "New Computer Modern")[#sym.arrow.t] +#let a-d = text(font: "New Computer Modern")[#sym.arrow.b] +#let a-ud = text(font: "New Computer Modern")[#sym.arrow.t.b] +#let a-lr = text(font: "New Computer Modern")[#sym.arrow.l.r] +#let a-sr = text(font: "New Computer Modern")[#sym.arrow.r.squiggly] +#let a-sl = text(font: "New Computer Modern")[#sym.arrow.l.squiggly] + +// NOTE: -- Function for context underline +#let blank(width: 2em) = box( + width: width, + height: 0.8em, + baseline: 50%, + stroke: (bottom: 0.5pt + black), +) + +// NOTE: -- Greek symbols (upright, for phonological notation) +#let alpha = sym.alpha +#let beta = sym.beta +#let gamma = sym.gamma +#let delta = sym.delta +#let lambda = sym.lambda +#let mu = sym.mu +#let phi = sym.phi +#let pi = sym.pi +#let sigma = sym.sigma +#let tau = sym.tau +#let omega = sym.omega +#let cap-phi = sym.Phi +#let cap-sigma = sym.Sigma +#let cap-omega = sym.Omega + +// NOTE: Extrametricality +#let extra(content) = [⟨#content⟩] + diff --git a/packages/preview/phonokit/0.6.1/features.typ b/packages/preview/phonokit/0.6.1/features.typ new file mode 100644 index 0000000000..d1064d2640 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/features.typ @@ -0,0 +1,2140 @@ +// Features module - Distinctive feature matrices from Hayes (2009) +// Based on Hayes, B. (2009). Introductory Phonology. Wiley-Blackwell. + +#import "ipa.typ": ipa-to-unicode +#import "_config.typ": phonokit-font +#import "ui-lang.typ": resolve-ui-lang, ui-lang-error, ui-feature-label + +// Function for feature matrices in SPE notation +/// Display feature matrix in SPE-style notation +/// +/// Creates a bracketed vertical list of features using math.vec. +/// Handles both individual arguments and comma-separated strings. +/// +/// Arguments: +/// - args (variadic): Feature specifications (e.g., "+consonantal", "-sonorant") +/// Can be passed as separate arguments or as a single comma-separated string +/// +/// Returns: Formatted feature matrix with proper spacing to prevent overlaps +/// +/// Example: +/// ``` +/// #feat("+consonantal", "-sonorant", "+voice") +/// #feat("+cons,-son,+voice") // comma-separated also works +/// ``` +#let feat(..args) = context { + let items = args.pos() + + // 1. Split string if comma-separated + if items.len() == 1 and type(items.at(0)) == str and items.at(0).contains(",") { + items = items.at(0).split(",") + } + + // 2. Style the items + let features = items.map(i => { + let content = if type(i) == str { i.trim() } else { i } + text(font: phonokit-font.get(), size: 1em, content) + }) + + // 3. Use math.vec for perfect axis alignment + set math.vec(gap: 0.5em) + let matrix = math.vec(delim: "[", ..features) + + // 4. Center the matrix on the math axis of the surrounding text (0.25em + // above the baseline, where arrows and slashes sit). box.baseline is not + // honored when the body is math content (the math baseline wins), so the + // matrix is wrapped in a stack first, which gives the box plain block + // content and makes the relative baseline behave. + box( + baseline: 50% - 0.25em, + inset: (top: 0.5em, bottom: 0.5em), + stack(matrix), + ) +} + +// Complete feature specifications for consonants and vowels +// Features: +, -, or 0 (not applicable/unspecified) +#let feature-data = ( + // CONSONANTS - Single place of articulation (Table 4.7) + // Bilabial + "p": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "b": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ɸ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "β": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "m": ( + consonantal: "+", + sonorant: "+", + continuant: "–", + delayed_release: "0", + approximant: "–", + tap: "–", + trill: "–", + nasal: "+", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ʙ": ( + consonantal: "+", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "+", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + // Labiodental + "f": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "–", + labiodental: "+", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "v": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "–", + labiodental: "+", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ɱ": ( + consonantal: "+", + sonorant: "+", + continuant: "–", + delayed_release: "0", + approximant: "–", + tap: "–", + trill: "–", + nasal: "+", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "–", + labiodental: "+", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ʋ": ( + consonantal: "–", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "–", + labiodental: "+", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + // Dental + "θ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "+", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ð": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "+", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + // Alveolar + "t": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "d": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "t͡s": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "+", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "d͡z": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "+", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "s": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "+", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "z": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "+", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "n": ( + consonantal: "+", + sonorant: "+", + continuant: "–", + delayed_release: "0", + approximant: "–", + tap: "–", + trill: "–", + nasal: "+", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "l": ( + consonantal: "+", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "–", + lateral: "+", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ɾ": ( + consonantal: "+", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "+", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "r": ( + consonantal: "+", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "+", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + // Postalveolar + "ʃ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "+", + strident: "+", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ʒ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "+", + strident: "+", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "t͡ʃ": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "+", + strident: "+", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "d͡ʒ": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "+", + strident: "+", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + // Retroflex + "ʈ": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ɖ": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ʂ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "–", + strident: "+", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ʐ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "–", + strident: "+", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ɳ": ( + consonantal: "+", + sonorant: "+", + continuant: "–", + delayed_release: "0", + approximant: "–", + tap: "–", + trill: "–", + nasal: "+", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ɭ": ( + consonantal: "+", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "–", + strident: "–", + lateral: "+", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ɽ": ( + consonantal: "+", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "+", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ɻ": ( + consonantal: "+", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "–", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + // Palatal + "c": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "+", + back: "–", + tense: "0", + ), + "ɟ": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "+", + back: "–", + tense: "0", + ), + "ç": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "+", + back: "–", + tense: "0", + ), + "ʝ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "+", + back: "–", + tense: "0", + ), + "ɲ": ( + consonantal: "+", + sonorant: "+", + continuant: "–", + delayed_release: "0", + approximant: "–", + tap: "–", + trill: "–", + nasal: "+", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "+", + back: "–", + tense: "0", + ), + "ʎ": ( + consonantal: "+", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "+", + dorsal: "+", + high: "+", + low: "–", + front: "+", + back: "–", + tense: "0", + ), + "j": ( + consonantal: "–", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "+", + back: "–", + tense: "+", + ), + // Velar + "k": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "–", + back: "0", + tense: "0", + ), + "ɡ": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "–", + back: "0", + tense: "0", + ), + "x": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "–", + back: "0", + tense: "0", + ), + "ɣ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "–", + back: "0", + tense: "0", + ), + "ŋ": ( + consonantal: "+", + sonorant: "+", + continuant: "–", + delayed_release: "0", + approximant: "–", + tap: "–", + trill: "–", + nasal: "+", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "–", + back: "0", + tense: "0", + ), + "ɰ": ( + consonantal: "–", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "–", + back: "0", + tense: "0", + ), + // Uvular + "q": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "–", + low: "–", + front: "–", + back: "+", + tense: "0", + ), + "ɢ": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "–", + low: "–", + front: "–", + back: "+", + tense: "0", + ), + "χ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "–", + low: "–", + front: "–", + back: "+", + tense: "0", + ), + "ʁ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "–", + low: "–", + front: "–", + back: "+", + tense: "0", + ), + "ɴ": ( + consonantal: "+", + sonorant: "+", + continuant: "–", + delayed_release: "0", + approximant: "–", + tap: "–", + trill: "–", + nasal: "+", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "–", + low: "–", + front: "–", + back: "+", + tense: "0", + ), + "ʀ": ( + consonantal: "+", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "+", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "–", + low: "–", + front: "–", + back: "+", + tense: "0", + ), + // Pharyngeal + "ħ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "–", + low: "+", + front: "–", + back: "+", + tense: "0", + ), + "ʕ": ( + consonantal: "+", + sonorant: "–", + continuant: "+", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "–", + low: "+", + front: "–", + back: "+", + tense: "0", + ), + // Glottal + "ʔ": ( + consonantal: "+", + sonorant: "–", + continuant: "–", + delayed_release: "–", + approximant: "–", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "–", + constr_gl: "+", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "h": ( + consonantal: "–", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "–", + spread_gl: "+", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + "ɦ": ( + consonantal: "–", + sonorant: "–", + continuant: "+", + delayed_release: "+", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "+", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "–", + high: "0", + low: "0", + front: "0", + back: "0", + tense: "0", + ), + // Complex segments (Table 4.8) + "w": ( + consonantal: "–", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "+", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "–", + back: "+", + tense: "+", + ), + "ɥ": ( + consonantal: "–", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "+", + round: "+", + labiodental: "–", + coronal: "–", + anterior: "0", + distributed: "0", + strident: "0", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "+", + back: "–", + tense: "+", + ), + "ɹ": ( + consonantal: "+", + sonorant: "+", + continuant: "+", + delayed_release: "0", + approximant: "+", + tap: "–", + trill: "–", + nasal: "–", + voice: "+", + spread_gl: "–", + constr_gl: "–", + labial: "–", + round: "–", + labiodental: "–", + coronal: "+", + anterior: "+", + distributed: "–", + strident: "–", + lateral: "–", + dorsal: "+", + high: "+", + low: "–", + front: "+", + back: "–", + tense: "0", + ), + // VOWELS (Table 4.9) + // High tense + "i": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "+", + low: "–", + tense: "+", + front: "+", + back: "–", + round: "–", + ), + "y": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "+", + low: "–", + tense: "+", + front: "+", + back: "–", + round: "+", + ), + "ɨ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "+", + low: "–", + tense: "+", + front: "–", + back: "–", + round: "–", + ), + "ʉ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "+", + low: "–", + tense: "+", + front: "–", + back: "–", + round: "+", + ), + "ɯ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "+", + low: "–", + tense: "+", + front: "–", + back: "+", + round: "–", + ), + "u": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "+", + low: "–", + tense: "+", + front: "–", + back: "+", + round: "+", + ), + // High lax + "ɪ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "+", + low: "–", + tense: "–", + front: "+", + back: "–", + round: "–", + ), + "ʏ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "+", + low: "–", + tense: "–", + front: "+", + back: "–", + round: "+", + ), + "ʊ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "+", + low: "–", + tense: "–", + front: "–", + back: "+", + round: "+", + ), + // Mid tense + "e": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "+", + front: "+", + back: "–", + round: "–", + ), + "ø": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "+", + front: "+", + back: "–", + round: "+", + ), + "ɘ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "+", + front: "–", + back: "–", + round: "–", + ), + "ɵ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "+", + front: "–", + back: "–", + round: "+", + ), + "ɤ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "+", + front: "–", + back: "+", + round: "–", + ), + "o": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "+", + front: "–", + back: "+", + round: "+", + ), + // Mid lax + "ɛ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "–", + front: "+", + back: "–", + round: "–", + ), + "œ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "–", + front: "+", + back: "–", + round: "+", + ), + "ə": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "–", + front: "–", + back: "–", + round: "–", + ), + "ɞ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "–", + front: "–", + back: "–", + round: "+", + ), + "ʌ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "–", + front: "–", + back: "+", + round: "–", + ), + "ɔ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "–", + tense: "–", + front: "–", + back: "+", + round: "+", + ), + // Low + "æ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "+", + tense: "0", + front: "+", + back: "–", + round: "–", + ), + "ɶ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "+", + tense: "0", + front: "+", + back: "–", + round: "+", + ), + "a": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "+", + tense: "0", + front: "–", + back: "–", + round: "–", + ), + "ɑ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "+", + tense: "0", + front: "–", + back: "+", + round: "–", + ), + "ɒ": ( + syllabic: "+", + consonantal: "–", + sonorant: "+", + continuant: "+", + voice: "+", + high: "–", + low: "+", + tense: "0", + front: "–", + back: "+", + round: "+", + ), +) + +// Feature matrix display function +/// Display complete feature matrix for an IPA segment +/// +/// Takes an IPA symbol (Unicode or tipa-style) and displays its complete +/// distinctive feature specification from Hayes (2009). +/// +/// Arguments: +/// - segment (string): IPA symbol (e.g., "p", "i", "\\t t s" for t͡s) +/// - all (bool): Show all features including 0 values (default: false) +/// +/// Returns: Formatted feature matrix in SPE-style notation +/// +/// Example: +/// ``` +/// #feat-matrix("p") +/// #feat-matrix("\\t t s") // affricate using tipa notation +/// #feat-matrix("i", all: true) // show all features including 0 +/// ``` +#let feat-matrix(segment, all: false, ui-lang: "en") = context { + let ui-locale = resolve-ui-lang(ui-lang) + if ui-locale == none { + return ui-lang-error(ui-lang) + } + + // Convert tipa notation to Unicode if needed + let symbol = ipa-to-unicode(segment).trim() + + // Look up features + if symbol not in feature-data { + return text(fill: red)[*Error:* No feature data for "#symbol"] + } + + let features = feature-data.at(symbol) + let feature-list = () + + // Build feature list with readable names + for (feature-name, value) in features { + if all or value != "0" { + let display-name = ui-feature-label(feature-name, ui-locale) + feature-list.push(value + display-name) + } + } + + // Display as inline block with top alignment to allow side-by-side placement + let phoneme = text(size: 1em, font: phonokit-font.get())[/#symbol/] + + // Build matrix manually for precise control over alignment + let features = feature-list.map(f => text(font: phonokit-font.get(), size: 0.8em)[#f]) + let content-stack = stack( + dir: ttb, + spacing: 0.55em, + ..features, + ) + + // Wrap in brackets with precise positioning + // Add horizontal padding inside brackets for better spacing + let matrix = box[ + $lr( + [#h(0.15em)#content-stack#h(0.15em)], + size: #100% + )$ + ] + + // Use box with baseline at bottom (100%) for top alignment + // Counter-intuitively, this makes the tops align + box(baseline: 100%)[ + #grid( + columns: 1, + row-gutter: 1.5em, + // Phoneme in large fixed box, anchored at bottom for consistent matrix starting point + // This ensures all matrices start at exactly the same vertical position + align(center, box(height: 2em, align(bottom, phoneme))), + // Feature matrix starts at fixed position below + align(center, matrix), + ) + ] +} diff --git a/packages/preview/phonokit/0.6.1/gallery/autoseg_example_1.png b/packages/preview/phonokit/0.6.1/gallery/autoseg_example_1.png new file mode 100644 index 0000000000000000000000000000000000000000..9d9ab51a5d9b2f8a56c68be0a1a6c2bc756044d0 GIT binary patch literal 5687 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 LrCM2Hp)9!a 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�A_EQmL^8s849ES{u)GIb7)p)qq9UA*26W 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

J@8f~UX^F~zjyr*;F~!WCb=TA*aBC7 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 z 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

Fqm{(|tPymU`Y-1=;y5);H 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 XP{Z~_`NM8{s-4g!V^r-0*M$E9@XqG9 literal 0 HcmV?d00001 diff --git a/packages/preview/phonokit/0.6.1/gallery/feat_geom.typ b/packages/preview/phonokit/0.6.1/gallery/feat_geom.typ new file mode 100644 index 0000000000..2b06484f7a --- /dev/null +++ b/packages/preview/phonokit/0.6.1/gallery/feat_geom.typ @@ -0,0 +1,9 @@ +#import "@preview/phonokit:0.6.1": * +#set page(height: auto, width: auto, margin: (bottom: 1em, top: 1em, x: 1em)) + +#geom-group( + (ph: "a"), + (ph: "n"), + arrows: ((from: "nasal2", to: "root1", ctrl: (1.1, -1.5)),), + curved: true, +) diff --git a/packages/preview/phonokit/0.6.1/gallery/features_example.png b/packages/preview/phonokit/0.6.1/gallery/features_example.png new file mode 100644 index 0000000000000000000000000000000000000000..5dceb415d9e8507a53382c452202d3b892a2c38f GIT binary patch literal 127933 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 F{9l6P64(F$ literal 0 HcmV?d00001 diff --git a/packages/preview/phonokit/0.6.1/gallery/features_example.typ b/packages/preview/phonokit/0.6.1/gallery/features_example.typ new file mode 100644 index 0000000000..57bf68efbd --- /dev/null +++ b/packages/preview/phonokit/0.6.1/gallery/features_example.typ @@ -0,0 +1,5 @@ +#import "@preview/phonokit:0.6.1": * +#set page(height: auto, width: auto, margin: (bottom: 1em, top: 1em, x: 1em)) + +#feat-matrix("p") #feat-matrix("\\ae") #feat-matrix("\\t tS") #feat-matrix("i") + diff --git a/packages/preview/phonokit/0.6.1/gallery/grid_example.png b/packages/preview/phonokit/0.6.1/gallery/grid_example.png new file mode 100644 index 0000000000000000000000000000000000000000..f92213e1f9d6edcb9b4cdab89b7abe14f3d50a3a GIT binary patch literal 5493 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 BF4q76 literal 0 HcmV?d00001 diff --git a/packages/preview/phonokit/0.6.1/gallery/grid_example.typ b/packages/preview/phonokit/0.6.1/gallery/grid_example.typ new file mode 100644 index 0000000000..0664bef5e2 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/gallery/grid_example.typ @@ -0,0 +1,4 @@ +#import "@preview/phonokit:0.6.1": * +#set page(height: auto, width: auto, margin: (bottom: 1em, top: 1em, x: 1em)) + +#met-grid(("b2", 3), ("R \\schwar", 1), ("flaI", 2)) diff --git a/packages/preview/phonokit/0.6.1/gallery/maxent_example.png b/packages/preview/phonokit/0.6.1/gallery/maxent_example.png new file mode 100644 index 0000000000000000000000000000000000000000..d902c9fe417214ae941bfa2652bf907db8917195 GIT binary patch literal 132255 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 AZZ>O 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

&whUpZ!bny!y?Juy8#d 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 waMEFhx;O+62&hcz>_ZVPP*l-K?0N&H-GXMYp literal 0 HcmV?d00001 diff --git a/packages/preview/phonokit/0.6.1/gallery/maxent_example.typ b/packages/preview/phonokit/0.6.1/gallery/maxent_example.typ new file mode 100644 index 0000000000..f8f7c391d8 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/gallery/maxent_example.typ @@ -0,0 +1,41 @@ +#import "@preview/phonokit:0.6.1": * +#set page(height: auto, width: auto, margin: (bottom: 1em, top: 1em, left: 1em, right: 2cm)) + +#maxent( + input: "kraTa", + candidates: ("[kra.Ta]", "[ka.Ta]", "[ka.ra.Ta]"), + constraints: ("Max", "Dep", "*Complex"), + weights: (2.5, 1.8, 1), + violations: ( + (0, 0, 1), + (1, 0, 0), + (0, 1, 0), + ), + visualize: true, // Show probability bars (default) +) + +#maxent( + input: "kraTa", + candidates: ("[kra.Ta]", "[ka.Ta]", "[ka.ra.Ta]"), + constraints: ("Max", "Dep", "*Complex"), + weights: (2.5, 1.8, 1), + violations: ( + (0, 0, 1), + (1, 0, 0), + (0, 1, 0), + ), + visualize: false, +) + +#maxent( + input: "kraTa", + candidates: ("[kra.Ta]", "[ka.Ta]", "[ka.ra.Ta]"), + constraints: ("Max", "Dep", "*Complex"), + weights: (2.5, 1.8, 1), + violations: ( + (0, 0, 1), + (1, 0, 0), + (0, 1, 0), + ), + sort: true, // Sort candidates by probability (most to least) +) diff --git a/packages/preview/phonokit/0.6.1/gallery/multi-tier_example.png b/packages/preview/phonokit/0.6.1/gallery/multi-tier_example.png new file mode 100644 index 0000000000000000000000000000000000000000..7f242b6e6f3cc346ea6c69760e409e3505dfb82a GIT binary patch literal 18041 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 literal 0 HcmV?d00001 diff --git a/packages/preview/phonokit/0.6.1/gallery/multi-tier_example.typ b/packages/preview/phonokit/0.6.1/gallery/multi-tier_example.typ new file mode 100644 index 0000000000..bf16ade284 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/gallery/multi-tier_example.typ @@ -0,0 +1,18 @@ +#import "@preview/phonokit:0.6.1": * +#set page(height: auto, width: auto, margin: (bottom: 1em, top: 1em, x: 1em)) + +#multi-tier( + levels: ( + ("O", "R", "", "O", "R", "O", "R"), + ("", "N1", "", "", "N2", "", "N3"), + ("", "x", "x", "x", "x", "x", "x"), + ("", "", "s", "t", "E", "m", ""), + ), + links: (("r2", "x2"),), + ipa: (3,), + arrows: (("t1", "s1"), ("r2", "r1")), + arrow-delinks: (1,), + spacing: 1, +) + +// Figure from Goad (2012); see phonokit.pdf for full reference. diff --git a/packages/preview/phonokit/0.6.1/gallery/ot_example.png b/packages/preview/phonokit/0.6.1/gallery/ot_example.png new file mode 100644 index 0000000000000000000000000000000000000000..ee608077d9eb8dc4f013803a188e18987e51a95d GIT binary patch literal 23071 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 E0cc=6&j0`b literal 0 HcmV?d00001 diff --git a/packages/preview/phonokit/0.6.1/gallery/ot_example.typ b/packages/preview/phonokit/0.6.1/gallery/ot_example.typ new file mode 100644 index 0000000000..d95e6d2c43 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/gallery/ot_example.typ @@ -0,0 +1,17 @@ +#import "@preview/phonokit:0.6.1": * +#set page(height: auto, width: auto, margin: (bottom: 1em, top: 1em, x: 1em)) + + +#tableau( + input: "kraTa", + candidates: ("[kra.Ta]", "[ka.Ta]", "[ka.ra.Ta]"), + constraints: ("Max", "Dep", "*Complex"), + violations: ( + ("", "", "*"), + ("*!", "", ""), + ("", "*!", ""), + ), + winner: 0, + dashed-lines: (0,), +) + diff --git a/packages/preview/phonokit/0.6.1/gallery/syllable_example.png b/packages/preview/phonokit/0.6.1/gallery/syllable_example.png new file mode 100644 index 0000000000000000000000000000000000000000..30d358c7c921b211c18742bb594b92da01aba302 GIT binary patch literal 9388 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 literal 0 HcmV?d00001 diff --git a/packages/preview/phonokit/0.6.1/gallery/vowels3d_example.typ b/packages/preview/phonokit/0.6.1/gallery/vowels3d_example.typ new file mode 100644 index 0000000000..9a4d3dde00 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/gallery/vowels3d_example.typ @@ -0,0 +1,4 @@ +#import "@preview/phonokit:0.6.1": * +#set page(height: auto, width: auto, margin: (bottom: 1em, top: 1em, x: 1em)) + +#vowels-3d() diff --git a/packages/preview/phonokit/0.6.1/gallery/vowels_example.png b/packages/preview/phonokit/0.6.1/gallery/vowels_example.png new file mode 100644 index 0000000000000000000000000000000000000000..154d0c45d54be9147e3fa1ab71507c4e6de27536 GIT binary patch literal 25610 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 RSlR8)r%qTLFEMh7{(qWX!G8b% literal 0 HcmV?d00001 diff --git a/packages/preview/phonokit/0.6.1/gallery/vowels_example.typ b/packages/preview/phonokit/0.6.1/gallery/vowels_example.typ new file mode 100644 index 0000000000..54b4b305ed --- /dev/null +++ b/packages/preview/phonokit/0.6.1/gallery/vowels_example.typ @@ -0,0 +1,17 @@ +#import "@preview/phonokit:0.6.1": * +#set page(height: auto, width: auto, margin: (bottom: 1em, top: 1em, x: 1em)) + +#vowels( + "english", + arrows: ( + ("a", "U"), + ("a", "I"), + ("e", "I"), + ("O", "I"), + ("o", "U"), + ), + arrow-color: blue.lighten(60%), + curved: true, + highlight: ("a", "e", "o", "O"), + highlight-color: blue.lighten(80%), +) diff --git a/packages/preview/phonokit/0.6.1/gallery/word_example.png b/packages/preview/phonokit/0.6.1/gallery/word_example.png new file mode 100644 index 0000000000000000000000000000000000000000..be1e56d11f336fa82f620dabf373ee40b46cda99 GIT binary patch literal 23435 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 z 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 Toj&1m6IWABN3}}D;`M(3& 0 and node.children.all(c => c.children.len() == 0) +} + +// ── All-non-leaf predicate ────────────────────────────────────────────────── +// True when every child of `node` is an internal node (has its own children). +// e.g. vocalic → [V-place, aperture]. These groups stagger vertically to +// avoid horizontal label collisions between wide class-node names. +#let _all-nonleaves(node) = { + node.children.len() > 0 and node.children.all(c => c.children.len() > 0) +} + +// ── Recursive subtree width ───────────────────────────────────────────────── +#let _tree-w(node) = { + if node.children.len() == 0 { return _leaf-w } + let gap = if _all-leaves(node) { _leaf-h-gap } else if node.kind == "root" { _root-h-gap } else { _h-gap } + let cw = node.children.map(c => _tree-w(c)) + calc.max(cw.sum() + (node.children.len() - 1) * gap, _leaf-w) +} + +// ── Recursive layout → flat list of positioned entries ────────────────────── +// Returns array of (label, kind, feats, x, y, par) dictionaries. +#let _layout(node, x0, y, par) = { + let w = _tree-w(node) + let my-x = x0 + w / 2 + let me = ((label: node.label, kind: node.kind, feats: node.at("features", default: ()), x: my-x, y: y, par: par),) + let gap = if _all-leaves(node) { _leaf-h-gap } else if node.kind == "root" { _root-h-gap } else { _h-gap } + // Count consecutive leading leaves (leaves before the first non-leaf child). + let n-leading = { + let count = 0 + for c in node.children { + if c.children.len() == 0 { count = count + 1 } else { break } + } + count + } + let cx = x0 + let out = me + for (i, child) in node.children.enumerate() { + let cw = _tree-w(child) + let all-prev-leaves = node.children.slice(0, i).all(c => c.children.len() == 0) + // Leaf children are staggered vertically by their rank among leaf siblings. + // Non-leaf children (e.g. C-place) always stay at the standard level. + let child-y = if child.children.len() == 0 { + let leaf-rank = node.children.slice(0, i).filter(c => c.children.len() == 0).len() + // "Leading" leaves: all siblings before this one are also leaves. + // • n-leading == 1: single leaf before a non-leaf (e.g. [nasal] before oral + // cavity) — use _mixed-leaf-vg for vertical position. + // • n-leading >= 2: multiple leaves (e.g. voice/continuant before C-place) — + // use _mixed-leaf-vg so the stagger distributes them around the non-leaf level. + // "Sandwiched" leaves: a non-leaf precedes this leaf → push below non-leaf level. + let base = if _all-leaves(node) { _v-gap } else if all-prev-leaves { _mixed-leaf-vg } else { + _v-gap + _stagger-dy * 0.3 + } + let step = if all-prev-leaves and not _all-leaves(node) { _ml-stagger-dy } else { _stagger-dy } + y - base - leaf-rank * step + } else { + // Non-leaf child: stagger vertically when all siblings are also non-leaves + // AND the parent is not the root node. Root's direct children (laryngeal, + // oral cavity) must stay at the same tier by convention. Deeper all-non-leaf + // groups (e.g. V-place + aperture under vocalic) do get staggered. + if _all-nonleaves(node) and node.kind != "root" { + y - _v-gap - i * _nonleaf-stagger-dy + } else if not _all-leaves(node) and not _all-nonleaves(node) { + y - _mixed-nonleaf-vg // mixed group: non-leaf drops further (e.g. C-place) + } else { + y - _v-gap + } + } + // Single leading leaf: shift further left so the line from the parent is + // more diagonal rather than nearly vertical next to the root label. + let cx-adj = if ( + child.children.len() == 0 and all-prev-leaves and not _all-leaves(node) and n-leading == 1 + ) { -0.40 } else { 0 } + out = out + _layout(child, cx + cx-adj, child-y, (my-x, y)) + cx = cx + cw + gap + } + out +} + +// ── Segment presets (Clements & Hume 1995) ────────────────────────────────── +// Pre-built spec dicts for common segments. Used by the `ph` parameter in +// geom() and as a `ph` key in geom-group spec dicts, e.g. (ph: "a"). +// Height is encoded by aperture (no features = high), front/back by V-place. +#let _presets = ( + // NOTE: The most common vowels: + "i": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + coronal: true, + aperture: ("-", "-", "-"), + segment: "i", + ), + "e": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + coronal: true, + aperture: ("-", "+", "-"), + segment: "e", + ), + "E": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + coronal: true, + aperture: ("-", "+", "+"), + segment: "E", + ), + "a": (root: ("+son", "+approx", "+vocoid"), vocalic: true, aperture: ("+", "+", "+"), segment: "a"), + "o": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + labial: true, + dorsal: true, + aperture: ("-", "+", "-"), + segment: "o", + ), + "O": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + labial: true, + dorsal: true, + aperture: ("-", "+", "+"), + segment: "O", + ), + "u": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + labial: true, + dorsal: true, + aperture: ("-", "-", "-"), + segment: "u", + ), + "I": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + coronal: true, + tense: "-", + aperture: ("-", "-", "-"), + segment: "I", + ), + "U": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + labial: true, + dorsal: true, + tense: "-", + aperture: ("-", "-", "-"), + segment: "U", + ), + // Additional vowels — high confidence: + "y": ( + // y close front rounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + coronal: true, + labial: true, + aperture: ("-", "-", "-"), + segment: "y", + ), + "W": ( + // ɯ close back unrounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + dorsal: true, + aperture: ("-", "-", "-"), + segment: "W", + ), + "7": ( + // ɤ close-mid back unrounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "+", + dorsal: true, + aperture: ("-", "+", "-"), + segment: "7", + ), + "\\o": ( + // ø close-mid front rounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "+", + coronal: true, + labial: true, + aperture: ("-", "+", "-"), + segment: "\\o", + ), + "\\oe": ( + // œ open-mid front rounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "-", + coronal: true, + labial: true, + aperture: ("-", "+", "+"), + segment: "\\oe", + ), + "2": ( + // ʌ open-mid back unrounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "-", + dorsal: true, + aperture: ("-", "+", "+"), + segment: "2", + ), + "A": ( + // ɑ open back unrounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + dorsal: true, + aperture: ("+", "+", "+"), + segment: "A", + ), + "6": ( + // ɒ open back rounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + labial: true, + dorsal: true, + aperture: ("+", "+", "+"), + segment: "6", + ), + // Flagged vowels — central/near-open: place analysis is theory-dependent. + "@": ( + // ə mid central — placeless in CH (no V-place node), aperture ("-","+","-") + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + aperture: ("-", "+", "-"), + segment: "@", + ), + "1": ( + // ɨ close central unrounded — placeless in CH + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + aperture: ("-", "-", "-"), + segment: "1", + ), + "0": ( + // ʉ close central rounded — labial only, no dorsal/coronal V-place + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + labial: true, + aperture: ("-", "-", "-"), + segment: "0", + ), + "\\ae": ( + // æ near-open front — aperture approximated as open-mid ("-","+","+"); same as /ɛ/ in CH + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + coronal: true, + aperture: ("-", "+", "+"), + segment: "\\ae", + ), + // NOTE: Some consonants: + "p": ( + root: ("-son", "-approx", "-vocoid"), + vocalic: false, + labial: true, + voice: "-", + segment: "p", + continuant: "-", + ), + "b": ( + root: ("-son", "-approx", "-vocoid"), + vocalic: false, + labial: true, + voice: "+", + segment: "b", + continuant: "-", + ), + "t": (root: ("-son", "-approx", "-vocoid"), coronal: true, anterior: "+", voice: "-", continuant: "-", segment: "t"), + "d": (root: ("-son", "-approx", "-vocoid"), coronal: true, anterior: "+", voice: "+", continuant: "-", segment: "d"), + "k": ( + root: ("-son", "-approx", "-vocoid"), + vocalic: false, + dorsal: true, + voice: "-", + segment: "k", + continuant: "-", + ), + "g": ( + root: ("-son", "-approx", "-vocoid"), + vocalic: false, + dorsal: true, + voice: "+", + segment: "g", + continuant: "-", + ), + "f": ( + root: ("-son", "-approx", "-vocoid"), + vocalic: false, + labial: true, + voice: "-", + segment: "f", + continuant: "+", + ), + "v": ( + root: ("-son", "-approx", "-vocoid"), + vocalic: false, + labial: true, + voice: "+", + segment: "v", + continuant: "+", + ), + "s": (root: ("-son", "-approx", "-vocoid"), coronal: true, anterior: "+", voice: "-", continuant: "+", segment: "s"), + "z": (root: ("-son", "-approx", "-vocoid"), coronal: true, anterior: "+", voice: "+", continuant: "+", segment: "z"), + // ʃ/ʒ: coronal [-anterior], NOT dorsal + "S": (root: ("-son", "-approx", "-vocoid"), coronal: true, anterior: "-", voice: "-", continuant: "+", segment: "S"), + "Z": (root: ("-son", "-approx", "-vocoid"), coronal: true, anterior: "-", voice: "+", continuant: "+", segment: "Z"), + // Affricates — [-continuant] following standard SPE/Kenstowicz analysis + "ts": ( + root: ("-son", "-approx", "-vocoid"), + coronal: true, + anterior: "+", + voice: "-", + continuant: ("-", "+"), + segment: "ts", + ), + "dz": ( + root: ("-son", "-approx", "-vocoid"), + coronal: true, + anterior: "+", + voice: "+", + continuant: ("-", "+"), + segment: "dz", + ), + "tS": ( + root: ("-son", "-approx", "-vocoid"), + coronal: true, + anterior: "-", + voice: "-", + continuant: ("-", "+"), + segment: "tS", + ), + "dZ": ( + root: ("-son", "-approx", "-vocoid"), + coronal: true, + anterior: "-", + voice: "+", + continuant: ("-", "+"), + segment: "dZ", + ), + "n": ( + root: ("+son", "-approx", "-vocoid"), + vocalic: false, + nasal: true, + coronal: true, + voice: "+", + segment: "n", + continuant: "-", + ), + "m": ( + root: ("+son", "-approx", "-vocoid"), + vocalic: false, + nasal: true, + labial: true, + voice: "+", + segment: "m", + continuant: "-", + ), + "N": ( + root: ("+son", "-approx", "-vocoid"), + vocalic: false, + nasal: true, + dorsal: true, + voice: "+", + segment: "N", + continuant: "-", + ), + // ɲ: palatal nasal = coronal [-anterior] + "\\nh": (root: ("+son", "-approx", "-vocoid"), coronal: true, anterior: "-", nasal: true, continuant: "-", segment: "\\nh"), + // N: nasal archiphoneme — deliberately unspecified for place + "\\N": (root: ("+son", "-approx", "-vocoid"), nasal: true, continuant: "-", segment: "\\N"), + // Additional consonants — high confidence: + "j": (root: ("+son", "+approx", "-vocoid"), dorsal: true, continuant: "+", segment: "j"), // j palatal approximant + "h": (root: ("-son", "-approx", "-vocoid"), spread: true, continuant: "+", segment: "h"), // h glottal fricative + "?": (root: ("-son", "-approx", "-vocoid"), constricted: true, continuant: "-", segment: "?"), // ʔ glottal stop + "T": ( + root: ("-son", "-approx", "-vocoid"), + coronal: true, + anterior: "+", + distributed: true, + voice: "-", + continuant: "+", + segment: "T", + ), // θ voiceless dental fricative + "D": ( + root: ("-son", "-approx", "-vocoid"), + coronal: true, + anterior: "+", + distributed: true, + voice: "+", + continuant: "+", + segment: "D", + ), // ð voiced dental fricative + "x": (root: ("-son", "-approx", "-vocoid"), dorsal: true, voice: "-", continuant: "+", segment: "x"), // x voiceless velar fricative + "G": (root: ("-son", "-approx", "-vocoid"), dorsal: true, voice: "+", continuant: "+", segment: "G"), // ɣ voiced velar fricative + "F": (root: ("-son", "-approx", "-vocoid"), labial: true, voice: "-", continuant: "+", segment: "F"), // ɸ voiceless bilabial fricative + "B": (root: ("-son", "-approx", "-vocoid"), labial: true, voice: "+", continuant: "+", segment: "B"), // β voiced bilabial fricative + "V": (root: ("+son", "+approx", "-vocoid"), labial: true, continuant: "+", segment: "V"), // ʋ labiodental approximant + "M": (root: ("+son", "-approx", "-vocoid"), labial: true, nasal: true, voice: "+", continuant: "-", segment: "M"), // ɱ labiodental nasal + "\\:t": ( + root: ("-son", "-approx", "-vocoid"), + coronal: true, + anterior: "-", + distributed: true, + voice: "-", + continuant: "-", + segment: "\\:t", + ), // ʈ retroflex stop (vl) + "\\:d": ( + root: ("-son", "-approx", "-vocoid"), + coronal: true, + anterior: "-", + distributed: true, + voice: "+", + continuant: "-", + segment: "\\:d", + ), // ɖ retroflex stop (vd) + "\\:s": ( + root: ("-son", "-approx", "-vocoid"), + coronal: true, + anterior: "-", + distributed: true, + voice: "-", + continuant: "+", + segment: "\\:s", + ), // ʂ retroflex fricative (vl) + "\\:z": ( + root: ("-son", "-approx", "-vocoid"), + coronal: true, + anterior: "-", + distributed: true, + voice: "+", + continuant: "+", + segment: "\\:z", + ), // ʐ retroflex fricative (vd) + "\\:n": ( + root: ("+son", "-approx", "-vocoid"), + coronal: true, + anterior: "-", + distributed: true, + nasal: true, + voice: "+", + continuant: "-", + segment: "\\:n", + ), // ɳ retroflex nasal + // Flagged consonants — place analysis is theory-dependent: + "r": (root: ("+son", "-approx", "-vocoid"), coronal: true, anterior: "+", voice: "+", continuant: "-", segment: "r"), // r alveolar trill — [-cont] following Kenstowicz (1994) + "l": (root: ("+son", "+approx", "-vocoid"), coronal: true, anterior: "+", voice: "+", continuant: "+", lateral: true, segment: "l"), // l alveolar lateral + "L": (root: ("+son", "+approx", "-vocoid"), coronal: true, anterior: "-", voice: "+", continuant: "+", lateral: true, segment: "L"), // ʎ palatal lateral approximant — palatal = coronal [−anterior], cf. ɲ/ç + "J": (root: ("-son", "-approx", "-vocoid"), dorsal: true, voice: "+", continuant: "+", segment: "J"), // ʝ voiced palatal fricative — NOTE: coronal vs. dorsal analysis contested; dorsal used here + "C": (root: ("-son", "-approx", "-vocoid"), coronal: true, anterior: "-", voice: "-", continuant: "+", segment: "C"), // ç voiceless palatal fricative — NOTE: strident not modelled (cf. /ʃ/) + // archiphoneme /T/: any stop — no place, no voice specified + "\\T": (root: ("-son", "-approx", "-vocoid"), continuant: "-", segment: "\\T"), + "\\C": (root: ("±son", "±approx", "-vocoid"), segment: "\\C"), + "\\V": (root: ("+son", "+approx", "+vocoid"), segment: "\\V"), +) + +// ── Segment presets (Sagey 1986) ───────────────────────────────────────────── +// Vowels only — consonants fall back to _presets in both models. The only +// consonant difference between models is where [lateral] attaches, which +// _build-tree handles from the `model` key (oral cavity vs. [coronal]). +// Height/backness encoded as dorsal sub-features; roundness as labial: ("round",). +// No aperture node. Note: [e]/[ɛ] and [o]/[ɔ] share the same basic features +// in Sagey (ATR/tense distinguishes them, which is not modelled here). +#let _presets-sagey = ( + "i": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + dorsal: ("+high", "-back"), + segment: "i", + ), + "e": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "+", + dorsal: ("-high", "-back"), + segment: "e", + ), + "E": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "-", + dorsal: ("-high", "-back"), + segment: "E", + ), + "a": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + dorsal: ("-high", "+lo"), + segment: "a", + ), + "o": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "+", + labial: ("round",), + dorsal: ("-high", "+back"), + segment: "o", + ), + "O": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "-", + labial: ("round",), + dorsal: ("-high", "+back"), + segment: "O", + ), + "u": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + labial: ("round",), + dorsal: ("+high", "+back"), + segment: "u", + ), + "I": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "-", + dorsal: ("+high", "-back"), + segment: "I", + ), + "U": ( + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "-", + labial: ("round",), + dorsal: ("+high", "+back"), + segment: "U", + ), + // Additional vowels — high confidence: + "y": ( + // y close front rounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + labial: ("round",), + dorsal: ("+high", "-back"), + segment: "y", + ), + "W": ( + // ɯ close back unrounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + dorsal: ("+high", "+back"), + segment: "W", + ), + "7": ( + // ɤ close-mid back unrounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "+", + dorsal: ("-high", "+back"), + segment: "7", + ), + "\\o": ( + // ø close-mid front rounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "+", + labial: ("round",), + dorsal: ("-high", "-back"), + segment: "\\o", + ), + "\\oe": ( + // œ open-mid front rounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "-", + labial: ("round",), + dorsal: ("-high", "-back"), + segment: "\\oe", + ), + "2": ( + // ʌ open-mid back unrounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + tense: "-", + dorsal: ("-high", "+back"), + segment: "2", + ), + "A": ( + // ɑ open back unrounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + dorsal: ("-high", "+lo", "+back"), + segment: "A", + ), + "6": ( + // ɒ open back rounded + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + labial: ("round",), + dorsal: ("-high", "+lo", "+back"), + segment: "6", + ), + // Flagged vowels — central/near-open: place analysis is theory-dependent. + "@": ( + // ə mid central — dorsal [-hi] only; back unspecified + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + dorsal: ("-high",), + segment: "@", + ), + "1": ( + // ɨ close central unrounded — dorsal [+hi], no back value + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + dorsal: ("+high",), + segment: "1", + ), + "0": ( + // ʉ close central rounded — [+hi] + labial, no back value + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + labial: ("round",), + dorsal: ("+high",), + segment: "0", + ), + "\\ae": ( + // æ near-open front — [-hi, +lo, -back]; +lo distinguishes from /ɛ/ (-hi, -back) + root: ("+son", "+approx", "+vocoid"), + vocalic: true, + vplace: true, + dorsal: ("-high", "+lo", "-back"), + segment: "\\ae", + ), +) + +// ── Build tree from spec dict ──────────────────────────────────────────────── +// Accepts a dict with the same keys as geom() (all optional, same defaults). +// Returns (tree: root-node-dict, is-vocoid: bool). +#let _build-tree(spec) = { + let root = spec.at("root", default: ()) + let laryngeal = spec.at("laryngeal", default: false) + let nasal = spec.at("nasal", default: false) + let spread = spec.at("spread", default: false) + let constricted = spec.at("constricted", default: false) + let voice = spec.at("voice", default: false) + let continuant = spec.at("continuant", default: false) + let labial = spec.at("labial", default: false) + let coronal = spec.at("coronal", default: false) + let anterior = spec.at("anterior", default: false) + let distributed = spec.at("distributed", default: false) + let dorsal = spec.at("dorsal", default: false) + let vocalic = spec.at("vocalic", default: false) + let vplace = spec.at("vplace", default: false) + let aperture = spec.at("aperture", default: false) + let lateral = spec.at("lateral", default: false) + // Feature-geometry model: "ch" (Clements & Hume) attaches [lateral] under the + // oral cavity (sibling of [continuant]); "sagey" attaches it under [coronal]. + let model = spec.at("model", default: "ch") + + // Normalize root to array + let root-feats = if type(root) == str { (root,) } else { root } + + // Auto-inference: show parent when any child is active + let radical = spec.at("radical", default: false) + + let laryngeal = laryngeal or spread or constricted or voice != false + + // [lateral] label and placement. In the Sagey model [lateral] hangs off + // [coronal]; in C&H it is a manner feature under the oral cavity. + let lateral-lbl = if lateral == true { "lateral" } else if lateral == "+" { "+lateral" } else if lateral == "-" { + "−lateral" + } else { none } + let lateral-cor = lateral-lbl != none and model == "sagey" // attach under [coronal] + let lateral-oc = lateral-lbl != none and model != "sagey" // attach under oral cavity + + // coronal may be bool or array; treat array as "active". + // A Sagey-style [lateral] forces [coronal] to appear as its host. + let coronal = if type(coronal) == array { coronal } else if ( + coronal or (anterior != false) or distributed or lateral-cor + ) { + true + } else { false } + let tense = spec.at("tense", default: false) + + // aperture is active when: true, or a non-empty array with at least one active element + let aperture-active = if type(aperture) == array { + aperture.any(v => v != false) + } else { aperture != false } + let vocalic = vocalic or vplace or aperture-active or (tense != false) + // If already in vocoid mode and place features are supplied, vplace is implied. + // labial/coronal/dorsal/radical may now be arrays (truthy) — != false covers all. + let place-active = (labial != false) or (coronal != false) or (dorsal != false) or (radical != false) + let vplace = vplace or (vocalic and place-active) + let vocalic = vocalic or vplace or aperture-active + let show-cplace = place-active or vocalic + + // [nasal] label + let nasal-lbl = if nasal == true { "nasal" } else if nasal == "+" { "+nasal" } else if nasal == "-" { + "−nasal" + } else { none } + + // [continuant] labels — accepts a single value or an array of up to 2 (affricates). + let _cont-lbl(v) = if v == true { "continuant" } else if v == "+" { "+continuant" } else if v == "-" { + "−continuant" + } else { none } + let continuant-lbls = if type(continuant) == array { + continuant.slice(0, calc.min(continuant.len(), 2)).map(_cont-lbl).filter(l => l != none) + } else { + let l = _cont-lbl(continuant) + if l != none { (l,) } else { () } + } + + // [voice] label + let voice-lbl = if voice == true { "voice" } else if voice == "+" { "+voice" } else if voice == "-" { + "−voice" + } else { none } + + // [anterior] label + let ant-lbl = if anterior == true { "anterior" } else if anterior == "+" { "+anterior" } else if anterior == "-" { + "−anterior" + } else { none } + + // [coronal] subtree — used when coronal is bool (existing anterior/distributed params) + let cor-ch-default = () + if ant-lbl != none { cor-ch-default = cor-ch-default + (_feat(ant-lbl),) } + if distributed { cor-ch-default = cor-ch-default + (_feat("distributed"),) } + + // Place features (shared by consonant and vocoid branches). + // Each of labial/coronal/dorsal/radical may be: + // false → node absent + // true → node shown, no sub-features (bool mode) + // array → node shown with sub-feature children from the array + // (e.g. dorsal: ("+high", "-back"), labial: ("round",)) + // Array mode for coronal replaces the anterior/distributed params. + let place-ch = () + if labial != false { + let ch = if type(labial) == array { labial.map(f => _feat(_norm-feat(f))) } else { () } + place-ch = place-ch + (_feat("labial", ch: ch),) + } + if coronal != false { + let ch = if type(coronal) == array { coronal.map(f => _feat(_norm-feat(f))) } else { cor-ch-default } + // Sagey model: [lateral] is a dependent of [coronal]. + if lateral-cor { ch = ch + (_feat(lateral-lbl),) } + place-ch = place-ch + (_feat("coronal", ch: ch),) + } + if radical != false { place-ch = place-ch + (_feat("radical"),) } + if dorsal != false { + let ch = if type(dorsal) == array { dorsal.map(f => _feat(_norm-feat(f))) } else { () } + place-ch = place-ch + (_feat("dorsal", ch: ch),) + } + + // [tense] label + let tense-lbl = if tense == true { "tense" } else if tense == "+" { "+tense" } else if tense == "-" { + "−tense" + } else { none } + + // Vocoid branch + let voc-ch = () + if tense-lbl != none { voc-ch = voc-ch + (_feat(tense-lbl),) } + if vplace { + voc-ch = voc-ch + (_class("V-place", place-ch),) + } + if aperture-active { + let ap-ch = if type(aperture) == array { + let names = ("open1", "open2", "open3") + let result = () + for (i, v) in aperture.slice(0, calc.min(aperture.len(), 3)).enumerate() { + let lbl = if v == true { names.at(i) } else if v == "+" { "+" + names.at(i) } else if v == "-" { + "−" + names.at(i) + } else { none } + if lbl != none { result = result + (_feat(lbl),) } + } + result + } else { () } + voc-ch = voc-ch + (_class("aperture", ap-ch),) + } + + // C-place children + let cplace-ch = if vocalic { (_class("vocalic", voc-ch),) } else { place-ch } + + // Auto-inference: != false covers true, "+", "-" + let show-oc = continuant-lbls.len() > 0 or show-cplace or lateral-oc + + // Oral cavity children. C&H model: [lateral] is a manner feature here, + // sitting alongside [continuant] and before C-place. + let oc-ch = continuant-lbls.map(_feat) + if lateral-oc { oc-ch = oc-ch + (_feat(lateral-lbl),) } + if show-cplace { oc-ch = oc-ch + (_class("C-place", cplace-ch),) } + + // Laryngeal children — [voice] is under laryngeal (Clements & Hume 1995) + let laryng-ch = () + if voice-lbl != none { laryng-ch = laryng-ch + (_feat(voice-lbl),) } + if spread { laryng-ch = laryng-ch + (_feat("spread"),) } + if constricted { laryng-ch = laryng-ch + (_feat("constricted"),) } + + // Root children + let root-ch = () + if laryngeal { root-ch = root-ch + (_class("laryngeal", laryng-ch),) } + if nasal-lbl != none { root-ch = root-ch + (_feat(nasal-lbl),) } + if show-oc { root-ch = root-ch + (_class("oral cavity", oc-ch),) } + + ( + tree: (label: "root", kind: "root", features: root-feats, children: root-ch), + is-vocoid: vocalic, + ) +} + +// ── Vocoid positional nudge ────────────────────────────────────────────────── +// Vocoid trees are naturally skewed: the deep vocalic subtree inflates the +// oral-cavity subtree's width, pushing it far right and laryngeal far left. +// Corrections: +// 1. Shift oral-cavity subtree left (oc-shift) +// 2. Shift laryngeal subtree right (lar-shift) +// 3. Individual nudges for [cont], [lab], [dor], [nasal] +// +// Subtrees identified via a single forward pass (pre-order). par coords are +// exact copies of the parent's (x,y), so array.contains() is exact. +#let _apply-vocoid-nudge(nodes, is-vocoid) = { + if not is-vocoid { return nodes } + + let oc-shift = -2.00 // move oral-cavity subtree left + let lar-shift = +1.10 // move laryngeal subtree right + + // Build a subtree membership set from a named root label. + let _build-sub(root-label) = { + let sub = () + let rn = nodes.find(e => e.label == root-label) + if rn != none { + sub = sub + ((rn.x, rn.y),) + for n in nodes { + if n.par != none and sub.contains(n.par) { + let pos = (n.x, n.y) + if not sub.contains(pos) { sub = sub + (pos,) } + } + } + } + sub + } + + let vp-extra = -0.35 // push V-place subtree left (more gap from aperture) + let ap-extra = +0.35 // push aperture subtree right (more gap from V-place) + // Only spread V-place/aperture when BOTH are present under vocalic; + // if only one is present it is the sole child and should stay centred. + + let oc-sub = _build-sub("oral cavity") + let lar-sub = _build-sub("laryngeal") + let vplace-sub = _build-sub("V-place") + let apt-sub = _build-sub("aperture") + + // Only apply root-level shifts when the root has multiple children. + let has-laryngeal = nodes.any(e => e.label == "laryngeal") + let has-nasal = nodes.any(e => e.label == "nasal" or e.label.ends-with("nasal")) + let has-open3 = nodes.any(e => e.label.ends-with("open3")) + let has-lab = nodes.any(e => e.label == "labial") + let has-cor = nodes.any(e => e.label == "coronal") + let has-dor = nodes.any(e => e.label == "dorsal") + // Full vocoid tree (laryngeal + nasal + OC): OC moves right to spread out. + // Partial tree (only laryngeal or only nasal): OC moves left as before. + let effective-oc-shift = if has-laryngeal and has-nasal { +0.10 } else if has-laryngeal or has-nasal { + oc-shift + } else { 0 } + let effective-lar-shift = if oc-sub.len() > 0 { lar-shift + (if has-open3 { 0.60 } else { 0 }) } else { 0 } + let effective-vp-extra = if vplace-sub.len() > 0 and apt-sub.len() > 0 { vp-extra } else { 0 } + let effective-ap-extra = if vplace-sub.len() > 0 and apt-sub.len() > 0 { ap-extra } else { 0 } + + // Total x displacement for a given (x,y) position — sum of all applicable shifts. + let _dx-for(pos) = { + let d = 0.0 + if oc-sub.contains(pos) { d = d + effective-oc-shift } + if vplace-sub.contains(pos) { d = d + effective-vp-extra } + if apt-sub.contains(pos) { d = d + effective-ap-extra } + if lar-sub.contains(pos) { d = d + effective-lar-shift } + d + } + + nodes.map(e => { + let base-x = e.x + _dx-for((e.x, e.y)) + let new-par = if e.par == none { none } else { + let pd = _dx-for(e.par) + if pd != 0 { (e.par.at(0) + pd, e.par.at(1)) } else { e.par } + } + + if e.label.ends-with("continuant") { + let cont-nudge = if not has-lab and has-cor and has-dor { -0.40 } else { 0 } + (..e, x: base-x + 2.80 + cont-nudge, par: new-par) + } else if e.label == "labial" { + // Only nudge toward [cor] when [cor] is actually present. + // Amount increases when [dor] is also there (three-way spread needs more room). + let lab-nudge = if has-cor { (if has-dor { 0.40 } else { 0.10 }) } else { 0 } + (..e, x: base-x + lab-nudge, par: new-par) + } else if e.label == "dorsal" { + if has-cor { + // [cor] is in the middle: pull [dor] left toward it and lift above open1. + (..e, x: base-x - 0.15, y: e.y + _stagger-dy, par: new-par) + } else { + // No [cor]: stay at natural position (sole feature, or alongside [lab] only). + (..e, x: base-x, par: new-par) + } + } else if e.label == "nasal" or e.label.ends-with("nasal") { + // Full vocoid tree only (has laryngeal): nudge nasal right. + let nx = if has-laryngeal { e.x + 0.90 + (if has-open3 { 0.60 } else { 0 }) } else { e.x } + (..e, x: nx, y: e.y - 0.30) + } else { + (..e, x: base-x, par: new-par) + } + }) +} + +// ── Node name for CeTZ anchor ──────────────────────────────────────────────── +// Based on the argument name: sign stripped, spaces→hyphens, NO abbreviation. +// "+anterior" → "anterior1", "oral cavity" → "oral-cavity2", "−voice" → "voice1" +#let _node-name(label, tidx) = { + let (_, base) = _sign-base(lower(label)) + base.replace(" ", "-") + str(tidx) +} + +// ── Manual position adjustments ────────────────────────────────────────────── +// `position` is an array of (key, dx, dy) triples. `key` is: +// - geom(): bare argument name, e.g. "continuant", "oral-cavity" +// - geom-group(): argument name + tree index, e.g. "continuant1", "oral-cavity2" +// `use-tidx`: when true, match key against _node-name(label, tidx); +// when false, match against bare base label (spaces→hyphens, no index). +// Moving a node also patches every node whose stored parent coords match the +// original position, so tree lines stay connected. +#let _apply-positions(nodes, position, use-tidx) = { + // Auto-wrap a single flat (key, dx, dy) triple + let position = if position.len() > 0 and type(position.at(0)) == str { + (position,) + } else { position } + if position.len() == 0 { return nodes } + + // Build adjustment dict: key → (dx, dy) + let adj = (:) + for entry in position { + adj.insert(entry.at(0), (entry.at(1), entry.at(2))) + } + + // Resolve key for a node entry + let node-key(e) = if use-tidx { + _node-name(e.label, e.tidx) + } else { + let (_, base) = _sign-base(lower(e.label)) + base.replace(" ", "-") + } + + // First pass: build "orig-x,orig-y" → new-(x,y) for all nodes that move, + // so we can patch children's stored parent coordinates in the second pass. + let moved = (:) + for e in nodes { + let key = node-key(e) + if key in adj { + let (dx, dy) = adj.at(key) + moved.insert(str(e.x) + "," + str(e.y), (e.x + dx, e.y + dy)) + } + } + + // Second pass: update node positions and parent references + nodes.map(e => { + let key = node-key(e) + let (nx, ny) = if key in adj { + let (dx, dy) = adj.at(key) + (e.x + dx, e.y + dy) + } else { (e.x, e.y) } + + let new-par = if e.par != none { + let pk = str(e.par.at(0)) + "," + str(e.par.at(1)) + if pk in moved { moved.at(pk) } else { e.par } + } else { none } + + (..e, x: nx, y: ny, par: new-par) + }) +} + +// ── Segment label normalisation ────────────────────────────────────────────── +// Strings are passed through ipa-to-unicode (TIPA conventions); content is used as-is. +// Strip phonemic/phonetic delimiters from a ph key so it can be looked up in _presets. +// "/a/" → "a", "[a]" → "a", "a" → "a" +#let _ph-bare(s) = if type(s) != str { s } else if s.starts-with("/") and s.ends-with("/") and s.len() > 2 { + s.slice(1, -1) +} else if s.starts-with("[") and s.ends-with("]") and s.len() > 2 { s.slice(1, -1) } else { s } + +// Render a segment label string. The user controls brackets by the string itself: +// "/a/" → /ipa("a")/ (phonemic) +// "[a]" → [ipa("a")] (phonetic) +// "a" → ipa("a") (bare) +// content → passed through unchanged +#let _seg(s) = if type(s) != str { s } else if s.starts-with("/") and s.ends-with("/") and s.len() > 2 { + [/#(ipa-to-unicode(s.slice(1, -1)))/] +} else if s.starts-with("[") and s.ends-with("]") and s.len() > 2 { + [\[#(ipa-to-unicode(s.slice(1, -1)))\]] +} else { ipa-to-unicode(s) } + +// ── Delink mark drawing ─────────────────────────────────────────────────────── +// Draws two parallel bars perpendicular to the parent→child line at its midpoint, +// matching the same symbol used in autoseg() / multi-tier(). +// (fx,fy) = parent bottom endpoint; (tx,ty) = child top endpoint. +#let _draw-delink(fx, fy, tx, ty, sw) = { + let dx = tx - fx + let dy = ty - fy + let len = calc.sqrt(dx * dx + dy * dy) + if len == 0 { return } + let dir-x = dx / len + let dir-y = dy / len + let perp-x = -dir-y + let perp-y = dir-x + let mid-x = (fx + tx) / 2 + let mid-y = (fy + ty) / 2 + let bar = 0.15 // half-length of each bar (canvas units) + let gap = 0.03 // half-gap between the two bars + for sign in (-1, 1) { + let cx = mid-x + sign * gap * dir-x + let cy = mid-y + sign * gap * dir-y + cetz.draw.line( + (cx - bar * perp-x, cy - bar * perp-y), + (cx + bar * perp-x, cy + bar * perp-y), + stroke: sw, + ) + } +} + +// ── General post-layout nudge (all tree types) ─────────────────────────────── +// When [voice] and [continuant] coexist they end up at nearly the same vertical +// level and close in x, causing overlap. Push [voice] down unconditionally. +#let _apply-general-nudge(nodes) = { + let has-cont = nodes.any(e => e.label.ends-with("continuant")) + let has-dor = nodes.any(e => e.label == "dorsal") + + // Full consonant tree: root has laryngeal + nasal + oral cavity all present. + // Shift the oral-cavity subtree slightly left so it doesn't crowd the tree. + // Gated on all three being present — leaves other consonant trees unchanged. + let has-lar = nodes.any(e => e.label == "laryngeal") + let has-nas = nodes.any(e => e.label == "nasal" or e.label.ends-with("nasal")) + let has-oc = nodes.any(e => e.label == "oral cavity") + let has-distr = nodes.any(e => e.label == "distributed") + let oc-shift = if has-lar and has-nas and has-oc { -0.50 } else { 0.0 } + + // Build oral-cavity subtree membership (pre-order, using original positions). + let oc-sub = if oc-shift != 0.0 { + let sub = () + let rn = nodes.find(e => e.label == "oral cavity") + if rn != none { + sub = sub + ((rn.x, rn.y),) + for n in nodes { + if n.par != none and sub.contains(n.par) { + let pos = (n.x, n.y) + if not sub.contains(pos) { sub = sub + (pos,) } + } + } + } + sub + } else { () } + + // Fix 2 — OC not centered under root when nasal+OC present but no laryngeal. + // Compute how far the root is displaced from the OC's current center. + let oc-center-shift = if not has-lar and has-nas and has-oc { + let rn = nodes.find(e => e.kind == "root") + let on = nodes.find(e => e.label == "oral cavity") + if rn != none and on != none { rn.x - on.x } else { 0.0 } + } else { 0.0 } + + // Build OC subtree membership for the center-shift (different gate from oc-sub). + let oc-center-sub = if oc-center-shift != 0.0 { + let sub = () + let on = nodes.find(e => e.label == "oral cavity") + if on != none { + sub = sub + ((on.x, on.y),) + for n in nodes { + if n.par != none and sub.contains(n.par) { + let pos = (n.x, n.y) + if not sub.contains(pos) { sub = sub + (pos,) } + } + } + } + sub + } else { () } + + // Fix 3 — [cor] and [rad] overlap when lab+cor+rad+dor all present. + // Pre-account for the rad nudge (-0.30 when dor present) and shift [cor] + // subtree to the midpoint between [lab].x and post-nudge [rad].x. + let rad-entry = nodes.find(e => e.label == "radical") + let lab-entry = nodes.find(e => e.label == "labial") + let cor-entry = nodes.find(e => e.label == "coronal") + let has-rad = rad-entry != none + + let cor-shift = if has-rad and cor-entry != none and lab-entry != none { + let rad-x = rad-entry.x + (if has-dor { -0.30 } else { 0.0 }) + (lab-entry.x + rad-x) / 2.0 - cor-entry.x + } else { 0.0 } + + let cor-sub = if cor-shift != 0.0 { + let sub = () + if cor-entry != none { + sub = sub + ((cor-entry.x, cor-entry.y),) + for n in nodes { + if n.par != none and sub.contains(n.par) { + let pos = (n.x, n.y) + if not sub.contains(pos) { sub = sub + (pos,) } + } + } + } + sub + } else { () } + + // Fix 4 — second [−cont] overlaps C-place in affricates (two continuant nodes). + let cont-nodes = nodes.filter(e => e.label.ends-with("continuant")) + let cont-count = cont-nodes.len() + let cont-right-x = if cont-count == 2 { + calc.max(..cont-nodes.map(e => e.x)) + } else { none } + + // Fix 5 — C&H [lateral] leaf is a sibling of [continuant] under oral cavity and + // its label crowds the C-place node. Locate the oral-cavity node so a lateral + // child of it can be nudged clear. (Sagey laterals sit under [coronal], untouched.) + let oc-lat-host = nodes.find(e => e.label == "oral cavity") + + // Fix 6 — a Sagey [lateral] is a child of [coronal], which widens the C-place + // subtree and pushes C-place right, stranding [+cont] far to the left under + // oral cavity. Detect that case so [+cont] can be nudged right to recentre it. + let cor-lat-host = nodes.find(e => e.label == "coronal") + let has-sagey-lat = cor-lat-host != none and nodes.any(e => ( + e.label.ends-with("lateral") and e.par == (cor-lat-host.x, cor-lat-host.y) + )) + + nodes.map(e => { + // [voice] drops down when [continuant] is also present (avoids overlap). + let e2 = if has-cont and (e.label == "voice" or e.label.ends-with("voice")) { + (..e, y: e.y - 0.80) + } else { e } + // Full tree: nudge [nasal] left and up so it sits naturally between laryngeal and oral cavity. + let e2 = if oc-shift != 0.0 and (e2.label == "nasal" or e2.label.ends-with("nasal")) { + (..e2, x: e2.x + 0.10, y: e2.y + 0.20) + } else { e2 } + // Fix 7: retroflex nasal ɳ has a wide oral-cavity subtree ([distr] under + // [cor]) that crowds [+nasal] against the laryngeal line — shift it right. + // Gated like the nudge above (oc-shift != 0 ⇒ full laryngeal+nasal+oral-cavity + // tree) plus [distr], so it only fires when that laryngeal line is present. + // Among the presets this is unique to ɳ; a hand-built nasal retroflex with a + // laryngeal hits the same crowding and benefits from the same shift. + let e2 = if oc-shift != 0.0 and has-distr and (e2.label == "nasal" or e2.label.ends-with("nasal")) { + (..e2, x: e2.x + 0.35) + } else { e2 } + // [rad] nudge left when [dor] is also present, to close the gap between them. + let e2 = if has-dor and e2.label == "radical" { + (..e2, x: e2.x - 0.30) + } else { e2 } + // Fix 5: C&H [lateral] child of oral cavity overlaps C-place — push left+down. + let e2 = if e2.label.ends-with("lateral") and oc-lat-host != none and e.par == ( + oc-lat-host.x, + oc-lat-host.y, + ) { + (..e2, x: e2.x - 0.35, y: e2.y - 0.30) + } else { e2 } + // Fix 6: Sagey [lateral] widens C-place — nudge [+cont] right under oral cavity. + let e2 = if has-sagey-lat and e2.label.ends-with("continuant") and oc-lat-host != none and e.par == ( + oc-lat-host.x, + oc-lat-host.y, + ) { + (..e2, x: e2.x + 0.45) + } else { e2 } + // Fix 3: shift [cor] subtree to midpoint between [lab] and post-nudge [rad]. + let e2 = if cor-sub.contains((e.x, e.y)) { + let new-par = if e2.par == none { none } else if cor-sub.contains(e2.par) { + (e2.par.at(0) + cor-shift, e2.par.at(1)) + } else { e2.par } + (..e2, x: e2.x + cor-shift, par: new-par) + } else { e2 } + // Fix 4: nudge the rightmost continuant node left+down when two are present. + let e2 = if cont-count == 2 and e2.label.ends-with("continuant") and e2.x == cont-right-x { + (..e2, x: e2.x - 0.25, y: e2.y - 0.20) + } else { e2 } + // Shift oral-cavity subtree left in full consonant trees (has-lar+has-nas+has-oc). + let e2 = if oc-sub.contains((e.x, e.y)) { + let new-par = if e2.par == none { none } else if oc-sub.contains(e2.par) { + (e2.par.at(0) + oc-shift, e2.par.at(1)) + } else { e2.par } + (..e2, x: e2.x + oc-shift, par: new-par) + } else { e2 } + // Fix 2: center OC subtree under root when nasal+OC present but no laryngeal. + if oc-center-sub.contains((e.x, e.y)) { + let new-par = if e2.par == none { none } else if oc-center-sub.contains(e2.par) { + (e2.par.at(0) + oc-center-shift, e2.par.at(1)) + } else { e2.par } + (..e2, x: e2.x + oc-center-shift, par: new-par) + } else { e2 } + }) +} + +/// Draw a feature-geometry tree for a consonant or vocoid. +/// +/// Arguments control which nodes are present. By default all nodes are absent. +/// Parent nodes are inferred automatically from their children (e.g. specifying +/// `spread: true` automatically shows "laryngeal"). +/// +/// - root (array): Feature strings for the root matrix, e.g. `("+son", "-vocoid")`. +/// Accepts the same formats as `feat()`. +/// - laryngeal (bool): Show "laryngeal" class node. +/// - nasal (bool, str): Show `[nasal]`. Pass `true` → `[nasal]`, +/// `"+"` → `[+nasal]`, `"-"` → `[−nasal]`. +/// - spread (bool): Show `[spread]` under laryngeal. +/// - constricted (bool): Show `[constricted]` under laryngeal. +/// - voice (bool, str): Show `[voice]` under laryngeal (Clements & Hume 1995). Pass +/// `true` → `[voice]`, `"+"` → `[+voice]`, `"-"` → `[−voice]`. +/// - continuant (bool, str): Show `[continuant]` under oral cavity. Pass `true` → `[cont]`, +/// `"+"` → `[+cont]`, `"-"` → `[−cont]`. +/// - labial (bool): Show `[labial]` (under C-place or V-place). +/// - coronal (bool): Show `[coronal]` (under C-place or V-place). +/// - anterior (bool, str): Show `[anterior]`. Pass `true` → `[anterior]`, +/// `"+"` → `[+anterior]`, `"-"` → `[−anterior]`. +/// - distributed (bool): Show `[distributed]` under `[coronal]`. +/// - radical (bool): Show `[rad]` (radical/pharyngeal) under C-place or V-place. +/// - dorsal (bool, array): Show `[dorsal]` (under C-place or V-place). +/// Pass an array of feature strings to add sub-features (Sagey-style): +/// `dorsal: ("+high", "-back")` → `[dor]` with `[+hi]` and `[−back]` children. +/// - labial (bool, array): Show `[labial]`. Array adds sub-features: +/// `labial: ("round",)` → `[lab]` with `[round]` child. +/// - coronal (bool, array): Show `[coronal]`. Array provides children directly, +/// replacing the separate `anterior`/`distributed` params: +/// `coronal: ("+ant", "-distr")` → `[cor]` with `[+ant]` and `[−distr]`. +/// - tense (bool, str): Show `[tense]` under the vocalic node. Pass `true` → `[tense]`, +/// `"+"` → `[+tense]`, `"-"` → `[−tense]`. Automatically infers `vocalic: true`. +/// Used in Sagey-style representations to distinguish [e]/[ɛ] and [o]/[ɔ]. +/// - vocalic (bool): Show "vocalic" class node under C-place (vocoid branch). +/// - vplace (bool): Show "V-place" under vocalic. When true, `labial`/`coronal`/`dorsal` +/// attach here instead of directly under C-place. Inferred automatically when +/// `vocalic` is active and any place feature is supplied. +/// - aperture (bool, array): Show "aperture" class node under vocalic. +/// Pass `true` → node only; pass an array of up to 3 values to show +/// `[open1]`/`[open2]`/`[open3]` as children. Each element may be +/// `true` → `[openN]`, `"+"` → `[+openN]`, `"-"` → `[−openN]`, +/// or `false` → omit that degree. E.g. `aperture: ("+", false, "-")`. +/// (Replaces the former `open` parameter.) +/// - lateral (bool, str): Show `[lateral]`. Pass `true` → `[lateral]`, +/// `"+"` → `[+lateral]`, `"-"` → `[−lateral]`. Attachment depends on `model`: +/// under the oral cavity (sibling of `[continuant]`) for `"ch"`, under +/// `[coronal]` for `"sagey"`. In the Sagey case `[coronal]` is shown automatically. +/// - scale (number): Uniform scale factor (default: 1). +/// - position (array): Manual position tweaks. Each entry: `(key, dx, dy)` where +/// `key` is the bare argument name (`"continuant"`, `"oral-cavity"`) and `dx`/`dy` +/// are canvas-unit offsets (positive x = right, positive y = up). +/// Example: `position: (("continuant", -0.2, 0.3),)` +/// - delinks (array): Node keys whose line *to their parent* is replaced with a +/// delink mark (two perpendicular bars). Keys follow the same convention as +/// `position`: bare argument name for `geom()`, e.g. `delinks: ("c-place",)`. +/// - prefix (str): String prepended to the segment label. `"-"` is automatically converted to `"–"`. E.g. `prefix: "-"` → `–/a/`. +/// - suffix (str): String appended to the segment label. `"-"` is automatically converted to `"–"`. +/// - segment (content): Optional label centred above the root node, e.g. `"s"` or `$s$`. +/// - ph (str): Pre-built segment preset. Supports `"a"`, `"e"`, `"i"`, `"o"`, `"u"`, +/// `"E"` (ɛ), `"O"` (ɔ). The segment label defaults to the `ph` value unless overridden +/// by `segment`. Any other explicitly-provided argument overrides the corresponding preset +/// value: `#geom(ph: "O", root: ("+son", "+approx"))` replaces its root features. +/// Example: `#geom(ph: "i", scale: 1.5)`. +/// - model (str): Feature-geometry model for preset vowels. `"ch"` (default) uses +/// Clements & Hume 1995 (aperture nodes for height). `"sagey"` uses Sagey 1986 +/// (dorsal sub-features for height/backness, labial `[round]` for rounding, no aperture). +/// Consonant presets are otherwise identical in both models, except for the +/// placement of `[lateral]` (oral cavity under `"ch"`, `[coronal]` under `"sagey"`). +/// -> content +#let geom( + ph: none, + ui-lang: "en", + model: "ch", + root: (), + laryngeal: false, + nasal: false, + spread: false, + constricted: false, + voice: false, + continuant: false, + labial: false, + coronal: false, + anterior: false, + distributed: false, + dorsal: false, + radical: false, + vocalic: false, + vplace: false, + aperture: false, + tense: false, + lateral: false, + scale: 1.0, + position: (), + delinks: (), + segment: none, + prefix: "", + suffix: "", + highlight: (), + timing: auto, +) = { + let ui-locale = resolve-ui-lang(ui-lang) + if ui-locale == none { + return ui-lang-error(ui-lang) + } + + // Auto-detect length from ph: "iː" or "i:" → long (two timing slots). + // Only a TRAILING colon counts as a length mark — a colon elsewhere belongs to + // a TIPA escape such as "\:t" (ʈ) and must not be stripped, or the preset lookup + // would fail. Strip just the trailing mark; keep the original as label fallback. + let _is-long = ph != none and type(ph) == str and (ph.ends-with("ː") or ph.ends-with(":")) + let _ph-orig = ph + let ph = if _is-long { ph.trim("ː", at: end, repeat: false).trim(":", at: end, repeat: false) } else { ph } + + // Resolve timing: + // auto → one × normally, two × when ph contains a length mark + // false → no timing tier + // string/symbol/array → explicit (normalized below) + let timing = if timing == false { + () + } else if timing == auto { + if _is-long { ($times$, $times$) } else { ($times$,) } + } else { + // Coerce bare string/symbol to array, then normalize "mora"/"mu" → μ, "x"/"X" → × + let t = if type(timing) == str or type(timing) == symbol { (timing,) } else { timing } + t.map(t => if type(t) == str { + let tl = lower(t) + if tl == "mora" or tl == "mu" { sym.mu } else if tl == "x" { $times$ } else { t } + } else { t }) + } + let prefix = prefix.replace("-", "–") + let suffix = suffix.replace("-", "–") + let scale-factor = scale + + // When ph is set, load the preset, then apply any explicitly-provided + // non-default arguments on top. This lets callers override individual keys: + // #geom(ph: "O", root: ("+son", "+approx")) ← root replaces preset's root + // Sagey model uses _presets-sagey for vowels; consonants fall back to _presets. + let ph-key = if ph != none { _ph-bare(ph) } else { none } + let spec = if ph-key != none and ph-key in _presets { + let preset-dict = if model == "sagey" and ph-key in _presets-sagey { + _presets-sagey + } else { _presets } + // Use segment label exactly as provided by the user; fall back to the preset's own segment field. + let seg = if segment != none { prefix + segment + suffix } else { prefix + _ph-orig + suffix } + let overrides = (:) + if root != () { overrides.insert("root", root) } + if laryngeal != false { overrides.insert("laryngeal", laryngeal) } + if nasal != false { overrides.insert("nasal", nasal) } + if spread != false { overrides.insert("spread", spread) } + if constricted != false { overrides.insert("constricted", constricted) } + if voice != false { overrides.insert("voice", voice) } + if continuant != false { overrides.insert("continuant", continuant) } + if labial != false { overrides.insert("labial", labial) } + if coronal != false { overrides.insert("coronal", coronal) } + if anterior != false { overrides.insert("anterior", anterior) } + if distributed != false { overrides.insert("distributed", distributed) } + if dorsal != false { overrides.insert("dorsal", dorsal) } + if radical != false { overrides.insert("radical", radical) } + if vocalic != false { overrides.insert("vocalic", vocalic) } + if vplace != false { overrides.insert("vplace", vplace) } + if aperture != false { overrides.insert("aperture", aperture) } + if tense != false { overrides.insert("tense", tense) } + if lateral != false { overrides.insert("lateral", lateral) } + (..(preset-dict.at(ph-key)), segment: seg, ..overrides) + } else { + ( + root: root, + laryngeal: laryngeal, + nasal: nasal, + spread: spread, + constricted: constricted, + voice: voice, + continuant: continuant, + labial: labial, + coronal: coronal, + anterior: anterior, + distributed: distributed, + dorsal: dorsal, + radical: radical, + vocalic: vocalic, + vplace: vplace, + aperture: aperture, + tense: tense, + lateral: lateral, + segment: if segment != none { prefix + segment + suffix } else if prefix != "" or suffix != "" { + prefix + suffix + } else { none }, + ) + } + let spec = (..spec, model: model) + let result = _build-tree(spec) + let tree = result.tree + let is-vocoid = result.is-vocoid + + let nodes = _layout(tree, 0.0, 0.0, none) + let nodes = _apply-vocoid-nudge(nodes, is-vocoid) + let nodes = _apply-general-nudge(nodes) + // Tag with tree index 1 (single tree) + let nodes = nodes.map(e => (..e, tidx: 1)) + let nodes = _apply-positions(nodes, position, false) + + // ── Render ──────────────────────────────────────────────────────────── + let _loff = 0.20 + let _dim = luma(65%) + let _norm = luma(15%) + // Per-node color: dim everything not in the highlight set (when set is non-empty). + let _nc = nname => if highlight.len() == 0 or nname in highlight { _norm } else { _dim } + + // Dynamic baseline: anchor root node (canvas y=0) at the text baseline. + let y-min = nodes.fold(0.0, (acc, e) => calc.min(acc, e.y)) + + context { + let em-in-cu = text.size / (scale-factor * 1cm) + let _timing-gap = 0.65 // vertical gap from root to timing tier + let _timing-y = 0.55 + _timing-gap // y-coordinate of timing nodes (root at 0) + let _t-spacing = 0.55 // horizontal gap between timing nodes + let seg-present = spec.at("segment", default: none) != none + let y-top = if timing.len() > 0 { + // timing nodes sit at _timing-y; segment label (if any) floats above them + let label-top = if seg-present { + _timing-y + 0.45 + text.size * 0.84 / (scale-factor * 1cm) + } else { + _timing-y + text.size * 0.35 / (scale-factor * 1cm) + } + label-top + } else if seg-present { + 0.55 + text.size * 0.84 / (scale-factor * 1cm) + } else { + text.size * 0.35 / (scale-factor * 1cm) + } + let bl = (em-in-cu + (-y-min)) / (2 * em-in-cu + y-top - y-min) + let fsz = text.size * 0.70 * scale-factor + let font = phonokit-font.get() + + box(inset: 1em * scale-factor, baseline: bl * 100%, { + cetz.canvas(length: scale-factor * 1cm, { + import cetz.draw: * + + for entry in nodes { + let nname = _node-name(entry.label, entry.tidx) + let is-delinked = nname.slice(0, -1) in delinks + let nc = _nc(nname) + + if entry.par != none { + let (px, py) = entry.par + let (fx, fy) = (px, py - _loff) + let (tx, ty) = (entry.x, entry.y + _loff) + let par-entry = nodes.find(e => e.x == px and e.y == py) + let par-nname = if par-entry != none { _node-name(par-entry.label, par-entry.tidx) } else { none } + let both-highlighted = ( + highlight.len() > 0 and nname in highlight and par-nname != none and par-nname in highlight + ) + let line-paint = if highlight.len() == 0 or both-highlighted { _norm } else { _dim } + let sw = (paint: line-paint, thickness: 0.016) + line((fx, fy), (tx, ty), stroke: sw) + if is-delinked { _draw-delink(fx, fy, tx, ty, sw) } + } + + if entry.kind == "root" { + content( + (entry.x, entry.y), + text(font: font, size: fsz, fill: nc, ui-geom-label("root", ui-locale)), + name: nname, + ) + if entry.feats.len() > 0 { + let mat-x = entry.x + 0.25 + let items = entry.feats.map(f => { + let norm = f.replace("-", "−") + let (sign, base) = if norm.starts-with("±") { ("±", norm.slice("±".len())) } else if norm.starts-with( + "+", + ) { ("+", norm.slice(1)) } else if norm.starts-with("−") { ("−", norm.slice("−".len())) } else { + ("", norm) + } + text(font: font, fill: nc, if sign != "" { box(width: 0.65em, align(center, sign)) + base } else { + norm + }) + }) + content( + (mat-x, entry.y), + { + set text(font: font, size: fsz, fill: nc) + box(baseline: 50%, math.vec( + align: left, + delim: "[", + gap: 0pt, + ..items, + )) + }, + anchor: "west", + ) + } + } else { + let inner = if entry.kind == "feature" { + [\[#(_display(entry.label, ui-lang: ui-locale))\]] + } else { + [#(_display(entry.label, ui-lang: ui-locale))] + } + content( + (entry.x, entry.y), + text(font: font, size: fsz, fill: nc, inner), + name: nname, + ) + } + } + + // Segment label — always full colour (never dimmed by highlight) + let seg-label = spec.at("segment", default: none) + if seg-label != none { + let root-entry = nodes.find(e => e.kind == "root") + if root-entry != none { + let label-y = if timing.len() > 0 { + root-entry.y + _timing-y + 0.45 + } else { + root-entry.y + 0.55 + } + content( + (root-entry.x, label-y), + text(font: font, size: fsz * 1.2, fill: _norm, _seg(seg-label)), + anchor: "south", + ) + } + } + + // Timing tier (X-slots, morae, etc.) + if timing.len() > 0 { + let root-entry = nodes.find(e => e.kind == "root") + if root-entry != none { + let rx = root-entry.x + let ry = root-entry.y + let t-y = ry + _timing-y + let n = timing.len() + let t-paint = if highlight.len() == 0 { _norm } else { _dim } + for (i, t) in timing.enumerate() { + let tx = if n == 1 { rx } else { rx + (i - (n - 1) / 2.0) * _t-spacing } + line( + (rx, ry + _loff), + (tx, t-y - _loff), + stroke: (paint: t-paint, thickness: 0.016), + ) + content((tx, t-y), text(font: font, size: fsz, fill: t-paint, t)) + } + } + } + }) + }) + } // context +} + +/// Draw two or more feature-geometry trees side by side in a single canvas, +/// with optional dashed arrows connecting nodes across trees. +/// +/// Each tree is specified as a dict with the same keys as `geom()` (all +/// optional, same defaults). Trees cannot be passed as rendered `#geom()` +/// content — pass spec dicts or `#let` variables instead: +/// +/// ```typst +/// #let consonant = (root: ("-son",), labial: true) +/// #let vowel = (root: ("+son",), vocalic: true, dorsal: true) +/// #geom-group(consonant, vowel, +/// arrows: (("labial1", "dorsal2"),)) +/// ``` +/// +/// Node names are formed by stripping `+`/`−` prefixes, replacing spaces with +/// hyphens, and appending the 1-based tree index: +/// `"anterior1"`, `"oral-cavity2"`, `"c-place1"`, `"root2"`, etc. +/// +/// Each arrow entry is either a simple array `(from, to)` or a dict with +/// named keys for full control: +/// ```typst +/// arrows: ( +/// ("labial1", "labial2"), // simple +/// (from: "cor1", to: "cor2", color: blue), // coloured +/// (from: "dor1", to: "dor2", ctrl: (1.0, -0.5)), // custom S-curve +/// ) +/// ``` +/// +/// - ..trees (arguments): Positional spec dicts, one per tree. +/// - arrows (array): Cross-tree arrows. Each entry: `(from, to)` array or +/// `(from: str, to: str, color: color, ctrl: array)` dict (all keys except +/// `from`/`to` optional). +/// - gap (number): Canvas-unit gap between trees (default: 1.5). +/// - scale (number): Uniform scale factor (default: 1). +/// - model (str): Feature-geometry model for preset vowels. `"ch"` (default) or `"sagey"`. +/// Applies to all trees in the group. See `geom()` for details. +/// - position (array): Manual position tweaks after layout. Each entry: `(key, dx, dy)` +/// where `key` is the node anchor name with tree index (`"continuant1"`, `"oral-cavity2"`) +/// and `dx`/`dy` are canvas-unit offsets. Arrows automatically use the adjusted positions. +/// Example: `position: (("continuant1", -0.2, 0.3),)` +/// - delinks (array): Node anchor names (with tree index) whose line to their parent is +/// replaced with a delink mark. E.g. `delinks: ("c-place1",)`. +/// - curved (bool): When `true`, arrows are drawn as bézier curves that bow away +/// from the straight line between the two nodes instead of crossing it directly. +/// Use per-arrow `ctrl` offsets to adjust the curvature if needed. (default: `false`) +/// -> content +#let geom-group( + ..args, + arrows: (), + gap: 1.5, + scale: 1.0, + ui-lang: "en", + model: "ch", + position: (), + delinks: (), + curved: false, + highlight: (), +) = { + let ui-locale = resolve-ui-lang(ui-lang) + if ui-locale == none { + return ui-lang-error(ui-lang) + } + + let specs = args.pos() + let scale-factor = scale + + // Auto-wrap a flat ("from", "to") pair so both forms are valid: + // arrows: ("labial1", "c-place3") ← single arrow, flat + // arrows: (("labial1", "c-place3"), ...) ← multiple arrows, nested + let arrows = if arrows.len() > 0 and type(arrows.at(0)) == str { + (arrows,) + } else { arrows } + + // ── Build and layout each tree, offset x by cumulative width + gap ──── + // Each spec may carry a `scale` key (default 1.0) that scales that tree's + // coordinates and font size relative to the group scale. + let all-nodes = () + let x-cursor = 0.0 + let seg-labels = () // (x, y, ts, text, root-nname, has-timing) + let timing-data = () // (root-x, ts, resolved-timing-array) + for (idx, spec) in specs.enumerate() { + // Auto-detect a TRAILING length mark in ph; strip just that so preset lookup + // works. A mid-string colon belongs to a TIPA escape like "\:t" (ʈ) — leave it. + let ph-raw = spec.at("ph", default: none) + let _is-long = ph-raw != none and type(ph-raw) == str and (ph-raw.ends-with("ː") or ph-raw.ends-with(":")) + let spec = if _is-long { + (..spec, ph: ph-raw.trim("ː", at: end, repeat: false).trim(":", at: end, repeat: false)) + } else { spec } + + // Resolve preset if ph key is present; explicit spec keys override the preset. + // Sagey model uses _presets-sagey for vowels; consonants fall back to _presets. + let spec = if "ph" in spec and _ph-bare(spec.at("ph")) in _presets { + let ph-val = spec.at("ph") + let ph-key = _ph-bare(ph-val) + let preset-dict = if model == "sagey" and ph-key in _presets-sagey { + _presets-sagey + } else { _presets } + let base = preset-dict.at(ph-key) + let px = spec.at("prefix", default: "").replace("-", "–") + let sx = spec.at("suffix", default: "").replace("-", "–") + // Use original ph (with length mark) as segment label fallback + let seg-ph = if ph-raw != none { ph-raw } else { ph-val } + let seg = if "segment" in spec { px + spec.at("segment") + sx } else { px + seg-ph + sx } + let overrides = (:) + for pair in spec.pairs() { + if pair.at(0) not in ("ph", "prefix", "suffix") { overrides.insert(pair.at(0), pair.at(1)) } + } + (..base, segment: seg, ..overrides) + } else { spec } + + // Resolve timing for this tree (same logic as geom()) + let timing-raw = spec.at("timing", default: auto) + let tree-timing = if timing-raw == false { + () + } else if timing-raw == auto { + if _is-long { ($times$, $times$) } else { ($times$,) } + } else { + let t = if type(timing-raw) == str or type(timing-raw) == symbol { (timing-raw,) } else { timing-raw } + t.map(t => if type(t) == str { + let tl = lower(t) + if tl == "mora" or tl == "mu" { sym.mu } else if tl == "x" { $times$ } else { t } + } else { t }) + } + + let spec = (..spec, model: model) + let result = _build-tree(spec) + let tree = result.tree + let is-vocoid = result.is-vocoid + let ts = spec.at("scale", default: 1.0) // per-tree relative scale + let px = spec.at("prefix", default: "").replace("-", "–") + let sx = spec.at("suffix", default: "").replace("-", "–") + let seg = if spec.at("segment", default: none) != none { px + spec.at("segment") + sx } else { none } + let nodes = _layout(tree, 0.0, 0.0, none) + let nodes = _apply-vocoid-nudge(nodes, is-vocoid) + let nodes = _apply-general-nudge(nodes) + let tidx = idx + 1 + // Scale coordinates and offset into the shared canvas. + all-nodes = ( + all-nodes + + nodes.map(e => ( + ..e, + x: e.x * ts + x-cursor, + y: e.y * ts, + par: if e.par == none { none } else { + (e.par.at(0) * ts + x-cursor, e.par.at(1) * ts) + }, + tidx: tidx, + tscale: ts, + )) + ) + let root-w = _tree-w(tree) + let root-x = x-cursor + root-w / 2 * ts + let root-nname = _node-name("root", tidx) + // Record segment label position + if seg != none { + seg-labels = seg-labels + ((root-x, 0.0, ts, seg, root-nname, tree-timing.len() > 0),) + } + // Record timing tier data + if tree-timing.len() > 0 { + timing-data = timing-data + ((root-x, ts, tree-timing),) + } + x-cursor = x-cursor + _tree-w(tree) * ts + gap + } + let all-nodes = _apply-positions(all-nodes, position, true) + + // ── Render ──────────────────────────────────────────────────────────── + let _loff = 0.20 + let _dim = luma(65%) + let _norm = luma(15%) + let _nc = nname => if highlight.len() == 0 or nname in highlight { _norm } else { _dim } + + let y-min-g = all-nodes.fold(0.0, (acc, e) => calc.min(acc, e.y)) + + // Build name → (x, y, loff) lookup OUTSIDE the canvas block. + // dict.insert() inside CeTZ's canvas block may not behave correctly + // because CeTZ processes drawing commands in a special context. + let name-to-pos = (:) + for e in all-nodes { + name-to-pos.insert(_node-name(e.label, e.tidx), (e.x, e.y, _loff * e.tscale)) + } + + context { + let em-in-cu-g = text.size / (scale-factor * 1cm) + let _timing-gap = 0.65 + let _timing-y = 0.55 + _timing-gap + let _t-spacing = 0.55 + // y-top-g: highest point across all trees (timing + segment labels) + let y-top-g = { + let timing-tops = timing-data.map(td => { + let (rx, ts, tt) = td + let has-seg = seg-labels.any(sl => calc.abs(sl.at(0) - rx) < 0.001) + if has-seg { + (_timing-y + 0.45 + text.size * 0.84 / (scale-factor * 1cm)) * ts + } else { + (_timing-y + text.size * 0.35 / (scale-factor * 1cm)) * ts + } + }) + let seg-tops = seg-labels + .filter(sl => not sl.at(5)) + .map(sl => 0.55 * sl.at(2) + text.size * 0.84 / (scale-factor * 1cm)) + let all-tops = timing-tops + seg-tops + if all-tops.len() > 0 { + all-tops.fold(text.size * 0.35 / (scale-factor * 1cm), calc.max) + } else { + text.size * 0.35 / (scale-factor * 1cm) + } + } + let bl-g = (em-in-cu-g + (-y-min-g)) / (2 * em-in-cu-g + y-top-g - y-min-g) + let fsz = text.size * 0.70 * scale-factor + let font = phonokit-font.get() + + box(inset: 1em * scale-factor, baseline: bl-g * 100%, { + cetz.canvas(length: scale-factor * 1cm, { + import cetz.draw: * + + // Draw all tree nodes + for entry in all-nodes { + let nname = _node-name(entry.label, entry.tidx) + let ts = entry.tscale + let efsz = fsz * ts + let eloff = _loff * ts + let nc = _nc(nname) + + if entry.par != none { + let (px, py) = entry.par + let (fx, fy) = (px, py - eloff) + let (tx, ty) = (entry.x, entry.y + eloff) + let par-entry = all-nodes.find(e => e.x == px and e.y == py) + let par-nname = if par-entry != none { _node-name(par-entry.label, par-entry.tidx) } else { none } + let both-highlighted = ( + highlight.len() > 0 and nname in highlight and par-nname != none and par-nname in highlight + ) + let line-paint = if highlight.len() == 0 or both-highlighted { _norm } else { _dim } + let sw = (paint: line-paint, thickness: 0.016) + line((fx, fy), (tx, ty), stroke: sw) + if nname in delinks { _draw-delink(fx, fy, tx, ty, sw) } + } + + if entry.kind == "root" { + content( + (entry.x, entry.y), + text(font: font, size: efsz, fill: nc, ui-geom-label("root", ui-locale)), + name: nname, + ) + if entry.feats.len() > 0 { + let mat-x = entry.x + 0.25 * ts + let items = entry.feats.map(f => { + let norm = f.replace("-", "−") + let (sign, base) = if norm.starts-with("±") { ("±", norm.slice("±".len())) } else if norm.starts-with( + "+", + ) { ("+", norm.slice(1)) } else if norm.starts-with("−") { ("−", norm.slice("−".len())) } else { + ("", norm) + } + text(font: font, fill: nc, if sign != "" { box(width: 0.65em, align(center, sign)) + base } else { + norm + }) + }) + content( + (mat-x, entry.y), + { + set text(font: font, size: efsz, fill: nc) + box(baseline: 50%, math.vec( + align: left, + delim: "[", + gap: 0pt, + ..items, + )) + }, + anchor: "west", + ) + } + } else { + let inner = if entry.kind == "feature" { + [\[#(_display(entry.label, ui-lang: ui-locale))\]] + } else { + [#(_display(entry.label, ui-lang: ui-locale))] + } + content( + (entry.x, entry.y), + text(font: font, size: efsz, fill: nc, inner), + name: nname, + ) + } + } + + // Segment labels — always full colour (never dimmed by highlight) + for (sx, sy, ts, seg, root-nname, has-timing) in seg-labels { + let label-y = sy + (if has-timing { (_timing-y + 0.45) * ts } else { 0.55 * ts }) + let seg-body = _seg(seg) + content( + (sx, label-y), + text(font: font, size: fsz * ts * 1.2, fill: _norm, seg-body), + anchor: "south", + ) + } + + // Timing tiers + let t-paint = if highlight.len() == 0 { _norm } else { _dim } + for (rx, ts, tt) in timing-data { + let ry = 0.0 + let t-y = ry + _timing-y * ts + let n = tt.len() + let loff = 0.20 * ts + for (i, t) in tt.enumerate() { + let tx = if n == 1 { rx } else { rx + (i - (n - 1) / 2.0) * _t-spacing * ts } + line( + (rx, ry + loff), + (tx, t-y - loff), + stroke: (paint: t-paint, thickness: 0.016), + ) + content((tx, t-y), text(font: font, size: fsz * ts, fill: t-paint, t)) + } + } + + // Draw cross-tree arrows. + // Shaft is dashed; head is a separate solid segment so the arrowhead + // contour is never dashed (same technique as #vowels). + // When curved: bézier bowing away from the straight line between the nodes. + let head-back = 0.12 // canvas units of solid segment before the tip + + for arrow in arrows { + // Accept both positional arrays ("from", "to") / ("from", "to", color) + // and dicts (from: "...", to: "...", color: ..., ctrl: ...). + let is-dict = type(arrow) == dictionary + let from-name = if is-dict { arrow.at("from") } else { arrow.at(0) } + let to-name = if is-dict { arrow.at("to") } else { arrow.at(1) } + let paint = if is-dict { arrow.at("color", default: luma(15%)) } else if arrow.len() >= 3 { + arrow.at(2) + } else { luma(15%) } + // ctrl: two-element array (lift1, lift2) — Y-offsets from each endpoint. + // When set, bypasses curved entirely. + let ctrl-val = if is-dict { arrow.at("ctrl", default: none) } else { none } + + if from-name in name-to-pos and to-name in name-to-pos { + let (fx, fy, f-loff) = name-to-pos.at(from-name) + let (tx, ty, t-loff) = name-to-pos.at(to-name) + // Dim arrow if neither endpoint is highlighted (when highlight is active). + let arrow-lit = highlight.len() == 0 or from-name in highlight or to-name in highlight + let paint = if arrow-lit { paint } else { _dim } + // Arrows connect to the TOP of each node (where parent lines terminate), + // EXCEPT for root nodes which have no parent — they connect at the BOTTOM + // (facing their children) to avoid landing near the segment label above. + let fy = if from-name.starts-with("root") { fy - f-loff } else { fy + f-loff } + let ty = ty - t-loff + let dx = tx - fx + let dy = ty - fy + let len = calc.sqrt(dx * dx + dy * dy) + + // ── Control points ───────────────────────────────────────────── + // Priority: ctrl > curved. + // ctrl: explicit Y-offsets from each endpoint → cubic Bézier, any shape. + // curved: auto S-curve scaled by distance. + let (ctrl1, ctrl2) = if ctrl-val != none { + // ctrl: (lift1, lift2) — Y-offsets from each endpoint. + ( + (fx + dx * 0.30, fy + ctrl-val.at(0)), + (tx - dx * 0.30, ty + ctrl-val.at(1)), + ) + } else { + if curved and len > 0 { + let v-reach = calc.abs(dy) + let h-reach = calc.abs(dx) + // ctrl1: departs with upward bias, scaling with whichever reach dominates. + let lift1 = calc.max(v-reach * 0.50, h-reach * 0.20, 0.50) + // ctrl2: arrives from below target — dip scales with vertical distance. + let dip2 = calc.max(v-reach * 0.25, 0.40) + ( + (fx + dx * 0.30, fy + lift1), + (tx - dx * 0.10, ty - dip2), + ) + } else { (none, none) } + } + + // ── Tangent at tip & pullback ─────────────────────────────────── + let (tang-x, tang-y) = if ctrl2 != none { + let ex = tx - ctrl2.at(0) + let ey = ty - ctrl2.at(1) + let ed = calc.sqrt(ex * ex + ey * ey) + if ed > 0 { (ex / ed, ey / ed) } else { (dx / len, dy / len) } + } else { + (dx / len, dy / len) + } + let hb = calc.min(head-back, len * 0.4) + let hax = tx - tang-x * hb + let hay = ty - tang-y * hb + + let shaft-stroke = (paint: paint, thickness: 0.018, dash: "dashed") + let head-stroke = (paint: paint, thickness: 0.018) + let mark-style = (end: ">", fill: paint, scale: 0.5) + + // Dashed shaft + if ctrl1 != none { + bezier((fx, fy), (hax, hay), ctrl1, ctrl2, stroke: shaft-stroke) + } else { + line((fx, fy), (hax, hay), stroke: shaft-stroke) + } + // Solid arrowhead + let tiny = 0.01 + line( + (hax - tang-x * tiny, hay - tang-y * tiny), + (tx, ty), + stroke: head-stroke, + mark: mark-style, + ) + } + } + }) + }) + } // context +} diff --git a/packages/preview/phonokit/0.6.1/grids.typ b/packages/preview/phonokit/0.6.1/grids.typ new file mode 100644 index 0000000000..0009963c28 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/grids.typ @@ -0,0 +1,114 @@ +#import "ipa.typ": ipa-to-unicode +#import "_config.typ": phonokit-font + +// Helper function to parse string-based input like "te2.ne1.see3" +#let parse-grid-string(input) = { + let units = input.split(".") + let parsed = () + + for unit in units { + if unit.len() > 0 { + // Extract the last character as the level + let level-str = unit.at(unit.len() - 1) + let level = int(level-str) + + // Extract everything except the last character as the syllable + let syllable = unit.slice(0, unit.len() - 1) + + parsed.push((text: syllable, level: level)) + } + } + + parsed +} + +// Main metrical grid function - creates metrical grid representations +// Supports two input formats: +// +// 1. String format (simple, not IPA-compatible): +// met-grid("te2.ne1.see3.Ti3.tans1") +// Format: syllable + level number, separated by dots +// +// 2. Array format (IPA-compatible): +// met-grid(("te", 2), ("ne", 1), ("see", 3)) +// met-grid(("te", 2), ("ne", 1), ("see", 3), ipa: true) // Auto-converts strings to IPA +// Format: array of (content, level) tuples +// +#let met-grid(..args, ipa: true) = { + let data = () + + // Determine input format + if args.pos().len() == 1 and type(args.pos().at(0)) == str { + // String format: "te2.ne1.see3" + data = parse-grid-string(args.pos().at(0)) + } else { + // Array format: ("te", 2), ("ne", 1), ... + for arg in args.pos() { + if type(arg) == array and arg.len() == 2 { + let text-content = arg.at(0) + let level = arg.at(1) + + // If ipa mode is enabled and text-content is a string, convert it + if ipa and type(text-content) == str { + text-content = context text(font: phonokit-font.get(), ipa-to-unicode(text-content)) + } + + data.push((text: text-content, level: level)) + } else { + return text(fill: red, weight: "bold")[⚠ Error: Each argument must be a (text, level) tuple] + } + } + } + + if data.len() == 0 { + return text(fill: red, weight: "bold")[⚠ Error: No data to display] + } + + // Find maximum level to determine number of rows + let max-level = 0 + for item in data { + if item.level > max-level { + max-level = item.level + } + } + + // Build the table rows from top to bottom + let rows = () + + // Create rows for each stress level (from highest to lowest) + for level in range(max-level, 0, step: -1) { + let row = () + for item in data { + if item.level >= level { + row.push($times$) + } else { + row.push([]) + } + } + rows.push(row) + } + + // Add the syllable row at the bottom + let syllable-row = () + for item in data { + if type(item.text) == str { + syllable-row.push(context text(font: phonokit-font.get(), item.text)) + } else { + syllable-row.push(item.text) + } + } + rows.push(syllable-row) + + // Create table with no borders, wrapped in box for inline placement + // baseline: 50% centers the grid vertically with text baseline + box( + baseline: 50%, + table( + columns: data.len(), + stroke: none, + align: center, + inset: 8pt, + ..rows.flatten() + ) + ) +} diff --git a/packages/preview/phonokit/0.6.1/hasse.typ b/packages/preview/phonokit/0.6.1/hasse.typ new file mode 100644 index 0000000000..772f64eb45 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/hasse.typ @@ -0,0 +1,444 @@ +// Hasse diagram visualization for OT constraint rankings +// Part of phonokit package + +#import "@preview/cetz:0.5.2" +#import "_config.typ": phonokit-font + +/// Create a Hasse diagram for Optimality Theory constraint rankings +/// +/// A Hasse diagram represents the partial order of constraint rankings, +/// showing only minimal domination relationships (transitive reduction). +/// Constraints higher in the diagram dominate those lower. +/// +/// Features: +/// - Supports partial orders (not all constraints need to be ranked) +/// - Handles floating constraints with no ranking relationships +/// - Automatically computes transitive reduction +/// - Auto-scales for complex hierarchies +/// +/// Arguments: +/// - rankings (array): Array of tuples representing rankings: +/// - Three-element tuple `(A, B, level)` means A dominates B, and A is at stratum `level` (REQUIRED) +/// - Four-element tuple `(A, B, level, style)` means A dominates B, A at stratum `level`, with line `style` +/// - Single-element tuple `(A,)` means A is floating (no ranking) +/// - Line styles: "solid" (default), "dashed", "dotted" +/// - Note: Level specification is REQUIRED for all edges to ensure proper stratification +/// - scale (number or auto): Scale factor for diagram (default: auto-scales based on complexity) +/// - node-spacing (number): Horizontal spacing between nodes (default: 2.5) +/// - level-spacing (number): Vertical spacing between levels (default: 1.5) +/// +/// Returns: A Hasse diagram showing the constraint hierarchy +/// +/// Example: +/// ``` +/// #hasse( +/// ( +/// ("Onset", "NoCoda", 0), +/// ("Onset", "Dep", 0), +/// ("Max", "Dep", 0), +/// ("Max", "NoCoda", 0), +/// ("Faith",) // floating constraint +/// ) +/// ) +/// +/// // With line styles +/// #hasse( +/// ( +/// ("Ident[F]", "Agree[place]", 0, "dashed"), // Level 0, dashed line +/// ("Dep", "Agree[vce]", 0), // Level 0, solid line (default) +/// ("Max", "Dep", 1, "dotted"), // Level 1, dotted line +/// ) +/// ) +/// ``` +#let hasse( + rankings, + scale: auto, + node-spacing: 2.5, + level-spacing: 1.5, +) = { + // Validate input + assert(type(rankings) == array, message: "rankings must be an array of tuples") + assert(rankings.len() > 0, message: "rankings array cannot be empty") + + // Extract all constraints and build graph structure + let all-constraints = () + let edges = () // (from, to) pairs + let floating = () + let user-specified-levels = (:) // Track user-specified levels + let edge-styles = (:) // Track line styles for edges + + for ranking in rankings { + assert(type(ranking) == array, message: "Each ranking must be a tuple (array)") + + if ranking.len() == 1 { + // Floating constraint + let constraint = ranking.at(0) + if constraint not in floating { + floating.push(constraint) + } + if constraint not in all-constraints { + all-constraints.push(constraint) + } + } else if ranking.len() >= 3 and ranking.len() <= 4 { + // Domination relationship (level specification is REQUIRED) + let from = ranking.at(0) + let to = ranking.at(1) + let level = ranking.at(2) + let style = "solid" + + // Validate that level is a number + assert(type(level) == int or type(level) == float, message: "Third element must be a level (number). Use (A, B, level) or (A, B, level, style)") + + if ranking.len() == 4 { + // Fourth element is style + style = ranking.at(3) + assert(type(style) == str, message: "Fourth element must be a line style string") + // Validate style + assert(style in ("solid", "dashed", "dotted"), message: "Line style must be 'solid', 'dashed', or 'dotted'") + } + + if (from, to) not in edges { + edges.push((from, to)) + } + if from not in all-constraints { + all-constraints.push(from) + } + if to not in all-constraints { + all-constraints.push(to) + } + + // Store user-specified level + user-specified-levels.insert(from, level) + + // Store edge style + edge-styles.insert(from + "->" + to, style) + } else { + assert(false, message: "Each ranking tuple must have 1 (floating), 3 (with level), or 4 (with level and style) elements") + } + } + + // Compute transitive reduction (remove redundant edges) + // For each edge (A, C), check if there's a path A -> B -> C + let reduced-edges = () + for edge in edges { + let (from, to) = edge + let is-redundant = false + + // Check if there's an intermediate node B such that (from, B) and (B, to) exist + for potential-middle in all-constraints { + if potential-middle != from and potential-middle != to { + let has-first = (from, potential-middle) in edges + let has-second = (potential-middle, to) in edges + if has-first and has-second { + is-redundant = true + break + } + } + } + + if not is-redundant { + reduced-edges.push(edge) + } + } + + // Compute levels using topological sort + // Start with user-specified levels + let constraint-levels = user-specified-levels + + // Level 0 = constraints with no incoming edges (top-ranked) + // Level k = constraints whose dominators are all at level < k + let unassigned = all-constraints.filter(c => c not in constraint-levels.keys()) + let current-level = 0 + + while unassigned.len() > 0 { + let assigned-this-round = () + + for constraint in unassigned { + // Check if all dominators (if any) are already assigned + let dominators = reduced-edges.filter(e => e.at(1) == constraint).map(e => e.at(0)) + + if dominators.len() == 0 { + // No dominators, can be assigned to current level + constraint-levels.insert(constraint, current-level) + assigned-this-round.push(constraint) + } else { + // Check if all dominators are assigned + let all-assigned = dominators.all(d => d in constraint-levels.keys()) + if all-assigned { + // Assign to one level below the maximum dominator level + let max-dom-level = calc.max(..dominators.map(d => constraint-levels.at(d))) + constraint-levels.insert(constraint, max-dom-level + 1) + assigned-this-round.push(constraint) + } + } + } + + // Remove assigned constraints + unassigned = unassigned.filter(c => c not in assigned-this-round) + current-level += 1 + + // Safety check to prevent infinite loop + if current-level > all-constraints.len() { + break + } + } + + // Assign floating constraints to the bottom + if floating.len() > 0 { + let max-level = if constraint-levels.len() > 0 { + calc.max(..constraint-levels.values()) + } else { + -1 + } + for fc in floating { + constraint-levels.insert(fc, max-level + 1) + } + } + + // Group constraints by level + let max-level-num = if constraint-levels.len() > 0 { + calc.max(..constraint-levels.values()) + } else { + 0 + } + + let levels = range(max-level-num + 1).map(i => ()) + for (constraint, level) in constraint-levels { + levels.at(level).push(constraint) + } + + // Minimize edge crossings using barycenter heuristic + // This reorders constraints at each level to reduce visual clutter + for iteration in range(3) { + // Multiple passes improve layout + // Top-down pass: order by average position of parents + for level-idx in range(1, levels.len()) { + let level-constraints = levels.at(level-idx) + + // Calculate barycenter (average parent position) for each constraint + let constraint-barycenters = () + for constraint in level-constraints { + let parents = reduced-edges.filter(e => e.at(1) == constraint).map(e => e.at(0)) + + if parents.len() > 0 { + // Find parent indices in previous level + let parent-indices = parents.map(p => { + let idx = levels.at(level-idx - 1).position(c => c == p) + if idx == none { 0 } else { idx } + }) + let barycenter = parent-indices.sum() / parents.len() + constraint-barycenters.push((constraint, barycenter)) + } else { + // No parents, keep original position + let original-idx = level-constraints.position(c => c == constraint) + constraint-barycenters.push((constraint, original-idx)) + } + } + + // Sort by barycenter + constraint-barycenters = constraint-barycenters.sorted(key: item => item.at(1)) + levels.at(level-idx) = constraint-barycenters.map(item => item.at(0)) + } + + // Bottom-up pass: order by average position of children + for level-idx in range(levels.len() - 1).rev() { + let level-constraints = levels.at(level-idx) + + // Calculate barycenter (average child position) for each constraint + let constraint-barycenters = () + for constraint in level-constraints { + let children = reduced-edges.filter(e => e.at(0) == constraint).map(e => e.at(1)) + + if children.len() > 0 { + // Find child indices in next level + let child-indices = children.map(c => { + let idx = levels.at(level-idx + 1).position(ch => ch == c) + if idx == none { 0 } else { idx } + }) + let barycenter = child-indices.sum() / children.len() + constraint-barycenters.push((constraint, barycenter)) + } else { + // No children, keep original position + let original-idx = level-constraints.position(c => c == constraint) + constraint-barycenters.push((constraint, original-idx)) + } + } + + // Sort by barycenter + constraint-barycenters = constraint-barycenters.sorted(key: item => item.at(1)) + levels.at(level-idx) = constraint-barycenters.map(item => item.at(0)) + } + } + + // Determine scale + let scale-factor = if scale == auto { + if all-constraints.len() > 8 { + 0.7 + } else if all-constraints.len() > 5 { + 0.85 + } else { + 1.0 + } + } else { + scale + } + + // Adjust node spacing based on constraint name lengths to prevent overlap + // Estimate text width: roughly 0.12 units per character in smallcaps at 10pt + let max-constraint-length = calc.max(..all-constraints.map(c => c.len())) + let estimated-width = max-constraint-length * 0.12 * scale-factor + let min-spacing = estimated-width + 0.4 // Add padding + let adjusted-node-spacing = calc.max(node-spacing, min-spacing) + + // Calculate layout positions for ranked constraints + let positions = (:) + for (level-idx, level-constraints) in levels.enumerate() { + let n = level-constraints.len() + let total-width = (n - 1) * adjusted-node-spacing + let start-x = -total-width / 2 + + for (i, constraint) in level-constraints.enumerate() { + let x = start-x + i * adjusted-node-spacing + let y = -level-idx * level-spacing + positions.insert(constraint, (x, y)) + } + } + + // Align one-to-one chains vertically for straight lines + // If a constraint has exactly one parent and that parent has exactly one child, + // align them vertically + for (constraint, pos) in positions { + let parents = reduced-edges.filter(e => e.at(1) == constraint).map(e => e.at(0)) + + if parents.len() == 1 { + let parent = parents.at(0) + let parent-children = reduced-edges.filter(e => e.at(0) == parent).map(e => e.at(1)) + + if parent-children.len() == 1 and parent in positions { + // One-to-one relationship: align x-coordinates + let parent-pos = positions.at(parent) + let (parent-x, parent-y) = parent-pos + let (child-x, child-y) = pos + + // Update child's x to match parent's x + positions.insert(constraint, (parent-x, child-y)) + } + } + // Note: For constraints with multiple parents, the barycenter heuristic + // already positioned them optimally to minimize crossings. Don't override. + } + + // Re-center the diagram for symmetry + // Calculate the average x position and shift to center at 0 + if positions.len() > 0 { + let all-x = positions.values().map(pos => pos.at(0)) + let min-x = calc.min(..all-x) + let max-x = calc.max(..all-x) + let center-offset = -(min-x + max-x) / 2 + + // Apply centering offset + let centered-positions = (:) + for (constraint, pos) in positions { + let (x, y) = pos + centered-positions.insert(constraint, (x + center-offset, y)) + } + positions = centered-positions + } + + // Draw the diagram + box(inset: 1.2em, baseline: 40%, cetz.canvas(length: scale-factor * 1cm, { + import cetz.draw: * + + // Draw edges first (so they appear behind nodes) + for edge in reduced-edges { + let (from, to) = edge + if from in positions and to in positions { + let (x1, y1) = positions.at(from) + let (x2, y2) = positions.at(to) + + // Get line style for this edge + let edge-key = from + "->" + to + let line-style = if edge-key in edge-styles { + edge-styles.at(edge-key) + } else { + "solid" + } + + // Scale stroke width with scale factor + let stroke-width = 0.8pt * scale-factor + + // Create stroke based on style + let stroke-style = if line-style == "dashed" { + (paint: black, thickness: stroke-width, dash: "dashed") + } else if line-style == "dotted" { + (paint: black, thickness: stroke-width, dash: "dotted") + } else { + stroke-width + black + } + + // Draw line (no arrowheads: Hasse diagrams encode direction by + // vertical position, so edges are plain lines) + line((x1, y1), (x2, y2), stroke: stroke-style) + } + } + + // Draw white rectangles behind constraint names (to create whitespace) + for (constraint, pos) in positions { + let (x, y) = pos + // Estimate text dimensions based on constraint name length + let name-length = constraint.len() + // Width: roughly 0.22 units per character (generous to ensure full coverage) + let text-width = name-length * 0.22 * scale-factor + // Height: based on font size (increased to cover actual rendered height) + let text-height = 0.5 * scale-factor + // Add padding + let padding = 0.50 + let rect-width = text-width + padding + let rect-height = text-height + padding + + // Draw rounded rectangle centered at constraint position + rect( + (x - rect-width / 2, y - rect-height / 2), + (x + rect-width / 2, y + rect-height / 2), + fill: white, + stroke: none, + radius: 0.6, + ) + } + + // Draw constraint names in smallcaps (but not text inside brackets) + for (constraint, pos) in positions { + let (x, y) = pos + + // Parse constraint name to apply smallcaps only outside brackets + let formatted-name = { + // Match pattern: text before bracket, bracket content, text after + let bracket-match = constraint.match(regex("^([^\[]*)\[([^\]]*)\](.*)$")) + + if bracket-match != none { + // Has brackets: smallcaps before, regular inside brackets, smallcaps after + let before = bracket-match.captures.at(0) + let inside = bracket-match.captures.at(1) + let after = bracket-match.captures.at(2) + + // Compose the parts using content block + [#smallcaps(before)\[#inside\]#if after != "" { smallcaps(after) }] + } else { + // No brackets: all smallcaps + smallcaps(constraint) + } + } + + content( + (x, y), + padding: 0.1, + anchor: "center", + context text( + font: phonokit-font.get(), + size: 10pt * scale-factor, + formatted-name, + ), + ) + } + })) +} diff --git a/packages/preview/phonokit/0.6.1/intonational.typ b/packages/preview/phonokit/0.6.1/intonational.typ new file mode 100644 index 0000000000..60c04987c7 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/intonational.typ @@ -0,0 +1,80 @@ +#import "_config.typ": phonokit-font + +/// Place a ToBI label above the current inline text position +/// +/// Designed to be placed inline immediately after the syllable or word being annotated. +/// The label floats above the text at the insertion point, optionally connected by +/// a vertical stem. +/// +/// Always pass labels as *strings* (e.g. `"H*"`), not bare content (`[H*]`), since +/// characters like `*` and `_` have special meaning in Typst markup. Use ASCII +/// hyphens for phrase accents (e.g. `"L-"`, `"L-H%"`); en/em dashes are +/// automatically normalized to hyphens. Double hyphens are converted to a +/// superscript en dash. +/// +/// Arguments: +/// - label (string): ToBI label +/// - line (boolean): draw a vertical stem connecting label to text (default: true) +/// - height (length): distance from text baseline to the bottom of the label (default: 2em) +/// - lift (length): gap between text baseline and stem bottom (default: 0.8em) +/// - gap (length): gap between stem top and label bottom (default: 0.22em) +/// - en-dash (boolean): render phrase-accent hyphens as en dashes (default: true) +/// +/// Example: +/// ``` +/// You're a were#int("*L")wolf?#h(1em)#int("H%", line: false) +/// ``` +#let int( + label, + line: true, + height: 2em, + lift: 0.8em, + gap: 0.22em, + en-dash: true, +) = context { + // Normalize all dash variants to ASCII hyphen, then output in the desired + // form. U+2011 (non-breaking hyphen, class GL) never breaks. For en-dash + // rendering, U+2060 (word joiner, class WJ) prepended to U+2013 suppresses + // any break opportunity around the en dash. + let single-dash = s => if en-dash { s.replace("-", "\u{2060}\u{2013}\u{2060}") } else { s.replace("-", "\u{2011}") } + let normalize-str = s => single-dash( + s.replace("\u{2011}", "-").replace("\u{2013}", "-").replace("\u{2014}", "-"), + ) + // "--" becomes a superscript en-dash (Zsiga-style phrase accent). + // Normalize other dashes first, then split on "--" before the + // single-dash replacement so the two hyphens aren't individually + // converted. Use a for loop to build content instead of .join() + // to avoid content-separator ambiguity. + let lbl-text = if type(label) == str { + let base = label.replace("\u{2011}", "-").replace("\u{2013}", "-").replace("\u{2014}", "-") + if base.contains("--") { + let parts = base.split("--").map(single-dash) + text(font: phonokit-font.get(), size: 0.8em, { + parts.at(0) + for i in range(1, parts.len()) { + super(size: 0.90em, [–]) + parts.at(i) + } + }) + } else { + text(font: phonokit-font.get(), size: 0.8em, normalize-str(label)) + } + } else { + text(font: phonokit-font.get(), size: 0.8em, label) + } + let lbl-w = measure(lbl-text).width + let lbl-h = measure(lbl-text).height + let lbl = box(width: lbl-w, lbl-text) + // baseline: 0pt places the box bottom at the text baseline, + // so the box extends upward to reserve space for the annotation. + // place() positions elements absolutely so surrounding alignment cannot shift them. + let stem-h = height - lift - gap + box(width: 0pt, height: height + lbl-h, baseline: 0pt, { + // Stem (anchored at bottom-left, offset upward by lift) + if line { + place(bottom + left, dy: -lift, rect(width: 0.05em, height: stem-h, fill: black, stroke: none)) + } + // Label (anchored at top, centered horizontally via dx) + place(top + left, dx: -lbl-w / 2, lbl) + }) +} diff --git a/packages/preview/phonokit/0.6.1/ipa.typ b/packages/preview/phonokit/0.6.1/ipa.typ new file mode 100644 index 0000000000..3e1cdcc590 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/ipa.typ @@ -0,0 +1,507 @@ +// Convert tipa-style notation to IPA Unicode (without font styling) +// This is exported separately so other modules can use the conversion logic +#let ipa-to-unicode(input) = { + // Define TIPA to IPA mappings + let mappings = ( + // CONSONANTS - Plosives + "p": "p", + "b": "b", + "t": "t", + "d": "d", + "\\:t": "ʈ", + "\\:d": "ɖ", + "\\textbardotlessj": "ɟ", + "\\barredj": "ɟ", + "c": "c", + "k": "k", + "g": "ɡ", + "q": "q", + "\\;G": "ɢ", + "?": "ʔ", + "P": "ʔ", + // CONSONANTS - Nasals + "m": "m", + "M": "ɱ", + "n": "n", + "\\:n": "ɳ", + "\\textltailn": "ɲ", + "N": "ŋ", + "\\;N": "ɴ", + "\\nh": "ɲ", + // CONSONANTS - Trills + "\\;B": "ʙ", + "r": "r", + "\\;R": "ʀ", + // CONSONANTS - Tap or Flap + "R": "ɾ", + "\\:r": "ɽ", + // CONSONANTS - Fricatives + "f": "f", + "v": "v", + "F": "ɸ", + "B": "β", + "T": "θ", + "D": "ð", + "s": "s", + "z": "z", + "S": "ʃ", + "Z": "ʒ", + "\\:s": "ʂ", + "\\:z": "ʐ", + "\\c{c}": "ç", + "C": "ç", + "J": "ʝ", + "x": "x", + "G": "ɣ", + "X": "χ", + "K": "ʁ", + "\\textcrh": "ħ", + "\\barredh": "ħ", + "Q": "ʕ", + "h": "h", + "H": "ɦ", + // CONSONANTS - Lateral Fricatives + "\\textbeltl": "ɬ", + "\\textlyoghlig": "ɮ", + "\\l3": "ɮ", + // CONSONANTS - Approximants + "V": "ʋ", + "\\*r": "ɹ", + "j": "j", + "\\textturnmrleg": "ɰ", + "\\mw": "ɰ", + "\\:R": "ɻ", + // CONSONANTS - Lateral Approximants + "l": "l", + "\\:l": "ɭ", + "L": "ʎ", + "\\;L": "ʟ", + // CONSONANTS - Velarized l + "\\darkl": "ɫ", + // OTHER CONSONANTS - Clicks + "\\!o": "ʘ", + "\\textpipe": "ǀ", + "!": "ǃ", + "\\textdoublebarpipe": "ǂ", + "\\doublebarpipe": "ǂ", + "||": "ǁ", + "\\textdoublepipe": "ǁ", + // OTHER CONSONANTS - Other + "\\textbarglotstop": "ʡ", + "\\barredP": "ʡ", + // OTHER CONSONANTS - Implosives + "\\!b": "ɓ", + "\\!d": "ɗ", + "\\!j": "ʄ", + "\\!g": "ɠ", + "\\!G": "ʛ", + // OTHER CONSONANTS - Additional Fricatives + "\\*w": "ʍ", + "\\texththeng": "ɧ", + "\\;H": "ʜ", + "\\textctz": "ʑ", + "\\textbarrevglotstop": "ʢ", + "\\barrevglotstop": "ʢ", + // OTHER CONSONANTS - Approximant/Flap + "\\textturnlonglegr": "ɺ", + "\\turnlonglegr": "ɺ", + // VOWELS - Close + "i": "i", + "I": "ɪ", + "y": "y", + "Y": "ʏ", + "1": "ɨ", + "0": "ʉ", + "W": "ɯ", + "u": "u", + "U": "ʊ", + // VOWELS - Close-mid/Mid + "e": "e", + "\\o": "ø", + "9": "ɘ", + "8": "ɵ", + "7": "ɤ", + "o": "o", + // VOWELS - Mid + "@": "ə", + // VOWELS - Open-mid + "E": "ɛ", + "\\oe": "œ", + "3": "ɜ", + "\\textcloseepsilon": "ɞ", + "\\closeepsilon": "ɞ", + "2": "ʌ", + "O": "ɔ", + // VOWELS - Near-open/Open + "\\ae": "æ", + "\\OE": "ɶ", + "a": "a", + "5": "ɐ", + "A": "ɑ", + "6": "ɒ", + "\\schwar": "ɚ", + "\\epsilonr": "ɝ", + // SUPRASEGMENTALS + "'": "ˈ", // primary stress + ",": "ˌ", // secondary stress + ":": "ː", // length mark + // SPACING + "\\s": " ", // space + // ARCHIPHONEMES escaped + "\\A": "A", + "\\B": "B", + "\\C": "C", + "\\D": "D", + "\\E": "E", + "\\F": "F", + "\\G": "G", + "\\H": "H", + "\\I": "I", + "\\J": "J", + "\\K": "K", + "\\L": "L", + "\\M": "M", + "\\N": "N", + "\\O": "O", + "\\P": "P", + "\\Q": "Q", + "\\R": "R", + "\\S": "S", + "\\T": "T", + "\\U": "U", + "\\V": "V", + "\\W": "W", + "\\X": "X", + "\\Y": "Y", + "\\Z": "Z", + // TIPA LONG-FORM ALTERNATIVES AND ADDITIONAL SYMBOLS + // A + "\\textturna": "ɐ", + "\\textscripta": "ɑ", + "\\textturnscripta": "ɒ", + "\\textsca": "ᴀ", + "\\;A": "ᴀ", + "\\textturnv": "ʌ", + // B + "\\texthtb": "ɓ", + "\\textscb": "ʙ", + "\\textcrb": "ƀ", + "\\textbarb": "ƀ", + "\\textbeta": "β", + "\\textsoftsign": "ь", + "\\texthardsign": "ъ", + // C + "\\textbarc": "ȼ", + "\\texthtc": "ƈ", + "\\v{c}": "č", + "\\textctc": "ɕ", + "\\textstretchc": "ʗ", + // D + "\\textcrd": "đ", + "\\textbard": "đ", + "\\texthtd": "ɗ", + "\\textrtaild": "ɖ", + "\\textctd": "ȡ", + "\\textdzlig": "ʣ", + "\\textdctzlig": "ʥ", + "\\textdyoghlig": "ʤ", + "\\dh": "ð", + // E + "\\textschwa": "ə", + "\\textrhookschwa": "ɚ", + "\\textreve": "ɘ", + "\\textsce": "ᴇ", + "\\;E": "ᴇ", + "\\textepsilon": "ɛ", + "\\textrevepsilon": "ɜ", + "\\textrhookrevepsilon": "ɝ", + "\\textcloserevepsilon": "ɞ", + // G + "\\textg": "ɡ", + "\\textbarg": "ǥ", + "\\textcrg": "ǥ", + "\\texthtg": "ɠ", + "\\textscg": "ɢ", + "\\texthtscg": "ʛ", + "\\textgamma": "ɣ", + "\\textbabygamma": "ɤ", + "\\textramshorns": "ɤ", + // H + "\\texthvlig": "ƕ", + "\\texthth": "ɦ", + "\\textturnh": "ɥ", + "4": "ɥ", + "\\textsch": "ʜ", + // I + "\\i": "ı", + "\\textbari": "ɨ", + "\\textiota": "ɩ", + "\\textlhti": "ɩ", + "\\textsci": "ɪ", + // J + "\\j": "ȷ", + "\\textctj": "ʝ", + "\\textscj": "ᴊ", + "\\;J": "ᴊ", + "\\v{\\j}": "ǰ", + "\\textObardotlessj": "ɟ", + "\\texthtbardotlessj": "ʄ", + // K + "\\texthtk": "ƙ", + "\\textturnk": "ʞ", + // L + "\\textltilde": "ɫ", + "\\textbarl": "ł", + "\\textrtaill": "ɭ", + "\\textOlyoghlig": "ɮ", + "\\textscl": "ʟ", + "\\textlambda": "λ", + "\\textcrlambda": "ƛ", + // M + "\\textltailm": "ɱ", + "\\textturnm": "ɯ", + // N + "\\textnrleg": "ƞ", + "\\ng": "ŋ", + "\\textrtailn": "ɳ", + "\\textctn": "ȵ", + "\\textscn": "ɴ", + // O + "\\textbullseye": "ʘ", + "\\textbaro": "ɵ", + "\\textscoelig": "ɶ", + "\\textopeno": "ɔ", + "\\textomega": "ω", + "\\textcloseomega": "ɷ", + // P + "\\textwynn": "ƿ", + "\\textthorn": "þ", + "\\th": "þ", + "\\texthtp": "ƥ", + "\\textphi": "ɸ", + // Q + "\\texthtq": "ʠ", + // R + "\\textfishhookr": "ɾ", + "\\textlonglegr": "ɼ", + "\\textrtailr": "ɽ", + "\\textturnr": "ɹ", + "\\textturnrrtail": "ɻ", + "\\textscr": "ʀ", + "\\textinvscr": "ʁ", + // S + "\\v{s}": "š", + "\\textrtails": "ʂ", + "\\textesh": "ʃ", + "\\textctesh": "ʆ", + // T + "\\texthtt": "ƭ", + "\\textlhookt": "ƫ", + "\\textrtailt": "ʈ", + "\\texttctclig": "ʨ", + "\\texttslig": "ʦ", + "\\texteshlig": "ʧ", + "\\textturnt": "ʇ", + "\\textctt": "ȶ", + "\\texttheta": "θ", + // U + "\\textbaru": "ʉ", + "\\textupsilon": "ʊ", + "\\textscu": "ᴜ", + "\\;U": "ᴜ", + // V + "\\textscriptv": "ʋ", + // W + "\\textturnw": "ʍ", + // X + "\\textchi": "χ", + // Y + "\\textturny": "ʎ", + "\\textscy": "ʏ", + // Z + "\\textcommatailz": "ʐ", + "\\v{z}": "ž", + "\\textrevyogh": "ʕ", + "\\textrtailz": "ʐ", + "\\textyogh": "ʒ", + "\\textctyogh": "ʓ", + "\\textcrtwo": "ƻ", + "\\textglotstop": "ʔ", + "\\textraiseglotstop": "ˀ", + "\\textinvglotstop": "ʖ", + "\\textrevglotstop": "ʕ", + ) + + // Define combining diacritics + // Forward-looking: precede the phoneme in input (e.g., \~ a → ã) + let forward_diacritics = ( + "\\~": "̃", // combining tilde (nasalization) + "\\r": "̥", // combining ring below (devoicing) + "\\v": "̩", // combining vertical line below (syllabic) + "\\t": "͡", // combining double inverted breve (tie bar for affricates) + "\\dental": "̪", // no trailing space + ) + + // Backward-looking: follow the phoneme in input (e.g., p \h → pʰ) + let backward_diacritics = ( + "\\*": "̚", // combining left angle above (unreleased) + "\\h": "ʰ", // modifier letter small h (aspirated) + "\\velar": "ˠ", + "\\palatal": "ʲ", + "\\labial": "ʷ", + "\\ej": "ʼ", // modifier letter apostrophe (ejective) + ) + + // Below-diacritics clash with descenders (e.g., ŋ̩). IPA convention: + // place them above instead (ŋ̍, ŋ̊). + let above_alternates = ( + "̩": "̍", // vertical line below -> vertical line above + "̥": "̊", // ring below -> ring above + "̪": "͆", // bridge below -> bridge above (dental) + ) + let descenders = ( + "g", "ɡ", "j", "p", "q", "y", "ç", "ŋ", "ɲ", "ɳ", "ɱ", "ɟ", "ʝ", + "ɣ", "ɰ", "ɽ", "ɻ", "ʐ", "ʂ", "ɖ", "ɭ", "ʈ", "ɠ", "ʄ", "ɥ", + "ʃ", "ʒ", "ʑ", "β", "ɸ", "ɧ", + "ɺ", // no descender, but marks read better above its long leg + "Q", // archiphoneme: its tail drops below the baseline + "ȷ", "ǰ", "ʞ", "ƞ", "ƥ", "ʠ", "ɼ", "ƿ", "þ", "ʆ", "ʓ", "ƫ", "ʤ", "ʧ", "ʥ", + "ʗ", // stretched c dips below the baseline + "ʖ", // inverted glottal stop: marks read better above + ) + let attach = (base, diacritic) => { + if diacritic in above_alternates and base in descenders { + above_alternates.at(diacritic) + } else { + diacritic + } + } + + // Split by spaces and process each token + let tokens = input.split(" ") + let result = "" + let i = 0 + let pending_diacritic = none + + while i < tokens.len() { + let token = tokens.at(i) + + // Check if this token is a forward-looking diacritic + if token in forward_diacritics { + // Store it to apply to next character + pending_diacritic = forward_diacritics.at(token) + } else if token in backward_diacritics { + // Apply immediately to previous character + result += backward_diacritics.at(token) + } else if token.contains("\\") { + // Backslash command + if token in mappings { + result += mappings.at(token) + // Apply pending diacritic if any + if pending_diacritic != none { + result += attach(mappings.at(token), pending_diacritic) + pending_diacritic = none + } + } else { + result += token + } + } else if token in mappings { + // No backslash, but the whole token is a mapping (e.g., "||") + result += mappings.at(token) + if pending_diacritic != none { + result += attach(mappings.at(token), pending_diacritic) + pending_diacritic = none + } + } else { + // No backslash: split into individual characters + let chars = token.clusters() + for (idx, char) in chars.enumerate() { + let out = if char in mappings { mappings.at(char) } else { char } + result += out + // Apply pending diacritic to first character only + if idx == 0 and pending_diacritic != none { + result += attach(out, pending_diacritic) + pending_diacritic = none + } + } + } + + i += 1 + } + + result +} + +// Main IPA function: converts tipa-style notation to IPA +#import "_config.typ": phonokit-font + +// Spacing fixes tuned by eye for Charis; other fonts keep their native spacing +#let charis-spacing(body) = { + // ʲ's tail curls left under the base glyph (pʲ, bʲ): nudge it right + show "ʲ": it => h(0.06em) + it + // f, ʃ, ʄ, ʎ, the hooked implosives and the rhotic vowels overhang to the + // right at the top and collide with superscripts + // Same for capitals (archiphonemes) whose serifed arms reach the right edge + show regex("[fʃʄʎɗɠʛɚɝƈƙʠƭƥʧTFVWY][ʰˠʲʷ]"): it => { + let (base, mark) = it.text.codepoints() + // The rhotic hook, ʧ and capital arms need less room, except before ʲ + let small = base in ("ɚ", "ɝ", "ʧ", "T", "F", "V", "W", "Y") + let gap = if small { 0.06em } else { 0.12em } + // ʲ already gets 0.06em from the rule above + if mark == "ʲ" and not small { gap -= 0.06em } + base + h(gap) + mark + } + // The unreleased mark (̚) collides with glyphs that reach the top right + // (f, ʃ, ʎ, hooks, glottals). It is combining, so it is re-hosted on a word + // joiner to be pushed right. + show regex("[fʃʄʎʔʕʡʢɓɗɠʛɦɧƈƙʠƭƥʖʧ]\u{031A}"): it => { + let (base, mark) = it.text.codepoints() + // Some reach further right than the rest + let gap = if base in ("ʄ", "ɗ", "ʛ") { 0.1em } else if base == "ʖ" { 0.07em } else { 0.05em } + base + h(gap) + "\u{2060}" + mark + } + // Ligatures anchor marks on their first letter; re-host them centered. + // Marks above are raised over the ascender. Widths are Charis advances. + let ligatures = (ʣ: 0.809, ʥ: 0.809, ʤ: 0.836, ʦ: 0.645, ʧ: 0.575, ʨ: 0.694, ƕ: 0.869) + show regex("[ʣʥʤʦʧʨƕ][\u{0303}\u{0325}\u{0329}\u{032A}\u{030A}\u{030D}\u{0346}]"): it => { + let (base, mark) = it.text.codepoints() + let above = mark in ("\u{0303}", "\u{030A}", "\u{030D}", "\u{0346}") + let shift = (0.272 - ligatures.at(base) / 2) * 1em + // ʦ and ʨ start with t, which is shorter than d, h or ʃ + let raise = if not above { 0em } else if base in ("ʦ", "ʨ") { 0.2em } else { 0.27em } + base + box(width: 0pt, baseline: -raise, move(dx: shift, "\u{2060}" + mark)) + } + // Glyphs whose anchors sit too low: marks above touch the hook, long leg or + // top curve. Re-host the mark on a word joiner, raise it, and center it. + // A hosted mark draws ~0.272em (half an o) left of the pen. + // base: (Charis advance width, raise) + let low-anchors = ( + ɧ: (0.555, 0.27em), + ɺ: (0.391, 0.33em), // stem top sits right under the mark + ʃ: (0.355, 0.25em), + ʆ: (0.355, 0.25em), + ) + show regex("[ɧɺʃʆ][\u{0303}\u{030A}\u{030D}\u{0346}]"): it => { + let (base, mark) = it.text.codepoints() + // ɧ̃ is fine as is + if base == "ɧ" and mark == "\u{0303}" { return it } + let (width, raise) = low-anchors.at(base) + // Shift with move(), not h(): extra inline width lets the mark wrap onto + // its own line in narrow containers (e.g., table cells) + base + box(width: 0pt, baseline: -raise, move(dx: (0.272 - width / 2) * 1em, "\u{2060}" + mark)) + } + body +} + +#let ipa(input) = { + let rendered = ipa-to-unicode(input) + context { + metadata(rendered) + let font = phonokit-font.get() + // The font may be a name, a fallback list, or (name: .., covers: ..) dicts + let primary = if type(font) == array { font.first() } else { font } + if type(primary) == dictionary { primary = primary.name } + let body = text(font: font, rendered) + if lower(primary).starts-with("charis") { charis-spacing(body) } else { body } + } +} diff --git a/packages/preview/phonokit/0.6.1/lib.typ b/packages/preview/phonokit/0.6.1/lib.typ new file mode 100644 index 0000000000..1badda9092 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/lib.typ @@ -0,0 +1,1234 @@ +// phonokit - a toolkit to create phonological representations +// Author: Guilherme D. Garcia +// +// This package provides: +// - IPA transcription with tipa-style input syntax +// - Prosodic structure visualization (syllables, moras, feet, prosodic words) +// - Autosegmental representations and processes (features and tones) +// - IPA vowel charts (trapezoid) with language inventories +// - 3D vowel space (rounding as depth) with adjustable rotation +// - IPA consonant tables (pulmonic) with language inventories +// - Optimality Theory (OT) tableaux with violation marking and shading +// - Harmonic Grammar (HG) tableaux with weighted harmony calculations +// - Noisy Harmonic Grammar (NHG) tableaux with stochastic simulations +// - Maximum Entropy (MaxEnt) grammar tableaux with probability calculations +// - SPE-style feature matrices for phonological representations +// - Feature-geometry trees (Clements & Hume 1995; Sagey 1986) + +// Import modules +#import "_config.typ": phonokit-init +#import "ipa.typ": * +#import "prosody.typ": * +#import "ot.typ": * +#import "hasse.typ": * +#import "extras.typ": * +#import "vowels.typ": * +#import "vowels-3d.typ": vowels-3d +#import "grids.typ": * +#import "consonants.typ": * +#import "features.typ": * +#import "sonority.typ": * +#import "autosegmental.typ": * +#import "multi-tier.typ": * +#import "sound-shift.typ": * +#import "ex.typ": ex, ex-num-label, ex-rules, subex-label +#import "intonational.typ": * +#import "geom.typ": * +#import "phonetics.typ": * + +/// Initialize phonokit settings +/// +/// Call this at the top of your document to configure package-wide settings. +/// Currently supports setting a custom font for all phonokit functions. +/// +/// Arguments: +/// - font (string): Font name to use for IPA rendering (default: "Charis") +/// +/// Example: +/// ``` +/// #import "@preview/phonokit:0.6.1": * +/// #phonokit-init(font: "Libertinus Serif") +/// ``` +#let phonokit-init = phonokit-init + +/// Convert tipa-style notation to IPA symbols +/// +/// Supports: +/// - IPA consonants and vowels +/// - Combining diacritics (\\~, \\r, \\v, \\t for nasal, devoiced, voiced, tie) +/// - Suprasegmentals (' and , for primary and secondary stress; : for length) +/// - Automatic character splitting for efficiency +/// +/// Example: `#ipa("'hEloU")` produces ˈhɛloʊ +/// +/// Arguments: +/// - input (string): tipa-style notation +/// +/// Returns: IPA symbols in the configured font (default: Charis) +#let ipa = ipa + +/// Visualize sonority profiles based on Parker (2011) +/// +/// Generates a visual sonority profile for a given phonemic transcription. +/// Phonemes are mapped to a vertical sonority axis (1-17) based on Parker's +/// acoustic scale and connected to show the sonority contour of the word. +/// +/// Features: +/// - Uses Parker (2011) hierarchy (e.g., Flaps > Laterals > Trills) +/// - Visualizes syllable boundaries using alternating background shading (white/gray) +/// - Automatic parsing of tipa-style input strings +/// +/// Arguments: +/// - word (string): Phonemic string in tipa-style (use "." for syllable boundaries) +/// - syl (none): Legacy parameter, kept for API compatibility +/// - stressed (int, optional): Index of stressed syllable (default: none) +/// - box-size (float): Size of individual phoneme boxes (default: 0.8) +/// - scale (float): Overall scale factor for the diagram (default: 1.0) +/// - y-range (array): Vertical axis range for plotting (default: (0, 17), matching Parker's scale) +/// - show-lines (bool): Connect phonemes with dashed lines (default: true) +/// +/// Returns: CeTZ drawing of the sonority profile +/// +/// Example: +/// ``` +/// // Visualizes "par.to.me" with 3 distinct background zones +/// #sonority("par.to.me") +/// +/// // Demonstrates Flap (R) > Lateral (l) ranking +/// #sonority("ka.Ra.lo") +/// ``` +/// +/// Note: Input is automatically truncated to the first 10 phonemes to prevent +/// visual overflow. +#let sonority = sonority + +/// Create an illustrative F1/F2 vowel cloud for teaching. +/// +/// Generates synthetic vowel tokens around built-in F1/F2 means and displays +/// them on an inverted F1/F2 diagram. +/// +/// Arguments: +/// - vowels (string): Tipa-style/IPA vowel string or a built-in language name +/// such as `"english"` +/// - source (array, optional): Tabular data from `csv(...)` with required +/// columns `vowel`, `f1`, and `f2`; a plain `csv("...")` table with a +/// header row is accepted +/// - sd (float): Spread of synthetic tokens in Hz +/// - sd2 (float, optional): Optional separate F2 spread in Hz +/// - n (int): Number of tokens per vowel (default: 10) +/// - seed (int): Deterministic seed for token placement (default: 1) +/// - labels (bool): Show vowel labels at the means (default: true) +/// - points (bool): Show vowel tokens (default: true) +/// - centers (bool): Show explicit `+` mean markers (default: false) +/// - ellipse (bool): Show 1-SD ellipses centered on the means (default: false) +/// - ellipse-stroke (stroke or auto): Stroke for SD ellipses +/// (default: `0.8pt + luma(190)`) +/// - ellipse-fill (fill): Fill for SD ellipses (default: none) +/// - grid (bool): Show the background grid (default: true) +/// - color-by-vowel (bool): Use a color cycle by vowel category (default: true) +/// - point-size (int): Marker size for synthetic tokens (default: 50) +/// - point-color (color or auto): Override token color (default: auto) +/// - point-alpha (ratio): Token transparency (default: 20%) +/// - vowel-color (color): Color of vowel labels (default: black) +/// - vowel-size (length): Font size of vowel labels (default: 18pt) +/// - vowel-weight (str): Font weight of vowel labels (default: `"regular"`) +/// - axis-size (length): Font size of axis labels and tick labels (default: 10pt) +/// - scale (float): Overall scale factor for the figure (default: 1.0) +/// - x-label (content): X-axis label (default: `[F2 (Hz)]`) +/// - y-label (content): Y-axis label (default: `[F1 (Hz)]`) +/// - width (length): Diagram width (default: 10cm) +/// - height (length): Diagram height (default: 7cm) +/// +/// Notes: +/// - In synthetic mode, ellipses visualize the user-provided spread parameters +/// (`sd`, `sd2`). +/// - In CSV mode, vowel means and ellipse sizes are computed from the observed +/// tokens in the input data. +/// +/// Example: +/// ``` +/// #formants("italian", scale: 0.6, ellipse: true, axis-size: 1.3em) +/// #formants(source: csv("extras/formants_sample.csv"), scale: 0.8) +/// ``` +#let formants = formants + +/// Draw a schematic voice onset time (VOT) timeline. +/// +/// Positive values show an aspiration interval between release and voicing +/// onset, zero values align release and voicing onset, and negative values +/// show prevoicing. +/// +/// Arguments: +/// - vot (number): Voice onset time in milliseconds +/// - closure (number): Closure duration in milliseconds (default: 40) +/// - vowel (number): Vowel region duration in milliseconds (default: 60) +/// - scale (float): Overall scale factor for the diagram (default: 1.0) +/// - label (auto or bool): Show the VOT value label (default: auto, equivalent +/// to true) +/// - keys (bool): Show event keys and legend (`R`, `V`, and compact interval +/// keys such as `A`; default: false) +/// - ui-lang (string): UI label language. Supported aliases: en/english, +/// fr/french, pt/portuguese (default: "en") +/// - closure-label, release-label, voicing-label, vowel-label, vot-label, +/// interval-label (auto, content, string, or none): Override individual +/// labels. `auto` uses localized defaults; `interval-label: auto` is the +/// localized aspiration label. Set `interval-label: none` to hide it +/// - interval-key (auto or content): Key used when `interval-label` does not +/// fit and `keys: true` (default: auto, usually `A` for aspiration) +/// - closure-segment, interval-segment, vowel-segment (string, content, or none): +/// Optional IPA/segment labels placed below the closure, interval, and +/// vowel regions. Strings use phonokit's IPA parser (default: none) +/// - segment-size (length): Font size for segment labels (default: 10pt) +/// - fill-closure (color): Closure region fill (default: luma(230)) +/// - fill-vowel (color): Vowel region fill (default: white) +/// - fill-aspiration (color): Positive-VOT interval fill (default: luma(245)) +/// - voicing (bool): Draw a schematic voicing/noise waveform (default: true) +/// - voicing-stroke (stroke or auto): Stroke for the voicing waveform (default: +/// auto) +/// +/// Region labels are placed above the boxes and are shown only when their +/// localized or overridden label fits the available region width. Positive-VOT +/// interval labels use the same fit logic; when they do not fit and `keys: +/// true`, the interval key is shown in the diagram and explained in the legend. +/// +/// Example: +/// ``` +/// #vot(65) +/// #vot( +/// 65, +/// closure-segment: "t", +/// interval-segment: "\\h", +/// vowel-segment: "\\ae", +/// ) +/// #vot(-60, voicing: false) +/// #vot(-60, ui-lang: "fr") +/// ``` +#let vot = vot + +/// Draw a single syllable's internal structure +/// +/// Visualizes only the syllable (σ) level with onset, rhyme, nucleus, and coda. +/// +/// Arguments: +/// - input (string): A single syllable (e.g., "ka" or "'va") +/// - scale (float): Scale factor for the diagram (default: 1.0) +/// - symbol (array): Domain labels top-down: (σ) (default: ("σ",)) +/// - distance (array, optional): Per-level vertical spacing adjustments as (level, factor) tuples, e.g. `((0, 1.2),)`; the factor scales the line between two levels, which never gets shorter than a small visible minimum (default: none) +/// - spacing (string): "auto" adapts vertical spacing to the tree (lines never get shorter going up; wide branches get more height); "fixed" uses constant spacing so trees with the same levels align (default: "auto") +/// +/// Returns: CeTZ drawing of syllable structure +/// +/// Example: `#syllable("\\t tS \\ae t", scale: 0.9)` +#let syllable = syllable + +/// Draw a mora-based structure +/// +/// Visualizes mora (μ) and syllable (σ) levels, showing how syllables +/// are decomposed into moras based on weight. +/// +/// Arguments: +/// - input (string): A single syllable (e.g., "kan" or "ka") +/// - coda (bool): Whether codas contribute to weight (default: false) +/// - scale (float): Scale factor for the diagram (default: 1.0) +/// - symbol (array): Domain labels top-down: (σ, μ) (default: ("σ", "μ")) +/// - distance (array, optional): Per-level vertical spacing adjustments as (level, factor) tuples, e.g. `((0, 1.2),)`; the factor scales the line between two levels, which never gets shorter than a small visible minimum (default: none) +/// - spacing (string): "auto" adapts vertical spacing to the tree (lines never get shorter going up; wide branches get more height); "fixed" uses constant spacing so trees with the same levels align (default: "auto") +/// +/// Returns: CeTZ drawing of moraic structure +/// +/// Examples: +/// - `#mora("\\t tS \\ae t", coda: true)` - Moraic representation with coda weight +/// - `#mora("tR \\~ a:m", coda: true)` - Long vowel represented with two moras +#let mora = mora + +/// Draw a foot with syllables +/// +/// Visualizes foot (Σ) and syllable (σ) levels. All syllables are part of the foot. +/// Stressed syllables are marked with an apostrophe before the syllable. +/// +/// Arguments: +/// - input (string): Syllables separated by dots (e.g., "ka.'va.lo") +/// - scale (float): Scale factor for the diagram (default: 1.0) +/// - symbol (array): Domain labels top-down: (Σ, σ) (default: ("Σ", "σ")) +/// - distance (array, optional): Per-level vertical spacing adjustments as (level, factor) tuples, e.g. `((0, 1.2),)`; the factor scales the line between two levels, which never gets shorter than a small visible minimum (default: none) +/// - spacing (string): "auto" adapts vertical spacing to the tree (lines never get shorter going up; wide branches get more height); "fixed" uses constant spacing so trees with the same levels align (default: "auto") +/// +/// Returns: CeTZ drawing of foot structure +/// +/// Example: `#foot("'p \\h \\ae.\\*r Is", scale: 0.9)` +#let foot = foot + +/// Draw a foot with moraic structure +/// +/// Visualizes foot (Σ), syllable (σ), and mora (μ) levels. Combines foot +/// structure with moraic weight representation. +/// Stressed syllables are marked with an apostrophe before the syllable. +/// +/// Arguments: +/// - input (string): Syllables separated by dots (e.g., "po.'Ral") +/// - coda (bool): Whether codas contribute to weight (default: false) +/// - scale (float): Scale factor for the diagram (default: 1.0) +/// - symbol (array): Domain labels top-down: (Σ, σ, μ) (default: ("Σ", "σ", "μ")) +/// - distance (array, optional): Per-level vertical spacing adjustments as (level, factor) tuples, e.g. `((0, 1.2),)`; the factor scales the line between two levels, which never gets shorter than a small visible minimum (default: none) +/// - spacing (string): "auto" adapts vertical spacing to the tree (lines never get shorter going up; wide branches get more height); "fixed" uses constant spacing so trees with the same levels align (default: "auto") +/// +/// Returns: CeTZ drawing of moraic foot structure +/// +/// Examples: +/// - `#foot-mora("po.'Ral", coda: true, scale: 0.9)` - Disyllabic foot with moraic structure +/// - `#foot-mora("'po.Ra.ma", coda: true, scale: 0.9)` - Dactyl with moraic structure +#let foot-mora = foot-mora + +/// Draw a prosodic word structure with explicit foot boundaries +/// +/// Visualizes prosodic word (PWd), foot (Σ), and syllable (σ) levels. +/// Use parentheses to mark foot boundaries. +/// Stressed syllables are marked with an apostrophe before the syllable. +/// +/// Arguments: +/// - input (string): Syllables with optional foot markers in parentheses +/// - foot (string): "R" (right-aligned) or "L" (left-aligned) for PWd alignment (default: "R") +/// - scale (float): Scale factor for the diagram (default: 1.0) +/// - symbol (array): Domain labels top-down: (ω, Σ, σ) (default: ("ω", "Σ", "σ")) +/// - distance (array, optional): Per-level vertical spacing adjustments as (level, factor) tuples, e.g. `((0, 1.2),)`; the factor scales the line between two levels, which never gets shorter than a small visible minimum (default: none) +/// - spacing (string): "auto" adapts vertical spacing to the tree (lines never get shorter going up; wide branches get more height); "fixed" uses constant spacing so trees with the same levels align (default: "auto") +/// +/// Returns: CeTZ drawing of prosodic structure +/// +/// Examples: +/// - `#word("('po.Ra).ma", scale: 0.9)` - One foot plus one unfooted syllable +/// - `#word("('po.Ra).('ma.pa)", foot: "R", scale: 0.9)` - Two feet, right-headed PWd +#let word = word + +/// Draw a prosodic word structure with moraic representation +/// +/// Visualizes prosodic word (PWd), foot (Σ), syllable (σ), and mora (μ) levels. +/// Combines prosodic word structure with moraic weight representation. +/// Use parentheses to mark foot boundaries. +/// Stressed syllables are marked with an apostrophe before the syllable. +/// +/// Arguments: +/// - input (string): Syllables with optional foot markers in parentheses +/// - foot (string): "R" (right-aligned) or "L" (left-aligned) for PWd alignment (default: "R") +/// - coda (bool): Whether codas contribute to weight (default: false) +/// - scale (float): Scale factor for the diagram (default: 1.0) +/// - symbol (array): Domain labels top-down: (ω, Σ, σ, μ) (default: ("ω", "Σ", "σ", "μ")) +/// - distance (array, optional): Per-level vertical spacing adjustments as (level, factor) tuples, e.g. `((0, 1.2),)`; the factor scales the line between two levels, which never gets shorter than a small visible minimum (default: none) +/// - spacing (string): "auto" adapts vertical spacing to the tree (lines never get shorter going up; wide branches get more height); "fixed" uses constant spacing so trees with the same levels align (default: "auto") +/// +/// Returns: CeTZ drawing of moraic prosodic structure +/// +/// Examples: +/// - `#word-mora("('po.Ra).ma", coda: true, scale: 0.9)` - Trochee with unfooted syllable +/// - `#word-mora("('po.Ra).('ma.pa)", foot: "L", coda: true, scale: 0.9)` - Two feet, left-headed PWd +#let word-mora = word-mora + +/// Create a metrical grid representation for stress and rhythm analysis +/// +/// Visualizes hierarchical stress levels using stacked × marks above syllables. +/// This follows metrical grid theory where each level represents a different +/// metrical prominence tier. +/// +/// Supports two input formats: +/// 1. String format (simple, but not IPA-compatible): +/// - Syllables separated by dots, each ending with a stress level number +/// 2. Array format (IPA-compatible): +/// - Array of (syllable, level) tuples +/// +/// Arguments: +/// - ..args: Either a single string or multiple (syllable, level) tuples +/// - String format: syllables separated by dots, each ending with stress level (e.g., "te2.ne1.see3") +/// - Tuple format: pairs of (syllable, level) passed as separate arguments +/// - ipa (bool): Automatically convert strings to IPA notation (default: true) +/// +/// Returns: Table showing syllables with stacked × marks indicating stress levels +/// +/// Examples: +/// - `#met-grid("bu3.tter1.fly2")` - String format +/// - `#met-grid( +/// ("b2", 3), +/// ("R \\schwar", 1), +/// ("flaI", 2), +/// )` - Array format +/// +/// Note: The string format uses numbers to indicate stress levels, which conflicts +/// with IPA numeric symbols. For IPA compatibility, use the array format. +#let met-grid = met-grid + + +/// Plot vowels on an IPA vowel trapezoid +/// +/// Visualizes vowels on the IPA vowel chart (trapezoid) with proper positioning +/// based on frontness, height, and roundedness. Supports language-specific +/// inventories, custom vowel sets, or tipa-style IPA notation. +/// +/// Arguments: +/// - vowel-string (string, optional): Vowel symbols to plot, a built-in language +/// name, or tipa-style IPA, passed positionally (e.g. `vowels("aeiou")` or +/// `vowels("spanish")`). Optional: you may omit it and pass `lang` instead +/// (e.g. `vowels(lang: "spanish")`). If both are given and the positional is a +/// language name, the positional wins. +/// - lang (string, optional): Built-in language whose inventory to plot, used when +/// no language-name positional is given (e.g. `vowels(lang: "spanish")`). Also +/// selects the nasal-vowel preset when the positional is a custom symbol string. +/// - width (float): Base width of trapezoid (default: 8) +/// - height (float): Base height of trapezoid (default: 6) +/// - rows (int): Number of horizontal grid lines (default: 3) +/// - cols (int): Number of vertical grid lines (default: 2) +/// - scale (float): Scale factor for entire chart (default: 0.7) +/// - nasals (bool): Draw schematic nasalized copies near their oral +/// counterparts. For preset languages, this currently adds only the French +/// nasal vowels; for custom strings, only vowels explicitly nasalized in the +/// input are shown in the nasal layer (default: false) +/// - arrows (array): List of (from-vowel, to-vowel) tuples for drawing directed +/// arrows between vowel positions (e.g. diphthongs). Each vowel string accepts +/// tipa-style notation. Unknown vowels are silently skipped. (default: ()) +/// - arrow-color (color): Color for arrow lines and heads (default: black) +/// - arrow-style (string): "solid" or "dashed" line style for arrows (default: "solid") +/// - curved (bool): Curve arrows with a quadratic bezier arc (default: false) +/// - shift (array): List of (vowel, x-offset, y-offset) tuples. Draws a copy of +/// the vowel symbol offset from its canonical trapezoid position by (x, y) in +/// CeTZ canvas units. If the vowel is already plotted, an additional copy is +/// drawn; otherwise it is created. Unknown vowels are silently skipped. (default: ()) +/// - shift-color (color): Color for shifted vowel symbols (default: gray) +/// - shift-size (length, optional): Font size for shifted vowels; none uses the +/// same size as regular vowels (default: none) +/// - highlight (array): List of tipa strings whose background circle is highlighted (default: ()) +/// - highlight-color (color): Circle color for highlighted vowels (default: luma(220)) +/// - circle-size (float): Multiplier for the radius of the highlight circles. +/// Arrows pointing at a highlighted vowel stop at its circle edge. Does not +/// affect the gaps cut in the grid lines behind vowels (default: 1) +/// - phoneme-colors (dictionary): Map vowel symbols to text colors. Keys accept +/// tipa-style notation and are matched after IPA conversion (default: (:)) +/// +/// Returns: CeTZ drawing of IPA vowel chart with positioned vowels +/// +/// Examples: +/// - `#vowels("english", scale: 0.6)` - Plot English vowel inventory +/// - `#vowels("french", scale: 0.6)` - Plot French vowel inventory +/// - `#vowels("aãioõu", nasals: true)` - Add only the nasal vowels marked in the custom inventory +/// - `#vowels("french", nasals: true)` - Add the French nasal vowels +/// - `#vowels("english", arrows: (("a", "U"), ("a", "I"), ("e", "I"), ("O", "I"), ("o", "U")), curved: true)` - Diphthong trajectories +/// - `#vowels("aeiou", phoneme-colors: ("i": red, "u": blue))` - Color selected phonemes +/// +/// Note: Diacritics and non-vowel symbols are ignored during plotting. Nasal +/// overlays are illustrative only and are not language-specific placements. +/// +/// Available languages: english, spanish, portuguese, italian, french, german, +/// japanese, russian, arabic +#let vowels = vowels + +/// Plot vowels in a rotatable 3D vowel space +/// +/// Brings the IPA vowel trapezoid into a prism along a rounding axis, +/// following Ladefoged & Maddieson (1996, p. 283). The front face of the prism +/// is unrounded and the back face is rounded. Front vowels sit on the left face, +/// back vowels on the right face, at a depth set by their degree of rounding: +/// /i/ is least rounded, /u/ most rounded, and the low vowels /a/ and /ɒ/ meet +/// halfway. Schwa is placed inside the prism. Edges hidden from the current +/// viewpoint are drawn dashed, and are recomputed for every angle. Vowels +/// farther from the viewer are drawn smaller (see `depth-cue`). +/// +/// Arguments: +/// - vowel-string (string, optional): Vowels to plot in tipa-style IPA, passed +/// positionally (e.g. `vowels-3d("ieau")`). Only i e E a u o O 6 @ +/// (i e ɛ a u o ɔ ɒ ə) are supported: either all nine or a subset of them. +/// Any other symbol, and any language name (e.g. "english"), stops +/// compilation with an error listing the supported vowels +/// (default: "ieEauoO6@", all nine vowels) +/// - rotation (angle or number): Spin around the vertical axis. 0deg is a straight-on +/// view, in which depth edges point straight up; positive angles send the +/// rounded face to the left, negative angles to the right. A plain number is +/// read as degrees, so `30` and `30deg` are equivalent +/// (default: 30deg, the Ladefoged & Maddieson view) +/// - tilt (angle or number): How far above the prism the viewer is; 0deg is eye level, +/// 90deg looks straight down, negative values look from below. A plain +/// number is read as degrees (default: 20deg) +/// - width (float): Base width of trapezoid (default: 8) +/// - height (float): Base height of trapezoid (default: 6) +/// - rows (int): Number of height levels; level lines wrap around all four +/// side faces (default: 3) +/// - cols (int): Number of front/back divisions on the front face (default: 2) +/// - scale (float): Scale factor for entire chart (default: 0.7) +/// - depth-cue (bool): Draws vowels farther from the viewer smaller, so the +/// view reads as 3D: in angled views such as the default, the farthest +/// vowel is drawn at 70% size. Only vowels +/// (symbols and dots) shrink; lines do not. The effect fades out +/// automatically as the view approaches a flat front or back view, which +/// then looks like the 2D trapezoid with all vowels at the same size. Top, +/// bottom and side views keep it. Use false to turn it off in every view +/// (default: true) +/// +/// Returns: CeTZ drawing of a 3D vowel prism +/// +/// Examples: +/// - `#vowels-3d()` - All nine vowels, Ladefoged & Maddieson view +/// - `#vowels-3d("ieEauoO6")` - Peripheral vowels only, no schwa +/// - `#vowels-3d("ieau@", rotation: -40deg, tilt: 35deg)` - Rotated view +/// - `#vowels-3d(rotation: 0deg, tilt: 0deg)` - Flat front view, as in `#vowels()` +/// - `#vowels-3d(depth-cue: false)` - All vowels at the same size +/// +/// Note: The rotation is computed at compile time, so each call produces a +/// static image. To show the prism rotating in a presentation, generate one +/// page per angle (e.g. `for r in range(30, 390, step: 10)`) inside a box of +/// fixed size so the figure stays centered. +#let vowels-3d = vowels-3d + +/// Plot consonants on an IPA consonant table +/// +/// Visualizes consonants on the pulmonic IPA consonant chart with proper +/// positioning by place and manner of articulation. Voiceless/voiced pairs +/// are shown left/right in each cell. Impossible articulations are grayed out. +/// +/// Arguments: +/// - consonant-string (string, optional): Consonant symbols to plot, a built-in +/// language name, or tipa-style IPA, passed positionally (e.g. +/// `consonants("ptk")` or `consonants("russian")`). Optional: you may omit it +/// and pass `lang` instead (e.g. `consonants(lang: "russian")`). If both are +/// given and the positional is a language name, the positional wins. +/// - lang (string, optional): Built-in language whose inventory to plot, used when +/// no language-name positional is given (e.g. `consonants(lang: "russian")`). +/// - ui-lang (string): UI label language. Supported aliases: en/english, fr/french, +/// pt/portuguese (default: "en") +/// - affricates (bool): Show affricate row after fricatives (default: false) +/// - aspirated (bool): Show aspirated plosive/affricate rows (default: false) +/// - abbreviate (bool): Use abbreviated place/manner labels (default: false) +/// - simplify (bool): Automatically drop empty rows and columns (default: false) +/// - delete-cols (array): 0-indexed column indices to remove (0=Bilabial ... 10=Glottal) +/// - delete-rows (array): 0-indexed row indices to remove (0=Plosive ... 7=Lateral approximant) +/// - cell-width (float): Width of each cell (default: 1.8) +/// - cell-height (float): Height of each cell (default: 0.9) +/// - label-width (float): Width of row labels (default: 3.5) +/// - label-height (float): Height of column labels (default: 1.2) +/// - scale (float): Scale factor for entire table (default: 0.7) +/// - phoneme-colors (dictionary): Map consonant symbols to text colors. Keys +/// accept tipa-style notation and are matched after IPA conversion (default: (:)) +/// +/// Returns: CeTZ drawing of IPA consonant table with positioned consonants +/// +/// Examples: +/// - `#consonants("italian", affricates: true, abbreviate: true)` - Italian inventory +/// - `#consonants("ts{ts}psS \\*r g{tS} {k \\h}", affricates: true, aspirated: true)` - Custom inventory +/// - `#consonants("english", affricates: true, simplify: true)` - Simplified English inventory +/// - `#consonants("italian", affricates: true, simplify: true, ui-lang: "fr")` - Localized labels +/// - `#consonants("ptk", phoneme-colors: ("p": red, "t": blue))` - Color selected phonemes +/// +/// Notes: +/// - /w/ (labiovelar) appears in both bilabial and velar columns when /ɰ/ is not present; otherwise only bilabial +/// - Affricates appear in a separate row when affricates: true (displayed without tie bars) +/// - Aspirated consonants appear in separate rows when aspirated: true (e.g., "Plosive (aspirated)") +/// - Both aspirated consonants and affricates must be wrapped with curly brackets: {p \\h} will produce an aspirated p, and {ts} will produce a voiceless alveolar affricate +/// - Diacritics and non-consonant symbols are ignored during plotting +/// +/// Available languages: all, english, spanish, french, german, italian, +/// japanese, portuguese, russian, arabic +#let consonants = consonants + +/// Create an Optimality Theory tableau +/// +/// Generates a formatted OT tableau with candidates, constraints, violations, +/// and shading for irrelevant cells after fatal violations. +/// +/// Arguments: +/// - input (string or content): The input form (can use IPA notation) +/// - candidates (array): Array of candidate forms (strings or content) +/// - constraints (array): Array of constraint names (strings) +/// - violations (array): 2D array of violation strings (use "*" for violations, "!" for fatal) +/// - winner (int): Index of the winning candidate (0-indexed) +/// - dashed-lines (array): Indices of constraints to show with dashed borders (optional) +/// - scale (number, optional): Scale factor for the tableau (default: none) +/// - shade (bool): Whether cells should be shaded after fatal violations (default: true) +/// - prosody-scale (float): Scale factor for prosodic structures in candidates (default: 0.5) +/// - letters (bool): Use letter labels (a, b, c, ...) for candidates (default: false) +/// - gloss (string, optional): Gloss text displayed below the input (default: none) +/// +/// Returns: Table showing OT tableau with winner marked by ☞ +/// +/// Example: +/// ``` +/// #tableau( +/// input: "kraTa", +/// candidates: ("kra.Ta", "ka.Ta", "ka.ra.Ta"), +/// constraints: ("Max", "Dep", "*Complex"), +/// violations: ( +/// ("", "", "*"), +/// ("*!", "", ""), +/// ("", "*!", ""), +/// ), +/// winner: 0, // <- Position of winning cand +/// dashed-lines: (0,) // <- Note the comma +/// ) +/// ``` +#let tableau = tableau + +/// Create a Maximum Entropy (MaxEnt) grammar tableau +/// +/// Generates a MaxEnt tableau showing harmony scores, probabilities, +/// and optional probability visualizations. +/// +/// Arguments: +/// - input (string or content): The input form +/// - candidates (array): Array of candidate forms +/// - constraints (array): Array of constraint names +/// - weights (array): Array of constraint weights (numbers) +/// - violations (array): 2D array of violation counts (numbers) +/// - visualize (bool): Whether to show probability bars (default: true) +/// - sort (bool): Whether to sort candidates by probability, most to least (default: false) +/// - scale (number, optional): Scale factor for the tableau (default: none) +/// - letters (bool): Use letter labels (a, b, c, ...) for candidates (default: false) +/// +/// Returns: Table showing MaxEnt tableau with H(x), P*(x), and P(x) columns +/// +/// Example: +/// ``` +/// #maxent( +/// input: "kraTa", +/// candidates: ("[kra.Ta]", "[ka.Ta]", "[ka.ra.Ta]"), +/// constraints: ("Max", "Dep", "Complex"), +/// weights: (2.5, 1.8, 1), +/// violations: ( +/// (0, 0, 1), +/// (1, 0, 0), +/// (0, 1, 0), +/// ), +/// visualize: true, +/// ) +/// ``` +#let maxent = maxent + +/// Create a Harmonic Grammar (HG) tableau +/// +/// Generates an HG tableau showing harmony scores calculated from +/// weighted constraint violations. HG is deterministic: the candidate +/// with the highest harmony wins. +/// +/// Arguments: +/// - input (string or content): The input form +/// - candidates (array): Array of candidate forms +/// - constraints (array): Array of constraint names +/// - weights (array): Array of constraint weights (numbers) +/// - violations (array): 2D array of violation counts (negative numbers) +/// - scale (number): Optional scale factor (default: auto-scales for >6 constraints) +/// - letters (bool): Use letter labels (a, b, c, ...) for candidates (default: false) +/// +/// Returns: Table showing HG tableau with constraint weights and h(y) harmony column +/// +/// Example: +/// ``` +/// #hg( +/// input: "kraTa", +/// candidates: ("[kra.Ta]", "[ka.Ta]", "[ka.ra.Ta]"), +/// constraints: ("Max", "Dep", "*Complex"), +/// weights: (2.5, 1.8, 1), +/// violations: ( +/// (0, 0, -1), +/// (-1, 0, 0), +/// (0, -1, 0), +/// ), +/// ) +/// ``` +/// +/// Note: h(y) = Σ(weight × violation). Candidate with highest (least negative) harmony wins. +#let hg = hg + +/// Create a Noisy Harmonic Grammar (NHG) tableau with symbolic noise +/// +/// Pedagogical version showing noise as symbolic formulas (e.g., "-n₁"). +/// Useful for teaching how noise affects harmony calculations. +/// +/// Arguments: +/// - input (string or content): The input form +/// - candidates (array): Array of candidate forms +/// - constraints (array): Array of constraint names +/// - weights (array): Array of constraint weights +/// - violations (array): 2D array of violation counts (negative numbers) +/// - probabilities (array): Optional array of probability values to display +/// - scale (number): Scale factor (default: auto-scales for >6 constraints) +/// - letters (bool): Use letter labels (a, b, c, ...) for candidates (default: false) +/// +/// Returns: Table with h(y), ε(y) (symbolic), and optional P(y) columns +/// +/// Example: +/// ``` +/// #nhg-demo( +/// input: "kraTa", +/// candidates: ("[kra.Ta]", "[ka.Ta]", "[ka.ra.Ta]"), +/// constraints: ("Max", "Dep", "*Complex"), +/// weights: (2.5, 1.8, 1), +/// violations: ( +/// (0, 0, -1), +/// (-1, 0, 0), +/// (0, -1, 0), +/// ), +/// probabilities: (0.673, 0.08, 0.247), +///) +/// ``` +#let nhg-demo = nhg-demo + +/// Create a Noisy Harmonic Grammar (NHG) tableau with Monte Carlo simulation +/// +/// Samples noise from N(0,1), calculates probabilities via simulation. +/// Noise is added to constraint weights, and the candidate with highest +/// noisy harmony wins each trial. +/// +/// Arguments: +/// - input (string or content): The input form +/// - candidates (array): Array of candidate forms +/// - constraints (array): Array of constraint names +/// - weights (array): Array of constraint weights +/// - violations (array): 2D array of violation counts (negative numbers) +/// - num-simulations (int): Number of Monte Carlo trials (default: 1000) +/// - seed (int, optional): Random seed for reproducibility (default: none) +/// - show-epsilon (bool): Whether to show epsilon column (default: true) +/// - scale (number): Scale factor (default: auto-scales for >6 constraints) +/// - letters (bool): Use letter labels (a, b, c, ...) for candidates (default: false) +/// +/// Returns: Table with h(y), optional ε(y) (one sample), and P(y) (from simulation) +/// +/// Example: +/// ``` +/// #nhg( +/// input: "kraTa", +/// candidates: ("[kra.Ta]", "[ka.Ta]", "[ka.ra.Ta]"), +/// constraints: ("Max", "Dep", "*Complex"), +/// weights: (2.5, 1.8, 1), +/// violations: ( +/// (0, 0, -1), +/// (-1, 0, 0), +/// (0, -1, 0), +/// ), +/// ) +/// ``` +/// +/// Note: Probabilities are estimated empirically. More simulations = more accurate +/// but slower compilation. Default 1000 is usually sufficient. +#let nhg = nhg + +/// Create a Hasse diagram for Optimality Theory constraint rankings +/// +/// Generates a visual representation of the partial order of constraint rankings. +/// +/// Features: +/// - Supports partial orders (not all constraints need to be ranked) +/// - Handles floating constraints with no ranking relationships +/// - Supports dashed and dotted line styles for different edge types +/// - Auto-scales for complex hierarchies +/// +/// Arguments: +/// - rankings (array): Array of tuples representing rankings: +/// - Three-element tuple `(A, B, level)` means A dominates B, and A is at stratum `level` (REQUIRED) +/// - Four-element tuple `(A, B, level, style)` means A dominates B, A at stratum `level`, with line `style` +/// - Single-element tuple `(A,)` means A is floating (no ranking) +/// - Line styles: "solid" (default), "dashed", "dotted" +/// - Note: Level specification is REQUIRED for all edges to ensure proper stratification +/// - scale (number or auto): Scale factor for diagram (default: auto-scales based on complexity) +/// - node-spacing (number): Horizontal spacing between nodes (default: 2.5) +/// - level-spacing (number): Vertical spacing between levels (default: 1.5) +/// +/// Returns: A Hasse diagram showing the constraint hierarchy +/// +/// Examples: +/// ``` +/// // Basic scenario +/// #hasse( +/// ( +/// ("*Complex", "Max", 0), +/// ("*Complex", "Dep", 0), +/// ("Onset", "Max", 0), +/// ("Onset", "Dep", 0), +/// ("Max", "NoCoda", 1), +/// ("Dep", "NoCoda", 1), +/// ), +/// scale: 0.9 +/// ) +/// ``` +#let hasse = hasse + +// SPE/Feature function +/// Create a feature matrix in SPE notation +/// +/// Displays phonological features in a vertical matrix with square brackets, +/// commonly used in Sound Pattern of English (SPE) style representations. +/// +/// Arguments: +/// - ..args: Features as separate arguments or comma-separated string +/// +/// Returns: Mathematical vector notation with features +/// +/// Examples: +/// - `#feat("+cons", "-son", "+voice")` - Three features as separate args +/// - `#feat("+cons,-son,+voice")` - Three features as comma-separated string +#let feat = feat + +/// Display complete distinctive feature matrix for an IPA segment +/// +/// Takes an IPA symbol and displays its complete distinctive feature specification +/// from Hayes (2009) Introductory Phonology. Features are shown in SPE-style +/// vertical matrix notation. +/// +/// Arguments: +/// - segment (string): IPA symbol using Unicode or tipa-style notation +/// - all (bool): Show all features including unspecified (0) values (default: false) +/// - ui-lang (string): UI label language. Supported aliases: en/english, fr/french, +/// pt/portuguese (default: "en") +/// +/// Returns: Complete feature matrix in SPE notation +/// +/// Examples: +/// - `#feat-matrix("p")` - Shows feature matrix for /p/ +/// - `#feat-matrix("\\ae")` - Shows feature matrix for /æ/ +/// - `#feat-matrix("\\t tS")` - Affricate using tipa-inspired notation +/// - `#feat-matrix("i", all: true)` - Shows all features including 0 values +/// +/// Note: Based on Hayes (2009) feature system. Includes manner, laryngeal, +/// and place features for consonants; syllabic, height, backness, and rounding +/// features for vowels. +#let feat-matrix = feat-matrix + +/// Create an autosegmental representation +/// +/// Generates an autosegmental representation visualizing features or tones +/// on a separate tier from segments. Supports spreading (one-to-many associations), +/// delinking, multiple linking, and floating features/tones. Ideal for illustrating +/// phonological processes like tone spreading, vowel harmony, or feature geometry. +/// +/// Arguments: +/// - segments (array): Segment strings (use "" for empty timing slots) +/// - features (array): Feature/tone labels corresponding to segments (use "" for no association) +/// - links (array): Tuples of (feature-index, segment-index) for association lines (default: ()) +/// - delinks (array): Tuples of (feature-index, segment-index) for delinking marks (default: ()) +/// - spacing (float): Horizontal spacing between segments (default: 1.5) +/// - arrow (bool): Show arrow between representations (for process diagrams) (default: false) +/// - tone (bool): Whether the representation shows tones vs features (default: false) +/// - highlight (array): Indices of segments to highlight with background color (default: ()) +/// - float (array): Indices of floating (unassociated) features/tones (default: ()) +/// - multilinks (array): Tuples of (feature-index, (seg1, seg2, ...)) for one-to-many links (default: ()) +/// - baseline (ratio): Vertical alignment of the box (default: 40%) +/// - gloss (string): Optional gloss text below baseline (default: "") +/// - dash (string): Line style for dashed association lines (default: "dashed") +/// +/// Returns: Autosegmental representation +/// +/// Examples: +/// ``` +/// // Basic tone spreading: L tone spreads to multiple syllables +/// #autoseg( +/// ("a", "", "g", "a", "f", "i"), +/// features: ("L", "", "", "H", "", ""), +/// links: ((0, 3),), +/// delinks: ((3, 3),), +/// tone: true, +/// spacing: 0.5, +/// multilinks: ((3, (3, 5)),), +/// ) +/// +/// // Feature spreading with arrow (showing phonological process) +/// #autoseg( +/// ("t", "a", "n"), +/// features: ("+nasal", "", ""), +/// links: ((0, 0),), +/// arrow: true, +/// ) +/// +/// // Floating tone with highlighting +/// #autoseg( +/// ("m", "a", "m", "a"), +/// features: ("H", "L", "", ""), +/// float: (0,), +/// highlight: (1, 3), +/// tone: true, +/// ) +/// ``` +/// +/// Note: Index numbering is 0-based. Use empty strings "" in segments or features +/// arrays to create timing slots without content or features without associations. +#let autoseg = autoseg + +/// Create a multi-tier phonological representation +/// +/// Draws N-tier diagrams for CV phonology, skeletal tier structures, and other +/// multi-level representations. Each tier is a row of labels connected by +/// association lines. Supports auto-linking, floating elements, highlighting, +/// dashed lines, and delinking marks. +/// +/// Arguments: +/// - levels (array): Array of arrays; each inner array is a tier of label strings (use "" for empty positions). +/// Entries can be "label", ("label", col) for fractional columns, or ("label", col, level) for fractional levels. +/// - links (array): Extra solid lines as either `((level1, col1), (level2, col2))` +/// tuples or `("name1", "name2")` pairs (default: ()) +/// - dashed (array): Dashed lines as either coordinate tuples or string-name pairs (default: ()) +/// - delinks (array): Cross marks on connections as either coordinate tuples or string-name pairs (default: ()) +/// - arrows (array): Rectangular-path arrows as either coordinate tuples or string-name pairs (default: ()). +/// Top-level arrows arc above; bottom-level arrows arc below. Arrowhead at destination. +/// - arrow-delinks (array): Indices of arrows that should have a delink mark (||) at the midpoint (default: ()) +/// - float (array): Positions excluded from auto-linking as `(level, col)` tuples or `"name"` strings (default: ()) +/// - highlight (array): Positions with circle highlight as `(level, col)` tuples or `"name"` strings (default: ()) +/// - ipa (array): Level indices whose labels should be rendered as IPA (default: ()) +/// - tier-labels (array): Labels for tiers as (level, "label") tuples, placed to the right (default: ()) +/// - spacing (float): Horizontal spacing between columns (default: 1.5) +/// - level-spacing (float): Vertical spacing between tiers (default: 1.2) +/// - stroke-width (length): Line thickness (default: 0.05em) +/// - baseline (ratio): Vertical alignment (default: 40%) +/// - scale (float): Uniform scale factor (default: 1.0) +/// - show-grid (bool): Show background grid for debugging layout (default: false) +/// - show-refs (bool): Show node references below nodes for debugging; string nodes use generated names and content nodes fall back to coordinates (default: false) +/// +/// Returns: Multi-tier phonological representation +/// +/// Example: +/// ``` +/// // From Goad (2012) +/// #multi-tier( +/// levels: ( +/// ("O", "R", "", "O", "R", "O", "R"), +/// ("", "N1", "", "", "N2", "", "N3"), +/// ("", "x", "x", "x", "x", "x", "x"), +/// ("", "", "s", "t", "E", "m", ""), +/// ), +/// links: ( +/// ("r2", "x2"), +/// ), +/// ipa: (3,), +/// arrows: ( +/// ("t1", "s1"), +/// ("r2", "r1"), +/// ), +/// arrow-delinks: ( +/// (1,) +/// ), +/// spacing: 1, +/// ) +/// ``` +/// +/// Note: Trailing digits in labels are automatically rendered as subscripts +/// (e.g., "O1" becomes O₁). Standalone "x" is rendered as "×" (multiplication sign). +/// Non-empty labels also receive automatic reference names in reading order +/// (e.g., `sigma1`, `sigma2`, `x3`), which can be used in `links`, `dashed`, +/// `delinks`, `float`, `highlight`, and `arrows`. +#let multi-tier = multi-tier + +/// Create free-positioned sound-shift diagrams +/// +/// Draws IPA labels at arbitrary 2D positions and connects them with arrows. +/// This is useful for historical or schematic shift diagrams that are awkward +/// to represent in an IPA trapezoid. +/// +/// Arguments: +/// - nodes (array): Array of dictionaries describing nodes. Each node needs +/// `at: (x, y)` and `label:`. When `label` is a string, it also serves as +/// the node identifier by default. Use `id:` only when labels are duplicated +/// or when the visible label should differ from the reference key. +/// - arrows (array): Array of arrow specs. Each arrow can be a `(from, to)` +/// tuple or a dictionary with `from:` and `to:`. Endpoints can be node ids +/// or raw coordinate pairs. +/// - highlights (array): Node ids or coordinate pairs to highlight with a +/// background circle (default: ()) +/// - node-size (length): Default IPA font size for nodes (default: 2.2em) +/// - text-fill (color): Default node color (default: black) +/// - highlight-fill (fill): Default node/highlight fill (default: `luma(230)`) +/// - highlight-radius (float): Default circle radius in canvas units (default: 0.42) +/// - arrow-color (color): Default arrow color (default: black) +/// - arrow-style (str): Default arrow style: `"solid"`, `"dashed"`, or `"dotted"` +/// - arrow-width (length): Default arrow stroke width (default: 0.8pt) +/// - arrow-size (float): Default arrowhead scale (default: 1.0) +/// - curved (bool): Curve arrows by default (default: false) +/// - curve (float): Default curvature multiplier for curved arrows (default: 0.45) +/// - scale (float): Uniform diagram scale factor (default: 1.0) +/// +/// Returns: A CeTZ-based sound-shift diagram +/// +/// Example: +/// ``` +/// #sound-shift( +/// nodes: ( +/// (label: "I", at: (-4.2, 2.8)), +/// (label: "E", at: (-1.9, 1.0)), +/// (label: "2", at: (0.9, 1.0)), +/// (label: "O", at: (3.8, 1.0)), +/// (label: "A", at: (2.4, -1.5)), +/// (label: "\\ae", at: (-1.0, -1.5)), +/// ), +/// arrows: ( +/// ("E", "2"), +/// ("2", "O"), +/// ("O", "A"), +/// ("A", "\\ae"), +/// ("I", "E"), +/// (from: "\\ae", to: "I", curved: true, curve: 0.28), +/// ), +/// scale: 0.7, +/// ) +/// ``` +#let sound-shift = sound-shift + +/// Create a numbered linguistic example +/// +/// Generates numbered examples (1), (2), etc. similar to linguex in LaTeX. +/// Wrap content directly for a single example, or use list syntax for +/// automatically lettered sub-examples. Use `labels` to make individual +/// sub-examples referenceable. +/// +/// Arguments: +/// - body (content): The example content +/// - number-dy (length): Vertical offset for the number (optional; default: 0.4em) +/// - caption (string): Caption for outline (hidden in document; optional) +/// - title (string, optional): Title for the example (default: none) +/// - labels (array): Array of labels for sub-examples (default: ()) +/// - columns (array): Column widths for data columns (default: ()) +/// +/// Returns: Numbered example that can be labeled and referenced +/// +/// Example: +/// ``` +/// #ex(caption: "A phonology example", labels: (, ), columns: (5em, 2em, 5em))[ +/// - #ipa("/anba/") & #a-r & #ipa("[amba]") +/// - #ipa("/anka/") & #a-r & #ipa("[aNka]") +/// ] +/// ``` +#let ex = ex + +/// Show rules for linguistic examples +/// +/// Apply this to enable proper reference formatting for ex(). +/// References render as (1), (1a), (1b), etc. +/// +/// Usage: `#show: ex-rules` +#let ex-rules = ex-rules + +/// Display the current example number inside an ex() body (legacy table mode). +/// +/// Use as the first-column cell of a table when managing numbering manually. +/// See also subex-label(). +#let ex-num-label = ex-num-label + +/// Display the next sub-example label (a., b., ...) inside an ex() body +/// (legacy table mode). Attach a label for cross-referencing, e.g. +/// `[#subex-label()]`. +#let subex-label = subex-label + +/// Arrow symbols for phonological rules and processes +/// +/// Convenience symbols for showing derivations, mappings, and processes. +/// All arrows use New Computer Modern font for consistent styling. +/// +/// Available arrows: +/// - `#a-r` → right arrow +/// - `#a-l` ← left arrow +/// - `#a-u` ↑ up arrow +/// - `#a-d` ↓ down arrow +/// - `#a-lr` ↔ bidirectional arrow +/// - `#a-ud` ↕ vertical bidirectional arrow +/// - `#a-sr` ↝ squiggly right arrow +/// - `#a-sl` ↜ squiggly left arrow +/// - `#a-r-large` → large right arrow with horizontal spacing +/// +/// Example: `#ipa("/anba/") #a-r #ipa("[amba]")` produces /anba/ → [amba] +#let a-r = a-r +#let a-l = a-l +#let a-u = a-u +#let a-d = a-d +#let a-lr = a-lr +#let a-ud = a-ud +#let a-sr = a-sr +#let a-sl = a-sl +#let a-r-large = a-r-large + +/// Upright Greek symbols for phonological notation +/// +/// Convenience bindings for commonly used Greek letters in phonology. +/// These render upright in text mode (unlike math-mode `$sigma$` which italicizes). +/// +/// Lowercase: +/// - `#alpha` α, `#beta` β, `#gamma` γ, `#delta` δ +/// - `#lambda` λ, `#mu` μ, `#phi` φ, `#pi` π +/// - `#sigma` σ, `#tau` τ, `#omega` ω +/// +/// Uppercase: +/// - `#cap-sigma` Σ (foot), `#cap-phi` Φ (phonological phrase), `#cap-omega` Ω (utterance) +/// +/// Example: `The syllable #sigma contains an onset and a rhyme.` +#let alpha = alpha +#let beta = beta +#let gamma = gamma +#let delta = delta +#let lambda = lambda +#let mu = mu +#let phi = phi +#let pi = pi +#let sigma = sigma +#let tau = tau +#let omega = omega +#let cap-phi = cap-phi +#let cap-sigma = cap-sigma +#let cap-omega = cap-omega + +/// Create an underline blank for fill-in exercises or SPE rules +/// +/// Generates a horizontal line (underline) useful for worksheets, +/// exercises, or indicating missing/redacted content. +/// +/// Arguments: +/// - width (length): Width of the blank line (default: 2em) +/// +/// Returns: A box with bottom stroke +/// +/// Example: `The word #blank() means "house".` +#let blank = blank + +/// Mark extrametrical content with angle brackets +/// +/// Wraps content in ⟨angle brackets⟩ to indicate extrametricality +/// in metrical phonology representations. +/// +/// Arguments: +/// - content: The content to mark as extrametrical +/// +/// Returns: Content wrapped in ⟨⟩ +/// +/// Example: `#extra[tion]` produces ⟨tion⟩ +#let extra = extra + +/// Place a ToBI intonation label above the current inline text position +/// +/// Designed to be placed inline immediately after the syllable or word being annotated. +/// The label floats above the text at the insertion point, optionally connected by +/// a vertical stem. +/// +/// Arguments: +/// - label (string): ToBI label, e.g., "*L", "H%", "L+H*", "!H*" +/// - line (boolean): draw a vertical stem connecting label to text (default: true) +/// - height (length): stem length — controls how far above the text the label sits (default: 2em) +/// - lift (length): gap between stem bottom and text baseline (default: 0.8em) +/// - gap (length): horizontal gap around the annotation (default: 0.22em) +/// - en-dash (bool): render dashes as en-dashes instead of non-breaking hyphens (default: true) +/// +/// Example: +/// ``` +/// You're a we#int("*L")rewolf?#h(2em)#int("H%", line: false) +/// ``` +#let int = int + +/// Draw a feature-geometry tree for a consonant or vocoid +/// +/// Produces a hierarchical diagram following Clements & Hume (1995) +/// or Sagey (1986). All feature nodes are optional; parent nodes are inferred +/// automatically from their children. +/// +/// Use `ph` to load a built-in segment preset and optionally override individual +/// features. The preset label (e.g. "/i/") is shown above the root unless +/// `segment` is given. +/// +/// Arguments: +/// - ph (str): Segment preset key in tipa-style notation. Supported segments +/// include common vowels (a e i o u E O I U y W 7 \o \oe 2 A 6 @ 1 0 \ae) +/// and consonants (p b t d k g f v s z S Z n m N \N j h ? T D x G F B V M +/// \:t \:d \:s \:z \:n r l J C \T). Wrap in slashes/brackets to override the +/// auto segment label: `ph: "/a/"`. +/// - model (str): `"ch"` (default) for Clements & Hume 1995 (aperture nodes for +/// height); `"sagey"` for Sagey 1986 (dorsal sub-features for height/backness, +/// `[round]` under labial). Affects preset vowels only; consonant presets are +/// identical in both models. +/// - ui-lang (string): UI label language. Supported aliases: en/english, fr/french, +/// pt/portuguese (default: "en") +/// - root (array): Root matrix features, e.g. `("+son", "-vocoid")`. +/// - laryngeal (bool): Show "laryngeal" class node explicitly. +/// - nasal (bool, str): `[nasal]`. Values: `true` → `[nasal]`, +/// `"+"` → `[+nasal]`, `"-"` → `[−nasal]`. +/// - spread (bool): `[spread glottis]` under laryngeal. +/// - constricted (bool): `[constricted glottis]` under laryngeal. +/// - voice (bool, str): `[voice]` under laryngeal. Same sign convention as `nasal`. +/// - continuant (bool, str): `[continuant]` under oral cavity. Same sign convention. +/// Pass an array of two values for affricates: `continuant: ("-", "+")`. +/// - lateral (bool, str): `[lateral]`. Same sign convention as `nasal`. Attaches +/// under oral cavity (CH model) or under coronal (Sagey model). +/// - labial (bool, array): `[labial]`. Array adds sub-features: +/// `labial: ("round",)`. +/// - coronal (bool, array): `[coronal]`. Array replaces `anterior`/`distributed`: +/// `coronal: ("+ant", "-distr")`. +/// - anterior (bool, str): `[anterior]` under coronal. Same sign convention. +/// - distributed (bool): `[distributed]` under coronal. +/// - dorsal (bool, array): `[dorsal]`. Array adds sub-features (Sagey-style): +/// `dorsal: ("+hi", "-back")`. +/// - radical (bool): `[rad]` (pharyngeal/radical place). +/// - vocalic (bool): Show "vocalic" class node (vocoid branch). +/// - vplace (bool): Show "V-place" under vocalic. Inferred automatically when +/// vocalic is active and any place feature is supplied. +/// - aperture (bool, array): "aperture" node under vocalic (CH model). Array of +/// up to 3 values controls `[open1]`/`[open2]`/`[open3]`: +/// `aperture: ("-", "+", "-")` → close-mid height. +/// - tense (bool, str): `[tense]` under vocalic. Same sign convention as `nasal`. +/// - scale (number): Uniform scale factor (default: 1.0). +/// - position (array): Manual layout tweaks. Each entry: `("node-key", dx, dy)`. +/// Node keys are bare argument names, e.g. `"continuant"`, `"oral-cavity"`. +/// - delinks (array): Node keys whose line to their parent is replaced with a +/// delink mark, e.g. `delinks: ("c-place",)`. +/// - segment (content): Label shown above root. Defaults to the `ph` value +/// wrapped in slashes when `ph` is set. +/// - prefix (string): Prefix text before the segment label (default: "") +/// - suffix (string): Suffix text after the segment label (default: "") +/// - highlight (array): Node names to highlight; all others are dimmed. +/// - timing (auto, false, array, or string): Timing tier specification. `auto` infers +/// from `ph` (e.g., long vowels get two timing slots), `false` hides the tier (default: auto) +/// +/// Returns: CeTZ drawing of the feature-geometry tree +/// +/// Examples: +/// ``` +/// // Preset segment +/// #geom(ph: "i") +/// +/// // Manual consonant: voiceless alveolar stop +/// #geom(root: ("-son", "-approx", "-vocoid"), +/// coronal: true, anterior: "+", voice: "-", continuant: "-", +/// segment: "/t/") +/// +/// // Sagey-model vowel /y/ +/// #geom(ph: "y", model: "sagey", scale: 1.2) +/// ``` +#let geom = geom + +/// Draw two or more feature-geometry trees side by side with optional inter-tree arrows +/// +/// Each tree is specified as a spec dict (the same keys as `geom()`, all optional). +/// The `model` and `scale` parameters apply uniformly to all trees. +/// +/// Cross-tree arrows connect nodes by their anchor names. Node names are formed +/// by lowercasing the argument name, replacing spaces with hyphens, and appending +/// the 1-based tree index: `"labial1"`, `"oral-cavity2"`, `"c-place1"`. +/// +/// Arguments: +/// - ..trees (arguments): Positional spec dicts, one per tree. Each may include +/// a `ph` key to load a preset, plus any `geom()` keys to override features. +/// A per-tree `scale` key (number) scales that tree's coordinates and font +/// size relative to the group `scale`. +/// - arrows (array): Cross-tree arrows. Each entry is either `(from, to)` or a +/// dict `(from: str, to: str, color: color, ctrl: (number, number))`. +/// - `ctrl`: two Y-lifts `(lift1, lift2)`, one per endpoint. Positive lifts +/// the departure upward; negative dips the arrival below the target. +/// Overrides `curved` when set. +/// All keys except `from`/`to` are optional. +/// - gap (number): Canvas-unit gap between trees (default: 1.5). +/// - scale (number): Uniform scale factor for the whole group (default: 1.0). +/// - ui-lang (string): UI label language. Supported aliases: en/english, fr/french, +/// pt/portuguese (default: "en") +/// - model (str): `"ch"` (default) or `"sagey"`. Applies to all trees. +/// - position (array): Layout tweaks. Each entry: `("node-key-with-index", dx, dy)`, +/// e.g. `("continuant1", -0.2, 0.3)`. Arrows follow the adjusted positions. +/// - delinks (array): Node anchor names (with tree index) whose parent line is +/// replaced with a delink mark, e.g. `delinks: ("c-place1",)`. +/// - curved (bool): Draw arrows as obstacle-avoiding Bézier curves (default: false). +/// - highlight (array): Node anchor names to highlight; all others are dimmed. +/// +/// Returns: CeTZ drawing of all trees in one canvas +/// +/// Example: +/// ``` +/// // Spreading: nasal spreading from n to a +/// #geom-group( +/// (ph: "a"), +/// (ph: "n"), +/// arrows: ((from: "nasal2", to: "root1", ctrl: (1.1, -1.5)),), +/// curved: true, +/// ) +/// ``` +#let geom-group = geom-group diff --git a/packages/preview/phonokit/0.6.1/multi-tier.typ b/packages/preview/phonokit/0.6.1/multi-tier.typ new file mode 100644 index 0000000000..f5b7c095dd --- /dev/null +++ b/packages/preview/phonokit/0.6.1/multi-tier.typ @@ -0,0 +1,510 @@ +// Multi-tier phonological representations (CV phonology, skeletal tiers, etc.) +// Part of phonokit package + +#import "@preview/cetz:0.5.2" +#import "_config.typ": phonokit-font +#import "ipa.typ": ipa as ipa-convert + +#let multi-tier( + levels: (), + links: (), + dashed: (), + delinks: (), + arrows: (), + arrow-delinks: (), + float: (), + highlight: (), + ipa: (), + tier-labels: (), + spacing: 1.5, + level-spacing: 1.2, + stroke-width: 0.05em, + baseline: 40%, + scale: 1.0, + show-grid: false, + show-refs: false, +) = { + // Validate input + assert(type(levels) == array, message: "levels must be an array of arrays") + assert(levels.len() > 0, message: "levels array cannot be empty") + + // Convert labels: "x" → "×" (skeletal slot), Greek names → Unicode, trailing digits → subscripts + let greek-map = ( + "sigma": "σ", + "Sigma": "Σ", + "mu": "μ", + "omega": "ω", + "Omega": "Ω", + "beta": "β", + "alpha": "α", + "gamma": "γ", + "delta": "δ", + "phi": "φ", + "Phi": "Φ", + "pi": "π", + "tau": "τ", + "lambda": "λ", + ) + + let render-label(label) = { + // Greek letter substitution (applied before digit subscripting so "sigma1" → "σ₁") + let m-greek = label.match(regex("^([A-Za-z]+?)(\d*)$")) + if m-greek != none { + let base = m-greek.captures.at(0) + let trail = m-greek.captures.at(1) + if base in greek-map { + label = greek-map.at(base) + trail + } + } + + let label = label.replace(regex("^x$"), "×") + + let m = label.match(regex("^(.*?)(\d+)$")) + if m != none { + let base = m.captures.at(0) + let digits = m.captures.at(1) + let subscript-map = ( + "0": "₀", + "1": "₁", + "2": "₂", + "3": "₃", + "4": "₄", + "5": "₅", + "6": "₆", + "7": "₇", + "8": "₈", + "9": "₉", + ) + let sub = digits.clusters().map(d => subscript-map.at(d, default: d)).join() + base + sub + } else { + label + } + } + + // Normalize a visible label into an automatic reference stem. + // Examples: "N1" -> "n1", "C'" -> "cbar", "sigma" -> "sigma". + let ref-stem(label) = { + if type(label) != str { return none } + + let stem = lower(label.trim()) + if stem == "" { return none } + + stem = stem.replace("'", "bar").replace("\u{2019}", "bar") + stem = stem.replace(regex("[^a-z0-9]+"), "-") + stem = stem.replace(regex("^-+"), "") + stem = stem.replace(regex("-+$"), "") + + if stem == "" { none } else { stem } + } + + // Parse levels into a grid of (label, x-position, y-level) tuples + // Entries can be: + // "label" → label at (array col index, array level index) + // ("label", col) → fractional column, normal level + // ("label", col, level) → fractional column, fractional level + // "" → empty slot + let parsed-grid = levels + .enumerate() + .map(((level-idx, row)) => { + row + .enumerate() + .map(((col-idx, entry)) => { + if type(entry) == array { + let col-pos = entry.at(1) + let level-pos = entry.at(2, default: level-idx) + (label: entry.at(0), col-x: col-pos, level-pos: level-pos) + } else { + (label: entry, col-x: col-idx, level-pos: level-idx) + } + }) + }) + + let ref-counts = (:) + let name-to-node = (:) + let tier-grid = () + + for (level-idx, row) in parsed-grid.enumerate() { + let tier-row = () + for (col-idx, cell) in row.enumerate() { + let stem = ref-stem(cell.label) + let ref-name = if cell.label == "" or stem == none { + none + } else { + let count = ref-counts.at(stem, default: 0) + 1 + ref-counts.insert(stem, count) + let key = stem + str(count) + name-to-node.insert(key, (level-idx, col-idx)) + key + } + + tier-row.push((..cell, ref-name: ref-name)) + } + tier-grid.push(tier-row) + } + + let resolve-node-ref(ref, arg-name: "reference") = { + if type(ref) == str { + assert(ref in name-to-node, message: arg-name + " references unknown multi-tier node `" + ref + "`") + name-to-node.at(ref) + } else { + assert(type(ref) == array and ref.len() == 2, message: arg-name + " node references must be `(level, col)` or a string key") + (ref.at(0), ref.at(1)) + } + } + + let resolve-link-ref(link, arg-name: "reference") = { + assert(type(link) == array and link.len() == 2, message: arg-name + " entries must contain exactly two node references") + ( + resolve-node-ref(link.at(0), arg-name: arg-name), + resolve-node-ref(link.at(1), arg-name: arg-name), + ) + } + + let resolved-float = float.map(item => resolve-node-ref(item, arg-name: "float")) + let resolved-highlight = highlight.map(item => resolve-node-ref(item, arg-name: "highlight")) + let resolved-links = links.map(item => resolve-link-ref(item, arg-name: "links")) + let resolved-dashed = dashed.map(item => resolve-link-ref(item, arg-name: "dashed")) + let resolved-delinks = delinks.map(item => resolve-link-ref(item, arg-name: "delinks")) + let resolved-arrows = arrows.map(item => resolve-link-ref(item, arg-name: "arrows")) + + let num-levels = tier-grid.len() + + // Find the rightmost column x-position across all levels (for tier label placement) + let max-col-x = calc.max(..tier-grid.map(row => calc.max(..row.map(cell => cell.col-x)))) + + // Bind parameters to avoid shadowing built-in/imported names inside CeTZ closure + let scale-factor = scale + let sw = stroke-width * scale-factor + let ipa-levels = ipa + let refs-visible = show-refs + + // Vertical offsets from level center (following prosody.typ pattern) + let text-above = 0.30 + let text-below = -0.58 + let line-top = 0.42 + let line-bot = -0.18 + + box(inset: 1.2em, baseline: baseline, cetz.canvas(length: scale-factor * 1cm, { + import cetz.draw: * + + let debug-ref(level, col, cell) = { + if cell.ref-name != none { + cell.ref-name + } else if cell.label != "" { + "(" + str(level) + ", " + str(col) + ")" + } else { + none + } + } + + // Helper: y-coordinate for a level index (used for default positioning) + let level-y(level) = -level * level-spacing + + // Helper: get a cell's actual position (respects fractional col and level overrides) + let cell-x(level, col) = tier-grid.at(level).at(col).col-x * spacing + let cell-y(level, col) = -tier-grid.at(level).at(col).level-pos * level-spacing + + // Helper: line departure point (bottom of label) + let bot-point(level, col) = { + (cell-x(level, col), cell-y(level, col) + line-bot) + } + + // Helper: line arrival point (top of label) + let top-point(level, col) = { + (cell-x(level, col), cell-y(level, col) + line-top) + } + + // Determine line attachment points for a pair of positions + let line-endpoints(l1, c1, l2, c2) = { + if l1 == l2 { + (bot-point(l1, c1), bot-point(l2, c2)) + } else if l1 < l2 { + (bot-point(l1, c1), top-point(l2, c2)) + } else { + (bot-point(l2, c2), top-point(l1, c1)) + } + } + + // === Layer 0: Debug grid (behind everything) === + if show-grid { + let grid-color = luma(180) + let grid-stroke = (paint: grid-color, thickness: 0.3pt, dash: "dashed") + + let max-col = int(max-col-x) + 1 + let pad = 0.6 + + // Vertical lines for each integer column + for col in range(max-col) { + let x = col * spacing + line( + (x, pad), + (x, -(num-levels - 1) * level-spacing - pad), + stroke: grid-stroke, + ) + } + + // Horizontal lines for each level + for level-i in range(num-levels) { + let y = level-y(level-i) + line( + (-pad, y), + ((max-col - 1) * spacing + pad, y), + stroke: grid-stroke, + ) + } + + // Column index labels (above the diagram) + for col in range(max-col) { + content( + (col * spacing, pad + 0.3), + text(size: 0.9em * scale-factor, fill: grid-color, font: "Courier New", str(col)), + ) + } + + // Level index labels (left of the diagram) + for level-i in range(num-levels) { + content( + (-pad - 0.4, level-y(level-i)), + text(size: 0.9em * scale-factor, fill: grid-color, font: "Courier New", str(level-i)), + ) + } + + // Column index labels (below the diagram) + for col in range(max-col) { + content( + (col * spacing, -(num-levels - 1) * level-spacing - pad - 0.3), + text(size: 0.9em * scale-factor, fill: grid-color, font: "Courier New", str(col)), + ) + } + + // Level index labels (right of the diagram) + for level-i in range(num-levels) { + content( + ((max-col - 1) * spacing + pad + 0.4, level-y(level-i)), + text(size: 0.9em * scale-factor, fill: grid-color, font: "Courier New", str(level-i)), + ) + } + + // Dots at every grid intersection + for level-i in range(num-levels) { + for col in range(max-col) { + circle( + (col * spacing, level-y(level-i)), + radius: 0.05, + fill: grid-color, + stroke: none, + ) + } + } + } + + // === Layer 1: Auto-link lines (between adjacent-level same-column non-empty cells) === + for level in range(num-levels - 1) { + let row-top = tier-grid.at(level) + let row-bot = tier-grid.at(level + 1) + let cols = calc.min(row-top.len(), row-bot.len()) + + for col in range(cols) { + if row-top.at(col).label == "" or row-bot.at(col).label == "" { continue } + if (level, col) in resolved-float or (level + 1, col) in resolved-float { continue } + + let (p1, p2) = line-endpoints(level, col, level + 1, col) + line(p1, p2, stroke: sw) + } + } + + // === Layer 2: Extra solid links (skip any that also appear in dashed) === + for link in resolved-links { + if link in resolved-dashed { continue } + let ((l1, c1), (l2, c2)) = link + let (p1, p2) = line-endpoints(l1, c1, l2, c2) + line(p1, p2, stroke: sw) + } + + // === Layer 3: Dashed lines === + for d in resolved-dashed { + let ((l1, c1), (l2, c2)) = d + let (p1, p2) = line-endpoints(l1, c1, l2, c2) + line(p1, p2, stroke: (dash: "dashed", thickness: sw)) + } + + // === Layer 4: Highlight circles (drawn behind labels, centered on text) === + for (level-idx, col-idx) in resolved-highlight { + let cell = tier-grid.at(level-idx).at(col-idx) + if cell.label == "" { continue } + + let x = cell-x(level-idx, col-idx) + let y = cell-y(level-idx, col-idx) + text-above / 3 + + // White mask circle (slightly larger, masks lines behind it) + circle((x, y), radius: 0.45, stroke: none, fill: white) + // Visible highlight circle + circle((x, y), radius: 0.35, stroke: sw, fill: none) + } + + // === Layer 5: Labels (identical rendering regardless of highlight) === + for (level-idx, row) in tier-grid.enumerate() { + for (col-idx, cell) in row.enumerate() { + if cell.label == "" { continue } + + let x = cell-x(level-idx, col-idx) + let y = cell-y(level-idx, col-idx) + text-above + + let is-ipa = level-idx in ipa-levels + + let label-content = if is-ipa { + context text(font: phonokit-font.get(), ipa-convert(cell.label)) + } else if type(cell.label) == str { + let rendered = render-label(cell.label) + context text(font: phonokit-font.get(), rendered) + } else { + context text(font: phonokit-font.get(), cell.label) + } + + content( + (x, y), + anchor: "north", + text(size: 1em * scale-factor, box( + inset: 0.15em, + align(center + horizon, label-content), + )), + ) + } + } + + // === Layer 6: Tier labels (right-aligned, to the right of the diagram) === + for tl in tier-labels { + let (level-idx, label-text) = tl + let x = (max-col-x + 1.5) * spacing + let y = level-y(level-idx) + text-above + + content( + (x, y), + anchor: "north-west", + text(size: 1em * scale-factor, context text(font: phonokit-font.get(), label-text)), + ) + } + + // === Layer 7: Debug references (below labels) === + if refs-visible { + let ref-color = luma(155) + + for (level-idx, row) in tier-grid.enumerate() { + for (col-idx, cell) in row.enumerate() { + let ref-label = debug-ref(level-idx, col-idx, cell) + if ref-label == none { continue } + + let x = cell-x(level-idx, col-idx) + let y = cell-y(level-idx, col-idx) + text-below + + content( + (x, y), + anchor: "south", + text( + size: 0.75em * scale-factor, + fill: ref-color, + font: "Courier New", + ref-label, + ), + ) + } + } + } + + // === Layer 8: Delink cross marks (on top of everything) === + for d in resolved-delinks { + let ((l1, c1), (l2, c2)) = d + + let (p1, p2) = line-endpoints(l1, c1, l2, c2) + let (x1, y1) = p1 + let (x2, y2) = p2 + + let mid-x = (x1 + x2) / 2 + let mid-y = (y1 + y2) / 2 + + let dx = x2 - x1 + let dy = y2 - y1 + let length = calc.sqrt(dx * dx + dy * dy) + + if length == 0 { continue } + + let dir-x = dx / length + let dir-y = dy / length + + let perp-x = -dir-y + let perp-y = dir-x + + let offset = 0.15 + let spacing-offset = 0.06 + + let p1-start = ( + mid-x - offset * perp-x - spacing-offset * dir-x, + mid-y - offset * perp-y - spacing-offset * dir-y, + ) + let p1-end = ( + mid-x + offset * perp-x - spacing-offset * dir-x, + mid-y + offset * perp-y - spacing-offset * dir-y, + ) + + let p2-start = ( + mid-x - offset * perp-x + spacing-offset * dir-x, + mid-y - offset * perp-y + spacing-offset * dir-y, + ) + let p2-end = ( + mid-x + offset * perp-x + spacing-offset * dir-x, + mid-y + offset * perp-y + spacing-offset * dir-y, + ) + + line(p1-start, p1-end, stroke: sw) + line(p2-start, p2-end, stroke: sw) + } + + // === Layer 9: Arrows (rectangular paths above top / below bottom level) === + let arrow-clearance = 0.5 + + for (arrow-idx, a) in resolved-arrows.enumerate() { + let ((l1, c1), (l2, c2)) = a + + let is-top = l1 == 0 and l2 == 0 + let is-bot = l1 == num-levels - 1 and l2 == num-levels - 1 + + let x1 = cell-x(l1, c1) + let x2 = cell-x(l2, c2) + + if is-top { + let y1 = cell-y(l1, c1) + line-top + let y2 = cell-y(l2, c2) + line-top + let y-bar = level-y(0) + line-top + arrow-clearance + + line((x1, y1), (x1, y-bar), stroke: sw) + line((x1, y-bar), (x2, y-bar), stroke: sw) + line((x2, y-bar), (x2, y2), stroke: sw, mark: (end: "stealth"), fill: black) + + if arrow-idx in arrow-delinks { + let mid-x = (x1 + x2) / 2 + let cross-h = 0.15 + let cross-gap = 0.06 + line((mid-x - cross-gap, y-bar - cross-h), (mid-x - cross-gap, y-bar + cross-h), stroke: sw) + line((mid-x + cross-gap, y-bar - cross-h), (mid-x + cross-gap, y-bar + cross-h), stroke: sw) + } + } else if is-bot { + let y1 = cell-y(l1, c1) + line-bot + let y2 = cell-y(l2, c2) + line-bot + let y-bar = level-y(num-levels - 1) + line-bot - arrow-clearance + + line((x1, y1), (x1, y-bar), stroke: sw) + line((x1, y-bar), (x2, y-bar), stroke: sw) + line((x2, y-bar), (x2, y2), stroke: sw, mark: (end: "stealth"), fill: black) + + if arrow-idx in arrow-delinks { + let mid-x = (x1 + x2) / 2 + let cross-h = 0.15 + let cross-gap = 0.06 + line((mid-x - cross-gap, y-bar - cross-h), (mid-x - cross-gap, y-bar + cross-h), stroke: sw) + line((mid-x + cross-gap, y-bar - cross-h), (mid-x + cross-gap, y-bar + cross-h), stroke: sw) + } + } + } + })) +} diff --git a/packages/preview/phonokit/0.6.1/ot.typ b/packages/preview/phonokit/0.6.1/ot.typ new file mode 100644 index 0000000000..beccf48c8e --- /dev/null +++ b/packages/preview/phonokit/0.6.1/ot.typ @@ -0,0 +1,1262 @@ +#import "ipa.typ": * +#import "_config.typ": phonokit-font +#import "prosody.typ": foot, foot-mora, mora, syllable, word, word-mora + +#let finger = text(size: 14pt)[☞] +#let viol-sym = text(size: 1.2em)[#sym.ast] + +// Helper: Format constraint names with smallcaps, but not text inside brackets +#let format-constraint(name) = { + // Match pattern: text before bracket, bracket content, text after + let bracket-match = name.match(regex("^([^\[]*)\[([^\]]*)\](.*)$")) + + if bracket-match != none { + // Has brackets: smallcaps before, regular inside brackets, smallcaps after + let before = bracket-match.captures.at(0) + let inside = bracket-match.captures.at(1) + let after = bracket-match.captures.at(2) + + // Compose the parts + [#smallcaps(before)\[#inside\]#if after != "" { smallcaps(after) }] + } else { + // No brackets: all smallcaps + smallcaps(name) + } +} + +// --- Helper: Parse Violation String --- +#let format-viol(v) = { + if v == "" { return [] } + let parts = () + let stars = v.matches("*").len() + let fatal = v.contains("!") + for _ in range(stars) { parts.push(viol-sym) } + if fatal { parts.push(strong("!")) } + parts.join(h(1pt)) +} + +// --- Helper: Parse string with rich formatting --- +// - _{...} or _x: subscript (not IPA-parsed) +// - ^{...} or ^x: superscript (not IPA-parsed) +// - {...}: raw text (not IPA-parsed) +// - \\{ and \\}: literal brace characters +// - \\,: literal comma (since , maps to secondary stress) +// - everything else: IPA-parsed as usual +#let _ends-with-space(s) = { + let cls = s.clusters() + cls.len() > 0 and cls.at(cls.len() - 1) == " " +} + +#let parse-ot-string(s) = { + if type(s) != str { return s } + + let clusters = s.clusters() + let parts = () + let buf = "" + let i = 0 + let len = clusters.len() + + while i < len { + let ch = clusters.at(i) + + if ( + ch == "\\" + and i + 1 < len + and (clusters.at(i + 1) == "{" or clusters.at(i + 1) == "}" or clusters.at(i + 1) == ",") + ) { + let literal = clusters.at(i + 1) + let tighten = buf != "" and not _ends-with-space(buf) + if buf != "" { + parts.push(ipa(buf)) + buf = "" + } + let tighten-after = literal == "{" and i + 2 < len and clusters.at(i + 2) != " " + let lit = context text(font: phonokit-font.get(), literal) + let before-kern = if literal == "}" { -0.3em } else { -0.25em } + let after-kern = if literal == "{" { -0.3em } else { -0.15em } + let lit = if tighten { [#h(before-kern)#lit] } else { lit } + parts.push(if tighten-after { [#lit#h(after-kern)] } else { lit }) + i += 2 + } else if (ch == "_" or ch == "^") and i + 1 < len { + let tighten = buf != "" and not _ends-with-space(buf) + if buf != "" { + parts.push(ipa(buf)) + buf = "" + } + let is-sub = ch == "_" + i += 1 + if clusters.at(i) == "{" { + i += 1 + let group = "" + while i < len and clusters.at(i) != "}" { + group += clusters.at(i) + i += 1 + } + if i < len { i += 1 } + let script = if is-sub { + sub(context text(font: phonokit-font.get(), group)) + } else { + super(context text(font: phonokit-font.get(), group)) + } + if is-sub { + parts.push(if tighten { [#h(-0.25em)#script] } else { script }) + } else { + parts.push(if tighten { [#h(-0.25em)#script] } else { script }) + } + } else { + let c = clusters.at(i) + i += 1 + let script = if is-sub { + sub(context text(font: phonokit-font.get(), c)) + } else { + super(context text(font: phonokit-font.get(), c)) + } + if is-sub { + parts.push(if tighten { [#h(-0.25em)#script] } else { script }) + } else { + parts.push(if tighten { [#h(-0.25em)#script] } else { script }) + } + } + } else if ch == "{" { + let tighten = buf != "" and not _ends-with-space(buf) + if buf != "" { + parts.push(ipa(buf)) + buf = "" + } + i += 1 + let raw = "" + while i < len and clusters.at(i) != "}" { + raw += clusters.at(i) + i += 1 + } + if i < len { i += 1 } + let raw-text = context text(font: phonokit-font.get(), raw) + parts.push(if tighten { [#h(-0.25em)#raw-text] } else { raw-text }) + } else { + buf += ch + i += 1 + } + } + + if buf != "" { parts.push(ipa(buf)) } + if parts.len() == 0 { return [] } + parts.fold([], (acc, part) => [#acc#part]) +} + +// --- Helper: Dispatch prosody function by name --- +#let dispatch-prosody(func-name, arg, ps) = { + if func-name == "syllable" { syllable(arg, scale: ps) } else if func-name == "mora" { mora(arg, scale: ps) } else if ( + func-name == "foot" + ) { foot(arg, scale: ps) } else if func-name == "foot-mora" { foot-mora(arg, scale: ps) } else if ( + func-name == "word" + ) { word(arg, scale: ps) } else if func-name == "word-mora" { word-mora(arg, scale: ps) } else { ipa(arg) } +} + +// --- Helper: Parse candidate string for prosodic function calls --- +#let prosody-pattern = regex("#(syllable|mora|foot-mora|foot|word-mora|word)\\('([^']*)'\\)") + +#let parse-candidate(cand, ps) = { + if type(cand) != str { return cand } + + let all-matches = cand.matches(prosody-pattern) + + if all-matches.len() == 0 { + return parse-ot-string(cand) + } + + let parts = () + let pos = 0 + + for m in all-matches { + if m.start > pos { + let before = cand.slice(pos, m.start).trim() + if before != "" and before != "+" { + parts.push(parse-ot-string(before)) + } + } + + let func-name = m.captures.at(0) + let arg = m.captures.at(1) + parts.push(dispatch-prosody(func-name, arg, ps)) + + pos = m.end + } + + if pos < cand.len() { + let after = cand.slice(pos).trim() + if after != "" and after != "+" { + parts.push(parse-ot-string(after)) + } + } + + parts.join() +} + +#let has-prosody(c) = { + if type(c) == str { c.matches(prosody-pattern).len() > 0 } else { type(c) == content } +} + +// NOTE: --- The Main Function --- +#let tableau( + input: "Input", + candidates: (), + constraints: (), + violations: (), + winner: 0, + dashed-lines: (), + scale: none, + shade: true, + prosody-scale: 0.5, + letters: false, + gloss: none, +) = { + // 1. Validation and Truncation + assert(constraints.len() <= 20, message: "Maximum 20 constraints allowed in tableau") + + // Truncate constraint names to 20 characters + let constraints = constraints.map(c => { + if c.len() > 20 { c.slice(0, 20) } else { c } + }) + + // Scale: use user-provided scale if given, otherwise auto-scale + let scale-factor = if scale != none { + scale + } else if constraints.len() > 6 { + 0.85 + } else { + 1.0 + } + let font-size = scale-factor * 1em + let scaled-finger = text(size: 14pt * scale-factor)[☞] + + // 2. Shading Logic (only if enabled) + let fatal-map = () + if shade { + for (r, row-viols) in violations.enumerate() { + let fatal-col = 999 + for (c, cell) in row-viols.enumerate() { + if cell.contains("!") { + fatal-col = c + break + } + } + fatal-map.push(fatal-col) + } + // Winner shading: shade after the rightmost fatal column among all losers + if winner != none and winner < fatal-map.len() { + let loser-fatals = fatal-map.enumerate().filter(((r, fc)) => r != winner and fc != 999) + if loser-fatals.len() > 0 { + let max-fatal = calc.max(..loser-fatals.map(((_, fc)) => fc)) + fatal-map.at(winner) = max-fatal + } + } + } + + // 3. Prepare Input Content + let gloss-content = if gloss != none { + [ _#gloss.at(0)_ '#gloss.at(1)'] + } else { [] } + let input-content = if type(input) == str { + [#parse-ot-string(input)#gloss-content] + } else { + [#input#gloss-content] + } + + // 4. Grid Definitions + let letter-labels = "abcdefghijklmnopqrstuvwxyz" + let cons-start = 3 // first constraint column index + let row-defs = (1.75em, 2pt) + candidates.map(c => if has-prosody(c) { auto } else { 1.75em }) + + context { + let text-style(it) = text(size: font-size, font: phonokit-font.get(), it) + + // Measure input-content (it spans col 0 and 1) + // Inset for spanned cell: (left: 5pt, right: 10pt) -> 15pt total + let w-input = measure(text-style(input-content)).width + 15pt + + // Measure Col 0 (Prefix/Finger) + // Inset for Col 0: (left: 5pt, right: 0pt) -> 5pt total + let w-col0-max = 0pt + for (i, cand) in candidates.enumerate() { + let finger-content = if i == winner { scaled-finger + " " } else { "" } + let it = if letters { + let letter = letter-labels.at(calc.min(i, 25)) + [#finger-content #letter.] + } else { + [#finger-content] + } + w-col0-max = calc.max(w-col0-max, measure(text-style(it)).width) + } + w-col0-max += 5pt + + // Measure Col 1 (Candidate) + // Inset for Col 1: (left: 8pt, right: 10pt) -> 18pt total + let w-col1-max = 0pt + for (i, cand) in candidates.enumerate() { + let cand-content = parse-candidate(cand, prosody-scale) + w-col1-max = calc.max(w-col1-max, measure(text-style(cand-content)).width) + } + w-col1-max += 18pt + + // Distribution: if input is wider than col0+col1, stretch col0 + let w0 = w-col0-max + let w1 = w-col1-max + if w-input > w0 + w1 { + w0 = w-input - w1 + } + + let col-defs = (w0, w1, 2pt) + constraints.map(_ => auto) + + text-style[#table( + columns: col-defs, + rows: row-defs, + align: (col, row) => { + let v-align = bottom + if row >= 2 { + if col <= 1 { + if has-prosody(candidates.at(row - 2)) { v-align = horizon } + } else { + v-align = horizon + } + } + if col <= 1 { right + v-align } else { center + v-align } + }, + inset: (col, row) => if col == 0 { + (left: 5pt, top: 5pt, bottom: 5pt, right: 0pt) + } else if col == 1 { + (left: 8pt, top: 5pt, bottom: 5pt, right: 10pt) + } else { 5pt }, + + stroke: (col, row) => { + let s = 0.4pt + black + if col == 0 { return (left: s, top: s, bottom: s, right: none) } + if col == 1 { return (left: none, top: s, bottom: s, right: s) } + let is-dashed = if col >= cons-start { dashed-lines.contains(col - cons-start) } else { false } + ( + left: s, + top: s, + bottom: s, + right: if is-dashed { (thickness: 0.4pt, dash: "dashed") } else { s }, + ) + }, + + fill: (col, row) => { + if not shade or row < 2 or col < cons-start { return none } + let cand-idx = row - 2 + let cons-idx = col - cons-start + if cand-idx < fatal-map.len() { + let fatal-col = fatal-map.at(cand-idx) + if cons-idx > fatal-col { + let has-solid-line = false + for c in range(fatal-col, cons-idx) { + if not dashed-lines.contains(c) { + has-solid-line = true + break + } + } + if has-solid-line { return luma(230) } + } + } + return none + }, + + // --- Content --- + table.cell(colspan: 2, inset: (left: 5pt, right: 10pt), align: right + bottom, input-content), + [], + ..constraints.map(c => format-constraint(c)), + + // Gap Row + ..range(col-defs.len()).map(_ => []), + + // Candidates + ..candidates + .enumerate() + .map(((i, cand)) => { + let cells = () + let cand-content = parse-candidate(cand, prosody-scale) + + let finger-content = if i == winner { scaled-finger + " " } else { "" } + if letters { + let letter = letter-labels.at(calc.min(i, 25)) + cells.push([#finger-content #letter.]) + } else { + cells.push([#finger-content]) + } + cells.push([#cand-content]) + cells.push([]) + + let row-viols = if i < violations.len() { violations.at(i) } else { () } + for j in range(constraints.len()) { + if j < row-viols.len() { + cells.push(format-viol(row-viols.at(j))) + } else { + cells.push([]) + } + } + return cells + }) + .flatten() + )] + } +} + +// NOTE: --- HG TABLEAU FUNCTION --- +#let hg( + input: "Input", + candidates: (), + constraints: (), + weights: (), + violations: (), + scale: none, + letters: false, +) = { + // 1. Validation and Truncation + assert(constraints.len() <= 20, message: "Maximum 20 constraints allowed") + let letter-labels = "abcdefghijklmnopqrstuvwxyz" + + // Truncate constraint names to 15 characters + let constraints = constraints.map(c => { + if c.len() > 15 { c.slice(0, 15) } else { c } + }) + + // Scale: use user-provided scale if given, otherwise auto-scale + let font-size = if scale != none { + scale * 1em + } else if constraints.len() > 6 { + 0.85em + } else { + 0.95em + } + + // 2. CALCULATIONS + let h-scores = () + + for row-viols in violations { + let h = 0.0 + for (i, v) in row-viols.enumerate() { + if i < weights.len() { + h += float(v) * float(weights.at(i)) + } + } + h-scores.push(h) + } + + // 3. GRID DEFINITIONS (simpler than maxent - only h(y) column) + let row-defs = (auto, 1.75em, 2pt) + candidates.map(c => if has-prosody(c) { auto } else { 1.75em }) + + context { + let text-style(it) = text(size: font-size, font: phonokit-font.get(), it) + let input-content = if type(input) == str { parse-ot-string(input) } else { input } + + // Measure input-content (it spans col 0 and 1) + let w-input = measure(text-style(input-content)).width + 15pt + + // Measure Col 0 (Prefix) + let w-col0-max = 0pt + for (i, cand) in candidates.enumerate() { + let it = if letters { + let letter = letter-labels.at(calc.min(i, 25)) + [#letter.] + } else { + [] + } + w-col0-max = calc.max(w-col0-max, measure(text-style(it)).width) + } + w-col0-max += 5pt + + // Measure Col 1 (Candidate) + let w-col1-max = 0pt + for (i, cand) in candidates.enumerate() { + let cand-content = parse-candidate(cand, 0.5) + w-col1-max = calc.max(w-col1-max, measure(text-style(cand-content)).width) + } + w-col1-max += 18pt + + // Distribution + let w0 = w-col0-max + let w1 = w-col1-max + if w-input > w0 + w1 { + w0 = w-input - w1 + } + + let col-defs = (w0, w1, 2pt) + constraints.map(_ => auto) + (2pt, auto) + + text-style[#table( + columns: col-defs, + rows: row-defs, + align: (col, row) => { + let v-align = bottom + if row >= 3 { + if col <= 1 { + if has-prosody(candidates.at(row - 3)) { v-align = horizon } + } else { + v-align = horizon + } + } + if col <= 1 { right + v-align } else { center + v-align } + }, + inset: (col, row) => if col == 0 { + (left: 5pt, top: 5pt, bottom: 5pt, right: 0pt) + } else if col == 1 { + (left: 8pt, top: 5pt, bottom: 5pt, right: 10pt) + } else { 5pt }, + + // --- STROKE LOGIC --- + stroke: (col, row) => { + if row == 0 { return none } + let s = 0.4pt + black + if col == 0 { return (left: s, top: s, bottom: s, right: none) } + if col == 1 { return (left: none, top: s, bottom: s, right: s) } + (left: s, top: s, bottom: s, right: s) + }, + + // --- ROW 0: WEIGHTS + [], [], [], + ..weights.map(w => text(size: 0.9em)[$w=#w$]), + // Fill remaining columns: gap (2pt) + h(y) column + [], [], + + // --- ROW 1: HEADERS --- + table.cell(colspan: 2, inset: (left: 5pt, right: 10pt), align: right + bottom, input-content), + [], + ..constraints.map(c => format-constraint(c)), + [], + [$h_i$], + + // --- ROW 2: GAP --- + ..range(col-defs.len()).map(_ => []), + + // --- ROWS 3+: CANDIDATES --- + ..candidates + .enumerate() + .map(((i, cand)) => { + let cells = () + let cand-content = parse-candidate(cand, 0.5) + + if letters { + let letter = letter-labels.at(calc.min(i, 25)) + cells.push([#letter.]) + } else { + cells.push([]) + } + cells.push([#cand-content]) + cells.push([]) + + // Violations + let row-viols = if i < violations.len() { violations.at(i) } else { () } + for j in range(constraints.len()) { + if j < row-viols.len() { cells.push(text(size: 0.85em)[#str(row-viols.at(j))]) } else { cells.push([]) } + } + + cells.push([]) + + // Only h(y) column - no MaxEnt probabilities + if i < h-scores.len() { + cells.push(text(size: 0.85em)[#str(calc.round(h-scores.at(i), digits: 2))]) + } else { + cells.push("-") + } + + return cells + }) + .flatten() + )] + } +} + +// NOTE: --- NHG DEMO TABLEAU (Pedagogical - shows symbolic noise) --- +#let nhg-demo( + input: "Input", + candidates: (), + constraints: (), + weights: (), + violations: (), + probabilities: none, // optional: if provided, will display P(y) values + scale: none, + letters: false, +) = { + // 1. Validation and Truncation + assert(constraints.len() <= 20, message: "Maximum 20 constraints allowed") + let letter-labels = "abcdefghijklmnopqrstuvwxyz" + + // Truncate constraint names to 15 characters + let constraints = constraints.map(c => { + if c.len() > 15 { c.slice(0, 15) } else { c } + }) + + // Scale: use user-provided scale if given, otherwise auto-scale + let font-size = if scale != none { + scale * 1em + } else if constraints.len() > 6 { + 0.85em + } else { + 0.95em + } + + // 2. CALCULATIONS + let h-scores = () + let epsilon-formulas = () + + for row-viols in violations { + // Calculate deterministic harmony h(y) + let h = 0.0 + for (i, v) in row-viols.enumerate() { + if i < weights.len() { + h += float(v) * float(weights.at(i)) + } + } + h-scores.push(h) + + // Build epsilon formula: ε(y) = Σ(n_i × v_i) + let epsilon-parts = () + for (i, v) in row-viols.enumerate() { + if i < constraints.len() and v != 0 { + let v-val = float(v) + let abs-v = calc.abs(v-val) + let sign = if v-val < 0 { "-" } else { "" } + let coef = if abs-v == 1 { "" } else { str(int(abs-v)) } + epsilon-parts.push(sign + coef + "n_" + str(i + 1)) + } + } + + // Join epsilon parts and create single math expression + let epsilon-formula = if epsilon-parts.len() > 0 { + let formula-str = epsilon-parts.join(" ") + eval("$" + formula-str + "$") + } else { + $0$ + } + epsilon-formulas.push(epsilon-formula) + } + + // 3. GRID DEFINITIONS (h, ε, P columns) + let row-defs = (auto, 1.75em, 2pt) + candidates.map(c => if has-prosody(c) { auto } else { 1.75em }) + + context { + let text-style(it) = text(size: font-size, font: phonokit-font.get(), it) + let input-content = if type(input) == str { parse-ot-string(input) } else { input } + + // Measure input-content (it spans col 0 and 1) + let w-input = measure(text-style(input-content)).width + 15pt + + // Measure Col 0 (Prefix) + let w-col0-max = 0pt + for (i, cand) in candidates.enumerate() { + let it = if letters { + let letter = letter-labels.at(calc.min(i, 25)) + [#letter.] + } else { + [] + } + w-col0-max = calc.max(w-col0-max, measure(text-style(it)).width) + } + w-col0-max += 5pt + + // Measure Col 1 (Candidate) + let w-col1-max = 0pt + for (i, cand) in candidates.enumerate() { + let cand-content = parse-candidate(cand, 0.5) + w-col1-max = calc.max(w-col1-max, measure(text-style(cand-content)).width) + } + w-col1-max += 18pt + + // Distribution + let w0 = w-col0-max + let w1 = w-col1-max + if w-input > w0 + w1 { + w0 = w-input - w1 + } + + let col-defs = (w0, w1, 2pt) + constraints.map(_ => auto) + (2pt, auto, auto) + if probabilities != none { + col-defs.push(auto) // Add P(y) column + } + + text-style[#table( + columns: col-defs, + rows: row-defs, + align: (col, row) => { + let v-align = bottom + if row >= 3 { + if col <= 1 { + if has-prosody(candidates.at(row - 3)) { v-align = horizon } + } else { + v-align = horizon + } + } + if col <= 1 { right + v-align } else { center + v-align } + }, + inset: (col, row) => if col == 0 { + (left: 5pt, top: 5pt, bottom: 5pt, right: 0pt) + } else if col == 1 { + (left: 8pt, top: 5pt, bottom: 5pt, right: 10pt) + } else { 5pt }, + + // --- STROKE LOGIC --- + stroke: (col, row) => { + if row == 0 { return none } + let s = 0.4pt + black + if col == 0 { return (left: s, top: s, bottom: s, right: none) } + if col == 1 { return (left: none, top: s, bottom: s, right: s) } + (left: s, top: s, bottom: s, right: s) + }, + + // --- ROW 0: WEIGHTS + [], [], [], + ..weights.map(w => text(size: 0.9em)[$w=#w$]), + // Fill remaining columns: gap + h + ε + (optional P) + ..range(if probabilities != none { 4 } else { 3 }).map(_ => []), + + // --- ROW 1: HEADERS --- + table.cell(colspan: 2, inset: (left: 5pt, right: 10pt), align: right + bottom, input-content), + [], + ..constraints.map(c => format-constraint(c)), + [], + [$h_i$], + [$epsilon_i$], + ..(if probabilities != none { ([$P_i$],) } else { () }), + + // --- ROW 2: GAP --- + ..range(col-defs.len()).map(_ => []), + + // --- ROWS 3+: CANDIDATES --- + ..candidates + .enumerate() + .map(((i, cand)) => { + let cells = () + let cand-content = parse-candidate(cand, 0.5) + + if letters { + let letter = letter-labels.at(calc.min(i, 25)) + cells.push([#letter.]) + } else { + cells.push([]) + } + cells.push([#cand-content]) + cells.push([]) + + // Violations + let row-viols = if i < violations.len() { violations.at(i) } else { () } + for j in range(constraints.len()) { + if j < row-viols.len() { + cells.push(text(size: 0.85em)[#str(row-viols.at(j))]) + } else { + cells.push([]) + } + } + + cells.push([]) + + // h(y) column + if i < h-scores.len() { + cells.push(text(size: 0.85em)[#str(calc.round(h-scores.at(i), digits: 2))]) + } else { + cells.push("-") + } + + // ε(y) column (formula) + if i < epsilon-formulas.len() { + cells.push(text(size: 0.85em)[#epsilon-formulas.at(i)]) + } else { + cells.push("-") + } + + // P(y) column (if provided) + if probabilities != none and i < probabilities.len() { + cells.push(text(size: 0.85em)[#str(calc.round(probabilities.at(i), digits: 3))]) + } else if probabilities != none { + cells.push("-") + } + + return cells + }) + .flatten() + )] + } +} + +// NOTE: --- NHG TABLEAU (Smart - samples noise and calculates probabilities) --- +#let nhg( + input: "Input", + candidates: (), + constraints: (), + weights: (), + violations: (), + num-simulations: 1000, + seed: none, + show-epsilon: true, + scale: none, + letters: false, +) = { + // 1. Validation and Truncation + assert(constraints.len() <= 20, message: "Maximum 20 constraints allowed") + let letter-labels = "abcdefghijklmnopqrstuvwxyz" + + // Truncate constraint names to 15 characters + let constraints = constraints.map(c => { + if c.len() > 15 { c.slice(0, 15) } else { c } + }) + + // Scale: use user-provided scale if given, otherwise auto-scale + let font-size = if scale != none { + scale * 1em + } else if constraints.len() > 6 { + 0.85em + } else { + 0.95em + } + + // 2. Generate all random samples upfront using LCG + let initial-seed = if seed != none { seed } else { 12345 } + let total-samples = (num-simulations + 1) * constraints.len() + + let normal-samples = () + let state = initial-seed + + for i in range(total-samples) { + // Generate two uniform random numbers for Box-Muller + state = calc.rem(state * 1103515245 + 12345, 2147483648) + let u1 = state / 2147483648.0 + state = calc.rem(state * 1103515245 + 12345, 2147483648) + let u2 = state / 2147483648.0 + + // Box-Muller transform + if u1 < 0.00001 { u1 = 0.00001 } + let sample = calc.sqrt(-2.0 * calc.ln(u1)) * calc.cos(2.0 * calc.pi * u2) + normal-samples.push(sample) + } + + // 3. Calculate deterministic harmonies + let h-scores = () + for row-viols in violations { + let h = 0.0 + for (i, v) in row-viols.enumerate() { + if i < weights.len() { + h += float(v) * float(weights.at(i)) + } + } + h-scores.push(h) + } + + // 4. Monte Carlo simulation + let win-counts = candidates.map(_ => 0) + let sample-idx = 0 + + for sim in range(num-simulations) { + // Get noise for this simulation + let noise = () + for c in range(constraints.len()) { + noise.push(normal-samples.at(sample-idx)) + sample-idx = sample-idx + 1 + } + + // Calculate noisy harmonies + let noisy-harmonies = () + for (cand-idx, row-viols) in violations.enumerate() { + let h = h-scores.at(cand-idx) + let epsilon = 0.0 + for (i, v) in row-viols.enumerate() { + if i < noise.len() { + epsilon += noise.at(i) * float(v) + } + } + noisy-harmonies.push(h + epsilon) + } + + // Find winner + let max-harmony = calc.max(..noisy-harmonies) + let winner-idx = noisy-harmonies.position(h => h == max-harmony) + if winner-idx != none { + win-counts.at(winner-idx) = win-counts.at(winner-idx) + 1 + } + } + + // Calculate probabilities + let probabilities = win-counts.map(count => float(count) / float(num-simulations)) + + // 5. Get epsilon values for display (one more set of samples) + let display-noise = () + for c in range(constraints.len()) { + display-noise.push(normal-samples.at(sample-idx)) + sample-idx = sample-idx + 1 + } + + let epsilon-values = () + for row-viols in violations { + let epsilon = 0.0 + for (i, v) in row-viols.enumerate() { + if i < display-noise.len() { + epsilon += display-noise.at(i) * float(v) + } + } + epsilon-values.push(epsilon) + } + + // 6. GRID DEFINITIONS + let row-defs = (auto, 1.75em, 2pt) + candidates.map(c => if has-prosody(c) { auto } else { 1.75em }) + + context { + let text-style(it) = text(size: font-size, font: phonokit-font.get(), it) + let input-content = if type(input) == str { parse-ot-string(input) } else { input } + + // Measure input-content (it spans col 0 and 1) + let w-input = measure(text-style(input-content)).width + 15pt + + // Measure Col 0 (Prefix) + let w-col0-max = 0pt + for (i, cand) in candidates.enumerate() { + let it = if letters { + let letter = letter-labels.at(calc.min(i, 25)) + [#letter.] + } else { + [] + } + w-col0-max = calc.max(w-col0-max, measure(text-style(it)).width) + } + w-col0-max += 5pt + + // Measure Col 1 (Candidate) + let w-col1-max = 0pt + for (i, cand) in candidates.enumerate() { + let cand-content = parse-candidate(cand, 0.5) + w-col1-max = calc.max(w-col1-max, measure(text-style(cand-content)).width) + } + w-col1-max += 18pt + + // Distribution + let w0 = w-col0-max + let w1 = w-col1-max + if w-input > w0 + w1 { + w0 = w-input - w1 + } + + let col-defs = (w0, w1, 2pt) + constraints.map(_ => auto) + (2pt, auto) + if show-epsilon { + col-defs.push(auto) // epsilon column + } + col-defs.push(auto) // P(y) column + + text-style[#table( + columns: col-defs, + rows: row-defs, + align: (col, row) => { + let v-align = bottom + if row >= 3 { + if col <= 1 { + if has-prosody(candidates.at(row - 3)) { v-align = horizon } + } else { + v-align = horizon + } + } + if col <= 1 { right + v-align } else { center + v-align } + }, + inset: (col, row) => if col == 0 { + (left: 5pt, top: 5pt, bottom: 5pt, right: 0pt) + } else if col == 1 { + (left: 8pt, top: 5pt, bottom: 5pt, right: 10pt) + } else { 5pt }, + + // --- STROKE LOGIC --- + stroke: (col, row) => { + if row == 0 { return none } + let s = 0.4pt + black + if col == 0 { return (left: s, top: s, bottom: s, right: none) } + if col == 1 { return (left: none, top: s, bottom: s, right: s) } + (left: s, top: s, bottom: s, right: s) + }, + + // --- ROW 0: WEIGHTS + [], [], [], + ..weights.map(w => text(size: 0.9em)[$w=#w$]), + // Fill remaining columns: gap + h + optional ε + P + ..range(if show-epsilon { 4 } else { 3 }).map(_ => []), + + // --- ROW 1: HEADERS --- + table.cell(colspan: 2, inset: (left: 5pt, right: 10pt), align: right + bottom, input-content), + [], + ..constraints.map(c => format-constraint(c)), + [], + [$h_i$], + ..(if show-epsilon { ([$epsilon_i$],) } else { () }), + [$P_i$], + + // --- ROW 2: GAP --- + ..range(col-defs.len()).map(_ => []), + + // --- ROWS 3+: CANDIDATES --- + ..candidates + .enumerate() + .map(((i, cand)) => { + let cells = () + let cand-content = parse-candidate(cand, 0.5) + + if letters { + let letter = letter-labels.at(calc.min(i, 25)) + cells.push([#letter.]) + } else { + cells.push([]) + } + cells.push([#cand-content]) + cells.push([]) + + // Violations + let row-viols = if i < violations.len() { violations.at(i) } else { () } + for j in range(constraints.len()) { + if j < row-viols.len() { + cells.push(text(size: 0.85em)[#str(row-viols.at(j))]) + } else { + cells.push([]) + } + } + + cells.push([]) + + // h(y) column + if i < h-scores.len() { + cells.push(text(size: 0.85em)[#str(calc.round(h-scores.at(i), digits: 2))]) + } else { + cells.push("-") + } + + // ε(y) column (sampled value) - only if show-epsilon + if show-epsilon { + if i < epsilon-values.len() { + cells.push(text(size: 0.85em)[#str(calc.round(epsilon-values.at(i), digits: 2))]) + } else { + cells.push("-") + } + } + + // P(y) column (estimated from simulations) + if i < probabilities.len() { + cells.push(text(size: 0.85em)[#str(calc.round(probabilities.at(i), digits: 3))]) + } else { + cells.push("-") + } + + return cells + }) + .flatten() + )] + } +} + +// NOTE: --- MAXENT TABLEAU FUNCTION --- +#let maxent( + input: "Input", + candidates: (), + constraints: (), + weights: (), + violations: (), + visualize: true, + sort: false, + scale: none, + letters: false, +) = { + // 1. Validation and Truncation + assert(constraints.len() <= 20, message: "Maximum 20 constraints allowed in maxent") + let letter-labels = "abcdefghijklmnopqrstuvwxyz" + + // Truncate constraint names to 15 characters + let constraints = constraints.map(c => { + if c.len() > 15 { c.slice(0, 15) } else { c } + }) + + // Scale: use user-provided scale if given, otherwise auto-scale + let font-size = if scale != none { + scale * 1em + } else if constraints.len() > 6 { + 0.85em + } else { + 0.95em + } + + // 2. CALCULATIONS + let h-scores = () + let p-star-scores = () + let total-p-star = 0.0 + + for row-viols in violations { + let h = 0.0 + for (i, v) in row-viols.enumerate() { + if i < weights.len() { + h += float(v) * float(weights.at(i)) + } + } + h-scores.push(h) + let p-star = calc.exp(-h) + p-star-scores.push(p-star) + total-p-star += p-star + } + + // Safety check for empty violations/division by zero + let p-scores = if total-p-star > 0 { + p-star-scores.map(x => x / total-p-star) + } else { + candidates.map(_ => 0.0) + } + + // 3. SORT BY PROBABILITY (if enabled) + let order = if sort { + range(candidates.len()).sorted(key: i => -p-scores.at(i)) + } else { + range(candidates.len()) + } + let candidates = order.map(i => candidates.at(i)) + let violations = order.map(i => violations.at(i)) + let h-scores = order.map(i => h-scores.at(i)) + let p-star-scores = order.map(i => p-star-scores.at(i)) + let p-scores = order.map(i => p-scores.at(i)) + + // 4. GRID DEFINITIONS + let bar-col-width = 3cm + let row-defs = (auto, 1.75em, 2pt) + candidates.map(c => if has-prosody(c) { auto } else { 1.75em }) + + context { + let text-style(it) = text(size: font-size, font: phonokit-font.get(), it) + let input-content = if type(input) == str { parse-ot-string(input) } else { input } + + // Measure input-content (it spans col 0 and 1) + let w-input = measure(text-style(input-content)).width + 15pt + + // Measure Col 0 (Prefix) + let w-col0-max = 0pt + for (i, cand) in candidates.enumerate() { + let it = if letters { + let letter = letter-labels.at(calc.min(i, 25)) + [#letter.] + } else { + [] + } + w-col0-max = calc.max(w-col0-max, measure(text-style(it)).width) + } + w-col0-max += 5pt + + // Measure Col 1 (Candidate) + let w-col1-max = 0pt + for (i, cand) in candidates.enumerate() { + let cand-content = parse-candidate(cand, 0.5) + w-col1-max = calc.max(w-col1-max, measure(text-style(cand-content)).width) + } + w-col1-max += 18pt + + // Distribution + let w0 = w-col0-max + let w1 = w-col1-max + if w-input > w0 + w1 { + w0 = w-input - w1 + } + + let col-defs = (w0, w1, 2pt) + constraints.map(_ => auto) + (2pt, auto, auto, auto) + if visualize { + col-defs.push(bar-col-width) // The Floating Column + } + let last-col-idx = col-defs.len() - 1 + + let tbl = text-style[#table( + columns: col-defs, + rows: row-defs, + align: (col, row) => { + let v-align = bottom + if row >= 3 { + if col <= 1 { + if has-prosody(candidates.at(row - 3)) { v-align = horizon } + } else { + v-align = horizon + } + } + if col <= 1 { right + v-align } else { center + v-align } + }, + inset: (col, row) => if col == 0 { + (left: 5pt, top: 5pt, bottom: 5pt, right: 0pt) + } else if col == 1 { + (left: 8pt, top: 5pt, bottom: 5pt, right: 10pt) + } else { 5pt }, + + // --- STROKE LOGIC --- + stroke: (col, row) => { + if row == 0 { return none } + if visualize and col == last-col-idx { return none } + let s = 0.4pt + black + if col == 0 { return (left: s, top: s, bottom: s, right: none) } + if col == 1 { return (left: none, top: s, bottom: s, right: s) } + (left: s, top: s, bottom: s, right: s) + }, + + // --- ROW 0: WEIGHTS + [], [], [], + ..weights.map(w => text(size: 0.9em)[$w=#w$]), + // Fill remaining columns with empty cells + ..range(if visualize { 5 } else { 4 }).map(_ => []), + + // --- ROW 1: HEADERS --- + table.cell(colspan: 2, inset: (left: 5pt, right: 10pt), align: right + bottom, input-content), + [], + ..constraints.map(c => format-constraint(c)), + [], + [$h_i$], [$e^(-h_i)$], [$P_i$], + // Add empty floating header if visualizing + ..(if visualize { ([],) } else { () }), + + // --- ROW 2: GAP --- + ..range(col-defs.len()).map(_ => []), + + // --- ROWS 3+: CANDIDATES (letters assigned AFTER sort) + ..candidates + .enumerate() + .map(((i, cand)) => { + let cells = () + let cand-content = parse-candidate(cand, 0.5) + + // Letters are assigned after sorting, so i reflects the sorted order + if letters { + let letter = letter-labels.at(calc.min(i, 25)) + cells.push([#letter.]) + } else { + cells.push([]) + } + cells.push([#cand-content]) + cells.push([]) + + // Violations + let row-viols = if i < violations.len() { violations.at(i) } else { () } + for j in range(constraints.len()) { + if j < row-viols.len() { cells.push(text(size: 0.85em)[#str(row-viols.at(j))]) } else { cells.push([]) } + } + + cells.push([]) + if i < h-scores.len() { + cells.push(text(size: 0.85em)[#str(calc.round(h-scores.at(i), digits: 2))]) + cells.push(text(size: 0.85em)[#str(calc.round(p-star-scores.at(i), digits: 3))]) + let p-val = p-scores.at(i) + cells.push(text(size: 0.85em)[#str(calc.round(p-val, digits: 3))]) + + // --- FLOATING VISUAL BAR --- + if visualize { + cells.push(align(left + horizon)[ + #box(width: 50%, height: 0.5em, stroke: 0.5pt + luma(100))[ + #rect( + width: p-val * 100%, + height: 100%, + fill: luma(100), + stroke: 0.5pt + luma(100), + ) + ] + ]) + } + } else { + // Fallback + cells.push("-") + cells.push("-") + cells.push("-") + if visualize { cells.push([]) } + } + + return cells + }) + .flatten() + )] + + if visualize { pad(right: -bar-col-width, tbl) } else { tbl } + } +} diff --git a/packages/preview/phonokit/0.6.1/phonetics.typ b/packages/preview/phonokit/0.6.1/phonetics.typ new file mode 100644 index 0000000000..370e191d5f --- /dev/null +++ b/packages/preview/phonokit/0.6.1/phonetics.typ @@ -0,0 +1,1017 @@ +#import "@preview/cetz:0.5.2" +#import "@preview/lilaq:0.6.0" as lq +#import "ipa.typ": ipa-to-unicode +#import "_config.typ": phonokit-font +#import "vowels.typ": language-vowels +#import "ui-lang.typ": resolve-ui-lang, ui-lang-error + +#let formants-default-vowel-size = 18pt + +#let phonetic-vowel-presets = ( + "i": (f1: 280, f2: 2290), + "y": (f1: 310, f2: 1990), + "ɨ": (f1: 300, f2: 1750), + "ʉ": (f1: 320, f2: 1400), + "ɯ": (f1: 300, f2: 1100), + "ɪ": (f1: 390, f2: 1990), + "ʏ": (f1: 400, f2: 1750), + "e": (f1: 390, f2: 2200), + "ø": (f1: 390, f2: 1800), + "ɘ": (f1: 420, f2: 1650), + "ɵ": (f1: 420, f2: 1400), + "ɤ": (f1: 420, f2: 1200), + "ɛ": (f1: 530, f2: 1840), + "œ": (f1: 540, f2: 1600), + "ɜ": (f1: 560, f2: 1500), + "ɞ": (f1: 560, f2: 1300), + "æ": (f1: 660, f2: 1720), + "ɐ": (f1: 650, f2: 1450), + "a": (f1: 730, f2: 1500), + "ɶ": (f1: 780, f2: 1450), + "ɑ": (f1: 730, f2: 1090), + "ɒ": (f1: 700, f2: 900), + "ə": (f1: 500, f2: 1500), + "ʌ": (f1: 610, f2: 1300), + "ɔ": (f1: 570, f2: 840), + "o": (f1: 430, f2: 930), + "ʊ": (f1: 440, f2: 1020), + "u": (f1: 300, f2: 870), +) + +// Sourced English preset adapted from the male values in Hillenbrand et al. (1995), +// as provided by the user from a secondary table reproducing those averages. +// For legibility in a teaching plot, a few values are slightly regularized so +// categories do not collapse visually. This is used only when the input preset +// is "english". Other presets remain schematic/illustrative for teaching. +#let english-hillenbrand-male = ( + "i": (f1: 342, f2: 2322), + "ɪ": (f1: 427, f2: 2034), + "e": (f1: 476, f2: 2089), + "ɛ": (f1: 580, f2: 1799), + "æ": (f1: 588, f2: 1952), + "a": (f1: 768, f2: 1425), + "ɑ": (f1: 768, f2: 1225), + "ɔ": (f1: 652, f2: 997), + "o": (f1: 497, f2: 910), + "u": (f1: 378, f2: 997), + "ʊ": (f1: 469, f2: 1122), + "ʌ": (f1: 623, f2: 1200), + "ə": (f1: 474, f2: 1379), + "ɚ": (f1: 474, f2: 1379), +) + +#let _unique(items) = { + let seen = () + for item in items { + if item not in seen { + seen.push(item) + } + } + seen +} + +#let _index-of(items, target) = { + for (idx, item) in items.enumerate() { + if item == target { + return idx + } + } + none +} + +#let _normalize-vowel-string(vowels) = { + let cleaned = vowels + if cleaned in language-vowels { + return language-vowels.at(cleaned).clusters() + } + let normalized = ipa-to-unicode(cleaned) + .replace("/", " ") + .replace("[", " ") + .replace("]", " ") + .replace(",", " ") + .replace(";", " ") + .replace("|", " ") + + let split-tokens = normalized.split(" ").filter(token => token != "") + if split-tokens.len() > 0 { + let expanded = () + for token in split-tokens { + if token in phonetic-vowel-presets { + expanded.push(token) + } else { + let pieces = token.clusters() + if pieces.all(piece => piece in phonetic-vowel-presets) { + for piece in pieces { + expanded.push(piece) + } + } else { + expanded.push(token) + } + } + } + expanded + } else { + normalized.clusters().filter(token => token in phonetic-vowel-presets) + } +} + +#let _next-rand(state) = calc.rem(state * 48271 + 1, 2147483647) + +#let _rand-unit(state) = { + let next = _next-rand(state) + (next, calc.abs(float(next)) / 2147483646.0) +} + +#let _rand-symmetric(state, spread) = { + let state-0 = state + let acc = 0.0 + for _ in range(6) { + let sample = _rand-unit(state-0) + state-0 = sample.at(0) + acc += sample.at(1) + } + (state-0, (acc - 3.0) * spread) +} + +#let _token-cloud(vowels, preset-map, n, sd, sd2, seed) = { + let tokens = () + let state = calc.max(seed, 1) + for (index, vowel) in vowels.enumerate() { + let mean = preset-map.at(vowel) + for token-index in range(n) { + let jitter-f1 = _rand-symmetric(state + index * 101 + token-index * 17, sd) + let jitter-f2 = _rand-symmetric(jitter-f1.at(0) + 29, sd2) + state = jitter-f2.at(0) + tokens.push(( + vowel: vowel, + f1: mean.f1 + jitter-f1.at(1), + f2: mean.f2 + jitter-f2.at(1), + )) + } + } + tokens +} + +#let _centroid-pairs(vowels, preset-map) = { + vowels.map(vowel => { + let mean = preset-map.at(vowel) + ( + vowel: vowel, + f1: mean.f1, + f2: mean.f2, + ) + }) +} + +#let _mean(values) = values.sum() / values.len() + +#let _sd(values) = { + if values.len() <= 1 { return 0.0 } + let mean = _mean(values) + let variance = values.map(value => calc.pow(value - mean, 2)).sum() / (values.len() - 1) + calc.sqrt(variance) +} + +#let _csv-tokens(source) = { + if type(source) != array { + return (error: [*Error:* `source` must be tabular data from `csv(..., row-type: dictionary)`], tokens: ()) + } + if source.len() == 0 { + return (error: [*Error:* `source` is empty], tokens: ()) + } + + let first = source.at(0) + let tokens = () + if type(first) == dictionary { + if not ("vowel" in first and "f1" in first and "f2" in first) { + return (error: [*Error:* CSV source must contain columns `"vowel"`, `"f1"`, and `"f2"`], tokens: ()) + } + for row in source { + if type(row) != dictionary or not ("vowel" in row and "f1" in row and "f2" in row) { + return (error: [*Error:* Every CSV row must contain `"vowel"`, `"f1"`, and `"f2"`], tokens: ()) + } + let vowel = ipa-to-unicode(str(row.at("vowel"))) + let f1 = float(row.at("f1")) + let f2 = float(row.at("f2")) + tokens.push((vowel: vowel, f1: f1, f2: f2)) + } + } else if type(first) == array { + if first.len() < 3 { + return (error: [*Error:* CSV source must contain columns `"vowel"`, `"f1"`, and `"f2"`], tokens: ()) + } + let headers = first.map(value => str(value)) + let vowel-idx = _index-of(headers, "vowel") + let f1-idx = _index-of(headers, "f1") + let f2-idx = _index-of(headers, "f2") + if vowel-idx == none or f1-idx == none or f2-idx == none { + return (error: [*Error:* CSV source must contain columns `"vowel"`, `"f1"`, and `"f2"`], tokens: ()) + } + for row in source.slice(1) { + if type(row) != array or calc.max(vowel-idx, f1-idx, f2-idx) >= row.len() { + return (error: [*Error:* Every CSV row must contain values for `"vowel"`, `"f1"`, and `"f2"`], tokens: ()) + } + let vowel = ipa-to-unicode(str(row.at(vowel-idx))) + let f1 = float(row.at(f1-idx)) + let f2 = float(row.at(f2-idx)) + tokens.push((vowel: vowel, f1: f1, f2: f2)) + } + } else { + return (error: [*Error:* `source` must be tabular data from `csv(...)`], tokens: ()) + } + (error: none, tokens: tokens) +} + +#let _centroids-from-tokens(tokens) = { + let vowels = _unique(tokens.map(token => token.vowel)) + vowels.map(vowel => { + let subset = tokens.filter(token => token.vowel == vowel) + ( + vowel: vowel, + f1: _mean(subset.map(token => token.f1)), + f2: _mean(subset.map(token => token.f2)), + sd-f1: _sd(subset.map(token => token.f1)), + sd-f2: _sd(subset.map(token => token.f2)), + ) + }) +} + +#let _warn(body) = text(fill: red.darken(20%), weight: "bold")[⚠ #body] + +#let _nice-tick-step(span, target: 6) = { + let rough = span / target + if rough <= 50 { 50 } else if rough <= 100 { 100 } else if rough <= 200 { 200 } else if rough <= 250 { 250 } else if ( + rough <= 500 + ) { 500 } else { 1000 } +} + +#let _make-ticks(minimum, maximum, step) = { + let start = int(calc.ceil(minimum / step) * step) + let stop = int(calc.floor(maximum / step) * step) + let ticks = () + let current = start + while current <= stop { + ticks.push(current) + current += step + } + ticks +} + +/// Create an illustrative F1/F2 vowel cloud for teaching. +/// +/// This function generates synthetic vowel tokens around built-in F1/F2 means +/// and displays them on an inverted F1/F2 diagram in the style common in +/// phonetics teaching. +/// +/// Data policy: +/// - If `vowels` is `"english"`, the means are based on the male averages in +/// Hillenbrand et al. (1995), lightly regularized for pedagogical clarity. +/// - Other vowel means in this module are schematic/illustrative defaults for +/// teaching and are not tied to a single empirical source. +/// +/// Arguments: +/// - vowels (string): Tipa-style/IPA vowel string or a built-in language name +/// such as `"english"`. +/// - source (array, optional): Tabular data from `csv(...)` with required +/// columns `vowel`, `f1`, and `f2`. Extra columns are ignored. +/// - sd (float): Standard deviation used for F1 jitter in Hz. +/// - sd2 (float, optional): Standard deviation for F2 jitter in Hz; defaults to `sd`. +/// - n (int): Number of synthetic tokens per vowel (default: 10). +/// - seed (int): Seed for deterministic token placement (default: 1). +/// - labels (bool): Show vowel labels at preset means (default: true). +/// - points (bool): Show synthetic tokens (default: true). +/// - centers (bool): Show explicit `+` mean markers (default: false). +/// - ellipse (bool): Show 1-SD ellipses centered on the vowel means (default: false). +/// - ellipse-stroke (stroke or auto): Stroke used for SD ellipses +/// (default: `0.8pt + luma(190)`). +/// - ellipse-fill (fill): Fill used for SD ellipses (default: none). +/// - grid (bool): Show the background grid (default: true). +/// - color-by-vowel (bool): Use a color cycle by vowel category (default: true). +/// - point-size (int): Marker size for synthetic tokens (default: 50). +/// - point-color (color or auto): Override token color; `auto` uses the plot cycle +/// (default: auto). +/// - point-alpha (ratio): Token transparency (default: 20%). +/// - vowel-color (color): Color used for vowel labels (default: black). +/// - vowel-size (length): Font size used for vowel labels (default: 20pt). +/// - vowel-weight (str): Font weight used for vowel labels (default: `"regular"`). +/// - axis-size (length): Font size used for axis labels and tick labels +/// (default: 10pt). +/// - scale (float): Overall scale factor for the figure (default: 1.0). +/// - x-label (content): X-axis label (default: `[F2 (Hz)]`). +/// - y-label (content): Y-axis label (default: `[F1 (Hz)]`). +/// - width (length): Diagram width (default: 10cm). +/// - height (length): Diagram height (default: 7cm). +/// +/// Notes: +/// - Language-name input is checked first. If `vowels` matches a built-in +/// language preset such as `"english"` or `"french"`, that inventory is used. +/// - Otherwise, the input is parsed through `ipa-to-unicode` by default, so +/// tipa-style strings like `"a e E o O i u"` work directly. +/// - In CSV mode, `source: csv("...")` assumes the first row is a header row. +/// - In synthetic mode, ellipses visualize the user-provided spread parameters +/// (`sd`, `sd2`); in CSV mode, they reflect sample standard deviations +/// computed from the observed tokens. +/// +/// Example: +/// ``` +/// #formants("i ɪ e ɛ æ a ə ɔ o ʊ u", sd: 80) +/// ``` +#let _formants_impl( + vowels: none, + source: none, + sd: 60, + sd2: none, + n: 10, + seed: 1, + labels: true, + points: true, + centers: false, + ellipse: false, + ellipse-stroke: 0.8pt + luma(190), + ellipse-fill: none, + grid: true, + color-by-vowel: true, + point-size: 50, + point-color: auto, + point-alpha: 20%, + vowel-color: black, + vowel-size: formants-default-vowel-size, + vowel-weight: "regular", + axis-size: 10pt, + scale: 1.0, + x-label: [F2 (Hz)], + y-label: [F1 (Hz)], + width: 10cm, + height: 7cm, +) = { + let tokens = () + let centroids = () + let spread2 = if sd2 == none { sd } else { sd2 } + let unknown = () + + if source != none { + let parsed = _csv-tokens(source) + if parsed.error != none { + return parsed.error + } + tokens = parsed.tokens + centroids = _centroids-from-tokens(tokens) + if tokens.len() == 0 { + return [*Error:* `source` is empty] + } + } else { + let preset-map = if vowels == "english" { + english-hillenbrand-male + } else { + phonetic-vowel-presets + } + let requested = _normalize-vowel-string(vowels) + let known = requested.filter(vowel => vowel in preset-map) + unknown = _unique(requested.filter(vowel => vowel not in preset-map)) + + if known.len() == 0 { + return _warn([No supported vowels found in input: "#vowels".]) + } + + tokens = _token-cloud(known, preset-map, n, sd, spread2, seed) + centroids = _centroid-pairs(known, preset-map).map(ct => (..ct, sd-f1: sd, sd-f2: spread2)) + } + + let f1-values = tokens.map(t => t.f1) + centroids.map(t => t.f1) + let f2-values = tokens.map(t => t.f2) + centroids.map(t => t.f2) + let pad-f1 = if source != none { + calc.max(calc.max(..centroids.map(ct => ct.sd-f1)) * 1.5, 60) + } else { + calc.max(sd * 1.5, 60) + } + let pad-f2 = if source != none { + calc.max(calc.max(..centroids.map(ct => ct.sd-f2)) * 1.5, 80) + } else { + calc.max(spread2 * 1.5, 80) + } + let f1-min = calc.min(..f1-values) - pad-f1 + let f1-max = calc.max(..f1-values) + pad-f1 + let f2-min = calc.min(..f2-values) - pad-f2 + let f2-max = calc.max(..f2-values) + pad-f2 + let x-step = _nice-tick-step(f2-max - f2-min) + let y-step = _nice-tick-step(f1-max - f1-min) + let x-ticks = _make-ticks(f2-min, f2-max, x-step) + let y-ticks = _make-ticks(f1-min, f1-max, y-step) + let groups = _unique(tokens.map(token => token.vowel)).map(vowel => ( + vowel, + tokens.filter(token => token.vowel == vowel), + )) + + let cycle = if color-by-vowel { + ( + rgb("#3b82f6"), + rgb("#ef4444"), + rgb("#10b981"), + rgb("#f59e0b"), + rgb("#8b5cf6"), + rgb("#ec4899"), + rgb("#14b8a6"), + rgb("#f97316"), + rgb("#06b6d4"), + rgb("#84cc16"), + ) + } else { + (rgb("#64748b"),) + } + + [ + #if unknown.len() > 0 { + _warn([Skipped unsupported vowel(s): #unknown.join(", ").]) + v(0.5em) + } + #context { + let doc-font = text.font + let scaled-width = width * scale + let scaled-height = height * scale + let scaled-point-size = point-size * scale + let scaled-vowel-size = vowel-size * scale + let scaled-axis-size = axis-size * scale + let axis-tick-color = gray.darken(35%) + let scaled-axis-stroke = 0.8pt * scale + axis-tick-color + let scaled-grid-stroke = 0.5pt * scale + luma(220) + let scaled-center-size = 48 * scale + let scaled-center-stroke = 1pt * scale + let tick-pad = scaled-axis-size * 0.35 + show lq.selector(lq.tick-label): it => [] + let x-tick-labels = x-ticks.map(value => text(font: doc-font, size: scaled-axis-size)[#str(value)]) + let y-tick-labels = y-ticks.map(value => text(font: doc-font, size: scaled-axis-size)[#str(value)]) + let max-x-tick-height = calc.max(..x-tick-labels.map(label => measure(label).height)) + let max-y-tick-width = calc.max(..y-tick-labels.map(label => measure(label).width)) + let x-label-pad = max-x-tick-height + scaled-axis-size * 0.9 + let y-label-pad = max-y-tick-width + scaled-axis-size * 1.05 + let axis-label-color = black + let x-axis-label = text(font: doc-font, size: scaled-axis-size, fill: axis-label-color)[#x-label] + let y-axis-label = rotate( + -90deg, + text(font: doc-font, size: scaled-axis-size, fill: axis-label-color)[#y-label], + reflow: true, + ) + lq.diagram( + width: scaled-width, + height: scaled-height, + xlim: (f2-max, f2-min), + ylim: (f1-max, f1-min), + xlabel: none, + ylabel: none, + xscale: "linear", + yscale: "linear", + xaxis: ( + position: top, + stroke: scaled-axis-stroke, + mirror: false, + exponent: none, + tick-distance: x-step, + ), + yaxis: ( + position: right, + stroke: scaled-axis-stroke, + mirror: false, + exponent: none, + tick-distance: y-step, + ), + grid: if grid { scaled-grid-stroke } else { none }, + fill: white, + cycle: cycle, + legend: none, + ..centroids.map(ct => if ellipse { + lq.ellipse( + ct.f2 - ct.sd-f2, + ct.f1 - ct.sd-f1, + width: ct.sd-f2 * 2, + height: ct.sd-f1 * 2, + stroke: ellipse-stroke, + fill: ellipse-fill, + ) + } else { + none + }), + ..groups.map(group => { + let vowel = group.at(0) + let cloud = group.at(1) + if points { + lq.scatter( + cloud.map(token => token.f2), + cloud.map(token => token.f1), + mark: "o", + color: point-color, + size: cloud.map(_ => scaled-point-size), + stroke: none, + alpha: point-alpha, + label: if color-by-vowel { context text(font: phonokit-font.get())[#vowel] } else { none }, + ) + } else { + none + } + }), + ..centroids.map(ct => if centers { + lq.scatter( + (ct.f2,), + (ct.f1,), + mark: "+", + size: (scaled-center-size,), + color: black, + stroke: (paint: black, thickness: scaled-center-stroke), + ) + } else { + none + }), + ..centroids.map(ct => if labels { + lq.place( + ct.f2, + ct.f1, + align: center + horizon, + context text( + font: phonokit-font.get(), + weight: vowel-weight, + size: scaled-vowel-size, + fill: vowel-color, + )[#ct.vowel], + ) + } else { + none + }), + ..x-ticks.map(value => lq.place( + value, + 0%, + align: bottom + center, + pad(bottom: tick-pad, text(font: doc-font, size: scaled-axis-size, fill: axis-tick-color)[#str(value)]), + )), + ..y-ticks.map(value => lq.place( + 100%, + value, + align: left + horizon, + pad(left: tick-pad, text(font: doc-font, size: scaled-axis-size, fill: axis-tick-color)[#str(value)]), + )), + lq.place( + 50%, + 0%, + align: bottom + center, + pad(bottom: x-label-pad, x-axis-label), + ), + lq.place( + 100%, + 50%, + align: left + horizon, + pad(left: y-label-pad, y-axis-label), + ), + ) + } + ] +} + +#let formants(..args) = { + let pos = args.pos() + let named = args.named() + let vowels = if "vowels" in named { named.at("vowels") } else { pos.at(0, default: none) } + + _formants_impl( + vowels: vowels, + source: named.at("source", default: none), + sd: named.at("sd", default: 60), + sd2: named.at("sd2", default: none), + n: named.at("n", default: 10), + seed: named.at("seed", default: 1), + labels: named.at("labels", default: true), + points: named.at("points", default: true), + centers: named.at("centers", default: false), + ellipse: named.at("ellipse", default: false), + ellipse-stroke: named.at("ellipse-stroke", default: 0.8pt + luma(190)), + ellipse-fill: named.at("ellipse-fill", default: none), + grid: named.at("grid", default: true), + color-by-vowel: named.at("color-by-vowel", default: true), + point-size: named.at("point-size", default: 50), + point-color: named.at("point-color", default: auto), + point-alpha: named.at("point-alpha", default: 20%), + vowel-color: named.at("vowel-color", default: black), + vowel-size: named.at("vowel-size", default: formants-default-vowel-size), + vowel-weight: named.at("vowel-weight", default: "regular"), + axis-size: named.at("axis-size", default: 10pt), + scale: named.at("scale", default: 1.0), + x-label: named.at("x-label", default: [F2 (Hz)]), + y-label: named.at("y-label", default: [F1 (Hz)]), + width: named.at("width", default: 10cm), + height: named.at("height", default: 7cm), + ) +} + +#let _vot-label(value, abbreviation) = { + let shown = if calc.abs(value) == calc.round(calc.abs(value)) { + str(int(value)) + } else { + str(value) + } + [#abbreviation: #shown ms] +} + +#let _vot-ui-labels = ( + "en": ( + closure: [closure], + release: [release], + voicing-onset: [voicing onset], + vowel: [vowel], + aspiration: [aspiration], + prevoicing: [prevoicing], + vot-abbr: [VOT], + ), + "fr": ( + closure: [occlusion], + release: [relâchement], + voicing-onset: [début du voisement], + vowel: [voyelle], + aspiration: [aspiration], + prevoicing: [prévoisement], + vot-abbr: [DEV], + ), + "pt": ( + closure: [oclusão], + release: [soltura], + voicing-onset: [início do vozeamento], + vowel: [vogal], + aspiration: [aspiração], + prevoicing: [pré-vozeamento], + vot-abbr: [VOT], + ), +) + +#let _vot-label-or-default(label, default) = { + if label == auto { + default + } else { + label + } +} + +#let vot( + vot, + closure: 40, + vowel: 60, + scale: 1.0, + label: auto, + keys: false, + ui-lang: "en", + closure-label: auto, + release-label: auto, + voicing-label: auto, + vowel-label: auto, + vot-label: auto, + interval-label: auto, + interval-key: auto, + closure-segment: none, + interval-segment: none, + vowel-segment: none, + segment-size: 10pt, + fill-closure: luma(230), + fill-vowel: white, + fill-aspiration: luma(245), + voicing: true, + voicing-stroke: auto, +) = context { + assert(type(vot) == int or type(vot) == float, message: "vot must be a number of milliseconds") + assert(type(closure) == int or type(closure) == float, message: "closure must be a number of milliseconds") + assert(type(vowel) == int or type(vowel) == float, message: "vowel must be a number of milliseconds") + let ui-locale = resolve-ui-lang(ui-lang) + if ui-locale == none { + return ui-lang-error(ui-lang) + } + let ui-labels = _vot-ui-labels.at(ui-locale) + + let vot-ms = float(vot) + let closure-ms = calc.max(float(closure), 0.0) + let vowel-ms = calc.max(float(vowel), 1.0) + let release = 0.0 + let voicing-onset-time = vot-ms + let closure-start-time = -closure-ms + calc.min(vot-ms, 0.0) + let left-time = calc.min(closure-start-time, calc.min(vot-ms, 0.0)) + let right-time = calc.max(vot-ms, 0.0) + vowel-ms + let time-span = calc.max(right-time - left-time, 1.0) + let width = calc.max(4.5, time-span * 0.045) + let tx = t => (t - left-time) / time-span * width + let show-label = if label == auto { true } else { label } + let closure-text = _vot-label-or-default(closure-label, ui-labels.closure) + let release-text = _vot-label-or-default(release-label, ui-labels.release) + let voicing-text = _vot-label-or-default(voicing-label, ui-labels.voicing-onset) + let vowel-text = _vot-label-or-default(vowel-label, ui-labels.vowel) + let vot-text = _vot-label-or-default(vot-label, _vot-label(vot-ms, ui-labels.vot-abbr)) + let interval-text = _vot-label-or-default(interval-label, ui-labels.aspiration) + let region-y1 = 0.1 + let region-y2 = 0.85 + let bracket-y = -0.86 + let event-label-y = 1.56 + let event-line-top = if keys { event-label-y } else { region-y2 } + let legend-y = 2.2 + let release-x = tx(release) + let voicing-x = tx(voicing-onset-time) + let vowel-start = calc.max(vot-ms, 0.0) + let scale-factor = scale + let doc-font-size = 8pt * scale-factor + let label-size = 7pt * scale-factor + let segment-font-size = segment-size * scale-factor + let event-size = 6pt * scale-factor + let region-stroke = 0.7pt * scale-factor + black + let release-stroke = ( + paint: black, + thickness: 0.7pt * scale-factor, + dash: "dashed", + cap: "butt", + join: "miter", + ) + let aspiration-stroke = ( + paint: gray.darken(35%), + thickness: 0.7pt * scale-factor, + dash: "dotted", + cap: "butt", + join: "miter", + ) + let wave-stroke = if voicing-stroke == auto { + (paint: gray.darken(45%), thickness: 0.55pt * scale-factor) + } else { + voicing-stroke + } + let waveform-stroke = if type(wave-stroke) == dictionary { + (cap: "round", join: "round", ..wave-stroke) + } else { + wave-stroke + } + let text-label = body => text(size: label-size, font: phonokit-font.get())[#body] + let text-doc = body => text(size: doc-font-size, font: phonokit-font.get())[#body] + let text-segment = body => { + let rendered = if type(body) == str { ipa-to-unicode(body) } else { body } + text(size: segment-font-size, font: phonokit-font.get())[#rendered] + } + let canvas-label-width = body => measure(text-label(body)).width / 1cm / scale-factor + let label-padding = 0.35 + let interval-width = calc.abs(voicing-x - release-x) + let closure-width = calc.abs(release-x - tx(closure-start-time)) + let vowel-width = calc.abs(tx(right-time) - tx(vowel-start)) + let minimum-labelled-interval = 5.0 + let show-closure-label = closure-ms > 0 and canvas-label-width(closure-text) + label-padding <= closure-width + let show-vowel-label = canvas-label-width(vowel-text) + label-padding <= vowel-width + let show-interval-segment = vot-ms >= minimum-labelled-interval and interval-segment != none + let show-positive-interval-label = ( + vot-ms >= minimum-labelled-interval + and interval-label != none + and canvas-label-width(interval-text) + label-padding <= interval-width + ) + let legend-interval = vot-ms > 0 and interval-label != none and not show-positive-interval-label + let interval-key-text = if interval-key != auto { + interval-key + } else if type(interval-text) == str and lower(interval-text).contains("aspir") { + [A] + } else { + [I] + } + let legend-text = { + [R = #release-text; V = #voicing-text] + if legend-interval { + [; #interval-key-text = #interval-text] + } + } + let event-tag = body => box( + width: 0.28cm * scale-factor, + height: 0.28cm * scale-factor, + stroke: region-stroke, + fill: white, + align(center + horizon, text(size: event-size, font: phonokit-font.get())[#body]), + ) + let event-key-width = measure(event-tag(interval-key-text)).width / 1cm / scale-factor + let show-interval-key = keys and legend-interval and event-key-width + label-padding <= interval-width + let region-label-y = region-y2 + 0.24 + let interval-label-y = region-label-y + let region-label-anchor = "base" + let segment-y = -0.22 + let event-line-bottom = bracket-y - 0.12 + + box(inset: 1em, baseline: 50%, cetz.canvas(length: scale-factor * 1cm, { + import cetz.draw: * + + let draw-wave = (x1, x2, y, amp: 0.08, step: 0.025, cycle-width: 0.65) => { + let span = x2 - x1 + if span > step { + let count = int(calc.max(18, calc.floor(span / step))) + let cycles = calc.max(1.0, span / cycle-width) + let points = () + for i in range(count) { + let p = i / (count - 1) + points.push((x1 + p * span, y + amp * calc.sin(p * cycles * 360deg))) + } + line(..points, stroke: waveform-stroke) + } + } + let noise-sample = i => { + (calc.sin(i * 137deg) * 0.55) + (calc.sin(i * 271deg) * 0.32) + (calc.sin(i * 53deg) * 0.18) + } + let draw-noise = (x1, x2, y, amp: 0.055, step: 0.04) => { + let span = x2 - x1 + if span > step { + let count = int(calc.max(8, calc.floor(span / step))) + let points = () + for i in range(count) { + let p = i / (count - 1) + points.push((x1 + p * span, y + amp * noise-sample(i))) + } + line(..points, stroke: waveform-stroke) + } + } + let draw-prevoicing = (x1, x2, y, amp: 0.035, step: 0.04, cycle-width: 0.32) => { + let span = x2 - x1 + if span > step { + let count = int(calc.max(10, calc.floor(span / step))) + let cycles = calc.max(1.0, span / cycle-width) + let points = () + for i in range(count) { + let p = i / (count - 1) + let modulation = 0.75 + 0.25 * calc.sin(p * 137deg) + let sample = ( + 0.45 * modulation * calc.sin(p * cycles * 360deg) + + 0.55 * calc.sin(p * cycles * 900deg + 37deg) + + 0.42 * calc.sin(p * cycles * 1450deg + 83deg) + + 0.34 * noise-sample(i) + ) + points.push((x1 + p * span, y + amp * sample)) + } + line(..points, stroke: waveform-stroke) + } + } + let draw-prevoicing-transition = (x1, x2, y, step: 0.04, cycle-width: 0.36) => { + let span = x2 - x1 + if span > step { + let count = int(calc.max(8, calc.floor(span / step))) + let cycles = calc.max(1.0, span / cycle-width) + let points = () + for i in range(count) { + let p = i / (count - 1) + let closure = (1.0 - p) * 0.008 * calc.sin(p * 180deg) + let prevoice = ( + p + * 0.035 + * ( + 0.45 * calc.sin(p * cycles * 360deg) + + 0.55 * calc.sin(p * cycles * 900deg + 37deg) + + 0.42 * calc.sin(p * cycles * 1450deg + 83deg) + + 0.34 * noise-sample(i) + ) + ) + points.push((x1 + p * span, y + closure + prevoice)) + } + line(..points, stroke: waveform-stroke) + } + } + let draw-waveforms = () => { + if voicing { + if closure-ms > 0 { + if vot-ms < 0 { + let transition-width = calc.min(voicing-x - tx(closure-start-time), 0.32) + let transition-start-x = voicing-x - transition-width + draw-wave( + tx(closure-start-time), + transition-start-x, + region-y1 + 0.16, + amp: 0.008, + step: 0.07, + cycle-width: 1.2, + ) + draw-prevoicing-transition(transition-start-x, voicing-x, region-y1 + 0.16) + draw-prevoicing(voicing-x, release-x, region-y1 + 0.16) + } else { + draw-wave(tx(closure-start-time), release-x, region-y1 + 0.16, amp: 0.008, step: 0.07, cycle-width: 1.2) + } + } + if vot-ms > 0 { + draw-noise(release-x, voicing-x, region-y1 + 0.16) + } + draw-wave(tx(vowel-start), tx(right-time), region-y1 + 0.16) + } + } + + if vot-ms > 0 { + rect( + (release-x, region-y1), + (voicing-x, region-y2), + fill: fill-aspiration, + stroke: none, + ) + line((release-x, region-y2), (voicing-x, region-y2), stroke: aspiration-stroke) + line((release-x, region-y1), (voicing-x, region-y1), stroke: aspiration-stroke) + } + + line((release-x, event-line-bottom), (release-x, event-line-top), stroke: release-stroke) + if vot-ms != 0 { + line((voicing-x, event-line-bottom), (voicing-x, event-line-top), stroke: release-stroke) + } + + if closure-ms > 0 { + rect( + (tx(closure-start-time), region-y1), + (release-x, region-y2), + fill: fill-closure, + stroke: region-stroke, + ) + if show-closure-label { + content( + ((tx(closure-start-time) + release-x) / 2, region-label-y), + text-label(closure-text), + anchor: region-label-anchor, + ) + } + if closure-segment != none { + let closure-segment-right-x = if vot-ms < 0 { voicing-x } else { release-x } + content( + ((tx(closure-start-time) + closure-segment-right-x) / 2, segment-y), + text-segment(closure-segment), + anchor: "center", + ) + } + } + + if vot-ms > 0 { + if show-positive-interval-label { + content( + ((release-x + voicing-x) / 2, interval-label-y), + text-label(interval-text), + anchor: region-label-anchor, + ) + } else if interval-label != none and show-interval-key { + content( + ((release-x + voicing-x) / 2, interval-label-y), + event-tag(interval-key-text), + anchor: "center", + ) + } + if show-interval-segment { + content( + ((release-x + voicing-x) / 2, segment-y), + text-segment(interval-segment), + anchor: "center", + ) + } + } + + rect( + (tx(vowel-start), region-y1), + (tx(right-time), region-y2), + fill: fill-vowel, + stroke: region-stroke, + ) + if show-vowel-label { + content( + ((tx(vowel-start) + tx(right-time)) / 2, region-label-y), + text-label(vowel-text), + anchor: region-label-anchor, + ) + } + if vowel-segment != none { + content( + ((tx(vowel-start) + tx(right-time)) / 2, segment-y), + text-segment(vowel-segment), + anchor: "center", + ) + } + + if keys { + content( + (release-x, event-label-y), + event-tag([R]), + anchor: "center", + ) + if vot-ms != 0 { + content( + (voicing-x, event-label-y), + event-tag([V]), + anchor: "center", + ) + } + content( + (tx(right-time), legend-y), + text-doc(legend-text), + anchor: "east", + ) + } + + draw-waveforms() + + if show-label { + if vot-ms == 0 { + content( + (release-x, bracket-y - 0.35), + text-doc(vot-text), + anchor: "center", + ) + } else { + let start-x = calc.min(release-x, voicing-x) + let end-x = calc.max(release-x, voicing-x) + line((start-x, bracket-y), (end-x, bracket-y), stroke: region-stroke) + line((start-x, bracket-y - 0.12), (start-x, bracket-y + 0.12), stroke: region-stroke) + line((end-x, bracket-y - 0.12), (end-x, bracket-y + 0.12), stroke: region-stroke) + content( + ((start-x + end-x) / 2, bracket-y - 0.35), + text-doc(vot-text), + anchor: "center", + ) + } + } + })) +} diff --git a/packages/preview/phonokit/0.6.1/prosody.typ b/packages/preview/phonokit/0.6.1/prosody.typ new file mode 100644 index 0000000000..277244cd4a --- /dev/null +++ b/packages/preview/phonokit/0.6.1/prosody.typ @@ -0,0 +1,2343 @@ +#import "@preview/cetz:0.5.2" +#import "ipa.typ": ipa-to-unicode +#import "_config.typ": phonokit-font + +// Follows the same spacing convention as #ipa(): +// - Backslash commands need spaces: "t \\ae p" → "tæp" +// - Single characters don't: "SIp" → "ʃɪp" +#let convert-prosody-input(input) = { + let result = "" + let buffer = "" + let chars = input.codepoints() + let i = 0 + + while i < chars.len() { + let char = chars.at(i) + + // Check if this is a structural marker + // Note: ' and , are stress markers (not printed, just for structure) + if char in ("'", ",", ".", "(", ")") { + // First, process any buffered content + if buffer != "" { + result += ipa-to-unicode(buffer) + buffer = "" + } + // Add the structural marker (will be stripped during parsing) + result += char + } else { + // Add to buffer (including spaces, which will be handled by ipa-to-unicode) + buffer += char + } + + i += 1 + } + + // Process any remaining buffer + if buffer != "" { + result += ipa-to-unicode(buffer) + } + + result +} + +#let is-vowel(cluster) = { + // Check the base character (first codepoint) to handle both + // precomposed vowels (like "ã") and combining forms (like "a" + "̃") + let base = cluster.codepoints().at(0) + + // Check if base is a vowel + // Covers the full IPA vowel inventory (see vowels.typ), plus rhotacized ɚ/ɝ + let base-is-vowel = ( + base + in ( + "a", "e", "i", "o", "u", "y", "ɚ", "ɝ", "ɯ", "ɐ", "ɒ", "æ", "ɛ", "ɪ", "ɔ", "ø", "œ", "ɨ", "ʉ", "ʊ", "ə", "ʌ", + "ɑ", "ʏ", "ɘ", "ɵ", "ɤ", "ɜ", "ɞ", "ɶ", + ) + ) + + // Also check for diphthongs and precomposed forms as complete clusters + let cluster-is-vowel = ( + cluster in ("aɪ", "eɪ", "oɪ", "aʊ", "oʊ", "ã", "ẽ", "ĩ", "õ", "ũ", "ỹ", "ɛ̃", "ɔ̃", "œ̃", "ɑ̃") + ) + + // Check if cluster contains syllabicity marker (̩ U+0329, or ̍ U+030D, + // which ipa-to-unicode uses instead on descenders: ŋ̍, ɲ̍) + // Syllabic consonants (like m̩, n̩, l̩) function as vowels/nuclei + let is-syllabic = cluster.contains("\u{0329}") or cluster.contains("\u{030D}") + + base-is-vowel or cluster-is-vowel or is-syllabic +} + +// Custom clustering that handles: +// 1. Affricates (tie bar U+0361) - merge "t͡" + "ʃ" → "t͡ʃ" +// 2. Spacing modifiers (like "ʰ") - merge "b" + "ʰ" → "bʰ" +// 3. Length marks (ː) - merge "a" + "ː" → "aː" as atomic unit +#let smart-clusters(text) = { + let basic-clusters = text.clusters() + let result = () + let i = 0 + + // Define spacing modifiers that should merge with preceding segment + let spacing-modifiers = ("ʰ", "ʷ", "ʲ", "ˠ", "ˤ", "̚") + // Length mark (U+02D0) + let length-mark = "ː" + + while i < basic-clusters.len() { + let current = basic-clusters.at(i) + + // Check if this cluster contains a tie bar + if current.contains("͡") { + // This cluster has a tie bar - merge with next cluster (affricate) + if i + 1 < basic-clusters.len() { + result.push(current + basic-clusters.at(i + 1)) + i += 2 // Skip both clusters + } else { + result.push(current) + i += 1 + } + } else if i + 1 < basic-clusters.len() and basic-clusters.at(i + 1) in spacing-modifiers { + // Next cluster is a spacing modifier - merge with current + result.push(current + basic-clusters.at(i + 1)) + i += 2 // Skip both clusters + } else if i + 1 < basic-clusters.len() and basic-clusters.at(i + 1) == length-mark { + // Next cluster is a length mark - merge with current (atomic long vowel) + result.push(current + basic-clusters.at(i + 1)) + i += 2 // Skip both clusters + } else { + // Regular cluster + result.push(current) + i += 1 + } + } + + result +} + +#let parse-syllable(syll) = { + // Use smart-clusters() to properly handle affricates and combining diacritics + let clusters = smart-clusters(syll) + let onset = "" + let nucleus = "" + let coda = "" + let found-nucleus = false + + for cluster in clusters { + if is-vowel(cluster) { + nucleus += cluster + found-nucleus = true + } else if not found-nucleus { + onset += cluster + } else { + coda += cluster + } + } + + (onset: onset, nucleus: nucleus, coda: coda) +} + +// Helper function to draw syllable internal structure +// Vertical spacing shared by the prosodic trees. Each gap between two tiers is +// (clearance below the upper label) + (visible line) + (clearance above the lower label). +// `distance` scales only the visible line, which never drops below `min-tier-line`, +// so the same lower bound applies at every level of every function. +#let min-tier-line = 0.15 + +// With spacing: "auto", a branch spanning a horizontal run h gets at least +// branch-slope × h of vertical line (≈ the angle of the original default trees) +#let branch-slope = 0.28 + +#let tier-line(natural, distance, level) = { + let factor = 1.0 + if distance != none { + for entry in distance { + if entry.at(0) == level { + factor = calc.max(0, entry.at(1)) + } + } + } + calc.max(min-tier-line, natural * factor) +} + +// Long vowels: horizontal distance between their two moras +#let long-mora-spacing = 0.4 + +// In moraic trees, non-moraic segments hang straight from σ, skipping the μ tier. +// Minimum horizontal distance from the nucleus to the nearest such segment so that +// its line clears the nucleus μ label(s); depends on the actual tier heights. +#let moraic-reach(sigma-y, mora-y, terminal-y, long: false) = { + let top = sigma-y + 0.25 + let f = (top - (mora-y + 0.2)) / (top - (terminal-y + 0.30)) + let mora-edge = (if long { long-mora-spacing / 2 } else { 0 }) + 0.15 + (mora-edge + 0.15) / f +} + +#let check-spacing(spacing) = assert( + spacing in ("auto", "fixed"), + message: "spacing must be \"auto\" or \"fixed\"", +) + +#let draw-syllable-structure( + x-offset, + sigma-y, + syll, + terminal-y, + diagram-scale: 1.0, + geminate-coda-x: none, + geminate-onset-x: none, + geminate-coda-text: none, + geminate-onset-text: none, + compact: false, + or-y: none, + n-y: none, +) = { + import cetz.draw: * + + // O/R and N/C level positions (defaults preserve original hardcoded offsets) + let or-level = if or-y == none { sigma-y - 0.75 } else { or-y } + let n-level = if n-y == none { sigma-y - 1.65 } else { n-y } + + // Choose offset values based on compact mode + let (line-offset, text-offset) = if compact { + (0.70, 0.40) // Compact: shorter lines, raised segments + } else { + (0.30, 0) // Standard: longer lines, lower segments + } + + let has-onset = syll.onset != "" + let has-coda = syll.coda != "" + + // Calculate segment counts for adaptive spacing + // Use smart-clusters() to properly count segments including affricates + let onset-segments = if has-onset { smart-clusters(syll.onset) } else { () } + let num-onset = if has-onset { onset-segments.len() } else { 0 } + + let nucleus-segments = smart-clusters(syll.nucleus) + let num-nucleus = nucleus-segments.len() + + let coda-segments = if has-coda { smart-clusters(syll.coda) } else { () } + let num-coda = if has-coda { coda-segments.len() } else { 0 } + + let segment-spacing = 0.35 + let min-gap = 0.75 + + // Headedness: Rhyme is head of syllable, Nucleus is head of Rhyme + // Heads align vertically, non-heads are angled + let rhyme-x = x-offset // vertical (head of syllable) + let nucleus-x = rhyme-x // MUST stay at rhyme-x (vertical, head of rhyme) + + // Adaptive positioning for onset (move left if many segments) + let onset-x = if has-onset { + let min-offset = (num-onset - 1) * segment-spacing / 2 + (num-nucleus - 1) * segment-spacing / 2 + min-gap + let default-offset = 0.7 + if min-offset > default-offset { x-offset - min-offset } else { x-offset - default-offset } + } else { + x-offset + } + + // Adaptive positioning for coda (move right to avoid nucleus segments) + let coda-x = if has-coda { + let min-offset = (num-nucleus + num-coda - 2) * segment-spacing / 2 + min-gap + let default-offset = 0.7 + if min-offset > default-offset { rhyme-x + min-offset } else { rhyme-x + default-offset } + } else { + rhyme-x + } + + // Branches from syllable + if has-onset { + line((x-offset, sigma-y + 0.25), (onset-x, or-level + 0.30)) + content((onset-x, or-level), context text(size: 10 * diagram-scale * 1pt, font: phonokit-font.get())[O]) + + // Branch to each onset segment + let onset-segments = smart-clusters(syll.onset) + let num-onset = onset-segments.len() + + // Always draw all segments, but handle geminates specially + let onset-total-width = (num-onset - 1) * segment-spacing + let onset-start-x = onset-x - onset-total-width / 2 + + for (i, segment) in onset-segments.enumerate() { + let seg-x = onset-start-x + i * segment-spacing + + // Check if this segment is the geminate + let is-geminate = (geminate-onset-x != none and segment == geminate-onset-text) + + if is-geminate { + // Geminate: draw line to geminate position (text drawn separately in geminate section) + line((onset-x, or-level - 0.35), (geminate-onset-x, terminal-y + line-offset)) + } else { + // Normal segment: draw line and text + line((onset-x, or-level - 0.35), (seg-x, terminal-y + line-offset)) + content( + (seg-x, terminal-y + text-offset), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + } + + // Rhyme branch + line((x-offset, sigma-y + 0.25), (rhyme-x, or-level + 0.30)) + content((rhyme-x, or-level), context text(size: 10 * diagram-scale * 1pt, font: phonokit-font.get())[R]) + + // Nucleus + line((rhyme-x, or-level - 0.35), (nucleus-x, n-level + 0.30)) + content((nucleus-x, n-level), context text(size: 10 * diagram-scale * 1pt, font: phonokit-font.get())[N]) + + // Branch to each nucleus segment + let nucleus-total-width = (num-nucleus - 1) * segment-spacing + let nucleus-start-x = nucleus-x - nucleus-total-width / 2 + + for (i, segment) in nucleus-segments.enumerate() { + let seg-x = nucleus-start-x + i * segment-spacing + line((nucleus-x, n-level - 0.25), (seg-x, terminal-y + line-offset)) + content( + (seg-x, terminal-y + text-offset), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + + // Coda (if exists) + if has-coda { + line((rhyme-x, or-level - 0.35), (coda-x, n-level + 0.30)) + content((coda-x, n-level), context text(size: 10 * diagram-scale * 1pt, font: phonokit-font.get())[C]) + + // Branch to each coda segment + // Always draw all segments, but handle geminates specially + let coda-total-width = (num-coda - 1) * segment-spacing + let coda-start-x = coda-x - coda-total-width / 2 + + for (i, segment) in coda-segments.enumerate() { + let seg-x = coda-start-x + i * segment-spacing + + // Check if this segment is the geminate + let is-geminate = (geminate-coda-x != none and segment == geminate-coda-text) + + if is-geminate { + // Geminate: draw line to geminate position (text drawn separately in geminate section) + line((coda-x, n-level - 0.25), (geminate-coda-x, terminal-y + line-offset)) + } else { + // Normal segment: draw line and text + line((coda-x, n-level - 0.25), (seg-x, terminal-y + line-offset)) + content( + (seg-x, terminal-y + text-offset), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + } +} + +// Visualizes a single syllable's internal structure (On/Rh/Nu/Co) +// Now accepts IPA-style input like "k a" or "'t a" +#let syllable(input, scale: 1.0, symbol: ("σ",), distance: none, spacing: "auto") = { + check-spacing(spacing) + // Check for syllable boundary markers + if input.contains(".") { + return text(fill: red, weight: "bold")[⚠ Warning: For more than one syllable, use \#foot() or \#word().] + } + + // Check for problematic diacritic sequences + let problematic-sequences = ("''", ",,", "\\* \\*", "\\t \\t", "::", "((", "))") + for seq in problematic-sequences { + if input.contains(seq) { + return text(fill: red, weight: "bold")[⚠ Warning: Problematic sequence involving diacritics: "#seq"] + } + } + + // Convert IPA-style input to Unicode + let converted = convert-prosody-input(input) + + // Parse a single syllable + // Strip ALL stress markers (' for primary, , for secondary) regardless of position + let stressed = converted.starts-with("'") or converted.starts-with(",") + let clean-input = converted.replace("'", "").replace(",", "") + + let parsed = parse-syllable(clean-input) + + // Check for too many codas (limit: 5 to avoid crossing lines) + let coda-segments-temp = if parsed.coda != "" { smart-clusters(parsed.coda) } else { () } + if coda-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many coda consonants (max 5 to avoid line crossings). Found: #coda-segments-temp.len()] + } + + // Check for too many onsets (limit: 5 to avoid crossing lines) + let onset-segments-temp = if parsed.onset != "" { smart-clusters(parsed.onset) } else { () } + if onset-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many onset consonants (max 5 to avoid line crossings). Found: #onset-segments-temp.len()] + } + + // Check for too many nucleus segments (limit: 5) + let nucleus-segments-temp = smart-clusters(parsed.nucleus) + if nucleus-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many nucleus segments (max 5 to avoid line crossings). Found: #nucleus-segments-temp.len()] + } + let syll = ( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: stressed, + ) + + let sym_syll = symbol.at(0, default: "σ") + + let diagram-scale = scale + box(baseline: 50%, cetz.canvas(length: 1cm * diagram-scale, { + import cetz.draw: * + set-style(stroke: 0.7 * diagram-scale * 1pt) + + let line-len(natural, level) = tier-line(natural, distance, level) + + let sigma-y = 0 + let x-offset = 0 + + // Default lines never get shorter going up the tree (each is at least as + // long as the one directly below it); explicit `distance` factors are + // applied afterwards. No upper tiers here, so `spacing` has no effect. + + // Sub-syllable level positions (0=σ→O/R, 1=O/R→N/C, 2=N/C→segments) + let n-seg-line = 0.55 + let or-n-line = calc.max(0.55, n-seg-line) + let sigma-or-line = calc.max(0.70, or-n-line) + let or-level = sigma-y + 0.25 - line-len(sigma-or-line, 0) - 0.30 + let n-level = or-level - 0.35 - line-len(or-n-line, 1) - 0.35 + let terminal-y = n-level - 0.25 - line-len(n-seg-line, 2) - 0.70 + + // Standalone syllable spacing: Nu/Co positioned lower (halfway between Rh and segments) + let line-offset = 0.70 + let text-offset = 0.40 + + let has-onset = syll.onset != "" + let has-coda = syll.coda != "" + + // Calculate segment counts for adaptive spacing + let onset-segments = if has-onset { smart-clusters(syll.onset) } else { () } + let num-onset = if has-onset { onset-segments.len() } else { 0 } + + let nucleus-segments = smart-clusters(syll.nucleus) + let num-nucleus = nucleus-segments.len() + + let coda-segments = if has-coda { smart-clusters(syll.coda) } else { () } + let num-coda = if has-coda { coda-segments.len() } else { 0 } + + let segment-spacing = 0.35 + let min-gap = 0.75 + + // Headedness: Rhyme is head of syllable, Nucleus is head of Rhyme + let rhyme-x = x-offset + let nucleus-x = rhyme-x + + // Adaptive positioning for onset + let onset-x = if has-onset { + let min-offset = (num-onset - 1) * segment-spacing / 2 + (num-nucleus - 1) * segment-spacing / 2 + min-gap + let default-offset = 0.7 + if min-offset > default-offset { x-offset - min-offset } else { x-offset - default-offset } + } else { + x-offset + } + + // Adaptive positioning for coda + let coda-x = if has-coda { + let min-offset = (num-nucleus + num-coda - 2) * segment-spacing / 2 + min-gap + let default-offset = 0.7 + if min-offset > default-offset { rhyme-x + min-offset } else { rhyme-x + default-offset } + } else { + rhyme-x + } + + // Syllable node + content((x-offset, sigma-y + 0.54), context text( + size: 12 * diagram-scale * 1pt, + font: phonokit-font.get(), + )[#sym_syll]) + + // Onset branches (if exists) + if has-onset { + line((x-offset, sigma-y + 0.25), (onset-x, or-level + 0.30)) + content((onset-x, or-level), context text(size: 10 * diagram-scale * 1pt, font: phonokit-font.get())[O]) + + let onset-total-width = (num-onset - 1) * segment-spacing + let onset-start-x = onset-x - onset-total-width / 2 + + for (i, segment) in onset-segments.enumerate() { + let seg-x = onset-start-x + i * segment-spacing + line((onset-x, or-level - 0.35), (seg-x, terminal-y + line-offset)) + content( + (seg-x, terminal-y + text-offset), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + + // Rhyme branch + line((x-offset, sigma-y + 0.25), (rhyme-x, or-level + 0.30)) + content((rhyme-x, or-level), context text(size: 10 * diagram-scale * 1pt, font: phonokit-font.get())[R]) + + // Nucleus + line((rhyme-x, or-level - 0.35), (nucleus-x, n-level + 0.35)) + content((nucleus-x, n-level), context text(size: 10 * diagram-scale * 1pt, font: phonokit-font.get())[N]) + + // Branch to each nucleus segment + let nucleus-total-width = (num-nucleus - 1) * segment-spacing + let nucleus-start-x = nucleus-x - nucleus-total-width / 2 + + for (i, segment) in nucleus-segments.enumerate() { + let seg-x = nucleus-start-x + i * segment-spacing + line((nucleus-x, n-level - 0.25), (seg-x, terminal-y + line-offset)) + content( + (seg-x, terminal-y + text-offset), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + + // Coda (if exists) + if has-coda { + line((rhyme-x, or-level - 0.35), (coda-x, n-level + 0.35)) + content((coda-x, n-level), context text(size: 10 * diagram-scale * 1pt, font: phonokit-font.get())[C]) + + let coda-total-width = (num-coda - 1) * segment-spacing + let coda-start-x = coda-x - coda-total-width / 2 + + for (i, segment) in coda-segments.enumerate() { + let seg-x = coda-start-x + i * segment-spacing + line((coda-x, n-level - 0.25), (seg-x, terminal-y + line-offset)) + content( + (seg-x, terminal-y + text-offset), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + })) +} + +// Visualizes moraic structure (Hyman 1976) +// Onsets: non-moraic (connect directly to σ) +// Nucleus: always moraic (connects to μ, which connects to σ) +// Coda: optionally moraic (coda: false → connects to σ; coda: true → connects to μ) +#let mora(input, coda: false, scale: 1.0, symbol: ("σ", "μ"), distance: none, spacing: "auto") = { + check-spacing(spacing) + // Check for syllable boundary markers + if input.contains(".") { + return text(fill: red, weight: "bold")[⚠ Warning: For more than one syllable, use \#foot() or \#word().] + } + + // Check for problematic diacritic sequences + let problematic-sequences = ("''", ",,", "\\* \\*", "\\t \\t", "::", "((", "))") + for seq in problematic-sequences { + if input.contains(seq) { + return text(fill: red, weight: "bold")[⚠ Warning: Problematic sequence involving diacritics: "#seq"] + } + } + + // Convert IPA-style input to Unicode + let converted = convert-prosody-input(input) + + // Parse syllable + // Strip ALL stress markers (' for primary, , for secondary) regardless of position + let clean-input = converted.replace("'", "").replace(",", "") + + let parsed = parse-syllable(clean-input) + + // Check for too many codas (limit: 5 to avoid crossing lines) + let coda-segments-temp = if parsed.coda != "" { smart-clusters(parsed.coda) } else { () } + if coda-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many coda consonants (max 5 to avoid line crossings). Found: #coda-segments-temp.len()] + } + + // Check for too many onsets (limit: 5 to avoid crossing lines) + let onset-segments-temp = if parsed.onset != "" { smart-clusters(parsed.onset) } else { () } + if onset-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many onset consonants (max 5 to avoid line crossings). Found: #onset-segments-temp.len()] + } + + // Check for too many nucleus segments (limit: 5) + let nucleus-segments-temp = smart-clusters(parsed.nucleus) + if nucleus-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many nucleus segments (max 5 to avoid line crossings). Found: #nucleus-segments-temp.len()] + } + + let sym_syll = symbol.at(0, default: "σ") + let sym_mora = symbol.at(1, default: "μ") + + let diagram-scale = scale + box(baseline: 50%, cetz.canvas(length: 1cm * diagram-scale, { + import cetz.draw: * + set-style(stroke: 0.7 * diagram-scale * 1pt) + + let line-len(natural, level) = tier-line(natural, distance, level) + + let sigma-y = 0 + let segment-spacing = 0.35 + let x-offset = 0 + + // Syllable node + content((x-offset, sigma-y + 0.54), context text( + size: 12 * diagram-scale * 1pt, + font: phonokit-font.get(), + )[#sym_syll]) + + // Calculate segment counts + let onset-segments = if parsed.onset != "" { smart-clusters(parsed.onset) } else { () } + let nucleus-segments = smart-clusters(parsed.nucleus) + let coda-segments = if parsed.coda != "" { smart-clusters(parsed.coda) } else { () } + + let num-onset = onset-segments.len() + let num-nucleus = nucleus-segments.len() + let num-coda = coda-segments.len() + + // Default lines never get shorter going up the tree (each is at least as + // long as the one directly below it); explicit `distance` factors are + // applied afterwards. No upper tiers here, so `spacing` has no effect. + + // Position calculations (σ→μ = level 0, μ→segments = level 1) + let mu-seg-line = 0.55 + let sigma-mu-line = calc.max(0.70, mu-seg-line) + let mora-y = sigma-y + 0.25 - line-len(sigma-mu-line, 0) - 0.35 + let terminal-y = mora-y - 0.35 - line-len(mu-seg-line, 1) - 0.30 + let nucleus-mora-x = x-offset + + // Onset position (left of nucleus mora) + // Adaptive positioning: move left based on number of segments to avoid crossings + let reach = moraic-reach(sigma-y, mora-y, terminal-y, long: parsed.nucleus.contains("ː")) + let onset-x = if num-onset > 0 { + let min-offset = (num-onset - 1) * segment-spacing / 2 + 0.8 + let default-offset = 1.2 + let clear-offset = reach + (num-onset - 1) * segment-spacing / 2 + nucleus-mora-x - calc.max(min-offset, default-offset, clear-offset) + } else { + nucleus-mora-x + } + + // Coda position (right of nucleus mora) + // Adaptive positioning: move right based on number of segments to avoid crossings + let coda-x = if num-coda > 0 { + let min-offset = (num-coda - 1) * segment-spacing / 2 + 0.8 + let default-offset = 1.2 + // A non-moraic coda hangs from σ like an onset + let clear-offset = if coda { 0 } else { reach + (num-coda - 1) * segment-spacing / 2 } + nucleus-mora-x + calc.max(min-offset, default-offset, clear-offset) + } else { + nucleus-mora-x + } + + // Draw ONSET (non-moraic - connects directly to σ) + if num-onset > 0 { + let onset-total-width = (num-onset - 1) * segment-spacing + let onset-start-x = onset-x - onset-total-width / 2 + + for (i, segment) in onset-segments.enumerate() { + let seg-x = onset-start-x + i * segment-spacing + line((x-offset, sigma-y + 0.25), (seg-x, terminal-y + 0.30)) + content( + (seg-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + + // Check if nucleus contains long vowel (Vː) + let has-long-vowel = parsed.nucleus.contains("ː") + + // Draw NUCLEUS MORA(E) - one mora for short vowel, two for long vowel + if has-long-vowel { + // Long vowel: draw TWO morae that branch from σ and converge on Vː + let mora-spacing = long-mora-spacing + let mora1-x = nucleus-mora-x - mora-spacing / 2 + let mora2-x = nucleus-mora-x + mora-spacing / 2 + + // Draw two μ nodes + content((mora1-x, mora-y), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#sym_mora]) + content((mora2-x, mora-y), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#sym_mora]) + + // Lines from σ to both morae + line((x-offset, sigma-y + 0.25), (mora1-x, mora-y + 0.35)) + line((x-offset, sigma-y + 0.25), (mora2-x, mora-y + 0.35)) + + // Both morae converge to the long vowel segment + let nucleus-total-width = (num-nucleus - 1) * segment-spacing + let nucleus-start-x = nucleus-mora-x - nucleus-total-width / 2 + + for (i, segment) in nucleus-segments.enumerate() { + let seg-x = nucleus-start-x + i * segment-spacing + // Lines from both morae converge to the segment + line((mora1-x, mora-y - 0.35), (seg-x, terminal-y + 0.30)) + line((mora2-x, mora-y - 0.35), (seg-x, terminal-y + 0.30)) + content( + (seg-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } else { + // Short vowel: one mora + content((nucleus-mora-x, mora-y), context text( + size: 12 * diagram-scale * 1pt, + font: phonokit-font.get(), + )[#sym_mora]) + line((x-offset, sigma-y + 0.25), (nucleus-mora-x, mora-y + 0.35)) + + // Draw nucleus segments below mora + let nucleus-total-width = (num-nucleus - 1) * segment-spacing + let nucleus-start-x = nucleus-mora-x - nucleus-total-width / 2 + + for (i, segment) in nucleus-segments.enumerate() { + let seg-x = nucleus-start-x + i * segment-spacing + line((nucleus-mora-x, mora-y - 0.35), (seg-x, terminal-y + 0.30)) + content( + (seg-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + + // Draw CODA + if num-coda > 0 { + if coda { + // Moraic coda: ONE μ for all coda segments (they share the mora) + // Draw the coda μ + content((coda-x, mora-y), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#sym_mora]) + + // Line from σ to coda μ + line((x-offset, sigma-y + 0.25), (coda-x, mora-y + 0.35)) + + // All coda segments branch from this single μ + let coda-total-width = (num-coda - 1) * segment-spacing + let coda-start-x = coda-x - coda-total-width / 2 + + for (i, segment) in coda-segments.enumerate() { + let seg-x = coda-start-x + i * segment-spacing + + // Line from shared μ to each segment + line((coda-x, mora-y - 0.35), (seg-x, terminal-y + 0.30)) + + // Draw segment + content( + (seg-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } else { + // Non-moraic coda: connects directly to σ + let coda-total-width = (num-coda - 1) * segment-spacing + let coda-start-x = coda-x - coda-total-width / 2 + + for (i, segment) in coda-segments.enumerate() { + let seg-x = coda-start-x + i * segment-spacing + line((x-offset, sigma-y + 0.25), (seg-x, terminal-y + 0.30)) + content( + (seg-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + } + })) +} + +// Helper function to draw moraic structure (used by foot.mora and word.mora) +#let draw-moraic-structure( + x-offset, + sigma-y, + syll, + terminal-y, + coda: false, + diagram-scale: 1.0, + geminate-coda-x: none, + geminate-onset-x: none, + mora-symbol: "μ", + mora-y-override: none, +) = { + import cetz.draw: * + + let segment-spacing = 0.35 + + // Calculate segment counts + let onset-segments = if syll.onset != "" { smart-clusters(syll.onset) } else { () } + let nucleus-segments = smart-clusters(syll.nucleus) + let coda-segments = if syll.coda != "" { smart-clusters(syll.coda) } else { () } + + let num-onset = onset-segments.len() + let num-nucleus = nucleus-segments.len() + let num-coda = coda-segments.len() + + // Check if nucleus contains long vowel (needed for spacing calculations) + let has-long-vowel = syll.nucleus.contains("ː") + + // Position calculations + let mora-y = if mora-y-override != none { mora-y-override } else { 0.4 * (sigma-y + 0.54) + 0.6 * terminal-y } + let nucleus-mora-x = x-offset + + // Adaptive onset position (same formula as draw-syllable-structure for uniform spacing) + let reach = moraic-reach(sigma-y, mora-y, terminal-y, long: has-long-vowel) + let onset-x = if num-onset > 0 { + let min-gap = 0.75 + let min-offset = (num-onset - 1) * segment-spacing / 2 + (num-nucleus - 1) * segment-spacing / 2 + min-gap + let default-offset = 0.7 + let clear-offset = reach + (num-onset - 1) * segment-spacing / 2 + nucleus-mora-x - calc.max(min-offset, default-offset, clear-offset) + } else { + nucleus-mora-x + } + + // Adaptive coda position (same formula as draw-syllable-structure for uniform spacing) + let coda-x = if num-coda > 0 { + let min-gap = 0.75 + let min-offset = (num-nucleus + num-coda - 2) * segment-spacing / 2 + min-gap + let default-offset = 0.7 + // A non-moraic coda hangs from σ like an onset + let clear-offset = if coda { 0 } else { reach + (num-coda - 1) * segment-spacing / 2 } + nucleus-mora-x + calc.max(min-offset, default-offset, clear-offset) + } else { + nucleus-mora-x + } + + // Draw ONSET (non-moraic - connects directly to σ) + if num-onset > 0 { + // Check if this is a geminate onset + if geminate-onset-x != none { + // Geminate: draw line to geminate position + line((x-offset, sigma-y + 0.25), (geminate-onset-x, terminal-y + 0.30)) + } else { + // Normal onset: draw branches to individual segments + let onset-total-width = (num-onset - 1) * segment-spacing + let onset-start-x = onset-x - onset-total-width / 2 + + for (i, segment) in onset-segments.enumerate() { + let seg-x = onset-start-x + i * segment-spacing + line((x-offset, sigma-y + 0.25), (seg-x, terminal-y + 0.30)) + content( + (seg-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + } + + // Draw NUCLEUS MORA(E) + if has-long-vowel { + // Long vowel: TWO morae + let mora-spacing = long-mora-spacing + let mora1-x = nucleus-mora-x - mora-spacing / 2 + let mora2-x = nucleus-mora-x + mora-spacing / 2 + + content((mora1-x, mora-y), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#mora-symbol]) + content((mora2-x, mora-y), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#mora-symbol]) + + line((x-offset, sigma-y + 0.25), (mora1-x, mora-y + 0.35)) + line((x-offset, sigma-y + 0.25), (mora2-x, mora-y + 0.35)) + + let nucleus-total-width = (num-nucleus - 1) * segment-spacing + let nucleus-start-x = nucleus-mora-x - nucleus-total-width / 2 + + for (i, segment) in nucleus-segments.enumerate() { + let seg-x = nucleus-start-x + i * segment-spacing + line((mora1-x, mora-y - 0.35), (seg-x, terminal-y + 0.30)) + line((mora2-x, mora-y - 0.35), (seg-x, terminal-y + 0.30)) + content( + (seg-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } else { + // Short vowel: ONE mora + content((nucleus-mora-x, mora-y), context text( + size: 12 * diagram-scale * 1pt, + font: phonokit-font.get(), + )[#mora-symbol]) + line((x-offset, sigma-y + 0.25), (nucleus-mora-x, mora-y + 0.35)) + + let nucleus-total-width = (num-nucleus - 1) * segment-spacing + let nucleus-start-x = nucleus-mora-x - nucleus-total-width / 2 + + for (i, segment) in nucleus-segments.enumerate() { + let seg-x = nucleus-start-x + i * segment-spacing + line((nucleus-mora-x, mora-y - 0.35), (seg-x, terminal-y + 0.30)) + content( + (seg-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + + // Draw CODA + if num-coda > 0 { + if coda { + // Moraic coda: ONE μ shared by all segments + content((coda-x, mora-y), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#mora-symbol]) + line((x-offset, sigma-y + 0.25), (coda-x, mora-y + 0.35)) + + // Check if this is a geminate coda + if geminate-coda-x != none { + // Geminate: draw line to geminate position + line((coda-x, mora-y - 0.35), (geminate-coda-x, terminal-y + 0.30)) + } else { + // Normal coda: all segments branch from shared μ + let coda-total-width = (num-coda - 1) * segment-spacing + let coda-start-x = coda-x - coda-total-width / 2 + + for (i, segment) in coda-segments.enumerate() { + let seg-x = coda-start-x + i * segment-spacing + line((coda-x, mora-y - 0.35), (seg-x, terminal-y + 0.30)) + content( + (seg-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + } else { + // Non-moraic coda: connects directly to σ + // Check if this is a geminate coda + if geminate-coda-x != none { + // Geminate: draw line to geminate position + line((x-offset, sigma-y + 0.25), (geminate-coda-x, terminal-y + 0.30)) + } else { + // Normal coda: branches to individual segments + let coda-total-width = (num-coda - 1) * segment-spacing + let coda-start-x = coda-x - coda-total-width / 2 + + for (i, segment) in coda-segments.enumerate() { + let seg-x = coda-start-x + i * segment-spacing + line((x-offset, sigma-y + 0.25), (seg-x, terminal-y + 0.30)) + content( + (seg-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#segment], + anchor: "north", + ) + } + } + } + } +} + +// Visualizes foot and syllable levels +// Now accepts IPA-style input like "k a.'v a.l o" +#let foot(input, scale: 1.0, symbol: ("Σ", "σ"), distance: none, spacing: "auto") = { + check-spacing(spacing) + // Check for parentheses (foot should not have multiple feet) + if input.contains("(") or input.contains(")") { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: foot() is for a single foot. If you need multiple feet, use word() instead.] + } + + // Convert IPA-style input to Unicode + let converted = convert-prosody-input(input) + + // Parse syllables from dotted input + let syllables = () + let buffer = "" + let chars = converted.codepoints() + let i = 0 + + while i < chars.len() { + let char = chars.at(i) + + if char == "." { + if buffer != "" { + let clean-buffer = if buffer.starts-with("'") { buffer.slice(1) } else { buffer } + let parsed = parse-syllable(clean-buffer) + syllables.push(( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: buffer.starts-with("'"), + )) + buffer = "" + } + } else { + buffer += char + } + i += 1 + } + + // Handle remaining buffer + if buffer != "" { + let clean-buffer = if buffer.starts-with("'") { buffer.slice(1) } else { buffer } + let parsed = parse-syllable(clean-buffer) + syllables.push(( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: buffer.starts-with("'"), + )) + } + + // Check for too many onsets/codas/nucleus in any syllable (limit: 5 to avoid crossing lines) + for (i, syll) in syllables.enumerate() { + let coda-segments-temp = if syll.coda != "" { smart-clusters(syll.coda) } else { () } + if coda-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many coda consonants in syllable #(i + 1) (max 5 to avoid line crossings). Found: #coda-segments-temp.len()] + } + let onset-segments-temp = if syll.onset != "" { smart-clusters(syll.onset) } else { () } + if onset-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many onset consonants in syllable #(i + 1) (max 5 to avoid line crossings). Found: #onset-segments-temp.len()] + } + let nucleus-segments-temp = smart-clusters(syll.nucleus) + if nucleus-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many nucleus segments in syllable #(i + 1) (max 5 to avoid line crossings). Found: #nucleus-segments-temp.len()] + } + } + + // Find stressed syllable (head of foot) + let head-idx = 0 + for (i, syll) in syllables.enumerate() { + if syll.stressed { + head-idx = i + break + } + } + + let sym_foot = symbol.at(0, default: "Σ") + let sym_syll = symbol.at(1, default: "σ") + + let diagram-scale = scale + + box(baseline: 50%, cetz.canvas(length: 1cm * diagram-scale, { + import cetz.draw: * + set-style(stroke: 0.7 * diagram-scale * 1pt) + + let line-len(natural, level) = tier-line(natural, distance, level) + + let segment-spacing = 0.35 + let min-gap-between-sylls = 0.8 + let default-spacing = 1.6 + + // Calculate extents for each syllable + let syllable-extents = () + for syll in syllables { + let has-onset = syll.onset != "" + let has-coda = syll.coda != "" + let num-onset = if has-onset { smart-clusters(syll.onset).len() } else { 0 } + let num-nucleus = smart-clusters(syll.nucleus).len() + let num-coda = if has-coda { smart-clusters(syll.coda).len() } else { 0 } + let min-gap = 0.75 + + // Calculate constituent positions + let onset-x-rel = if has-onset { + let min-offset = (num-onset - 1) * segment-spacing / 2 + (num-nucleus - 1) * segment-spacing / 2 + min-gap + let default-offset = 0.7 + if min-offset > default-offset { -min-offset } else { -default-offset } + } else { 0 } + + let coda-x-rel = if has-coda { + let min-offset = (num-nucleus + num-coda - 2) * segment-spacing / 2 + 0.4 + let default-offset = 0.7 + if min-offset > default-offset { min-offset } else { default-offset } + } else { 0 } + + // Calculate segment widths + let onset-width = if has-onset { (num-onset - 1) * segment-spacing } else { 0 } + let nucleus-width = (num-nucleus - 1) * segment-spacing + let coda-width = if has-coda { (num-coda - 1) * segment-spacing } else { 0 } + + // Calculate left and right extents + let left-parts = ( + if has-onset { onset-x-rel - onset-width / 2 } else { 0 }, + -nucleus-width / 2, + if has-coda { coda-x-rel - coda-width / 2 } else { 0 }, + ) + let right-parts = ( + if has-onset { onset-x-rel + onset-width / 2 } else { 0 }, + nucleus-width / 2, + if has-coda { coda-x-rel + coda-width / 2 } else { 0 }, + ) + + let left-extent = calc.min(..left-parts) + let right-extent = calc.max(..right-parts) + + syllable-extents.push((left: left-extent, right: right-extent)) + } + + // Calculate adaptive spacing and positions + let syllable-positions = () + for (i, extent) in syllable-extents.enumerate() { + if i == 0 { + syllable-positions.push(0) + } else { + let prev-right = syllable-extents.at(i - 1).right + let required-spacing = prev-right - extent.left + min-gap-between-sylls + let actual-spacing = calc.max(required-spacing, default-spacing) + let prev-position = syllable-positions.at(i - 1) + syllable-positions.push(prev-position + actual-spacing) + } + } + + // Center the structure + let first-left = syllable-positions.at(0) + syllable-extents.at(0).left + let last-right = syllable-positions.at(-1) + syllable-extents.at(-1).right + let total-width = last-right - first-left + let start-x = -total-width / 2 - first-left + + let foot-x = start-x + syllable-positions.at(head-idx) + + // Default lines never get shorter going up the tree (each is at least as + // long as the one directly below it); explicit `distance` factors are + // applied afterwards. spacing: "auto" additionally lengthens upper lines + // to fit wide branches; "fixed" keeps them constant so trees align. + let fit = spacing == "auto" + + // Sub-syllable tiers (σ → O/R → N/C → segments) + let n-seg-line = 0.40 + let or-n-line = calc.max(0.25, n-seg-line) + let sigma-or-line = calc.max(0.70, or-n-line) + let sigma-y = -2.4 + let sigma-y-label = sigma-y + 0.54 + let or-y = sigma-y-label - 0.29 - line-len(sigma-or-line, 1) - 0.30 + let n-y = or-y - 0.35 - line-len(or-n-line, 2) - 0.30 + let terminal-y = n-y - 0.25 - line-len(n-seg-line, 3) - 0.30 + let below-sigma-line = sigma-or-line + + // Σ → σ: "auto" fits the line to the widest branch; "fixed" grows it with the syllable count + let foot-run = 0 + for (i, _) in syllables.enumerate() { + foot-run = calc.max(foot-run, calc.abs(start-x + syllable-positions.at(i) - foot-x)) + } + let foot-line = if fit { + calc.max(0.45, branch-slope * foot-run, below-sigma-line) + } else { below-sigma-line } + let ft-height = sigma-y-label + 0.26 + line-len(foot-line, 0) + 0.25 + + // Draw Ft node above the head + content((foot-x, ft-height), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#sym_foot]) + + // Detect geminates + // A geminate occurs when the last consonant of a coda matches the first consonant of the next onset + let geminates = () + for i in range(syllables.len() - 1) { + if syllables.at(i).coda != "" and syllables.at(i + 1).onset != "" { + let coda-segments = smart-clusters(syllables.at(i).coda) + let onset-segments = smart-clusters(syllables.at(i + 1).onset) + let last-coda = coda-segments.at(-1) + let first-onset = onset-segments.at(0) + + // Check if they match and are consonants (not vowels) + if last-coda == first-onset and not is-vowel(last-coda) { + let gem-x = start-x + (syllable-positions.at(i) + syllable-positions.at(i + 1)) / 2 + geminates.push((syll-idx: i, gem-x: gem-x, gem-text: last-coda)) + } + } + } + + // Draw syllables + for (i, syll) in syllables.enumerate() { + let x-offset = start-x + syllable-positions.at(i) + + // Syllable node + content((x-offset, sigma-y + 0.54), context text( + size: 12 * diagram-scale * 1pt, + font: phonokit-font.get(), + )[#sym_syll]) + + // Line from Ft to σ + line((foot-x, ft-height - 0.25), (x-offset, sigma-y + 0.8)) + + // Check for geminate + let gem-coda-x = none + let gem-coda-text = none + let gem-onset-x = none + let gem-onset-text = none + for gem in geminates { + if gem.syll-idx == i { + gem-coda-x = gem.gem-x + gem-coda-text = gem.gem-text + } + if gem.syll-idx == i - 1 { + gem-onset-x = gem.gem-x + gem-onset-text = gem.gem-text + } + } + + draw-syllable-structure( + x-offset, + sigma-y, + syll, + terminal-y, + diagram-scale: diagram-scale, + geminate-coda-x: gem-coda-x, + geminate-onset-x: gem-onset-x, + geminate-coda-text: gem-coda-text, + geminate-onset-text: gem-onset-text, + or-y: or-y, + n-y: n-y, + ) + } + + // Draw geminate segments + for gem in geminates { + content( + (gem.gem-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#gem.gem-text], + anchor: "north", + ) + } + })) +} + +// Visualizes word, foot, and syllable levels +// Now accepts IPA-style input like "(k a.'v a).l o" +#let word(input, foot: "R", scale: 1.0, symbol: ("ω", "Σ", "σ"), distance: none, spacing: "auto") = { + check-spacing(spacing) + // Convert IPA-style input to Unicode + let converted = convert-prosody-input(input) + + let syllables = () + let feet = () + let current-foot = () + let in-foot = false + let buffer = "" + + let chars = converted.codepoints() + let i = 0 + + while i < chars.len() { + let char = chars.at(i) + + if char == "(" { + in-foot = true + current-foot = () + } else if char == ")" { + if buffer != "" { + let clean-buffer = if buffer.starts-with("'") { buffer.slice(1) } else { buffer } + let parsed = parse-syllable(clean-buffer) + syllables.push(( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: buffer.starts-with("'"), + )) + current-foot.push(syllables.len() - 1) + buffer = "" + } + if current-foot.len() > 0 { + feet.push(current-foot) + } + in-foot = false + } else if char == "." { + if buffer != "" { + let clean-buffer = if buffer.starts-with("'") { buffer.slice(1) } else { buffer } + let parsed = parse-syllable(clean-buffer) + syllables.push(( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: buffer.starts-with("'"), + )) + + if in-foot { + current-foot.push(syllables.len() - 1) + } + + buffer = "" + } + } else { + buffer += char + } + + i += 1 + } + + // Handle remaining buffer + if buffer != "" { + let clean-buffer = if buffer.starts-with("'") { buffer.slice(1) } else { buffer } + let parsed = parse-syllable(clean-buffer) + syllables.push(( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: buffer.starts-with("'"), + )) + + if in-foot { + current-foot.push(syllables.len() - 1) + } + } + + // Check for too many onsets/codas/nucleus in any syllable (limit: 5 to avoid crossing lines) + for (i, syll) in syllables.enumerate() { + let coda-segments-temp = if syll.coda != "" { smart-clusters(syll.coda) } else { () } + if coda-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many coda consonants in syllable #(i + 1) (max 5 to avoid line crossings). Found: #coda-segments-temp.len()] + } + let onset-segments-temp = if syll.onset != "" { smart-clusters(syll.onset) } else { () } + if onset-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many onset consonants in syllable #(i + 1) (max 5 to avoid line crossings). Found: #onset-segments-temp.len()] + } + let nucleus-segments-temp = smart-clusters(syll.nucleus) + if nucleus-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many nucleus segments in syllable #(i + 1) (max 5 to avoid line crossings). Found: #nucleus-segments-temp.len()] + } + } + + // Determine which syllables are in feet + let in-foot-set = () + for foot in feet { + for syll-idx in foot { + in-foot-set.push(syll-idx) + } + } + + let sym_word = symbol.at(0, default: "ω") + let sym_foot = symbol.at(1, default: "Σ") + let sym_syll = symbol.at(2, default: "σ") + + let diagram-scale = scale + + // Draw the structure + box(baseline: 50%, cetz.canvas(length: 1cm * diagram-scale, { + import cetz.draw: * + + set-style(stroke: 0.7 * diagram-scale * 1pt) + + let segment-spacing = 0.35 + let min-gap-between-sylls = 0.8 + let default-spacing = 1.6 + + let line-len(natural, level) = tier-line(natural, distance, level) + + // Default lines never get shorter going up the tree (each is at least as + // long as the one directly below it); explicit `distance` factors are + // applied afterwards. spacing: "auto" additionally lengthens upper lines + // to fit wide branches; "fixed" keeps them constant so trees align. + let fit = spacing == "auto" + + // Sub-syllable tiers (σ → O/R → N/C → segments) + let n-seg-line = 0.40 + let or-n-line = calc.max(0.25, n-seg-line) + let sigma-or-line = calc.max(0.70, or-n-line) + let sigma-y = -2.4 + let sigma-y-label = sigma-y + 0.54 + let or-y = sigma-y-label - 0.29 - line-len(sigma-or-line, 2) - 0.30 + let n-y = or-y - 0.35 - line-len(or-n-line, 3) - 0.30 + let terminal-y = n-y - 0.25 - line-len(n-seg-line, 4) - 0.30 + let below-sigma-line = sigma-or-line + + + // Calculate extents for each syllable + let syllable-extents = () + for syll in syllables { + let has-onset = syll.onset != "" + let has-coda = syll.coda != "" + let num-onset = if has-onset { smart-clusters(syll.onset).len() } else { 0 } + let num-nucleus = smart-clusters(syll.nucleus).len() + let num-coda = if has-coda { smart-clusters(syll.coda).len() } else { 0 } + let min-gap = 0.75 + + let onset-x-rel = if has-onset { + let min-offset = (num-onset - 1) * segment-spacing / 2 + (num-nucleus - 1) * segment-spacing / 2 + min-gap + let default-offset = 0.7 + if min-offset > default-offset { -min-offset } else { -default-offset } + } else { 0 } + + let coda-x-rel = if has-coda { + let min-offset = (num-nucleus + num-coda - 2) * segment-spacing / 2 + 0.4 + let default-offset = 0.7 + if min-offset > default-offset { min-offset } else { default-offset } + } else { 0 } + + let onset-width = if has-onset { (num-onset - 1) * segment-spacing } else { 0 } + let nucleus-width = (num-nucleus - 1) * segment-spacing + let coda-width = if has-coda { (num-coda - 1) * segment-spacing } else { 0 } + + let left-parts = ( + if has-onset { onset-x-rel - onset-width / 2 } else { 0 }, + -nucleus-width / 2, + if has-coda { coda-x-rel - coda-width / 2 } else { 0 }, + ) + let right-parts = ( + if has-onset { onset-x-rel + onset-width / 2 } else { 0 }, + nucleus-width / 2, + if has-coda { coda-x-rel + coda-width / 2 } else { 0 }, + ) + + let left-extent = calc.min(..left-parts) + let right-extent = calc.max(..right-parts) + + syllable-extents.push((left: left-extent, right: right-extent)) + } + + // Calculate adaptive spacing and positions + let syllable-positions = () + for (i, extent) in syllable-extents.enumerate() { + if i == 0 { + syllable-positions.push(0) + } else { + let prev-right = syllable-extents.at(i - 1).right + let required-spacing = prev-right - extent.left + min-gap-between-sylls + let actual-spacing = calc.max(required-spacing, default-spacing) + let prev-position = syllable-positions.at(i - 1) + syllable-positions.push(prev-position + actual-spacing) + } + } + + // Center the structure + let first-left = syllable-positions.at(0) + syllable-extents.at(0).left + let last-right = syllable-positions.at(-1) + syllable-extents.at(-1).right + let total-width = last-right - first-left + let start-x = -total-width / 2 - first-left + + // Determine PWd x-position + let pwd-x = 0 + + if feet.len() > 0 { + let target-foot = if foot == "L" { feet.at(0) } else { feet.at(-1) } + + let head-idx = target-foot.at(0) + for syll-idx in target-foot { + if syllables.at(syll-idx).stressed { + head-idx = syll-idx + break + } + } + pwd-x = start-x + syllable-positions.at(head-idx) + } else if syllables.len() > 0 { + // If the user marked stress with ' (even without using feet), align + // the PWd node to that syllable instead of defaulting to L/R extremes. + let stressed-idx = none + for (idx, syll) in syllables.enumerate() { + if syll.stressed { + stressed-idx = idx + break + } + } + let target-idx = if stressed-idx != none { + stressed-idx + } else if foot == "L" { 0 } else { syllables.len() - 1 } + pwd-x = start-x + syllable-positions.at(target-idx) + } + + // Upper tiers (ω, Σ). With spacing: "auto", a branch spanning a horizontal + // run h gets at least slope × h of vertical line (≈ the angle of the default + // trees), and vertical branches get a short line. With spacing: "fixed", + // lines are constant so trees with the same tiers align exactly. + + let foot-xs = () + for ft in feet { + let head-idx = ft.at(0) + for syll-idx in ft { + if syllables.at(syll-idx).stressed { + head-idx = syll-idx + break + } + } + foot-xs.push(start-x + syllable-positions.at(head-idx)) + } + + // Σ → σ + let foot-run = 0 + for (j, ft) in feet.enumerate() { + for syll-idx in ft { + foot-run = calc.max(foot-run, calc.abs(start-x + syllable-positions.at(syll-idx) - foot-xs.at(j))) + } + } + let foot-line = if fit { calc.max(0.45, branch-slope * foot-run, below-sigma-line) } else { below-sigma-line } + let foot-y = sigma-y-label + 0.26 + line-len(foot-line, 1) + 0.25 + + // ω → Σ, or ω → σ directly when there are no feet + let sigma-top = sigma-y-label + 0.21 + let below-top = if feet.len() > 0 { foot-y + 0.25 } else { sigma-top } + let pwd-line = if fit { + let need = if feet.len() > 0 { foot-line } else { below-sigma-line } + for fx in foot-xs { + need = calc.max(need, branch-slope * calc.abs(fx - pwd-x)) + } + // Unfooted σ hang from ω across the whole ω–σ distance + for (i, syll) in syllables.enumerate() { + if i not in in-foot-set { + let run = calc.abs(start-x + syllable-positions.at(i) - pwd-x) + need = calc.max(need, branch-slope * run - (below-top - sigma-top)) + } + } + need + } else if feet.len() > 0 { foot-line } else { below-sigma-line } + let pwd-height = below-top + line-len(pwd-line, 0) + 0.3 + + // ω lines to unfooted σ must pass clear above any Σ lying between them + let clearance-margin = 0.25 + for (i, syll) in syllables.enumerate() { + if i not in in-foot-set { + let syll-x = start-x + syllable-positions.at(i) + for fx in foot-xs { + let is-between = (pwd-x < fx and fx < syll-x) or (syll-x < fx and fx < pwd-x) + if is-between and calc.abs(syll-x - pwd-x) > 0.01 { + let t = (fx - pwd-x) / (syll-x - pwd-x) + let required = 0.3 + (foot-y + 0.25 + clearance-margin - t * sigma-top) / (1 - t) + pwd-height = calc.max(pwd-height, required) + } + } + } + } + + content((pwd-x, pwd-height), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#sym_word]) + + // Detect geminates + // A geminate occurs when the last consonant of a coda matches the first consonant of the next onset + let geminates = () + for i in range(syllables.len() - 1) { + if syllables.at(i).coda != "" and syllables.at(i + 1).onset != "" { + let coda-segments = smart-clusters(syllables.at(i).coda) + let onset-segments = smart-clusters(syllables.at(i + 1).onset) + let last-coda = coda-segments.at(-1) + let first-onset = onset-segments.at(0) + + // Check if they match and are consonants (not vowels) + if last-coda == first-onset and not is-vowel(last-coda) { + let gem-x = start-x + (syllable-positions.at(i) + syllable-positions.at(i + 1)) / 2 + geminates.push((syll-idx: i, gem-x: gem-x, gem-text: last-coda)) + } + } + } + + // Draw footless syllables + for (i, syll) in syllables.enumerate() { + if i not in in-foot-set { + let x-offset = start-x + syllable-positions.at(i) + + content((x-offset, sigma-y + 0.54), context text( + size: 12 * diagram-scale * 1pt, + font: phonokit-font.get(), + )[#sym_syll]) + line((pwd-x, pwd-height - 0.3), (x-offset, sigma-y + 0.75)) + + let gem-coda-x = none + let gem-coda-text = none + let gem-onset-x = none + let gem-onset-text = none + for gem in geminates { + if gem.syll-idx == i { + gem-coda-x = gem.gem-x + gem-coda-text = gem.gem-text + } + if gem.syll-idx == i - 1 { + gem-onset-x = gem.gem-x + gem-onset-text = gem.gem-text + } + } + + draw-syllable-structure( + x-offset, + sigma-y, + syll, + terminal-y, + diagram-scale: diagram-scale, + geminate-coda-x: gem-coda-x, + geminate-onset-x: gem-onset-x, + geminate-coda-text: gem-coda-text, + geminate-onset-text: gem-onset-text, + or-y: or-y, + n-y: n-y, + ) + } + } + + // Draw each foot + for foot in feet { + let head-idx = foot.at(0) + for syll-idx in foot { + if syllables.at(syll-idx).stressed { + head-idx = syll-idx + break + } + } + + let foot-x = start-x + syllable-positions.at(head-idx) + + content((foot-x, foot-y), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#sym_foot]) + line((pwd-x, pwd-height - 0.3), (foot-x, foot-y + 0.25)) + + for syll-idx in foot { + let x-offset = start-x + syllable-positions.at(syll-idx) + let syll = syllables.at(syll-idx) + + content((x-offset, sigma-y + 0.54), context text( + size: 12 * diagram-scale * 1pt, + font: phonokit-font.get(), + )[#sym_syll]) + line((foot-x, foot-y - 0.25), (x-offset, sigma-y + 0.8)) + + let gem-coda-x = none + let gem-coda-text = none + let gem-onset-x = none + let gem-onset-text = none + for gem in geminates { + if gem.syll-idx == syll-idx { + gem-coda-x = gem.gem-x + gem-coda-text = gem.gem-text + } + if gem.syll-idx == syll-idx - 1 { + gem-onset-x = gem.gem-x + gem-onset-text = gem.gem-text + } + } + + draw-syllable-structure( + x-offset, + sigma-y, + syll, + terminal-y, + diagram-scale: diagram-scale, + geminate-coda-x: gem-coda-x, + geminate-onset-x: gem-onset-x, + geminate-coda-text: gem-coda-text, + geminate-onset-text: gem-onset-text, + or-y: or-y, + n-y: n-y, + ) + } + } + + // Draw geminate segments + for gem in geminates { + content( + (gem.gem-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#gem.gem-text], + anchor: "north", + ) + } + })) +} + +// Visualizes foot with moraic structure +// Now accepts IPA-style input like "k a.'v a.l o" +#let foot-mora(input, coda: false, scale: 1.0, symbol: ("Σ", "σ", "μ"), distance: none, spacing: "auto") = { + check-spacing(spacing) + // Check for problematic diacritic sequences + let problematic-sequences = ("''", ",,", "\\* \\*", "\\t \\t", "::", "((", "))") + for seq in problematic-sequences { + if input.contains(seq) { + return text(fill: red, weight: "bold")[⚠ Warning: Problematic sequence involving diacritics: "#seq"] + } + } + + // Convert IPA-style input to Unicode + let converted = convert-prosody-input(input) + + // Parse syllables from dotted input + let syllables = () + let buffer = "" + let chars = converted.codepoints() + let i = 0 + + while i < chars.len() { + let char = chars.at(i) + + if char == "." { + if buffer != "" { + let clean-buffer = if buffer.starts-with("'") { buffer.slice(1) } else { buffer } + let parsed = parse-syllable(clean-buffer) + syllables.push(( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: buffer.starts-with("'"), + )) + buffer = "" + } + } else if char == "(" or char == ")" { + // Skip parentheses - they're just delimiters, not segments + } else { + buffer += char + } + i += 1 + } + + // Handle remaining buffer + if buffer != "" { + let clean-buffer = if buffer.starts-with("'") { buffer.slice(1) } else { buffer } + let parsed = parse-syllable(clean-buffer) + syllables.push(( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: buffer.starts-with("'"), + )) + } + + // Check for too many onsets/codas/nucleus in any syllable (limit: 5 to avoid crossing lines) + for (i, syll) in syllables.enumerate() { + let coda-segments-temp = if syll.coda != "" { smart-clusters(syll.coda) } else { () } + if coda-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many coda consonants in syllable #(i + 1) (max 5 to avoid line crossings). Found: #coda-segments-temp.len()] + } + let onset-segments-temp = if syll.onset != "" { smart-clusters(syll.onset) } else { () } + if onset-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many onset consonants in syllable #(i + 1) (max 5 to avoid line crossings). Found: #onset-segments-temp.len()] + } + let nucleus-segments-temp = smart-clusters(syll.nucleus) + if nucleus-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many nucleus segments in syllable #(i + 1) (max 5 to avoid line crossings). Found: #nucleus-segments-temp.len()] + } + } + + // Find stressed syllable (head of foot) + let head-idx = 0 + for (i, syll) in syllables.enumerate() { + if syll.stressed { + head-idx = i + break + } + } + + let sym_foot = symbol.at(0, default: "Σ") + let sym_syll = symbol.at(1, default: "σ") + let sym_mora = symbol.at(2, default: "μ") + + let diagram-scale = scale + + box(baseline: 50%, cetz.canvas(length: 1cm * diagram-scale, { + import cetz.draw: * + set-style(stroke: 0.7 * diagram-scale * 1pt) + + let line-len(natural, level) = tier-line(natural, distance, level) + + // Mora tiers (σ → μ = level 1, μ → segments = level 2) + let mu-seg-line = 0.55 + let sigma-mu-line = calc.max(0.70, mu-seg-line) + let sigma-y = -2.4 + let sigma-y-label = sigma-y + 0.54 + let mora-y = sigma-y-label - 0.29 - line-len(sigma-mu-line, 1) - 0.35 + let terminal-y = mora-y - 0.35 - line-len(mu-seg-line, 2) - 0.30 + let below-sigma-line = sigma-mu-line + + let segment-spacing = 0.35 + let min-gap-between-sylls = 0.8 // Same as foot() to prevent overlap + let default-spacing = 1.6 // Same as foot() to prevent overlap + + // Calculate extents for each syllable + let syllable-extents = () + for syll in syllables { + let has-onset = syll.onset != "" + let has-coda = syll.coda != "" + let num-onset = if has-onset { smart-clusters(syll.onset).len() } else { 0 } + let num-nucleus = smart-clusters(syll.nucleus).len() + let num-coda = if has-coda { smart-clusters(syll.coda).len() } else { 0 } + let min-gap = 0.75 + + // Calculate constituent positions (simplified like word()) + // Use segment-based extents without mora adjustments for uniform spacing + // Non-moraic onsets/codas must clear the μ labels (same rule as draw-moraic-structure) + let reach = moraic-reach(sigma-y, mora-y, terminal-y, long: syll.nucleus.contains("ː")) + let onset-x-rel = if has-onset { + let min-offset = (num-onset - 1) * segment-spacing / 2 + (num-nucleus - 1) * segment-spacing / 2 + min-gap + let default-offset = 0.7 + let clear-offset = reach + (num-onset - 1) * segment-spacing / 2 + -calc.max(min-offset, default-offset, clear-offset) + } else { 0 } + + let coda-x-rel = if has-coda { + let min-offset = (num-nucleus + num-coda - 2) * segment-spacing / 2 + 0.4 + let default-offset = 0.7 + let clear-offset = if coda { 0 } else { reach + (num-coda - 1) * segment-spacing / 2 } + calc.max(min-offset, default-offset, clear-offset) + } else { 0 } + + // Calculate segment widths + let onset-width = if has-onset { (num-onset - 1) * segment-spacing } else { 0 } + let nucleus-width = (num-nucleus - 1) * segment-spacing + let coda-width = if has-coda { (num-coda - 1) * segment-spacing } else { 0 } + + // Calculate left and right extents (same as word()) + let left-parts = ( + if has-onset { onset-x-rel - onset-width / 2 } else { 0 }, + -nucleus-width / 2, + if has-coda { coda-x-rel - coda-width / 2 } else { 0 }, + ) + let right-parts = ( + if has-onset { onset-x-rel + onset-width / 2 } else { 0 }, + nucleus-width / 2, + if has-coda { coda-x-rel + coda-width / 2 } else { 0 }, + ) + + let left-extent = calc.min(..left-parts) + let right-extent = calc.max(..right-parts) + + syllable-extents.push((left: left-extent, right: right-extent)) + } + + // Calculate spacing (same as regular foot() for uniform segment spacing) + let syllable-positions = () + for (i, extent) in syllable-extents.enumerate() { + if i == 0 { + syllable-positions.push(0) + } else { + let prev-right = syllable-extents.at(i - 1).right + let required-spacing = prev-right - extent.left + min-gap-between-sylls + let actual-spacing = calc.max(required-spacing, default-spacing) + let prev-position = syllable-positions.at(i - 1) + syllable-positions.push(prev-position + actual-spacing) + } + } + + // Center the structure + let first-left = syllable-positions.at(0) + syllable-extents.at(0).left + let last-right = syllable-positions.at(-1) + syllable-extents.at(-1).right + let total-width = last-right - first-left + let start-x = -total-width / 2 - first-left + + let foot-x = start-x + syllable-positions.at(head-idx) + + // Vertical level positions + // Default lines never get shorter going up the tree (each is at least as + // long as the one directly below it); explicit `distance` factors are + // applied afterwards. spacing: "auto" additionally lengthens upper lines + // to fit wide branches; "fixed" keeps them constant so trees align. + let fit = spacing == "auto" + + + // Σ → σ: "auto" fits the line to the widest branch; "fixed" grows it with the syllable count + let foot-run = 0 + for (i, _) in syllables.enumerate() { + foot-run = calc.max(foot-run, calc.abs(start-x + syllable-positions.at(i) - foot-x)) + } + let foot-line = if fit { + calc.max(0.45, branch-slope * foot-run, below-sigma-line) + } else { below-sigma-line } + let ft-height = sigma-y-label + 0.26 + line-len(foot-line, 0) + 0.25 + + // Draw Ft node above the head + content((foot-x, ft-height), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#sym_foot]) + + // Detect geminates + // A geminate occurs when the last consonant of a coda matches the first consonant of the next onset + let geminates = () + for i in range(syllables.len() - 1) { + if syllables.at(i).coda != "" and syllables.at(i + 1).onset != "" { + let coda-segments = smart-clusters(syllables.at(i).coda) + let onset-segments = smart-clusters(syllables.at(i + 1).onset) + let last-coda = coda-segments.at(-1) + let first-onset = onset-segments.at(0) + + // Check if they match and are consonants (not vowels) + if last-coda == first-onset and not is-vowel(last-coda) { + let gem-x = start-x + (syllable-positions.at(i) + syllable-positions.at(i + 1)) / 2 + geminates.push((syll-idx: i, gem-x: gem-x, gem-text: last-coda)) + } + } + } + + // Draw syllables with moraic structure + for (i, syll) in syllables.enumerate() { + let x-offset = start-x + syllable-positions.at(i) + + // Syllable node + content((x-offset, sigma-y + 0.54), context text( + size: 12 * diagram-scale * 1pt, + font: phonokit-font.get(), + )[#sym_syll]) + + // Line from Ft to σ + line((foot-x, ft-height - 0.25), (x-offset, sigma-y + 0.8)) + + // Check for geminate + let gem-coda-x = none + let gem-onset-x = none + for gem in geminates { + if gem.syll-idx == i { + gem-coda-x = gem.gem-x + } + if gem.syll-idx == i - 1 { + gem-onset-x = gem.gem-x + } + } + + draw-moraic-structure( + x-offset, + sigma-y, + syll, + terminal-y, + coda: coda, + diagram-scale: diagram-scale, + geminate-coda-x: gem-coda-x, + geminate-onset-x: gem-onset-x, + mora-symbol: sym_mora, + mora-y-override: mora-y, + ) + } + + // Draw geminate segments + for gem in geminates { + content( + (gem.gem-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#gem.gem-text], + anchor: "north", + ) + } + })) +} + +// Visualizes word with moraic structure +// Now accepts IPA-style input like "(k a.'v a).l o" +#let word-mora(input, foot: "R", coda: false, scale: 1.0, symbol: ("ω", "Σ", "σ", "μ"), distance: none, spacing: "auto") = { + check-spacing(spacing) + // Check for problematic diacritic sequences + let problematic-sequences = ("''", ",,", "\\* \\*", "\\t \\t", "::", "((", "))") + for seq in problematic-sequences { + if input.contains(seq) { + return text(fill: red, weight: "bold")[⚠ Warning: Problematic sequence involving diacritics: "#seq"] + } + } + + // Convert IPA-style input to Unicode + let converted = convert-prosody-input(input) + + let syllables = () + let feet = () + let current-foot = () + let in-foot = false + let buffer = "" + + let chars = converted.codepoints() + let i = 0 + + while i < chars.len() { + let char = chars.at(i) + + if char == "(" { + in-foot = true + current-foot = () + } else if char == ")" { + if buffer != "" { + let clean-buffer = if buffer.starts-with("'") { buffer.slice(1) } else { buffer } + let parsed = parse-syllable(clean-buffer) + syllables.push(( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: buffer.starts-with("'"), + )) + current-foot.push(syllables.len() - 1) + buffer = "" + } + if current-foot.len() > 0 { + feet.push(current-foot) + } + in-foot = false + } else if char == "." { + if buffer != "" { + let clean-buffer = if buffer.starts-with("'") { buffer.slice(1) } else { buffer } + let parsed = parse-syllable(clean-buffer) + syllables.push(( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: buffer.starts-with("'"), + )) + + if in-foot { + current-foot.push(syllables.len() - 1) + } + + buffer = "" + } + } else { + buffer += char + } + + i += 1 + } + + // Handle remaining buffer + if buffer != "" { + let clean-buffer = if buffer.starts-with("'") { buffer.slice(1) } else { buffer } + let parsed = parse-syllable(clean-buffer) + syllables.push(( + onset: parsed.onset, + nucleus: parsed.nucleus, + coda: parsed.coda, + stressed: buffer.starts-with("'"), + )) + + if in-foot { + current-foot.push(syllables.len() - 1) + } + } + + // Check for too many onsets/codas/nucleus in any syllable (limit: 5 to avoid crossing lines) + for (i, syll) in syllables.enumerate() { + let coda-segments-temp = if syll.coda != "" { smart-clusters(syll.coda) } else { () } + if coda-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many coda consonants in syllable #(i + 1) (max 5 to avoid line crossings). Found: #coda-segments-temp.len()] + } + let onset-segments-temp = if syll.onset != "" { smart-clusters(syll.onset) } else { () } + if onset-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many onset consonants in syllable #(i + 1) (max 5 to avoid line crossings). Found: #onset-segments-temp.len()] + } + let nucleus-segments-temp = smart-clusters(syll.nucleus) + if nucleus-segments-temp.len() > 5 { + return text( + fill: red, + weight: "bold", + )[⚠ Warning: Too many nucleus segments in syllable #(i + 1) (max 5 to avoid line crossings). Found: #nucleus-segments-temp.len()] + } + } + + // Determine which syllables are in feet + let in-foot-set = () + for foot in feet { + for syll-idx in foot { + in-foot-set.push(syll-idx) + } + } + + let sym_word = symbol.at(0, default: "ω") + let sym_foot = symbol.at(1, default: "Σ") + let sym_syll = symbol.at(2, default: "σ") + let sym_mora = symbol.at(3, default: "μ") + + let diagram-scale = scale + + // Draw the structure + box(baseline: 50%, cetz.canvas(length: 1cm * diagram-scale, { + import cetz.draw: * + + set-style(stroke: 0.7 * diagram-scale * 1pt) + + let line-len(natural, level) = tier-line(natural, distance, level) + + // Mora tiers (σ → μ = level 2, μ → segments = level 3) + let mu-seg-line = 0.55 + let sigma-mu-line = calc.max(0.70, mu-seg-line) + let sigma-y = -2.4 + let sigma-y-label = sigma-y + 0.54 + let mora-y = sigma-y-label - 0.29 - line-len(sigma-mu-line, 2) - 0.35 + let terminal-y = mora-y - 0.35 - line-len(mu-seg-line, 3) - 0.30 + let below-sigma-line = sigma-mu-line + + let segment-spacing = 0.35 + let min-gap-between-sylls = 0.6 + let default-spacing = 1.4 + + // Calculate extents for each syllable + let syllable-extents = () + for syll in syllables { + let has-onset = syll.onset != "" + let has-coda = syll.coda != "" + let num-onset = if has-onset { smart-clusters(syll.onset).len() } else { 0 } + let num-nucleus = smart-clusters(syll.nucleus).len() + let num-coda = if has-coda { smart-clusters(syll.coda).len() } else { 0 } + + // Calculate constituent positions (simplified like word()) + // Use segment-based extents without mora adjustments for uniform spacing + let min-gap = 0.75 + + // Non-moraic onsets/codas must clear the μ labels (same rule as draw-moraic-structure) + let reach = moraic-reach(sigma-y, mora-y, terminal-y, long: syll.nucleus.contains("ː")) + let onset-x-rel = if has-onset { + let min-offset = (num-onset - 1) * segment-spacing / 2 + (num-nucleus - 1) * segment-spacing / 2 + min-gap + let default-offset = 0.7 + let clear-offset = reach + (num-onset - 1) * segment-spacing / 2 + -calc.max(min-offset, default-offset, clear-offset) + } else { 0 } + + let coda-x-rel = if has-coda { + let min-offset = (num-nucleus + num-coda - 2) * segment-spacing / 2 + 0.4 + let default-offset = 0.7 + let clear-offset = if coda { 0 } else { reach + (num-coda - 1) * segment-spacing / 2 } + calc.max(min-offset, default-offset, clear-offset) + } else { 0 } + + // Calculate segment widths + let onset-width = if has-onset { (num-onset - 1) * segment-spacing } else { 0 } + let nucleus-width = (num-nucleus - 1) * segment-spacing + let coda-width = if has-coda { (num-coda - 1) * segment-spacing } else { 0 } + + // Calculate left and right extents (same as word()) + let left-parts = ( + if has-onset { onset-x-rel - onset-width / 2 } else { 0 }, + -nucleus-width / 2, + if has-coda { coda-x-rel - coda-width / 2 } else { 0 }, + ) + let right-parts = ( + if has-onset { onset-x-rel + onset-width / 2 } else { 0 }, + nucleus-width / 2, + if has-coda { coda-x-rel + coda-width / 2 } else { 0 }, + ) + + let left-extent = calc.min(..left-parts) + let right-extent = calc.max(..right-parts) + + syllable-extents.push((left: left-extent, right: right-extent)) + } + + // Calculate spacing based on uniform segment-to-segment distance + let syllable-positions = () + for (i, extent) in syllable-extents.enumerate() { + if i == 0 { + syllable-positions.push(0) + } else { + // Use same spacing calculation as word() for uniform segment spacing + let prev-right = syllable-extents.at(i - 1).right + let required-spacing = prev-right - extent.left + min-gap-between-sylls + let actual-spacing = calc.max(required-spacing, default-spacing) + let prev-position = syllable-positions.at(i - 1) + syllable-positions.push(prev-position + actual-spacing) + } + } + + // Center the structure + let first-left = syllable-positions.at(0) + syllable-extents.at(0).left + let last-right = syllable-positions.at(-1) + syllable-extents.at(-1).right + let total-width = last-right - first-left + let start-x = -total-width / 2 - first-left + + // Determine PWd x-position + let pwd-x = 0 + + if feet.len() > 0 { + let target-foot = if foot == "L" { feet.at(0) } else { feet.at(-1) } + let target-syll-idx = target-foot.at(0) + for (i, syll) in target-foot.enumerate() { + let this-syll = syllables.at(syll) + if this-syll.stressed { + target-syll-idx = syll + break + } + } + pwd-x = start-x + syllable-positions.at(target-syll-idx) + } else if syllables.len() > 0 { + // If the user marked stress with ' (even without using feet), align + // the PWd node to that syllable instead of defaulting to L/R extremes. + let stressed-idx = none + for (idx, syll) in syllables.enumerate() { + if syll.stressed { + stressed-idx = idx + break + } + } + let target-idx = if stressed-idx != none { + stressed-idx + } else if foot == "L" { 0 } else { syllables.len() - 1 } + pwd-x = start-x + syllable-positions.at(target-idx) + } + + // Default lines never get shorter going up the tree (each is at least as + // long as the one directly below it); explicit `distance` factors are + // applied afterwards. spacing: "auto" additionally lengthens upper lines + // to fit wide branches; "fixed" keeps them constant so trees align. + let fit = spacing == "auto" + + + // Upper tiers (ω, Σ). With spacing: "auto", a branch spanning a horizontal + // run h gets at least slope × h of vertical line (≈ the angle of the default + // trees), and vertical branches get a short line. With spacing: "fixed", + // lines are constant so trees with the same tiers align exactly. + + let foot-xs = () + for ft in feet { + let head-idx = ft.at(0) + for syll-idx in ft { + if syllables.at(syll-idx).stressed { + head-idx = syll-idx + break + } + } + foot-xs.push(start-x + syllable-positions.at(head-idx)) + } + + // Σ → σ + let foot-run = 0 + for (j, ft) in feet.enumerate() { + for syll-idx in ft { + foot-run = calc.max(foot-run, calc.abs(start-x + syllable-positions.at(syll-idx) - foot-xs.at(j))) + } + } + let foot-line = if fit { calc.max(0.45, branch-slope * foot-run, below-sigma-line) } else { below-sigma-line } + let ft-y = sigma-y-label + 0.26 + line-len(foot-line, 1) + 0.25 + + // ω → Σ, or ω → σ directly when there are no feet + let sigma-top = sigma-y-label + 0.21 + let below-top = if feet.len() > 0 { ft-y + 0.25 } else { sigma-top } + let pwd-line = if fit { + let need = if feet.len() > 0 { foot-line } else { below-sigma-line } + for fx in foot-xs { + need = calc.max(need, branch-slope * calc.abs(fx - pwd-x)) + } + // Unfooted σ hang from ω across the whole ω–σ distance + for (i, syll) in syllables.enumerate() { + if i not in in-foot-set { + let run = calc.abs(start-x + syllable-positions.at(i) - pwd-x) + need = calc.max(need, branch-slope * run - (below-top - sigma-top)) + } + } + need + } else if feet.len() > 0 { foot-line } else { below-sigma-line } + let pwd-y = below-top + line-len(pwd-line, 0) + 0.3 + + // ω lines to unfooted σ must pass clear above any Σ lying between them + let clearance-margin = 0.25 + for (i, syll) in syllables.enumerate() { + if i not in in-foot-set { + let syll-x = start-x + syllable-positions.at(i) + for fx in foot-xs { + let is-between = (pwd-x < fx and fx < syll-x) or (syll-x < fx and fx < pwd-x) + if is-between and calc.abs(syll-x - pwd-x) > 0.01 { + let t = (fx - pwd-x) / (syll-x - pwd-x) + let required = 0.3 + (ft-y + 0.25 + clearance-margin - t * sigma-top) / (1 - t) + pwd-y = calc.max(pwd-y, required) + } + } + } + } + + // Draw PWd node + content((pwd-x, pwd-y), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#sym_word]) + + // Detect geminates + // A geminate occurs when the last consonant of a coda matches the first consonant of the next onset + let geminates = () + for i in range(syllables.len() - 1) { + if syllables.at(i).coda != "" and syllables.at(i + 1).onset != "" { + let coda-segments = smart-clusters(syllables.at(i).coda) + let onset-segments = smart-clusters(syllables.at(i + 1).onset) + let last-coda = coda-segments.at(-1) + let first-onset = onset-segments.at(0) + + // Check if they match and are consonants (not vowels) + if last-coda == first-onset and not is-vowel(last-coda) { + let gem-x = start-x + (syllable-positions.at(i) + syllable-positions.at(i + 1)) / 2 + geminates.push((syll-idx: i, gem-x: gem-x, gem-text: last-coda)) + } + } + } + + // Draw feet + for (foot-idx, foot-sylls) in feet.enumerate() { + // Find head of foot (stressed syllable) + let head-idx = foot-sylls.at(0) + for syll-idx in foot-sylls { + if syllables.at(syll-idx).stressed { + head-idx = syll-idx + break + } + } + + let foot-x = start-x + syllable-positions.at(head-idx) + content((foot-x, ft-y), context text(size: 12 * diagram-scale * 1pt, font: phonokit-font.get())[#sym_foot]) + line((pwd-x, pwd-y - 0.3), (foot-x, ft-y + 0.25)) + + // Draw lines from Ft to syllables in this foot + for syll-idx in foot-sylls { + let syll-x = start-x + syllable-positions.at(syll-idx) + line((foot-x, ft-y - 0.25), (syll-x, sigma-y + 0.8)) + } + } + + // Draw lines from PWd to unfooted syllables + for (i, syll) in syllables.enumerate() { + if i not in in-foot-set { + let syll-x = start-x + syllable-positions.at(i) + line((pwd-x, pwd-y - 0.3), (syll-x, sigma-y + 0.75)) + } + } + + // Draw syllables with moraic structure + for (i, syll) in syllables.enumerate() { + let x-offset = start-x + syllable-positions.at(i) + + // Syllable node + content((x-offset, sigma-y + 0.54), context text( + size: 12 * diagram-scale * 1pt, + font: phonokit-font.get(), + )[#sym_syll]) + + // Check for geminate + let gem-coda-x = none + let gem-onset-x = none + for gem in geminates { + if gem.syll-idx == i { + gem-coda-x = gem.gem-x + } + if gem.syll-idx == i - 1 { + gem-onset-x = gem.gem-x + } + } + + draw-moraic-structure( + x-offset, + sigma-y, + syll, + terminal-y, + coda: coda, + diagram-scale: diagram-scale, + geminate-coda-x: gem-coda-x, + geminate-onset-x: gem-onset-x, + mora-symbol: sym_mora, + mora-y-override: mora-y, + ) + } + + // Draw geminate segments + for gem in geminates { + content( + (gem.gem-x, terminal-y), + context text(size: 11 * diagram-scale * 1pt, font: phonokit-font.get(), bottom-edge: "descender")[#gem.gem-text], + anchor: "north", + ) + } + })) +} diff --git a/packages/preview/phonokit/0.6.1/sonority.typ b/packages/preview/phonokit/0.6.1/sonority.typ new file mode 100644 index 0000000000..fe0880ef0b --- /dev/null +++ b/packages/preview/phonokit/0.6.1/sonority.typ @@ -0,0 +1,279 @@ +// Sonority Module +// Visualize sonority profiles of phonemic transcriptions +// Based on Parker (2011) sonority scale + +#import "ipa.typ": ipa, ipa-to-unicode +#import "_config.typ": phonokit-font +#import "@preview/cetz:0.5.2" + +// Sonority scale: Parker (2011: 18), "Final hierarchy of relative sonority" (27). +// One index per natural class, 1–17. Classes Parker does not list are placed +// by manner and voicing: lateral fricatives with fricatives, labiodental ʋ with +// glides, implosives with voiced stops. +#let sonority-scale = ( + // Low vowels + "a": 17, + "ɑ": 17, + "æ": 17, + "ɐ": 17, + "ɒ": 17, + "ɶ": 17, + // Mid peripheral vowels (not [ə]) + "e": 16, + "ø": 16, + "ɤ": 16, + "o": 16, + "ɛ": 16, + "œ": 16, + "ʌ": 16, + "ɔ": 16, + // High peripheral vowels (not [ɨ]) + "i": 15, + "y": 15, + "ɯ": 15, + "u": 15, + "ɪ": 15, + "ʏ": 15, + "ʊ": 15, + // Mid interior vowels ([ə]) + "ə": 14, + "ɘ": 14, + "ɵ": 14, + "ɜ": 14, + "ɞ": 14, + "ɚ": 14, + "ɝ": 14, + // High interior vowels ([ɨ]) + "ɨ": 13, + "ʉ": 13, + // Glides + "j": 12, + "w": 12, + "ɥ": 12, + "ɰ": 12, + "ʋ": 12, + // Rhotic approximants ([ɹ]) + "ɹ": 11, + "ɻ": 11, + // Flaps + "ɾ": 10, + "ɽ": 10, + "ⱱ": 10, + // Laterals + "l": 9, + "ɫ": 9, + "ɭ": 9, + "ʎ": 9, + "ʟ": 9, + // Trills + "r": 8, + "ʀ": 8, + "ʙ": 8, + // Nasals + "m": 7, + "ɱ": 7, + "n": 7, + "ɳ": 7, + "ɲ": 7, + "ŋ": 7, + "ɴ": 7, + // Voiced fricatives + "β": 6, + "v": 6, + "ð": 6, + "z": 6, + "ʒ": 6, + "ʐ": 6, + "ʑ": 6, + "ʝ": 6, + "ɣ": 6, + "ʁ": 6, + "ʕ": 6, + "ɦ": 6, + "ɮ": 6, + // Voiced affricates + "d͡ʒ": 5, + "d͡z": 5, + "d͡ʑ": 5, + "ɖ͡ʐ": 5, + // Voiced stops + "b": 4, + "d": 4, + "ɖ": 4, + "ɟ": 4, + "g": 4, + "ɡ": 4, + "ɢ": 4, + "ɓ": 4, + "ɗ": 4, + "ʄ": 4, + // Voiceless fricatives (including [h]) + "ɸ": 3, + "f": 3, + "θ": 3, + "s": 3, + "ʃ": 3, + "ʂ": 3, + "ɕ": 3, + "ç": 3, + "x": 3, + "χ": 3, + "ħ": 3, + "h": 3, + "ɬ": 3, + // Voiceless affricates + "t͡ʃ": 2, + "t͡s": 2, + "t͡ɕ": 2, + "ʈ͡ʂ": 2, + "t͡θ": 2, + "p͡f": 2, + // Voiceless stops (including [ʔ]) + "p": 1, + "t": 1, + "ʈ": 1, + "c": 1, + "k": 1, + "q": 1, + "ʔ": 1, +) + +#let get-sonority(phoneme) = { + if phoneme in sonority-scale { + sonority-scale.at(phoneme) + } else { + let base = phoneme.codepoints().at(0) + if base in sonority-scale { + sonority-scale.at(base) + } else { + 5 + } + } +} + +// Parse IPA string into individual phonemes and syllable boundaries +#let parse-phonemes(ipa-string) = { + let cleaned = ipa-string.replace("ˈ", "").replace("ˌ", "").replace("ː", "") + let syllable-boundaries = () + let phonemes = () + let position = 0 + let basic-clusters = cleaned.clusters() + let i = 0 + + while i < basic-clusters.len() { + let cluster = basic-clusters.at(i) + if cluster == "." { + syllable-boundaries.push(position) + i += 1 + } else if cluster == " " or cluster == "-" { + i += 1 + } else if cluster.contains("͡") { + if i + 1 < basic-clusters.len() { + let next = basic-clusters.at(i + 1) + if next != " " and next != "-" and next != "." { + phonemes.push(cluster + next) + i += 2 + position += 1 + } else { + phonemes.push(cluster) + i += 1 + position += 1 + } + } else { + phonemes.push(cluster) + i += 1 + position += 1 + } + } else { + phonemes.push(cluster) + i += 1 + position += 1 + } + } + (phonemes, syllable-boundaries) +} + +// Main sonority plotting function +#let sonority( + word, // Tipa-style string + syl: none, // (Legacy/unused directly in calculation now, kept for API compatibility) + stressed: none, // Index of stressed syllable + box-size: 0.8, // Size of phoneme boxes + scale: 1.0, // Overall scale + y-range: (0, 17), // Sonority range for y-axis (Parker's scale maxes at 17) + show-lines: true, // Connect phonemes with lines +) = { + // Convert tipa-style input to IPA + let ipa-string = ipa-to-unicode(word) + let (phonemes, syllable-boundaries) = parse-phonemes(ipa-string) + + // Truncation check + let original-count = phonemes.len() + let truncated = original-count > 10 + if truncated { + phonemes = phonemes.slice(0, 10) + syllable-boundaries = syllable-boundaries.filter(pos => pos < 10) + } + + let sonority-values = phonemes.map(p => get-sonority(p)) + let n-phonemes = phonemes.len() + let height = 3 + + if truncated { + text(size: 9pt, fill: red, weight: "bold")[⚠ Warning: Truncated to first 10 phonemes.] + v(0.5em) + } + + // Bind locally: inside the canvas closure `scale` resolves to Typst's built-in + let scale-factor = scale + + cetz.canvas(length: scale-factor * 1cm, { + import cetz.draw: * + set-origin((0, 0)) + + // Draw connecting lines first + if show-lines and n-phonemes > 1 { + for i in range(n-phonemes - 1) { + let x1 = float(i) * 1.5 + let y1 = float((sonority-values.at(i) - y-range.at(0))) / float((y-range.at(1) - y-range.at(0))) * float(height) + let x2 = float(i + 1) * 1.5 + let y2 = ( + float((sonority-values.at(i + 1) - y-range.at(0))) / float((y-range.at(1) - y-range.at(0))) * float(height) + ) + line((x1, y1), (x2, y2), stroke: (thickness: 0.5pt * scale-factor, paint: gray, dash: "dashed")) + } + } + + // Draw phoneme boxes with syllable-based alternating colors + for (i, phoneme) in phonemes.enumerate() { + let sonority = sonority-values.at(i) + let x = float(i) * 1.5 + let y = float((sonority - y-range.at(0))) / float((y-range.at(1) - y-range.at(0))) * float(height) + + // Determine syllable index by checking how many boundaries we have passed + let syllable-index = syllable-boundaries.filter(b => b <= i).len() + + // Alternate colors: Even syllables = White, Odd syllables = Gray + let box-fill = if calc.even(syllable-index) { + white + } else { + rgb("dddddd") // Light gray + } + + // Draw box + rect( + (x - box-size / 2, y - box-size / 2), + (x + box-size / 2, y + box-size / 2), + fill: box-fill, + stroke: (0.5pt * scale-factor) + black, + ) + + // Add phoneme label (always black now) + content( + (x, y), + context text(size: 10pt * scale-factor, font: phonokit-font.get(), fill: black)[#phoneme], + anchor: "center", + ) + } + }) +} diff --git a/packages/preview/phonokit/0.6.1/sound-shift.typ b/packages/preview/phonokit/0.6.1/sound-shift.typ new file mode 100644 index 0000000000..a1e6229631 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/sound-shift.typ @@ -0,0 +1,261 @@ +#import "@preview/cetz:0.5.2" +#import "ipa.typ": ipa-to-unicode +#import "_config.typ": phonokit-font + +#let _ss-render-label(label) = { + if type(label) == str { + ipa-to-unicode(label) + } else { + label + } +} + +#let _ss-node-id(node) = { + if "id" in node { + str(node.id) + } else if "label" in node and type(node.label) == str { + node.label + } else { + panic("Each sound-shift node must have either an id or a string label.") + } +} + +#let _ss-node-label(node) = { + if "label" in node { + node.label + } else if "id" in node { + node.id + } else { + panic("Each sound-shift node must have either a label or an id.") + } +} + +#let _ss-node-pos(node) = { + if "at" in node { + node.at + } else if "pos" in node { + node.pos + } else { + panic("Each sound-shift node must define a position with at: (x, y).") + } +} + +#let _ss-arrow-end(endpoint, nodes) = { + if type(endpoint) == str { + let key = str(endpoint) + let node = nodes.find(n => n.id == key) + assert(node != none, message: "Unknown sound-shift endpoint: " + key) + (pos: node.at, radius: node.radius) + } else if type(endpoint) == array and endpoint.len() >= 2 { + let radius = endpoint.at(2, default: 0) + (pos: (endpoint.at(0), endpoint.at(1)), radius: radius) + } else { + panic("Arrow endpoints must be node ids or coordinate pairs.") + } +} + +#let _ss-arrow-dict(arrow) = { + if type(arrow) == dictionary { + arrow + } else if type(arrow) == array and arrow.len() >= 2 { + (from: arrow.at(0), to: arrow.at(1)) + } else { + panic("Each sound-shift arrow must be a dictionary or a (from, to) tuple.") + } +} + +#let sound-shift( + nodes: (), + arrows: (), + highlights: (), + node-size: 2.2em, + text-fill: black, + highlight-fill: luma(230), + highlight-radius: 0.42, + arrow-color: black, + arrow-style: "solid", + arrow-width: 0.8pt, + arrow-size: 1.0, + curved: false, + curve: 0.45, + scale: 1.0, +) = { + assert(type(nodes) == array, message: "nodes must be an array") + assert(nodes.len() > 0, message: "nodes array cannot be empty") + + let scale-factor = scale + let normalized-nodes = nodes.map(node => { + assert(type(node) == dictionary, message: "Each sound-shift node must be a dictionary.") + let id = _ss-node-id(node) + let label = _ss-node-label(node) + let pos = _ss-node-pos(node) + let normalized = ( + id: id, + label: label, + at: pos, + fill: node.at("fill", default: none), + size: node.at("size", default: node-size), + text-fill: node.at("text-fill", default: text-fill), + radius: node.at("radius", default: highlight-radius), + ) + normalized + }) + + let all-points = normalized-nodes.map(n => n.at) + let arrow-points = arrows.map(arrow => { + let spec = _ss-arrow-dict(arrow) + let from = _ss-arrow-end(spec.from, normalized-nodes) + let to = _ss-arrow-end(spec.to, normalized-nodes) + (from.pos, to.pos) + }) + + let pad = 0.6 + let xs = all-points.map(p => p.at(0)) + arrow-points.map(pair => pair.at(0).at(0)) + arrow-points.map(pair => pair.at(1).at(0)) + let ys = all-points.map(p => p.at(1)) + arrow-points.map(pair => pair.at(0).at(1)) + arrow-points.map(pair => pair.at(1).at(1)) + let min-x = calc.min(..xs) - pad + let max-x = calc.max(..xs) + pad + let min-y = calc.min(..ys) - pad + let max-y = calc.max(..ys) + pad + + box(inset: 1.2em, baseline: 40%, cetz.canvas(length: scale-factor * 1cm, { + import cetz.draw: * + + for item in highlights { + let pos = _ss-arrow-end(item, normalized-nodes).pos + circle( + pos, + radius: highlight-radius, + fill: highlight-fill, + stroke: none, + ) + } + + for node in normalized-nodes { + if node.fill != none { + circle( + node.at, + radius: node.radius, + fill: node.fill, + stroke: none, + ) + } + } + + for arrow in arrows { + let spec = _ss-arrow-dict(arrow) + let from-end = _ss-arrow-end(spec.from, normalized-nodes) + let to-end = _ss-arrow-end(spec.to, normalized-nodes) + let from-pos = from-end.pos + let to-pos = to-end.pos + let dx = to-pos.at(0) - from-pos.at(0) + let dy = to-pos.at(1) - from-pos.at(1) + let dist = calc.sqrt(dx * dx + dy * dy) + let safe-dist = if dist == 0 { 1 } else { dist } + let ux = dx / safe-dist + let uy = dy / safe-dist + let start-gap = spec.at("start-gap", default: from-end.radius) + let end-gap = spec.at("end-gap", default: to-end.radius) + let drawn-from = ( + from-pos.at(0) + ux * start-gap, + from-pos.at(1) + uy * start-gap, + ) + let drawn-to = ( + to-pos.at(0) - ux * end-gap, + to-pos.at(1) - uy * end-gap, + ) + let px = -dy / safe-dist + let py = dx / safe-dist + let amount = spec.at("curve", default: curve) + let ctrl = spec.at("ctrl", default: ( + (from-pos.at(0) + to-pos.at(0)) / 2 + px * safe-dist * amount, + (from-pos.at(1) + to-pos.at(1)) / 2 + py * safe-dist * amount, + )) + let local-curved = spec.at("curved", default: curved) + let local-color = spec.at("color", default: arrow-color) + let local-width = spec.at("width", default: arrow-width) * scale-factor + let local-style = spec.at("style", default: arrow-style) + let start-tangent = if local-curved { + let sx = ctrl.at(0) - from-pos.at(0) + let sy = ctrl.at(1) - from-pos.at(1) + let sd = calc.sqrt(sx * sx + sy * sy) + if sd == 0 { (ux, uy) } else { (sx / sd, sy / sd) } + } else { + (ux, uy) + } + let end-tangent = if local-curved { + let ex = to-pos.at(0) - ctrl.at(0) + let ey = to-pos.at(1) - ctrl.at(1) + let ed = calc.sqrt(ex * ex + ey * ey) + if ed == 0 { (ux, uy) } else { (ex / ed, ey / ed) } + } else { + (ux, uy) + } + let drawn-from = ( + from-pos.at(0) + start-tangent.at(0) * start-gap, + from-pos.at(1) + start-tangent.at(1) * start-gap, + ) + let drawn-to = ( + to-pos.at(0) - end-tangent.at(0) * end-gap, + to-pos.at(1) - end-tangent.at(1) * end-gap, + ) + let drawn-ctrl = if local-curved { + let from-shift = ( + drawn-from.at(0) - from-pos.at(0), + drawn-from.at(1) - from-pos.at(1), + ) + let to-shift = ( + drawn-to.at(0) - to-pos.at(0), + drawn-to.at(1) - to-pos.at(1), + ) + ( + ctrl.at(0) + (from-shift.at(0) + to-shift.at(0)) / 2, + ctrl.at(1) + (from-shift.at(1) + to-shift.at(1)) / 2, + ) + } else { + ctrl + } + let shaft-stroke = if local-style == "dashed" { + (paint: local-color, thickness: local-width, dash: "dashed") + } else if local-style == "dotted" { + (paint: local-color, thickness: local-width, dash: "dotted") + } else { + (paint: local-color, thickness: local-width) + } + let head-stroke = (paint: local-color, thickness: local-width) + let mark-style = (end: ">", fill: local-color, scale: spec.at("arrow-size", default: arrow-size) * scale-factor) + + if local-style == "dashed" or local-style == "dotted" { + if local-curved { + bezier(drawn-from, drawn-to, drawn-ctrl, stroke: shaft-stroke) + } else { + line(drawn-from, drawn-to, stroke: shaft-stroke) + } + let tiny = 0.01 + let head-anchor = ( + drawn-to.at(0) - end-tangent.at(0) * tiny, + drawn-to.at(1) - end-tangent.at(1) * tiny, + ) + line(head-anchor, drawn-to, stroke: head-stroke, mark: mark-style) + } else if local-curved { + bezier(drawn-from, drawn-to, drawn-ctrl, stroke: shaft-stroke, mark: mark-style) + } else { + line(drawn-from, drawn-to, stroke: shaft-stroke, mark: mark-style) + } + } + + for node in normalized-nodes { + content( + node.at, + anchor: "center", + context text( + font: phonokit-font.get(), + size: node.size * scale-factor, + fill: node.text-fill, + top-edge: "x-height", + bottom-edge: "baseline", + _ss-render-label(node.label), + ), + ) + } + })) +} diff --git a/packages/preview/phonokit/0.6.1/typst.toml b/packages/preview/phonokit/0.6.1/typst.toml new file mode 100644 index 0000000000..a46354a5d9 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/typst.toml @@ -0,0 +1,23 @@ +[package] +categories = ["utility", "text", "visualization"] +disciplines = ["linguistics"] +name = "phonokit" +version = "0.6.1" +exclude = ["gallery/"] +keywords = [ + "linguistics", + "phonology", + "phonetics", + "IPA", + "transcription", + "prosody", + "optimality-theory", + "features", + "autosegmental", +] +entrypoint = "lib.typ" +authors = ["Guilherme D. Garcia "] +license = "MIT" +description = "A toolkit to create phonological representations" +repository = "https://github.com/guilhermegarcia/phonokit" +homepage = "https://gdgarcia.ca/phonokit" diff --git a/packages/preview/phonokit/0.6.1/ui-lang.typ b/packages/preview/phonokit/0.6.1/ui-lang.typ new file mode 100644 index 0000000000..3b33162fa7 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/ui-lang.typ @@ -0,0 +1,369 @@ +// Shared UI-language helpers for translatable labels. + +#let _ui-lang-aliases = ( + "en": "en", + "english": "en", + "fr": "fr", + "french": "fr", + "pt": "pt", + "portuguese": "pt", +) + +#let _ui-lang-available = "en, english, fr, french, pt, portuguese" + +#let resolve-ui-lang(ui-lang) = { + if type(ui-lang) != str { + none + } else { + let key = lower(ui-lang.trim()) + _ui-lang-aliases.at(key, default: none) + } +} + +#let ui-lang-error(ui-lang) = [*Error:* UI language "#ui-lang" not available. \ + Available UI languages: #_ui-lang-available] + +#let _consonant-ui-labels = ( + "en": ( + places: ( + "Bilabial", + "Labiodental", + "Dental", + "Alveolar", + "Postalveolar", + "Retroflex", + "Palatal", + "Velar", + "Uvular", + "Pharyngeal", + "Glottal", + ), + places_short: ( + "Bilab", + "Labdent", + "Dent", + "Alv", + "Postalv", + "Retro", + "Pal", + "Vel", + "Uvu", + "Phar", + "Glot", + ), + manners: ( + "Plosive", + "Nasal", + "Trill", + "Tap or Flap", + "Fricative", + "Lateral fricative", + "Approximant", + "Lateral approximant", + ), + manners_short: ( + "Plos", + "Nas", + "Trill", + "Tap/Flap", + "Fric", + "Lat fric", + "Approx", + "Lat approx", + ), + aspirated_plosive: "Plosive (aspirated)", + aspirated_plosive_short: "Plos (asp)", + affricate: "Affricate", + affricate_short: "Affr", + aspirated_affricate: "Affricate (aspirated)", + aspirated_affricate_short: "Affr (asp)", + ), + "fr": ( + places: ( + "Bilabiale", + "Labio-dentale", + "Dentale", + "Alvéolaire", + "Post-alvéolaire", + "Rétroflexe", + "Palatale", + "Vélaire", + "Uvulaire", + "Pharyngale", + "Glottale", + ), + places_short: ( + "Bilab", + "Labiod", + "Dent", + "Alv", + "Postalv", + "Retrof", + "Pal", + "Vel", + "Uvul", + "Phar", + "Glot", + ), + manners: ( + "Plosive", + "Nasale", + "Vibrante", + "Battue", + "Fricative", + "Fricative latérale", + "Approximante", + "Approximante latérale", + ), + manners_short: ( + "Plos", + "Nas", + "Vibr", + "Batt", + "Fric", + "Fric lat", + "Approx", + "Approx lat", + ), + aspirated_plosive: "Plosive (aspirée)", + aspirated_plosive_short: "Plos (asp)", + affricate: "Affriquée", + affricate_short: "Affr", + aspirated_affricate: "Affriquée (aspirée)", + aspirated_affricate_short: "Affr (asp)", + ), + "pt": ( + places: ( + "Bilabial", + "Labiodental", + "Dental", + "Alveolar", + "Pós-alveolar", + "Retroflexa", + "Palatal", + "Velar", + "Uvular", + "Faríngea", + "Glotal", + ), + places_short: ( + "Bilab", + "Labdent", + "Dent", + "Alv", + "Posalv", + "Retrof", + "Pal", + "Vel", + "Uvul", + "Far", + "Glot", + ), + manners: ( + "Plosiva", + "Nasal", + "Vibrante", + "Tepe", + "Fricativa", + "Fricativa lateral", + "Aproximante", + "Aproximante lateral", + ), + manners_short: ( + "Plos", + "Nas", + "Vibr", + "Tepe", + "Fric", + "Fric lat", + "Aprox", + "Aprox lat", + ), + aspirated_plosive: "Plosiva (aspirada)", + aspirated_plosive_short: "Plos (asp)", + affricate: "Africada", + affricate_short: "Afr", + aspirated_affricate: "Africada (aspirada)", + aspirated_affricate_short: "Afr (asp)", + ), +) + +#let ui-consonant-labels(ui-lang) = _consonant-ui-labels.at(ui-lang) + +#let _feature-ui-labels = ( + "en": ( + "consonantal": "consonantal", + "sonorant": "sonorant", + "continuant": "continuant", + "delayed_release": "del rel", + "approximant": "approximant", + "tap": "tap", + "trill": "trill", + "nasal": "nasal", + "voice": "voice", + "spread_gl": "spread gl", + "constr_gl": "constr gl", + "labial": "labial", + "round": "round", + "labiodental": "labiodental", + "coronal": "coronal", + "anterior": "anterior", + "distributed": "distributed", + "strident": "strident", + "lateral": "lateral", + "dorsal": "dorsal", + "high": "high", + "low": "low", + "front": "front", + "back": "back", + "tense": "tense", + ), + "fr": ( + "consonantal": "consonantique", + "sonorant": "sonant", + "continuant": "continu", + "delayed_release": "rel ret", + "approximant": "approximant", + "tap": "battu", + "trill": "vibrant", + "nasal": "nasal", + "voice": "voisé", + "spread_gl": "gl écartée", + "constr_gl": "gl contr", + "labial": "labial", + "round": "arrondi", + "labiodental": "labio-dental", + "coronal": "coronal", + "anterior": "antérieur", + "distributed": "distribué", + "strident": "strident", + "lateral": "latéral", + "dorsal": "dorsal", + "high": "haut", + "low": "bas", + "front": "antérieur", + "back": "postérieur", + "tense": "tendu", + ), + "pt": ( + "consonantal": "consonantal", + "sonorant": "sonorante", + "continuant": "contínuo", + "delayed_release": "lib ret", + "approximant": "aproximante", + "tap": "tepe", + "trill": "vibrante", + "nasal": "nasal", + "voice": "vozeado", + "spread_gl": "gl aberta", + "constr_gl": "gl constr", + "labial": "labial", + "round": "arredondado", + "labiodental": "labiodental", + "coronal": "coronal", + "anterior": "anterior", + "distributed": "distribuído", + "strident": "estridente", + "lateral": "lateral", + "dorsal": "dorsal", + "high": "alto", + "low": "baixo", + "front": "anterior", + "back": "posterior", + "tense": "tenso", + ), +) + +#let ui-feature-label(key, ui-lang) = _feature-ui-labels.at(ui-lang).at(key, default: key) + +#let _geom-ui-labels = ( + "en": ( + "root": "root", + "laryngeal": "laryngeal", + "oral cavity": "oral cavity", + "C-place": "C-place", + "vocalic": "vocalic", + "V-place": "V-place", + "aperture": "aperture", + "constricted": "constr", + "continuant": "cont", + "distributed": "distr", + "dorsal": "dor", + "coronal": "cor", + "labial": "lab", + "anterior": "ant", + "radical": "rad", + "high": "hi", + "low": "low", + "back": "back", + "round": "round", + "tense": "tense", + "voice": "voice", + "nasal": "nasal", + "lateral": "lat", + "spread": "spread", + "open1": "open1", + "open2": "open2", + "open3": "open3", + ), + "fr": ( + "root": "racine", + "laryngeal": "laryng", + "oral cavity": "cav orale", + "C-place": "lieu C", + "vocalic": "vocaliq", + "V-place": "lieu V", + "aperture": "apert", + "constricted": "constr", + "continuant": "cont", + "distributed": "distr", + "dorsal": "dor", + "coronal": "cor", + "labial": "lab", + "anterior": "ant", + "radical": "rad", + "high": "haut", + "low": "bas", + "back": "post", + "round": "arr", + "tense": "tens", + "voice": "vois", + "nasal": "nas", + "lateral": "lat", + "spread": "écart", + "open1": "ouv1", + "open2": "ouv2", + "open3": "ouv3", + ), + "pt": ( + "root": "raiz", + "laryngeal": "laring", + "oral cavity": "cav oral", + "C-place": "ponto C", + "vocalic": "vocal", + "V-place": "ponto V", + "aperture": "abert", + "constricted": "constr", + "continuant": "cont", + "distributed": "distr", + "dorsal": "dor", + "coronal": "cor", + "labial": "lab", + "anterior": "ant", + "radical": "rad", + "high": "alto", + "low": "baixo", + "back": "post", + "round": "arr", + "tense": "tens", + "voice": "voz", + "nasal": "nas", + "lateral": "lat", + "spread": "gl ab", + "open1": "abr1", + "open2": "abr2", + "open3": "abr3", + ), +) + +#let ui-geom-label(key, ui-lang) = _geom-ui-labels.at(ui-lang).at(key, default: key) diff --git a/packages/preview/phonokit/0.6.1/vowels-3d.typ b/packages/preview/phonokit/0.6.1/vowels-3d.typ new file mode 100644 index 0000000000..fff1138458 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/vowels-3d.typ @@ -0,0 +1,212 @@ +#import "@preview/cetz:0.5.2": canvas, draw +#import "ipa.typ": ipa-to-unicode +#import "_config.typ": phonokit-font +#import "vowels.typ": language-vowels, vowel-data + +// Vowels supported by the 3D chart (Ladefoged & Maddieson 1996, p. 283) +#let vowels-3d-inventory = ("i", "e", "ɛ", "a", "u", "o", "ɔ", "ɒ", "ə") + +// Distance between the unrounded (front) and rounded (back) faces; fixed by design +#let vowels-3d-depth = 4 + +// Accept an angle, or a plain number read as degrees (30 = 30deg) +#let to-angle(value, name) = { + if type(value) == angle { value } else if type(value) in (int, float) { value * 1deg } else { + panic("vowels-3d: " + name + " must be an angle (e.g. 30deg) or a number of degrees (e.g. 30)") + } +} + +// Roundedness score (depth): 0 = front face (least rounded), 1 = back face (most rounded) +// High vowels sit at the extremes (i least, u most rounded); low vowels meet at 0.5 +#let get-roundedness-score(vowel-info) = { + if vowel-info.rounded { + 0.5 + (vowel-info.height * 0.5) - ((1 - vowel-info.frontness) * vowel-info.height) + } else { + ((1 - vowel-info.height) * 0.5) + (vowel-info.frontness * vowel-info.height) + } +} + +// Orthographic projection: spin around the vertical axis by `rotation`, +// then tilt the view down by `tilt`. Returns (screen-x, screen-y, view-depth); +// larger view-depth = farther from the viewer. +#let project-3d(p, rotation, tilt) = { + let (x, y, z) = p + let x1 = x * calc.cos(rotation) + z * calc.sin(rotation) + let z1 = -x * calc.sin(rotation) + z * calc.cos(rotation) + let y2 = y * calc.cos(tilt) + z1 * calc.sin(tilt) + let z2 = -y * calc.sin(tilt) + z1 * calc.cos(tilt) + (x1, y2, z2) +} + +#let vowels-3d( + ..args, // Optional positional vowel string (tipa-style); default: all 9 vowels + rotation: 30deg, // Spin around the vertical axis (0deg = straight-on; positive = depth recedes left); number = degrees + tilt: 20deg, // Viewing angle from above (0deg = eye level); number = degrees + width: 8, + height: 6, + rows: 3, + cols: 2, + scale: 0.7, + depth-cue: true, // Draw farther vowels smaller; fades out automatically in flat front/back views +) = { + assert(type(depth-cue) == bool, message: "vowels-3d: depth-cue must be true or false") + assert(args.pos().len() <= 1, + message: "vowels-3d: expected at most one positional argument (the vowel string)") + assert(args.named().len() == 0, + message: "vowels-3d: unexpected named argument(s): " + args.named().keys().join(", ")) + let vowel-string = args.pos().at(0, default: "ieEauoO6@") + let rotation = to-angle(rotation, "rotation") + let tilt = to-angle(tilt, "tilt") + let supported = "i e ɛ a u o ɔ ɒ ə (tipa: i e E a u o O 6 @)" + assert( + type(vowel-string) != str or lower(vowel-string) not in language-vowels, + message: "vowels-3d: language names are not accepted (got \"" + str(vowel-string) + + "\"). Use the nine default vowels or a subset of " + supported, + ) + let scale-factor = scale // `scale` is shadowed by the built-in inside canvas closures + let vowels-to-plot = ipa-to-unicode(vowel-string).clusters().filter(v => v.trim() != "") + for v in vowels-to-plot { + assert( + v in vowels-3d-inventory, + message: "vowels-3d: unsupported symbol \"" + v + "\". Only the nine default vowels or a subset of them are accepted: " + supported, + ) + } + + let scaled-width = width * scale + let scaled-height = height * scale + let scaled-depth = vowels-3d-depth * scale + let scaled-font-size = 22 * scale + let scaled-line-thickness = 0.85 * scale + + let top = scaled-height / 2 + let bottom = -scaled-height / 2 + // Same trapezoid as the 2D vowels() chart (x, y), extruded along z + let tl = (-scaled-width / 2, top) + let tr = (scaled-width / 2, top) + let br = (scaled-width / 2, bottom) + let bl = (-scaled-width / 10, bottom) + + // Left/right edge x at a given height + let edge-x(corner-top, corner-bottom, y) = { + let t = (top - y) / scaled-height + corner-top.at(0) * (1 - t) + corner-bottom.at(0) * t + } + + // Negated so positive rotation sends the depth up-left, as in Ladefoged & Maddieson + let proj(p) = project-3d(p, -rotation, tilt) + let to-screen(p) = proj(p).slice(0, 2) + + // A face is visible when its outward normal points toward the viewer + let facing(normal) = proj(normal).at(2) < 0 + let slant-len = calc.sqrt(scaled-height * scaled-height + calc.pow(bl.at(0) - tl.at(0), 2)) + let visible = ( + front: facing((0, 0, -1)), + back: facing((0, 0, 1)), + top: facing((0, 1, 0)), + bottom: facing((0, -1, 0)), + right: facing((1, 0, 0)), + left: facing((-scaled-height / slant-len, -(bl.at(0) - tl.at(0)) / slant-len, 0)), + ) + + let solid-stroke = (paint: black, thickness: scaled-line-thickness * 1pt) + let grid-stroke = (paint: gray, thickness: scaled-line-thickness * 1pt) + let hidden-stroke = (paint: gray, thickness: scaled-line-thickness * 0.8pt, dash: "dashed") + + // Segments: (start-3d, end-3d, faces it belongs to, is-outline) + let segments = () + let z0 = 0 + let z1 = scaled-depth + let corners = (tl, tr, br, bl) + let corner-faces = (("top", "left"), ("top", "right"), ("bottom", "right"), ("bottom", "left")) + let side-faces = ("top", "right", "bottom", "left") + for i in range(4) { + let a = corners.at(i) + let b = corners.at(calc.rem(i + 1, 4)) + let side = side-faces.at(i) + segments.push(((..a, z0), (..b, z0), ("front", side), true)) + segments.push(((..a, z1), (..b, z1), ("back", side), true)) + segments.push(((..a, z0), (..a, z1), corner-faces.at(i), true)) + } + + // Height levels wrap around the four vertical faces + for i in range(1, rows) { + let y = top - scaled-height * i / rows + let lx = edge-x(tl, bl, y) + let rx = edge-x(tr, br, y) + segments.push(((lx, y, z0), (rx, y, z0), ("front",), false)) + segments.push(((rx, y, z0), (rx, y, z1), ("right",), false)) + segments.push(((rx, y, z1), (lx, y, z1), ("back",), false)) + segments.push(((lx, y, z1), (lx, y, z0), ("left",), false)) + } + + // Front/back divisions on the front face + for i in range(1, cols) { + let t = i / cols + let x-top = tl.at(0) * (1 - t) + tr.at(0) * t + let x-bottom = bl.at(0) * (1 - t) + br.at(0) * t + segments.push(((x-top, top, z0), (x-bottom, bottom, z0), ("front",), false)) + } + + // Peripheral vowels sit on the left (front) or right (back) face; central vowels + // float inside the prism. Depth is set by roundedness. + let vowel-points = vowels-to-plot.dedup().map(v => { + let info = vowel-data.at(v) + let y = bottom + info.height * scaled-height + let lx = edge-x(tl, bl, y) + let rx = edge-x(tr, br, y) + let interior = info.frontness >= 0.4 and info.frontness <= 0.6 + let x = if interior { lx + info.frontness * (rx - lx) } else if info.frontness < 0.5 { lx } else { rx } + (vowel: v, pos: (x, y, get-roundedness-score(info) * scaled-depth), interior: interior) + }) + + canvas({ + import draw: * + + // Hidden edges first, so visible lines are drawn on top + for (a, b, faces, outline) in segments { + if not faces.any(f => visible.at(f)) { + line(to-screen(a), to-screen(b), stroke: hidden-stroke) + } + } + for (a, b, faces, outline) in segments { + if faces.any(f => visible.at(f)) { + line(to-screen(a), to-screen(b), stroke: if outline { solid-stroke } else { grid-stroke }) + } + } + + // Depth cue: vowels farther from the viewer are drawn smaller (lines are unchanged). + // Distance is normalized between the nearest and farthest prism corners. + // Strength fades to 0 only as the line of sight approaches the rounding axis + // (flat front or back view, which looks like the 2D trapezoid). Top, bottom and + // side views keep the cue. m = 1 when looking straight along that axis. + let max-shrink = 0.3 // Farthest vowel at 70% size + let full-at = 0.15 // 1 - m at which the cue reaches full strength (default view: ~0.19) + let m = calc.abs(proj((0, 0, 1)).at(2)) + let t = calc.min(1, (1 - m) / full-at) + let strength = if depth-cue { max-shrink * t * t * (3 - 2 * t) } else { 0 } // smoothstep + let corner-depths = corners.map(c => (proj((..c, z0)).at(2), proj((..c, z1)).at(2))).flatten() + let near = calc.min(..corner-depths) + let far = calc.max(..corner-depths) + let size-at(p) = if far - near < 1e-9 { 1 } else { + 1 - strength * (proj(p).at(2) - near) / (far - near) + } + + // Draw farthest first, so nearer vowels overlap farther ones + for vp in vowel-points.sorted(key: vp => -proj(vp.pos).at(2)) { + let (sx, sy) = to-screen(vp.pos) + let k = size-at(vp.pos) + circle((sx, sy), radius: scaled-line-thickness * 0.12 * k, fill: black, stroke: none) + content( + (sx + 0.15 * scale-factor * k, sy + 0.15 * scale-factor * k), + anchor: "south-west", + context text( + size: scaled-font-size * k * 1pt, + font: phonokit-font.get(), + top-edge: "x-height", + bottom-edge: "baseline", + vp.vowel, + ), + ) + } + }) +} diff --git a/packages/preview/phonokit/0.6.1/vowels.typ b/packages/preview/phonokit/0.6.1/vowels.typ new file mode 100644 index 0000000000..e5be9c7a13 --- /dev/null +++ b/packages/preview/phonokit/0.6.1/vowels.typ @@ -0,0 +1,605 @@ +#import "@preview/cetz:0.5.2": canvas, draw +#import "ipa.typ": ipa-to-unicode +#import "_config.typ": phonokit-font + +// Vowel data with relative positions (0-1 scale) +// frontness: 0 = front, 0.5 = central, 1 = back +// height: 1 = close, 0.67 = close-mid, 0.33 = open-mid, 0 = open +// rounded: affects horizontal positioning within minimal pairs +#let vowel-data = ( + "i": (frontness: 0.05, height: 1.00, rounded: false), + "y": (frontness: 0.05, height: 1.00, rounded: true), + "ɨ": (frontness: 0.50, height: 1.00, rounded: false), + "ʉ": (frontness: 0.50, height: 1.00, rounded: true), + "ɯ": (frontness: 0.95, height: 1.00, rounded: false), + "u": (frontness: 0.95, height: 1.00, rounded: true), + "ɪ": (frontness: 0.15, height: 0.85, rounded: false), + "ʏ": (frontness: 0.25, height: 0.85, rounded: true), + "ʊ": (frontness: 0.85, height: 0.85, rounded: true), + "e": (frontness: 0.05, height: 0.67, rounded: false), + "ø": (frontness: 0.05, height: 0.67, rounded: true), + "ɘ": (frontness: 0.50, height: 0.67, rounded: false), + "ɵ": (frontness: 0.50, height: 0.67, rounded: true), + "ɤ": (frontness: 0.95, height: 0.67, rounded: false), + "o": (frontness: 0.95, height: 0.67, rounded: true), + "ə": (frontness: 0.585, height: 0.51, rounded: false), + "ɛ": (frontness: 0.05, height: 0.34, rounded: false), + "œ": (frontness: 0.05, height: 0.34, rounded: true), + "ɜ": (frontness: 0.50, height: 0.34, rounded: false), + "ɞ": (frontness: 0.50, height: 0.34, rounded: true), + "ʌ": (frontness: 0.95, height: 0.34, rounded: false), + "ɔ": (frontness: 0.95, height: 0.34, rounded: true), + "æ": (frontness: 0.05, height: 0.15, rounded: false), + "ɐ": (frontness: 0.585, height: 0.18, rounded: false), + "a": (frontness: 0.05, height: 0.00, rounded: false), + "ɶ": (frontness: 0.05, height: 0.00, rounded: true), + "ɑ": (frontness: 0.95, height: 0.00, rounded: false), + "ɒ": (frontness: 0.95, height: 0.00, rounded: true), +) + +// Calculate actual position from relative coordinates +#let get-vowel-position(vowel-info, trapezoid, width, height, offset) = { + let front = vowel-info.frontness + let h = vowel-info.height + + // Calculate y coordinate + let y = -height / 2 + (h * height) + + // Interpolate x based on trapezoid shape at this height + let t = 1 - h // interpolation factor (0 at top, 1 at bottom) + let left-x = trapezoid.at(0).at(0) * (1 - t) + trapezoid.at(3).at(0) * t + let right-x = trapezoid.at(1).at(0) * (1 - t) + trapezoid.at(2).at(0) * t + + let x = 0 + + // Front vowels (frontness < 0.4) + if front < 0.4 { + // Extreme front (< 0.15): tense vowels like i, e + if front < 0.15 { + if vowel-info.rounded { + // Front rounded: inside (right of left edge) + x = left-x + offset + } else { + // Front unrounded: outside (left of left edge) + x = left-x - offset + } + } // Near-front (0.15-0.4): lax vowels like ɪ, ʏ + // Always positioned inside the trapezoid + else { + x = left-x + (front * (right-x - left-x)) + } + } // Back vowels (frontness > 0.6) + else if front > 0.6 { + // Extreme back (> 0.85): tense vowels like u, o + if front > 0.85 { + if vowel-info.rounded { + // Back rounded: outside (right of right edge) + x = right-x + offset + } else { + // Back unrounded: inside (left of right edge) + x = right-x - offset + } + } // Near-back (0.6-0.85): lax vowels like ʊ + // Always positioned inside the trapezoid + else { + x = left-x + (front * (right-x - left-x)) + } + } // Central vowels + else { + // Calculate base central position + let center-x = left-x + (front * (right-x - left-x)) + + // Apply full offset for rounded/unrounded pairs (same as front/back) + if vowel-info.rounded { + x = center-x + offset // Rounded to the right + } else { + x = center-x - offset // Unrounded to the left + } + } + + (x, y) +} + +// Language vowel inventories +#let language-vowels = ( + "spanish": "aeoiu", + "portuguese": "iɔeaouɛ", + "italian": "iɔeaouɛ", + "english": "iɪaeɛæɑɔoʊuʌə", + "french": "iœɑɔøeaouɛyə", + "german": "iyʊuɪʏeøoɔɐaɛœə", + "japanese": "ieaou", + "russian": "iɨueoa", + "arabic": "aiu", + "all": "iyɨʉɯuɪʏʊeøɘɵɤoəɛœɜɞʌɔæɐaɶɑɒ", + // Add more languages here or adjust existing inventories +) + +#let language-nasal-vowels = ( + "french": ("ɛ", "œ", "ɔ", "ɑ"), +) + +#let _strip-nasal(vowel) = vowel.normalize(form: "nfd").replace("̃", "") + +#let _collect-custom-vowels(input) = { + let converted = ipa-to-unicode(input.replace(regex("\\s+"), " ")) + let oral = "" + let nasals = () + let rejected = () + + for cluster in converted.clusters() { + let decomposed = cluster.normalize(form: "nfd") + let base = _strip-nasal(decomposed) + if base in vowel-data { + if base not in oral { + oral += base + } + if "̃" in decomposed and base not in nasals { + nasals.push(base) + } + } else if cluster not in rejected { + rejected.push(cluster) + } + } + + (oral: oral, nasals: nasals, rejected: rejected) +} + +// Main vowels function +#let vowels( + ..args, // Optional positional vowel-string (read below) — allows `lang`-only calls + lang: none, + width: 8, + height: 6, + rows: 3, // Only 2 internal horizontal lines + cols: 2, // Only 1 vertical line inside trapezoid + scale: 0.7, // Scale factor for entire chart + nasals: false, // Draw nasalized copies of plotted vowels + arrows: (), // List of (from-tipa-str, to-tipa-str) tuples + arrow-color: black, // Color for arrow lines and heads + arrow-style: "solid", // "solid" or "dashed" + curved: false, // Curve arrows with a quadratic bezier arc + shift: (), // List of (tipa-str, x-offset, y-offset) tuples + shift-color: gray, // Color for shifted vowel symbols + shift-size: none, // Font size for shifted vowels; none = same as regular + highlight: (), // List of tipa strings whose background circle is highlighted + highlight-color: luma(220), // Circle color for highlighted vowels (default: light gray) + circle-size: 1, // Multiplier for the radius of highlight circles + phoneme-colors: (:), // Dictionary of phoneme-specific text colors +) = { + // Read the optional positional argument: vowel symbols, a language name, or + // tipa-style IPA. It is optional (via the `..args` sink) so that `lang`-only + // calls like `vowels(lang: "spanish")` work — a parameter with a default value + // would be named-only in Typst and could not be passed positionally. + assert(args.pos().len() <= 1, + message: "vowels: expected at most one positional argument (the vowel string)") + assert(args.named().len() == 0, + message: "vowels: unexpected named argument(s): " + args.named().keys().join(", ")) + let vowel-string = args.pos().at(0, default: none) + // An unfinished inventory should stay quiet while the user is typing. + if lang == none and (vowel-string == none or vowel-string.trim() == "") { + return none + } + + // Determine which vowels to plot + let vowels-to-plot = "" + let nasal-target-vowels = () + let error-msg = none + let inventory-error = [ + *Error:* Available presets: #(language-vowels.keys().map(raw).join(", ")). + Use a listed preset or provide only vowel symbols. + ] + + // Check if vowel-string is actually a language name + if vowel-string != none and vowel-string in language-vowels { + // It's a language name - use language vowels + vowels-to-plot = language-vowels.at(vowel-string) + } else if lang != none { + // Explicit lang parameter provided + if lang in language-vowels { + vowels-to-plot = language-vowels.at(lang) + } else { + // Language not available - prepare error message + return inventory-error + } + } else if vowel-string != none and vowel-string != "" { + // Use as manual vowel specification - keep oral bases for plotting and + // remember which vowels were explicitly nasalized in the input. + let parsed = _collect-custom-vowels(vowel-string) + if parsed.rejected.len() > 0 { + return inventory-error + } + if parsed.oral == "" { + return inventory-error + } + vowels-to-plot = parsed.oral + nasal-target-vowels = parsed.nasals + } else { + // Nothing specified + error-msg = inventory-error + } + + // If there's an error, display it and return + if error-msg != none { + return error-msg + } + + // Calculate scaled dimensions + let scaled-width = width * scale + let scaled-height = height * scale + let scaled-offset = 0.55 * scale + // White mask circles: fixed size, only cut gaps in the grid lines behind vowels + let scaled-circle-radius = 0.35 * scale + // Colored highlight circles: drawn above the masks, resizable via circle-size + let scaled-highlight-radius = 0.4 * circle-size * scale + let scaled-bullet-radius = 0.09 * scale + let scaled-font-size = 22 * scale + let scaled-line-thickness = 0.85 * scale + let scaled-arrow-mark = 1.5 * scale + let scaled-nasal-dy = 0.44 * scale + let scaled-nasal-font-size = scaled-font-size * 0.9 * 1pt + let nasal-color = gray.darken(10%) + let resolved-shift-size = if shift-size != none { shift-size * scale } else { scaled-font-size * 1pt } + // Split highlight into regular-vowel highlights (strings) and shifted-vowel + // highlights (arrays in the same (tipa-str, x, y) format as shift:) + let highlight-set = highlight.filter(h => type(h) == str).map(ipa-to-unicode) + let highlight-shifts = highlight.filter(h => type(h) != str) + .map(h => (ipa-to-unicode(h.at(0)), h.at(1), h.at(2))) + let resolved-phoneme-colors = (:) + for (phoneme, color) in phoneme-colors { + resolved-phoneme-colors.insert(ipa-to-unicode(phoneme), color) + } + let vowel-text = (size, phoneme) => { + if phoneme in resolved-phoneme-colors { + text(size: size, font: phonokit-font.get(), fill: resolved-phoneme-colors.at(phoneme), top-edge: "x-height", bottom-edge: "baseline", phoneme) + } else { + text(size: size, font: phonokit-font.get(), top-edge: "x-height", bottom-edge: "baseline", phoneme) + } + } + let shifted-vowel-text = (size, phoneme) => { + text(size: size, font: phonokit-font.get(), fill: resolved-phoneme-colors.at(phoneme, default: shift-color), top-edge: "x-height", bottom-edge: "baseline", phoneme) + } + + canvas({ + import draw: * + + // Define the trapezoidal quadrilateral using scaled dimensions + let trapezoid = ( + (-scaled-width / 2, scaled-height / 2.), + (scaled-width / 2., scaled-height / 2), + (scaled-width / 2., -scaled-height / 2), + (-scaled-width / 10, -scaled-height / 2), + ) + + // Draw horizontal grid lines + for i in range(1, rows) { + let t = i / rows + let left-x = trapezoid.at(0).at(0) * (1 - t) + trapezoid.at(3).at(0) * t + let right-x = trapezoid.at(1).at(0) * (1 - t) + trapezoid.at(2).at(0) * t + let y = scaled-height / 2 - (scaled-height * t) + + line((left-x, y), (right-x, y), stroke: (paint: gray.lighten(30%), thickness: scaled-line-thickness * 1pt)) + } + + // Draw vertical grid lines + for i in range(1, cols) { + let t = i / cols + let top-x = trapezoid.at(0).at(0) * (1 - t) + trapezoid.at(1).at(0) * t + let bottom-x = trapezoid.at(3).at(0) * (1 - t) + trapezoid.at(2).at(0) * t + + line((top-x, scaled-height / 2), (bottom-x, -scaled-height / 2), stroke: ( + paint: gray.lighten(30%), + thickness: scaled-line-thickness * 1pt, + )) + } + + // Draw the outline + line(..trapezoid, close: true, stroke: (paint: gray.lighten(30%), thickness: scaled-line-thickness * 1pt)) + + // Resolve an arrow endpoint to a canvas position. + // endpoint is either a tipa string (canonical vowel position) or a + // (tipa-str, x-offset, y-offset) array (shifted position, same format as shift:). + // Returns (found, position) where found is false if the vowel is unknown. + let pos-of(endpoint) = { + let is-str = type(endpoint) == str + let v = ipa-to-unicode(if is-str { endpoint } else { endpoint.at(0) }) + let x-off = if is-str { 0 } else { endpoint.at(1) } + let y-off = if is-str { 0 } else { endpoint.at(2) } + if v in vowel-data { + let base = get-vowel-position(vowel-data.at(v), trapezoid, scaled-width, scaled-height, scaled-offset) + (true, (base.at(0) + x-off, base.at(1) + y-off)) + } else { + (false, (0, 0)) + } + } + + // Radius at which an arrow pointing at `endpoint` should stop: the edge of + // the highlight circle if that vowel is highlighted, else the mask edge. + let end-radius(endpoint) = { + let highlighted = if type(endpoint) == str { + ipa-to-unicode(endpoint) in highlight-set + } else { + let v = ipa-to-unicode(endpoint.at(0)) + highlight-shifts.any(h => h.at(0) == v and h.at(1) == endpoint.at(1) and h.at(2) == endpoint.at(2)) + } + if highlighted { calc.max(scaled-circle-radius, scaled-highlight-radius) } else { scaled-circle-radius } + } + + // Collect vowel positions + let vowel-positions = () + for vowel in vowels-to-plot.clusters() { + if vowel in vowel-data { + let vowel-info = vowel-data.at(vowel) + let pos = get-vowel-position(vowel-info, trapezoid, scaled-width, scaled-height, scaled-offset) + vowel-positions.push((vowel: vowel, info: vowel-info, pos: pos)) + } + } + + // Build the global obstacle list for curved-arrow avoidance: + // all plotted vowels plus all shifted copies, pre-computed once. + let shifted-obs = shift + .filter(s => ipa-to-unicode(s.at(0)) in vowel-data) + .map(s => { + let sv = ipa-to-unicode(s.at(0)) + let base = get-vowel-position(vowel-data.at(sv), trapezoid, scaled-width, scaled-height, scaled-offset) + (base.at(0) + s.at(1), base.at(1) + s.at(2)) + }) + let preset-name = if vowel-string != none and vowel-string in language-vowels { + vowel-string + } else { + lang + } + let nasal-bases = if preset-name != none and preset-name in language-nasal-vowels { + language-nasal-vowels.at(preset-name) + } else { + nasal-target-vowels + } + let nasal-positions = if nasals { + vowel-positions + .filter(vp => vp.vowel in nasal-bases) + .map(vp => ( + vowel: vp.vowel, + pos: (vp.pos.at(0), vp.pos.at(1) + scaled-nasal-dy), + label: vp.vowel + "̃", + )) + } else { + () + } + let all-obstacle-positions = vowel-positions.map(vp => vp.pos) + shifted-obs + nasal-positions.map(np => np.pos) + + // True if p is either at/near endpoint ep, or is its minimal-pair partner. + // Minimal-pair partners share height (dy ≈ 0) and lie exactly 2×scaled-offset + // apart in x (one rounded, one unrounded). They are geometrically inseparable + // from their partner, so arrows approaching ep will inevitably pass near the + // partner and should not try to avoid it. The ±40% relative tolerance on the + // distance keeps the check scale-independent while excluding near-front lax + // pairs like ɪ/ʏ (whose inter-vowel distance is only ~68% of 2×scaled-offset). + let near-or-pair(p, ep) = { + let dx = p.at(0) - ep.at(0) + let dy = p.at(1) - ep.at(1) + let d = calc.sqrt(dx*dx + dy*dy) + let at-endpoint = d < scaled-circle-radius * 0.5 + let is-pair = calc.abs(dy) < scaled-offset * 0.1 and calc.abs(d - 2*scaled-offset) < scaled-offset * 0.4 + at-endpoint or is-pair + } + + // Draw arrows in three phases so that arrowhead clustering can be applied + // after all control points and tangents are known. + + // ── Phase 1: compute drawing parameters for every valid arrow ──────────── + let arrows-data = () + for arrow in arrows { + let fr = pos-of(arrow.at(0)) + let tr = pos-of(arrow.at(1)) + if fr.at(0) and tr.at(0) { + let from-pos = fr.at(1) + let to-pos = tr.at(1) + let dx = to-pos.at(0) - from-pos.at(0) + let dy = to-pos.at(1) - from-pos.at(1) + let dist = calc.sqrt(dx * dx + dy * dy) + let mid-x = (from-pos.at(0) + to-pos.at(0)) / 2 + let mid-y = (from-pos.at(1) + to-pos.at(1)) / 2 + + // Control point selection for curved arrows. + // Two obstacle lists are used in priority order: + // strict – excludes only the exact endpoints; pair partners are live + // obstacles so the algorithm avoids departing through them + // (e.g. ɔ→ɪ must not start by crossing through ʌ). + // loose – also excludes pair partners; used as a fallback only when + // no strict-safe path exists. + // Sampling at t = 0.1 and 0.9 (in addition to interior midpoints) catches + // obstacles very close to the source or destination vowel. + let ctrl = if curved { + let px = -dy / dist // CCW perpendicular unit vector + let py = dx / dist + let ccw-sm = (mid-x + px * dist * 0.30, mid-y + py * dist * 0.30) + let cw-sm = (mid-x - px * dist * 0.30, mid-y - py * dist * 0.30) + let ccw-lg = (mid-x + px * dist * 0.55, mid-y + py * dist * 0.55) + let cw-lg = (mid-x - px * dist * 0.55, mid-y - py * dist * 0.55) + let local-obs-strict = all-obstacle-positions.filter(p => { + let dfx = p.at(0) - from-pos.at(0) + let dfy = p.at(1) - from-pos.at(1) + let dtx = p.at(0) - to-pos.at(0) + let dty = p.at(1) - to-pos.at(1) + let far-from = calc.sqrt(dfx*dfx + dfy*dfy) > scaled-circle-radius * 0.5 + let far-to = calc.sqrt(dtx*dtx + dty*dty) > scaled-circle-radius * 0.5 + far-from and far-to + }) + let local-obs-loose = all-obstacle-positions.filter(p => + not near-or-pair(p, from-pos) and not near-or-pair(p, to-pos) + ) + let clearance = scaled-circle-radius * 1.3 + let sample-ts = (0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9) + let hits(obs, c) = obs.any(ob => + sample-ts.any(t => { + let bx = (1-t)*(1-t)*from-pos.at(0) + 2*t*(1-t)*c.at(0) + t*t*to-pos.at(0) + let by = (1-t)*(1-t)*from-pos.at(1) + 2*t*(1-t)*c.at(1) + t*t*to-pos.at(1) + let ex = ob.at(0) - bx + let ey = ob.at(1) - by + calc.sqrt(ex*ex + ey*ey) < clearance + }) + ) + let ctrl-candidates = (ccw-sm, cw-sm, ccw-lg, cw-lg) + let chosen = ctrl-candidates.find(c => not hits(local-obs-strict, c)) + let chosen = if chosen != none { chosen } else { + ctrl-candidates.find(c => not hits(local-obs-loose, c)) + } + if chosen != none { chosen } else { ccw-sm } + } else { + (mid-x + (-dy / dist) * dist * 0.3, mid-y + (dx / dist) * dist * 0.3) + } + + // Tangent at destination: ctrl→to-pos for curves, chord for straight lines. + let tangent = if curved { + let ex = to-pos.at(0) - ctrl.at(0) + let ey = to-pos.at(1) - ctrl.at(1) + let ed = calc.sqrt(ex * ex + ey * ey) + (ex / ed, ey / ed) + } else { + (dx / dist, dy / dist) + } + + // Pull endpoint back to circle edge along the arrival tangent + let end-r = end-radius(arrow.at(1)) + let adjusted-to = ( + to-pos.at(0) - tangent.at(0) * end-r, + to-pos.at(1) - tangent.at(1) * end-r, + ) + + arrows-data.push(( + from-pos: from-pos, + to-pos: to-pos, + ctrl: ctrl, + tangent: tangent, + adjusted-to: adjusted-to, + end-r: end-r, + )) + } + } + + // ── Phase 2: merge arrowheads converging at the same vowel ─────────────── + // When multiple arrows target the same vowel and their adjusted-to points + // are within cluster-radius of each other, snap them all to their centroid + // and use the averaged (renormalized) tangent for a consistent arrowhead. + let cluster-radius = scaled-circle-radius + let arrows-data = arrows-data.map(a => { + let cluster = arrows-data.filter(b => { + let dtx = b.to-pos.at(0) - a.to-pos.at(0) + let dty = b.to-pos.at(1) - a.to-pos.at(1) + let same-target = calc.sqrt(dtx*dtx + dty*dty) < 0.01 + let dax = b.adjusted-to.at(0) - a.adjusted-to.at(0) + let day = b.adjusted-to.at(1) - a.adjusted-to.at(1) + same-target and calc.sqrt(dax*dax + day*day) < cluster-radius + }) + if cluster.len() > 1 { + let n = cluster.len() + let tx = cluster.map(b => b.tangent.at(0)).sum() / n + let ty = cluster.map(b => b.tangent.at(1)).sum() / n + let tn = calc.sqrt(tx*tx + ty*ty) + let avg-tan = if tn > 0.001 { (tx/tn, ty/tn) } else { a.tangent } + // Re-derive adjusted-to from the normalised tangent so the tip lands + // exactly on the circle edge (the centroid of circle-edge points sits + // strictly inside the circle and would leave the head floating there). + let snapped = ( + a.to-pos.at(0) - avg-tan.at(0) * a.end-r, + a.to-pos.at(1) - avg-tan.at(1) * a.end-r, + ) + (from-pos: a.from-pos, to-pos: a.to-pos, ctrl: a.ctrl, + tangent: avg-tan, adjusted-to: snapped, end-r: a.end-r) + } else { + a + } + }) + + // ── Phase 3: render ────────────────────────────────────────────────────── + let shaft-stroke = (paint: arrow-color, thickness: scaled-line-thickness * 1.5pt, + dash: if arrow-style == "dashed" { "dashed" } else { none }) + let head-stroke = (paint: arrow-color, thickness: scaled-line-thickness * 1.5pt) + let mark-style = (end: ">", fill: arrow-color, scale: scaled-arrow-mark) + for a in arrows-data { + let from-pos = a.from-pos + let adjusted-to = a.adjusted-to + let ctrl = a.ctrl + let tangent = a.tangent + if arrow-style == "dashed" { + // Draw dashed shaft without a mark + if curved { + bezier(from-pos, adjusted-to, ctrl, stroke: shaft-stroke) + } else { + line(from-pos, adjusted-to, stroke: shaft-stroke) + } + // Solid near-zero segment at the tip renders the mark independently of + // the dash pattern, correctly oriented along the arrival tangent + let tiny = 0.01 + let head-anchor = ( + adjusted-to.at(0) - tangent.at(0) * tiny, + adjusted-to.at(1) - tangent.at(1) * tiny, + ) + line(head-anchor, adjusted-to, stroke: head-stroke, mark: mark-style) + } else { + // Solid: shaft and arrowhead in one draw call + if curved { + bezier(from-pos, adjusted-to, ctrl, stroke: shaft-stroke, mark: mark-style) + } else { + line(from-pos, adjusted-to, stroke: shaft-stroke, mark: mark-style) + } + } + } + + // White mask circles: cut a gap in the grid lines behind every vowel + for vp in vowel-positions { + circle(vp.pos, radius: scaled-circle-radius, fill: white, stroke: none) + } + + // Highlight circles: above the masks, below the minimal-pair bullets and symbols + for vp in vowel-positions.filter(vp => vp.vowel in highlight-set) { + circle(vp.pos, radius: scaled-highlight-radius, fill: highlight-color, stroke: none) + } + + // Draw bullets between minimal pairs (same frontness/height, different rounding) + for i in range(vowel-positions.len()) { + for j in range(i + 1, vowel-positions.len()) { + let v1 = vowel-positions.at(i) + let v2 = vowel-positions.at(j) + + // Check if they form a minimal pair + let same-front = v1.info.frontness == v2.info.frontness + let same-height = v1.info.height == v2.info.height + let diff-round = v1.info.rounded != v2.info.rounded + + if same-front and same-height and diff-round { + // Draw bullet at midpoint between vowels + let mid-x = (v1.pos.at(0) + v2.pos.at(0)) / 2 + let mid-y = (v1.pos.at(1) + v2.pos.at(1)) / 2 + circle((mid-x, mid-y), radius: scaled-bullet-radius, fill: black) + } + } + } + + // Plot vowel symbols + for vp in vowel-positions { + content(vp.pos, context vowel-text(scaled-font-size * 1pt, vp.vowel)) + } + + // Draw schematic nasalized copies slightly offset from the oral vowels. + for np in nasal-positions { + content(np.pos, context text(size: scaled-nasal-font-size, font: phonokit-font.get(), fill: nasal-color, top-edge: "x-height", bottom-edge: "baseline", np.label)) + } + + // Draw shifted vowels (on top of regular vowels) + for s in shift { + let vowel = ipa-to-unicode(s.at(0)) + let x-off = s.at(1) + let y-off = s.at(2) + if vowel in vowel-data { + let base-pos = get-vowel-position(vowel-data.at(vowel), trapezoid, scaled-width, scaled-height, scaled-offset) + let shifted-pos = (base-pos.at(0) + x-off, base-pos.at(1) + y-off) + circle(shifted-pos, radius: scaled-circle-radius, fill: white, stroke: none) + if highlight-shifts.any(h => h.at(0) == vowel and h.at(1) == x-off and h.at(2) == y-off) { + circle(shifted-pos, radius: scaled-highlight-radius, fill: highlight-color, stroke: none) + } + content(shifted-pos, context shifted-vowel-text(resolved-shift-size, vowel)) + } + } + }) +}