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

1
sks/target/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.d

34
sks/target/Makefile Normal file
View File

@@ -0,0 +1,34 @@
# Klammertext sks/target/ Makefile
# Improved version with automatic header dependency tracking
K := $(KLAMMERTEXT_HOME)
KS := $(K)/sks
KM := $(K)/mac
include $(KM)/env/makefile.env
# Source files
SOURCES := html_util.cpp latex_util.cpp font_resolve.cpp
OBJECTS := html_util.o latex_util.o font_resolve.o
DEPFILES := html_util.d latex_util.d font_resolve.d
# Additional include paths
LOCAL_CPPFLAGS := -I$(KM) -I$(KS)/kutil
# Compiler flags for dependency generation
DEPFLAGS = -MMD -MP -MF $(@:.o=.d)
.PHONY: all clean
# Default target
all : $(OBJECTS)
# Pattern rule for object files
%.o : %.cpp
$(CXX) -c $(CPPFLAGS) $(LOCAL_CPPFLAGS) $(CXXFLAGS) $(DEPFLAGS) $< -o $@
clean :
rm -f $(OBJECTS) $(DEPFILES) *.so html_test latex_util_test *~
# Include generated dependency files (if they exist)
-include $(DEPFILES)

1
sks/target/css/list.txt Normal file
View File

@@ -0,0 +1 @@
target.css

165
sks/target/css/target.css Normal file
View File

@@ -0,0 +1,165 @@
.unpad {
padding: 0px;
margin: 0px;
}
.rborder {
border-right: 1px;
border-color: black;
}
.outline {
border: solid black 1px;
padding: .5em 1em;
vertical-align: middle;
}
.center {
display: flex;
justify-content: center;
}
/* -------------------------------------------------------------------------------- */
.bgap {
margin-bottom: .5rem;
}
.tgap {
margin-top: .5rem;
}
.lgap {
margin-left: .5rem;
margin-right: 1rem;
/* font-style: normal;*/
text-align: left;
}
.rgap {
margin-left: 1rem;
margin-right: .5rem;
/*font-style: normal;*/
text-align: left;
}
/*
.mno {
margin-top: 1rem;
margin-bottom: 1.5rem;
padding-top: .5rem;
}
*/
.mbot {
margin-top: .75rem;
margin-bottom: .5rem;
padding-top: .5rem;
}
.mtop {
margin-top: .5rem;
margin-bottom: 1.5rem;
}
/*
.ebot {
margin-bottom: 1.5rem;
margin-top: 1.5rem;
}
*/
.caption {
line-height: 1.3;
/* text-align: center; */
}
.caption_text {
text-align: center;
/* padding-left: 15px;
padding-right: 15px;
*/
line-height: 1.2;
margin-top: .5rem;
}
.caption_bottom {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.caption_bottom .caption {
font-style: italic;
text-align: center;
}
.caption_top {
display: inline-flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.caption_top .caption {
font-style: italic;
}
.caption_left {
display: inline-flex;
flex-direction: row;
align-items: center;
margin: .75rem 0 .75rem 0;
}
.caption_right {
display: inline-flex;
flex-direction: row;
align-items: center;
margin: .75rem 0 .75rem 0;
}
.element_container {
display: flex;
justify-content: center;
}
.element_left {
display: inline-block;
}
.wraparound {
display: inline;
}
.hpos_left {
display: flex;
flex-direction: row;
align-items: center;
justify-content: left;
}
.hpos_center {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
}
.hpos_right {
display: flex;
flex-direction: row;
align-items: center;
justify-content: right;
}
.hpos_none {
display: inline-flex;
}
.hpos_margin {
margin: 0 1em 0 1em;
}

329
sks/target/font_resolve.cpp Normal file
View File

@@ -0,0 +1,329 @@
#include "font_resolve.h"
#include "file.h"
#include "util.h"
#include "error.h"
#include "log.h"
#include "show.h"
#include "kutil.h"
#include <algorithm>
#include <regex>
#include <sstream>
#include <filesystem>
namespace fs = std::filesystem;
std::string name_to_dirname(std::string name)
{
std::string result {};
for (char c : name) {
if (c == ' ')
result += '-';
else
result += std::tolower(c);
}
return result;
}
std::string name_to_google_query(std::string name)
{
std::string result {};
for (char c : name) {
if (c == ' ')
result += '+';
else
result += c;
}
return result;
}
static void classify_from_css(Resolved_font& font, std::string css_path)
{
// Parse the @font-face blocks in the CSS to determine variant → filename mapping
std::string css = string_from_file(css_path);
std::regex face_rgx(
R"(@font-face\s*\{[^}]*font-style:\s*(\w+);[^}]*font-weight:\s*(\w+);[^}]*url\('([^']+\.ttf)'\)[^}]*\})",
std::regex::multiline);
auto begin = std::sregex_iterator(css.begin(), css.end(), face_rgx);
auto end = std::sregex_iterator();
for (auto it = begin; it != end; ++it) {
std::string style = (*it)[1];
std::string weight = (*it)[2];
std::string url_path = (*it)[3];
// URL is like 'dir-name/Filename.ttf' — extract just the filename
std::string filename = url_path.substr(url_path.rfind('/') + 1);
bool is_bold = (weight == "700" || weight == "bold");
bool is_italic = (style == "italic" || style == "oblique");
if (is_bold && is_italic)
font.bold_italic = filename;
else if (is_bold)
font.bold = filename;
else if (is_italic)
font.italic = filename;
else
font.regular = filename;
}
}
static Resolved_font resolve_bundled(std::string family_name, std::string dir_name)
{
std::string bundled_dir = klammertext_dir() + "/sks/font/fonts/" + dir_name;
std::string bundled_css = bundled_dir + ".css";
if (fs::exists(bundled_dir) && fs::exists(bundled_css)) {
Resolved_font font {};
font.family_name = family_name;
font.dir_name = dir_name;
font.font_dir = bundled_dir;
font.css_file = bundled_css;
font.from_cache = false;
classify_from_css(font, bundled_css);
return font;
}
return {};
}
static Resolved_font resolve_cached(std::string family_name, std::string dir_name)
{
std::string cache_base = cache_directory("_fonts");
std::string cache_dir = cache_base + "/" + dir_name;
std::string cache_css = cache_base + "/" + dir_name + ".css";
if (fs::exists(cache_dir) && fs::exists(cache_css)) {
Resolved_font font {};
font.family_name = family_name;
font.dir_name = dir_name;
font.font_dir = cache_dir;
font.css_file = cache_css;
font.from_cache = true;
classify_from_css(font, cache_css);
return font;
}
return {};
}
static Resolved_font fetch_google_font(std::string family_name, std::string dir_name)
{
(void)K::log(1, "Fetching font \"" + family_name + "\" from Google Fonts");
std::string query = name_to_google_query(family_name);
std::string url =
"https://fonts.googleapis.com/css2?family=" + query +
":ital,wght@0,400;0,700;1,400;1,700&display=swap";
std::string cmd = "curl -s -H 'User-Agent: Mozilla/4.0' '" + url + "'";
std::string css_response = exec(cmd.c_str());
if (css_response.empty() || css_response.find("@font-face") == std::string::npos) {
return {};
}
// Parse @font-face blocks to extract style, weight, and .ttf URL
struct Font_variant {
std::string style; // "normal" or "italic"
std::string weight; // "400" or "700"
std::string url;
};
std::vector<Font_variant> variants {};
std::regex face_rgx(
R"(@font-face\s*\{[^}]*font-style:\s*(\w+);[^}]*font-weight:\s*(\d+);[^}]*src:\s*url\((https?://[^)]+\.ttf)\)[^}]*\})",
std::regex::multiline);
auto begin = std::sregex_iterator(css_response.begin(), css_response.end(), face_rgx);
auto end = std::sregex_iterator();
for (auto it = begin; it != end; ++it) {
Font_variant v {};
v.style = (*it)[1];
v.weight = (*it)[2];
v.url = (*it)[3];
variants.push_back(v);
}
if (variants.empty()) {
return {};
}
// Create cache directory
std::string cache_base = cache_directory("_fonts");
if (!fs::exists(cache_base))
fs::create_directories(cache_base);
std::string cache_dir = cache_base + "/" + dir_name;
if (!fs::exists(cache_dir))
fs::create_directory(cache_dir);
Resolved_font font {};
font.family_name = family_name;
font.dir_name = dir_name;
font.font_dir = cache_dir;
font.from_cache = true;
// Download each .ttf file with descriptive names
for (auto& v : variants) {
std::string local_name;
if (v.style == "normal" && v.weight == "400")
local_name = "Regular.ttf";
else if (v.style == "normal" && v.weight == "700")
local_name = "Bold.ttf";
else if (v.style == "italic" && v.weight == "400")
local_name = "Italic.ttf";
else if (v.style == "italic" && v.weight == "700")
local_name = "BoldItalic.ttf";
else
continue;
std::string ttf_path = cache_dir + "/" + local_name;
if (!fs::exists(ttf_path)) {
std::string dl_cmd = "curl -s -o '" + ttf_path + "' '" + v.url + "'";
(void)exec(dl_cmd.c_str());
if (!fs::exists(ttf_path)) {
(void)K::log(1, "Failed to download font file: " + v.url);
continue;
}
}
if (local_name == "Regular.ttf")
font.regular = local_name;
else if (local_name == "Bold.ttf")
font.bold = local_name;
else if (local_name == "Italic.ttf")
font.italic = local_name;
else if (local_name == "BoldItalic.ttf")
font.bold_italic = local_name;
}
// Generate @font-face CSS file
std::string css_file = cache_base + "/" + dir_name + ".css";
std::stringstream css {};
auto emit_face = [&](std::string style, std::string weight, std::string filename) {
if (filename.empty())
return;
css << "\n@font-face {\n"
<< " font-family: '" << family_name << "';\n"
<< " font-style: " << style << ";\n"
<< " font-weight: " << weight << ";\n"
<< " src: url('" << dir_name << "/" << filename << "') format('truetype');\n"
<< "}\n";
};
emit_face("normal", "400", font.regular);
emit_face("normal", "700", font.bold);
emit_face("italic", "400", font.italic);
emit_face("italic", "700", font.bold_italic);
string_to_file(css_file, css.str());
font.css_file = css_file;
return font;
}
static void extract_font_metrics(Resolved_font& font)
{
if (font.regular.empty() || font.font_dir.empty())
return;
std::string ttf_path = font.font_dir + "/" + font.regular;
if (!fs::exists(ttf_path))
return;
// Extract both x-height and cap-height ratios from OS/2 table
std::string script =
"python3 -c \""
"import struct; "
"f = open('" + ttf_path + "', 'rb'); "
"_, n = struct.unpack('>IH', f.read(6)); "
"f.read(6); "
"t = {};\n"
"for _ in range(n):\n"
" tag = f.read(4).decode('latin-1').strip('\\\\x00'); "
" _, o, l = struct.unpack('>III', f.read(12)); "
" t[tag] = o\n"
"f.seek(t['head'] + 18); "
"upm = struct.unpack('>H', f.read(2))[0]; "
"f.seek(t['OS/2']); "
"ver = struct.unpack('>H', f.read(2))[0]; "
"f.seek(t['OS/2'] + 86); "
"xh, ch = struct.unpack('>hh', f.read(4)); "
"print(f'{xh/upm:.4f} {ch/upm:.4f}') if ver >= 2 else None; "
"f.close()\"";
std::string result = trim(exec(script.c_str()));
if (!result.empty()) {
try {
auto pos = result.find(' ');
if (pos != std::string::npos) {
font.xheight_ratio = std::stof(result.substr(0, pos));
font.capheight_ratio = std::stof(result.substr(pos + 1));
}
} catch (...) {}
}
}
Resolved_font resolve_font(std::string family_name)
{
if (family_name.empty())
return {};
std::string dir_name = name_to_dirname(family_name);
// 1. Check bundled fonts
Resolved_font font = resolve_bundled(family_name, dir_name);
if (!font.family_name.empty()) {
extract_font_metrics(font);
return font;
}
// 2. Check font cache
font = resolve_cached(family_name, dir_name);
if (!font.family_name.empty()) {
extract_font_metrics(font);
return font;
}
// 3. Fetch from Google Fonts
font = fetch_google_font(family_name, dir_name);
if (!font.family_name.empty()) {
extract_font_metrics(font);
return font;
}
// 4. Error
throw Argument_error(
"Font \"" + family_name + "\" not found.\n"
" Not bundled in sks/font/fonts/" + dir_name + "/,\n"
" not cached, and not available from Google Fonts.\n"
" Check the font name or install it locally.");
}
// Font assets are copied with copy_file_stream() (mac/file.h) rather than
// std::filesystem::copy_file, which fails on Apple `container` virtiofs mounts
// — see the note on copy_file_stream() in file.cpp for the full rationale.
void install_resolved_font(const Resolved_font& font, std::string output_dir)
{
if (font.family_name.empty())
return;
std::string output_font_dir = output_dir + "/fonts";
if (!fs::exists(output_font_dir))
fs::create_directory(output_font_dir);
// Copy .css file and font directory to output
std::string dest_css = output_font_dir + "/" + font.dir_name + ".css";
std::string dest_dir = output_font_dir + "/" + font.dir_name;
copy_file_stream(font.css_file, dest_css);
if (!fs::exists(dest_dir)) {
fs::create_directory(dest_dir);
for (auto& entry : fs::directory_iterator(font.font_dir)) {
copy_file_stream(entry.path(),
dest_dir + "/" + entry.path().filename().string());
}
}
}

24
sks/target/font_resolve.h Normal file
View File

@@ -0,0 +1,24 @@
#pragma once
#include <string>
#include <vector>
struct Resolved_font {
std::string family_name {}; // "Crimson Pro"
std::string dir_name {}; // "crimson-pro"
std::string font_dir {}; // Full path to font directory
std::string css_file {}; // Full path to .css file
bool from_cache = false;
// .ttf filenames for each variant (empty if variant not available):
std::string regular {};
std::string bold {};
std::string italic {};
std::string bold_italic {};
float xheight_ratio = 0.0f; // x-height / unitsPerEm from OS/2 table
float capheight_ratio = 0.0f; // cap-height / unitsPerEm from OS/2 table
};
std::string name_to_dirname(std::string name);
std::string name_to_google_query(std::string name);
Resolved_font resolve_font(std::string family_name);
void install_resolved_font(const Resolved_font& font, std::string output_dir);

74
sks/target/html_test.cpp Normal file
View File

@@ -0,0 +1,74 @@
#include <iostream>
#include "html_util.h"
int main()
{
std::cout << page("HTML target test",
html("i", "An HTML page, all in italic text."),
{"a.css", "b.css", "c.css"},
{"j.js", "k.js", "l.js"}) << "\n";
}
//auto m { meta_elements() };
/*
HTML h = html(
"html", { html("head", meta_elements()),
html("body", "body") });
std::cout << h << "\n";
std::cout << preamble() << "\n";
*/
/*
std::string body {};
std::vector<HTML> page {};
page.push_back(HTML("!DOCTYPE"));
page.push_back(HTML("html")
HTML head("head", {"
<html lang="en">
HTML h("i", "italic text");
h.attr("class", "italic").attr("style", "margin; 1em;");
HTML b("b", "bold text");
b.cls("italic").sty("margin; 1em;");
HTML img("img");
img.attr("href", "dog.png").attr("class", "framed");
HTML p1("p", "paragraph 1");
HTML p2("p", "paragraph 2");
//std::vector<HTML> pars { p1, p2 };
//HTML body("body", pars);
HTML body("body", { p1, p2});
HTML body2("body", { {"p", "paragraph 1"}, {"p", "paragraph 2"} });
//element_contents c {p1, p2};
// std::cout << h << "\n" << b << "\n" << img << "\n"
// << p1 << "\n" << p2
// << body2
// << "\n";
std::cout << body2 << "\n";
*/

739
sks/target/html_util.cpp Normal file
View File

@@ -0,0 +1,739 @@
#include <iostream>
#include <iterator>
#include <regex>
#include "util.h"
#include "kutil.h"
#include "error.h"
#include "file.h"
#include "html_util.h"
#include "log.h"
#include "show.h"
std::ostream& operator<<(std::ostream& os, const attr_t& a)
{
if (std::holds_alternative<int>(a))
os << std::get<int>(a);
else if (std::holds_alternative<float>(a))
os << std::get<float>(a);
else if (std::holds_alternative<std::string>(a))
os << std::get<std::string>(a);
return os;
}
int _indent = 0;
std::ostream& operator<<(std::ostream& os, const html_element_t& h)
{
if (std::holds_alternative<std::string>(h))
os << std::get<std::string>(h);
else if (std::holds_alternative<HTML>(h))
os << std::get<HTML>(h);
return os;
}
std::ostream& operator<<(std::ostream& os, const elements_t& elements)
{
// for (const HTML& h : elements) {
for (const html_element_t& h : elements) {
/*n
if (std::holds_alternative<std::string>(h))
os << h;
else if (std::holds_alternative<HTML>(h))
os << h;
}
*/
os << h;
}
return os;
}
bool attribute_with_value(attr_t attr)
{
return std::holds_alternative<std::string>(attr)
&& !(std::get<std::string>(attr)).empty();
}
std::ostream& operator<<(std::ostream& os, const HTML& h)
{
bool void_element = h.void_element(h.m_tag);
//bool newline = !(h.void_element(h.m_tag) or h.empty_element(h.m_tag));
//bool empty_element = h.empty_element(h.m_tag) and trim(h.m_text).size() == 0;
os << "<" << h.m_tag;
for (auto [t,v] : h.m_attrs) {
os << " " << t;
if (attribute_with_value(v)) {
os << "=\"" << v << "\"";
}
}
os << ">";
if (h.m_elements.size() > 0) {
os << "\n" << h.m_elements;
}
//if (!empty_element) {
if (trim(h.m_text).size() > 0) {
if (h.m_text.find(' ') != std::string::npos) {
os << "\n";
}
os << trim(h.m_text); // << "\n";
}
/*
//os << h.m_text;
std::string intext = "\n" + h.m_text;
std::string sp(_indent, ' ');
intext = std::regex_replace(intext, std::regex("\n"), "\n" + sp);
os << intext;
*/
//os << trim(h.m_text) << "\n";
// }
/*
if (true or newline) {
os << "\n";
_indent -= 2;
}
*/
//if (!void_element and !empty_element) {
if (!void_element) {
if (h.m_text.find(' ') != std::string::npos) {
os << "\n";
}
os << "</" << h.m_tag << ">";
}
os << "\n";
/*
if (true or !void_element and !empty_element) {
//std::string sp(_indent, ' ');
//os << sp << "</" << h.m_tag << ">";
os << "\n</" << h.m_tag << ">";
}
os << "\n";
*/
return os;
}
std::string to_string(elements_t e)
{
std::stringstream ss{};
ss << e;
return ss.str();
}
bool HTML::void_element(std::string tag) const
{
if (tag == "!DOCTYPE")
return true;
else
return std::find
(m_void_tags.begin(), m_void_tags.end(), tag) != m_void_tags.end();
}
bool HTML::empty_element(std::string tag) const
{
if (tag == preamble_tag)
return true;
else
return std::find
(m_empty_tags.begin(), m_empty_tags.end(), tag) != m_empty_tags.end();
}
//HTML& HTML::attr(std::string name, std::variant<int, float, std::string> value)
HTML& HTML::attr(std::string name, attr_t value)
{
m_attrs.push_back({name, value});
return *this;
}
HTML& HTML::attrs(std::string s)
{
static const std::regex attr_rgx(R"((\w+)\s*=\s*\"(.*?)\")");
auto attrs_begin = std::sregex_iterator(s.begin(), s.end(), attr_rgx);
auto attrs_end = std::sregex_iterator();
//std::cout << "Found " << std::distance(attrs_begin, attrs_end) << " attrs\n";
for (std::sregex_iterator iter = attrs_begin; iter != attrs_end; ++iter) {
std::smatch match = *iter;
//std::cout << "attr: " << s << ": " << (*iter)[1] << " " << (*iter)[2] << "\n";
m_attrs.push_back({(*iter)[1], (*iter)[2]});
}
return *this;
}
// Functions:
namespace html {
HTML elt(std::string tag)
{
HTML h(tag);
return h;
}
HTML elt(std::string tag, std::string text)
{
HTML h(tag, trim(text));
return h;
}
HTML elt(std::string tag, elements_t elements)
{
HTML h(tag, elements);
return h;
}
HTML elt(std::string tag, HTML e, elements_t elts)
{
elements_t elements = {e};
elements.insert(elements.end(), elts.begin(), elts.end());
HTML h(tag, elements);
return h;
}
HTML preamble()
{
return elt(preamble_tag);
}
// <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
// <meta http-equiv="Pragma" content="no-cache" />
// <meta http-equiv="Expires" content="0" />
elements_t meta_elements()
{
elements_t result {
elt("meta")
.attr("name", "viewport")
.attr("content", "width=device-width, initial-scale=1"),
elt("meta")
.attr("charset", "UTF-8"),
// Does not conform to HTML 5:
// elt("meta")
// .attr("http-equiv", "Cache-Control")
// .attr("content", "no-cache, no-store, must-revalidate"),
// elt("meta")
// .attr("http-equiv", "Pragma")
// .attr("content", "no-cache"),
// elt("meta")
// .attr("http-equiv", "Expires")
// .attr("content", "0")
};
/*
result.push_back(
elt("link")
.attr("rel", "icon")
.attr("href", "/favicon.ico")
.attr("type", "image/x-icon"))
*/
return result;
}
elements_t local_font_elements(strings_t fontnames)
{
elements_t result {};
for (auto name : fontnames) {
// std::cout << "Font: " << name << "\n";
result.push_back(elt("link")
.attr("href", "fonts/" + name + ".css")
.attr("rel", "stylesheet"));
}
return result;
}
elements_t google_font_elements(strings_t fontnames)
{
elements_t result {};
if (fontnames.size() > 0) {
result.push_back(elt("link")
.attr("rel", "preconnect")
.attr("href", "https://fonts.googleapis.com"));
result.push_back(elt("link")
.attr("rel", "preconnect")
.attr("href", "https://fonts.gstatic.com"));
}
for (auto name : fontnames)
result.push_back(
elt("link")
.attr("href", "https://fonts.googleapis.com/css2?family=" + name + "&display=swap")
.attr("rel", "stylesheet"));
return result;
}
elements_t javascript(std::string output_dir, strings_t js_filenames)
{
//elements_t result { jquery_elements() };
elements_t result {};
std::string js_dir = output_dir + "/js/";
fs::create_directory(js_dir);
for (auto fname : js_filenames) {
/*
std::string base = file_basename(fname) + ".js";
std::string js_page_filename = js_dir + base;
if (file_exists(js_page_filename)) {
fs::remove(js_page_filename);
}
fs::copy(fname, js_page_filename);
*/
result.push_back(elt("script")
.attr("src", fname)); //"js/" + base));
}
return result;
}
HTML head(std::string title,
std::string css,
strings_t css_filenames,
strings_t local_fonts, strings_t google_fonts)
{
elements_t elts = meta_elements();
// msg() << "ELTS sks: " << elts << "\n";
for (auto e : local_font_elements(local_fonts))
elts.push_back(e);
for (auto e : google_font_elements(google_fonts))
elts.push_back(e);
//elts += google_font_prolog();
//std::string css_dir = output_dir + "/css/";
// No; a single file at the top level...?
// std::string css_dir = output_dir + "/";
// fs::create_directory(css_dir);
for (auto fname : css_filenames) {
// msg() << "CSS: " << fname << "\n";
/*
std::string base = file_basename(fname) + ".css";
std::string page_filename = css_dir + base;
if (!file_exists(page_filename)) {
fs::copy(fname, page_filename);
}
*/
elts.push_back(elt("link")
.attr("rel", "stylesheet")
.attr("href", fname)); // base));
}
if (!css.empty()) {
elts.push_back(elt("style", css));
}
if (!title.empty()) {
elts.push_back(elt("title", trim(title)));
}
HTML result = elt("head", elts);
return result;
}
HTML button(std::string id, std::string name,
std::string attrib="", std::string value="")
{
std::vector<std::string> hidden = {"Clear", "Back"};
HTML result = elt("span", string_replace(name, " ", "&#160;")).id(id).cls("navb");
if (attrib != "") {
result.attr(attrib, value);
}
if (std::find(hidden.begin(), hidden.end(), name) != hidden.end()) {
result.attr("hidden", "");
}
return result;
}
elements_t navtools(int maximum_depth=5)
{
elements_t result {};
for (auto [name, id] : std::vector<std::pair<std::string, std::string>>
{{ "Text only", "textshow" },
{ "Next", "next" },
{ "Previous", "previous" }}) {
//{ "Fit", "fit" }}) {
result.push_back(button(id, name));
}
for (int n = 1; n <= maximum_depth; n++) {
result.push_back(
elt("span", std::to_string(n)).cls("navb depthchoice").attr("data-depth", std::to_string(n)));
}
elements_t fit_elements = {
elt("input", "")
.id("fit_check")
.attr("name", "fit_check")
.attr("type", "checkbox")
.attr("active", "false")
.attr("autocomplete", "off"),
//.attr("checked", ""),
elt("label", "Fit")
.id("fit_check_label")
.attr("for", "fit_check")
};
result.push_back(elt("span", fit_elements)
.attr("id", "fit"));
elements_t linkshow_elements = {
elt("input", "")
.id("linkshow_check")
.attr("name", "linkshow_check")
.attr("type", "checkbox")
.attr("active", "false")
.attr("autocomplete", "off"),
//.attr("checked", ""),
elt("label", "Links")
.id("linkshow_check_label")
.attr("for", "linkshow_check")
};
result.push_back(elt("span", linkshow_elements)
.attr("id", "linkshow"));
return result;
}
elements_t search()
{
elements_t result {};
for (auto [name, id] : std::vector<std::pair<std::string, std::string>>
{{ "Clear", "search_clear" },
{ "Back", "search_back_label"},
{ "Search", "search_label" }
})
result.push_back(button(id, name));
elements_t search_input { elt("span", "&#9421;").id("search_clear_input").attr("hidden", "") };
result.push_back(
elt("span", {
elt("input", "").id("search_input")
.attr("type", "text").attr("name", "search_input")
.attr("autocomplete", "off")
.attr("required", "required"),
elt("span", search_input).id("search_clear_input_box")
}).id("search_box"));
//result.push_back(button("help", "Help"));
/*
elements_t search_input {};
search_input.push_back(
elt("span", result).id("foldersearch"));
return search_input;
*/
return result;
}
HTML navigation(int max_level) {
return elt("div", {
elt("span", navtools(max_level)).id("navtools"),
elt("span", { elt("span", search()).id("searchtools"),
elt("span", button("help", "Help"))})})
.id("nav");
}
elements_t middle(std::string text,
//std::string nav,
std::string toc)
{
elements_t result {};
toc = trim(toc);
text = trim(text);
if (!toc.empty()) {
result.push_back(elt("div", toc)
.attr("id", "toc"));
result.push_back(elt("div")
.attr("id", "resizer"));
}
if (!text.empty()) {
text += "<div class=\"endspace\"></div>\n";
elements_t content {};
content.push_back(elt("div", text).attr("id", "text"));
//if (!nav.empty()) {
//content.push_back(elt("div", "<h1>SEARCH</h1>")
// .attr("id", "search_page"));
//content.push_back(elt("div", "<h1>HELP</h1>").attr("id", "help_page"));
//}
result.push_back(elt("div", content).attr("id", "content"));
}
else {
throw Argument_error("The text content of the HTML page is not defined");
}
return result;
}
void install_local_fonts(strings_t font_dirs, strings_t names, std::string output_dir)
{
std::string output_font_dir = output_dir + "/fonts";
//std::cout << "Font directory for website: " << output_font_dir << "\n";
if (!file_exists(output_font_dir)) {
fs::create_directory(output_font_dir);
//std::cout << " Font directory created: " << output_font_dir << "\n";
}
for (auto name : names) {
//std::cout << "Font search for " << name << ": " << klammertext_dir() << "/sks/font/fonts/\n";
std::string src_font_dir = klammertext_dir() + "/sks/font/fonts/" + name;
if (!file_exists(src_font_dir)) {
bool found = false;
for (std::string font_dir : font_dirs) {
src_font_dir = font_dir + "/" + name;
//std::cout << "Font search for " << name << ": " << font_dir << "/\n";
if (file_exists(src_font_dir)) {
found = true;
break;
}
}
if (!found) {
throw Argument_error(
"Local font directory \"" + src_font_dir + "\" does not exist");
}
}
//std::cout << "Font " << name << ": " << src_font_dir << "\n";
std::string src_font_css = src_font_dir + ".css";
if (!fs::exists(src_font_css)) {
throw Argument_error(
"Local font CSS file \"" + src_font_css + "\" does not exist");
}
std::string font_copy =
"cp -r " + src_font_css + " " + src_font_dir + " " + output_font_dir;
//std::string files_copy = "cp -r " + basename + " " + font_dir;
//cout << css_copy << "\n" << files_copy << "\n";
//std::cout << system(css_copy.c_str()) << "\n";
//std::cout << system(files_copy.c_str()) << "\n";
if (system(font_copy.c_str()) != 0) {
std::cout << "Font copy command: " << font_copy << "\n";
throw Argument_error("Local font \"" + name + "\" not found");
}
}
}
elements_t status(std::string date, std::string version, std::string copyright)
{
elements_t result {};
if (!date.empty()) {
result.push_back(elt("span", date).cls("footeritem"));
}
if (!version.empty()) {
result.push_back(elt("span", version).cls("footeritem"));
}
if (!copyright.empty()) {
result.push_back(elt("span", copyright).cls("footeritem"));
}
return result;
}
elements_t page(
std::string title,
std::string page_title,
std::string output_dir,
std::string nav,
int max_level,
std::string toc,
std::string text,
std::string date,
std::string version,
std::string copyright,
std::string css,
strings_t css_filenames,
strings_t js_filenames,
strings_t local_fonts,
strings_t google_fonts,
std::string logo)
{
if (page_title.empty())
page_title = title;
elements_t body {};
if (!title.empty()) {
elements_t title_bar {};
title_bar.push_back(elt("span", trim(title)).attr("id", "title_text"));
if (!logo.empty()) {
title_bar.push_back(elt("span", trim(logo)).attr("id", "logo"));
}
body.push_back(elt("div", title_bar).attr("id", "title"));
}
if (!nav.empty()) {
body.push_back(navigation(max_level));
}
if (!nav.empty() and !toc.empty()) {
body.push_back(elt("div",
elt("div",
middle(text, toc))
.attr("id", "middle")));
} else {
//std::cout << "ONLY TEXT\n" << text << "\n\n";
body.push_back(elt("div",
elt("div",
text)
.attr("id", "text"))
.attr("id", "middle"));
}
elements_t status_elements = status(date, version, copyright);
if (!empty(status_elements)) {
body.push_back(elt("div", status_elements).attr("id", "status"));
}
//if (!status.empty())
// body.push_back(elt("div", trim(status)).attr("id", "status"));
if (!nav.empty() and !toc.empty())
body.push_back(elt("div", "").attr("id", "pagecache"));
if (!js_filenames.empty()) {
auto js_elements { javascript(output_dir, js_filenames) };
body.insert(std::end(body), std::begin(js_elements), std::end(js_elements));
}
elements_t result { preamble() };
if (page_title.empty())
page_title = title;
result.push_back(
elt("html",
{ head(page_title, css, css_filenames, local_fonts, google_fonts),
elt("body", body)
}).attr("lang", "en"));
return result;
}
void add_title(elements_t& body, std::string title, std::string logo)
{
elements_t title_bar {};
title_bar.push_back(elt("span", trim(title)).attr("id", "title_text"));
if (!logo.empty()) {
title_bar.push_back(elt("span", trim(logo)).attr("id", "logo"));
}
body.push_back(elt("div", title_bar).attr("id", "title"));
}
void add_nav(elements_t& body, int max_level)
{
body.push_back(navigation(max_level));
}
void add_text(elements_t& body, std::string text, std::string toc, bool text_only)
{
if (text_only) {
elements_t content {};
content.push_back(elt("div", text).attr("id", "text"));
body.push_back(
elt("div", elt("div", content).attr("id", "content")).attr("id", "middle"));
} else {
body.push_back(elt("div", middle(text, toc)).attr("id", "middle"));
}
}
void add_bottom_spacer(elements_t& body)
{
body.push_back(elt("div").cls("endspace"));
}
void add_status_bar(
elements_t& body, std::string date, std::string version, std::string copyright)
{
elements_t status_elements = status(date, version, copyright);
if (!empty(status_elements)) {
body.push_back(elt("div", status_elements).attr("id", "status"));
}
}
void add_page_cache(elements_t& body)
{
body.push_back(elt("div", "").attr("id", "pagecache"));
}
void add_js_links(elements_t& body, strings_t js_filenames, std::string output_dir)
{
if (!js_filenames.empty()) {
auto js_elements { javascript(output_dir, js_filenames) };
body.insert(std::end(body), std::begin(js_elements), std::end(js_elements));
}
}
elements_t make_page(
elements_t body,
std::string page_title, std::string css,
strings_t css_filenames, strings_t local_fonts, strings_t google_fonts)
{
elements_t page { preamble() };
page.push_back(
elt("html",
{ head(page_title, css, css_filenames, local_fonts, google_fonts),
elt("body", body)
}).attr("lang", "en"));
return page;
}
bool tag_is_block_element(std::string tag)
{
auto location = std::find(block_elements.begin(), block_elements.end(), tag);
return location != block_elements.end();
}
bool block_element(std::string par)
{
(void)K::log(3);
bool result = false;
par = trim(par);
if (par[0] == '<') {
static const std::regex element_rgx(R"(^\s*</?([^\s>]+).*?>)");
std::smatch match {};
std::regex_search(par, match, element_rgx);
result = tag_is_block_element(match[1]);
}
return result;
}
std::string make_paragraphs(std::string html_text)
{
(void)K::log(3);
std::string result {};
//html_text = std::regex_replace(html_text, std::regex(R"((<pre .*?>)\s+)"), "$1\n");
//html_text = std::regex_replace(html_text, std::regex(R"(\s+</pre>)"), "\n</pre>");
static const std::regex paragraph_split_rgx(R"(\n *(\n *)+)");
for (auto par : regex_split(html_text, paragraph_split_rgx)) {
par = trim(par);
if (par.size() > 0) {
if (!block_element(par)) {
par = "<p>" + par + "</p>";
}
result += par + "\n\n";
}
}
return result;
}
std::string minimize_css(std::string src)
{
(void)K::log(3);
std::string result = src;
//std::smatch match {};
std::regex line_end_space(R"(\\}[ ]+)", std::regex::awk);
result = regex_replace(result, line_end_space, "}");
std::regex whitespace(R"([\n ]+)", std::regex::awk);
result = regex_replace(result, whitespace, " ");
std::regex line_end(R"(}[ ]+)", std::regex::awk);
result = regex_replace(result, line_end, "}\n");
return result;
}
std::string minimize_js(std::string src)
{
(void)K::log(3);
return src;
}
}

119
sks/target/html_util.h Normal file
View File

@@ -0,0 +1,119 @@
#pragma once
#include <string>
#include <sstream>
#include <vector>
#include <algorithm>
#include <variant>
#include <utility>
using strings_t = std::vector<std::string>;
using attr_t = std::variant<int, float, std::string>;
class HTML;
using html_element_t = std::variant<std::string,HTML>;
using elements_t = std::vector<html_element_t>;
const std::string preamble_tag = "!DOCTYPE html";
const std::vector<std::string> block_elements {
"!DOCTYPE", "html", "head", "body",
"address", "article", "aside", "blockquote", "details", "dialog",
"div", "dl", "dt", "dd", "fieldset", "figcaption", "figure", "footer", "form",
"kt-part", "kt-chapter", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "img", "li",
"main", "nav", "ol", "p", "section", "table", "ul", "td", "tr", "pre" };
std::ostream& operator<<(std::ostream& os, const elements_t& elements);
std::string to_string(elements_t e);
class HTML {
public:
operator std::string() {
std::stringstream ss {}; ss << *this; return ss.str(); };
HTML(std::string tag) :
m_tag(tag) {};
HTML(std::string tag, std::string text) :
m_tag(tag), m_text({text}) {};
HTML(std::string tag, elements_t elements) :
m_tag(tag), m_elements(elements) {};
HTML& attr(std::string name, attr_t value);
HTML& attrs(std::string s);
HTML& cls(std::string name) { return attr("class", name); };
HTML& sty(std::string css) { return attr("style", css); };
HTML& id(std::string id) { return attr("id", id); };
bool void_element(std::string tag) const;
bool empty_element(std::string tag) const;
friend std::ostream& operator<<(std::ostream& os, const HTML& h);
friend std::ostream& operator<<(std::ostream& os, const std::vector<HTML>& hv);
private:
std::string m_tag {};
std::vector<std::pair<std::string, attr_t>> m_attrs {};
std::string m_text {};
elements_t m_elements {};
inline static const std::vector<std::string> m_void_tags {
"!DOCTYPE html", "area", "base", "br", "col", "command", "embed", "hr","img",
"input", "keygen", "link", "meta", "param", "source", "track", "wbr" };
inline static const std::vector<std::string> m_empty_tags { "script" };
};
namespace html {
HTML elt(std::string tag);
HTML elt(std::string tag, std::string text);
HTML elt(std::string tag, elements_t elements);
HTML elt(std::string tag, HTML e, elements_t elts);
//HTML preamble();
//HTML head(strings_t css_filenames, strings_t js_filenames);
void install_local_fonts(strings_t font_dirs, strings_t names, std::string output_dir);
elements_t page(
std::string title,
std::string page_title,
std::string output_dir,
std::string nav,
int max_level,
std::string toc,
std::string text,
std::string date,
std::string version,
std::string copyright,
std::string css,
strings_t css_filenames = {},
strings_t js_filenames = {},
strings_t local_fonts = {},
strings_t google_fonts = {},
std::string logo = {});
void add_title(elements_t& body, std::string title, std::string logo="");
void add_title(elements_t& body, std::string title, std::string logo);
void add_nav(elements_t& body, int max_level);
void add_text(elements_t& body, std::string text, std::string toc_text, bool text_only);
void add_bottom_spacer(elements_t& body);
void add_status_bar(elements_t& body, std::string date, std::string version, std::string copyright);
void add_page_cache(elements_t& body);
void add_js_links(elements_t& body, std::vector<std::string> js_filenames, std::string output_dir);
elements_t make_page(
elements_t body,
std::string page_title, std::string css,
std::vector<std::string> css_filenames,
std::vector<std::string> local_fonts,
std::vector<std::string> google_fonts);
bool tag_is_block_element(std::string tag);
std::string make_paragraphs(std::string html_text);
std::string minimize_css(std::string src);
std::string minimize_js(std::string src);
}

290
sks/target/html_util.py Normal file
View File

@@ -0,0 +1,290 @@
import os
import re
import sys
import textwrap
sys.path.append(f'{os.environ.get("KLAMMERTEXT_HOME")}/sks/kutil')
import kutil
_novalue = '__no_value__'
class E:
void_elements = set("area base br col embed hr img input link meta param source track wbr".split())
def __init__(self, tag):
self._tag = tag
self._sty = []
self._cls = []
self._body = []
self._attr = []
self.no_value = '__novalue__'
def sty(self, name, value=_novalue):
if name and value is _novalue:
self._sty.append(name.strip(';'))
elif value and value is not _novalue:
if type(value) is list:
value = " ".join(value)
self._sty.append('{}:{}'.format(name, value))
return self
def cls(self, c):
if c:
self._cls.append(c)
return self
def body(self, b, newline=True):
b = str(b)
#print("B:", b)
if newline and (b and b[0] == '<' or '\n' in b or len(b) > 72):
b = '\n' + b + '\n'
self._body.append(b)
return self
def attr(self, name, value):
self._attr.append('{}="{}"'.format(name, value))
return self
def data(self, name, value="true"):
attr = 'data-{}'.format(name)
attr = '{}="{}"'.format(attr, value)
self._attr.append(attr)
return self
def __str__(self):
def label(name, s):
return ' {}="{}"'.format(name, s) if s else ''
c = label('class', " ".join(self._cls))
s = "; ".join(self._sty).strip(' ;')
s = label('style', s + ';' if s else '')
a = " ".join(self._attr)
a = " " + a.strip() if a.strip() else ""
b = "\n".join(self._body)
tag = '{}{}{}{}'.format(self._tag, a, c, s).strip()
end_tag = '</{}>'.format(self._tag) if self._tag not in E.void_elements else ""
return '<{}>{}{}\n'.format(tag, b, end_tag)
def __repr__(self):
return self.__str__()
def __add__(self, e):
return str(self) + '\n' + str(e)
def str(self, newline=True):
result = str(self)
#print(f"begin: |{result}|")
if newline is None:
#result = result.strip("\n")
result = re.sub(">\n", ">", result)
#print("newline: None")
#print(f"str: |{result}|\n")
elif newline:
result = result.rstrip() + "\n"
return result
def html_indent(filename):
command = "(progn (setq make-backup-files nil) (mark-whole-buffer) "
command += "(indent-region (point-min) (point-max) nil) (save-buffer))"
os.system(f'emacs -nw -q --batch {filename} --eval "{command}" --kill 2> /dev/null')
def page(head_elt, body, js_files=[], load_jquery=True):
if True or js_files and load_jquery:
body += E("script").attr(
"src", "https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js").str()
for j in js_files:
body += E("script").attr("src", j).str()
#body_elt = E("body").attr("id", "body").body(f"\n\n{body}\n\n").str().strip()
body_elt = E("body").body(f"\n\n{body}\n\n").str().strip()
result = "<!DOCTYPE html>\n" + E("html").attr("lang", "en").body(head_elt + body_elt).str()
return result
def head(title, js_files=[], js_code="", css_files=[], css_code="",
include_fonts=True, google_font=[], favicon=None, load_jquery=True):
result = '<meta name="viewport" content="width=device-width, initial-scale=1">\n'
result += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n'
if favicon:
result += f'<link rel="icon" type="image/x-icon" href="{favicon}">\n'
result += "".join([E("link").attr("href", e).attr("rel", "stylesheet").str() for e in css_files])
fonts = None
css = ''
if not css_files and not css_code and include_fonts:
fonts, css = default_fonts()
fonts = google_font + fonts
if fonts:
result += E("link").attr("rel", "preconnect").attr("href", "https://fonts.gstatic.com").str()
#font = "|".join(google_font)
for font in fonts:
result += E("link").attr("href", f"https://fonts.googleapis.com/css?family={font}&display=swap") \
.attr("rel", "stylesheet").str()
#.attr("type", "text/css").str()
if css + css_code:
result += E("style").body(css + css_code).str()
if js_files and load_jquery:
result += E("script").attr(
"src", "https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js").str()
for j in js_files:
result += E("script").attr("src", j).str()
result += E("title").body(title).str().strip()
result = E("head").body(result).str()
return result
def default_fonts():
def font_name(spec):
return re.compile('[&|:]').split(re.sub(r'\+', ' ', spec))[0]
#serif = "PT+Serif:ital,wght@0,400;0,700;1,400"
#serif = "Manuale:ital,wght^@0,400;0,600;1,400"
serif = "Gentium+Book+Basic:ital,wght^@0,400;0,700;1,400"
monospace = "Roboto+Mono:ital,wght^@0,400;0,600;1,400"
sans = "PT+Sans:ital,wght^@0,400;0,700;1,400"
fonts = [serif, sans, monospace]
css = f'body {{ font-family: "{font_name(serif)}", serif; margin: 3rem; font-size: 14px; }}\n'
css += f'tt {{ font-family: "{font_name(monospace)}", sans-serif; font-size: 13px; }}\n'
css += f'.sans {{ font-family: "{font_name(sans)}", monospace; font-size: 14px; }}\n'
css += f'h1, h2, h3, h4 {{ font-family: "{font_name(sans)}", monospace; font-size: 20px; }}'
return fonts, css
def css_reldir():
return "css.ktdir"
"""
def css_basenames():
kdir = kutil.klammertext_dir()
result = []
for base, sks_dir in kutil.sks_dirs():
css_filename = f'{sks_dir}/{base}.css'
if os.path.exists(css_filename):
result.append(d)
return result
def css_source_files():
result = []
kdir = kutil.klammertext_dir()
for basename in css_basenames():
result.append([basename, f"{kdir}/sks/{basename}/{basename}.css"])
return result
"""
def css(styles, use_sks=False):
code = f'<style>\n{styles}\n</style>' if styles else ''
files = []
if use_sks:
for basename, filename in kutil.sks_files_of_type("css"):
files.append(f"{css_reldir()}/{basename}.css")
"""
kdir = kutil.klammertext_dir()
css_files = []
for basename in css_basenames():
files.append(f'{css_reldir()}/{basename}.css')
"""
return code, files
def element_tag(element):
tag_match = re.compile(r"<(\w+).*", re.S).match(str(element))
if not tag_match:
raise Exception(f"No tag found in {element}")
else:
return tag_match.group(1)
# 1. Relationship of element to caption
# 2. Relationship of element with/without caption to page
def font_class(font):
return {"r" : "", "i" : "ritalic", "t" : "monospace", "s" : "sanserif"}[font]
def add_caption(element, caption_label, number, caption_text,
font_symbol="i", hpos="center", side="bottom", as_string=True, font_size=.9):
tag = element_tag(element)
# Caption
caption = ""
if number == "true":
caption = kutil.caption_marker(caption_label, caption_text)
elif caption_text:
caption = caption_text
if caption:
# caption = kutil.protect_klammertext_special_characters(caption)
import font
caption = font.html_fontify(caption, font_symbol, font_size)
caption = E("div").cls("caption").body(caption, newline=False)
if caption_text and side == "bottom" or side == "top":
caption.cls("caption_text")
if caption:
if side == "left":
element.cls("lgap")
caption.cls("rgap")
elif side == "right":
element.cls("rgap")
caption.cls("lgap")
elif side == "bottom":
element.cls("bgap")
elif side == "top":
element.cls("tgap")
if side in {"left", "top"}:
element = str(caption) + "\n" + str(element)
else:
#element += "\n" + "\n".join(textwrap.wrap(str(caption), 80))
element += "\n" + re.sub("\n", " ", str(caption))
#print("-"*80)
#print(element)
#print("-"*80)
element = E("div").cls("caption_" + side).attr("data-label", caption_label).body(element)
result = element
result = E("div").cls("hpos_" + hpos).body(element)
if hpos != "center":
result = result.cls("hpos_margin")
if number:
result.cls("element_container")
if caption and hpos != "none":
if side == "bottom":
result.cls("mbot")
elif side == "top":
result.cls("mtop")
if as_string:
result = str(result) + "\n"
return result
if __name__ == '__main__':
elt = E("img").attr("src", "tree.jpg")
print(elt)
sys.exit(0)
import re
print(css("", True))
sys.exit(0)
def make_pars(s):
s = re.compile(r';(.*?)\.', re.S).sub(r';<tt>\1</tt>.', s)
s = re.sub('--', '&#8212;', s)
s = re.compile(r'"(\s)', re.S).sub(r'&#8221;\1', s)
s = re.compile('"', re.S).sub('&#8220;', s)
s = re.compile("'", re.S).sub('&#8217;', s)
s = re.sub("whale", "<i>whale</i>", s)
s = re.sub("Queequeg", "<b>Queegueg</b>", s)
s = re.compile(r"(CHAPTER.*?Rope)\.", re.S).sub(r'<span class="sans">\1</span>', s)
s = re.sub(" and ", ' <span class="sans"><b>and</b></span> ', s)
pars = re.compile('\n\n+').split(s)
result = "\n\n".join(["<p>{}</p>".format(e) for e in pars])
return result
google_font, css = default_fonts()
with open("../../../moby/moby-072.txt") as fp:
txt = make_pars(fp.read())
hd = head("Moby Dick - Chapter 72", css_code=css, google_font=google_font)
pg = page(hd, txt)
filename = "chapter_72.html"
with open(filename, "w") as fp:
fp.write(pg)
#html_indent(filename)

55
sks/target/js/target.js Normal file
View File

@@ -0,0 +1,55 @@
/*
function resize_grid_images() {
K.getv("[data-cell-width]").forEach(function (container) {
let cell_width = parseFloat(container.getAttribute("data-cell-width"));
let img = container.getElementsByTagName("img")[0];
console.log("resize_grid_image:")
console.log(img);
let text_width = K.width("#text");
//console.log( Math.trunc(.9 * text_width * cell_width));
K.width(img, Math.trunc(.9 * text_width * cell_width));
});
}
function resize_captions() {
console.log("resize_captions");
let caption_margins = 30; // See target.css .caption_text
K.getv(".caption_bottom").forEach(function (caption_container) {
//console.log("container_node:");
console.log(caption_container);
//if (!caption_container.parentNode.classList.contains("image_grid")) {
//console.log("resize_caption: " + caption_container);
let image = caption_container.children[0];
let caption = caption_container.children[1];
//console.log({caption_container, image});
//console.log("width: " + K.width(image));
K.width(caption, K.width(image) - caption_margins);
//}
});
K.getv(".caption_top").forEach(function (caption_container) {
console.log(caption_container);
let image = caption_container.children[1];
let caption = caption_container.children[0];
//console.log({caption_container, image});
//console.log("width: " + K.width(image));
K.width(caption, K.width(image) - caption_margins);
});
}
window.addEventListener("load", function (event) {
window.addEventListener(
"resize",
function (event) {
resize_captions();
//resize_grid_images();
});
// resize_grid_images();
});
window.addEventListener("load", function (event) {
window.dispatchEvent(new Event("resize"));
});
*/

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;
}

33
sks/target/latex_util.h Normal file
View File

@@ -0,0 +1,33 @@
#pragma once
#include <string>
#include <vector>
#include "font_resolve.h"
namespace latex {
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 = 1.0f);
}
void check_for_xelatex();
std::vector<std::string> find_latex_error_lines(const std::string& log_content);

116
sks/target/latex_util.py Normal file
View File

@@ -0,0 +1,116 @@
import re, glob
import kutil
import font
def tex_style():
result = ""
for basename, filename in kutil.sks_files_of_type("sty"):
with open(filename) as fp:
src = fp.read()
result += f"% {basename}.sty\n{src.strip()}\n\n"
return result
# \usepackage{quoting}
def environment(name, body, required=None, optional=None):
req = f"{{{required}}}" if required else ""
opt = f"[{optional}]" if optional else ""
result = f"""\\begin{{{name}}}{opt}{req}
{body}
\\end{{{name}}}
"""
return result
def page(title, subtitle, leading, pointsize, ragged_right, body):
title = f"\\textsf{{\\Large {title}}}" if title else ""
subtitle = f"\\vspace*{{4pt}}\\textsf{{\\large {subtitle}}}" if subtitle else ""
#leading = f"\\renewcommand{{\\baselinestretch}}{{{leading}}}"
leading = f"\\setstretch{{{leading}}}"
raggedright = "\\raggedright" if ragged_right else ""
if title or subtitle:
title = f"\\begin{{center}}{title} \\\\ {subtitle}\\end{{center}}\n\n"
result = f"""\\documentclass[a4paper,{pointsize}]{{article}}
{tex_style()}
{leading}
{raggedright}
\\begin{{document}}
\\thispagestyle{{empty}}
"""
result += title + body + "\n\\end{document}\n"
return result
TARGET_ID = 0
#def add_caption(element, label, numbered, caption_text, center, side, width, side_center, vmargin, caption_margin):
def minipage(content, width="\\textwidth", vertical="c", center=True, vmargin="", outline=False):
vertical = f"[{vertical}]" if vertical else ""
center = "\n\\centering" if center else ""
vmargin = f"\n\\vspace*{{{vmargin}}}" if vmargin else ""
result = f"""\\begin{{minipage}}{vertical}{{{width}}}{center}{vmargin}
{content}{vmargin}
\\end{{minipage}}"""
if outline:
result = f"\\fbox{{{result}}}"
return result
def caption_wrapper(element, hpos, bottom_margin=.67):
vmargin = f"{bottom_margin}\\baselineskip"
result = element
if hpos == "center":
result = minipage(element, vmargin=vmargin)
elif hpos == "left":
result = minipage("\\hfill" + element, vmargin=vmargin)
elif hpos == "right":
result = minipage(element + "\\hfill", vmargin=vmargin)
return result
def make_caption_text(number, label, text, font_symbol, font_size):
caption = None
if number:
caption = kutil.caption_marker(label, text)
elif text:
# caption = text # f"{{\\small \\it \\par {caption_text}}}"
caption = text
font_symbol = "r"
font_size = 1.2
caption = font.tex_fontify(caption, font_symbol, font_size)
return caption
def add_caption(element, caption_label, number, caption_text, latex_width,
hpos="center", side="bottom", font_symbol="i", font_size=.9):
top_margin = .75 if "includegraphics" in element else .5
caption = make_caption_text(number, caption_label, caption_text, font_symbol, font_size)
if caption:
if side in {"left", "right"}:
caption_margin_size = "12pt"
caption_margin = f"\\hspace{{{caption_margin_size}}}"
caption_width = f"\\textwidth - {latex_width} - {caption_margin_size}"
else:
#caption_margin = "\\vspace*{8pt}"
# caption_margin = "\\vstrut{.25\\baselineskip}\n"
pass
if side == "left":
element = minipage(minipage(caption, caption_width, center=False) + #, vmargin=top_margin) +
caption_margin +
minipage(element, latex_width))
elif side == "right":
element = minipage(minipage(element, latex_width) +
caption_margin +
minipage(caption, caption_width, center=False))
elif side == "bottom":
element = minipage(element + "\n\\vstrut{1.33\\baselineskip}" + caption,
# minipage(element, vertical="t") +
# caption_margin + "\n" +
# minipage(caption),
latex_width, vertical="t")
elif side == "top":
element = minipage(caption + "\n" + element,
# minipage(caption, vertical="t") +
# caption_margin + "\n" +
# minipage(element),
latex_width, vertical="t")
return caption_wrapper(element, hpos)

320
sks/target/phases.py Normal file
View File

@@ -0,0 +1,320 @@
import sys, os, re, textwrap, pprint
basedir = f'{os.environ["KLAMMERTEXT_HOME"]}/sks'
sys.path = [f"{basedir}/kutil"] + sys.path
sys.path = [f"{basedir}/target"] + sys.path
import kutil
import html_util
def tex_to_pdf(text):
print("in tex_to_pdf")
def expand_whitespace_markers(text, K=None):
def count(count_match):
count = int(count_match) if count_match else 1
return count
def replace_spaces(match):
return " " * count(match.group(1))
def replace_newlines(match):
return "\n" * count(match.group(1))
result = text
# result = re.sub(r"\s*#-\s*", "", result)
# space_pat = re.compile(r" *#\+(\d*) *", re.S)
# print("hits:", space_pat.findall(text))
# for p in space_pat.findall(text):
# print("FOUND:", p)
#
#result = re.sub(" ", "SPACE", result)
#result = re.sub("#/", "\n", result)
#result = re.sub("X", " ", result)
result = re.compile(r" *#- *", re.S).sub("", result)
result = re.compile(r" *#\+(\d*) *", re.S).sub(replace_spaces, result)
result = re.compile(r"\s*\#\/(\d*)\s*", re.S).sub(replace_newlines, result)
#print(result)
#sys.exit(0)
#print("expand_whitespace_markers", text, result)
return result
def handle_dashes(html_text, K=None):
def replace(match):
tag_start, text, tag_end = match.groups()
text = re.sub("__MDASH__", "---", text)
text = re.sub("__NDASH__", "--", text)
result = f"{tag_start}{text}{tag_end}"
return result
result = html_text
result = re.compile('(<span class="monospace">)(.*?)(</span>)', re.S).sub(replace, result)
result = re.sub("__MDASH__", "&#x2014;", result);
result = re.sub("__NDASH__", "&#x2013;", result);
return result
def add_tex_caption_numbers(tex_text, K=None):
chapter_pat = re.compile(r"\\section\\{")
index = {}
result = ""
chapter_number = 1
for line in tex_text.split("\n"):
if chapter_pat.search(line):
chapter_number += 1
reset_indices(index)
if kutil.caption_delimiter() in line:
before, caption_type, caption, after = line.split(kutil.caption_delimiter())
if index.get(caption_type) is None:
index[caption_type] = 1
n = index[caption_type]
number = f"{chapter_number}.{n}" if chapter_number != 0 else n
line = f"{before}{caption_type} {number} {caption}{after}"
index[caption_type] += 1
result += line + "\n"
return result
def remove_redundant_vspace(tex_text, K=None):
#print("remove_redundant_vspace")
rgx = re.compile(r"(\\vspace\*\{-[^}]+\})\s*\\vspace\*\{-[^}]+\}", re.S)
return rgx.sub(r"\1", tex_text)
def restore_backslash(tex_text, K=None):
#print("restore backslash")
#return re.compile(r"\^/").sub(r"\\", tex_text)
return re.sub("\6", "", tex_text)
#--------------------------------------------------------------------------------
def levels_to_section(levels):
result = ".".join([str(e) for e in levels])
result = re.sub(r"\.0", "", result)
return result
def chapter_title_span():
return '<span class="chapter_title">'
def add_html_section_numbers(html_text, K=None, start=0):
depth = 6
levels = [start-1] + ([0] * (depth-1))
section_pat = re.compile(r"(.*?)<h(\d)(.*?)>(.*?)</h\2>")
id_pat = re.compile(r'.*?id="([-\w]+)".*', re.S)
result = ""
id_number = 1
id_map = []
for line in html_text.split('\n'):
match = section_pat.match(line)
if match:
pre, level, attr, text = match.groups()
id_match = id_pat.fullmatch(attr)
if id_match:
id = id_match.group(1)
else:
id = f"id{id_number}"
attr = " " + f'id="{id}" {attr}'.strip()
id_number += 1
level = int(level)
levels[level-1] = levels[level-1] + 1
for i in range(level, depth):
levels[i] = 0
section = levels_to_section(levels)
line = f'{pre}<h{level}{attr}>{chapter_title_span()}{section}&#160;&#160;</span>{text}</h{level}>'
id_map.append([level, section, id, text])
result += line + "\n"
[print(e) for e in id_map]
return result, id_map
def make_html_table_of_contents(titles):
result = '<div id="_toc" class="toc-title">Table of contents</div>\n'
for level, section, id, text in titles:
result += f'<div class="level{level}"><a href="#{id}" class="level">{section} {text}</a></div>\n'
return result
def process_html_sections(html_text, K=None, start=1):
html, id_map = add_html_section_numbers(html_text)
toc = make_html_table_of_contents(id_map)
result = re.sub(r'(<div id="middle">)', rf"\1\n{toc}", html)
return result
def reset_indices(indices):
for key in indices.keys():
indices[key] = 1
def add_html_caption_numbers(html_text, K=None):
chapter_pat = re.compile(rf"{chapter_title_span()}(\d+)</span>")
index = {}
result = ""
chapter_number = ""
for line in html_text.split("\n"):
if '<span' in line:
match = chapter_pat.search(line)
print(match.groups())
if match and match.group(1) != "0":
chapter_number = f"{match.group(1)}."
reset_indices(index)
if kutil.caption_delimiter() in line:
before, caption_type, caption, after = line.split(kutil.caption_delimiter())
if index.get(caption_type) is None:
index[caption_type] = 1
n = index[caption_type]
line = f"{before}{caption_type} {chapter_number}{n} {caption}{after}"
index[caption_type] += 1
result += line + "\n"
return result
def escape_pre_angle_brackets(code, K=None):
def replace(match):
code = re.sub(r"<", "&#x003C;", match.group(1))
return f"<pre>{code}</pre>"
pat = re.compile("<pre>(.*?)</pre>", re.S)
return pat.sub(replace, code)
def block_elements():
return """
address article aside blockquote details dialog dd div dl dt fieldset
figcaption figure footer form h0 h1 h2 h3 h4 h5 h6 header hgroup hr li
main nav ol p section table ul td tr pre
""".strip().split()
def not_a_block(par):
if par.strip()[0] != "<":
return True
pat = re.compile(r'^</?([^\s>]+).*?>', re.S|re.M)
match = pat.match(par.strip())
return match.group(1) not in block_elements()
def make_paragraphs(html_text, K):
def replace(match):
before, content, after = match.groups()
result = ""
for par in [e.strip() for e in
re.compile(r"\n *(\n *)+", re.S).split(content)]:
if par.strip() and not_a_block(par):
#jpar = "\n".join(textwrap.wrap(par, width=80))
#result += f"\n<p>\n{jpar}\n</p>\n"
jpar = "\n".join(textwrap.wrap(f"<p>{par}</p>", width=80))
result += f"\n{jpar}\n"
else:
result += f"\n{par}\n"
result = re.compile(r"\s*</(\w+)>\n<\1>").sub(r"\n</\1>\n\n<\1>", result)
return f"{before}{result}{after}"
pat = re.compile(r'(.*?<div id="middle">)(.*?)(</div>\s*<div id="bottom">.*)', re.S)
match = pat.match(html_text)
result = pat.sub(replace, html_text)
result = re.compile(r"</td>\s+<td>", re.S).sub("</td>\n<td>", result)
result = re.compile(r"\n *(\n *)+", re.S).sub("\n\n", result)
return result
def indent_html(filename, K):
#print('indent_html')
html_util.html_indent(filename)
def copy_html_resources(filename, K):
output_dir = os.path.dirname(K._output_filename) or "."
css_dir = f"{output_dir}/{html_util.css_reldir()}"
kutil.make_dir_if_necessary(css_dir, delete_contents=True)
for basename, source in kutil.sks_files_of_type("css"):
command = f"cp {source} {css_dir}/{basename}.css"
os.system(command)
# def latex_escapes(latex_text):
# return latex_text
# result = latex_text
# result = re.sub('#', '\\#', result)
# result = re.sub('\^', '\\^', result)
# result += "LATEX"
# return result
# def restore_backslash(latex_text, K): # ?
# return latex_text
# print(latex_text)
# print("restore_backslash")
# result = latex_text
# result = re.sub('\b', r'\\b', result)
# result = re.sub(r'\\t', r'\\b', result)
# return result
def make_pdf_from_tex(filename, K, twice=True):
#debug_mode = int(K.K_verbose_level) > 1
verbose_level = int(K.K_verbose_level)
pathname = os.path.abspath(filename)
dirname, filename = os.path.split(pathname)
basename, ext = os.path.splitext(filename)
command = f"mv {pathname} {dirname}/{basename}.tex"
log_file = f"{dirname}/{basename}.log"
pdf_file = f"{dirname}/{basename}.pdf"
result = os.system(command)
#debug_mode = True
if verbose_level == 3:
remove_log = ''
else:
dbg_log = "/dev/null"
remove_log = ' >{} 2>&1'.format(dbg_log)
env_var = "KLAMMERTEXT_TEXLIVE_BIN"
texbin = os.environ.get(env_var)
if texbin is None:
msg = f"The directory of TeX Live commands must be defined by ${env_var}"
raise Exception(msg)
latex_command = f"{texbin}/xelatex"
#latex_command = f"{texbin}/pdflatex"
if not os.path.exists(latex_command):
raise Exception(f"LaTeX command not found: {latex_command}")
flags = "--halt-on-error"
#flags = "-file-line-error --shell-escape -halt-on-error -interaction nonstopmode -output-directory"
env = "export max_print_line=1000 ; export TEXINPUTS=${KLAMMERTEXT_HOME}/sks//: ;"
command = f"{env} cd {dirname} ; {latex_command} {flags} {basename}.tex {remove_log}"
#print(command)
result = os.system(command)
if result != 0:
os.system(f"tail -n 20 {log_file}")
msg = f"Error in LaTeX processing. See file {os.path.relpath(log_file)}"
#print(f"Error in LaTeX processing. See file {os.path.relpath(log_file)}")
raise Exception(msg)
# No error; is repetition necessary for links, etc? Check log for this.
# result = os.system(command)
# print(f"Wrote {os.path.relpath(pdf_file)}")
if twice:
result = os.system(command)
aux_files = 'aux out toc'.split()
if verbose_level < 2:
aux_files += 'tex log'.split()
for unused_ext in aux_files:
os.system('rm -rf {}/{}.{}'.format(dirname, basename, unused_ext))
def justify_blocks(text, K=None):
rgx = re.compile("\n\n+", re.S)
delim = '__DIVIDE__'
text = rgx.sub(delim, text)
result = ""
for par in text.split(delim):
#print(par)
if par[0] not in {' ', '['}:
par = "\n".join(textwrap.wrap(par, width=80))
result += par + "\n\n"
return result

1
sks/target/sty/table.sty Normal file
View File

@@ -0,0 +1 @@
\usepackage{longtable}

55
sks/target/target.k Normal file
View File

@@ -0,0 +1,55 @@
#[
The targets in the SKS use the LaTeX convention for converting ASCII
characters into standard typographical characters. This is done
by LaTeX by default; other targets must use the transforms parameter
of Parameter_set, as defined for the m_parameters variable of the
Target_set class. (See file target_set.cpp.)
The LaTeX transformations supported by the SKS are:
-- -> en-dash
--- -> em-dash
` -> open single quote
' -> close single quote
`` -> open double quote
'' -> close double quote
~ -> non-breaking space
]#
@@@target txt | Plain text with formatting | # This txt target is not full implemented in the SKS yet.
-- - |
--- -- |
`` " |
'' " |
` ` |
' ' |
~ ^0020^
:after_apply
phases.justify_blocks
@@@
@@@target html | HTML page
# :escape < &lt; & &amp; |
|
--- &mdash; |
-- &ndash; |
`` “ |
'' ” |
` |
' |
^^~ &tilde; |
~ &nbsp;
@@@
@@@target tex | LaTeX
:escape \ \textbackslash{} & \& { \{ } \} $ \$ % \% _ \_
@@@
@@@target pdf | PDF from LaTeX
:includes tex
:after_apply
^:cpp *KLAMMERTEXT_HOME*/sks/document/document tex_to_pdf
@@@