#include #include #include #include "machine.h" #include "character.h" #include "error.h" #include "show.h" #include "font_store.h" #include "ktype.h" // extern "C" is needed for dlsym name lookup. Returning std::string from // C-linkage functions is technically non-standard but works correctly on // Linux (Itanium ABI) where C and C++ calling conventions are identical. #if defined(__clang__) #pragma clang diagnostic ignored "-Wreturn-type-c-linkage" #endif extern "C" std::string diacritics_examples([[maybe_unused]] Machine& machine) { if (diacritics.size() != diacritic_symbols_order.size()) { throw Internal_error("Mismatch between diacritics order list and their definitions"); } //std::cout << machine; std::stringstream kt {}; 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) { std::string sym { symbol }; auto [code, name] = diacritics[sym]; std::string letter = diacritic_example_letter[sym]; std::string written = "^" + letter + sym; if (sym == "~") { written = "^" + letter + "^~"; } if (sym == "`") { written = "^" + letter + "^`"; } std::string display = "^" + letter + sym; kt << display << " | @t ^" << written << " @ | " << name << " ||\n"; } kt << "table@"; return kt.str(); } extern "C" std::string extended_latin_examples([[maybe_unused]] Machine& machine) { std::stringstream kt {}; kt << "@s2 Non-Roman characters @\n\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) { auto [code, name] = extended_latin[symbol]; std::string coded = "^" + symbol + "^"; std::string file_literal = "^^ #- " + symbol + " #- ^^"; kt << coded << " | @t " << file_literal << " @ | " << name << " ||\n"; } kt << "table@"; return kt.str(); } extern "C" std::string pinyin_examples([[maybe_unused]] Machine& machine) { std::stringstream kt {}; kt << "@s2 Chinese Pinyin syllable accents @\n\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) { auto [code, name] = codename; std::string literal = "^a" + symbol; kt << literal << " | @t ^" << literal << " @ | " << name << " ||\n"; } kt << "table@"; return kt.str(); } extern "C" std::string unicode_examples([[maybe_unused]] Machine& machine) { std::stringstream kt {}; kt << "\n@s2 Including Unicode in Klammertext files @\n\n" << "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 " << "Unicode character in Klammertext, you surround its hexadecimal number with " << "the \"^^\" character. For example, the Cyrillic capital letter \"I\" is \"И\", " << "included in Klammertext as @t ^^0418^^ @.\n\n" << "To search for Unicode characters by their description, see: " << "@link https://www.fileformat.info/info/unicode/char/search.htm @\n\n"; kt << "@s3 Egyptian hieroglyphs @\n\n"; kt << "@table :caption Examples of Egyptian hieroglyphs :cell_hpos c c c l :leading 1.1 |\n" << "Hieroglyph | Written | Sound | Gardiner ||\n"; // The body font carries no hieroglyphs, so the glyph names the // typeface it needs (shipped in fnt/); @document declares and localizes // it. Under txt @typeface passes the text through unchanged. for (auto glyph : ptolemy) { std::string code = "^" + glyph[2] + "^"; kt << "@egyptian " << process_unicode_codepoint(code) << " @ | " << "@t ^'" << code << "'^ @ | " << glyph[1] << " | " << glyph[0] << " ||\n"; } kt << "table@\n\n" << "\nIn @link https://en.wikipedia.org/wiki/List_of_Egyptian_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"; 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(); } extern "C" std::string argtype_definitions(Machine& machine) { auto argtypes = machine.m_argtypes; std::stringstream kt {}; std::set custom {}; std::map> custom_types {}; kt << "@s2 Types of klammer arguments @\n\n" << "@define :style r | \n"; // Hack to make the "Pattern:" label line up vertically; // there should be a klammer that aligns text at "tab stops". auto name_width = std::string("klammertext").size() + 1; for (const auto& name : argtypes.m_names) { auto argtype = argtypes.m_types.at(name); auto filename = argtype.m_loc.m_filename; if (contains(filename, "argtype_registry")) { std::string fill(name_width - name.size(), '~'); kt << "\n@c " << name << " c@ @t " << fill << " t@ @i Pattern: i@ ~ @c " << argtype.m_symbolic_pattern << " c@ |\n" << capitalize(trim(argtype.m_desc)) << " ||\n"; } else { custom.insert(filename); custom_types[filename].push_back(name); } } kt << "\ndefine@\n\n" << "@vspace .5 @\n@s3 Reusing argtype symbols @\n\n" << "A previously defined type can be included in the definition of a new type " << "by surrounding the name of the existing type in single quotation marks. " << "For example, the @c uint c@ type (unsigned integer) is used to define an integer with a " << "`+' or `-' character before it.\n\n" << "@vspace .5 @\n@s3 Custom argument types @\n\n" << "Other argtypes are defined for special purposes for klammers defined in the following files:\n\n"; kt << "@define :style t |\n"; for (auto [filename, types] : custom_types) { auto type_count = types.size(); int column_count = type_count < 4 ? 1 : int(float(type_count)/3); // auto count = int(float(types.size()) / 3); kt << symbolic_klammertext_filename(filename) << " |\n" << "@columns :style c :n " << column_count << " | " << join(types, " ") << " @ ||\n\n"; } kt << "define@\n\n"; return kt.str(); } // Verbatim text as a table cell: a regex, a replacement string. It goes // through @c, whose literal content is exempt from every special (a bar, a // hash, an at sign) -- but a literal value has its caret forms processed as // it is built, so a caret is written quoted: bare, "[^^@#|]+" would display // as "[^@#|]+" and "[@|#^:*]" would lose its caret to the colon. The // exception is a caret before an apostrophe: "^'" is not a quoting form (it // opens a literal span elsewhere) and passes through as written. static std::string code_cell(const std::string& text) { std::string quoted {}; for (size_t i = 0; i < text.size(); i++) { const char c = text[i]; const bool before_apostrophe = i + 1 < text.size() && text[i + 1] == '\''; quoted += (c == '^' && !before_apostrophe) ? "^^" : std::string(1, c); } return "@c :nd | " + quoted + " c@"; } // The Regex cell of a katom type. A type the Machine assigns is matched by // no regex and its cell is empty -- ws_added among them, whose m_pattern is a // description and not a regex (see the note in mac/katom_matching.cpp). The // two whitespace types are named, since "@c c@" would show nothing. static std::string katom_regex_cell(const Ktype& k) { if (k.m_pattern.empty() || k.m_type == katom_t::ws_added) { return ""; } if (k.m_type == katom_t::space) { return "@symb-space"; } if (k.m_type == katom_t::newline) { return "@t \\n @"; } return code_cell(k.m_pattern); } extern "C" std::string katom_descriptions([[maybe_unused]] Machine& machine) { std::stringstream kt {}; kt << R"( A ``katom'' is an individual element in the Klammertext input text. Each katom has a type, listed below. You can see how Klammertext divides up text into katoms with the @t-kdiag command. In the katom patterns, @symb-name is a word that begins with a letter and only contains letters, numbers, or the underscore (_) or period (.) characters. The @symb-n symbol is an integer greater than or equal to 1. The @symb-code symbol is one of the codes used with the @t ^^ @ character to create characters that are not available in the English alphabet. The codes for producing those characters can be displayed by the @t-kdesc command: @t kdesc -c -u @ )"; // ":txt_width N" widens the TABLE alone. This text is re-read by a // Machine of its own (Eval::eval), so a @@@state here changes the width // the @table below is laid out to and nothing else: the prose above // leaves that Machine as plain text and is filled to the caller's width // by the caller's phases, which skip the table because it is laid out. const std::string txt_width = machine.m_state.value("txt_width", false); if (!txt_width.empty()) { kt << "@@@state Target_txt_width :replace " << txt_width << " @@@\n"; } const bool show_regex = machine.m_state.value("regex", false) == "true"; // The rows first, so that the rules can be placed by the number of rows // before them (the header is the first): one above each group's title // and one below it, which is what marks the row as a title. The first // group needs none above: "head" already rules the header off. const std::string b = " | "; const std::string title_cells = show_regex ? b + b + b + b : b + b + b; std::stringstream rows {}; std::stringstream hlines {}; int row_count = 1; for (const auto& group : katom_type_groups) { if (row_count > 1) { hlines << " " << row_count; } rows << title_cells << "@i " << group.m_title << " @ ||\n"; row_count++; hlines << " " << row_count; // ... and one below the title for (const auto& type : group.m_types) { const Ktype& k = type_to_katom_instance(type.m_type); rows << static_cast(k.m_type) << b << k.m_name << b << type.m_pattern << b; if (show_regex) { rows << katom_regex_cell(k) << b; } rows << type.m_description << " ||\n"; row_count++; } } kt << "@table " << ":number false " << ":hline head bottom" << hlines.str() << " :leading .95 "; if (show_regex) { kt << ":cell_hpos c l :style r r t t r :column_width fit fit fit fit fill |\n" << "Index | Name | Pattern | Regex | Description ||\n"; } else { kt << ":cell_hpos c l :style r r t r :column_width fit fit fit fill |\n" << "Index | Name | Pattern | Description ||\n"; } kt << rows.str() << "table@\n"; return kt.str(); } extern "C" std::string katom_rewrite_patterns([[maybe_unused]] Machine& machine) { std::stringstream kt {}; kt << "@s2 Katom rewrite patterns @\n\n" << R"(The table lists how a text is reformed or split into one or more katoms. @i [Need a longer description here.] @ )" << "@table :caption The rewriting patterns for elements that are not katoms | " << "Category | Description | Pattern | Replacement ||\n"; // Dispatch order, not declaration order: what a reader needs is the // sequence the rules are actually TRIED in, since that is what decides // which one fires. rule_category shown {}; bool first = true; std::string b = " | "; for (size_t i : rule_order(rewrite_order::by_category)) { const Rewrite_rule& rule = rewrite_rules[i]; std::string category = category_name(rule.category); if (first || rule.category != shown) { shown = rule.category; first = false; } else { category = ""; // name a category once, at its head } std::string desc = rule.description; desc[0] = static_cast(toupper(desc[0])); kt << category << b << desc << b << code_cell(rule.pattern.m_pattern) << b << (rule.special ? "(hyphens separate word arguments)" : code_cell(rule.replacement)) << " ||\n"; } kt << "table@\n"; return kt.str(); } // A path written into generated Klammertext: the six specials and the // txt target's nbsp marker ~ quoted with ^, which any ASCII punctuation // accepts. A font directory is the user's to name, so it may hold any of them. static std::string kt_literal(const std::string& text) { static const std::string quoted = "@|#^:*~"; std::string result {}; for (char c : text) { if (quoted.find(c) != std::string::npos) { result += '^'; } result += c; } return result; } // The font store as a table per search directory, in search order. The // search path is DESCRIBED here, not validated: a directory that does not // exist is a row saying so, never an error, because resolve_font() skips // one silently (a document renders fine with it) and a fresh install has no // ~/.klammertext/fonts yet -- the default entry would fail every new user. // kdesc --klammersets treats a missing search directory the same way. extern "C" std::string klammertext_fonts([[maybe_unused]] Machine& machine) { std::stringstream kt {}; kt << "@s2 Klammertext typefaces and font styles @\n\n"; // The directory each dir_name was FIRST seen in: the store searches in // this order and stops at the first hit, so a later one is shadowed. std::map first_seen {}; strings_t bases = installed_font_dirs(); bases.push_back(default_font_dir()); for (size_t i = 0; i < bases.size(); i++) { const std::string& base = bases[i]; bool is_default = (i == bases.size() - 1); std::string caption = is_default ? "Klammertext default typefaces: " + kt_literal(symbolic_klammertext_filename(base)) : "Installed typeface directory: " + kt_literal(base); // Rows before the header: the "Shadowed by" column exists only in a // table that has something to put in it. struct Row { std::string family, styles, shadowed_by; }; std::vector rows {}; bool any_shadowed = false; for (const Resolved_font& font : fonts_in_directory(base)) { strings_t styles {}; if (!font.regular.empty()) styles.push_back("Regular"); if (!font.bold.empty()) styles.push_back("Bold"); if (!font.italic.empty()) styles.push_back("Italic"); if (!font.bold_italic.empty()) styles.push_back("BoldItalic"); Row row { kt_literal(font.family_name), join(styles, ", "), "" }; if (first_seen.contains(font.dir_name)) { row.shadowed_by = kt_literal(first_seen[font.dir_name]); any_shadowed = true; } else { first_seen[font.dir_name] = base; } rows.push_back(row); } kt << "\n@table :caption " << caption << " |\n" << "Typeface name | Font styles" << (any_shadowed ? " | Shadowed by" : "") << " ||\n"; if (!fs::is_directory(base)) { kt << "[directory does not exist] ||\n"; } else if (rows.empty()) { kt << "[no fonts] ||\n"; } for (const Row& row : rows) { kt << row.family << " | " << row.styles; if (any_shadowed) { kt << " | " << row.shadowed_by; } kt << " ||\n"; } kt << "table@\n\n"; } return kt.str(); }