#include "util.h" #include "error.h" #include "log.h" #include "kutil.h" #include "html_util.h" #include "heading.h" #include "document_class.h" #include "character.h" #include "reference.h" #include "show.h" #include "file.h" std::string unescape_newlines(const std::string& s) { return string_replace(s, " ___NL___ ", "\n"); } int ID = 0; std::string BASE {}; static const std::regex newline_rgx(R"(\n)"); static const std::regex element_open_rgx(R"(<([\w-]+)(\s+.*?)?>)"); static const std::regex id_attr_rgx("id=\"(.*?)\""); std::string insert_missing_ids(std::smatch match) { (void)K::log(3); std::string result = match[0]; std::string tag = match[1]; if (html::tag_is_block_element(tag)) { std::string attrs = match[2]; std::stringstream ss {}; if (attrs.find(" id=") == std::string::npos) { ss << "id=\"_e" << ID++<< "\""; } ss << attrs; if (!BASE.empty()) { ss << " data-basename=\"" << BASE << "\""; } result = "<" + tag + " " + trim(ss.str()) + ">"; } return result; } std::string insert_ids(const std::string& html_text) { (void)K::log(3); std::string result = html_text; result = freplace(result, element_open_rgx, insert_missing_ids); return result; } std::string Document_class::make_help_page() { (void)K::log(3); std::string kt_filename = klammertext_filename("doc/html_help.kt"); std::string basename = "help"; std::string help_page; //if (!in_modification_order(kt_filename, output_filename, no_cache)) { if (cache_requires_update(m_cache_dir, kt_filename, basename)) { Machine Mh = m_machine; Mh.read(fs::path(klammertext_filename("doc/html_help.kt"))); help_page = Mh.apply("html"); help_page += "\n
\n"; help_page = html::make_paragraphs(help_page); write_to_cache(m_cache_dir, basename, help_page); } else { help_page = read_from_cache(m_cache_dir, basename); } return help_page; } std::string Document_class::color_definitions() { std::stringstream ss {}; ss << ":root {\n" << " --frame_background_color: " << frame_background_color << ";\n" << " --frame_text_color: " << frame_text_color << ";\n" << " --nav_background_color: " << nav_background_color << ";\n" << " --nav_text_color: " << nav_text_color << ";\n}\n"; return ss.str(); } strings_t Document_class::resolved_font_names() { strings_t result {}; auto add = [&](const Resolved_font& rf) { if (!rf.family_name.empty()) result.push_back(rf.dir_name); }; add(m_resolved_serif); add(m_resolved_sans); add(m_resolved_mono); return result; } std::string Document_class::font_definitions() { std::stringstream ss {}; if (!m_serif_font.empty() || !m_sans_font.empty() || !m_mono_font.empty()) { // Family names are quoted: an unquoted name with a digit-initial // word ("Source Sans 3") is invalid CSS, and a font-family using // var() with such a value computes to inherit, silently losing // the font. ss << ":root {\n"; if (!m_serif_font.empty()) ss << " --serif: \"" << m_serif_font << "\", serif;\n"; if (!m_sans_font.empty()) ss << " --sans-serif: \"" << m_sans_font << "\", sans-serif;\n"; if (!m_mono_font.empty()) ss << " --monospace: \"" << m_mono_font << "\", monospace;\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]; std::snprintf(buf, sizeof(buf), "%.4f", m_font_scale); ss << "body { font-size: " << buf << "rem; }\n"; } return ss.str(); } void Document_class::write_basenames_js_file(const std::string& filename, const std::vector