File indexing completed on 2024-12-22 04:09:06
0001 /* 0002 * SPDX-FileCopyrightText: 2023 Alvin Wong <alvin@alvinhc.com> 0003 * 0004 * SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "KisTofuGlyph.h" 0008 0009 #include <QPainterPath> 0010 #include <QPolygon> 0011 #include <QTransform> 0012 #include <QVector> 0013 0014 namespace KisTofuGlyph 0015 { 0016 0017 // These functions build the QPainterPath for each hex char using polygons. 0018 // Each char glyph is formed by 3x5 grid of squares constructed from polygons 0019 // wound in the clockwise direction (counterclockwise to subtract). 0020 0021 static inline QPolygon upperHole() 0022 { 0023 return {QVector<QPoint>{{1, 1}, {1, 2}, {2, 2}, {2, 1}}}; 0024 } 0025 0026 static inline QPolygon lowerHole() 0027 { 0028 return {QVector<QPoint>{{1, 3}, {1, 4}, {2, 4}, {2, 3}}}; 0029 } 0030 0031 static inline QPainterPath hexChar0() 0032 { 0033 static const QPainterPath s_path0 = []() { 0034 const QPolygon b{QVector<QPoint>{{1, 1}, {1, 4}, {2, 4}, {2, 1}}}; 0035 QPainterPath p; 0036 p.addRect(0, 0, 3, 5); 0037 p.addPolygon(b); 0038 return p; 0039 }(); 0040 return s_path0; 0041 } 0042 0043 static inline QPainterPath hexChar1() 0044 { 0045 static const QPainterPath s_path1 = []() { 0046 QPainterPath p; 0047 p.addRect(1, 0, 1, 5); 0048 return p; 0049 }(); 0050 return s_path1; 0051 } 0052 0053 static inline QPainterPath hexChar2() 0054 { 0055 static const QPainterPath s_path2 = []() { 0056 const QPolygon a{QVector<QPoint>{ 0057 {0, 0}, 0058 {3, 0}, 0059 {3, 3}, 0060 {1, 3}, 0061 {1, 4}, 0062 {3, 4}, 0063 {3, 5}, 0064 {0, 5}, 0065 {0, 2}, 0066 {2, 2}, 0067 {2, 1}, 0068 {0, 1}, 0069 }}; 0070 QPainterPath p; 0071 p.addPolygon(a); 0072 return p; 0073 }(); 0074 return s_path2; 0075 } 0076 0077 static inline QPainterPath hexChar3() 0078 { 0079 static const QPainterPath s_path3 = []() { 0080 const QPolygon a{QVector<QPoint>{ 0081 {0, 0}, 0082 {3, 0}, 0083 {3, 5}, 0084 {0, 5}, 0085 {0, 4}, 0086 {2, 4}, 0087 {2, 3}, 0088 {0, 3}, 0089 {0, 2}, 0090 {2, 2}, 0091 {2, 1}, 0092 {0, 1}, 0093 }}; 0094 QPainterPath p; 0095 p.addPolygon(a); 0096 return p; 0097 }(); 0098 return s_path3; 0099 } 0100 0101 static inline QPainterPath hexChar4() 0102 { 0103 static const QPainterPath s_path4 = []() { 0104 const QPolygon a{QVector<QPoint>{ 0105 {0, 0}, 0106 {1, 0}, 0107 {1, 2}, 0108 {2, 2}, 0109 {2, 0}, 0110 {3, 0}, 0111 {3, 5}, 0112 {2, 5}, 0113 {2, 3}, 0114 {0, 3}, 0115 }}; 0116 QPainterPath p; 0117 p.addPolygon(a); 0118 return p; 0119 }(); 0120 return s_path4; 0121 } 0122 0123 static inline QPainterPath hexChar5() 0124 { 0125 static const QPainterPath s_path5 = []() { 0126 // Just mirror a "2". 0127 QPainterPath p = hexChar2(); 0128 return QTransform::fromScale(-1, 1).map(p).toReversed().translated(3, 0); 0129 }(); 0130 return s_path5; 0131 } 0132 0133 static inline QPainterPath hexChar6() 0134 { 0135 static const QPainterPath s_path6 = []() { 0136 const QPolygon a{QVector<QPoint>{ 0137 {0, 0}, 0138 {3, 0}, 0139 {3, 1}, 0140 {1, 1}, 0141 {1, 2}, 0142 {3, 2}, 0143 {3, 5}, 0144 {0, 5}, 0145 }}; 0146 QPainterPath p; 0147 p.addPolygon(a); 0148 p.addPolygon(lowerHole()); 0149 return p; 0150 }(); 0151 return s_path6; 0152 } 0153 0154 static inline QPainterPath hexChar7() 0155 { 0156 static const QPainterPath s_path7 = []() { 0157 const QPolygon a{QVector<QPoint>{ 0158 {0, 0}, 0159 {3, 0}, 0160 {3, 5}, 0161 {2, 5}, 0162 {2, 1}, 0163 {0, 1}, 0164 }}; 0165 QPainterPath p; 0166 p.addPolygon(a); 0167 return p; 0168 }(); 0169 return s_path7; 0170 } 0171 0172 static inline QPainterPath hexChar8() 0173 { 0174 static const QPainterPath s_path8 = []() { 0175 QPainterPath p; 0176 p.addRect(0, 0, 3, 5); 0177 p.addPolygon(upperHole()); 0178 p.addPolygon(lowerHole()); 0179 return p; 0180 }(); 0181 return s_path8; 0182 } 0183 0184 static inline QPainterPath hexChar9() 0185 { 0186 static const QPainterPath s_path9 = []() { 0187 // Just rotate a "6" upside-down 0188 QPainterPath p = hexChar6(); 0189 return QTransform::fromScale(-1, -1).map(p).translated(3, 5); 0190 }(); 0191 return s_path9; 0192 } 0193 0194 static inline QPainterPath hexCharA() 0195 { 0196 static const QPainterPath s_pathA = []() { 0197 const QPolygon a{QVector<QPoint>{ 0198 {0, 0}, 0199 {3, 0}, 0200 {3, 5}, 0201 {2, 5}, 0202 {2, 3}, 0203 {1, 3}, 0204 {1, 5}, 0205 {0, 5}, 0206 }}; 0207 QPainterPath p; 0208 p.addPolygon(a); 0209 p.addPolygon(upperHole()); 0210 return p; 0211 }(); 0212 return s_pathA; 0213 } 0214 0215 static inline QPainterPath hexCharB() 0216 { 0217 static const QPainterPath s_pathB = []() { 0218 const QPolygon a{QVector<QPoint>{ 0219 {0, 0}, 0220 {2, 0}, 0221 {2, 1}, 0222 {3, 1}, 0223 {3, 2}, 0224 {2, 2}, 0225 {2, 3}, 0226 {3, 3}, 0227 {3, 4}, 0228 {2, 4}, 0229 {2, 5}, 0230 {0, 5}, 0231 }}; 0232 QPainterPath p; 0233 p.addPolygon(a); 0234 p.addPolygon(upperHole()); 0235 p.addPolygon(lowerHole()); 0236 return p; 0237 }(); 0238 return s_pathB; 0239 } 0240 0241 static inline QPainterPath hexCharC() 0242 { 0243 static const QPainterPath s_pathC = []() { 0244 const QPolygon a{QVector<QPoint>{ 0245 {0, 0}, 0246 {3, 0}, 0247 {3, 1}, 0248 {1, 1}, 0249 {1, 4}, 0250 {3, 4}, 0251 {3, 5}, 0252 {0, 5}, 0253 }}; 0254 QPainterPath p; 0255 p.addPolygon(a); 0256 return p; 0257 }(); 0258 return s_pathC; 0259 } 0260 0261 static inline QPainterPath hexCharD() 0262 { 0263 static const QPainterPath s_pathD = []() { 0264 const QPolygon a{QVector<QPoint>{ 0265 {0, 0}, 0266 {2, 0}, 0267 {2, 1}, 0268 {1, 1}, 0269 {1, 4}, 0270 {2, 4}, 0271 {2, 5}, 0272 {0, 5}, 0273 }}; 0274 const QPolygon b{QVector<QPoint>{{2, 1}, {3, 1}, {3, 4}, {2, 4}}}; 0275 QPainterPath p; 0276 p.addPolygon(a); 0277 p.addPolygon(b); 0278 return p; 0279 }(); 0280 return s_pathD; 0281 } 0282 0283 static inline QPainterPath hexCharE() 0284 { 0285 static const QPainterPath s_pathE = []() { 0286 const QPolygon a{QVector<QPoint>{ 0287 {0, 0}, 0288 {3, 0}, 0289 {3, 1}, 0290 {1, 1}, 0291 {1, 2}, 0292 {3, 2}, 0293 {3, 3}, 0294 {1, 3}, 0295 {1, 4}, 0296 {3, 4}, 0297 {3, 5}, 0298 {0, 5}, 0299 }}; 0300 QPainterPath p; 0301 p.addPolygon(a); 0302 return p; 0303 }(); 0304 return s_pathE; 0305 } 0306 0307 static inline QPainterPath hexCharF() 0308 { 0309 static const QPainterPath s_pathF = []() { 0310 const QPolygon a{QVector<QPoint>{ 0311 {0, 0}, 0312 {3, 0}, 0313 {3, 1}, 0314 {1, 1}, 0315 {1, 2}, 0316 {3, 2}, 0317 {3, 3}, 0318 {1, 3}, 0319 {1, 5}, 0320 {0, 5}, 0321 }}; 0322 QPainterPath p; 0323 p.addPolygon(a); 0324 return p; 0325 }(); 0326 return s_pathF; 0327 } 0328 0329 static QPainterPath getHexChar(unsigned value) 0330 { 0331 switch (value) { 0332 case 0x0: 0333 return hexChar0(); 0334 case 0x1: 0335 return hexChar1(); 0336 case 0x2: 0337 return hexChar2(); 0338 case 0x3: 0339 return hexChar3(); 0340 case 0x4: 0341 return hexChar4(); 0342 case 0x5: 0343 return hexChar5(); 0344 case 0x6: 0345 return hexChar6(); 0346 case 0x7: 0347 return hexChar7(); 0348 case 0x8: 0349 return hexChar8(); 0350 case 0x9: 0351 return hexChar9(); 0352 case 0xA: 0353 return hexCharA(); 0354 case 0xB: 0355 return hexCharB(); 0356 case 0xC: 0357 return hexCharC(); 0358 case 0xD: 0359 return hexCharD(); 0360 case 0xE: 0361 return hexCharE(); 0362 case 0xF: 0363 return hexCharF(); 0364 } 0365 return {}; 0366 } 0367 0368 /** 0369 * @brief Adds a hex char at the specified row/column to the QPainterPath. 0370 */ 0371 static void addHexChar(QPainterPath &p, unsigned value, int row, int col) 0372 { 0373 QPainterPath glyph = getHexChar(value); 0374 glyph.translate(2 + col * 4, 2 + row * 6); 0375 p.addPath(glyph); 0376 } 0377 0378 /** 0379 * @brief Gets the hex digit at a place. 0380 * 0381 * @param codepoint 0382 * @param place 0-base digit index 0383 * @return the digit 0384 */ 0385 static constexpr unsigned valueAt(const char32_t codepoint, const unsigned place) 0386 { 0387 return (codepoint >> (place * 4)) & 0xF; 0388 } 0389 0390 /** 0391 * @brief Creates the frame of a tofu glyph 0392 */ 0393 static inline QPainterPath makeFrame(const int width) 0394 { 0395 const int inner = width - 1; 0396 const QPolygon a{QVector<QPoint>{{0, 0}, {width, 0}, {width, 15}, {0, 15}}}; 0397 const QPolygon b{QVector<QPoint>{{1, 1}, {1, 14}, {inner, 14}, {inner, 1}}}; 0398 QPainterPath p; 0399 p.addPolygon(a); 0400 p.addPolygon(b); 0401 return p; 0402 } 0403 0404 QPainterPath create(const char32_t codepoint, double height) 0405 { 0406 // We build the glyph as a 15x15 or 11x15 grid of squares. 0407 QPainterPath p; 0408 if (codepoint > 0xFFFF) { 0409 // Codepoints outside the BMP need more than 4 digits to display, so we show 6. 0410 // +---+ 0411 // |01F| 0412 // |389| => U+1F389 0413 // +---+ 0414 static const QPainterPath s_outline15 = makeFrame(15); 0415 p.addPath(s_outline15); 0416 addHexChar(p, valueAt(codepoint, 5), 0, 0); 0417 addHexChar(p, valueAt(codepoint, 4), 0, 1); 0418 addHexChar(p, valueAt(codepoint, 3), 0, 2); 0419 addHexChar(p, valueAt(codepoint, 2), 1, 0); 0420 addHexChar(p, valueAt(codepoint, 1), 1, 1); 0421 addHexChar(p, valueAt(codepoint, 0), 1, 2); 0422 } else { 0423 // +--+ 0424 // |27| 0425 // |64| => U+2764 0426 // +--+ 0427 static const QPainterPath s_outline11 = makeFrame(11); 0428 p.addPath(s_outline11); 0429 addHexChar(p, valueAt(codepoint, 3), 0, 0); 0430 addHexChar(p, valueAt(codepoint, 2), 0, 1); 0431 addHexChar(p, valueAt(codepoint, 1), 1, 0); 0432 addHexChar(p, valueAt(codepoint, 0), 1, 1); 0433 } 0434 const auto scale = (1. / 15.) * height; 0435 return QTransform::fromScale(scale, scale).map(p); 0436 } 0437 0438 } // namespace KisTofuGlyph