File indexing completed on 2024-04-28 04:05:20

0001 /*
0002     SPDX-FileCopyrightText: 2015 Jakob Gruber <jakob.gruber@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <config.h>
0008 #include "renderer.h"
0009 
0010 #include <QDir>
0011 #include <QFile>
0012 #include <QPainter>
0013 #include <QPixmapCache>
0014 #include <QStandardPaths>
0015 #include <assert.h>
0016 #include <iostream>
0017 
0018 #include "src/constants.h"
0019 #include "src/outofboundsexception.h"
0020 #include "src/logic/settings.h"
0021 #include "src/systemexception.h"
0022 
0023 #define MIN_STREAK_COUNT (4)
0024 
0025 Renderer::Renderer() : m_tilesize(47), m_overview_tilesize(12),
0026     m_streak_grid_count(6)
0027 {
0028     loadResources();
0029 
0030     m_names << QStringLiteral("transparent") << QStringLiteral("background") << QStringLiteral("cellframe")
0031             << QStringLiteral("box") << QStringLiteral("cross") << QStringLiteral("highlight") << QStringLiteral("streak1")
0032             << QStringLiteral("streak2") << QStringLiteral("divider") << QStringLiteral("overview_box")
0033             << QStringLiteral("overview_cross");
0034 }
0035 
0036 void Renderer::loadResources() {
0037 
0038     /* Fonts. */
0039 
0040     for (int i = 0; i < FontSizeLength; i++) {
0041         m_fonts[i].setPointSize(24);
0042     }
0043 
0044     /* Tiles. */
0045 
0046     const QString prefix = QStringLiteral("themes/");
0047     QList<QString> paths;
0048     paths << QString(prefix)
0049           << QStringLiteral(FILEPATH) + QStringLiteral("/") + prefix
0050           << QStandardPaths::locate(QStandardPaths::AppDataLocation,
0051                                     prefix,
0052                                     QStandardPaths::LocateOption::LocateDirectory);
0053 
0054     /* try loading first from working directory, then the system directories */
0055     for (int i = 0; i < paths.size(); i++) {
0056         const QString filenameSvg = QDir::toNativeSeparators(paths[i] + QStringLiteral("picmi.svgz"));
0057 
0058         if (!QFile::exists(filenameSvg)) {
0059             continue;
0060         }
0061         m_renderer = QSharedPointer<QSvgRenderer>(new QSvgRenderer(filenameSvg));
0062         return;
0063     }
0064 
0065     throw SystemException(QStringLiteral("Resources not found"));
0066 }
0067 
0068 int Renderer::gridSize(const QSize &size, int board_width, int board_height) const {
0069     int grid = size.width() / (board_width + m_streak_grid_count);
0070 
0071     if ((board_height + m_streak_grid_count) * grid > size.height()) {
0072         grid = size.height() / (board_height + m_streak_grid_count);
0073     }
0074 
0075     return grid;
0076 }
0077 
0078 bool Renderer::streaksFit(const QStringList &streaks) const {
0079     QFontMetrics fm(m_fonts[Regular]);
0080 
0081     /* Subtract a little from real size to account for padding. */
0082     const int len = m_streak_grid_count * m_tilesize - m_tilesize;
0083     const int limit = 8 * m_tilesize;
0084     const QRect limrect(0, 0, limit, limit);
0085 
0086     for (const QString &str : streaks) {
0087         /* QFontMetrics.boundingRect defaults to Qt::SingleLine, which handles \n
0088            as a normal character instead of a line break. Manually specify flags. */
0089         QRect rect = fm.boundingRect(limrect, Qt::AlignLeft | Qt::AlignTop, str);
0090         if (qMax(rect.width(), rect.height()) > len) {
0091             return false;
0092         }
0093     }
0094 
0095     return true;
0096 }
0097 
0098 void Renderer::setSize(const QSize &size, int board_width, int board_height,
0099                        const QStringList &streaks) {
0100     if (board_width < 0 || board_height < 0) {
0101         throw OutOfBoundsException();
0102     }
0103 
0104     /* Calculate the tile size, given the window size, the board dimensions,
0105        and the list of streak strings. The tile size must be the largest value
0106        such that all streaks will still fit into
0107        m_streak_grid_count * m_tilesize.
0108 
0109        Start with the default minimum grid size, and keep
0110        recalculating the tile size until all streaks fit. */
0111 
0112     m_streak_grid_count = MIN_STREAK_COUNT - 1;
0113     do {
0114         m_streak_grid_count++;
0115         m_tilesize = gridSize(size, board_width, board_height);
0116         setFontSize();
0117     } while (!streaksFit(streaks));
0118 
0119     /* the overview is a square area at the top left of the field with dimensions
0120        getXOffset() x getYOffset(). using the same logic as for calculating the
0121        main tilesize, get the overview tilesize such that the entire board fits */
0122 
0123     const int buffer = 15;
0124     QSize overview_size(getXOffset() - buffer, getYOffset() - buffer);
0125     m_overview_tilesize = gridSize(overview_size, board_width, board_height);
0126 }
0127 
0128 int Renderer::getOverviewTilesize() const {
0129     return m_overview_tilesize;
0130 }
0131 
0132 const QFont &Renderer::getFont(enum FontSize size) const {
0133     return m_fonts[size];
0134 }
0135 
0136 #define MIN_FONT_SIZE (5)
0137 
0138 void Renderer::setFontSize() {
0139     int size;
0140 
0141     size = (m_tilesize - 10) * 0.5 + 5;
0142     m_fonts[Regular].setPointSize(qMax(MIN_FONT_SIZE, size));
0143 
0144     size = (m_tilesize - 10) * 0.75 + 7;
0145     m_fonts[Large].setPointSize(qMax(MIN_FONT_SIZE, size));
0146 }
0147 
0148 Renderer *Renderer::instance() {
0149     static Renderer instance;
0150     return &instance;
0151 }
0152 
0153 QPixmap Renderer::getPixmap(Renderer::Resource resource) const {
0154     switch (resource) {
0155     case Background: return getCachedPixmap(resource, 1200, 1920);
0156     case Streak1:
0157     case Streak2: return getCachedPixmap(resource, m_tilesize, m_tilesize * m_streak_grid_count);
0158     case OverviewBox:
0159     case OverviewCross: return getCachedPixmap(resource, m_overview_tilesize, m_overview_tilesize);
0160     default: return getCachedPixmap(resource, m_tilesize, m_tilesize);
0161     }
0162 }
0163 
0164 QPixmap Renderer::getCachedPixmap(Renderer::Resource resource, int h, int w) const
0165 {
0166     /* Special case for custom background. */
0167     if (resource == Background) {
0168         if (Settings::instance()->customBgEnabled()) {
0169             return QPixmap(Settings::instance()->customBgPath());
0170         }
0171     }
0172 
0173     QString key = QStringLiteral("%1:%2x%3").arg(m_names[resource]).arg(w).arg(h);
0174 
0175     QPixmap pixmap;
0176     if (!QPixmapCache::find(key, &pixmap)) {
0177         pixmap = QPixmap(w, h);
0178         pixmap.fill(Qt::transparent);
0179         QPainter painter(&pixmap);
0180         m_renderer->render(&painter, m_names[resource], QRectF(0, 0, w, h));
0181         painter.end();
0182         QPixmapCache::insert(key, pixmap);
0183     }
0184 
0185     return pixmap;
0186 }
0187 
0188 int Renderer::getTilesize() const {
0189     return m_tilesize;
0190 }
0191 
0192 int Renderer::getYOffset() const {
0193     return m_streak_grid_count * m_tilesize;
0194 }
0195 
0196 int Renderer::getXOffset() const {
0197     return m_streak_grid_count * m_tilesize;
0198 }
0199 
0200 int Renderer::getStreakGridCount() const {
0201     return m_streak_grid_count;
0202 }