Files
klammertext/sks/document/document_html.cpp
T
ack fe475e774a Klammertext runs natively on Windows; a Windows source-install guide; the bundled sh for @eval :shell
Assembled from klammertext-dev f344a37d0f6e. The commands, the library,
the Standard Klammer Set, the reflection klammerset and the Python
binding build and run natively on Windows (MSYS2 UCRT64 gcc, python.org
Python), and both test tiers pass there. doc/install/windows_source_install.md
is the fifth install guide: a source install parallel to the Linux and
macOS ones, verified by following it on a pristine Windows 11.

Behaviour on every system: paths Klammertext prints use forward slashes;
output is LF; the command line is UTF-8 on Windows; @eval :shell is a
POSIX shell command everywhere -- on Windows it runs the sh Klammertext
ships, BusyBox for Windows (win/busybox/, GPLv2, with its licence and the
source pointer). KLAMMERTEXT_FONTS and KLAMMERTEXT_KLAMMERSETS are lists in
the system's PATH convention (';' on Windows). On Linux and macOS nothing
observable changes.
2026-09-25 00:32:55 +02:00

799 lines
30 KiB
C++

#include <iomanip>
#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").string();
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<div class=\"endspace\"></div>\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);
// Typefaces a @typeface klammer asked for: each needs its own <link>
// to the localized css, which is what carries the @font-face.
for (const auto& rf : m_resolved_extra)
add(rf);
return result;
}
std::string Document_class::font_definitions()
{
std::stringstream ss {};
if (!m_serif_typeface.empty() || !m_sans_typeface.empty() || !m_mono_typeface.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";
// The family the store RESOLVED, which is what the localized css
// declares -- the name as written may be the store's directory
// name ("crimson-pro"), which matches no @font-face.
if (!m_serif_typeface.empty())
ss << " --serif: \"" << m_resolved_serif.family_name << "\", serif;\n";
if (!m_sans_typeface.empty())
ss << " --sans-serif: \"" << m_resolved_sans.family_name << "\", sans-serif;\n";
if (!m_mono_typeface.empty())
ss << " --monospace: \"" << m_resolved_mono.family_name << "\", monospace;\n";
// The factor a @typeface span's font-size is multiplied by (2026-09-18).
// font.css renders the body at "font-size-adjust: ex-height 0.5", so
// the text the reader sees is 0.5 / x-height times its nominal size
// (EB Garamond, x-height 0.400: 1.25). A @typeface span switches
// the adjust OFF (a script with no x-height cannot be matched), so
// its ":scale N" was N times the NOMINAL size -- about a fifth
// smaller against the visible text than the same N in pdf, where
// fontspec's Scale= is against text rendered at nominal size.
// Folding the body's enlargement in here makes :scale mean the
// same thing in both targets. Emitted as a custom property the
// span multiplies in with calc(), so a fragment rendered without
// @document falls back to 1. SYNC: the anchor 0.5 is font.css's.
const float xheight_anchor = 0.5f;
if (m_resolved_serif.xheight_ratio > 0.0f) {
std::stringstream factor {};
factor << std::setprecision(4) << xheight_anchor / m_resolved_serif.xheight_ratio;
ss << " --typeface_scale: " << factor.str() << ";\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 --typeface still use it).
// Global font scale: applied to body font-size
if (m_text_scale != 1.0f) {
char buf[16];
std::snprintf(buf, sizeof(buf), "%.4f", m_text_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<std::string>& basenames)
{
(void)K::log(3);
std::string tab(" ");
std::stringstream ss {};
ss << "function pages_basenames()\n{\n return [\n";
int i = 0;
int last = basenames.size() - 1;
for (std::string base : basenames) {
ss << tab << "\"" << base << "\"";
if (i < last) {
ss << ",";
}
ss << "\n";
i++;
}
ss << " ];\n}\n";
write(filename, ss.str());
}
std::string Document_class::create_html_output_directories()
{
(void)K::log(3);
std::string output_directory =
absolute_pathname(get("K_output_dir")) + "/" + get("K_output_basename");
if (!m_toc_only) {
if (!file_exists(output_directory)) {
fs::create_directory(output_directory);
}
install_resolved_font(m_resolved_serif, output_directory);
install_resolved_font(m_resolved_sans, output_directory);
install_resolved_font(m_resolved_mono, output_directory);
}
m_cache_dir = cache_directory("_html_pages");
if (m_no_cache && fs::exists(m_cache_dir)) {
for (auto& entry : std::filesystem::directory_iterator(m_cache_dir)) {
// What ":cache false" actually removed: a derived consequence of
// an option, so "-v 1".
(void)K::log(1, "Removing cache directory:", entry.path().string());
std::filesystem::remove_all(entry);
}
}
if (!m_toc_only) {
if (!file_exists(output_directory, false, true)) {
fs::create_directory(output_directory);
}
}
return output_directory;
}
strings_t get_css_files(const strings_t& custom_css_files)
{
(void)K::log(3);
strings_t result = sks_files_of_type("css");
result.insert(result.end(), custom_css_files.begin(), custom_css_files.end());
return result;
}
std::vector<std::string> js_basenames(bool nav)
{
if (nav) {
return { "kutil", "document", "image", "resize_window", "resize_text", "resize_toc",
"toc", "level", "next_last", "help", "show_links", "search", "state", "code", "link" };
} else {
return {"kutil", "document", "image"};
}
}
strings_t Document_class::get_js_files(
bool nav, bool include_sks_js,
const std::string& pages_basenames_filename)
{
(void)K::log(3);
strings_t result {};
if (include_sks_js) {
for (auto b : js_basenames(nav)) {
std::vector<fs::path> js_files =
find_file_recursive(klammertext_dir() + "/sks", b + ".js");
if (js_files.size() == 0) {
throw File_error("File " + q_(b) + " not found.", Locator());
}
result.push_back(js_files[0].string());
}
if (!pages_basenames_filename.empty()) {
result.push_back(pages_basenames_filename);
}
}
return result;
}
strings_t link_filenames(strings_t& full_filenames, const std::string& page_dir)
{
strings_t result {};
fs::path page_dir_path(page_dir);
// int i = 0;
std::transform(full_filenames.begin(), full_filenames.end(), std::back_inserter(result),
[&](const std::string& f) {
fs::path full(f);
//msg() << i << full << " " << full.filename() << "\n";
fs::path path(page_dir_path / full.filename());
//std::cout << i++ << path << "\n";
return path.string(); });
return result;
}
std::pair<std::vector<std::string>, std::vector<std::string>>
Document_class::html_auxiliary_files(const std::string& output_directory, const std::string& pages_basenames_filename,
const std::vector<std::string>& custom_css_files)
{
(void)K::log(3);
strings_t all_css_files = custom_css_files;
strings_t css_link_filenames {};
if (write_files && m_include_sks_css) {
all_css_files = get_css_files(m_css_filenames);
//output_dir, color_definitions(), css_files, write_files);
}
if (write_files) {
copy_preserving_basename(
all_css_files, output_directory, "css");
css_link_filenames = link_filenames(all_css_files, "css");
}
strings_t all_js_files {};
strings_t js_link_filenames {};
if (write_files) {
bool nav = m_structure == docstruct_t::book;
all_js_files = get_js_files(
nav, m_include_sks_js,
pages_basenames_filename); // , output_dir, write_files);
copy_preserving_basename(
all_js_files, output_directory, "js");
js_link_filenames = link_filenames(all_js_files, "js");
}
// msg() << "all_css_files:\n " << join(all_css_files, "\n ") << "\n\n";
// msg() << "all_js_files:\n " << join(all_js_files, "\n ") << "\n\n";
return {css_link_filenames, js_link_filenames};
}
void Document_class::process_input_files()
{
(void)K::log(3);
m_file_components.clear();
size_t basename_width = 0;
for (auto input_filename : m_files) {
fs::path inpath = input_filename;
if (!file_exists(inpath.string())) {
inpath = parse_input_filename(input_filename, m_input_dir);
}
std::string basename = file_basename(input_filename);
basename_width = std::max(basename_width, basename.size());
std::string html_text;
//if (true || !in_modification_order(inpath, cached_filename, m_no_cache)) {
if (m_no_cache || cache_requires_update(m_cache_dir, inpath.string(), basename)) {
Machine M = m_machine;
M.read(inpath);
html_text = trim(M.apply("html"));
html_text = trim(html::make_paragraphs(html_text)) + "\n";
html_text = unescape_newlines(html_text);
BASE = basename;
html_text = insert_ids(html_text);
m_file_components.push_back({basename, html_text});
write_to_cache(m_cache_dir, basename, html_text);
} else {
html_text = read_from_cache(m_cache_dir, basename);
m_file_components.push_back({basename, html_text});
}
}
// for (auto [src, result] : m_file_components) {
// msg() << std::left << std::setw(basename_width) << src << " " << abbrev(result, 96) << "\n";
// }
}
std::string increment_level(int level, std::vector<int> &levels)
{
levels[level]++;
int zero_offset = level == 0 ? 2 : 1;
for (unsigned int i = level + zero_offset; i < levels.size(); i++) {
levels[i] = 0;
}
std::stringstream result {};
if (level == 0) {
result << levels[0];
} else { // Parts not included in numbering
for (auto i = 1; i <= level; i++) {
result << levels[i];
if (i < level) {
result << ".";
}
}
}
// msg() << level << sp_arrow << result.str() << sp_arrow << "\n";
return result.str();
}
std::string extract_id(const std::string& attr)
{
std::smatch match {};
if (!std::regex_match(attr, match, id_attr_rgx)) {
throw Internal_error("Pattern for id attribute incorrect: " + q_(attr));
}
return match[1];
}
std::vector<Heading>
Document_class::insert_section_numbers(const std::string& marker, bool add_to_toc)
{
std::vector<int> levels(9, 0);
std::smatch match {};
// 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 {};
std::vector<Heading> headings;
bool include_toc = m_structure != docstruct_t::plain;
for (auto [src, result] : m_file_components) {
std::stringstream modified {};
for (std::string line : regex_split(result, newline_rgx, false)) {
if (std::regex_match(line, match, heading_rgx)) {
// xheading
std::string tag = match[1];
std::string id = extract_id(match[2]);
std::string title = match[3];
std::string heading = match[4];
const int level = html_tags[match[1]];
// msg() << line << sp_arrow << broken_bar << tag << broken_bar
// << level << broken_bar << "ATTR" << broken_bar << title << broken_bar
// << heading << broken_bar << "\n";
std::string number = increment_level(level, levels);
line = string_replace(line, marker, number);
if (include_toc) {
line = "<a href=\"#_c" + id + "\" draggable=\"false\">" + line + "</a>";
}
//msg() << attr << " " << number << " " << heading << "\n";
if (add_to_toc) {
m_toc_components.push_back({tag, id, number, heading});
}
Heading hd(level, src, number, id, number, heading);
headings.push_back(hd);
// msg() << line << "\n\n";
}
modified << line << "\n";
}
modified_components.push_back({src, modified.str()});
}
m_file_components = modified_components;
return headings;
}
int get_caption_number(std::map<std::string, int>& numbers, const std::string& label)
{
int result;
if (numbers.count(label) == 0) {
result = 1;
numbers[label] = 2;
} else {
result = numbers[label];
numbers[label]++;
}
return result;
}
void Document_class::insert_html_caption_numbers()
{
// msg() << "insert_html_caption_numbers\n";
std::regex chapter_rgx(R"(.*sectionnumber">(\d+)</span>.*)");
std::regex caption_rgx(R"(__CAPTION__(.*?)__CAPTION__(.*?)__CAPTION__)");
std::string current_chapter {};
std::map<std::string, int> current_caption_number {};
std::vector<std::pair<std::string, std::string>> modified_components {};
for (auto [src, result] : m_file_components) {
std::stringstream modified {};
//std::vector<std::string> references {};
for (std::string line : regex_split(result, newline_rgx, false)) {
std::smatch chapter_match {};
if (std::regex_match(line, chapter_match, chapter_rgx)) {
current_chapter = chapter_match[1];
// msg() << "Chapter " << current_chapter << " " << line << "\n";
current_caption_number.clear();
}
// msg() << "Line: " << line << "\n";
std::smatch caption_match {};
if (std::regex_search(line, caption_match, caption_rgx)) {
std::string label = caption_match[1];
std::string ctext = caption_match[2];
int caption_number = get_caption_number(current_caption_number, label);
// msg() << " caption: " << current_chapter << "." << caption_number << "\n";
// msg() << "Label: " << label << " Text: " << ctext << "\n";
std::stringstream ss {};
ss << label << " ";
if (!current_chapter.empty()) {
ss << current_chapter << ".";
}
ss << caption_number << ctext;
line = std::regex_replace(line, caption_rgx, trim(ss.str()));
// msg() << "line: " << line << "\n";
// references.push_back(line);
}
// if (std::regex_search(line, reference_rgx)) {
// references.push_back(line);
// }
modified << line << "\n";
}
modified_components.push_back({src, modified.str()});
//m_reference_sequences.push_back(references);
}
m_file_components = modified_components;
}
std::vector<std::tuple<std::string, std::string, std::string>>
Document_class::reference_list(const std::string& target_marker)
{
std::regex reference_rgx("__REF__");
std::regex target_rgx("<div id=\"(.*?)\" data-label=\"(.*?)\" class=\"caption_");
std::vector<std::tuple<std::string, std::string, std::string>> references {};
for (auto [src, result] : m_file_components) {
for (std::string line : regex_split(result, newline_rgx, false)) {
if (std::regex_search(line, reference_rgx)) {
references.push_back({target_marker, src, line});
} else {
std::smatch match {};
if (std::regex_search(line, match, target_rgx)) {
references.push_back({"", match[2], match[1]});
}
}
}
}
return references;
}
std::map<std::string, std::string> Document_class::id_to_caption_number()
{
std::regex target_rgx("<div id=\"(.*?)\" data-label=\"(.*?)\" class=\"caption_");
std::regex caption_rgx(R"(<div id=.*?>(\w+\s+[\w.]+))");
std::map<std::string, std::string> result {};
for (auto [src, ftext] : m_file_components) {
auto lines = regex_split(ftext, newline_rgx, false);
for (unsigned int i = 0; i < lines.size(); i++) {
std::smatch match {};
if (std::regex_search(lines[i], match, target_rgx)) {
std::string id = match[1];
unsigned int j = i + 1;
std::smatch caption_match {};
while (j < lines.size()) {
if (std::regex_search(lines[j], caption_match, caption_rgx)) {
std::string caption = caption_match[1];
// msg() << id << sp_arrow << caption << "\n";
result[id] = caption;
break;
}
j++;
}
}
}
}
return result;
}
std::pair<int, int> offset_spec_to_count(const std::string& spec)
{
std::regex rgx(R"((\w+)\s*(\d*))");
std::smatch match {};
int offset = 1;
int dir = 1;
if (std::regex_match(spec, match, rgx)) {
std::string desc = match[1];
std::string value = match[2];
if (!value.empty()) {
offset = std::stoi(value);
}
if (desc == "before") {
dir = -1;
}
} else {
throw Internal_error("Reference offset spec incorrect: " + spec);
}
return {offset, dir};
}
std::map<std::string, std::vector<std::pair<std::string, std::string>>>
Document_class::html_references()
{
std::string target_marker = "@";
auto references = reference_list(target_marker);
// msg() << "\n";
for (unsigned int i = 0; i < references.size(); i++) {
auto [mark, name, ref] = references[i];
// msg() << " " << i << ": " << mark << " " << name << " " << ref << "\n";
}
// msg() << "\n";
auto id_captions = id_to_caption_number();
std::regex ref_rgx(R"(__REF__(.*?)__(.*?)__)");
std::string offset_spec;
std::string type;
std::map<std::string, std::vector<std::pair<std::string, std::string>>> result {};
for (unsigned int i = 0; i < references.size(); i++) {
auto [mark, name, ref] = references[i];
// msg() << " " << i << ": " << mark << " " << name << " " << ref << "\n";
if (mark == target_marker) {
std::smatch match {};
if (std::regex_search(ref, match, ref_rgx)) {
offset_spec = match[1];
type = match[2];
} else {
throw Internal_error("Reference form is incorrect: " + ref);
}
auto [offset, dir] = offset_spec_to_count(offset_spec);
int j = i + dir;
while (offset > 0 && j >= 0 && j < (int)references.size()) {
// msg() << " check: " << std::get<2>(references[j]) << "\n";
if (std::get<1>(references[j]) == type) {
offset--;
}
if (offset == 0) {
break;
}
j += dir;
}
if (j < 0 || j >= (int)references.size()) {
// msg() << "Not found: " << std::get<2>(references[i]) << "\n";
} else {
std::string id = std::get<2>(references[j]);
std::string line = std::get<2>(references[i]);
// msg() << "Ref: " << line << boldblack << " found: " << black << id << "\n";
std::string link = "<a href=\"#" + id + "\">" + id_captions[id] + "</a>";
// msg() << link << "\n";
std::string modified_line = std::regex_replace(line, ref_rgx, link);
// msg() << "modified_line: " << name << sp_arrow << modified_line << "\n";
result[name].push_back({line, modified_line});
}
}
}
return result;
}
void Document_class::resolve_html_references()
{
auto references = html_references();
std::vector<std::pair<std::string, std::string>> modified_components {};
for (auto [src, ftext] : m_file_components) {
// msg() << "resolve " << src << ":\n";
std::string modified_text = ftext;
for (auto [old_line, new_line] : references[src]) {
// msg() << old_line << sp_arrow << new_line << "\n";
modified_text = string_replace(modified_text, old_line, new_line);
}
modified_components.push_back({src, modified_text});
}
m_file_components = modified_components;
}
std::string Document_class::single_page_toc()
{
// <h1 id="_e9"><span class="sectionnumber">2.1</span> Section two.one</h1>
std::regex id_rgx("id=\"(.*?)\"");
std::stringstream ss {};
ss << "<div class=\"toctitle\">Contents</div>\n";
int minimum_depth = 7;
// Adjust left minimum margin based on whether parts or chapters are the top level:
for (auto [tag, id, number, heading] : m_toc_components) {
minimum_depth = std::min(minimum_depth, html_tags[tag]);
}
for (auto [tag, id, number, heading] : m_toc_components) {
int depth = html_tags[tag];
// msg() << "TOC: " << depth << " " << id << " " << number << " " << heading << "\n";
ss << "<div id=\"_c" << id << "\" class=\"tab" << depth - minimum_depth << " toc\"><a href=\"#" << id << "\">"
<< "<span class=\"tocnumber\">" << number << "</span>" << heading << "</a></div>\n";
}
// msg() << ss.str();
return ss.str();
}
elements_t Document_class::page(const std::string& body_text, const std::string& output_dir, int max_level)
{
(void)K::log(3);
std::string toc {}; // Calculate
bool text_only = m_structure == docstruct_t::plain;
// msg() << "output_dir: " << output_dir << "\n";
auto [css_filenames, js_filenames] =
html_auxiliary_files(output_dir, "", m_css_filenames);
std::string toc_text {};
elements_t body {};
html::add_title(body, m_title, m_logo);
if (m_structure == docstruct_t::book) {
html::add_nav(body, max_level);
}
html::add_text(body, body_text, toc_text, text_only);
// html::add_bottom_spacer(body);
html::add_status_bar(body, m_date, m_version, m_copyright);
if (!text_only) {
html::add_page_cache(body);
}
html::add_js_links(body, js_filenames, output_dir) ;
std::string css_text = color_definitions() + font_definitions() + "\n" + m_css_text;
std::string page_title = m_page_title.empty() ? m_title : m_page_title;
elements_t page = html::make_page(
body, page_title,
css_text, css_filenames, resolved_font_names());
return page;
}
std::string Document_class::make_single_html_page(const std::string& output_directory)
{
(void)K::log(3);
std::stringstream ss {};;
if (m_structure != docstruct_t::plain) {
ss << single_page_toc();
}
for (auto [basename, page_text] : m_file_components) {
ss << "<!-- " << basename << ".kt -->\n\n" << page_text << "\n";
}
std::string single_page = to_string(page(ss.str(), output_directory, 10));
std::string output_file = output_directory + "/index.html";
// msg() << "single_page to " << output_file << "\n";
string_to_file(output_file, single_page);
return "";
}
std::string Document_class::make_html_navigation_structure(
const std::string& output_directory, std::vector<Heading> headings)
{
// Which structure path was taken is a DERIVED fact -- ":structure book"
// chose it -- so it belongs at "-v 1". It was a msg(), which printed it
// to stdout on every book render, ahead of the document.
(void)K::log(1, "Navigation format");
std::vector<std::string> pages_basenames {};
for (auto [basename, page_text] : m_file_components) {
pages_basenames.push_back(basename);
// A trace of the work, not a decision: verbosity 3.
(void)K::log(3, basename + ": " + abbrev(page_text, 96));
}
std::string pages_basenames_filename = m_cache_dir + "/_pages_basenames.js";
write_basenames_js_file(pages_basenames_filename, pages_basenames);
std::string help_content = make_help_page();
// Build embedded pagecache to avoid Same Origin Policy errors with file:// protocol
std::stringstream pagecache;
pagecache << "<div id=\"pagecache\" style=\"display:none\">\n";
for (auto [basename, page_text] : m_file_components) {
pagecache << "<div id=\"_page_" << basename
<< "\" class=\"content_page\">"
<< page_text << "</div>\n";
}
pagecache << "<div id=\"help_page\">" << help_content << "</div>\n";
pagecache << "<div id=\"search_page\"></div>\n";
pagecache << "</div>\n";
std::pair<int, std::string> toc_spec = make_navigation_table_of_contents(headings);
int max_level = std::get<0>(toc_spec);
std::string toc_body = std::get<1>(toc_spec);
std::string nav_body = "Navigation";
auto [all_css_files, all_js_files] =
html_auxiliary_files(output_directory, pages_basenames_filename, m_css_filenames);
std::string css_text = color_definitions() + font_definitions() + "\n" + m_css_text;
std::stringstream t {};
t << html::page(
m_title, m_page_title, output_directory, nav_body, max_level, toc_body,
m_file_components[0].second,
m_date, m_version, m_copyright,
css_text, all_css_files, all_js_files,
resolved_font_names(),
m_logo);
std::string result = t.str();
result = string_replace(result,
"<div id=\"pagecache\"></div>",
pagecache.str());
std::string output_basename = m_machine.m_state.value("K_output_basename");
std::string output_filename = output_directory + "/index.html";
write(output_filename, result);
return "";
}
std::string Document_class::html()
{
(void)K::log(3);
// msg() << "HTML document\n";
// m_cache_dir = cache_directory(kt_root_filename, "_html_pages");
std::string output_directory = create_html_output_directories();
save_string_input_as_file();
process_input_files();
// @typeface markers are collected AFTER processing (a klammer cannot
// declare a typeface itself) but BEFORE the page is assembled, so the
// localized files and the <link> to each typeface's css are in place
// when make_*_html_page() runs. The markers survive into the page
// CACHE deliberately: a cached file must still declare its typefaces
// on a later run, so the stripping happens here, on the assembled
// components, not before write_to_cache().
for (auto& [basename, text] : m_file_components) {
collect_typefaces(text);
}
for (const auto& rf : m_resolved_extra) {
install_resolved_font(rf, output_directory);
}
for (auto& [basename, text] : m_file_components) {
text = resolve_typeface_families(text);
}
auto headings = insert_section_numbers();
insert_section_numbers("___NUM2___", false);
insert_html_caption_numbers();
resolve_html_references();
if (m_structure != docstruct_t::book) {
make_single_html_page(output_directory);
} else {
make_html_navigation_structure(output_directory, headings);
}
return "";
}