Draw characters in exact positions

QT can't be made to draw monospaced text (if the font does not cooperate),
so avoid combining characters, using a QPainter::drawText() call for each
character.

For bidi text support this change requires konsole to reorder and reshape
the characters. This is done using the ICU library (which QT also uses).

This change allows for some improvements related to text rendering:

- More precise bidi reordering, which is no longer changed by characters'
  attributes and selection.
- underlines drawn separately from the text, allowing for differing
  underline modes (double, curly, dashed, dotted, colored).
- Overriding font for emoji characters.

This commit fixes a few bugs and addresses a lot more:

Feature requests: More standard conforming RTL and various underlines:
BUG: 403729
BUG: 387811

Using non-monospace font:
BUG: 416508
BUG: 452087
BUG: 425973
BUG: 430822
BUG: 442742
BUG: 441037
BUG: 430822



Emoji:
BUG: 440070
CCBUG: 450017
CCBUG: 445846
CCBUG: 453086

Regression: devanagari rendering
CCBUG: 381593
CCBUG: 451716
This commit is contained in:
Matan Ziv-Av
2022-08-06 19:15:42 +03:00
committed by Kurt Hindenburg
parent 4e875cdfdd
commit 76f879cd70
26 changed files with 1313 additions and 431 deletions

View File

@@ -102,6 +102,8 @@ set_package_properties(KF5DocTools PROPERTIES DESCRIPTION
TYPE OPTIONAL
)
find_package(ICU 61.0 COMPONENTS uc i18n REQUIRED)
if(NOT APPLE)
option(WITHOUT_X11 "Build without X11 integration (skips finding X11)" OFF)
if (NOT WITHOUT_X11)

View File

@@ -92,6 +92,7 @@ add_library(konsoleprivate_core STATIC ${konsoleprivate_core_SRCS})
# Needed to link this static lib to shared libs
set_target_properties(konsoleprivate_core PROPERTIES POSITION_INDEPENDENT_CODE ON)
target_link_libraries(konsoleprivate_core ${konsole_LIBS})
target_link_libraries(konsoleprivate_core ICU::uc ICU::i18n)
set(konsolehelpers_SRCS
LabelsAligner.cpp

View File

@@ -17,45 +17,62 @@
using namespace Konsole;
FontDialog::FontDialog(QWidget *parent)
FontDialog::FontDialog(QWidget *parent, bool emoji, const QFont font)
: QDialog(parent)
, _fontChooser(nullptr)
, _showAllFonts(nullptr)
, _buttonBox(nullptr)
, _emoji(emoji)
{
setWindowTitle(i18nc("@title:window", "Select font"));
KFontChooser::DisplayFlag onlyFixed = _emoji ? KFontChooser::FixedFontsOnly : KFontChooser::FixedFontsOnly;
#if KWIDGETSADDONS_VERSION >= QT_VERSION_CHECK(5, 86, 0)
_fontChooser = new KFontChooser(KFontChooser::FixedFontsOnly, this);
_fontChooser = new KFontChooser(onlyFixed, this);
if (_emoji) {
QStringList list = KFontChooser::createFontList(0).filter(QStringLiteral("emoji"), Qt::CaseInsensitive);
_fontChooser->setFont(font);
_fontChooser->setFontListItems(KFontChooser::createFontList(0).filter(QStringLiteral("emoji"), Qt::CaseInsensitive));
_fontChooser->setFont(font);
}
#else
_fontChooser = new KFontChooser(this, KFontChooser::FixedFontsOnly);
_fontChooser = new KFontChooser(this, onlyFixed);
#endif
_showAllFonts = new QCheckBox(i18nc("@action:button", "Show all fonts"), this);
_showAllFontsWarningButton = new QToolButton(this);
_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
_fontChooser->setSampleText(
QStringLiteral("0OQ 1Il!| 5S 8B rnm :; ,. \"'` ~-= ({[<>]})\n"
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\n"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n"
"abcdefghijklmnopqrstuvwxyz"));
_showAllFontsWarningButton->setIcon(QIcon::fromTheme(QStringLiteral("emblem-warning")));
_showAllFontsWarningButton->setAutoRaise(true);
if (_emoji) {
_fontChooser->setSampleText(
/* clang-format off */
QStringLiteral(" 🏴🤘🚬🌍🌎🌏🥆💣🗡🔫⚗️⚛️☢️☣️🌿🎱🏧💉💊🕴️📡🤻🦑🇦🇶👩‍🔬🪤🚱✊🏿🔬🧬🏴‍☠️🤽\n"
"0123456789\n"
"👆🏻 👆🏼 👆🏽 👆🏾 👆🏿 👨‍❤️‍👨 👨‍❤️‍💋‍👨 👩‍👩‍👧‍👧 👩🏻‍🤝‍👨🏿 👨‍👨‍👧‍👦\n"
"🇧🇲 🇨🇭 🇨🇿 🇪🇺 🇬🇱 🇲🇬 🇲🇹 🇸🇿 🇿🇲"));
/* clang-format on */
} else {
_fontChooser->setSampleText(
QStringLiteral("0OQ 1Il!| 5S 8B rnm :; ,. \"'` ~-= ({[<>]})\n"
"!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~\n"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ 0123456789\n"
"abcdefghijklmnopqrstuvwxyz"));
_showAllFontsWarningButton->setIcon(QIcon::fromTheme(QStringLiteral("emblem-warning")));
_showAllFontsWarningButton->setAutoRaise(true);
connect(_fontChooser, &KFontChooser::fontSelected, this, &FontDialog::fontChanged);
connect(_showAllFonts, &QCheckBox::toggled, this, [this](bool enable) {
_fontChooser->setFont(_fontChooser->font(), !enable);
});
connect(_showAllFontsWarningButton, &QToolButton::clicked, this, [this](bool) {
const QString message = i18nc("@info:status",
"By its very nature, a terminal program requires font characters that are equal width (monospace). Any non monospaced "
"font may cause display issues. This should not be necessary except in rare cases.");
const QPoint pos = QPoint(_showAllFonts->width() / 2, _showAllFonts->height());
QWhatsThis::showText(_showAllFonts->mapToGlobal(pos), message, _showAllFonts);
});
connect(_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
connect(_showAllFonts, &QCheckBox::toggled, this, [this](bool enable) {
_fontChooser->setFont(_fontChooser->font(), !enable);
});
connect(_showAllFontsWarningButton, &QToolButton::clicked, this, [this](bool) {
const QString message =
i18nc("@info:status",
"By its very nature, a terminal program requires font characters that are equal width (monospace). Any non monospaced "
"font may cause display issues. This should not be necessary except in rare cases.");
const QPoint pos = QPoint(_showAllFonts->width() / 2, _showAllFonts->height());
QWhatsThis::showText(_showAllFonts->mapToGlobal(pos), message, _showAllFonts);
});
}
auto *showAllFontsLayout = new QHBoxLayout();
showAllFontsLayout->addWidget(_showAllFonts);
@@ -66,11 +83,16 @@ FontDialog::FontDialog(QWidget *parent)
auto *layout = new QVBoxLayout(this);
layout->addWidget(_fontChooser, 1);
layout->addLayout(showAllFontsLayout);
if (!_emoji) {
layout->addLayout(showAllFontsLayout);
}
layout->addWidget(_buttonBox);
connect(_fontChooser, &KFontChooser::fontSelected, this, &FontDialog::fontChanged);
connect(_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
connect(_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
}
void FontDialog::setFont(const QFont &font)
{
_fontChooser->setFont(font, !_showAllFonts->isChecked());
_fontChooser->setFont(font, !_showAllFonts->isChecked() && !_emoji);
}

View File

@@ -21,7 +21,7 @@ class FontDialog : public QDialog
Q_OBJECT
public:
explicit FontDialog(QWidget *parent = nullptr);
explicit FontDialog(QWidget *parent = nullptr, bool emoji = false, const QFont font = QFont());
QFont font() const
{
@@ -37,6 +37,7 @@ private:
QCheckBox *_showAllFonts;
QToolButton *_showAllFontsWarningButton;
QDialogButtonBox *_buttonBox;
bool _emoji;
};
}

View File

@@ -70,7 +70,10 @@ Screen::Screen(int lines, int columns)
, _cuY(0)
, _currentForeground(CharacterColor())
, _currentBackground(CharacterColor())
, _currentRendition(DEFAULT_RENDITION)
, _currentRendition({DEFAULT_RENDITION})
, _ulColorQueueStart(0)
, _ulColorQueueEnd(0)
, _currentULColor(0)
, _topMargin(0)
, _bottomMargin(0)
, _replMode(REPL_None)
@@ -83,7 +86,7 @@ Screen::Screen(int lines, int columns)
, _blockSelectionMode(false)
, _effectiveForeground(CharacterColor())
, _effectiveBackground(CharacterColor())
, _effectiveRendition(DEFAULT_RENDITION)
, _effectiveRendition({DEFAULT_RENDITION})
, _lastPos(-1)
, _lastDrawnChar(0)
, _escapeSequenceUrlExtractor(nullptr)
@@ -286,7 +289,7 @@ void Screen::deleteChars(int n)
_screenLines[_cuY].remove(_cuX, n);
// Append space(s) with current attributes
Character spaceWithCurrentAttrs(' ', _effectiveForeground, _effectiveBackground, _effectiveRendition, 0);
Character spaceWithCurrentAttrs(' ', _effectiveForeground, _effectiveBackground, _effectiveRendition.all, 0);
for (int i = 0; i < n; ++i) {
_screenLines[_cuY].append(spaceWithCurrentAttrs);
@@ -646,7 +649,7 @@ void Screen::reverseRendition(Character &p) const
void Screen::updateEffectiveRendition()
{
_effectiveRendition = _currentRendition;
if ((_currentRendition & RE_REVERSE) != 0) {
if ((_currentRendition.f.reverse) != 0) {
_effectiveForeground = _currentBackground;
_effectiveBackground = _currentForeground;
} else {
@@ -654,12 +657,12 @@ void Screen::updateEffectiveRendition()
_effectiveBackground = _currentBackground;
}
if ((_currentRendition & RE_BOLD) != 0) {
if ((_currentRendition & RE_FAINT) == 0) {
if ((_currentRendition.f.bold) != 0) {
if ((_currentRendition.f.faint) == 0) {
_effectiveForeground.setIntensive();
}
} else {
if ((_currentRendition & RE_FAINT) != 0) {
if ((_currentRendition.f.faint) != 0) {
_effectiveForeground.setFaint();
}
}
@@ -688,7 +691,7 @@ void Screen::copyFromHistory(Character *dest, int startLine, int count) const
if (_selBegin != -1) {
for (int column = 0; column < lastColumn; ++column) {
if (isSelected(column, line)) {
dest[destLineOffset + column].rendition |= RE_SELECTED;
dest[destLineOffset + column].rendition.f.selected = 1;
}
}
}
@@ -720,7 +723,7 @@ void Screen::copyFromScreen(Character *dest, int startLine, int count) const
if (_selBegin != -1) {
for (int column = 0; column < lastColumn; ++column) {
if (isSelected(column, line + historyLines)) {
dest[destLineOffset + column].rendition |= RE_SELECTED;
dest[destLineOffset + column].rendition.f.selected = 1;
}
}
}
@@ -761,7 +764,7 @@ void Screen::getImage(Character *dest, int size, int startLine, int endLine) con
// mark the character at the current cursor position
int cursorIndex = loc(visX, _cuY + linesInHistoryBuffer);
if (getMode(MODE_Cursor) && cursorIndex < _columns * mergedLines) {
dest[cursorIndex].rendition |= RE_CURSOR;
dest[cursorIndex].rendition.f.cursor = 1;
}
}
@@ -947,13 +950,14 @@ void Screen::displayCharacter(uint c)
// putting the cursor one right to the last column of the screen.
int w = Character::width(c);
const QChar::Category category = QChar::category(c);
if (w < 0) {
// Non-printable character
return;
} else if (w == 0) {
const QChar::Category category = QChar::category(c);
if (category != QChar::Mark_NonSpacing && category != QChar::Letter_Other && category != QChar::Other_Format) {
} else if (category == QChar::Mark_SpacingCombining || w == 0 || Character::emoji(c) || c == 0x20E3) {
bool emoji = Character::emoji(c);
if (category != QChar::Mark_SpacingCombining && category != QChar::Mark_NonSpacing && category != QChar::Letter_Other && category != QChar::Other_Format
&& !emoji && c != 0x20E3) {
return;
}
// Find previous "real character" to try to combine with
@@ -980,6 +984,9 @@ void Screen::displayCharacter(uint c)
} while (_screenLines.at(charToCombineWithY).at(charToCombineWithX).isRightHalfOfDoubleWide());
if (!previousChar) {
if (emoji) {
goto notcombine;
}
if (!Hangul::isHangul(c)) {
return;
} else {
@@ -990,24 +997,88 @@ void Screen::displayCharacter(uint c)
Character &currentChar = _screenLines[charToCombineWithY][charToCombineWithX];
if (c == 0x20E3) {
// Combining Enclosing Keycap - only combines with presentation mode #,*,0-9
if ((currentChar.character != 0x23 && currentChar.character != 0x2A && (currentChar.character < '0' || currentChar.character > '9'))
|| (currentChar.flags & EF_EMOJI_REPRESENTATION) == 0) {
// Is this the right thing TODO?
return;
}
}
if (c == 0xFE0F) {
// Emoji presentation - should not be included
currentChar.flags |= EF_EMOJI_REPRESENTATION;
return;
}
if (c == 0x200D) {
// Zero width joiner
currentChar.flags |= EF_EMOJI_REPRESENTATION;
}
if (c >= 0xE0020 && c <= 0xE007F) {
// Tags - used for some flags
currentChar.flags |= EF_EMOJI_REPRESENTATION;
}
if (c >= 0x1f3fb && c <= 0x1f3ff) {
// Emoji modifier Fitzpatrick - changes skin color
uint currentUcs4 = currentChar.character;
if (currentChar.rendition.f.extended == 1) {
ushort extendedCharLength;
const uint *oldChars = ExtendedCharTable::instance.lookupExtendedChar(currentChar.character, extendedCharLength);
currentUcs4 = oldChars[extendedCharLength - 1];
}
if (currentUcs4 < 0x261d || (currentUcs4 > 0x270d && currentUcs4 < 0x1efff) || currentUcs4 > 0x1faff) {
goto notcombine;
}
currentChar.flags |= EF_EMOJI_REPRESENTATION;
} else if (c >= 0x1f1e6 && c <= 0x1f1ff) {
// Regional indicators - flag components
if (currentChar.rendition.f.extended == 1 || currentChar.character < 0x1f1e6 || currentChar.character > 0x1f1ff) {
goto notcombine;
}
currentChar.flags |= EF_EMOJI_REPRESENTATION;
} else if (emoji) {
if (currentChar.rendition.f.extended == 0) {
goto notcombine;
}
ushort extendedCharLength;
const uint *oldChars = ExtendedCharTable::instance.lookupExtendedChar(currentChar.character, extendedCharLength);
if (oldChars[extendedCharLength - 1] != 0x200d) {
goto notcombine;
}
}
if (Hangul::isHangul(c) && !Hangul::combinesWith(currentChar, c)) {
w = 2;
goto notcombine;
}
if ((currentChar.rendition & RE_EXTENDED_CHAR) == 0) {
if (currentChar.rendition.f.extended == 0) {
const uint chars[2] = {currentChar.character, c};
currentChar.rendition |= RE_EXTENDED_CHAR;
currentChar.rendition.f.extended = 1;
auto extChars = [this]() {
return usedExtendedChars();
};
currentChar.character = ExtendedCharTable::instance.createExtendedChar(chars, 2, extChars);
if (category == QChar::Mark_SpacingCombining) {
// ensure current line vector has enough elements
if (_screenLines[_cuY].size() < _cuX + w) {
_screenLines[_cuY].resize(_cuX + w);
}
Character &ch = _screenLines[_cuY][_cuX];
ch.setRightHalfOfDoubleWide();
ch.foregroundColor = _effectiveForeground;
ch.backgroundColor = _effectiveBackground;
ch.rendition = _effectiveRendition;
ch.flags = setRepl(EF_UNREAL, _replMode);
_cuX += 1;
}
} else {
ushort extendedCharLength;
const uint *oldChars = ExtendedCharTable::instance.lookupExtendedChar(currentChar.character, extendedCharLength);
Q_ASSERT(extendedCharLength > 1);
Q_ASSERT(oldChars);
if (((oldChars) != nullptr) && extendedCharLength < 8) {
Q_ASSERT(extendedCharLength > 1);
if (((oldChars) != nullptr) && extendedCharLength < 10) {
Q_ASSERT(extendedCharLength < 65535); // redundant due to above check
auto chars = std::make_unique<uint[]>(extendedCharLength + 1);
std::copy_n(oldChars, extendedCharLength, chars.get());
@@ -1051,7 +1122,10 @@ notcombine:
currentChar.foregroundColor = _effectiveForeground;
currentChar.backgroundColor = _effectiveBackground;
currentChar.rendition = _effectiveRendition;
currentChar.flags = setRepl(EF_REAL, _replMode);
currentChar.flags = setRepl(EF_REAL, _replMode) | SetULColor(0, _currentULColor);
if (Character::emojiPresentation(c)) {
currentChar.flags |= EF_EMOJI_REPRESENTATION;
}
_lastDrawnChar = c;
@@ -1416,13 +1490,19 @@ void Screen::clearEntireLine()
void Screen::setRendition(RenditionFlags rendition)
{
_currentRendition |= rendition;
_currentRendition.all |= rendition;
updateEffectiveRendition();
}
void Screen::setUnderlineType(int type)
{
_currentRendition.f.underline = type;
updateEffectiveRendition();
}
void Screen::resetRendition(RenditionFlags rendition)
{
_currentRendition &= ~rendition;
_currentRendition.all &= ~rendition;
updateEffectiveRendition();
}
@@ -1430,7 +1510,7 @@ void Screen::setDefaultRendition()
{
setForeColor(COLOR_SPACE_DEFAULT, DEFAULT_FORE_COLOR);
setBackColor(COLOR_SPACE_DEFAULT, DEFAULT_BACK_COLOR);
_currentRendition = DEFAULT_RENDITION;
_currentRendition = {DEFAULT_RENDITION};
updateEffectiveRendition();
}
@@ -1456,6 +1536,31 @@ void Screen::setBackColor(int space, int color)
}
}
void Screen::setULColor(int space, int color)
{
CharacterColor col(quint8(space), color);
if (col.isValid()) {
int end = _ulColorQueueEnd;
if (end < _ulColorQueueStart) {
end += 15;
}
for (int i = _ulColorQueueStart; i < end; i++) {
if (col == _ulColors[i % 15]) {
_currentULColor = i % 15 + 1;
return;
}
}
_ulColors[_ulColorQueueEnd] = col;
_currentULColor = _ulColorQueueEnd + 1;
_ulColorQueueEnd = (_ulColorQueueEnd + 1) % 15;
if (_ulColorQueueEnd == _ulColorQueueStart) {
_ulColorQueueStart = (_ulColorQueueStart + 1) % 15;
}
} else {
_currentULColor = 0;
}
}
void Screen::clearSelection()
{
_selBottomRight = -1;
@@ -1920,7 +2025,6 @@ int Screen::copyLineToStream(int line,
int p = 0;
for (int i = 0; i < count; i++) {
Character c = characterBuffer[spacesCount + i];
// fprintf(stderr, "copy %i %i %i\n", i, c.character, c.flags);
if (((options & ExcludePrompt) != 0 && (c.flags & EF_REPL) == EF_REPL_PROMPT)
|| ((options & ExcludeInput) != 0 && (c.flags & EF_REPL) == EF_REPL_INPUT)
|| ((options & ExcludeOutput) != 0 && (c.flags & EF_REPL) == EF_REPL_OUTPUT)) {

View File

@@ -314,6 +314,7 @@ public:
* @see Character::rendition
*/
void setRendition(RenditionFlags rendition);
void setUnderlineType(int type);
/**
* Disables the given @p rendition flag. Rendition flags control the appearance
* of characters on the screen.
@@ -346,6 +347,13 @@ public:
*/
void setDefaultRendition();
void setULColor(int space, int color);
CharacterColor const *ulColorTable() const
{
return _ulColors;
};
/** Returns the column which the cursor is positioned at. */
int getCursorX() const;
/** Returns the line which the cursor is positioned on. */
@@ -642,7 +650,7 @@ public:
for (int i = 0; i < _lines; ++i) {
const ImageLine &il = _screenLines[i];
for (int j = 0; j < il.length(); ++j) {
if (il[j].rendition & RE_EXTENDED_CHAR) {
if (il[j].rendition.f.extended) {
result << il[j].character;
}
}
@@ -800,7 +808,12 @@ private:
// cursor color and rendition info
CharacterColor _currentForeground;
CharacterColor _currentBackground;
RenditionFlags _currentRendition;
RenditionFlagsC _currentRendition;
CharacterColor _ulColors[15];
int _ulColorQueueStart;
int _ulColorQueueEnd;
int _currentULColor;
// margins ----------------
int _topMargin;
@@ -829,7 +842,7 @@ private:
// effective colors and rendition ------------
CharacterColor _effectiveForeground; // These are derived from
CharacterColor _effectiveBackground; // the cu_* variables above
RenditionFlags _effectiveRendition; // to speed up operation
RenditionFlagsC _effectiveRendition; // to speed up operation
class SavedState
{
@@ -838,7 +851,7 @@ private:
: cursorColumn(0)
, cursorLine(0)
, originMode(0)
, rendition(0)
, rendition({0})
, foreground(CharacterColor())
, background(CharacterColor())
{
@@ -847,7 +860,7 @@ private:
int cursorColumn;
int cursorLine;
int originMode;
RenditionFlags rendition;
RenditionFlagsC rendition;
CharacterColor foreground;
CharacterColor background;
};

View File

@@ -511,7 +511,7 @@ void Vt102Emulation::csi_dispatch(const uint cc)
processToken(token_csi_pq(cc), 0, 0);
} else if (tokenBufferPos != 0 && tokenBuffer[0] == '>') {
processToken(token_csi_pg(cc), 0, 0);
} else if (cc == 'm' && !params.sub[i].count && params.count - i >= 4 && (params.value[i] == 38 || params.value[i] == 48)
} else if (cc == 'm' && !params.sub[i].count && params.count - i >= 4 && (params.value[i] == 38 || params.value[i] == 48 || params.value[i] == 58)
&& params.value[i + 1] == 2) {
// ESC[ ... 48;2;<red>;<green>;<blue> ... m -or- ESC[ ... 38;2;<red>;<green>;<blue> ... m
i += 2;
@@ -519,22 +519,27 @@ void Vt102Emulation::csi_dispatch(const uint cc)
COLOR_SPACE_RGB,
(params.value[i] << 16) | (params.value[i + 1] << 8) | params.value[i + 2]);
i += 2;
} else if (cc == 'm' && params.sub[i].count >= 5 && (params.value[i] == 38 || params.value[i] == 48) && params.sub[i].value[1] == 2) {
} else if (cc == 'm' && params.sub[i].count >= 5 && (params.value[i] == 38 || params.value[i] == 48 || params.value[i] == 58)
&& params.sub[i].value[1] == 2) {
// ESC[ ... 48:2:<id>:<red>:<green>:<blue> ... m -or- ESC[ ... 38:2:<id>:<red>:<green>:<blue> ... m
processToken(token_csi_ps(cc, params.value[i]),
COLOR_SPACE_RGB,
(params.sub[i].value[3] << 16) | (params.sub[i].value[4] << 8) | params.sub[i].value[5]);
} else if (cc == 'm' && params.sub[i].count == 4 && (params.value[i] == 38 || params.value[i] == 48) && params.sub[i].value[1] == 2) {
} else if (cc == 'm' && params.sub[i].count == 4 && (params.value[i] == 38 || params.value[i] == 48 || params.value[i] == 58)
&& params.sub[i].value[1] == 2) {
// ESC[ ... 48:2:<red>:<green>:<blue> ... m -or- ESC[ ... 38:2:<red>:<green>:<blue> ... m
processToken(token_csi_ps(cc, params.value[i]),
COLOR_SPACE_RGB,
(params.sub[i].value[2] << 16) | (params.sub[i].value[3] << 8) | params.sub[i].value[4]);
} else if (cc == 'm' && !params.sub[i].count && params.count - i >= 2 && (params.value[i] == 38 || params.value[i] == 48)
} else if (cc == 'm' && params.sub[i].count == 1 && params.value[i] == 4) {
processToken(token_csi_ps(cc, params.value[i]), params.sub[i].value[1], 1);
} else if (cc == 'm' && !params.sub[i].count && params.count - i >= 2 && (params.value[i] == 38 || params.value[i] == 48 || params.value[i] == 58)
&& params.value[i + 1] == 5) {
// ESC[ ... 48;5;<index> ... m -or- ESC[ ... 38;5;<index> ... m
i += 2;
processToken(token_csi_ps(cc, params.value[i - 2]), COLOR_SPACE_256, params.value[i]);
} else if (cc == 'm' && params.sub[i].count >= 2 && (params.value[i] == 38 || params.value[i] == 48) && params.sub[i].value[1] == 5) {
} else if (cc == 'm' && params.sub[i].count >= 2 && (params.value[i] == 38 || params.value[i] == 48 || params.value[i] == 58)
&& params.sub[i].value[1] == 5) {
// ESC[ ... 48:5:<index> ... m -or- ESC[ ... 38:5:<index> ... m
processToken(token_csi_ps(cc, params.value[i]), COLOR_SPACE_256, params.sub[i].value[2]);
} else if (_nIntermediate == 0) {
@@ -985,16 +990,16 @@ void Vt102Emulation::processChecksumRequest([[maybe_unused]] int crargc, int cra
// XXX: Apparently, VT520 uses 0x00 for uninitialized cells, konsole can't tell uninitialized cells from spaces
Character c = image[y * _currentScreen->getColumns() + x];
if (c.rendition & RE_CONCEAL) {
if (c.rendition.f.conceal) {
checksum += 0x20; // don't reveal secrets
} else {
checksum += c.character;
}
checksum += (c.rendition & RE_BOLD) / RE_BOLD * 0x80;
checksum += (c.rendition & RE_BLINK) / RE_BLINK * 0x40;
checksum += (c.rendition & RE_REVERSE) / RE_REVERSE * 0x20;
checksum += (c.rendition & RE_UNDERLINE) / RE_UNDERLINE * 0x10;
checksum += c.rendition.f.bold * 0x80;
checksum += c.rendition.f.blink * 0x40;
checksum += c.rendition.f.reverse * 0x20;
checksum += !!(c.rendition.all & RE_UNDERLINE_MASK) * 0x10;
}
}
@@ -1353,7 +1358,13 @@ void Vt102Emulation::processToken(int token, int p, int q)
case token_csi_ps('m', 1) : _currentScreen-> setRendition (RE_BOLD ); break; //VT100
case token_csi_ps('m', 2) : _currentScreen-> setRendition (RE_FAINT ); break;
case token_csi_ps('m', 3) : _currentScreen-> setRendition (RE_ITALIC ); break; //VT100
case token_csi_ps('m', 4) : _currentScreen-> setRendition (RE_UNDERLINE); break; //VT100
case token_csi_ps('m', 4) :
if (q == 1) {
_currentScreen->setUnderlineType(p);
} else {
_currentScreen->setUnderlineType(RE_UNDERLINE);
}
break; //VT100
case token_csi_ps('m', 5) : _currentScreen-> setRendition (RE_BLINK ); break; //VT100
case token_csi_ps('m', 7) : _currentScreen-> setRendition (RE_REVERSE ); break;
case token_csi_ps('m', 8) : _currentScreen-> setRendition (RE_CONCEAL ); break;
@@ -1362,11 +1373,11 @@ void Vt102Emulation::processToken(int token, int p, int q)
case token_csi_ps('m', 10) : /* IGNORED: mapping related */ break; //LINUX
case token_csi_ps('m', 11) : /* IGNORED: mapping related */ break; //LINUX
case token_csi_ps('m', 12) : /* IGNORED: mapping related */ break; //LINUX
case token_csi_ps('m', 21) : _currentScreen->resetRendition (RE_BOLD ); break;
case token_csi_ps('m', 21) : _currentScreen->setUnderlineType(RE_UNDERLINE_DOUBLE); break;
case token_csi_ps('m', 22) : _currentScreen->resetRendition (RE_BOLD );
_currentScreen->resetRendition (RE_FAINT ); break;
case token_csi_ps('m', 23) : _currentScreen->resetRendition (RE_ITALIC ); break; //VT100
case token_csi_ps('m', 24) : _currentScreen->resetRendition (RE_UNDERLINE); break;
case token_csi_ps('m', 24) : _currentScreen->resetRendition (RE_UNDERLINE_MASK); break;
case token_csi_ps('m', 25) : _currentScreen->resetRendition (RE_BLINK ); break;
case token_csi_ps('m', 27) : _currentScreen->resetRendition (RE_REVERSE ); break;
case token_csi_ps('m', 28) : _currentScreen->resetRendition (RE_CONCEAL ); break;
@@ -1399,6 +1410,10 @@ void Vt102Emulation::processToken(int token, int p, int q)
case token_csi_ps('m', 49) : _currentScreen->setBackColor (COLOR_SPACE_DEFAULT, 1); break;
case token_csi_ps('m', 58) : _currentScreen->setULColor (p, q); break;
case token_csi_ps('m', 59) : _currentScreen->setULColor (COLOR_SPACE_UNDEFINED, 0); break;
case token_csi_ps('m', 90) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 8); break;
case token_csi_ps('m', 91) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 9); break;
case token_csi_ps('m', 92) : _currentScreen->setForeColor (COLOR_SPACE_SYSTEM, 10); break;

View File

@@ -10,33 +10,4 @@
#include <QTest>
#include <cstdint>
void Konsole::CharacterTest::testCanBeGrouped()
{
// Test for Capital Latin
for (uint32_t c = U'A'; c <= U'Z'; ++c) {
Konsole::Character uppercase_latin_char(c);
const bool res = uppercase_latin_char.canBeGrouped(false, false);
QVERIFY(res);
}
// Test for Non Capital Latin
for (uint32_t c = U'a'; c <= U'z'; ++c) {
Konsole::Character lowercase_latin_char(c);
const bool res = lowercase_latin_char.canBeGrouped(false, false);
QVERIFY(res);
}
// Test for Braille
for (uint32_t c = 0x2800; c <= 0x28ff; ++c) {
Konsole::Character braille_char(c);
const bool res = braille_char.canBeGrouped(false, false);
QCOMPARE(res, false);
// TEST FOR REGRESSION - This was failing with different bidirectional modes.
Konsole::Character braille_char_bidi_enabled(c);
const bool res2 = braille_char_bidi_enabled.canBeGrouped(true, false);
QCOMPARE(res2, false);
}
}
QTEST_GUILESS_MAIN(Konsole::CharacterTest)

View File

@@ -15,7 +15,6 @@ class CharacterTest : public QObject
Q_OBJECT
private Q_SLOTS:
void testCanBeGrouped();
};
}

View File

@@ -28,7 +28,7 @@ void TerminalCharacterDecoderTest::convertToCharacter(Character *charResult, con
}
for (int i = 0; i < text.size(); ++i) {
charResult[i] = Character(text.at(i).unicode());
charResult[i].rendition = renditions.at(i);
charResult[i].rendition.all = renditions.at(i);
}
}

View File

@@ -22,7 +22,27 @@ namespace Konsole
{
typedef quint32 LineProperty;
#pragma pack(1)
typedef union {
quint16 all;
struct {
uint bold : 1;
uint blink : 1;
uint transparent : 1;
uint reverse : 1;
uint italic : 1;
uint cursor : 1;
uint extended : 1;
uint faint : 1;
uint strikeout : 1;
uint conceal : 1;
uint overline : 1;
uint selected : 1;
uint underline : 4;
} f;
} RenditionFlagsC;
typedef quint16 RenditionFlags;
#pragma pack()
typedef quint16 ExtraFlags;
@@ -41,7 +61,7 @@ const int LINE_LEN_MASK = (0xfff << LINE_LEN_POS);
const RenditionFlags DEFAULT_RENDITION = 0;
const RenditionFlags RE_BOLD = (1 << 0);
const RenditionFlags RE_BLINK = (1 << 1);
const RenditionFlags RE_UNDERLINE = (1 << 2);
const RenditionFlags RE_TRANSPARENT = (1 << 2);
const RenditionFlags RE_REVERSE = (1 << 3); // Screen only
const RenditionFlags RE_ITALIC = (1 << 4);
const RenditionFlags RE_CURSOR = (1 << 5);
@@ -51,7 +71,17 @@ const RenditionFlags RE_STRIKEOUT = (1 << 8);
const RenditionFlags RE_CONCEAL = (1 << 9);
const RenditionFlags RE_OVERLINE = (1 << 10);
const RenditionFlags RE_SELECTED = (1 << 11);
const RenditionFlags RE_TRANSPARENT = (1 << 12);
const RenditionFlags RE_UNDERLINE_MASK = (15 << 12);
const RenditionFlags RE_UNDERLINE_NONE = 0;
const RenditionFlags RE_UNDERLINE = 1;
const RenditionFlags RE_UNDERLINE_DOUBLE= 2;
const RenditionFlags RE_UNDERLINE_CURL = 3;
const RenditionFlags RE_UNDERLINE_DOT = 4;
const RenditionFlags RE_UNDERLINE_DASH = 5;
// Masks of flags that matter for drawing what is below/above the text
const RenditionFlags RE_MASK_UNDER = RE_TRANSPARENT | RE_REVERSE | RE_CURSOR | RE_SELECTED;
const RenditionFlags RE_MASK_ABOVE = RE_TRANSPARENT | RE_REVERSE | RE_CURSOR | RE_SELECTED | RE_STRIKEOUT | RE_CONCEAL | RE_OVERLINE | RE_UNDERLINE_MASK;
const ExtraFlags EF_UNREAL = 0;
const ExtraFlags EF_REAL = (1 << 0);
@@ -60,7 +90,11 @@ const ExtraFlags EF_REPL_NONE = (0 << 1);
const ExtraFlags EF_REPL_PROMPT = (1 << 1);
const ExtraFlags EF_REPL_INPUT = (2 << 1);
const ExtraFlags EF_REPL_OUTPUT = (3 << 1);
const ExtraFlags EF_UNDERLINE_COLOR = (15 << 3);
const ExtraFlags EF_UNDERLINE_COLOR_1 = (1 << 3);
const ExtraFlags EF_EMOJI_REPRESENTATION = (1 << 7);
#define SetULColor(f, m) (((f) & ~EF_UNDERLINE_COLOR) | ((m) * EF_UNDERLINE_COLOR_1))
#define setRepl(f, m) (((f) & ~EF_REPL) | ((m) * EF_REPL_PROMPT))
#define LineLength(f) static_cast<int>(((f) & LINE_LEN_MASK) >> LINE_LEN_POS)
#define SetLineLength(f, l) (((f) & ~LINE_LEN_MASK) | ((l) << LINE_LEN_POS))
@@ -90,7 +124,7 @@ public:
RenditionFlags _r = DEFAULT_RENDITION,
ExtraFlags _flags = EF_REAL)
: character(_c)
, rendition(_r)
, rendition({_r})
, foregroundColor(_f)
, backgroundColor(_b)
, flags(_flags)
@@ -106,7 +140,7 @@ public:
uint character;
/** A combination of RENDITION flags which specify options for drawing the character. */
RenditionFlags rendition;
RenditionFlagsC rendition;
/** The foreground color used to draw this character. */
CharacterColor foregroundColor;
@@ -114,14 +148,10 @@ public:
/** The color used to draw this character's background. */
CharacterColor backgroundColor;
/** Indicate whether this character really exists, or exists simply as place holder.
*
* TODO: this boolean filed can be further improved to become a enum filed, which
* indicates different roles:
*
* RealCharacter: a character which really exists
* PlaceHolderCharacter: a character which exists as place holder
* TabStopCharacter: a special place holder for HT("\t")
/** Flags which are not specific to rendition
* Indicate whether this character really exists, or exists simply as place holder.
* REPL mode
* Character type (script, etc.)
*/
ExtraFlags flags;
@@ -144,7 +174,7 @@ public:
constexpr bool isSpace() const
{
if (rendition & RE_EXTENDED_CHAR) {
if (rendition.f.extended) {
return false;
} else {
return QChar(character).isSpace();
@@ -161,6 +191,79 @@ public:
return flags & EF_REPL;
}
static constexpr int emojiPresentation1Start = 8986;
static constexpr int emojiPresentation1End = 11093;
static constexpr int emojiPresentation2Start = 126980;
static constexpr int emojiPresentation2End = 129782;
/* clang-format off */
static constexpr uint64_t emojiPresentation1Bits[] = {
0x3, 0x0, 0x0, 0x2478000, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0xc00001800000000, 0x3ffc00000000000, 0x200002000000000, 0x4100c1800030080, 0x308090b010000,
0x2e14000000004000, 0x3800000000000000, 0x2000400000, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x840000000000006
};
static constexpr uint64_t emojiPresentation2Bits[] = {
0x1, 0x0, 0x0, 0x800, 0x0, 0x0, 0x7fe400, 0x2ffffffc00000000,
0x77c80000400000, 0x3000, 0x0, 0xf000000000000000,
0xfffbfe001fffffff, 0xfdffffffffffffff, 0xfffffffff000ffff, 0xfff11ffff000f87f,
0xd7ffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xf9ffffffffffffff,
0x3ffffffffffffff, 0x40000ffffff780, 0x100060000, 0xff80000000000000,
0xffffffffffffffff, 0xf000000000000fff, 0xffffffffffffffff, 0x1ff01800e0e7103, 0x0, 0x0, 0x0, 0x10fff0000000,
0x0, 0x0, 0x0, 0x0, 0xff7fffffffffff00, 0xfffffffffffffffb, 0xffffffffffffffff, 0xfffffffffffffff,
0x0, 0xf1f1f00000000000, 0xf07ff1fffffff007, 0x7f00ff03ff003
};
/* clang-format on */
static bool emojiPresentation(uint ucs4)
{
if (ucs4 >= emojiPresentation1Start && ucs4 <= emojiPresentation1End) {
return (emojiPresentation1Bits[(ucs4 - emojiPresentation1Start) / 64] & (1l << ((ucs4 - emojiPresentation1Start) % 64))) != 0;
} else if (ucs4 >= emojiPresentation2Start && ucs4 <= emojiPresentation2End) {
return (emojiPresentation2Bits[(ucs4 - emojiPresentation2Start) / 64] & (1l << ((ucs4 - emojiPresentation2Start) % 64))) != 0;
}
return false;
}
static constexpr int emoji1Start = 8252;
static constexpr int emoji1End = 12953;
static constexpr int emoji2Start = 126980;
static constexpr int emoji2End = 129782;
/* clang-format off */
static constexpr uint64_t emoji1Bits[] = {
0x2001, 0x0, 0x0, 0x2000004000000000, 0x0, 0x60003f000000, 0x0, 0x0,
0x0, 0x0, 0x0, 0x1000c0000000, 0x0, 0x0, 0x70ffe00000080000, 0x0,
0x0, 0x0, 0x40, 0x0, 0x0, 0x400c00000000000, 0x8000000000000010, 0x700c44d2132401f7,
0x8000169800fff050, 0x30c831afc0000c, 0x7bf0600001ac1306, 0x1801022054bf242, 0x1800b850900, 0x1000200e000000, 0x8, 0x0,
0x0, 0x0, 0x0, 0x300000000000000, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x180000e00, 0x2100000, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x10000000000000,
0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
0x0, 0x28000000
};
static constexpr uint64_t emoji2Bits[] = {
0x1, 0x0, 0x0, 0x800, 0x0, 0xc00300000000000, 0x7fe400, 0x6ffffffc00000000,
0x7fc80000400000, 0x3000, 0x0, 0xf000000000000000,
0xffffffff3fffffff, 0xffffffffffffffff, 0xfffffffffcecffff, 0xfffb9fffffffffff,
0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xfbffffffffffffff,
0x3ffffffffffffff, 0x7f980ffffff7e0, 0xc1006013000613c8, 0xffc08810a700e001,
0xffffffffffffffff, 0xf000000000000fff, 0xffffffffffffffff, 0x1ff91a3fe0e7f83, 0x0, 0x0, 0x0, 0x10fff0000000,
0x0, 0x0, 0x0, 0x0, 0xff7fffffffffff00, 0xfffffffffffffffb, 0xffffffffffffffff, 0xfffffffffffffff,
0x0, 0xf1f1f00000000000, 0xf07ff1fffffff007, 0x7f00ff03ff003
};
/* clang-format on */
static bool emoji(uint ucs4)
{
if (ucs4 >= emoji1Start && ucs4 <= emoji1End) {
return (emoji1Bits[(ucs4 - emoji1Start) / 64] & (1l << ((ucs4 - emoji1Start) % 64))) != 0;
} else if (ucs4 >= emoji2Start && ucs4 <= emoji2End) {
return (emoji2Bits[(ucs4 - emoji2Start) / 64] & (1l << ((ucs4 - emoji2Start) % 64))) != 0;
}
return false;
}
static int width(uint ucs4)
{
// ASCII
@@ -205,22 +308,9 @@ public:
return stringWidth(ucs4Str.constData(), ucs4Str.length());
}
inline bool canBeGrouped(bool bidirectionalEnabled, bool isDoubleWidth) const
{
if (character <= 0x7e) {
return true;
}
if (QChar::script(character) == QChar::Script_Braille) {
return false;
}
return (rendition & RE_EXTENDED_CHAR) || (bidirectionalEnabled && !isDoubleWidth);
}
inline uint baseCodePoint() const
{
if (rendition & RE_EXTENDED_CHAR) {
if (rendition.f.extended) {
ushort extendedCharLength = 0;
const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(character, extendedCharLength);
// FIXME: Coverity-Dereferencing chars, which is known to be nullptr
@@ -247,7 +337,7 @@ public:
inline bool hasSameRendition(Character lhs) const
{
return (lhs.rendition & ~RE_EXTENDED_CHAR) == (rendition & ~RE_EXTENDED_CHAR) && lhs.flags == flags;
return (lhs.rendition.all & ~RE_EXTENDED_CHAR) == (rendition.all & ~RE_EXTENDED_CHAR) && lhs.flags == flags;
};
inline bool hasSameLineDrawStatus(Character lhs) const
@@ -284,7 +374,7 @@ constexpr bool operator!=(const Character &a, const Character &b)
constexpr bool Character::equalsFormat(const Character &other) const
{
return backgroundColor == other.backgroundColor && foregroundColor == other.foregroundColor && rendition == other.rendition;
return backgroundColor == other.backgroundColor && foregroundColor == other.foregroundColor && rendition.all == other.rendition.all;
}
}
Q_DECLARE_TYPEINFO(Konsole::Character, Q_MOVABLE_TYPE);

View File

@@ -133,7 +133,7 @@ bool Hangul::combinesWith(Character prevChar, uint c)
{
Hangul::SyllablePos syllablePos = Hangul::NotInSyllable;
if ((prevChar.rendition & RE_EXTENDED_CHAR) == 0) {
if (prevChar.rendition.f.extended == 0) {
const uint prev = prevChar.character;
updateHangulSyllablePos(syllablePos, prev);
} else {

View File

@@ -100,13 +100,14 @@ void HTMLDecoder::decodeLine(const Character *const characters, int count, LineP
for (int i = 0; i < count; i++) {
// check if appearance of character is different from previous char
if (characters[i].rendition != _lastRendition || characters[i].foregroundColor != _lastForeColor || characters[i].backgroundColor != _lastBackColor) {
if (characters[i].rendition.all != _lastRendition || characters[i].foregroundColor != _lastForeColor
|| characters[i].backgroundColor != _lastBackColor) {
if (_innerSpanOpen) {
closeSpan(text);
_innerSpanOpen = false;
}
_lastRendition = characters[i].rendition;
_lastRendition = characters[i].rendition.all;
_lastForeColor = characters[i].foregroundColor;
_lastBackColor = characters[i].backgroundColor;
@@ -118,7 +119,7 @@ void HTMLDecoder::decodeLine(const Character *const characters, int count, LineP
style.append(QLatin1String("font-weight:bold;"));
}
if ((_lastRendition & RE_UNDERLINE) != 0) {
if ((_lastRendition & RE_UNDERLINE_MASK) != 0) {
style.append(QLatin1String("font-decoration:underline;"));
}
@@ -140,7 +141,7 @@ void HTMLDecoder::decodeLine(const Character *const characters, int count, LineP
// output current character
if (spaceCount < 2) {
if ((characters[i].rendition & RE_EXTENDED_CHAR) != 0) {
if ((characters[i].rendition.all & RE_EXTENDED_CHAR) != 0) {
ushort extendedCharLength = 0;
const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(characters[i].character, extendedCharLength);
if (chars != nullptr) {

View File

@@ -110,7 +110,7 @@ void PlainTextDecoder::decodeLine(const Character *const characters, int count,
characterBuffer.reserve(count);
for (int i = start; i < outputCount;) {
if ((characters[i].rendition & RE_EXTENDED_CHAR) != 0) {
if (characters[i].rendition.f.extended != 0) {
ushort extendedCharLength = 0;
const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(characters[i].character, extendedCharLength);
if (chars != nullptr) {

View File

@@ -90,6 +90,7 @@ const std::vector<Profile::PropertyInfo> Profile::DefaultProperties = {
{DimValue, "DimmValue", APPEARANCE_GROUP, 128},
{DimWhenInactive, "DimWhenInactive", GENERAL_GROUP, false},
{InvertSelectionColors, "InvertSelectionColors", GENERAL_GROUP, false},
{EmojiFont, "EmojiFont", APPEARANCE_GROUP, QFont()},
// Keyboard
#ifdef Q_OS_MACOS
@@ -112,6 +113,8 @@ const std::vector<Profile::PropertyInfo> Profile::DefaultProperties = {
{BlinkingTextEnabled, "BlinkingTextEnabled", TERMINAL_GROUP, true},
{FlowControlEnabled, "FlowControlEnabled", TERMINAL_GROUP, true},
{BidiRenderingEnabled, "BidiRenderingEnabled", TERMINAL_GROUP, true},
{BidiLineLTR, "BidiLineLTR", TERMINAL_GROUP, true},
{BidiTableDirOverride, "BidiTableDirOverride", TERMINAL_GROUP, true},
{BlinkingCursorEnabled, "BlinkingCursorEnabled", TERMINAL_GROUP, false},
{BellMode, "BellMode", TERMINAL_GROUP, Enum::NotifyBell},
{VerticalLine, "VerticalLine", TERMINAL_GROUP, false},

View File

@@ -160,6 +160,14 @@ public:
* text display
*/
BidiRenderingEnabled,
/** (bool) If true, lines are always LTR. Otherwise, decided by
* first strong charachter as defined by the BiDi algorithm.
*/
BidiLineLTR,
/** (bool) Specifies whether the table drawing characters will be
* considered strong LTR by the BiDi algorithm.
*/
BidiTableDirOverride,
/** (bool) Specifies whether text in terminal displays is allowed
* to blink.
*/
@@ -363,6 +371,9 @@ public:
/** (bool) If true, move cursor with Left/Right keys when mouse clicks in input area
*/
SemanticInputClick,
/** (QFont) Emoji font override
*/
EmojiFont,
};
Q_ENUM(Property)
@@ -569,6 +580,12 @@ public:
return property<QFont>(Profile::Font);
}
/** Convenience method for property<QFont>(Profile::EmojiFont) */
QFont emojiFont() const
{
return property<QFont>(Profile::EmojiFont);
}
/** Convenience method for property<QString>(Profile::ColorScheme) */
QString colorScheme() const
{
@@ -599,6 +616,18 @@ public:
return property<bool>(Profile::BidiRenderingEnabled);
}
/** Convenience method for property<bool>(Profile::BidiLineLTR) */
bool bidiLineLTR() const
{
return property<bool>(Profile::BidiLineLTR);
}
/** Convenience method for property<bool>(Profile::BidiTableDirOverride) */
bool bidiTableDirOverride() const
{
return property<bool>(Profile::BidiTableDirOverride);
}
/** Convenience method for property<bool>(Profile::LineSpacing) */
int lineSpacing() const
{

View File

@@ -78,6 +78,12 @@
#include "TerminalPainter.h"
#include "TerminalScrollBar.h"
#include "unicode/localpointer.h"
#include "unicode/ubidi.h"
#include "unicode/uchar.h"
#include "unicode/ushape.h"
#include "unicode/utypes.h"
using namespace Konsole;
inline int TerminalDisplay::loc(int x, int y) const
@@ -148,6 +154,14 @@ void TerminalDisplay::setScreenWindow(ScreenWindow *window)
}
}
static UCharDirection BiDiClass([[maybe_unused]] const void *context, UChar32 c)
{
if (c >= 0x2500 && c <= 0x25ff) {
return U_LEFT_TO_RIGHT;
}
return U_CHAR_DIRECTION_COUNT;
};
/* ------------------------------------------------------------------------- */
/* */
/* Accessibility */
@@ -511,7 +525,7 @@ void TerminalDisplay::updateImage()
if (!_resizing) { // not while _resizing, we're expecting a paintEvent
for (x = 0; x < columnsToUpdate; ++x) {
_hasTextBlinker |= (newLine[x].rendition & RE_BLINK);
_hasTextBlinker |= newLine[x].rendition.f.blink;
// Start drawing if this character or the next one differs.
// We also take the next one into account to handle the situation
@@ -522,7 +536,7 @@ void TerminalDisplay::updateImage()
}
const bool lineDraw = LineBlockCharacters::canDraw(newLine[x + 0].character);
const bool doubleWidth = (x + 1 == columnsToUpdate) ? false : newLine[x + 1].isRightHalfOfDoubleWide();
const RenditionFlags cr = newLine[x].rendition;
const RenditionFlags cr = newLine[x].rendition.all;
const CharacterColor clipboard = newLine[x].backgroundColor;
if (newLine[x].foregroundColor != cf) {
cf = newLine[x].foregroundColor;
@@ -537,7 +551,7 @@ void TerminalDisplay::updateImage()
const bool nextIsDoubleWidth = (x + len + 1 == columnsToUpdate) ? false : newLine[x + len + 1].isRightHalfOfDoubleWide();
if (ch.foregroundColor != cf || ch.backgroundColor != clipboard || (ch.rendition & ~RE_EXTENDED_CHAR) != (cr & ~RE_EXTENDED_CHAR)
if (ch.foregroundColor != cf || ch.backgroundColor != clipboard || (ch.rendition.all & ~RE_EXTENDED_CHAR) != (cr & ~RE_EXTENDED_CHAR)
|| (dirtyMask[x + len] == 0) || LineBlockCharacters::canDraw(ch.character) != lineDraw || nextIsDoubleWidth != doubleWidth) {
break;
}
@@ -683,7 +697,7 @@ void TerminalDisplay::paintEvent(QPaintEvent *pe)
paint.setRenderHint(QPainter::TextAntialiasing, _terminalFont->antialiasText());
for (const QRect &rect : qAsConst(dirtyImageRegion)) {
_terminalPainter->drawContents(_image, paint, rect, false, _imageSize, _bidiEnabled, _lineProperties);
_terminalPainter->drawContents(_image, paint, rect, false, _imageSize, _bidiEnabled, _lineProperties, _screenWindow->screen()->ulColorTable());
}
if (screenWindow()->currentResultLine() != -1) {
@@ -1479,6 +1493,19 @@ QPair<int, int> TerminalDisplay::getCharacterPosition(const QPoint &widgetPoint,
int column =
qBound(0, (widgetPoint.x() + xOffset - contentsRect().left() - _contentRect.left()) / _terminalFont->fontWidth() / (doubleWidth ? 2 : 1), columnMax);
// Visual column to logical
if (_bidiEnabled && column < _usedColumns) {
#define MAX_LINE_WIDTH 1024
int log2line[MAX_LINE_WIDTH];
int line2log[MAX_LINE_WIDTH];
uint16_t shapemap[MAX_LINE_WIDTH];
int32_t vis2line[MAX_LINE_WIDTH];
const int pos = loc(0, line);
QString line;
bidiMap(_image + pos, line, log2line, line2log, shapemap, vis2line, false);
column = line2log[vis2line[column]];
}
return qMakePair(line, column);
}
@@ -1874,7 +1901,7 @@ QPoint TerminalDisplay::findWordEnd(const QPoint &pnt)
out:
y -= curLine;
// In word selection mode don't select @ (64) if at end of word.
if (((image[j].rendition & RE_EXTENDED_CHAR) == 0) && (QChar(image[j].character) == QLatin1Char('@')) && (y > pnt.y() || x > pnt.x())) {
if ((image[j].rendition.f.extended == 0) && (QChar(image[j].character) == QLatin1Char('@')) && (y > pnt.y() || x > pnt.x())) {
if (x > 0) {
x--;
} else {
@@ -1992,7 +2019,7 @@ bool TerminalDisplay::focusNextPrevChild(bool next)
QChar TerminalDisplay::charClass(const Character &ch) const
{
if ((ch.rendition & RE_EXTENDED_CHAR) != 0) {
if (ch.rendition.f.extended != 0) {
ushort extendedCharLength = 0;
const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(ch.character, extendedCharLength);
if ((chars != nullptr) && extendedCharLength > 0) {
@@ -2832,6 +2859,8 @@ void TerminalDisplay::applyProfile(const Profile::Ptr &profile)
_ctrlRequiredForDrag = profile->property<bool>(Profile::CtrlRequiredForDrag);
_dropUrlsAsText = profile->property<bool>(Profile::DropUrlsAsText);
_bidiEnabled = profile->bidiRenderingEnabled();
_bidiLineLTR = profile->bidiLineLTR();
_bidiTableDirOverride = profile->bidiTableDirOverride();
_semanticUpDown = profile->semanticUpDown();
_semanticHints = profile->semanticHints();
_semanticInputClick = profile->semanticInputClick();
@@ -2899,3 +2928,69 @@ int TerminalDisplay::selectionState() const
{
return _actSel;
}
int TerminalDisplay::bidiMap(Character *screenline, QString &line, int *log2line, int *line2log, uint16_t *shapemap, int32_t *vis2line, bool shape, bool bidi)
const
{
const int linewidth = _usedColumns;
uint64_t notSkipped[MAX_LINE_WIDTH / 64] = {};
int i;
int lastNonSpace = 0;
for (i = 0; i < linewidth; i++) {
log2line[i] = line.size();
line2log[line.size()] = i;
notSkipped[line.size() / 64] |= 1ul << (line.size() % 64);
const Character char_value = screenline[i];
if (char_value.rendition.f.extended != 0) {
// sequence of characters
ushort extendedCharLength = 0;
const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(char_value.character, extendedCharLength);
if (chars != nullptr) {
Q_ASSERT(extendedCharLength > 1);
line += QString::fromUcs4(chars, extendedCharLength);
}
lastNonSpace = i;
} else {
line += QString::fromUcs4(&char_value.character, 1);
if (!line[line.size() - 1].isSpace()) {
lastNonSpace = i;
}
}
}
log2line[i] = line.size();
// line.truncate(lastNonSpace + 1);
UErrorCode errorCode = U_ZERO_ERROR;
if (shape) {
UChar shaped_line[MAX_LINE_WIDTH];
u_shapeArabic(reinterpret_cast<const UChar *>(line.utf16()),
line.length(),
shaped_line,
1024,
U_SHAPE_AGGREGATE_TASHKEEL_NOOP | U_SHAPE_LENGTH_FIXED_SPACES_NEAR | U_SHAPE_LETTERS_SHAPE,
&errorCode);
for (int i = 0; i < line.length(); i++) {
if (line[i] != shaped_line[i]) {
shapemap[i] = shaped_line[i];
}
}
}
if (!bidi) {
return lastNonSpace;
}
UBiDi *ubidi = ubidi_open();
UBiDiLevel paraLevel = _bidiLineLTR ? 0 : UBIDI_DEFAULT_LTR;
if (_bidiTableDirOverride) {
ubidi_setClassCallback(ubidi, BiDiClass, nullptr, nullptr, nullptr, &errorCode);
}
ubidi_setPara(ubidi, reinterpret_cast<const UChar *>(line.utf16()), line.length(), paraLevel, nullptr, &errorCode);
int len = ubidi_getProcessedLength(ubidi);
int32_t semi_vis2line[MAX_LINE_WIDTH];
ubidi_getVisualMap(ubidi, semi_vis2line, &errorCode);
int p = 0;
for (int i = 0; i < len; i++) {
if ((notSkipped[i / 64] & (1ul << (i % 64))) != 0) {
vis2line[p++] = semi_vis2line[i];
}
}
return lastNonSpace;
}

View File

@@ -398,6 +398,11 @@ public:
// Used to show/hide the message widget
void updateReadOnlyState(bool readonly);
// Get mapping between visual and logical positions in line
// returns the index of the last non space character.
int bidiMap(Character *screenline, QString &line, int *log2line, int *line2log, uint16_t *shapemap, int32_t *vis2line, bool shape = true, bool bidi = true)
const;
public Q_SLOTS:
/**
* Causes the terminal display to fetch the latest character image from the associated
@@ -669,6 +674,8 @@ private:
bool _resizing = false;
bool _showTerminalSizeHint = true;
bool _bidiEnabled = false;
bool _bidiLineLTR = true;
bool _bidiTableDirOverride = true;
bool _usesMouseTracking = false;
bool _allowMouseTracking = true;
bool _bracketedPasteMode = false;

View File

@@ -35,6 +35,15 @@ void TerminalFont::applyProfile(const Profile::Ptr &profile)
m_useFontLineCharacters = profile->useFontLineCharacters();
m_lineSpacing = uint(profile->lineSpacing());
setVTFont(profile->font());
extraFonts[0] = profile->emojiFont();
if (extraFonts[0] == QFont()) {
extraFonts[0] = QFont(QStringLiteral("Noto Color Emoji"));
// extraFonts[0] = QFont(QStringLiteral("Apple Color Emoji"));
// extraFonts[0] = QFont(QStringLiteral("Emoji One"));
if (extraFonts[0] == QFont()) {
extraFonts.remove(0);
}
}
}
void TerminalFont::setVTFont(const QFont &f)
@@ -196,6 +205,26 @@ int TerminalFont::fontAscent() const
return m_fontAscent;
}
int TerminalFont::lineWidth() const
{
return m_lineWidth;
}
int TerminalFont::underlinePos() const
{
return m_underlinePos;
}
int TerminalFont::strikeOutPos() const
{
return m_strikeOutPos;
}
int TerminalFont::overlinePos() const
{
return m_overlinePos;
}
bool TerminalFont::boldIntense() const
{
return m_boldIntense;
@@ -225,8 +254,21 @@ void TerminalFont::fontChange(const QFont &)
}
m_fontAscent = fm.ascent();
m_lineWidth = fm.lineWidth();
m_underlinePos = fm.underlinePos();
m_strikeOutPos = fm.strikeOutPos();
m_overlinePos = fm.overlinePos();
qobject_cast<TerminalDisplay *>(m_parent)->propagateSize();
}
bool TerminalFont::hasExtraFont(int i) const
{
return extraFonts.contains(i);
}
QFont TerminalFont::getExtraFont(int i) const
{
return extraFonts[i];
}
}

View File

@@ -38,10 +38,17 @@ public:
int fontHeight() const;
int fontWidth() const;
int fontAscent() const;
int lineWidth() const;
int underlinePos() const;
int strikeOutPos() const;
int overlinePos() const;
bool boldIntense() const;
bool antialiasText() const;
bool useFontLineCharacters() const;
bool hasExtraFont(int i) const;
QFont getExtraFont(int i) const;
protected:
void fontChange(const QFont &);
@@ -51,9 +58,14 @@ private:
int m_fontHeight = 1;
int m_fontWidth = 1;
int m_fontAscent = 1;
int m_lineWidth = 1;
int m_underlinePos = 1;
int m_strikeOutPos = 1;
int m_overlinePos = 1;
bool m_boldIntense = false;
bool m_antialiasText = true;
bool m_useFontLineCharacters = false;
QMap<int, QFont> extraFonts;
Profile::Ptr m_profile;
};

View File

@@ -23,6 +23,7 @@
#include <QChar>
#include <QColor>
#include <QDebug>
#include <QElapsedTimer>
#include <QPainter>
#include <QPen>
#include <QRect>
@@ -68,9 +69,7 @@ static inline bool isLineCharString(const QString &string)
bool isInvertedRendition(const TerminalDisplay *display)
{
auto currentProfile = SessionManager::instance()->sessionProfile(display->session());
const bool isInvert = currentProfile ? currentProfile->property<bool>(Profile::InvertSelectionColors) : false;
return isInvert;
return currentProfile ? currentProfile->property<bool>(Profile::InvertSelectionColors) : false;
}
void TerminalPainter::drawContents(Character *image,
@@ -79,167 +78,165 @@ void TerminalPainter::drawContents(Character *image,
bool printerFriendly,
int imageSize,
bool bidiEnabled,
QVector<LineProperty> lineProperties)
QVector<LineProperty> lineProperties,
CharacterColor const *ulColorTable)
{
const bool invertedRendition = isInvertedRendition(m_parentDisplay);
QVector<uint> univec;
univec.reserve(m_parentDisplay->usedColumns());
int placementIdx = 0;
const int leftPadding = m_parentDisplay->contentRect().left() + m_parentDisplay->contentsRect().left();
const int topPadding = m_parentDisplay->contentRect().top() + m_parentDisplay->contentsRect().top();
const int fontWidth = m_parentDisplay->terminalFont()->fontWidth();
const int fontHeight = m_parentDisplay->terminalFont()->fontHeight();
const QRect textArea(QPoint(leftPadding + fontWidth * rect.x(), topPadding + rect.y() * fontHeight),
QSize(rect.width() * fontWidth, rect.height() * fontHeight));
if (!printerFriendly) {
drawImagesBelowText(paint, textArea, fontWidth, fontHeight, placementIdx);
}
static const QFont::Weight FontWeights[] = {
QFont::Thin,
QFont::Light,
QFont::Normal,
QFont::Bold,
QFont::Black,
};
const auto normalWeight = m_parentDisplay->font().weight();
auto it = std::upper_bound(std::begin(FontWeights), std::end(FontWeights), normalWeight);
const QFont::Weight boldWeight = it != std::end(FontWeights) ? *it : QFont::Black;
paint.setRenderHint(QPainter::Antialiasing, m_parentDisplay->terminalFont()->antialiasText());
paint.setLayoutDirection(Qt::LeftToRight);
for (int y = rect.y(); y <= rect.bottom(); y++) {
const int textY = topPadding + fontHeight * y;
int x = rect.x();
bool doubleHeightLinePair = false;
int x = rect.x();
LineProperty lineProperty = y < lineProperties.size() ? lineProperties[y] : 0;
// Search for start of multi-column character
if (image[m_parentDisplay->loc(rect.x(), y)].isRightHalfOfDoubleWide() && (x != 0)) {
x--;
}
QTransform textScale;
bool doubleHeight = false;
bool doubleWidthLine = false;
if ((lineProperty & LINE_DOUBLEWIDTH) != 0) {
textScale.scale(2, 1);
doubleWidthLine = true;
}
doubleHeight = lineProperty & (LINE_DOUBLEHEIGHT_TOP | LINE_DOUBLEHEIGHT_BOTTOM);
if (doubleHeight) {
textScale.scale(1, 2);
}
if (y < lineProperties.size() - 1) {
if (((lineProperties[y] & LINE_DOUBLEHEIGHT_TOP) != 0) && ((lineProperties[y + 1] & LINE_DOUBLEHEIGHT_BOTTOM) != 0)) {
doubleHeightLinePair = true;
}
}
// Apply text scaling matrix
paint.setWorldTransform(textScale, true);
// Calculate the area in which the text will be drawn
const int textX = leftPadding + fontWidth * rect.x() * (doubleWidthLine ? 2 : 1);
const int textWidth = fontWidth * rect.width();
const int textHeight = doubleHeight && !doubleHeightLinePair ? fontHeight / 2 : fontHeight;
int pos = m_parentDisplay->loc(0, y);
// move the calculated area to take account of scaling applied to the painter.
// the position of the area from the origin (0,0) is scaled
// by the opposite of whatever
// transformation has been applied to the painter. this ensures that
// painting does actually start from textArea.topLeft()
//(instead of textArea.topLeft() * painter-scale)
QString line;
#define MAX_LINE_WIDTH 1024
int log2line[MAX_LINE_WIDTH];
int line2log[MAX_LINE_WIDTH];
uint16_t shapemap[MAX_LINE_WIDTH];
int32_t vis2line[MAX_LINE_WIDTH];
int lastNonSpace = m_parentDisplay->bidiMap(image + pos, line, log2line, line2log, shapemap, vis2line, bidiEnabled, bidiEnabled);
const QRect textArea(textScale.inverted().map(QPoint(textX, textY)), QSize(textWidth, textHeight));
if (!printerFriendly) {
drawBelowText(paint,
textArea,
image + pos,
rect.x(),
rect.width(),
fontWidth,
m_parentDisplay->terminalColor()->colorTable(),
invertedRendition,
vis2line,
line2log,
bidiEnabled);
}
RenditionFlags oldRendition = -1;
QColor oldColor = QColor();
for (; x <= rect.right(); x++) {
int len = 1;
const int pos = m_parentDisplay->loc(x, y);
const Character char_value = image[pos];
// is this a single character or a sequence of characters ?
if ((char_value.rendition & RE_EXTENDED_CHAR) != 0) {
// sequence of characters
ushort extendedCharLength = 0;
const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(char_value.character, extendedCharLength);
if (chars != nullptr) {
Q_ASSERT(extendedCharLength > 1);
for (int index = 0; index < extendedCharLength; index++) {
univec << chars[index];
}
}
if (x > lastNonSpace) {
// What about the cursor?
// break;
}
int log_x;
int line_x;
if (bidiEnabled) {
line_x = vis2line[x];
log_x = line2log[line_x];
} else {
if (!char_value.isRightHalfOfDoubleWide()) {
univec << char_value.character;
}
log_x = x;
}
// TODO: Move all those lambdas to Character, so it's easy to test.
const bool doubleWidth = image[qMin(pos + 1, imageSize - 1)].isRightHalfOfDoubleWide();
const Character char_value = image[pos + log_x];
const auto isInsideDrawArea = [rectRight = rect.right()](int column) {
return column <= rectRight;
};
const auto hasSameWidth = [imageSize, image, doubleWidth](int nextPos) {
const int characterLoc = qMin(nextPos + 1, imageSize - 1);
return image[characterLoc].isRightHalfOfDoubleWide() == doubleWidth;
};
if (char_value.canBeGrouped(bidiEnabled, doubleWidth)) {
while (isInsideDrawArea(x + len)) {
const int nextPos = m_parentDisplay->loc(x + len, y);
const Character next_char = image[nextPos];
if (!hasSameWidth(nextPos) || !next_char.canBeGrouped(bidiEnabled, doubleWidth) || !char_value.hasSameAttributes(next_char)) {
break;
}
const uint c = next_char.character;
if ((next_char.rendition & RE_EXTENDED_CHAR) != 0) {
// sequence of characters
ushort extendedCharLength = 0;
const uint *chars = ExtendedCharTable::instance.lookupExtendedChar(c, extendedCharLength);
if (chars != nullptr) {
Q_ASSERT(extendedCharLength > 1);
for (int index = 0; index < extendedCharLength; index++) {
univec << chars[index];
}
}
} else {
// single character
if (c != 0u) {
univec << c;
}
}
if (doubleWidth) {
len++;
}
len++;
}
} else {
// Group spaces following any non-wide character with the character. This allows for
// rendering ambiguous characters with wide glyphs without clipping them.
while (!doubleWidth && isInsideDrawArea(x + len)) {
const Character next_char = image[m_parentDisplay->loc(x + len, y)];
if (next_char.character == ' ' && char_value.hasSameColors(next_char) && char_value.hasSameRendition(next_char)) {
univec << next_char.character;
len++;
} else {
// break otherwise, we don't want to be stuck in this loop
break;
}
}
if (!printerFriendly && char_value.isSpace() && char_value.rendition.f.cursor == 0) {
continue;
}
// Adjust for trailing part of multi-column character
if ((x + len < m_parentDisplay->usedColumns()) && image[m_parentDisplay->loc(x + len, y)].isRightHalfOfDoubleWide()) {
len++;
}
QTransform textScale;
bool doubleHeight = false;
bool doubleWidthLine = false;
if ((lineProperty & LINE_DOUBLEWIDTH) != 0) {
textScale.scale(2, 1);
doubleWidthLine = true;
}
doubleHeight = lineProperty & (LINE_DOUBLEHEIGHT_TOP | LINE_DOUBLEHEIGHT_BOTTOM);
if (doubleHeight) {
textScale.scale(1, 2);
}
if (y < lineProperties.size() - 1) {
if (((lineProperties[y] & LINE_DOUBLEHEIGHT_TOP) != 0) && ((lineProperties[y + 1] & LINE_DOUBLEHEIGHT_BOTTOM) != 0)) {
doubleHeightLinePair = true;
}
}
// Apply text scaling matrix
paint.setWorldTransform(textScale, true);
// Calculate the area in which the text will be drawn
const int textX = leftPadding + fontWidth * x * (doubleWidthLine ? 2 : 1);
const int textWidth = fontWidth * len;
const int textHeight = doubleHeight && !doubleHeightLinePair ? fontHeight / 2 : fontHeight;
// move the calculated area to take account of scaling applied to the painter.
// the position of the area from the origin (0,0) is scaled
// by the opposite of whatever
// transformation has been applied to the painter. this ensures that
// painting does actually start from textArea.topLeft()
//(instead of textArea.topLeft() * painter-scale)
const QRect textArea(textScale.inverted().map(QPoint(textX, textY)), QSize(textWidth, textHeight));
const QString unistr = QString::fromUcs4(univec.data(), univec.length());
univec.clear();
const QString unistr = line.mid(log2line[log_x], log2line[log_x + 1] - log2line[log_x]);
// paint text fragment
if (printerFriendly) {
drawPrinterFriendlyTextFragment(paint, textArea, unistr, image[pos], lineProperty);
} else {
drawTextFragment(paint, textArea, unistr, image[pos], m_parentDisplay->terminalColor()->colorTable(), invertedRendition, lineProperty);
if (unistr.length() && unistr[0] != QChar(0)) {
int textWidth = fontWidth * 1;
int textX = leftPadding + fontWidth * x * (doubleWidthLine ? 2 : 1);
const QRect textAreaOneChar(textScale.inverted().map(QPoint(textX, textY)), QSize(textWidth, textHeight));
drawTextCharacters(paint,
textAreaOneChar,
unistr,
image[pos + log_x],
m_parentDisplay->terminalColor()->colorTable(),
invertedRendition,
lineProperty,
printerFriendly,
oldRendition,
oldColor,
normalWeight,
boldWeight);
}
paint.setWorldTransform(textScale.inverted(), true);
x += len - 1;
}
if ((lineProperty & LINE_PROMPT_START) != 0
if (!printerFriendly) {
drawAboveText(paint,
textArea,
image + pos,
rect.x(),
rect.width(),
fontWidth,
m_parentDisplay->terminalColor()->colorTable(),
invertedRendition,
vis2line,
line2log,
bidiEnabled,
ulColorTable);
}
paint.setRenderHint(QPainter::Antialiasing, false);
paint.setWorldTransform(textScale.inverted(), true);
if ((lineProperty & LINE_PROMPT_START)
&& ((m_parentDisplay->semanticHints() == Enum::SemanticHintsURL && m_parentDisplay->filterChain()->showUrlHint())
|| m_parentDisplay->semanticHints() == Enum::SemanticHintsAlways)) {
QPen pen(m_parentDisplay->terminalColor()->foregroundColor());
@@ -251,6 +248,9 @@ void TerminalPainter::drawContents(Character *image,
y++;
}
}
if (!printerFriendly) {
drawImagesAboveText(paint, textArea, fontWidth, fontHeight, placementIdx);
}
}
void TerminalPainter::drawCurrentResultRect(QPainter &painter, const QRect &searchResultRect)
@@ -395,129 +395,6 @@ static void reverseRendition(Character &p)
p.backgroundColor = f;
}
void TerminalPainter::drawTextFragment(QPainter &painter,
const QRect &rect,
const QString &text,
Character style,
const QColor *colorTable,
const bool invertedRendition,
const LineProperty lineProperty)
{
// setup painter
// Sets the text selection colors, either:
// - using reverseRendition(), which inverts the foreground/background
// colors OR
// - blending the foreground/background colors
if (style.rendition & RE_SELECTED) {
if (invertedRendition) {
reverseRendition(style);
}
}
QColor foregroundColor = style.foregroundColor.color(colorTable);
QColor backgroundColor = style.backgroundColor.color(colorTable);
if (style.rendition & RE_SELECTED) {
if (!invertedRendition) {
backgroundColor = calculateBackgroundColor(style, colorTable).value_or(foregroundColor);
if (backgroundColor == foregroundColor) {
foregroundColor = style.backgroundColor.color(colorTable);
}
}
}
Screen *screen = m_parentDisplay->screenWindow()->screen();
int placementIdx = 0;
qreal opacity = painter.opacity();
int scrollDelta = m_parentDisplay->terminalFont()->fontHeight() * (m_parentDisplay->screenWindow()->currentLine() - screen->getHistLines());
const bool origClipping = painter.hasClipping();
const auto origClipRegion = painter.clipRegion();
if (screen->hasGraphics()) {
painter.setClipRect(rect);
while (1) {
TerminalGraphicsPlacement_t *p = screen->getGraphicsPlacement(placementIdx);
if (!p || p->z >= 0) {
break;
}
int x = p->col * m_parentDisplay->terminalFont()->fontWidth() + p->X + m_parentDisplay->contentRect().left();
int y = p->row * m_parentDisplay->terminalFont()->fontHeight() + p->Y + m_parentDisplay->contentRect().top();
QRectF srcRect(0, 0, p->pixmap.width(), p->pixmap.height());
QRectF dstRect(x, y - scrollDelta, p->pixmap.width(), p->pixmap.height());
painter.setOpacity(p->opacity);
painter.drawPixmap(dstRect, p->pixmap, srcRect);
placementIdx++;
}
painter.setOpacity(opacity);
}
bool drawBG = backgroundColor != colorTable[DEFAULT_BACK_COLOR];
if (screen->hasGraphics() && style.rendition == RE_TRANSPARENT) {
drawBG = false;
}
if (drawBG) {
drawBackground(painter, rect, backgroundColor, false);
}
QColor characterColor = foregroundColor;
if ((style.rendition & RE_CURSOR) != 0) {
drawCursor(painter, rect, foregroundColor, backgroundColor, characterColor);
}
if (m_parentDisplay->filterChain()->showUrlHint()) {
if ((style.flags & EF_REPL) == EF_REPL_PROMPT) {
int h, s, v;
characterColor.getHsv(&h, &s, &v);
s = s / 2;
v = v / 2;
characterColor.setHsv(h, s, v);
}
if ((style.flags & EF_REPL) == EF_REPL_INPUT) {
int h, s, v;
characterColor.getHsv(&h, &s, &v);
s = (511 + s) / 3;
v = (511 + v) / 3;
characterColor.setHsv(h, s, v);
}
}
// draw text
drawCharacters(painter, rect, text, style, characterColor, lineProperty);
if (screen->hasGraphics()) {
while (1) {
TerminalGraphicsPlacement_t *p = screen->getGraphicsPlacement(placementIdx);
if (!p) {
break;
}
QPixmap image = p->pixmap;
int x = p->col * m_parentDisplay->terminalFont()->fontWidth() + p->X + m_parentDisplay->contentRect().left();
int y = p->row * m_parentDisplay->terminalFont()->fontHeight() + p->Y + m_parentDisplay->contentRect().top();
QRectF srcRect(0, 0, image.width(), image.height());
QRectF dstRect(x, y - scrollDelta, image.width(), image.height());
painter.setOpacity(p->opacity);
painter.drawPixmap(dstRect, image, srcRect);
placementIdx++;
}
painter.setOpacity(opacity);
painter.setClipRegion(origClipRegion);
painter.setClipping(origClipping);
}
}
void TerminalPainter::drawPrinterFriendlyTextFragment(QPainter &painter,
const QRect &rect,
const QString &text,
Character style,
const LineProperty lineProperty)
{
style.foregroundColor = CharacterColor(COLOR_SPACE_RGB, 0x00000000);
style.backgroundColor = CharacterColor(COLOR_SPACE_RGB, 0xFFFFFFFF);
drawCharacters(painter, rect, text, style, QColor(), lineProperty);
}
void TerminalPainter::drawBackground(QPainter &painter, const QRect &rect, const QColor &backgroundColor, bool useOpacitySetting)
{
if (useOpacitySetting && !m_parentDisplay->wallpaper()->isNull()
@@ -595,11 +472,11 @@ void TerminalPainter::drawCharacters(QPainter &painter,
const QColor &characterColor,
const LineProperty lineProperty)
{
if (m_parentDisplay->textBlinking() && ((style.rendition & RE_BLINK) != 0)) {
if (m_parentDisplay->textBlinking() && (style.rendition.f.blink != 0)) {
return;
}
if ((style.rendition & RE_CONCEAL) != 0) {
if (style.rendition.f.conceal != 0) {
return;
}
@@ -619,11 +496,11 @@ void TerminalPainter::drawCharacters(QPainter &painter,
auto it = std::upper_bound(std::begin(FontWeights), std::end(FontWeights), normalWeight);
const QFont::Weight boldWeight = it != std::end(FontWeights) ? *it : QFont::Black;
const bool useBold = (((style.rendition & RE_BOLD) != 0) && m_parentDisplay->terminalFont()->boldIntense());
const bool useUnderline = ((style.rendition & RE_UNDERLINE) != 0) || m_parentDisplay->font().underline();
const bool useItalic = ((style.rendition & RE_ITALIC) != 0) || m_parentDisplay->font().italic();
const bool useStrikeOut = ((style.rendition & RE_STRIKEOUT) != 0) || m_parentDisplay->font().strikeOut();
const bool useOverline = ((style.rendition & RE_OVERLINE) != 0) || m_parentDisplay->font().overline();
const bool useBold = ((style.rendition.f.bold != 0) && m_parentDisplay->terminalFont()->boldIntense());
const bool useUnderline = (style.rendition.f.underline != 0) || m_parentDisplay->font().underline();
const bool useItalic = (style.rendition.f.italic != 0) || m_parentDisplay->font().italic();
const bool useStrikeOut = (style.rendition.f.strikeout != 0) || m_parentDisplay->font().strikeOut();
const bool useOverline = (style.rendition.f.overline != 0) || m_parentDisplay->font().overline();
QFont currentFont = painter.font();
@@ -633,7 +510,8 @@ void TerminalPainter::drawCharacters(QPainter &painter,
|| currentFont.underline() != useUnderline
|| currentFont.italic() != useItalic
|| currentFont.strikeOut() != useStrikeOut
|| currentFont.overline() != useOverline)
|| currentFont.overline() != useOverline
)
{ // clang-format on
currentFont.setWeight(useBold ? boldWeight : normalWeight);
currentFont.setUnderline(useUnderline);
@@ -651,9 +529,6 @@ void TerminalPainter::drawCharacters(QPainter &painter,
pen.setColor(color);
painter.setPen(color);
}
const bool origClipping = painter.hasClipping();
const auto origClipRegion = painter.clipRegion();
painter.setClipRect(rect);
// draw text
if (isLineCharString(text) && !m_parentDisplay->terminalFont()->useFontLineCharacters()) {
int y = rect.y();
@@ -687,21 +562,16 @@ void TerminalPainter::drawCharacters(QPainter &painter,
y += m_parentDisplay->terminalFont()->lineSpacing() - shifted;
}
}
painter.setClipRegion(origClipRegion);
painter.setClipping(origClipping);
}
void TerminalPainter::drawLineCharString(TerminalDisplay *display, QPainter &painter, int x, int y, const QString &str, const Character attributes)
{
painter.setRenderHint(QPainter::Antialiasing, display->terminalFont()->antialiasText());
const bool useBoldPen = (attributes.rendition & RE_BOLD) != 0 && display->terminalFont()->boldIntense();
const bool useBoldPen = (attributes.rendition.f.bold) != 0 && display->terminalFont()->boldIntense();
QRect cellRect = {x, y, display->terminalFont()->fontWidth(), display->terminalFont()->fontHeight()};
QVector<uint> ucs4str = str.toUcs4();
for (int i = 0; i < ucs4str.length(); i++) {
LineBlockCharacters::draw(painter, cellRect.translated(i * display->terminalFont()->fontWidth(), 0), ucs4str[i], useBoldPen);
}
painter.setRenderHint(QPainter::Antialiasing, false);
}
void TerminalPainter::drawInputMethodPreeditString(QPainter &painter, const QRect &rect, TerminalDisplay::InputMethodData &inputMethodData, Character *image)
@@ -724,4 +594,407 @@ void TerminalPainter::drawInputMethodPreeditString(QPainter &painter, const QRec
inputMethodData.previousPreeditRect = rect;
}
void TerminalPainter::drawBelowText(QPainter &painter,
const QRect &rect,
Character *style,
int startX,
int width,
int fontWidth,
const QColor *colorTable,
const bool invertedRendition,
int *vis2line,
int *line2log,
bool bidiEnabled)
{
// setup painter
bool first = true;
QRect constRect(0, 0, 0, 0);
QColor backgroundColor;
QColor foregroundColor;
bool drawBG = false;
int lastX = 0;
for (int i = 0;; i++) {
int x;
if (bidiEnabled && i < width) {
x = line2log[vis2line[i + startX]];
} else {
x = i + startX;
}
if (first || style[x].rendition.all != style[lastX].rendition.all || style[x].foregroundColor != style[lastX].foregroundColor
|| style[x].backgroundColor != style[lastX].backgroundColor || i == width) {
if (first) {
first = false;
} else {
if (drawBG) {
painter.fillRect(constRect, backgroundColor);
}
}
if (i == width) {
return;
}
// Sets the text selection colors, either:
// - using reverseRendition(), which inverts the foreground/background
// colors OR
// - blending the foreground/background colors
if (style[x].rendition.f.selected && invertedRendition) {
backgroundColor = style[x].foregroundColor.color(colorTable);
foregroundColor = style[x].backgroundColor.color(colorTable);
} else {
foregroundColor = style[x].foregroundColor.color(colorTable);
backgroundColor = style[x].backgroundColor.color(colorTable);
}
if (style[x].rendition.f.selected) {
if (!invertedRendition) {
backgroundColor = calculateBackgroundColor(style[x], colorTable).value_or(foregroundColor);
if (backgroundColor == foregroundColor) {
foregroundColor = style[x].backgroundColor.color(colorTable);
}
}
}
drawBG = backgroundColor != colorTable[DEFAULT_BACK_COLOR];
if (style[x].rendition.f.transparent) {
drawBG = false;
}
constRect = QRect(rect.x() + fontWidth * i, rect.y(), fontWidth, rect.height());
} else {
constRect.setWidth(constRect.width() + fontWidth);
}
lastX = x;
}
}
void TerminalPainter::drawAboveText(QPainter &painter,
const QRect &rect,
Character *style,
int startX,
int width,
int fontWidth,
const QColor *colorTable,
const bool invertedRendition,
int *vis2line,
int *line2log,
bool bidiEnabled,
CharacterColor const *ulColorTable)
{
bool first = true;
QRect constRect(0, 0, 0, 0);
QColor backgroundColor;
QColor foregroundColor;
int lastX = 0;
int startUnderline = -1;
int startOverline = -1;
int startStrikeOut = -1;
for (int i = 0;; i++) {
int x;
if (bidiEnabled && i < width) {
x = line2log[vis2line[i + startX]];
} else {
x = i + startX;
}
if (first || ((style[x].rendition.all ^ style[lastX].rendition.all) & RE_MASK_ABOVE) || ((style[x].flags ^ style[lastX].flags) & EF_UNDERLINE_COLOR)
|| style[x].foregroundColor != style[lastX].foregroundColor || style[x].backgroundColor != style[lastX].backgroundColor || i == width) {
if (first) {
first = false;
} else {
if ((i == width || style[x].rendition.f.strikeout == 0) && startStrikeOut >= 0) {
QPen pen(foregroundColor);
int y = rect.y() + m_parentDisplay->terminalFont()->fontAscent();
pen.setWidth(m_parentDisplay->terminalFont()->lineWidth());
painter.setPen(pen);
painter.drawLine(rect.x() + fontWidth * startStrikeOut,
y - m_parentDisplay->terminalFont()->strikeOutPos(),
rect.x() + fontWidth * i - 1,
y - m_parentDisplay->terminalFont()->strikeOutPos());
startStrikeOut = -1;
}
if ((i == width || style[x].rendition.f.overline == 0) && startOverline >= 0) {
QPen pen(foregroundColor);
int y = rect.y() + m_parentDisplay->terminalFont()->fontAscent();
pen.setWidth(m_parentDisplay->terminalFont()->lineWidth());
painter.setPen(pen);
painter.drawLine(rect.x() + fontWidth * startOverline,
y - m_parentDisplay->terminalFont()->overlinePos(),
rect.x() + fontWidth * i - 1,
y - m_parentDisplay->terminalFont()->overlinePos());
startOverline = -1;
}
int underline = style[lastX].rendition.f.underline;
if ((i == width || style[x].rendition.f.underline != underline || ((style[x].flags ^ style[lastX].flags) & EF_UNDERLINE_COLOR))
&& startUnderline >= 0) {
QPen pen(foregroundColor);
if (ulColorTable != nullptr && (style[lastX].flags & EF_UNDERLINE_COLOR) != 0) {
pen.setColor(ulColorTable[((style[lastX].flags & EF_UNDERLINE_COLOR)) / EF_UNDERLINE_COLOR_1 - 1].color(colorTable));
}
int y = rect.y() + m_parentDisplay->terminalFont()->fontAscent() + m_parentDisplay->terminalFont()->underlinePos()
+ m_parentDisplay->terminalFont()->lineWidth();
int lw = m_parentDisplay->terminalFont()->lineWidth();
if (underline == RE_UNDERLINE_DOUBLE || underline == RE_UNDERLINE_CURL) {
y = rect.bottom() - 1;
lw = 1;
} else {
if (lw + lw + m_parentDisplay->terminalFont()->fontAscent() + m_parentDisplay->terminalFont()->underlinePos() > rect.height()) {
lw = rect.height() - m_parentDisplay->terminalFont()->fontAscent() - m_parentDisplay->terminalFont()->underlinePos() - lw;
}
}
pen.setWidth(lw);
if (underline == RE_UNDERLINE_DOT) {
pen.setStyle(Qt::DotLine);
} else if (underline == RE_UNDERLINE_DASH) {
pen.setStyle(Qt::DashLine);
}
if (underline == RE_UNDERLINE_CURL) {
QVector<qreal> dashes(2, 2);
pen.setDashPattern(dashes);
}
painter.setPen(pen);
painter.drawLine(rect.x() + fontWidth * startUnderline, y, rect.x() + fontWidth * i - 1, y);
if (underline == RE_UNDERLINE_DOUBLE) {
painter.drawLine(rect.x() + fontWidth * startUnderline, y - 2, rect.x() + fontWidth * i - 1, y - 2);
}
if (underline == RE_UNDERLINE_CURL) {
painter.drawLine(rect.x() + fontWidth * startUnderline + 2, y - 1, rect.x() + fontWidth * i - 1, y - 1);
}
startUnderline = -1;
}
}
if (i == width) {
return;
}
// Sets the text selection colors, either:
// - using reverseRendition(), which inverts the foreground/background
// colors OR
// - blending the foreground/background colors
if (style[x].rendition.f.selected && invertedRendition) {
backgroundColor = style[x].foregroundColor.color(colorTable);
foregroundColor = style[x].backgroundColor.color(colorTable);
} else {
foregroundColor = style[x].foregroundColor.color(colorTable);
backgroundColor = style[x].backgroundColor.color(colorTable);
}
if (style[x].rendition.f.selected) {
if (!invertedRendition) {
backgroundColor = calculateBackgroundColor(style[x], colorTable).value_or(foregroundColor);
if (backgroundColor == foregroundColor) {
foregroundColor = style[x].backgroundColor.color(colorTable);
}
}
}
if (style[x].rendition.f.strikeout && startStrikeOut == -1) {
startStrikeOut = i;
}
if (style[x].rendition.f.overline && startOverline == -1) {
startOverline = i;
}
if (style[x].rendition.f.underline && startUnderline == -1) {
startUnderline = i;
}
lastX = x;
}
}
}
void TerminalPainter::drawImagesBelowText(QPainter &painter, const QRect &rect, int fontWidth, int fontHeight, int &placementIdx)
{
Screen *screen = m_parentDisplay->screenWindow()->screen();
placementIdx = 0;
qreal opacity = painter.opacity();
int scrollDelta = m_parentDisplay->terminalFont()->fontHeight() * (m_parentDisplay->screenWindow()->currentLine() - screen->getHistLines());
const bool origClipping = painter.hasClipping();
const auto origClipRegion = painter.clipRegion();
if (screen->hasGraphics()) {
painter.setClipRect(rect);
while (1) {
TerminalGraphicsPlacement_t *p = screen->getGraphicsPlacement(placementIdx);
if (!p || p->z >= 0) {
break;
}
int x = p->col * fontWidth + p->X + m_parentDisplay->contentRect().left();
int y = p->row * fontHeight + p->Y + m_parentDisplay->contentRect().top();
QRectF srcRect(0, 0, p->pixmap.width(), p->pixmap.height());
QRectF dstRect(x, y - scrollDelta, p->pixmap.width(), p->pixmap.height());
painter.setOpacity(p->opacity);
painter.drawPixmap(dstRect, p->pixmap, srcRect);
placementIdx++;
}
painter.setOpacity(opacity);
painter.setClipRegion(origClipRegion);
painter.setClipping(origClipping);
}
}
void TerminalPainter::drawImagesAboveText(QPainter &painter, const QRect &rect, int fontWidth, int fontHeight, int &placementIdx)
{
// setup painter
Screen *screen = m_parentDisplay->screenWindow()->screen();
qreal opacity = painter.opacity();
int scrollDelta = fontHeight * (m_parentDisplay->screenWindow()->currentLine() - screen->getHistLines());
const bool origClipping = painter.hasClipping();
const auto origClipRegion = painter.clipRegion();
if (screen->hasGraphics()) {
painter.setClipRect(rect);
while (1) {
TerminalGraphicsPlacement_t *p = screen->getGraphicsPlacement(placementIdx);
if (!p) {
break;
}
QPixmap image = p->pixmap;
int x = p->col * fontWidth + p->X + m_parentDisplay->contentRect().left();
int y = p->row * fontHeight + p->Y + m_parentDisplay->contentRect().top();
QRectF srcRect(0, 0, image.width(), image.height());
QRectF dstRect(x, y - scrollDelta, image.width(), image.height());
painter.setOpacity(p->opacity);
painter.drawPixmap(dstRect, image, srcRect);
placementIdx++;
}
painter.setOpacity(opacity);
painter.setClipRegion(origClipRegion);
painter.setClipping(origClipping);
}
}
void TerminalPainter::drawTextCharacters(QPainter &painter,
const QRect &rect,
const QString &text,
Character style,
const QColor *colorTable,
const bool invertedRendition,
const LineProperty lineProperty,
bool printerFriendly,
RenditionFlags &oldRendition,
QColor oldColor,
int normalWeight,
QFont::Weight boldWeight)
{
// setup painter
if (style.rendition.f.conceal != 0) {
return;
}
QColor characterColor;
if (!printerFriendly) {
// Sets the text selection colors, either:
// - invertedRendition, which inverts the foreground/background colors OR
// - blending the foreground/background colors
if (m_parentDisplay->textBlinking() && (style.rendition.f.blink != 0)) {
return;
}
if (style.rendition.f.selected) {
if (invertedRendition) {
reverseRendition(style);
}
}
QColor foregroundColor = style.foregroundColor.color(colorTable);
QColor backgroundColor = style.backgroundColor.color(colorTable);
if (style.rendition.f.selected) {
if (!invertedRendition) {
backgroundColor = calculateBackgroundColor(style, colorTable).value_or(foregroundColor);
if (backgroundColor == foregroundColor) {
foregroundColor = style.backgroundColor.color(colorTable);
}
}
}
characterColor = foregroundColor;
if (style.rendition.f.cursor != 0) {
drawCursor(painter, rect, foregroundColor, backgroundColor, characterColor);
}
if (m_parentDisplay->filterChain()->showUrlHint()) {
if ((style.flags & EF_REPL) == EF_REPL_PROMPT) {
int h, s, v;
characterColor.getHsv(&h, &s, &v);
s = s / 2;
v = v / 2;
characterColor.setHsv(h, s, v);
}
if ((style.flags & EF_REPL) == EF_REPL_INPUT) {
int h, s, v;
characterColor.getHsv(&h, &s, &v);
s = (511 + s) / 3;
v = (511 + v) / 3;
characterColor.setHsv(h, s, v);
}
}
} else {
characterColor = QColor(0, 0, 0);
}
// The weight used as bold depends on selected font's weight.
// "Regular" will use "Bold", but e.g. "Thin" will use "Light".
// Note that QFont::weight/setWeight() returns/takes an int in Qt5,
// and a QFont::Weight in Qt6
QFont savedFont;
bool restoreFont = false;
if ((style.flags & EF_EMOJI_REPRESENTATION) && m_parentDisplay->terminalFont()->hasExtraFont(0)) {
savedFont = painter.font();
restoreFont = true;
painter.setFont(m_parentDisplay->terminalFont()->getExtraFont(0));
} else {
if (oldRendition != style.rendition.all) {
const bool useBold = ((style.rendition.f.bold != 0) && m_parentDisplay->terminalFont()->boldIntense());
const bool useItalic = (style.rendition.f.italic != 0) || m_parentDisplay->font().italic();
QFont currentFont = painter.font();
const bool isCurrentBold = currentFont.weight() >= boldWeight;
if (isCurrentBold != useBold || currentFont.italic() != useItalic) {
currentFont.setWeight(useBold ? boldWeight : normalWeight);
currentFont.setItalic(useItalic);
painter.setFont(currentFont);
}
}
}
if (characterColor != oldColor) {
QPen pen = painter.pen();
if (pen.color() != characterColor) {
painter.setPen(characterColor);
}
}
// const bool origClipping = painter.hasClipping();
// const auto origClipRegion = painter.clipRegion();
// painter.setClipRect(rect);
// draw text
if (isLineCharString(text) && !m_parentDisplay->terminalFont()->useFontLineCharacters()) {
int y = rect.y();
if (lineProperty & LINE_DOUBLEHEIGHT_BOTTOM) {
y -= m_parentDisplay->terminalFont()->fontHeight() / 2;
}
drawLineCharString(m_parentDisplay, painter, rect.x(), y, text, style);
} else {
int y = rect.y() + m_parentDisplay->terminalFont()->fontAscent();
if (lineProperty & LINE_DOUBLEHEIGHT_BOTTOM) {
y -= m_parentDisplay->terminalFont()->fontHeight() / 2;
} else {
// We shift half way down here to center
y += m_parentDisplay->terminalFont()->lineSpacing() / 2;
}
painter.drawText(rect.x(), y, text);
if (0 && text.toUcs4().length() > 1) {
fprintf(stderr, " %i ", text.toUcs4().length());
for (int i = 0; i < text.toUcs4().length(); i++) {
fprintf(stderr, " %04x ", text.toUcs4()[i]);
}
fprintf(stderr, "\n");
}
}
if (restoreFont) {
painter.setFont(savedFont);
}
}
}

View File

@@ -54,7 +54,8 @@ public Q_SLOTS:
bool PrinterFriendly,
int imageSize,
bool bidiEnabled,
QVector<LineProperty> lineProperties);
QVector<LineProperty> lineProperties,
CharacterColor const *ulColorTable = nullptr);
// draw a transparent rectangle over the line of the current match
void drawCurrentResultRect(QPainter &painter, const QRect &searchResultRect);
@@ -86,22 +87,48 @@ private:
// draws a string of line graphics
void drawLineCharString(TerminalDisplay *display, QPainter &painter, int x, int y, const QString &str, const Character attributes);
// draws a section of text, all the text in this section
// has a common color and style
void drawTextFragment(QPainter &painter,
const QRect &rect,
const QString &text,
Character style,
const QColor *colorTable,
const bool invertedRendition,
const LineProperty lineProperty);
void drawPrinterFriendlyTextFragment(QPainter &painter, const QRect &rect, const QString &text, Character style, const LineProperty lineProperty);
// draws the cursor character
void drawCursor(QPainter &painter, const QRect &rect, const QColor &foregroundColor, const QColor &backgroundColor, QColor &characterColor);
TerminalDisplay *m_parentDisplay = nullptr;
void drawBelowText(QPainter &painter,
const QRect &rect,
Character *style,
int startX,
int width,
int fontWidth,
const QColor *colorTable,
const bool invertedRendition,
int *vis2line,
int *line2log,
bool bidiEnabled);
void drawAboveText(QPainter &painter,
const QRect &rect,
Character *style,
int startX,
int width,
int fontWidth,
const QColor *colorTable,
const bool invertedRendition,
int *vis2line,
int *line2log,
bool bidiEnabled,
CharacterColor const *ulColorTable);
void drawImagesBelowText(QPainter &painter, const QRect &rect, int fontWidth, int fontHeight, int &placementIdx);
void drawImagesAboveText(QPainter &painter, const QRect &rect, int fontWidth, int fontHeight, int &placementIdx);
void drawTextCharacters(QPainter &painter,
const QRect &rect,
const QString &text,
Character style,
const QColor *colorTable,
const bool invertedRendition,
const LineProperty lineProperty,
bool printerFriendly,
RenditionFlags &oldRendition,
QColor oldColor,
int normalWeight,
QFont::Weight boldWeight);
};
}

View File

@@ -218,6 +218,7 @@
</property>
</widget>
</item>
<!--
<item row="5" column="1">
<widget class="QCheckBox" name="enableBidiRenderingButton">
<property name="sizePolicy">
@@ -234,6 +235,7 @@
</property>
</widget>
</item>
-->
<item row="6" column="1">
<spacer>
<property name="orientation">

View File

@@ -26,7 +26,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
<number>9</number>
</property>
<widget class="QWidget" name="colorSchemeTab">
<attribute name="title">
@@ -56,7 +56,7 @@
<number>0</number>
</property>
<item>
<layout name="colorSchemesBtnLayout" class="QVBoxLayout">
<layout class="QVBoxLayout" name="colorSchemesBtnLayout">
<property name="spacing">
<number>6</number>
</property>
@@ -755,6 +755,116 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="CTLtab">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Complex Text Layout&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<attribute name="title">
<string>Complex Text Layout</string>
</attribute>
<widget class="QWidget" name="layoutWidget">
<property name="geometry">
<rect>
<x>3</x>
<y>3</y>
<width>611</width>
<height>441</height>
</rect>
</property>
<layout class="QGridLayout" name="CTL_2">
<property name="spacing">
<number>6</number>
</property>
<item row="1" column="0">
<widget class="QLabel" name="label_17">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Emoji Font:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="emojiFontPreview">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="enableBidiRenderingButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Enable Bi-Directional display on terminals (valid for Arabic, Farsi or Hebrew only)</string>
</property>
<property name="text">
<string>Bi-Directional text rendering</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="bidiLineLTR">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Lines are always LTR, rather than determined by first strong character</string>
</property>
<property name="text">
<string>force LTR line direction</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QCheckBox" name="enableBidiTableDirOverrideButton">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>Treat table drawing characters as strong LTR.</string>
</property>
<property name="text">
<string>Table characters BiDi mode override</string>
</property>
</widget>
</item>
<item row="10" column="0" colspan="2">
<spacer>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</widget>
</item>
</layout>
@@ -833,7 +943,7 @@
</connection>
</connections>
<buttongroups>
<buttongroup name="cursorShape"/>
<buttongroup name="cursorColor"/>
<buttongroup name="cursorShape"/>
</buttongroups>
</ui>

View File

@@ -697,6 +697,8 @@ void EditProfileDialog::setupAppearancePage(const Profile::Ptr &profile)
connect(_appearanceUi->chooseFontButton, &QAbstractButton::clicked, this, &EditProfileDialog::showFontDialog);
connect(_appearanceUi->emojiFontPreview, &QAbstractButton::clicked, this, &EditProfileDialog::showEmojiFontDialog);
// setup font preview
const bool antialias = profile->antiAliasFonts();
@@ -706,6 +708,10 @@ void EditProfileDialog::setupAppearancePage(const Profile::Ptr &profile)
_appearanceUi->fontPreview->setFont(profileFont);
_appearanceUi->fontPreview->setText(QStringLiteral("%1 %2pt").arg(profileFont.family()).arg(profileFont.pointSize()));
QFont emojiFont = profile->emojiFont();
_appearanceUi->emojiFontPreview->setFont(profileFont);
_appearanceUi->emojiFontPreview->setText(QStringLiteral("%1 %2pt").arg(emojiFont.family()).arg(emojiFont.pointSize()));
// setup font smoothing
_appearanceUi->antialiasTextButton->setChecked(antialias);
connect(_appearanceUi->antialiasTextButton, &QCheckBox::toggled, this, &Konsole::EditProfileDialog::setAntialiasText);
@@ -791,6 +797,17 @@ void EditProfileDialog::setupAppearancePage(const Profile::Ptr &profile)
getNewButton->setText(QStringLiteral("Get New..."));
getNewButton->setConfigFile(QStringLiteral("konsole.knsrc"));
_appearanceUi->colorSchemesBtnLayout->addWidget(getNewButton);
_appearanceUi->enableBidiRenderingButton->setChecked(profile->bidiRenderingEnabled());
connect(_appearanceUi->enableBidiRenderingButton, &QPushButton::toggled, this, &EditProfileDialog::togglebidiRendering);
_appearanceUi->enableBidiTableDirOverrideButton->setChecked(profile->property<bool>(Profile::BidiTableDirOverride));
connect(_appearanceUi->enableBidiTableDirOverrideButton, &QPushButton::toggled, this, &EditProfileDialog::togglebidiTableDirOverride);
_appearanceUi->enableBidiTableDirOverrideButton->setEnabled(profile->bidiRenderingEnabled());
_appearanceUi->bidiLineLTR->setChecked(profile->property<bool>(Profile::BidiLineLTR));
connect(_appearanceUi->bidiLineLTR, &QPushButton::toggled, this, &EditProfileDialog::togglebidiLineLTR);
_appearanceUi->bidiLineLTR->setEnabled(profile->bidiRenderingEnabled());
}
void EditProfileDialog::setAntialiasText(bool enable)
@@ -1076,11 +1093,33 @@ void EditProfileDialog::showFontDialog()
updateFontPreview(_profile->font());
});
}
_fontDialog->setFont(_profile->font());
_fontDialog->show();
}
void EditProfileDialog::showEmojiFontDialog()
{
if (_emojiFontDialog == nullptr) {
_emojiFontDialog = new FontDialog(this, true, _profile->emojiFont());
_emojiFontDialog->setModal(true);
connect(_emojiFontDialog, &FontDialog::fontChanged, this, [this](const QFont &font) {
preview(Profile::EmojiFont, font);
updateEmojiFontPreview(font);
});
connect(_emojiFontDialog, &FontDialog::accepted, this, [this]() {
const QFont font = _emojiFontDialog->font();
preview(Profile::EmojiFont, font);
updateTempProfileProperty(Profile::EmojiFont, font);
updateEmojiFontPreview(font);
});
connect(_emojiFontDialog, &FontDialog::rejected, this, [this]() {
unpreview(Profile::EmojiFont);
updateEmojiFontPreview(_profile->emojiFont());
});
}
_emojiFontDialog->show();
}
void EditProfileDialog::updateFontPreview(QFont font)
{
bool aa = _profile->antiAliasFonts();
@@ -1090,6 +1129,15 @@ void EditProfileDialog::updateFontPreview(QFont font)
_appearanceUi->fontPreview->setText(QStringLiteral("%1 %2pt").arg(font.family()).arg(font.pointSize()));
}
void EditProfileDialog::updateEmojiFontPreview(QFont font)
{
bool aa = _profile->antiAliasFonts();
font.setStyleStrategy(aa ? QFont::PreferAntialias : QFont::NoAntialias);
QFont emojiFont = _profile->emojiFont();
_appearanceUi->emojiFontPreview->setFont(_profile->font());
_appearanceUi->emojiFontPreview->setText(QStringLiteral("%1 %2pt").arg(emojiFont.family()).arg(emojiFont.pointSize()));
}
void EditProfileDialog::removeColorScheme()
{
const QModelIndexList selected = _appearanceUi->colorSchemeList->selectionModel()->selectedIndexes();
@@ -1732,8 +1780,6 @@ void EditProfileDialog::setupAdvancedPage(const Profile::Ptr &profile)
connect(_advancedUi->enableFlowControlButton, &QPushButton::toggled, this, &EditProfileDialog::toggleFlowControl);
_appearanceUi->enableBlinkingCursorButton->setChecked(profile->property<bool>(Profile::BlinkingCursorEnabled));
connect(_appearanceUi->enableBlinkingCursorButton, &QPushButton::toggled, this, &EditProfileDialog::toggleBlinkingCursor);
_advancedUi->enableBidiRenderingButton->setChecked(profile->property<bool>(Profile::BidiRenderingEnabled));
connect(_advancedUi->enableBidiRenderingButton, &QPushButton::toggled, this, &EditProfileDialog::togglebidiRendering);
_advancedUi->enableReverseUrlHints->setChecked(profile->property<bool>(Profile::ReverseUrlHints));
connect(_advancedUi->enableReverseUrlHints, &QPushButton::toggled, this, &EditProfileDialog::toggleReverseUrlHints);
@@ -1820,6 +1866,18 @@ void EditProfileDialog::wordCharactersChanged(const QString &text)
void EditProfileDialog::togglebidiRendering(bool enable)
{
updateTempProfileProperty(Profile::BidiRenderingEnabled, enable);
_appearanceUi->enableBidiTableDirOverrideButton->setEnabled(enable);
_appearanceUi->bidiLineLTR->setEnabled(enable);
}
void EditProfileDialog::togglebidiTableDirOverride(bool enable)
{
updateTempProfileProperty(Profile::BidiTableDirOverride, enable);
}
void EditProfileDialog::togglebidiLineLTR(bool enable)
{
updateTempProfileProperty(Profile::BidiLineLTR, enable);
}
void EditProfileDialog::toggleUnderlineLinks(bool enable)

View File

@@ -151,6 +151,9 @@ private Q_SLOTS:
void terminalMarginChanged(int margin);
void lineSpacingChanged(int);
void setTerminalCenter(bool enable);
void togglebidiRendering(bool);
void togglebidiTableDirOverride(bool);
void togglebidiLineLTR(bool);
#if KNEWSTUFF_VERSION >= QT_VERSION_CHECK(5, 91, 0)
void gotNewColorSchemes(const QList<KNSCore::EntryInternal> &changedEntries);
@@ -168,6 +171,7 @@ private Q_SLOTS:
void colorSchemeSelected();
void previewColorScheme(const QModelIndex &index);
void showFontDialog();
void showEmojiFontDialog();
void toggleMouseWheelZoom(bool enable);
// scrolling page
@@ -210,7 +214,6 @@ private Q_SLOTS:
// advanced page
void toggleBlinkingText(bool);
void toggleFlowControl(bool);
void togglebidiRendering(bool);
void updateUrlHintsModifier(bool);
void toggleReverseUrlHints(bool);
@@ -280,6 +283,7 @@ private:
void updateTransparencyWarning();
void updateFontPreview(QFont font);
void updateEmojiFontPreview(QFont font);
// Update _tempProfile in a way of respecting the apply button.
// When used with some previewed property, this method should
@@ -347,6 +351,7 @@ private:
ColorSchemeEditor *_colorDialog = nullptr;
QDialogButtonBox *_buttonBox = nullptr;
FontDialog *_fontDialog = nullptr;
FontDialog *_emojiFontDialog = nullptr;
InitialProfileState _profileState = ExistingProfile;
};