Unicode sequences, @typeface, and the font/typeface distinction

A run of Unicode code points now needs one pair of carets, not one pair
each: ^9053 53EF 9053^ renders the characters adjacently, and a single
character inside such a run stands for itself.  An invalid code point is
an error naming the position that wrote it, rather than an apology
rendered into the finished document; noncharacters are refused alongside
surrogates; and a code point that lands on a Klammertext special is safe
through the re-reads that assemble a document.  The character tables now
produce the precomposed form, so a diacritic written as ^a" and one typed
directly are the same text to a search or a copy-paste.

"kdesc -u" describes how to write a Unicode character, with tables of
glyphs beside the codes that produce them.  It is new and not yet
complete: it predates the sequence form and the typeface klammer below.

A typeface and a style are no longer both called "font".  A typeface is a
family resolved by name; a style code is the one letter naming a type
style.  So @document takes :serif_typeface and its siblings, a table takes
:style, captions take :caption_style and :caption_size, the uniform scale
is :text_scale, and the command is "kdesc --typeface".  This renames
arguments a document may already use; an old name is a located error
rather than a silent omission.

@typeface sets a passage in another typeface for the length of that
passage:

    @typeface Noto Sans Egyptian Hieroglyphs | ^132AA 133CF^ :scale 1.5 @

Writing the klammer is the whole declaration -- the document resolves the
typeface, embeds it in what it produces, and carries its license along.
Two typefaces for scripts no text font covers now ship with Klammertext:
Noto Sans Egyptian Hieroglyphs and Noto Sans Cuneiform.  Every typeface in
the distribution now carries its license file.

(from dev 61e598698891)
This commit is contained in:
ack committed 2026-08-25 23:30:55 +02:00
1 parent 37b6ba1c4f
commit af29bbf7ac
47 files changed
+1921 -135

No files matched your search

+59 -7
View File
@@ -2,8 +2,12 @@
#
# character_test.sh — Regression tests for the ^-character forms the engine
# resolves at read time: the ^UUUU^ Unicode code point (4-6 hex digits,
# since 2026-08-23 covering the full range to U+10FFFF), and the character
# tables (mnemonics, diacritics) it must not disturb.
# since 2026-08-23 covering the full range to U+10FFFF), the
# ^UUUU UUUU ...^ SEQUENCE form (added 2026-08-25: one caret pair around
# whitespace-delimited tokens, assembled at katom level by
# process_unicode_sequences — so, unlike the string-level single form, it
# is skipped inside @eval/@read/@cond spans and for literal content), and
# the character tables (mnemonics, diacritics) it must not disturb.
#
# SKS-independent: character handling is katomizer/engine machinery
# (mac/ktype.h nonascii katom, mac/character.cpp), so everything runs with
@@ -39,6 +43,26 @@ reset=$'\033[0m'
trim() { awk '{ sub(/[ \t\r]+$/, "") } { line[NR]=$0 } END { f=1; while (f<=NR && line[f]=="") f++; l=NR; while (l>=1 && line[l]=="") l--; for (i=f;i<=l;i++) print line[i] }'; }
# check_err NAME PHRASE INPUT — the render must FAIL (nonzero exit) with
# PHRASE on stderr. Matched wrap-insensitively: error messages are
# justified to 80 columns and may wrap anywhere.
check_err() {
local name="$1" phrase="$2" input="$3"
local err status
err=$("$KTEXT" --klammersets none -s "$input" -d 2>&1 >/dev/null); status=$?
err=$(printf '%s' "$err" | tr '\n' ' ' | tr -s ' ')
if [ $status -eq 0 ]; then
echo "${red}FAIL${reset} $name — expected an error, got exit 0"
FAIL=$((FAIL+1)); return
fi
case "$err" in
*"$phrase"*) echo "${green}PASS${reset} $name"; PASS=$((PASS+1));;
*) echo "${red}FAIL${reset} $name"
echo " wanted phrase: [$phrase]"
echo " stderr: $(echo "$err" | head -c 200)"; FAIL=$((FAIL+1));;
esac
}
# check NAME EXPECTED INPUT — render INPUT (no klammer set, default
# target); exit 0 and stdout == EXPECTED.
check() {
@@ -66,14 +90,42 @@ check " 1. 4-digit BMP code point" $'A ☺ B' 'A ^263A^ B'
check " 2. 5-digit code point, whole" $'A \xf0\x93\x80\x80 B' 'A ^13000^ B'
check " 3. 5-digit emoji" $'\xf0\x9f\x98\x80' '^1F600^'
check " 4. 6-digit top of the range" $'\xf4\x8f\xbf\xbd' '^10FFFD^'
check " 5. above U+10FFFF is invalid" 'Invalid Unicode: 16777215' '^FFFFFF^'
check " 6. surrogate is invalid" 'Invalid Unicode: 55296' '^D800^'
# An invalid code point is a located ERROR (2026-08-25, the
# no-warn-and-continue policy) -- it was in-band "Invalid Unicode" text in
# the document before, which is the wrong channel for a generator's typo.
check_err " 5. above U+10FFFF is an error" 'beyond U+10FFFF' '^FFFFFF^'
check_err " 6. surrogate is an error" 'a surrogate' '^D800^'
check_err " 6a. noncharacter is an error" 'a noncharacter' '^FDD0^'
check " 7. mnemonic undisturbed" 'ß' '^s^'
# The diacritic tables compose base + COMBINING mark (a U+0308), not the
# precomposed letter -- the expected string is built the same way.
check " 8. diacritic undisturbed" $'Ma\xcc\x88dchen' 'M^a"dchen'
# The tables emit the PRECOMPOSED (NFC) character where one exists (since
# 2026-08-25; they formerly emitted base + combining mark) -- the same
# bytes an editor's direct entry produces, so ^a" and a typed ä compare
# equal in search and copy-paste. The literal ä below is precomposed.
check " 8. diacritic is the precomposed character" 'Mädchen' 'M^a"dchen'
check " 9. code point beside quoted punctuation" $'☺ --' '^263A^ ^-^-'
echo
echo "-- sequences: ^UUUU UUUU ...^ (one caret pair, tokens 4-6 hex digits,"
echo " no space at the elbows, one line; process_unicode_sequences)"
check "10. CJK sequence" $'X \xe9\x81\x93\xe5\x8f\xaf\xe9\x81\x93\xe9\x9d\x9e\xe5\xb8\xb8\xe9\x81\x93 Y' 'X ^9053 53EF 9053 975E 5E38 9053^ Y'
check "11. mixed widths" $'\xe2\x98\xba\xf0\x93\x80\x80\xf0\x9f\x98\x80' '^263A 13000 1F600^'
check "12. space after opening caret: not a sequence" 'A ^ 263A 263A^ B' 'A ^ 263A 263A^ B'
check "13. space before closing caret: not a sequence" 'A ^263A 263A ^ B' 'A ^263A 263A ^ B'
check "14. newline aborts (one line only)" $'A ^263A\n263A^ B' $'A ^263A\n263A^ B'
check "15. short token aborts whole sequence" 'A ^263A 263^ B' 'A ^263A 263^ B'
check "16. @eval code keeps its carets" '11' '@eval len("^263A 263A^") @'
check "17. literal span: sequence stays verbatim" 'x ^263A 263A^ y' "^'x ^263A 263A^ y'^"
check "17a. single-character token stands for itself" $'\xe9\x81\x93x\xe5\x8f\xaf' '^9053 x 53EF^'
check "17b. non-alnum single token behaves as quoted" '☺-☺' '^263A - 263A^'
check "17c. structural code points are re-read-safe" '@@' '^0040 0040^'
check_err "17d. invalid code point in a CLOSED sequence" 'in a ^...^ sequence' '^263A FFFFFF^'
check "17e. unclosed hex-like prose is untouched" 'about ^263A FFFFFF prose' 'about ^263A FFFFFF prose'
echo
echo "-- direct entry and NFC convergence"
check "18. pinyin tone is precomposed" 'ā' '^a1'
check "19. typed UTF-8 passes through byte-identical" 'Mädchen naïveté ü — done' 'Mädchen naïveté ü — done'
echo
echo "================================================================="
echo "${bold}Results: $PASS passed, $FAIL failed${reset}"
+12
View File
@@ -83,6 +83,18 @@ shows " 1. -k lists klammers" '@table' "$KDESC" -k
shows " 2. -t lists targets" 'html' "$KDESC" -t
shows " 3. -c lists character codes" '.' "$KDESC" -c
shows " 4. -i reads the named input" '@x' "$KDESC" -i "$DIR/split.k" -k
# -u describes how to write a Unicode character. Asserted by OUTCOME, on
# the two things the section must carry however its prose is rewritten:
# the ^...^ notation it teaches, and a rendered character beside its code
# point -- the whole point of the section is that a reader sees the glyph
# they are about to write. It does NOT pin the prose, which is meant to
# grow (as of 2026-08-25 the section predates the sequence form and the
# typeface klammer, and does not yet mention either).
shows " 4a. -u describes the Unicode syntax" '\^0418\^' "$KDESC" -u
# The rendered glyph, as UTF-8 BYTE escapes: macOS ships bash 3.2, which
# expands $'\xHH' but not $'\U...' (that difference failed this suite's
# sibling on Olion, 2026-08-23).
shows " 4b. ... and shows the characters" $'\xf0\x93\x8a\xaa' "$KDESC" -u
echo
echo "-- the multi-letter topics --"
+3 -1
View File
@@ -281,7 +281,9 @@ shows "35. --ws applies whitespace operators" 'ab' --ws 'a#- b'
# DIAERESIS, not the precomposed U+00FC. The diacritic FOLLOWS the base
# letter -- that is the design of the ^ codes -- so the two spellings look
# identical in an editor and only one of them matches.
shows "36. --nonascii decodes to a combining sequence" 'ü' --nonascii '^u"'
# Precomposed since 2026-08-25: the tables emit NFC, the same bytes as
# direct entry (formerly base + combining mark).
shows "36. --nonascii decodes to the precomposed character" 'ü' --nonascii '^u"'
shows "37. --literal marks a literal span" 'x' --literal "^'x'^"
# Evaluating needs a target: Eval::eval re-reads its result under K_target, and
# kdiag has no target argument, so with the variable unset EVERY @eval died