diff --git a/doc/command-line-options.md b/doc/command-line-options.md index 49cc3aa681..f98c5930b7 100644 --- a/doc/command-line-options.md +++ b/doc/command-line-options.md @@ -38,6 +38,7 @@ Option | Description `` | Desktop applet arguments. `--env ` | Set environment variable. `--cwd ` | Set current working directory. +`--fonts [v[erbose]]` | Print available fonts (with horizontal scrolling). #### Inline configuration diff --git a/src/netxs/apps/test.hpp b/src/netxs/apps/test.hpp index fead9f3d04..7f3b48f3ec 100644 --- a/src/netxs/apps/test.hpp +++ b/src/netxs/apps/test.hpp @@ -65,6 +65,7 @@ namespace netxs::app::test .add("\n") .add(header(skin::globals().NsInfoSubcellSize)) .add("\n") + .add("๐Ÿ˜‰", vss<11>) // Color emoji font load trigger. .add("\2Cell", utf::vs10, utf::vs08, vss<11>) .add(" ") .add("\2Word", utf::vs10, vss<11>) diff --git a/src/netxs/desktopio/ansivt.hpp b/src/netxs/desktopio/ansivt.hpp index 8c72d17a77..ddf9a376f5 100644 --- a/src/netxs/desktopio/ansivt.hpp +++ b/src/netxs/desktopio/ansivt.hpp @@ -441,6 +441,11 @@ namespace netxs::ansi return f < 8 ? add("\033[", f + 30, ";22m") // CSI22m: Linux console unexpectedly sets the high intensity bit when CSI 9x m used. : add("\033[", f + 90 - 8, "m"); } + auto& bgc_16(si32 f) // basevt: SGR Foreground color (16-color mode). + { + return f < 8 ? add("\033[", f + 40, "m") + : add("\033[", f + 100 - 8, "m"); + } auto& bgc_8(si32 b) // basevt: SGR Background color (8-color mode). { return add("\033[", b + 40, 'm'); @@ -448,25 +453,41 @@ namespace netxs::ansi template auto& fgc(argb c) // basevt: SGR Foreground color. RGB: red, green, blue (+alpha for VT2D). { - if constexpr (Mode == svga::vt16 ) return fgc_16(c.to_vtm16(true)); - else if constexpr (Mode == svga::vt256) return fgc256(c.to_256cube()); - else if constexpr (Mode == svga::vt_2D) return fgx(c); - else if constexpr (Mode == svga::vtrgb) return c.chan.a == 0 ? add("\033[39m") - : add("\033[38;2;", c.chan.r, ';', - c.chan.g, ';', - c.chan.b, 'm'); + auto i = c.is_indexed(); + if constexpr (Mode == svga::vt16 ) return fgc_16(i ? argb{ argb::vt256[i - 1] }.to_vtm16(true) : c.to_vtm16(true)); + else if constexpr (Mode == svga::vt256) + { + if (i == 0 ) return fgc256(c.to_256cube()); + else if (i >= 16) return fgc256(i - 1); + else return fgc_16(i - 1); + } + else if constexpr (Mode == svga::vt_2D) return fgx(i ? argb{ argb::vt256[i - 1] } : c); + else if constexpr (Mode == svga::vtrgb) + { + if (i == 0 ) return c.chan.a == 0 ? add("\033[39m") : add("\033[38;2;", c.chan.r, ';', c.chan.g, ';', c.chan.b, 'm'); + else if (i <= 16) return fgc_16(i - 1); + else return fgc256(i - 1); + } else return block; } template auto& bgc(argb c) // basevt: SGR Background color. RGB: red, green, blue. { - if constexpr (Mode == svga::vt16 ) return bgc_8(c.to_vtm8()); - else if constexpr (Mode == svga::vt256) return bgc256(c.to_256cube()); - else if constexpr (Mode == svga::vt_2D) return bgx(c); - else if constexpr (Mode == svga::vtrgb) return c.chan.a == 0 ? add("\033[49m") - : add("\033[48;2;", c.chan.r, ';', - c.chan.g, ';', - c.chan.b, 'm'); + auto i = c.is_indexed(); + if constexpr (Mode == svga::vt16 ) return bgc_8(i ? argb{ argb::vt256[i - 1] }.to_vtm8() : c.to_vtm8()); + else if constexpr (Mode == svga::vt256) + { + if (i == 0 ) return bgc256(c.to_256cube()); + else if (i <= 16) return bgc_16(i - 1); + else return bgc256(i - 1); + } + else if constexpr (Mode == svga::vt_2D) return bgx(i ? argb{ argb::vt256[i - 1] } : c); + else if constexpr (Mode == svga::vtrgb) + { + if (i == 0 ) return c.chan.a == 0 ? add("\033[49m") : add("\033[48;2;", c.chan.r, ';', c.chan.g, ';', c.chan.b, 'm'); + else if (i <= 16) return bgc_16(i - 1); + else return bgc256(i - 1); + } else return block; } template @@ -1247,15 +1268,16 @@ namespace netxs::ansi { using tree = func; - tree table ; - tree table_quest ; - tree table_excl ; - tree table_gt ; - tree table_lt ; - tree table_equals ; - tree table_hash ; + tree table; + tree table_quest; + tree table_quest_dollarsn; + tree table_excl; + tree table_gt; + tree table_lt; + tree table_equals; + tree table_hash; tree table_dollarsn; - tree table_space ; + tree table_space; tree table_dblqoute; tree table_sglqoute; tree table_asterisk; @@ -1290,6 +1312,8 @@ namespace netxs::ansi * - void cursor0(si32 i); // Set cursor inside the cell. */ + table_quest_dollarsn.resize(0x100); + table_quest_dollarsn[csi_ccc] = nullptr; table_quest .resize(0x100); table_quest[dec_set] = nullptr; table_quest[dec_rst] = nullptr; @@ -1443,18 +1467,19 @@ namespace netxs::ansi } void proceed(si32 cmd, T*& client) { table.execute(cmd, client); } - void proceed (fifo& q, T*& p) { table .execute(q, p); } - void proceed_quest (fifo& q, T*& p) { table_quest .execute(q, p); } - void proceed_gt (fifo& q, T*& p) { table_gt .execute(q, p); } - void proceed_lt (fifo& q, T*& p) { table_lt .execute(q, p); } - void proceed_hash (fifo& q, T*& p) { table_hash .execute(q, p); } - void proceed_equals (fifo& q, T*& p) { table_equals .execute(q, p); } - void proceed_excl (fifo& q, T*& p) { table_excl .execute(q, p); } - void proceed_dollarsn (fifo& q, T*& p) { table_dollarsn.execute(q, p); } - void proceed_space (fifo& q, T*& p) { table_space .execute(q, p); } - void proceed_dblqoute (fifo& q, T*& p) { table_dblqoute.execute(q, p); } - void proceed_sglqoute (fifo& q, T*& p) { table_sglqoute.execute(q, p); } - void proceed_asterisk (fifo& q, T*& p) { table_asterisk.execute(q, p); } + void proceed (fifo& q, T*& p) { table .execute(q, p); } + void proceed_quest (fifo& q, T*& p) { table_quest .execute(q, p); } + void proceed_quest_dollarsn(fifo& q, T*& p) { table_quest_dollarsn.execute(q, p); } + void proceed_gt (fifo& q, T*& p) { table_gt .execute(q, p); } + void proceed_lt (fifo& q, T*& p) { table_lt .execute(q, p); } + void proceed_hash (fifo& q, T*& p) { table_hash .execute(q, p); } + void proceed_equals (fifo& q, T*& p) { table_equals .execute(q, p); } + void proceed_excl (fifo& q, T*& p) { table_excl .execute(q, p); } + void proceed_dollarsn (fifo& q, T*& p) { table_dollarsn .execute(q, p); } + void proceed_space (fifo& q, T*& p) { table_space .execute(q, p); } + void proceed_dblqoute (fifo& q, T*& p) { table_dblqoute .execute(q, p); } + void proceed_sglqoute (fifo& q, T*& p) { table_sglqoute .execute(q, p); } + void proceed_asterisk (fifo& q, T*& p) { table_asterisk .execute(q, p); } }; template @@ -1463,8 +1488,16 @@ namespace netxs::ansi static auto vt_parser = typename T::template vt_parser{}; return vt_parser; } - template void parse(view utf8, T*& dest) { ansi::get_parser().parse(utf8, dest); } - template void parse(view utf8, T*&& dest) { auto dptr = dest; parse(utf8, dptr); } + template void parse(view utf8, T*& dest) + { + auto& vt_parser = ansi::get_parser(); + vt_parser.parse(utf8, dest); + } + template void parse(view utf8, T*&& dest) + { + auto dptr = dest; + parse(utf8, dptr); + } template using esc_t = func; template using osc_h = std::function; @@ -1503,22 +1536,24 @@ namespace netxs::ansi auto decsg = client->decsg; auto s = [&](auto const& traits, qiew utf8) { - client->defer = faux; + client->last_cluster = {}; intro.execute(traits.control, utf8, client); // Make one iteration using firstcmd and return. decsg = client->decsg; // Sync DECSG mode if buffer/client changed. //todo Should we make it shared among buffers? return utf8; }; + auto p = [&](si32 cmatrix) + { + client->pop_cluster(cmatrix); + }; auto y = [&](auto const& cluster) { client->post(cluster); - client->defer = true; }; auto a = [&](view plain) { client->ascii(plain); - client->defer = true; }; - utf::decode(s, y, a, utf8, decsg); + client->last_cluster = utf::decode(client->last_cluster, utf8, decsg, s, y, a, p); client->flush(); } // vt_parser: Static UTF-8/ANSI parser proc. @@ -1614,7 +1649,11 @@ namespace netxs::ansi { ascii.pop_front(); fill(queue); - if (c == '?' ) csier.proceed_quest (queue, client); + if (c == '?' ) + { + if (b == '$') csier.proceed_quest_dollarsn(queue, client); + else csier.proceed_quest (queue, client); + } else if (c == '>' ) csier.proceed_gt (queue, client); else if (c == '<' ) csier.proceed_lt (queue, client); else if (c == '=' ) csier.proceed_equals (queue, client); @@ -1783,8 +1822,8 @@ namespace netxs::ansi deco style{}; // parser: Parser style. deco state{}; // parser: Parser style last state. mark brush{}; // parser: Parser brush. + text last_cluster{}; // parser: The last cluster that could continue to grow. si32 decsg{}; // parser: DEC Special Graphics Mode. - bool defer{}; // parser: The last character was a cluster that could continue to grow. private: core::body proto_cells{}; // parser: Proto lyric. @@ -1835,7 +1874,7 @@ namespace netxs::ansi } proto_count = 0; proto_cells.clear(); - defer = faux; + last_cluster = {}; } auto empty() const { @@ -1902,7 +1941,6 @@ namespace netxs::ansi { // Test : // echo -e '\U2069+123'; echo -e '+\U2068+123'; echo -e '\e[42m+123\U2068\e[m'; echo -e '123\U202C++'; - //todo implement LRE RLE etc. Now all Cf's are stored within clusters. //auto& marker = get_ansi_marker(); //if (auto set_prop = marker.setter[attr.control]) @@ -1954,6 +1992,7 @@ namespace netxs::ansi } virtual void meta(deco const& /*old_style*/) { }; virtual void data(si32 /*width*/, si32 /*height*/, core::body const& /*proto*/) { }; + virtual void pop_cluster(si32 /*cmatrix*/) { }; }; // ansi: Cursor manipulation command list. diff --git a/src/netxs/desktopio/application.hpp b/src/netxs/desktopio/application.hpp index 4ca747d8fa..9f5ef806b9 100644 --- a/src/netxs/desktopio/application.hpp +++ b/src/netxs/desktopio/application.hpp @@ -22,7 +22,7 @@ namespace netxs::app namespace netxs::app::shared { - static const auto version = "v2026.04.19"; + static const auto version = "v2026.04.24"; static const auto repository = "https://github.com/directvt/vtm"; static const auto usr_config = "~/.config/vtm/settings.xml"s; static const auto sys_config = "/etc/vtm/settings.xml"s; @@ -1162,7 +1162,10 @@ namespace netxs::app::shared //todo sync settings with tui_domain (auth::config) auto gui_event_domain = netxs::events::auth{}; auto window = gui_event_domain.create(gui_event_domain, gc, dot_21); - window->connect(); + if (window->fcache) + { + window->connect(); + } }; if (os::stdout_fd != os::invalid_fd) { diff --git a/src/netxs/desktopio/canvas.hpp b/src/netxs/desktopio/canvas.hpp index fcf29394d4..279288846b 100644 --- a/src/netxs/desktopio/canvas.hpp +++ b/src/netxs/desktopio/canvas.hpp @@ -223,10 +223,14 @@ namespace netxs auto ok = (c.token & netxs::letoh(argb::indexed_mask)) == netxs::letoh(argb::indexed_color); // Check if it is in an indexed color format. return ok ? c.chan.b + 1 : 0; } + auto is_indexed() const + { + return argb::is_indexed_color(*this); + } // argb: Unpack true color. static auto unpack_indexed_color(argb c, auto& ext_vt256) { - if (auto index = is_indexed_color(c)) + if (auto index = argb::is_indexed_color(c)) { return argb{ ext_vt256[index - 1] }; } @@ -235,6 +239,26 @@ namespace netxs return c; } } + auto& unpack_indexed_color(auto& ext_vt256) + { + if (auto index = is_indexed()) + { + token = netxs::letoh(ext_vt256[index - 1]); + } + return *this; + } + auto& unpack_indexed_color(auto& ext_vt256, argb def_clr) + { + if (token == 0) // argb::transparent + { + token = def_clr.token; + } + else if (auto index = is_indexed()) + { + token = netxs::letoh(ext_vt256[index - 1]); + } + return *this; + } auto& swap_rb() { token = ((token >> 0) & 0xFF'00'FF'00) | @@ -2146,8 +2170,10 @@ namespace netxs { if constexpr (UseSGR) { - auto f = fg.to_vtm16(); - auto b = bg.to_vtm8(); + auto f = fg.is_indexed(); + auto b = bg.is_indexed(); + f = f ? argb{ argb::vt256[f - 1] }.to_vtm16() : fg.to_vtm16(); + b = b ? argb{ argb::vt256[b - 1] }.to_vtm8() : bg.to_vtm8(); if (fg != bg && f == b) // Avoid color collizions. { fix_collision_vtm8(f); @@ -2884,7 +2910,7 @@ namespace netxs constexpr auto& stl() { return st.token; } // cell: Return style token. constexpr auto& stl() const { return st.token; } // cell: Return style token. constexpr auto link() const { return id; } // cell: Return object ID. - constexpr auto isspc() const { return gc.is_space(); } // cell: Return true if char is whitespace. + constexpr auto isspc() const { return gc.is_space(); } // cell: Return true if char is whitespace (null included). constexpr auto isnul() const { return gc.is_null(); } // cell: Return true if char is null. auto issame_visual(cell const& c) const // cell: Is the cell visually identical. { @@ -2892,7 +2918,7 @@ namespace netxs { if (uv.bg == c.uv.bg) { - if (xy() == 0 || txt().front() == ' ') + if (xy() == 0 || isspc()) { return true; } @@ -3581,6 +3607,7 @@ namespace netxs } using vrgb = netxs::raw_vector>; + using pals = std::remove_const_t; // canvas: Core grid. class core @@ -4164,6 +4191,28 @@ namespace netxs } return hit; } + void unpack_indexed_colors(pals const& palette, cell defclr) + { + auto def_fgc = defclr.fgc(); + auto def_bgc = defclr.bgc(); + for (auto& c : canvas) + { + c.fgc().unpack_indexed_color(palette, def_fgc); + c.bgc().unpack_indexed_color(palette, def_bgc); + } + } + void unpack_indexed_colors(core& dest, pals const& palette, cell defclr) const + { + auto def_fgc = defclr.fgc(); + auto def_bgc = defclr.bgc(); + dest.size(region.size); + netxs::oncopy(*this, dest, [&](auto& src, auto& dst) + { + dst = src; + dst.fgc().unpack_indexed_color(palette, def_fgc); + dst.bgc().unpack_indexed_color(palette, def_bgc); + }); + } }; } diff --git a/src/netxs/desktopio/consrv.hpp b/src/netxs/desktopio/consrv.hpp index 66bd9cc136..59734891ed 100644 --- a/src/netxs/desktopio/consrv.hpp +++ b/src/netxs/desktopio/consrv.hpp @@ -2135,7 +2135,7 @@ struct impl : consrv && next.cdpoint < 65536 ? BMPtoOEM[next.cdpoint] : defchar(); auto size = code < 256 ? 1u : 2u; - if (rest < size) // Leave the last code point to indicate that the buffer is not empty. + if (rest < size) // Leave the last codepoint to indicate that the buffer is not empty. { crop.push_back((byte)(code >> 8)); lastbyte = (byte)(code & 0xFF); diff --git a/src/netxs/desktopio/controls.hpp b/src/netxs/desktopio/controls.hpp index 4372f45aa7..2c8791030e 100644 --- a/src/netxs/desktopio/controls.hpp +++ b/src/netxs/desktopio/controls.hpp @@ -420,7 +420,7 @@ namespace netxs::events text luna::run(context_t& context, view script_body, auto&& param) { using T = std::decay_t; - if constexpr (debugmode) log("%%script:\n%pads%%script%", prompt::lua, prompt::pads, ansi::hi(script_body)); + //if constexpr (debugmode) log("%%script:\n%pads%%script%", prompt::lua, prompt::pads, ansi::hi(script_body)); //if constexpr (std::is_same_v) log("%%script:\n%pads%%script%", prompt::lua, prompt::pads, ansi::hi(script_body)); //else log("%%script:\n%pads%%script%\n with arg: %%", prompt::lua, prompt::pads, ansi::hi(script_body), param); diff --git a/src/netxs/desktopio/directvt.hpp b/src/netxs/desktopio/directvt.hpp index 9cb07bbca5..7454c3bd8b 100644 --- a/src/netxs/desktopio/directvt.hpp +++ b/src/netxs/desktopio/directvt.hpp @@ -2131,7 +2131,21 @@ namespace netxs::directvt for (auto& jgc : lock.thing) { jumbos.set(jgc.token, jgc.cluster); - if constexpr (debugmode) log(prompt::s11n, "New gc token: ", utf::to_hex_0x(jgc.token), " cluster size ", jgc.cluster.size(), " data: ", jgc.cluster); + if constexpr (debugmode) + { + auto new_cluster = jgc.cluster; + utf::to_utf_from_code(utf::matrix::vs_runtime(3, 1, 0, 0), new_cluster); + auto cpit = utf::cpit{ jgc.cluster }; + while (cpit) + { + auto code = utf::to_hex(cpit.next().cdpoint); + auto code_view = view{ code }; + utf::trim_front(code_view, '0'); + new_cluster += ' '; + new_cluster += code_view; + } + log(prompt::s11n, "New gc token: ", utf::to_hex_0x(jgc.token), " cluster size ", jgc.cluster.size(), " data: ", new_cluster); + } } } // s11n: Recycle logs. diff --git a/src/netxs/desktopio/gui.hpp b/src/netxs/desktopio/gui.hpp index 5ae4bb864b..0545c01e3b 100644 --- a/src/netxs/desktopio/gui.hpp +++ b/src/netxs/desktopio/gui.hpp @@ -34,6 +34,13 @@ namespace netxs::gui static constexpr auto bold = 2; static constexpr auto bold_italic = bold | italic; static constexpr auto count = 4; + static auto str(si32 style_id) + { + return style_id == regular ? "regular" + : style_id == italic ? "italic" + : style_id == bold ? "bold" + : style_id == bold_italic ? "bold|italic" : "na"; + } }; struct cfg_t @@ -122,6 +129,8 @@ namespace netxs::gui using ft_library_sptr = sptr>; using irgb = netxs::irgb; + bool initialized{ true }; // fonts: Set faux if something is going wrong. + struct color_type { static constexpr auto _counter = 1 + __COUNTER__; @@ -347,12 +356,12 @@ namespace netxs::gui if ((!file_buff && !os::io::load_file(file_path, file_buff.buffer)) || FT_Err_Ok != ::FT_New_Memory_Face(fcache.ft_library.get(), file_buff.buffer.data(), (FT_Long)file_buff.buffer.size(), bare_face_ptr->face_index, &raw_face)) // Read a whole font/collection file (relatively slow, e.g. with 30Mb font files). { - log("%%Failed to load font family file '%family_name%' (style_id=%%): %filename%", prompt::gui, family_ref.family_name, style_id, utf8_path); + log("%%Failed to load font family file '%family_name%' (style_id=%%): %filename%", prompt::gui, family_ref.family_name, font_style::str(style_id), utf8_path); bare_face_ptr->valid = faux; } else { - log("%%Using font family '%family_name%': %iscolor%, index %index%, style=%%", prompt::gui, family_ref.family_name, family_ref.is_color != fonts::color_type::mono ? "color" : "monochromatic", fcache.font_fallback.size(), style_id); + log("%%Using font family '%family_name%': %iscolor%, index %index%, style=%%", prompt::gui, family_ref.family_name, family_ref.is_color != fonts::color_type::mono ? "color" : "monochromatic", fcache.font_fallback.size(), font_style::str(style_id)); // Apply axes. if (bare_face_ptr->is_variable_font) { @@ -424,7 +433,6 @@ namespace netxs::gui bool monospaced{ faux }; text font_name; - font_face_t() = default; font_face_t(font_face_t&&) = default; font_face_t(fonts& fcache, font_family_t& family_rec) : fcache{ fcache }, @@ -510,7 +518,7 @@ namespace netxs::gui //log("Sort family '%%'", family_ref.family_name); //for (auto style_id : { font_style::regular, font_style::bold, font_style::italic, font_style::bold_italic }) //{ - // log("style_id=", style_id); + // log("style_id=", font_style::str(style_id)); // auto& sorted_list = sorted_face_list[style_id]; // for (auto& bare_face_rec : sorted_list) // { @@ -912,7 +920,6 @@ namespace netxs::gui if (FT_Err_Ok != ::FT_Init_FreeType(&library)) { log("%%Could not initialize FreeType library", prompt::gui); - std::terminate(); } return ft_library_sptr(library, [](auto l){ if (l) ::FT_Done_FreeType(l); }); } @@ -1043,7 +1050,10 @@ namespace netxs::gui } } log("%%No fonts found in the system", prompt::gui); - std::terminate(); + initialized = faux; + static auto empty_fr = font_family_t{}; + static auto empty_ff = font_face_t{ *this, empty_fr }; + return empty_ff; } void log_fonts(bool show_ranges = faux) { @@ -1205,11 +1215,18 @@ namespace netxs::gui rect dashline; // fonts: Dashed underline rectangle block within the cell. rect wavyline; // fonts: Wavy underline outer rectangle block within the cell. + operator bool () const { return initialized; } + fonts(std::list& family_names, cfg_t::axis_vals_t& font_axes, si32 cell_height) : ft_library{ make_ft_library() }, os_locale{ ::hb_language_from_string(os::env::get_locale().c_str(), -1) }, font_shaper{ *this } { + if (!ft_library) + { + initialized = faux; + return; + } #if defined(_WIN32) auto registered_fonts = L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Fonts"sv; auto filter = [](auto& item) @@ -1501,7 +1518,7 @@ namespace netxs::gui log(prompt::gui, "No fonts provided, fallback to the first available font"); find_family(single_codepoint, true); // Take the first available font. } - set_cellsz(cell_height); + if (initialized) set_cellsz(cell_height); } }; diff --git a/src/netxs/desktopio/richtext.hpp b/src/netxs/desktopio/richtext.hpp index accde92009..0795b21149 100644 --- a/src/netxs/desktopio/richtext.hpp +++ b/src/netxs/desktopio/richtext.hpp @@ -1169,7 +1169,7 @@ namespace netxs::ui } //todo make it 2D // rich: Pop glyph matrix. - auto pop_cluster() + auto pop_cluster(bool peek = faux) { auto cluster = netxs::text{}; auto size = (si32)core::canvas.size(); @@ -1201,8 +1201,11 @@ namespace netxs::ui } if (head == tail) { - cluster = back.txt(); - if (cluster.size()) + if (peek) + { + cluster = back.txt(); + } + else { core::crop(size - w); } @@ -1248,28 +1251,21 @@ namespace netxs::ui auto& operator = (auto utf8) { wipe(brush); ansi::parse(utf8, this); return *this; } auto& operator += (auto utf8) { - if (parser::defer && caret && caret == length()) - { - //if constexpr (debugmode) log("try to reassemble cluster=", lyric->back().txt()); - auto last_cluster = lyric->pop_cluster(); - if (caret != length()) - { - caret = length(); - auto reassembled_cluster = text{}; - reassembled_cluster.reserve(last_cluster.length() + utf8.length()); - reassembled_cluster += last_cluster; - reassembled_cluster += utf8; - ansi::parse(reassembled_cluster, this); - //if constexpr (debugmode) log("\treassembled_cluster=", utf::buffer_to_hex(reassembled_cluster, true)); - return *this; - } - } ansi::parse(utf8, this); return *this; } operator writ const& () const { return locus; } + void pop_cluster(si32 /*cmatrix*/) override + { + //todo use cmatrix + if (length()) + { + lyric->pop_cluster(faux); + caret = length(); + } + } void decouple() { lyric = ptr::shared(*lyric); } // para: Make canvas isolated copy. void content(rich& r){ *lyric = r; caret = r.length(); } // para: Set paragraph content. auto& content() const { return *lyric; } // para: Return paragraph content. @@ -1474,8 +1470,8 @@ namespace netxs::ui auto left = line.take_piece(0, caret); auto right = line.take_piece(caret); std::swap(left, line); - parser::defer = true; auto prev_caret = caret; + parser::last_cluster = line.pop_cluster(true); operator+=(utf8); caret = line.length(); if (inserting) diff --git a/src/netxs/desktopio/system.hpp b/src/netxs/desktopio/system.hpp index e5162dedf4..5b783acdaf 100644 --- a/src/netxs/desktopio/system.hpp +++ b/src/netxs/desktopio/system.hpp @@ -625,20 +625,20 @@ namespace netxs::os template auto attr(cell const& c) { - auto& fgc = c.fgc(); - auto& bgc = c.bgc(); - auto f = si32{}; - auto b = si32{}; + auto fgc = c.fgc(); + auto bgc = c.bgc(); + auto f = fgc.is_indexed(); + auto b = bgc.is_indexed(); if constexpr (Mode == svga::nt16) { - f = fgc.to_vga16(true); - b = bgc.to_vga16(faux); + if (!f) f = fgc.to_vga16(true); else if (--f >= 16) f = argb{ argb::vt256[f] }.to_vga16(true); + if (!b) b = bgc.to_vga16(faux); else if (--b >= 16) b = argb{ argb::vt256[b] }.to_vga16(faux); if (f == b && fgc != bgc) cell::clrs::fix_collision_vga16(f); } else { - f = fgc.to_vtm16(true); - b = bgc.to_vtm16(faux); + if (!f) f = fgc.to_vtm16(true); else f = argb{ argb::vt256[f - 1] }.to_vtm16(true); + if (!b) b = bgc.to_vtm16(faux); else b = argb{ argb::vt256[b - 1] }.to_vtm16(faux); if (f == b && fgc != bgc) cell::clrs::fix_collision_vtm16(f); } if (c.inv()) std::swap(f, b); diff --git a/src/netxs/desktopio/terminal.hpp b/src/netxs/desktopio/terminal.hpp index 223ff129d3..8a8b55b523 100644 --- a/src/netxs/desktopio/terminal.hpp +++ b/src/netxs/desktopio/terminal.hpp @@ -227,8 +227,6 @@ namespace netxs::ui // term: Terminal configuration. struct termconfig { - using pals = std::remove_const_t; - si32 def_mxline; si32 def_length; si32 def_growdt; @@ -721,7 +719,6 @@ namespace netxs::ui // term: Terminal 16/256 color palette tracking functionality. struct c_tracking { - using pals = std::remove_const_t; using func = utf::unordered_map>; enum class type { invalid, rgbcolor, request }; @@ -1184,6 +1181,7 @@ namespace netxs::ui vt.csier.table_quest[dec_set] = V{ p->owner.decset(q); }; vt.csier.table_quest[dec_rst] = V{ p->owner.decrst(q); }; + vt.csier.table_quest_dollarsn[csi_ccc] = V{ p->owner.decrqm(q); }; // DECRQM: CSI ? mode $ p vt.csier.table[dec_set] = V{ p->owner.modset(q); }; // ESC [ n h vt.csier.table[dec_rst] = V{ p->owner.modrst(q); }; // ESC [ n l @@ -1833,14 +1831,45 @@ namespace netxs::ui void msg(si32 cmd, qiew& q) { parser::flush(); - static constexpr auto DECRQSS_str = "$q"sv; // Request settings. (Neovim using it) - static constexpr auto SIXEL_str = "q"sv; // Sixel image. - static constexpr auto ST_str = "\x1b\\"sv; // ST. + static constexpr auto XTGETTCAP_str = "+q"sv; // Request terminal capabilities. (Neovim using it) \eP+q5463;524742;73657472676266;73657472676262\e\\ . + static constexpr auto DECRQSS_str = "$q"sv; // Request settings. (Neovim using it) + static constexpr auto SIXEL_str = "q"sv; // Sixel image. + static constexpr auto ST_str = "\x1b\\"sv; // ST. auto reply = flux{}; auto data = utf::take_binary_front(q, std::tuple{ "\x1b\\"sv, "\a"sv }); if (q.size() > 0 && q.front() == '\a' ) q.remove_prefix(1); // Pop '\a'. else if (q.size() > 1 && q.front() == '\x1b') q.remove_prefix(2); // Pop "\e\\". - if (data.starts_with(DECRQSS_str)) + if (data.starts_with(XTGETTCAP_str)) + { + data.remove_prefix(XTGETTCAP_str.size()); + if (data) + { + struct cap_info + { + view name; + view reply; + }; + // Full reply: \eP1+r5463=1;524742=1\e\\ . + //static constexpr auto setrgbf_str = "setrgbf"sv; // SGR state request. Reply: \eP1+r73657472676266=1b5b33383a323a25703125643a25703225643a25703325646d\e\\ (setrgbf=\e[38:2::%p1%d:%p2%d:%p3%dm) + //static constexpr auto setrgbb_str = "setrgbb"sv; // SGR state request. Reply: ... (setrgbb=\e[48:2::%p1%d:%p2%d:%p3%dm) + // Unsupported: \eP0+r\e\\ . + // Reply: \eP1+rHEXKEY=1\e\\ = \eP1+r524742=1\e\\ . + static constexpr auto caps = std::to_array({ { utf::make_hex_view<"Tc" >(), "1" }, // Tc cap request. Reply: \eP1+r5463=1\e\\ . + { utf::make_hex_view<"RGB" >(), "1" }, // RGB cap request. Reply: \eP1+r524742=1\e\\ . + { utf::make_hex_view<"Ms" >(), utf::make_hex_view<"\x1b]52;%p1%s;%p2%s\a">() }, // OSC52. + { utf::make_hex_view<"Smulx" >(), utf::make_hex_view<"\x1b[4:%p1%dm">() }, // Styled underlines cap request. + { utf::make_hex_view<"Setulc">(), utf::make_hex_view<"\x1b[58:2::%p1%d:%p2%d:%p3%dm">() } }); // Colored underlines cap request. + reply << "\x1bP+r"; // DCS + auto count = 0; + utf::split(data, ';', [&](auto termcap) + { + for (auto& cap : caps) if (termcap == cap.name) reply << (count++ ? ";" : "") << cap.name << "=" << cap.reply; + }); + reply << ST_str; + owner.write(reply.str()); + } + } + else if (data.starts_with(DECRQSS_str)) { data.remove_prefix(DECRQSS_str.size()); static constexpr auto SGR_str = "m"sv; // SGR state request. @@ -2558,6 +2587,7 @@ namespace netxs::ui // bufferbase: Pickup selected data from canvas. void selection_pickup(escx& buffer, rich& canvas, twod seltop, twod selend, si32 selmod, bool selbox) { + auto use_true_color = selmod == mime::richtext || selmod == mime::htmltext; auto limits = panel - dot_11; auto curtop = std::clamp(seltop, dot_00, limits); auto curend = std::clamp(selend, dot_00, limits); @@ -2569,10 +2599,25 @@ namespace netxs::ui square.normalize_itself(); if (selbox || grip_1.coor.y == grip_2.coor.y) { - selmod == mime::disabled || - selmod == mime::textonly || - selmod == mime::safetext ? buffer.s11n(canvas, square) - : buffer.s11n(canvas, square); + if (selmod == mime::disabled + || selmod == mime::textonly + || selmod == mime::safetext) + { + buffer.s11n(canvas, square); + } + else + { + if (use_true_color) + { + auto baked = core{}; + canvas.unpack_indexed_colors(baked, owner.ctrack.color, owner.defclr); + buffer.s11n(baked, square); + } + else + { + buffer.s11n(canvas, square); + } + } } else { @@ -2590,9 +2635,20 @@ namespace netxs::ui } else { - buffer.s11n(canvas, part_1); - buffer.s11n(canvas, part_2); - buffer.s11n(canvas, part_3); + if (use_true_color) + { + auto baked = core{}; + canvas.unpack_indexed_colors(baked, owner.ctrack.color, owner.defclr); + buffer.s11n(baked, part_1); + buffer.s11n(baked, part_2); + buffer.s11n(baked, part_3); + } + else + { + buffer.s11n(canvas, part_1); + buffer.s11n(canvas, part_2); + buffer.s11n(canvas, part_3); + } } } } @@ -2820,7 +2876,7 @@ namespace netxs::ui canvas.cutoff(coord, n, brush.spare.spc()); } // alt_screen: '\x7F' Delete letter backward. - void del(si32 n) override + void _del(si32 n) { bufferbase::flush(); n = std::max(0, n); @@ -2832,6 +2888,11 @@ namespace netxs::ui canvas.backsp(coord, n, brush.spare.spc()); if (coord.y < 0) coord = dot_00; } + void del(si32 n) override + { + bufferbase::flush(); + _del(n); + } // alt_screen: Move cursor by n in line. void move(si32 n) override { @@ -2955,6 +3016,12 @@ namespace netxs::ui _data(count, proto, fuse); } } + // alt_screen: Pop back the last cluster (parser callback). + void pop_cluster(si32 cmatrix) override + { + auto [w, h, x, y] = utf::matrix::whxy(cmatrix); + _del(w); + } // alt_screen: Parser callback. void data(si32 width, si32 height, core::body const& proto) override { @@ -3074,7 +3141,10 @@ namespace netxs::ui // } // else // { - // crop.s11n(stripe, brush_state); + // auto use_true_color = owner.selmod == mime::richtext || selmod == mime::htmltext; + // auto baked = core{}; + // if (use_true_color) stripe.unpack_indexed_colors(baked, owner.ctrack.color, owner.defclr); + // crop.s11n(use_true_colors ? baked : stripe, brush_state); // } // return crop; //} @@ -4934,9 +5004,8 @@ namespace netxs::ui assert(test_coord()); } // scroll_buf: '\x7F' Delete letters backward (by defclr) and move cursor back. Nobody do it (tested in WT, VTE). - void del(si32 n) override + void _del(si32 n) { - bufferbase::flush(); n = std::min(n, batch.caret); if (batch.caret > 0 && n > 0) { @@ -4945,6 +5014,11 @@ namespace netxs::ui curln.splice(batch.caret, n, brush.spare.spc()); } } + void del(si32 n) override + { + bufferbase::flush(); + _del(n); + } // scroll_buf: Move cursor by n in line. void move(si32 n) override { @@ -5297,6 +5371,12 @@ namespace netxs::ui _data(count, proto, fuse); } } + // scroll_buf: Pop back the last cluster (parser callback). + void pop_cluster(si32 cmatrix) override + { + auto [w, h, x, y] = utf::matrix::whxy(cmatrix); + _del(w); + } // scroll_buf: Proceed new text (parser callback). void data(si32 width, si32 height, core::body const& proto) override { @@ -5791,7 +5871,10 @@ namespace netxs::ui // } // else // { - // crop.s11n(stripe, brush_state); + // auto use_true_color = owner.selmod == mime::richtext || selmod == mime::htmltext; + // auto baked = core{}; + // if (use_true_color) stripe.unpack_indexed_colors(baked, owner.ctrack.color, owner.defclr); + // crop.s11n(use_true_colors ? baked : stripe, brush_state); // } // return crop; //} @@ -6589,6 +6672,7 @@ namespace netxs::ui if (i_top == -1) return; + auto use_true_color = selmod == mime::richtext || selmod == mime::htmltext; auto data = batch.begin(); auto head = data + i_top; auto tail = data + i_end; @@ -6612,6 +6696,10 @@ namespace netxs::ui coor.y += curln.height(panel.x); } while (head++ != tail); + if (use_true_color) + { + dest.unpack_indexed_colors(owner.ctrack.color, owner.defclr); + } selmod == mime::disabled || selmod == mime::textonly || selmod == mime::safetext ? yield.s11n(dest, mark) @@ -6619,6 +6707,7 @@ namespace netxs::ui } else { + auto baked = core{}; auto field = rect{ dot_00, dot_01 }; auto accum = cell{}; auto build = [&](auto print) @@ -6669,7 +6758,8 @@ namespace netxs::ui s = curln.style; } auto block = escx{}; - block.s11n(curln, field, accum); + if (use_true_color) curln.unpack_indexed_colors(baked, owner.ctrack.color, owner.defclr); + block.s11n(use_true_color ? baked : curln, field, accum); if (block.size() > 0) yield.add(block); else yield.eol(); }); @@ -7236,15 +7326,22 @@ namespace netxs::ui { upbox.remove_image_bits(removed_image_indexes); dnbox.remove_image_bits(removed_image_indexes); - auto wipe_batch = [&](auto policy) // Try to parallelize. - { - std::for_each(policy, batch.begin(), batch.end(), [&](auto& l) + #if defined (__ANDROID__) + std::for_each(batch.begin(), batch.end(), [&](auto& l) { l.remove_image_bits(removed_image_indexes); }); - }; - batch.length() > 100000 ? wipe_batch(std::execution::par) - : wipe_batch(std::execution::seq); + #else + auto wipe_batch = [&](auto policy) // Try to parallelize. + { + std::for_each(policy, batch.begin(), batch.end(), [&](auto& l) + { + l.remove_image_bits(removed_image_indexes); + }); + }; + batch.length() > 100000 ? wipe_batch(std::execution::par) + : wipe_batch(std::execution::seq); + #endif } }; @@ -7284,6 +7381,7 @@ namespace netxs::ui si32 altscr; // term: Alternate scroll mode. prot kbmode; // term: Keyboard input mode. escx w32key; // term: win32-input-mode forward buffer. + escx escbuf; // term: Reply buffer. bool ime_on; // term: IME composition is active. para imebox; // term: IME composition preview render. text imetxt; // term: IME composition preview source. @@ -7919,7 +8017,7 @@ namespace netxs::ui normal.brush.reset(); ipccon.reset(); } - // term: Set termnail parameters. (DECSET). + // term: Set terminal parameters. (DECSET). void _decset(si32 n) { switch (n) @@ -8009,13 +8107,13 @@ namespace netxs::ui break; } } - // term: Set termnail parameters. (DECSET). + // term: Set terminal parameters. (DECSET). void decset(si32 n) { target->flush(); _decset(n); } - // term: Set termnail parameters. (DECSET). + // term: Set terminal parameters. (DECSET). void decset(fifo& q) { target->flush(); @@ -8122,19 +8220,19 @@ namespace netxs::ui break; } } - // term: Reset termnail parameters. (DECRST). + // term: Reset terminal parameters. (DECRST). void decrst(si32 n) { target->flush(); _decrst(n); } - // term: Reset termnail parameters. (DECRST). + // term: Reset terminal parameters. (DECRST). void decrst(fifo& q) { target->flush(); while (auto next = q(0)) _decrst(next); } - // term: Set termnail parameters. + // term: Set terminal parameters. void _modset(si32 n) { switch (n) @@ -8149,7 +8247,7 @@ namespace netxs::ui break; } } - // term: Reset termnail parameters. + // term: Reset terminal parameters. void _modrst(si32 n) { switch (n) @@ -8164,18 +8262,61 @@ namespace netxs::ui break; } } - // term: Set termnail parameters. + // term: Set terminal parameters. void modset(fifo& q) { target->flush(); while (auto next = q(0)) _modset(next); } - // term: Reset termnail parameters. + // term: Reset terminal parameters. void modrst(fifo& q) { target->flush(); while (auto next = q(0)) _modrst(next); } + // term: Reset terminal parameters. + void _decrqm(si32 n) + { + switch (n) + { + case 69: // Left/Right Margins. + escbuf.add("\x1b[?69;0$y"); // We don't support this mode. + break; + case 1000: // Mouse reporting. + case 1002: // + case 1003: // + case 1006: // + case 1004: // Focus reporting. + case 1049: // Altbuf. + case 2004: // Bracketed Paste. + escbuf.add("\x1b[?").add(n).add(";1$y"); + break; + case 2026: // Synchronized Updates. + escbuf.add("\x1b[?2026;0$y"); // We do not support this mode (we rely on lazy rendering). + break; + case 2027: // Unicode Core (support grapheme clusters). + // Reply: \e[?2027;2$y Turned off. + // Reply: \e[?2027;1$y Terminal supports/active. + // Reply: \e[?2027;0$y Unknown. + escbuf.add("\x1b[?2027;1$y"); + break; + case 2031: // Extended Keys / Kitty Keyboard Protocol. + escbuf.add("\x1b[?2031;0$y"); + break; + case 2048: // Graphics. //todo + escbuf.add(""); + break; + default: + break; + } + } + // term: Request terminal mode. (DECRQM). + void decrqm(fifo& q) + { + target->flush(); + while (auto next = q(0)) _decrqm(next); + answer(escbuf); + } // term: Set scrollback buffer size and grow step. void sbsize(fifo& q) { @@ -8637,10 +8778,11 @@ namespace netxs::ui { if (gear.whlsi) { - auto count = std::abs(gear.whlsi); + //auto count = std::abs(gear.whlsi); auto arrow = decckm ? gear.whlsi > 0 ? "\033OA"sv : "\033OB"sv : gear.whlsi > 0 ? "\033[A"sv : "\033[B"sv; - data_out(utf::repeat(arrow, count)); + //data_out(utf::repeat(arrow, count)); // Don't repeat arrow keys in alternate scroll mode. + data_out(arrow); } gear.dismiss(); } diff --git a/src/netxs/desktopio/unidata.hpp b/src/netxs/desktopio/unidata.hpp index 609c8e3085..928755b3b0 100644 --- a/src/netxs/desktopio/unidata.hpp +++ b/src/netxs/desktopio/unidata.hpp @@ -2,7 +2,7 @@ * unidata.hpp autogenerated 2025-09-24 13:48:16.736453 * * Provides access to the Unicode Character Database. - * Properties of a single Unicode character are accessed by its code point value. + * Properties of a single Unicode character are accessed by its codepoint value. * * Format conventions * https://www.unicode.org/reports/tr44/ @@ -274,7 +274,7 @@ namespace netxs::unidata // Unicode 15.1.0 UAX #29 https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules bool allied(unidata const& next) { - static const auto lut = [] + static constexpr auto lut = [] { auto table = std::array{}; auto check = [](auto l, auto r) diff --git a/src/netxs/desktopio/unidatagen.py b/src/netxs/desktopio/unidatagen.py index 256f0d9333..4df60a03bc 100644 --- a/src/netxs/desktopio/unidatagen.py +++ b/src/netxs/desktopio/unidatagen.py @@ -94,9 +94,9 @@ 'Control' : 'Cc', # a C0 or C1 control code 'Format' : 'Cf', # a format control character - 'Surrogate' : 'Cs', # a surrogate code point + 'Surrogate' : 'Cs', # a surrogate codepoint 'Private_Use' : 'Co', # a private-use character - 'Unassigned' : 'Cn', # a reserved unassigned code point or a noncharacter + 'Unassigned' : 'Cn', # a reserved unassigned codepoint or a noncharacter 'Other' : 'C' } # Cc | Cf | Cs | Co | Cn # classification: empirically @@ -172,7 +172,7 @@ * {header} autogenerated {moment} * * Provides access to the Unicode Character Database. - * Properties of a single Unicode character are accessed by its code point value. + * Properties of a single Unicode character are accessed by its codepoint value. * * Format conventions * https://www.unicode.org/reports/tr44/ @@ -319,7 +319,7 @@ // Unicode 15.1.0 UAX #29 https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules bool allied(unidata const& next) {{ - static const auto lut = [] + static constexpr auto lut = [] {{ auto table = std::array{{}}; auto check = [](auto l, auto r) diff --git a/src/netxs/desktopio/utf.hpp b/src/netxs/desktopio/utf.hpp index a7e739f7fe..eb3db037ef 100644 --- a/src/netxs/desktopio/utf.hpp +++ b/src/netxs/desktopio/utf.hpp @@ -40,6 +40,36 @@ namespace netxs::utf { using ctrl = unidata::cntrls; + template + struct _fixed_string + { + char data[N]; + constexpr _fixed_string(const char (&s)[N]) { for (auto i = 0u; i < N; ++i) data[i] = s[i]; } + }; + template<_fixed_string S> + constexpr auto _make_hex_array() + { + constexpr auto to_hex_char = [](auto v){ return (char)(v < 10 ? '0' + v : 'a' + (v - 10)); }; + constexpr auto len = sizeof(S.data) - 1; + auto b = std::array{}; + for (auto i = 0u; i < len; ++i) + { + b[i * 2] = to_hex_char((byte)S.data[i] >> 4 & 0xF); + b[i * 2 + 1] = to_hex_char((byte)S.data[i] & 0xF); + } + return b; + } + template<_fixed_string S> + struct _hex_storage + { + static constexpr auto buffer = _make_hex_array(); + }; + template<_fixed_string S> + constexpr auto make_hex_view() + { + return view{ _hex_storage::buffer.data(), _hex_storage::buffer.size() }; + } + template struct _str2array { @@ -366,44 +396,37 @@ namespace netxs::utf { } constexpr prop& operator = (prop const&) = default; - // prop: Check if the next codepooint could be attached to the cluster. Return zero to continue attaching. Return non-zero if cluster is closed. + // prop: Append any non-control codepoint if the current cluster has no width. + auto we_has_no_width(prop const& next) + { + return unidata::ucwidth == 0 && cdpoint && !next.is_cmd(); + } + // prop: Combine if included. Return true if the next codepoint should be part of a cluster. auto combine(prop const& next) { - if (next.cdpoint && next.utf8len) // The codepoint '\0' cannot be a cluster fragment. + if (next.cdpoint >= matrix::min_vs_code && next.cdpoint <= matrix::max_vs_code) // Set matrix size. { - if (unidata::allied(next)) - { - if (next.cdpoint >= matrix::min_vs_code && next.cdpoint <= matrix::max_vs_code) // Set matrix size. - { - cmatrix = (si32)(next.cdpoint - matrix::vs_block); - // Drop the next.cdpoint by returning 0_sz. - //todo no more codepoints should be added (matrix modifier has the gbreak::ext property). - } - else - { - if (next.ucwidth > unidata::ucwidth) - { - unidata::ucwidth = next.ucwidth; - cmatrix = mtx[unidata::ucwidth]; - } - utf8len += next.utf8len; - cpcount += 1; - } - return 0_sz; - } - else if (unidata::ucwidth == 0 && cdpoint && !next.is_cmd()) // Append any non-control code point if the current cluster has no width. + cmatrix = (si32)(next.cdpoint - matrix::vs_block); // Update cluster metrics. + // Drop the next.cdpoint. Break grapheme cluster despite the matrix modifier has the gbreak::ext property. + return faux; + } + else + { + if (next.ucwidth > unidata::ucwidth) { - if (next.ucwidth > unidata::ucwidth) - { - unidata::ucwidth = next.ucwidth; - cmatrix = mtx[unidata::ucwidth]; - } - utf8len += next.utf8len; - cpcount += 1; - return 0_sz; + unidata::ucwidth = next.ucwidth; + cmatrix = mtx[unidata::ucwidth]; } + utf8len += next.utf8len; + cpcount += 1; + return true; } - return utf8len; + } + // prop: Check if the next codepooint could be attached to the cluster. Return zero to continue attaching. Return non-zero if cluster is closed. + auto do_include(prop const& next) + { + return next.cdpoint && next.utf8len // The codepoint '\0' cannot be a cluster fragment. + && (unidata::allied(next) || we_has_no_width(next)); // Append any non-control codepoint if the current cluster has no width. } }; @@ -594,24 +617,22 @@ namespace netxs::utf } auto head = code.textptr; auto left = next; - do + if (left.correct) { - code.step(); - if (next.correct) + while (code) { + code.step(); next = code.take(); - if (auto size = left.combine(next)) - { - return frag{ view(head, size), left }; - } - } - else - { - next.utf8len = left.utf8len; - return frag{ replacement, next }; + if (!next.correct) break; + if (!left.do_include(next)) break; // Break on non combining character. + if (!left.combine(next)) break; // Break on matrix modifier. } + return frag{ view(head, left.utf8len), left }; + } + else + { + return frag{ replacement, left }; } - while (true); } return frag{ replacement, prop{ 0 } }; } @@ -645,60 +666,63 @@ namespace netxs::utf { return frag::take_cluster(utf8); } - // utf: Break text into grapheme clusters filtered from codepoints. + // utf: Break text into grapheme clusters filtered from control codepoints. void decode_clusters(view utf8, auto yield) { - if (auto code = cpit{ utf8 }) + auto code = cpit{ utf8 }; + while (code) { auto next = code.take(); - do + if (next.correct && !utf::non_control(next.cdpoint)) // Skip controls. { - if (!utf::non_control(next.cdpoint)) + code.step(); + } + else + { + auto head = code.textptr; + auto left = next; + while (true) { code.step(); - next = code.take(); - } - else - { - auto head = code.textptr; - auto left = next; - do + if (next.correct) { - code.step(); - if (next.correct) + if (!code) { - if (!code) - { - auto crop = view(head, left.utf8len); - if (!yield(crop)) return; - break; - } - next = code.take(); - if (!utf::non_control(next.cdpoint)) // Skip controls. - { - code.step(); - next = code.take(); - break; - } - if (left.combine(next)) + auto crop = view(head, left.utf8len); + yield(crop); + return; + } + next = code.take(); + if (next.correct && !utf::non_control(next.cdpoint)) // Skip controls. + { + code.step(); + break; + } + if (left.do_include(next)) + { + if (!left.combine(next)) { auto crop = view(head, left.utf8len); if (!yield(crop)) return; + code.step(); // Skip matrix modifier. break; } } else { - auto crop = replacement; + auto crop = view(head, left.utf8len); if (!yield(crop)) return; - next = code.take(); break; } } - while (true); + else + { + auto crop = replacement; + if (!yield(crop)) return; + break; + } } } - while (code); } } // utf: Break the text into the grapheme clusters. @@ -709,22 +733,171 @@ namespace netxs::utf // auto y = [&](frag const& cluster){}; // ascii: Processes ASCII chars (fast forward). // auto a = [&](frag const& cluster){}; + // pop_cluster: Pop the last cluster to rebuild it. + // auto p = [&](){}; // Clusterize: Decode cluster-by-cluster (if true) or codepoint-by-codepoint (if faux). - template - void decode(auto serve, auto yield, auto ascii, view utf8, si32& decsg) + template + view decode(text& last_cluster_str, view utf8, si32& decsg, auto serve, auto yield, auto ascii, P pop_cluster = P{}) { static const auto dec_sgm_lookup = std::vector // DEC Special Graphics mode lookup table. - {// _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ }; + {// _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~ "w"sv, "โ™ฆ"sv, "โ–’"sv, "โ‰"sv, "โŒ"sv, "โ"sv, "โŠ"sv, "ยฐ"sv, "ยฑ"sv, "โค"sv, "โ‹"sv, "โ”˜"sv, "โ”"sv, "โ”Œ"sv, "โ””"sv, "โ”ผ"sv, "โŽบ"sv, "โŽป"sv, "โ”€"sv, "โŽผ"sv, "โŽฝ"sv, "โ”œ"sv, "โ”ค"sv, "โ”ด"sv, "โ”ฌ"sv, "โ”‚"sv, "โ‰ค"sv, "โ‰ฅ"sv, "ฯ€"sv, "โ‰ "sv, "ยฃ"sv, "ยท"sv, }; + auto is_plain = [](byte c){ return c >= 0x20 && c < 0x7f; }; + + auto last_cluster = view{}; if (auto code = cpit{ utf8 }) { auto next = code.take(); + if constexpr (Clusterize) // Try to append new codepoints to the last_cluster_str. + if (last_cluster_str.length()) + if (next.correct) + if (!next.is_cmd()) + if (auto code0 = cpit{ last_cluster_str }) // Restore the last cluster state. This is a copy of the main loop for checking if last_cluster_str can grow. + { + auto next0 = code0.take(); + auto left0 = next0; + if (next0.cdpoint == matrix::stx) // Continue VT2D cluster. + { + auto rest = code.rest(); + auto utf8len = 0_sz; // The length of the string to append. + auto cpcount = 1; // STX. Overall codepoints number. + code0.step(); + if (code0) // Take base char from last_cluster_str. + { + next0 = code0.take(); + left0 = next0; // Base char of VT2D cluster from last_cluster_str. + do + { + cpcount += 1; + code0.step(); + next0 = code0.take(); + } + while (code0); // Eat all last_cluster_str. + } + else // Take base char from utf8. + { + left0 = next; // Base char of VT2D cluster from utf8. + utf8len += next.utf8len; + cpcount += 1; + code.step(); + next = code.take(); + } + while (next.correct && (next.cdpoint < matrix::min_vs_code || next.cdpoint > matrix::max_vs_code)) // Eat all until VS. + { + utf8len += next.utf8len; + cpcount += 1; + code.step(); + next = code.take(); + } + left0.utf8len = last_cluster_str.length() + utf8len; // Ignore cmatrix modifier. + left0.cpcount = cpcount; + last_cluster_str += rest.substr(0, utf8len); + pop_cluster(matrix::vs<11,11>); + if (next.correct) + { + left0.cmatrix = next.cdpoint - matrix::vs_block; + auto crop = frag{ last_cluster_str, left0 }; + yield(crop); // Write final cluster. + } + else // Incomplete cluster. Yeld the incomplete cluster as is. + { + left0.cmatrix = matrix::vs<11,11>; + auto crop = frag{ last_cluster_str, left0 }; + yield(crop); // Rewrite incomplete cluster. + if (!code) // Reach the end. Or next codepoint was broken. Take next. + { + last_cluster = last_cluster_str; // Remember incomplete cluster. + return last_cluster; + } + // Forget broken cluster. + } + code.step(); // Go to the next character. + if (!code) return last_cluster; + next = code.take(); + } + else // Continue classic cluster. + { + while (next0.correct) + { + code0.step(); + if (code0) + { + next0 = code0.take(); + if (!left0.do_include(next0) // Unexpected break inside the last_cluster_str. Abort. + || !left0.combine(next0)) break; // Unexpected matrix modifier. + } + else // The end of the last_cluster_str has been reached. + { + auto cmatrix = left0.cmatrix; + if (left0.do_include(next)) // Rebuild (append) cluster. + { + if (!left0.combine(next)) // Matrix modifier is found at the first place. Don't append it to the last_cluster_str. + { + pop_cluster(cmatrix); + auto crop = frag{ last_cluster_str, left0 }; + yield(crop); + code.step(); + if (!code) return last_cluster; + next = code.take(); + } + else + { + auto next_utf8len = 0_sz; + while (true) // Iterate over code/next. + { + next_utf8len += next.utf8len; + code.step(); + if (!code) + { + pop_cluster(cmatrix); + last_cluster_str += utf8.substr(0, next_utf8len); // Append new codepoints to the last_cluster_str. + auto crop = frag{ last_cluster_str, left0 }; + yield(crop); + code.step(); + if (!code) return view{ last_cluster_str }; + next = code.take(); + break; + } + next = code.take(); + if (next.correct) + { + if (!left0.do_include(next) + || !left0.combine(next)) // Matrix modifier is found. + { + pop_cluster(cmatrix); + last_cluster_str += utf8.substr(0, next_utf8len); // Append new codepoints to the last_cluster_str. + auto crop = frag{ last_cluster_str, left0 }; + yield(crop); + code.step(); + if (!code) return last_cluster; + next = code.take(); + break; + } + } + else // Broken cluster. Don't append. + { + auto crop = frag{ replacement, next }; + yield(crop); + code.step(); + if (!code) return last_cluster; + next = code.take(); + break; + } + } + } + } + break; // Cluster can't grow. Abort. + } + } + } + } + do { if (next.is_cmd()) { - auto custom_cluster_initiator = Clusterize && next.cdpoint == matrix::stx && code.balance > 1; + auto custom_cluster_initiator = Clusterize && next.cdpoint == matrix::stx; if (custom_cluster_initiator) { auto rest = code.rest(); @@ -742,34 +915,35 @@ namespace netxs::utf } if (next.correct) { - left.utf8len = utf8len; - left.cpcount = cpcount; left.cmatrix = next.cdpoint - matrix::vs_block; - auto crop = frag{ rest.substr(0, left.utf8len), left }; - yield(crop); - code.step(); - next = code.take(); + last_cluster = {}; } - else // Broken cluster. Silently ignore STX. + else // Incomplete cluster. Yeld the incomplete cluster as is. { - code.redo(rest.substr(1)); - next = left; + left.cmatrix = matrix::vs<11,11>; + last_cluster = rest.substr(0, utf8len); } + left.utf8len = utf8len; + left.cpcount = cpcount; + auto crop = frag{ rest.substr(0, utf8len), left }; + yield(crop); + code.step(); } else // Proceed general control. { + last_cluster = {}; code.step(); auto rest = code.rest(); auto chars = serve(next, rest); code.redo(chars); - next = code.take(); } + next = code.take(); } else { - auto is_plain = [](byte c){ return c >= 0x20 && c < 0x7f; }; if (decsg && next.cdpoint <= 0x7e && next.cdpoint >= 0x5f) // ['_' - '~'] { + last_cluster = {}; yield(dec_sgm_lookup[next.cdpoint - 0x5f]); code.step(); next = code.take(); @@ -786,6 +960,7 @@ namespace netxs::utf auto plain = view{ head, iter }; if (iter == tail) { + last_cluster = view{ iter - 1, iter }; // Last character. ascii(plain); break; } @@ -798,6 +973,7 @@ namespace netxs::utf if (head != iter) { plain = view{ head, iter }; + last_cluster = {}; // All clusters are done. ascii(plain); } code.redo(view{ iter, tail }); @@ -807,6 +983,7 @@ namespace netxs::utf } else { + last_cluster = {}; // All clusters are done. ascii(plain); continue; } @@ -815,20 +992,40 @@ namespace netxs::utf { auto head = code.textptr; auto left = next; - do + while (true) { code.step(); + if (!code) + { + last_cluster = view(head, left.utf8len); + auto crop = frag{ last_cluster, left }; + yield(crop); + break; + } + next = code.take(); if (next.correct) { - if (!code || (next = code.take(), left.combine(next))) + if (!left.do_include(next)) // Classic cluster. + { + last_cluster = view(head, left.utf8len); + auto crop = frag{ last_cluster, left }; + yield(crop); + break; + } + else if (!left.combine(next)) // Combine and check if VT2D. { + last_cluster = {}; auto crop = frag{ view(head, left.utf8len), left }; yield(crop); + code.step(); // Skip cmatrix. + if (!code) return last_cluster; + next = code.take(); break; } } else { + last_cluster = {}; next.utf8len = left.utf8len; auto crop = frag{ replacement, next }; yield(crop); @@ -836,17 +1033,18 @@ namespace netxs::utf break; } } - while (true); } else { if (next.correct) { - auto crop = frag{ view(code.textptr, next.utf8len), next }; + last_cluster = view(code.textptr, next.utf8len); + auto crop = frag{ last_cluster, next }; yield(crop); } else { + last_cluster = {}; auto crop = frag{ replacement, next }; yield(crop); } @@ -857,6 +1055,7 @@ namespace netxs::utf } while (code); } + return last_cluster; } auto codepoint_count(auto&& cluster) { @@ -1907,7 +2106,8 @@ namespace netxs::utf //else if (cluster.text.front() == ' ') buff += "\x20"; else buff += cluster.text; }; - decode(s, y, a, utf8, mode); + auto empty_str = text{}; + decode(empty_str, utf8, mode, s, y, a); return (si32)(buff.length() - init); } // utf: Return a string without control chars (replace all ctrls with printables). @@ -2689,17 +2889,18 @@ namespace netxs::utf << utf::adjust(std::to_string(milli), 3, '0', true) << utf::adjust(std::to_string(micro), 3, '0', true); } - #if not defined(_WIN32) //todo Implementation for gcc 11 (waiting for gcc 13) auto& operator << (std::ostream& s, std::filesystem::file_time_type const& ftime) { auto sct = std::chrono::time_point_cast(ftime - std::filesystem::file_time_type::clock::now() + std::chrono::system_clock::now()); auto tt = std::chrono::system_clock::to_time_t(sct); auto tm = std::tm{}; - //::localtime_s(&tm, &tt); // Windows - ::localtime_r(&tt, &tm); // POSIX + #if not defined(_WIN32) // Implementation for gcc 11 (waiting for gcc 13). + ::localtime_r(&tt, &tm); // POSIX + #else // Implementation for Win8. The std::chrono standard for working with file time (std::chrono::file_clock::time_point) in C++20 is not fully supported on Win8. + ::localtime_s(&tm, &tt); // Windows + #endif return s << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); } - #endif void print2(auto& input, view& format) { input << format; diff --git a/src/vtm.cpp b/src/vtm.cpp index 489952dd27..dbe8ed5fa7 100644 --- a/src/vtm.cpp +++ b/src/vtm.cpp @@ -8,7 +8,7 @@ using namespace netxs; -enum class type { client, server, daemon, logmon, runapp, config }; +enum class type { client, server, daemon, logmon, runapp, config, lsfont }; enum class code { noaccess, noserver, nodaemon, nosrvlog, interfer, errormsg }; int main(int argc, char* argv[]) @@ -21,6 +21,7 @@ int main(int argc, char* argv[]) auto script = text{}; auto rungui = true; auto system = faux; + auto detail = faux; auto getopt = os::process::args{ argc, argv }; if (getopt.starts("ssh")) { @@ -29,7 +30,12 @@ int main(int argc, char* argv[]) } else while (getopt) { - if (getopt.match("--cwd")) + if (getopt.match("--fonts")) + { + whoami = type::lsfont; + detail = !getopt.next().empty(); + } + else if (getopt.match("--cwd")) { auto path = getopt.next(); if (path.size()) @@ -190,6 +196,7 @@ int main(int argc, char* argv[]) "\n Desktop applet arguments." "\n --env Set environment variable." "\n --cwd Set current working directory." + "\n --fonts [v[erbose]] Print available fonts (with horizontal scrolling)." "\n" "\n Desktop applet โ”‚ Type โ”‚ Arguments" "\n โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€" @@ -235,7 +242,7 @@ int main(int argc, char* argv[]) auto interactive = whoami == type::runapp || whoami == type::client; os::dtvt::initialize(rungui, true, interactive); - if (os::dtvt::vtmode & ui::console::redirio && (whoami == type::runapp || whoami == type::client)) + if (whoami != type::lsfont && os::dtvt::vtmode & ui::console::redirio && (whoami == type::runapp || whoami == type::client)) { whoami = type::logmon; } @@ -262,6 +269,16 @@ int main(int argc, char* argv[]) { return failed(code::errormsg); } + else if (whoami == type::lsfont) + { + auto config = xml::settings{}; + app::shared::load::settings(config, cliopt, faux); + auto gui_config = app::shared::get_gui_config(config); + if (auto fcache = gui::fonts{ gui_config.font_names, gui_config.font_axes, gui_config.cell_height }) + { + fcache.log_fonts(detail); + } + } else if (whoami == type::config) { auto config = xml::settings{};