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

@@ -113,38 +113,14 @@ std::string Document_class::font_definitions()
ss << " --monospace: \"" << m_mono_font << "\", monospace;\n";
ss << "}\n";
}
// Emit scale factors so sans and mono fonts match the serif font.
// Three scaling methods (uncomment the desired one):
// x-height: serif_xh / other_xh (matches lowercase, like fontspec MatchLowercase)
// cap-height: serif_ch / other_ch (matches capitals)
// average: mean(serif_xh,serif_ch) / mean(other_xh,other_ch) (compromise)
float serif_xh = m_resolved_serif.xheight_ratio;
float serif_ch = m_resolved_serif.capheight_ratio;
float serif_avg = (serif_xh + serif_ch) / 2.0f;
if (serif_avg > 0.0f) {
auto scale = [&](const Resolved_font& other) -> std::string {
float other_avg = (other.xheight_ratio + other.capheight_ratio) / 2.0f;
if (other_avg > 0.0f && other_avg != serif_avg) {
char buf[16];
// float ratio = serif_xh / other.xheight_ratio; // x-height
// float ratio = serif_ch / other.capheight_ratio; // cap-height
float ratio = serif_avg / other_avg; // average
std::snprintf(buf, sizeof(buf), "%.4f", ratio);
return buf;
}
return "";
};
std::string sans_scale = scale(m_resolved_sans);
std::string mono_scale = scale(m_resolved_mono);
if (!sans_scale.empty() || !mono_scale.empty()) {
ss << ":root {\n";
if (!sans_scale.empty())
ss << " --sans-serif-scale: " << sans_scale << ";\n";
if (!mono_scale.empty())
ss << " --monospace-scale: " << mono_scale << ";\n";
ss << "}\n";
}
}
// Font-size normalization across families is no longer emitted from
// here (2026-08-22): font.css declares "font-size-adjust: ex-height 0.5"
// on body, and the browser renders every font at the same x-height --
// the same computation the former --sans-serif-scale/--monospace-scale
// factors did from build-time OS/2 metrics, but applied to EVERY family
// switch instead of the four CSS sites that remembered to multiply.
// The metric extraction in the font store remains (the tex path and
// kdesc --font still use it).
// Global font scale: applied to body font-size
if (m_font_scale != 1.0f) {
char buf[16];
@@ -368,7 +344,15 @@ Document_class::insert_section_numbers(const std::string& marker, bool add_to_to
{
std::vector<int> levels(9, 0);
std::smatch match {};
std::string pattern = R"(<([\w-]+)\s*(.*?)>(.*?)MARKER\s*</span>\s*(.*?)<.*)";
// The heading text (group 4) runs to the heading element's OWN closing
// tag (the \1 backreference), not to the first "<": a nested element in
// a section title -- @c's <span class="code">, an @i's <em> -- would
// otherwise end the capture early and silently truncate the title in
// BOTH tables of contents (article's single_page_toc and book's
// navigation TOC read the same capture). Found 2026-08-22 via a @c in
// an @s2 title; heading.cpp's section_rgx already used the
// close-on-own-tag idiom.
std::string pattern = R"(<([\w-]+)\s*(.*?)>(.*?)MARKER\s*</span>\s*(.*?)</\1>.*)";
pattern = string_replace(pattern, "MARKER", marker);
std::regex heading_rgx(pattern);
std::vector<std::pair<std::string, std::string>> modified_components {};