[vte/wip/egmont/bidi: 2/6] bidi: Find the BiDi mapping for the RingView's contents
- From: Egmont Koblinger <egmontkob src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [vte/wip/egmont/bidi: 2/6] bidi: Find the BiDi mapping for the RingView's contents
- Date: Wed, 5 Jun 2019 07:59:43 +0000 (UTC)
commit 272a9c09298610f973bc6c3711a7ae0af3807d59
Author: Egmont Koblinger <egmont gmail com>
Date: Sat Jun 1 15:34:13 2019 +0200
bidi: Find the BiDi mapping for the RingView's contents
Find the mapping between logical and visual positions for the rows
of the RingView, according to the paragraph's BiDi parameters.
For implicit paragraphs, the Unicode Bidirectional Algorithm is run
via the FriBidi library.
Basic Arabic shaping is also performed using presentation form
characters.
meson.build | 9 +
meson_options.txt | 7 +
src/bidi.cc | 770 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
src/bidi.hh | 116 ++++++++
src/meson.build | 3 +
src/ringview.cc | 99 ++++++-
src/ringview.hh | 19 ++
src/vte.cc | 2 +
src/vtegtk.cc | 6 +
src/vteunistr.cc | 33 +++
src/vteunistr.h | 23 ++
11 files changed, 1082 insertions(+), 5 deletions(-)
---
diff --git a/meson.build b/meson.build
index 3760b3f8..451195b9 100644
--- a/meson.build
+++ b/meson.build
@@ -34,6 +34,7 @@ project(
gtk3_req_version = '3.8.0'
gtk4_req_version = '4.0.0'
+fribidi_req_version = '1.0.0'
gio_req_version = '2.40.0'
glib_req_version = '2.40.0'
gnutls_req_version = '3.2.7'
@@ -109,6 +110,7 @@ config_h = configuration_data()
config_h.set_quoted('GETTEXT_PACKAGE', vte_gettext_domain)
config_h.set_quoted('VERSION', vte_version)
config_h.set('VTE_DEBUG', enable_debug)
+config_h.set('WITH_FRIBIDI', get_option('fribidi'))
config_h.set('WITH_GNUTLS', get_option('gnutls'))
config_h.set('WITH_ICONV', get_option('iconv'))
@@ -373,6 +375,12 @@ pcre2_dep = dependency('libpcre2-8', version: '>=' + pcre2_req_version)
pthreads_dep = dependency('threads')
zlib_dep = dependency('zlib')
+if get_option('fribidi')
+ fribidi_dep = dependency('fribidi', version: '>=' + fribidi_req_version)
+else
+ fribidi_dep = dependency('', required: false)
+endif
+
if get_option('gnutls')
gnutls_dep = dependency('gnutls', version: '>=' + gnutls_req_version)
else
@@ -443,6 +451,7 @@ output += '\n'
output += ' Coverage: ' + get_option('b_coverage').to_string() + '\n'
output += ' Debug: ' + enable_debug.to_string() + '\n'
output += ' Docs: ' + get_option('docs').to_string() + '\n'
+output += ' FRIBIDI: ' + get_option('fribidi').to_string() + '\n'
output += ' GNUTLS: ' + get_option('gnutls').to_string() + '\n'
output += ' GTK+ 3.0: ' + get_option('gtk3').to_string() + '\n'
output += ' GTK+ 4.0: ' + get_option('gtk4').to_string() + '\n'
diff --git a/meson_options.txt b/meson_options.txt
index b8a11a84..d29c66a3 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -34,6 +34,13 @@ option(
description: 'Enable GObject Introspection',
)
+option(
+ 'fribidi',
+ type: 'boolean',
+ value: true,
+ description: 'Enable FriBidi support',
+)
+
option(
'gnutls',
type: 'boolean',
diff --git a/src/bidi.cc b/src/bidi.cc
new file mode 100644
index 00000000..f7650f46
--- /dev/null
+++ b/src/bidi.cc
@@ -0,0 +1,770 @@
+/*
+ * Copyright © 2018–2019 Egmont Koblinger
+ *
+ * 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 2.1 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/*
+ * A BidiRow object stores the BiDi mapping between logical and visual positions
+ * for one visual line of text. (Characters are always shuffled within a line,
+ * never across lines; although how they are shuffled within a line depends on
+ * the rest of the paragraph too.)
+ *
+ * It also stores additional per-character properties: the character's direction
+ * (needed for mirroring and mouse selecting) and Arabic shaping (as currently
+ * done using presentation form characters, although HarfBuzz would probably be
+ * a better approach).
+ *
+ * There are per-line properties as well, which are actually per-paragraph
+ * properties stored for each line: the overall potentially autodetected
+ * direction (needed for keyboard arrow swapping), and whether the paragraph
+ * contains any foreign direction character (used for the cursor shape).
+ *
+ * Note that the trivial LTR mapping, with no RTL or shaped characters at all,
+ * might be denoted by setting the BidiRow's width to 0.
+ *
+ * BidiRunner is a collection of methods that run the BiDi algorithm on one
+ * paragraph of RingView, and stores the result in BidiRow objects.
+ *
+ * BiDi is implemented according to Terminal-wg/bidi v0.2:
+ * https://terminal-wg.pages.freedesktop.org/bidi/
+ */
+
+#include <config.h>
+
+#ifdef WITH_FRIBIDI
+#include <fribidi.h>
+#endif
+
+#include "bidi.hh"
+#include "debug.h"
+#include "vtedefines.hh"
+#include "vteinternal.hh"
+
+#ifdef WITH_FRIBIDI
+static_assert (sizeof (gunichar) == sizeof (FriBidiChar), "Whoooo");
+#endif
+
+using namespace vte::base;
+
+BidiRow::BidiRow()
+{
+ /* The value of 0 is a valid representation of the trivial LTR mapping. */
+ m_width = 0;
+
+ /* These will be initialized / allocated on demand, when some RTL is encountered. */
+ m_width_alloc = 0;
+ m_log2vis = nullptr;
+ m_vis2log = nullptr;
+ m_vis_rtl = nullptr;
+ m_vis_shaped_char = nullptr;
+}
+
+BidiRow::~BidiRow()
+{
+ g_free (m_log2vis);
+ g_free (m_vis2log);
+ g_free (m_vis_rtl);
+ g_free (m_vis_shaped_char);
+}
+
+void BidiRow::set_width(vte::grid::column_t width)
+{
+ if (G_UNLIKELY (width > m_width_alloc)) {
+ if (m_width_alloc == 0) {
+ m_width_alloc = 128;
+ }
+ while (width > m_width_alloc) {
+ /* Don't realloc too aggressively. */
+ m_width_alloc *= 5 / 4;
+ }
+ m_log2vis = (vte::grid::column_t *) g_realloc (m_log2vis, sizeof (vte::grid::column_t) *
m_width_alloc);
+ m_vis2log = (vte::grid::column_t *) g_realloc (m_vis2log, sizeof (vte::grid::column_t) *
m_width_alloc);
+ m_vis_rtl = (guint8 *) g_realloc (m_vis_rtl, sizeof (guint8) * m_width_alloc);
+ m_vis_shaped_char = (gunichar *) g_realloc (m_vis_shaped_char, sizeof (gunichar) *
m_width_alloc);
+ }
+
+ m_width = width;
+}
+
+/* Converts from logical to visual column. Offscreen columns are mirrored
+ * for RTL lines, e.g. (assuming 80 columns) -1 <=> 80, -2 <=> 81 etc. */
+vte::grid::column_t BidiRow::log2vis(vte::grid::column_t col) const
+{
+ if (col >= 0 && col < m_width) {
+ return m_log2vis[col];
+ } else {
+ return m_base_rtl ? m_width - 1 - col : col;
+ }
+}
+
+/* Converts from visual to logical column. Offscreen columns are mirrored
+ * for RTL lines, e.g. (assuming 80 columns) -1 <=> 80, -2 <=> 81 etc. */
+vte::grid::column_t BidiRow::vis2log(vte::grid::column_t col) const
+{
+ if (col >= 0 && col < m_width) {
+ return m_vis2log[col];
+ } else {
+ return m_base_rtl ? m_width - 1 - col : col;
+ }
+}
+
+/* Whether the cell at the given visual position has RTL directionality.
+ * For offscreen columns the line's base direction is returned. */
+bool BidiRow::vis_is_rtl(vte::grid::column_t col) const
+{
+ if (col >= 0 && col < m_width) {
+ return m_vis_rtl[col];
+ } else {
+ return m_base_rtl;
+ }
+}
+
+/* Whether the cell at the given logical position has RTL directionality.
+ * For offscreen columns the line's base direction is returned. */
+bool BidiRow::log_is_rtl(vte::grid::column_t col) const
+{
+ if (col >= 0 && col < m_width) {
+ col = m_log2vis[col];
+ return m_vis_rtl[col];
+ } else {
+ return m_base_rtl;
+ }
+}
+
+/* Get the shaped character (vteunistr) for the given visual position.
+ *
+ * The unshaped character (vteunistr) needs to be passed to this method because
+ * the BiDi component may not store it if no shaping was required, and does not
+ * store combining accents. This method takes care of preserving combining accents.
+ *
+ * FIXMEegmont This is no longer true, we store vteunistr with combining accents.
+ * FIXMEegmont This should have a wrapper method in RingView!!!
+ */
+vteunistr
+BidiRow::vis_get_shaped_char(vte::grid::column_t col, vteunistr s) const
+{
+ if (col >= m_width || m_vis_shaped_char[col] == 0)
+ return s;
+
+ return _vte_unistr_replace_base(s, m_vis_shaped_char[col]);
+}
+
+/* Whether the line's base direction is RTL. */
+bool BidiRow::base_is_rtl() const
+{
+ return m_base_rtl;
+}
+
+/* Whether the implicit paragraph contains a foreign directionality character.
+ * This is used in the cursor, showing the character's directionality. */
+bool BidiRow::has_foreign() const
+{
+ return m_has_foreign;
+}
+
+
+BidiRunner::BidiRunner(RingView *ringview)
+{
+ m_ringview = ringview;
+}
+
+BidiRunner::~BidiRunner() {}
+
+#ifdef WITH_FRIBIDI
+bool BidiRunner::is_arabic(gunichar c)
+{
+ return FRIBIDI_IS_ARABIC (fribidi_get_bidi_type (c));
+}
+#endif /* WITH_FRIBIDI */
+
+/* Returns whether there's an Arabic character in the row so that shaping might be required. */
+bool BidiRunner::needs_shaping(vte::grid::row_t row)
+{
+#ifdef WITH_FRIBIDI
+ const VteRowData *row_data = m_ringview->get_row(row);
+
+ for (int i = 0; i < row_data->len; i++) {
+ const VteCell *cell = _vte_row_data_get(row_data, i);
+ if (cell != nullptr) {
+ gunichar c = _vte_unistr_get_base(cell->c);
+ if (G_UNLIKELY (is_arabic(c)))
+ return true;
+ }
+ }
+#endif /* WITH_FRIBIDI */
+ return false;
+}
+
+#ifdef WITH_FRIBIDI
+/* Perform Arabic shaping on an explicit line (which could be explicit LTR or explicit RTL),
+ * using presentation form characters.
+ *
+ * Don't do shaping across lines. (I'm unsure about this design decision.
+ * Shaping across soft linebreaks would require an even much more complex code.)
+ *
+ * The FriBiDi API doesn't have a method for shaping a visual string, so we need to extract
+ * Arabic words ourselves, by walking in the visual order from right to left. It's painful.
+ *
+ * This whole shaping business with presentation form characters should be replaced by HarfBuzz.
+ */
+void BidiRunner::explicit_line_shape(vte::grid::row_t row)
+{
+ const VteRowData *row_data = m_ringview->get_row(row);
+
+ BidiRow *bidirow = m_ringview->get_bidirow_writable(row);
+
+ auto width = m_ringview->get_width();
+
+ GArray *fribidi_chars_array = nullptr;
+
+ FriBidiParType pbase_dir = FRIBIDI_PAR_RTL;
+ FriBidiLevel level;
+ FriBidiChar *fribidi_chars;
+ FriBidiCharType *fribidi_chartypes;
+ FriBidiBracketType *fribidi_brackettypes;
+ FriBidiJoiningType *fribidi_joiningtypes;
+ FriBidiLevel *fribidi_levels;
+
+ int count;
+
+ const VteCell *cell;
+ gunichar c;
+ gunichar base;
+ int i, j; /* visual columns */
+
+ fribidi_chars_array = g_array_new (FALSE, FALSE, sizeof (FriBidiChar));
+
+ /* Walk in visual order from right to left. */
+ i = width - 1;
+ while (i >= 0) {
+ cell = _vte_row_data_get(row_data, bidirow->m_vis2log[i]);
+ c = cell ? cell->c : 0;
+ base = _vte_unistr_get_base(c);
+ if (!is_arabic(base)) {
+ i--;
+ continue;
+ }
+
+ /* Found an Arabic character. Keep walking to the left, extracting the word. */
+ g_array_set_size(fribidi_chars_array, 0);
+ j = i;
+ do {
+ auto prev_len = fribidi_chars_array->len;
+ _vte_unistr_append_to_gunichars (cell->c, fribidi_chars_array);
+ g_assert_cmpint (fribidi_chars_array->len, >, prev_len);
+
+ j--;
+ if (j >= 0) {
+ cell = _vte_row_data_get(row_data, bidirow->m_vis2log[j]);
+ c = cell ? cell->c : 0;
+ base = _vte_unistr_get_base(c);
+ } else {
+ /* Pretend that visual column -1 contains a stop char. */
+ base = 0;
+ }
+ } while (is_arabic(base));
+
+ /* Extracted the Arabic run. Do the BiDi. */
+
+ /* Convenience stuff, we no longer need the auto-growing GArray wrapper. */
+ count = fribidi_chars_array->len;
+ fribidi_chars = (FriBidiChar *) fribidi_chars_array->data;
+
+ /* Run the BiDi algorithm on the paragraph to get the embedding levels. */
+ fribidi_chartypes = g_newa (FriBidiCharType, count);
+ fribidi_brackettypes = g_newa (FriBidiBracketType, count);
+ fribidi_joiningtypes = g_newa (FriBidiJoiningType, count);
+ fribidi_levels = g_newa (FriBidiLevel, count);
+
+ fribidi_get_bidi_types (fribidi_chars, count, fribidi_chartypes);
+ fribidi_get_bracket_types (fribidi_chars, count, fribidi_chartypes, fribidi_brackettypes);
+ fribidi_get_joining_types (fribidi_chars, count, fribidi_joiningtypes);
+ level = fribidi_get_par_embedding_levels_ex (fribidi_chartypes, fribidi_brackettypes, count,
&pbase_dir, fribidi_levels);
+ if (level == 0) {
+ /* Error. Skip shaping this word. */
+ i = j - 1;
+ continue;
+ }
+
+ /* Shaping. */
+ fribidi_join_arabic (fribidi_chartypes, count, fribidi_levels, fribidi_joiningtypes);
+ fribidi_shape_arabic (FRIBIDI_FLAGS_ARABIC, fribidi_levels, count, fribidi_joiningtypes,
fribidi_chars);
+
+ /* Walk through the Arabic word again. */
+ j = i;
+ while (count > 0) {
+ g_assert_cmpint (j, >=, 0);
+ cell = _vte_row_data_get(row_data, bidirow->m_vis2log[j]);
+ c = cell->c;
+ base = _vte_unistr_get_base(c);
+ if (*fribidi_chars != base) {
+ /* Shaping changed the codepoint. Apply combining accents and store. */
+ bidirow->m_vis_shaped_char[j] = _vte_unistr_replace_base(c, *fribidi_chars);
+ }
+ int len = _vte_unistr_strlen(c);
+ fribidi_chars += len;
+ count -= len;
+ j--;
+ }
+
+ /* Ready to look for the next word. Skip the stop char which isn't Arabic. */
+ i = j - 1;
+ }
+
+ g_array_free (fribidi_chars_array, TRUE);
+}
+#endif /* WITH_FRIBIDI */
+
+/* Set up the mapping according to explicit mode for a given line.
+ *
+ * If @do_shaping then perform Arabic shaping on the visual string, independently
+ * from the paragraph direction (the @rtl parameter). This is done using
+ * presentation form characters, until we have something better (e.g. HarfBuzz)
+ * in place.
+ */
+void BidiRunner::explicit_line(vte::grid::row_t row, bool rtl, bool do_shaping)
+{
+ int i;
+
+ BidiRow *bidirow = m_ringview->get_bidirow_writable(row);
+ if (G_UNLIKELY (bidirow == nullptr))
+ return;
+ bidirow->m_base_rtl = rtl;
+ bidirow->m_has_foreign = false;
+
+ auto width = m_ringview->get_width();
+
+ if (G_LIKELY (!rtl)) {
+ if (!do_shaping || !needs_shaping(row)) {
+ /* Shortcut notation: a width of 0 means the trivial LTR mapping. */
+ bidirow->set_width(0);
+ return;
+ }
+ bidirow->set_width(width);
+ for (i = 0; i < width; i++) {
+ bidirow->m_log2vis[i] = bidirow->m_vis2log[i] = i;
+ bidirow->m_vis_rtl[i] = false;
+ bidirow->m_vis_shaped_char[i] = 0;
+ }
+ } else {
+ bidirow->set_width(width);
+ for (i = 0; i < width; i++) {
+ bidirow->m_log2vis[i] = bidirow->m_vis2log[i] = width - 1 - i;
+ bidirow->m_vis_rtl[i] = true;
+ bidirow->m_vis_shaped_char[i] = 0;
+ }
+ if (!do_shaping || !needs_shaping(row)) {
+ return;
+ }
+ }
+
+#ifdef WITH_FRIBIDI
+ explicit_line_shape(row);
+#endif
+}
+
+/* Figure out the mapping for the paragraph between the given rows. */
+void BidiRunner::paragraph(vte::grid::row_t start, vte::grid::row_t end,
+ bool do_bidi, bool do_shaping)
+{
+ const VteRowData *row_data = m_ringview->get_row(start);
+
+ if (!do_bidi) {
+ explicit_paragraph(start, end, false, do_shaping);
+ return;
+ }
+
+#ifdef WITH_FRIBIDI
+ /* Have a consistent limit on the number of rows in a paragraph
+ * that can get implicit BiDi treatment, which is independent from
+ * the current scroll position. */
+ if ((row_data->attr.bidi_flags & VTE_BIDI_FLAG_IMPLICIT) &&
+ end - start <= VTE_RINGVIEW_PARAGRAPH_LENGTH_MAX) {
+ if (implicit_paragraph(start, end, do_shaping))
+ return;
+ }
+#endif
+
+ explicit_paragraph(start, end, row_data->attr.bidi_flags & VTE_BIDI_FLAG_RTL, do_shaping);
+}
+
+/* Set up the mapping according to explicit mode, for all the lines
+ * of a paragraph between the given lines. */
+void BidiRunner::explicit_paragraph(vte::grid::row_t start, vte::grid::row_t end, bool rtl, bool do_shaping)
+{
+ for (; start < end; start++) {
+ explicit_line(start, rtl, do_shaping);
+ }
+}
+
+#ifdef WITH_FRIBIDI
+/* Figure out the mapping for the implicit paragraph between the given rows.
+ * Returns success. */
+bool BidiRunner::implicit_paragraph(vte::grid::row_t start, vte::grid::row_t end, bool do_shaping)
+{
+ const VteCell *cell;
+ const VteRowData *row_data;
+ bool rtl;
+ bool autodir;
+ vte::grid::row_t row;
+ FriBidiParType pbase_dir;
+ FriBidiLevel level;
+ FriBidiChar *fribidi_chars;
+ FriBidiCharType *fribidi_chartypes;
+ FriBidiBracketType *fribidi_brackettypes;
+ FriBidiJoiningType *fribidi_joiningtypes;
+ FriBidiLevel *fribidi_levels;
+ FriBidiStrIndex *fribidi_map;
+ FriBidiStrIndex *fribidi_to_term;
+ BidiRow *bidirow;
+
+ auto width = m_ringview->get_width();
+
+ row_data = m_ringview->get_row(start);
+ rtl = row_data->attr.bidi_flags & VTE_BIDI_FLAG_RTL;
+ autodir = row_data->attr.bidi_flags & VTE_BIDI_FLAG_AUTO;
+
+ int lines[VTE_RINGVIEW_PARAGRAPH_LENGTH_MAX + 1]; /* offsets to the beginning of lines */
+ lines[0] = 0;
+ int line = 0; /* line number within the paragraph */
+ int count; /* total character count */
+ int tl, tv; /* terminal logical and visual */
+ int fl, fv; /* fribidi logical and visual */
+ unsigned int col;
+
+ GArray *fribidi_chars_array = g_array_new (FALSE, FALSE, sizeof (FriBidiChar));
+ GArray *fribidi_map_array = g_array_new (FALSE, FALSE, sizeof (FriBidiStrIndex));
+ GArray *fribidi_to_term_array = g_array_new (FALSE, FALSE, sizeof (FriBidiStrIndex));
+
+ /* Extract the paragraph's contents, omitting unused and fragment cells. */
+
+ /* Example of what is going on, showing the most important steps:
+ *
+ * Let's take the string produced by this command:
+ * echo -e "\u0041\u05e9\u05b8\u05c1\u05dc\u05d5\u05b9\u05dd\u0031\u0032\uff1c\u05d0"
+ *
+ * This string consists of:
+ * - English letter A
+ * - Hebrew word Shalom:
+ * - Letter Shin: ש
+ * - Combining accent Qamats
+ * - Combining accent Shin Dot
+ * - Letter Lamed: ל
+ * - Letter Vav: ו
+ * - Combining accent Holam
+ * - Letter Final Mem: ם
+ * - Digits One and Two
+ * - Full-width less-than sign U+ff1c: <
+ * - Hebrew letter Alef: א
+ *
+ * Features of this example:
+ * - Overall LTR direction for convenience (set up by the leading English letter)
+ * - Combining accents within RTL
+ * - Double width character with RTL resolved direction
+ * - A mapping that is not its own inverse (due to the digits being LTR inside RTL inside LTR),
+ * to help catch if we'd look up something in the wrong direction
+ *
+ * Not demonstrated in this example:
+ * - Wrapping a paragraph to lines
+ * - Spacing marks
+ *
+ * Pre-BiDi (logical) order, using approximating glyphs ("Shalom" is "w7io", Alef is "x"):
+ * Aw7io12<x
+ *
+ * Post-BiDi (visual) order, using approximating glyphs ("Shalom" is "oi7w", note the mirrored
less-than):
+ * Ax>12oi7w
+ *
+ * Terminal's logical cells:
+ * [0] [1] [2] [3] [4] [5] [6] [7] [8] [9]
+ * row_data: A Shin+qam+dot Lam Vav+hol Mem One Two Less Less (cont) Alef
+ *
+ * Extracted to pass to FriBidi (combining accents get -1, double wides' continuation cells are
skipped):
+ * [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
+ * fribidi_chars: A Shin qam dot Lam Vav hol Mem One Two Less Alef
+ * fribidi_map: 0 1 -1 -1 4 5 -1 7 8 9 10 11
+ * fribidi_to_term: 0 1 -1 -1 2 3 -1 4 5 6 7 9
+ *
+ * Embedding levels and other properties (shaping etc.) are looked up:
+ * [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
+ * fribidi_levels: 0 1 1 1 1 1 1 1 2 2 1 1
+ *
+ * The steps above were per-paragraph. The steps below are per-line.
+ *
+ * After fribidi_reorder_line (only this array gets shuffled):
+ * [0] [1] [2] [3] [4] [5] [6] [7] [8] [9] [10] [11]
+ * fribidi_map: 0 11 10 8 9 7 5 -1 4 1 -1 -1
+ *
+ * To get the visual order: walk in the new fribidi_map, and for each real entry look up the
+ * logical terminal column using fribidi_to_term:
+ * - map[0] is 0, to_term[0] is 0, hence visual column 0 belongs to logical column 0 (A)
+ * - map[1] is 11, to_term[11] is 9, hence visual column 1 belongs to logical column 9 (Alef)
+ * - map[2] is 10, to_term[10] is 7, row_data[7] is the "<" sign
+ * - this is a double wide character, we need to map the next two visual cells to two logical
cells
+ * - due to levels[10] being odd, this character has a resolved RTL direction
+ * - thus we map in reverse order: visual 2 <=> logical 8, visual 3 <=> logical 7
+ * - the glyph is also mirrorable, it'll be displayed accordingly
+ * - [3] -> 8 -> 5, so visual 4 <=> logical 5 (One)
+ * - [4] -> 9 -> 6, so visual 5 <=> logical 6 (Two)
+ * - [5] -> 7 -> 4, so visual 6 <=> logical 4 (Mem, the last, leftmost letter of Shalom)
+ * - [6] -> 5 -> 3, so visual 7 <=> logical 3 (Vav+hol)
+ * - [7] -> -1, skipped
+ * - [8] -> 4 -> 2, so visual 8 <=> logical 2 (Lam)
+ * - [9] -> 1 -> 1, so visual 9 <=> logical 1 (Shin+qam+dot, the first, rightmost letter of Shalom)
+ * - [10] -> -1, skipped
+ * - [11] -> -1, skipped
+ *
+ * Silly FriBidi API almost allows us to skip one level of indirection, by placing the to_term values
+ * in the map to be shuffled. However, we can't get the embedding levels then.
+ * TODO: File an issue for a better API.
+ */
+ for (row = start; row < end; row++) {
+ row_data = m_ringview->get_row(row);
+
+ /* A row_data might be longer, in case rewrapping is disabled and the window was narrowed.
+ * Truncate the logical data before applying BiDi. */
+ // FIXME what the heck to do if this truncation cuts a TAB or CJK in half???
+ for (tl = 0; tl < width && tl < row_data->len; tl++) {
+ auto prev_len = fribidi_chars_array->len;
+ FriBidiStrIndex val;
+
+ cell = _vte_row_data_get (row_data, tl);
+ if (cell->attr.fragment())
+ continue;
+
+ /* Extract the base character and combining accents.
+ * Convert mid-line erased cells to spaces.
+ * Note: see the static assert at the top of this file. */
+ _vte_unistr_append_to_gunichars (cell->c ? cell->c : ' ', fribidi_chars_array);
+ /* Make sure at least one character was produced. */
+ g_assert_cmpint (fribidi_chars_array->len, >, prev_len);
+
+ /* Track the base character, assign to it its current index in fribidi_chars.
+ * Don't track combining accents, assign -1's to them. */
+ val = prev_len;
+ g_array_append_val (fribidi_map_array, val);
+ val = tl;
+ g_array_append_val (fribidi_to_term_array, val);
+ prev_len++;
+ val = -1;
+ while (prev_len++ < fribidi_chars_array->len) {
+ g_array_append_val (fribidi_map_array, val);
+ g_array_append_val (fribidi_to_term_array, val);
+ }
+ }
+
+ lines[++line] = fribidi_chars_array->len;
+ }
+
+ /* Convenience stuff, we no longer need the auto-growing GArray wrapper. */
+ count = fribidi_chars_array->len;
+ fribidi_chars = (FriBidiChar *) fribidi_chars_array->data;
+ fribidi_map = (FriBidiStrIndex *) fribidi_map_array->data;
+ fribidi_to_term = (FriBidiStrIndex *) fribidi_to_term_array->data;
+
+ /* Run the BiDi algorithm on the paragraph to get the embedding levels. */
+ fribidi_chartypes = g_newa (FriBidiCharType, count);
+ fribidi_brackettypes = g_newa (FriBidiBracketType, count);
+ fribidi_joiningtypes = g_newa (FriBidiJoiningType, count);
+ fribidi_levels = g_newa (FriBidiLevel, count);
+
+ pbase_dir = autodir ? (rtl ? FRIBIDI_PAR_WRTL : FRIBIDI_PAR_WLTR)
+ : (rtl ? FRIBIDI_PAR_RTL : FRIBIDI_PAR_LTR );
+
+ fribidi_get_bidi_types (fribidi_chars, count, fribidi_chartypes);
+ fribidi_get_bracket_types (fribidi_chars, count, fribidi_chartypes, fribidi_brackettypes);
+ fribidi_get_joining_types (fribidi_chars, count, fribidi_joiningtypes);
+ level = fribidi_get_par_embedding_levels_ex (fribidi_chartypes, fribidi_brackettypes, count,
&pbase_dir, fribidi_levels);
+
+ if (level == 0) {
+ /* error */
+ g_array_free (fribidi_chars_array, TRUE);
+ g_array_free (fribidi_map_array, TRUE);
+ g_array_free (fribidi_to_term_array, TRUE);
+ return false;
+ }
+
+ if (do_shaping) {
+ /* Arabic shaping (on the entire paragraph in a single run). */
+ fribidi_join_arabic (fribidi_chartypes, count, fribidi_levels, fribidi_joiningtypes);
+ fribidi_shape_arabic (FRIBIDI_FLAGS_ARABIC, fribidi_levels, count, fribidi_joiningtypes,
fribidi_chars);
+ }
+
+ /* For convenience, from now on this variable contains the resolved (i.e. possibly autodetected)
value. */
+ g_assert_cmpint (pbase_dir, !=, FRIBIDI_PAR_ON);
+ rtl = (pbase_dir == FRIBIDI_PAR_RTL || pbase_dir == FRIBIDI_PAR_WRTL);
+
+ if (!rtl && level == 1) {
+ /* Fast shortcut for LTR-only paragraphs. */
+ g_array_free (fribidi_chars_array, TRUE);
+ g_array_free (fribidi_map_array, TRUE);
+ g_array_free (fribidi_to_term_array, TRUE);
+ explicit_paragraph (start, end, false, false);
+ return true;
+ }
+
+ /* Reshuffle line by line. */
+ for (row = start, line = 0; row < end; row++, line++) {
+ bidirow = m_ringview->get_bidirow_writable(row);
+ if (bidirow == nullptr)
+ continue;
+
+ bidirow->m_base_rtl = rtl;
+ bidirow->m_has_foreign = true;
+ bidirow->set_width(width);
+
+ row_data = m_ringview->get_row(row);
+
+ level = fribidi_reorder_line (FRIBIDI_FLAGS_DEFAULT,
+ fribidi_chartypes,
+ lines[line + 1] - lines[line],
+ lines[line],
+ pbase_dir,
+ fribidi_levels,
+ NULL,
+ fribidi_map);
+
+ if (level == 0) {
+ /* error, what should we do? */
+ explicit_line (row, rtl, true);
+ bidirow->m_has_foreign = true;
+ continue;
+ }
+
+ if (!rtl && level == 1) {
+ /* Fast shortcut for LTR-only lines. */
+ explicit_line (row, false, false);
+ bidirow->m_has_foreign = true;
+ continue;
+ }
+
+ /* Copy to our realm. Proceed in visual order.*/
+ tv = 0;
+ if (rtl) {
+ /* Unused cells on the left for RTL paragraphs */
+ int unused = MAX(width - row_data->len, 0);
+ for (; tv < unused; tv++) {
+ bidirow->m_vis2log[tv] = width - 1 - tv;
+ bidirow->m_vis_rtl[tv] = true;
+ bidirow->m_vis_shaped_char[tv] = 0;
+ }
+ }
+ for (fv = lines[line]; fv < lines[line + 1]; fv++) {
+ /* Inflate fribidi's result by inserting fragments. */
+ fl = fribidi_map[fv];
+ if (fl == -1)
+ continue;
+ tl = fribidi_to_term[fl];
+ cell = _vte_row_data_get (row_data, tl);
+ g_assert (!cell->attr.fragment());
+ g_assert (cell->attr.columns() > 0);
+ if (FRIBIDI_LEVEL_IS_RTL(fribidi_levels[fl])) {
+ /* RTL character directionality. Map fragments in reverse order. */
+ for (col = 0; col < cell->attr.columns(); col++) {
+ bidirow->m_vis2log[tv + col] = tl + cell->attr.columns() - 1 - col;
+ bidirow->m_vis_rtl[tv + col] = true;
+ bidirow->m_vis_shaped_char[tv + col] = fribidi_chars[fl];
+ }
+ tv += cell->attr.columns();
+ tl += cell->attr.columns();
+ } else {
+ /* LTR character directionality. */
+ for (col = 0; col < cell->attr.columns(); col++) {
+ bidirow->m_vis2log[tv] = tl;
+ bidirow->m_vis_rtl[tv] = false;
+ bidirow->m_vis_shaped_char[tv] = fribidi_chars[fl];
+ tv++;
+ tl++;
+ }
+ }
+ }
+ if (!rtl) {
+ /* Unused cells on the right for LTR paragraphs */
+ g_assert_cmpint (tv, ==, MIN(row_data->len, width));
+ for (; tv < width; tv++) {
+ bidirow->m_vis2log[tv] = tv;
+ bidirow->m_vis_rtl[tv] = false;
+ bidirow->m_vis_shaped_char[tv] = 0;
+ }
+ }
+ g_assert_cmpint (tv, ==, width);
+
+ /* From vis2log create the log2vis mapping too.
+ * In debug mode assert that we have a bijective mapping. */
+ if (_vte_debug_on (VTE_DEBUG_BIDI)) {
+ for (tl = 0; tl < width; tl++) {
+ bidirow->m_log2vis[tl] = -1;
+ }
+ }
+
+ for (tv = 0; tv < width; tv++) {
+ bidirow->m_log2vis[bidirow->m_vis2log[tv]] = tv;
+ }
+
+ if (_vte_debug_on (VTE_DEBUG_BIDI)) {
+ for (tl = 0; tl < width; tl++) {
+ g_assert_cmpint (bidirow->m_log2vis[tl], !=, -1);
+ }
+ }
+ }
+
+ g_array_free (fribidi_chars_array, TRUE);
+ g_array_free (fribidi_map_array, TRUE);
+ g_array_free (fribidi_to_term_array, TRUE);
+ return true;
+}
+#endif /* WITH_FRIBIDI */
+
+
+/* Find the mirrored counterpart of a codepoint, just like
+ * fribidi_get_mirror_char() or g_unichar_get_mirror_char() does.
+ * Two additions:
+ * - works with vteunistr, that is, preserves combining accents;
+ * - optionally mirrors box drawing characters.
+ */
+gboolean vte_bidi_get_mirror_char (vteunistr unistr, gboolean mirror_box_drawing, vteunistr *out)
+{
+ static const unsigned char mirrored_2500[0x80] = {
+ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x10, 0x11, 0x12,
0x13,
+ 0x0c, 0x0d, 0x0e, 0x0f, 0x18, 0x19, 0x1a, 0x1b, 0x14, 0x15, 0x16, 0x17, 0x24, 0x25, 0x26,
0x27,
+ 0x28, 0x29, 0x2a, 0x2b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23, 0x2c, 0x2e, 0x2d,
0x2f,
+ 0x30, 0x32, 0x31, 0x33, 0x34, 0x36, 0x35, 0x37, 0x38, 0x3a, 0x39, 0x3b, 0x3c, 0x3e, 0x3d,
0x3f,
+ 0x40, 0x41, 0x42, 0x44, 0x43, 0x46, 0x45, 0x47, 0x48, 0x4a, 0x49, 0x4b, 0x4c, 0x4d, 0x4e,
0x4f,
+ 0x50, 0x51, 0x55, 0x56, 0x57, 0x52, 0x53, 0x54, 0x5b, 0x5c, 0x5d, 0x58, 0x59, 0x5a, 0x61,
0x62,
+ 0x63, 0x5e, 0x5f, 0x60, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6e, 0x6d,
0x70,
+ 0x6f, 0x72, 0x71, 0x73, 0x76, 0x75, 0x74, 0x77, 0x7a, 0x79, 0x78, 0x7b, 0x7e, 0x7d, 0x7c,
0x7f };
+
+ gunichar base_ch = _vte_unistr_get_base (unistr);
+ gunichar base_ch_mirrored = base_ch;
+
+ if (G_UNLIKELY (base_ch >= 0x2500 && base_ch < 0x2580)) {
+ if (G_UNLIKELY (mirror_box_drawing))
+ base_ch_mirrored = 0x2500 + mirrored_2500[base_ch - 0x2500];
+ } else {
+#ifdef WITH_FRIBIDI
+ /* Prefer the FriBidi variant as that's more likely to be in sync with the rest of our BiDi
stuff. */
+ fribidi_get_mirror_char (base_ch, &base_ch_mirrored);
+#else
+ /* Fall back to glib, so that we still get mirrored characters in explicit RTL mode without
BiDi support. */
+ g_unichar_get_mirror_char (base_ch, &base_ch_mirrored);
+#endif
+ }
+
+ vteunistr unistr_mirrored = _vte_unistr_replace_base (unistr, base_ch_mirrored);
+
+ if (out)
+ *out = unistr_mirrored;
+ return unistr_mirrored == unistr;
+}
diff --git a/src/bidi.hh b/src/bidi.hh
new file mode 100644
index 00000000..d4ba8ff3
--- /dev/null
+++ b/src/bidi.hh
@@ -0,0 +1,116 @@
+/*
+ * Copyright © 2018–2019 Egmont Koblinger
+ *
+ * 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 2.1 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+#pragma once
+
+#include <glib.h>
+
+#include "ring.hh"
+#include "ringview.hh"
+#include "vterowdata.hh"
+#include "vtetypes.hh"
+#include "vteunistr.h"
+
+namespace vte {
+
+namespace base { // FIXME ???
+
+class RingView;
+
+/* BidiRow contains the BiDi transformation of a single row. */
+class BidiRow {
+ friend class BidiRunner;
+
+public:
+ BidiRow();
+ ~BidiRow();
+
+ // prevent accidents
+ BidiRow(BidiRow& o) = delete;
+ BidiRow(BidiRow const& o) = delete;
+ BidiRow(BidiRow&& o) = delete;
+ BidiRow& operator= (BidiRow& o) = delete;
+ BidiRow& operator= (BidiRow const& o) = delete;
+ BidiRow& operator= (BidiRow&& o) = delete;
+
+ vte::grid::column_t log2vis(vte::grid::column_t col) const;
+ vte::grid::column_t vis2log(vte::grid::column_t col) const;
+ bool log_is_rtl(vte::grid::column_t col) const;
+ bool vis_is_rtl(vte::grid::column_t col) const;
+ vteunistr vis_get_shaped_char(vte::grid::column_t col, vteunistr s) const;
+ bool base_is_rtl() const;
+ bool has_foreign() const;
+
+private:
+ void set_width(vte::grid::column_t width);
+
+ vte::grid::column_t m_width;
+ vte::grid::column_t m_width_alloc;
+
+ vte::grid::column_t *m_log2vis;
+ vte::grid::column_t *m_vis2log;
+ guint8 *m_vis_rtl;
+ gunichar *m_vis_shaped_char;
+
+ guint8 m_base_rtl: 1;
+ guint8 m_has_foreign: 1;
+};
+
+
+/* BidiRunner is not a "real" class, rather the collection of methods that run the BiDi algorithm. */
+class BidiRunner {
+public:
+ BidiRunner(RingView *ringview);
+ ~BidiRunner();
+
+ // prevent accidents
+ BidiRunner(BidiRunner& o) = delete;
+ BidiRunner(BidiRunner const& o) = delete;
+ BidiRunner(BidiRunner&& o) = delete;
+ BidiRunner& operator= (BidiRunner& o) = delete;
+ BidiRunner& operator= (BidiRunner const& o) = delete;
+ BidiRunner& operator= (BidiRunner&& o) = delete;
+
+ void paragraph(vte::grid::row_t start, vte::grid::row_t end,
+ bool do_bidi, bool do_shaping);
+
+private:
+ RingView *m_ringview;
+
+ bool needs_shaping(vte::grid::row_t row);
+#ifdef WITH_FRIBIDI
+ static bool is_arabic(gunichar c);
+ void explicit_line_shape(vte::grid::row_t row);
+#endif
+
+ void explicit_line(vte::grid::row_t row, bool rtl, bool do_shaping);
+ void explicit_paragraph(vte::grid::row_t start, vte::grid::row_t end, bool rtl, bool do_shaping);
+#ifdef WITH_FRIBIDI
+ bool implicit_paragraph(vte::grid::row_t start, vte::grid::row_t end, bool do_shaping);
+#endif
+};
+
+}; /* namespace base */
+
+}; /* namespace vte */
+
+G_BEGIN_DECLS
+
+gboolean vte_bidi_get_mirror_char (vteunistr unistr, gboolean mirror_box_drawing, vteunistr
*unistr_mirrored);
+
+G_END_DECLS
diff --git a/src/meson.build b/src/meson.build
index 6b97aaec..1481c089 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -56,6 +56,8 @@ utf8_sources = files(
libvte_common_sources = debug_sources + modes_sources + parser_sources + utf8_sources + files(
'attr.hh',
+ 'bidi.cc',
+ 'bidi.hh',
'buffer.h',
'caps.hh',
'cell.hh',
@@ -146,6 +148,7 @@ libvte_common_public_deps = [
]
libvte_common_deps = libvte_common_public_deps + [
+ fribidi_dep,
gnutls_dep,
pcre2_dep,
libm_dep,
diff --git a/src/ringview.cc b/src/ringview.cc
index 75542957..71bb8f21 100644
--- a/src/ringview.cc
+++ b/src/ringview.cc
@@ -18,6 +18,7 @@
#include <config.h>
+#include "bidi.hh"
#include "debug.h"
#include "vtedefines.hh"
#include "vteinternal.hh"
@@ -30,6 +31,11 @@ RingView::RingView()
m_start = m_len = m_width = 0;
m_rows_alloc_len = 0;
+ m_enable_bidi = true; /* These two are the most convenient defaults */
+ m_enable_shaping = false; /* for short-lived ringviews. */
+ m_bidirunner = new BidiRunner(this);
+ m_bidirows_alloc_len = 0;
+
m_invalid = true;
m_paused = true;
}
@@ -37,6 +43,7 @@ RingView::RingView()
RingView::~RingView()
{
pause();
+ delete m_bidirunner;
}
/* Pausing a RingView frees up pretty much all of its memory.
@@ -54,8 +61,8 @@ void RingView::pause()
if (m_paused)
return;
- _vte_debug_print (VTE_DEBUG_RINGVIEW, "Ringview: pause, freeing %d rows.\n",
- m_rows_alloc_len);
+ _vte_debug_print (VTE_DEBUG_RINGVIEW, "Ringview: pause, freeing %d rows, %d bidirows.\n",
+ m_rows_alloc_len, m_bidirows_alloc_len);
for (i = 0; i < m_rows_alloc_len; i++) {
_vte_row_data_fini(m_rows[i]);
@@ -64,6 +71,12 @@ void RingView::pause()
g_free (m_rows);
m_rows_alloc_len = 0;
+ for (i = 0; i < m_bidirows_alloc_len; i++) {
+ delete m_bidirows[i];
+ }
+ g_free (m_bidirows);
+ m_bidirows_alloc_len = 0;
+
m_invalid = true;
m_paused = true;
}
@@ -82,8 +95,19 @@ void RingView::resume()
_vte_row_data_init (m_rows[i]);
}
- _vte_debug_print (VTE_DEBUG_RINGVIEW, "Ringview: resume, allocating %d rows\n",
- m_rows_alloc_len);
+ /* +2: Likely prevent a quickly following realloc.
+ * The number of lines of interest keeps jumping up and down by one
+ * due to per-pixel scrolling, and by another one due sometimes having
+ * to reshuffle another line below the bottom for the overflowing bits
+ * of the outline rectangle cursor. */
+ m_bidirows_alloc_len = m_len + 2;
+ m_bidirows = (BidiRow **) g_malloc (sizeof (BidiRow *) * m_bidirows_alloc_len);
+ for (int i = 0; i < m_bidirows_alloc_len; i++) {
+ m_bidirows[i] = new BidiRow();
+ }
+
+ _vte_debug_print (VTE_DEBUG_RINGVIEW, "Ringview: resume, allocating %d rows, %d bidirows\n",
+ m_rows_alloc_len, m_bidirows_alloc_len);
m_paused = false;
}
@@ -121,6 +145,21 @@ void RingView::set_rows(vte::grid::row_t start, vte::grid::row_t len)
/* m_rows is expanded on demand in update() */
+ /* m_bidirows needs exactly this many lines */
+ if (G_UNLIKELY (!m_paused && len > m_bidirows_alloc_len)) {
+ int i = m_bidirows_alloc_len;
+ while (len > m_bidirows_alloc_len) {
+ /* Don't realloc too aggressively. */
+ m_bidirows_alloc_len = std::max(m_bidirows_alloc_len + 1, m_bidirows_alloc_len * 5 /
4 /* whatever */);
+ }
+ _vte_debug_print (VTE_DEBUG_RINGVIEW, "Ringview: reallocate to %d bidirows\n",
+ m_bidirows_alloc_len);
+ m_bidirows = (BidiRow **) g_realloc (m_bidirows, sizeof (BidiRow *) * m_bidirows_alloc_len);
+ for (; i < m_bidirows_alloc_len; i++) {
+ m_bidirows[i] = new BidiRow();
+ }
+ }
+
m_start = start;
m_len = len;
m_invalid = true;
@@ -134,6 +173,32 @@ VteRowData const* RingView::get_row(vte::grid::row_t row) const
return m_rows[row - m_top];
}
+void RingView::set_enable_bidi(bool enable_bidi)
+{
+ if (G_LIKELY (enable_bidi == m_enable_bidi))
+ return;
+
+ m_enable_bidi = enable_bidi;
+ m_invalid = true;
+
+// FIXME review this
+ if (!m_enable_bidi)
+ pause();
+}
+
+void RingView::set_enable_shaping(bool enable_shaping)
+{
+ if (G_LIKELY (enable_shaping == m_enable_shaping))
+ return;
+
+ m_enable_shaping = enable_shaping;
+ m_invalid = true;
+
+// FIXME review this
+ if (!m_enable_shaping)
+ pause();
+}
+
void RingView::update()
{
if (!m_invalid)
@@ -210,7 +275,11 @@ void RingView::update()
if (!row_data->attr.soft_wrapped || row == m_top + m_rows_len - 1) {
/* Found a paragraph from @top to @row, inclusive. */
- /* Doing BiDi, syntax highlighting etc. come here in the future. */
+ /* Run the BiDi algorithm. */
+ m_bidirunner->paragraph(top, row + 1,
+ m_enable_bidi, m_enable_shaping);
+
+ /* Doing syntax highlighting etc. come here in the future. */
top = row + 1;
}
@@ -219,3 +288,23 @@ void RingView::update()
m_invalid = false;
}
+
+BidiRow const* RingView::get_bidirow(vte::grid::row_t row) const
+{
+ g_assert_cmpint (row, >=, m_start);
+ g_assert_cmpint (row, <, m_start + m_len);
+ g_assert_false (m_invalid);
+ g_assert_false (m_paused);
+
+ return m_bidirows[row - m_start];
+}
+
+/* For internal use by BidiRunner. Get where the BiDi mapping for the given row
+ * needs to be stored, of nullptr if it's a context row. */
+BidiRow* RingView::get_bidirow_writable(vte::grid::row_t row) const
+{
+ if (row < m_start || row >= m_start + m_len)
+ return nullptr;
+
+ return m_bidirows[row - m_start];
+}
diff --git a/src/ringview.hh b/src/ringview.hh
index 0500843b..85c58742 100644
--- a/src/ringview.hh
+++ b/src/ringview.hh
@@ -20,6 +20,7 @@
#include <glib.h>
+#include "bidi.hh"
#include "ring.hh"
#include "vterowdata.hh"
#include "vtetypes.hh"
@@ -29,6 +30,9 @@ namespace vte {
namespace base { // FIXME ???
+class BidiRow;
+class BidiRunner;
+
/*
* RingView provides a "view" to a continuous segment of the Ring (or stream),
* typically the user visible area.
@@ -49,6 +53,8 @@ namespace base { // FIXME ???
* infrastructure one day.
*/
class RingView {
+ friend class BidiRunner;
+
public:
RingView();
~RingView();
@@ -65,6 +71,8 @@ public:
void set_rows(vte::grid::row_t start, vte::grid::row_t len);
void set_width(vte::grid::column_t width);
vte::grid::column_t get_width() { return m_width; }
+ void set_enable_bidi(bool enable_bidi);
+ void set_enable_shaping(bool enable_shaping);
inline void invalidate() { m_invalid = true; }
void update();
@@ -72,6 +80,8 @@ public:
VteRowData const* get_row(vte::grid::row_t row) const;
+ BidiRow const* get_bidirow(vte::grid::row_t row) const;
+
private:
Ring *m_ring;
@@ -79,6 +89,13 @@ private:
int m_rows_len;
int m_rows_alloc_len;
+ bool m_enable_bidi;
+ bool m_enable_shaping;
+ BidiRow **m_bidirows;
+ int m_bidirows_alloc_len;
+
+ BidiRunner *m_bidirunner;
+
vte::grid::row_t m_top; /* the row of the Ring corresponding to m_rows[0] */
vte::grid::row_t m_start;
@@ -89,6 +106,8 @@ private:
bool m_paused;
void resume();
+
+ BidiRow* get_bidirow_writable(vte::grid::row_t row) const;
};
}; /* namespace base */
diff --git a/src/vte.cc b/src/vte.cc
index 0491bb09..82320dc3 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -9033,6 +9033,8 @@ Terminal::ringview_update()
m_ringview.set_ring (m_screen->row_data);
m_ringview.set_rows (first_row, last_row - first_row + 1);
m_ringview.set_width (m_column_count);
+ m_ringview.set_enable_bidi (m_enable_bidi);
+ m_ringview.set_enable_shaping (m_enable_shaping);
m_ringview.update ();
}
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index 338d7f41..63fc193a 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -1823,6 +1823,12 @@ const char *
vte_get_features (void)
{
return
+#ifdef WITH_FRIBIDI
+ "+BIDI"
+#else
+ "-BIDI"
+#endif
+ " "
#ifdef WITH_GNUTLS
"+GNUTLS"
#else
diff --git a/src/vteunistr.cc b/src/vteunistr.cc
index 69c47fe0..319bd6bf 100644
--- a/src/vteunistr.cc
+++ b/src/vteunistr.cc
@@ -162,6 +162,39 @@ _vte_unistr_get_base (vteunistr s)
return (gunichar) s;
}
+void
+_vte_unistr_append_to_gunichars (vteunistr s, GArray *a)
+{
+ if (G_UNLIKELY (s >= VTE_UNISTR_START)) {
+ struct VteUnistrDecomp *decomp;
+ decomp = &DECOMP_FROM_UNISTR (s);
+ _vte_unistr_append_to_gunichars (decomp->prefix, a);
+ s = decomp->suffix;
+ }
+ gunichar val = (gunichar) s;
+ g_array_append_val (a, val);
+}
+
+vteunistr
+_vte_unistr_replace_base (vteunistr s, gunichar c)
+{
+ g_return_val_if_fail (s < unistr_next, s);
+
+ if (G_LIKELY (_vte_unistr_get_base(s) == c))
+ return s;
+
+ GArray *a = g_array_new (FALSE, FALSE, sizeof (gunichar));
+ _vte_unistr_append_to_gunichars (s, a);
+ g_assert_cmpint(a->len, >=, 1);
+
+ s = c;
+ for (glong i = 1; i < a->len; i++)
+ s = _vte_unistr_append_unichar (s, g_array_index (a, gunichar, i));
+
+ g_array_free (a, TRUE);
+ return s;
+}
+
void
_vte_unistr_append_to_string (vteunistr s, GString *gs)
{
diff --git a/src/vteunistr.h b/src/vteunistr.h
index 9bde6b2c..84cb0562 100644
--- a/src/vteunistr.h
+++ b/src/vteunistr.h
@@ -73,6 +73,19 @@ _vte_unistr_append_unistr (vteunistr s, vteunistr t);
gunichar
_vte_unistr_get_base (vteunistr s);
+/**
+ * _vte_unistr_append_to_string:
+ * @s: a #vteunistr
+ * @c: Unicode character to replace the base character of @s.
+ *
+ * Creates a vteunistr value where the base character from @s is
+ * replaced by @c, while the combining characters from @s are carried over.
+ *
+ * Returns: the new #vteunistr value
+ */
+vteunistr
+_vte_unistr_replace_base (vteunistr s, gunichar c);
+
/**
* _vte_unistr_append_to_string:
* @s: a #vteunistr
@@ -84,6 +97,16 @@ _vte_unistr_get_base (vteunistr s);
void
_vte_unistr_append_to_string (vteunistr s, GString *gs);
+/**
+ * _vte_unistr_append_to_gunichars:
+ * @s: a #vteunistr
+ * @a: a #GArray of #gunichar items to append @s to
+ *
+ * Appends @s to @a.
+ **/
+void
+_vte_unistr_append_to_gunichars (vteunistr s, GArray *a);
+
/**
* _vte_unistr_strlen:
* @s: a #vteunistr
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]