txt section numbering, @typeface :middle/:lift and html scale, NewGardinerOmni, html :leading (from dev 9fa469dd74ed)
Section headings under txt are numbered inside a @document only; @typeface gains :middle and :lift and its :scale now means the same in html and pdf; NewGardinerOmni ships (OFL 1.1, Mark-Jan Nederhof) and @egyptian / @chinese replace @cjk; @table :leading and :colsep reach html; kdesc --typeface help names the flag; @document.txt is a Python class. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
0ead40f272
commit
63956c00e5
26 files changed
+436
-172
No files matched your search
@@ -51,7 +51,7 @@ are regenerated on each release — patches cannot be merged directly.
|
|||||||
Report problems (or send patches) to the author; accepted changes are
|
Report problems (or send patches) to the author; accepted changes are
|
||||||
applied to the development tree and appear in a following snapshot.
|
applied to the development tree and appear in a following snapshot.
|
||||||
|
|
||||||
This snapshot was assembled from development commit `d6993110e9b3`.
|
This snapshot was assembled from development commit `9fa469dd74ed`.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
+9
-9
@@ -17,18 +17,18 @@ static void font_usage()
|
|||||||
{
|
{
|
||||||
std::cout <<
|
std::cout <<
|
||||||
"Font maintenance commands:\n"
|
"Font maintenance commands:\n"
|
||||||
" --font List the available fonts\n"
|
" --typeface List the available fonts\n"
|
||||||
" --font list The same\n"
|
" --typeface list The same\n"
|
||||||
" --font samples <output-dir> Write a sample page of the available fonts\n"
|
" --typeface samples <output-dir> Write a sample page of the available fonts\n"
|
||||||
" to <output-dir>/index.html\n"
|
" to <output-dir>/index.html\n"
|
||||||
" --font samples <input-dir> <output-dir>\n"
|
" --typeface samples <input-dir> <output-dir>\n"
|
||||||
" Write a sample page for the (not yet\n"
|
" Write a sample page for the (not yet\n"
|
||||||
" installed) font files under <input-dir>\n"
|
" installed) font files under <input-dir>\n"
|
||||||
" --font install <input-dir> Install the font files found under\n"
|
" --typeface install <input-dir> Install the font files found under\n"
|
||||||
" <input-dir> into the user font directory\n"
|
" <input-dir> into the user font directory\n"
|
||||||
" --font install <input-dir> <output-dir>\n"
|
" --typeface install <input-dir> <output-dir>\n"
|
||||||
" Install into <output-dir> instead\n"
|
" Install into <output-dir> instead\n"
|
||||||
" --font help This description\n"
|
" --typeface help This description\n"
|
||||||
"\n"
|
"\n"
|
||||||
"Fonts are searched in the directories of the KLAMMERTEXT_FONTS\n"
|
"Fonts are searched in the directories of the KLAMMERTEXT_FONTS\n"
|
||||||
"environment variable (colon-separated; default $HOME/.klammertext/fonts)\n"
|
"environment variable (colon-separated; default $HOME/.klammertext/fonts)\n"
|
||||||
@@ -141,7 +141,7 @@ static void font_command(const strings_t& words)
|
|||||||
} else if (verb == "help") {
|
} else if (verb == "help") {
|
||||||
font_usage();
|
font_usage();
|
||||||
} else {
|
} else {
|
||||||
std::cout << "Unrecognized font command: --font " << join(words, " ")
|
std::cout << "Unrecognized typeface command: --typeface " << join(words, " ")
|
||||||
<< "\n\n";
|
<< "\n\n";
|
||||||
font_usage();
|
font_usage();
|
||||||
}
|
}
|
||||||
@@ -256,7 +256,7 @@ int main(int argc, char* argv[])
|
|||||||
describe_rewrite_patterns();
|
describe_rewrite_patterns();
|
||||||
}
|
}
|
||||||
|
|
||||||
// The --font subcommands operate on the Klammertext font store
|
// The --typeface subcommands operate on the Klammertext font store
|
||||||
// (infrastructure) and load no klammer set.
|
// (infrastructure) and load no klammer set.
|
||||||
if (args.given("typeface")) {
|
if (args.given("typeface")) {
|
||||||
font_command(option_words(args, "typeface"));
|
font_command(option_words(args, "typeface"));
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: 'NewGardinerOmni';
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
src: url('newgardineromni/Regular.ttf') format('truetype');
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
Copyright (c) 2020, Mark-Jan Nederhof https://mjn.host.cs.st-andrews.ac.uk,
|
||||||
|
with Reserved Font Name NewGardiner.
|
||||||
|
|
||||||
|
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||||
|
This license is copied below, and is also available with a FAQ at:
|
||||||
|
http://scripts.sil.org/OFL
|
||||||
|
|
||||||
|
|
||||||
|
-----------------------------------------------------------
|
||||||
|
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||||
|
-----------------------------------------------------------
|
||||||
|
|
||||||
|
PREAMBLE
|
||||||
|
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||||
|
development of collaborative font projects, to support the font creation
|
||||||
|
efforts of academic and linguistic communities, and to provide a free and
|
||||||
|
open framework in which fonts may be shared and improved in partnership
|
||||||
|
with others.
|
||||||
|
|
||||||
|
The OFL allows the licensed fonts to be used, studied, modified and
|
||||||
|
redistributed freely as long as they are not sold by themselves. The
|
||||||
|
fonts, including any derivative works, can be bundled, embedded,
|
||||||
|
redistributed and/or sold with any software provided that any reserved
|
||||||
|
names are not used by derivative works. The fonts and derivatives,
|
||||||
|
however, cannot be released under any other type of license. The
|
||||||
|
requirement for fonts to remain under this license does not apply
|
||||||
|
to any document created using the fonts or their derivatives.
|
||||||
|
|
||||||
|
DEFINITIONS
|
||||||
|
"Font Software" refers to the set of files released by the Copyright
|
||||||
|
Holder(s) under this license and clearly marked as such. This may
|
||||||
|
include source files, build scripts and documentation.
|
||||||
|
|
||||||
|
"Reserved Font Name" refers to any names specified as such after the
|
||||||
|
copyright statement(s).
|
||||||
|
|
||||||
|
"Original Version" refers to the collection of Font Software components as
|
||||||
|
distributed by the Copyright Holder(s).
|
||||||
|
|
||||||
|
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||||
|
or substituting -- in part or in whole -- any of the components of the
|
||||||
|
Original Version, by changing formats or by porting the Font Software to a
|
||||||
|
new environment.
|
||||||
|
|
||||||
|
"Author" refers to any designer, engineer, programmer, technical
|
||||||
|
writer or other person who contributed to the Font Software.
|
||||||
|
|
||||||
|
PERMISSION & CONDITIONS
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||||
|
redistribute, and sell modified and unmodified copies of the Font
|
||||||
|
Software, subject to the following conditions:
|
||||||
|
|
||||||
|
1) Neither the Font Software nor any of its individual components,
|
||||||
|
in Original or Modified Versions, may be sold by itself.
|
||||||
|
|
||||||
|
2) Original or Modified Versions of the Font Software may be bundled,
|
||||||
|
redistributed and/or sold with any software, provided that each copy
|
||||||
|
contains the above copyright notice and this license. These can be
|
||||||
|
included either as stand-alone text files, human-readable headers or
|
||||||
|
in the appropriate machine-readable metadata fields within text or
|
||||||
|
binary files as long as those fields can be easily viewed by the user.
|
||||||
|
|
||||||
|
3) No Modified Version of the Font Software may use the Reserved Font
|
||||||
|
Name(s) unless explicit written permission is granted by the corresponding
|
||||||
|
Copyright Holder. This restriction only applies to the primary font name as
|
||||||
|
presented to the users.
|
||||||
|
|
||||||
|
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||||
|
Software shall not be used to promote, endorse or advertise any
|
||||||
|
Modified Version, except to acknowledge the contribution(s) of the
|
||||||
|
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||||
|
permission.
|
||||||
|
|
||||||
|
5) The Font Software, modified or unmodified, in part or in whole,
|
||||||
|
must be distributed entirely under this license, and must not be
|
||||||
|
distributed under any other license. The requirement for fonts to
|
||||||
|
remain under this license does not apply to any document created
|
||||||
|
using the Font Software.
|
||||||
|
|
||||||
|
TERMINATION
|
||||||
|
This license becomes null and void if any of the above conditions are
|
||||||
|
not met.
|
||||||
|
|
||||||
|
DISCLAIMER
|
||||||
|
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||||
|
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||||
|
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||||
|
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||||
|
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||||
|
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||||
Binary file not shown.
@@ -175,6 +175,15 @@ strings_t available_font_families()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool font_installed(const std::string& name)
|
||||||
|
{
|
||||||
|
const std::string wanted = name_to_dirname(name);
|
||||||
|
for (const std::string& family : available_font_families())
|
||||||
|
if (name_to_dirname(family) == wanted)
|
||||||
|
return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static void extract_font_metrics(Resolved_font& font)
|
static void extract_font_metrics(Resolved_font& font)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ std::vector<std::string> installed_font_dirs();
|
|||||||
std::string default_font_dir();
|
std::string default_font_dir();
|
||||||
// Every installable font family found across those directories.
|
// Every installable font family found across those directories.
|
||||||
std::vector<std::string> available_font_families();
|
std::vector<std::string> available_font_families();
|
||||||
|
// Whether the store can resolve NAME -- written as the family name or as
|
||||||
|
// the store's directory name, since both resolve. The question a klammer
|
||||||
|
// asks before it names a typeface that is not shipped (a CJK face), so it
|
||||||
|
// can render the text plainly and say how to install the font instead of
|
||||||
|
// failing the document. Never throws; resolve_font() is the error path.
|
||||||
|
bool font_installed(const std::string& name);
|
||||||
// The Locator is the DOCUMENT location to blame when the typeface is not
|
// The Locator is the DOCUMENT location to blame when the typeface is not
|
||||||
// installed: without it the "not installed" error cites font_store.cpp,
|
// installed: without it the "not installed" error cites font_store.cpp,
|
||||||
// a source location no user can act on (the no-.cpp-location rule).
|
// a source location no user can act on (the no-.cpp-location rule).
|
||||||
|
|||||||
+52
-25
@@ -4,6 +4,7 @@
|
|||||||
#include "character.h"
|
#include "character.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "show.h"
|
#include "show.h"
|
||||||
|
#include "font_store.h"
|
||||||
|
|
||||||
// extern "C" is needed for dlsym name lookup. Returning std::string from
|
// extern "C" is needed for dlsym name lookup. Returning std::string from
|
||||||
// C-linkage functions is technically non-standard but works correctly on
|
// C-linkage functions is technically non-standard but works correctly on
|
||||||
@@ -21,8 +22,11 @@ std::string diacritics_examples([[maybe_unused]] Machine& machine)
|
|||||||
//std::cout << machine;
|
//std::cout << machine;
|
||||||
|
|
||||||
std::stringstream kt {};
|
std::stringstream kt {};
|
||||||
kt << "@table :caption Diacritics (with typical base characters) :cell_hpos c c l |\n"
|
|
||||||
<< "@i-Displayed | @i-Written | @i-Name ||\n";
|
kt << "@s2 Roman letters with a diacritical mark @\n\n";
|
||||||
|
|
||||||
|
kt << "@table :caption Diacritics (with typical base characters) :cell_hpos c c l :leading 1.2 |\n"
|
||||||
|
<< "Displayed | Written | Name ||\n";
|
||||||
|
|
||||||
for (char symbol : diacritic_symbols_order) {
|
for (char symbol : diacritic_symbols_order) {
|
||||||
std::string sym { symbol };
|
std::string sym { symbol };
|
||||||
@@ -43,8 +47,10 @@ extern "C"
|
|||||||
std::string extended_latin_examples([[maybe_unused]] Machine& machine)
|
std::string extended_latin_examples([[maybe_unused]] Machine& machine)
|
||||||
{
|
{
|
||||||
std::stringstream kt {};
|
std::stringstream kt {};
|
||||||
kt << "@table :caption Extended Latin characters and ligatures :cell_hpos c c l |\n"
|
kt << "@s2 Non-Roman characters @\n\n";
|
||||||
<< "@i-Displayed | @i-Written | @i-Name ||\n";
|
|
||||||
|
kt << "@table :caption Extended Latin characters and ligatures :cell_hpos c c l :leading 1.2 |\n"
|
||||||
|
<< "Displayed | Written | Name ||\n";
|
||||||
for (const std::string& symbol : extended_latin_symbols) {
|
for (const std::string& symbol : extended_latin_symbols) {
|
||||||
auto [code, name] = extended_latin[symbol];
|
auto [code, name] = extended_latin[symbol];
|
||||||
std::string coded = "^" + symbol + "^";
|
std::string coded = "^" + symbol + "^";
|
||||||
@@ -59,8 +65,10 @@ extern "C"
|
|||||||
std::string pinyin_examples([[maybe_unused]] Machine& machine)
|
std::string pinyin_examples([[maybe_unused]] Machine& machine)
|
||||||
{
|
{
|
||||||
std::stringstream kt {};
|
std::stringstream kt {};
|
||||||
kt << "@table :caption Mandarin pinyin tones (using vowel ``a'') :cell_hpos c c l |\n"
|
kt << "@s2 Chinese Pinyin syllable accents @\n\n";
|
||||||
<< " @i-Displayed | @i-Written | @i-Name ||\n";
|
|
||||||
|
kt << "@table :caption Mandarin pinyin tones (using vowel ``a'') :cell_hpos c c l :leading 1.2 |\n"
|
||||||
|
<< "Displayed | Written | Name ||\n";
|
||||||
|
|
||||||
for (auto [symbol, codename] : pinyin_tones) {
|
for (auto [symbol, codename] : pinyin_tones) {
|
||||||
auto [code, name] = codename;
|
auto [code, name] = codename;
|
||||||
@@ -79,33 +87,24 @@ std::string unicode_examples([[maybe_unused]] Machine& machine)
|
|||||||
<< "To specify a wide variety of different letter forms, the Unicode Standard "
|
<< "To specify a wide variety of different letter forms, the Unicode Standard "
|
||||||
<< "defines a large set of \"code points\" organized by groups. To include a "
|
<< "defines a large set of \"code points\" organized by groups. To include a "
|
||||||
<< "Unicode character in Klammertext, you surround its hexadecimal number with "
|
<< "Unicode character in Klammertext, you surround its hexadecimal number with "
|
||||||
<< "the \"^^\" character. For example, the Cyrillic captial letter \"I\" is \"И\", "
|
<< "the \"^^\" character. For example, the Cyrillic capital letter \"I\" is \"И\", "
|
||||||
<< "included in Klammertext as ^^0418^^.\n\n"
|
<< "included in Klammertext as @t ^^0418^^ @.\n\n"
|
||||||
<< "To search for Unicode characters by their description, see: @nl@\n"
|
<< "To search for Unicode characters by their description, see: "
|
||||||
<< "@link https://www.fileformat.info/info/unicode/char/search.htm @\n\n";
|
<< "@link https://www.fileformat.info/info/unicode/char/search.htm @\n\n";
|
||||||
|
|
||||||
kt << "@table :caption Examples of Chinese characters :cell_hpos c c l |\n"
|
kt << "@s3 Egyptian hieroglyphs @\n\n";
|
||||||
<< " Character | Written | Pinyin ||\n";
|
|
||||||
|
|
||||||
for (auto chr : dao) {
|
kt << "@table :caption Examples of Egyptian hieroglyphs :cell_hpos c c c l :leading 1.1 |\n"
|
||||||
std::string code = "^" + chr[1] + "^";
|
|
||||||
kt << process_unicode_codepoint(code) << " | "
|
|
||||||
<< "^" << code << "^" << " | "
|
|
||||||
<< chr[0] << " ||\n";
|
|
||||||
}
|
|
||||||
kt << "table@\n\n";
|
|
||||||
|
|
||||||
|
|
||||||
kt << "@table :caption Examples of Egyptian hieroglyphs :cell_hpos c c c l |\n"
|
|
||||||
<< "Hieroglyph | Written | Sound | Gardiner ||\n";
|
<< "Hieroglyph | Written | Sound | Gardiner ||\n";
|
||||||
|
|
||||||
// The body font carries no hieroglyphs, so the glyph names the
|
// The body font carries no hieroglyphs, so the glyph names the
|
||||||
// typeface it needs (shipped in fnt/); @document declares and localizes
|
// typeface it needs (shipped in fnt/); @document declares and localizes
|
||||||
// it. Under txt @typeface passes the text through unchanged.
|
// it. Under txt @typeface passes the text through unchanged.
|
||||||
for (auto glyph : ptolemy) {
|
for (auto glyph : ptolemy) {
|
||||||
kt << "@typeface noto-sans-egyptian-hieroglyphs :scale 1.5 | "
|
std::string code = "^" + glyph[2] + "^";
|
||||||
<< process_unicode_codepoint("^" + glyph[2] + "^") << " @ | "
|
kt << "@egyptian "
|
||||||
<< "^^" + glyph[2] + "^^ | "
|
<< process_unicode_codepoint(code) << " @ | "
|
||||||
|
<< "@t ^'" << code << "'^ @ | "
|
||||||
<< glyph[1] << " | "
|
<< glyph[1] << " | "
|
||||||
<< glyph[0] << " ||\n";
|
<< glyph[0] << " ||\n";
|
||||||
}
|
}
|
||||||
@@ -114,6 +113,34 @@ std::string unicode_examples([[maybe_unused]] Machine& machine)
|
|||||||
<< "the Unicode codepoint in the second column of the \"List of hieroglyphs\" "
|
<< "the Unicode codepoint in the second column of the \"List of hieroglyphs\" "
|
||||||
<< "table is the number in the ^^...^^ code used by Klammertext.\n\n";
|
<< "table is the number in the ^^...^^ code used by Klammertext.\n\n";
|
||||||
|
|
||||||
|
|
||||||
|
kt << "@s3 Chinese characters @\n\n";
|
||||||
|
|
||||||
|
bool chinese = font_installed("Noto Serif SC");
|
||||||
|
std::string target = machine.m_state.get("K_target").m_value;
|
||||||
|
const bool txt_or_ansi = target == "txt"|| target == "ansi";
|
||||||
|
|
||||||
|
if (txt_or_ansi || chinese) {
|
||||||
|
kt << "@table :caption Examples of Chinese characters :cell_hpos c c l :leading 1.2 |\n"
|
||||||
|
<< " Character | Written | Pinyin ||\n";
|
||||||
|
|
||||||
|
for (auto chr : dao) {
|
||||||
|
std::string code = "^" + chr[1] + "^";
|
||||||
|
kt << "@chinese " << process_unicode_codepoint(code) << " @ | "
|
||||||
|
<< "@t ^'" << code << "'^ @" << " | "
|
||||||
|
<< chr[0] << " ||\n";
|
||||||
|
}
|
||||||
|
kt << "table@\n\n";
|
||||||
|
}
|
||||||
|
if (!txt_or_ansi && !chinese) {
|
||||||
|
kt << "To display Chinese characters, you need to install a Chinese typeface, "
|
||||||
|
"which Klammertext does not ship. "
|
||||||
|
"Download @i Noto Serif SC @ from Google Fonts, unzip it, and run: \n\n"
|
||||||
|
" @t kdesc ^-^-typeface install @ ~ @i the-installed-font-directory @ \n\n"
|
||||||
|
"The font goes into your "
|
||||||
|
"@t ^~/.klammertext/fonts @ directory or the first directory in the list of "
|
||||||
|
"directories defined by the @t KLAMMERTEXT_FONTS @ environment variable.\n\n";
|
||||||
|
}
|
||||||
return kt.str();
|
return kt.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +163,7 @@ std::string argtype_definitions(Machine& machine)
|
|||||||
auto filename = argtype.m_loc.m_filename;
|
auto filename = argtype.m_loc.m_filename;
|
||||||
if (contains(filename, "argtype_registry")) {
|
if (contains(filename, "argtype_registry")) {
|
||||||
std::string fill(name_width - name.size(), '~');
|
std::string fill(name_width - name.size(), '~');
|
||||||
kt << "\n@c " << name << " c@ @t " << fill << " t@ @i Pattern: i@ @c "
|
kt << "\n@c " << name << " c@ @t " << fill << " t@ @i Pattern: i@ ~ @c "
|
||||||
<< argtype.m_symbolic_pattern << " c@ |\n"
|
<< argtype.m_symbolic_pattern << " c@ |\n"
|
||||||
<< capitalize(trim(argtype.m_desc)) << " ||\n";
|
<< capitalize(trim(argtype.m_desc)) << " ||\n";
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ document.so : $(LOCAL_OBJECTS) $(SKS_OBJECTS) $(LIBRARY) document.cpp | deps
|
|||||||
document.cpp -o $@ $(ALL_OBJECTS) -lklammertext $(LDLIBS)
|
document.cpp -o $@ $(ALL_OBJECTS) -lklammertext $(LDLIBS)
|
||||||
|
|
||||||
clean :
|
clean :
|
||||||
rm -f $(LOCAL_OBJECTS) $(LOCAL_DEPFILES) *.so *~ __pycache__
|
rm -f $(LOCAL_OBJECTS) $(LOCAL_DEPFILES) *.so *~
|
||||||
|
# document_txt.py is imported at render time (2026-09-18), so this
|
||||||
|
# directory now exists here; rm -f refused it and failed every clean.
|
||||||
|
rm -rf __pycache__
|
||||||
|
|
||||||
# Include generated dependency files (if they exist)
|
# Include generated dependency files (if they exist)
|
||||||
-include $(LOCAL_DEPFILES)
|
-include $(LOCAL_DEPFILES)
|
||||||
+4
-15
@@ -62,21 +62,10 @@
|
|||||||
: Top-level document structure
|
: Top-level document structure
|
||||||
@@
|
@@
|
||||||
|
|
||||||
@@document.html,tex :: @eval :cpp *KLAMMERTEXT_HOME*/sks/document/document document @ @@
|
@@document.html,tex ::
|
||||||
|
@eval :cpp *KLAMMERTEXT_HOME*/sks/document/document document @
|
||||||
# The following definition of @document.txt is not adequate (it ignores :files,
|
@@
|
||||||
# for example), but it enables tests of other txt klammers for now. When one of
|
|
||||||
# the arguments is empty, the justifcation postprocess should remove multiple
|
|
||||||
# lines, but __VSPACE__ inserts a space character, which prevents the lines
|
|
||||||
# removal from the justification function. You can also see the extra space
|
|
||||||
# before the *subtitle* value.
|
|
||||||
|
|
||||||
@@document.txt ::
|
@@document.txt ::
|
||||||
@eval "*title*".upper() @ @nl@
|
@eval document_txt.Document(K) @
|
||||||
*subtitle*
|
|
||||||
|
|
||||||
*author* @nl@
|
|
||||||
*date*
|
|
||||||
|
|
||||||
*text*
|
|
||||||
@@
|
@@
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import kutil
|
|
||||||
import html_util
|
|
||||||
import tex_util
|
|
||||||
|
|
||||||
class Document(klammer_base.Klammer_base):
|
|
||||||
def __init__(self, K):
|
|
||||||
super().__init__(K)
|
|
||||||
|
|
||||||
def html(self):
|
|
||||||
if self.include_css == "true":
|
|
||||||
css_code, css_files = html_util.css(self.css, self._sks_enabled)
|
|
||||||
head = html_util.head(self.title, css_files=css_files, css_code=css_code)
|
|
||||||
else:
|
|
||||||
head = head = html_util.head(self.title, include_fonts=self.include_fonts == "true")
|
|
||||||
return html_util.page(head, self.body)
|
|
||||||
|
|
||||||
def tex(self):
|
|
||||||
ragged_right = self.ragged_right in {"true", "True", "1"}
|
|
||||||
return tex_util.page(
|
|
||||||
self.title, self.subtitle, self.leading, self.pointsize, ragged_right, self.body)
|
|
||||||
|
|
||||||
def txt(self):
|
|
||||||
return self.body
|
|
||||||
@@ -340,9 +340,14 @@ std::string Document_class::typeface_preamble()
|
|||||||
std::stringstream ss {};
|
std::stringstream ss {};
|
||||||
for (size_t i = 0; i < m_extra_typefaces.size(); i++) {
|
for (size_t i = 0; i < m_extra_typefaces.size(); i++) {
|
||||||
const auto& [name, scale] = m_extra_typefaces[i];
|
const auto& [name, scale] = m_extra_typefaces[i];
|
||||||
|
// Compared by directory name, as collect_typefaces() compares: a
|
||||||
|
// @typeface may write the family name or the store's directory
|
||||||
|
// name, and family_name holds what the css declares (2026-09-12),
|
||||||
|
// so a name-for-name comparison missed the directory spelling and
|
||||||
|
// left \ktface<n> undefined in the .tex (found 2026-09-17).
|
||||||
const Resolved_font* rf = nullptr;
|
const Resolved_font* rf = nullptr;
|
||||||
for (const auto& f : m_resolved_extra)
|
for (const auto& f : m_resolved_extra)
|
||||||
if (f.family_name == name) rf = &f;
|
if (f.dir_name == name_to_dirname(name)) rf = &f;
|
||||||
if (!rf) continue;
|
if (!rf) continue;
|
||||||
// \newfontfamily declares a switch command, unlike \setmainfont,
|
// \newfontfamily declares a switch command, unlike \setmainfont,
|
||||||
// which changes the document's font permanently.
|
// which changes the document's font permanently.
|
||||||
@@ -351,22 +356,3 @@ std::string Document_class::typeface_preamble()
|
|||||||
}
|
}
|
||||||
return ss.str();
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
void output_msg(const std::string& msg)
|
|
||||||
{
|
|
||||||
std::cout << red;
|
|
||||||
(void)K::log(1, msg);
|
|
||||||
std::cout << black;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Document_class::create_directory(const std::string& directory)
|
|
||||||
{
|
|
||||||
if (write_files) {
|
|
||||||
fs::create_directories(directory);
|
|
||||||
} else {
|
|
||||||
output_msg("Output directory: " + directory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
#include <iomanip>
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
@@ -118,6 +119,24 @@ std::string Document_class::font_definitions()
|
|||||||
ss << " --sans-serif: \"" << m_resolved_sans.family_name << "\", sans-serif;\n";
|
ss << " --sans-serif: \"" << m_resolved_sans.family_name << "\", sans-serif;\n";
|
||||||
if (!m_mono_typeface.empty())
|
if (!m_mono_typeface.empty())
|
||||||
ss << " --monospace: \"" << m_resolved_mono.family_name << "\", monospace;\n";
|
ss << " --monospace: \"" << m_resolved_mono.family_name << "\", monospace;\n";
|
||||||
|
// The factor a @typeface span's font-size is multiplied by (2026-09-18).
|
||||||
|
// font.css renders the body at "font-size-adjust: ex-height 0.5", so
|
||||||
|
// the text the reader sees is 0.5 / x-height times its nominal size
|
||||||
|
// (EB Garamond, x-height 0.400: 1.25). A @typeface span switches
|
||||||
|
// the adjust OFF (a script with no x-height cannot be matched), so
|
||||||
|
// its ":scale N" was N times the NOMINAL size -- about a fifth
|
||||||
|
// smaller against the visible text than the same N in pdf, where
|
||||||
|
// fontspec's Scale= is against text rendered at nominal size.
|
||||||
|
// Folding the body's enlargement in here makes :scale mean the
|
||||||
|
// same thing in both targets. Emitted as a custom property the
|
||||||
|
// span multiplies in with calc(), so a fragment rendered without
|
||||||
|
// @document falls back to 1. SYNC: the anchor 0.5 is font.css's.
|
||||||
|
const float xheight_anchor = 0.5f;
|
||||||
|
if (m_resolved_serif.xheight_ratio > 0.0f) {
|
||||||
|
std::stringstream factor {};
|
||||||
|
factor << std::setprecision(4) << xheight_anchor / m_resolved_serif.xheight_ratio;
|
||||||
|
ss << " --typeface_scale: " << factor.str() << ";\n";
|
||||||
|
}
|
||||||
ss << "}\n";
|
ss << "}\n";
|
||||||
}
|
}
|
||||||
// Font-size normalization across families is no longer emitted from
|
// Font-size normalization across families is no longer emitted from
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import kutil
|
||||||
|
import klammer_base
|
||||||
|
import phases
|
||||||
|
|
||||||
|
class Document(klammer_base.Klammer_base):
|
||||||
|
def __init__(self, K):
|
||||||
|
super().__init__(K)
|
||||||
|
if self.files:
|
||||||
|
raise Exception("The :files option is not defined for the 'txt' target")
|
||||||
|
|
||||||
|
def html(self):
|
||||||
|
raise Exception("Implemented by document.cpp")
|
||||||
|
|
||||||
|
def tex(self):
|
||||||
|
raise Exception("Implemented by document.cpp")
|
||||||
|
|
||||||
|
def txt(self):
|
||||||
|
# The document marker: section numbering under txt is a document
|
||||||
|
# service, and the numbering phase numbers only when it finds this
|
||||||
|
# at the head of the text (a fragment, as kdesc renders, has none).
|
||||||
|
result = phases.txt_document_marker() + "\n"
|
||||||
|
if self.title:
|
||||||
|
result += f"@b {self.title} @ @nl@\n"
|
||||||
|
if self.subtitle:
|
||||||
|
result += f"@b {self.subtitle} @ @nl@\n"
|
||||||
|
result += "\n"
|
||||||
|
if self.author:
|
||||||
|
result += f"@i {self.author} @ @nl@\n"
|
||||||
|
if self.date:
|
||||||
|
result += f"@i {self.date} @ @nl@\n"
|
||||||
|
result += "\n"
|
||||||
|
result += self.text
|
||||||
|
return result
|
||||||
+29
-4
@@ -26,13 +26,28 @@ font as document text instead of setting anything. @typeface is the
|
|||||||
temporary, all-target replacement.
|
temporary, all-target replacement.
|
||||||
]#
|
]#
|
||||||
|
|
||||||
@@typeface.k name | text :scale.float 1.0 :
|
@@typeface.k name | text :scale.float 1.0 :middle.bool :lift.length :
|
||||||
A temporary change of typeface for *text*: the typeface *name* is
|
A temporary change of typeface for *text*: the typeface *name* is
|
||||||
resolved in the Klammertext font store (the directories of
|
resolved in the Klammertext font store (the directories of
|
||||||
KLAMMERTEXT_FONTS, then the distribution's own), and the document
|
KLAMMERTEXT_FONTS, then the distribution's own), and the document
|
||||||
declares and localizes it automatically -- writing this klammer is the
|
declares and localizes it automatically -- writing this klammer is the
|
||||||
only declaration needed. *scale* multiplies the surrounding text size,
|
only declaration needed. *scale* multiplies the size of the surrounding
|
||||||
which a script whose glyphs are drawn small usually wants.
|
text as it appears, the same in html and pdf, which a script whose glyphs
|
||||||
|
are drawn small usually wants.
|
||||||
|
|
||||||
|
*middle* centres *text* on the surrounding line instead of standing it on
|
||||||
|
the baseline -- for a script with no baseline relationship to the text,
|
||||||
|
such as hieroglyphs, whose font declares a tall ascender that would
|
||||||
|
otherwise open up every line the glyphs sit in. Html only: in tex a
|
||||||
|
row is already as tight as its tallest glyph. Never for a Latin
|
||||||
|
typeface, whose baseline must stay shared with the text around it.
|
||||||
|
|
||||||
|
*lift* shifts *text* up by a length (down when negative), in em, pt or
|
||||||
|
px -- the correction for a font that draws its glyphs off the baseline,
|
||||||
|
which is a property of the font and so belongs in the klammer that names
|
||||||
|
it: Noto Sans Egyptian Hieroglyphs floats its signs and wants about
|
||||||
|
0.15em, NewGardinerOmni stands them on the baseline and wants none.
|
||||||
|
Html and tex alike; the txt target has no vertical position.
|
||||||
|
|
||||||
The change ends with *text*: nothing after it is affected. Under the
|
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
|
txt target a typeface cannot be expressed at all, so *text* is emitted
|
||||||
@@ -107,9 +122,19 @@ than a typeface -- xeCJK for the tex path), so this klammer errors with
|
|||||||
the list of available typefaces until one is installed with
|
the list of available typefaces until one is installed with
|
||||||
"kdesc --typeface install". That is the honest behaviour: the klammer
|
"kdesc --typeface install". That is the honest behaviour: the klammer
|
||||||
states what it needs and the store reports what it has.
|
states what it needs and the store reports what it has.
|
||||||
]#
|
|
||||||
@@cjk.k text : Set *text* in a typeface that supports CJK @@
|
@@cjk.k text : Set *text* in a typeface that supports CJK @@
|
||||||
@@cjk.html,tex,txt :: @typeface Noto Sans CJK SC | *text* @ @@
|
@@cjk.html,tex,txt :: @typeface Noto Sans CJK SC | *text* @ @@
|
||||||
|
]#
|
||||||
|
|
||||||
|
# Only defining on Chinese style for now; could be parameterized.
|
||||||
|
@@chinese.k text :scale.float 1 : Set *text* in a Chinese font @@
|
||||||
|
@@chinese.html,tex,txt :: @typeface Noto Serif SC :scale *scale* | *text* @ @@
|
||||||
|
|
||||||
|
# Egyptian hieroglyphs
|
||||||
|
@@egyptian.k text :scale.float 1.75 : Set *text* in an Egyptian hieroglyph font @@
|
||||||
|
@@egyptian.html,tex,txt :: @typeface NewGardinerOmni :scale *scale* :middle | *text* @ @@
|
||||||
|
# @@egyptian.html,tex,txt :: @typeface Noto Sans Egyptian Hieroglyphs :scale *scale* :middle | *text* @ @@
|
||||||
|
|
||||||
|
|
||||||
@@fraktur.k s : Set text *s* in Fraktur @@
|
@@fraktur.k s : Set text *s* in Fraktur @@
|
||||||
@@fraktur.tex :: \textfrak{*s*} @@
|
@@fraktur.tex :: \textfrak{*s*} @@
|
||||||
|
|||||||
+52
-3
@@ -60,9 +60,38 @@ class Typeface(klammer_base.Klammer_base):
|
|||||||
# this sits inside a double-quoted html attribute. What is quoted
|
# this sits inside a double-quoted html attribute. What is quoted
|
||||||
# here is the MARKER: @document replaces it with the resolved
|
# here is the MARKER: @document replaces it with the resolved
|
||||||
# family name (see the marker note above).
|
# family name (see the marker note above).
|
||||||
|
# The size is the scale times the body's own enlargement: the body
|
||||||
|
# is rendered at "font-size-adjust: ex-height 0.5" (font.css), so
|
||||||
|
# the visible text is larger than its nominal size, and a span
|
||||||
|
# with the adjust off at a plain "Nem" sat about a fifth smaller
|
||||||
|
# against it than the same :scale in pdf. @document emits the
|
||||||
|
# factor as --typeface_scale (document_html.cpp, font_definitions);
|
||||||
|
# the fallback 1 is for a fragment rendered without @document.
|
||||||
|
# The "*" of calc() survives the @eval re-read (tested 2026-09-18);
|
||||||
|
# the "--" of the property name is hidden below with the rest.
|
||||||
style = "', serif; font-size-adjust: none"
|
style = "', serif; font-size-adjust: none"
|
||||||
if float(self.scale) != 1.0:
|
style += f"; font-size: calc({float(self.scale)}em * var(--typeface_scale, 1))"
|
||||||
style += f"; font-size: {float(self.scale)}em"
|
# :middle -- for a script with no baseline relationship to the
|
||||||
|
# text. A browser sizes the span's inline box from the FONT's
|
||||||
|
# ascender and descender, not the glyph's ink, and centres it on
|
||||||
|
# the content area's midpoint; a hieroglyph font declares an
|
||||||
|
# ascender of 1.0 em and no descender (NewGardinerOmni; Noto:
|
||||||
|
# 1.324/-0.326), so at :scale 1.75 the box sat half an em too
|
||||||
|
# high and every table row grew from 31 to 47 px (Noto: 62).
|
||||||
|
# line-height 1 drops the body's leading from the enlarged box and
|
||||||
|
# vertical-align middle centres it on the PARENT's x-height, which
|
||||||
|
# is why this uses the text's metrics, not the script font's zeros.
|
||||||
|
# Measured 2026-09-18: 40 px, the floor the ink allows; line-height
|
||||||
|
# 0 alone gave 31 px with the glyphs overlapping the rows above.
|
||||||
|
# Off by default: a Latin typeface must keep the shared baseline.
|
||||||
|
if self.middle:
|
||||||
|
style += "; line-height: 1; vertical-align: middle"
|
||||||
|
if self.lift_length():
|
||||||
|
# Relative positioning moves the ink and nothing else: the
|
||||||
|
# line box is laid out as if the span had not moved, which is
|
||||||
|
# what a baseline correction wants (vertical-align would
|
||||||
|
# re-lay the line). Negative top = up, as \raisebox in tex.
|
||||||
|
style += f"; position: relative; top: -{self.lift_length()}"
|
||||||
# The STYLE is markup, so its typographically active characters are
|
# The STYLE is markup, so its typographically active characters are
|
||||||
# hidden: the html target's transforms turn a single quote into a
|
# hidden: the html target's transforms turn a single quote into a
|
||||||
# curly one, which broke the css font-family value. The MARKER is
|
# curly one, which broke the css font-family value. The MARKER is
|
||||||
@@ -81,7 +110,27 @@ class Typeface(klammer_base.Klammer_base):
|
|||||||
# makes the change temporary (the retired @font emitted a bare
|
# makes the change temporary (the retired @font emitted a bare
|
||||||
# \setmainfont, which changed the font for the rest of the
|
# \setmainfont, which changed the font for the rest of the
|
||||||
# document).
|
# document).
|
||||||
return "{" + typeface_marker(self.name, self.scale) + " " + self.text + "}"
|
group = "{" + typeface_marker(self.name, self.scale) + " " + self.text + "}"
|
||||||
|
if self.lift_length():
|
||||||
|
# A \raisebox produced HERE is safe from the generator escaping
|
||||||
|
# pass: this klammer is applied after a generator's result has
|
||||||
|
# been escaped, so its backslash is never swept -- the trap
|
||||||
|
# @nl.tex fell into (2026-09-07) is a klammer applied inside a
|
||||||
|
# generator's ARGUMENT, which @typeface shares with @b and @i.
|
||||||
|
return "\\raisebox{" + self.lift_length() + "}" + group
|
||||||
|
return group
|
||||||
|
|
||||||
|
def lift_length(self):
|
||||||
|
# :lift, validated to the units both targets can move by. The
|
||||||
|
# length argtype admits w/h fractions and a quoted string, which
|
||||||
|
# mean nothing as a vertical shift; a bare number would be columns
|
||||||
|
# or lines and is refused for the same reason.
|
||||||
|
lift = self.lift
|
||||||
|
if not lift:
|
||||||
|
return ""
|
||||||
|
if not re.fullmatch(r"-?(\d+\.?\d*|\.\d+)(em|pt|px)", lift):
|
||||||
|
raise Exception(f"@typeface :lift takes a length in em, pt or px, not '{lift}'")
|
||||||
|
return lift
|
||||||
|
|
||||||
def txt(self):
|
def txt(self):
|
||||||
# A typeface cannot be expressed in plain text. Declared in the
|
# A typeface cannot be expressed in plain text. Declared in the
|
||||||
|
|||||||
+1
-42
@@ -19,8 +19,7 @@ class Image(klammer_base.Klammer_base):
|
|||||||
id = 0
|
id = 0
|
||||||
def __init__(self, K, as_string=True, width=None):
|
def __init__(self, K, as_string=True, width=None):
|
||||||
super().__init__(K)
|
super().__init__(K)
|
||||||
# pprint.pprint(self.__dict__)
|
|
||||||
# self.show()
|
|
||||||
# /dev/shm is a fast RAM-backed tmpfs on Linux; on macOS it does not
|
# /dev/shm is a fast RAM-backed tmpfs on Linux; on macOS it does not
|
||||||
# exist, so fall back to the platform temp directory.
|
# exist, so fall back to the platform temp directory.
|
||||||
cache_dir = "/dev/shm"
|
cache_dir = "/dev/shm"
|
||||||
@@ -48,21 +47,11 @@ class Image(klammer_base.Klammer_base):
|
|||||||
def html(self):
|
def html(self):
|
||||||
img_dir = f"{self.K_output_dir}/{self.K_output_basename}/{self.Image_output_dir}"
|
img_dir = f"{self.K_output_dir}/{self.K_output_basename}/{self.Image_output_dir}"
|
||||||
|
|
||||||
# output_dir = self.K_output_dir or "."
|
|
||||||
# input_basename = self.__dict__.get("K_input_name", "")
|
|
||||||
# input_basename = "/" + input_basename if input_basename else ""
|
|
||||||
# img_dir = f"{output_dir}/{self.image_output_dir}{input_basename}"
|
|
||||||
#print("img_dir:", img_dir)
|
|
||||||
|
|
||||||
if not os.path.exists(img_dir):
|
if not os.path.exists(img_dir):
|
||||||
os.makedirs(img_dir)
|
os.makedirs(img_dir)
|
||||||
if not os.path.exists(f"{img_dir}/{os.path.basename(self.source)}"):
|
if not os.path.exists(f"{img_dir}/{os.path.basename(self.source)}"):
|
||||||
shutil.copy2(self.source, img_dir)
|
shutil.copy2(self.source, img_dir)
|
||||||
|
|
||||||
# width, scale_x, scale_y = kutil.parse_length(
|
|
||||||
# "html",
|
|
||||||
# self.width if self.width != "none" else self.image_default_width)
|
|
||||||
|
|
||||||
scale_x, scale_y = 1, 1 # Old
|
scale_x, scale_y = 1, 1 # Old
|
||||||
|
|
||||||
result = E("img")
|
result = E("img")
|
||||||
@@ -72,9 +61,6 @@ class Image(klammer_base.Klammer_base):
|
|||||||
filename = f"{self.Image_output_dir}/{os.path.basename(self.source)}"
|
filename = f"{self.Image_output_dir}/{os.path.basename(self.source)}"
|
||||||
|
|
||||||
result.attr("src", filename).attr("alt", os.path.basename(filename))
|
result.attr("src", filename).attr("alt", os.path.basename(filename))
|
||||||
#if self.width:
|
|
||||||
|
|
||||||
# kutil.msg("rel: " + str(self.rel_fraction))
|
|
||||||
|
|
||||||
length, value, ltype = kutil.parse_width("html", self.width, self.rel_fraction)
|
length, value, ltype = kutil.parse_width("html", self.width, self.rel_fraction)
|
||||||
if ltype == "w":
|
if ltype == "w":
|
||||||
@@ -90,19 +76,6 @@ class Image(klammer_base.Klammer_base):
|
|||||||
if self.border:
|
if self.border:
|
||||||
result.cls("image_border")
|
result.cls("image_border")
|
||||||
|
|
||||||
# print(result)
|
|
||||||
|
|
||||||
#result.attr("style", f"width: {width}")
|
|
||||||
# if width and width != "none":
|
|
||||||
# result.attr("style", f"width: {width}")
|
|
||||||
# if width == "none":
|
|
||||||
# if scale_x:
|
|
||||||
# result.attr("style", "width: 0px;")
|
|
||||||
# result.attr("scale-x", scale_x)
|
|
||||||
# elif scale_y:
|
|
||||||
# result.attr("style", "height: 0px;")
|
|
||||||
# result.attr("scale-y", scale_y)
|
|
||||||
|
|
||||||
if self.file_error:
|
if self.file_error:
|
||||||
self.caption += self.file_error_message
|
self.caption += self.file_error_message
|
||||||
|
|
||||||
@@ -120,14 +93,7 @@ class Image(klammer_base.Klammer_base):
|
|||||||
|
|
||||||
|
|
||||||
def tex(self):
|
def tex(self):
|
||||||
# kutil.msg(f"number: {self.number}")
|
|
||||||
#selfsource, width, height = self.cache.get(self.K_target, self.basename)
|
|
||||||
source = os.path.abspath(self.source)
|
source = os.path.abspath(self.source)
|
||||||
#if self.abswidth != 0:
|
|
||||||
# width = f"{self.abswidth}\\paperwidth"
|
|
||||||
#if self.caption or self.number:
|
|
||||||
# width = "\\textwidth"
|
|
||||||
#else:
|
|
||||||
width = kutil.parse_width("tex", self.width, self.rel_fraction)[0]
|
width = kutil.parse_width("tex", self.width, self.rel_fraction)[0]
|
||||||
width = re.sub("px", "pt", width)
|
width = re.sub("px", "pt", width)
|
||||||
#result = f'\\includegraphics[width={width}]{{{source}}}'
|
#result = f'\\includegraphics[width={width}]{{{source}}}'
|
||||||
@@ -138,8 +104,6 @@ class Image(klammer_base.Klammer_base):
|
|||||||
result = latex_util.add_caption(
|
result = latex_util.add_caption(
|
||||||
result, "Figure", self.number, self.caption, width,
|
result, "Figure", self.number, self.caption, width,
|
||||||
self.hpos, self.caption_side, offset=self.offset)
|
self.hpos, self.caption_side, offset=self.offset)
|
||||||
#result, "Figure", self.number, self.caption, self.hpos, self.caption_side,
|
|
||||||
#self.width, self.caption_side_center, self.vmargin, self.caption_margin)
|
|
||||||
else:
|
else:
|
||||||
result = f'\\includegraphics[width={width}]{{{source}}}'
|
result = f'\\includegraphics[width={width}]{{{source}}}'
|
||||||
result = latex_util.caption_wrapper(result, self.hpos, offset=self.offset)
|
result = latex_util.caption_wrapper(result, self.hpos, offset=self.offset)
|
||||||
@@ -150,8 +114,3 @@ class Image(klammer_base.Klammer_base):
|
|||||||
# box material: it must sit in vertical mode or it is typeset beside
|
# box material: it must sit in vertical mode or it is typeset beside
|
||||||
# any text it follows.
|
# any text it follows.
|
||||||
return latex_util.block(result)
|
return latex_util.block(result)
|
||||||
|
|
||||||
|
|
||||||
# Better to cause an error; there's no good text substitute for an image
|
|
||||||
# def txt(self):
|
|
||||||
# return f"[Image: {self.basename}\nCaption: {self.caption}]"
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
import pprint
|
|
||||||
import re
|
import re
|
||||||
import kutil
|
import kutil
|
||||||
|
|
||||||
@@ -41,25 +40,18 @@ def hide_typographic(s):
|
|||||||
|
|
||||||
class Klammer_base:
|
class Klammer_base:
|
||||||
def __init__(self, K):
|
def __init__(self, K):
|
||||||
#args = {k:escape(v) for k, v in K.__dict__.items()
|
|
||||||
args = {k:v for k, v in K.__dict__.items()
|
args = {k:v for k, v in K.__dict__.items()
|
||||||
if not k.startswith('__')}
|
if not k.startswith('__')}
|
||||||
for key in args:
|
for key in args:
|
||||||
setattr(self, key, args[key])
|
setattr(self, key, args[key])
|
||||||
#setattr(self, "_klammer_name", klammer_name)
|
|
||||||
#pprint.pprint(self.__dict__)
|
|
||||||
|
|
||||||
|
|
||||||
def html(self):
|
def html(self):
|
||||||
#return '[{}: HTML]'.format(self.__class__.__name__)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def tex(self):
|
def tex(self):
|
||||||
#return '[{}: LaTeX]'.format(self.__class__.__name__)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def txt(self):
|
def txt(self):
|
||||||
#return '[{}: Plain text]'.format(self.__class__.__name__)
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def show(self, label=""):
|
def show(self, label=""):
|
||||||
|
|||||||
+2
-1
@@ -29,7 +29,8 @@ class List(klammer_base.Klammer_base):
|
|||||||
body = "\n\n".join([f"\\item {e}" for e in self.items])
|
body = "\n\n".join([f"\\item {e}" for e in self.items])
|
||||||
command = {'ol' : 'enumerate', 'ul' : 'itemize'}[self.list_type]
|
command = {'ol' : 'enumerate', 'ul' : 'itemize'}[self.list_type]
|
||||||
init = ""
|
init = ""
|
||||||
if self.__dict__.get("initial") and self.initial != 1:
|
# :initial is @ol's alone (list.k); a @ul has no such attribute.
|
||||||
|
if getattr(self, "initial", 1) != 1:
|
||||||
init = "\\addtocounter{enumi}{" + str(self.initial - 1) + "}"
|
init = "\\addtocounter{enumi}{" + str(self.initial - 1) + "}"
|
||||||
#topsep = '[noitemsep,topsep=0pt]'
|
#topsep = '[noitemsep,topsep=0pt]'
|
||||||
topsep = "[noitemsep,topsep=0pt," if self.compressed else "[topsep=0pt,itemsep=2pt,"
|
topsep = "[noitemsep,topsep=0pt," if self.compressed else "[topsep=0pt,itemsep=2pt,"
|
||||||
|
|||||||
+13
-8
@@ -1,6 +1,11 @@
|
|||||||
# Book-specific component headings (others possible to correspond to
|
# Book-specific component headings (others possible to correspond to
|
||||||
# the front and back matter of a book):
|
# the front and back matter of a book):
|
||||||
|
|
||||||
|
@@section_args.o :id :n.int -1 :
|
||||||
|
Common arguments for section headings. If *n* is not the default, it is used
|
||||||
|
as the section number.
|
||||||
|
@@
|
||||||
|
|
||||||
@@preface.k :id :name Preface : Preface text @@
|
@@preface.k :id :name Preface : Preface text @@
|
||||||
@@preface.html,tex,txt :: @eval section.Section(K, "preface") eval@ @@
|
@@preface.html,tex,txt :: @eval section.Section(K, "preface") eval@ @@
|
||||||
|
|
||||||
@@ -9,30 +14,30 @@
|
|||||||
@@part.k title :id :name Part : Part heading @@
|
@@part.k title :id :name Part : Part heading @@
|
||||||
@@part.html,tex,txt :: @eval section.Section(K, 0) eval@ @@
|
@@part.html,tex,txt :: @eval section.Section(K, 0) eval@ @@
|
||||||
|
|
||||||
@@s1.k title :id :n : Top level division, numbered @@
|
@@s1.k title @section_args@ : Top level division, numbered @@
|
||||||
@@s1.html,tex,txt :: @eval section.Section(K, 1) eval@ @@
|
@@s1.html,tex,txt :: @eval section.Section(K, 1) eval@ @@
|
||||||
|
|
||||||
@@s2.k title :id :n : Second level division, numbered @@
|
@@s2.k title @section_args@ : Second level division, numbered @@
|
||||||
@@s2.html,tex,txt :: @eval section.Section(K, 2) eval@ @@
|
@@s2.html,tex,txt :: @eval section.Section(K, 2) eval@ @@
|
||||||
|
|
||||||
@@s3.k title :id :n : Third level division, numbered @@
|
@@s3.k title @section_args@ : Third level division, numbered @@
|
||||||
@@s3.html,tex,txt :: @eval section.Section(K, 3) eval@ @@
|
@@s3.html,tex,txt :: @eval section.Section(K, 3) eval@ @@
|
||||||
|
|
||||||
@@s4.k title :id :n : Fourth level division, numbered @@
|
@@s4.k title @section_args@ : Fourth level division, numbered @@
|
||||||
@@s4.html,tex,txt :: @eval section.Section(K, 4) eval@ @@
|
@@s4.html,tex,txt :: @eval section.Section(K, 4) eval@ @@
|
||||||
|
|
||||||
@@s5.k title :id :n : Fifth level division, numbered @@
|
@@s5.k title @section_args@ : Fifth level division, numbered @@
|
||||||
@@s5.html,tex,txt :: @eval section.Section(K, 5) eval@ @@
|
@@s5.html,tex,txt :: @eval section.Section(K, 5) eval@ @@
|
||||||
|
|
||||||
@@s6.k title :id :n : Sixth level division, numbered @@
|
@@s6.k title @section_args@ : Sixth level division, numbered @@
|
||||||
@@s6.html,tex,txt :: @eval section.Section(K, 6) eval@ @@
|
@@s6.html,tex,txt :: @eval section.Section(K, 6) eval@ @@
|
||||||
|
|
||||||
@@s7.k title :id :n : Seventh level division, numbered @@
|
@@s7.k title @section_args@ : Seventh level division, numbered @@
|
||||||
@@s7.html,tex,txt :: @eval section.Section(K, 7) eval@ @@
|
@@s7.html,tex,txt :: @eval section.Section(K, 7) eval@ @@
|
||||||
|
|
||||||
# Unnumbered section headings:
|
# Unnumbered section headings:
|
||||||
|
|
||||||
@@h1.k title :id :n : Top level division, unnumbered @@
|
@@h1.k title :id : Top level division, unnumbered @@
|
||||||
@@h1.html,tex,txt :: @eval section.Section(K, 1, numbered=False) eval@ @@
|
@@h1.html,tex,txt :: @eval section.Section(K, 1, numbered=False) eval@ @@
|
||||||
|
|
||||||
@@h2.k title :id : Second level division, unnumbered @@
|
@@h2.k title :id : Second level division, unnumbered @@
|
||||||
|
|||||||
+16
-7
@@ -1,5 +1,6 @@
|
|||||||
import kutil
|
import kutil
|
||||||
import klammer_base
|
import klammer_base
|
||||||
|
import phases
|
||||||
|
|
||||||
NUMBER_VAR = "___NUM___"
|
NUMBER_VAR = "___NUM___"
|
||||||
|
|
||||||
@@ -48,7 +49,6 @@ class Section(klammer_base.Klammer_base):
|
|||||||
# names = "part chapter section subsection subsubsection paragraph subparagraph subsubparagraph subsubsubparagraph".split()
|
# names = "part chapter section subsection subsubsection paragraph subparagraph subsubparagraph subsubsubparagraph".split()
|
||||||
names = "part sA sB sC sD sE".split()
|
names = "part sA sB sC sD sE".split()
|
||||||
|
|
||||||
#self.show()
|
|
||||||
if self.id:
|
if self.id:
|
||||||
id = self.id
|
id = self.id
|
||||||
else:
|
else:
|
||||||
@@ -56,13 +56,8 @@ class Section(klammer_base.Klammer_base):
|
|||||||
Section.label_number += 1
|
Section.label_number += 1
|
||||||
|
|
||||||
result = ''
|
result = ''
|
||||||
if self.__dict__.get("n") and self.n:
|
if getattr(self, "n", -1) > -1: # absent for @part and @h1..@h7
|
||||||
result += f'\\setcounter{{chapter}}{{{int(self.n)-1}}}\n'
|
result += f'\\setcounter{{chapter}}{{{int(self.n)-1}}}\n'
|
||||||
#if self.id:
|
|
||||||
# result +=
|
|
||||||
#if self.level == 1:
|
|
||||||
#result += '\\newpage\n\n'
|
|
||||||
# result += '\\vspace*{24pt}'
|
|
||||||
suffix = "" if self.numbered else "*"
|
suffix = "" if self.numbered else "*"
|
||||||
result += f'\\{names[self.level]}{suffix}{{{self.title}}}'
|
result += f'\\{names[self.level]}{suffix}{{{self.title}}}'
|
||||||
result += f'\n\\hypertarget{{{id}}}{{}}'
|
result += f'\n\\hypertarget{{{id}}}{{}}'
|
||||||
@@ -73,4 +68,18 @@ class Section(klammer_base.Klammer_base):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
def txt(self):
|
def txt(self):
|
||||||
|
# The number is a marker phases.txt_insert_section_numbers resolves
|
||||||
|
# -- to a number inside a @document, to nothing outside one -- so
|
||||||
|
# the space after it is the marker's, not the title's. A preface
|
||||||
|
# and an unnumbered heading carry no marker at all; a part carries
|
||||||
|
# a level-0 marker and its name, as the html "Part" prefix.
|
||||||
|
if self.preface:
|
||||||
|
return f"@b {self.name} @"
|
||||||
|
if not self.numbered:
|
||||||
return f"@b {self.title} @"
|
return f"@b {self.title} @"
|
||||||
|
# :n comes from section_args, which @part and the @h headings do
|
||||||
|
# not declare, so it is absent there rather than -1.
|
||||||
|
marker = phases.txt_section_marker(self.level, getattr(self, "n", -1))
|
||||||
|
if self.level == 0:
|
||||||
|
return f"@b {marker} {self.name} {self.title} @"
|
||||||
|
return f"@b {marker} {self.title} @"
|
||||||
@@ -12,8 +12,13 @@ tr:first-child {
|
|||||||
|
|
||||||
|
|
||||||
td {
|
td {
|
||||||
/* padding-left: 1rem; */
|
/* Vertical space is the table's line-height (:leading, set on the
|
||||||
padding: .1rem .5rem .2rem .5rem;
|
<table> by table.py -- the html equivalent of \arraystretch), so no
|
||||||
|
vertical padding here: it was an html-only extra that kept rows
|
||||||
|
looser than the pdf's. Horizontal padding is :colsep, the same
|
||||||
|
value that becomes \tabcolsep; the fallback is for a table that
|
||||||
|
reaches this rule without the property. */
|
||||||
|
padding: 0 var(--colsep, 4pt);
|
||||||
vertical-align: top;
|
vertical-align: top;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+20
-2
@@ -575,6 +575,24 @@ class Table(klammer_base.Klammer_base):
|
|||||||
# would wrap it as a stray <p> inside the table.
|
# would wrap it as a stray <p> inside the table.
|
||||||
return E("colgroup").body(cols.strip(), newline=False).str(), layout, table_width
|
return E("colgroup").body(cols.strip(), newline=False).str(), layout, table_width
|
||||||
|
|
||||||
|
def html_style(self):
|
||||||
|
# :leading and :colsep under html (2026-09-18; both were tex-only).
|
||||||
|
# \arraystretch multiplies the row strut, which is \baselineskip,
|
||||||
|
# 1.2 times the font size -- so :leading L is a 1.2 L em row, and
|
||||||
|
# the html equivalent is that line-height on the table, inherited
|
||||||
|
# by every cell. The cells' vertical padding went with it
|
||||||
|
# (table.css): an html-only extra with no tex counterpart, which
|
||||||
|
# kept a :leading 1 table 0.3rem looser per row than its pdf.
|
||||||
|
# :colsep is a custom property the td rule reads for its horizontal
|
||||||
|
# padding (padding does not inherit); the flush-edge classes still
|
||||||
|
# zero the outer ones. Measured 2026-09-18: rows of :leading 1 and
|
||||||
|
# :leading 2 in the ratio 1:2 in both targets.
|
||||||
|
line_height = f"{1.2 * float(self.leading):.4g}"
|
||||||
|
# Hidden: the result is re-read and the html target's transforms
|
||||||
|
# would turn the property's "--" into an en dash (font.py's trap).
|
||||||
|
return klammer_base.hide_typographic(
|
||||||
|
f"line-height: {line_height}; --colsep: {self.colsep}")
|
||||||
|
|
||||||
def html(self):
|
def html(self):
|
||||||
result = ""
|
result = ""
|
||||||
for row_i, row in enumerate(self.cells):
|
for row_i, row in enumerate(self.cells):
|
||||||
@@ -592,7 +610,7 @@ class Table(klammer_base.Klammer_base):
|
|||||||
# algorithm), matching the tex \ratio division. The max-width
|
# algorithm), matching the tex \ratio division. The max-width
|
||||||
# cap goes on the caption wrapper when there is one (the table's
|
# cap goes on the caption wrapper when there is one (the table's
|
||||||
# own percentage would be circular in a shrink-to-fit wrapper).
|
# own percentage would be circular in a shrink-to-fit wrapper).
|
||||||
result = E("table").body(result)
|
result = E("table").body(result).attr("style", self.html_style())
|
||||||
if self.number or self.caption:
|
if self.number or self.caption:
|
||||||
result = html_util.add_caption(
|
result = html_util.add_caption(
|
||||||
result, "Table", self.number, self.caption,
|
result, "Table", self.number, self.caption,
|
||||||
@@ -605,7 +623,7 @@ class Table(klammer_base.Klammer_base):
|
|||||||
result = html_util.hpos_container(result, self.hpos, self.offset).str()
|
result = html_util.hpos_container(result, self.hpos, self.offset).str()
|
||||||
return result
|
return result
|
||||||
colgroup, layout, table_width = self.html_colgroup()
|
colgroup, layout, table_width = self.html_colgroup()
|
||||||
result = E("table").body(colgroup + result)
|
result = E("table").body(colgroup + result).attr("style", self.html_style())
|
||||||
if self.number or self.caption:
|
if self.number or self.caption:
|
||||||
# The caption wrapper carries the width (a percentage on the
|
# The caption wrapper carries the width (a percentage on the
|
||||||
# shrink-to-fit wrapper itself would be circular) and the table
|
# shrink-to-fit wrapper itself would be circular) and the table
|
||||||
|
|||||||
@@ -248,6 +248,50 @@ def txt_format_footnotes(text, K):
|
|||||||
+ label + " " + footnote + "\n\n")
|
+ label + " " + footnote + "\n\n")
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Section numbering under txt (2026-09-18). Numbering is a DOCUMENT
|
||||||
|
# service, as it is in html (a pass @document runs over the page) and tex
|
||||||
|
# (LaTeX's own counters inside the document): a heading rendered outside
|
||||||
|
# a @document -- kdesc's renders of the ref/ generators -- is not
|
||||||
|
# numbered. Document.txt() (sks/document/document_txt.py) opens its
|
||||||
|
# result with the document marker; Section.txt() emits a section marker
|
||||||
|
# carrying the level and the :n override; this phase, first in the txt
|
||||||
|
# and ansi lists, numbers the headings when the document marker is there
|
||||||
|
# and strips every marker either way. A heading at a level with no
|
||||||
|
# parent numbers as "0.1", as html and tex print it -- the txt target
|
||||||
|
# does not diverge from them. The single-source functions are the
|
||||||
|
# TODO #47 pattern.
|
||||||
|
|
||||||
|
def txt_document_marker():
|
||||||
|
return "__KTEXTDOCUMENT__"
|
||||||
|
|
||||||
|
def txt_section_marker(level, n=-1):
|
||||||
|
# LEVEL 0 is a part, 1-7 the sections; N > -1 sets the number.
|
||||||
|
return f"__KTEXTSECTION__{level}__{n}__"
|
||||||
|
|
||||||
|
def txt_insert_section_numbers(text, K):
|
||||||
|
# The marker is the first thing the document emits, but the result
|
||||||
|
# reaches the phase behind whatever newlines the apply left in front.
|
||||||
|
head = text.lstrip()
|
||||||
|
in_document = head.startswith(txt_document_marker())
|
||||||
|
if in_document:
|
||||||
|
text = head[len(txt_document_marker()):].lstrip("\n")
|
||||||
|
levels = [0] * 8 # 0: parts; 1-7: sections (parts do not reset them, as in LaTeX)
|
||||||
|
marker_rgx = re.compile(r"__KTEXTSECTION__(\d+)__(-?\d+)__ ?")
|
||||||
|
def number(match):
|
||||||
|
if not in_document:
|
||||||
|
return ""
|
||||||
|
level, n = int(match.group(1)), int(match.group(2))
|
||||||
|
levels[level] = n if n > -1 else levels[level] + 1
|
||||||
|
if level == 0:
|
||||||
|
return f"{levels[0]} " # a part resets nothing below it
|
||||||
|
for i in range(level + 1, len(levels)):
|
||||||
|
levels[i] = 0
|
||||||
|
return ".".join(str(v) for v in levels[1:level + 1]) + " "
|
||||||
|
return marker_rgx.sub(number, text)
|
||||||
|
|
||||||
|
# HTML
|
||||||
|
|
||||||
def html_footnote_marker():
|
def html_footnote_marker():
|
||||||
return "kt-footnote"
|
return "kt-footnote"
|
||||||
|
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ of this: neither the transforms nor the quoting reach it.
|
|||||||
' ' |
|
' ' |
|
||||||
~ __KTEXTNBSP__
|
~ __KTEXTNBSP__
|
||||||
:after_apply
|
:after_apply
|
||||||
|
phases.txt_insert_section_numbers ;
|
||||||
phases.txt_format_footnotes ;
|
phases.txt_format_footnotes ;
|
||||||
phases.txt_insert_captions ;
|
phases.txt_insert_captions ;
|
||||||
phases.txt_justify_blocks ;
|
phases.txt_justify_blocks ;
|
||||||
@@ -83,6 +84,7 @@ of this: neither the transforms nor the quoting reach it.
|
|||||||
@@@target ansi | Plain text with ANSI escape sequences for type styles
|
@@@target ansi | Plain text with ANSI escape sequences for type styles
|
||||||
:includes txt
|
:includes txt
|
||||||
:after_apply
|
:after_apply
|
||||||
|
phases.txt_insert_section_numbers ;
|
||||||
phases.txt_format_footnotes ;
|
phases.txt_format_footnotes ;
|
||||||
phases.txt_insert_captions ;
|
phases.txt_insert_captions ;
|
||||||
phases.txt_justify_blocks ;
|
phases.txt_justify_blocks ;
|
||||||
|
|||||||
+6
-1
@@ -8,7 +8,7 @@
|
|||||||
# * a flag a user reaches for often gets a single letter (-k klammers,
|
# * a flag a user reaches for often gets a single letter (-k klammers,
|
||||||
# -t targets, -c character codes, -i input); a more specialised topic gets
|
# -t targets, -c character codes, -i input); a more specialised topic gets
|
||||||
# a multi-letter name (--argtypes, --katoms, --rewrite, --optionsets,
|
# a multi-letter name (--argtypes, --katoms, --rewrite, --optionsets,
|
||||||
# --coverage, --klammersets, --font);
|
# --coverage, --klammersets, --typeface);
|
||||||
# * -v says how much to show about the command's PROCESSING and never what
|
# * -v says how much to show about the command's PROCESSING and never what
|
||||||
# its RESULT contains. So the katom regex column is "--katoms full" and
|
# its RESULT contains. So the katom regex column is "--katoms full" and
|
||||||
# the coverage file column is "--coverage all", not verbosity levels.
|
# the coverage file column is "--coverage all", not verbosity levels.
|
||||||
@@ -151,6 +151,11 @@ shows "24. --coverage help explains" 'Coverage commands' "$KDESC" --cover
|
|||||||
shows "25. --katoms help explains" 'Katom commands' "$KDESC" --katoms help
|
shows "25. --katoms help explains" 'Katom commands' "$KDESC" --katoms help
|
||||||
shows "26. an unknown coverage word" 'Unrecognized coverage command' "$KDESC" --coverage nonsense
|
shows "26. an unknown coverage word" 'Unrecognized coverage command' "$KDESC" --coverage nonsense
|
||||||
shows "27. an unknown katom word" 'Unrecognized katom command' "$KDESC" --katoms nonsense
|
shows "27. an unknown katom word" 'Unrecognized katom command' "$KDESC" --katoms nonsense
|
||||||
|
# The help text and the error must name the flag by its CURRENT spelling:
|
||||||
|
# both still said --font for three weeks after the 2026-08-25 rename, and # retired-ok
|
||||||
|
# the retired-name guard's entries did not match the bare word.
|
||||||
|
shows "27a. --typeface help names --typeface install" '--typeface install' "$KDESC" --typeface help
|
||||||
|
shows "27b. an unknown typeface word names the flag" 'Unrecognized typeface command: --typeface' "$KDESC" --typeface nonsense
|
||||||
|
|
||||||
echo
|
echo
|
||||||
echo "-- provenance: -i shows what the INPUT defines --"
|
echo "-- provenance: -i shows what the INPUT defines --"
|
||||||
|
|||||||
Reference in new issue
Block a user