diff options
author | Christian Persch <chpe@src.gnome.org> | 2021-02-13 22:52:05 +0100 |
---|---|---|
committer | Christian Persch <chpe@src.gnome.org> | 2021-02-13 22:52:05 +0100 |
commit | d578bd30b18a0d040305f356a3327fbd011e5451 (patch) | |
tree | 8d78abc7fb5c61382ff0c0b5cbd26abd7ea299ec | |
parent | 249a9c77cc5870f04dc9650dd76c800c368ff03e (diff) | |
download | vte-d578bd30b18a0d040305f356a3327fbd011e5451.tar.gz |
all: Remove SIXEL support from stable branch
The SIXEL support is not in a releasable state with
important and fundamental problems still unsolved.
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | meson_options.txt | 7 | ||||
-rw-r--r-- | src/app/app.cc | 6 | ||||
-rw-r--r-- | src/debug.cc | 1 | ||||
-rw-r--r-- | src/debug.h | 1 | ||||
-rw-r--r-- | src/fwd.hh | 8 | ||||
-rw-r--r-- | src/image.cc | 68 | ||||
-rw-r--r-- | src/image.hh | 102 | ||||
-rw-r--r-- | src/meson.build | 56 | ||||
-rw-r--r-- | src/parser-cat.cc | 327 | ||||
-rwxr-xr-x | src/parser-seq.py | 2 | ||||
-rw-r--r-- | src/pty.cc | 4 | ||||
-rw-r--r-- | src/ring.cc | 225 | ||||
-rw-r--r-- | src/ring.hh | 45 | ||||
-rw-r--r-- | src/sixel-context.cc | 518 | ||||
-rw-r--r-- | src/sixel-context.hh | 676 | ||||
-rw-r--r-- | src/sixel-fuzzer.cc | 763 | ||||
-rw-r--r-- | src/sixel-parser.hh | 670 | ||||
-rw-r--r-- | src/sixel-test.cc | 1589 | ||||
-rw-r--r-- | src/vte.cc | 179 | ||||
-rw-r--r-- | src/vtedefines.hh | 6 | ||||
-rw-r--r-- | src/vtegtk.cc | 26 | ||||
-rw-r--r-- | src/vteinternal.hh | 38 | ||||
-rw-r--r-- | src/vteseq.cc | 223 | ||||
-rw-r--r-- | src/widget.hh | 3 |
25 files changed, 19 insertions, 5526 deletions
diff --git a/meson.build b/meson.build index 4f5287e3..436edc24 100644 --- a/meson.build +++ b/meson.build @@ -124,7 +124,6 @@ config_h.set('WITH_A11Y', get_option('a11y')) config_h.set('WITH_FRIBIDI', get_option('fribidi')) config_h.set('WITH_GNUTLS', get_option('gnutls')) config_h.set('WITH_ICU', get_option('icu')) -config_h.set('WITH_SIXEL', get_option('sixel')) ver = glib_min_req_version.split('.') config_h.set('GLIB_VERSION_MIN_REQUIRED', '(G_ENCODE_VERSION(' + ver[0] + ',' + ver[1] + '))') @@ -509,7 +508,6 @@ output += ' GTK+ 4.0: ' + get_option('gtk4').to_string() + '\n' output += ' ICU: ' + get_option('icu').to_string() + '\n' output += ' GIR: ' + get_option('gir').to_string() + '\n' output += ' systemd: ' + systemd_dep.found().to_string() + '\n' -output += ' SIXEL: ' + get_option('sixel').to_string() + '\n' output += ' Glade: ' + get_option('glade').to_string() + '\n' output += ' Vala: ' + get_option('vapi').to_string() + '\n' output += '\n' diff --git a/meson_options.txt b/meson_options.txt index 72c3de5e..0e259910 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -93,13 +93,6 @@ option( ) option( - 'sixel', - type: 'boolean', - value: false, - description: 'Enable SIXEL support', -) - -option( '_systemd', type: 'boolean', value: true, diff --git a/src/app/app.cc b/src/app/app.cc index 01975287..036eab8f 100644 --- a/src/app/app.cc +++ b/src/app/app.cc @@ -76,7 +76,6 @@ public: gboolean no_scrollbar{false}; gboolean no_shaping{false}; gboolean no_shell{false}; - gboolean no_sixel{false}; gboolean no_systemd_scope{false}; gboolean object_notifications{false}; gboolean require_systemd_scope{false}; @@ -580,8 +579,6 @@ public: "Disable Arabic shaping", nullptr }, { "no-shell", 'S', 0, G_OPTION_ARG_NONE, &no_shell, "Disable spawning a shell inside the terminal", nullptr }, - { "no-sixel", 0, 0, G_OPTION_ARG_NONE, &no_sixel, - "Disable SIXEL images", nullptr }, { "no-systemd-scope", 0, 0, G_OPTION_ARG_NONE, &no_systemd_scope, "Don't use systemd user scope", nullptr }, { "object-notifications", 'N', 0, G_OPTION_ARG_NONE, &object_notifications, @@ -594,8 +591,6 @@ public: "Require use of a systemd user scope", nullptr }, { "scrollback-lines", 'n', 0, G_OPTION_ARG_INT, &scrollback_lines, "Specify the number of scrollback-lines (-1 for infinite)", nullptr }, - { "sixel", 0, G_OPTION_FLAG_REVERSE, G_OPTION_ARG_NONE, &no_sixel, - "Enable SIXEL images", nullptr }, { "transparent", 'T', 0, G_OPTION_ARG_INT, &transparency_percent, "Enable the use of a transparent background", "0..100" }, { "verbose", 'v', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, @@ -2144,7 +2139,6 @@ vteapp_window_constructed(GObject *object) vte_terminal_set_cursor_shape(window->terminal, options.cursor_shape); vte_terminal_set_enable_bidi(window->terminal, !options.no_bidi); vte_terminal_set_enable_shaping(window->terminal, !options.no_shaping); - vte_terminal_set_enable_sixel(window->terminal, !options.no_sixel); vte_terminal_set_enable_fallback_scrolling(window->terminal, !options.no_fallback_scrolling); vte_terminal_set_mouse_autohide(window->terminal, true); vte_terminal_set_rewrap_on_resize(window->terminal, !options.no_rewrap); diff --git a/src/debug.cc b/src/debug.cc index 4185a57a..cda5d47b 100644 --- a/src/debug.cc +++ b/src/debug.cc @@ -60,7 +60,6 @@ _vte_debug_init(void) { "bidi", VTE_DEBUG_BIDI }, { "conversion", VTE_DEBUG_CONVERSION }, { "exceptions", VTE_DEBUG_EXCEPTIONS }, - { "image", VTE_DEBUG_IMAGE }, }; _vte_debug_flags = g_parse_debug_string (g_getenv("VTE_DEBUG"), diff --git a/src/debug.h b/src/debug.h index c91c4968..30e6761b 100644 --- a/src/debug.h +++ b/src/debug.h @@ -67,7 +67,6 @@ typedef enum : unsigned { VTE_DEBUG_BIDI = 1u << 28, VTE_DEBUG_CONVERSION = 1u << 29, VTE_DEBUG_EXCEPTIONS = 1u << 30, - VTE_DEBUG_IMAGE = 1u << 31, } VteDebugFlags; void _vte_debug_init(void); @@ -36,14 +36,6 @@ class Widget; } // namespace platform -namespace sixel { - -class Context; -class Parser; -class Sequence; - -} // namespace sixel - namespace view { class FontInfo; diff --git a/src/image.cc b/src/image.cc deleted file mode 100644 index 16ffa5e7..00000000 --- a/src/image.cc +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright © 2016-2020 Hayaki Saito <saitoha@me.com> - * Copyright © 2020 Hans Petter Jansson <hpj@cl.no> - * - * This library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see <https://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include <glib.h> -#include <stdio.h> -#include "vteinternal.hh" - -#include "image.hh" - -namespace vte { - -namespace image { - -/* Paint the image with provided cairo context */ -void -Image::paint(cairo_t* cr, - int offset_x, - int offset_y, - int cell_width, - int cell_height) const noexcept -{ - auto scale_x = 1.0; - auto scale_y = 1.0; - - auto real_offset_x = double(offset_x); - auto real_offset_y = double(offset_y); - - if (cell_width != m_cell_width || cell_height != m_cell_height) { - scale_x = cell_width / (double) m_cell_width; - scale_y = cell_height / (double) m_cell_height; - - real_offset_x /= scale_x; - real_offset_y /= scale_y; - } - - cairo_save(cr); - cairo_set_operator(cr, CAIRO_OPERATOR_OVER); - - if (!(_vte_double_equal (scale_x, 1.0) && _vte_double_equal (scale_y, 1.0))) - cairo_scale (cr, scale_x, scale_y); - - cairo_rectangle(cr, real_offset_x, real_offset_y, m_width_pixels, m_height_pixels); - cairo_clip(cr); - cairo_set_source_surface(cr, m_surface.get(), real_offset_x, real_offset_y); - cairo_paint(cr); - cairo_restore(cr); -} - -} // namespace image - -} // namespace vte diff --git a/src/image.hh b/src/image.hh deleted file mode 100644 index 6f88a123..00000000 --- a/src/image.hh +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright © 2016-2020 Hayaki Saito <saitoha@me.com> - * Copyright © 2020 Hans Petter Jansson <hpj@cl.no> - * - * This library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see <https://www.gnu.org/licenses/>. - */ - -#pragma once - -#include <pango/pangocairo.h> -#include "cairo-glue.hh" - -namespace vte { - -namespace image { - -class Image { -private: - // Image data, device-independent - vte::Freeable<cairo_surface_t> m_surface{}; - - // Draw/prune priority, must be unique - size_t m_priority; - - // Image dimensions in pixels - int m_width_pixels; - int m_height_pixels; - - // Top left corner offset in cell units - int m_left_cells; - int m_top_cells; - - // Cell dimensions in pixels at time of image creation - int m_cell_width; - int m_cell_height; - -public: - Image(vte::Freeable<cairo_surface_t> surface, - size_t priority, - int width_pixels, - int height_pixels, - int col, - int row, - int cell_width, - int cell_height) noexcept - : m_surface{std::move(surface)}, - m_priority{priority}, - m_width_pixels{width_pixels}, - m_height_pixels{height_pixels}, - m_left_cells{col}, - m_top_cells{row}, - m_cell_width{cell_width}, - m_cell_height{cell_height} - { - } - - ~Image() = default; - - Image(Image const&) = delete; - Image(Image&&) = delete; - Image operator=(Image const&) = delete; - Image operator=(Image&&) = delete; - - inline constexpr auto get_priority() const noexcept { return m_priority; } - inline constexpr auto get_left() const noexcept { return m_left_cells; } - inline auto get_top() const noexcept { return m_top_cells; } - inline void set_top(int row) noexcept { m_top_cells = row; } - inline constexpr auto get_width() const noexcept { return (m_width_pixels + m_cell_width - 1) / m_cell_width; } - inline constexpr auto get_height() const noexcept { return (m_height_pixels + m_cell_height - 1) / m_cell_height; } - inline auto get_bottom() const noexcept { return m_top_cells + get_height() - 1; } - - inline auto resource_size() const noexcept - { - if (cairo_image_surface_get_stride(m_surface.get()) != 0) - return cairo_image_surface_get_stride(m_surface.get()) * m_height_pixels; - - /* Not an image surface: Only the device knows for sure, so we guess */ - return m_width_pixels * m_height_pixels * 4; - } - - void paint(cairo_t* cr, - int offset_x, - int offset_y, - int cell_width, - int cell_height) const noexcept; - -}; // class Image - -} // namespace image - -} // namespace vte diff --git a/src/meson.build b/src/meson.build index bfbeebb2..7b7d8ee9 100644 --- a/src/meson.build +++ b/src/meson.build @@ -123,20 +123,6 @@ regex_sources = files( 'regex.hh' ) -sixel_parser_sources = files( - 'sixel-parser.hh', -) - -sixel_context_sources = files( - 'sixel-context.cc', - 'sixel-context.hh', -) - -sixel_sources = sixel_parser_sources + sixel_context_sources + files( - 'image.cc', - 'image.hh', -) - std_glue_sources = files( 'std-glue.hh', ) @@ -220,10 +206,6 @@ if get_option('icu') libvte_common_sources += icu_sources endif -if get_option('sixel') - libvte_common_sources += sixel_sources -endif - if systemd_dep.found() libvte_common_sources += systemd_sources endif @@ -355,10 +337,6 @@ parser_cat_sources = config_sources + glib_glue_sources + libc_glue_sources + pa 'vtedefines.hh', ) -if get_option('sixel') - parser_cat_sources += sixel_parser_sources -endif - parser_cat = executable( 'parser-cat', parser_cat_sources, @@ -523,34 +501,6 @@ test_refptr = executable( install: false, ) -if get_option('sixel') - fuzz_sixel_sources = config_sources + files( - 'sixel-fuzzer.cc', - ) - - fuzz_sixel = executable( - 'fuzz-sixel', - sources: fuzz_sixel_sources, - dependencies: [glib_dep,], - include_directories: top_inc, - install: false, - ) - - test_sixel_sources = config_sources + glib_glue_sources + sixel_parser_sources + sixel_context_sources + files( - 'cairo-glue.hh', - 'sixel-test.cc', - 'vtedefines.hh', - ) - - test_sixel = executable( - 'test-sixel', - sources: test_sixel_sources, - dependencies: [glib_dep,], - include_directories: top_inc, - install: false, - ) -endif - test_stream_sources = config_sources + files( 'vtestream-base.h', 'vtestream-file.h', @@ -624,12 +574,6 @@ test_units = [ ['vtetypes', test_vtetypes], ] -if get_option('sixel') - test_units += [ - ['sixel', test_sixel], - ] -endif - foreach test: test_units test( test[0], diff --git a/src/parser-cat.cc b/src/parser-cat.cc index 5fade9db..afb6fb1a 100644 --- a/src/parser-cat.cc +++ b/src/parser-cat.cc @@ -40,10 +40,6 @@ #include "utf8.hh" #include "vtedefines.hh" -#ifdef WITH_SIXEL -#include "sixel-parser.hh" -#endif - enum { #define _VTE_SGR(...) #define _VTE_NGR(name, value) VTE_SGR_##name = value, @@ -58,9 +54,6 @@ enum class DataSyntax { ECMA48_UTF8, /* ECMA48_PCTERM, */ /* ECMA48_ECMA35, */ - #ifdef WITH_SIXEL - DECSIXEL, - #endif }; char* @@ -154,9 +147,6 @@ private: std::string m_str; bool m_plain; bool m_codepoints; -#ifdef WITH_SIXEL - char32_t m_sixel_st; -#endif inline constexpr bool plain() const noexcept { return m_plain; } @@ -389,48 +379,6 @@ private: } } -#ifdef WITH_SIXEL - - void - print_params(vte::sixel::Sequence const& seq) noexcept - { - auto const size = seq.size(); - if (size > 0) - m_str.push_back(' '); - - for (unsigned int i = 0; i < size; i++) { - if (!seq.param_default(i)) - print_format("%d", seq.param(i)); - if (i + 1 < size) - m_str.push_back(';'); - } - } - - void - print_seq(vte::sixel::Sequence const& seq) noexcept - { - ReverseAttr attr(this); - GreenAttr green(this); - - m_str.push_back('{'); - switch (seq.command()) { - case vte::sixel::Command::DECGRI: m_str.append("DECGRI"); break; - case vte::sixel::Command::DECGRA: m_str.append("DECGRA"); break; - case vte::sixel::Command::DECGCI: m_str.append("DECGCI"); break; - case vte::sixel::Command::DECGCR: m_str.append("DECGCR"); break; - case vte::sixel::Command::DECGNL: m_str.append("DECGNL"); break; - default: - print_format("%d/%d", - int(seq.command()) / 16, - int(seq.command()) % 16); - break; - } - print_params(seq); - m_str.push_back('}'); - } - -#endif /* WITH_SIXEL */ - void printout() noexcept { @@ -463,45 +411,9 @@ public: printout(); } -#ifdef WITH_SIXEL - - void SIXEL_CMD(vte::sixel::Sequence const& seq) noexcept - { - print_seq(seq); - - switch (seq.command()) { - case vte::sixel::Command::DECGCR: - case vte::sixel::Command::DECGNL: - printout(); - break; - default: - break; - } - } - - void SIXEL(uint8_t sixel) noexcept - { - print_format("%c", sixel + 0x3f); - } - - void SIXEL_ST(char32_t st) noexcept - { - m_sixel_st = st; - } - -#endif /* WITH_SIXEL */ - void enter_data_syntax(DataSyntax syntax) noexcept { switch (syntax) { -#ifdef WITH_SIXEL - case DataSyntax::DECSIXEL: { - GreenAttr green(this); - print_literal("<SIXEL["); - m_sixel_st = 0; - break; - } -#endif default: break; } @@ -511,17 +423,6 @@ public: bool success) noexcept { switch (syntax) { -#ifdef WITH_SIXEL - case DataSyntax::DECSIXEL: - if (success) { - GreenAttr green(this); - print_literal("]ST>"); - } else { - RedAttr green(this); - print_literal("]>"); - } - break; -#endif default: break; } @@ -718,13 +619,6 @@ private: } } -#ifdef WITH_SIXEL - char32_t m_sixel_st{0}; - bool m_seen_sixel_commands{false}; - bool m_seen_sixel_data{false}; - std::bitset<VTE_SIXEL_NUM_COLOR_REGISTERS> m_sixel_color_set; -#endif - public: Linter() noexcept = default; ~Linter() noexcept = default; @@ -753,22 +647,6 @@ public: check_sgr(seq); break; -#ifdef WITH_SIXEL - case VTE_CMD_DECSIXEL: - /* OR mode is a nonstandard NetBSD/x68k extension that is - * not supported in VTE. - */ - if (seq.collect1(1) == 5) - warn("DECSIXEL OR-mode not supported"); - - /* Image ID (args[3]) is a nonstandard RLogin extension that is - * not supported in VTE. - */ - if (seq.collect1(3) != -1) - warn("DECSIXEL ID extension not supported"); - break; -#endif /* WITH_SIXEL */ - default: if (cmd >= VTE_CMD_NOP_FIRST) warn("%s is unimplemented", cmd_to_str(cmd)); @@ -776,135 +654,8 @@ public: } } -#ifdef WITH_SIXEL - - void SIXEL(uint8_t raw) noexcept - { - m_seen_sixel_data = true; - } - - void SIXEL_CMD(vte::sixel::Sequence const& seq) noexcept - { - switch (seq.command()) { - case vte::sixel::Command::DECGRI: { - auto const count = seq.param(0, 1); - if (count < 3) - warn("DECGRI %d wastes space", seq.param(0)); - else if (count == 3) - warn("DECGRI %d saves no space", count); - else if (count > 255) - warn("DECGRI %d exceeds DEC limit of 255", count); - break; - } - - case vte::sixel::Command::DECGRA: - if (m_seen_sixel_commands || m_seen_sixel_data) - warn("DECGRA ignored after any SIXEL commands or data"); - break; - - case vte::sixel::Command::DECGCI: { - - auto reg = seq.param(0); - if (reg == -1) { - warn("DECGCI does not admit a default value for parameter 1"); - break; - } else if (reg >= VTE_SIXEL_NUM_COLOR_REGISTERS) { - warn("DECGCI %d exceeds number of available colour registers, wrapped to register %d", reg, reg & (VTE_SIXEL_NUM_COLOR_REGISTERS - 1)); - reg &= (VTE_SIXEL_NUM_COLOR_REGISTERS - 1); - } - - if (seq.size() > 1) { - switch (seq.param(1)) { - case -1: /* default */ - warn("DECGCI does not admit a default value for parameter 2"); - break; - case 1: /* HLS */ { - auto const h = seq.param(2, 0); - auto const l = seq.param(3, 0); - auto const s = seq.param(4, 0); - if (h > 360) - warn("DECGCI HSL colour hue %d exceeds range 0..360", h); - if (l > 100) - warn("DECGCI HSL colour luminosity %d exceeds range 0..100", l); - if (s > 100) - warn("DECGCI HSL colour saturation %d exceeds range 0..100", s); - break; - } - - case 2: /* RGB */ { - auto const r = seq.param(2, 0); - auto const g = seq.param(3, 0); - auto const b = seq.param(4, 0); - if (r > 100) - warn("DECGCI RGB colour red %d exceeds range 0..100", r); - if (g > 100) - warn("DECGCI RGB colour red %d exceeds range 0..100", g); - if (b > 100) - warn("DECGCI RGB colour red %d exceeds range 0..100", b); - break; - } - - case 3: /* RGB truecolour. - * This is an RLogin extension and not supported by VTE. - */ - warn("DECGCI RGB truecolour extension is not supported"); - break; - - case 0: - default: - warn("DECGCI unknown colour coordinate system %d", seq.param(1)); - break; - } - - m_sixel_color_set.set(reg); - } else { - /* Select colour register param[0] */ - - if (!m_sixel_color_set.test(reg)) - warn("DECGCI %d selects colour which has not been defined", reg); - } - - break; - } - - case vte::sixel::Command::DECGCR: - break; - - case vte::sixel::Command::DECGNL: - break; - - default: - warn("Ignoring unknown SIXEL command %d/%d '%c'", - int(seq.command()) / 16, - int(seq.command()) % 16, - char(seq.command())); - break; - } - - m_seen_sixel_commands = true; - } - - void SIXEL_ST(char32_t st) noexcept - { - m_sixel_st = st; - } - -#endif /* WITH_SIXEL */ - void enter_data_syntax(DataSyntax syntax) noexcept { - switch (syntax) { -#ifdef WITH_SIXEL - case DataSyntax::DECSIXEL: - m_sixel_st = 0; - m_seen_sixel_commands = m_seen_sixel_data = false; - m_sixel_color_set.reset(); - break; -#endif - - default: - break; - } } void leave_data_syntax(DataSyntax syntax, @@ -922,12 +673,6 @@ class Sink { public: void VT(vte::parser::Sequence const& seq) noexcept { } -#ifdef WITH_SIXEL - void SIXEL(uint8_t raw) noexcept { } - void SIXEL_CMD(vte::sixel::Sequence const& seq) noexcept { } - void SIXEL_ST(char32_t st) noexcept { } -#endif - void enter_data_syntax(DataSyntax syntax) noexcept { } void leave_data_syntax(DataSyntax syntax, bool success) noexcept { } @@ -943,7 +688,6 @@ private: D& m_delegate; size_t m_buffer_size{0}; - bool m_no_sixel{false}; bool m_statistics{false}; bool m_benchmark{false}; @@ -956,10 +700,6 @@ private: vte::base::UTF8Decoder m_utf8_decoder{}; vte::parser::Parser m_parser{}; -#ifdef WITH_SIXEL - vte::sixel::Parser m_sixel_parser{}; -#endif - DataSyntax m_primary_data_syntax{DataSyntax::ECMA48_UTF8}; DataSyntax m_current_data_syntax{DataSyntax::ECMA48_UTF8}; @@ -971,12 +711,6 @@ private: m_utf8_decoder.reset(); break; -#ifdef WITH_SIXEL - case DataSyntax::DECSIXEL: - m_sixel_parser.reset(); - break; -#endif - default: break; } @@ -994,20 +728,6 @@ private: process_seq(vte::parser::Sequence const& seq) noexcept { m_delegate.VT(seq); - -#ifdef WITH_SIXEL - if (G_UNLIKELY(!m_no_sixel && - seq.command() == VTE_CMD_DECSIXEL && - seq.is_unripe())) { - m_parser.reset(); // sixel parser takes over until ST - m_sixel_parser.reset(); - m_current_data_syntax = DataSyntax::DECSIXEL; - - m_delegate.enter_data_syntax(m_current_data_syntax); - return false; - } -#endif /* WITH_SIXEL */ - return true; } @@ -1070,33 +790,6 @@ private: return bufend; } -#ifdef WITH_SIXEL - - uint8_t const* - process_data_decsixel(uint8_t const* const bufstart, - uint8_t const* const bufend, - bool eos) noexcept - { - auto [status, ip] = m_sixel_parser.parse(bufstart, bufend, eos, m_delegate); - - switch (status) { - case vte::sixel::Parser::ParseStatus::CONTINUE: - break; - - case vte::sixel::Parser::ParseStatus::COMPLETE: - case vte::sixel::Parser::ParseStatus::ABORT: { - auto const success = (status == vte::sixel::Parser::ParseStatus::COMPLETE); - m_delegate.leave_data_syntax(m_current_data_syntax, success); - m_current_data_syntax = m_primary_data_syntax; - break; - } - } - - return ip; - } - -#endif /* WITH_SIXEL */ - void process_fd(int fd) { @@ -1124,12 +817,6 @@ private: sptr = process_data_utf8(sptr, bufend, eos); break; -#ifdef WITH_SIXEL - case DataSyntax::DECSIXEL: - sptr = process_data_decsixel(sptr, bufend, eos); - break; -#endif - default: g_assert_not_reached(); break; @@ -1179,22 +866,16 @@ public: Processor(Delegate& delegate, size_t buffer_size, - bool no_sixel, bool statistics, bool benchmark) noexcept : m_delegate{delegate}, m_buffer_size{std::max(buffer_size, k_buf_overlap + 1)}, - m_no_sixel{no_sixel}, m_statistics{statistics}, m_benchmark{benchmark} { memset(&m_seq_stats, 0, sizeof(m_seq_stats)); memset(&m_cmd_stats, 0, sizeof(m_cmd_stats)); m_bench_times = g_array_new(false, true, sizeof(int64_t)); - -#ifdef WITH_SIXEL - m_parser.set_dispatch_unripe(!m_no_sixel); -#endif } ~Processor() noexcept @@ -1290,7 +971,6 @@ private: bool m_benchmark{false}; bool m_codepoints{false}; bool m_lint{false}; - bool m_no_sixel{false}; bool m_plain{false}; bool m_quiet{false}; bool m_statistics{false}; @@ -1310,7 +990,6 @@ public: inline constexpr size_t buffer_size() const noexcept { return m_buffer_size; } inline constexpr bool codepoints() const noexcept { return m_codepoints; } inline constexpr bool lint() const noexcept { return m_lint; } - inline constexpr bool no_sixel() const noexcept { return m_no_sixel; } inline constexpr bool plain() const noexcept { return m_plain; } inline constexpr bool quiet() const noexcept { return m_quiet; } inline constexpr bool statistics() const noexcept { return m_statistics; } @@ -1328,7 +1007,6 @@ public: auto benchmark = BoolOption{m_benchmark, false}; auto codepoints = BoolOption{m_codepoints, false}; auto lint = BoolOption{m_lint, false}; - auto no_sixel = BoolOption{m_no_sixel, false}; auto plain = BoolOption{m_plain, false}; auto quiet = BoolOption{m_quiet, false}; auto statistics = BoolOption{m_statistics, false}; @@ -1345,10 +1023,6 @@ public: "Output unicode code points by number", nullptr }, { "lint", 'l', 0, G_OPTION_ARG_NONE, &lint, "Check input", nullptr }, -#ifdef WITH_SIXEL - { "no-sixel", 0, 0, G_OPTION_ARG_NONE, &no_sixel, - "Disable DECSIXEL processing", nullptr }, -#endif { "plain", 'p', 0, G_OPTION_ARG_NONE, &plain, "Output plain text without attributes", nullptr }, { "quiet", 'q', 0, G_OPTION_ARG_NONE, &quiet, @@ -1377,7 +1051,6 @@ process(Options const& options, { auto proc = Processor{delegate, options.buffer_size(), - options.no_sixel(), options.statistics(), options.benchmark()}; diff --git a/src/parser-seq.py b/src/parser-seq.py index bd973e76..27145ba0 100755 --- a/src/parser-seq.py +++ b/src/parser-seq.py @@ -889,7 +889,7 @@ sequences = [ comment='restore terminal state'), seq_DCS('XTERM_STCAP', 'p', intermediates=(Intermediate.PLUS,), flags=Flags.NOP, comment='xterm set termcap/terminfo'), - seq_DCS('DECSIXEL', 'q', flags=Flags.UNRIPE | Flags.HANDLER_RV, + seq_DCS('DECSIXEL', 'q', flags=Flags.NOP, comment='SIXEL graphics'), seq_DCS('DECRQSS', 'q', intermediates=(Intermediate.CASH,), comment='request selection or setting'), @@ -293,10 +293,6 @@ Pty::set_size(int rows, memset(&size, 0, sizeof(size)); size.ws_row = rows > 0 ? rows : 24; size.ws_col = columns > 0 ? columns : 80; -#ifdef WITH_SIXEL - size.ws_ypixel = size.ws_row * cell_height_px; - size.ws_xpixel = size.ws_col * cell_width_px; -#endif _vte_debug_print(VTE_DEBUG_PTY, "Setting size on fd %d to (%d,%d).\n", master, columns, rows); diff --git a/src/ring.cc b/src/ring.cc index 23d79600..fe5bab17 100644 --- a/src/ring.cc +++ b/src/ring.cc @@ -25,20 +25,6 @@ #include <string.h> -#ifdef WITH_SIXEL - -#include "cxx-utils.hh" - -/* We should be able to hold a single fullscreen 4K image at most. - * 35MiB equals 3840 * 2160 * 4 plus a little extra. */ -#define IMAGE_FAST_MEMORY_USED_MAX (35 * 1024 * 1024) - -/* Hard limit on number of images to keep around. This limits the impact - * of potential issues related to algorithmic complexity. */ -#define IMAGE_FAST_COUNT_MAX 4096 - -#endif /* WITH_SIXEL */ - /* * Copy the common attributes from VteCellAttr to VteStreamCellAttr or vice versa. */ @@ -204,115 +190,6 @@ Ring::hyperlink_maybe_gc(row_t increment) hyperlink_gc(); } -#ifdef WITH_SIXEL - -void -Ring::image_gc_region() noexcept -{ - cairo_region_t *region = cairo_region_create(); - - for (auto rit = m_image_map.rbegin(); - rit != m_image_map.rend(); - ) { - auto const& image = rit->second; - auto const rect = cairo_rectangle_int_t{image->get_left(), - image->get_top(), - image->get_width(), - image->get_height()}; - - if (cairo_region_contains_rectangle(region, &rect) == CAIRO_REGION_OVERLAP_IN) { - /* vte::image::Image has been completely overdrawn; delete it */ - - m_image_fast_memory_used -= image->resource_size(); - - /* Apparently this is the cleanest way to erase() with a reverse iterator... */ - /* Unlink the image from m_image_by_top_map, then erase it from m_image_map */ - unlink_image_from_top_map(image.get()); - rit = image_map_type::reverse_iterator{m_image_map.erase(std::next(rit).base())}; - continue; - } - - cairo_region_union_rectangle(region, &rect); - ++rit; - } - - cairo_region_destroy(region); -} - -void -Ring::image_gc() noexcept -{ - while (m_image_fast_memory_used > IMAGE_FAST_MEMORY_USED_MAX || - m_image_map.size() > IMAGE_FAST_COUNT_MAX) { - if (m_image_map.empty()) { - /* If this happens, we've miscounted somehow. */ - break; - } - - auto& image = m_image_map.begin()->second; - m_image_fast_memory_used -= image->resource_size(); - unlink_image_from_top_map(image.get()); - m_image_map.erase(m_image_map.begin()); - } -} - -void -Ring::unlink_image_from_top_map(vte::image::Image const* image) noexcept -{ - auto [begin, end] = m_image_by_top_map.equal_range(image->get_top()); - - for (auto it = begin; it != end; ++it) { - if (it->second != image) - continue; - - m_image_by_top_map.erase(it); - break; - } -} - -void -Ring::rebuild_image_top_map() /* throws */ -{ - m_image_by_top_map.clear(); - - for (auto it = m_image_map.begin(), end = m_image_map.end(); - it != end; - ++it) { - auto const& image = it->second; - m_image_by_top_map.emplace(std::piecewise_construct, - std::forward_as_tuple(image->get_top()), - std::forward_as_tuple(image.get())); - } -} - -bool -Ring::rewrap_images_in_range(Ring::image_by_top_map_type::iterator& it, - size_t text_start_ofs, - size_t text_end_ofs, - row_t new_row_index) noexcept -{ - for (auto const end = m_image_by_top_map.end(); - it != end; - ++it) { - auto const& image = it->second; - auto ofs = CellTextOffset{}; - - if (!frozen_row_column_to_text_offset(image->get_top(), 0, &ofs)) - return false; - - if (ofs.text_offset >= text_end_ofs) - break; - - if (ofs.text_offset >= text_start_ofs && ofs.text_offset < text_end_ofs) { - image->set_top(new_row_index); - } - } - - return true; -} - -#endif /* WITH_SIXEL */ - /* * Find existing idx for the hyperlink or allocate a new one. * @@ -713,13 +590,6 @@ Ring::reset() m_start = m_writable = m_end; m_cached_row_num = (row_t)-1; -#ifdef WITH_SIXEL - m_image_by_top_map.clear(); - m_image_map.clear(); - m_next_image_priority = 0; - m_image_fast_memory_used = 0; -#endif - return m_end; } @@ -1150,16 +1020,7 @@ Ring::frozen_row_column_to_text_offset(row_t position, } else records[1].text_start_offset = _vte_stream_head(m_text_stream); - offset->fragment_cells = 0; - offset->eol_cells = -1; - offset->text_offset = records[0].text_start_offset; - - /* Save some work if we're in column 0. This holds true for images, whose column - * positions are disregarded for the purposes of wrapping. */ - if (column == 0) - return true; - - g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset); + g_string_set_size (buffer, records[1].text_start_offset - records[0].text_start_offset); if (!_vte_stream_read(m_text_stream, records[0].text_start_offset, buffer->str, buffer->len)) return false; @@ -1171,6 +1032,8 @@ Ring::frozen_row_column_to_text_offset(row_t position, /* row and buffer now contain the same text, in different representation */ /* count the number of characters up to the given column */ + offset->fragment_cells = 0; + offset->eol_cells = -1; num_chars = 0; for (i = 0, cell = row->cells; i < row->len && i < column; i++, cell++) { if (G_LIKELY (!cell->attr.fragment())) { @@ -1191,7 +1054,7 @@ Ring::frozen_row_column_to_text_offset(row_t position, off++; if ((buffer->str[off] & 0xC0) != 0x80) num_chars--; } - offset->text_offset += off; + offset->text_offset = records[0].text_start_offset + off; return true; } @@ -1303,9 +1166,6 @@ Ring::rewrap(column_t columns, gsize paragraph_len; /* excluding trailing '\n' */ gsize attr_offset; gsize old_ring_end; -#ifdef WITH_SIXEL - auto image_it = m_image_by_top_map.begin(); -#endif if (G_UNLIKELY(length() == 0)) return; @@ -1441,14 +1301,6 @@ Ring::rewrap(column_t columns, } } -#ifdef WITH_SIXEL - if (!rewrap_images_in_range(image_it, - new_record.text_start_offset, - text_offset, - new_row_index)) - goto err; -#endif - new_row_index++; new_record.text_start_offset = text_offset; new_record.attr_start_offset = attr_offset; @@ -1498,14 +1350,6 @@ Ring::rewrap(column_t columns, } } -#ifdef WITH_SIXEL - if (!rewrap_images_in_range(image_it, - new_record.text_start_offset, - paragraph_end_text_offset, - new_row_index)) - goto err; -#endif - new_row_index++; paragraph_start_text_offset = paragraph_end_text_offset; } @@ -1538,14 +1382,6 @@ Ring::rewrap(column_t columns, g_free(marker_text_offsets); g_free(new_markers); -#ifdef WITH_SIXEL - try { - rebuild_image_top_map(); - } catch (...) { - vte::log_exception(); - } -#endif - _vte_debug_print(VTE_DEBUG_RING, "Ring after rewrapping:\n"); validate(); return; @@ -1649,56 +1485,3 @@ Ring::write_contents(GOutputStream* stream, return true; } - -#ifdef WITH_SIXEL - -/** - * Ring::append_image: - * @surface: A Cairo surface object - * @pixelwidth: vte::image::Image width in pixels - * @pixelheight: vte::image::Image height in pixels - * @left: Left position of image in cell units - * @top: Top position of image in cell units - * @cell_width: Width of image in cell units - * @cell_height: Height of image in cell units - * - * Append an image to the internal image list. - */ -void -Ring::append_image(vte::Freeable<cairo_surface_t> surface, - int pixelwidth, - int pixelheight, - long left, - long top, - long cell_width, - long cell_height) /* throws */ -{ - auto const priority = m_next_image_priority; - auto [it, success] = m_image_map.try_emplace - (priority, // key - std::make_unique<vte::image::Image>(std::move(surface), - priority, - pixelwidth, - pixelheight, - left, - top, - cell_width, - cell_height)); - if (!success) - return; - - ++m_next_image_priority; - - auto const& image = it->second; - - m_image_by_top_map.emplace(std::piecewise_construct, - std::forward_as_tuple(image->get_top()), - std::forward_as_tuple(image.get())); - - m_image_fast_memory_used += image->resource_size (); - - image_gc_region(); - image_gc(); -} - -#endif /* WITH_SIXEL */ diff --git a/src/ring.hh b/src/ring.hh index 9b38a480..00dac4ea 100644 --- a/src/ring.hh +++ b/src/ring.hh @@ -27,13 +27,6 @@ #include "vterowdata.hh" #include "vtestream.h" -#ifdef WITH_SIXEL -#include "cairo-glue.hh" -#include "image.hh" -#include <map> -#include <memory> -#endif - #include <type_traits> typedef struct _VteVisualPosition { @@ -232,44 +225,6 @@ private: hyperlink_idx_t m_hyperlink_hover_idx{0}; /* The hyperlink idx of the hovered cell. An idx is allocated on hover even if the cell is scrolled out to the streams. */ row_t m_hyperlink_maybe_gc_counter{0}; /* Do a GC when it reaches 65536. */ - -#ifdef WITH_SIXEL - -private: - size_t m_next_image_priority{0}; - size_t m_image_fast_memory_used{0}; - - /* m_image_priority_map stores the Image. key is the priority of the image. */ - using image_map_type = std::map<size_t, std::unique_ptr<vte::image::Image>>; - image_map_type m_image_map{}; - - /* m_image_by_top_map stores only an iterator to the Image in m_image_priority_map; - * key is the top row of the image. - */ - using image_by_top_map_type = std::multimap<row_t, vte::image::Image*>; - image_by_top_map_type m_image_by_top_map{}; - - void image_gc() noexcept; - void image_gc_region() noexcept; - void unlink_image_from_top_map(vte::image::Image const* image) noexcept; - void rebuild_image_top_map() /* throws */; - bool rewrap_images_in_range(image_by_top_map_type::iterator& it, - size_t text_start_ofs, - size_t text_end_ofs, - row_t new_row_index) noexcept; - -public: - auto const& image_map() const noexcept { return m_image_map; } - - void append_image(vte::Freeable<cairo_surface_t> surface, - int pixelwidth, - int pixelheight, - long left, - long top, - long cell_width, - long cell_height) /* throws */; - -#endif /* WITH_SIXEL */ }; }; /* namespace base */ diff --git a/src/sixel-context.cc b/src/sixel-context.cc deleted file mode 100644 index aa5c9aee..00000000 --- a/src/sixel-context.cc +++ /dev/null @@ -1,518 +0,0 @@ -/* - * Copyright © 2020 Christian Persch - * - * This library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see <https://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include "sixel-context.hh" - -#include <algorithm> -#include <cmath> -#include <cstdint> - -#ifdef VTE_DEBUG -#include "debug.h" -#include "libc-glue.hh" -#endif - -namespace vte::sixel { - -/* BEGIN */ - -/* The following code is copied from xterm/graphics.c where it is under the - * licence below; and modified and used here under the GNU Lesser General Public - * Licence, version 3 (or, at your option), any later version. - */ - -/* - * Copyright 2013-2019,2020 by Ross Combs - * Copyright 2013-2019,2020 by Thomas E. Dickey - * - * All Rights Reserved - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * Except as contained in this notice, the name(s) of the above copyright - * holders shall not be used in advertising or otherwise to promote the - * sale, use or other dealings in this Software without prior written - * authorization. - */ - -/* - * Context::make_color_hls: - * @h: hue - * @l: luminosity - * @s: saturation - * - * Returns the colour specified by (h, l, s) as RGB, 8 bit per component. - * - * Primary color hues are blue: 0 degrees, red: 120 degrees, and green: 240 degrees. - */ -Context::color_t -Context::make_color_hls(int h, - int l, - int s) noexcept -{ - auto const c2p = std::abs(2 * l - 100); - auto const cp = ((100 - c2p) * s) << 1; - auto const hs = ((h + 240) / 60) % 6; - auto const xp = (hs & 1) ? cp : 0; - auto const mp = 200 * l - (cp >> 1); - - int r1p, g1p, b1p; - switch (hs) { - case 0: - r1p = cp; - g1p = xp; - b1p = 0; - break; - case 1: - r1p = xp; - g1p = cp; - b1p = 0; - break; - case 2: - r1p = 0; - g1p = cp; - b1p = xp; - break; - case 3: - r1p = 0; - g1p = xp; - b1p = cp; - break; - case 4: - r1p = xp; - g1p = 0; - b1p = cp; - break; - case 5: - r1p = cp; - g1p = 0; - b1p = xp; - break; - default: - __builtin_unreachable(); - } - - auto const r = ((r1p + mp) * 255 + 10000) / 20000; - auto const g = ((g1p + mp) * 255 + 10000) / 20000; - auto const b = ((b1p + mp) * 255 + 10000) / 20000; - - return make_color(r, g, b); -} - -/* END */ - -/* This is called when resetting the Terminal which is currently using - * DataSyntax::DECSIXEL syntax. Clean up buffers, but don't reset colours - * etc since they will be re-initialised anyway when the context is - * used the next time. - */ -void -Context::reset() noexcept -{ - /* Keep buffer of default size */ - if (m_scanlines_data_capacity > minimum_capacity()) { - m_scanlines_data.reset(); - m_scanlines_data_capacity = 0; - } - - m_scanline_begin = m_scanline_pos = m_scanline_end = nullptr; -} - -/* - * Ensure that the scanlines buffer has space for the image (as specified - * by the raster and actual dimensions) and at least one full k_max_width - * scanline. - * - * The scanline offsets must be up-to-date before calling this function. - * - * On success, m_scanline_begin and m_scanline_pos will point to the start - * of the current scanline (that is, m_scanline_data + *m_scanlines_offsets_pos), - * and m_scanline_end will point to the end of the scanline of k_max_width sixels, - * and %true returned. - * - * On failure, all of m_scanline_begin/pos/end will be set to nullptr, and - * %false returned. - */ - bool - Context::ensure_scanlines_capacity() noexcept - { - auto const width = std::max(m_raster_width, m_width); - auto const height = std::max(m_raster_height, m_height); - - /* This is guaranteed not to overflow since width and height - * are limited by k_max_{width,height}. - */ - auto const needed_capacity = capacity(width, height); - auto const old_capacity = m_scanlines_data_capacity; - - if (needed_capacity <= old_capacity) - return true; - - /* Not enought space, so we need to enlarge the buffer. Don't - * overallocate, but also don't reallocate too often; so try - * doubling but use an upper limit. - */ - auto const new_capacity = std::min(std::max({minimum_capacity(), - needed_capacity, - old_capacity * 2}), - capacity(k_max_width, k_max_height)); - - m_scanlines_data = vte::glib::take_free_ptr(reinterpret_cast<color_index_t*>(g_try_realloc_n(m_scanlines_data.release(), - new_capacity, - sizeof(color_index_t)))); - if (!m_scanlines_data) { - m_scanlines_data_capacity = 0; - m_scanline_pos = m_scanline_begin = m_scanline_end = nullptr; - return false; - } - - /* Clear newly allocated capacity */ - std::memset(m_scanlines_data.get() + old_capacity, 0, - (new_capacity - old_capacity) * sizeof(*m_scanlines_data.get())); - - m_scanlines_data_capacity = new_capacity; - - /* Relocate the buffer pointers. The update_scanline_offsets() above - * made sure that m_scanlines_offsets is up to date. - */ - auto const old_scanline_pos = m_scanline_pos - m_scanline_begin; - m_scanline_begin = m_scanlines_data.get() + m_scanlines_offsets_pos[0]; - m_scanline_end = m_scanlines_data.get() + m_scanlines_offsets_pos[1]; - m_scanline_pos = m_scanline_begin + old_scanline_pos; - - assert(m_scanline_begin <= scanlines_data_end()); - assert(m_scanline_pos <= scanlines_data_end()); - assert(m_scanline_end <= scanlines_data_end()); - - return true; -} - -void -Context::reset_colors() noexcept -{ - /* DECPPLV2 says that on startup, and after DECSTR, DECSCL and RIS, - * all colours are assigned to Black, *not* to a palette. - * Instead, it says that devices may have 8- or 16-colour palettes, - * and which HLS and RGB values used in DECGCI will result in which - * of these 8 or 64 colours being actually used. - * - * It also says that between DECSIXEL invocations, colour registers - * are preserved; in xterm, whether colours are kept or cleared, - * is controlled by the XTERM_SIXEL_PRIVATE_COLOR_REGISTERS private - * mode. - */ - - /* Background fill colour, fully transparent by default */ - m_colors[0] = 0u; - - /* This is the VT340 default colour palette of 16 colours. - * PPLV2 defines 8- and 64-colour palettes; not sure - * why everyone seems to use the VT340 one? - * - * Colours 9..14 (name marked with '*') are less saturated - * versions of colours 1..6. - */ - m_colors[0 + 2] = make_color_rgb( 0, 0, 0); /* HLS( 0, 0, 0) */ /* Black */ - m_colors[1 + 2] = make_color_rgb(20, 20, 80); /* HLS( 0, 50, 60) */ /* Blue */ - m_colors[2 + 2] = make_color_rgb(80, 13, 13); /* HLS(120, 46, 72) */ /* Red */ - m_colors[3 + 2] = make_color_rgb(20, 80, 20); /* HLS(240, 50, 60) */ /* Green */ - m_colors[4 + 2] = make_color_rgb(80, 20, 80); /* HLS( 60, 50, 60) */ /* Magenta */ - m_colors[5 + 2] = make_color_rgb(20, 80, 80); /* HLS(300, 50, 60) */ /* Cyan */ - m_colors[6 + 2] = make_color_rgb(80, 80, 20); /* HLS(180, 50, 60) */ /* Yellow */ - m_colors[7 + 2] = make_color_rgb(53, 53, 53); /* HLS( 0, 53, 0) */ /* Grey 50% */ - m_colors[8 + 2] = make_color_rgb(26, 26, 26); /* HLS( 0, 26, 0) */ /* Grey 25% */ - m_colors[9 + 2] = make_color_rgb(33, 33, 60); /* HLS( 0, 46, 29) */ /* Blue* */ - m_colors[10 + 2] = make_color_rgb(60, 26, 26); /* HLS(120, 43, 39) */ /* Red* */ - m_colors[11 + 2] = make_color_rgb(33, 60, 33); /* HLS(240, 46, 29) */ /* Green* */ - m_colors[12 + 2] = make_color_rgb(60, 33, 60); /* HLS( 60, 46, 29) */ /* Magenta* */ - m_colors[13 + 2] = make_color_rgb(33, 60, 60); /* HLS(300, 46, 29) */ /* Cyan* */ - m_colors[14 + 2] = make_color_rgb(60, 60, 33); /* HLS(180, 46, 29) */ /* Yellow* */ - m_colors[15 + 2] = make_color_rgb(80, 80, 80); /* HLS( 0, 80, 0) */ /* Grey 75% */ - - /* Devices may use the same colour palette for DECSIXEL as for - * text mode, so initialise colours 16..255 to the standard 256-colour - * palette. I haven't seen any documentation from DEC that says - * this is what they actually did, but this is what all the libsixel - * related terminal emulator patches did, so let's copy that. Except - * that they use a variant of the 666 colour cube which - * uses make_color_rgb(r * 51, g * 51, b * 51) instead of the formula - * below which is the same as for the text 256-colour palette's 666 - * colour cube, and make_color_rgb(i * 11, i * 11, i * 11) instead of - * the formula below which is the same as for the text 256-colour palette - * greyscale ramp. - */ - /* 666-colour cube */ - auto make_cube_color = [&](unsigned r, - unsigned g, - unsigned b) constexpr noexcept -> auto - { - return make_color(r ? r * 40u + 55u : 0, - g ? g * 40u + 55u : 0, - b ? b * 40u + 55u : 0); - }; - - for (auto n = 0; n < 216; ++n) - m_colors[n + 16 + 2] = make_cube_color(n / 36, (n / 6) % 6, n % 6); - - /* 24-colour greyscale ramp */ - for (auto n = 0; n < 24; ++n) - m_colors[n + 16 + 216 + 2] = make_color(8 + n * 10, 8 + n * 10, 8 + n * 10); - - /* Set all other colours to black */ - for (auto n = 256 + 2; n < k_num_colors + 2; ++n) - m_colors[n] = make_color(0, 0, 0); -} - -void -Context::prepare(uint32_t introducer, - unsigned fg_red, - unsigned fg_green, - unsigned fg_blue, - unsigned bg_red, - unsigned bg_green, - unsigned bg_blue, - bool bg_transparent, - bool private_color_registers, - double pixel_aspect) noexcept -{ - m_introducer = introducer; - m_st = 0; - m_width = m_height = 0; - m_raster_width = m_raster_height = 0; - - if (private_color_registers) - reset_colors(); - - if (bg_transparent) - m_colors[0] = 0u; /* fully transparent */ - else - m_colors[0] = make_color(bg_red, bg_green, bg_blue); - - m_colors[1] = make_color(fg_red, fg_green, fg_blue); - - /* - * DEC PPLV2 says that on entering DECSIXEL mode, the active colour - * is set to colour register 0. Xterm defaults to register 3. - * We use the current foreground color in our special register 1. - */ - set_current_color(1); - - /* Clear buffer and scanline offsets */ - std::memset(m_scanlines_offsets, 0, sizeof(m_scanlines_offsets)); - - if (m_scanlines_data) - std::memset(m_scanlines_data.get(), 0, - m_scanlines_data_capacity * sizeof(color_index_t)); - - m_scanlines_offsets_pos = scanlines_offsets_begin(); - m_scanlines_offsets[0] = 0; - - ensure_scanline(); -} - -template<typename C, - typename P> -inline C* -Context::image_data(size_t* size, - unsigned stride, - P pen) noexcept -{ - auto const height = image_height(); - auto const width = image_width(); - if (height == 0 || width == 0 || !m_scanlines_data) - return nullptr; - - if (size) - *size = height * stride; - - auto wdata = vte::glib::take_free_ptr(reinterpret_cast<C*>(g_try_malloc_n(height, stride))); - if (!wdata) - return nullptr; - - /* FIXMEchpe: this can surely be optimised, perhaps using SIMD, and - * being more cache-friendly. - */ - - assert((stride % sizeof(C)) == 0); - auto wstride = stride / sizeof(C); - assert(wstride >= width); - // auto wdata_end = wdata + wstride * height; - - /* There may be one scanline at the bottom that extends below the image's height, - * and needs to be handled specially. First convert all the full scanlines, then - * the last partial one. - * - * FIXMEchpe: colour data needs byteswapping for big endian? - */ - auto scanlines_offsets_pos = scanlines_offsets_begin(); - auto wdata_pos = wdata.get(); - auto y = 0u; - for (; - (scanlines_offsets_pos + 1) < scanlines_offsets_end() && (y + 6) <= height; - ++scanlines_offsets_pos, wdata_pos += 6 * wstride, y += 6) { - auto const scanline_begin = m_scanlines_data.get() + scanlines_offsets_pos[0]; - auto const scanline_end = m_scanlines_data.get() + scanlines_offsets_pos[1]; - auto x = 0u; - for (auto scanline_pos = scanline_begin; scanline_pos < scanline_end; ++x) { - for (auto n = 0; n < 6; ++n) { - wdata_pos[n * wstride + x] = pen(*scanline_pos++); - } - } - - /* Clear leftover space */ - if (x < wstride) { - auto const bg = pen(0); - for (auto n = 0; n < 6; ++n) { - std::fill(&wdata_pos[n * wstride + x], - &wdata_pos[(n + 1) * wstride], - bg); - } - } - } - - if (y < height && (y + 6) > height && - (scanlines_offsets_pos + 1) < scanlines_offsets_end()) { - auto const h = height - y; - auto const scanline_begin = m_scanlines_data.get() + scanlines_offsets_pos[0]; - auto const scanline_end = m_scanlines_data.get() + scanlines_offsets_pos[1]; - auto x = 0u; - for (auto scanline_pos = scanline_begin; scanline_pos < scanline_end; ++x) { - for (auto n = 0u; n < h; ++n) { - wdata_pos[n * wstride + x] = pen(*scanline_pos++); - } - - scanline_pos += 6 - h; - } - - /* Clear leftover space */ - if (x < wstride) { - auto const bg = pen(0); - for (auto n = 0u; n < h; ++n) { - std::fill(&wdata_pos[n * wstride + x], - &wdata_pos[(n + 1) * wstride], - bg); - } - } - } - - /* We drop the scanlines buffer here if it's bigger than the default buffer size, - * so that parsing a big image doesn't retain the large buffer forever. - */ - if (m_scanlines_data_capacity > minimum_capacity()) { - m_scanlines_data.reset(); - m_scanlines_data_capacity = 0; - } - - return wdata.release(); -} - -// This is only used in the test suite -Context::color_index_t* -Context::image_data_indexed(size_t* size, - unsigned extra_width_stride) noexcept -{ - return image_data<color_index_t>(size, - (image_width() + extra_width_stride) * sizeof(color_index_t), - [](color_index_t pen) noexcept -> color_index_t { return pen; }); -} - -#ifdef VTE_COMPILATION - -uint8_t* -Context::image_data() noexcept -{ - return reinterpret_cast<uint8_t*>(image_data<color_t>(nullptr, - cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, image_width()), - [&](color_index_t pen) noexcept -> color_t { return m_colors[pen]; })); -} - -vte::Freeable<cairo_surface_t> -Context::image_cairo() noexcept -{ - static cairo_user_data_key_t s_data_key; - - auto data = image_data(); - if (!data) - return nullptr; - - auto const stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32, image_width()); - auto surface = vte::take_freeable(cairo_image_surface_create_for_data(data, - CAIRO_FORMAT_ARGB32, - image_width(), - image_height(), - stride)); - -#ifdef VTE_DEBUG - _VTE_DEBUG_IF(VTE_DEBUG_IMAGE) { - static auto num = 0; - - auto tmpl = vte::glib::take_string(g_strdup_printf("vte-image-sixel-%05d-XXXXXX.png", - ++num)); - auto err = vte::glib::Error{}; - char* path = nullptr; - auto fd = vte::libc::FD{g_file_open_tmp(tmpl.get(), &path, err)}; - if (fd) { - auto rv = cairo_surface_write_to_png(surface.get(), path); - if (rv == CAIRO_STATUS_SUCCESS) - g_printerr("SIXEL Image written to '%s'\n", path); - else - g_printerr("Failed to write SIXEL image to '%s': %m\n", path); - } else { - g_printerr("Failed to create tempfile for SIXEL image: %s\n", err.message()); - } - g_free(path); - } -#endif /* VTE_DEBUG */ - - if (cairo_surface_set_user_data(surface.get(), - &s_data_key, - data, - (cairo_destroy_func_t)&g_free) != CAIRO_STATUS_SUCCESS) { - /* When this fails, it's not documented whether the destroy func - * will have been called; reading cairo code, it appears it is *not*. - */ - cairo_surface_finish(surface.get()); // drop data buffer - g_free(data); - - return nullptr; - } - - return surface; -} - -#endif /* VTE_COMPILATION */ - -} // namespace vte::sixel diff --git a/src/sixel-context.hh b/src/sixel-context.hh deleted file mode 100644 index 8990748c..00000000 --- a/src/sixel-context.hh +++ /dev/null @@ -1,676 +0,0 @@ -/* - * Copyright © 2020 Christian Persch - * - * This library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see <https://www.gnu.org/licenses/>. - */ - -#pragma once - -#include <cstdint> -#include <iterator> -#include <utility> - -#ifdef VTE_COMPILATION -#include <cairo.h> -#include "cairo-glue.hh" -#endif - -#include "glib-glue.hh" -//#include "parser-glue.hh" -#include "sixel-parser.hh" -#include "vtedefines.hh" - -#if __cplusplus <= 201703L -// FIXMEchpe remove this and just upgrade to C++20 -namespace std { -enum class endian -{ - little = __ORDER_LITTLE_ENDIAN__, - big = __ORDER_BIG_ENDIAN__, - native = __BYTE_ORDER__ -}; -} // namespace std -#else -#include <bit> -#endif - -namespace vte::sixel { - -class Context { - - friend class Parser; - -public: - Context() = default; - ~Context() = default; - - Context(Context const&) = delete; - Context(Context&&) noexcept = delete; - - Context& operator=(Context const&) = delete; - Context& operator=(Context&&) noexcept = delete; - - /* Packed colour, RGBA 8 bits per component */ - using color_t = uint32_t; - - /* Indexed colour */ - using color_index_t = uint16_t; - -private: - - uint32_t m_introducer{0}; - uint32_t m_st{0}; - - static inline constexpr unsigned const k_max_width = VTE_SIXEL_MAX_WIDTH; - - static inline constexpr unsigned const k_max_height = VTE_SIXEL_MAX_HEIGHT; - static_assert((k_max_height % 6) == 0, "k_max_height not divisible by 6"); - - static inline constexpr int const k_num_colors = VTE_SIXEL_NUM_COLOR_REGISTERS; - static_assert((k_num_colors & (k_num_colors - 1)) == 0, "k_num_colors not a power of 2"); - - /* The width and height as set per DECGRA */ - unsigned m_raster_width{0}; - unsigned m_raster_height{0}; - - /* The width and height as per the SIXEL data received */ - unsigned m_width{0}; - unsigned m_height{0}; - -public: - - constexpr auto max_width() const noexcept { return k_max_width; } - constexpr auto max_height() const noexcept { return k_max_height; } - constexpr auto num_colors() const noexcept { return k_num_colors; } - - constexpr auto image_width() const noexcept - { - return std::max(m_width, m_raster_width); - } - - constexpr auto image_height() const noexcept - { - return std::max(m_height, m_raster_height); - } - -private: - - color_t m_colors[2 + k_num_colors]; - bool m_palette_modified{false}; - - color_index_t m_current_color{0}; - - Parser m_sixel_parser{}; - - /* All sixels on the current scanline OR'd together */ - uint8_t m_scanline_mask{0}; - - int m_repeat_count{1}; - - /* - * m_scanlines_data stores the pixel data in indexed colours (not resolved - * RGBA colours). - * - * Pixels are stored interleaved in scan lines of six vertical pixels. - * This makes writing them cache-efficient, and allows to easily write - * more pixels in one scanline than the previous scanlines without having - * to copy and pad already-written data. The buffer is created at the - * start, and enlarged (if necessary) when starting a new scanline. - * - * m_scanlines_data is allocated/re-allocated as needed, and stores - * m_scanlines_data_capacity color_index_t items. - * - * The offsets of the scanlines in m_scanlines_data are stored in - * m_scanlines_offsets; scanline N occupies - * [m_scanlines_offsets[N], m_scanlines_offsets[N+1]). - * - * m_scanlines_offsets_pos points to the offset in m_scanlines_offsets of the - * current scanline, and is never nullptr. When in a valid scanline, there is - * space to write to m_scanlines_offsets_pos[1] to store the scanline end - * position. - * - * m_scanline_begin is a pointer to the current scanline being written; - * m_scanline_pos is a pointer to the current write position, and - * m_scanline_end is a pointer to the end of the scanline. All scanlines - * have space to write up to k_max_width sixels (i.e. have 6 * k_max_width - * items), regardless of m_width. - * If allocation fails, or height limits are exceeded, all three pointers - * are set to nullptr. - * - * [FIXME: This could be further improved (e.g. wrt. memory fragmentation) by - * using a tempfile to store the pixel data, having only a fixed buffer - * of N * k_max_width * 6 size, and writing out the scanline data on DECGNL, - * instead of re-/allocating memory for the whole buffer.] - */ - - size_t m_scanlines_data_capacity{0}; - vte::glib::FreePtr<color_index_t> m_scanlines_data{}; - - color_index_t* m_scanline_begin{nullptr}; - color_index_t* m_scanline_end{nullptr}; - color_index_t* m_scanline_pos{nullptr}; - unsigned m_scanlines_offsets[(k_max_height + 5) / 6 + 1]; // one more than the maximum - // number of scanlines since - // we need to store begin and - // end offsets for each scanline - unsigned* m_scanlines_offsets_pos{nullptr}; - - inline auto scanlines_data_begin() const noexcept - { - return m_scanlines_data.get(); - } - - inline auto scanlines_data_end() const noexcept - { - return m_scanlines_data.get() + m_scanlines_data_capacity; - } - - inline auto scanlines_offsets_begin() noexcept - { - return std::begin(m_scanlines_offsets); - } - - inline constexpr auto scanlines_offsets_end() const noexcept - { - return std::end(m_scanlines_offsets); - } - - inline constexpr auto scanline_capacity() const noexcept - { - return k_max_width * 6; - } - - inline constexpr auto scanlines_count() const noexcept - { - return unsigned(m_scanlines_offsets_pos - std::begin(m_scanlines_offsets)); - } - - /* Returns the capacity needed to storage an image of width×height - * dimensions, plus one max-sized scanline. - */ - inline constexpr auto - capacity(size_t const width, - size_t const height) noexcept - { - auto const scanlines = (height + 5) / 6; - return (width * scanlines + k_max_width) * 6; - } - - inline constexpr auto minimum_capacity() noexcept { return capacity(k_max_width, 64); } - - bool ensure_scanlines_capacity() noexcept; - - void - ensure_scanline() noexcept - { - if (!ensure_scanlines_capacity()) { - m_scanline_pos = m_scanline_begin = m_scanline_end = nullptr; - return; - } - - m_scanlines_offsets_pos[1] = m_scanlines_offsets_pos[0]; - m_scanline_pos = m_scanline_begin = scanlines_data_begin() + m_scanlines_offsets_pos[0]; - m_scanline_end = m_scanline_begin + scanline_capacity(); - } - - void - update_scanline_offsets() noexcept - { - /* Update the scanline end offset and the line width */ - auto const width = unsigned(m_scanline_pos - m_scanline_begin); - assert((width % 6) == 0); - m_width = std::min(std::max(m_width, width / 6), k_max_width); - - auto const pos = unsigned(m_scanline_pos - m_scanlines_data.get()); - assert((pos % 6) == 0); - m_scanlines_offsets_pos[1] = std::max(m_scanlines_offsets_pos[1], pos); - } - - bool - finish_scanline() - { - if (m_scanline_begin == m_scanline_end) - return false; - - auto msb = [](unsigned v) constexpr noexcept -> unsigned - { - return 8 * sizeof(unsigned) - __builtin_clz(v); - }; - - static_assert(msb(0b1u) == 1, "wrong"); - static_assert(msb(0b10u) == 2, "wrong"); - static_assert(msb(0b100u) == 3, "wrong"); - static_assert(msb(0b1000u) == 4, "wrong"); - static_assert(msb(0b1'0000u) == 5, "wrong"); - static_assert(msb(0b10'0000u) == 6, "wrong"); - static_assert(msb(0b11'1111u) == 6, "wrong"); - - /* Update the image height if there was any pixel set in the current scanline. */ - m_height = m_scanline_mask ? std::min(scanlines_count() * 6 + msb(m_scanline_mask), k_max_height) : m_height; - - m_scanline_mask = 0; - m_repeat_count = 1; - - update_scanline_offsets(); - - return true; - } - - inline constexpr auto - param_to_color_register(int param) noexcept - { - /* Colour registers are wrapped, as per DEC documentation. - * - * We internally reserve registers 0 and 1 for the background - * and foreground colors, the buffer being initialized to 0. - * Therefore the user-provided registers are stored at + 2 their - * public number. - */ - return (param & (k_num_colors - 1)) + 2; - } - - inline constexpr color_t - make_color(unsigned r, - unsigned g, - unsigned b) noexcept - { - if constexpr (std::endian::native == std::endian::little) { - return b | g << 8 | r << 16 | 0xffu << 24 /* opaque */; - } else if constexpr (std::endian::native == std::endian::big) { - return 0xffu /* opaque */ | r << 8 | g << 16 | b << 24; - } else { - __builtin_unreachable(); - } - } - - color_t - make_color_hls(int h, - int l, - int s) noexcept; - - inline constexpr color_t - make_color_rgb(unsigned r, - unsigned g, - unsigned b) noexcept - { - auto scale = [](unsigned value) constexpr noexcept -> auto - { - return (value * 255u + 50u) / 100u; - }; - - return make_color(scale(r), scale(g), scale(b)); - } - - void - set_color(color_index_t reg, - color_t color) noexcept - { - m_colors[m_current_color = reg] = color; - m_palette_modified = true; - } - - void - set_color_hls(unsigned reg, - unsigned h, - unsigned l, - unsigned s) noexcept - { - set_color(reg, make_color_hls(h, l, s)); - } - - void - set_color_rgb(unsigned reg, - unsigned r, - unsigned g, - unsigned b) noexcept - { - set_color(reg, make_color_rgb(r, g, b)); - } - - void - set_current_color(unsigned reg) noexcept - { - m_current_color = reg; - } - - template<typename C, - typename P> - inline C* image_data(size_t* size, - unsigned stride, - P pen) noexcept; - - void - DECGCI(vte::sixel::Sequence const& seq) noexcept - { - /* - * DECGCI - DEC Graphics Color Introducer - * Selects and defines the current colour. - * - * Arguments: - * args[0]: colour register - * args[1]: colour coordinate system - * 1: HLS - * 2: RGB - * args[2..4]: colour components - * args[2]: 0..360 for HLS or 0..100 for RGB - * args[3]: 0..100 for HSL and RGB - * args[4]: 0..100 for HSL and RGB - * - * Defaults: - * args[0]: 0 - * args[2]: no default - * args[3..5]: 0 - * - * If only one parameter is specified, selects the colour register - * for the following SIXELs to use. If more parameters are specified, - * additionally re-defines that colour register with the colour - * specified by the parameters. - * - * If the colour values exceed the ranges specified above, the DEC - * documentation says that the sequence is ignored. - * [FIXMEchpe: alternatively, we could just clamp to the range] - * [FIXMEchpe: check whether we need to set the current colour - * register even in that case] - * - * References: DEC PPLV2 § 5.8 - */ - - m_repeat_count = 1; - - auto const reg = param_to_color_register(seq.param(0, 0)); - - switch (seq.size()) { - case 0: /* no param means param 0 has default value */ - case 1: - /* Switch to colour register */ - set_current_color(reg); - break; - - case 2 ... 5: - switch (seq.param(1)) { - case -1: /* this parameter admits no default */ - default: - break; - - case 1: /* HLS */ { - auto const h = seq.param(2, 0); - auto const l = seq.param(3, 0); - auto const s = seq.param(4, 0); - if (G_UNLIKELY(h > 360 || l > 100 || s > 100)) - break; - - set_color_hls(reg, h, l, s); - break; - } - - case 2: /* RGB */ { - auto const r = seq.param(2, 0); - auto const g = seq.param(3, 0); - auto const b = seq.param(4, 0); - if (G_UNLIKELY(r > 100 || g > 100 || b > 100)) - break; - - set_color_rgb(reg, r, g, b); - break; - } - } - break; - - default: - break; - } - } - - void - DECGCR(vte::sixel::Sequence const& seq) noexcept - { - /* DECGCR - DEC Graphics Carriage Return - * Moves the active position to the left margin. - * - * (Note: DECCRNLM mode does not apply here.) - * - * References: DEC PPLV2 § 5.8 - */ - - /* Failed already, or exceeded limits */ - if (m_scanline_begin == m_scanline_end) - return; - - /* Update the scanline end offset of the current scanline, and return - * position to the start of the scanline. - */ - update_scanline_offsets(); - - m_repeat_count = 1; - m_scanline_pos = m_scanline_begin; - } - - void - DECGNL(vte::sixel::Sequence const& seq) noexcept - { - /* DECGNL - DEC Graphics Next Line - * Moves the active position to the left margin and - * down by one scanline (6 pixels). - * - * References: DEC PPLV2 § 5.8 - */ - - /* Failed already, or exceeded limits */ - if (!finish_scanline()) - return; - - /* Go to next scanline. If the number of scanlines exceeds the maximum - * (as defined by k_max_height), set the scanline pointers to nullptr. - */ - ++m_scanlines_offsets_pos; - if (m_scanlines_offsets_pos + 1 >= scanlines_offsets_end()) { - m_scanline_pos = m_scanline_begin = m_scanline_end = nullptr; - return; - } - - ensure_scanline(); - } - - void - DECGRA(vte::sixel::Sequence const& seq) noexcept - { - /* - * DECGRA - DEC Graphics Raster Attributes - * Selects the raster attributes for the SIXEL data following. - * - * Arguments: - * args[0]: pixel aspect ratio numerator (max: 32k) - * args[1]: pixel aspect ratio denominator (max: 32k) - * args[2]: horizontal size (in px) of the image - * args[3]: vertical size (in px) of the image - * - * Defaults: - * args[0]: 1 - * args[1]: 1 - * args[2]: no default - * args[3]: no default - - * Note that the image will not be clipped to the provided - * size. - * - * References: DEC PPLV2 § 5.8 - */ - - /* If any SIXEL data, or positioning command (DECGCR, DECGNL) has - * been received prior to this command, then DECGRA should be ignored. - * This check only approximates that condition, but that's good enough. - */ - if (m_scanlines_offsets_pos != scanlines_offsets_begin() || - m_scanline_begin != m_scanlines_data.get() || - m_scanline_pos != m_scanline_begin || - m_scanlines_offsets[1] != 0 || - m_scanlines_offsets[1] != m_scanlines_offsets[0]) - return; - - #if 0 - /* VTE doesn't currently use the pixel aspect ratio */ - auto const aspect_num = seq.param(0, 1, 1, 1 << 15 /* 32Ki */); - auto const aspect_den = seq.param(1, 1, 1, 1 << 15 /* 32Ki */); - auto const pixel_aspect = std::clamp(aspect_num / aspect_den, 0.1, 10.0); - #endif - - m_raster_width = seq.param(2, 0, 0, k_max_width); - m_raster_height = seq.param(3, 0, 0, k_max_height); - - /* Nothing else needs to be done here right now; the current - * scanline has enough space for k_max_width sixels, and the - * new raster width and height will be taken into account when - * resizing the m_scanlines_data buffer next. - */ - } - - void - DECGRI(vte::sixel::Sequence const& seq) noexcept - { - /* DECGRI - DEC Graphics Repeat Introducer - * Specifies the repeat count for the following SIXEL. - * - * Arguments: - * args[0]: the repeat count - * - * Defaults: - * args[0]: 1 - * - * References: DEC PPLV2 § 5.8 - */ - - /* DEC terminals limited the repetition count to 255, but the SIXEL - * test data includes repeat counts much greater. Since we limit to - * k_max_width anyway when executing the repeat on the next sixel, - * don't limit here. - */ - m_repeat_count = seq.param(0, 1); - } - - /* FIXMEchpe: should also set - * - * m_repeat_count = 1; - * - * for all the unused RESERVED_* sixel commands. - */ - - void - SIXEL(uint8_t sixel) noexcept - { - /* SIXEL data - * Data encodes a scanline of six pixels in the integer range - * 0x00 .. 0x3f, with the LSB representing the top pixel - * and the MSB representing the bottom pixel. - * - * References: DEC PPLV2 § 5.5.1 - */ - - if (sixel) { - auto const color = m_current_color; - auto const scanline_end = m_scanline_end; - auto scanline_pos = m_scanline_pos; - - for (auto n = m_repeat_count; - n > 0 && G_LIKELY(scanline_pos < scanline_end); - --n) { - /* Note that the scanline has space for at least 6 pixels, wo we - * don't need to check scanline_pos < scanline_end in this inner loop. - * - * FIXMEchpe: this can likely be optimised with some SIMD? - */ - for (auto mask = 0b1u; mask < 0b100'0000u; mask <<= 1) { - auto const old_color = *scanline_pos; - *scanline_pos++ = sixel & mask ? color : old_color; - } - - assert(scanline_pos <= scanline_end); - } - - m_scanline_pos = scanline_pos; - m_scanline_mask |= sixel; - - } else { - /* If there are no bits to set, just advance the position, - * making sure to guard against overflow. - */ - m_scanline_pos = std::clamp(m_scanline_pos + m_repeat_count * 6, - m_scanline_begin, m_scanline_end); - } - - m_repeat_count = 1; - } - - void - SIXEL_ST(char32_t st) noexcept - { - m_st = st; - - /* Still need to finish the current scanline. */ - finish_scanline(); - } - -public: - - void prepare(uint32_t introducer, - unsigned fg_red, - unsigned fg_green, - unsigned fg_blue, - unsigned bg_red, - unsigned bg_green, - unsigned bg_blue, - bool bg_transparent, - bool private_color_registers, - double pixel_aspect = 1.0) noexcept; - - void reset_colors() noexcept; - - void reset() noexcept; - - uint8_t* image_data() noexcept; - - // These are only used in the test suite - color_index_t* image_data_indexed(size_t* size = nullptr, - unsigned extra_width_stride = 0) noexcept; - auto color(unsigned idx) const noexcept { return m_colors[idx]; } - -#ifdef VTE_COMPILATION - vte::Freeable<cairo_surface_t> image_cairo() noexcept; -#endif - - void - set_mode(Parser::Mode mode) - { - m_sixel_parser.set_mode(mode); - } - - auto - parse(uint8_t const* const bufstart, - uint8_t const* const bufend, - bool eos) noexcept -> auto - { - return m_sixel_parser.parse(bufstart, bufend, eos, *this); - } - - constexpr auto introducer() const noexcept { return m_introducer; } - constexpr auto st() const noexcept { return m_st; } - - constexpr bool - is_matching_controls() const noexcept - { - return ((introducer() ^ st()) & 0x80) == 0; - } - -}; // class Context - -} // namespace vte::sixel diff --git a/src/sixel-fuzzer.cc b/src/sixel-fuzzer.cc deleted file mode 100644 index 207f3cfd..00000000 --- a/src/sixel-fuzzer.cc +++ /dev/null @@ -1,763 +0,0 @@ -/* - * Copyright © 2020 Hans Petter Jansson <hpj@cl.no> - * - * This library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see <https://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include <assert.h> -#include <errno.h> -#include <stdlib.h> -#include <string.h> -#include <stdint.h> -#include <stdio.h> - -#include <termios.h> /* TIOCGWINSZ */ -#include <sys/ioctl.h> /* ioctl() */ - -#include <glib.h> - -/* The image data is stored as a series of palette indexes, with 16 bits - * per pixel and TRANSPARENT_SLOT indicating transparency. This allows for - * palette sizes up to 65535 colors. - * - * TRANSPARENT_SLOT can be any u16 value. Typically, the first or last - * slot (0, n_colors) is used. The transparency index is never emitted; - * instead pixels with this value are left blank in the output. */ - -#define N_COLORS_MAX 65536 -#define TRANSPARENT_SLOT ((N_COLORS_MAX) - 1) - -#define WIDTH_MAX 65536 -#define HEIGHT_MAX 65536 - -#define N_PIXELS_IN_SIXEL 6 - -#define PRE_SEQ "\x1bP" -#define POST_SEQ "\x1b\\" - -#define TEST_IMAGE_SIZE_MIN 16 -#define TEST_IMAGE_SIZE_MAX 512 - -/* Big palettes make our toy printer extremely slow; use with caution */ -#define TEST_PALETTE_SIZE_MIN 1 -#define TEST_PALETTE_SIZE_MAX 16 - -/* --- Helpers --- */ - -static int -random_int_in_range (int min, int max) -{ - if (min == max) - return min; - - if (min > max) { - int t = max; - max = min; - min = t; - } - - return min + (random () % (max - min)); -} - -static int -round_up_to_multiple (int n, int m) -{ - n += m - 1; - return n - (n % m); -} - -static int -round_down_to_multiple (int n, int m) -{ - return round_up_to_multiple (n, m + 1) - m; -} - -static void -memset_u16 (uint16_t *buf, uint16_t val, int n) -{ - int i; - - for (i = 0; i < n; i++) { - buf [i] = val; - } -} - -static uint16_t -pen_to_slot (int i) -{ - if (i >= TRANSPARENT_SLOT) - return i + 1; - - return i; -} - -static uint8_t -interp_u8 (uint8_t a, uint8_t b, int fraction, int total) -{ - uint32_t ta, tb; - - assert (fraction >= 0 && fraction <= total); - - /* Only one color in palette */ - if (total == 0) - return a; - - ta = (uint32_t) a * (total - fraction) / total; - tb = (uint32_t) b * fraction / total; - - return ta + tb; -} - -static uint32_t -interp_colors (uint32_t a, uint32_t b, int fraction, int total) -{ - return interp_u8 (a, b, fraction, total) - | (interp_u8 (a >> 8, b >> 8, fraction, total) << 8) - | (interp_u8 (a >> 16, b >> 16, fraction, total) << 16) - | (interp_u8 (a >> 24, b >> 24, fraction, total) << 24); -} - -static int -transform_range (int n, int old_min, int old_max, int new_min, int new_max) -{ - if (new_min == new_max) - return new_min; - - if (n < old_min) - n = old_min; - if (n > old_max) - n = old_max; - - return ((n - old_min) * (new_max - new_min) / (old_max - old_min)) + new_min; -} - -/* Transform to sixel color channels, which are in the 0..100 range. */ -static void -argb_to_sixel_rgb (uint32_t argb, int *r, int *g, int *b) -{ - *r = transform_range ((argb >> 16) & 0xff, 0, 256, 0, 101); - *g = transform_range ((argb >> 8) & 0xff, 0, 256, 0, 101); - *b = transform_range (argb & 0xff, 0, 256, 0, 101); -} - -/* --- Image gen and sixel conversion --- */ - -typedef struct -{ - int width, height; - int n_colors; - uint32_t palette [N_COLORS_MAX]; - uint16_t *pixels; -} -Image; - -static void -image_init (Image *image, int width, int height, int n_colors) -{ - int alloc_height; - int n_pixels; - - assert (width > 0 && width <= WIDTH_MAX); - assert (height > 0 && height <= HEIGHT_MAX); - assert (n_colors > 0 && n_colors < N_COLORS_MAX); - - image->width = width; - image->height = height; - image->n_colors = n_colors; - - alloc_height = round_up_to_multiple (height, N_PIXELS_IN_SIXEL); - - n_pixels = width * alloc_height; - image->pixels = (uint16_t *) malloc (n_pixels * sizeof (uint16_t)); - memset_u16 (image->pixels, TRANSPARENT_SLOT, n_pixels); -} - -static void -image_deinit (Image *image) -{ - free (image->pixels); - image->pixels = NULL; -} - -static void -image_generate_palette (Image *image, uint32_t first_color, uint32_t last_color) -{ - int pen; - - for (pen = 0; pen < image->n_colors; pen++) { - image->palette [pen_to_slot (pen)] - = interp_colors (first_color, last_color, pen, image->n_colors - 1); - } -} - -static void -image_set_pixel (Image *image, int x, int y, uint16_t value) -{ - image->pixels [y * image->width + x] = value; -} - -static uint16_t -image_get_pixel (const Image *image, int x, int y) -{ - return image->pixels [y * image->width + x]; -} - -static uint8_t -image_get_sixel (const Image *image, int x, int y, uint16_t value) -{ - uint8_t sixel = 0; - int i; - - for (i = 0; i < N_PIXELS_IN_SIXEL; i++) { - uint16_t p = image_get_pixel (image, x, y + N_PIXELS_IN_SIXEL - 1 - i); - - sixel <<= 1; - if (p == value) - sixel |= 1; - } - - return sixel; -} - -static void -image_draw_shape (Image *image) -{ - int y, x; - - for (y = 0; y < image->height; y++) { - int pen = ((image->n_colors - 1) * y + image->height / 2) / image->height; - - for (x = 0; x < image->width; x++) { - if (x == 0 || x == image->width - 1 /* Box left/right */ - || y == 0 || y == image->height - 1 /* Box top/bottom */ - || y == x || y == image->width - 1 - x) /* X diagonals */ - image_set_pixel (image, x, y, pen_to_slot (pen)); - } - } -} - -static void -image_generate (Image *image, uint32_t first_color, uint32_t last_color) -{ - image_generate_palette (image, first_color, last_color); - image_draw_shape (image); -} - -static void -image_print_sixels_palette (const Image *image, GString *gstr) -{ - int pen; - - for (pen = 0; pen < image->n_colors; pen++) { - uint32_t col = image->palette [pen_to_slot (pen)]; - int r, g, b; - - argb_to_sixel_rgb (col, &r, &g, &b); - g_string_append_printf (gstr, "#%d;2;%d;%d;%d", - pen_to_slot (pen), r, g, b); - } -} - -static void -emit_sixels (GString *gstr, uint8_t sixel, int n, uint16_t slot, - bool pass_ended, uint16_t *emitted_slot_inout, - bool *need_emit_cr_inout, bool *need_emit_cr_inout_next) -{ - if (n == 0) { - return; - } - - if (!pass_ended || sixel != 0) { - char c = '?' + (char) sixel; - - if (*need_emit_cr_inout) { - g_string_append_c (gstr, '$'); - *need_emit_cr_inout = FALSE; - } - - if (slot != *emitted_slot_inout) { - g_string_append_printf (gstr, "#%d", slot); - *emitted_slot_inout = slot; - } - - while (n > 255) { - g_string_append_printf (gstr, "!255%c", c); - n -= 255; - } - - if (n >= 4) { - g_string_append_printf (gstr, "!%d%c", n, c); - n = 0; - } else { - for ( ; n > 0; n--) - g_string_append_c (gstr, c); - } - } - - if (sixel != 0) - *need_emit_cr_inout_next = TRUE; -} - -static void -image_print_sixels_row (const Image *image, GString *gstr, int y, uint16_t *emitted_slot_inout) -{ - bool need_emit_cr = FALSE; - bool need_emit_cr_next = FALSE; - int pen; - - for (pen = 0; pen < image->n_colors; pen++) { - uint16_t slot = pen_to_slot (pen); - uint8_t cur_sixel = 0; - int n_cur_sixel = 0; - int x; - - for (x = 0; x < image->width; x++) { - uint8_t next_sixel = image_get_sixel (image, x, y, slot); - - if (next_sixel == cur_sixel) { - n_cur_sixel++; - continue; - } - - emit_sixels (gstr, cur_sixel, n_cur_sixel, slot, FALSE, - emitted_slot_inout, &need_emit_cr, &need_emit_cr_next); - cur_sixel = next_sixel; - n_cur_sixel = 1; - } - - emit_sixels (gstr, cur_sixel, n_cur_sixel, slot, TRUE, - emitted_slot_inout, &need_emit_cr, &need_emit_cr_next); - need_emit_cr = need_emit_cr_next; - } - - /* CR + Linefeed */ - g_string_append_c (gstr, '-'); -} - -static void -image_print_sixels_data (const Image *image, GString *gstr) -{ - uint16_t emitted_slot = TRANSPARENT_SLOT; - int y; - - for (y = 0; y < image->height; y += N_PIXELS_IN_SIXEL) { - image_print_sixels_row (image, gstr, y, &emitted_slot); - } -} - -static void -image_print_sixels (const Image *image, GString *gstr) -{ - g_string_append_printf (gstr, PRE_SEQ "0;0;0q\"1;1;%d;%d", - image->width, image->height); - image_print_sixels_palette (image, gstr); - image_print_sixels_data (image, gstr); - g_string_append (gstr, POST_SEQ); -} - -/* --- Main loop and printing --- */ - -typedef enum -{ - TEST_MODE_UNSET, - TEST_MODE_FUZZ -} -TestMode; - -typedef struct -{ - TestMode mode; - - float delay; - int n_errors; - int n_frames; - int seed; - int n_scroll; - - int term_width_cells, term_height_cells; - int term_width_pixels, term_height_pixels; - - int term_cell_width, term_cell_height; -} -Options; - -static void -cursor_to_offset (gint x, gint y, GString *gstr) -{ - g_string_printf (gstr, "\x1b[%d;%df", y, x); -} - -static void -cursor_to_random_offset (gint x_max, gint y_max, GString *gstr) -{ - cursor_to_offset (random_int_in_range (0, x_max), - random_int_in_range (0, y_max), - gstr); -} - -static void -scroll_n_lines (const Options *options, int n, GString *gstr) -{ - if (n < 1) - return; - - cursor_to_offset (0, options->term_height_cells, gstr); - for (int i = 0; i < n; i++) - g_string_append_printf (gstr, "\n"); -} - -static void -print_text (const gchar *text, GString *gstr) -{ - g_string_append (gstr, text); -} - -static void -print_random_image (const Options *options, GString *gstr) -{ - int dim_max = MIN (TEST_IMAGE_SIZE_MAX, - MAX (TEST_IMAGE_SIZE_MIN, - MIN (options->term_width_pixels, - round_down_to_multiple (options->term_height_pixels - options->term_cell_height, - N_PIXELS_IN_SIXEL)))); - int dim = random_int_in_range (TEST_IMAGE_SIZE_MIN, dim_max + 1); - Image image; - - image_init (&image, dim, dim, - random_int_in_range (TEST_PALETTE_SIZE_MIN, TEST_PALETTE_SIZE_MAX)); - - /* In order to produce colors that contrast both white and black backgrounds, - * limit the range of the red component. This doesn't work reliably with grey - * backgrounds, but eh. */ - image_generate (&image, - random_int_in_range (0x00400000, 0x00a00000), - random_int_in_range (0x00400000, 0x00a00000)); - - cursor_to_random_offset ((options->term_width_pixels - dim) / options->term_cell_width, - (options->term_height_pixels - dim) / options->term_cell_height, - gstr); - image_print_sixels (&image, gstr); - - image_deinit (&image); -} - -static void -print_random_text (const Options *options, GString *gstr) -{ - cursor_to_random_offset (options->term_width_cells - strlen ("Hallo!"), - options->term_height_cells, - gstr); - print_text ("Hallo!", gstr); -} - -typedef enum -{ - FUZZ_REPLACE, - FUZZ_COPY, - FUZZ_SWAP, - - FUZZ_MAX -} -FuzzType; - -static void -fuzz_replace (GString *gstr) -{ - int a, b; - - a = random_int_in_range (0, gstr->len - 1); - b = a + random_int_in_range (0, MIN (gstr->len - a, 64)); - - for (int i = a; i < b; i++) { - gstr->str [i] = random_int_in_range (1, 256); - } -} - -static void -fuzz_copy (GString *gstr) -{ - int a, b, c; - - a = random_int_in_range (0, gstr->len - 1); - b = random_int_in_range (0, MIN (gstr->len - a, 64)); - c = random_int_in_range (0, gstr->len - b); - - memcpy (gstr->str + c, gstr->str + a, b); -} - -static void -fuzz_swap (GString *gstr) -{ - unsigned char buf [64]; - int a, b, c; - - a = random_int_in_range (0, gstr->len - 1); - b = random_int_in_range (0, MIN (gstr->len - a, 64)); - c = random_int_in_range (0, gstr->len - b); - - memcpy (buf, gstr->str + c, b); - memcpy (gstr->str + c, gstr->str + a, b); - memcpy (gstr->str + c, buf, b); -} - -static void -random_fuzz (const Options *options, GString *gstr) -{ - if (gstr->len < 1) - return; - - for (int i = 0; i < options->n_errors; i++) { - FuzzType fuzz_type = FuzzType(random () % FUZZ_MAX); - - switch (fuzz_type) { - case FUZZ_REPLACE: - fuzz_replace (gstr); - break; - case FUZZ_COPY: - fuzz_copy (gstr); - break; - case FUZZ_SWAP: - fuzz_swap (gstr); - break; - default: - break; - } - } -} - -static void -print_loop (const Options *options) -{ - for (int i = 0; options->n_frames == 0 || i < options->n_frames; i++) { - GString *gstr; - - gstr = g_string_new (""); - - scroll_n_lines (options, options->n_scroll, gstr); - - if (random () % 2) { - print_random_image (options, gstr); - } else { - print_random_text (options, gstr); - } - - random_fuzz (options, gstr); - - fwrite (gstr->str, sizeof (char), gstr->len, stdout); - g_string_free (gstr, TRUE); - fflush (stdout); - - if (options->delay > 0.000001f) - g_usleep (options->delay * 1000000.0f); - } -} - -/* --- Argument parsing and init --- */ - -static bool -parse_int (const char *arg, const char *val, int *out) -{ - char *endptr; - int ret; - bool result = FALSE; - - assert (arg != NULL); - assert (val != NULL); - assert (out != NULL); - - if (*val == '\0') { - fprintf (stderr, "Empty value for argument '%s'. Aborting.\n", arg); - goto out; - } - - ret = strtol (val, &endptr, 10); - - if (*endptr != '\0') { - fprintf (stderr, "Unrecognized value for argument '%s': '%s'. Aborting.\n", arg, val); - goto out; - } - - *out = ret; - result = TRUE; - -out: - return result; -} - -static bool -parse_float (const char *arg, const char *val, float *out) -{ - char *endptr; - float ret; - bool result = FALSE; - - assert (arg != NULL); - assert (val != NULL); - assert (out != NULL); - - if (*val == '\0') { - fprintf (stderr, "Empty value for argument '%s'. Aborting.\n", arg); - goto out; - } - - ret = strtof (val, &endptr); - - if (*endptr != '\0') { - fprintf (stderr, "Unrecognized value for argument '%s': '%s'. Aborting.\n", arg, val); - goto out; - } - - *out = ret; - result = TRUE; - -out: - return result; -} - -static bool -parse_options (Options *options, int argc, char **argv) -{ - bool result = FALSE; - int i; - - if (argc < 2) { - fprintf (stderr, "Usage: %s <mode> [options]\n\n" - "Modes:\n" - " fuzz Perform fuzzing test.\n\n" - "Options:\n" - " -d <float> Delay between frames, in seconds (default: 0.0).\n" - " -e <int> Maximum number of random errors per frame (default: 0).\n" - " -n <int> Number of frames to output (default: infinite).\n" - " -r <int> Random seed to use (default: current time).\n" - " -s <int> Number of lines to scroll for each frame (default: 0).\n\n", - argv [0]); - goto out; - } - - for (i = 1; i < argc; ) { - const char *arg = argv [i]; - const char *val; - - if (!strcmp (arg, "fuzz")) { - options->mode = TEST_MODE_FUZZ; - i++; - continue; - } - - if (i + 1 >= argc) - break; - - val = argv [i + 1]; - - if (!strcmp (arg, "-d")) { - if (!parse_float (arg, val, &options->delay)) - goto out; - i += 2; - } else if (!strcmp (arg, "-e")) { - if (!parse_int (arg, val, &options->n_errors)) - goto out; - i += 2; - } else if (!strcmp (arg, "-n")) { - if (!parse_int (arg, val, &options->n_frames)) - goto out; - i += 2; - } else if (!strcmp (arg, "-r")) { - if (!parse_int (arg, val, &options->seed)) - goto out; - i += 2; - } else if (!strcmp (arg, "-s")) { - if (!parse_int (arg, val, &options->n_scroll)) - goto out; - i += 2; - } else { - fprintf (stderr, "Unrecognized option '%s'. Aborting.\n", arg); - goto out; - } - } - - if (i != argc) { - fprintf (stderr, "Stray option '%s'. Aborting.\n", argv [i]); - goto out; - } - - if (options->mode == TEST_MODE_UNSET) { - fprintf (stderr, "No test mode specified. Try \"fuzz\".\n"); - goto out; - } - - result = TRUE; - -out: - return result; -} - -static bool -query_terminal (Options *options) -{ - struct winsize wsz; - - if (ioctl (fileno (stdout), TIOCGWINSZ, &wsz) != 0) { - fprintf (stderr, "ioctl() failed: %s\n", strerror (errno)); - return FALSE; - } - - options->term_width_cells = wsz.ws_col; - options->term_height_cells = wsz.ws_row; - options->term_width_pixels = wsz.ws_xpixel; - options->term_height_pixels = wsz.ws_ypixel; - - if (options->term_width_cells < 4 - || options->term_height_cells < 4) { - fprintf (stderr, "Terminal window is too small (must be greater than 4x4 cells).\n"); - return FALSE; - } - - if (options->term_width_pixels == 0 - || options->term_height_pixels == 0) { - fprintf (stderr, "Terminal did not report its pixel size.\n"); - return FALSE; - } - - if (options->term_width_pixels < 16 - || options->term_height_pixels < 16) { - fprintf (stderr, "Terminal window is too small (must be greater than 16x16 pixels).\n"); - return FALSE; - } - - options->term_cell_width = wsz.ws_xpixel / wsz.ws_col; - options->term_cell_height = wsz.ws_ypixel / wsz.ws_row; - - return TRUE; -} - -/* --- Entry point --- */ - -int -main (int argc, char *argv []) -{ - static Options options = { }; - - options.seed = (int) time (NULL); - - if (!parse_options (&options, argc, argv)) - return 1; - - if (!query_terminal (&options)) - return 2; - - print_loop (&options); - return 0; -} diff --git a/src/sixel-parser.hh b/src/sixel-parser.hh deleted file mode 100644 index 821acdc9..00000000 --- a/src/sixel-parser.hh +++ /dev/null @@ -1,670 +0,0 @@ -/* - * Copyright © 2018, 2020 Christian Persch - * - * This library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see <https://www.gnu.org/licenses/>. - */ - -#pragma once - -#include <cstdint> -#include <cstdio> -#include <cstring> -#include <initializer_list> -#include <type_traits> -#include <utility> - -#include "cxx-utils.hh" -#include "parser-arg.hh" - -#define VTE_SIXEL_PARSER_ARG_MAX (8) - -namespace vte::sixel { - -class Parser; - -enum class Command : uint8_t { - NONE = 0x20, - DECGRI = 0x21, // DEC Graphics Repeat Introducer - DECGRA = 0x22, // DEC Set Raster Attributes - DECGCI = 0x23, // DEC Graphics Color Introducer - DECGCR = 0x24, // DEC Graphics Carriage Return - DECGNL = 0x2d, // DEC Graphics Next Line - RESERVED_2_05 = 0x25, - RESERVED_2_06 = 0x26, - RESERVED_2_07 = 0x27, - RESERVED_2_08 = 0x28, - RESERVED_2_09 = 0x29, - RESERVED_2_10 = 0x2a, - RESERVED_2_11 = 0x2b, - RESERVED_2_12 = 0x2c, - RESERVED_2_14 = 0x2e, - RESERVED_2_15 = 0x2f, - RESERVED_3_12 = 0x3c, - RESERVED_3_13 = 0x3d, - RESERVED_3_14 = 0x3e, -}; - -class Sequence { -protected: - friend class Parser; - - unsigned m_command{(unsigned)Command::NONE}; - unsigned m_n_args{0}; - vte_seq_arg_t m_args[VTE_SIXEL_PARSER_ARG_MAX]{0, 0, 0, 0, 0, 0 ,0 ,0}; - - constexpr auto capacity() const noexcept - { - return sizeof(m_args) / sizeof(m_args[0]); - } - -public: - - constexpr Sequence() noexcept = default; - - Sequence(Command cmd, - std::initializer_list<int> params = {}) noexcept - : m_command(vte::to_integral(cmd)) - { - assert(params.size() <= capacity()); - for (auto p : params) - m_args[m_n_args++] = vte_seq_arg_init(std::min(p, 0xffff)); - } - - ~Sequence() = default; - - Sequence(Sequence const&) noexcept = default; - Sequence(Sequence&&) noexcept = default; - - Sequence& operator=(Sequence const&) noexcept = default; - Sequence& operator=(Sequence&&) noexcept = default; - - constexpr bool - operator==(Sequence const& rhs) const noexcept - { - return command() == rhs.command() && - size() == rhs.size() && - std::memcmp(m_args, rhs.m_args, m_n_args * sizeof(m_args[0])) == 0; - } - - /* command: - * - * Returns: the command the sequence codes for. - */ - inline constexpr Command command() const noexcept - { - return Command(m_command); - } - - /* size: - * - * Returns: the number of parameters - */ - inline constexpr unsigned int size() const noexcept - { - return m_n_args; - } - - /* param_default: - * @idx: - * - * Returns: whether the parameter at @idx has default value - */ - inline constexpr bool param_default(unsigned int idx) const noexcept - { - return __builtin_expect(idx < size(), 1) ? vte_seq_arg_default(m_args[idx]) : true; - } - - /* param: - * @idx: - * @default_v: the value to use for default parameters - * - * Returns: the value of the parameter at index @idx, or @default_v if - * the parameter at this index has default value, or the index - * is out of bounds - */ - inline constexpr int param(unsigned int idx, - int default_v = -1) const noexcept - { - return __builtin_expect(idx < size(), 1) ? vte_seq_arg_value(m_args[idx], default_v) : default_v; - } - - /* param: - * @idx: - * @default_v: the value to use for default parameters - * @min_v: the minimum value - * @max_v: the maximum value - * - * Returns: the value of the parameter at index @idx, or @default_v if - * the parameter at this index has default value, or the index - * is out of bounds. The returned value is clamped to the - * range @min_v..@max_v (or returns min_v, if min_v > max_v). - */ - inline constexpr int param(unsigned int idx, - int default_v, - int min_v, - int max_v) const noexcept - { - auto const v = param(idx, default_v); - // not using std::clamp() since it's not guaranteed that min_v <= max_v - return std::max(std::min(v, max_v), min_v); - } - -}; // class Sequence - -/* SIXEL parser. - * - * Known differences to the DEC terminal SIXEL parser: - * - * * Input bytes with the high bit set are ignored, and not processed as if masked - * with ~0x80; except for C1 controls in Mode::EIGHTBIT mode which will abort parsing - * - * * Supports UTF-8 C1 controls. C1 ST will finish parsing; all other C1 controls - * will abort parsing (in Mode::UTF8) - * - * * All C0 controls (except CAN, ESC, SUB) and not just the format effector controls - * (HT, BS, LF, VT, FF, CR) are ignored, not executed as if received before the DCS start - * - * * 3/10 ':' is reserved for future use as subparameter separator analogous to - * the main parser; any parameter sequences including ':' will be ignored. - * - * * When the number of parameter exceeds the maximum (16), DEC executes the function - * with these parameters, ignoring the excessive parameters; vte ignores the - * whole function instead. - */ - -class Parser { -public: - enum class Mode { - UTF8, /* UTF-8 */ - EIGHTBIT, /* ECMA-35, 8 bit */ - SEVENBIT, /* ECMA-35, 7 bit */ - }; - - enum class Status { - CONTINUE = 0, - COMPLETE, - ABORT, - ABORT_REWIND_ONE, - ABORT_REWIND_TWO, - }; - - Parser() = default; - ~Parser() = default; - - Parser(Mode mode) : - m_mode{mode} - { - } - -private: - Parser(Parser const&) = delete; - Parser(Parser&&) = delete; - - Parser& operator=(Parser const&) = delete; - Parser& operator=(Parser&) = delete; - - enum class State { - GROUND, /* initial state and ground */ - PARAMS, /* have command, now parsing parameters */ - IGNORE, /* ignore until next command */ - ESC, /* have seen ESC, waiting for backslash */ - UTF8_C2, /* have seen 0xC2, waiting for second UTF-8 byte */ - }; - - Mode m_mode{Mode::UTF8}; - State m_state{State::GROUND}; - Sequence m_seq{}; - - [[gnu::always_inline]] - void - params_clear() noexcept - { - /* The (m_n_args+1)th parameter may have been started but not - * finialised, so it needs cleaning too. All further params - * have not been touched, so need not be cleaned. - */ - unsigned int n_args = G_UNLIKELY(m_seq.m_n_args >= VTE_SIXEL_PARSER_ARG_MAX) - ? VTE_SIXEL_PARSER_ARG_MAX - : m_seq.m_n_args + 1; - memset(m_seq.m_args, 0, n_args * sizeof(m_seq.m_args[0])); -#ifdef PARSER_EXTRA_CLEAN - /* Assert that the assumed-clean params are actually clean. */ - for (auto n = n_args; n < VTE_SIXEL_PARSER_ARG_MAX; ++n) - g_assert_cmpuint(m_seq.m_args[n], ==, VTE_SEQ_ARG_INIT_DEFAULT); -#endif - - m_seq.m_n_args = 0; - } - - [[gnu::always_inline]] - void - params_overflow() noexcept - { - /* An overflow of the parameter number occurs when - * m_n_arg == VTE_SIXEL_PARSER_ARG_MAX, and either an 0…9 - * is encountered, starting the next param, or an - * explicit ':' or ';' terminating a (defaulted) (sub)param, - * or when the next command or sixel data character occurs - * after a defaulted (sub)param. - * - * Transition to IGNORE to ignore the whole sequence. - */ - transition(0, State::IGNORE); - } - - [[gnu::always_inline]] - void - params_finish() noexcept - { - if (G_LIKELY(m_seq.m_n_args < VTE_SIXEL_PARSER_ARG_MAX)) { - if (m_seq.m_n_args > 0 || - vte_seq_arg_started(m_seq.m_args[m_seq.m_n_args])) { - vte_seq_arg_finish(&m_seq.m_args[m_seq.m_n_args], false); - ++m_seq.m_n_args; - } - } - } - - [[gnu::always_inline]] - Status - param_finish(uint8_t raw) noexcept - { - if (G_LIKELY(m_seq.m_n_args < VTE_SIXEL_PARSER_ARG_MAX - 1)) { - vte_seq_arg_finish(&m_seq.m_args[m_seq.m_n_args], false); - ++m_seq.m_n_args; - } else - params_overflow(); - - return Status::CONTINUE; - } - - [[gnu::always_inline]] - Status - param(uint8_t raw) noexcept - { - if (G_LIKELY(m_seq.m_n_args < VTE_SIXEL_PARSER_ARG_MAX)) - vte_seq_arg_push(&m_seq.m_args[m_seq.m_n_args], raw); - else - params_overflow(); - - return Status::CONTINUE; - } - - template<class D, class = std::void_t<>> - struct has_SIXEL_CMD_member : std::false_type { }; - - template<class D> - struct has_SIXEL_CMD_member<D, std::void_t<decltype(&D::SIXEL_CMD)>> : std::true_type { }; - - template<class D> - [[gnu::always_inline]] - std::enable_if_t<has_SIXEL_CMD_member<D>::value> - dispatch(uint8_t raw, - D& delegate) noexcept - { - params_finish(); - delegate.SIXEL_CMD(m_seq); - } - - template<class D> - [[gnu::always_inline]] - std::enable_if_t<!has_SIXEL_CMD_member<D>::value> - dispatch(uint8_t raw, - D& delegate) noexcept - { - params_finish(); - switch (m_seq.command()) { - case Command::DECGRI: return delegate.DECGRI(m_seq); - case Command::DECGRA: return delegate.DECGRA(m_seq); - case Command::DECGCI: return delegate.DECGCI(m_seq); - case Command::DECGCR: return delegate.DECGCR(m_seq); - case Command::DECGNL: return delegate.DECGNL(m_seq); - case Command::NONE: - case Command::RESERVED_2_05: - case Command::RESERVED_2_06: - case Command::RESERVED_2_07: - case Command::RESERVED_2_08: - case Command::RESERVED_2_09: - case Command::RESERVED_2_10: - case Command::RESERVED_2_11: - case Command::RESERVED_2_12: - case Command::RESERVED_2_14: - case Command::RESERVED_2_15: - case Command::RESERVED_3_12: - case Command::RESERVED_3_13: - case Command::RESERVED_3_14: - default: - return; - } - } - - template<class D> - [[gnu::always_inline]] - Status - data(uint8_t sixel, - D& delegate) noexcept - { - delegate.SIXEL(sixel); - return Status::CONTINUE; - } - - [[gnu::always_inline]] - Status - transition(uint8_t raw, - State state) noexcept - { - m_state = state; - return Status::CONTINUE; - - } - - [[gnu::always_inline]] - Status - abort(uint8_t raw, - Status result) noexcept - { - transition(raw, State::GROUND); - return result; - } - - template<class D> - [[gnu::always_inline]] - Status - complete(uint8_t raw, - D& delegate) noexcept - { - transition(raw, State::GROUND); - delegate.SIXEL_ST(raw); - return Status::COMPLETE; - } - - [[gnu::always_inline]] - Status - consume(uint8_t raw) noexcept - { - params_clear(); - m_seq.m_command = raw; - return transition(raw, State::PARAMS); - } - - [[gnu::always_inline]] - Status - nop(uint8_t raw) noexcept - { - return Status::CONTINUE; - } - -public: - - template<class D> - Status - feed(uint8_t raw, - D& delegate) noexcept - { - // Refer to Table 2-2 in DECPPLV2 for information how to handle C0 and C1 - // controls, DEL, and GR data (in 8-bit mode). - switch (m_state) { - case State::PARAMS: - switch (raw) { - case 0x00 ... 0x17: - case 0x19: - case 0x1c ... 0x1f: /* C0 \ { CAN, SUB, ESC } */ - /* FIXMEchpe: maybe only do this for the format effector controls?, - * and let GROUND handle everything else C0? - */ - return nop(raw); - case 0x30 ... 0x39: /* '0' ... '9' */ - return param(raw); - case 0x3a: /* ':' */ - // Reserved for subparams; just ignore the whole sequence. - return transition(raw, State::IGNORE); - case 0x3b: /* ';' */ - return param_finish(raw); - case 0x7f: /* DEL */ - case 0xa0 ... 0xc1: - case 0xc3 ... 0xff: - return nop(raw); - case 0xc2: /* Start byte for UTF-8 C1 controls */ - if (m_mode == Mode::EIGHTBIT) - return nop(raw); - - [[fallthrough]]; - case 0x80 ... 0x9f: - if (m_mode == Mode::SEVENBIT) - return nop(raw); - - [[fallthrough]]; - case 0x18: /* CAN */ - case 0x1b: /* ESC */ - case 0x20 ... 0x2f: - case 0x3c ... 0x7e: - // Dispatch the current command and continue parsing - dispatch(raw, delegate); - [[fallthrough]]; - case 0x1a: /* SUB */ - /* The question is whether SUB should only act like '?' or - * also dispatch the current sequence. I interpret the DEC - * docs as indicating it aborts the sequence without dispatching - * it and only inserts the '?'. - */ - transition(raw, State::GROUND);; - } - - [[fallthrough]]; - case State::GROUND: - ground: - switch (raw) { - case 0x00 ... 0x17: - case 0x19: - case 0x1c ... 0x1f: /* C0 \ { CAN, SUB, ESC } */ - // According to DECPPLV2, the format effector controls - // (HT, BS, LF, VT, FF, CR) should be executed as if - // received before the DECSIXEL DCS, and then processing - // to continue for the control string, and the other C0 - // controls should be ignored. - // VTE just ignores all C0 controls except ESC, CAN, SUB - return nop(raw); - case 0x18: /* CAN */ - return abort(raw, Status::ABORT_REWIND_ONE); - case 0x1b: /* ESC */ - return transition(raw, State::ESC); - case 0x20: /* SP */ - return nop(raw); - case 0x21 ... 0x2f: - case 0x3c ... 0x3e: - return consume(raw); - case 0x30 ... 0x3b: /* { '0' .. '9', ':', ';' } */ - // Parameters, but we don't have a command yet. - // Ignore the whole sequence. - return transition(raw, State::IGNORE); - case 0x1a: /* SUB */ - // Same as 3/15 '?' according to DECPPLV2 - raw = 0x3fu; - [[fallthrough]]; - case 0x3f ... 0x7e: /* { '?' .. '~' } */ - // SIXEL data - return data(raw - 0x3f, delegate); - case 0x7f: /* DEL */ - // Ignore according to DECPPLV2 - return nop(raw); - case 0xc2: /* Start byte for UTF-8 C1 controls */ - if (m_mode == Mode::UTF8) - return transition(raw, State::UTF8_C2); - return nop(raw); - case 0x9c: /* raw C1 ST */ - if (m_mode == Mode::EIGHTBIT) - return complete(raw, delegate); - [[fallthrough]]; - case 0x80 ... 0x9b: - case 0x9d ... 0x9f: /* raw C1 \ { ST } */ - // Abort and execute C1 control - if (m_mode == Mode::EIGHTBIT) - return abort(raw, Status::ABORT_REWIND_ONE); - [[fallthrough]]; - case 0xa0 ... 0xc1: - case 0xc3 ... 0xff: /* GR */ - return nop(raw); - - } - break; - - case State::IGNORE: - switch (raw) { - // FIXMEchpe do we need to nop() C0 constrols (except SUB, CAN, ESC) here? - case 0x30 ... 0x3b: /* { '0' .. '9', ':', ';' } */ - case 0x7f: /* DEL */ - return nop(raw); - case 0x00 ... 0x2f: - case 0x3c ... 0x7e: - case 0x80 ... 0xff: - transition(raw, State::GROUND); - goto ground; - } - break; - - case State::ESC: - switch (raw) { - case 0x5c: /* '\' */ - return complete(raw, delegate); - case 0x7f: /* DEL */ - // FIXMEchpe is this correct? check with main parser / spec / DEC - return nop(raw); - case 0x00 ... 0x5b: - case 0x5d ... 0x7e: - case 0x80 ... 0xff: - /* Abort and let the outer parser handle the ESC again */ - return abort(raw, Status::ABORT_REWIND_TWO); - } - break; - - case State::UTF8_C2: - switch (raw) { - case 0x1b: /* ESC */ - return transition(raw, State::ESC); - case 0x80 ... 0x9b: - case 0x9d ... 0x9f: /* C1 \ { ST } */ - /* Abort and let the outer parser handle the C1 control again */ - return abort(raw, Status::ABORT_REWIND_TWO); - case 0x9c: /* ST */ - return complete(raw, delegate); - case 0xc2: - return transition(raw, State::UTF8_C2); - case 0x00 ... 0x1a: - case 0x1c ... 0x7f: /* including DEL */ - case 0xa0 ... 0xc1: - case 0xc3 ... 0xff: - transition(raw, State::GROUND); - goto ground; - } - break; - default: - break; - } - __builtin_unreachable(); - return Status::CONTINUE; - } - - template<class D> - Status - flush(D& delegate) noexcept - { - switch (m_state) { - case State::PARAMS: - dispatch(0, delegate); - [[fallthrough]]; - case State::GROUND: - case State::IGNORE: - return abort(0, Status::ABORT); - default: - __builtin_unreachable(); - [[fallthrough]]; - case State::ESC: - case State::UTF8_C2: - return abort(0, Status::ABORT_REWIND_ONE); - } - } - - void - reset() noexcept - { - transition(0, State::GROUND); - } - - void - set_mode(Mode mode) noexcept - { - reset(); - m_mode = mode; - } - - constexpr auto const& sequence() const noexcept { return m_seq; } - - enum class ParseStatus { - CONTINUE, - COMPLETE, - ABORT - }; - - template<class D> - std::pair<ParseStatus, uint8_t const*> - parse(uint8_t const* const bufstart, - uint8_t const* const bufend, - bool eos, - D& delegate) noexcept - { - for (auto sptr = bufstart; sptr < bufend; ) { - switch (feed(*(sptr++), delegate)) { - case vte::sixel::Parser::Status::CONTINUE: - break; - - case vte::sixel::Parser::Status::COMPLETE: - return {ParseStatus::COMPLETE, sptr}; - - case vte::sixel::Parser::Status::ABORT_REWIND_TWO: - --sptr; - [[fallthrough]]; - case vte::sixel::Parser::Status::ABORT_REWIND_ONE: - --sptr; - [[fallthrough]]; - case vte::sixel::Parser::Status::ABORT: - return {ParseStatus::ABORT, sptr}; - } - } - - if (eos) { - auto sptr = bufend; - switch (flush(delegate)) { - case vte::sixel::Parser::Status::CONTINUE: - break; - - case vte::sixel::Parser::Status::COMPLETE: - return {ParseStatus::COMPLETE, sptr}; - - case vte::sixel::Parser::Status::ABORT_REWIND_TWO: - --sptr; - [[fallthrough]]; - case vte::sixel::Parser::Status::ABORT_REWIND_ONE: - --sptr; - [[fallthrough]]; - case vte::sixel::Parser::Status::ABORT: - return {ParseStatus::ABORT, sptr}; - } - } - - return {ParseStatus::CONTINUE, bufend}; - } - -}; // class Parser - -} // namespace vte::sixel diff --git a/src/sixel-test.cc b/src/sixel-test.cc deleted file mode 100644 index 3f9fd312..00000000 --- a/src/sixel-test.cc +++ /dev/null @@ -1,1589 +0,0 @@ -/* - * Copyright © 2020 Christian Persch - * - * This library is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this library. If not, see <https://www.gnu.org/licenses/>. - */ - -#include "config.h" - -#include <algorithm> -#include <cstddef> -#include <cstdint> -#include <cstring> -#include <initializer_list> -#include <string> -#include <variant> -#include <vector> - -#include <glib.h> - -#include "sixel-parser.hh" -#include "sixel-context.hh" - -using namespace std::literals; - -using Command = vte::sixel::Command; -using Context = vte::sixel::Context; -using Mode = vte::sixel::Parser::Mode; -using ParseStatus = vte::sixel::Parser::ParseStatus; - -// Parser tests - -static inline constexpr auto -param_to_color_register(unsigned reg) -{ - return reg + 2; /* Public colour registers start at 2 */ -} - -static char const* -cmd_to_str(Command command) -{ - switch (command) { - case Command::DECGRI: return "DECGRI"; - case Command::DECGRA: return "DECGRA"; - case Command::DECGCI: return "DECGCI"; - case Command::DECGCR: return "DECGCR"; - case Command::DECGNL: return "DECGNL"; - case Command::NONE: return "NONE"; - default: - static char buf[32]; - snprintf(buf, sizeof(buf), "UNKOWN(%d/%02d)", - (int)command / 16, - (int)command % 16); - return buf; - } -} - -enum class StType { - C0, - C1_UTF8, - C1_EIGHTBIT -}; - -inline constexpr auto -ST(StType type) -{ - switch (type) { - case StType::C0: return "\e\\"sv; - case StType::C1_UTF8: return "\xc2\x9c"sv; - case StType::C1_EIGHTBIT: return "\x9c"sv; - default: __builtin_unreachable(); - } -} - -inline constexpr auto -ST(Mode mode) -{ - switch (mode) { - case Mode::UTF8: return ST(StType::C1_UTF8); - case Mode::EIGHTBIT: return ST(StType::C1_EIGHTBIT); - case Mode::SEVENBIT: return ST(StType::C0); - default: __builtin_unreachable(); - } -} - -class Sequence : public vte::sixel::Sequence { -public: - using Base = vte::sixel::Sequence; - - Sequence(Base const& seq) - : Base{seq} - { - } - - Sequence(Command cmd, - std::vector<int> const& params) noexcept - : Base{cmd} - { - assert(params.size() <= (sizeof(m_args) / sizeof(m_args[0]))); - for (auto p : params) - m_args[m_n_args++] = vte_seq_arg_init(std::min(p, 0xffff)); - } - - void append(std::string& str) const - { - if (command() != Command::NONE) - str.append(1, char(command())); - for (auto i = 0u; i < size(); ++i) { - auto const p = param(i); - if (p != -1) { - char buf[12]; - auto const len = g_snprintf(buf, sizeof(buf), "%d", p); - str.append(buf, len); - } - if ((i + 1) < size()) - str.append(1, ';'); - } - } - - void prettyprint(std::string& str) const - { - str.append("Sequence("); - str.append(cmd_to_str(command())); - if (size()) { - str.append(" "); - for (auto i = 0u; i < size(); ++i) { - auto const p = param(i); - - char buf[12]; - auto const len = g_snprintf(buf, sizeof(buf), "%d", p); - str.append(buf, len); - - if ((i + 1) < size()) - str.append(1, ';'); - } - } - str.append(")"); - } -}; - -constexpr bool operator==(Sequence const& lhs, Sequence const& rhs) noexcept -{ - if (lhs.command() != rhs.command()) - return false; - - auto const m = std::min(lhs.size(), rhs.size()); - for (auto n = 0u; n < m; ++n) - if (lhs.param(n) != rhs.param(n)) - return false; - - if (lhs.size() == rhs.size()) - return true; - - if ((lhs.size() == (rhs.size() + 1)) && lhs.param(rhs.size()) == -1) - return true; - - if (((lhs.size() + 1) == rhs.size()) && rhs.param(lhs.size()) == -1) - return true; - - return false; -} - -class Sixel { -public: - constexpr Sixel(uint8_t sixel) - : m_sixel(sixel) - { - assert(m_sixel < 0b100'0000); - } - - ~Sixel() = default; - - constexpr auto sixel() const noexcept { return m_sixel; } - - void append(std::string& str) const { str.append(1, char(m_sixel + 0x3f)); } - - void prettyprint(std::string& str) const - { - str.append("Sixel("); - char buf[3]; - auto const len = g_snprintf(buf, sizeof(buf), "%02x", sixel()); - str.append(buf, len); - str.append(")"); - } - -private: - uint8_t m_sixel{0}; -}; - -constexpr bool operator==(Sixel const& lhs, Sixel const& rhs) noexcept -{ - return lhs.sixel() == rhs.sixel(); -} - -class Unicode { -public: - Unicode(char32_t c) : - m_c{c} - { - m_utf8_len = g_unichar_to_utf8(c, m_utf8_buf); - } - ~Unicode() = default; - - constexpr auto unicode() const noexcept { return m_c; } - - void append(std::string& str) const { str.append(m_utf8_buf, m_utf8_len); } - - void prettyprint(std::string& str) const - { - str.append("Unicode("); - char buf[7]; - auto const len = g_snprintf(buf, sizeof(buf), "%04X", unicode()); - str.append(buf, len); - str.append(")"); - } - -private: - char32_t m_c{0}; - size_t m_utf8_len{0}; - char m_utf8_buf[4]{0, 0, 0, 0}; -}; - -constexpr bool operator==(Unicode const& lhs, Unicode const& rhs) noexcept -{ - return lhs.unicode() == rhs.unicode(); -} - -class C0Control { -public: - C0Control(uint8_t c) : - m_control{c} - { - assert(c < 0x20 || c == 0x7f); - } - ~C0Control() = default; - - constexpr auto control() const noexcept { return m_control; } - - void append(std::string& str) const { str.append(1, char(m_control)); } - - void prettyprint(std::string& str) const - { - str.append("C0("); - char buf[3]; - auto const len = g_snprintf(buf, sizeof(buf), "%02X", control()); - str.append(buf, len); - str.append(")"); - } - -private: - uint8_t m_control{0}; -}; - -constexpr bool operator==(C0Control const& lhs, C0Control const& rhs) noexcept -{ - return lhs.control() == rhs.control(); -} - -class C1Control { -public: - C1Control(uint8_t c) : - m_control{c} - { - assert(c >= 0x80 && c < 0xa0); - auto const len = g_unichar_to_utf8(c, m_utf8_buf); - assert(len == 2); - } - ~C1Control() = default; - - constexpr auto control() const noexcept { return m_control; } - - void append(std::string& str, - Mode mode) const { - switch (mode) { - case Mode::UTF8: - str += std::string_view(m_utf8_buf, 2); - break; - case Mode::EIGHTBIT: - str.append(1, char(m_control)); - break; - case Mode::SEVENBIT: - str.append(1, char(0x1b)); - str.append(1, char(m_control - 0x40)); - break; - } - } - - void prettyprint(std::string& str) const - { - str.append("C1("); - char buf[3]; - auto const len = g_snprintf(buf, sizeof(buf), "%02X", control()); - str.append(buf, len); - str.append(")"); - } - -private: - uint8_t m_control{0}; - char m_utf8_buf[2]{0, 0}; -}; - -constexpr bool operator==(C1Control const& lhs, C1Control const& rhs) noexcept -{ - return lhs.control() == rhs.control(); -} - -class Raw { -public: - Raw(uint8_t raw) : - m_raw{raw} - { - } - ~Raw() = default; - - constexpr auto raw() const noexcept { return m_raw; } - - void append(std::string& str) const { str += char(m_raw); } - - void prettyprint(std::string& str) const - { - str.append("Raw("); - char buf[3]; - auto const len = g_snprintf(buf, sizeof(buf), "%02X", raw()); - str.append(buf, len); - str.append(")"); - } - -private: - uint8_t m_raw{0}; -}; - -constexpr bool operator==(Raw const& lhs, Raw const& rhs) noexcept -{ - return lhs.raw() == rhs.raw(); -} - -inline auto -DECGRI(int count) noexcept -{ - return Sequence{Command::DECGRI, {count}}; -} - -inline auto -DECGRA(int an, - int ad, - int w, - int h) noexcept -{ - return Sequence{Command::DECGRA, {an, ad, w, h}}; -} - -inline auto -DECGCI(int reg) noexcept -{ - return Sequence{Command::DECGCI, {reg}}; -} - -inline auto -DECGCI_HLS(int reg, - int h, - int l, - int s) noexcept -{ - return Sequence{Command::DECGCI, {reg, 1, h, l, s}}; -} - -inline auto -DECGCI_RGB(int reg, - int r, - int g, - int b) noexcept -{ - return Sequence{Command::DECGCI, {reg, 2, r, g, b}}; -} - -inline auto -DECGCR() noexcept -{ - return Sequence{Command::DECGCR}; -} - -inline auto -DECGNL() noexcept -{ - return Sequence{Command::DECGNL}; -} - -using Item = std::variant<Sequence, Sixel, C0Control, C1Control, Unicode, Raw>; -using ItemList = std::vector<Item>; - -#if 0 - -class ItemPrinter { -public: - ItemPrinter(Item const& item) - { - std::visit(*this, item); - } - - ~ItemPrinter() = default; - - std::string const& string() const noexcept { return m_str; } - std::string_view string_view() const noexcept { return m_str; } - - void operator()(Sequence const& seq) { seq.prettyprint(m_str); } - void operator()(Sixel const& sixel) { sixel.prettyprint(m_str); } - void operator()(C0Control const& control) { control.prettyprint(m_str); } - void operator()(C1Control const& control) { control.prettyprint(m_str); } - void operator()(Unicode const& unicode) { unicode.prettyprint(m_str); } - void operator()(Raw const& raw) { raw.prettyprint(m_str); } - -private: - std::string m_str{}; -}; - -static void -print_items(char const* intro, - ItemList const& items) -{ - auto str = std::string{}; - - for (auto const& item : items) { - str += ItemPrinter{item}.string(); - str += " "; - } - - g_printerr("%s: %s\n", intro, str.c_str()); -} - -#endif - -class ItemStringifier { -public: - ItemStringifier(Mode mode = Mode::UTF8) : - m_mode{mode} - { } - - ItemStringifier(Item const& item, - Mode mode = Mode::UTF8) : - m_mode{mode} - { - std::visit(*this, item); - } - - ItemStringifier(ItemList const& items, - Mode mode = Mode::UTF8) : - m_mode{mode} - { - for (auto&& i : items) - std::visit(*this, i); - } - - ~ItemStringifier() = default; - - std::string string() const noexcept { return m_str; } - std::string_view string_view() const noexcept { return m_str; } - - void operator()(Sequence const& seq) { seq.append(m_str); } - void operator()(Sixel const& sixel) { sixel.append(m_str); } - void operator()(C0Control const& control) { control.append(m_str); } - void operator()(C1Control const& control) { control.append(m_str, m_mode); } - void operator()(Unicode const& unicode) { unicode.append(m_str); } - void operator()(Raw const& raw) { raw.append(m_str); } - -private: - std::string m_str{}; - Mode m_mode; -}; - -class SimpleContext { - - friend class Parser; -public: - SimpleContext() = default; - ~SimpleContext() = default; - - auto parse(std::string_view const& str, - size_t end_pos = size_t(-1)) - { - auto const beginptr = reinterpret_cast<uint8_t const*>(str.data()); - auto const endptr = reinterpret_cast<uint8_t const*>(beginptr + str.size()); - return m_parser.parse(beginptr, endptr, true, *this); - } - - auto parse(Item const& item, - Mode input_mode) - { - return parse(ItemStringifier{{item}, input_mode}.string_view()); - } - - auto parse(ItemList const& list, - Mode input_mode) - { - return parse(ItemStringifier{list, input_mode}.string_view()); - } - - void set_mode(Mode mode) - { - m_parser.set_mode(mode); - } - - void reset_mode() - { - set_mode(Mode::UTF8); - } - - void reset() - { - m_parser.reset(); - m_parsed_items.clear(); - m_st = 0; - } - - auto const& parsed_items() const noexcept { return m_parsed_items; } - - void SIXEL(uint8_t raw) noexcept - { - m_parsed_items.push_back(Sixel(raw)); - } - - void SIXEL_CMD(vte::sixel::Sequence const& seq) noexcept - { - m_parsed_items.push_back(Sequence(seq)); - } - - void SIXEL_ST(char32_t st) noexcept - { - m_st = st; - } - - vte::sixel::Parser m_parser{}; - ItemList m_parsed_items{}; - char32_t m_st{0}; - -}; // class SimpleContext - -/* - * assert_parse: - * @context: - * @mode: - * @str: - * @str_size: - * @expected_parsed_len: - * @expected_status: - * - * Asserts that parsing @str (up to @str_size, or until its size if @str_size is -1) - * in mode @mode results in @expected_status, with the endpointer pointing to the end - * of @str if @expected_parsed_len is -1, or to @expected_parsed_len otherwise. - */ -template<class C> -static void -assert_parse(C& context, - Mode mode, - std::string_view const& str, - size_t str_size = size_t(-1), - size_t expected_parse_end = size_t(-1), - ParseStatus expected_status = ParseStatus::COMPLETE, - int line = __builtin_LINE()) -{ - context.reset(); - context.set_mode(mode); - - auto const beginptr = reinterpret_cast<uint8_t const*>(str.data()); - auto const len = str_size == size_t(-1) ? str.size() : str_size; - auto const [status, ip] = context.parse(str, len); - auto const parsed_len = size_t(ip - beginptr); - - g_assert_cmpint(int(status), ==, int(expected_status)); - g_assert_cmpint(parsed_len, ==, expected_parse_end == size_t(-1) ? len : expected_parse_end); -} - -/* - * assert_parse: - * @context: - * @mode: - * @str: - * @expected_items: - * @str_size: - * @expected_parsed_len: - * @expected_status: - * - * Asserts that parsing @str (up to @str_size, or until its size if @str_size is -1) - * in mode @mode results in @expected_status, with the parsed items equal to - * @expected_items, and the endpointer pointing to the end of @str if @expected_parsed_len - * is -1, or to @expected_parsed_len otherwise. - */ -template<class C> -static void -assert_parse(C& context, - Mode mode, - std::string_view const& str, - ItemList const& expected_items, - size_t str_size = size_t(-1), - size_t expected_parse_end = size_t(-1), - ParseStatus expected_status = ParseStatus::COMPLETE, - int line = __builtin_LINE()) -{ - assert_parse(context, mode, str, str_size, expected_parse_end, expected_status, line); - - g_assert_true(context.parsed_items() == expected_items); -} - -/* - * assert_parse_st: - * - * Like assert_parse above, but ST-terminates the passed string. - */ -template<class C> -static void -assert_parse_st(C& context, - Mode mode, - std::string_view const& str, - size_t str_size = size_t(-1), - size_t expected_parse_end = size_t(-1), - ParseStatus expected_status = ParseStatus::COMPLETE, - StType st = StType::C0, - int line = __builtin_LINE()) -{ - auto str_st = std::string{str}; - str_st.append(ST(st)); - auto str_st_size = str_size; - - assert_parse(context, mode, str_st, str_st_size, expected_parse_end, expected_status, line); -} - -/* - * assert_parse_st: - * - * Like assert_parse above, but ST-terminates the passed string. - */ -template<class C> -static void -assert_parse_st(C& context, - Mode mode, - std::string_view const& str, - ItemList const& expected_items, - size_t str_size = size_t(-1), - size_t expected_parse_end = size_t(-1), - ParseStatus expected_status = ParseStatus::COMPLETE, - StType st = StType::C0, - int line = __builtin_LINE()) -{ - auto str_st = std::string{str}; - str_st.append(ST(st)); - auto str_st_size = str_size == size_t(-1) ? str_st.size() : str_size; - - assert_parse(context, mode, str_st, expected_items, str_st_size, expected_parse_end, expected_status, line); -} - -/* - * assert_parse_st: - * - * Like assert_parse above, but ST-terminates the passed string. - */ -template<class C> -static void -assert_parse_st(C& context, - Mode mode, - ItemList const& items, - ItemList const& expected_items, - ParseStatus expected_status = ParseStatus::COMPLETE, - StType st = StType::C0, - int line = __builtin_LINE()) -{ - assert_parse_st(context, mode, ItemStringifier{items, mode}.string_view(), expected_items, -1, -1, expected_status, st, line); -} - -static void -test_parser_seq_params(SimpleContext& context, - Mode mode, - std::vector<int> const& params) -{ - for (auto i = 0x20; i < 0x3f; ++i) { - if (i >= 0x30 && i < 0x3c) // Parameter characters - continue; - - - auto const items = ItemList{Sequence{Command(i), params}}; - assert_parse_st(context, mode, items, - (i == 0x20) ? ItemList{} /* 0x20 is ignored */ : items); - } -} - -static void -test_parser_seq_params(SimpleContext& context, - vte_seq_arg_t params[8], - bool as_is = false) -{ - for (auto mode : {Mode::UTF8, Mode::EIGHTBIT, Mode::SEVENBIT}) { - context.set_mode(mode); - - for (auto n = 0; n <= 8; ++n) { - auto pv = std::vector<int>(¶ms[0], ¶ms[n]); - - test_parser_seq_params(context, mode, pv); - - if (n > 0 && !as_is) { - pv[n - 1] = -1; - test_parser_seq_params(context, mode, pv); - } - } - } - - context.reset_mode(); -} - -static void -test_parser_seq_params(void) -{ - auto context = SimpleContext{}; - - /* Tests sixel commands, which have the form I P...P with an initial byte - * in the 2/0..2/15, 3/12..3/14 range, and parameter bytes P from 3/0..3/11. - */ - vte_seq_arg_t params1[8]{1, 0, 1000, 10000, 65534, 65535, 65536, 1}; - test_parser_seq_params(context, params1); - - vte_seq_arg_t params2[8]{1, -1, -1, -1, 1, -1, 1, 1}; - test_parser_seq_params(context, params2, true); -} - -static void -test_parser_seq_subparams(void) -{ - // Test that subparams cause the whole sequence to be ignored - - auto context = SimpleContext{}; - - for (auto mode : {Mode::UTF8, Mode::EIGHTBIT, Mode::SEVENBIT}) { - - assert_parse_st(context, mode, "#0;1:2;#:#;1;3:#;:;;"sv, ItemList{}); - } -} - -static void -test_parser_seq_params_clear(void) -{ - /* Check that parameters are cleared from the last sequence */ - - auto context = SimpleContext{}; - - for (auto mode : {Mode::UTF8, Mode::EIGHTBIT, Mode::SEVENBIT}) { - auto items = ItemList{Sequence{Command::DECGCI, {0, 1, 2, 3, 4, 5, 6, 7}}, - Sequence{Command::DECGRI, {5, 3}}, - Sequence{Command::DECGNL}}; - assert_parse_st(context, mode, items, items); - - auto parsed_items = context.parsed_items(); - - /* Verify that non-specified paramaters have default value */ - auto& item1 = std::get<Sequence>(parsed_items[1]); - for (auto n = 2; n < 8; ++n) - g_assert_cmpint(item1.param(n), ==, -1); - - - auto& item2 = std::get<Sequence>(parsed_items[2]); - for (auto n = 0; n < 8; ++n) - g_assert_cmpint(item2.param(n), ==, -1); - } -} - -static void -test_parser_seq_params_max(void) -{ - /* Check that an excessive number of parameters causes the - * sequence to be ignored. - */ - - auto context = SimpleContext{}; - - auto items = ItemList{Sequence{Command::DECGRA, {0, 1, 2, 3, 4, 5, 6, 7}}}; - auto str = ItemStringifier{items, Mode::SEVENBIT}.string(); - - /* The sequence with VTE_SIXEL_PARSER_ARG_MAX args must be parsed */ - assert_parse_st(context, Mode::UTF8, str, items); - - /* Now test that adding one more parameter (whether with an - * explicit value, or default), causes the sequence to be ignored. - */ - assert_parse_st(context, Mode::UTF8, str + ";8"s, ItemList{}); - assert_parse_st(context, Mode::UTF8, str + ";"s, ItemList{}); -} - -static void -test_parser_seq_glue_arg(void) -{ - /* The sixel Sequence's parameter accessors are copied from the main parser's - * Sequence class, so we don't need to test them here again. - */ -} - -static void -test_parser_st(void) -{ - /* Test that ST is recognised in all forms and from all states, and - * that different-mode C1 ST is not recognised. - */ - - auto context = SimpleContext{}; - - assert_parse(context, Mode::UTF8, "?\x9c\e\\"sv, {Sixel{0}}); - assert_parse(context, Mode::UTF8, "!5\x9c\e\\"sv, {Sequence{Command::DECGRI, {5}}}); - assert_parse(context, Mode::UTF8, "5\x9c\e\\"sv, ItemList{}); - assert_parse(context, Mode::UTF8, "\x9c\xc2\e\\"sv, ItemList{}); - - assert_parse(context, Mode::UTF8, "?\x9c\xc2\x9c"sv, {Sixel{0}}); - assert_parse(context, Mode::UTF8, "!5\x9c\xc2\x9c"sv, {Sequence{Command::DECGRI, {5}}}); - assert_parse(context, Mode::UTF8, "5\x9c\xc2\x9c"sv, ItemList{}); - assert_parse(context, Mode::UTF8, "\x9c\xc2\xc2\x9c"sv, ItemList{}); - - assert_parse(context, Mode::EIGHTBIT, "?\e\\"sv, {Sixel{0}}); - assert_parse(context, Mode::EIGHTBIT, "!5\e\\"sv, {Sequence{Command::DECGRI, {5}}}); - assert_parse(context, Mode::EIGHTBIT, "5\e\\"sv, ItemList{}); - assert_parse(context, Mode::EIGHTBIT, "\xc2\e\\"sv, ItemList{}); - - assert_parse(context, Mode::EIGHTBIT, "?\xc2\x9c"sv, {Sixel{0}}); - assert_parse(context, Mode::EIGHTBIT, "!5\xc2\x9c"sv, {Sequence{Command::DECGRI, {5}}}); - assert_parse(context, Mode::EIGHTBIT, "5\xc2\x9c"sv, ItemList{}); - assert_parse(context, Mode::EIGHTBIT, "\xc2\xc2\x9c"sv, ItemList{}); - - assert_parse(context, Mode::SEVENBIT, "?\xc2\x9c\e\\"sv, {Sixel{0}}); - assert_parse(context, Mode::SEVENBIT, "!5\xc2\x9c\e\\"sv, {Sequence{Command::DECGRI, {5}}}); - assert_parse(context, Mode::SEVENBIT, "5\xc2\x9c\e\\"sv, ItemList{}); - assert_parse(context, Mode::SEVENBIT, "\xc2\x9c\xc2\e\\"sv, ItemList{}); -} - -static constexpr auto -test_string() -{ - return "a#22a#22\xc2z22a22\xc2"sv; -} - -template<class C> -static void -test_parser_insert(C& context, - Mode mode, - std::string_view const& str, - std::string_view const& insert_str, - ParseStatus expected_status = ParseStatus::COMPLETE, - int line = __builtin_LINE()) -{ - for (auto pos = 0u; pos <= str.size(); ++pos) { - auto estr = std::string{str}; - estr.insert(pos, insert_str); - - assert_parse_st(context, mode, estr, -1, - expected_status == ParseStatus::COMPLETE ? size_t(-1) : size_t(pos), - expected_status, StType::C0, line); - - if (expected_status == ParseStatus::COMPLETE) { - auto items = context.parsed_items(); // copy - - assert_parse_st(context, mode, str); - assert(items == context.parsed_items()); - } - } -} - -template<class C> -static void -test_parser_insert(C& context, - std::string_view const& str, - std::string_view const& insert_str, - ParseStatus expected_status = ParseStatus::COMPLETE, - int line = __builtin_LINE()) -{ - for (auto mode : {Mode::UTF8, Mode::EIGHTBIT, Mode::SEVENBIT}) { - test_parser_insert(context, mode, str, insert_str, expected_status, line); - } -} - -static void -test_parser_controls_c0_esc(void) -{ - /* Test that ESC (except C0 ST) always aborts the parsing at the position of the ESC */ - - auto context = SimpleContext{}; - auto const str = test_string(); - - for (auto c = 0x20; c < 0x7f; ++c) { - if (c == 0x5c) /* '\' */ - continue; - - char esc[2] = {0x1b, char(c)}; - test_parser_insert(context, str, {esc, 2}, ParseStatus::ABORT); - } -} - -static void -test_parser_controls_c0_can(void) -{ - /* Test that CAN is handled correctly in all states */ - - auto context = SimpleContext{}; - - for (auto mode : {Mode::UTF8, Mode::EIGHTBIT, Mode::SEVENBIT}) { - - assert_parse_st(context, mode, "@\x18"sv, {Sixel{1}}, -1, 1, ParseStatus::ABORT); - assert_parse_st(context, mode, "!5\x18"sv, {Sequence{Command::DECGRI, {5}}}, -1, 2, ParseStatus::ABORT); - assert_parse_st(context, mode, "5\x18"sv, ItemList{}, -1, 1, ParseStatus::ABORT); - assert_parse_st(context, mode, "\xc2\x18"sv, ItemList{}, -1, 1, ParseStatus::ABORT); - } -} - -static void -test_parser_controls_c0_sub(void) -{ - /* Test that SUB is handled correctly in all states */ - - auto context = SimpleContext{}; - - for (auto mode : {Mode::UTF8, Mode::EIGHTBIT, Mode::SEVENBIT}) { - - assert_parse_st(context, mode, "@\x1a"sv, {Sixel{1}, Sixel{0}}); - - /* The parser chooses to not dispatch the current sequence on SUB; see the - * comment in the Parser class. Otherwise there'd be a - * Sequence{Command::DECGRI, {5}} as the first expected item here. - */ - assert_parse_st(context, mode, "!5\x1a"sv, {Sixel{0}}); - - assert_parse_st(context, mode, "5\x1a"sv, {Sixel{0}}); - assert_parse_st(context, mode, "\xc2\x1a"sv, {Sixel{0}}); - } -} - -static void -test_parser_controls_c0_ignored(void) -{ - /* Test that all C0 controls except ESC, CAN, and SUB, are ignored, - * that is, parsing a string results in the same parsed item when inserting - * the C0 control at any position (except after \xc2 + 0x80..0x9f in UTF-8 mode, - * where the \xc2 + C0 produces an U+FFFD (which is ignored) plus the raw C1 which - * is itself ignored). - */ - - auto context = SimpleContext{}; - auto const str = test_string(); - - for (auto c0 = 0; c0 < 0x20; ++c0) { - if (c0 == 0x18 /* CAN */ || - c0 == 0x1a /* SUB */ || - c0 == 0x1b /* ESC */) - continue; - - char c[1] = {char(c0)}; - test_parser_insert(context, str, {c, 1}); - - assert_parse_st(context, Mode::UTF8, "?\xc2"s + std::string{c, 1} + "\x80@"s, {Sixel{0}, Sixel{1}}); - } -} - -static void -test_parser_controls_del(void) -{ - /* Test that DEL is ignored (except between 0xc2 and 0x80..0x9f in UTF-8 mode) */ - - auto context = SimpleContext{}; - - for (auto mode : {Mode::UTF8, Mode::EIGHTBIT, Mode::SEVENBIT}) { - - assert_parse_st(context, mode, "!2\x7f;3"sv, {Sequence{Command::DECGRI, {2, 3}}}); - assert_parse_st(context, mode, "2\x7f;3"sv, ItemList{}); - } - - assert_parse_st(context, Mode::UTF8, "?\xc2\x7f\x9c", {Sixel{0}}); -} - -static void -test_parser_controls_c1(void) -{ - /* Test that any C1 control aborts the parsing at the insertion position, - * except in 7-bit mode where C1 controls are ignored. - */ - - auto context = SimpleContext{}; - auto const str = test_string(); - for (auto c1 = 0x80; c1 < 0xa0; ++c1) { - if (c1 == 0x9c /* ST */) - continue; - - char c1_utf8[2] = {char(0xc2), char(c1)}; - test_parser_insert(context, Mode::UTF8, str, {c1_utf8, 2}, ParseStatus::ABORT); - test_parser_insert(context, Mode::SEVENBIT, str, {c1_utf8, 2}); - - char c1_raw[1] = {char(c1)}; - test_parser_insert(context, Mode::EIGHTBIT, str, {c1_raw, 2}, ParseStatus::ABORT); - test_parser_insert(context, Mode::SEVENBIT, str, {c1_utf8, 2}); - } -} - -// Context tests - -class TestContext: public Context { -public: - using base_type = Context; - using base_type::base_type; - - auto parse(std::string_view const& str) - { - auto const beginptr = reinterpret_cast<uint8_t const*>(str.data()); - auto const endptr = reinterpret_cast<uint8_t const*>(beginptr + str.size()); - return Context::parse(beginptr, endptr, true); - } - -}; // class TestContext - -template<class C> -static void -parse_image(C& context, - std::string_view const& str, - unsigned fg_red, - unsigned fg_green, - unsigned fg_blue, - unsigned bg_red, - unsigned bg_green, - unsigned bg_blue, - bool private_color_registers = true, - int line = __builtin_LINE()) -{ - context.reset(); - context.prepare(0x50 /* C0 DCS */, - fg_red, fg_green, fg_blue, - bg_red, bg_green, bg_blue, - false /* bg transparent */, - private_color_registers); - - auto str_st = std::string{str}; - str_st.append(ST(StType::C0)); - auto [status, ip] = context.parse(str_st); - g_assert_cmpint(int(status), ==, int(ParseStatus::COMPLETE)); -} - -template<class C> -static void -parse_image(C& context, - ItemList const& items, - unsigned fg_red, - unsigned fg_green, - unsigned fg_blue, - unsigned bg_red, - unsigned bg_green, - unsigned bg_blue, - bool private_color_registers = true, - int line = __builtin_LINE()) -{ - parse_image(context, ItemStringifier(items).string(), - fg_red, fg_green, fg_blue, - bg_red, bg_green, bg_blue, - private_color_registers, - line); -} - -template<class C> -static void -parse_image(C& context, - std::string_view const& str, - int line = __builtin_LINE()) -{ - parse_image(context, str, 0xffu, 0xffu, 0xffu, 0xff8, 0xffu, 0xffu, true, line); -} - -template<class C> -static void -parse_image(C& context, - ItemList const& items, - int line = __builtin_LINE()) -{ - parse_image(context, ItemStringifier{items, Mode::UTF8}.string_view(), line); -} - -template<class C> -static auto -parse_pixels(C& context, - std::string_view const& str, - unsigned extra_width_stride = 0, - int line = __builtin_LINE()) -{ - parse_image(context, str, line); - auto size = size_t{}; - auto ptr = vte::glib::take_free_ptr(context.image_data_indexed(&size, extra_width_stride)); - return std::pair{std::move(ptr), size}; -} - -/* BEGIN */ - -/* The following code is copied from xterm/graphics.c where it is under the - * licence below; and modified and used here under the GNU Lesser General Public - * Licence, version 3 (or, at your option), any later version. - */ - -/* - * Copyright 2013-2019,2020 by Ross Combs - * Copyright 2013-2019,2020 by Thomas E. Dickey - * - * All Rights Reserved - * - * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the - * "Software"), to deal in the Software without restriction, including - * without limitation the rights to use, copy, modify, merge, publish, - * distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to - * the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY - * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, - * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE - * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - * - * Except as contained in this notice, the name(s) of the above copyright - * holders shall not be used in advertising or otherwise to promote the - * sale, use or other dealings in this Software without prior written - * authorization. - */ - -static void -hls2rgb_double(int - h, - int l, - int s, - int* r, - int* g, - int* b) noexcept -{ - const int hs = ((h + 240) / 60) % 6; - const double lv = l / 100.0; - const double sv = s / 100.0; - double c, x, m, c2; - double r1, g1, b1; - - if (s == 0) { - *r = *g = *b = (short) (lv * 255. + 0.5); - return; - } - - c2 = (2.0 * lv) - 1.0; - if (c2 < 0.0) - c2 = -c2; - c = (1.0 - c2) * sv; - x = (hs & 1) ? c : 0.0; - m = lv - 0.5 * c; - - switch (hs) { - case 0: - r1 = c; - g1 = x; - b1 = 0.0; - break; - case 1: - r1 = x; - g1 = c; - b1 = 0.0; - break; - case 2: - r1 = 0.0; - g1 = c; - b1 = x; - break; - case 3: - r1 = 0.0; - g1 = x; - b1 = c; - break; - case 4: - r1 = x; - g1 = 0.0; - b1 = c; - break; - case 5: - r1 = c; - g1 = 0.0; - b1 = x; - break; - default: - *r = (short) 255; - *g = (short) 255; - *b = (short) 255; - return; - } - - *r = (short) ((r1 + m) * 255.0 + 0.5); - *g = (short) ((g1 + m) * 255.0 + 0.5); - *b = (short) ((b1 + m) * 255.0 + 0.5); - - if (*r < 0) - *r = 0; - else if (*r > 255) - *r = 255; - if (*g < 0) - *g = 0; - else if (*g > 255) - *g = 255; - if (*b < 0) - *b = 0; - else if (*b > 255) - *b = 255; -} - -/* This is essentially Context::make_color_hls from sixel-context.cc, - * only changed to return the colour components separately. - */ -static void -hls2rgb_int(int h, - int l, - int s, - int* r, - int* g, - int* b) noexcept -{ - auto const c2p = std::abs(2 * l - 100); - auto const cp = ((100 - c2p) * s) << 1; - auto const hs = ((h + 240) / 60) % 6; - auto const xp = (hs & 1) ? cp : 0; - auto const mp = 200 * l - (cp >> 1); - - int r1p, g1p, b1p; - switch (hs) { - case 0: - r1p = cp; - g1p = xp; - b1p = 0; - break; - case 1: - r1p = xp; - g1p = cp; - b1p = 0; - break; - case 2: - r1p = 0; - g1p = cp; - b1p = xp; - break; - case 3: - r1p = 0; - g1p = xp; - b1p = cp; - break; - case 4: - r1p = xp; - g1p = 0; - b1p = cp; - break; - case 5: - r1p = cp; - g1p = 0; - b1p = xp; - break; - default: - __builtin_unreachable(); - } - - *r = ((r1p + mp) * 255 + 10000) / 20000; - *g = ((g1p + mp) * 255 + 10000) / 20000; - *b = ((b1p + mp) * 255 + 10000) / 20000; -} - -/* END */ - -static void -test_context_color_hls(void) -{ - /* Test that our HLS colour conversion gives the right results - * by comparing it against the xterm/libsixel implementation. - * - * The values may differ by 1, which happen only for (L, S) in - * {(5, 100), (40, 75), (50, 80), (60, 75), (75, 60), (95, 100)}. - * There, one or more of the R, G, B components' unscaled values, - * times 255, produces an exact fraction of .5 in hsl2rgb_double, - * which, plus 0.5,, and due to inexactness, result in the truncated - * value "(short)v" being one less than the result of the integer - * computation. - */ - - for (auto h = 0; h <= 360; ++h) { - for (auto l = 0; l <= 100; ++l) { - for (auto s = 0; s <= 100; ++s) { - int rd, gd, bd, ri, gi, bi; - - hls2rgb_double(h, l, s, &rd, &gd, &bd); - hls2rgb_int(h, l, s, &ri, &gi, &bi); - - g_assert_true((rd == ri || (rd + 1) == ri) && - (gd == gi || (gd + 1) == gi) && - (bd == bi || (bd + 1) == bi)); - } - } - } -} - -template<class C> -static void -assert_image_dimensions(C& context, - unsigned width, - unsigned height, - int line = __builtin_LINE()) -{ - g_assert_cmpuint(context.image_width(), ==, width); - g_assert_cmpuint(context.image_height(), ==, height); -} - -static void -test_context_raster_attributes(void) -{ - /* Test that DECGRA sets the image dimensions */ - - auto context = TestContext{}; - parse_image(context, "\"0;0;64;128"sv); - assert_image_dimensions(context, 64, 128); -} - -static void -test_context_repeat(void) -{ - /* Test that DECGRI repetition works */ - - auto context = TestContext{}; - auto [pixels, size] = parse_pixels(context, "#1!5@"sv); - assert_image_dimensions(context, 5, 1); - - auto data = pixels.get(); - auto const v = *data++; - for (auto x = 1u; x < context.image_width(); ++x) - g_assert_cmpuint(*data++, ==, v); - - g_assert_cmpuint(size_t(data - pixels.get()), <=, size); -} - -static void -test_context_scanlines_grow(void) -{ - /* Test that scanlines grow on demand */ - - auto context = TestContext{}; - parse_image(context, "@$AA$?$??~-~"sv); - assert_image_dimensions(context, 3, 12); -} - -static void -test_context_scanlines_underfull(void) -{ - /* Test that the image height is determined by the last set sixel, not - * necessarily the number of scanlines. - */ - - auto context = TestContext{}; - - parse_image(context, "?"sv); - assert_image_dimensions(context, 1, 0); - - for (auto n = 0; n < 6; ++n) { - parse_image(context, {Sixel(1u << n)}); - assert_image_dimensions(context, 1, n + 1); - - parse_image(context, {Sixel(0), Sixel(0), DECGNL(), Sixel(1u << n)}); - assert_image_dimensions(context, 2, 6 + n + 1); - } -} - -static void -test_context_scanlines_max_width(void) -{ - /* Test that scanlines up to max_width() work, and scanlines longer than that - * are accepted but do not write outside the maximum width. - */ - - auto context = TestContext{}; - - parse_image(context, {Sixel(1u << 0), DECGNL(), DECGRI(context.max_width() - 1), Sixel(0x3f)}); - assert_image_dimensions(context, context.max_width() - 1, 12); - - parse_image(context, {Sixel(1u << 0), DECGNL(), DECGRI(context.max_width()), Sixel(0x3f)}); - assert_image_dimensions(context, context.max_width(), 12); - - parse_image(context, {Sixel(1u << 0), DECGNL(), DECGRI(context.max_width() + 1), Sixel(0x3f)}); - assert_image_dimensions(context, context.max_width(), 12); -} - -static void -test_context_scanlines_max_height(void) -{ - /* Test that scanlines up to max_height() work, and scanlines beyond that - * are accepted but do nothing. - */ - - auto context = TestContext{}; - - auto items = ItemList{}; - for (auto n = 0u; n < (context.max_height() / 6 - 1); ++n) { - if (n > 0) - items.emplace_back(DECGNL()); - items.emplace_back(Sixel(1u << 5)); - } - - parse_image(context, items); - assert_image_dimensions(context, 1, context.max_height() - 6); - - items.emplace_back(DECGNL()); - items.emplace_back(Sixel(1u << 4)); - - parse_image(context, items); - assert_image_dimensions(context, 1, context.max_height() - 1); - - items.emplace_back(DECGCR()); - items.emplace_back(Sixel(1u << 5)); - - parse_image(context, items); - assert_image_dimensions(context, 1, context.max_height()); - - /* Image cannot grow further */ - - items.emplace_back(DECGNL()); - items.emplace_back(Sixel(1u << 0)); - - parse_image(context, items); - assert_image_dimensions(context, 1, context.max_height()); - - items.emplace_back(DECGNL()); - items.emplace_back(Sixel(1u << 5)); - - parse_image(context, items); - assert_image_dimensions(context, 1, context.max_height()); -} - -static void -test_context_image_stride(void) -{ - /* Test that data in the stride padding is set to background */ - - auto context = TestContext{}; - - auto const extra_stride = 3u; - auto [pixels, size] = parse_pixels(context, "#1~~-~~"sv, extra_stride); - assert_image_dimensions(context, 2, 12); - - auto data = pixels.get(); - auto const reg = param_to_color_register(1); - - for (auto y = 0u; y < context.image_height(); ++y) { - for (auto x = 0u; x < context.image_width(); ++x) - g_assert_cmpuint(*data++, ==, unsigned(reg)); - for (auto e = 0u; e < extra_stride; ++e) - g_assert_cmpuint(*data++, ==, 0); - } - - g_assert_cmpuint(size_t(data - pixels.get()), <=, size); -} - -class RGB { -public: - uint8_t r{0}; - uint8_t g{0}; - uint8_t b{0}; - - RGB() = default; - ~RGB() = default; - - RGB(int rv, int gv, int bv) - : r(rv), g(gv), b(bv) - { - } -}; - -static void -test_context_image_palette(void) -{ - /* Test that the colour palette is recognised, and that colour registers - * wrap around. - */ - - auto make_color_rgb = [](unsigned rp, - unsigned gp, - unsigned bp) constexpr noexcept -> auto - { - auto scale = [](unsigned value) constexpr noexcept -> auto - { - return (value * 255u + 50u) / 100u; - }; - - auto make_color = [](unsigned r, - unsigned g, - unsigned b) constexpr noexcept -> Context::color_t - { - if constexpr (std::endian::native == std::endian::little) { - return b | g << 8 | r << 16 | 0xffu << 24 /* opaque */; - } else if constexpr (std::endian::native == std::endian::big) { - return 0xffu /* opaque */ | r << 8 | g << 16 | b << 24; - } else { - __builtin_unreachable(); - } - }; - - return make_color(scale(rp), scale(gp), scale(bp)); - }; - - auto context = TestContext{}; - - std::array<RGB, context.num_colors()> palette; - for (auto& p : palette) { - p = RGB(g_test_rand_int_range(0, 100), - g_test_rand_int_range(0, 100), - g_test_rand_int_range(0, 100)); - } - - auto items = ItemList{}; - auto reg = context.num_colors(); - for (auto const& p : palette) { - items.emplace_back(DECGCI_RGB(reg++, p.r, p.g, p.b)); - } - - parse_image(context, items); - - for (auto n = 0; n < context.num_colors(); ++n) { - g_assert_cmpuint(make_color_rgb(palette[n].r, palette[n].g, palette[n].b), - ==, - context.color(param_to_color_register(n))); - } -} - -static void -test_context_image_compositing(void) -{ - /* Test that multiple sixels in different colours are composited. */ - - auto context = TestContext{}; - - auto [pixels, size] = parse_pixels(context, - "#256!24F$#257!24w-#258!24F$#259!24w-#260!24F$#261!24w"sv); - - auto data = pixels.get(); - for (auto y = 0u; y < context.image_height(); ++y) { - auto const reg = param_to_color_register((256 + y / 3)); - for (auto x = 0u; x < context.image_width(); ++x) - g_assert_cmpuint(*data++, ==, reg); - } - - - g_assert_cmpuint(size_t(data - pixels.get()), <=, size); -} - -// Main - -int -main(int argc, - char* argv[]) -{ - g_test_init(&argc, &argv, nullptr); - - g_test_add_func("/vte/sixel/parser/sequences/parameters", test_parser_seq_params); - g_test_add_func("/vte/sixel/parser/sequences/subparameters", test_parser_seq_subparams); - g_test_add_func("/vte/sixel/parser/sequences/parameters-clear", test_parser_seq_params_clear); - g_test_add_func("/vte/sixel/parser/sequences/parameters-max", test_parser_seq_params_max); - g_test_add_func("/vte/sixel/parser/sequences/glue/arg", test_parser_seq_glue_arg); - g_test_add_func("/vte/sixel/parser/st", test_parser_st); - g_test_add_func("/vte/sixel/parser/controls/c0/escape", test_parser_controls_c0_esc); - g_test_add_func("/vte/sixel/parser/controls/c0/can", test_parser_controls_c0_can); - g_test_add_func("/vte/sixel/parser/controls/c0/sub", test_parser_controls_c0_sub); - g_test_add_func("/vte/sixel/parser/controls/c0/ignored", test_parser_controls_c0_ignored); - g_test_add_func("/vte/sixel/parser/controls/del", test_parser_controls_del); - g_test_add_func("/vte/sixel/parser/controls/c1", test_parser_controls_c1); - g_test_add_func("/vte/sixel/context/color/hls", test_context_color_hls); - g_test_add_func("/vte/sixel/context/raster-attributes", test_context_raster_attributes); - g_test_add_func("/vte/sixel/context/repeat", test_context_repeat); - g_test_add_func("/vte/sixel/context/scanlines/grow", test_context_scanlines_grow); - g_test_add_func("/vte/sixel/context/scanlines/underfull", test_context_scanlines_underfull); - g_test_add_func("/vte/sixel/context/scanlines/max-width", test_context_scanlines_max_width); - g_test_add_func("/vte/sixel/context/scanlines/max-height", test_context_scanlines_max_height); - g_test_add_func("/vte/sixel/context/image/stride", test_context_image_stride); - g_test_add_func("/vte/sixel/context/image/palette", test_context_image_palette); - g_test_add_func("/vte/sixel/context/image/compositing", test_context_image_compositing); - - return g_test_run(); -} @@ -3132,43 +3132,6 @@ not_inserted: m_line_wrapped = line_wrapped; } -#ifdef WITH_SIXEL - -void -Terminal::insert_image(ProcessingContext& context, - vte::Freeable<cairo_surface_t> image_surface) /* throws */ -{ - if (!image_surface) - return; - - auto const image_width_px = cairo_image_surface_get_width(image_surface.get()); - auto const image_height_px = cairo_image_surface_get_height(image_surface.get()); - - /* Calculate geometry */ - - auto const left = m_screen->cursor.col; - auto const top = m_screen->cursor.row; - auto const width = (image_width_px + m_cell_width_unscaled - 1) / m_cell_width_unscaled; - auto const height = (image_height_px + m_cell_height_unscaled - 1) / m_cell_height_unscaled; - - m_screen->row_data->append_image(std::move(image_surface), - image_width_px, - image_height_px, - left, - top, - m_cell_width_unscaled, - m_cell_height_unscaled); - - /* Erase characters under the image. Since this inserts content, we need - * to update the processing context's bbox. - */ - context.pre_GRAPHIC(*this); - erase_image_rect(height, width); - context.post_GRAPHIC(*this); -} - -#endif /* WITH_SIXEL */ - guint8 Terminal::get_bidi_flags() const noexcept { @@ -3532,12 +3495,6 @@ Terminal::process_incoming() break; #endif -#ifdef WITH_SIXEL - case DataSyntax::DECSIXEL: - process_incoming_decsixel(context, *chunk); - break; -#endif - default: g_assert_not_reached(); break; @@ -3741,7 +3698,10 @@ Terminal::process_incoming_utf8(ProcessingContext& context, } } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-label" switched_data_syntax: +#pragma GCC diagnostic pop // Update start for data consumed chunk.set_begin_reading(ip); @@ -3866,7 +3826,10 @@ Terminal::process_incoming_pcterm(ProcessingContext& context, return; } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-label" switched_data_syntax: +#pragma GCC diagnostic pop // Update start for data consumed chunk.set_begin_reading(ip); @@ -3880,44 +3843,6 @@ Terminal::process_incoming_pcterm(ProcessingContext& context, #endif /* WITH_ICU */ -#ifdef WITH_SIXEL - -void -Terminal::process_incoming_decsixel(ProcessingContext& context, - vte::base::Chunk& chunk) -{ - auto const [status, ip] = m_sixel_context->parse(chunk.begin_reading(), - chunk.end_reading(), - chunk.eos()); - - // Update start for data consumed - chunk.set_begin_reading(ip); - - switch (status) { - case vte::sixel::Parser::ParseStatus::CONTINUE: - break; - - case vte::sixel::Parser::ParseStatus::COMPLETE: - /* Like the main parser, the sequence only takes effect - * if introducer and terminator match (both C0 or both C1). - */ - if (m_sixel_context->is_matching_controls()) { - try { - insert_image(context, m_sixel_context->image_cairo()); - } catch (...) { - } - } - - [[fallthrough]]; - case vte::sixel::Parser::ParseStatus::ABORT: - m_sixel_context->reset(); - pop_data_syntax(); - break; - } -} - -#endif /* WITH_SIXEL */ - bool Terminal::pty_io_read(int const fd, GIOCondition const condition) @@ -7751,12 +7676,6 @@ Terminal::Terminal(vte::platform::Widget* w, for (auto i = 0; i < VTE_PALETTE_SIZE; i++) m_palette[i].sources[VTE_COLOR_SOURCE_ESCAPE].is_set = FALSE; - /* Dispatch unripe DCS (for now, just DECSIXEL) sequences, - * so we can switch data syntax and parse the contents with - * the SIXEL subparser. - */ - m_parser.set_dispatch_unripe(true); - /* Set up I/O encodings. */ m_outgoing = _vte_byte_array_new(); @@ -8238,11 +8157,7 @@ Terminal::draw_cells(vte::view::DrawingContext::TextRequest* items, else rgb_from_index<4, 5, 4>(deco, dc); -#ifndef WITH_SIXEL if (clear && (draw_default_bg || back != VTE_DEFAULT_BG)) { -#else - { -#endif /* Paint the background. */ i = 0; while (i < n) { @@ -8261,25 +8176,11 @@ Terminal::draw_cells(vte::view::DrawingContext::TextRequest* items, } } -#ifdef WITH_SIXEL - if (back == VTE_DEFAULT_BG) { - /* Clear cells in order to properly overdraw images */ - m_draw.clear(xl, - y, - xr - xl, row_height, - get_color(VTE_DEFAULT_BG), m_background_alpha); - } - - if (clear && (draw_default_bg || back != VTE_DEFAULT_BG)) { -#else - { -#endif - m_draw.fill_rectangle( - xl, - y, - xr - xl, row_height, - &bg, VTE_DRAW_OPAQUE); - } + m_draw.fill_rectangle( + xl, + y, + xr - xl, row_height, + &bg, VTE_DRAW_OPAQUE); } } @@ -8889,14 +8790,12 @@ Terminal::draw_rows(VteScreen *screen_, nhilite = (nhyperlink && cell->attr.hyperlink_idx == m_hyperlink_hover_idx) || (!nhyperlink && regex_match_has_current() && m_match_span.contains(row, lcol)); if (cell->c == 0 || -#ifndef WITH_SIXEL ((cell->c == ' ' || cell->c == '\t') && // FIXME '\t' is newly added now, double check cell->attr.has_none(VTE_ATTR_UNDERLINE_MASK | VTE_ATTR_STRIKETHROUGH_MASK | VTE_ATTR_OVERLINE_MASK) && !nhyperlink && !nhilite) || -#endif cell->attr.fragment() || cell->attr.invisible()) { /* Skip empty or fragment cell, but erase on ' ' and '\t', since @@ -9256,9 +9155,6 @@ Terminal::widget_draw(cairo_t *cr) int allocated_width, allocated_height; int extra_area_for_cursor; bool text_blink_enabled_now; -#ifdef WITH_SIXEL - VteRing *ring = m_screen->row_data; -#endif gint64 now = 0; if (!gdk_cairo_get_clip_rectangle (cr, &clip_rect)) @@ -9297,33 +9193,6 @@ Terminal::widget_draw(cairo_t *cr) /* Transform to view coordinates */ cairo_region_translate(region, -m_padding.left, -m_padding.top); -#ifdef WITH_SIXEL - /* Draw images */ - if (m_images_enabled) { - vte::grid::row_t top_row = first_displayed_row(); - vte::grid::row_t bottom_row = last_displayed_row(); - auto const& image_map = ring->image_map(); - auto const image_map_end = image_map.end(); - for (auto it = image_map.begin(); it != image_map_end; ++it) { - auto const& image = it->second; - - if (image->get_bottom() < top_row || - image->get_top() > bottom_row) - continue; - - auto const x = image->get_left () * m_cell_width; - auto const y = (image->get_top () - m_screen->scroll_delta) * m_cell_height; - - /* Clear cell extent; image may be slightly smaller */ - m_draw.clear(x, y, image->get_width() * m_cell_width, - image->get_height() * m_cell_height, - get_color(VTE_DEFAULT_BG), m_background_alpha); - - image->paint(cr, x, y, m_cell_width, m_cell_height); - } - } -#endif /* WITH_SIXEL */ - /* Whether blinking text should be visible now */ m_text_blink_state = true; text_blink_enabled_now = (unsigned)m_text_blink_mode & (unsigned)(m_has_focus ? TextBlinkMode::eFOCUSED : TextBlinkMode::eUNFOCUSED); @@ -9890,12 +9759,6 @@ Terminal::reset_data_syntax() return; switch (current_data_syntax()) { -#ifdef WITH_SIXEL - case DataSyntax::DECSIXEL: - m_sixel_context->reset(); - break; -#endif - default: break; } @@ -9903,15 +9766,6 @@ Terminal::reset_data_syntax() pop_data_syntax(); } -void -Terminal::reset_graphics_color_registers() -{ -#ifdef WITH_SIXEL - if (m_sixel_context) - m_sixel_context->reset_colors(); -#endif -} - /* * Terminal::reset: * @clear_tabstops: whether to reset tabstops @@ -10016,11 +9870,6 @@ Terminal::reset(bool clear_tabstops, /* Clear modifiers. */ m_modifiers = 0; -#ifdef WITH_SIXEL - if (m_sixel_context) - m_sixel_context->reset_colors(); -#endif - /* Reset the saved cursor. */ save_cursor(&m_normal_screen); save_cursor(&m_alternate_screen); @@ -10031,12 +9880,6 @@ Terminal::reset(bool clear_tabstops, /* Reset XTerm window controls */ m_xterm_wm_iconified = false; - - /* When not using private colour registers, we should - * clear (assign to black) all SIXEL colour registers. - * (DEC PPLV2 § 5.8) - */ - reset_graphics_color_registers(); } void diff --git a/src/vtedefines.hh b/src/vtedefines.hh index b1720735..c8ecb1a8 100644 --- a/src/vtedefines.hh +++ b/src/vtedefines.hh @@ -142,9 +142,3 @@ #define VTE_VERSION_NUMERIC ((VTE_MAJOR_VERSION) * 10000 + (VTE_MINOR_VERSION) * 100 + (VTE_MICRO_VERSION)) #define VTE_TERMINFO_NAME "xterm-256color" - -#define VTE_SIXEL_ENABLED_DEFAULT false - -#define VTE_SIXEL_MAX_WIDTH (2048) -#define VTE_SIXEL_MAX_HEIGHT (2052) -#define VTE_SIXEL_NUM_COLOR_REGISTERS (1024) diff --git a/src/vtegtk.cc b/src/vtegtk.cc index a1be90df..090975b2 100644 --- a/src/vtegtk.cc +++ b/src/vtegtk.cc @@ -1844,11 +1844,7 @@ vte_terminal_class_init(VteTerminalClass *klass) */ pspecs[PROP_ENABLE_SIXEL] = g_param_spec_boolean ("enable-sixel", nullptr, nullptr, -#ifdef WITH_SIXEL - VTE_SIXEL_ENABLED_DEFAULT, -#else false, -#endif (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY)); @@ -2161,12 +2157,6 @@ vte_get_features (void) noexcept #else "-ICU" #endif - " " -#ifdef WITH_SIXEL - "+SIXEL" -#else - "-SIXEL" -#endif #ifdef __linux__ " " #ifdef WITH_SYSTEMD @@ -2197,9 +2187,6 @@ vte_get_feature_flags(void) noexcept #ifdef WITH_ICU VTE_FEATURE_FLAG_ICU | #endif -#ifdef WITH_SIXEL - VTE_FEATURE_FLAG_SIXEL | -#endif #ifdef __linux__ #ifdef WITH_SYSTEMD VTE_FEATURE_FLAG_SYSTEMD | @@ -5778,12 +5765,7 @@ vte_terminal_set_enable_sixel(VteTerminal *terminal, gboolean enabled) noexcept try { -#ifdef WITH_SIXEL g_return_if_fail(VTE_IS_TERMINAL(terminal)); - - if (WIDGET(terminal)->set_sixel_enabled(enabled != FALSE)) - g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_ENABLE_SIXEL]); -#endif } catch (...) { @@ -5794,7 +5776,7 @@ catch (...) * vte_terminal_get_enable_sixel: * @terminal: a #VteTerminal * - * Returns: %TRUE if SIXEL image support is enabled, %FALSE otherwise + * Returns: %FALSE * * Since: 0.62 */ @@ -5802,13 +5784,7 @@ gboolean vte_terminal_get_enable_sixel(VteTerminal *terminal) noexcept try { -#ifdef WITH_SIXEL - g_return_val_if_fail(VTE_IS_TERMINAL(terminal), FALSE); - - return WIDGET(terminal)->sixel_enabled(); -#else return false; -#endif } catch (...) { diff --git a/src/vteinternal.hh b/src/vteinternal.hh index 41766c0e..7a257afe 100644 --- a/src/vteinternal.hh +++ b/src/vteinternal.hh @@ -67,10 +67,6 @@ #include "icu-converter.hh" #endif -#ifdef WITH_SIXEL -#include "sixel-context.hh" -#endif - enum { VTE_BIDI_FLAG_IMPLICIT = 1 << 0, VTE_BIDI_FLAG_RTL = 1 << 1, @@ -293,9 +289,6 @@ public: /* ECMA48_ECMA35, not supported */ /* The following can never be primary data syntax: */ -#ifdef WITH_SIXEL - DECSIXEL, -#endif }; DataSyntax m_primary_data_syntax{DataSyntax::ECMA48_UTF8}; @@ -354,10 +347,6 @@ public: } } -#ifdef WITH_SIXEL - std::unique_ptr<vte::sixel::Context> m_sixel_context{}; -#endif - /* Screen data. We support the normal screen, and an alternate * screen, which seems to be a DEC-specific feature. */ VteScreen m_normal_screen; @@ -466,21 +455,6 @@ public: this), "mouse-autoscroll-timer"}; - /* Inline images */ - bool m_sixel_enabled{VTE_SIXEL_ENABLED_DEFAULT}; - bool m_images_enabled{VTE_SIXEL_ENABLED_DEFAULT}; - - bool set_sixel_enabled(bool enabled) noexcept - { - auto const changed = m_sixel_enabled != enabled; - m_sixel_enabled = m_images_enabled = enabled; - if (changed) - invalidate_all(); - return changed; - } - - constexpr bool sixel_enabled() const noexcept { return m_sixel_enabled; } - /* State variables for handling match checks. */ int m_match_regex_next_tag{0}; auto regex_match_next_tag() noexcept { return m_match_regex_next_tag++; } @@ -755,11 +729,6 @@ public: bool insert, bool invalidate_now); - #ifdef WITH_SIXEL - void insert_image(ProcessingContext& context, - vte::Freeable<cairo_surface_t> image_surface) /* throws */; - #endif - void invalidate_row(vte::grid::row_t row); void invalidate_rows(vte::grid::row_t row_start, vte::grid::row_t row_end /* inclusive */); @@ -785,10 +754,6 @@ public: void process_incoming_pcterm(ProcessingContext& context, vte::base::Chunk& chunk); #endif - #ifdef WITH_SIXEL - void process_incoming_decsixel(ProcessingContext& context, - vte::base::Chunk& chunk); - #endif bool process(bool emit_adj_changed); inline bool is_processing() const { return m_active_terminals_link != nullptr; } void start_processing(); @@ -958,7 +923,6 @@ public: void im_reset(); void im_update_cursor(); - void reset_graphics_color_registers(); void reset(bool clear_tabstops, bool clear_history, bool from_api = false); @@ -1324,8 +1288,6 @@ public: inline void move_cursor_down(vte::grid::row_t rows); inline void erase_characters(long count, bool use_basic = false); - void erase_image_rect(vte::grid::row_t rows, - vte::grid::column_t columns); inline void insert_blank_character(); template<unsigned int redbits, unsigned int greenbits, unsigned int bluebits> diff --git a/src/vteseq.cc b/src/vteseq.cc index 1b76ff22..bde2fa7b 100644 --- a/src/vteseq.cc +++ b/src/vteseq.cc @@ -983,33 +983,6 @@ Terminal::erase_characters(long count, m_text_deleted_flag = TRUE; } -void -Terminal::erase_image_rect(vte::grid::row_t rows, - vte::grid::column_t columns) -{ - auto const top = m_screen->cursor.row; - - /* FIXMEchpe: simplify! */ - for (auto i = 0; i < rows; ++i) { - auto const row = top + i; - - erase_characters(columns, true); - - if (row > m_screen->insert_delta - 1 && - row < m_screen->insert_delta + m_row_count) - set_hard_wrapped(row); - - if (i == rows - 1) { - if (m_modes_private.MINTTY_SIXEL_SCROLL_CURSOR_RIGHT()) - move_cursor_forward(columns); - else - cursor_down(true); - } else { - cursor_down(true); - } - } -} - /* Insert a blank character. */ void Terminal::insert_blank_character() @@ -2404,9 +2377,6 @@ Terminal::DA1(vte::parser::Sequence const& seq) return; reply(seq, VTE_REPLY_DECDA1R, {65, 1, -#ifdef WITH_SIXEL - m_sixel_enabled ? 4 : -2 /* skip */, -#endif 9}); } @@ -4117,8 +4087,6 @@ Terminal::DECSCL(vte::parser::Sequence const& seq) break; } #endif - - reset_graphics_color_registers(); } void @@ -4363,7 +4331,7 @@ Terminal::DECSGR(vte::parser::Sequence const& seq) /* TODO: consider implementing sub/superscript? */ } -bool +void Terminal::DECSIXEL(vte::parser::Sequence const& seq) { /* @@ -4391,116 +4359,6 @@ Terminal::DECSIXEL(vte::parser::Sequence const& seq) * References: VT330 * DEC PPLV2 § 5.4 */ - -#ifdef WITH_SIXEL - auto process_sixel = false; - auto mode = vte::sixel::Parser::Mode{}; - if (m_sixel_enabled) { - switch (primary_data_syntax()) { - case DataSyntax::ECMA48_UTF8: - process_sixel = true; - mode = vte::sixel::Parser::Mode::UTF8; - break; - -#ifdef WITH_ICU - case DataSyntax::ECMA48_PCTERM: - /* It's not really clear how DECSIXEL should be processed in PCTERM mode. - * The DEC documentation available isn't very detailed on PCTERM mode, - * and doesn't appear to mention its interaction with DECSIXEL at all. - * - * Since (afaik) a "real" DEC PCTERM mode only (?) translates the graphic - * characters, not the whole data stream, as we do, let's assume that - * DECSIXEL content should be processed as raw bytes, i.e. without any - * translation. - * Also, since C1 controls don't exist in PCTERM mode, let's process - * DECSIXEL in 7-bit mode. - * - * As an added complication, we can only switch data syntaxes if - * the data stream is exact, that is the charset converter has - * not consumed more data than we have currently read output bytes - * from it. So we need to check that the converter has no pending - * characters. - * - * Alternatively, we could just refuse to process DECSIXEL in - * PCTERM mode. - */ - process_sixel = !m_converter->decoder().pending(); - mode = vte::sixel::Parser::Mode::SEVENBIT; - break; -#endif /* WITH_ICU */ - - default: - __builtin_unreachable(); - process_sixel = false; - } - } - - /* How to interpret args[1] is not entirely clear from the DEC - * documentation and other terminal emulators. - * We choose to make args[1]==1 mean to use transparent background. - * and treat all other values (default, 0, 2) as using the current - * SGR background colour. See the discussion in issue #253. - * - * Also use the current SGR foreground colour to initialise - * the special colour register so that SIXEL images which set - * no colours get a sensible default. - */ - auto transparent_bg = bool{}; - switch (seq.collect1(1, 2)) { - case -1: /* default */ - case 0: - case 2: - transparent_bg = false; - break; - - case 1: - transparent_bg = true; - break; - - case 5: /* OR mode (a nonstandard NetBSD/x68k extension; not supported */ - process_sixel = false; - break; - - default: - transparent_bg = false; - break; - } - - /* Ignore the whole sequence */ - if (!process_sixel || seq.is_ripe() /* that shouldn't happen */) { - m_parser.ignore_until_st(); - return false; - } - - auto fore = unsigned{}, back = unsigned{}; - auto fg = vte::color::rgb{}, bg = vte::color::rgb{}; - resolve_normal_colors(&m_defaults, &fore, &back, fg, bg); - - try { - if (!m_sixel_context) - m_sixel_context = std::make_unique<vte::sixel::Context>(); - - m_sixel_context->prepare(seq.introducer(), - fg.red >> 8, fg.green >> 8, fg.blue >> 8, - bg.red >> 8, bg.green >> 8, bg.blue >> 8, - back == VTE_DEFAULT_BG || transparent_bg, - m_modes_private.XTERM_SIXEL_PRIVATE_COLOR_REGISTERS()); - - m_sixel_context->set_mode(mode); - - /* We need to reset the main parser, so that when it is in the ground state - * when processing returns to the primary data syntax from DECSIXEL - */ - m_parser.reset(); - push_data_syntax(DataSyntax::DECSIXEL); - - return true; /* switching data syntax */ - } catch (...) { - } -#endif /* WITH_SIXEL */ - - m_parser.ignore_until_st(); - return false; } void @@ -8887,84 +8745,7 @@ Terminal::XTERM_SMGRAPHICS(vte::parser::Sequence const& seq) */ auto const attr = seq.collect1(0); - auto status = 3, rv0 = -2, rv1 = -2; - - switch (attr) { -#ifdef WITH_SIXEL - case 0: /* Colour registers. - * - * VTE doesn't support changing the number of colour registers, so always - * return the fixed number, and set() returns success iff the passed number - * was less or equal that number. - */ - switch (seq.collect1(1)) { - case 1: /* read */ - case 2: /* reset */ - case 4: /* read maximum */ - status = 0; - rv0 = VTE_SIXEL_NUM_COLOR_REGISTERS; - break; - case 3: /* set */ - status = (seq.collect1(2) <= VTE_SIXEL_NUM_COLOR_REGISTERS) ? 0 : 2; - rv0 = VTE_SIXEL_NUM_COLOR_REGISTERS; - break; - case -1: /* no default */ - default: - status = 2; - break; - } - break; - - case 1: /* SIXEL graphics geometry. - * - * VTE doesn't support variable geometries; always report - * the maximum size of a SIXEL graphic, and set() returns success iff the - * passed numbers are less or equal to that number. - */ - switch (seq.collect1(1)) { - case 1: /* read */ - case 2: /* reset */ - case 4: /* read maximum */ - status = 0; - rv0 = VTE_SIXEL_MAX_WIDTH; - rv1 = VTE_SIXEL_MAX_HEIGHT; - break; - - case 3: /* set */ { - auto w = int{}, h = int{}; - if (seq.collect(2, {&w, &h}) && - w > 0 && w <= VTE_SIXEL_MAX_WIDTH && - h > 0 && h <= VTE_SIXEL_MAX_HEIGHT) { - rv0 = VTE_SIXEL_MAX_WIDTH; - rv1 = VTE_SIXEL_MAX_HEIGHT; - status = 0; - } else { - status = 3; - } - - break; - } - - case -1: /* no default */ - default: - status = 2; - break; - } - break; - -#endif /* WITH_SIXEL */ - -#if 0 /* ifdef WITH_REGIS */ - case 2: - status = 1; - break; -#endif - - case -1: /* no default value */ - default: - status = 1; - break; - } + auto status = 1, rv0 = -2, rv1 = -2; reply(seq, VTE_REPLY_XTERM_SMGRAPHICS_REPORT, {attr, status, rv0, rv1}); } diff --git a/src/widget.hh b/src/widget.hh index ede09be3..7ee94dcd 100644 --- a/src/widget.hh +++ b/src/widget.hh @@ -369,9 +369,6 @@ public: bool should_emit_signal(int id) noexcept; - bool set_sixel_enabled(bool enabled) noexcept { return m_terminal->set_sixel_enabled(enabled); } - bool sixel_enabled() const noexcept { return m_terminal->sixel_enabled(); } - protected: enum class CursorType { |