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