A section link names the TEXT of a heading: :section alone means the current file, a value names the file of the same @document that has it. Resolved by @document at assembly in html and tex; errors for a missing file, a missing title, a doubled title and a link outside a @document. The Markdown converter reads a Markdown anchor as a section link to the heading its slug names. Also from dev: the converter's four silent defects fixed and --chapter (9ae2c4cd), and the engine's alone-value error text. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
121 lines
5.6 KiB
C++
121 lines
5.6 KiB
C++
#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<std::pair<std::string, std::string>>& pieces)
|
|
{
|
|
static const std::regex announce_rgx(
|
|
R"(%__sectionlink__(.*?)__KTSL__(.*?)__KTSL__(.*)__\n?)");
|
|
static const std::regex link_rgx(R"(__seclink__(.*?)__KTSL__(.*?)__)");
|
|
std::vector<Section_target> 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<std::pair<std::string, std::string>> 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);
|
|
}
|