/*
* 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 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 .
*/
/*
* 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.)
*
* 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
#if WITH_FRIBIDI
#include
#endif
#include "bidi.hh"
#include "debug.h"
#include "vtedefines.hh"
#include "vteinternal.hh"
#if WITH_FRIBIDI
static_assert (sizeof (FriBidiChar) == sizeof (gunichar), "Unexpected FriBidiChar size");
#endif
/* Don't do Arabic ligatures as per bug 142. */
#define VTE_ARABIC_SHAPING_FLAGS (FRIBIDI_FLAGS_ARABIC & ~FRIBIDI_FLAG_SHAPE_ARAB_LIGA)
using namespace vte::base;
BidiRow::~BidiRow()
{
g_free (m_log2vis);
g_free (m_vis2log);
g_free (m_vis_rtl);
g_free (m_vis_shaped_base_char);
}
void
BidiRow::set_width(vte::grid::column_t width)
{
g_assert_cmpint(width, >=, 0);
if (G_UNLIKELY (width > G_MAXUSHORT)) {
width = G_MAXUSHORT;
}
if (G_UNLIKELY (width > m_width_alloc)) {
uint32_t alloc = m_width_alloc; /* use a wider data type to avoid overflow */
if (alloc == 0) {
alloc = MAX(width, 80);
}
while (width > alloc) {
/* Don't realloc too aggressively. */
alloc = alloc * 5 / 4;
}
if (alloc > G_MAXUSHORT) {
alloc = G_MAXUSHORT;
}
m_width_alloc = alloc;
m_log2vis = (uint16_t *) g_realloc (m_log2vis, sizeof (uint16_t) * m_width_alloc);
m_vis2log = (uint16_t *) g_realloc (m_vis2log, sizeof (uint16_t) * m_width_alloc);
m_vis_rtl = (uint8_t *) g_realloc (m_vis_rtl, sizeof (uint8_t) * m_width_alloc);
m_vis_shaped_base_char = (gunichar *) g_realloc (m_vis_shaped_base_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 (including combining accents, i.e. vteunistr) for the
* given visual position.
*
* The unshaped character (including combining accents, i.e. vteunistr) needs to be
* passed to this method.
*
* m_vis_shaped_base_char stores the shaped base character without combining accents.
* Apply the combining accents here. There's no design rationale behind this, it's
* just much simpler to do it here than during the BiDi algorithm.
*
* In some cases a fully LTR line is denoted by m_width being 0. In other cases a
* character that didn't need shaping is stored as the value 0. In order to provide a
* consistent and straightforward behavior (where the caller doesn't need to special
* case the return value of 0) we need to ask for the unshaped character anyway.
*
* FIXMEegmont This should have a wrapper method in RingView. That could always return
* the actual (potentially shaped) character without asking for the unshaped one.
*/
vteunistr
BidiRow::vis_get_shaped_char(vte::grid::column_t col, vteunistr s) const
{
g_assert_cmpint (col, >=, 0);
if (col >= m_width || m_vis_shaped_base_char[col] == 0)
return s;
return _vte_unistr_replace_base(s, m_vis_shaped_base_char[col]);
}
#if WITH_FRIBIDI
static inline bool
is_arabic(gunichar c)
{
return FRIBIDI_IS_ARABIC (fribidi_get_bidi_type (c));
}
/* 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_sized_new (FALSE, FALSE, sizeof (FriBidiChar), width);
/* Walk in visual order from right to left. */
i = width - 1;
while (i >= 0) {
cell = _vte_row_data_get(row_data, bidirow->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->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 = reinterpret_cast(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) - 1;
if (level == (FriBidiLevel)(-1)) {
/* Error. Skip shaping this word. */
i = j - 1;
continue;
}
/* Shaping. */
fribidi_join_arabic (fribidi_chartypes, count, fribidi_levels, fribidi_joiningtypes);
fribidi_shape_arabic (VTE_ARABIC_SHAPING_FLAGS, fribidi_levels, count, fribidi_joiningtypes, fribidi_chars);
/* If we have the shortcut notation for the trivial LTR mapping, we need to
* expand it to the nontrivial notation, in order to store the shaped character. */
if (bidirow->m_width == 0) {
bidirow->set_width(width);
for (int k = 0; k < width; k++) {
bidirow->m_log2vis[k] = bidirow->m_vis2log[k] = k;
bidirow->m_vis_rtl[k] = false;
bidirow->m_vis_shaped_base_char[k] = 0;
}
}
/* Walk through the Arabic word again. */
j = i;
while (count > 0) {
g_assert_cmpint (j, >=, 0);
cell = _vte_row_data_get(row_data, bidirow->vis2log(j));
c = cell->c;
base = _vte_unistr_get_base(c);
if (*fribidi_chars != base) {
/* Shaping changed the codepoint, store it. */
bidirow->m_vis_shaped_base_char[j] = *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)) {
/* Shortcut notation: a width of 0 means the trivial LTR mapping. */
bidirow->set_width(0);
} else {
/* Set up the explicit RTL mapping. */
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_base_char[i] = 0;
}
}
#if WITH_FRIBIDI
if (do_shaping)
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 (G_UNLIKELY (m_ringview->get_width() > G_MAXUSHORT)) {
/* log2vis and vis2log mappings have 2 bytes per cell.
* Don't do BiDi for extremely wide terminals. */
explicit_paragraph(start, end, false, false);
return;
}
if (!do_bidi) {
explicit_paragraph(start, end, false, do_shaping);
return;
}
#if 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);
}
}
#if 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;
bool has_foreign;
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_sized_new (FALSE, FALSE, sizeof (FriBidiChar), (end - start) * width);
GArray *fribidi_map_array = g_array_sized_new (FALSE, FALSE, sizeof (FriBidiStrIndex), (end - start) * width);
GArray *fribidi_to_term_array = g_array_sized_new (FALSE, FALSE, sizeof (FriBidiStrIndex), (end - start) * width);
/* 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"):
* Aw7io1212oi7w
*
* 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);
for (tl = 0; 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 = reinterpret_cast(fribidi_chars_array->data);
fribidi_map = reinterpret_cast(fribidi_map_array->data);
fribidi_to_term = reinterpret_cast(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) - 1;
if (level == (FriBidiLevel)(-1)) {
/* 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 (VTE_ARABIC_SHAPING_FLAGS, 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 == 0) {
/* Fast and memory saving 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;
}
/* Check if the paragraph has a foreign directionality character. In fact, also catch
* and treat it so if the paragraph has a mixture of multiple embedding levels, even if all
* of them has the same parity (direction). */
if (!rtl) {
/* LTR. We already bailed out above if level == 0, so there must be a character
* with a higher embedding level. */
has_foreign = true;
} else {
/* RTL. Check if any character has a level other than 1. Check the paragraph's
* maximum level as a shortcut, but note that in case of an empty paragraph
* its value is 0 rather than 1. */
if (level <= 1) {
has_foreign = false;
for (int i = 0; i < count; i++) {
if (fribidi_levels[i] != 1) {
has_foreign = true;
break;
}
}
} else {
has_foreign = 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 = has_foreign;
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) - 1;
if (level == (FriBidiLevel)(-1)) {
/* error, what should we do? */
explicit_line (row, rtl, true);
bidirow->m_has_foreign = has_foreign;
continue;
}
if (!rtl && level == 0) {
/* Fast shortcut for LTR-only lines. */
explicit_line (row, false, false);
bidirow->m_has_foreign = has_foreign;
continue;
}
/* Copy to our realm. Proceed in visual order.*/
tv = 0;
if (rtl) {
/* Unused cells on the left for RTL paragraphs */
int unused = width - row_data->len;
for (; tv < unused; tv++) {
bidirow->m_vis2log[tv] = width - 1 - tv;
bidirow->m_vis_rtl[tv] = true;
bidirow->m_vis_shaped_base_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_base_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_base_char[tv] = fribidi_chars[fl];
tv++;
tl++;
}
}
}
if (!rtl) {
/* Unused cells on the right for LTR paragraphs */
g_assert_cmpint (tv, ==, row_data->len);
for (; tv < width; tv++) {
bidirow->m_vis2log[tv] = tv;
bidirow->m_vis_rtl[tv] = false;
bidirow->m_vis_shaped_base_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 {
#if 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;
}