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:
2026-07-18 18:48:23 +02:00
commit 2ba7ceee7a
272 changed files with 27634 additions and 0 deletions

325
sks/target/latex_util.cpp Normal file
View 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;
}