File indexing completed on 2024-05-05 05:53:44

0001 /*
0002     SPDX-FileCopyrightText: 2018-2019 Mariusz Glebocki <mglb@arccos-1.net>
0003     SPDX-FileCopyrightText: 2018 Martin T. H. Sandsmark <martin.sandsmark@kde.org>
0004     SPDX-FileCopyrightText: 2015-2018 Kurt Hindenburg <kurt.hindenburg@gmail.com>
0005     SPDX-FileCopyrightText: 2005 Maksim Orlovich <maksim@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 // Own
0011 #include "LineBlockCharacters.h"
0012 
0013 // Qt
0014 #include <QPainterPath>
0015 
0016 namespace Konsole
0017 {
0018 namespace LineBlockCharacters
0019 {
0020 enum LineType { LtNone = 0, LtDouble = 1, LtLight = 2, LtHeavy = 3 };
0021 
0022 // PackedLineTypes is an 8-bit number representing types of 4 line character's lines. Each line is
0023 // represented by 2 bits. Lines order, starting from MSB: top, right, bottom, left.
0024 static inline constexpr quint8 makePackedLineTypes(LineType top, LineType right, LineType bottom, LineType left)
0025 {
0026     return (int(top) & 3) << 6 | (int(right) & 3) << 4 | (int(bottom) & 3) << 2 | (int(left) & 3);
0027 }
0028 
0029 /* clang-format off */
0030 static constexpr const quint8 PackedLineTypesLut[] = {
0031     //                  top       right     bottom    left
0032     makePackedLineTypes(LtNone  , LtLight , LtNone  , LtLight ), /* U+2500 ─ */
0033     makePackedLineTypes(LtNone  , LtHeavy , LtNone  , LtHeavy ), /* U+2501 ━ */
0034     makePackedLineTypes(LtLight , LtNone  , LtLight , LtNone  ), /* U+2502 │ */
0035     makePackedLineTypes(LtHeavy , LtNone  , LtHeavy , LtNone  ), /* U+2503 ┃ */
0036     0, 0, 0, 0, 0, 0, 0, 0, /* U+2504-0x250b */
0037     makePackedLineTypes(LtNone  , LtLight , LtLight , LtNone  ), /* U+250C ┌ */
0038     makePackedLineTypes(LtNone  , LtHeavy , LtLight , LtNone  ), /* U+250D ┍ */
0039     makePackedLineTypes(LtNone  , LtLight , LtHeavy , LtNone  ), /* U+250E ┎ */
0040     makePackedLineTypes(LtNone  , LtHeavy , LtHeavy , LtNone  ), /* U+250F ┏ */
0041     makePackedLineTypes(LtNone  , LtNone  , LtLight , LtLight ), /* U+2510 ┐ */
0042     makePackedLineTypes(LtNone  , LtNone  , LtLight , LtHeavy ), /* U+2511 ┑ */
0043     makePackedLineTypes(LtNone  , LtNone  , LtHeavy , LtLight ), /* U+2512 ┒ */
0044     makePackedLineTypes(LtNone  , LtNone  , LtHeavy , LtHeavy ), /* U+2513 ┓ */
0045     makePackedLineTypes(LtLight , LtLight , LtNone  , LtNone  ), /* U+2514 └ */
0046     makePackedLineTypes(LtLight , LtHeavy , LtNone  , LtNone  ), /* U+2515 ┕ */
0047     makePackedLineTypes(LtHeavy , LtLight , LtNone  , LtNone  ), /* U+2516 ┖ */
0048     makePackedLineTypes(LtHeavy , LtHeavy , LtNone  , LtNone  ), /* U+2517 ┗ */
0049     makePackedLineTypes(LtLight , LtNone  , LtNone  , LtLight ), /* U+2518 ┘ */
0050     makePackedLineTypes(LtLight , LtNone  , LtNone  , LtHeavy ), /* U+2519 ┙ */
0051     makePackedLineTypes(LtHeavy , LtNone  , LtNone  , LtLight ), /* U+251A ┚ */
0052     makePackedLineTypes(LtHeavy , LtNone  , LtNone  , LtHeavy ), /* U+251B ┛ */
0053     makePackedLineTypes(LtLight , LtLight , LtLight , LtNone  ), /* U+251C ├ */
0054     makePackedLineTypes(LtLight , LtHeavy , LtLight , LtNone  ), /* U+251D ┝ */
0055     makePackedLineTypes(LtHeavy , LtLight , LtLight , LtNone  ), /* U+251E ┞ */
0056     makePackedLineTypes(LtLight , LtLight , LtHeavy , LtNone  ), /* U+251F ┟ */
0057     makePackedLineTypes(LtHeavy , LtLight , LtHeavy , LtNone  ), /* U+2520 ┠ */
0058     makePackedLineTypes(LtHeavy , LtHeavy , LtLight , LtNone  ), /* U+2521 ┡ */
0059     makePackedLineTypes(LtLight , LtHeavy , LtHeavy , LtNone  ), /* U+2522 ┢ */
0060     makePackedLineTypes(LtHeavy , LtHeavy , LtHeavy , LtNone  ), /* U+2523 ┣ */
0061     makePackedLineTypes(LtLight , LtNone  , LtLight , LtLight ), /* U+2524 ┤ */
0062     makePackedLineTypes(LtLight , LtNone  , LtLight , LtHeavy ), /* U+2525 ┥ */
0063     makePackedLineTypes(LtHeavy , LtNone  , LtLight , LtLight ), /* U+2526 ┦ */
0064     makePackedLineTypes(LtLight , LtNone  , LtHeavy , LtLight ), /* U+2527 ┧ */
0065     makePackedLineTypes(LtHeavy , LtNone  , LtHeavy , LtLight ), /* U+2528 ┨ */
0066     makePackedLineTypes(LtHeavy , LtNone  , LtLight , LtHeavy ), /* U+2529 ┩ */
0067     makePackedLineTypes(LtLight , LtNone  , LtHeavy , LtHeavy ), /* U+252A ┪ */
0068     makePackedLineTypes(LtHeavy , LtNone  , LtHeavy , LtHeavy ), /* U+252B ┫ */
0069     makePackedLineTypes(LtNone  , LtLight , LtLight , LtLight ), /* U+252C ┬ */
0070     makePackedLineTypes(LtNone  , LtLight , LtLight , LtHeavy ), /* U+252D ┭ */
0071     makePackedLineTypes(LtNone  , LtHeavy , LtLight , LtLight ), /* U+252E ┮ */
0072     makePackedLineTypes(LtNone  , LtHeavy , LtLight , LtHeavy ), /* U+252F ┯ */
0073     makePackedLineTypes(LtNone  , LtLight , LtHeavy , LtLight ), /* U+2530 ┰ */
0074     makePackedLineTypes(LtNone  , LtLight , LtHeavy , LtHeavy ), /* U+2531 ┱ */
0075     makePackedLineTypes(LtNone  , LtHeavy , LtHeavy , LtLight ), /* U+2532 ┲ */
0076     makePackedLineTypes(LtNone  , LtHeavy , LtHeavy , LtHeavy ), /* U+2533 ┳ */
0077     makePackedLineTypes(LtLight , LtLight , LtNone  , LtLight ), /* U+2534 ┴ */
0078     makePackedLineTypes(LtLight , LtLight , LtNone  , LtHeavy ), /* U+2535 ┵ */
0079     makePackedLineTypes(LtLight , LtHeavy , LtNone  , LtLight ), /* U+2536 ┶ */
0080     makePackedLineTypes(LtLight , LtHeavy , LtNone  , LtHeavy ), /* U+2537 ┷ */
0081     makePackedLineTypes(LtHeavy , LtLight , LtNone  , LtLight ), /* U+2538 ┸ */
0082     makePackedLineTypes(LtHeavy , LtLight , LtNone  , LtHeavy ), /* U+2539 ┹ */
0083     makePackedLineTypes(LtHeavy , LtHeavy , LtNone  , LtLight ), /* U+253A ┺ */
0084     makePackedLineTypes(LtHeavy , LtHeavy , LtNone  , LtHeavy ), /* U+253B ┻ */
0085     makePackedLineTypes(LtLight , LtLight , LtLight , LtLight ), /* U+253C ┼ */
0086     makePackedLineTypes(LtLight , LtLight , LtLight , LtHeavy ), /* U+253D ┽ */
0087     makePackedLineTypes(LtLight , LtHeavy , LtLight , LtLight ), /* U+253E ┾ */
0088     makePackedLineTypes(LtLight , LtHeavy , LtLight , LtHeavy ), /* U+253F ┿ */
0089     makePackedLineTypes(LtHeavy , LtLight , LtLight , LtLight ), /* U+2540 ╀ */
0090     makePackedLineTypes(LtLight , LtLight , LtHeavy , LtLight ), /* U+2541 ╁ */
0091     makePackedLineTypes(LtHeavy , LtLight , LtHeavy , LtLight ), /* U+2542 ╂ */
0092     makePackedLineTypes(LtHeavy , LtLight , LtLight , LtHeavy ), /* U+2543 ╃ */
0093     makePackedLineTypes(LtHeavy , LtHeavy , LtLight , LtLight ), /* U+2544 ╄ */
0094     makePackedLineTypes(LtLight , LtLight , LtHeavy , LtHeavy ), /* U+2545 ╅ */
0095     makePackedLineTypes(LtLight , LtHeavy , LtHeavy , LtLight ), /* U+2546 ╆ */
0096     makePackedLineTypes(LtHeavy , LtHeavy , LtLight , LtHeavy ), /* U+2547 ╇ */
0097     makePackedLineTypes(LtLight , LtHeavy , LtHeavy , LtHeavy ), /* U+2548 ╈ */
0098     makePackedLineTypes(LtHeavy , LtLight , LtHeavy , LtHeavy ), /* U+2549 ╉ */
0099     makePackedLineTypes(LtHeavy , LtHeavy , LtHeavy , LtLight ), /* U+254A ╊ */
0100     makePackedLineTypes(LtHeavy , LtHeavy , LtHeavy , LtHeavy ), /* U+254B ╋ */
0101     0, 0, 0, 0, /* U+254C - U+254F */
0102     makePackedLineTypes(LtNone  , LtDouble, LtNone  , LtDouble), /* U+2550 ═ */
0103     makePackedLineTypes(LtDouble, LtNone  , LtDouble, LtNone  ), /* U+2551 ║ */
0104     makePackedLineTypes(LtNone  , LtDouble, LtLight , LtNone  ), /* U+2552 ╒ */
0105     makePackedLineTypes(LtNone  , LtLight , LtDouble, LtNone  ), /* U+2553 ╓ */
0106     makePackedLineTypes(LtNone  , LtDouble, LtDouble, LtNone  ), /* U+2554 ╔ */
0107     makePackedLineTypes(LtNone  , LtNone  , LtLight , LtDouble), /* U+2555 ╕ */
0108     makePackedLineTypes(LtNone  , LtNone  , LtDouble, LtLight ), /* U+2556 ╖ */
0109     makePackedLineTypes(LtNone  , LtNone  , LtDouble, LtDouble), /* U+2557 ╗ */
0110     makePackedLineTypes(LtLight , LtDouble, LtNone  , LtNone  ), /* U+2558 ╘ */
0111     makePackedLineTypes(LtDouble, LtLight , LtNone  , LtNone  ), /* U+2559 ╙ */
0112     makePackedLineTypes(LtDouble, LtDouble, LtNone  , LtNone  ), /* U+255A ╚ */
0113     makePackedLineTypes(LtLight , LtNone  , LtNone  , LtDouble), /* U+255B ╛ */
0114     makePackedLineTypes(LtDouble, LtNone  , LtNone  , LtLight ), /* U+255C ╜ */
0115     makePackedLineTypes(LtDouble, LtNone  , LtNone  , LtDouble), /* U+255D ╝ */
0116     makePackedLineTypes(LtLight , LtDouble, LtLight , LtNone  ), /* U+255E ╞ */
0117     makePackedLineTypes(LtDouble, LtLight , LtDouble, LtNone  ), /* U+255F ╟ */
0118     makePackedLineTypes(LtDouble, LtDouble, LtDouble, LtNone  ), /* U+2560 ╠ */
0119     makePackedLineTypes(LtLight , LtNone  , LtLight , LtDouble), /* U+2561 ╡ */
0120     makePackedLineTypes(LtDouble, LtNone  , LtDouble, LtLight ), /* U+2562 ╢ */
0121     makePackedLineTypes(LtDouble, LtNone  , LtDouble, LtDouble), /* U+2563 ╣ */
0122     makePackedLineTypes(LtNone  , LtDouble, LtLight , LtDouble), /* U+2564 ╤ */
0123     makePackedLineTypes(LtNone  , LtLight , LtDouble, LtLight ), /* U+2565 ╥ */
0124     makePackedLineTypes(LtNone  , LtDouble, LtDouble, LtDouble), /* U+2566 ╦ */
0125     makePackedLineTypes(LtLight , LtDouble, LtNone  , LtDouble), /* U+2567 ╧ */
0126     makePackedLineTypes(LtDouble, LtLight , LtNone  , LtLight ), /* U+2568 ╨ */
0127     makePackedLineTypes(LtDouble, LtDouble, LtNone  , LtDouble), /* U+2569 ╩ */
0128     makePackedLineTypes(LtLight , LtDouble, LtLight , LtDouble), /* U+256A ╪ */
0129     makePackedLineTypes(LtDouble, LtLight , LtDouble, LtLight ), /* U+256B ╫ */
0130     makePackedLineTypes(LtDouble, LtDouble, LtDouble, LtDouble), /* U+256C ╬ */
0131     0, 0, 0, 0, 0, 0, 0, /* U+256D - U+2573 */
0132     makePackedLineTypes(LtNone  , LtNone  , LtNone  , LtLight ), /* U+2574 ╴ */
0133     makePackedLineTypes(LtLight , LtNone  , LtNone  , LtNone  ), /* U+2575 ╵ */
0134     makePackedLineTypes(LtNone  , LtLight , LtNone  , LtNone  ), /* U+2576 ╶ */
0135     makePackedLineTypes(LtNone  , LtNone  , LtLight , LtNone  ), /* U+2577 ╷ */
0136     makePackedLineTypes(LtNone  , LtNone  , LtNone  , LtHeavy ), /* U+2578 ╸ */
0137     makePackedLineTypes(LtHeavy , LtNone  , LtNone  , LtNone  ), /* U+2579 ╹ */
0138     makePackedLineTypes(LtNone  , LtHeavy , LtNone  , LtNone  ), /* U+257A ╺ */
0139     makePackedLineTypes(LtNone  , LtNone  , LtHeavy , LtNone  ), /* U+257B ╻ */
0140     makePackedLineTypes(LtNone  , LtHeavy , LtNone  , LtLight ), /* U+257C ╼ */
0141     makePackedLineTypes(LtLight , LtNone  , LtHeavy , LtNone  ), /* U+257D ╽ */
0142     makePackedLineTypes(LtNone  , LtLight , LtNone  , LtHeavy ), /* U+257E ╾ */
0143     makePackedLineTypes(LtHeavy , LtNone  , LtLight , LtNone  ), /* U+257F ╿ */
0144 };
0145 /* clang-format on */
0146 
0147 // Bitwise rotate left
0148 template<typename T>
0149 inline static T rotateBitsLeft(T value, quint8 amount)
0150 {
0151     static_assert(std::is_unsigned<T>(), "T must be unsigned type");
0152     Q_ASSERT(amount < sizeof(value) * 8);
0153     return value << amount | value >> (sizeof(value) * 8 - amount);
0154 }
0155 
0156 inline static const QPen pen(const QPainter &paint, uint lineWidth)
0157 {
0158     return QPen(paint.pen().brush(), lineWidth, Qt::SolidLine, Qt::FlatCap, Qt::MiterJoin);
0159 }
0160 
0161 static inline uint lineWidth(uint fontWidth, bool heavy, bool bold)
0162 {
0163     static const qreal LightWidthToFontWidthRatio = 1.0 / 6.5;
0164     static const qreal HeavyHalfExtraToLightRatio = 1.0 / 3.0;
0165     static const qreal BoldCoefficient = 1.5;
0166 
0167     //        ▄▄▄▄▄▄▄ } heavyHalfExtraWidth  ⎫
0168     // ██████████████ } lightWidth           ⎬ heavyWidth
0169     //        ▀▀▀▀▀▀▀                        ⎭
0170     //  light  heavy
0171 
0172     const qreal baseWidth = fontWidth * LightWidthToFontWidthRatio;
0173     const qreal boldCoeff = bold ? BoldCoefficient : 1.0;
0174     // Unless font size is too small, make bold lines at least 1px wider than regular lines
0175     const qreal minWidth = bold && fontWidth >= 7 ? baseWidth + 1.0 : 1.0;
0176     const uint lightWidth = qRound(qMax(baseWidth * boldCoeff, minWidth));
0177     const uint heavyHalfExtraWidth = qRound(qMax(lightWidth * HeavyHalfExtraToLightRatio, 1.0));
0178 
0179     return heavy ? lightWidth + 2 * heavyHalfExtraWidth : lightWidth;
0180 }
0181 
0182 // Draws characters composed of straight solid lines
0183 static bool drawBasicLineCharacter(QPainter &paint, int x, int y, int w, int h, uchar code, bool bold)
0184 {
0185     quint8 packedLineTypes = code >= sizeof(PackedLineTypesLut) ? 0 : PackedLineTypesLut[code];
0186     if (packedLineTypes == 0) {
0187         return false;
0188     }
0189 
0190     const uint lightLineWidth = lineWidth(w, false, bold);
0191     const uint heavyLineWidth = lineWidth(w, true, bold);
0192     // Distance from double line's parallel axis to each line's parallel axis
0193     const uint doubleLinesDistance = lightLineWidth;
0194 
0195     const QPen lightPen = pen(paint, lightLineWidth);
0196     const QPen heavyPen = pen(paint, heavyLineWidth);
0197 
0198     static constexpr const unsigned LinesNum = 4;
0199 
0200     // Pixel aligned center point
0201     const QPointF center = {
0202         x + int(w / 2) + 0.5 * (lightLineWidth % 2),
0203         y + int(h / 2) + 0.5 * (lightLineWidth % 2),
0204     };
0205 
0206     // Lines starting points, on the cell edges
0207     const QPointF origin[] = {
0208         QPointF(center.x(), y),
0209         QPointF(x + w, center.y()),
0210         QPointF(center.x(), y + h),
0211         QPointF(x, center.y()),
0212     };
0213     // Unit vectors with directions from center to the line's origin point
0214     static const QPointF dir[] = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};
0215 
0216     const auto removeLineType = [&packedLineTypes](quint8 lineId) -> void {
0217         lineId = LinesNum - 1 - lineId % LinesNum;
0218         packedLineTypes &= ~(3 << (2 * lineId));
0219     };
0220     const auto getLineType = [&packedLineTypes](quint8 lineId) -> LineType {
0221         lineId = LinesNum - 1 - lineId % LinesNum;
0222         return LineType(packedLineTypes >> 2 * lineId & 3);
0223     };
0224 
0225     QPainterPath lightPath; // PainterPath for light lines (Painter Path Light)
0226     QPainterPath heavyPath; // PainterPath for heavy lines (Painter Path Heavy)
0227     // Returns ppl or pph depending on line type
0228     const auto pathForLine = [&](quint8 lineId) -> QPainterPath & {
0229         Q_ASSERT(getLineType(lineId) != LtNone);
0230         return getLineType(lineId) == LtHeavy ? heavyPath : lightPath;
0231     };
0232 
0233     // Process all single up-down/left-right lines for every character that has them. Doing it here
0234     // reduces amount of combinations below.
0235     // Fully draws: ╋ ╂ ┃ ┿ ┼ │ ━ ─
0236     for (unsigned int topIndex = 0; topIndex < LinesNum / 2; topIndex++) {
0237         unsigned iB = (topIndex + 2) % LinesNum;
0238         const bool isSingleLine = (getLineType(topIndex) == LtLight || getLineType(topIndex) == LtHeavy);
0239         if (isSingleLine && getLineType(topIndex) == getLineType(iB)) {
0240             pathForLine(topIndex).moveTo(origin[topIndex]);
0241             pathForLine(topIndex).lineTo(origin[iB]);
0242             removeLineType(topIndex);
0243             removeLineType(iB);
0244         }
0245     }
0246 
0247     // Find base rotation of a character and map rotated line indices to the original rotation's
0248     // indices. The base rotation is defined as the one with largest packedLineTypes value. This way
0249     // we can use the same code for drawing 4 possible character rotations (see switch() below)
0250 
0251     uint topIndex = 0; // index of an original top line in a base rotation
0252     quint8 basePackedLineTypes = packedLineTypes;
0253     for (uint i = 0; i < LinesNum; i++) {
0254         const quint8 rotatedPackedLineTypes = rotateBitsLeft(packedLineTypes, i * 2);
0255         if (rotatedPackedLineTypes > basePackedLineTypes) {
0256             topIndex = i;
0257             basePackedLineTypes = rotatedPackedLineTypes;
0258         }
0259     }
0260     uint rightIndex = (topIndex + 1) % LinesNum;
0261     uint bottomIndex = (topIndex + 2) % LinesNum;
0262     uint leftIndex = (topIndex + 3) % LinesNum;
0263 
0264     // Common paths
0265     const auto drawDoubleUpRightShorterLine = [&](quint8 top, quint8 right) { // ╚
0266         lightPath.moveTo(origin[top] + dir[right] * doubleLinesDistance);
0267         lightPath.lineTo(center + (dir[right] + dir[top]) * doubleLinesDistance);
0268         lightPath.lineTo(origin[right] + dir[top] * doubleLinesDistance);
0269     };
0270     const auto drawUpRight = [&](quint8 top, quint8 right) { // └┗
0271         pathForLine(top).moveTo(origin[top]);
0272         pathForLine(top).lineTo(center);
0273         pathForLine(top).lineTo(origin[right]);
0274     };
0275 
0276     switch (basePackedLineTypes) {
0277     case makePackedLineTypes(LtHeavy, LtNone, LtLight, LtNone): // ╿ ; ╼ ╽ ╾ ╊ ╇ ╉ ╈ ╀ ┾ ╁ ┽
0278         lightPath.moveTo(origin[bottomIndex]);
0279         lightPath.lineTo(center + dir[topIndex] * lightLineWidth / 2.0);
0280         Q_FALLTHROUGH();
0281     case makePackedLineTypes(LtHeavy, LtNone, LtNone, LtNone): // ╹ ; ╺ ╻ ╸ ┻ ┣ ┳ ┫ ┸ ┝ ┰ ┥
0282     case makePackedLineTypes(LtLight, LtNone, LtNone, LtNone): // ╵ ; ╶ ╷ ╴ ┷ ┠ ┯ ┨ ┴ ├ ┬ ┤
0283         pathForLine(topIndex).moveTo(origin[topIndex]);
0284         pathForLine(topIndex).lineTo(center);
0285         break;
0286 
0287     case makePackedLineTypes(LtHeavy, LtHeavy, LtLight, LtLight): // ╄ ; ╃ ╆ ╅
0288         drawUpRight(bottomIndex, leftIndex);
0289         Q_FALLTHROUGH();
0290     case makePackedLineTypes(LtHeavy, LtHeavy, LtNone, LtNone): // ┗ ; ┛ ┏ ┓
0291     case makePackedLineTypes(LtLight, LtLight, LtNone, LtNone): // └ ; ┘ ┌ ┐
0292         drawUpRight(topIndex, rightIndex);
0293         break;
0294 
0295     case makePackedLineTypes(LtHeavy, LtLight, LtNone, LtNone): // ┖ ; ┙ ┍ ┒
0296         qSwap(leftIndex, rightIndex);
0297         Q_FALLTHROUGH();
0298     case makePackedLineTypes(LtHeavy, LtNone, LtNone, LtLight): // ┚ ; ┕ ┎ ┑
0299         lightPath.moveTo(origin[leftIndex]);
0300         lightPath.lineTo(center);
0301         heavyPath.moveTo(origin[topIndex]);
0302         heavyPath.lineTo(center + dir[bottomIndex] * lightLineWidth / 2.0);
0303         break;
0304 
0305     case makePackedLineTypes(LtLight, LtDouble, LtNone, LtNone): // ╘ ; ╜ ╓ ╕
0306         qSwap(leftIndex, rightIndex);
0307         Q_FALLTHROUGH();
0308     case makePackedLineTypes(LtLight, LtNone, LtNone, LtDouble): // ╛ ; ╙ ╒ ╖
0309         lightPath.moveTo(origin[topIndex]);
0310         lightPath.lineTo(center + dir[bottomIndex] * doubleLinesDistance);
0311         lightPath.lineTo(origin[leftIndex] + dir[bottomIndex] * doubleLinesDistance);
0312         lightPath.moveTo(origin[leftIndex] - dir[bottomIndex] * doubleLinesDistance);
0313         lightPath.lineTo(center - dir[bottomIndex] * doubleLinesDistance);
0314         break;
0315 
0316     case makePackedLineTypes(LtHeavy, LtHeavy, LtLight, LtNone): // ┡ ; ┹ ┪ ┲
0317         qSwap(leftIndex, bottomIndex);
0318         qSwap(rightIndex, topIndex);
0319         Q_FALLTHROUGH();
0320     case makePackedLineTypes(LtHeavy, LtHeavy, LtNone, LtLight): // ┺ ; ┩ ┢ ┱
0321         drawUpRight(topIndex, rightIndex);
0322         lightPath.moveTo(origin[leftIndex]);
0323         lightPath.lineTo(center);
0324         break;
0325 
0326     case makePackedLineTypes(LtHeavy, LtLight, LtLight, LtNone): // ┞ ; ┵ ┧ ┮
0327         qSwap(leftIndex, rightIndex);
0328         Q_FALLTHROUGH();
0329     case makePackedLineTypes(LtHeavy, LtNone, LtLight, LtLight): // ┦ ; ┶ ┟ ┭
0330         heavyPath.moveTo(origin[topIndex]);
0331         heavyPath.lineTo(center + dir[bottomIndex] * lightLineWidth / 2.0);
0332         drawUpRight(bottomIndex, leftIndex);
0333         break;
0334 
0335     case makePackedLineTypes(LtLight, LtDouble, LtNone, LtDouble): // ╧ ; ╟ ╢ ╤
0336         lightPath.moveTo(origin[topIndex]);
0337         lightPath.lineTo(center - dir[bottomIndex] * doubleLinesDistance);
0338         qSwap(leftIndex, bottomIndex);
0339         qSwap(rightIndex, topIndex);
0340         Q_FALLTHROUGH();
0341     case makePackedLineTypes(LtDouble, LtNone, LtDouble, LtNone): // ║ ; ╫ ═ ╪
0342         lightPath.moveTo(origin[topIndex] + dir[leftIndex] * doubleLinesDistance);
0343         lightPath.lineTo(origin[bottomIndex] + dir[leftIndex] * doubleLinesDistance);
0344         lightPath.moveTo(origin[topIndex] + dir[rightIndex] * doubleLinesDistance);
0345         lightPath.lineTo(origin[bottomIndex] + dir[rightIndex] * doubleLinesDistance);
0346         break;
0347 
0348     case makePackedLineTypes(LtDouble, LtNone, LtNone, LtNone): // ╨ ; ╞ ╥ ╡
0349         lightPath.moveTo(origin[topIndex] + dir[leftIndex] * doubleLinesDistance);
0350         lightPath.lineTo(center + dir[leftIndex] * doubleLinesDistance);
0351         lightPath.moveTo(origin[topIndex] + dir[rightIndex] * doubleLinesDistance);
0352         lightPath.lineTo(center + dir[rightIndex] * doubleLinesDistance);
0353         break;
0354 
0355     case makePackedLineTypes(LtDouble, LtDouble, LtDouble, LtDouble): // ╬
0356         drawDoubleUpRightShorterLine(topIndex, rightIndex);
0357         drawDoubleUpRightShorterLine(bottomIndex, rightIndex);
0358         drawDoubleUpRightShorterLine(topIndex, leftIndex);
0359         drawDoubleUpRightShorterLine(bottomIndex, leftIndex);
0360         break;
0361 
0362     case makePackedLineTypes(LtDouble, LtDouble, LtDouble, LtNone): // ╠ ; ╩ ╣ ╦
0363         lightPath.moveTo(origin[topIndex] + dir[leftIndex] * doubleLinesDistance);
0364         lightPath.lineTo(origin[bottomIndex] + dir[leftIndex] * doubleLinesDistance);
0365         drawDoubleUpRightShorterLine(topIndex, rightIndex);
0366         drawDoubleUpRightShorterLine(bottomIndex, rightIndex);
0367         break;
0368 
0369     case makePackedLineTypes(LtDouble, LtDouble, LtNone, LtNone): // ╚ ; ╝ ╔ ╗
0370         lightPath.moveTo(origin[topIndex] + dir[leftIndex] * doubleLinesDistance);
0371         lightPath.lineTo(center + (dir[leftIndex] + dir[bottomIndex]) * doubleLinesDistance);
0372         lightPath.lineTo(origin[rightIndex] + dir[bottomIndex] * doubleLinesDistance);
0373         drawDoubleUpRightShorterLine(topIndex, rightIndex);
0374         break;
0375     }
0376 
0377     // Draw paths
0378     if (!lightPath.isEmpty()) {
0379         paint.strokePath(lightPath, lightPen);
0380     }
0381     if (!heavyPath.isEmpty()) {
0382         paint.strokePath(heavyPath, heavyPen);
0383     }
0384 
0385     return true;
0386 }
0387 
0388 static inline bool drawDashedLineCharacter(QPainter &paint, int x, int y, int w, int h, uchar code, bool bold)
0389 {
0390     if (!((0x04 <= code && code <= 0x0B) || (0x4C <= code && code <= 0x4F))) {
0391         return false;
0392     }
0393 
0394     const uint lightLineWidth = lineWidth(w, false, bold);
0395     const uint heavyLineWidth = lineWidth(w, true, bold);
0396 
0397     const auto lightPen = pen(paint, lightLineWidth);
0398     const auto heavyPen = pen(paint, heavyLineWidth);
0399 
0400     // Pixel aligned center point
0401     const QPointF center = {
0402         int(x + w / 2.0) + 0.5 * (lightLineWidth % 2),
0403         int(y + h / 2.0) + 0.5 * (lightLineWidth % 2),
0404     };
0405 
0406     const qreal halfGapH = qMax(w / 20.0, 0.5);
0407     const qreal halfGapV = qMax(h / 26.0, 0.5);
0408     // For some reason vertical double dash has bigger gap
0409     const qreal halfGapDDV = qMax(h / 14.0, 0.5);
0410 
0411     static const int LinesNumMax = 4;
0412 
0413     enum Orientation { Horizontal, Vertical };
0414     struct {
0415         int linesNum;
0416         Orientation orientation;
0417         QPen pen;
0418         qreal halfGap;
0419     } lineProps;
0420 
0421     /* clang-format off */
0422     switch (code) {
0423     case 0x4C: lineProps = {2, Horizontal, lightPen, halfGapH  }; break; // ╌
0424     case 0x4D: lineProps = {2, Horizontal, heavyPen, halfGapH  }; break; // ╍
0425     case 0x4E: lineProps = {2, Vertical  , lightPen, halfGapDDV}; break; // ╎
0426     case 0x4F: lineProps = {2, Vertical  , heavyPen, halfGapDDV}; break; // ╏
0427     case 0x04: lineProps = {3, Horizontal, lightPen, halfGapH  }; break; // ┄
0428     case 0x05: lineProps = {3, Horizontal, heavyPen, halfGapH  }; break; // ┅
0429     case 0x06: lineProps = {3, Vertical  , lightPen, halfGapV  }; break; // ┆
0430     case 0x07: lineProps = {3, Vertical  , heavyPen, halfGapV  }; break; // ┇
0431     case 0x08: lineProps = {4, Horizontal, lightPen, halfGapH  }; break; // ┈
0432     case 0x09: lineProps = {4, Horizontal, heavyPen, halfGapH  }; break; // ┉
0433     case 0x0A: lineProps = {4, Vertical  , lightPen, halfGapV  }; break; // ┊
0434     case 0x0B: lineProps = {4, Vertical  , heavyPen, halfGapV  }; break; // ┋
0435     }
0436     /* clang-format on */
0437 
0438     Q_ASSERT(lineProps.linesNum <= LinesNumMax);
0439     const int size = (lineProps.orientation == Horizontal ? w : h);
0440     const int pos = (lineProps.orientation == Horizontal ? x : y);
0441     QLineF lines[LinesNumMax];
0442 
0443     for (int i = 0; i < lineProps.linesNum; i++) {
0444         const qreal start = pos + qreal(size * (i)) / lineProps.linesNum;
0445         const qreal end = pos + qreal(size * (i + 1)) / lineProps.linesNum;
0446         if (lineProps.orientation == Horizontal) {
0447             lines[i] = QLineF{start + lineProps.halfGap, center.y(), end - lineProps.halfGap, center.y()};
0448         } else {
0449             lines[i] = QLineF{center.x(), start + lineProps.halfGap, center.x(), end - lineProps.halfGap};
0450         }
0451     }
0452 
0453     const auto origPen = paint.pen();
0454 
0455     paint.setPen(lineProps.pen);
0456     paint.drawLines(lines, lineProps.linesNum);
0457 
0458     paint.setPen(origPen);
0459     return true;
0460 }
0461 
0462 static inline bool drawRoundedCornerLineCharacter(QPainter &paint, int x, int y, int w, int h, uchar code, bool bold)
0463 {
0464     if (!(0x6D <= code && code <= 0x70)) {
0465         return false;
0466     }
0467 
0468     const uint lightLineWidth = lineWidth(w, false, bold);
0469     const auto lightPen = pen(paint, lightLineWidth);
0470 
0471     // Pixel aligned center point
0472     const QPointF center = {
0473         int(x + w / 2.0) + 0.5 * (lightLineWidth % 2),
0474         int(y + h / 2.0) + 0.5 * (lightLineWidth % 2),
0475     };
0476 
0477     const int r = w * 3 / 8;
0478     const int d = 2 * r;
0479 
0480     QPainterPath path;
0481 
0482     // lineTo() between moveTo and arcTo is redundant - arcTo() draws line
0483     // to the arc's starting point
0484     switch (code) {
0485     case 0x6D: // BOX DRAWINGS LIGHT ARC DOWN AND RIGHT
0486         path.moveTo(center.x(), y + h);
0487         path.arcTo(center.x(), center.y(), d, d, 180, -90);
0488         path.lineTo(x + w, center.y());
0489         break;
0490     case 0x6E: // BOX DRAWINGS LIGHT ARC DOWN AND LEFT
0491         path.moveTo(center.x(), y + h);
0492         path.arcTo(center.x() - d, center.y(), d, d, 0, 90);
0493         path.lineTo(x, center.y());
0494         break;
0495     case 0x6F: // BOX DRAWINGS LIGHT ARC UP AND LEFT
0496         path.moveTo(center.x(), y);
0497         path.arcTo(center.x() - d, center.y() - d, d, d, 0, -90);
0498         path.lineTo(x, center.y());
0499         break;
0500     case 0x70: // BOX DRAWINGS LIGHT ARC UP AND RIGHT
0501         path.moveTo(center.x(), y);
0502         path.arcTo(center.x(), center.y() - d, d, d, 180, 90);
0503         path.lineTo(x + w, center.y());
0504         break;
0505     }
0506     paint.strokePath(path, lightPen);
0507 
0508     return true;
0509 }
0510 
0511 static inline bool drawDiagonalLineCharacter(QPainter &paint, int x, int y, int w, int h, uchar code, bool bold)
0512 {
0513     if (!(0x71 <= code && code <= 0x73)) {
0514         return false;
0515     }
0516 
0517     const uint lightLineWidth = lineWidth(w, false, bold);
0518     const auto lightPen = pen(paint, lightLineWidth);
0519 
0520     const QLineF lines[] = {
0521         QLineF(x + w, y, x, y + h), // '/'
0522         QLineF(x, y, x + w, y + h), // '\'
0523     };
0524 
0525     const auto origPen = paint.pen();
0526 
0527     paint.setPen(lightPen);
0528     switch (code) {
0529     case 0x71: // BOX DRAWINGS LIGHT DIAGONAL UPPER RIGHT TO LOWER LEFT
0530         paint.drawLine(lines[0]);
0531         break;
0532     case 0x72: // BOX DRAWINGS LIGHT DIAGONAL UPPER LEFT TO LOWER RIGHT
0533         paint.drawLine(lines[1]);
0534         break;
0535     case 0x73: // BOX DRAWINGS LIGHT DIAGONAL CROSS
0536         paint.drawLines(lines, 2);
0537         break;
0538     }
0539 
0540     paint.setPen(origPen);
0541     return true;
0542 }
0543 
0544 static inline bool drawBlockCharacter(QPainter &paint, int x, int y, int w, int h, uchar code, bool bold)
0545 {
0546     Q_UNUSED(bold)
0547 
0548     const QColor color = paint.pen().color();
0549 
0550     // Center point
0551     const QPointF center = {
0552         x + w / 2.0,
0553         y + h / 2.0,
0554     };
0555 
0556     // Default rect fills entire cell
0557     QRectF rect(x, y, w, h);
0558 
0559     // LOWER ONE EIGHTH BLOCK to LEFT ONE EIGHTH BLOCK
0560     if (code >= 0x81 && code <= 0x8f) {
0561         if (code < 0x88) { // Horizontal
0562             const qreal height = h * (0x88 - code) / 8.0;
0563             rect.setY(y + height);
0564             rect.setHeight(h - height);
0565         } else if (code > 0x88) { // Vertical
0566             const qreal width = w * (0x90 - code) / 8.0;
0567             rect.setWidth(width);
0568         }
0569         paint.fillRect(rect, color);
0570 
0571         return true;
0572     }
0573 
0574     // Combinations of quarter squares
0575     // LEFT ONE EIGHTH BLOCK to QUADRANT UPPER RIGHT AND LOWER LEFT AND LOWER RIGHT
0576     if (code >= 0x96 && code <= 0x9F) {
0577         const QRectF upperLeft(x, y, w / 2.0, h / 2.0);
0578         const QRectF upperRight(center.x(), y, w / 2.0, h / 2.0);
0579         const QRectF lowerLeft(x, center.y(), w / 2.0, h / 2.0);
0580         const QRectF lowerRight(center.x(), center.y(), w / 2.0, h / 2.0);
0581 
0582         QPainterPath path;
0583 
0584         switch (code) {
0585         case 0x96: // ▖
0586             path.addRect(lowerLeft);
0587             break;
0588         case 0x97: // ▗
0589             path.addRect(lowerRight);
0590             break;
0591         case 0x98: // ▘
0592             path.addRect(upperLeft);
0593             break;
0594         case 0x99: // ▙
0595             path.addRect(upperLeft);
0596             path.addRect(lowerLeft);
0597             path.addRect(lowerRight);
0598             break;
0599         case 0x9a: // ▚
0600             path.addRect(upperLeft);
0601             path.addRect(lowerRight);
0602             break;
0603         case 0x9b: // ▛
0604             path.addRect(upperLeft);
0605             path.addRect(upperRight);
0606             path.addRect(lowerLeft);
0607             break;
0608         case 0x9c: // ▜
0609             path.addRect(upperLeft);
0610             path.addRect(upperRight);
0611             path.addRect(lowerRight);
0612             break;
0613         case 0x9d: // ▝
0614             path.addRect(upperRight);
0615             break;
0616         case 0x9e: // ▞
0617             path.addRect(upperRight);
0618             path.addRect(lowerLeft);
0619             break;
0620         case 0x9f: // ▟
0621             path.addRect(upperRight);
0622             path.addRect(lowerLeft);
0623             path.addRect(lowerRight);
0624             break;
0625         }
0626         paint.fillPath(path, color);
0627 
0628         return true;
0629     }
0630 
0631     QBrush lightShade;
0632     QBrush mediumShade;
0633     QBrush darkShade;
0634     if (paint.testRenderHint(QPainter::Antialiasing)) {
0635         lightShade = QColor(color.red(), color.green(), color.blue(), 64);
0636         mediumShade = QColor(color.red(), color.green(), color.blue(), 128);
0637         darkShade = QColor(color.red(), color.green(), color.blue(), 192);
0638     } else {
0639         lightShade = QBrush(color, Qt::Dense6Pattern);
0640         mediumShade = QBrush(color, Qt::Dense4Pattern);
0641         darkShade = QBrush(color, Qt::Dense2Pattern);
0642     }
0643     // And the random stuff
0644     switch (code) {
0645     case 0x80: // Top half block
0646         rect.setHeight(h / 2.0);
0647         paint.fillRect(rect, color);
0648         return true;
0649     case 0x90: // Right half block
0650         rect.setX(center.x());
0651         paint.fillRect(rect, color);
0652         return true;
0653     case 0x94: // Top one eighth block
0654         rect.setHeight(h / 8.0);
0655         paint.fillRect(rect, color);
0656         return true;
0657     case 0x95: // Right one eighth block
0658         rect.setX(x + 7 * w / 8.0);
0659         paint.fillRect(rect, color);
0660         return true;
0661     case 0x91: // Light shade
0662         paint.fillRect(rect, lightShade);
0663         return true;
0664     case 0x92: // Medium shade
0665         paint.fillRect(rect, mediumShade);
0666         return true;
0667     case 0x93: // Dark shade
0668         paint.fillRect(rect, darkShade);
0669         return true;
0670 
0671     default:
0672         return false;
0673     }
0674 }
0675 
0676 static inline bool drawLegacyCharacter(QPainter &paint, int x, int y, int w, int h, uint code, bool bold)
0677 {
0678     Q_UNUSED(bold)
0679     QPainterPath path;
0680     const QColor color = paint.pen().color();
0681 
0682     if (code >= 0x13c && code <= 0x16f) {
0683         // Smooth mosaic terminal graphic characters
0684         QVector<QPointF> points(13);
0685         for (int i = 0; i < 12; i++) {
0686             qreal px = x + (i >> 2) * w / 2.0;
0687             qreal py = y + (3 - (i & 3)) * h / 3.0;
0688             points[i] = QPointF(px, py);
0689         }
0690         points[12] = QPointF(x + w / 2.0, y + h / 2.0);
0691         QList<std::string> chars = {
0692             "014",   "018",   "024",   "028",   "034",  "027;8", "02;8",  "017;8", // 0x1fb3c - 0x1fb43
0693             "01;8",  "07;8",  "01:8",  "498",   "098",  "4:8",   "0:8",   "4;8", // 44 - 4b
0694             "037:8", "03:8",  "03798", "0398",  "0378", "0298",  "13;84", "13;8", // 4c - 53
0695             "23;84", "23;8",  "3;84",  "237",   "23;",  "137",   "13;",   "037", // 54 - 5b
0696             "13;:",  "03;94", "03;9",  "03;:4", // 5c-5f
0697             "03;:",  "03;4",  "7;:",   "3;:",   "7;9",  "3;9",   "7;8",   "23;9", // 60-67
0698             "0<3;8", "03<;8", "03;<8", "03;8<", "03<",  "3;<",   "<;8",   "0<8" // 68-6f
0699         };
0700         std::string str = chars[code - 0x13c];
0701         QVector<QPointF> vec;
0702         for (uint i = 0; i < str.length(); i++) {
0703             vec.append(points[str[i] - '0']);
0704         }
0705         path.addPolygon(QPolygonF(vec));
0706         paint.fillPath(path, color);
0707         return true;
0708     }
0709     if (code >= 0x170 && code <= 0x175) {
0710         // Vertical eighths
0711         path.addRect(QRectF(x + (code - 0x16f) * w / 8.0, y, w / 8.0, h));
0712         paint.fillPath(path, color);
0713         return true;
0714     }
0715     if (code >= 0x176 && code <= 0x17b) {
0716         // Horizontal eighths
0717         path.addRect(QRectF(x, y + (code - 0x175) * h / 8.0, w, h / 8.0));
0718         paint.fillPath(path, color);
0719         return true;
0720     }
0721     if (code >= 0x17c && code <= 0x17f) {
0722         // Corner eighths
0723         qreal y1 = y;
0724         qreal y2 = y;
0725         if (code == 0x17c || code == 0x17f) {
0726             y1 += 7 * h / 8.0;
0727         } else {
0728             y2 += h / 8.0;
0729         }
0730         qreal x1 = x;
0731         if (code > 0x17d) {
0732             x1 += 7 * w / 8.0;
0733         }
0734         path.addRect(QRectF(x, y1, w, h / 8.0));
0735         path.addRect(QRectF(x1, y2, w / 8.0, 7 * h / 8.0));
0736         paint.fillPath(path, color);
0737         return true;
0738     }
0739     if (code == 0x180) {
0740         // Horizontal eighths 18
0741         path.addRect(QRectF(x, y, w, h / 8.0));
0742         path.addRect(QRectF(x, y + 7 * h / 8.0, w, h / 8.0));
0743         paint.fillPath(path, color);
0744         return true;
0745     }
0746     if (code == 0x181) {
0747         // Horizontal eighths 1358
0748         path.addRect(QRectF(x, y, w, h / 8.0));
0749         path.addRect(QRectF(x, y + 2 * h / 8.0, w, h / 8.0));
0750         path.addRect(QRectF(x, y + 4 * h / 8.0, w, h / 8.0));
0751         path.addRect(QRectF(x, y + 7 * h / 8.0, w, h / 8.0));
0752         paint.fillPath(path, color);
0753         return true;
0754     }
0755     if (code >= 0x182 && code <= 0x186) {
0756         // Horizontal upper 2,3,5,6,7 eighths
0757         int hs[5] = {2, 3, 5, 6, 7};
0758         path.addRect(QRectF(x, y, w, hs[code - 0x182] * h / 8.0));
0759         paint.fillPath(path, color);
0760         return true;
0761     }
0762     if (code >= 0x187 && code <= 0x18b) {
0763         // Vertical right 2,3,5,6,7 eighths
0764         int hs[5] = {2, 3, 5, 6, 7};
0765         path.addRect(QRectF(x + (8 - hs[code - 0x187]) * w / 8.0, y, hs[code - 0x187] * w / 8.0, h));
0766         paint.fillPath(path, color);
0767         return true;
0768     }
0769 
0770     if (code >= 0x128) {
0771         code += 1;
0772     }
0773     if (code >= 0x114) {
0774         code += 1;
0775     }
0776     code += 1;
0777 
0778     // Center point
0779     const QPointF center = {
0780         x + w / 2.0,
0781         y + h / 2.0,
0782     };
0783 
0784     // Default rect fills entire cell
0785     QRectF rect(x, y, w, h);
0786     if (code <= 0x13f) {
0787         const QRectF upperLeft(x, y, w / 2.0, h / 3.0);
0788         const QRectF upperRight(center.x(), y, w / 2.0, h / 3.0);
0789         const QRectF midLeft(x, y + h / 3.0, w / 2.0, h / 3.0);
0790         const QRectF midRight(center.x(), y + h / 3.0, w / 2.0, h / 3.0);
0791         const QRectF lowerLeft(x, y + 2.0 * h / 3.0, w / 2.0, h / 3.0);
0792         const QRectF lowerRight(center.x(), y + 2.0 * h / 3.0, w / 2.0, h / 3.0);
0793 
0794         if (code & 0x01) {
0795             path.addRect(upperLeft);
0796         }
0797         if (code & 0x02) {
0798             path.addRect(upperRight);
0799         }
0800         if (code & 0x04) {
0801             path.addRect(midLeft);
0802         }
0803         if (code & 0x08) {
0804             path.addRect(midRight);
0805         }
0806         if (code & 0x10) {
0807             path.addRect(lowerLeft);
0808         }
0809         if (code & 0x20) {
0810             path.addRect(lowerRight);
0811         }
0812 
0813         paint.fillPath(path, color);
0814         return true;
0815     }
0816     return false;
0817 }
0818 
0819 void draw(QPainter &paint, const QRect &cellRect, const uint &chr, bool bold)
0820 {
0821     static const ushort FirstBoxDrawingCharacterCodePoint = 0x2500;
0822     static const uint FirstLegacyCharacterCodePoint = 0x1fb00;
0823     uint code;
0824     if (chr >= FirstLegacyCharacterCodePoint) {
0825         code = chr - FirstLegacyCharacterCodePoint + 0x100;
0826     } else {
0827         code = chr - FirstBoxDrawingCharacterCodePoint;
0828     }
0829 
0830     int x = cellRect.x();
0831     int y = cellRect.y();
0832     int w = cellRect.width();
0833     int h = cellRect.height();
0834 
0835     if (code >= 0x100) {
0836         drawLegacyCharacter(paint, x, y, w, h, code, bold);
0837         return;
0838     }
0839     // Each function below returns true when it has drawn the character, false otherwise.
0840     drawBasicLineCharacter(paint, x, y, w, h, code, bold) || drawDashedLineCharacter(paint, x, y, w, h, code, bold)
0841         || drawRoundedCornerLineCharacter(paint, x, y, w, h, code, bold) || drawDiagonalLineCharacter(paint, x, y, w, h, code, bold)
0842         || drawBlockCharacter(paint, x, y, w, h, code, bold);
0843 }
0844 
0845 } // namespace LineBlockCharacters
0846 } // namespace Konsole