Verbatim-safe typography, ^-punctuation quoting, full-range ^UUUU^, polyglot html

Typographic transforms (---, quote pairs, ~) no longer touch verbatim
text: @c/@code/@source_listing content and ^'...'^ spans show exactly the
characters written. "^" before any punctuation character quotes it in
every target (the apostrophe excepted: ^' opens a literal span), with the
new :resolve option on @@@target declaring per-target renderings. The
^UUUU^ code-point form accepts 4-6 hex digits, the full Unicode range.
The html output and transform spellings are polyglot (XML-valid), in
preparation for an EPUB target. New suites: transform_test, character_test
(engine), typography_test (SKS).

(from dev 07ce5ea86a0a)
This commit is contained in:
2026-08-23 20:48:26 +02:00
parent d982c0d6cc
commit 37b6ba1c4f
77 changed files with 1665 additions and 1608 deletions

View File

@@ -36,6 +36,9 @@ std::string utf8char(int cp)
c[1] = ((cp>>12)&63)+128;
c[2] = ((cp>>6)&63)+128;
c[3]=(cp&63)+128;
} else {
// Reachable since ^UUUU^ accepts 6 hex digits: FFFFFF > 10FFFF.
return "Invalid Unicode: " + std::to_string(cp);
}
return std::string(c);
}
@@ -65,7 +68,13 @@ std::string unicode_hex_to_char(std::string s, int width=4) //, std::string mark
std::string process_diacritics(std::string s)
{
(void)K::log(4);
std::regex diacritic_re("\\^([^\\s`'~@|^:*#])([" + diacritic_symbols + "])");
// The base may not be whitespace, a digit, or ASCII punctuation (the
// four ranges !-/ :-@ [-` {-~): a diacritic sits on a letter. Without
// the exclusion, ^ before a quoted punctuation character followed by a
// mark character composed nonsense -- ^-- became a hyphen with a macron
// instead of a literal hyphen before a hyphen. A multi-byte (non-ASCII)
// base is unaffected: its bytes are outside every excluded range.
std::regex diacritic_re("\\^([^\\s0-9!-/:-@\\[-`{-~])([" + diacritic_symbols + "])");
std::string result {s};
std::sregex_iterator end {};
@@ -126,9 +135,14 @@ std::string process_unicode_codepoint(std::string s)
//return std::regex_replace(s, unicode_re, hidehat + "$1" + hidehat);
std::string result {s};
std::sregex_iterator end {};
for (std::sregex_iterator p { s.begin(), s.end(), unicode_re }; p!= end; ++p) {
for (std::sregex_iterator p { s.begin(), s.end(), unicode_re }; p!= end; ++p) {
std::regex hit_re { regex_escape((*p)[0].str()) };
result = std::regex_replace(result, hit_re, unicode_hex_to_char((*p)[1].str()));
// Convert the captured hex DIRECTLY: unicode_hex_to_char() re-scans
// its argument at a fixed width, and its 4-digit default truncated a
// 5-digit code point to its first four digits (^13000^ rendered as
// U+1300 followed by a literal "0").
result = std::regex_replace(result, hit_re,
utf8char(std::stoi((*p)[1].str(), nullptr, 16)));
}
return result;