[vte] lib: Replace SIXEL handling



commit e0ff2c71b71f45c50d8d704f411f2d2f5c18e469
Author: Christian Persch <chpe src gnome org>
Date:   Mon Oct 19 00:16:36 2020 +0200

    lib: Replace SIXEL handling
    
    Remove the old sixel parser and context, and move over to the new parser
    and context.
    
    https://gitlab.gnome.org/GNOME/vte/-/issues/253

 src/icu-decoder.hh   |  15 ++
 src/image.hh         |   2 +-
 src/meson.build      |   2 -
 src/parser-seq.py    |   2 +-
 src/parser-string.hh |   9 -
 src/ring.cc          |  16 +-
 src/ring.hh          |   5 +-
 src/sixelparser.cc   | 713 ---------------------------------------------------
 src/sixelparser.hh   |  75 ------
 src/vte.cc           | 149 +++++++++--
 src/vteinternal.hh   |  35 ++-
 src/vteseq.cc        | 207 ++++++++-------
 12 files changed, 307 insertions(+), 923 deletions(-)
---
diff --git a/src/icu-decoder.hh b/src/icu-decoder.hh
index 8c4eada1..094e9400 100644
--- a/src/icu-decoder.hh
+++ b/src/icu-decoder.hh
@@ -68,6 +68,21 @@ public:
 
         void reset() noexcept;
 
+        /*
+         * pending():
+         *
+         * Returns whether the converter has output pending (i.e. will produce more
+         * output without consuming more input.
+         */
+        constexpr auto pending() const noexcept
+        {
+                /* Due to the way we drive the ICU converter and the way ICU converters work
+                 * by first writing out any internally buffered output before consuming more
+                 * input, this should be a safe guess about whether there is pending output.
+                 */
+                return m_index + 1 < m_available;
+        }
+
 private:
         enum class State {
                 eInput  = 0,
diff --git a/src/image.hh b/src/image.hh
index fa972c9b..9537bbca 100644
--- a/src/image.hh
+++ b/src/image.hh
@@ -46,7 +46,7 @@ private:
         int m_cell_height;
 
 public:
-        Image(vte::cairo::Surface&& surface,
+        Image(vte::cairo::Surface surface,
               int priority,
               int width_pixels,
               int height_pixels,
diff --git a/src/meson.build b/src/meson.build
index 1b6e8b27..248053c9 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -111,8 +111,6 @@ sixel_context_sources = files(
 sixel_sources = sixel_parser_sources + sixel_context_sources + files(
   'image.cc',
   'image.hh',
-  'sixelparser.cc',
-  'sixelparser.hh',
 )
 
 systemd_sources = files(
diff --git a/src/parser-seq.py b/src/parser-seq.py
index 7b2c6ebe..90a68246 100755
--- a/src/parser-seq.py
+++ b/src/parser-seq.py
@@ -865,7 +865,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',
+    seq_DCS('DECSIXEL', 'q', flags=Flags.UNRIPE | Flags.HANDLER_RV,
             comment='SIXEL graphics'),
     seq_DCS('DECRQSS', 'q', intermediates=(Intermediate.CASH,),
             comment='request selection or setting'),
diff --git a/src/parser-string.hh b/src/parser-string.hh
index 746051e4..45472fb2 100644
--- a/src/parser-string.hh
+++ b/src/parser-string.hh
@@ -35,16 +35,7 @@ typedef struct vte_seq_string_t {
 
 #define VTE_SEQ_STRING_DEFAULT_CAPACITY (1 << 7) /* must be power of two */
 
-#ifdef WITH_SIXEL
-/* This needs to be somewhat large for the time being; it accommodates inline
- * graphics formats (e.g. sixels), and there is no provision for parsing
- * those incrementally yet. 8M characters is typically enough for an
- * RLE-incompressible 256-color 1920x1080 image. Since VTE seqs store 32 bits
- * per character, this corresponds to a 32MiB buffer. */
-#define VTE_SEQ_STRING_MAX_CAPACITY     (1 << 23)
-#else
 #define VTE_SEQ_STRING_MAX_CAPACITY     (1 << 12)
-#endif
 
 /*
  * vte_seq_string_init:
diff --git a/src/ring.cc b/src/ring.cc
index ae86d23e..be639f90 100644
--- a/src/ring.cc
+++ b/src/ring.cc
@@ -1677,15 +1677,21 @@ Ring::write_contents(GOutputStream* stream,
  * Append an image to the internal image list.
  */
 void
-Ring::append_image (cairo_surface_t *surface, gint pixelwidth, gint pixelheight, glong left, glong top, 
glong cell_width, glong cell_height)
+Ring::append_image(vte::cairo::Surface surface,
+                   int pixelwidth,
+                   int pixelheight,
+                   long left,
+                   long top,
+                   long cell_width,
+                   long cell_height) /* throws */
 {
         Image *image;
 
-        image = new (std::nothrow) Image (vte::cairo::Surface(surface),
-                                          m_next_image_priority++,
+        image = new (std::nothrow) Image(std::move(surface),
+                                         m_next_image_priority++,
                                           pixelwidth, pixelheight,
-                                          left, top,
-                                          cell_width, cell_height);
+                                         left, top,
+                                         cell_width, cell_height);
         if (!image)
                 return;
 
diff --git a/src/ring.hh b/src/ring.hh
index 85fd7236..d84f75eb 100644
--- a/src/ring.hh
+++ b/src/ring.hh
@@ -29,6 +29,7 @@
 #include "vtestream.h"
 
 #ifdef WITH_SIXEL
+#include "cairo-glue.hh"
 #include "image.hh"
 #include <map>
 #endif
@@ -104,10 +105,10 @@ public:
                             GError** error);
 
 #ifdef WITH_SIXEL
-        void append_image (cairo_surface_t *surface,
+        void append_image (vte::cairo::Surface surface,
                            gint pixelwidth, gint pixelheight,
                            glong left, glong top,
-                           glong cell_width, glong cell_height);
+                           glong cell_width, glong cell_height) /* throws */;
         std::map<gint, vte::image::Image *> *m_image_by_top_map;
         std::map<int, vte::image::Image *> *m_image_priority_map;
 #endif
diff --git a/src/vte.cc b/src/vte.cc
index adc28c98..496324bf 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -2020,13 +2020,13 @@ Terminal::set_encoding(char const* charset,
          */
 
         if (to_utf8) {
-                if (primary_data_syntax() == DataSyntax::eECMA48_UTF8)
+                if (primary_data_syntax() == DataSyntax::ECMA48_UTF8)
                         return true;
 
                 m_converter.reset();
-                m_primary_data_syntax = DataSyntax::eECMA48_UTF8;
+                m_primary_data_syntax = DataSyntax::ECMA48_UTF8;
         } else {
-                if (primary_data_syntax() == DataSyntax::eECMA48_PCTERM &&
+                if (primary_data_syntax() == DataSyntax::ECMA48_PCTERM &&
                     m_converter->charset() == charset)
                         return true;
 
@@ -2036,7 +2036,7 @@ Terminal::set_encoding(char const* charset,
                                return false;
 
                         m_converter = std::move(converter);
-                        m_primary_data_syntax = DataSyntax::eECMA48_PCTERM;
+                        m_primary_data_syntax = DataSyntax::ECMA48_PCTERM;
 
                 } catch (...) {
                         return vte::glib::set_error_from_exception(error);
@@ -2055,7 +2055,7 @@ Terminal::set_encoding(char const* charset,
         reset_decoder();
 
         if (pty())
-                pty()->set_utf8(primary_data_syntax() == DataSyntax::eECMA48_UTF8);
+                pty()->set_utf8(primary_data_syntax() == DataSyntax::ECMA48_UTF8);
 
        _vte_debug_print(VTE_DEBUG_IO,
                          "Set terminal encoding to `%s'.\n",
@@ -3018,6 +3018,56 @@ not_inserted:
         m_line_wrapped = line_wrapped;
 }
 
+#ifdef WITH_SIXEL
+
+void
+Terminal::insert_image(vte::cairo::Surface 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());
+
+        /* Convert to device-compatible surface for m_widget */
+        auto device_surface = 
vte::cairo::Surface{gdk_window_create_similar_surface(gtk_widget_get_window(widget()->gtk()),
+                                                                                    
CAIRO_CONTENT_COLOR_ALPHA,
+                                                                                    image_width_px,
+                                                                                    image_height_px)};
+
+        auto cr = cairo_create(device_surface.get());
+        cairo_set_source_surface(cr, image_surface.get(), 0, 0);
+        cairo_paint(cr);
+        cairo_destroy(cr);
+
+        /* Reduce memory fragmentation by dropping the ref as soon as we no longer need it */
+        image_surface.reset();
+
+        /* FIXMEchpe: should insert a 'missing image' pattern instead! */
+        if (cairo_surface_status(device_surface.get()) != CAIRO_STATUS_SUCCESS)
+                return;
+
+        /* 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 - 1) / m_cell_width;
+        auto const height = (image_height_px + m_cell_height - 1) / m_cell_height;
+
+        m_screen->row_data->append_image(std::move(device_surface),
+                                         image_width_px,
+                                         image_height_px,
+                                         left,
+                                         top,
+                                         m_cell_width,
+                                         m_cell_height);
+
+        /* Erase characters under the image */
+        erase_image_rect(height, width);
+}
+
+#endif /* WITH_SIXEL */
+
 guint8
 Terminal::get_bidi_flags() const noexcept
 {
@@ -3479,16 +3529,22 @@ Terminal::process_incoming()
                 }
 
                 switch (current_data_syntax()) {
-                case DataSyntax::eECMA48_UTF8:
+                case DataSyntax::ECMA48_UTF8:
                         process_incoming_utf8(context, *chunk);
                         break;
 
 #ifdef WITH_ICU
-                case DataSyntax::eECMA48_PCTERM:
+                case DataSyntax::ECMA48_PCTERM:
                         process_incoming_pcterm(context, *chunk);
                         break;
 #endif
 
+#ifdef WITH_SIXEL
+                case DataSyntax::DECSIXEL:
+                        process_incoming_decsixel(context, *chunk);
+                        break;
+#endif
+
                 default:
                         g_assert_not_reached();
                         break;
@@ -3692,7 +3748,7 @@ Terminal::process_incoming_utf8(ProcessingContext& context,
                 }
         }
 
- switched_data_syntax:
+switched_data_syntax:
 
         // Update start for data consumed
         chunk.set_begin_reading(ip);
@@ -3831,6 +3887,45 @@ 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())
+                        break;
+
+                try {
+                        insert_image(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)
@@ -4096,17 +4191,19 @@ Terminal::send_child(std::string_view const& data)
 
         /* Note that for backward compatibility, we need to emit the
          * ::commit signal even if there is no PTY. See issue vte#222.
+         *
+         * We use the primary data syntax to decide on the format.
          */
 
         switch (primary_data_syntax()) {
-        case DataSyntax::eECMA48_UTF8:
+        case DataSyntax::ECMA48_UTF8:
                 emit_commit(data);
                 if (pty())
                         _vte_byte_array_append(m_outgoing, data.data(), data.size());
                 break;
 
 #ifdef WITH_ICU
-        case DataSyntax::eECMA48_PCTERM: {
+        case DataSyntax::ECMA48_PCTERM: {
                 auto converted = m_converter->convert(data);
 
                 emit_commit(converted);
@@ -7665,6 +7762,12 @@ Terminal::Terminal(vte::platform::Widget* w,
        for (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();
 
@@ -7684,11 +7787,6 @@ Terminal::Terminal(vte::platform::Widget* w,
         save_cursor(&m_normal_screen);
         save_cursor(&m_alternate_screen);
 
-#ifdef WITH_SIXEL
-       /* Initialize SIXEL color register */
-       sixel_parser_set_default_color(&m_sixel_state);
-#endif
-
        /* Matching data. */
         m_match_span.clear(); // FIXMEchpe unnecessary
        match_hilite_clear(); // FIXMEchpe unnecessary
@@ -9786,12 +9884,12 @@ void
 Terminal::reset_decoder()
 {
         switch (primary_data_syntax()) {
-        case DataSyntax::eECMA48_UTF8:
+        case DataSyntax::ECMA48_UTF8:
                 m_utf8_decoder.reset();
                 break;
 
 #ifdef WITH_ICU
-        case DataSyntax::eECMA48_PCTERM:
+        case DataSyntax::ECMA48_PCTERM:
                 m_converter->decoder().reset();
                 break;
 #endif
@@ -9808,6 +9906,12 @@ Terminal::reset_data_syntax()
                 return;
 
         switch (current_data_syntax()) {
+#ifdef WITH_SIXEL
+        case DataSyntax::DECSIXEL:
+                m_sixel_context->reset();
+                break;
+#endif
+
         default:
                 break;
         }
@@ -9920,8 +10024,8 @@ Terminal::reset(bool clear_tabstops,
        m_modifiers = 0;
 
 #ifdef WITH_SIXEL
-       /* Reset SIXEL color register */
-       sixel_parser_set_default_color(&m_sixel_state);
+        if (m_sixel_context)
+                m_sixel_context->reset_colors();
 #endif
 
         /* Reset the saved cursor. */
@@ -9934,6 +10038,11 @@ 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)
+         */
 }
 
 void
@@ -9979,7 +10088,7 @@ Terminal::set_pty(vte::base::Pty *new_pty)
 
         set_size(m_column_count, m_row_count);
 
-        if (!pty()->set_utf8(primary_data_syntax() == DataSyntax::eECMA48_UTF8)) {
+        if (!pty()->set_utf8(primary_data_syntax() == DataSyntax::ECMA48_UTF8)) {
                 // nothing we can do here
         }
 
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index 307c1f9e..1e01305a 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -49,7 +49,6 @@
 
 #include "vtepcre2.h"
 #include "vteregexinternal.hh"
-#include "sixelparser.hh"
 
 #include "chunk.hh"
 #include "pty.hh"
@@ -66,6 +65,10 @@
 #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,
@@ -383,15 +386,18 @@ public:
 
         enum class DataSyntax {
                 /* The primary data syntax is always one of the following: */
-                eECMA48_UTF8,
+                ECMA48_UTF8,
                 #ifdef WITH_ICU
-                eECMA48_PCTERM,
+                ECMA48_PCTERM,
                 #endif
                 /* ECMA48_ECMA35, not supported */
+
+                /* The following can never be primary data syntax: */
+                DECSIXEL,
         };
 
-        DataSyntax m_primary_data_syntax{DataSyntax::eECMA48_UTF8};
-        DataSyntax m_current_data_syntax{DataSyntax::eECMA48_UTF8};
+        DataSyntax m_primary_data_syntax{DataSyntax::ECMA48_UTF8};
+        DataSyntax m_current_data_syntax{DataSyntax::ECMA48_UTF8};
 
         auto primary_data_syntax() const noexcept { return m_primary_data_syntax; }
         auto current_data_syntax() const noexcept { return m_current_data_syntax; }
@@ -438,14 +444,18 @@ public:
         char const* encoding() const noexcept
         {
                 switch (primary_data_syntax()) {
-                case DataSyntax::eECMA48_UTF8:   return "UTF-8";
+                case DataSyntax::ECMA48_UTF8:   return "UTF-8";
                 #ifdef WITH_ICU
-                case DataSyntax::eECMA48_PCTERM: return m_converter->charset().c_str();
+                case DataSyntax::ECMA48_PCTERM: return m_converter->charset().c_str();
                 #endif
                 default: g_assert_not_reached(); return nullptr;
                 }
         }
 
+#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;
@@ -561,7 +571,6 @@ public:
         /* Inline images */
         bool m_sixel_enabled{VTE_SIXEL_ENABLED_DEFAULT};
         bool m_images_enabled{VTE_SIXEL_ENABLED_DEFAULT};
-        sixel_state_t m_sixel_state;
 
         bool set_sixel_enabled(bool enabled) noexcept
         {
@@ -843,6 +852,10 @@ public:
                          bool insert,
                          bool invalidate_now);
 
+        #ifdef WITH_SIXEL
+        void insert_image(vte::cairo::Surface 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 */);
@@ -868,6 +881,10 @@ 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();
@@ -1390,6 +1407,8 @@ 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 4f2d6b83..6f174502 100644
--- a/src/vteseq.cc
+++ b/src/vteseq.cc
@@ -984,6 +984,33 @@ 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()
@@ -2379,7 +2406,7 @@ Terminal::DA1(vte::parser::Sequence const& seq)
 
         reply(seq, VTE_REPLY_DECDA1R, {65, 1,
 #ifdef WITH_SIXEL
-                                       4,
+                                       m_sixel_enabled ? 4 : -2 /* skip */,
 #endif
                                        9});
 }
@@ -4059,6 +4086,10 @@ Terminal::DECSCL(vte::parser::Sequence const& seq)
          *   args[0]: 64
          *   args[1]: 0
          *
+         * When not using private colour registers, this
+         * must also clear (assign to black) all SIXEL
+         * colour registers. (DEC PPLV2 § 5.8)
+         *
          * References: VT525
          */
 #if 0
@@ -4330,113 +4361,107 @@ Terminal::DECSGR(vte::parser::Sequence const& seq)
         /* TODO: consider implementing sub/superscript? */
 }
 
-void
+bool
 Terminal::DECSIXEL(vte::parser::Sequence const& seq)
 {
         /*
          * DECSIXEL - SIXEL graphics
+         * Image data in DECSIXEL format.
+         *
+         * Arguments:
+         *  args[0]: macro parameter (should always use 0 and use DECGRA instead)
+         *    See DEC PPLV Table 5–2 in § 5.4.1.1 for more information.
+         *  args[1]: background
+         *    0: device default (same as 2)
+         *    1: pixels with colour 0 retain the colour
+         *    2: pixels with colour 0 are set to the current background
+         *  args[2]: horizontal grid size in the unit set by SSU
+         *
+         * Defaults:
+         *   args[0]: 0
+         *   args[0]: 2 (1 for printers)
+         *   args[0]: no default
          *
          * References: VT330
+         *             DEC PPLV2 § 5.4
          */
 
 #ifdef WITH_SIXEL
-        if (!m_sixel_enabled)
-                return;
-
-       unsigned char *pixels = NULL;
-       auto fg = get_color(VTE_DEFAULT_FG);
-       auto bg = get_color(VTE_DEFAULT_BG);
-       int nfg = (fg->red >> 8) | ((fg->green >> 8) << 8) | ((fg->blue >> 8) << 16);
-       int nbg = (bg->red >> 8) | ((bg->green >> 8) << 8) | ((bg->blue >> 8) << 16);
-       glong left, top, width, height;
-       glong pixelwidth, pixelheight;
-       glong i;
-       cairo_surface_t *image_surface, *surface;
-       cairo_t *cr;
-
-        /* This is unfortunate, but it avoids copying or measuring potentially
-         * megabytes of data */
-        const vte_seq_string_t *arg_str = &((*((vte::parser::Sequence &) seq).seq_ptr())->arg_str);
-
-       /* Parse image */
-
-       if (sixel_parser_init(&m_sixel_state, nfg, nbg,
-                              m_modes_private.XTERM_SIXEL_PRIVATE_COLOR_REGISTERS()) < 0) {
-               sixel_parser_deinit(&m_sixel_state);
-               return;
-       }
-       if (sixel_parser_feed(&m_sixel_state, arg_str->buf, arg_str->len) < 0) {
-               sixel_parser_deinit(&m_sixel_state);
-               return;
-       }
-       pixels = (unsigned char *)g_try_malloc(m_sixel_state.image.width * m_sixel_state.image.height * 4);
-       if (!pixels) {
-               sixel_parser_deinit(&m_sixel_state);
-               return;
-       }
-       if (sixel_parser_finalize(&m_sixel_state, pixels) < 0) {
-                g_free(pixels);
-               sixel_parser_deinit(&m_sixel_state);
-               return;
-       }
-       sixel_parser_deinit(&m_sixel_state);
-
-       /* Calculate geometry */
-
-       left = m_screen->cursor.col;
-       top = m_screen->cursor.row;
-       width = (m_sixel_state.image.width + m_cell_width - 1) / m_cell_width;
-       height = (m_sixel_state.image.height + m_cell_height - 1) / m_cell_height;
-       pixelwidth = m_sixel_state.image.width;
-       pixelheight = m_sixel_state.image.height;
+        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;
 
-       /* Convert to device-compatible surface for m_widget */
+#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 converted 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 */
 
-       image_surface = cairo_image_surface_create_for_data(pixels, CAIRO_FORMAT_ARGB32, pixelwidth, 
pixelheight, pixelwidth * 4);
-        if (!image_surface) {
-                g_free(pixels);
-                return;
+                default:
+                        __builtin_unreachable();
+                        process_sixel = false;
+                }
         }
 
-       surface = gdk_window_create_similar_surface(gtk_widget_get_window (m_widget), 
CAIRO_CONTENT_COLOR_ALPHA, pixelwidth, pixelheight);
-        if (!surface) {
-                cairo_surface_destroy(image_surface);
-                g_free(pixels);
-                return;
+        if (!process_sixel || seq.is_ripe() /* that shouldn't happen */) {
+                m_parser.ignore_until_st();
+                return false;
         }
 
-       cr = cairo_create(surface);
-       cairo_set_source_surface(cr, image_surface, 0, 0);
-       cairo_paint(cr);
-       cairo_destroy(cr);
-       cairo_surface_destroy(image_surface);
-       g_free(pixels);
+        try {
+                if (!m_sixel_context)
+                        m_sixel_context = std::make_unique<vte::sixel::Context>();
 
-       /* Append image to Ring */
+                auto const fg = get_color(VTE_DEFAULT_FG);
+                auto const bg = get_color(VTE_DEFAULT_BG);
+                auto nfg = uint32_t(fg->red >> 8) | ((fg->green >> 8) << 8) | ((fg->blue >> 8) << 16) | 0xff 
<< 24;
+                auto nbg = uint32_t(bg->red >> 8) | ((bg->green >> 8) << 8) | ((bg->blue >> 8) << 16) | 0xff 
<< 24;
 
-       m_screen->row_data->append_image(surface, pixelwidth, pixelheight, left, top, m_cell_width, 
m_cell_height);
+                m_sixel_context->prepare(seq.st(),
+                                         nfg, nbg,
+                                         m_modes_private.XTERM_SIXEL_PRIVATE_COLOR_REGISTERS());
 
-       /* Erase characters under the image */
+                m_sixel_context->set_mode(mode);
 
-       for (i = 0; i < height; ++i) {
-                vte::grid::row_t row = top + i;
-
-               erase_characters(width, true);
-
-                if (row > m_screen->insert_delta - 1
-                    && row < m_screen->insert_delta + m_row_count)
-                        set_hard_wrapped(row);
+                /* 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);
 
-               if (i == height - 1) {
-                       if (m_modes_private.MINTTY_SIXEL_SCROLL_CURSOR_RIGHT())
-                               move_cursor_forward(width);
-                       else
-                               cursor_down(true);
-               } else {
-                       cursor_down(true);
-               }
-       }
+                return true; /* switching data syntax */
+        } catch (...) {
+        }
 #endif /* WITH_SIXEL */
+
+        m_parser.ignore_until_st();
+        return false;
 }
 
 void
@@ -4939,6 +4964,10 @@ Terminal::DECSTR(vte::parser::Sequence const& seq)
          * Perform a soft reset to the default values.
          * [list of default values]
          *
+         * When not using private colour registers, this
+         * must also clear (assign to black) all SIXEL
+         * colour registers. (DEC PPLV2 § 5.8)
+         *
          * References: VT525
          */
 
@@ -6918,6 +6947,10 @@ Terminal::RIS(vte::parser::Sequence const& seq)
          * Reset to initial state.
          * [list of things reset]
          *
+         * When not using private colour registers, this
+         * must also clear (assign to black) all SIXEL
+         * colour registers. (DEC PPLV2 § 5.8)
+         *
          * References: ECMA-48 § 8.3.105
          */
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]