Unicode sequences, @typeface, and the font/typeface distinction
A run of Unicode code points now needs one pair of carets, not one pair
each: ^9053 53EF 9053^ renders the characters adjacently, and a single
character inside such a run stands for itself. An invalid code point is
an error naming the position that wrote it, rather than an apology
rendered into the finished document; noncharacters are refused alongside
surrogates; and a code point that lands on a Klammertext special is safe
through the re-reads that assemble a document. The character tables now
produce the precomposed form, so a diacritic written as ^a" and one typed
directly are the same text to a search or a copy-paste.
"kdesc -u" describes how to write a Unicode character, with tables of
glyphs beside the codes that produce them. It is new and not yet
complete: it predates the sequence form and the typeface klammer below.
A typeface and a style are no longer both called "font". A typeface is a
family resolved by name; a style code is the one letter naming a type
style. So @document takes :serif_typeface and its siblings, a table takes
:style, captions take :caption_style and :caption_size, the uniform scale
is :text_scale, and the command is "kdesc --typeface". This renames
arguments a document may already use; an old name is a located error
rather than a silent omission.
@typeface sets a passage in another typeface for the length of that
passage:
@typeface Noto Sans Egyptian Hieroglyphs | ^132AA 133CF^ :scale 1.5 @
Writing the klammer is the whole declaration -- the document resolves the
typeface, embeds it in what it produces, and carries its license along.
Two typefaces for scripts no text font covers now ship with Klammertext:
Noto Sans Egyptian Hieroglyphs and Noto Sans Cuneiform. Every typeface in
the distribution now carries its license file.
(from dev 61e598698891)
This commit is contained in:
1 parent
37b6ba1c4f
commit
af29bbf7ac
47 files changed
+1921
-135
No files matched your search
@@ -278,8 +278,8 @@ class Code(klammer_base.Klammer_base):
|
||||
return measure + L.add_caption(
|
||||
boxed, "Listing", self.number, self.caption, width,
|
||||
hpos=self.hpos, side=self.caption_side,
|
||||
font_symbol=self.caption_font,
|
||||
font_size=self.caption_font_size, offset=self.offset)
|
||||
font_symbol=self.caption_style,
|
||||
font_size=self.caption_size, offset=self.offset)
|
||||
return measure + L.caption_wrapper(boxed, self.hpos,
|
||||
offset=self.offset)
|
||||
|
||||
@@ -298,7 +298,7 @@ class Code(klammer_base.Klammer_base):
|
||||
if captioned:
|
||||
caption = L.make_caption_text(
|
||||
self.number, "Listing", self.caption,
|
||||
self.caption_font, self.caption_font_size)
|
||||
self.caption_style, self.caption_size)
|
||||
# \nobreak: a caption must not be separated from its listing by
|
||||
# a page break, even though the listing itself may break.
|
||||
block = (f"\\noindent\\hspace*{{{shift}}}"
|
||||
@@ -327,8 +327,8 @@ class Code(klammer_base.Klammer_base):
|
||||
if self.number or self.caption:
|
||||
return html_util.add_caption(
|
||||
listing, "Listing", self.number, self.caption,
|
||||
self.caption_font, self.hpos, self.caption_side, True,
|
||||
self.caption_font_size, offset=self.offset)
|
||||
self.caption_style, self.hpos, self.caption_side, True,
|
||||
self.caption_size, offset=self.offset)
|
||||
return html_util.hpos_container(listing, self.hpos, self.offset).str()
|
||||
|
||||
|
||||
|
||||
@@ -40,10 +40,10 @@
|
||||
:include_sks_js.bool true
|
||||
:include_fonts.bool true
|
||||
|
||||
:serif_font Crimson Pro
|
||||
:sans_font Open Sans
|
||||
:mono_font Inconsolata
|
||||
:font_scale 1.0
|
||||
:serif_typeface Crimson Pro
|
||||
:sans_typeface Open Sans
|
||||
:mono_typeface Inconsolata
|
||||
:text_scale 1.0
|
||||
|
||||
:landscape.bool.tex false
|
||||
:paper_size..tex a4paper
|
||||
|
||||
+143
-10
@@ -31,6 +31,10 @@ Document_class::Document_class(Machine& machine) : Klammer_base(machine)
|
||||
// show("document in main SKS");
|
||||
m_machine = machine;
|
||||
sloc = get("K_loc");
|
||||
// The klammer's own position, so an argument error points at the
|
||||
// @document application rather than at document_class.cpp: a
|
||||
// default-constructed Locator captures the C++ source location.
|
||||
loc = Locator::from_string(sloc);
|
||||
m_title = get("title");
|
||||
m_subtitle = get("subtitle");
|
||||
m_page_title = get("page_title");
|
||||
@@ -77,17 +81,17 @@ Document_class::Document_class(Machine& machine) : Klammer_base(machine)
|
||||
s = s.substr(1, s.size() - 2);
|
||||
return s;
|
||||
};
|
||||
m_serif_font = strip_quotes(get("serif_font"));
|
||||
m_sans_font = strip_quotes(get("sans_font"));
|
||||
m_mono_font = strip_quotes(get("mono_font"));
|
||||
m_font_scale = stof(get("font_scale"));
|
||||
m_serif_typeface = strip_quotes(get("serif_typeface"));
|
||||
m_sans_typeface = strip_quotes(get("sans_typeface"));
|
||||
m_mono_typeface = strip_quotes(get("mono_typeface"));
|
||||
m_text_scale = stof(get("text_scale"));
|
||||
|
||||
if (!m_serif_font.empty())
|
||||
m_resolved_serif = resolve_font(m_serif_font);
|
||||
if (!m_sans_font.empty())
|
||||
m_resolved_sans = resolve_font(m_sans_font);
|
||||
if (!m_mono_font.empty())
|
||||
m_resolved_mono = resolve_font(m_mono_font);
|
||||
if (!m_serif_typeface.empty())
|
||||
m_resolved_serif = resolve_font(m_serif_typeface, loc);
|
||||
if (!m_sans_typeface.empty())
|
||||
m_resolved_sans = resolve_font(m_sans_typeface, loc);
|
||||
if (!m_mono_typeface.empty())
|
||||
m_resolved_mono = resolve_font(m_mono_typeface, loc);
|
||||
paper_size = get("paper_size");
|
||||
landscape = strbool(get("landscape"), loc);
|
||||
leading = stof(get("leading"));
|
||||
@@ -207,6 +211,135 @@ void write_file(const std::string& filename, const std::string& contents, bool w
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
// Typeface markers (@typeface; see the marker note in sks/font/font.py)
|
||||
//
|
||||
// Hand-rolled scanning, no std::regex: these run over the whole assembled
|
||||
// document and libstdc++'s regex executor recurses per character (the
|
||||
// documented SIGSEGV hazard).
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
|
||||
const std::string typeface_tag { "__TYPEFACE__" };
|
||||
|
||||
// The next marker at or after `from`. Returns false when there is none.
|
||||
// A marker is __TYPEFACE__<name>__<scale>__ : the two fields are taken up
|
||||
// to the next "__", so neither may contain one (a typeface name with a
|
||||
// double underscore is not a thing, and the scale is a number).
|
||||
bool next_typeface_marker(const std::string& text, size_t from,
|
||||
size_t& start, size_t& end,
|
||||
std::string& name, std::string& scale)
|
||||
{
|
||||
start = text.find(typeface_tag, from);
|
||||
if (start == std::string::npos) return false;
|
||||
size_t name_at = start + typeface_tag.size();
|
||||
size_t name_end = text.find("__", name_at);
|
||||
if (name_end == std::string::npos) return false;
|
||||
size_t scale_at = name_end + 2;
|
||||
size_t scale_end = text.find("__", scale_at);
|
||||
if (scale_end == std::string::npos) return false;
|
||||
name = text.substr(name_at, name_end - name_at);
|
||||
scale = text.substr(scale_at, scale_end - scale_at);
|
||||
end = scale_end + 2;
|
||||
return true;
|
||||
}
|
||||
|
||||
// The fontspec command name for the nth distinct (typeface, scale) pair.
|
||||
// A TeX command name may hold only letters, and a typeface name may hold
|
||||
// anything -- so the pairs are NUMBERED rather than transliterated, and
|
||||
// the number is turned into letters. Nothing outside this file needs to
|
||||
// know the rule: the klammer leaves a marker and this replaces it, so
|
||||
// there is no second implementation to keep in step.
|
||||
std::string typeface_command(size_t index)
|
||||
{
|
||||
std::string suffix {};
|
||||
do {
|
||||
suffix = char('a' + (index % 26)) + suffix;
|
||||
index /= 26;
|
||||
} while (index > 0);
|
||||
return "\\ktface" + suffix;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Document_class::collect_typefaces(const std::string& text)
|
||||
{
|
||||
size_t pos = 0, start = 0, end = 0;
|
||||
std::string name {}, scale {};
|
||||
while (next_typeface_marker(text, pos, start, end, name, scale)) {
|
||||
std::pair<std::string, std::string> entry { name, scale };
|
||||
if (std::find(m_extra_typefaces.begin(), m_extra_typefaces.end(), entry)
|
||||
== m_extra_typefaces.end()) {
|
||||
m_extra_typefaces.push_back(entry);
|
||||
// Resolve ONCE per distinct typeface: resolve_font() throws a
|
||||
// located error naming the available typefaces if the store
|
||||
// does not have it, which is the whole error path for a
|
||||
// misspelled or uninstalled typeface.
|
||||
bool seen = false;
|
||||
for (const auto& rf : m_resolved_extra)
|
||||
if (rf.family_name == name) seen = true;
|
||||
if (!seen) {
|
||||
// What a @typeface klammer asked for, and what the store
|
||||
// resolved it to: a decision the user cannot read off
|
||||
// their own input, so "-v 1".
|
||||
Resolved_font rf = resolve_font(name, loc);
|
||||
(void)K::log(1, "Typeface \"" + name + "\" resolved to:",
|
||||
rf.font_dir);
|
||||
m_resolved_extra.push_back(rf);
|
||||
}
|
||||
}
|
||||
pos = end;
|
||||
}
|
||||
}
|
||||
|
||||
std::string Document_class::strip_typeface_markers(const std::string& text)
|
||||
{
|
||||
std::string result {};
|
||||
size_t pos = 0, start = 0, end = 0;
|
||||
std::string name {}, scale {};
|
||||
while (next_typeface_marker(text, pos, start, end, name, scale)) {
|
||||
result += text.substr(pos, start - pos);
|
||||
pos = end;
|
||||
}
|
||||
result += text.substr(pos);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Document_class::resolve_typeface_markers(const std::string& text)
|
||||
{
|
||||
std::string result {};
|
||||
size_t pos = 0, start = 0, end = 0;
|
||||
std::string name {}, scale {};
|
||||
while (next_typeface_marker(text, pos, start, end, name, scale)) {
|
||||
result += text.substr(pos, start - pos);
|
||||
auto entry = std::make_pair(name, scale);
|
||||
auto it = std::find(m_extra_typefaces.begin(), m_extra_typefaces.end(), entry);
|
||||
if (it != m_extra_typefaces.end())
|
||||
result += typeface_command(it - m_extra_typefaces.begin());
|
||||
pos = end;
|
||||
}
|
||||
result += text.substr(pos);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Document_class::typeface_preamble()
|
||||
{
|
||||
std::stringstream ss {};
|
||||
for (size_t i = 0; i < m_extra_typefaces.size(); i++) {
|
||||
const auto& [name, scale] = m_extra_typefaces[i];
|
||||
const Resolved_font* rf = nullptr;
|
||||
for (const auto& f : m_resolved_extra)
|
||||
if (f.family_name == name) rf = &f;
|
||||
if (!rf) continue;
|
||||
// \newfontfamily declares a switch command, unlike \setmainfont,
|
||||
// which changes the document's font permanently.
|
||||
ss << latex::font_command("newfontfamily" + typeface_command(i),
|
||||
*rf, stof(scale));
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/*
|
||||
void output_msg(const std::string& msg)
|
||||
{
|
||||
|
||||
@@ -121,14 +121,35 @@ public:
|
||||
|
||||
strings_t m_font_dirs {};
|
||||
|
||||
std::string m_serif_font {};
|
||||
std::string m_sans_font {};
|
||||
std::string m_mono_font {};
|
||||
float m_font_scale = 1.0f;
|
||||
std::string m_serif_typeface {};
|
||||
std::string m_sans_typeface {};
|
||||
std::string m_mono_typeface {};
|
||||
float m_text_scale = 1.0f;
|
||||
Resolved_font m_resolved_serif {};
|
||||
Resolved_font m_resolved_sans {};
|
||||
Resolved_font m_resolved_mono {};
|
||||
|
||||
// Typefaces requested by @typeface klammers inside the document, in
|
||||
// first-appearance order, each with the scale it was asked for. A
|
||||
// klammer cannot declare a typeface itself (see font.py's
|
||||
// typeface_marker), so it leaves a marker and @document collects them
|
||||
// here, resolves and localizes each, declares it, and replaces every
|
||||
// marker with the target's inline switch.
|
||||
// SYNC: typeface_marker() in sks/font/font.py builds this marker.
|
||||
std::vector<std::pair<std::string, std::string>> m_extra_typefaces {};
|
||||
std::vector<Resolved_font> m_resolved_extra {};
|
||||
// Collect the (typeface, scale) pairs of `text` into
|
||||
// m_extra_typefaces, resolving each typeface once. Returns nothing;
|
||||
// the markers are replaced by the per-target functions below.
|
||||
void collect_typefaces(const std::string& text);
|
||||
// html: the marker carries no inline switch (the span holds the family
|
||||
// name), so each is replaced by nothing.
|
||||
std::string strip_typeface_markers(const std::string& text);
|
||||
// tex: each distinct (typeface, scale) gets a \newfontfamily command in
|
||||
// the preamble; the marker is replaced by that command.
|
||||
std::string resolve_typeface_markers(const std::string& text);
|
||||
std::string typeface_preamble();
|
||||
|
||||
std::string prolog {};
|
||||
std::string paper_size {};
|
||||
bool m_two_column = false;
|
||||
|
||||
@@ -93,24 +93,28 @@ strings_t Document_class::resolved_font_names()
|
||||
add(m_resolved_serif);
|
||||
add(m_resolved_sans);
|
||||
add(m_resolved_mono);
|
||||
// Typefaces a @typeface klammer asked for: each needs its own <link>
|
||||
// to the localized css, which is what carries the @font-face.
|
||||
for (const auto& rf : m_resolved_extra)
|
||||
add(rf);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string Document_class::font_definitions()
|
||||
{
|
||||
std::stringstream ss {};
|
||||
if (!m_serif_font.empty() || !m_sans_font.empty() || !m_mono_font.empty()) {
|
||||
if (!m_serif_typeface.empty() || !m_sans_typeface.empty() || !m_mono_typeface.empty()) {
|
||||
// Family names are quoted: an unquoted name with a digit-initial
|
||||
// word ("Source Sans 3") is invalid CSS, and a font-family using
|
||||
// var() with such a value computes to inherit, silently losing
|
||||
// the font.
|
||||
ss << ":root {\n";
|
||||
if (!m_serif_font.empty())
|
||||
ss << " --serif: \"" << m_serif_font << "\", serif;\n";
|
||||
if (!m_sans_font.empty())
|
||||
ss << " --sans-serif: \"" << m_sans_font << "\", sans-serif;\n";
|
||||
if (!m_mono_font.empty())
|
||||
ss << " --monospace: \"" << m_mono_font << "\", monospace;\n";
|
||||
if (!m_serif_typeface.empty())
|
||||
ss << " --serif: \"" << m_serif_typeface << "\", serif;\n";
|
||||
if (!m_sans_typeface.empty())
|
||||
ss << " --sans-serif: \"" << m_sans_typeface << "\", sans-serif;\n";
|
||||
if (!m_mono_typeface.empty())
|
||||
ss << " --monospace: \"" << m_mono_typeface << "\", monospace;\n";
|
||||
ss << "}\n";
|
||||
}
|
||||
// Font-size normalization across families is no longer emitted from
|
||||
@@ -120,11 +124,11 @@ std::string Document_class::font_definitions()
|
||||
// factors did from build-time OS/2 metrics, but applied to EVERY family
|
||||
// switch instead of the four CSS sites that remembered to multiply.
|
||||
// The metric extraction in the font store remains (the tex path and
|
||||
// kdesc --font still use it).
|
||||
// kdesc --typeface still use it).
|
||||
// Global font scale: applied to body font-size
|
||||
if (m_font_scale != 1.0f) {
|
||||
if (m_text_scale != 1.0f) {
|
||||
char buf[16];
|
||||
std::snprintf(buf, sizeof(buf), "%.4f", m_font_scale);
|
||||
std::snprintf(buf, sizeof(buf), "%.4f", m_text_scale);
|
||||
ss << "body { font-size: " << buf << "rem; }\n";
|
||||
}
|
||||
return ss.str();
|
||||
@@ -743,6 +747,22 @@ std::string Document_class::html()
|
||||
std::string output_directory = create_html_output_directories();
|
||||
save_string_input_as_file();
|
||||
process_input_files();
|
||||
// @typeface markers are collected AFTER processing (a klammer cannot
|
||||
// declare a typeface itself) but BEFORE the page is assembled, so the
|
||||
// localized files and the <link> to each typeface's css are in place
|
||||
// when make_*_html_page() runs. The markers survive into the page
|
||||
// CACHE deliberately: a cached file must still declare its typefaces
|
||||
// on a later run, so the stripping happens here, on the assembled
|
||||
// components, not before write_to_cache().
|
||||
for (auto& [basename, text] : m_file_components) {
|
||||
collect_typefaces(text);
|
||||
}
|
||||
for (const auto& rf : m_resolved_extra) {
|
||||
install_resolved_font(rf, output_directory);
|
||||
}
|
||||
for (auto& [basename, text] : m_file_components) {
|
||||
text = strip_typeface_markers(text);
|
||||
}
|
||||
auto headings = insert_section_numbers();
|
||||
insert_section_numbers("___NUM2___", false);
|
||||
insert_html_caption_numbers();
|
||||
|
||||
@@ -72,6 +72,14 @@ std::string Document_class::tex()
|
||||
}
|
||||
latex_text = join(lines, "\n");
|
||||
latex_text = string_replace(latex_text, "___AT___", "^@");
|
||||
// @typeface: collect what the document asked for, declare each as a
|
||||
// \newfontfamily switch command in the preamble, and replace every
|
||||
// marker with its command. The declarations are appended to the
|
||||
// prolog, which latex::page emits AFTER \setmainfont -- so fontspec is
|
||||
// loaded and \newfontfamily is valid there.
|
||||
collect_typefaces(latex_text);
|
||||
latex_text = resolve_typeface_markers(latex_text);
|
||||
prolog += typeface_preamble();
|
||||
std::string structure_name =
|
||||
m_structure == docstruct_t::book ? "book" :
|
||||
m_structure == docstruct_t::article ? "article" : "plain";
|
||||
@@ -81,5 +89,5 @@ std::string Document_class::tex()
|
||||
point_size, ragged_right,
|
||||
cover, landscape, latex_text,
|
||||
m_resolved_serif, m_resolved_sans, m_resolved_mono,
|
||||
m_font_scale);
|
||||
m_text_scale);
|
||||
}
|
||||
+43
-16
@@ -2,28 +2,44 @@
|
||||
@@@state font_dirs :desc Directories containing local fonts @@@
|
||||
|
||||
|
||||
@@@argtype font |
|
||||
an abbreivation for a font
|
||||
@@@argtype style_code |
|
||||
a style code: one letter naming a type style
|
||||
:pattern r^|i^|b^|s^|t^|c
|
||||
@@@
|
||||
|
||||
@@@argtype font_list |
|
||||
a list of font symbols. One or more of 'r' (roman), 'i' (italic),
|
||||
@@@argtype style_code_list |
|
||||
a list of style codes. One or more of 'r' (roman), 'i' (italic),
|
||||
'b' (bold), 's' (sans-serif), 't' (typewriter or monospace) or 'c'
|
||||
(code) for each cell in a row. If there are fewer font symbols than
|
||||
cells in a row, the last symbol is repeated. Extra symbols are
|
||||
(code) for each cell in a row. If there are fewer style codes than
|
||||
cells in a row, the last code is repeated. Extra codes are
|
||||
ignored.
|
||||
:pattern ('font'^|\s)+
|
||||
:pattern ('style_code'^|\s)+
|
||||
:python_cast (lambda s : s.split())
|
||||
:default r
|
||||
@@@
|
||||
|
||||
@@font.k fontname : Switch to an available font for PDF output. @@
|
||||
#[
|
||||
@font was retired 2026-08-25 and replaced by @typeface. It switched the
|
||||
main font PERMANENTLY (a bare \setmainfont, no group) for tex, and for
|
||||
html and txt its body was "*fontname*" -- so it printed the NAME of the
|
||||
font as document text instead of setting anything. @typeface is the
|
||||
temporary, all-target replacement.
|
||||
]#
|
||||
|
||||
@@font.html :: *fontname* @@
|
||||
@@font.tex :: \setmainfont[Ligatures=TeX]{*fontname*} @@
|
||||
# @@font.tex : \setmainfont[Ligatures=TeX,Numbers=Lining]{*fontname*} @@
|
||||
@@font.txt :: *fontname* @@
|
||||
@@typeface.k name | text :scale.float 1.0 :
|
||||
A temporary change of typeface for *text*: the typeface *name* is
|
||||
resolved in the Klammertext font store (the directories of
|
||||
KLAMMERTEXT_FONTS, then the distribution's own), and the document
|
||||
declares and localizes it automatically -- writing this klammer is the
|
||||
only declaration needed. *scale* multiplies the surrounding text size,
|
||||
which a script whose glyphs are drawn small usually wants.
|
||||
|
||||
The change ends with *text*: nothing after it is affected. Under the
|
||||
txt target a typeface cannot be expressed at all, so *text* is emitted
|
||||
unchanged.
|
||||
@@
|
||||
|
||||
@@typeface.html,tex,txt :: @eval font.Typeface(K) @ @@
|
||||
|
||||
@@r.k text : Use the current Roman font for *text* @@
|
||||
@@r.html :: <span class="plain">*text*</span> @@
|
||||
@@ -74,10 +90,21 @@
|
||||
\setmainfont{Garamond Libre}
|
||||
@@
|
||||
|
||||
@@cjk.k text : Switch to font that supports CJK for *text* @@
|
||||
@@cjk.html :: *cjk* @@
|
||||
@@cjk.tex :: { #- @font Noto Sans CJK SC @ *cjk* #- } @@
|
||||
@@cjk.txt :: *cjk* @@
|
||||
#[
|
||||
Before 2026-08-25 every one of these bodies substituted "*cjk*" while the
|
||||
declaration named the parameter "text", so @cjk raised "Variable "cjk" not
|
||||
defined" under EVERY target -- it had never worked. Rewritten on
|
||||
@typeface, which is what it was reaching for.
|
||||
|
||||
The typeface it names is NOT part of the distribution (a usable Noto Sans
|
||||
CJK is some 16-20 MB per weight, and Chinese line breaking needs more
|
||||
than a typeface -- xeCJK for the tex path), so this klammer errors with
|
||||
the list of available typefaces until one is installed with
|
||||
"kdesc --typeface install". That is the honest behaviour: the klammer
|
||||
states what it needs and the store reports what it has.
|
||||
]#
|
||||
@@cjk.k text : Set *text* in a typeface that supports CJK @@
|
||||
@@cjk.html,tex,txt :: @typeface Noto Sans CJK SC | *text* @ @@
|
||||
|
||||
@@fraktur.k s : Set text *s* in Fraktur @@
|
||||
@@fraktur.tex :: \textfrak{*s*} @@
|
||||
|
||||
@@ -23,6 +23,63 @@ class Subsuper(klammer_base.Klammer_base):
|
||||
else:
|
||||
return f"${self.base}^{self.script}$"
|
||||
|
||||
# The typeface-declaration marker. A klammer cannot declare a typeface
|
||||
# itself: in html the @font-face must be in the page's css and the files
|
||||
# localized into <output>/fonts/, and in tex the \newfontfamily must be in
|
||||
# the preamble -- both are @document's, and @document runs AFTER the
|
||||
# klammers it contains (application is post-order). So @typeface leaves
|
||||
# this marker where the typeface is needed, and @document collects the
|
||||
# markers, resolves and localizes each typeface, declares it, and replaces
|
||||
# the marker with whatever inline switch the target needs (a font-switch
|
||||
# command for tex, nothing for html, which carries the family name in the
|
||||
# span's own style). Same shape as the __CAPTION__ markers.
|
||||
#
|
||||
# SYNC: typeface_marker_rgx in sks/document/document_class.h -- a C++/Python
|
||||
# pair, so the constant cannot be single-sourced the way a .k/.py pair can
|
||||
# (TODO #47); change both or neither.
|
||||
def typeface_marker(name, scale):
|
||||
return f"__TYPEFACE__{name}__{scale}__"
|
||||
|
||||
class Typeface(klammer_base.Klammer_base):
|
||||
"""A temporary typeface change (@typeface). See sks/font/font.k."""
|
||||
|
||||
def __init__(self, K):
|
||||
super().__init__(K)
|
||||
|
||||
def html(self):
|
||||
# font-size-adjust is switched OFF for the span: the document
|
||||
# normalizes families to a common x-height (font.css), which is
|
||||
# meaningless for a script whose glyphs have no x-height at all --
|
||||
# hieroglyphs, cuneiform -- and would resize them unpredictably.
|
||||
# The family name is quoted -- an unquoted digit-initial word
|
||||
# ("Source Sans 3") is invalid CSS -- with SINGLE quotes, because
|
||||
# this sits inside a double-quoted html attribute.
|
||||
style = (f"font-family: '{self.name}', serif; "
|
||||
f"font-size-adjust: none")
|
||||
if float(self.scale) != 1.0:
|
||||
style += f"; font-size: {float(self.scale)}em"
|
||||
# The STYLE is markup, so its typographically active characters are
|
||||
# hidden: the html target's transforms turn a single quote into a
|
||||
# curly one, which broke the css font-family value. The TEXT is
|
||||
# writer content and is deliberately left alone -- its quotes and
|
||||
# dashes should be transformed.
|
||||
return (typeface_marker(self.name, self.scale)
|
||||
+ f'<span style="{klammer_base.hide_typographic(style)}">'
|
||||
+ f'{self.text}</span>')
|
||||
|
||||
def tex(self):
|
||||
# The marker becomes the font-switch command; the group is what
|
||||
# makes the change temporary (the retired @font emitted a bare
|
||||
# \setmainfont, which changed the font for the rest of the
|
||||
# document).
|
||||
return "{" + typeface_marker(self.name, self.scale) + " " + self.text + "}"
|
||||
|
||||
def txt(self):
|
||||
# A typeface cannot be expressed in plain text. Declared in the
|
||||
# .k description, so this is documented behaviour rather than a
|
||||
# silent per-target divergence.
|
||||
return self.text
|
||||
|
||||
def html_fontify(text, font_symbol, font_size):
|
||||
cls = {"r" : "",
|
||||
"i" :"ritalic",
|
||||
|
||||
+2
-2
@@ -102,8 +102,8 @@ class Image(klammer_base.Klammer_base):
|
||||
self.caption += self.file_error_message
|
||||
|
||||
result = html_util.add_caption(
|
||||
result, "Figure", self.number, self.caption, self.caption_font,
|
||||
self.hpos, self.caption_side, False, self.caption_font_size,
|
||||
result, "Figure", self.number, self.caption, self.caption_style,
|
||||
self.hpos, self.caption_side, False, self.caption_size,
|
||||
offset=self.offset)
|
||||
|
||||
result = E("div").body(result).cls("image_margin")
|
||||
|
||||
@@ -15,7 +15,7 @@ class Kargs:
|
||||
def __init__(self, target, K, **kwargs):
|
||||
self.__dict__ = kwargs
|
||||
self.center = False
|
||||
self.caption_font = "i"
|
||||
self.caption_style = "i"
|
||||
self.caption_side = "bottom"
|
||||
self.caption_side_center = False
|
||||
self.Image_search_path = K.Image_search_path
|
||||
@@ -40,7 +40,7 @@ class Kargs:
|
||||
self.border = False
|
||||
self.vmargin = False
|
||||
self.abswidth = 0.0
|
||||
self.caption_font_size = .9
|
||||
self.caption_size = .9
|
||||
|
||||
def img(K, target, basename, caption, width):
|
||||
result = image.Image(
|
||||
|
||||
+2
-2
@@ -163,8 +163,8 @@ other structures. The ^:offset is used only if the ^:hpos value is "left" or
|
||||
:caption
|
||||
:number.bool true
|
||||
:caption_side.side
|
||||
:caption_font.font i
|
||||
:caption_font_size.float .9
|
||||
:caption_style.style_code i
|
||||
:caption_size.float .9
|
||||
: Arguments that define a caption for a block element.
|
||||
@@
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@
|
||||
@@ol.k list_items.rest :cmp.bool false :initial.int 1 : Ordered list, with numbered list elements. @@
|
||||
@@ol.html,tex,txt :: @eval list.List(K, "ol") eval@ @@
|
||||
|
||||
@@define.k descriptions.rest(2) :font i : A list of words or phrases and their definitions or descriptions @@
|
||||
@@define.k descriptions.rest(2) :style.style_code i : A list of words or phrases and their definitions or descriptions @@
|
||||
@@define.html,tex :: @eval list.Define(K) eval@ @@
|
||||
|
||||
@@columns.k items :n.int 2 : Create two or more columns from the text @@
|
||||
|
||||
+2
-2
@@ -66,7 +66,7 @@ class Define(klammer_base.Klammer_base):
|
||||
"b" : f"<b>{s}</b>",
|
||||
"t" : f'<span class="monospace">{s}</span>',
|
||||
"c" : f'<span class="monospace">{s}</span>',
|
||||
"s" : f'<span class="sansserif">{s}</span>'}[self.font]
|
||||
"s" : f'<span class="sansserif">{s}</span>'}[self.style]
|
||||
result = "\n\n".join([f"<dt>{fontify(term)}</dt>\n<dd>{definition}</dd>"
|
||||
for term,definition in self.items])
|
||||
result = f"<dl>\n{result}\n</dl>"
|
||||
@@ -79,7 +79,7 @@ class Define(klammer_base.Klammer_base):
|
||||
'b' : r'\normalfont\bfseries',
|
||||
't' : r'\normalfont\ttfamily',
|
||||
'c' : r'\normalfont\ttfamily',
|
||||
's' : r'\normalfont\sffamily'}[self.font]
|
||||
's' : r'\normalfont\sffamily'}[self.style]
|
||||
return f"{font} {s}"
|
||||
# The "\hfill\newline" was to force the description to the next line even if the item was small:
|
||||
# result = "\n\n".join([f"\\item[{{{fontify(term)}}}]\\hfill\n\\newline {definition}"
|
||||
|
||||
+2
-2
@@ -206,8 +206,8 @@
|
||||
:grid.bool false
|
||||
:cell_hpos.cell_hpos
|
||||
:justify.table_justify
|
||||
:header_font.font i
|
||||
:font.font_list
|
||||
:header_style.style_code i
|
||||
:style.style_code_list
|
||||
:colspan.table_span
|
||||
:rowspan.table_span
|
||||
:calc.table_calc
|
||||
|
||||
+14
-14
@@ -84,7 +84,7 @@ class Table(klammer_base.Klammer_base):
|
||||
self.apply_formats()
|
||||
self.cell_hpos = extend(parse_hpos(self.K_target, self.cell_hpos), self.row_size)
|
||||
#self.cell_hpos = self.cell_hpos.split(";")
|
||||
self.font = extend(self.font, self.row_size)
|
||||
self.style = extend(self.style, self.row_size)
|
||||
# In html, a 'fit' column mixed with sized columns must be clamped
|
||||
# to its widest entry (tex's \widthof semantics) or it absorbs the
|
||||
# window width: its cells get nowrap (class Wfit; see table.css).
|
||||
@@ -445,7 +445,7 @@ class Table(klammer_base.Klammer_base):
|
||||
self.column_widths = []
|
||||
for col_i in range(len(self.cells[0])):
|
||||
longest = ""
|
||||
longest_font = "r"
|
||||
longest_style = "r"
|
||||
for row_i in range(len(self.cells)):
|
||||
cell = self.cells[row_i][col_i]
|
||||
# A colspan anchor's text spans several columns and must
|
||||
@@ -456,9 +456,9 @@ class Table(klammer_base.Klammer_base):
|
||||
longest_in_line = lines[-1]
|
||||
if len(longest_in_line) > len(longest):
|
||||
longest = longest_in_line
|
||||
longest_font = cell.font
|
||||
if longest_font != "r":
|
||||
longest = font.tex_fontify(longest, longest_font, 1.0)
|
||||
longest_style = cell.font
|
||||
if longest_style != "r":
|
||||
longest = font.tex_fontify(longest, longest_style, 1.0)
|
||||
self.column_widths.append(longest)
|
||||
|
||||
# ---- :hpos -------------------------------------------------------------
|
||||
@@ -500,9 +500,9 @@ class Table(klammer_base.Klammer_base):
|
||||
for cell_i, cell in enumerate(row):
|
||||
rspan = self.span_count(self.s_rowspan, cell_i, row_i)
|
||||
cspan = self.span_count(self.s_colspan, row_i, cell_i)
|
||||
font = self.font[cell_i]
|
||||
font = self.style[cell_i]
|
||||
if row_i == 0 and self.header:
|
||||
font = self.header_font
|
||||
font = self.header_style
|
||||
# A span anchor's right and bottom borders come from the
|
||||
# boundary at the END of the merged region.
|
||||
right_i = cell_i + max(cspan, 1)
|
||||
@@ -589,9 +589,9 @@ class Table(klammer_base.Klammer_base):
|
||||
if self.number or self.caption:
|
||||
result = html_util.add_caption(
|
||||
result, "Table", self.number, self.caption,
|
||||
self.caption_font, hpos=self.hpos,
|
||||
self.caption_style, hpos=self.hpos,
|
||||
side=self.caption_side,
|
||||
font_size=self.caption_font_size, max_width="100%",
|
||||
font_size=self.caption_size, max_width="100%",
|
||||
offset=self.offset)
|
||||
else:
|
||||
result.attr("style", "max-width: 100%")
|
||||
@@ -606,9 +606,9 @@ class Table(klammer_base.Klammer_base):
|
||||
if table_width:
|
||||
result.attr("style", f"{layout}width: 100%")
|
||||
result = html_util.add_caption(
|
||||
result, "Table", self.number, self.caption, self.caption_font,
|
||||
result, "Table", self.number, self.caption, self.caption_style,
|
||||
hpos=self.hpos, side=self.caption_side,
|
||||
font_size=self.caption_font_size, width=table_width,
|
||||
font_size=self.caption_size, width=table_width,
|
||||
offset=self.offset)
|
||||
else:
|
||||
# An uncaptioned table still gets the position container, so
|
||||
@@ -757,7 +757,7 @@ class Table(klammer_base.Klammer_base):
|
||||
def make_caption(self):
|
||||
column_count = str(len(self.rows[0]))
|
||||
caption = latex_util.make_caption_text(
|
||||
self.number, "Table", self.caption, self.caption_font, self.caption_font_size)
|
||||
self.number, "Table", self.caption, self.caption_style, self.caption_size)
|
||||
result = "\\multicolumn{" + column_count + "}{c}{\\parbox{\\tableboxwidth}{"
|
||||
result += "\\vspace*{8pt}\\centering\\small\\em "
|
||||
result += caption
|
||||
@@ -836,8 +836,8 @@ class Table(klammer_base.Klammer_base):
|
||||
result = latex_util.add_caption(
|
||||
result, "Table", self.number, self.caption, "\\tableboxwidth",
|
||||
hpos=self.hpos, side=self.caption_side,
|
||||
font_symbol=self.caption_font,
|
||||
font_size=self.caption_font_size, offset=self.offset)
|
||||
font_symbol=self.caption_style,
|
||||
font_size=self.caption_size, offset=self.offset)
|
||||
else:
|
||||
result = latex_util.caption_wrapper(result, self.hpos, offset=self.offset)
|
||||
result = measure + result
|
||||
|
||||
+18
-18
@@ -163,10 +163,10 @@ namespace latex {
|
||||
const std::string& cover,
|
||||
bool landscape,
|
||||
const std::string& body,
|
||||
Resolved_font serif_font,
|
||||
Resolved_font sans_font,
|
||||
Resolved_font mono_font,
|
||||
float font_scale)
|
||||
Resolved_font serif_typeface,
|
||||
Resolved_font sans_typeface,
|
||||
Resolved_font mono_typeface,
|
||||
float text_scale)
|
||||
{
|
||||
(void)K::log(3);
|
||||
bool book_format = (structure == "book");
|
||||
@@ -202,10 +202,10 @@ namespace latex {
|
||||
// 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_xh = serif_typeface.xheight_ratio;
|
||||
float serif_ch = serif_typeface.capheight_ratio;
|
||||
float serif_avg = (serif_xh + serif_ch) / 2.0f;
|
||||
auto font_scale_fn = [&](const Resolved_font& other) -> float {
|
||||
auto text_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
|
||||
@@ -213,21 +213,21 @@ namespace latex {
|
||||
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);
|
||||
// Global text_scale multiplies all font sizes uniformly
|
||||
float gs = (text_scale != 1.0f) ? text_scale : 0.0f;
|
||||
float sans_s = text_scale_fn(sans_typeface);
|
||||
float mono_s = text_scale_fn(mono_typeface);
|
||||
// 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;
|
||||
sans_s = (sans_s > 0.0f) ? sans_s * text_scale : text_scale;
|
||||
mono_s = (mono_s > 0.0f) ? mono_s * text_scale : text_scale;
|
||||
}
|
||||
ss << font_command("setmainfont", serif_font, gs);
|
||||
ss << font_command("setsansfont", sans_font, sans_s);
|
||||
ss << font_command("setmonofont", mono_font, mono_s);
|
||||
ss << font_command("setmainfont", serif_typeface, gs);
|
||||
ss << font_command("setsansfont", sans_typeface, sans_s);
|
||||
ss << font_command("setmonofont", mono_typeface, mono_s);
|
||||
|
||||
// Scale leading proportionally with font_scale
|
||||
float effective_leading = leading * ((font_scale != 1.0f) ? font_scale : 1.0f);
|
||||
// Scale leading proportionally with text_scale
|
||||
float effective_leading = leading * ((text_scale != 1.0f) ? text_scale : 1.0f);
|
||||
ss << "\n\\setstretch{" << effective_leading << "}\n";
|
||||
if (ragged_right)
|
||||
ss << "\\raggedright\n";
|
||||
|
||||
+12
-4
@@ -6,6 +6,14 @@
|
||||
|
||||
namespace latex {
|
||||
|
||||
// A fontspec declaration: "\<command>[Path=...,Scale=...]{<regular>}".
|
||||
// The command is "setmainfont"/"setsansfont"/"setmonofont" for the
|
||||
// document's three role typefaces, and "newfontfamily\ktface<n>" for a
|
||||
// typeface a @typeface klammer asked for (a switch command rather than a
|
||||
// permanent change).
|
||||
std::string font_command(const std::string& command, const Resolved_font& font,
|
||||
float scale);
|
||||
|
||||
std::string page(const std::string& structure,
|
||||
const std::string& title,
|
||||
const std::string& subtitle,
|
||||
@@ -23,10 +31,10 @@ std::string page(const std::string& structure,
|
||||
const std::string& cover,
|
||||
bool landscape,
|
||||
const std::string& body,
|
||||
Resolved_font serif_font = {},
|
||||
Resolved_font sans_font = {},
|
||||
Resolved_font mono_font = {},
|
||||
float font_scale = 1.0f);
|
||||
Resolved_font serif_typeface = {},
|
||||
Resolved_font sans_typeface = {},
|
||||
Resolved_font mono_typeface = {},
|
||||
float text_scale = 1.0f);
|
||||
}
|
||||
|
||||
void check_for_xelatex();
|
||||
|
||||
Reference in new issue
Block a user