mirror of
https://github.com/Swordfish90/cool-retro-term.git
synced 2026-02-08 00:32:27 +00:00
532 lines
13 KiB
C++
532 lines
13 KiB
C++
#include "fontmanager.h"
|
|
|
|
#include <QFont>
|
|
#include <QFontDatabase>
|
|
#include <QtMath>
|
|
|
|
namespace {
|
|
constexpr int kModernRasterization = 4;
|
|
constexpr int kBaseFontPixelHeight = 32;
|
|
constexpr int kSystemFontPixelSize = 32;
|
|
}
|
|
|
|
FontManager::FontManager(QObject *parent)
|
|
: QObject(parent)
|
|
, m_fontListModel(this)
|
|
, m_filteredFontListModel(this)
|
|
{
|
|
populateBundledFonts();
|
|
populateSystemFonts();
|
|
m_fontListModel.setFonts(m_allFonts);
|
|
updateFilteredFonts();
|
|
updateComputedFont();
|
|
}
|
|
|
|
QStringList FontManager::retrieveMonospaceFonts()
|
|
{
|
|
QStringList result;
|
|
|
|
QFontDatabase fontDatabase;
|
|
const QStringList fontFamilies = fontDatabase.families();
|
|
|
|
for (const QString &fontFamily : fontFamilies) {
|
|
QFont font(fontFamily);
|
|
if (fontDatabase.isFixedPitch(font.family())) {
|
|
result.append(fontFamily);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void FontManager::refresh()
|
|
{
|
|
updateFilteredFonts();
|
|
updateComputedFont();
|
|
}
|
|
|
|
FontListModel *FontManager::fontList()
|
|
{
|
|
return &m_fontListModel;
|
|
}
|
|
|
|
FontListModel *FontManager::filteredFontList()
|
|
{
|
|
return &m_filteredFontListModel;
|
|
}
|
|
|
|
int FontManager::fontSource() const
|
|
{
|
|
return m_fontSource;
|
|
}
|
|
|
|
void FontManager::setFontSource(int fontSource)
|
|
{
|
|
if (m_fontSource == fontSource) {
|
|
return;
|
|
}
|
|
m_fontSource = fontSource;
|
|
emit fontSourceChanged();
|
|
updateFilteredFonts();
|
|
updateComputedFont();
|
|
}
|
|
|
|
int FontManager::rasterization() const
|
|
{
|
|
return m_rasterization;
|
|
}
|
|
|
|
void FontManager::setRasterization(int rasterization)
|
|
{
|
|
if (m_rasterization == rasterization) {
|
|
return;
|
|
}
|
|
m_rasterization = rasterization;
|
|
emit rasterizationChanged();
|
|
updateFilteredFonts();
|
|
updateComputedFont();
|
|
}
|
|
|
|
QString FontManager::fontName() const
|
|
{
|
|
return m_fontName;
|
|
}
|
|
|
|
void FontManager::setFontName(const QString &fontName)
|
|
{
|
|
if (m_fontName == fontName) {
|
|
return;
|
|
}
|
|
m_fontName = fontName;
|
|
emit fontNameChanged();
|
|
updateFilteredFonts();
|
|
updateComputedFont();
|
|
}
|
|
|
|
qreal FontManager::fontScaling() const
|
|
{
|
|
return m_fontScaling;
|
|
}
|
|
|
|
void FontManager::setFontScaling(qreal fontScaling)
|
|
{
|
|
if (qFuzzyCompare(m_fontScaling, fontScaling)) {
|
|
return;
|
|
}
|
|
m_fontScaling = fontScaling;
|
|
emit fontScalingChanged();
|
|
updateComputedFont();
|
|
}
|
|
|
|
qreal FontManager::fontWidth() const
|
|
{
|
|
return m_fontWidth;
|
|
}
|
|
|
|
void FontManager::setFontWidth(qreal fontWidth)
|
|
{
|
|
if (qFuzzyCompare(m_fontWidth, fontWidth)) {
|
|
return;
|
|
}
|
|
m_fontWidth = fontWidth;
|
|
emit fontWidthChanged();
|
|
updateComputedFont();
|
|
}
|
|
|
|
qreal FontManager::lineSpacing() const
|
|
{
|
|
return m_lineSpacing;
|
|
}
|
|
|
|
void FontManager::setLineSpacing(qreal lineSpacing)
|
|
{
|
|
if (qFuzzyCompare(m_lineSpacing, lineSpacing)) {
|
|
return;
|
|
}
|
|
m_lineSpacing = lineSpacing;
|
|
emit lineSpacingChanged();
|
|
updateComputedFont();
|
|
}
|
|
|
|
qreal FontManager::baseFontScaling() const
|
|
{
|
|
return m_baseFontScaling;
|
|
}
|
|
|
|
void FontManager::setBaseFontScaling(qreal baseFontScaling)
|
|
{
|
|
if (qFuzzyCompare(m_baseFontScaling, baseFontScaling)) {
|
|
return;
|
|
}
|
|
m_baseFontScaling = baseFontScaling;
|
|
emit baseFontScalingChanged();
|
|
updateComputedFont();
|
|
}
|
|
|
|
bool FontManager::lowResolutionFont() const
|
|
{
|
|
return m_lowResolutionFont;
|
|
}
|
|
|
|
void FontManager::setFontSubstitutions(const QString &family, const QStringList &substitutes)
|
|
{
|
|
if (family.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QFont::removeSubstitutions(family);
|
|
|
|
if (substitutes.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QFont::insertSubstitutions(family, substitutes);
|
|
}
|
|
|
|
void FontManager::removeFontSubstitution(const QString &family)
|
|
{
|
|
if (family.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
QFont::removeSubstitutions(family);
|
|
}
|
|
|
|
void FontManager::populateBundledFonts()
|
|
{
|
|
m_allFonts.clear();
|
|
|
|
addBundledFont(
|
|
"TERMINESS_SCALED",
|
|
"Terminess",
|
|
":/fonts/terminus/TerminessNerdFontMono-Regular.ttf",
|
|
1.0,
|
|
12,
|
|
true);
|
|
addBundledFont(
|
|
"BIGBLUE_TERMINAL_SCALED",
|
|
"BigBlue Terminal",
|
|
":/fonts/bigblue-terminal/BigBlueTerm437NerdFontMono-Regular.ttf",
|
|
1.0,
|
|
12,
|
|
true);
|
|
addBundledFont(
|
|
"EXCELSIOR_SCALED",
|
|
"Fixedsys Excelsior",
|
|
":/fonts/fixedsys-excelsior/FSEX301-L2.ttf",
|
|
1.0,
|
|
16,
|
|
true,
|
|
"UNSCII_16_SCALED");
|
|
addBundledFont(
|
|
"GREYBEARD_SCALED",
|
|
"Greybeard",
|
|
":/fonts/greybeard/Greybeard-16px.ttf",
|
|
1.0,
|
|
16,
|
|
true,
|
|
"UNSCII_16_SCALED");
|
|
addBundledFont(
|
|
"COMMODORE_PET_SCALED",
|
|
"Commodore PET",
|
|
":/fonts/pet-me/PetMe.ttf",
|
|
0.5,
|
|
8,
|
|
true,
|
|
"UNSCII_8_SCALED");
|
|
addBundledFont(
|
|
"COZETTE_SCALED",
|
|
"Cozette",
|
|
":/fonts/cozette/CozetteVector.ttf",
|
|
1.0,
|
|
13,
|
|
true);
|
|
addBundledFont(
|
|
"UNSCII_8_SCALED",
|
|
"Unscii 8",
|
|
":/fonts/unscii/unscii-8.ttf",
|
|
0.5,
|
|
8,
|
|
true,
|
|
"UNSCII_8_SCALED");
|
|
addBundledFont(
|
|
"UNSCII_8_THIN_SCALED",
|
|
"Unscii 8 Thin",
|
|
":/fonts/unscii/unscii-8-thin.ttf",
|
|
0.5,
|
|
8,
|
|
true,
|
|
"UNSCII_8_SCALED");
|
|
addBundledFont(
|
|
"UNSCII_16_SCALED",
|
|
"Unscii 16",
|
|
":/fonts/unscii/unscii-16-full.ttf",
|
|
1.0,
|
|
16,
|
|
true,
|
|
"UNSCII_16_SCALED");
|
|
addBundledFont(
|
|
"APPLE_II_SCALED",
|
|
"Apple ][",
|
|
":/fonts/apple2/PrintChar21.ttf",
|
|
0.5,
|
|
8,
|
|
true,
|
|
"UNSCII_8_SCALED");
|
|
addBundledFont(
|
|
"ATARI_400_SCALED",
|
|
"Atari 400-800",
|
|
":/fonts/atari-400-800/AtariClassic-Regular.ttf",
|
|
0.5,
|
|
8,
|
|
true,
|
|
"UNSCII_8_SCALED");
|
|
addBundledFont(
|
|
"COMMODORE_64_SCALED",
|
|
"Commodore 64",
|
|
":/fonts/pet-me/PetMe64.ttf",
|
|
0.5,
|
|
8,
|
|
true,
|
|
"UNSCII_8_SCALED");
|
|
addBundledFont(
|
|
"IBM_EGA_8x8",
|
|
"IBM EGA 8x8",
|
|
":/fonts/oldschool-pc-fonts/PxPlus_IBM_EGA_8x8.ttf",
|
|
0.5,
|
|
8,
|
|
true,
|
|
"UNSCII_8_SCALED");
|
|
addBundledFont(
|
|
"IBM_VGA_8x16",
|
|
"IBM VGA 8x16",
|
|
":/fonts/oldschool-pc-fonts/PxPlus_IBM_VGA_8x16.ttf",
|
|
1.0,
|
|
16,
|
|
true,
|
|
"UNSCII_16_SCALED");
|
|
|
|
addBundledFont(
|
|
"TERMINESS",
|
|
"Terminess",
|
|
":/fonts/terminus/TerminessNerdFontMono-Regular.ttf",
|
|
1.0,
|
|
32,
|
|
false);
|
|
addBundledFont(
|
|
"HACK",
|
|
"Hack",
|
|
":/fonts/hack/Hack-Regular.ttf",
|
|
1.0,
|
|
32,
|
|
false);
|
|
addBundledFont(
|
|
"FIRA_CODE",
|
|
"Fira Code",
|
|
":/fonts/fira-code/FiraCode-Medium.ttf",
|
|
1.0,
|
|
32,
|
|
false);
|
|
addBundledFont(
|
|
"IOSEVKA",
|
|
"Iosevka",
|
|
":/fonts/iosevka/IosevkaTerm-ExtendedMedium.ttf",
|
|
1.0,
|
|
32,
|
|
false);
|
|
addBundledFont(
|
|
"JETBRAINS_MONO",
|
|
"JetBrains Mono",
|
|
":/fonts/jetbrains-mono/JetBrainsMono-Medium.ttf",
|
|
1.0,
|
|
32,
|
|
false);
|
|
addBundledFont(
|
|
"IBM_3278",
|
|
"IBM 3278",
|
|
":/fonts/ibm-3278/3270-Regular.ttf",
|
|
1.0,
|
|
32,
|
|
false);
|
|
}
|
|
|
|
void FontManager::addBundledFont(const QString &name,
|
|
const QString &text,
|
|
const QString &source,
|
|
qreal baseWidth,
|
|
int pixelSize,
|
|
bool lowResolutionFont,
|
|
const QString &fallbackName)
|
|
{
|
|
FontEntry entry;
|
|
entry.name = name;
|
|
entry.text = text;
|
|
entry.source = source;
|
|
entry.baseWidth = baseWidth;
|
|
entry.pixelSize = pixelSize;
|
|
entry.lowResolutionFont = lowResolutionFont;
|
|
entry.isSystemFont = false;
|
|
entry.fallbackName = fallbackName;
|
|
entry.family = resolveFontFamily(source);
|
|
m_allFonts.append(entry);
|
|
}
|
|
|
|
void FontManager::populateSystemFonts()
|
|
{
|
|
const QStringList families = retrieveMonospaceFonts();
|
|
for (const QString &family : families) {
|
|
if (m_bundledFamilies.contains(family)) {
|
|
continue;
|
|
}
|
|
FontEntry entry;
|
|
entry.name = family;
|
|
entry.text = family;
|
|
entry.source = QString();
|
|
entry.baseWidth = 1.0;
|
|
entry.pixelSize = kSystemFontPixelSize;
|
|
entry.lowResolutionFont = false;
|
|
entry.isSystemFont = true;
|
|
entry.family = family;
|
|
m_allFonts.append(entry);
|
|
}
|
|
}
|
|
|
|
void FontManager::updateFilteredFonts()
|
|
{
|
|
QVector<FontEntry> filtered;
|
|
bool fontNameFound = false;
|
|
const bool modernMode = (m_rasterization == kModernRasterization);
|
|
|
|
for (const FontEntry &font : m_allFonts) {
|
|
const bool isBundled = !font.isSystemFont;
|
|
const bool matchesSource = (m_fontSource == 0 && isBundled)
|
|
|| (m_fontSource == 1 && font.isSystemFont);
|
|
|
|
if (!matchesSource) {
|
|
continue;
|
|
}
|
|
|
|
const bool matchesRasterization = font.isSystemFont
|
|
|| (modernMode == !font.lowResolutionFont);
|
|
|
|
if (!matchesRasterization) {
|
|
continue;
|
|
}
|
|
|
|
filtered.append(font);
|
|
if (font.name == m_fontName) {
|
|
fontNameFound = true;
|
|
}
|
|
}
|
|
|
|
if (!fontNameFound && !filtered.isEmpty()) {
|
|
if (m_fontName != filtered.first().name) {
|
|
m_fontName = filtered.first().name;
|
|
emit fontNameChanged();
|
|
}
|
|
}
|
|
|
|
m_filteredFontListModel.setFonts(filtered);
|
|
emit filteredFontListChanged();
|
|
}
|
|
|
|
void FontManager::updateComputedFont()
|
|
{
|
|
const FontEntry *font = findFontByName(m_fontName);
|
|
if (!font) {
|
|
const QVector<FontEntry> &filteredFonts = m_filteredFontListModel.fonts();
|
|
if (!filteredFonts.isEmpty()) {
|
|
font = &filteredFonts.first();
|
|
}
|
|
}
|
|
|
|
if (!font) {
|
|
return;
|
|
}
|
|
|
|
const qreal totalFontScaling = m_baseFontScaling * m_fontScaling;
|
|
const qreal targetPixelHeight = kBaseFontPixelHeight * totalFontScaling;
|
|
const qreal lineSpacingFactor = m_lineSpacing;
|
|
|
|
const int lineSpacing = qRound(targetPixelHeight * lineSpacingFactor);
|
|
const int pixelSize = font->lowResolutionFont
|
|
? font->pixelSize
|
|
: static_cast<int>(targetPixelHeight);
|
|
|
|
const qreal nativeLineHeight = font->pixelSize + qRound(font->pixelSize * lineSpacingFactor);
|
|
const qreal targetLineHeight = targetPixelHeight + lineSpacing;
|
|
const qreal screenScaling = font->lowResolutionFont
|
|
? (nativeLineHeight > 0 ? targetLineHeight / nativeLineHeight : 1.0)
|
|
: 1.0;
|
|
|
|
const qreal fontWidth = font->baseWidth * m_fontWidth;
|
|
|
|
QString fontFamily = font->family.isEmpty() ? font->name : font->family;
|
|
QString fallbackFontFamily;
|
|
|
|
if (!font->fallbackName.isEmpty() && font->fallbackName != font->name) {
|
|
const FontEntry *fallback = findFontByName(font->fallbackName);
|
|
if (fallback) {
|
|
fallbackFontFamily = fallback->family.isEmpty() ? fallback->name : fallback->family;
|
|
}
|
|
}
|
|
|
|
QStringList fallbackChain;
|
|
if (!fallbackFontFamily.isEmpty()) {
|
|
fallbackChain.append(fallbackFontFamily);
|
|
}
|
|
#if defined(Q_OS_MAC)
|
|
fallbackChain.append(QStringLiteral("Menlo"));
|
|
#else
|
|
fallbackChain.append(QStringLiteral("Monospace"));
|
|
#endif
|
|
setFontSubstitutions(fontFamily, fallbackChain);
|
|
|
|
if (m_lowResolutionFont != font->lowResolutionFont) {
|
|
m_lowResolutionFont = font->lowResolutionFont;
|
|
emit lowResolutionFontChanged();
|
|
}
|
|
|
|
emit terminalFontChanged(fontFamily,
|
|
pixelSize,
|
|
lineSpacing,
|
|
screenScaling,
|
|
fontWidth,
|
|
fallbackFontFamily,
|
|
font->lowResolutionFont);
|
|
}
|
|
|
|
const FontEntry *FontManager::findFontByName(const QString &name) const
|
|
{
|
|
for (const FontEntry &font : m_allFonts) {
|
|
if (font.name == name) {
|
|
return &font;
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
QString FontManager::resolveFontFamily(const QString &sourcePath)
|
|
{
|
|
const auto cached = m_loadedFamilies.constFind(sourcePath);
|
|
if (cached != m_loadedFamilies.constEnd()) {
|
|
return cached.value();
|
|
}
|
|
|
|
const int fontId = QFontDatabase::addApplicationFont(sourcePath);
|
|
QString family;
|
|
if (fontId != -1) {
|
|
const QStringList families = QFontDatabase::applicationFontFamilies(fontId);
|
|
if (!families.isEmpty()) {
|
|
family = families.first();
|
|
}
|
|
}
|
|
|
|
if (!family.isEmpty()) {
|
|
m_bundledFamilies.insert(family);
|
|
}
|
|
|
|
m_loadedFamilies.insert(sourcePath, family);
|
|
return family;
|
|
}
|