diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -34,8 +34,6 @@ option(REMOVE_SENDTEXT_RUNCOMMAND_DBUS_METHODS "Konsole: remove sendText and runCommand dbus methods" OFF) ### Development tools -option(KONSOLE_BUILD_FONTEMBEDDER "Konsole: build fontembedder executable" OFF) -option(KONSOLE_GENERATE_LINEFONT "Konsole: regenerate LineFont file" OFF) option(KONSOLE_BUILD_UNI2CHARACTERWIDTH "Konsole: build uni2characterwidth executable" OFF) ### Konsole source files shared between embedded terminal and main application @@ -99,6 +97,7 @@ ExtendedCharTable.cpp TerminalDisplay.cpp TerminalDisplayAccessible.cpp + LineBlockCharacters.cpp ViewContainer.cpp ViewManager.cpp ViewProperties.cpp diff --git a/src/Character.h b/src/Character.h --- a/src/Character.h +++ b/src/Character.h @@ -53,21 +53,6 @@ const RenditionFlags RE_CONCEAL = (1 << 9); const RenditionFlags RE_OVERLINE = (1 << 10); -/** - * Unicode character in the range of U+2500 ~ U+257F are known as line - * characters, or box-drawing characters. Currently, konsole draws those - * characters itself, instead of using the glyph provided by the font. - * Unfortunately, the triple and quadruple dash lines (┄┅┆┇┈┉┊┋) are too - * detailed too be drawn cleanly at normal font scales without anti - * -aliasing, so those are drawn as regular characters. - */ -inline bool isSupportedLineChar(uint codePoint) -{ - return ((codePoint & 0xFF80) == 0x2500 // Unicode block: Mathematical Symbols - Box Drawing - && !(0x2504 <= codePoint && codePoint <= 0x250B)) || // Triple and quadruple dash range - (codePoint >= 0x2580 && codePoint <= 0x259F); // Block characters -} - /** * A single character in the terminal which consists of a unicode character * value, foreground and background colors and a set of rendition attributes @@ -143,15 +128,6 @@ */ friend bool operator !=(const Character &a, const Character &b); - inline bool isLineChar() const - { - if (rendition & RE_EXTENDED_CHAR) { - return false; - } else { - return isSupportedLineChar(character); - } - } - inline bool isSpace() const { if (rendition & RE_EXTENDED_CHAR) { diff --git a/src/LineBlockCharacters.h b/src/LineBlockCharacters.h new file mode 100644 --- /dev/null +++ b/src/LineBlockCharacters.h @@ -0,0 +1,57 @@ +/* + This file is part of Konsole, a terminal emulator for KDE. + + Copyright 2019 by Mariusz Glebocki + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +#ifndef LINEBLOCKCHARACTERS_H +#define LINEBLOCKCHARACTERS_H + +// Qt +#include +#include + +namespace Konsole { + +/** + * Helper functions for drawing characters from "Box Drawing" and "Block Elements" Unicode blocks. + */ +namespace LineBlockCharacters { + + /** + * Returns true if the character can be drawn by draw() function. + * + * @param ucs4cp Character to test's UCS4 code point + */ + inline static bool canDraw(uint ucs4cp) { + return (0x2500 <= ucs4cp && ucs4cp <= 0x259F); + } + + /** + * Draws character. + * + * @param paint QPainter to draw on + * @param cellRect Rectangle to draw in + * @param chr Character to be drawn + */ + void draw(QPainter &paint, const QRect &cellRect, const QChar &chr, bool bold); + +} // namespace LineBlockCharacters +} // namespace Konsole + +#endif // LINEBLOCKCHARACTERS_H diff --git a/src/LineBlockCharacters.cpp b/src/LineBlockCharacters.cpp new file mode 100644 --- /dev/null +++ b/src/LineBlockCharacters.cpp @@ -0,0 +1,722 @@ +/* + This file is part of Konsole, a terminal emulator for KDE. + + Copyright 2018-2019 by Mariusz Glebocki + Copyright 2018 by Martin T. H. Sandsmark + Copyright 2015-2018 by Kurt Hindenburg + Copyright 2005 by Maksim Orlovich + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + 02110-1301 USA. +*/ + +// Own +#include "LineBlockCharacters.h" + +// Qt +#include + +namespace Konsole { +namespace LineBlockCharacters { + +enum LineType { + LtNone = 0, + LtDouble = 1, + LtLight = 2, + LtHeavy = 3, +}; + +// PackedLineTypes is an 8-bit number representing types of 4 line character's lines. Each line is +// represented by 2 bits. Lines order, starting from MSB: top, right, bottom, left. +static inline constexpr quint8 makePackedLineTypes(LineType top, LineType right, LineType bottom, LineType left) +{ + return (int(top) & 3) << 6 | (int(right) & 3) << 4 | (int(bottom) & 3) << 2 | (int(left) & 3); +} + +static constexpr const quint8 PackedLineTypesLut[] = { + // top right bottom left + makePackedLineTypes(LtNone , LtLight , LtNone , LtLight ), /* U+2500 ─ */ + makePackedLineTypes(LtNone , LtHeavy , LtNone , LtHeavy ), /* U+2501 ━ */ + makePackedLineTypes(LtLight , LtNone , LtLight , LtNone ), /* U+2502 │ */ + makePackedLineTypes(LtHeavy , LtNone , LtHeavy , LtNone ), /* U+2503 ┃ */ + 0, 0, 0, 0, 0, 0, 0, 0, /* U+2504-0x250b */ + makePackedLineTypes(LtNone , LtLight , LtLight , LtNone ), /* U+250C ┌ */ + makePackedLineTypes(LtNone , LtHeavy , LtLight , LtNone ), /* U+250D ┍ */ + makePackedLineTypes(LtNone , LtLight , LtHeavy , LtNone ), /* U+250E ┎ */ + makePackedLineTypes(LtNone , LtHeavy , LtHeavy , LtNone ), /* U+250F ┏ */ + makePackedLineTypes(LtNone , LtNone , LtLight , LtLight ), /* U+2510 ┐ */ + makePackedLineTypes(LtNone , LtNone , LtLight , LtHeavy ), /* U+2511 ┑ */ + makePackedLineTypes(LtNone , LtNone , LtHeavy , LtLight ), /* U+2512 ┒ */ + makePackedLineTypes(LtNone , LtNone , LtHeavy , LtHeavy ), /* U+2513 ┓ */ + makePackedLineTypes(LtLight , LtLight , LtNone , LtNone ), /* U+2514 └ */ + makePackedLineTypes(LtLight , LtHeavy , LtNone , LtNone ), /* U+2515 ┕ */ + makePackedLineTypes(LtHeavy , LtLight , LtNone , LtNone ), /* U+2516 ┖ */ + makePackedLineTypes(LtHeavy , LtHeavy , LtNone , LtNone ), /* U+2517 ┗ */ + makePackedLineTypes(LtLight , LtNone , LtNone , LtLight ), /* U+2518 ┘ */ + makePackedLineTypes(LtLight , LtNone , LtNone , LtHeavy ), /* U+2519 ┙ */ + makePackedLineTypes(LtHeavy , LtNone , LtNone , LtLight ), /* U+251A ┚ */ + makePackedLineTypes(LtHeavy , LtNone , LtNone , LtHeavy ), /* U+251B ┛ */ + makePackedLineTypes(LtLight , LtLight , LtLight , LtNone ), /* U+251C ├ */ + makePackedLineTypes(LtLight , LtHeavy , LtLight , LtNone ), /* U+251D ┝ */ + makePackedLineTypes(LtHeavy , LtLight , LtLight , LtNone ), /* U+251E ┞ */ + makePackedLineTypes(LtLight , LtLight , LtHeavy , LtNone ), /* U+251F ┟ */ + makePackedLineTypes(LtHeavy , LtLight , LtHeavy , LtNone ), /* U+2520 ┠ */ + makePackedLineTypes(LtHeavy , LtHeavy , LtLight , LtNone ), /* U+2521 ┡ */ + makePackedLineTypes(LtLight , LtHeavy , LtHeavy , LtNone ), /* U+2522 ┢ */ + makePackedLineTypes(LtHeavy , LtHeavy , LtHeavy , LtNone ), /* U+2523 ┣ */ + makePackedLineTypes(LtLight , LtNone , LtLight , LtLight ), /* U+2524 ┤ */ + makePackedLineTypes(LtLight , LtNone , LtLight , LtHeavy ), /* U+2525 ┥ */ + makePackedLineTypes(LtHeavy , LtNone , LtLight , LtLight ), /* U+2526 ┦ */ + makePackedLineTypes(LtLight , LtNone , LtHeavy , LtLight ), /* U+2527 ┧ */ + makePackedLineTypes(LtHeavy , LtNone , LtHeavy , LtLight ), /* U+2528 ┨ */ + makePackedLineTypes(LtHeavy , LtNone , LtLight , LtHeavy ), /* U+2529 ┩ */ + makePackedLineTypes(LtLight , LtNone , LtHeavy , LtHeavy ), /* U+252A ┪ */ + makePackedLineTypes(LtHeavy , LtNone , LtHeavy , LtHeavy ), /* U+252B ┫ */ + makePackedLineTypes(LtNone , LtLight , LtLight , LtLight ), /* U+252C ┬ */ + makePackedLineTypes(LtNone , LtLight , LtLight , LtHeavy ), /* U+252D ┭ */ + makePackedLineTypes(LtNone , LtHeavy , LtLight , LtLight ), /* U+252E ┮ */ + makePackedLineTypes(LtNone , LtHeavy , LtLight , LtHeavy ), /* U+252F ┯ */ + makePackedLineTypes(LtNone , LtLight , LtHeavy , LtLight ), /* U+2530 ┰ */ + makePackedLineTypes(LtNone , LtLight , LtHeavy , LtHeavy ), /* U+2531 ┱ */ + makePackedLineTypes(LtNone , LtHeavy , LtHeavy , LtLight ), /* U+2532 ┲ */ + makePackedLineTypes(LtNone , LtHeavy , LtHeavy , LtHeavy ), /* U+2533 ┳ */ + makePackedLineTypes(LtLight , LtLight , LtNone , LtLight ), /* U+2534 ┴ */ + makePackedLineTypes(LtLight , LtLight , LtNone , LtHeavy ), /* U+2535 ┵ */ + makePackedLineTypes(LtLight , LtHeavy , LtNone , LtLight ), /* U+2536 ┶ */ + makePackedLineTypes(LtLight , LtHeavy , LtNone , LtHeavy ), /* U+2537 ┷ */ + makePackedLineTypes(LtHeavy , LtLight , LtNone , LtLight ), /* U+2538 ┸ */ + makePackedLineTypes(LtHeavy , LtLight , LtNone , LtHeavy ), /* U+2539 ┹ */ + makePackedLineTypes(LtHeavy , LtHeavy , LtNone , LtLight ), /* U+253A ┺ */ + makePackedLineTypes(LtHeavy , LtHeavy , LtNone , LtHeavy ), /* U+253B ┻ */ + makePackedLineTypes(LtLight , LtLight , LtLight , LtLight ), /* U+253C ┼ */ + makePackedLineTypes(LtLight , LtLight , LtLight , LtHeavy ), /* U+253D ┽ */ + makePackedLineTypes(LtLight , LtHeavy , LtLight , LtLight ), /* U+253E ┾ */ + makePackedLineTypes(LtLight , LtHeavy , LtLight , LtHeavy ), /* U+253F ┿ */ + makePackedLineTypes(LtHeavy , LtLight , LtLight , LtLight ), /* U+2540 ╀ */ + makePackedLineTypes(LtLight , LtLight , LtHeavy , LtLight ), /* U+2541 ╁ */ + makePackedLineTypes(LtHeavy , LtLight , LtHeavy , LtLight ), /* U+2542 ╂ */ + makePackedLineTypes(LtHeavy , LtLight , LtLight , LtHeavy ), /* U+2543 ╃ */ + makePackedLineTypes(LtHeavy , LtHeavy , LtLight , LtLight ), /* U+2544 ╄ */ + makePackedLineTypes(LtLight , LtLight , LtHeavy , LtHeavy ), /* U+2545 ╅ */ + makePackedLineTypes(LtLight , LtHeavy , LtHeavy , LtLight ), /* U+2546 ╆ */ + makePackedLineTypes(LtHeavy , LtHeavy , LtLight , LtHeavy ), /* U+2547 ╇ */ + makePackedLineTypes(LtLight , LtHeavy , LtHeavy , LtHeavy ), /* U+2548 ╈ */ + makePackedLineTypes(LtHeavy , LtLight , LtHeavy , LtHeavy ), /* U+2549 ╉ */ + makePackedLineTypes(LtHeavy , LtHeavy , LtHeavy , LtLight ), /* U+254A ╊ */ + makePackedLineTypes(LtHeavy , LtHeavy , LtHeavy , LtHeavy ), /* U+254B ╋ */ + 0, 0, 0, 0, /* U+254C - U+254F */ + makePackedLineTypes(LtNone , LtDouble, LtNone , LtDouble), /* U+2550 ═ */ + makePackedLineTypes(LtDouble, LtNone , LtDouble, LtNone ), /* U+2551 ║ */ + makePackedLineTypes(LtNone , LtDouble, LtLight , LtNone ), /* U+2552 ╒ */ + makePackedLineTypes(LtNone , LtLight , LtDouble, LtNone ), /* U+2553 ╓ */ + makePackedLineTypes(LtNone , LtDouble, LtDouble, LtNone ), /* U+2554 ╔ */ + makePackedLineTypes(LtNone , LtNone , LtLight , LtDouble), /* U+2555 ╕ */ + makePackedLineTypes(LtNone , LtNone , LtDouble, LtLight ), /* U+2556 ╖ */ + makePackedLineTypes(LtNone , LtNone , LtDouble, LtDouble), /* U+2557 ╗ */ + makePackedLineTypes(LtLight , LtDouble, LtNone , LtNone ), /* U+2558 ╘ */ + makePackedLineTypes(LtDouble, LtLight , LtNone , LtNone ), /* U+2559 ╙ */ + makePackedLineTypes(LtDouble, LtDouble, LtNone , LtNone ), /* U+255A ╚ */ + makePackedLineTypes(LtLight , LtNone , LtNone , LtDouble), /* U+255B ╛ */ + makePackedLineTypes(LtDouble, LtNone , LtNone , LtLight ), /* U+255C ╜ */ + makePackedLineTypes(LtDouble, LtNone , LtNone , LtDouble), /* U+255D ╝ */ + makePackedLineTypes(LtLight , LtDouble, LtLight , LtNone ), /* U+255E ╞ */ + makePackedLineTypes(LtDouble, LtLight , LtDouble, LtNone ), /* U+255F ╟ */ + makePackedLineTypes(LtDouble, LtDouble, LtDouble, LtNone ), /* U+2560 ╠ */ + makePackedLineTypes(LtLight , LtNone , LtLight , LtDouble), /* U+2561 ╡ */ + makePackedLineTypes(LtDouble, LtNone , LtDouble, LtLight ), /* U+2562 ╢ */ + makePackedLineTypes(LtDouble, LtNone , LtDouble, LtDouble), /* U+2563 ╣ */ + makePackedLineTypes(LtNone , LtDouble, LtLight , LtDouble), /* U+2564 ╤ */ + makePackedLineTypes(LtNone , LtLight , LtDouble, LtLight ), /* U+2565 ╥ */ + makePackedLineTypes(LtNone , LtDouble, LtDouble, LtDouble), /* U+2566 ╦ */ + makePackedLineTypes(LtLight , LtDouble, LtNone , LtDouble), /* U+2567 ╧ */ + makePackedLineTypes(LtDouble, LtLight , LtNone , LtLight ), /* U+2568 ╨ */ + makePackedLineTypes(LtDouble, LtDouble, LtNone , LtDouble), /* U+2569 ╩ */ + makePackedLineTypes(LtLight , LtDouble, LtLight , LtDouble), /* U+256A ╪ */ + makePackedLineTypes(LtDouble, LtLight , LtDouble, LtLight ), /* U+256B ╫ */ + makePackedLineTypes(LtDouble, LtDouble, LtDouble, LtDouble), /* U+256C ╬ */ + 0, 0, 0, 0, 0, 0, 0, /* U+256D - U+2573 */ + makePackedLineTypes(LtNone , LtNone , LtNone , LtLight ), /* U+2574 ╴ */ + makePackedLineTypes(LtLight , LtNone , LtNone , LtNone ), /* U+2575 ╵ */ + makePackedLineTypes(LtNone , LtLight , LtNone , LtNone ), /* U+2576 ╶ */ + makePackedLineTypes(LtNone , LtNone , LtLight , LtNone ), /* U+2577 ╷ */ + makePackedLineTypes(LtNone , LtNone , LtNone , LtHeavy ), /* U+2578 ╸ */ + makePackedLineTypes(LtHeavy , LtNone , LtNone , LtNone ), /* U+2579 ╹ */ + makePackedLineTypes(LtNone , LtHeavy , LtNone , LtNone ), /* U+257A ╺ */ + makePackedLineTypes(LtNone , LtNone , LtHeavy , LtNone ), /* U+257B ╻ */ + makePackedLineTypes(LtNone , LtHeavy , LtNone , LtLight ), /* U+257C ╼ */ + makePackedLineTypes(LtLight , LtNone , LtHeavy , LtNone ), /* U+257D ╽ */ + makePackedLineTypes(LtNone , LtLight , LtNone , LtHeavy ), /* U+257E ╾ */ + makePackedLineTypes(LtHeavy , LtNone , LtLight , LtNone ), /* U+257F ╿ */ +}; + + + +// Bitwise rotate left +template +inline static T rotateBitsLeft(T value, quint8 amount) +{ + static_assert (std::is_unsigned(), "T must be unsigned type"); + assert(amount < sizeof(value) * 8); + return value << amount | value >> (sizeof(value) * 8 - amount); +} + +inline static const QPen pen(const QPainter &paint, uint lineWidth) +{ + return QPen(paint.pen().brush(), lineWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin); +} + + + +static inline uint lineWidth(uint fontWidth, bool heavy, bool bold) +{ + static const qreal LightWidthToFontWidthRatio = 1.0 / 6.5; + static const qreal HeavyHalfExtraToLightRatio = 1.0 / 3.0; + static const qreal BoldCoefficient = 1.5; + + // ▄▄▄▄▄▄▄ } heavyHalfExtraWidth ⎫ + // ██████████████ } lightWidth ⎬ heavyWidth + // ▀▀▀▀▀▀▀ ⎭ + // light heavy + + const qreal baseWidth = fontWidth * LightWidthToFontWidthRatio; + const qreal boldCoeff = bold ? BoldCoefficient : 1.0; + // Unless font size is too small, make bold lines at least 1px wider than regular lines + const qreal minWidth = bold && fontWidth >= 7 ? baseWidth + 1.0 : 1.0; + const uint lightWidth = qRound(qMax(baseWidth * boldCoeff, minWidth)); + const uint heavyHalfExtraWidth = qRound(qMax(lightWidth * HeavyHalfExtraToLightRatio, 1.0)); + + return heavy ? lightWidth + 2 * heavyHalfExtraWidth : lightWidth; +} + +// Draws characters composed of straight solid lines +static bool drawBasicLineCharacter(QPainter& paint, int x, int y, int w, int h, uchar code, + bool bold) +{ + quint8 packedLineTypes = code >= sizeof(PackedLineTypesLut) ? 0 : PackedLineTypesLut[code]; + if (packedLineTypes == 0) { + return false; + } + + const uint lightLineWidth = lineWidth(w, false, bold); + const uint heavyLineWidth = lineWidth(w, true, bold); + // Distance from double line's parallel axis to each line's parallel axis + const uint doubleLinesDistance = lightLineWidth; + + const QPen lightPen = pen(paint, lightLineWidth); + const QPen heavyPen = pen(paint, heavyLineWidth); + + static constexpr const unsigned LinesNum = 4; + + // Pixel aligned center point + const QPointF center = { + x + int(w/2) + 0.5 * (lightLineWidth % 2), + y + int(h/2) + 0.5 * (lightLineWidth % 2), + }; + + // Lines starting points, on the cell edges + const QPointF origin[] = { + QPointF(center.x(), y ), + QPointF(x+w , center.y() ), + QPointF(center.x(), y+h ), + QPointF(x , center.y() ), + }; + // Unit vectors with directions from center to the line's origin point + static const QPointF dir[] = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}}; + + const auto removeLineType = [&packedLineTypes](quint8 lineId)->void { + lineId = LinesNum - 1 - lineId % LinesNum; + packedLineTypes &= ~(3 << (2 * lineId)); + }; + const auto getLineType = [&packedLineTypes](quint8 lineId)->LineType { + lineId = LinesNum - 1 - lineId % LinesNum; + return LineType(packedLineTypes >> 2 * lineId & 3); + }; + + QPainterPath lightPath; // PainterPath for light lines (Painter Path Light) + QPainterPath heavyPath; // PainterPath for heavy lines (Painter Path Heavy) + // Returns ppl or pph depending on line type + const auto pathForLine = [&](quint8 lineId) -> QPainterPath& { + Q_ASSERT(getLineType(lineId) != LtNone); + return getLineType(lineId) == LtHeavy ? heavyPath : lightPath; + }; + + // Process all single up-down/left-right lines for every character that has them. Doing it here + // reduces amount of combinations below. + // Fully draws: ╋ ╂ ┃ ┿ ┼ │ ━ ─ + for (unsigned topIndex = 0; topIndex < LinesNum/2; topIndex++) { + unsigned iB = (topIndex + 2) % LinesNum; + const bool isSingleLine = (getLineType(topIndex) == LtLight + || getLineType(topIndex) == LtHeavy); + if (isSingleLine && getLineType(topIndex) == getLineType(iB)) { + pathForLine(topIndex).moveTo(origin[topIndex]); + pathForLine(topIndex).lineTo(origin[iB]); + removeLineType(topIndex); + removeLineType(iB); + } + } + + // Find base rotation of a character and map rotated line indices to the original rotation's + // indices. The base rotation is defined as the one with largest packedLineTypes value. This way + // we can use the same code for drawing 4 possible character rotations (see switch() below) + + uint topIndex = 0; // index of an original top line in a base rotation + quint8 basePackedLineTypes = packedLineTypes; + for (uint i = 0; i < LinesNum; i++) { + const quint8 rotatedPackedLineTypes = rotateBitsLeft(packedLineTypes, i * 2); + if (rotatedPackedLineTypes > basePackedLineTypes) { + topIndex = i; + basePackedLineTypes = rotatedPackedLineTypes; + } + } + uint rightIndex = (topIndex + 1) % LinesNum; + uint bottomIndex = (topIndex + 2) % LinesNum; + uint leftIndex = (topIndex + 3) % LinesNum; + + // Common paths + const auto drawDoubleUpRightShorterLine = [&](quint8 top, quint8 right) { // ╚ + lightPath.moveTo(origin[top] + dir[right] * doubleLinesDistance); + lightPath.lineTo(center + (dir[right] + dir[top]) * doubleLinesDistance); + lightPath.lineTo(origin[right] + dir[top] * doubleLinesDistance); + }; + const auto drawUpRight = [&](quint8 top, quint8 right) { // └┗ + pathForLine(top).moveTo(origin[top]); + pathForLine(top).lineTo(center); + pathForLine(top).lineTo(origin[right]); + }; + + switch (basePackedLineTypes) { + case makePackedLineTypes(LtHeavy , LtNone , LtLight , LtNone ): // ╿ ; ╼ ╽ ╾ ╊ ╇ ╉ ╈ ╀ ┾ ╁ ┽ + lightPath.moveTo(origin[bottomIndex]); + lightPath.lineTo(center + dir[topIndex] * lightLineWidth / 2.0); + Q_FALLTHROUGH(); + case makePackedLineTypes(LtHeavy , LtNone , LtNone , LtNone ): // ╹ ; ╺ ╻ ╸ ┻ ┣ ┳ ┫ ┸ ┝ ┰ ┥ + case makePackedLineTypes(LtLight , LtNone , LtNone , LtNone ): // ╵ ; ╶ ╷ ╴ ┷ ┠ ┯ ┨ ┴ ├ ┬ ┤ + pathForLine(topIndex).moveTo(origin[topIndex]); + pathForLine(topIndex).lineTo(center); + break; + + case makePackedLineTypes(LtHeavy , LtHeavy , LtLight , LtLight ): // ╄ ; ╃ ╆ ╅ + drawUpRight(bottomIndex, leftIndex); + Q_FALLTHROUGH(); + case makePackedLineTypes(LtHeavy , LtHeavy , LtNone , LtNone ): // ┗ ; ┛ ┏ ┓ + case makePackedLineTypes(LtLight , LtLight , LtNone , LtNone ): // └ ; ┘ ┌ ┐ + drawUpRight(topIndex, rightIndex); + break; + + case makePackedLineTypes(LtHeavy , LtLight , LtNone , LtNone ): // ┖ ; ┙ ┍ ┒ + qSwap(leftIndex, rightIndex); + Q_FALLTHROUGH(); + case makePackedLineTypes(LtHeavy , LtNone , LtNone , LtLight ): // ┚ ; ┕ ┎ ┑ + lightPath.moveTo(origin[leftIndex]); + lightPath.lineTo(center); + heavyPath.moveTo(origin[topIndex]); + heavyPath.lineTo(center + dir[bottomIndex] * lightLineWidth / 2.0); + break; + + case makePackedLineTypes(LtLight , LtDouble, LtNone , LtNone ): // ╘ ; ╜ ╓ ╕ + qSwap(leftIndex, rightIndex); + Q_FALLTHROUGH(); + case makePackedLineTypes(LtLight , LtNone , LtNone , LtDouble): // ╛ ; ╙ ╒ ╖ + lightPath.moveTo(origin[topIndex]); + lightPath.lineTo(center + dir[bottomIndex] * doubleLinesDistance); + lightPath.lineTo(origin[leftIndex] + dir[bottomIndex] * doubleLinesDistance); + lightPath.moveTo(origin[leftIndex] - dir[bottomIndex] * doubleLinesDistance); + lightPath.lineTo(center - dir[bottomIndex] * doubleLinesDistance); + break; + + case makePackedLineTypes(LtHeavy , LtHeavy , LtLight , LtNone ): // ┡ ; ┹ ┪ ┲ + qSwap(leftIndex, bottomIndex); + qSwap(rightIndex, topIndex); + Q_FALLTHROUGH(); + case makePackedLineTypes(LtHeavy , LtHeavy , LtNone , LtLight ): // ┺ ; ┩ ┢ ┱ + drawUpRight(topIndex, rightIndex); + lightPath.moveTo(origin[leftIndex]); + lightPath.lineTo(center); + break; + + case makePackedLineTypes(LtHeavy , LtLight , LtLight , LtNone ): // ┞ ; ┵ ┧ ┮ + qSwap(leftIndex, rightIndex); + Q_FALLTHROUGH(); + case makePackedLineTypes(LtHeavy , LtNone , LtLight , LtLight ): // ┦ ; ┶ ┟ ┭ + heavyPath.moveTo(origin[topIndex]); + heavyPath.lineTo(center + dir[bottomIndex] * lightLineWidth / 2.0); + drawUpRight(bottomIndex, leftIndex); + break; + + case makePackedLineTypes(LtLight , LtDouble, LtNone , LtDouble): // ╧ ; ╟ ╢ ╤ + lightPath.moveTo(origin[topIndex]); + lightPath.lineTo(center - dir[bottomIndex] * doubleLinesDistance); + qSwap(leftIndex, bottomIndex); + qSwap(rightIndex, topIndex); + Q_FALLTHROUGH(); + case makePackedLineTypes(LtDouble, LtNone , LtDouble, LtNone ): // ║ ; ╫ ═ ╪ + lightPath.moveTo(origin[topIndex] + dir[leftIndex] * doubleLinesDistance); + lightPath.lineTo(origin[bottomIndex] + dir[leftIndex] * doubleLinesDistance); + lightPath.moveTo(origin[topIndex] + dir[rightIndex] * doubleLinesDistance); + lightPath.lineTo(origin[bottomIndex] + dir[rightIndex] * doubleLinesDistance); + break; + + case makePackedLineTypes(LtDouble, LtNone , LtNone , LtNone ): // ╨ ; ╞ ╥ ╡ + lightPath.moveTo(origin[topIndex] + dir[leftIndex] * doubleLinesDistance); + lightPath.lineTo(center + dir[leftIndex] * doubleLinesDistance); + lightPath.moveTo(origin[topIndex] + dir[rightIndex] * doubleLinesDistance); + lightPath.lineTo(center + dir[rightIndex] * doubleLinesDistance); + break; + + case makePackedLineTypes(LtDouble, LtDouble, LtDouble, LtDouble): // ╬ + drawDoubleUpRightShorterLine(topIndex, rightIndex); + drawDoubleUpRightShorterLine(bottomIndex, rightIndex); + drawDoubleUpRightShorterLine(topIndex, leftIndex); + drawDoubleUpRightShorterLine(bottomIndex, leftIndex); + break; + + case makePackedLineTypes(LtDouble, LtDouble, LtDouble, LtNone ): // ╠ ; ╩ ╣ ╦ + lightPath.moveTo(origin[topIndex] + dir[leftIndex] * doubleLinesDistance); + lightPath.lineTo(origin[bottomIndex] + dir[leftIndex] * doubleLinesDistance); + drawDoubleUpRightShorterLine(topIndex, rightIndex); + drawDoubleUpRightShorterLine(bottomIndex, rightIndex); + break; + + case makePackedLineTypes(LtDouble, LtDouble, LtNone , LtNone ): // ╚ ; ╝ ╔ ╗ + lightPath.moveTo(origin[topIndex] + dir[leftIndex] * doubleLinesDistance); + lightPath.lineTo(center + (dir[leftIndex] + dir[bottomIndex]) * doubleLinesDistance); + lightPath.lineTo(origin[rightIndex] + dir[bottomIndex] * doubleLinesDistance); + drawDoubleUpRightShorterLine(topIndex, rightIndex); + break; + } + + // Draw paths + if (!lightPath.isEmpty()) { + paint.strokePath(lightPath, lightPen); + } + if (!heavyPath.isEmpty()) { + paint.strokePath(heavyPath, heavyPen); + } + + return true; +} + +static inline bool drawDashedLineCharacter(QPainter &paint, int x, int y, int w, int h, uchar code, + bool bold) +{ + if (!((0x04 <= code && code <= 0x0B) || (0x4C <= code && code <= 0x4F))) { + return false; + } + + const uint lightLineWidth = lineWidth(w, false, bold); + const uint heavyLineWidth = lineWidth(w, true, bold); + + const auto lightPen = pen(paint, lightLineWidth); + const auto heavyPen = pen(paint, heavyLineWidth); + + // Pixel aligned center point + const QPointF center = { + int(x + w/2.0) + 0.5 * (lightLineWidth%2), + int(y + h/2.0) + 0.5 * (lightLineWidth%2), + }; + + const qreal halfGapH = qMax(w / 20.0, 0.5); + const qreal halfGapV = qMax(h / 26.0, 0.5); + // For some reason vertical double dash has bigger gap + const qreal halfGapDDV = qMax(h / 14.0, 0.5); + + static const int LinesNumMax = 4; + + enum Orientation {Horizontal, Vertical}; + struct { + int linesNum; + Orientation orientation; + QPen pen; + qreal halfGap; + } lineProps; + + switch (code) { + case 0x4C: lineProps = {2, Horizontal, lightPen, halfGapH }; break; // ╌ + case 0x4D: lineProps = {2, Horizontal, heavyPen, halfGapH }; break; // ╍ + case 0x4E: lineProps = {2, Vertical , lightPen, halfGapDDV}; break; // ╎ + case 0x4F: lineProps = {2, Vertical , heavyPen, halfGapDDV}; break; // ╏ + case 0x04: lineProps = {3, Horizontal, lightPen, halfGapH }; break; // ┄ + case 0x05: lineProps = {3, Horizontal, heavyPen, halfGapH }; break; // ┅ + case 0x06: lineProps = {3, Vertical , lightPen, halfGapV }; break; // ┆ + case 0x07: lineProps = {3, Vertical , heavyPen, halfGapV }; break; // ┇ + case 0x08: lineProps = {4, Horizontal, lightPen, halfGapH }; break; // ┈ + case 0x09: lineProps = {4, Horizontal, heavyPen, halfGapH }; break; // ┉ + case 0x0A: lineProps = {4, Vertical , lightPen, halfGapV }; break; // ┊ + case 0x0B: lineProps = {4, Vertical , heavyPen, halfGapV }; break; // ┋ + } + + Q_ASSERT(lineProps.linesNum <= LinesNumMax); + const int size = (lineProps.orientation == Horizontal ? w : h); + const int pos = (lineProps.orientation == Horizontal ? x : y); + QLineF lines[LinesNumMax]; + + for (int i = 0; i < lineProps.linesNum; i++) { + const qreal start = pos + qreal(size * (i )) / lineProps.linesNum; + const qreal end = pos + qreal(size * (i+1)) / lineProps.linesNum; + if (lineProps.orientation == Horizontal) { + lines[i] = QLineF{start + lineProps.halfGap, center.y(), + end - lineProps.halfGap, center.y()}; + } else { + lines[i] = QLineF{center.x(), start + lineProps.halfGap, + center.x(), end - lineProps.halfGap}; + } + } + + const auto origPen = paint.pen(); + + paint.setPen(lineProps.pen); + paint.drawLines(lines, lineProps.linesNum); + + paint.setPen(origPen); + return true; +} + +static inline bool drawRoundedCornerLineCharacter(QPainter &paint, int x, int y, int w, int h, + uchar code, bool bold) +{ + if (!(0x6D <= code && code <= 0x70)) { + return false; + } + + const uint lightLineWidth = lineWidth(w, false, bold); + const auto lightPen = pen(paint, lightLineWidth); + + // Pixel aligned center point + const QPointF center = { + int(x + w/2.0) + 0.5 * (lightLineWidth%2), + int(y + h/2.0) + 0.5 * (lightLineWidth%2), + }; + + const int r = w * 3 / 8; + const int d = 2 * r; + + QPainterPath path; + + // lineTo() between moveTo and arcTo is redundant - arcTo() draws line + // to the arc's starting point + switch (code) { + case 0x6D: // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT + path.moveTo(center.x(), y + h); + path.arcTo(center.x(), center.y(), d, d, 180, -90); + path.lineTo(x + w, center.y()); + break; + case 0x6E: // BOX DRAWINGS LIGHT ARC DOWN AND LEFT + path.moveTo(center.x(), y + h); + path.arcTo(center.x() - d, center.y(), d, d, 0, 90); + path.lineTo(x, center.y()); + break; + case 0x6F: // BOX DRAWINGS LIGHT ARC UP AND LEFT + path.moveTo(center.x(), y); + path.arcTo(center.x() - d, center.y() - d, d, d, 0, -90); + path.lineTo(x, center.y()); + break; + case 0x70: // BOX DRAWINGS LIGHT ARC UP AND RIGHT + path.moveTo(center.x(), y); + path.arcTo(center.x(), center.y() - d, d, d, 180, 90); + path.lineTo(x + w, center.y()); + break; + } + paint.strokePath(path, lightPen); + + return true; +} + +static inline bool drawDiagonalLineCharacter(QPainter &paint, int x, int y, int w, int h, + uchar code, bool bold) +{ + if (!(0x71 <= code && code <= 0x73)) { + return false; + } + + const uint lightLineWidth = lineWidth(w, false, bold); + const auto lightPen = pen(paint, lightLineWidth); + + const QLineF lines[] = { + QLineF(x+w, y, x , y+h), // '/' + QLineF(x , y, x+w, y+h), // '\' + }; + + const auto origPen = paint.pen(); + + paint.setPen(lightPen); + switch (code) { + case 0x71: // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT + paint.drawLine(lines[0]); + break; + case 0x72: // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT + paint.drawLine(lines[1]); + break; + case 0x73: // BOX DRAWINGS LIGHT DIAGONAL CROSS + paint.drawLines(lines, 2); + break; + } + + paint.setPen(origPen); + return true; +} + +static inline bool drawBlockCharacter(QPainter &paint, int x, int y, int w, int h, uchar code, + bool bold) +{ + Q_UNUSED(bold); + + const QColor color = paint.pen().color(); + + // Center point + const QPointF center = { + x + w/2.0, + y + h/2.0, + }; + + // Default rect fills entire cell + QRectF rect(x, y, w, h); + + // LOWER ONE EIGHTH BLOCK to LEFT ONE EIGHTH BLOCK + if (code >= 0x81 && code <= 0x8f) { + if (code < 0x88) { // Horizontal + const qreal height = h * (0x88 - code) / 8.0; + rect.setY(y + height); + rect.setHeight(h - height); + } else if (code > 0x88) { // Vertical + const qreal width = w * (0x90 - code) / 8.0; + rect.setWidth(width); + } + paint.fillRect(rect, color); + + return true; + } + + // Combinations of quarter squares + // LEFT ONE EIGHTH BLOCK to QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT + if (code >= 0x96 && code <= 0x9F) { + const QRectF upperLeft (x , y , w/2.0, h/2.0); + const QRectF upperRight(center.x(), y , w/2.0, h/2.0); + const QRectF lowerLeft (x , center.y(), w/2.0, h/2.0); + const QRectF lowerRight(center.x(), center.y(), w/2.0, h/2.0); + + QPainterPath path; + + switch (code) { + case 0x96: // ▖ + path.addRect(lowerLeft); + break; + case 0x97: // ▗ + path.addRect(lowerRight); + break; + case 0x98: // ▘ + path.addRect(upperLeft); + break; + case 0x99: // ▙ + path.addRect(upperLeft); + path.addRect(lowerLeft); + path.addRect(lowerRight); + break; + case 0x9a: // ▚ + path.addRect(upperLeft); + path.addRect(lowerRight); + break; + case 0x9b: // ▛ + path.addRect(upperLeft); + path.addRect(upperRight); + path.addRect(lowerLeft); + break; + case 0x9c: // ▜ + path.addRect(upperLeft); + path.addRect(upperRight); + path.addRect(lowerRight); + break; + case 0x9d: // ▝ + path.addRect(upperRight); + break; + case 0x9e: // ▞ + path.addRect(upperRight); + path.addRect(lowerLeft); + break; + case 0x9f: // ▟ + path.addRect(upperRight); + path.addRect(lowerLeft); + path.addRect(lowerRight); + break; + } + paint.fillPath(path, color); + + return true; + } + + QBrush lightShade, mediumShade, darkShade; + if (paint.testRenderHint(QPainter::Antialiasing)) { + lightShade = QColor(color.red(), color.green(), color.blue(), 64); + mediumShade = QColor(color.red(), color.green(), color.blue(), 128); + darkShade = QColor(color.red(), color.green(), color.blue(), 192); + } else { + lightShade = QBrush(color, Qt::Dense6Pattern); + mediumShade = QBrush(color, Qt::Dense4Pattern); + darkShade = QBrush(color, Qt::Dense2Pattern); + } + // And the random stuff + switch (code) { + case 0x80: // Top half block + rect.setHeight(h/2.0); + paint.fillRect(rect, color); + return true; + case 0x90: // Right half block + rect.moveLeft(center.x()); + paint.fillRect(rect, color); + return true; + case 0x94: // Top one eighth block + rect.setHeight(h/8.0); + paint.fillRect(rect, color); + return true; + case 0x95: { // Right one eighth block + const qreal width = 7 * w / 8.0; + rect.moveLeft(x + width); + paint.fillRect(rect, color); + return true; + } + case 0x91: // Light shade + paint.fillRect(rect, lightShade); + return true; + case 0x92: // Medium shade + paint.fillRect(rect, mediumShade); + return true; + case 0x93: // Dark shade + paint.fillRect(rect, darkShade); + return true; + + default: + return false; + } +} + +void draw(QPainter &paint, const QRect &cellRect, const QChar &chr, bool bold) +{ + static const ushort FirstBoxDrawingCharacterCodePoint = 0x2500; + const uchar code = chr.unicode() - FirstBoxDrawingCharacterCodePoint; + + int x = cellRect.x(); + int y = cellRect.y(); + int w = cellRect.width(); + int h = cellRect.height(); + + // Each function below returns true when it has drawn the character, false otherwise. + drawBasicLineCharacter(paint, x, y, w, h, code, bold) + || drawDashedLineCharacter(paint, x, y, w, h, code, bold) + || drawRoundedCornerLineCharacter(paint, x, y, w, h, code, bold) + || drawDiagonalLineCharacter(paint, x, y, w, h, code, bold) + || drawBlockCharacter(paint, x, y, w, h, code, bold); +} + +} // namespace LineBlockCharacters +} // namespace Konsole diff --git a/src/LineFont.h b/src/LineFont.h deleted file mode 100644 --- a/src/LineFont.h +++ /dev/null @@ -1,21 +0,0 @@ -// WARNING: Autogenerated by "fontembedder /Volumes/Projects/KDE/src/kde/applications/konsole/src/LineFont.src". -// You probably do not want to hand-edit this! - -static const quint32 LineChars[] = { - 0x00007c00, 0x000fffe0, 0x00421084, 0x00e739ce, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00427000, 0x004e7380, 0x00e77800, 0x00ef7bc0, - 0x00421c00, 0x00439ce0, 0x00e73c00, 0x00e7bde0, 0x00007084, 0x000e7384, 0x000079ce, 0x000f7bce, - 0x00001c84, 0x00039ce4, 0x00003dce, 0x0007bdee, 0x00427084, 0x004e7384, 0x004279ce, 0x00e77884, - 0x00e779ce, 0x004f7bce, 0x00ef7bc4, 0x00ef7bce, 0x00421c84, 0x00439ce4, 0x00423dce, 0x00e73c84, - 0x00e73dce, 0x0047bdee, 0x00e7bde4, 0x00e7bdee, 0x00427c00, 0x0043fce0, 0x004e7f80, 0x004fffe0, - 0x00e77c00, 0x00e7fde0, 0x00ef7fc0, 0x00efffe0, 0x00007c84, 0x0003fce4, 0x000e7f84, 0x000fffe4, - 0x00007dce, 0x0007fdee, 0x000f7fce, 0x000fffee, 0x00427c84, 0x0043fce4, 0x004e7f84, 0x004fffe4, - 0x00427dce, 0x00e77c84, 0x00e77dce, 0x0047fdee, 0x004f7fce, 0x00e7fde4, 0x00ef7fc4, 0x004fffee, - 0x00efffe4, 0x00e7fdee, 0x00ef7fce, 0x00efffee, 0x00000000, 0x00000000, 0x00000000, 0x00000000, - 0x000f83e0, 0x00a5294a, 0x004e1380, 0x00a57800, 0x00ad0bc0, 0x004390e0, 0x00a53c00, 0x00a5a1e0, - 0x000e1384, 0x0000794a, 0x000f0b4a, 0x000390e4, 0x00003d4a, 0x0007a16a, 0x004e1384, 0x00a5694a, - 0x00ad0b4a, 0x004390e4, 0x00a52d4a, 0x00a5a16a, 0x004f83e0, 0x00a57c00, 0x00ad83e0, 0x000f83e4, - 0x00007d4a, 0x000f836a, 0x004f93e4, 0x00a57d4a, 0x00ad836a, 0x00000000, 0x00000000, 0x00000000, - 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00001c00, 0x00001084, 0x00007000, 0x00421000, - 0x00039ce0, 0x000039ce, 0x000e7380, 0x00e73800, 0x000e7f80, 0x00e73884, 0x0003fce0, 0x004239ce -}; diff --git a/src/LineFont.src b/src/LineFont.src deleted file mode 100644 --- a/src/LineFont.src +++ /dev/null @@ -1,786 +0,0 @@ -#2500: single horizontal line -2500 - - ------ - - - -#2501: triple horizontal line -2501 - ------ ------ ------ - - -#2502: single vertical line -2502 - | - | - | - | - | - -#2503: triple vertical line -2503 - ||| - ||| - ||| - ||| - ||| - -#2504-250B are dashed - not handled - -#250C: top-left corner (lines on bottom + right) -250C - - - .-- - | - | - -#250D: as above, but top line triple-width -250D - - .-- - .-- - |-- - | - -#250E: now the vert line triple-width -250E - - - ..-- - ||| - ||| - -#250F: and now both lines triple-width -250F - - .___ - |.-- - ||._ - ||| - -#2510: top-right corner -2510 - - ---. - | - | - -2511 - -==. -==. -==| - | - -2512 - - -==.. - ||| - ||| - -2513 - -===. -==.| -=.|| - ||| - -#2514: bottom-left corner -2514 - | - | - .== - - - -2515 - | - |== - |== - === - - - -2516 - ||| - ||| - |.== - - - -2517 - ||| - ||.= - |.== - .=== - - -#2518: bottm-right corner -2518 - | - | -==. - - - -2519 - | -==| -==| -=== - - - -251A - ||| - ||| -==== - - - -251B - ||| -=.|| -==.| -===. - - -#251C: Join of vertical line and one from the right -251C - | - | - |== - | - | - -251D - | - |== - |== - |== - | - -251E - ||| - ||| - ||== - | - | - -251F - | - | - ||== - ||| - ||| - - -2520 - ||| - ||| - ||== - ||| - ||| - -2521 - ||| - |||= - ||== - .|== - | - -2522 - | - .|== - ||== - |||= - ||| - -2523 - ||| - ||.= - ||== - ||.= - ||| - -#2524: Join of vertical line and one from the left -2524 - | - | -==| - | - | - -2525 - | -==| -==| -==| - | - -2526 - ||| - ||| -==+| - | - | - -2527 - | - | -==+| - ||| - ||| - -2528 - ||| - ||| -==+| - ||| - ||| - -2529 - ||| -=+|| -==+| -===+ - | - -252A - | -=+|| -==+| -===+ - ||| - -252B - ||| -=+|| -==+| -=+|| - ||| - -#252C: horizontal line joined to from below -252C - - -===== - | - | - -252D - -=== -==|== -==| - | - -252E - - === -==|== - |== - | - -252F - -==+== -==|== -==|== - | - -2530 - - -===== - ||| - ||| - -2531 - -===| -==||= -=||| - ||| - -2532 - - |=== -=||== - ||== - ||| - -2533 - -===== -==|== -=+|+= - ||| - -#2534: bottom line, connected to from top -2534 - | - | -===== - - - -2535 - | -==| -===== -=== - - -2536 - | - |== -===== - === - - -2537 - | -==|== -===== -===== - - -2538 - ||| - ||| -===== - - - -2539 - ||| -==|| -===== -===| - - - -253A - ||| - ||== -=|=== - |=== - - -253B - ||| -==|== -===== -===== - - -#253C: vertical + horizontal lines intersecting -253C - | - | -===== - | - | - -253D - | -==| -===== -==| - | - -253E - | - |== -===== - |== - | - -253F - | -==|== -===== -==|== - | - -2540 - ||| - ||| -===== - | - | - -2541 - | - | -===== - ||| - ||| - -2542 - ||| - ||| -===== - ||| - ||| - -2543 - ||| -=||| -===== -==|+ - | - -2544 - ||| - ||== -===== - ||== - | - -2545 - | -==|+ -===== -=||| - ||| - -2546 - | - ||== -===== - ||== - ||| - -2547 - ||| -=|||= -===== -=|||= - | - -2548 - | -=|||= -===== -=|||= - ||| - -2549 - ||| -=||| -===== -=||| - ||| - -254A - ||| - |||= -===== - |||= - ||| - -254B - ||| -=|||= -===== -=|||= - ||| - -#254C-254F are dashed -2550 - -_____ - -_____ - - -2551 - | | - | | - | | - | | - | | - -2552 - - |-- - | - |-- - | - -2553 - - - ---- - | | - | | - -2554 - - +--- - | - + +- - | | - -2555 - ---+ - | ---+ - | - -2556 - - --+-+ - | | - | | - -2557 - ----+ - | --+ | - | | - -2558 - | - +-- - | - +-- - -2559 - | | - | | - +-+- - - - -255A - | | - | +- - | - +--- - - -255B - | ---+ - | ---+ - - -255C - | | - | | --+-+ - - -255D - | | --+ | - | ----+ - - -255E - | - +-- - | - +-- - | - -255F - | | - | | - | +- - | | - | | - -2560 - | | - | +- - | - | +- - | | - -2561 - | ---+ - | ---+ - | - -2562 - | | - | | --+ + - | | - | | - -2563 - | | --+ | - | --+ | - | | - -2564 - ------ - ---+-- - | - -2565 - - --+-+- - | | - | | - -2566 - ------ - --+ +- - | | - -2567 - | ---+-- - ------ - - -2568 - | | - | | --+-+- - - - -2569 - | | --+ +- - ------ - - -256A - | ---+-- - | ---+-- - | - -256B - | | - | | --+-+- - | | - | | - -256C - | | --+ +- - --+ +- - | | - -#256F-2570 are curly, -#2571-2573 are slashes and X - -2574 - - -___ - - - -2575 - | - | - | - - - -2576 - - - ___ - - - -2577 - - - | - | - | - -2578 - -___ -___ -___ - - -2579 - ||| - ||| - ||| - - - -257A - - ___ - ___ - ___ - - -257B - - - ||| - ||| - ||| - -257C - - ___ -_____ - ___ - - -257D - | - | - ||| - ||| - ||| - -257E - -___ -_____ -___ - - -257F - ||| - ||| - ||| - | - | diff --git a/src/TerminalDisplay.cpp b/src/TerminalDisplay.cpp --- a/src/TerminalDisplay.cpp +++ b/src/TerminalDisplay.cpp @@ -45,6 +45,7 @@ #include #include #include +#include // KDE #include @@ -63,7 +64,6 @@ #include "konsoledebug.h" #include "TerminalCharacterDecoder.h" #include "Screen.h" -#include "LineFont.h" #include "SessionController.h" #include "ExtendedCharTable.h" #include "TerminalDisplayAccessible.h" @@ -73,6 +73,7 @@ #include "IncrementalSearchBar.h" #include "Profile.h" #include "ViewManager.h" // for colorSchemeForProfile. // TODO: Rewrite this. +#include "LineBlockCharacters.h" using namespace Konsole; @@ -211,7 +212,7 @@ return false; } - return isSupportedLineChar(string.at(0).unicode()); + return LineBlockCharacters::canDraw(string.at(0).unicode()); } void TerminalDisplay::fontChange(const QFont&) @@ -566,382 +567,16 @@ /* */ /* ------------------------------------------------------------------------- */ -/** - A table for emulating the simple (single width) unicode drawing chars. - It represents the 250x - 257x glyphs. If it's zero, we can't use it. - if it's not, it's encoded as follows: imagine a 5x5 grid where the points are numbered - 0 to 24 left to top, top to bottom. Each point is represented by the corresponding bit. - - Then, the pixels basically have the following interpretation: - _|||_ - -...- - -...- - -...- - _|||_ - -where _ = none - | = vertical line. - - = horizontal line. - */ - -enum LineEncode { - TopL = (1 << 1), - TopC = (1 << 2), - TopR = (1 << 3), - - LeftT = (1 << 5), - Int11 = (1 << 6), - Int12 = (1 << 7), - Int13 = (1 << 8), - RightT = (1 << 9), - - LeftC = (1 << 10), - Int21 = (1 << 11), - Int22 = (1 << 12), - Int23 = (1 << 13), - RightC = (1 << 14), - - LeftB = (1 << 15), - Int31 = (1 << 16), - Int32 = (1 << 17), - Int33 = (1 << 18), - RightB = (1 << 19), - - BotL = (1 << 21), - BotC = (1 << 22), - BotR = (1 << 23) -}; - -static void drawLineChar(QPainter& paint, int x, int y, int w, int h, uchar code) -{ - //Calculate cell midpoints, end points. - const int cx = x + w / 2; - const int cy = y + h / 2. - 0.5; - const int ex = x + w - 1; - const int ey = y + h - 1; - - const quint32 toDraw = LineChars[code]; - - //Top _lines: - if ((toDraw & TopL) != 0u) { - paint.drawLine(cx - 1, y, cx - 1, cy - 2); - } - if ((toDraw & TopC) != 0u) { - paint.drawLine(cx, y, cx, cy - 2); - } - if ((toDraw & TopR) != 0u) { - paint.drawLine(cx + 1, y, cx + 1, cy - 2); - } - - //Bot _lines: - if ((toDraw & BotL) != 0u) { - paint.drawLine(cx - 1, cy + 2, cx - 1, ey); - } - if ((toDraw & BotC) != 0u) { - paint.drawLine(cx, cy + 2, cx, ey); - } - if ((toDraw & BotR) != 0u) { - paint.drawLine(cx + 1, cy + 2, cx + 1, ey); - } - - //Left _lines: - if ((toDraw & LeftT) != 0u) { - paint.drawLine(x, cy - 1, cx - 2, cy - 1); - } - if ((toDraw & LeftC) != 0u) { - paint.drawLine(x, cy, cx - 2, cy); - } - if ((toDraw & LeftB) != 0u) { - paint.drawLine(x, cy + 1, cx - 2, cy + 1); - } - - //Right _lines: - if ((toDraw & RightT) != 0u) { - paint.drawLine(cx + 2, cy - 1, ex, cy - 1); - } - if ((toDraw & RightC) != 0u) { - paint.drawLine(cx + 2, cy, ex, cy); - } - if ((toDraw & RightB) != 0u) { - paint.drawLine(cx + 2, cy + 1, ex, cy + 1); - } - - //Intersection points. - if ((toDraw & Int11) != 0u) { - paint.drawPoint(cx - 2, cy - 2); - } - if ((toDraw & Int12) != 0u) { - paint.drawPoint(cx - 1, cy - 2); - } - if ((toDraw & Int13) != 0u) { - paint.drawPoint(cx - 0, cy - 2); - } - - if ((toDraw & Int21) != 0u) { - paint.drawPoint(cx - 2, cy - 1); - } - if ((toDraw & Int22) != 0u) { - paint.drawPoint(cx - 1, cy - 1); - } - if ((toDraw & Int23) != 0u) { - paint.drawPoint(cx - 0, cy - 1); - } - - if ((toDraw & Int31) != 0u) { - paint.drawPoint(cx - 2, cy); - } - if ((toDraw & Int32) != 0u) { - paint.drawPoint(cx - 1, cy); - } - if ((toDraw & Int33) != 0u) { - paint.drawPoint(cx - 0, cy); - } -} - -static void drawOtherChar(QPainter& paint, int x, int y, int w, int h, uchar code) -{ - //Calculate cell midpoints, end points. - const int cx = x + w / 2; - const int cy = y + h / 2. - 0.5; // Compensate for the translation, to match fonts - const int ex = x + w - 1; - const int ey = y + h - 1; - - // Double dashes - if (0x4C <= code && code <= 0x4F) { - const int xHalfGap = qMax(w / 15, 1); - const int yHalfGap = qMax(h / 15, 1); - switch (code) { - case 0x4D: // BOX DRAWINGS HEAVY DOUBLE DASH HORIZONTAL - paint.drawLine(x, cy - 1, cx - xHalfGap - 1, cy - 1); - paint.drawLine(x, cy + 1, cx - xHalfGap - 1, cy + 1); - paint.drawLine(cx + xHalfGap, cy - 1, ex, cy - 1); - paint.drawLine(cx + xHalfGap, cy + 1, ex, cy + 1); - // No break! - Q_FALLTHROUGH(); - case 0x4C: // BOX DRAWINGS LIGHT DOUBLE DASH HORIZONTAL - paint.drawLine(x, cy, cx - xHalfGap - 1, cy); - paint.drawLine(cx + xHalfGap, cy, ex, cy); - break; - case 0x4F: // BOX DRAWINGS HEAVY DOUBLE DASH VERTICAL - paint.drawLine(cx - 1, y, cx - 1, cy - yHalfGap - 1); - paint.drawLine(cx + 1, y, cx + 1, cy - yHalfGap - 1); - paint.drawLine(cx - 1, cy + yHalfGap, cx - 1, ey); - paint.drawLine(cx + 1, cy + yHalfGap, cx + 1, ey); - // No break! - Q_FALLTHROUGH(); - case 0x4E: // BOX DRAWINGS LIGHT DOUBLE DASH VERTICAL - paint.drawLine(cx, y, cx, cy - yHalfGap - 1); - paint.drawLine(cx, cy + yHalfGap, cx, ey); - break; - } - } - - // Rounded corner characters - else if (0x6D <= code && code <= 0x70) { - const int r = w * 3 / 8; - const int d = 2 * r; - switch (code) { - case 0x6D: // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT - paint.drawLine(cx, cy + r, cx, ey); - paint.drawLine(cx + r, cy, ex, cy); - paint.drawArc(cx, cy, d, d, 90 * 16, 90 * 16); - break; - case 0x6E: // BOX DRAWINGS LIGHT ARC DOWN AND LEFT - paint.drawLine(cx, cy + r, cx, ey); - paint.drawLine(x, cy, cx - r, cy); - paint.drawArc(cx - d, cy, d, d, 0 * 16, 90 * 16); - break; - case 0x6F: // BOX DRAWINGS LIGHT ARC UP AND LEFT - paint.drawLine(cx, y, cx, cy - r); - paint.drawLine(x, cy, cx - r, cy); - paint.drawArc(cx - d, cy - d, d, d, 270 * 16, 90 * 16); - break; - case 0x70: // BOX DRAWINGS LIGHT ARC UP AND RIGHT - paint.drawLine(cx, y, cx, cy - r); - paint.drawLine(cx + r, cy, ex, cy); - paint.drawArc(cx, cy - d, d, d, 180 * 16, 90 * 16); - break; - } - } - - // Diagonals - else if (0x71 <= code && code <= 0x73) { - switch (code) { - case 0x71: // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT - paint.drawLine(ex, y, x, ey); - break; - case 0x72: // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT - paint.drawLine(x, y, ex, ey); - break; - case 0x73: // BOX DRAWINGS LIGHT DIAGONAL CROSS - paint.drawLine(ex, y, x, ey); - paint.drawLine(x, y, ex, ey); - break; - } - } -} - -static void drawBlockChar(QPainter& paint, int x, int y, int w, int h, uchar code) -{ - const QColor color = paint.pen().color(); - - const float left = x - 0.5; - const float top = y - 0.5; - - const float cx = left + w / 2; - const float cy = top + h / 2; - const float right = x + w - 0.5; - const float bottom = y + h - 0.5; - - // Default rect fills entire cell - QRectF rect(left, top, w, h); - - // LOWER ONE EIGHTH BLOCK to LEFT ONE EIGHTH BLOCK - if (code >= 0x81 && code <= 0x8f) { - if (code < 0x88) { // Horizontal - const int height = h * (0x88 - code) / 8; - rect.setY(top + height); - rect.setHeight(h - height); - } else if (code > 0x88) { // Vertical - const int width = w * (0x90 - code) / 8; - rect.setWidth(width); - } - - paint.fillRect(rect, color); - - return; - } - - // Combinations of quarter squares - // LEFT ONE EIGHTH BLOCK to QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT - if (code >= 0x96 && code <= 0x9F) { - bool upperLeft = false, upperRight = false, - lowerLeft = false, lowerRight = false; - - switch(code) { - case 0x96: - lowerLeft = true; - break; - case 0x97: - lowerRight = true; - break; - case 0x98: - upperLeft = true; - break; - case 0x99: - upperLeft = true; - lowerLeft = true; - lowerRight = true; - break; - case 0x9a: - upperLeft = true; - lowerRight = true; - break; - case 0x9b: - upperLeft = true; - upperRight = true; - lowerLeft = true; - break; - case 0x9c: - upperLeft = true; - upperRight = true; - lowerRight = true; - break; - case 0x9d: - upperRight = true; - break; - case 0x9e: - upperRight = true; - lowerLeft = true; - break; - case 0x9f: - upperRight = true; - lowerLeft = true; - lowerRight = true; - break; - default: - break; - } - - if (upperLeft) { - paint.fillRect(QRectF(QPointF(left, top), QPointF(cx, cy)), color); - } - if (upperRight) { - paint.fillRect(QRectF(QPointF(cx, top), QPointF(right, cy)), color); - } - if (lowerLeft) { - paint.fillRect(QRectF(QPointF(left, cy), QPointF(cx, bottom)), color); - } - if (lowerRight) { - paint.fillRect(QRectF(QPointF(cx, cy), QPointF(right, bottom)), color); - } - - return; - } - - // And the random stuff - switch(code) { - case 0x80: // Top half block - rect.setHeight(h / 2); - paint.fillRect(rect, color); - return; - case 0x90: // Right half block - paint.fillRect(QRectF(QPointF(cx, top), QPointF(right, bottom)), color); - return; - case 0x94: // Top one eighth block - rect.setHeight(h / 8); - paint.fillRect(rect, color); - return; - case 0x95: { // Right one eighth block - const float width = 7 * w / 8; - rect.setX(left + width); - rect.setWidth(w - width); - paint.fillRect(rect, color); - return; - } - case 0x91: // Light shade - paint.fillRect(rect, QBrush(color, Qt::Dense6Pattern)); - return; - case 0x92: // Medium shade - paint.fillRect(rect, QBrush(color, Qt::Dense4Pattern)); - return; - case 0x93: // Dark shade - paint.fillRect(rect, QBrush(color, Qt::Dense2Pattern)); - return; - - default: - break; - } -} - void TerminalDisplay::drawLineCharString(QPainter& painter, int x, int y, const QString& str, const Character* attributes) { - painter.save(); - - // For antialiasing, we need to shift it so the single pixel width is in the middle - painter.translate(0.5, 0.5); - - if (((attributes->rendition & RE_BOLD) != 0) && _boldIntense) { - QPen boldPen(painter.pen()); - boldPen.setWidth(4); - painter.setPen(boldPen); - } - + const bool useBoldPen = (attributes->rendition & RE_BOLD) != 0 && _boldIntense; + QRect cellRect = {x, y, _fontWidth, _fontHeight}; for (int i = 0 ; i < str.length(); i++) { - const uchar code = str[i].cell(); - - if (code >= 0x80 && code <= 0x9F) { // UPPER HALF BLOCK to QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT - drawBlockChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code); - } else if (LineChars[code] != 0u) { - drawLineChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code); - } else { - drawOtherChar(painter, x + (_fontWidth * i), y, _fontWidth, _fontHeight, code); - } + LineBlockCharacters::draw(painter, cellRect.translated(i * _fontWidth, 0), str[i], + useBoldPen); } - - painter.restore(); } void TerminalDisplay::setKeyboardCursorShape(Enum::CursorShapeEnum shape) @@ -1135,6 +770,9 @@ painter.setPen(color); } + const bool origClipping = painter.hasClipping(); + const auto origClipRegion = painter.clipRegion(); + painter.setClipRect(rect); // draw text if (isLineCharString(text) && !_useFontLineCharacters) { drawLineCharString(painter, rect.x(), rect.y(), text, style); @@ -1145,14 +783,14 @@ // This still allows RTL characters to be rendered in the RTL way. painter.setLayoutDirection(Qt::LeftToRight); - painter.setClipRect(rect); if (_bidiEnabled) { painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, text); } else { painter.drawText(rect.x(), rect.y() + _fontAscent + _lineSpacing, LTR_OVERRIDE_CHAR + text); } - painter.setClipping(false); } + painter.setClipRegion(origClipRegion); + painter.setClipping(origClipping); } void TerminalDisplay::drawTextFragment(QPainter& painter , @@ -1450,7 +1088,7 @@ if (newLine[x + 0].character == 0u) { continue; } - const bool lineDraw = newLine[x + 0].isLineChar(); + const bool lineDraw = LineBlockCharacters::canDraw(newLine[x + 0].character); const bool doubleWidth = (x + 1 == columnsToUpdate) ? false : (newLine[x + 1].character == 0); const RenditionFlags cr = newLine[x].rendition; const CharacterColor clipboard = newLine[x].backgroundColor; @@ -1471,7 +1109,7 @@ ch.backgroundColor != clipboard || (ch.rendition & ~RE_EXTENDED_CHAR) != (cr & ~RE_EXTENDED_CHAR) || (dirtyMask[x + len] == 0) || - ch.isLineChar() != lineDraw || + LineBlockCharacters::canDraw(ch.character) != lineDraw || nextIsDoubleWidth != doubleWidth) { break; } @@ -1874,7 +1512,7 @@ } } - const bool lineDraw = _image[loc(x, y)].isLineChar(); + const bool lineDraw = LineBlockCharacters::canDraw(_image[loc(x, y)].character); const bool doubleWidth = (_image[qMin(loc(x, y) + 1, _imageSize - 1)].character == 0); const CharacterColor currentForeground = _image[loc(x, y)].foregroundColor; const CharacterColor currentBackground = _image[loc(x, y)].backgroundColor; diff --git a/tests/line_block_characters_table.py b/tests/line_block_characters_table.py new file mode 100755 --- /dev/null +++ b/tests/line_block_characters_table.py @@ -0,0 +1,67 @@ +#!/usr/bin/python3 + +# Prints tables with all characters supported by LineBlockCharactersDrawer, +# one for normal weight and one for bold. + +first = 0x2500 +last = 0x259F + +cpPerLine = 32 + +lineFmt = "\033[48;5;243;38;5;231m" + +def fmtLine(text): + return "{}\033[{}X {}\033[49;39m".format(lineFmt, cpPerLine*2+1, text) +def fmtCh(text): + return "\033[48;5;231;38;5;16m{}{}".format(text, lineFmt) +def fmtRefCh(text): + return "\033[48;5;252;38;5;16m{}{}".format(text, lineFmt) +def setNoWrap(enable): + print("\033[?7l" if enable else "\033[?7h", end="") +def setBold(enable): + print("\033[1m" if enable else "\033[21m", end="") +def fmtBold(text): + return "\033[1m{}\033[21m".format(text) + +refChars = [["|", "│┃"], ["_-", "─━"], ["L", "└┗"], ["+", "┼╋"], ["=F", "╒╬"], + ["/", "╱"], ["\\", "╲"], ["X", "╳"]] +boxes = \ + " +-----------+ ************* ,============, ╲\\ ╱/\n" \ + " | ┌───────┐ | @ ┏━━━━━━━┓ @ # ╔════════╗ # ╲\\╱/ \n" \ + " | │ Light │ | @ ┃ Heavy ┃ @ # ║ Double ║ # ╳X \n" \ + " | └───────┘ | @ ┗━━━━━━━┛ @ # ╚════════╝ # ╱/╲\\ \n" \ + " +-----------+ ************* \"============\" ╱/ ╲\\\n" \ + +lines = [] +for cp in range(first, last+1): + columnId = int((cp - first) % cpPerLine) + lineId = int((cp - first) / cpPerLine) + if columnId == 0: + lines.append([]) + lines[lineId].append(chr(cp)) + +setNoWrap(True) + +refCharsLine = " ".join(fmtRefCh(rc[0]) + fmtCh(rc[1]) for rc in refChars) +print(fmtLine("{:8s} line width reference: {}".format("Normal", refCharsLine))) + +print(fmtLine("")) +for line in lines: + print(fmtLine(" ".join(fmtCh(ch) for ch in line))) + print(fmtLine("")) + +print("\n" + boxes) + +setBold(True) +refCharsLine = " ".join(fmtRefCh(rc[0]) + fmtCh(rc[1]) for rc in refChars) +print(fmtLine("{:8s} line width reference: {}".format("Bold", refCharsLine))) + +print(fmtLine("")) +for line in lines: + print(fmtLine(" ".join(fmtCh(ch) for ch in line))) + print(fmtLine("")) + +print("\n" + boxes) + +setBold(False) +setNoWrap(False) diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt --- a/tools/CMakeLists.txt +++ b/tools/CMakeLists.txt @@ -2,19 +2,4 @@ ### konsoleprofile command-line tool install(PROGRAMS konsoleprofile DESTINATION ${KDE_INSTALL_BINDIR}) -### Line graphics font -### Attempting to auto-create LineFont.h for multiple systems is a headache. -### If LineFont.h is needed to be recreated use: -### fontembedder LineFont.src > LineFont.h -### Then commit the new LineFont.h -if(KONSOLE_BUILD_FONTEMBEDDER OR KONSOLE_GENERATE_LINEFONT) - - find_package(Qt5Core ${QT_MIN_VERSION} CONFIG REQUIRED) - - ### Font Embedder - set(fontembedder_SRCS fontembedder.cpp) - add_executable(fontembedder ${fontembedder_SRCS}) - target_link_libraries(fontembedder Qt5::Core) -endif() - add_subdirectory( uni2characterwidth ) diff --git a/tools/fontembedder.cpp b/tools/fontembedder.cpp deleted file mode 100644 --- a/tools/fontembedder.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* - This file is part of Konsole, an X terminal. - Copyright 2005 by Maksim Orlovich - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA - 02110-1301 USA. -*/ - -// Standard -#include -#include -#include - -// Qt -#include -#include -#include - -using namespace std; - -static quint32 charVal(QChar val) -{ - if (val == QLatin1Char(' ')) { - return 0; - } else { - return 1; - } -} - -static quint32 readGlyphLine(QTextStream& input) -{ - QString line = input.readLine(); - while (line.length() < 5) { - line += QLatin1Char(' '); - } - - quint32 val = charVal(line[0]) | - (charVal(line[1]) << 1) | - (charVal(line[2]) << 2) | - (charVal(line[3]) << 3) | - (charVal(line[4]) << 4); - return val; -} - -static quint32 readGlyph(QTextStream& input) -{ - return readGlyphLine(input) | - (readGlyphLine(input) << 5) | - (readGlyphLine(input) << 10) | - (readGlyphLine(input) << 15) | - (readGlyphLine(input) << 20); -} - -int main(int argc, char **argv) -{ - if (argc != 2) { - qWarning("usage: fontembedder LineFont.src > LineFont.h"); - exit(1); - } - QFile inFile(QString::fromLocal8Bit(argv[1])); - if (!inFile.open(QIODevice::ReadOnly)) { - qWarning("Can not open %s", argv[1]); - exit(1); - } - - QTextStream input(&inFile); - - // Currently, for Konsole, the input glyphs file ranges from 0x2500 - // (9472) to x257f (9599) so this 128 will handle it. However, if - // more glyphs are added to the input file, this will be an issue. - quint32 glyphStates[128]; - QMap glyphMap; - - for (unsigned int & glyphState : glyphStates) { - glyphState = 0; //nothing.. - } - - while (!input.atEnd()) { - QString line = input.readLine(); - line = line.trimmed(); - if (line.isEmpty()) { - continue; //Skip empty lines - } - if (line[0] == QLatin1Char('#')) { - continue; //Skip comments - } - - //Must be a glyph ID. - int glyph = line.toInt(nullptr, 16); - if ((glyph < 0x2500) || (glyph > 0x257f)) { - qWarning("Invalid glyph number: %d aborting...", glyph); - exit(1); - } else { - glyph = glyph - 0x2500; - - glyphStates[glyph] = readGlyph(input); - // qWarning()<