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