#include "section_link.h" #include "util.h" #include "kutil.h" #include "log.h" #include "document_class.h" #include "reference.h" #include "file.h" #include "show.h" std::string Document_class::create_tex_output_directories() { (void)K::log(3); std::string output_directory = absolute_pathname(get("K_output_dir")); // + "/" + get("K_output_basename"); m_cache_dir = cache_directory("_latex_files"); if (!m_toc_only) { if (!file_exists(output_directory, false, true)) { fs::create_directory(output_directory); } } return output_directory; } // @link :section (section_link.h). Section.tex() announces every heading // in a comment line; the links are resolved against them, every link on a // line. The resolver this replaces (2026-10-12) read ONE link per line -- // std::regex_match once per line -- so a second @link ... :section @ on a // line reached the pdf as a dead \hyperlink{__seclink__...}; it ignored // :basename, and it wrote \hyperlink{} for an unknown title. std::string Document_class::resolve_tex_section_links( const std::vector>& pieces) { static const std::regex announce_rgx( R"(%__sectionlink__(.*?)__KTSL__(.*?)__KTSL__(.*)__\n?)"); static const std::regex link_rgx(R"(__seclink__(.*?)__KTSL__(.*?)__)"); std::vector table {}; strings_t files {}; for (const auto& [basename, text] : pieces) { if (!basename.empty()) files.push_back(basename); for (const std::string& line : find_all(text, announce_rgx, 0)) { std::smatch m {}; std::regex_match(line, m, announce_rgx); table.push_back({normalize_section_title(m[3]), m[2], m[1]}); } } std::string result {}; for (const auto& [basename, text] : pieces) { std::string stripped = std::regex_replace(text, announce_rgx, ""); auto begin = stripped.cbegin(); for (std::sregex_iterator it(stripped.cbegin(), stripped.cend(), link_rgx), end; it != end; ++it) { const std::smatch& m = *it; result.append(begin, m[0].first); result += resolve_section_target(table, m[1], m[2], basename, files).id; begin = m[0].second; } result.append(begin, stripped.cend()); } return result; } std::string Document_class::tex() { (void)K::log(3); create_tex_output_directories(); // :text content is already processed (klammers applied by the outer Machine). // Use it directly; don't re-process through a sub-Machine. // The pieces stay apart until the :section links are resolved, so an // error can name the chapter that holds the link; "" is the :text. std::vector> pieces {}; if (!m_text.empty()) { pieces.push_back({"", add_latex_caption_numbers(m_text)}); } if (m_files.size() > 0) { for (auto input_filename : m_files) { std::string basename = file_basename(input_filename); std::string cached_filename = m_cache_dir + "/" + basename; fs::path inpath = parse_input_filename(input_filename, m_input_dir); // if (!in_modification_order(input_filename, cached_filename, m_no_cache)) { if (m_no_cache || cache_requires_update(m_cache_dir, inpath.string(), basename)) { Machine M = m_machine; M.m_state.set("document_landscape", landscape ? "true" : "false"); // , "Landscape mode"); // The chapter's basename: Section.tex() builds stable // \hypertarget names from it (a counter alone collided // when the cache served some chapters and not others), and // phases.check_section_links reads its presence as "inside // a @document". Same in the html path. M.m_state.set("K_document_file", basename); M.read(inpath); std::string processed = trim(M.apply("tex", false)); processed = add_latex_caption_numbers(processed); write_to_cache(m_cache_dir, basename, processed); pieces.push_back({basename, processed}); } else { pieces.push_back({basename, read_from_cache(m_cache_dir, basename)}); } } } std::string latex_text = resolve_tex_section_links(pieces); auto split_lines = regex_split(latex_text, std::regex(R"(\n)"), false); latex_text = resolve_caption_references("latex", split_lines, latex_line_states(split_lines)); latex_text = string_replace(latex_text, "___AT___", "^@"); // @typeface: collect what the document asked for, declare each as a // \newfontfamily switch command in the preamble, and replace every // marker with its command. The declarations are appended to the // prolog, which latex::page emits AFTER \setmainfont -- so fontspec is // loaded and \newfontfamily is valid there. collect_typefaces(latex_text); latex_text = resolve_typeface_markers(latex_text); prolog += typeface_preamble(); std::string structure_name = m_structure == docstruct_t::book ? "book" : m_structure == docstruct_t::article ? "article" : "plain"; return latex::page(structure_name, m_title, m_subtitle, m_author, m_date, m_version, m_copyright, m_bottom, prolog, paper_size, m_two_column, leading, point_size, ragged_right, cover, landscape, latex_text, m_resolved_serif, m_resolved_sans, m_resolved_mono, m_text_scale); }