BibleTime
cdisplaytemplatemgr.cpp
Go to the documentation of this file.
1/*********
2*
3* In the name of the Father, and of the Son, and of the Holy Spirit.
4*
5* This file is part of BibleTime's source code, https://bibletime.info/
6*
7* Copyright 1999-2025 by the BibleTime developers.
8* The BibleTime source code is licensed under the GNU General Public License
9* version 2.0.
10*
11**********/
12
13#include "cdisplaytemplatemgr.h"
14
15#include <algorithm>
16#include <QDir>
17#include <QFile>
18#include <QFileInfo>
19#include <QFont>
20#include <QIODevice>
21#include <QObject>
22#include <QStringList>
23#include <QTextStream>
24#include "../../util/btassert.h"
25#include "../../util/directory.h"
26#include "../config/btconfig.h"
27#include "../drivers/btmodulelist.h"
28#include "../drivers/cswordmoduleinfo.h"
29#include "cswordbackend.h"
30
31
32#define CSSTEMPLATEBASE "Basic.tmpl"
33
34namespace {
35
36inline QString readFileToString(const QString & filename) {
37 QFile f(filename);
38 return f.open(QIODevice::ReadOnly) ? QTextStream(&f).readAll() : QString();
39}
40
41auto const defaultTemplateName = QStringLiteral("Blue.css");
42
43} // anonymous namespace
44
46
48 m_multiModuleHeaders(true) {
50 m_instance = this;
51
52 {
53 namespace DU = util::directory;
54 const QDir::Filters readableFileFilter(QDir::Files | QDir::Readable);
55 const QDir & td = DU::getDisplayTemplatesDir(); // Global template directory
56 const QDir & utd = DU::getUserDisplayTemplatesDir(); // User template directory
57
58 // Load regular templates:
59 {
60 const QStringList filter(QStringLiteral("*.tmpl"));
61 // Preload global display templates from disk:
62 for (auto const & file : td.entryList(filter, readableFileFilter))
63 loadTemplate(td.canonicalPath() + '/' + file);
64 // Preload user display templates from disk:
65 for (auto const & file : utd.entryList(filter, readableFileFilter))
66 loadTemplate(utd.canonicalPath() + '/' + file);
67 }
68
69 if (!m_templateMap.contains(CSSTEMPLATEBASE)) {
70 errorMessage = QObject::tr("CSS base template not found!");
71 return;
72 }
73
74 // Load CSS templates:
75 {
76 const QStringList cssfilter(QStringLiteral("*.css"));
77 // Load global app stylesheets
78 for (auto const & file : td.entryList(cssfilter, readableFileFilter))
79 loadCSSTemplate(td.canonicalPath() + '/' + file);
80 // Load user app stylesheets
81 for (auto const & file : utd.entryList(cssfilter, readableFileFilter))
82 loadCSSTemplate(utd.canonicalPath() + '/' + file);
83 }
84 }
85
86 if (!m_cssMap.contains(defaultTemplateName)) {
87 errorMessage = QObject::tr("Default template \"%1\" not found!")
88 .arg(defaultTemplateName);
89 return;
90 }
91
92 // Create template names cache:
94 const bool b = m_availableTemplateNamesCache.removeOne(CSSTEMPLATEBASE);
95 BT_ASSERT(b);
97 std::sort(m_availableTemplateNamesCache.begin(),
99
100 errorMessage = QString();
101}
102
103QString CDisplayTemplateMgr::fillTemplate(const QString & name,
104 const QString & content,
105 const Settings & settings) const
106{
107 BT_ASSERT(name != CSSTEMPLATEBASE);
108 BT_ASSERT(name.endsWith(QStringLiteral(".css"))
109 || name.endsWith(QStringLiteral(".tmpl")));
110 BT_ASSERT(!name.endsWith(QStringLiteral(".css"))
111 || m_cssMap.contains(name));
112 BT_ASSERT(!name.endsWith(QStringLiteral(".tmpl"))
113 || m_templateMap.contains(name));
114 const bool templateIsCss = name.endsWith(QStringLiteral(".css"));
115
116 QString displayTypeString;
117 QString moduleName;
118
119 if (!settings.pageCSS_ID.isEmpty()) {
120 displayTypeString = settings.pageCSS_ID;
121 }
122 else {
123 if (settings.modules.count()) {
124 switch (settings.modules.first()->type()) {
125
127 displayTypeString = QStringLiteral("bible");
128 break;
129
131 displayTypeString = QStringLiteral("book");
132 break;
133
136 default:
137 displayTypeString = QStringLiteral("singleentry");
138 break;
139 }
140 moduleName = settings.modules.first()->name();
141 }
142 else { //use bible as default type if no modules are set
143 displayTypeString = QStringLiteral("bible");
144 }
145 }
146
147 QString newContent = content;
148 const int moduleCount = settings.modules.count();
149
150 if (moduleCount >= 2 && m_multiModuleHeaders) {
151 //create header for the modules
152 // qDebug() << "There were more than 1 module, create headers";
153 QString header;
154
155 for (auto const * const mi : settings.modules)
156 header.append(
157 QStringLiteral("<th style=\"width:%1%;\">%2</th>")
158 .arg(QString::number(static_cast<int>(100.0 / moduleCount)),
159 mi->name()));
160
161 newContent = QStringLiteral("<table><tr>%1</tr>%2</table>")
162 .arg(header, content);
163 }
164
165 QString langCSS;
166 {
167 const QFont & f = btConfig().getDefaultFont();
168 langCSS.append(
169 QStringLiteral("#content{font-family:%1;font-size:%2pt;"
170 "font-weight:%3;font-style:%4}")
171 .arg(f.family(),
172 QString::number(f.pointSizeF(), 'f'),
173 f.bold()
174 ? QStringLiteral("bold")
175 : QStringLiteral("normal"),
176 f.italic()
177 ? QStringLiteral("italic")
178 : QStringLiteral("normal")));
179 }
180 {
181 auto const availableLanguages =
183 BT_ASSERT(availableLanguages);
184 for (auto const & lang : *availableLanguages) {
185 if (lang->abbrev().isEmpty())
186 continue;
187
189 if (fp.first) {
190 const QFont & f = fp.second;
191
192 /* QFont::weight() returns an int in the range [0, 99] but CSS
193 requires a real number in the range [1, 1000]. No extra
194 checks are needed for floating point precision in the result
195 range. */
196 auto const fontWeight = 1.0 + (f.weight() * 999.0) / 99.0;
197
198 auto const fontStyleString =
199 [&f]() {
200 switch ((int) f.style()) {
201 case QFont::StyleItalic:
202 return QStringLiteral("italic");
203 case QFont::StyleOblique:
204 return QStringLiteral("oblique");
205 case QFont::StyleNormal:
206 default:
207 return QStringLiteral("normal");
208 }
209 }();
210
211 auto const textDecorationString =
212 f.underline()
213 ? (f.strikeOut()
214 ? QStringLiteral("underline line-through")
215 : QStringLiteral("underline"))
216 : (f.strikeOut()
217 ? QStringLiteral("line-through")
218 : QStringLiteral("none"));
219
220 /// \todo Add support translating more QFont properties to CSS.
221
222 langCSS.append(
223 QStringLiteral("*[lang=%1]{font-family:%2;"
224 "font-size:%3pt;font-weight:%4;"
225 "font-style:%5;text-decoration:%6}")
226 .arg(lang->abbrev(),
227 f.family(),
228 QString::number(f.pointSizeF(), 'f'),
229 QString::number(fontWeight),
230 fontStyleString,
231 textDecorationString));
232 }
233 }
234 }
235
236 namespace DU = util::directory;
237 QString output(m_templateMap[templateIsCss
238 ? QString(CSSTEMPLATEBASE)
239 : name]); // don't change the map's content directly, use a copy
240 output.replace(QStringLiteral("#TITLE#"), settings.title)
241 .replace(QStringLiteral("#LANG_ABBREV#"), settings.langAbbrev)
242 .replace(QStringLiteral("#DISPLAYTYPE#"), displayTypeString)
243 .replace(QStringLiteral("#LANG_CSS#"), langCSS)
244 .replace(QStringLiteral("#PAGE_DIRECTION#"),
246 .replace(QStringLiteral("#CONTENT#"), newContent)
247 .replace(QStringLiteral("#BODY_CLASSES#"),
248 QStringLiteral("%1 %1_%2").arg(displayTypeString,
249 moduleName))
250 .replace(QStringLiteral("#DISPLAY_TEMPLATES_PATH#"),
251 DU::getDisplayTemplatesDir().absolutePath());
252
253 if (templateIsCss)
254 output.replace(QStringLiteral("#THEME_STYLE#"), m_cssMap[name]);
255
256 return output;
257}
258
260 auto const tn =
261 btConfig().value<QString>(QStringLiteral("GUI/activeTemplateName"),
262 QString());
263 return (tn.isEmpty()
264 || !instance()->m_availableTemplateNamesCache.contains(tn))
265 ? defaultTemplateName
266 : tn;
267}
268
269void CDisplayTemplateMgr::loadTemplate(const QString & filename) {
270 BT_ASSERT(filename.endsWith(QStringLiteral(".tmpl")));
271 BT_ASSERT(QFileInfo(filename).isFile());
272 const QString templateString = readFileToString(filename);
273 if (!templateString.isEmpty())
274 m_templateMap.insert(QFileInfo(filename).fileName(), templateString);
275}
276
277void CDisplayTemplateMgr::loadCSSTemplate(const QString & filename) {
278 BT_ASSERT(filename.endsWith(QStringLiteral(".css")));
279 const QFileInfo fi(filename);
280 BT_ASSERT(fi.isFile());
281 if (fi.isReadable())
282 m_cssMap.insert(fi.fileName(), readFileToString(filename));
283}
284
#define BT_ASSERT(...)
Definition btassert.h:17
BtConfig & btConfig()
This is a shortchand for BtConfig::getInstance().
Definition btconfig.h:305
#define CSSTEMPLATEBASE
T value(QString const &key, T const &defaultValue=T()) const
Returns the settings value for the given global key.
QFont const & getDefaultFont() const
Definition btconfig.h:192
QPair< bool, QFont > FontSettingsPair
Definition btconfig.h:47
FontSettingsPair getFontForLanguage(Language const &language)
Get font for a language.
Definition btconfig.cpp:354
CDisplayTemplateMgr(QString &errorMessage)
void setMultiModuleHeadersVisible(bool visible)
Turn on or off displaying module names during parallel text rendering.
QString fillTemplate(const QString &name, const QString &content, const Settings &settings) const
Fills the template.
static QString activeTemplateName()
QHash< QString, QString > m_templateMap
void loadCSSTemplate(const QString &filename)
QHash< QString, QString > m_cssMap
static CDisplayTemplateMgr * m_instance
static CDisplayTemplateMgr * instance()
void loadTemplate(const QString &filename)
QStringList m_availableTemplateNamesCache
std::shared_ptr< AvailableLanguagesCacheContainer const > availableLanguages() noexcept
static CSwordBackend & instance() noexcept
QString readFileToString(const QString &filename)
char const * textDirectionAsHtmlDirAttr() const noexcept