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)
757 lines
28 KiB
C++
757 lines
28 KiB
C++
#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<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);
|
|
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<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)) {
|
|
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, 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();
|
|
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 "";
|
|
}
|