Initial commit: Klammertext source distribution
Curated source subset assembled by klammertext-dev's doc/make_dist.sh: the Klammermachine (mac), the Standard Klammer Set (sks), the commands (com), editor plugins and install guides (doc), a test subset (tst), and lib/bin placeholders. Builds with 'make -C com'. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
325
sks/target/latex_util.cpp
Normal file
325
sks/target/latex_util.cpp
Normal file
@@ -0,0 +1,325 @@
|
||||
#include "file.h"
|
||||
#include "kutil.h"
|
||||
#include "util.h"
|
||||
#include "log.h"
|
||||
#include "show.h"
|
||||
#include "font_resolve.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<std::string> find_latex_error_lines(const std::string& log_content)
|
||||
{
|
||||
std::vector<std::string> 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;
|
||||
}
|
||||
Reference in New Issue
Block a user