diff --git a/src/scriptapps/tools/fontprep/LICENSE-cascadia-OFL.txt b/src/scriptapps/tools/fontprep/LICENSE-cascadia-OFL.txt new file mode 100644 index 00000000..b7f91076 --- /dev/null +++ b/src/scriptapps/tools/fontprep/LICENSE-cascadia-OFL.txt @@ -0,0 +1,94 @@ +Copyright (c) 2019 - Present, Microsoft Corporation, +with Reserved Font Name Cascadia Code. + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/src/scriptapps/tools/fontprep/README.md b/src/scriptapps/tools/fontprep/README.md new file mode 100644 index 00000000..88948739 --- /dev/null +++ b/src/scriptapps/tools/fontprep/README.md @@ -0,0 +1,70 @@ +# fontprep - punkdoc-mono web font for the punk-native documentation pipeline + +`punkdoc-mono.woff2` is the pinned monospace web font consumed by the +punk-native html doc generation (`../punkargs_punknative.tcl` and its +successors). It exists because the ansi->html rendering of punk::args usage +tables, textblock output and cp437/ansi art (src/testansi) needs glyphs - +Unicode 16 octants (U+1CC00-1CEBF) and Symbols for Legacy Computing +(U+1FB00-1FBFF) in particular - that are absent from the monospace fonts +commonly installed on reader machines. Coverage was measured empirically: +see the glyph inventory probe notes below. + +## What it is + +A glyph subset of **Cascadia Mono v2404.23** (variable font, `wght` axis +200-700 retained so browsers render true bold from this single file), +renamed to **punkdoc-mono**. + +- Renaming is REQUIRED, not cosmetic: Cascadia's SIL OFL 1.1 licence declares + the Reserved Font Name "Cascadia Code", and a modified version (subsetting + is modification) must not use it. `punkdoc-mono` avoids the Cascadia name + entirely. The font's embedded name table carries the original Microsoft + copyright (nameID 0, preserved verbatim as the OFL requires), the OFL + licence statement/url (nameID 13/14), and a provenance description + (nameID 10). The original trademark record was removed (a renamed copy must + not assert Microsoft's trademark). +- Licence: SIL OFL 1.1 - full text in `LICENSE-cascadia-OFL.txt` (fetched from + the v2404.23 source tag). The OFL applies to the font only; it places no + conditions on punkshell (BSD) or on documents rendered with the font. + Track in the project licence inventory (G-063). +- No italic face is included: browsers synthesize oblique for italic spans + (advance widths are preserved, so terminal-grid alignment holds). + +## Provenance (reproducibility) + +- Source: https://github.com/microsoft/cascadia-code/releases/download/v2404.23/CascadiaCode-2404.23.zip + - release zip sha256: A911410626C0E09D03FA3FDDA827188FDA96607DF50FECC3C5FEE5906E33251B + - input file within zip: ttf/CascadiaMono.ttf (variable) + - input ttf sha256: 056F33BA2D9DA954750320EC6ECFB7D83CFB5F93AD593722170CD4498F5D4E95 +- Tooling: python fonttools 4.63.0 + brotli (one-time prep only - the doc + BUILD never invokes python; it just reads/copies/base64s the .woff2) +- Command: + `python make_punkdoc_mono.py CascadiaMono.ttf punkdoc-unicodes.txt ` +- Output punkdoc-mono.woff2 sha256: 3476B6C12B3306D25A787C01882B0F484C3E11C3E05A59969065B199056641A4 + (1561 cmap codepoints, 4319 glyphs, 79780 bytes) + +## Subset ranges + +`punkdoc-unicodes.txt` - whole blocks (with headroom) covering the demand +measured 2026-07-13 by the glyph inventory probe (scratch trial folder +argdoc2man-trial-2026-07-13: glyph_inventory.tcl over src/testansi decoded as +ansicat does + textblock::periodic + all textblock frametypes + +punk::args::usage = 160 unique non-ascii codepoints), plus the full CP437 +repertoire and braille (U+2800-28FF) for future plot rendering. +Verified: the subset covers 157/160 of the measured inventory - identical to +the full source font. The 3 misses are deliberate non-goals: em-space U+2003 +(converters should normalize exotic spaces to plain spaces - terminals treat +them as one cell) and fullwidth forms U+FF30/FF31 (inherently double-width; +CJK-font fallback territory). + +## Regenerating (when the glyph domain grows) + +1. Re-run the glyph inventory probe over the current content; if new + codepoints fall outside `punkdoc-unicodes.txt`, widen the ranges. +2. `pip install fonttools brotli` (any machine; a venv is fine) +3. Fetch the pinned Cascadia release (or a newer one - update shas here) +4. Run the command above; verify coverage (e.g. via + Windows.Media.GlyphTypeface against the inventory - see font_coverage.ps1 + in the trial scratch folder) and that name records contain no "Cascadia" + outside the nameID 10 provenance credit. +5. Commit the new .woff2 together with updated shas in this README. diff --git a/src/scriptapps/tools/fontprep/make_punkdoc_mono.py b/src/scriptapps/tools/fontprep/make_punkdoc_mono.py new file mode 100644 index 00000000..abd9d1a4 --- /dev/null +++ b/src/scriptapps/tools/fontprep/make_punkdoc_mono.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +"""make_punkdoc_mono.py - one-time font-prep for the punkshell doc pipeline. + +Subsets the Cascadia Mono VARIABLE font (weight axis kept, so browsers get +true bold from one file) to the punkdoc unicode ranges, renames the family to +'punkdoc-mono' (REQUIRED: Cascadia's OFL declares Reserved Font Name +'Cascadia Code' - a modified version must not use the reserved name), and +writes woff2 (for the web) + ttf (for cmap verification tooling). + +usage: python make_punkdoc_mono.py +""" +import sys +import os +from fontTools import version as fonttools_version +from fontTools.ttLib import TTFont +from fontTools.subset import Subsetter, Options + +SRC_FAMILY_NAMES = ["Cascadia Mono", "CascadiaMono", "Cascadia Code", "CascadiaCode"] +NEW_FAMILY = "punkdoc-mono" + +def parse_unicodes(path): + cps = set() + with open(path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line or line.startswith("#"): + continue + if "-" in line: + lo, hi = line.split("-") + cps.update(range(int(lo, 16), int(hi, 16) + 1)) + else: + cps.add(int(line, 16)) + return cps + +# name records that carry the FAMILY NAME and must be renamed (OFL Reserved +# Font Name requirement). Copyright (0), designer/vendor credits (8,9,11,12) +# are preserved untouched - the OFL requires the copyright notice be retained. +FAMILY_NAME_IDS = (1, 3, 4, 6, 16, 17, 21, 22, 25) +OFL_ID13 = ("This Font Software is licensed under the SIL Open Font License, " + "Version 1.1.") +PROVENANCE_ID10 = ("punkdoc-mono is a glyph subset of Cascadia Mono v2404.23 " + "(c) Microsoft Corporation, renamed as required by the SIL " + "OFL 1.1 Reserved Font Name condition. Prepared for the " + "punkshell documentation pipeline.") + +def rename(font): + name = font["name"] + keep = [] + for rec in name.names: + # drop the trademark record entirely: the original text is about the + # Cascadia name (which this font no longer uses) and a renamed copy + # must not assert Microsoft's trademark over the new name + if rec.nameID == 7: + continue + if rec.nameID in FAMILY_NAME_IDS: + s = rec.toUnicode() + for old in SRC_FAMILY_NAMES: + s = s.replace(old, NEW_FAMILY) + s = s.replace(old.replace(" ", ""), NEW_FAMILY) + if rec.nameID == 6: + s = s.replace(" ", "") + rec.string = s + elif rec.nameID == 13: + rec.string = OFL_ID13 + keep.append(rec) + name.names = keep + # embedded provenance/description (mentioning the source font as credit is + # permitted - the RFN restriction is on naming the font, not attribution) + for platformID, platEncID, langID in ((3, 1, 0x409), (1, 0, 0)): + name.setName(PROVENANCE_ID10, 10, platformID, platEncID, langID) + +def main(): + src, unifile, outdir = sys.argv[1], sys.argv[2], sys.argv[3] + os.makedirs(outdir, exist_ok=True) + cps = parse_unicodes(unifile) + print(f"fonttools {fonttools_version}; requested codepoints: {len(cps)}") + + font = TTFont(src) + opts = Options() + opts.layout_features = ["*"] # keep default layout behaviour + opts.name_IDs = ["*"] # keep all name records (license notice ids too) + opts.name_legacy = True + opts.name_languages = ["*"] + opts.notdef_outline = True + opts.recalc_bounds = True + opts.recalc_average_width = True + opts.prune_unicode_ranges = True + subsetter = Subsetter(options=opts) + subsetter.populate(unicodes=cps) + subsetter.subset(font) + + covered = set() + for table in font["cmap"].tables: + covered.update(table.cmap.keys()) + print(f"glyphs in subset: {font['maxp'].numGlyphs}; cmap codepoints: {len(covered)}") + + rename(font) + + ttf_path = os.path.join(outdir, "punkdoc-mono.ttf") + font.save(ttf_path) + print(f"wrote {ttf_path} ({os.path.getsize(ttf_path)} bytes)") + + font.flavor = "woff2" + woff2_path = os.path.join(outdir, "punkdoc-mono.woff2") + font.save(woff2_path) + print(f"wrote {woff2_path} ({os.path.getsize(woff2_path)} bytes)") + +if __name__ == "__main__": + main() diff --git a/src/scriptapps/tools/fontprep/punkdoc-mono.woff2 b/src/scriptapps/tools/fontprep/punkdoc-mono.woff2 new file mode 100644 index 00000000..27087f10 Binary files /dev/null and b/src/scriptapps/tools/fontprep/punkdoc-mono.woff2 differ diff --git a/src/scriptapps/tools/fontprep/punkdoc-unicodes.txt b/src/scriptapps/tools/fontprep/punkdoc-unicodes.txt new file mode 100644 index 00000000..98927a38 --- /dev/null +++ b/src/scriptapps/tools/fontprep/punkdoc-unicodes.txt @@ -0,0 +1,21 @@ +# punkdoc-mono subset ranges - whole blocks covering (with headroom) the +# glyph demand measured by glyph_inventory.tcl over src/testansi + +# textblock::periodic/frames + punk::args::usage (2026-07-13, 160 codepoints) +# plus the full CP437 repertoire and braille for future plot rendering. +0020-007E +00A0-00FF +0180-024F +0370-03FF +2000-206F +2070-209F +20A0-20CF +2190-21FF +2200-22FF +2300-23FF +2400-243F +2500-25FF +2600-26FF +2700-27BF +2800-28FF +1CC00-1CEBF +1FB00-1FBFF diff --git a/src/scriptapps/tools/punkargs_punknative.tcl b/src/scriptapps/tools/punkargs_punknative.tcl index f027b8e9..c4036a42 100644 --- a/src/scriptapps/tools/punkargs_punknative.tcl +++ b/src/scriptapps/tools/punkargs_punknative.tcl @@ -4,10 +4,18 @@ # synopsis conventions ([...] optionals, placeholders) - bypassing # doctools entirely (doctools stays Tcl-standard; no man-page output here). # -# usage: tclsh punkargs_punknative.tcl ?-boxmap none|light? +# usage: tclsh punkargs_punknative.tcl ?-boxmap none|light? ?-assets embed|link|none? # -boxmap none (default): faithful heavy box-drawing glyphs # -boxmap light: map heavy/mixed box chars to the light set (portability # fallback for viewers with only minimal mono fonts; _lightbox suffix) +# -assets embed (default): data-uri the pinned punkdoc-mono web font +# (fontprep/punkdoc-mono.woff2) into the page - fully self-contained +# single file; every glyph (incl. octants/legacy computing) renders +# identically on any machine (~106KB page overhead) +# -assets link: reference punkdoc-mono.woff2 relatively and copy it to +# - one cached font shared by a whole generated doc set +# -assets none: no font shipped; rely on the viewer's installed fonts +# via the css stack (octants/legacy-computing glyphs may be missing) # writes: /_punknative.html terminal-faithful rendering: # punk::args::usage ANSI output converted to styled HTML spans # (dark terminal theme, box-drawing tables, colours, italics) @@ -54,6 +62,7 @@ lassign $argv pkgname outdir #Verified 2026-07-13: heavy renders aligned in chrome + firefox with the #pre-level font stack. set boxmap none +set assets embed foreach {k v} [lrange $argv 2 end] { switch -- $k { -boxmap { @@ -63,8 +72,15 @@ foreach {k v} [lrange $argv 2 end] { } set boxmap $v } + -assets { + if {$v ni {embed link none}} { + puts stderr "-assets must be embed, link or none (got '$v')" + exit 2 + } + set assets $v + } default { - puts stderr "unknown option '$k' (known: -boxmap)" + puts stderr "unknown option '$k' (known: -boxmap -assets)" exit 2 } } @@ -292,15 +308,44 @@ puts "punk-native docgen: [llength $cmd_ids] command definitions for $pkgname $p # --------------------------------------------------------------------------- # html: terminal-faithful - ansi2html of punk::args::usage per command # --------------------------------------------------------------------------- +#pinned web font (see fontprep/README.md): guarantees every glyph in the punk +#domain - incl. unicode 16 octants and legacy computing blocks that no +#commonly-installed font provides. Variable (wght 200-700) so bold spans use +#real bold. OFL-subset of Cascadia Mono renamed punkdoc-mono (RFN condition). +set fontfile [file join [file dirname [file dirname [file normalize [info script]/__]]] fontprep punkdoc-mono.woff2] +set fontface "" +if {$assets ne "none"} { + if {![file exists $fontfile]} { + puts stderr "WARNING: -assets $assets requested but $fontfile not found - falling back to -assets none" + set assets none + } else { + if {$assets eq "embed"} { + set fd [open $fontfile rb] + set fontb64 [binary encode base64 [read $fd]] + close $fd + set fontsrc "data:font/woff2;base64,$fontb64" + } else { + #link mode: ship the font next to the page(s) - shared+cached + file copy -force $fontfile [file join $outdir punkdoc-mono.woff2] + set fontsrc "punkdoc-mono.woff2" + } + set fontface "@font-face {font-family:\"punkdoc-mono\"; src:url($fontsrc) format(\"woff2\"); font-weight:200 700; font-display:block;}\n" + } +} + set html "" append html "\n\n" append html "$pkgname $pkgversion - punk::args reference\n" append html "