#include "file.h" #include "kutil.h" #include "util.h" #include "log.h" #include "show.h" #include "font_store.h" namespace latex { std::string tex_style() { (void)K::log(3); std::stringstream ss {}; for (auto filename : sks_files_of_type("sty")) { //for (auto filename : find_files_with_extension(klammertext_dir() + "/sks", "sty")) { // msg() << "File: " << filename << "\n"; ss << "\\input{" << filename << "}\n"; /* //ss << "% " << filename << "\n\n" << read_file(filename) << "\n"; std::string pname = regex_split(filename, std::regex("/sks/"))[1]; ss << "% sks/" << pname << "\n\n" << string_from_file(filename) << "\n"; //ss << "\\usepackage{" << pname << "}\n"; */ } std::string result = ss.str(); /* result = std::regex_replace(result, std::regex(R"(\n\n+)"), "\n"); result = std::regex_replace(result, std::regex(R"(#)"), "^#"); //result = std::regex_replace(result, std::regex(R"(%+ *[^/].*?\n)"), ""); */ return result; } std::string title_line( std::string text, std::string font="1.3", std::string vskip="4pt", bool vskip_if_missing=false) { std::stringstream ss {}; if (text.empty()) { if (vskip_if_missing) { ss << "\\vspace*{" << vskip << "}\n"; } } else { ss << "{\\sfont{" << font << "}" << text << "}\\\\[" << vskip << "]\n"; } return ss.str(); } std::string default_cover( std::string title, std::string subtitle, std::string author, std::string date, std::string version) { std::string test = trim(title + subtitle + author + date + version); std::stringstream ss {}; if (!test.empty()) { ss << "\\thispagestyle{empty}\\vspace*{1in}\n"; ss << title_line(title, "2.0", "10pt") << title_line(subtitle, "1.7", "60pt", true) << title_line(author) << title_line(date) << title_line(version); } return ss.str(); } std::string nonbook_title( std::string title, std::string subtitle, std::string author, std::string date, std::string version) { std::string test = trim(title + subtitle + author + date + version); std::stringstream ss {}; if (!test.empty()) { // ss << Pagestyle? std::string full_title = title; if (!full_title.empty() && !subtitle.empty()) { full_title += " --- " + subtitle; } ss << title_line(full_title, "1.8", "8pt") << title_line(author) << title_line(date) << title_line(version) << "\n"; } return ss.str(); } std::string pagenumber(int n, std::string style="arabic") { (void)K::log(3); std::stringstream ss {}; ss << "\\pagenumbering{" << style << "}\\setcounter{page}{" << n << "}\n"; return ss.str(); } std::string book_verso_page(std::string copyright) { (void)K::log(3); std::stringstream ss {}; ss << "\n% Verso page (page ii)\n"; ss << "\\newpage\\thispagestyle{empty}\n"; if (!copyright.empty()) { ss << "\\leavevmode\\vfill\n" << copyright << "\n" << "\\vspace*{8pt}\n"; } else { ss << "\\leavevmode\n"; // blank verso page } ss << "\\newpage\n"; return ss.str(); } std::string font_command(std::string command, const Resolved_font& font, float scale) { std::stringstream ss {}; if (font.family_name.empty()) return ""; // Format scale option if needed std::string scale_opt {}; if (scale > 0.0f && scale != 1.0f) { char buf[16]; std::snprintf(buf, sizeof(buf), "%.4f", scale); scale_opt = std::string(",Scale=") + buf; } if (!font.regular.empty()) { // Use Path= to point at the .ttf files directly // fontspec syntax: \setmainfont[options]{filename} // Wrap in \ExplSyntaxOn to handle underscores in paths ss << "\\ExplSyntaxOn\n" << "\\" << command << "[Path={" << font.font_dir << "/}" << scale_opt; if (!font.bold.empty()) ss << ",BoldFont=" << font.bold; if (!font.italic.empty()) ss << ",ItalicFont=" << font.italic; if (!font.bold_italic.empty()) ss << ",BoldItalicFont=" << font.bold_italic; ss << "]{" << font.regular << "}\n" << "\\ExplSyntaxOff\n"; } else { // No .ttf files — use font name (fontspec finds by name) ss << "\\" << command; if (!scale_opt.empty()) ss << "[" << scale_opt.substr(1) << "]"; // strip leading comma ss << "{" << font.family_name << "}\n"; } return ss.str(); } std::string page(std::string structure, std::string title, std::string subtitle, std::string authors, std::string date, std::string version, std::string copyright, std::string bottom, std::string prolog, std::string paper_size, bool two_column, float leading, int pointsize, bool ragged_right, std::string cover, bool landscape, std::string body, Resolved_font serif_font, Resolved_font sans_font, Resolved_font mono_font, float font_scale) { (void)K::log(3); bool book_format = (structure == "book"); bool toc = (structure != "plain"); std::string document_class = book_format ? "book" : "article"; std::string page_side = book_format ? "twoside" : "oneside"; std::stringstream ss {}; ss << "\\documentclass[" << paper_size << "," << pointsize << "pt"; if (landscape) { ss << ",landscape"; } if (two_column) { ss << ",twocolumn"; } ss << "]{" << document_class << "}\n"; ss << "\\newcommand{\\documentclassname}{" << document_class << "}\n"; ss << "\\newcommand{\\documentside}{" << page_side << "}\n"; ss << "\\newcommand{\\documenttwocolumn}{" << (two_column ? "true" : "false") << "}\n"; ss << "\\newcommand{\\documentlandscape}{" << (landscape ? "true" : "false") << "}\n"; ss << "\\newcommand{\\documentisbook}{" << (book_format ? "true" : "false") << "}\n"; if (!bottom.empty()) { ss << "\\newcommand{\\documentbottom}{" << bottom << "}\n"; } ss << tex_style(); // Compute font scale factors: serif is the reference. // Three methods (uncomment the desired one): // x-height: serif_xh / other_xh (matches lowercase) // cap-height: serif_ch / other_ch (matches capitals) // average: mean(serif_xh,serif_ch) / mean(other_xh,other_ch) float serif_xh = serif_font.xheight_ratio; float serif_ch = serif_font.capheight_ratio; float serif_avg = (serif_xh + serif_ch) / 2.0f; auto font_scale_fn = [&](const Resolved_font& other) -> float { float other_avg = (other.xheight_ratio + other.capheight_ratio) / 2.0f; if (serif_avg > 0.0f && other_avg > 0.0f) // return serif_xh / other.xheight_ratio; // x-height // return serif_ch / other.capheight_ratio; // cap-height return serif_avg / other_avg; // average return 0.0f; }; // Global font_scale multiplies all font sizes uniformly float gs = (font_scale != 1.0f) ? font_scale : 0.0f; float sans_s = font_scale_fn(sans_font); float mono_s = font_scale_fn(mono_font); // Apply global scale: multiply into per-font scale, or use alone if (gs > 0.0f) { sans_s = (sans_s > 0.0f) ? sans_s * font_scale : font_scale; mono_s = (mono_s > 0.0f) ? mono_s * font_scale : font_scale; } ss << font_command("setmainfont", serif_font, gs); ss << font_command("setsansfont", sans_font, sans_s); ss << font_command("setmonofont", mono_font, mono_s); // Scale leading proportionally with font_scale float effective_leading = leading * ((font_scale != 1.0f) ? font_scale : 1.0f); ss << "\n\\setstretch{" << effective_leading << "}\n"; if (ragged_right) ss << "\\raggedright\n"; if (!prolog.empty()) { ss << prolog << "\n"; } ss << "\\begin{document}\n"; if (book_format) { // Front matter in roman numerals ss << pagenumber(1, "roman"); // Cover page (page i, no displayed number) if (!cover.empty()) { ss << cover << "\n"; } else { ss << default_cover(title, subtitle, authors, date, version); } // Verso page (page ii): copyright at bottom, or blank // Ensures TOC starts on recto (page iii) ss << book_verso_page(copyright); // Table of contents (starts on page iii, recto) ss << "\\pagestyle{noheader}\n" << "\\tableofcontents\n"; // Advance to the next recto page for the body. // After the TOC (on an odd page like iii), we need a blank verso // so the first chapter starts on recto. \cleardoublepage and // \mainmatter fail here because \pagenumbering{arabic} resets the // counter to 1 (odd), and \chapter's internal \cleardoublepage // then sees an odd page and skips the blank. // // Solution: explicitly emit a blank verso page after the TOC, // then switch to arabic numbering starting at page 1. ss << "\\clearpage\n" << "\\thispagestyle{empty}\\mbox{}\\clearpage\n" << "\\pagenumbering{arabic}\n" << "\\pagestyle{sksbook}\n"; } else { ss << "\\pagestyle{sks" << structure << "}\n\n"; // Plain or article: title block with optional copyright footnote if (!copyright.empty()) { ss << "\\renewcommand{\\thefootnote}{}\n"; ss << "\\footnotetext{" << copyright << "}\n"; ss << "\\renewcommand{\\thefootnote}{\\arabic{footnote}}\n"; } ss << nonbook_title(title, subtitle, authors, date, version); if (toc) { ss << "\\tableofcontents\n"; ss << "\\vspace*{\\baselineskip}\n"; } /* if (!landscape) { ss << "\\vspace*{\\baselineskip}\n"; } */ } ss << body << "\n"; ss << "\\end{document}\n"; return ss.str(); } } void check_for_xelatex() { std::string command = "which xelatex"; std::string xelatex_path = trim(exec(command.c_str())); if (trim(xelatex_path).empty()) { throw Environment_error( "The \"xelatex\" command required to make PDF files is not installed", Locator()); } (void)K::log(2, "xelatex path is " + xelatex_path); } std::vector find_latex_error_lines(const std::string& log_content) { std::vector errors; std::istringstream stream(log_content); std::string line; int i = 1; while (std::getline(stream, line)) { if (!line.empty() && line[0] == '!') { errors.push_back(std::to_string(i) + ": " + line); } i++; } return errors; }