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
+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)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user