[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[freetype2-demos] master bf88f4d 26/41: [ftinspect] Add "Font Info" tab.
From: |
Werner Lemberg |
Subject: |
[freetype2-demos] master bf88f4d 26/41: [ftinspect] Add "Font Info" tab. |
Date: |
Mon, 3 Oct 2022 11:27:03 -0400 (EDT) |
branch: master
commit bf88f4d1381c8d20d0cae43ac08449eb0df0ad02
Author: Charlie Jiang <w@chariri.moe>
Commit: Werner Lemberg <wl@gnu.org>
[ftinspect] Add "Font Info" tab.
* src/ftinspect/panels/info.cpp, src/ftinspect/panels/info.hpp:
New files, add the `InfoTab` class.
* src/ftinspect/engine/fontinfo.cpp, src/ftinspect/engine/fontinfo.hpp:
Add `SFNTTableInfo`, `FontBasicInfo`, `FontTypeEntries`, `FontFixedSize`
and `CompositeGlyphInfo`. The `SFNTTableInfo` and `CompositeGlyphInfo`
classes retrieve info without the related FreeType API, but directly
parse the font data.
* src/ftinspect/models/fontinfomodels.cpp,
src/ftinspect/models/fontinfomodels.hpp:
New files. Add models for the tables and the tree view in the info tab to
use.
* src/ftinspect/engine/engine.cpp, src/ftinspect/engine/engine.hpp:
Add `loadDefaults` function for the composite glyphs view to draw its
small icon.
Add `currentFontHasGlyphName`, `currentFontPSInfo`,
`currentFontPSPrivateInfo` and `currentFontSFNTTableInfo` to obtain info.
Add getters.
* src/ftinspect/engine/fontinfonamesmapping.cpp: New file for name mapping.
* src/ftinspect/engine/fontfilemanager.cpp,
src/ftinspect/engine/fontfilemanager.hpp:
Add `currentReloadDueToPeriodicUpdate` so the composite glyph tree isn't
refreshed (which is a very expensive process) for the periodic updating of
symbolic font files.
* src/ftinspect/maingui.cpp, src/ftinspect/maingui.hpp:
Add the font info tab info the main window and wire events.
* src/ftinspect/CMakeLists.txt, src/ftinspect/meson.build: Updated.
---
src/ftinspect/CMakeLists.txt | 3 +
src/ftinspect/engine/engine.cpp | 77 ++
src/ftinspect/engine/engine.hpp | 10 +
src/ftinspect/engine/fontfilemanager.cpp | 2 +
src/ftinspect/engine/fontfilemanager.hpp | 7 +
src/ftinspect/engine/fontinfo.cpp | 530 +++++++++++++
src/ftinspect/engine/fontinfo.hpp | 276 ++++++-
src/ftinspect/engine/fontinfonamesmapping.cpp | 567 ++++++++++++++
src/ftinspect/engine/rendering.cpp | 18 +
src/ftinspect/engine/rendering.hpp | 2 +
src/ftinspect/maingui.cpp | 8 +
src/ftinspect/maingui.hpp | 2 +
src/ftinspect/meson.build | 5 +
src/ftinspect/models/fontinfomodels.cpp | 738 ++++++++++++++++++
src/ftinspect/models/fontinfomodels.hpp | 294 +++++++
src/ftinspect/panels/info.cpp | 1039 +++++++++++++++++++++++++
src/ftinspect/panels/info.hpp | 327 ++++++++
src/ftinspect/panels/settingpanel.cpp | 10 +-
src/ftinspect/panels/settingpanelmmgx.cpp | 1 +
src/ftinspect/widgets/charmapcombobox.cpp | 21 +-
20 files changed, 3921 insertions(+), 16 deletions(-)
diff --git a/src/ftinspect/CMakeLists.txt b/src/ftinspect/CMakeLists.txt
index 7f271a7..6a7a025 100644
--- a/src/ftinspect/CMakeLists.txt
+++ b/src/ftinspect/CMakeLists.txt
@@ -26,6 +26,7 @@ add_executable(ftinspect
"engine/paletteinfo.cpp"
"engine/mmgx.cpp"
"engine/fontinfo.cpp"
+ "engine/fontinfonamesmapping.cpp"
"engine/stringrenderer.cpp"
"engine/charmap.cpp"
@@ -44,12 +45,14 @@ add_executable(ftinspect
"widgets/charmapcombobox.cpp"
"models/customcomboboxmodels.cpp"
+ "models/fontinfomodels.cpp"
"panels/settingpanel.cpp"
"panels/settingpanelmmgx.cpp"
"panels/singular.cpp"
"panels/continuous.cpp"
"panels/comparator.cpp"
+ "panels/info.cpp"
"panels/glyphdetails.cpp"
)
target_link_libraries(ftinspect
diff --git a/src/ftinspect/engine/engine.cpp b/src/ftinspect/engine/engine.cpp
index 87ee808..8ebced7 100644
--- a/src/ftinspect/engine/engine.cpp
+++ b/src/ftinspect/engine/engine.cpp
@@ -463,6 +463,15 @@ Engine::currentFontHasColorLayers()
}
+bool
+Engine::currentFontHasGlyphName()
+{
+ if (!ftFallbackFace_)
+ return false;
+ return FT_HAS_GLYPH_NAMES(ftFallbackFace_);
+}
+
+
std::vector<int>
Engine::currentFontFixedSizes()
{
@@ -477,6 +486,41 @@ Engine::currentFontFixedSizes()
}
+bool
+Engine::currentFontPSInfo(PS_FontInfoRec& outInfo)
+{
+ if (!ftSize_)
+ return false;
+ if (FT_Get_PS_Font_Info(ftSize_->face, &outInfo) == FT_Err_Ok)
+ return true;
+ return false;
+}
+
+
+bool
+Engine::currentFontPSPrivateInfo(PS_PrivateRec& outInfo)
+{
+ if (!ftSize_)
+ return false;
+ if (FT_Get_PS_Font_Private(ftSize_->face, &outInfo) == FT_Err_Ok)
+ return true;
+ return false;
+}
+
+
+std::vector<SFNTTableInfo>&
+Engine::currentFontSFNTTableInfo()
+{
+ if (!curSFNTTablesValid_)
+ {
+ SFNTTableInfo::getForAll(this, curSFNTTables_);
+ curSFNTTablesValid_ = true;
+ }
+
+ return curSFNTTables_;
+}
+
+
int
Engine::currentFontFirstUnicodeCharMap()
{
@@ -816,6 +860,39 @@ Engine::resetCache()
}
+void
+Engine::loadDefaults()
+{
+ if (fontType_ == FontType_CFF)
+ setCFFHintingMode(engineDefaults_.cffHintingEngineDefault);
+ else if (fontType_ == FontType_TrueType)
+ {
+ if (currentFontTricky())
+ setTTInterpreterVersion(TT_INTERPRETER_VERSION_35);
+ else
+ setTTInterpreterVersion(engineDefaults_.ttInterpreterVersionDefault);
+ }
+ setStemDarkening(false);
+ applyMMGXDesignCoords(NULL, 0);
+
+ setAntiAliasingEnabled(true);
+ setAntiAliasingTarget(FT_LOAD_TARGET_NORMAL);
+ setHinting(true);
+ setAutoHinting(false);
+ setEmbeddedBitmapEnabled(true);
+ setPaletteIndex(0);
+ setUseColorLayer(true);
+
+ renderingEngine()->setBackground(qRgba(255, 255, 255, 255));
+ renderingEngine()->setForeground(qRgba(0, 0, 0, 255));
+ renderingEngine()->setGamma(1.8);
+
+ resetCache();
+ reloadFont();
+ loadPalette();
+}
+
+
void
Engine::queryEngine()
{
diff --git a/src/ftinspect/engine/engine.hpp b/src/ftinspect/engine/engine.hpp
index d9ea177..e71564e 100644
--- a/src/ftinspect/engine/engine.hpp
+++ b/src/ftinspect/engine/engine.hpp
@@ -95,6 +95,7 @@ public:
void update();
void resetCache();
+ void loadDefaults();
//////// Getters
@@ -108,6 +109,7 @@ public:
int numberOfOpenedFonts();
// (for current fonts)
+ int currentFontIndex() { return curFontIndex_; }
FT_Face currentFallbackFtFace() { return ftFallbackFace_; }
FT_Size currentFtSize() { return ftSize_; }
FT_Size_Metrics const& currentFontMetrics();
@@ -140,7 +142,12 @@ public:
bool currentFontBitmapOnly();
bool currentFontHasEmbeddedBitmap();
bool currentFontHasColorLayers();
+ bool currentFontHasGlyphName();
+
std::vector<int> currentFontFixedSizes();
+ bool currentFontPSInfo(PS_FontInfoRec& outInfo);
+ bool currentFontPSPrivateInfo(PS_PrivateRec& outInfo);
+ std::vector<SFNTTableInfo>& currentFontSFNTTableInfo();
int currentFontFirstUnicodeCharMap();
// Note: the current font face must be properly set
@@ -226,6 +233,9 @@ private:
int curNumGlyphs_ = -1;
std::vector<CharMapInfo> curCharMaps_;
std::vector<PaletteInfo> curPaletteInfos_;
+
+ bool curSFNTTablesValid_ = false;
+ std::vector<SFNTTableInfo> curSFNTTables_;
MMGXState curMMGXState_ = MMGXState::NoMMGX;
std::vector<MMGXAxisInfo> curMMGXAxes_;
std::vector<SFNTName> curSFNTNames_;
diff --git a/src/ftinspect/engine/fontfilemanager.cpp
b/src/ftinspect/engine/fontfilemanager.cpp
index 9b44fce..f058dc2 100644
--- a/src/ftinspect/engine/fontfilemanager.cpp
+++ b/src/ftinspect/engine/fontfilemanager.cpp
@@ -183,7 +183,9 @@ FontFileManager::validateFontFile(QString const& fileName)
void
FontFileManager::onTimerFire()
{
+ periodicUpdating_ = true;
onWatcherFire();
+ periodicUpdating_ = false;
}
diff --git a/src/ftinspect/engine/fontfilemanager.hpp
b/src/ftinspect/engine/fontfilemanager.hpp
index 06a8f52..0874fd0 100644
--- a/src/ftinspect/engine/fontfilemanager.hpp
+++ b/src/ftinspect/engine/fontfilemanager.hpp
@@ -34,6 +34,11 @@ public:
void timerStart();
void loadFromCommandLine();
+ // If this is true, then the current font reloading is due to a periodic
+ // reloading for symbolic font files. Use this if you want to omit some
+ // updating for periodic reloading.
+ bool currentReloadDueToPeriodicUpdate() { return periodicUpdating_; }
+
signals:
void currentFileChanged();
@@ -47,6 +52,8 @@ private:
QFileSystemWatcher* fontWatcher_;
QTimer* watchTimer_;
+ bool periodicUpdating_ = false;
+
FT_Error validateFontFile(QString const& fileName);
};
diff --git a/src/ftinspect/engine/fontinfo.cpp
b/src/ftinspect/engine/fontinfo.cpp
index 3eb5b78..5927409 100644
--- a/src/ftinspect/engine/fontinfo.cpp
+++ b/src/ftinspect/engine/fontinfo.cpp
@@ -6,6 +6,8 @@
#include "engine.hpp"
+#include <map>
+#include <unordered_map>
#include <memory>
#include <utility>
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
@@ -16,6 +18,14 @@
#endif
#include <freetype/ftmodapi.h>
#include <freetype/ttnameid.h>
+#include <freetype/tttables.h>
+#include <freetype/tttags.h>
+
+#ifdef _MSC_VER // To use intrin
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <intrin.h>
+#endif
void
@@ -163,4 +173,524 @@ SFNTName::utf16BEToQString(char const* str,
}
+FontBasicInfo
+FontBasicInfo::get(Engine* engine)
+{
+ auto fontIndex = engine->currentFontIndex();
+ if (fontIndex < 0)
+ return {};
+ FontBasicInfo result;
+ result.numFaces = engine->numberOfFaces(fontIndex);
+
+ engine->reloadFont();
+ auto face = engine->currentFallbackFtFace();
+ if (!face)
+ return result;
+
+ if (face->family_name)
+ result.familyName = QString(face->family_name);
+ if (face->style_name)
+ result.styleName = QString(face->style_name);
+
+ auto psName = FT_Get_Postscript_Name(face);
+ if (psName)
+ result.postscriptName = QString(psName);
+
+ auto head = static_cast<TT_Header*>(FT_Get_Sfnt_Table(face, FT_SFNT_HEAD));
+ if (head)
+ {
+ uint64_t createdTimestamp
+ = head->Created[1] | static_cast<uint64_t>(head->Created[0]) << 32;
+ uint64_t modifiedTimestamp
+ = head->Modified[1] | static_cast<uint64_t>(head->Modified[0]) << 32;
+
+ result.createdTime
+ = QDateTime::fromSecsSinceEpoch(createdTimestamp, Qt::OffsetFromUTC)
+ .addSecs(-2082844800);
+ result.modifiedTime
+ = QDateTime::fromSecsSinceEpoch(modifiedTimestamp, Qt::OffsetFromUTC)
+ .addSecs(-2082844800);
+
+ auto revDouble = head->Font_Revision / 65536.0;
+ if (head->Font_Revision & 0xFFC0)
+ result.revision = QString::number(revDouble, 'g', 4);
+ else
+ result.revision = QString::number(revDouble, 'g', 2);
+ }
+
+ return result;
+}
+
+
+FontTypeEntries
+FontTypeEntries::get(Engine* engine)
+{
+ engine->reloadFont();
+ auto face = engine->currentFallbackFtFace();
+ if (!face)
+ return {};
+
+ FontTypeEntries result = {};
+ result.driverName = QString(FT_FACE_DRIVER_NAME(face));
+ result.sfnt = FT_IS_SFNT(face);
+ result.scalable = FT_IS_SCALABLE(face);
+ if (result.scalable)
+ result.mmgx = FT_HAS_MULTIPLE_MASTERS(face);
+ else
+ result.mmgx = false;
+ result.fixedSizes = FT_HAS_FIXED_SIZES(face);
+ result.hasHorizontal = FT_HAS_HORIZONTAL(face);
+ result.hasVertical = FT_HAS_VERTICAL(face);
+ result.fixedWidth = FT_IS_FIXED_WIDTH(face);
+ result.glyphNames = FT_HAS_GLYPH_NAMES(face);
+
+ if (result.scalable)
+ {
+ result.emSize = face->units_per_EM;
+ result.globalBBox = face->bbox;
+ result.ascender = face->ascender;
+ result.descender = face->descender;
+ result.height = face->height;
+ result.maxAdvanceWidth = face->max_advance_width;
+ result.maxAdvanceHeight = face->max_advance_height;
+ result.underlinePos = face->underline_position;
+ result.underlineThickness = face->underline_thickness;
+ }
+
+ return result;
+}
+
+
+bool
+operator==(const PS_FontInfoRec& lhs,
+ const PS_FontInfoRec& rhs)
+{
+ // XXX: possible security flaw with `strcmp`?
+ return strcmp(lhs.version, rhs.version) == 0
+ && strcmp(lhs.notice, rhs.notice) == 0
+ && strcmp(lhs.full_name, rhs.full_name) == 0
+ && strcmp(lhs.family_name, rhs.family_name) == 0
+ && strcmp(lhs.weight, rhs.weight) == 0
+ && lhs.italic_angle == rhs.italic_angle
+ && lhs.is_fixed_pitch == rhs.is_fixed_pitch
+ && lhs.underline_position == rhs.underline_position
+ && lhs.underline_thickness == rhs.underline_thickness;
+}
+
+
+bool
+operator==(const PS_PrivateRec& lhs,
+ const PS_PrivateRec& rhs)
+{
+ return lhs.unique_id == rhs.unique_id
+ && lhs.lenIV == rhs.lenIV
+ && lhs.num_blue_values == rhs.num_blue_values
+ && lhs.num_other_blues == rhs.num_other_blues
+ && lhs.num_family_blues == rhs.num_family_blues
+ && lhs.num_family_other_blues == rhs.num_family_other_blues
+ && std::equal(std::begin(lhs.blue_values), std::end(lhs.blue_values),
+ std::begin(rhs.blue_values))
+ && std::equal(std::begin(lhs.other_blues), std::end(lhs.other_blues),
+ std::begin(rhs.other_blues))
+ && std::equal(std::begin(lhs.family_blues),
std::end(lhs.family_blues),
+ std::begin(rhs.family_blues))
+ && std::equal(std::begin(lhs.family_other_blues),
+ std::end(lhs.family_other_blues),
+ std::begin(rhs.family_other_blues))
+ && lhs.blue_scale == rhs.blue_scale
+ && lhs.blue_shift == rhs.blue_shift
+ && lhs.blue_fuzz == rhs.blue_fuzz
+ && std::equal(std::begin(lhs.standard_width),
+ std::end(lhs.standard_width),
+ std::begin(rhs.standard_width))
+ && std::equal(std::begin(lhs.standard_height),
+ std::end(lhs.standard_height),
+ std::begin(rhs.standard_height))
+ && lhs.num_snap_widths == rhs.num_snap_widths
+ && lhs.num_snap_heights == rhs.num_snap_heights
+ && lhs.force_bold == rhs.force_bold
+ && lhs.round_stem_up == rhs.round_stem_up
+ && std::equal(std::begin(lhs.snap_widths), std::end(lhs.snap_widths),
+ std::begin(rhs.snap_widths))
+ && std::equal(std::begin(lhs.snap_heights),
std::end(lhs.snap_heights),
+ std::begin(rhs.snap_heights))
+ && lhs.expansion_factor == rhs.expansion_factor
+ && lhs.language_group == rhs.language_group
+ && lhs.password == rhs.password
+ && std::equal(std::begin(lhs.min_feature), std::end(lhs.min_feature),
+ std::begin(rhs.min_feature));
+}
+
+
+bool
+FontFixedSize::get(Engine* engine,
+ std::vector<FontFixedSize>& list,
+ const std::function<void()>& onUpdateNeeded)
+{
+ engine->reloadFont();
+ auto face = engine->currentFallbackFtFace();
+ if (!face)
+ {
+ if (list.empty())
+ return false;
+
+ onUpdateNeeded();
+ list.clear();
+ return true;
+ }
+
+ auto changed = false;
+ if (list.size() != static_cast<size_t>(face->num_fixed_sizes))
+ {
+ changed = true;
+ onUpdateNeeded();
+ list.resize(face->num_fixed_sizes);
+ }
+
+ for (int i = 0; i < face->num_fixed_sizes; ++i)
+ {
+ FontFixedSize ffs = {};
+ auto bSize = face->available_sizes + i;
+ ffs.height = bSize->height;
+ ffs.width = bSize->width;
+ ffs.size = bSize->size / 64.0;
+ ffs.xPpem = bSize->x_ppem / 64.0;
+ ffs.yPpem = bSize->y_ppem / 64.0;
+ if (ffs != list[i])
+ {
+
+ if (!changed)
+ {
+ onUpdateNeeded();
+ changed = true;
+ }
+
+ list[i] = ffs;
+ }
+ }
+
+ return changed;
+}
+
+
+struct TTCHeaderRec
+{
+ uint32_t ttcTag;
+ uint16_t majorVersion;
+ uint16_t minorVersion;
+ uint32_t numFonts;
+};
+
+
+struct SFNTHeaderRec
+{
+ uint32_t formatTag;
+ uint16_t numTables;
+ // There'll be some padding, but it doesn't matter.
+};
+
+
+struct TTTableRec
+{
+ uint32_t tag;
+ uint32_t checksum;
+ uint32_t offset;
+ uint32_t length;
+};
+
+
+uint32_t
+bigEndianToNative(uint32_t n)
+{
+#ifdef _MSC_VER
+ #if REG_DWORD == REG_DWORD_LITTLE_ENDIAN
+ return _byteswap_ulong(n);
+ #else
+ return n;
+ #endif
+#else
+ auto np = reinterpret_cast<unsigned char*>(&n);
+
+ return (static_cast<uint32_t>(np[0]) << 24)
+ | (static_cast<uint32_t>(np[1]) << 16)
+ | (static_cast<uint32_t>(np[2]) << 8)
+ | (static_cast<uint32_t>(np[3]));
+#endif
+}
+
+
+uint16_t
+bigEndianToNative(uint16_t n)
+{
+#ifdef _MSC_VER
+#if REG_DWORD == REG_DWORD_LITTLE_ENDIAN
+ return _byteswap_ushort(n);
+#else
+ return n;
+#endif
+#else
+ auto np = reinterpret_cast<unsigned char*>(&n);
+
+ return static_cast<uint16_t>((static_cast<uint16_t>(np[0]) << 8)
+ | (static_cast<uint16_t>(np[1])));
+#endif
+}
+
+
+void readSingleFace(QFile& file,
+ uint32_t offset,
+ unsigned faceIndex,
+ std::vector<TTTableRec>& tempTables,
+ std::map<unsigned long, SFNTTableInfo>& result)
+{
+ if (!file.seek(offset))
+ return;
+
+ SFNTHeaderRec sfntHeader = {};
+ if (file.read(reinterpret_cast<char*>(&sfntHeader),
+ sizeof(SFNTHeaderRec))
+ != sizeof(SFNTHeaderRec))
+ return;
+ sfntHeader.formatTag = bigEndianToNative(sfntHeader.formatTag);
+ sfntHeader.numTables = bigEndianToNative(sfntHeader.numTables);
+
+ unsigned short validEntries = sfntHeader.numTables;
+
+ if (sfntHeader.formatTag != TTAG_OTTO)
+ {
+ // TODO check SFNT Header
+ //checkSFNTHeader();
+ }
+
+ if (!file.seek(offset + 12))
+ return;
+
+ tempTables.resize(validEntries);
+ auto desiredLen = static_cast<long long>(validEntries * sizeof(TTTableRec));
+ auto readLen = file.read(reinterpret_cast<char*>(tempTables.data()),
desiredLen);
+ if (readLen != desiredLen)
+ return;
+
+ for (auto& t : tempTables)
+ {
+ t.tag = bigEndianToNative(t.tag);
+ t.offset = bigEndianToNative(t.offset);
+ t.checksum = bigEndianToNative(t.checksum);
+ t.length = bigEndianToNative(t.length);
+
+ auto it = result.find(t.offset);
+ if (it == result.end())
+ {
+ auto emplaced = result.emplace(t.offset, SFNTTableInfo());
+ it = emplaced.first;
+
+ auto& info = it->second;
+ info.tag = t.tag;
+ info.length = t.length;
+ info.offset = t.offset;
+ info.sharedFaces.emplace(faceIndex);
+ info.valid = true;
+ }
+ else
+ {
+ it->second.sharedFaces.emplace(faceIndex);
+ // TODO check
+ }
+ }
+}
+
+
+void
+SFNTTableInfo::getForAll(Engine* engine,
+ std::vector<SFNTTableInfo>& infos)
+{
+ infos.clear();
+ auto face = engine->currentFallbackFtFace();
+ if (!face || !FT_IS_SFNT(face))
+ return;
+
+ auto index = engine->currentFontIndex();
+ auto& mgr = engine->fontFileManager();
+ if (index < 0 || index >= mgr.size())
+ return;
+
+ auto& fileInfo = mgr[index];
+ QFile file(fileInfo.filePath());
+ if (!file.open(QIODevice::ReadOnly))
+ return;
+
+ auto fileSize = file.size();
+ if (fileSize < 12)
+ return;
+
+ std::vector<TTTableRec> tables;
+ std::map<unsigned long, SFNTTableInfo> result;
+
+ TTCHeaderRec ttcHeader = {};
+ auto readLen = file.read(reinterpret_cast<char*>(&ttcHeader),
+ sizeof(TTCHeaderRec));
+
+ if (readLen != sizeof(TTCHeaderRec))
+ return;
+
+ ttcHeader.ttcTag = bigEndianToNative(ttcHeader.ttcTag);
+ ttcHeader.majorVersion = bigEndianToNative(ttcHeader.majorVersion);
+ ttcHeader.minorVersion = bigEndianToNative(ttcHeader.minorVersion);
+ ttcHeader.numFonts = bigEndianToNative(ttcHeader.numFonts);
+
+ if (ttcHeader.ttcTag == TTAG_ttcf
+ && (ttcHeader.majorVersion == 2 || ttcHeader.majorVersion == 1))
+ {
+ // Valid TTC file
+ std::unique_ptr<unsigned> offsets(new unsigned[ttcHeader.numFonts]);
+ auto desiredLen = static_cast<long long>(ttcHeader.numFonts
+ * sizeof(unsigned));
+ readLen = file.read(reinterpret_cast<char*>(offsets.get()), desiredLen);
+ if (readLen != desiredLen)
+ return;
+
+ for (unsigned faceIndex = 0;
+ faceIndex < ttcHeader.numFonts;
+ faceIndex++)
+ {
+ auto offset = bigEndianToNative(offsets.get()[faceIndex]);
+ readSingleFace(file, offset, faceIndex, tables, result);
+ }
+ }
+ else
+ {
+ // Not TTC file, try single SFNT
+ if (!file.seek(0))
+ return;
+ readSingleFace(file, 0, 0, tables, result);
+ }
+
+ infos.reserve(result.size());
+ for (auto& pr : result)
+ infos.emplace_back(std::move(pr.second));
+}
+
+
+void
+CompositeGlyphInfo::get(Engine* engine,
+ std::vector<CompositeGlyphInfo>& list)
+{
+ list.clear();
+ engine->reloadFont();
+ auto face = engine->currentFallbackFtFace();
+ if (!face || !FT_IS_SFNT(face))
+ {
+ if (list.empty())
+ return;
+ }
+
+ // We're not using the FreeType's subglyph APIs, but directly reading from
+ // the `glyf` table since it's faster
+ auto head = static_cast<TT_Header*>(FT_Get_Sfnt_Table(face, FT_SFNT_HEAD));
+ auto maxp
+ = static_cast<TT_MaxProfile*>(FT_Get_Sfnt_Table(face, FT_SFNT_MAXP));
+ if (!head || !maxp)
+ return;
+
+ FT_ULong locaLength = head->Index_To_Loc_Format ? 4 * maxp->numGlyphs + 4
+ : 2 * maxp->numGlyphs + 2;
+ std::unique_ptr<unsigned char[]> locaBufferGuard(
+ new unsigned char[locaLength]);
+ auto offset = locaBufferGuard.get();
+ auto error = FT_Load_Sfnt_Table(face, TTAG_loca, 0, offset, &locaLength);
+ if (error)
+ return;
+
+ FT_ULong glyfLength = 0;
+ error = FT_Load_Sfnt_Table(face, TTAG_glyf, 0, NULL, &glyfLength);
+ if (error || !glyfLength)
+ return;
+
+ std::unique_ptr<unsigned char[]> glyfBufferGuard(
+ new unsigned char[glyfLength]);
+ auto buffer = glyfBufferGuard.get();
+ error = FT_Load_Sfnt_Table(face, TTAG_glyf, 0, buffer, &glyfLength);
+ if (error)
+ return;
+
+ for (size_t i = 0; i < maxp->numGlyphs; i++)
+ {
+ FT_UInt32 loc, end;
+ if (head->Index_To_Loc_Format)
+ {
+ loc = static_cast<FT_UInt32>(offset[4 * i ]) << 24 |
+ static_cast<FT_UInt32>(offset[4 * i + 1]) << 16 |
+ static_cast<FT_UInt32>(offset[4 * i + 2]) << 8 |
+ static_cast<FT_UInt32>(offset[4 * i + 3]) ;
+ end = static_cast<FT_UInt32>(offset[4 * i + 4]) << 24 |
+ static_cast<FT_UInt32>(offset[4 * i + 5]) << 16 |
+ static_cast<FT_UInt32>(offset[4 * i + 6]) << 8 |
+ static_cast<FT_UInt32>(offset[4 * i + 7]) ;
+ }
+ else
+ {
+ loc = static_cast<FT_UInt32>(offset[2 * i ]) << 9 |
+ static_cast<FT_UInt32>(offset[2 * i + 1]) << 1 ;
+ end = static_cast<FT_UInt32>(offset[2 * i + 2]) << 9 |
+ static_cast<FT_UInt32>(offset[2 * i + 3]) << 1 ;
+ }
+
+ if (end > glyfLength)
+ end = glyfLength;
+
+ if (loc + 16 > end)
+ continue;
+
+ auto len = static_cast<FT_Int16>(buffer[loc] << 8 | buffer[loc + 1]);
+ loc += 10; // skip header
+ if (len >= 0) // not a composite one
+ continue;
+
+ std::vector<SubGlyph> subglyphs;
+
+ while (true)
+ {
+ if (loc + 6 > end)
+ break;
+ auto flags = static_cast<FT_UInt16>(buffer[loc] << 8 | buffer[loc + 1]);
+ loc += 2;
+ auto index = static_cast<FT_UInt16>(buffer[loc] << 8 | buffer[loc + 1]);
+ loc += 2;
+ FT_Int16 arg1, arg2;
+
+ //
https://docs.microsoft.com/en-us/typography/opentype/spec/glyf#composite-glyph-description
+ if (flags & 0x0001)
+ {
+ arg1 = static_cast<FT_Int16>(buffer[loc] << 8 | buffer[loc + 1]);
+ loc += 2;
+ arg2 = static_cast<FT_Int16>(buffer[loc] << 8 | buffer[loc + 1]);
+ loc += 2;
+ }
+ else
+ {
+ arg1 = buffer[loc];
+ arg2 = buffer[loc + 1];
+ loc += 2;
+ }
+ if (flags & 0x0008)
+ loc += 2;
+ else if (flags & 0x0040)
+ loc += 4;
+ else if (flags & 0x0080)
+ loc += 8;
+
+ subglyphs.emplace_back(index, flags,
+ flags & 0x0002 ? SubGlyph::PT_Offset
+ : SubGlyph::PT_Align,
+ std::pair<short, short>(arg1, arg2));
+
+ if (!(flags & 0x0020))
+ break;
+ }
+
+ list.emplace_back(static_cast<int>(i), std::move(subglyphs));
+ }
+}
+
+
// end of fontinfo.cpp
diff --git a/src/ftinspect/engine/fontinfo.hpp
b/src/ftinspect/engine/fontinfo.hpp
index c1b9ff3..f28dc81 100644
--- a/src/ftinspect/engine/fontinfo.hpp
+++ b/src/ftinspect/engine/fontinfo.hpp
@@ -5,13 +5,47 @@
#pragma once
#include <set>
-#include <vector>
+#include <QDateTime>
#include <QByteArray>
#include <QString>
#include <freetype/freetype.h>
#include <freetype/ftsnames.h>
+#include <freetype/t1tables.h>
class Engine;
+
+struct SFNTTableInfo
+{
+ unsigned long tag = 0;
+ unsigned long offset = 0;
+ unsigned long length = 0;
+ bool valid = false;
+ std::set<unsigned long> sharedFaces;
+
+ static void getForAll(Engine* engine, std::vector<SFNTTableInfo>& infos);
+
+
+ friend bool
+ operator==(const SFNTTableInfo& lhs,
+ const SFNTTableInfo& rhs)
+ {
+ return lhs.tag == rhs.tag
+ && lhs.offset == rhs.offset
+ && lhs.length == rhs.length
+ && lhs.valid == rhs.valid
+ && lhs.sharedFaces == rhs.sharedFaces;
+ }
+
+
+ friend bool
+ operator!=(const SFNTTableInfo& lhs,
+ const SFNTTableInfo& rhs)
+ {
+ return !(lhs == rhs);
+ }
+};
+
+
struct SFNTName
{
unsigned short nameID;
@@ -58,4 +92,244 @@ struct SFNTName
};
+struct FontBasicInfo
+{
+ int numFaces = -1;
+ QString familyName;
+ QString styleName;
+ QString postscriptName;
+ QDateTime createdTime;
+ QDateTime modifiedTime;
+ QString revision;
+ QString copyright;
+ QString trademark;
+ QString manufacturer;
+
+ static FontBasicInfo get(Engine* engine);
+
+
+ // Oh, we have no C++20 :(
+ friend bool
+ operator==(const FontBasicInfo& lhs,
+ const FontBasicInfo& rhs)
+ {
+ return lhs.numFaces == rhs.numFaces
+ && lhs.familyName == rhs.familyName
+ && lhs.styleName == rhs.styleName
+ && lhs.postscriptName == rhs.postscriptName
+ && lhs.createdTime == rhs.createdTime
+ && lhs.modifiedTime == rhs.modifiedTime
+ && lhs.revision == rhs.revision
+ && lhs.copyright == rhs.copyright
+ && lhs.trademark == rhs.trademark
+ && lhs.manufacturer == rhs.manufacturer;
+ }
+
+
+ friend bool
+ operator!=(const FontBasicInfo& lhs,
+ const FontBasicInfo& rhs)
+ {
+ return !(lhs == rhs);
+ }
+};
+
+
+struct FontTypeEntries
+{
+ QString driverName;
+ bool sfnt : 1;
+ bool scalable : 1;
+ bool mmgx : 1;
+ bool fixedSizes : 1;
+ bool hasHorizontal : 1;
+ bool hasVertical : 1;
+ bool fixedWidth : 1;
+ bool glyphNames : 1;
+
+ int emSize;
+ FT_BBox globalBBox;
+ int ascender;
+ int descender;
+ int height;
+ int maxAdvanceWidth;
+ int maxAdvanceHeight;
+ int underlinePos;
+ int underlineThickness;
+
+ static FontTypeEntries get(Engine* engine);
+
+
+ // Oh, we have no C++20 :(
+ friend bool
+ operator==(const FontTypeEntries& lhs,
+ const FontTypeEntries& rhs)
+ {
+ return lhs.driverName == rhs.driverName
+ && lhs.sfnt == rhs.sfnt
+ && lhs.scalable == rhs.scalable
+ && lhs.mmgx == rhs.mmgx
+ && lhs.fixedSizes == rhs.fixedSizes
+ && lhs.hasHorizontal == rhs.hasHorizontal
+ && lhs.hasVertical == rhs.hasVertical
+ && lhs.fixedWidth == rhs.fixedWidth
+ && lhs.glyphNames == rhs.glyphNames
+ && lhs.emSize == rhs.emSize
+ && lhs.globalBBox.xMax == rhs.globalBBox.xMax
+ && lhs.globalBBox.xMin == rhs.globalBBox.xMin
+ && lhs.globalBBox.yMax == rhs.globalBBox.yMax
+ && lhs.globalBBox.yMin == rhs.globalBBox.yMin
+ && lhs.ascender == rhs.ascender
+ && lhs.descender == rhs.descender
+ && lhs.height == rhs.height
+ && lhs.maxAdvanceWidth == rhs.maxAdvanceWidth
+ && lhs.maxAdvanceHeight == rhs.maxAdvanceHeight
+ && lhs.underlinePos == rhs.underlinePos
+ && lhs.underlineThickness == rhs.underlineThickness;
+ }
+
+
+ friend bool
+ operator!=(const FontTypeEntries& lhs,
+ const FontTypeEntries& rhs)
+ {
+ return !(lhs == rhs);
+ }
+};
+
+
+// For PostScript `PS_FontInfoRec` and `PS_PrivateRec`, we don't create our own
+// structs but direct use the ones provided by FreeType.
+// But we still need to provided `operator==`
+// No operator== for PS_FontInfoRec since there's little point to deep-copy it
+// bool operator==(const PS_FontInfoRec& lhs, const PS_FontInfoRec& rhs);
+bool operator==(const PS_PrivateRec& lhs, const PS_PrivateRec& rhs);
+
+
+struct FontFixedSize
+{
+ short height;
+ short width;
+ double size;
+ double xPpem;
+ double yPpem;
+
+
+ // Returns that if the list is updated
+ // Using a callback because Qt needs `beginResetModel` to be called
**before**
+ // the internal storage updates.
+ static bool get(Engine* engine,
+ std::vector<FontFixedSize>& list,
+ const std::function<void()>& onUpdateNeeded);
+
+
+ friend bool
+ operator==(const FontFixedSize& lhs,
+ const FontFixedSize& rhs)
+ {
+ return lhs.height == rhs.height
+ && lhs.width == rhs.width
+ && lhs.size == rhs.size
+ && lhs.xPpem == rhs.xPpem
+ && lhs.yPpem == rhs.yPpem;
+ }
+
+
+ friend bool
+ operator!=(const FontFixedSize& lhs,
+ const FontFixedSize& rhs)
+ {
+ return !(lhs == rhs);
+ }
+};
+
+
+struct CompositeGlyphInfo
+{
+ struct SubGlyph
+ {
+ enum PositionType
+ {
+ PT_Offset, // Child's points are added with a xy-offset
+ PT_Align // One point of the child is aligned with one point of the
parent
+ };
+ unsigned short index;
+ unsigned short flag;
+ PositionType positionType;
+ // For PT_Offset: <deltaX, deltaY>
+ // For PT_Align: <childPoint, parentPoint>
+ std::pair<short, short> position;
+
+ SubGlyph(unsigned short index,
+ unsigned short flag,
+ PositionType positionType,
+ std::pair<short, short> position)
+ : index(index),
+ flag(flag),
+ positionType(positionType),
+ position(std::move(position))
+ { }
+
+
+ friend bool
+ operator==(const SubGlyph& lhs,
+ const SubGlyph& rhs)
+ {
+ return lhs.index == rhs.index
+ && lhs.flag == rhs.flag
+ && lhs.positionType == rhs.positionType
+ && lhs.position == rhs.position;
+ }
+
+
+ friend bool
+ operator!=(const SubGlyph& lhs,
+ const SubGlyph& rhs)
+ {
+ return !(lhs == rhs);
+ }
+ };
+
+
+ int index;
+ std::vector<SubGlyph> subglyphs;
+
+
+ CompositeGlyphInfo(short index,
+ std::vector<SubGlyph> subglyphs)
+ : index(index),
+ subglyphs(std::move(subglyphs))
+ { }
+
+
+ friend bool
+ operator==(const CompositeGlyphInfo& lhs,
+ const CompositeGlyphInfo& rhs)
+ {
+ return lhs.index == rhs.index
+ && lhs.subglyphs == rhs.subglyphs;
+ }
+
+
+ friend bool
+ operator!=(const CompositeGlyphInfo& lhs,
+ const CompositeGlyphInfo& rhs)
+ {
+ return !(lhs == rhs);
+ }
+
+
+ // expensive
+ static void get(Engine* engine, std::vector<CompositeGlyphInfo>& list);
+};
+
+
+QString* mapSFNTNameIDToName(unsigned short nameID);
+QString* mapTTPlatformIDToName(unsigned short platformID);
+QString* mapTTEncodingIDToName(unsigned short platformID,
+ unsigned short encodingID);
+QString* mapTTLanguageIDToName(unsigned short platformID,
+ unsigned short languageID);
+
+
// end of fontinfo.hpp
diff --git a/src/ftinspect/engine/fontinfonamesmapping.cpp
b/src/ftinspect/engine/fontinfonamesmapping.cpp
new file mode 100644
index 0000000..e764ff0
--- /dev/null
+++ b/src/ftinspect/engine/fontinfonamesmapping.cpp
@@ -0,0 +1,567 @@
+// fontinfonamesmapping.cpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#include "fontinfo.hpp"
+
+#include <unordered_map>
+#include <freetype/ttnameid.h>
+
+#define FTI_UnknownID 0xFFFE
+
+// No more Qt containers since there's no any apparent advantage.
+using TableType = std::unordered_map<unsigned short, QString>;
+TableType ttSFNTNames;
+
+TableType ttPlatformNames;
+TableType ttEncodingUnicodeNames;
+TableType ttEncodingMacNames;
+TableType ttEncodingWindowsNames;
+TableType ttEncodingISONames;
+TableType ttEncodingAdobeNames;
+
+TableType ttLanguageMacNames;
+TableType ttLanguageWindowsNames;
+
+QString*
+mapSFNTNameIDToName(unsigned short nameID)
+{
+ if (ttSFNTNames.empty())
+ {
+ ttSFNTNames[FTI_UnknownID] = "Unknown";
+ ttSFNTNames[TT_NAME_ID_COPYRIGHT] = "Copyright";
+ ttSFNTNames[TT_NAME_ID_FONT_FAMILY] = "Font Family";
+ ttSFNTNames[TT_NAME_ID_FONT_SUBFAMILY] = "Font Subfamily";
+ ttSFNTNames[TT_NAME_ID_UNIQUE_ID] = "Unique Font ID";
+ ttSFNTNames[TT_NAME_ID_FULL_NAME] = "Full Name";
+ ttSFNTNames[TT_NAME_ID_VERSION_STRING] = "Version String";
+ ttSFNTNames[TT_NAME_ID_PS_NAME] = "PostScript Name";
+ ttSFNTNames[TT_NAME_ID_TRADEMARK] = "Trademark";
+ ttSFNTNames[TT_NAME_ID_MANUFACTURER] = "Manufacturer";
+ ttSFNTNames[TT_NAME_ID_DESIGNER] = "Designer";
+ ttSFNTNames[TT_NAME_ID_DESCRIPTION] = "Description";
+ ttSFNTNames[TT_NAME_ID_VENDOR_URL] = "Vendor URL";
+ ttSFNTNames[TT_NAME_ID_DESIGNER_URL] = "Designer URL";
+ ttSFNTNames[TT_NAME_ID_LICENSE] = "License";
+ ttSFNTNames[TT_NAME_ID_LICENSE_URL] = "License URL";
+ ttSFNTNames[TT_NAME_ID_TYPOGRAPHIC_FAMILY] = "Typographic Family";
+ ttSFNTNames[TT_NAME_ID_TYPOGRAPHIC_SUBFAMILY] = "Typographic Subfamily";
+ ttSFNTNames[TT_NAME_ID_MAC_FULL_NAME] = "Mac Full Name";
+ ttSFNTNames[TT_NAME_ID_SAMPLE_TEXT] = "Sample Text";
+ ttSFNTNames[TT_NAME_ID_WWS_FAMILY] = "WWS Family Name";
+ ttSFNTNames[TT_NAME_ID_WWS_SUBFAMILY] = "WWS Subfamily Name";
+ ttSFNTNames[TT_NAME_ID_LIGHT_BACKGROUND] = "Light Background Palette";
+ ttSFNTNames[TT_NAME_ID_DARK_BACKGROUND] = "Dark Background Palette";
+ ttSFNTNames[TT_NAME_ID_VARIATIONS_PREFIX]
+ = "Variations PostScript Name Prefix";
+ }
+
+ auto it = ttSFNTNames.find(nameID);
+ if (it == ttSFNTNames.end())
+ return &ttSFNTNames[FTI_UnknownID];
+ return &it->second;
+}
+
+QString*
+mapTTPlatformIDToName(unsigned short platformID)
+{
+ if (ttPlatformNames.empty())
+ {
+ ttPlatformNames[FTI_UnknownID] = "Unknown Platform";
+ // Unicode codepoints are encoded as UTF-16BE
+ ttPlatformNames[TT_PLATFORM_APPLE_UNICODE] = "Apple (Unicode)";
+ ttPlatformNames[TT_PLATFORM_MACINTOSH] = "Macintosh";
+ ttPlatformNames[TT_PLATFORM_ISO] = "ISO (deprecated)";
+ ttPlatformNames[TT_PLATFORM_MICROSOFT] = "Microsoft";
+ ttPlatformNames[TT_PLATFORM_CUSTOM] = "Custom";
+ ttPlatformNames[TT_PLATFORM_ADOBE] = "Adobe";
+ }
+
+ auto it = ttPlatformNames.find(platformID);
+ if (it == ttPlatformNames.end())
+ return &ttPlatformNames[FTI_UnknownID];
+ return &it->second;
+}
+
+
+QString*
+mapTTEncodingIDToName(unsigned short platformID,
+ unsigned short encodingID)
+{
+ if (ttEncodingUnicodeNames.empty())
+ {
+ // Note: different from the Apple doc.
+ ttEncodingUnicodeNames[FTI_UnknownID] = "Unknown Encoding";
+ ttEncodingUnicodeNames[TT_APPLE_ID_DEFAULT] = "Unicode 1.0";
+ ttEncodingUnicodeNames[TT_APPLE_ID_UNICODE_1_1] = "Unicode 1.1";
+ ttEncodingUnicodeNames[TT_APPLE_ID_ISO_10646] = "ISO/IEC 10646";
+ ttEncodingUnicodeNames[TT_APPLE_ID_UNICODE_2_0]
+ = "Unicode 2.0 or later (BMP only)";
+ ttEncodingUnicodeNames[TT_APPLE_ID_UNICODE_32]
+ = "Unicode 2.0 or later (non-BMP characters allowed)";
+ //ttEncodingUnicodeNames[TT_APPLE_ID_VARIANT_SELECTOR] = "Variant
Selector";
+ //ttEncodingUnicodeNames[TT_APPLE_ID_FULL_UNICODE] = ???;
+ }
+
+ if (ttEncodingMacNames.empty())
+ {
+ ttEncodingMacNames[FTI_UnknownID] = "Unknown Encoding";
+ ttEncodingMacNames[0] = "Roman";
+ ttEncodingMacNames[1] = "Japanese";
+ ttEncodingMacNames[2] = "Chinese (Traditional)";
+ ttEncodingMacNames[3] = "Korean";
+ ttEncodingMacNames[4] = "Arabic";
+ ttEncodingMacNames[5] = "Hebrew";
+ ttEncodingMacNames[6] = "Greek";
+ ttEncodingMacNames[7] = "Russian";
+ ttEncodingMacNames[8] = "RSymbol";
+ ttEncodingMacNames[9] = "Devanagari";
+ ttEncodingMacNames[10] = "Gurmukhi";
+ ttEncodingMacNames[11] = "Gujarati";
+ ttEncodingMacNames[12] = "Oriya";
+ ttEncodingMacNames[13] = "Bengali";
+ ttEncodingMacNames[14] = "Tamil";
+ ttEncodingMacNames[15] = "Telugu";
+ ttEncodingMacNames[16] = "Kannada";
+ ttEncodingMacNames[17] = "Malayalam";
+ ttEncodingMacNames[18] = "Sinhalese";
+ ttEncodingMacNames[19] = "Burmese";
+ ttEncodingMacNames[20] = "Khmer";
+ ttEncodingMacNames[21] = "Thai";
+ ttEncodingMacNames[22] = "Laotian";
+ ttEncodingMacNames[23] = "Georgian";
+ ttEncodingMacNames[24] = "Armenian";
+ ttEncodingMacNames[25] = "Chinese (Simplified)";
+ ttEncodingMacNames[26] = "Tibetan";
+ ttEncodingMacNames[27] = "Mongolian";
+ ttEncodingMacNames[28] = "Geez";
+ ttEncodingMacNames[29] = "Slavic";
+ ttEncodingMacNames[30] = "Vietnamese";
+ ttEncodingMacNames[31] = "Sindhi";
+ ttEncodingMacNames[32] = "Uninterpreted";
+ }
+
+ if (ttEncodingWindowsNames.empty())
+ {
+ ttEncodingMacNames[FTI_UnknownID] = "Unknown Encoding";
+ ttEncodingWindowsNames[0] = "Symbol";
+ ttEncodingWindowsNames[1] = "Unicode BMP";
+ ttEncodingWindowsNames[2] = "ShiftJIS";
+ ttEncodingWindowsNames[3] = "GBK";
+ ttEncodingWindowsNames[4] = "Big5";
+ ttEncodingWindowsNames[5] = "Wansung";
+ ttEncodingWindowsNames[6] = "Johab";
+ ttEncodingWindowsNames[7] = "Reserved";
+ ttEncodingWindowsNames[8] = "Reserved";
+ ttEncodingWindowsNames[9] = "Reserved";
+ ttEncodingWindowsNames[10] = "Unicode full repertoire";
+ }
+
+ if (ttEncodingISONames.empty())
+ {
+ ttEncodingISONames[FTI_UnknownID] = "Unknown Encoding";
+ ttEncodingISONames[TT_ISO_ID_7BIT_ASCII] = "ASCII";
+ ttEncodingISONames[TT_ISO_ID_10646] = "ISO/IEC 10646";
+ ttEncodingISONames[TT_ISO_ID_8859_1] = "ISO 8859-1";
+ }
+
+ if (ttEncodingAdobeNames.empty())
+ {
+ ttEncodingAdobeNames[FTI_UnknownID] = "Unknown Encoding";
+ ttEncodingAdobeNames[TT_ADOBE_ID_STANDARD] = "Adobe Standard";
+ ttEncodingAdobeNames[TT_ADOBE_ID_EXPERT] = "Adobe Expert";
+ ttEncodingAdobeNames[TT_ADOBE_ID_CUSTOM] = "Adobe Custom";
+ ttEncodingAdobeNames[TT_ADOBE_ID_LATIN_1] = "Adobe Latin 1";
+ }
+
+ TableType* table = NULL;
+
+ switch (platformID)
+ {
+ case TT_PLATFORM_APPLE_UNICODE:
+ table = &ttEncodingUnicodeNames;
+ break;
+ case TT_PLATFORM_MACINTOSH:
+ table = &ttEncodingMacNames;
+ break;
+ case TT_PLATFORM_MICROSOFT:
+ table = &ttEncodingWindowsNames;
+ break;
+ case TT_PLATFORM_ISO:
+ table = &ttEncodingISONames;
+ break;
+ case TT_PLATFORM_ADOBE:
+ table = &ttEncodingAdobeNames;
+ break;
+
+ default:
+ return &ttEncodingUnicodeNames[FTI_UnknownID];
+ }
+
+ auto it = table->find(encodingID);
+ if (it == table->end())
+ return &(*table)[FTI_UnknownID];
+ return &it->second;
+}
+
+
+QString*
+mapTTLanguageIDToName(unsigned short platformID,
+ unsigned short languageID)
+{
+ if (ttLanguageMacNames.empty())
+ {
+ ttLanguageMacNames[FTI_UnknownID] = "Unknown Language";
+ ttLanguageMacNames[0] = "English";
+ ttLanguageMacNames[1] = "French";
+ ttLanguageMacNames[2] = "German";
+ ttLanguageMacNames[3] = "Italian";
+ ttLanguageMacNames[4] = "Dutch";
+ ttLanguageMacNames[5] = "Swedish";
+ ttLanguageMacNames[6] = "Spanish";
+ ttLanguageMacNames[7] = "Danish";
+ ttLanguageMacNames[8] = "Portuguese";
+ ttLanguageMacNames[9] = "Norwegian";
+ ttLanguageMacNames[10] = "Hebrew";
+ ttLanguageMacNames[11] = "Japanese";
+ ttLanguageMacNames[12] = "Arabic";
+ ttLanguageMacNames[13] = "Finnish";
+ ttLanguageMacNames[14] = "Greek";
+ ttLanguageMacNames[15] = "Icelandic";
+ ttLanguageMacNames[16] = "Maltese";
+ ttLanguageMacNames[17] = "Turkish";
+ ttLanguageMacNames[18] = "Croatian";
+ ttLanguageMacNames[19] = "Chinese (Traditional)";
+ ttLanguageMacNames[20] = "Urdu";
+ ttLanguageMacNames[21] = "Hindi";
+ ttLanguageMacNames[22] = "Thai";
+ ttLanguageMacNames[23] = "Korean";
+ ttLanguageMacNames[24] = "Lithuanian";
+ ttLanguageMacNames[25] = "Polish";
+ ttLanguageMacNames[26] = "Hungarian";
+ ttLanguageMacNames[27] = "Estonian";
+ ttLanguageMacNames[28] = "Latvian";
+ ttLanguageMacNames[29] = "Sami";
+ ttLanguageMacNames[30] = "Faroese";
+ ttLanguageMacNames[31] = "Farsi/Persian";
+ ttLanguageMacNames[32] = "Russian";
+ ttLanguageMacNames[33] = "Chinese (Simplified)";
+ ttLanguageMacNames[34] = "Flemish";
+ ttLanguageMacNames[35] = "Irish Gaelic";
+ ttLanguageMacNames[36] = "Albanian";
+ ttLanguageMacNames[37] = "Romanian";
+ ttLanguageMacNames[38] = "Czech";
+ ttLanguageMacNames[39] = "Slovak";
+ ttLanguageMacNames[40] = "Slovenian";
+ ttLanguageMacNames[41] = "Yiddish";
+ ttLanguageMacNames[42] = "Serbian";
+ ttLanguageMacNames[43] = "Macedonian";
+ ttLanguageMacNames[44] = "Bulgarian";
+ ttLanguageMacNames[45] = "Ukrainian";
+ ttLanguageMacNames[46] = "Byelorussian";
+ ttLanguageMacNames[47] = "Uzbek";
+ ttLanguageMacNames[48] = "Kazakh";
+ ttLanguageMacNames[49] = "Azerbaijani (Cyrillic script)";
+ ttLanguageMacNames[50] = "Azerbaijani (Arabic script)";
+ ttLanguageMacNames[51] = "Armenian";
+ ttLanguageMacNames[52] = "Georgian";
+ ttLanguageMacNames[53] = "Moldavian";
+ ttLanguageMacNames[54] = "Kirghiz";
+ ttLanguageMacNames[55] = "Tajiki";
+ ttLanguageMacNames[56] = "Turkmen";
+ ttLanguageMacNames[57] = "Mongolian (Mongolian script)";
+ ttLanguageMacNames[58] = "Mongolian (Cyrillic script)";
+ ttLanguageMacNames[59] = "Pashto";
+ ttLanguageMacNames[60] = "Kurdish";
+ ttLanguageMacNames[61] = "Kashmiri";
+ ttLanguageMacNames[62] = "Sindhi";
+ ttLanguageMacNames[63] = "Tibetan";
+ ttLanguageMacNames[64] = "Nepali";
+ ttLanguageMacNames[65] = "Sanskrit";
+ ttLanguageMacNames[66] = "Marathi";
+ ttLanguageMacNames[67] = "Bengali";
+ ttLanguageMacNames[68] = "Assamese";
+ ttLanguageMacNames[69] = "Gujarati";
+ ttLanguageMacNames[70] = "Punjabi";
+ ttLanguageMacNames[71] = "Oriya";
+ ttLanguageMacNames[72] = "Malayalam";
+ ttLanguageMacNames[73] = "Kannada";
+ ttLanguageMacNames[74] = "Tamil";
+ ttLanguageMacNames[75] = "Telugu";
+ ttLanguageMacNames[76] = "Sinhalese";
+ ttLanguageMacNames[77] = "Burmese";
+ ttLanguageMacNames[78] = "Khmer";
+ ttLanguageMacNames[79] = "Lao";
+ ttLanguageMacNames[80] = "Vietnamese";
+ ttLanguageMacNames[81] = "Indonesian";
+ ttLanguageMacNames[82] = "Tagalog";
+ ttLanguageMacNames[83] = "Malay (Roman script)";
+ ttLanguageMacNames[84] = "Malay (Arabic script)";
+ ttLanguageMacNames[85] = "Amharic";
+ ttLanguageMacNames[86] = "Tigrinya";
+ ttLanguageMacNames[87] = "Galla";
+ ttLanguageMacNames[88] = "Somali";
+ ttLanguageMacNames[89] = "Swahili";
+ ttLanguageMacNames[90] = "Kinyarwanda/Ruanda";
+ ttLanguageMacNames[91] = "Rundi";
+ ttLanguageMacNames[92] = "Nyanja/Chewa";
+ ttLanguageMacNames[93] = "Malagasy";
+ ttLanguageMacNames[94] = "Esperanto";
+ ttLanguageMacNames[128] = "Welsh";
+ ttLanguageMacNames[129] = "Basque";
+ ttLanguageMacNames[130] = "Catalan";
+ ttLanguageMacNames[131] = "Latin";
+ ttLanguageMacNames[132] = "Quechua";
+ ttLanguageMacNames[133] = "Guarani";
+ ttLanguageMacNames[134] = "Aymara";
+ ttLanguageMacNames[135] = "Tatar";
+ ttLanguageMacNames[136] = "Uighur";
+ ttLanguageMacNames[137] = "Dzongkha";
+ ttLanguageMacNames[138] = "Javanese (Roman script)";
+ ttLanguageMacNames[139] = "Sundanese (Roman script)";
+ ttLanguageMacNames[140] = "Galician";
+ ttLanguageMacNames[141] = "Afrikaans";
+ ttLanguageMacNames[142] = "Breton";
+ ttLanguageMacNames[143] = "Inuktitut";
+ ttLanguageMacNames[144] = "Scottish Gaelic";
+ ttLanguageMacNames[145] = "Manx Gaelic";
+ ttLanguageMacNames[146] = "Irish Gaelic (with dot above)";
+ ttLanguageMacNames[147] = "Tongan";
+ ttLanguageMacNames[148] = "Greek (polytonic)";
+ ttLanguageMacNames[149] = "Greenlandic";
+ ttLanguageMacNames[150] = "Azerbaijani (Roman script)";
+ }
+
+ if (ttLanguageWindowsNames.empty())
+ {
+ ttLanguageWindowsNames[FTI_UnknownID] = "Unknown Language";
+ ttLanguageWindowsNames[0x0436] = "Afrikaans";
+ ttLanguageWindowsNames[0x041C] = "Albanian";
+ ttLanguageWindowsNames[0x0402] = "Bulgarian";
+ ttLanguageWindowsNames[0x0484] = "Alsatian";
+ ttLanguageWindowsNames[0x045E] = "Amharic";
+ ttLanguageWindowsNames[0x0405] = "Czech";
+ ttLanguageWindowsNames[0x1401] = "Arabic";
+ ttLanguageWindowsNames[0x3C01] = "Arabic";
+ ttLanguageWindowsNames[0x0C01] = "Arabic";
+ ttLanguageWindowsNames[0x0401] = "Arabic";
+ ttLanguageWindowsNames[0x0404] = "Chinese";
+ ttLanguageWindowsNames[0x0406] = "Danish";
+ ttLanguageWindowsNames[0x0423] = "Belarusian";
+ ttLanguageWindowsNames[0x0445] = "Bengali";
+ ttLanguageWindowsNames[0x0465] = "Divehi";
+ ttLanguageWindowsNames[0x0801] = "Arabic";
+ ttLanguageWindowsNames[0x2C01] = "Arabic";
+ ttLanguageWindowsNames[0x0403] = "Catalan";
+ ttLanguageWindowsNames[0x0413] = "Dutch";
+ ttLanguageWindowsNames[0x0483] = "Corsican";
+ ttLanguageWindowsNames[0x0804] = "Chinese";
+ ttLanguageWindowsNames[0x0813] = "Dutch";
+ ttLanguageWindowsNames[0x0845] = "Bengali";
+ ttLanguageWindowsNames[0x1001] = "Arabic";
+ ttLanguageWindowsNames[0x1004] = "Chinese";
+ ttLanguageWindowsNames[0x1009] = "English";
+ ttLanguageWindowsNames[0x1404] = "Chinese";
+ ttLanguageWindowsNames[0x1801] = "Arabic";
+ ttLanguageWindowsNames[0x2001] = "Arabic";
+ ttLanguageWindowsNames[0x2401] = "Arabic";
+ ttLanguageWindowsNames[0x2801] = "Arabic";
+ ttLanguageWindowsNames[0x3001] = "Arabic";
+ ttLanguageWindowsNames[0x3401] = "Arabic";
+ ttLanguageWindowsNames[0x3801] = "Arabic";
+ ttLanguageWindowsNames[0x4001] = "Arabic";
+ ttLanguageWindowsNames[0x1C01] = "Arabic";
+ ttLanguageWindowsNames[0x042B] = "Armenian";
+ ttLanguageWindowsNames[0x044D] = "Assamese";
+ ttLanguageWindowsNames[0x082C] = "Azeri (Cyrillic)";
+ ttLanguageWindowsNames[0x042C] = "Azeri (Latin)";
+ ttLanguageWindowsNames[0x046D] = "Bashkir";
+ ttLanguageWindowsNames[0x042D] = "Basque";
+ ttLanguageWindowsNames[0x201A] = "Bosnian (Cyrillic)";
+ ttLanguageWindowsNames[0x141A] = "Bosnian (Latin)";
+ ttLanguageWindowsNames[0x047E] = "Breton";
+ ttLanguageWindowsNames[0x0C04] = "Chinese";
+ ttLanguageWindowsNames[0x041A] = "Croatian";
+ ttLanguageWindowsNames[0x101A] = "Croatian (Latin)";
+ ttLanguageWindowsNames[0x048C] = "Dari";
+ ttLanguageWindowsNames[0x0C09] = "English";
+ ttLanguageWindowsNames[0x0407] = "German";
+ ttLanguageWindowsNames[0x0408] = "Greek";
+ ttLanguageWindowsNames[0x0409] = "English";
+ ttLanguageWindowsNames[0x0410] = "Italian";
+ ttLanguageWindowsNames[0x0411] = "Japanese";
+ ttLanguageWindowsNames[0x0421] = "Indonesian";
+ ttLanguageWindowsNames[0x0425] = "Estonian";
+ ttLanguageWindowsNames[0x0434] = "isiXhosa";
+ ttLanguageWindowsNames[0x0435] = "isiZulu";
+ ttLanguageWindowsNames[0x0437] = "Georgian";
+ ttLanguageWindowsNames[0x0438] = "Faroese";
+ ttLanguageWindowsNames[0x0439] = "Hindi";
+ ttLanguageWindowsNames[0x0447] = "Gujarati";
+ ttLanguageWindowsNames[0x0453] = "Khmer";
+ ttLanguageWindowsNames[0x0456] = "Galician";
+ ttLanguageWindowsNames[0x0462] = "Frisian";
+ ttLanguageWindowsNames[0x0464] = "Filipino";
+ ttLanguageWindowsNames[0x0468] = "Hausa (Latin)";
+ ttLanguageWindowsNames[0x0470] = "Igbo";
+ ttLanguageWindowsNames[0x0486] = "K’iche";
+ ttLanguageWindowsNames[0x0807] = "German";
+ ttLanguageWindowsNames[0x0809] = "English";
+ ttLanguageWindowsNames[0x0810] = "Italian";
+ ttLanguageWindowsNames[0x1007] = "German";
+ ttLanguageWindowsNames[0x1407] = "German";
+ ttLanguageWindowsNames[0x1409] = "English";
+ ttLanguageWindowsNames[0x1809] = "English";
+ ttLanguageWindowsNames[0x2009] = "English";
+ ttLanguageWindowsNames[0x2409] = "English";
+ ttLanguageWindowsNames[0x2809] = "English";
+ ttLanguageWindowsNames[0x3009] = "English";
+ ttLanguageWindowsNames[0x3409] = "English";
+ ttLanguageWindowsNames[0x4009] = "English";
+ ttLanguageWindowsNames[0x4409] = "English";
+ ttLanguageWindowsNames[0x4809] = "English";
+ ttLanguageWindowsNames[0x1C09] = "English";
+ ttLanguageWindowsNames[0x2C09] = "English";
+ ttLanguageWindowsNames[0x040B] = "Finnish";
+ ttLanguageWindowsNames[0x080C] = "French";
+ ttLanguageWindowsNames[0x0C0C] = "French";
+ ttLanguageWindowsNames[0x040C] = "French";
+ ttLanguageWindowsNames[0x140c] = "French";
+ ttLanguageWindowsNames[0x180C] = "French";
+ ttLanguageWindowsNames[0x100C] = "French";
+ ttLanguageWindowsNames[0x0C07] = "German";
+ ttLanguageWindowsNames[0x046F] = "Greenlandic";
+ ttLanguageWindowsNames[0x040D] = "Hebrew";
+ ttLanguageWindowsNames[0x040E] = "Hungarian";
+ ttLanguageWindowsNames[0x040F] = "Icelandic";
+ ttLanguageWindowsNames[0x045D] = "Inuktitut";
+ ttLanguageWindowsNames[0x085D] = "Inuktitut (Latin)";
+ ttLanguageWindowsNames[0x083C] = "Irish";
+ ttLanguageWindowsNames[0x044B] = "Kannada";
+ ttLanguageWindowsNames[0x043F] = "Kazakh";
+ ttLanguageWindowsNames[0x0412] = "Korean";
+ ttLanguageWindowsNames[0x0426] = "Latvian";
+ ttLanguageWindowsNames[0x0427] = "Lithuanian";
+ ttLanguageWindowsNames[0x0440] = "Kyrgyz";
+ ttLanguageWindowsNames[0x0441] = "Kiswahili";
+ ttLanguageWindowsNames[0x0454] = "Lao";
+ ttLanguageWindowsNames[0x0457] = "Konkani";
+ ttLanguageWindowsNames[0x0481] = "Maori";
+ ttLanguageWindowsNames[0x0487] = "Kinyarwanda";
+ ttLanguageWindowsNames[0x082E] = "Lower Sorbian";
+ ttLanguageWindowsNames[0x046E] = "Luxembourgish";
+ ttLanguageWindowsNames[0x042F] = "Macedonian";
+ ttLanguageWindowsNames[0x083E] = "Malay";
+ ttLanguageWindowsNames[0x043E] = "Malay";
+ ttLanguageWindowsNames[0x044C] = "Malayalam";
+ ttLanguageWindowsNames[0x043A] = "Maltese";
+ ttLanguageWindowsNames[0x047A] = "Mapudungun";
+ ttLanguageWindowsNames[0x044E] = "Marathi";
+ ttLanguageWindowsNames[0x047C] = "Mohawk";
+ ttLanguageWindowsNames[0x0414] = "Norwegian (Bokmal)";
+ ttLanguageWindowsNames[0x0415] = "Polish";
+ ttLanguageWindowsNames[0x0416] = "Portuguese";
+ ttLanguageWindowsNames[0x0417] = "Romansh";
+ ttLanguageWindowsNames[0x0418] = "Romanian";
+ ttLanguageWindowsNames[0x0419] = "Russian";
+ ttLanguageWindowsNames[0x0446] = "Punjabi";
+ ttLanguageWindowsNames[0x0448] = "Odia (formerly Oriya)";
+ ttLanguageWindowsNames[0x0450] = "Mongolian (Cyrillic)";
+ ttLanguageWindowsNames[0x0461] = "Nepali";
+ ttLanguageWindowsNames[0x0463] = "Pashto";
+ ttLanguageWindowsNames[0x0482] = "Occitan";
+ ttLanguageWindowsNames[0x0814] = "Norwegian (Nynorsk)";
+ ttLanguageWindowsNames[0x0816] = "Portuguese";
+ ttLanguageWindowsNames[0x0850] = "Mongolian (Traditional)";
+ ttLanguageWindowsNames[0x046B] = "Quechua";
+ ttLanguageWindowsNames[0x086B] = "Quechua";
+ ttLanguageWindowsNames[0x0C6B] = "Quechua";
+ ttLanguageWindowsNames[0x243B] = "Sami (Inari)";
+ ttLanguageWindowsNames[0x103B] = "Sami (Lule)";
+ ttLanguageWindowsNames[0x143B] = "Sami (Lule)";
+ ttLanguageWindowsNames[0x0C3B] = "Sami (Northern)";
+ ttLanguageWindowsNames[0x043B] = "Sami (Northern)";
+ ttLanguageWindowsNames[0x083B] = "Sami (Northern)";
+ ttLanguageWindowsNames[0x203B] = "Sami (Skolt)";
+ ttLanguageWindowsNames[0x183B] = "Sami (Southern)";
+ ttLanguageWindowsNames[0x1C3B] = "Sami (Southern)";
+ ttLanguageWindowsNames[0x044F] = "Sanskrit";
+ ttLanguageWindowsNames[0x1C1A] = "Serbian (Cyrillic)";
+ ttLanguageWindowsNames[0x0C1A] = "Serbian (Cyrillic)";
+ ttLanguageWindowsNames[0x181A] = "Serbian (Latin)";
+ ttLanguageWindowsNames[0x081A] = "Serbian (Latin)";
+ ttLanguageWindowsNames[0x046C] = "Sesotho sa Leboa";
+ ttLanguageWindowsNames[0x0432] = "Setswana";
+ ttLanguageWindowsNames[0x045B] = "Sinhala";
+ ttLanguageWindowsNames[0x041B] = "Slovak";
+ ttLanguageWindowsNames[0x0424] = "Slovenian";
+ ttLanguageWindowsNames[0x2C0A] = "Spanish";
+ ttLanguageWindowsNames[0x400A] = "Spanish";
+ ttLanguageWindowsNames[0x340A] = "Spanish";
+ ttLanguageWindowsNames[0x240A] = "Spanish";
+ ttLanguageWindowsNames[0x140A] = "Spanish";
+ ttLanguageWindowsNames[0x1C0A] = "Spanish";
+ ttLanguageWindowsNames[0x300A] = "Spanish";
+ ttLanguageWindowsNames[0x440A] = "Spanish";
+ ttLanguageWindowsNames[0x100A] = "Spanish";
+ ttLanguageWindowsNames[0x480A] = "Spanish";
+ ttLanguageWindowsNames[0x080A] = "Spanish";
+ ttLanguageWindowsNames[0x4C0A] = "Spanish";
+ ttLanguageWindowsNames[0x180A] = "Spanish";
+ ttLanguageWindowsNames[0x3C0A] = "Spanish";
+ ttLanguageWindowsNames[0x280A] = "Spanish";
+ ttLanguageWindowsNames[0x500A] = "Spanish";
+ ttLanguageWindowsNames[0x0C0A] = "Spanish (Modern Sort)";
+ ttLanguageWindowsNames[0x040A] = "Spanish (Traditional Sort)";
+ ttLanguageWindowsNames[0x540A] = "Spanish";
+ ttLanguageWindowsNames[0x380A] = "Spanish";
+ ttLanguageWindowsNames[0x200A] = "Spanish";
+ ttLanguageWindowsNames[0x081D] = "Swedish";
+ ttLanguageWindowsNames[0x041D] = "Swedish";
+ ttLanguageWindowsNames[0x045A] = "Syriac";
+ ttLanguageWindowsNames[0x0420] = "Urdu";
+ ttLanguageWindowsNames[0x0428] = "Tajik (Cyrillic)";
+ ttLanguageWindowsNames[0x085F] = "Tamazight (Latin)";
+ ttLanguageWindowsNames[0x0443] = "Uzbek (Latin)";
+ ttLanguageWindowsNames[0x0444] = "Tatar";
+ ttLanguageWindowsNames[0x0449] = "Tamil";
+ ttLanguageWindowsNames[0x044A] = "Telugu";
+ ttLanguageWindowsNames[0x041E] = "Thai";
+ ttLanguageWindowsNames[0x0422] = "Ukrainian";
+ ttLanguageWindowsNames[0x0442] = "Turkmen";
+ ttLanguageWindowsNames[0x0451] = "Tibetan";
+ ttLanguageWindowsNames[0x041F] = "Turkish";
+ ttLanguageWindowsNames[0x0480] = "Uighur";
+ ttLanguageWindowsNames[0x042E] = "Upper Sorbian";
+ ttLanguageWindowsNames[0x0452] = "Welsh";
+ ttLanguageWindowsNames[0x0478] = "Yi";
+ ttLanguageWindowsNames[0x0485] = "Yakut";
+ ttLanguageWindowsNames[0x0488] = "Wolof";
+ ttLanguageWindowsNames[0x0843] = "Uzbek (Cyrillic)";
+ ttLanguageWindowsNames[0x042A] = "Vietnamese";
+ ttLanguageWindowsNames[0x046A] = "Yoruba";
+ }
+
+ TableType* table = NULL;
+
+ switch (platformID)
+ {
+ case TT_PLATFORM_MACINTOSH:
+ table = &ttLanguageMacNames;
+ break;
+ case TT_PLATFORM_MICROSOFT:
+ table = &ttLanguageWindowsNames;
+ break;
+
+ default:
+ return &ttLanguageWindowsNames[FTI_UnknownID];
+ }
+
+ auto it = table->find(languageID);
+ if (it == table->end())
+ return &(*table)[FTI_UnknownID];
+ return &it->second;
+}
+
+
+// end of fontinfonamesmapping.cpp
diff --git a/src/ftinspect/engine/rendering.cpp
b/src/ftinspect/engine/rendering.cpp
index 4fbb251..4fa28e3 100644
--- a/src/ftinspect/engine/rendering.cpp
+++ b/src/ftinspect/engine/rendering.cpp
@@ -5,6 +5,9 @@
#include "rendering.hpp"
#include <cmath>
+#include <QPixmap>
+#include <QPainter>
+
#include <freetype/ftbitmap.h>
#include "engine.hpp"
@@ -446,6 +449,21 @@ RenderingEngine::tryDirectRenderColorLayers(int glyphIndex,
}
+QPixmap
+RenderingEngine::padToSize(QImage* image, int ppem)
+{
+ auto width = std::max(image->width(), ppem);
+ auto height = std::max(image->height(), ppem);
+ auto result = QPixmap(width, height);
+ result.fill(backgroundColor_);
+ QPainter painter(&result);
+ auto pos = QPoint { width / 2 - image->width() / 2,
+ height / 2 - image->height() / 2};
+ painter.drawImage(pos, *image);
+ return result;
+}
+
+
void
convertLCDToARGB(FT_Bitmap& bitmap,
QImage& image,
diff --git a/src/ftinspect/engine/rendering.hpp
b/src/ftinspect/engine/rendering.hpp
index c7e44b2..95fe00d 100644
--- a/src/ftinspect/engine/rendering.hpp
+++ b/src/ftinspect/engine/rendering.hpp
@@ -48,6 +48,8 @@ public:
QRect* outRect,
bool inverseRectY = false);
+ QPixmap padToSize(QImage* image, int ppem);
+
private:
Engine* engine_;
diff --git a/src/ftinspect/maingui.cpp b/src/ftinspect/maingui.cpp
index 3aa7281..1b7ca32 100644
--- a/src/ftinspect/maingui.cpp
+++ b/src/ftinspect/maingui.cpp
@@ -276,6 +276,7 @@ MainGUI::createLayout()
continuousTab_ = new ContinuousTab(this, engine_,
glyphDetailsDockWidget_, glyphDetails_);
comparatorTab_ = new ComparatorTab(this, engine_);
+ infoTab_ = new InfoTab(this, engine_);
tabWidget_ = new QTabWidget(this);
tabWidget_->setObjectName("mainTab"); // for stylesheet
@@ -287,6 +288,8 @@ MainGUI::createLayout()
tabWidget_->addTab(continuousTab_, tr("Continuous View"));
tabs_.push_back(comparatorTab_);
tabWidget_->addTab(comparatorTab_, tr("Comparator View"));
+ tabs_.push_back(infoTab_);
+ tabWidget_->addTab(infoTab_, tr("Font Info"));
lastTab_ = singularTab_;
tabWidget_->setTabToolTip(0, tr("View single glyph in grid view.\n"
@@ -297,6 +300,8 @@ MainGUI::createLayout()
tabWidget_->setTabToolTip(2, tr("Compare the output of the font "
"in different rendering settings "
"(e.g. hintings)."));
+ tabWidget_->setTabToolTip(3, tr("View font info and metadata."));
+
tripletSelector_ = new TripletSelector(this, engine_);
rightLayout_ = new QVBoxLayout;
@@ -349,6 +354,9 @@ MainGUI::createConnections()
connect(continuousTab_, &ContinuousTab::switchToSingular,
this, &MainGUI::switchToSingular);
+ connect(infoTab_, &InfoTab::switchToSingular,
+ [&](int index) { switchToSingular(index, -1); });
+
connect(glyphDetails_, &GlyphDetails::closeDockWidget,
this, &MainGUI::closeDockWidget);
connect(glyphDetails_, &GlyphDetails::switchToSingular,
diff --git a/src/ftinspect/maingui.hpp b/src/ftinspect/maingui.hpp
index d401382..add7671 100644
--- a/src/ftinspect/maingui.hpp
+++ b/src/ftinspect/maingui.hpp
@@ -12,6 +12,7 @@
#include "panels/singular.hpp"
#include "panels/continuous.hpp"
#include "panels/comparator.hpp"
+#include "panels/info.hpp"
#include "panels/glyphdetails.hpp"
#include <vector>
@@ -95,6 +96,7 @@ private:
SingularTab* singularTab_;
ContinuousTab* continuousTab_;
ComparatorTab* comparatorTab_;
+ InfoTab* infoTab_;
QWidget* lastTab_ = NULL;
QDockWidget* glyphDetailsDockWidget_;
diff --git a/src/ftinspect/meson.build b/src/ftinspect/meson.build
index 546ee5d..13a6bf9 100644
--- a/src/ftinspect/meson.build
+++ b/src/ftinspect/meson.build
@@ -27,6 +27,7 @@ if qt5_dep.found()
'engine/paletteinfo.cpp',
'engine/mmgx.cpp',
'engine/fontinfo.cpp',
+ 'engine/fontinfonamesmapping.cpp',
'engine/stringrenderer.cpp',
'engine/charmap.cpp',
@@ -45,12 +46,14 @@ if qt5_dep.found()
'widgets/charmapcombobox.cpp',
'models/customcomboboxmodels.cpp',
+ 'models/fontinfomodels.cpp',
'panels/settingpanel.cpp',
'panels/settingpanelmmgx.cpp',
'panels/singular.cpp',
'panels/continuous.cpp',
'panels/comparator.cpp',
+ 'panels/info.cpp',
'panels/glyphdetails.cpp',
'ftinspect.cpp',
@@ -70,11 +73,13 @@ if qt5_dep.found()
'widgets/charmapcombobox.hpp',
'maingui.hpp',
'models/customcomboboxmodels.hpp',
+ 'models/fontinfomodels.hpp',
'panels/settingpanel.hpp',
'panels/settingpanelmmgx.hpp',
'panels/singular.hpp',
'panels/continuous.hpp',
'panels/comparator.hpp',
+ 'panels/info.hpp',
'panels/glyphdetails.hpp',
],
dependencies: qt5_dep)
diff --git a/src/ftinspect/models/fontinfomodels.cpp
b/src/ftinspect/models/fontinfomodels.cpp
new file mode 100644
index 0000000..ea63e03
--- /dev/null
+++ b/src/ftinspect/models/fontinfomodels.cpp
@@ -0,0 +1,738 @@
+// fontinfomodels.cpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#include "fontinfomodels.hpp"
+#include "../engine/engine.hpp"
+
+int
+FixedSizeInfoModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return static_cast<int>(storage_.size());
+}
+
+
+int
+FixedSizeInfoModel::columnCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return FSIM_Max;
+}
+
+
+QVariant
+FixedSizeInfoModel::data(const QModelIndex& index,
+ int role) const
+{
+ if (index.row() < 0 || index.column() < 0)
+ return {};
+ auto r = static_cast<size_t>(index.row());
+ if ((role != Qt::DisplayRole && role != Qt::ToolTipRole)
+ || r > storage_.size())
+ return {};
+
+ auto& obj = storage_[r];
+ switch (static_cast<Columns>(index.column()))
+ {
+ case FSIM_Height:
+ return obj.height;
+ case FSIM_Width:
+ return obj.width;
+ case FSIM_Size:
+ return obj.size;
+ case FSIM_XPpem:
+ return obj.xPpem;
+ case FSIM_YPpem:
+ return obj.yPpem;
+ default:
+ break;
+ }
+
+ return {};
+}
+
+
+QVariant
+FixedSizeInfoModel::headerData(int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole)
+ return {};
+ if (orientation == Qt::Vertical)
+ return section;
+ if (orientation != Qt::Horizontal)
+ return {};
+
+ switch (static_cast<Columns>(section))
+ {
+ case FSIM_Height:
+ return tr("Height");
+ case FSIM_Width:
+ return tr("Width");
+ case FSIM_Size:
+ return tr("Size");
+ case FSIM_XPpem:
+ return tr("X ppem");
+ case FSIM_YPpem:
+ return tr("Y ppem");
+ default:
+ break;
+ }
+
+ return {};
+}
+
+
+int
+CharMapInfoModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return static_cast<int>(storage_.size());
+}
+
+
+int
+CharMapInfoModel::columnCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return CMIM_Max;
+}
+
+
+QVariant
+CharMapInfoModel::data(const QModelIndex& index,
+ int role) const
+{
+ // TODO reduce duplication
+ if (index.row() < 0 || index.column() < 0)
+ return {};
+ auto r = static_cast<size_t>(index.row());
+ if ((role != Qt::DisplayRole && role != Qt::ToolTipRole)
+ || r > storage_.size())
+ return {};
+
+ auto& obj = storage_[r];
+ switch (static_cast<Columns>(index.column()))
+ {
+ case CMIM_Platform:
+ return QString("%1 {%2}")
+ .arg(obj.platformID)
+ .arg(*mapTTPlatformIDToName(obj.platformID));
+ case CMIM_Encoding:
+ return QString("%1 {%2}")
+ .arg(obj.encodingID)
+ .arg(*obj.encodingName);
+ case CMIM_FormatID:
+ return static_cast<long long>(obj.formatID);
+ case CMIM_Language:
+ return static_cast<unsigned long long>(obj.languageID);
+ case CMIM_MaxIndex:
+ return obj.maxIndex;
+ default:
+ break;
+ }
+
+ return {};
+}
+
+
+QVariant
+CharMapInfoModel::headerData(int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole)
+ return {};
+ if (orientation == Qt::Vertical)
+ return section;
+ if (orientation != Qt::Horizontal)
+ return {};
+
+ switch (static_cast<Columns>(section))
+ {
+ case CMIM_Platform:
+ return "Platform";
+ case CMIM_Encoding:
+ return "Encoding";
+ case CMIM_FormatID:
+ return "Format ID";
+ case CMIM_Language:
+ return "Language";
+ case CMIM_MaxIndex:
+ return "Max Code Point";
+ default:
+ break;
+ }
+
+ return {};
+}
+
+
+int
+SFNTNameModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return static_cast<int>(storage_.size());
+}
+
+
+int
+SFNTNameModel::columnCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return SNM_Max;
+}
+
+
+QVariant
+SFNTNameModel::data(const QModelIndex& index,
+ int role) const
+{
+ if (index.row() < 0 || index.column() < 0)
+ return {};
+
+ if (role == Qt::ToolTipRole && index.column() == SNM_Content)
+ return tr("Double click to view the string.");
+
+ auto r = static_cast<size_t>(index.row());
+ if ((role != Qt::DisplayRole && role != Qt::ToolTipRole)
+ || r > storage_.size())
+ return {};
+
+ auto& obj = storage_[r];
+ switch (static_cast<Columns>(index.column()))
+ {
+ case SNM_Name:
+ if (obj.nameID >= 256)
+ return QString::number(obj.nameID);
+ return QString("%1 {%2}").arg(QString::number(obj.nameID),
+ *mapSFNTNameIDToName(obj.nameID));
+
+ case SNM_Platform:
+ return QString("%1 {%2}")
+ .arg(obj.platformID)
+ .arg(*mapTTPlatformIDToName(obj.platformID));
+ case SNM_Encoding:
+ return QString("%1 {%2}")
+ .arg(obj.encodingID)
+ .arg(*mapTTEncodingIDToName(obj.platformID, obj.encodingID));
+ case SNM_Language:
+ if (obj.languageID >= 0x8000)
+ return obj.langTag + "(lang tag)";
+ if (obj.platformID == 3)
+ return QString("0x%1 {%2}")
+ .arg(obj.languageID, 4, 16, QChar('0'))
+ .arg(*mapTTLanguageIDToName(obj.platformID, obj.languageID));
+ return QString("%1 {%2}")
+ .arg(obj.languageID)
+ .arg(*mapTTLanguageIDToName(obj.platformID, obj.languageID));
+ case SNM_Content:
+ return obj.str;
+ default:
+ break;
+ }
+
+ return {};
+}
+
+
+QVariant
+SFNTNameModel::headerData(int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole)
+ return {};
+ if (orientation == Qt::Vertical)
+ return section;
+ if (orientation != Qt::Horizontal)
+ return {};
+
+ switch (static_cast<Columns>(section))
+ {
+ case SNM_Name:
+ return "Name";
+ case SNM_Platform:
+ return "Platform";
+ case SNM_Encoding:
+ return "Encoding";
+ case SNM_Language:
+ return "Language";
+ case SNM_Content:
+ return "Content";
+ default:
+ break;
+ }
+
+ return {};
+}
+
+
+QString
+tagToString(unsigned long tag)
+{
+ QString str(4, '0');
+ str[0] = static_cast<char>(tag >> 24);
+ str[1] = static_cast<char>(tag >> 16);
+ str[2] = static_cast<char>(tag >> 8);
+ str[3] = static_cast<char>(tag);
+ return str;
+}
+
+
+int
+SFNTTableInfoModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return static_cast<int>(storage_.size());
+}
+
+
+int
+SFNTTableInfoModel::columnCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return STIM_Max;
+}
+
+
+QVariant
+SFNTTableInfoModel::data(const QModelIndex& index,
+ int role) const
+{
+ if (index.row() < 0 || index.column() < 0)
+ return {};
+ auto r = static_cast<size_t>(index.row());
+ if ((role != Qt::DisplayRole && role != Qt::ToolTipRole)
+ || r > storage_.size())
+ return {};
+
+ auto& obj = storage_[r];
+ switch (static_cast<Columns>(index.column()))
+ {
+ case STIM_Tag:
+ return tagToString(obj.tag);
+ case STIM_Offset:
+ return static_cast<unsigned long long>(obj.offset);
+ case STIM_Length:
+ return static_cast<unsigned long long>(obj.length);
+ case STIM_Valid:
+ return obj.valid;
+ case STIM_SharedFaces:
+ if (obj.sharedFaces.empty())
+ return "[]";
+ {
+ auto result = QString('[') + QString::number(*obj.sharedFaces.begin());
+ for (auto it = std::next(obj.sharedFaces.begin());
+ it != obj.sharedFaces.end();
+ ++it)
+ {
+ auto xStr = QString::number(*it);
+ result.reserve(result.length() + xStr.length() + 2);
+ result += ", ";
+ result += xStr;
+ }
+ result += ']';
+ return result;
+ }
+ default:
+ break;
+ }
+
+ return {};
+}
+
+
+QVariant
+SFNTTableInfoModel::headerData(int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole)
+ return {};
+ if (orientation == Qt::Vertical)
+ return section;
+ if (orientation != Qt::Horizontal)
+ return {};
+
+ switch (static_cast<Columns>(section))
+ {
+ case STIM_Tag:
+ return "Tag";
+ case STIM_Offset:
+ return "Offset";
+ case STIM_Length:
+ return "Length";
+ case STIM_Valid:
+ return "Valid";
+ case STIM_SharedFaces:
+ return "Subfont Indices";
+ default:;
+ }
+
+ return {};
+}
+
+
+int
+MMGXAxisInfoModel::rowCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return static_cast<int>(storage_.size());
+}
+
+
+int
+MMGXAxisInfoModel::columnCount(const QModelIndex& parent) const
+{
+ if (parent.isValid())
+ return 0;
+ return MAIM_Max;
+}
+
+
+QVariant
+MMGXAxisInfoModel::data(const QModelIndex& index,
+ int role) const
+{
+ if (index.row() < 0 || index.column() < 0)
+ return {};
+ auto r = static_cast<size_t>(index.row());
+ if ((role != Qt::DisplayRole && role != Qt::ToolTipRole)
+ || r > storage_.size())
+ return {};
+
+ auto& obj = storage_[r];
+ switch (static_cast<Columns>(index.column()))
+ {
+ case MAIM_Tag:
+ return tagToString(obj.tag);
+ case MAIM_Minimum:
+ return obj.minimum;
+ case MAIM_Default:
+ return obj.def;
+ case MAIM_Maximum:
+ return obj.maximum;
+ case MAIM_Hidden:
+ return obj.hidden;
+ case MAIM_Name:
+ return obj.name;
+ default:
+ break;
+ }
+
+ return {};
+}
+
+
+QVariant
+MMGXAxisInfoModel::headerData(int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole)
+ return {};
+ if (orientation == Qt::Vertical)
+ return section;
+ if (orientation != Qt::Horizontal)
+ return {};
+
+ switch (static_cast<Columns>(section))
+ {
+ case MAIM_Tag:
+ return "Tag";
+ case MAIM_Minimum:
+ return "Minimum";
+ case MAIM_Default:
+ return "Default";
+ case MAIM_Maximum:
+ return "Maximum";
+ case MAIM_Hidden:
+ return "Hidden";
+ case MAIM_Name:
+ return "Name";
+ default: ;
+ }
+
+ return {};
+}
+
+
+int
+CompositeGlyphsInfoModel::rowCount(const QModelIndex& parent) const
+{
+ if (!parent.isValid())
+ return static_cast<int>(glyphs_.size());
+ auto id = parent.internalId();
+ if (id < 0 || id >= nodes_.size())
+ return 0;
+ auto gid = nodes_[id].glyphIndex;
+ auto iter = glyphMapper_.find(gid);
+ if (iter == glyphMapper_.end())
+ return 0;
+ if (iter->second > glyphs_.size())
+ return 0;
+ return static_cast<int>(glyphs_[iter->second]. subglyphs.size());
+}
+
+
+int
+CompositeGlyphsInfoModel::columnCount(const QModelIndex& parent) const
+{
+ return CGIM_Max;
+}
+
+
+QModelIndex
+CompositeGlyphsInfoModel::index(int row,
+ int column,
+ const QModelIndex& parent) const
+{
+ long long parentIdx = -1; // node index.
+ if (parent.isValid()) // Not top-level
+ parentIdx = static_cast<long long>(parent.internalId());
+ if (parentIdx < 0)
+ parentIdx = -1;
+ // find existing node by row and parent index, -1 for top-level
+ auto lookupPair = std::pair<int, long long>(row, parentIdx);
+
+ auto iter = nodeLookup_.find(lookupPair);
+ if (iter != nodeLookup_.end())
+ {
+ if (iter->second < 0 || static_cast<size_t>(iter->second) >= nodes_.size())
+ return {};
+ return createIndex(row, column, iter->second);
+ }
+
+ int glyphIndex = -1;
+ CompositeGlyphInfo::SubGlyph const* sgInfo = nullptr;
+ if (!parent.isValid()) // top-level nodes
+ glyphIndex = glyphs_[row].index;
+ else if (parent.internalId() < nodes_.size())
+ {
+ auto& parentInfoIndex = nodes_[parent.internalId()].glyphInfoIndex;
+ if (parentInfoIndex < 0
+ || static_cast<size_t>(parentInfoIndex) > glyphs_.size())
+ return {};
+
+ auto& sg = glyphs_[parentInfoIndex].subglyphs;
+ glyphIndex = sg[row].index;
+ sgInfo = &sg[row];
+ }
+
+ if (glyphIndex < 0)
+ return {};
+
+ ptrdiff_t glyphInfoIndex = -1;
+ auto iterGlyphInfoIter = glyphMapper_.find(glyphIndex);
+ if (iterGlyphInfoIter != glyphMapper_.end())
+ glyphInfoIndex = static_cast<ptrdiff_t>(iterGlyphInfoIter->second);
+
+ InfoNode node = {
+ parentIdx,
+ row, glyphIndex, glyphInfoIndex,
+ sgInfo
+ };
+ nodes_.push_back(node);
+ nodeLookup_.emplace(std::pair<int, long long>(row, parentIdx),
+ nodes_.size() - 1);
+
+ return createIndex(row, column, nodes_.size() - 1);
+}
+
+
+QModelIndex
+CompositeGlyphsInfoModel::parent(const QModelIndex& child) const
+{
+ if (!child.isValid())
+ return {};
+
+ auto id = static_cast<long long>(child.internalId());
+ if (id < 0 || static_cast<size_t>(id) >= nodes_.size())
+ return {};
+
+ auto pid = nodes_[id].parentNodeIndex;
+ if (pid < 0 || static_cast<size_t>(pid) >= nodes_.size())
+ return {};
+
+ auto& p = nodes_[pid];
+ return createIndex(p.indexInParent, 0, pid);
+}
+
+
+QVariant
+CompositeGlyphsInfoModel::data(const QModelIndex& index,
+ int role) const
+{
+ if (!index.isValid())
+ return {};
+
+ auto id = index.internalId();
+ if (id >= nodes_.size())
+ return {};
+ auto& n = nodes_[id];
+ auto glyphIdx = n.glyphIndex;
+
+ if (role == Qt::ToolTipRole && index.column() == CGIM_Position)
+ {
+ if (!n.subGlyphInfo)
+ return {};
+ auto pos = n.subGlyphInfo->position;
+ switch (n.subGlyphInfo->positionType)
+ {
+ case CompositeGlyphInfo::SubGlyph::PT_Offset:
+ return QString("Add a offset (%1, %2) to the subglyph's points")
+ .arg(pos.first)
+ .arg(pos.second);
+ case CompositeGlyphInfo::SubGlyph::PT_Align:
+ return QString("Align parent's point %1 to subglyph's point %2")
+ .arg(pos.first)
+ .arg(pos.second);
+ }
+ return {};
+ }
+
+ if (role == Qt::DecorationRole && index.column() == CGIM_Glyph)
+ {
+ auto glyphIndex = n.glyphIndex;
+ auto iter = glyphIcons_.find(glyphIndex);
+ if (iter == glyphIcons_.end())
+ iter = glyphIcons_.emplace(glyphIndex, renderIcon(glyphIndex)).first;
+
+ auto& pixmap = iter->second;
+ if (pixmap.isNull())
+ return {};
+ return pixmap;
+ }
+
+ if (role != Qt::DisplayRole)
+ return {};
+
+ switch (static_cast<Columns>(index.column()))
+ {
+ case CGIM_Glyph:
+ if (engine_->currentFontHasGlyphName())
+ return QString("%1
{%2}").arg(glyphIdx).arg(engine_->glyphName(glyphIdx));
+ return QString::number(glyphIdx);
+ case CGIM_Flag:
+ if (!n.subGlyphInfo)
+ return {};
+ return QString::number(n.subGlyphInfo->flag, 16).rightJustified(4, '0');
+ case CGIM_Position:
+ {
+ if (!n.subGlyphInfo)
+ return {};
+ auto pos = n.subGlyphInfo->position;
+ switch (n.subGlyphInfo->positionType)
+ {
+ case CompositeGlyphInfo::SubGlyph::PT_Offset:
+ return QString("Offset (%1, %2)").arg(pos.first).arg(pos.second);
+ case CompositeGlyphInfo::SubGlyph::PT_Align:
+ return QString("Align %1 -> %2").arg(pos.first).arg(pos.second);
+ }
+ }
+ default:;
+ }
+
+ return {};
+}
+
+
+QVariant
+CompositeGlyphsInfoModel::headerData(int section,
+ Qt::Orientation orientation,
+ int role) const
+{
+ if (role != Qt::DisplayRole)
+ return {};
+ if (orientation != Qt::Horizontal)
+ return {};
+
+ switch (static_cast<Columns>(section))
+ {
+ case CGIM_Glyph:
+ return tr("Glyph");
+ case CGIM_Flag:
+ return tr("Flags");
+ case CGIM_Position:
+ return tr("Position");
+ default:;
+ }
+ return {};
+}
+
+
+int
+CompositeGlyphsInfoModel::glyphIndexFromIndex(const QModelIndex& idx)
+{
+ if (!idx.isValid())
+ return -1;
+
+ auto id = idx.internalId();
+ if (id >= nodes_.size())
+ return -1;
+ auto& n = nodes_[id];
+ return n.glyphIndex;
+}
+
+
+void
+CompositeGlyphsInfoModel::beginModelUpdate()
+{
+ beginResetModel();
+ glyphs_.clear();
+ nodeLookup_.clear();
+ nodes_.clear();
+}
+
+
+void
+CompositeGlyphsInfoModel::endModelUpdate()
+{
+ glyphMapper_.clear();
+ for (size_t i = 0; i < glyphs_.size(); i++)
+ glyphMapper_.emplace(glyphs_[i].index, i);
+
+ glyphIcons_.clear();
+ endResetModel();
+}
+
+
+QPixmap
+CompositeGlyphsInfoModel::renderIcon(int glyphIndex) const
+{
+ engine_->setSizeByPixel(20); // This size is arbitrary
+ if (!engine_->currentPalette())
+ engine_->loadPalette();
+ auto image = engine_->renderingEngine()
+ ->tryDirectRenderColorLayers(glyphIndex, NULL, false);
+ if (!image)
+ {
+ auto glyph = engine_->loadGlyph(glyphIndex);
+ if (!glyph)
+ return {};
+ image = engine_->renderingEngine()
+ ->convertGlyphToQImage(glyph, NULL, false);
+ }
+
+ if (!image)
+ return {};
+
+ auto result = engine_->renderingEngine()->padToSize(image, 20);
+ delete image;
+ return result;
+}
+
+
+// end of fontinfomodels.cpp
diff --git a/src/ftinspect/models/fontinfomodels.hpp
b/src/ftinspect/models/fontinfomodels.hpp
new file mode 100644
index 0000000..22d4aab
--- /dev/null
+++ b/src/ftinspect/models/fontinfomodels.hpp
@@ -0,0 +1,294 @@
+// fontinfomodels.hpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#pragma once
+
+#include "../engine/fontinfo.hpp"
+#include "../engine/charmap.hpp"
+#include "../engine/mmgx.hpp"
+
+#include <vector>
+#include <unordered_map>
+#include <QAbstractTableModel>
+#include <QPixmap>
+
+class FixedSizeInfoModel
+: public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ explicit FixedSizeInfoModel(QObject* parent) : QAbstractTableModel(parent) {}
+ ~FixedSizeInfoModel() override = default;
+
+ int rowCount(const QModelIndex& parent) const override;
+ int columnCount(const QModelIndex& parent) const override;
+ QVariant data(const QModelIndex& index,
+ int role) const override;
+ QVariant headerData(int section,
+ Qt::Orientation orientation,
+ int role) const override;
+
+ // Since we need to call `beginResetModel` right before updating, and need to
+ // call `endResetModel` after the internal storage is changed
+ // The model should be updated on-demand, and the internal storage is updated
+ // from `FontFixedSize::get`, we provide a callback for `get` to ensure the
+ // `beginResetModel` is called before the storage is changed,
+ // and the caller is responsible to call `endResetModel` according to `get`'s
+ // return value.
+ void beginModelUpdate() { beginResetModel(); }
+ void endModelUpdate() { endResetModel(); }
+ std::vector<FontFixedSize>& storage() { return storage_; }
+
+ enum Columns : int
+ {
+ FSIM_Height = 0,
+ FSIM_Width,
+ FSIM_Size,
+ FSIM_XPpem,
+ FSIM_YPpem,
+ FSIM_Max
+ };
+
+private:
+ // Don't let the item count exceed INT_MAX!
+ std::vector<FontFixedSize> storage_;
+};
+
+
+class CharMapInfoModel
+: public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ explicit CharMapInfoModel(QObject* parent) : QAbstractTableModel(parent) {}
+ ~CharMapInfoModel() override = default;
+
+ int rowCount(const QModelIndex& parent) const override;
+ int columnCount(const QModelIndex& parent) const override;
+ QVariant data(const QModelIndex& index,
+ int role) const override;
+ QVariant headerData(int section,
+ Qt::Orientation orientation,
+ int role) const override;
+
+ // Same to `FixedSizeInfoModel`
+ void beginModelUpdate() { beginResetModel(); }
+ void endModelUpdate() { endResetModel(); }
+ std::vector<CharMapInfo>& storage() { return storage_; }
+
+ enum Columns : int
+ {
+ CMIM_Platform = 0,
+ CMIM_Encoding,
+ CMIM_FormatID,
+ CMIM_Language,
+ CMIM_MaxIndex,
+ CMIM_Max
+ };
+
+private:
+ // Don't let the item count exceed INT_MAX!
+ std::vector<CharMapInfo> storage_;
+};
+
+
+class SFNTNameModel
+: public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ explicit SFNTNameModel(QObject* parent) : QAbstractTableModel(parent) {}
+ ~SFNTNameModel() override = default;
+
+ int rowCount(const QModelIndex& parent) const override;
+ int columnCount(const QModelIndex& parent) const override;
+ QVariant data(const QModelIndex& index,
+ int role) const override;
+ QVariant headerData(int section,
+ Qt::Orientation orientation,
+ int role) const override;
+
+ // Same to `FixedSizeInfoModel`
+ void beginModelUpdate() { beginResetModel(); }
+ void endModelUpdate() { endResetModel(); }
+ std::vector<SFNTName>& storage() { return storage_; }
+
+ enum Columns : int
+ {
+ SNM_Name = 0,
+ SNM_Platform,
+ SNM_Encoding,
+ SNM_Language,
+ SNM_Content,
+ SNM_Max
+ };
+
+private:
+ // Don't let the item count exceed INT_MAX!
+ std::vector<SFNTName> storage_;
+};
+
+
+class SFNTTableInfoModel
+: public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ explicit SFNTTableInfoModel(QObject* parent) : QAbstractTableModel(parent) {}
+ ~SFNTTableInfoModel() override = default;
+
+ int rowCount(const QModelIndex& parent) const override;
+ int columnCount(const QModelIndex& parent) const override;
+ QVariant data(const QModelIndex& index,
+ int role) const override;
+ QVariant headerData(int section,
+ Qt::Orientation orientation,
+ int role) const override;
+
+ // Same to `FixedSizeInfoModel`
+ void beginModelUpdate() { beginResetModel(); }
+ void endModelUpdate() { endResetModel(); }
+ std::vector<SFNTTableInfo>& storage() { return storage_; }
+
+ enum Columns : int
+ {
+ STIM_Tag = 0,
+ STIM_Offset,
+ STIM_Length,
+ STIM_Valid,
+ STIM_SharedFaces,
+ STIM_Max
+ };
+
+private:
+ // Don't let the item count exceed INT_MAX!
+ std::vector<SFNTTableInfo> storage_;
+};
+
+
+class MMGXAxisInfoModel
+: public QAbstractTableModel
+{
+ Q_OBJECT
+public:
+ explicit MMGXAxisInfoModel(QObject* parent) : QAbstractTableModel(parent) {}
+ ~MMGXAxisInfoModel() override = default;
+
+ int rowCount(const QModelIndex& parent) const override;
+ int columnCount(const QModelIndex& parent) const override;
+ QVariant data(const QModelIndex& index,
+ int role) const override;
+ QVariant headerData(int section,
+ Qt::Orientation orientation,
+ int role) const override;
+
+ // Same to `FixedSizeInfoModel`
+ void beginModelUpdate() { beginResetModel(); }
+ void endModelUpdate() { endResetModel(); }
+ std::vector<MMGXAxisInfo>& storage() { return storage_; }
+
+ enum Columns : int
+ {
+ MAIM_Tag = 0,
+ MAIM_Minimum,
+ MAIM_Default,
+ MAIM_Maximum,
+ MAIM_Hidden,
+ MAIM_Name,
+ MAIM_Max
+ };
+
+private:
+ // Don't let the item count exceed INT_MAX!
+ std::vector<MMGXAxisInfo> storage_;
+};
+
+
+struct LookupPairHash
+{
+public:
+ std::size_t
+ operator()(const std::pair<int, long long>& p) const
+ {
+ std::size_t seed = 0x291FEEA8;
+ seed ^= (seed << 6) + (seed >> 2) + 0x25F3E86D
+ + static_cast<std::size_t>(p.first);
+ seed ^= (seed << 6) + (seed >> 2) + 0x436E6B92
+ + static_cast<std::size_t>(p.second);
+ return seed;
+ }
+};
+
+
+// A tree model, so much more complicated.
+class CompositeGlyphsInfoModel : public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+ // A lazily created info node.
+ struct InfoNode
+ {
+ long long parentNodeIndex;
+ int indexInParent;
+ int glyphIndex;
+ ptrdiff_t glyphInfoIndex;
+ CompositeGlyphInfo::SubGlyph const* subGlyphInfo;
+ };
+
+ explicit CompositeGlyphsInfoModel(QObject* parent, Engine* engine)
+ : QAbstractItemModel(parent), engine_(engine)
+ {
+ }
+
+ ~CompositeGlyphsInfoModel() override = default;
+
+ int rowCount(const QModelIndex& parent) const override;
+ int columnCount(const QModelIndex& parent) const override;
+ QModelIndex index(int row, int column,
+ const QModelIndex& parent) const override;
+ QModelIndex parent(const QModelIndex& child) const override;
+ QVariant data(const QModelIndex& index, int role) const override;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role) const override;
+ int glyphIndexFromIndex(const QModelIndex& idx);
+
+ void beginModelUpdate();
+ void endModelUpdate();
+ std::vector<CompositeGlyphInfo>& storage() { return glyphs_; }
+
+ enum Columns : int
+ {
+ CGIM_Glyph = 0, // TODO: transformation, scale? consider more flags?
+ CGIM_Flag = 1,
+ CGIM_Position = 2,
+ CGIM_Max
+ };
+
+private:
+ Engine* engine_;
+ /*
+ * Take care of 3 types of index:
+ * 1. Glyph Index in Font File
+ * 2. Glyph Index in `glyphs_` - often called as "glyph info index"
+ * 3. Node Index
+ */
+ std::vector<CompositeGlyphInfo> glyphs_;
+
+ // glyph index -> glyph info index
+ std::unordered_map<int, size_t> glyphMapper_;
+ // map <row, parentId> to node
+ // the internal id of `QModelIndex` is the node's index
+ mutable std::unordered_map<std::pair<int, long long>,
+ long long, LookupPairHash>
+ nodeLookup_;
+ mutable std::vector<InfoNode> nodes_;
+
+ mutable std::unordered_map<int, QPixmap> glyphIcons_;
+
+ // has to be const
+ QPixmap renderIcon(int glyphIndex) const;
+};
+
+
+// end of fontinfomodels.hpp
diff --git a/src/ftinspect/panels/info.cpp b/src/ftinspect/panels/info.cpp
new file mode 100644
index 0000000..6c64465
--- /dev/null
+++ b/src/ftinspect/panels/info.cpp
@@ -0,0 +1,1039 @@
+// info.cpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#include "info.hpp"
+
+#include "../uihelper.hpp"
+#include "../engine/engine.hpp"
+
+#include <cstring>
+#include <QStringList>
+#include <QHeaderView>
+
+
+#define GL2CRow(l, w) gridLayout2ColAddWidget(l, \
+ w##PromptLabel_, \
+ w##Label_)
+
+
+InfoTab::InfoTab(QWidget* parent,
+ Engine* engine)
+: QWidget(parent), engine_(engine)
+{
+ createLayout();
+ createConnections();
+}
+
+
+void
+InfoTab::reloadFont()
+{
+ for (auto tab : tabs_)
+ tab->reloadFont();
+}
+
+
+void
+InfoTab::createLayout()
+{
+ generalTab_ = new GeneralInfoTab(this, engine_);
+ sfntTab_ = new SFNTInfoTab(this, engine_);
+ postScriptTab_ = new PostScriptInfoTab(this, engine_);
+ mmgxTab_ = new MMGXInfoTab(this, engine_);
+ compositeGlyphsTab_ = new CompositeGlyphsTab(this, engine_);
+
+ tab_ = new QTabWidget(this);
+ tab_->addTab(generalTab_, tr("General"));
+ tab_->addTab(sfntTab_, tr("SFNT"));
+ tab_->addTab(postScriptTab_, tr("PostScript"));
+ tab_->addTab(mmgxTab_, tr("MM/GX"));
+ tab_->addTab(compositeGlyphsTab_, tr("Composite Glyphs"));
+
+ tabs_.append(generalTab_);
+ tabs_.append(sfntTab_);
+ tabs_.append(postScriptTab_);
+ tabs_.append(mmgxTab_);
+ tabs_.append(compositeGlyphsTab_);
+
+ layout_ = new QHBoxLayout;
+ layout_->addWidget(tab_);
+
+ setLayout(layout_);
+}
+
+
+void
+InfoTab::createConnections()
+{
+ connect(compositeGlyphsTab_, &CompositeGlyphsTab::switchToSingular,
+ this, &InfoTab::switchToSingular);
+}
+
+
+GeneralInfoTab::GeneralInfoTab(QWidget* parent,
+ Engine* engine)
+: QWidget(parent), engine_(engine)
+{
+ createLayout();
+}
+
+
+void
+GeneralInfoTab::reloadFont()
+{
+ auto basicInfo = FontBasicInfo::get(engine_);
+ // don't update when unnecessary
+ if (basicInfo != oldFontBasicInfo_)
+ {
+ oldFontBasicInfo_ = basicInfo;
+ if (basicInfo.numFaces < 0)
+ numFacesLabel_->clear();
+ else
+ numFacesLabel_->setText(QString::number(basicInfo.numFaces));
+
+ familyLabel_->setText(basicInfo.familyName);
+ styleLabel_->setText(basicInfo.styleName);
+ postscriptLabel_->setText(basicInfo.postscriptName);
+ revisionLabel_->setText(basicInfo.revision);
+ copyrightLabel_->setText(basicInfo.copyright);
+ trademarkLabel_->setText(basicInfo.trademark);
+ manufacturerLabel_->setText(basicInfo.manufacturer);
+
+ createdLabel_->setText(
+ basicInfo.createdTime.toString("yyyy-MM-dd hh:mm:ss t"));
+ modifiedLabel_->setText(
+ basicInfo.modifiedTime.toString("yyyy-MM-dd hh:mm:ss t"));
+ }
+
+ auto fontTypeEntries = FontTypeEntries::get(engine_);
+
+ if (fontTypeEntries != oldFontTypeEntries_)
+ {
+ oldFontTypeEntries_ = fontTypeEntries;
+ QString directionText;
+ // Don't want to do concat...
+ if (fontTypeEntries.hasHorizontal && fontTypeEntries.hasVertical)
+ directionText = "honizontal, vertical";
+ else if (fontTypeEntries.hasHorizontal)
+ directionText = "horizontal";
+ else if (fontTypeEntries.hasVertical)
+ directionText = "vertical";
+
+ QStringList types;
+ if (fontTypeEntries.scalable)
+ types += "scalable";
+ if (fontTypeEntries.mmgx)
+ types += "multiple master";
+ if (fontTypeEntries.fixedSizes)
+ types += "fixed sizes";
+
+ driverNameLabel_->setText(fontTypeEntries.driverName);
+ sfntLabel_->setText(fontTypeEntries.sfnt ? "yes" : "no");
+ fontTypeLabel_->setText(types.join(", "));
+ directionLabel_->setText(directionText);
+ fixedWidthLabel_->setText(fontTypeEntries.fixedWidth ? "yes" : "no");
+ glyphNamesLabel_->setText(fontTypeEntries.glyphNames ? "available"
+ : "unavailable");
+
+ if (fontTypeEntries.scalable)
+ {
+ emSizeLabel_->setText(QString::number(fontTypeEntries.emSize));
+ bboxLabel_->setText(QString("(%1, %2) : (%3, %4)")
+ .arg(fontTypeEntries.globalBBox.xMin)
+ .arg(fontTypeEntries.globalBBox.yMin)
+ .arg(fontTypeEntries.globalBBox.xMax)
+ .arg(fontTypeEntries.globalBBox.yMax));
+ ascenderLabel_->setText(QString::number(fontTypeEntries.ascender));
+ descenderLabel_->setText(QString::number(fontTypeEntries.descender));
+ maxAdvanceWidthLabel_
+ ->setText(QString::number(fontTypeEntries.maxAdvanceWidth));
+ maxAdvanceHeightLabel_
+ ->setText(QString::number(fontTypeEntries.maxAdvanceHeight));
+ ulPosLabel_
+ ->setText(QString::number(fontTypeEntries.underlinePos));
+ ulThicknessLabel_
+ ->setText(QString::number(fontTypeEntries.underlineThickness));
+
+ for (auto label : scalableOnlyLabels_)
+ label->setEnabled(true);
+ }
+ else
+ {
+ for (auto label : scalableOnlyLabels_)
+ label->setEnabled(false);
+ }
+ }
+
+ fixedSizesTable_->setEnabled(fontTypeEntries.fixedSizes);
+ bool reset
+ = FontFixedSize::get(engine_,
+ fixedSizeInfoModel_->storage(),
+ [&] { fixedSizeInfoModel_->beginModelUpdate(); });
+ if (reset)
+ fixedSizeInfoModel_->endModelUpdate();
+
+ if (engine_->currentFontCharMaps() != charMapInfoModel_->storage())
+ {
+ charMapInfoModel_->beginModelUpdate();
+ charMapInfoModel_->storage() = engine_->currentFontCharMaps();
+ charMapInfoModel_->endModelUpdate();
+ }
+}
+
+
+void
+GeneralInfoTab::createLayout()
+{
+ numFacesPromptLabel_ = new QLabel(tr("Num of Faces:"), this);
+ familyPromptLabel_ = new QLabel(tr("Family Name:"), this);
+ stylePromptLabel_ = new QLabel(tr("Style Name:"), this);
+ postscriptPromptLabel_ = new QLabel(tr("PostScript Name:"), this);
+ createdPromptLabel_ = new QLabel(tr("Created at:"), this);
+ modifiedPromptLabel_ = new QLabel(tr("Modified at:"), this);
+ revisionPromptLabel_ = new QLabel(tr("Font Revision:"), this);
+ copyrightPromptLabel_ = new QLabel(tr("Copyright:"), this);
+ trademarkPromptLabel_ = new QLabel(tr("Trademark:"), this);
+ manufacturerPromptLabel_ = new QLabel(tr("Manufacturer:"), this);
+
+ numFacesLabel_ = new QLabel(this);
+ familyLabel_ = new QLabel(this);
+ styleLabel_ = new QLabel(this);
+ postscriptLabel_ = new QLabel(this);
+ createdLabel_ = new QLabel(this);
+ modifiedLabel_ = new QLabel(this);
+ revisionLabel_ = new QLabel(this);
+ copyrightLabel_ = new QLabel(this);
+ trademarkLabel_ = new QLabel(this);
+ manufacturerLabel_ = new QLabel(this);
+
+ setLabelSelectable( numFacesLabel_);
+ setLabelSelectable( familyLabel_);
+ setLabelSelectable( styleLabel_);
+ setLabelSelectable( postscriptLabel_);
+ setLabelSelectable( createdLabel_);
+ setLabelSelectable( modifiedLabel_);
+ setLabelSelectable( revisionLabel_);
+ setLabelSelectable( copyrightLabel_);
+ setLabelSelectable( trademarkLabel_);
+ setLabelSelectable(manufacturerLabel_);
+
+ copyrightLabel_->setWordWrap(true);
+ trademarkLabel_->setWordWrap(true);
+ manufacturerLabel_->setWordWrap(true);
+
+ driverNamePromptLabel_ = new QLabel(tr("Driver:"), this);
+ sfntPromptLabel_ = new QLabel(tr("SFNT Wrapped:"), this);
+ fontTypePromptLabel_ = new QLabel(tr("Type:"), this);
+ directionPromptLabel_ = new QLabel(tr("Direction:"), this);
+ fixedWidthPromptLabel_ = new QLabel(tr("Fixed Width:"), this);
+ glyphNamesPromptLabel_ = new QLabel(tr("Glyph Names:"), this);
+
+ driverNameLabel_ = new QLabel(this);
+ sfntLabel_ = new QLabel(this);
+ fontTypeLabel_ = new QLabel(this);
+ directionLabel_ = new QLabel(this);
+ fixedWidthLabel_ = new QLabel(this);
+ glyphNamesLabel_ = new QLabel(this);
+
+ setLabelSelectable(driverNameLabel_);
+ setLabelSelectable( sfntLabel_);
+ setLabelSelectable( fontTypeLabel_);
+ setLabelSelectable( directionLabel_);
+ setLabelSelectable(fixedWidthLabel_);
+ setLabelSelectable(glyphNamesLabel_);
+
+ emSizePromptLabel_ = new QLabel(tr("EM Size:"), this);
+ bboxPromptLabel_ = new QLabel(tr("Global BBox:"), this);
+ ascenderPromptLabel_ = new QLabel(tr("Ascender:"), this);
+ descenderPromptLabel_ = new QLabel(tr("Descender:"), this);
+ maxAdvanceWidthPromptLabel_ = new QLabel(tr("Max Advance Width:"), this);
+ maxAdvanceHeightPromptLabel_ = new QLabel(tr("Max Advance Height:"), this);
+ ulPosPromptLabel_ = new QLabel(tr("Underline Position:"), this);
+ ulThicknessPromptLabel_ = new QLabel(tr("Underline Thickness:"), this);
+
+ emSizeLabel_ = new QLabel(this);
+ bboxLabel_ = new QLabel(this);
+ ascenderLabel_ = new QLabel(this);
+ descenderLabel_ = new QLabel(this);
+ maxAdvanceWidthLabel_ = new QLabel(this);
+ maxAdvanceHeightLabel_ = new QLabel(this);
+ ulPosLabel_ = new QLabel(this);
+ ulThicknessLabel_ = new QLabel(this);
+
+ setLabelSelectable( emSizeLabel_);
+ setLabelSelectable( bboxLabel_);
+ setLabelSelectable( ascenderLabel_);
+ setLabelSelectable( descenderLabel_);
+ setLabelSelectable( maxAdvanceWidthLabel_);
+ setLabelSelectable(maxAdvanceHeightLabel_);
+ setLabelSelectable( ulPosLabel_);
+ setLabelSelectable( ulThicknessLabel_);
+
+ scalableOnlyLabels_.push_back( emSizePromptLabel_);
+ scalableOnlyLabels_.push_back( bboxPromptLabel_);
+ scalableOnlyLabels_.push_back( ascenderPromptLabel_);
+ scalableOnlyLabels_.push_back( descenderPromptLabel_);
+ scalableOnlyLabels_.push_back( maxAdvanceWidthPromptLabel_);
+ scalableOnlyLabels_.push_back(maxAdvanceHeightPromptLabel_);
+ scalableOnlyLabels_.push_back( ulPosPromptLabel_);
+ scalableOnlyLabels_.push_back( ulThicknessPromptLabel_);
+ scalableOnlyLabels_.push_back( emSizeLabel_);
+ scalableOnlyLabels_.push_back( bboxLabel_);
+ scalableOnlyLabels_.push_back( ascenderLabel_);
+ scalableOnlyLabels_.push_back( descenderLabel_);
+ scalableOnlyLabels_.push_back( maxAdvanceWidthLabel_);
+ scalableOnlyLabels_.push_back(maxAdvanceHeightLabel_);
+ scalableOnlyLabels_.push_back( ulPosLabel_);
+ scalableOnlyLabels_.push_back( ulThicknessLabel_);
+
+ basicGroupBox_ = new QGroupBox(tr("Basic"), this);
+ typeEntriesGroupBox_ = new QGroupBox(tr("Type Entries"), this);
+ charMapGroupBox_ = new QGroupBox(tr("CharMaps"), this);
+ fixedSizesGroupBox_ = new QGroupBox(tr("Fixed Sizes"), this);
+
+ charMapsTable_ = new QTableView(this);
+ fixedSizesTable_ = new QTableView(this);
+
+ charMapInfoModel_ = new CharMapInfoModel(this);
+ charMapsTable_->setModel(charMapInfoModel_);
+ auto header = charMapsTable_->verticalHeader();
+ // This will force the minimal size to be used
+ header->setDefaultSectionSize(0);
+ header->setSectionResizeMode(QHeaderView::Fixed);
+
+ fixedSizeInfoModel_ = new FixedSizeInfoModel(this);
+ fixedSizesTable_->setModel(fixedSizeInfoModel_);
+ header = fixedSizesTable_->verticalHeader();
+ header->setDefaultSectionSize(0);
+ header->setSectionResizeMode(QHeaderView::Fixed);
+
+ leftWidget_ = new QWidget(this);
+ leftScrollArea_ = new UnboundScrollArea(this);
+ leftScrollArea_->setWidgetResizable(true);
+ leftScrollArea_->setWidget(leftWidget_);
+ leftScrollArea_->setStyleSheet("QScrollArea
{background-color:transparent;}");
+ leftWidget_->setStyleSheet("background-color:transparent;");
+
+ basicLayout_ = new QGridLayout;
+ typeEntriesLayout_ = new QGridLayout;
+ charMapLayout_ = new QHBoxLayout;
+ fixedSizesLayout_ = new QHBoxLayout;
+
+#define BasicRow(w) GL2CRow(basicLayout_, w)
+#define FTERow(w) GL2CRow(typeEntriesLayout_, w)
+
+ BasicRow( numFaces);
+ BasicRow( family);
+ BasicRow( style);
+ BasicRow( postscript);
+ BasicRow( created);
+ BasicRow( modified);
+ BasicRow( revision);
+ BasicRow( copyright);
+ BasicRow( trademark);
+ BasicRow(manufacturer);
+
+ FTERow( driverName);
+ FTERow( sfnt);
+ FTERow( fontType);
+ FTERow( direction);
+ FTERow( fixedWidth);
+ FTERow( glyphNames);
+ FTERow( emSize);
+ FTERow( bbox);
+ FTERow( ascender);
+ FTERow( descender);
+ FTERow( maxAdvanceWidth);
+ FTERow(maxAdvanceHeight);
+ FTERow( ulPos);
+ FTERow( ulThickness);
+
+ basicLayout_->setColumnStretch(1, 1);
+ typeEntriesLayout_->setColumnStretch(1, 1);
+
+ charMapLayout_->addWidget(charMapsTable_);
+ fixedSizesLayout_->addWidget(fixedSizesTable_);
+
+ basicGroupBox_ ->setLayout(basicLayout_ );
+ typeEntriesGroupBox_ ->setLayout(typeEntriesLayout_);
+ charMapGroupBox_ ->setLayout(charMapLayout_ );
+ fixedSizesGroupBox_ ->setLayout(fixedSizesLayout_ );
+
+ leftLayout_ = new QVBoxLayout;
+ rightLayout_ = new QVBoxLayout;
+ mainLayout_ = new QHBoxLayout;
+
+ leftLayout_->addWidget(basicGroupBox_);
+ leftLayout_->addWidget(typeEntriesGroupBox_);
+ leftLayout_->addSpacerItem(new QSpacerItem(0, 0,
+ QSizePolicy::Preferred,
+ QSizePolicy::Expanding));
+
+ leftWidget_->setLayout(leftLayout_);
+
+ rightLayout_->addWidget(charMapGroupBox_);
+ rightLayout_->addWidget(fixedSizesGroupBox_);
+
+ mainLayout_->addWidget(leftScrollArea_);
+ mainLayout_->addLayout(rightLayout_);
+ setLayout(mainLayout_);
+}
+
+
+StringViewDialog::StringViewDialog(QWidget* parent)
+: QDialog(parent)
+{
+ createLayout();
+}
+
+
+void
+StringViewDialog::updateString(QByteArray const& rawArray,
+ QString const& str)
+{
+ textEdit_->setText(str);
+ hexTextEdit_->setText(rawArray.toHex());
+}
+
+
+void
+StringViewDialog::createLayout()
+{
+ textEdit_ = new QTextEdit(this);
+ hexTextEdit_ = new QTextEdit(this);
+
+ textEdit_->setLineWrapMode(QTextEdit::WidgetWidth);
+ hexTextEdit_->setLineWrapMode(QTextEdit::WidgetWidth);
+
+ textLabel_ = new QLabel(tr("Text"), this);
+ hexTextLabel_ = new QLabel(tr("Raw Bytes"), this);
+
+ layout_ = new QVBoxLayout;
+
+ layout_->addWidget(textLabel_);
+ layout_->addWidget(textEdit_);
+ layout_->addWidget(hexTextLabel_);
+ layout_->addWidget(hexTextEdit_);
+
+ resize(600, 400);
+
+ setLayout(layout_);
+}
+
+
+SFNTInfoTab::SFNTInfoTab(QWidget* parent,
+ Engine* engine)
+: QWidget(parent), engine_(engine)
+{
+ createLayout();
+ createConnections();
+}
+
+
+void
+SFNTInfoTab::reloadFont()
+{
+ engine_->reloadFont();
+ auto face = engine_->currentFallbackFtFace();
+ setEnabled(face && FT_IS_SFNT(face));
+
+ if (engine_->currentFontSFNTNames() != sfntNamesModel_->storage())
+ {
+ sfntNamesModel_->beginModelUpdate();
+ sfntNamesModel_->storage() = engine_->currentFontSFNTNames();
+ sfntNamesModel_->endModelUpdate();
+ }
+
+ if (engine_->currentFontSFNTTableInfo() != sfntTablesModel_->storage())
+ {
+ sfntTablesModel_->beginModelUpdate();
+ sfntTablesModel_->storage() = engine_->currentFontSFNTTableInfo();
+ sfntTablesModel_->endModelUpdate();
+ }
+}
+
+
+void
+SFNTInfoTab::createLayout()
+{
+ sfntNamesGroupBox_ = new QGroupBox(tr("SFNT Name Table"), this);
+ sfntTablesGroupBox_ = new QGroupBox(tr("SFNT Tables"), this);
+
+ sfntNamesTable_ = new QTableView(this);
+ sfntTablesTable_ = new QTableView(this);
+
+ sfntNamesModel_ = new SFNTNameModel(this);
+ sfntNamesTable_->setModel(sfntNamesModel_);
+ auto header = sfntNamesTable_->verticalHeader();
+ // This will force the minimal size to be used
+ header->setDefaultSectionSize(0);
+ header->setSectionResizeMode(QHeaderView::Fixed);
+ sfntNamesTable_->horizontalHeader()->setStretchLastSection(true);
+
+ sfntTablesModel_ = new SFNTTableInfoModel(this);
+ sfntTablesTable_->setModel(sfntTablesModel_);
+ header = sfntTablesTable_->verticalHeader();
+ // This will force the minimal size to be used
+ header->setDefaultSectionSize(0);
+ header->setSectionResizeMode(QHeaderView::Fixed);
+ sfntTablesTable_->horizontalHeader()->setStretchLastSection(true);
+
+ sfntNamesLayout_ = new QHBoxLayout;
+ sfntTablesLayout_ = new QHBoxLayout;
+
+ sfntNamesLayout_->addWidget(sfntNamesTable_);
+ sfntTablesLayout_->addWidget(sfntTablesTable_);
+
+ sfntNamesGroupBox_->setLayout(sfntNamesLayout_);
+ sfntTablesGroupBox_->setLayout(sfntTablesLayout_);
+
+ mainLayout_ = new QHBoxLayout;
+
+ mainLayout_->addWidget(sfntNamesGroupBox_);
+ mainLayout_->addWidget(sfntTablesGroupBox_);
+
+ setLayout(mainLayout_);
+
+ stringViewDialog_ = new StringViewDialog(this);
+}
+
+
+void
+SFNTInfoTab::createConnections()
+{
+ connect(sfntNamesTable_, &QTableView::doubleClicked,
+ this, &SFNTInfoTab::nameTableDoubleClicked);
+}
+
+
+void
+SFNTInfoTab::nameTableDoubleClicked(QModelIndex const& index)
+{
+ if (index.column() != SFNTNameModel::SNM_Content)
+ return;
+ auto& storage = sfntNamesModel_->storage();
+ if (index.row() < 0 || static_cast<size_t>(index.row()) > storage.size())
+ return;
+
+ auto& obj = storage[index.row()];
+ stringViewDialog_->updateString(obj.strBuf, obj.str);
+ stringViewDialog_->exec();
+}
+
+
+PostScriptInfoTab::PostScriptInfoTab(QWidget* parent,
+ Engine* engine)
+: QWidget(parent), engine_(engine)
+{
+ std::memset(&oldFontPrivate_, 0, sizeof(PS_PrivateRec));
+ createLayout();
+}
+
+
+template<class T>
+QString genArrayString(T* arr, size_t size)
+{
+ // TODO: optimize
+ QString result = "[";
+ for (size_t i = 0; i < size; i++)
+ {
+ result += QString::number(arr[i]);
+ if (i < size - 1)
+ result += ", ";
+ }
+ return result + "]";
+}
+
+
+// We don't have C++20, so...
+template <class T, std::ptrdiff_t N>
+constexpr std::ptrdiff_t
+arraySize(const T (&)[N]) noexcept
+{
+ return N;
+}
+
+
+void
+PostScriptInfoTab::reloadFont()
+{
+ PS_FontInfoRec fontInfo;
+ auto hasInfo = engine_->currentFontPSInfo(fontInfo);
+ infoGroupBox_->setEnabled(hasInfo);
+ if (hasInfo)
+ {
+ versionLabel_->setText(QString::fromUtf8(fontInfo.version));
+ noticeLabel_->setText(QString::fromUtf8(fontInfo.notice));
+ fullNameLabel_->setText(QString::fromUtf8(fontInfo.full_name));
+ familyNameLabel_->setText(QString::fromUtf8(fontInfo.family_name));
+ weightLabel_->setText(QString::fromUtf8(fontInfo.weight));
+ italicAngleLabel_->setText(QString::number(fontInfo.italic_angle));
+ fixedPitchLabel_->setText(fontInfo.is_fixed_pitch ? "yes" : "no");
+ ulPosLabel_->setText(QString::number(fontInfo.underline_position));
+ ulThicknessLabel_->setText(QString::number(fontInfo.underline_thickness));
+ }
+ else
+ {
+ versionLabel_->clear();
+ noticeLabel_->clear();
+ fullNameLabel_->clear();
+ familyNameLabel_->clear();
+ weightLabel_->clear();
+ italicAngleLabel_->clear();
+ fixedPitchLabel_->clear();
+ ulPosLabel_->clear();
+ ulThicknessLabel_->clear();
+ }
+
+ PS_PrivateRec fontPrivate;
+ // Don't do zero-initialization since we need to zero out paddings
+ std::memset(&fontPrivate, 0, sizeof(PS_PrivateRec));
+ hasInfo = engine_->currentFontPSPrivateInfo(fontPrivate);
+ privateGroupBox_->setEnabled(hasInfo);
+ if (hasInfo)
+ {
+ if (std::memcmp(&fontPrivate, &oldFontPrivate_, sizeof(PS_PrivateRec)))
+ {
+ std::memcpy(&oldFontPrivate_, &fontPrivate, sizeof(PS_PrivateRec));
+
+ uniqueIDLabel_->setText(QString::number(fontPrivate.unique_id));
+ blueShiftLabel_->setText(QString::number(fontPrivate.blue_shift));
+ blueFuzzLabel_->setText(QString::number(fontPrivate.blue_fuzz));
+ forceBoldLabel_->setText(fontPrivate.force_bold ? "true" : "false");
+
languageGroupLabel_->setText(QString::number(fontPrivate.language_group));
+ passwordLabel_->setText(QString::number(fontPrivate.password));
+ lenIVLabel_->setText(QString::number(fontPrivate.lenIV));
+ roundStemUpLabel_->setText(fontPrivate.round_stem_up ? "true" : "false");
+
+ familyBluesLabel_->setText(
+ genArrayString(fontPrivate.family_blues,
fontPrivate.num_family_blues));
+ blueValuesLabel_->setText(
+ genArrayString(fontPrivate.blue_values, fontPrivate.num_blue_values));
+ otherBluesLabel_->setText(
+ genArrayString(fontPrivate.other_blues, fontPrivate.num_other_blues));
+ familyOtherBluesLabel_->setText(
+ genArrayString(fontPrivate.family_other_blues,
+ fontPrivate.num_family_other_blues));
+ stdWidthsLabel_->setText(
+ genArrayString(fontPrivate.standard_width,
+ arraySize(fontPrivate.standard_width)));
+ stdHeightsLabel_->setText(
+ genArrayString(fontPrivate.standard_height,
+ arraySize(fontPrivate.standard_height)));
+ snapWidthsLabel_->setText(
+ genArrayString(fontPrivate.snap_widths, fontPrivate.num_snap_widths));
+ snapHeightsLabel_->setText(
+ genArrayString(fontPrivate.snap_heights,
fontPrivate.num_snap_heights));
+ minFeatureLabel_->setText(
+ genArrayString(fontPrivate.min_feature,
+ arraySize(fontPrivate.min_feature)));
+
+ blueScaleLabel_->setText(
+ QString::number(fontPrivate.blue_scale / 65536.0 / 1000.0, 'f', 6));
+ expansionFactorLabel_->setText(
+ QString::number(fontPrivate.expansion_factor / 65536.0, 'f', 4));
+ }
+ }
+ else
+ {
+ std::memset(&oldFontPrivate_, 0, sizeof(PS_PrivateRec));
+ uniqueIDLabel_->clear();
+ blueValuesLabel_->clear();
+ otherBluesLabel_->clear();
+ familyBluesLabel_->clear();
+ familyOtherBluesLabel_->clear();
+ blueScaleLabel_->clear();
+ blueShiftLabel_->clear();
+ blueFuzzLabel_->clear();
+ stdWidthsLabel_->clear();
+ stdHeightsLabel_->clear();
+ snapWidthsLabel_->clear();
+ snapHeightsLabel_->clear();
+ forceBoldLabel_->clear();
+ languageGroupLabel_->clear();
+ passwordLabel_->clear();
+ lenIVLabel_->clear();
+ minFeatureLabel_->clear();
+ roundStemUpLabel_->clear();
+ expansionFactorLabel_->clear();
+ }
+}
+
+
+void
+PostScriptInfoTab::createLayout()
+{
+ versionPromptLabel_ = new QLabel(tr("/version:"), this);
+ noticePromptLabel_ = new QLabel(tr("/Notice:"), this);
+ fullNamePromptLabel_ = new QLabel(tr("/FullName:"), this);
+ familyNamePromptLabel_ = new QLabel(tr("/FamilyName:"), this);
+ weightPromptLabel_ = new QLabel(tr("/Weight:"), this);
+ italicAnglePromptLabel_ = new QLabel(tr("/ItaticAngle:"), this);
+ fixedPitchPromptLabel_ = new QLabel(tr("/isFixedPitch:"), this);
+ ulPosPromptLabel_ = new QLabel(tr("/UnderlinePosition:"), this);
+ ulThicknessPromptLabel_ = new QLabel(tr("/UnderlineThickness:"), this);
+
+ versionLabel_ = new QLabel(this);
+ noticeLabel_ = new QLabel(this);
+ fullNameLabel_ = new QLabel(this);
+ familyNameLabel_ = new QLabel(this);
+ weightLabel_ = new QLabel(this);
+ italicAngleLabel_ = new QLabel(this);
+ fixedPitchLabel_ = new QLabel(this);
+ ulPosLabel_ = new QLabel(this);
+ ulThicknessLabel_ = new QLabel(this);
+
+ setLabelSelectable( versionLabel_);
+ setLabelSelectable( noticeLabel_);
+ setLabelSelectable( fullNameLabel_);
+ setLabelSelectable( familyNameLabel_);
+ setLabelSelectable( weightLabel_);
+ setLabelSelectable(italicAngleLabel_);
+ setLabelSelectable( fixedPitchLabel_);
+ setLabelSelectable( ulPosLabel_);
+ setLabelSelectable(ulThicknessLabel_);
+
+ uniqueIDPromptLabel_ = new QLabel(tr("/UniqueID:"), this);
+ blueValuesPromptLabel_ = new QLabel(tr("/BlueValues:"), this);
+ otherBluesPromptLabel_ = new QLabel(tr("/OtherBlues:"), this);
+ familyBluesPromptLabel_ = new QLabel(tr("/FamilyBlues:"), this);
+ familyOtherBluesPromptLabel_ = new QLabel(tr("/FamilyOtherBlues:"), this);
+ blueScalePromptLabel_ = new QLabel(tr("/BlueScale:"), this);
+ blueShiftPromptLabel_ = new QLabel(tr("/BlueShift:"), this);
+ blueFuzzPromptLabel_ = new QLabel(tr("/BlueFuzz:"), this);
+ stdWidthsPromptLabel_ = new QLabel(tr("/StdHW:"), this);
+ stdHeightsPromptLabel_ = new QLabel(tr("/StdVW:"), this);
+ snapWidthsPromptLabel_ = new QLabel(tr("/StemSnapH:"), this);
+ snapHeightsPromptLabel_ = new QLabel(tr("/StemSnapV:"), this);
+ forceBoldPromptLabel_ = new QLabel(tr("/ForceBold:"), this);
+ languageGroupPromptLabel_ = new QLabel(tr("/LanguageGroup:"), this);
+ passwordPromptLabel_ = new QLabel(tr("/password:"), this);
+ lenIVPromptLabel_ = new QLabel(tr("/lenIV:"), this);
+ minFeaturePromptLabel_ = new QLabel(tr("/MinFeature:"), this);
+ roundStemUpPromptLabel_ = new QLabel(tr("/RndStemUp:"), this);
+ expansionFactorPromptLabel_ = new QLabel(tr("/ExpansionFactor:"), this);
+
+ uniqueIDLabel_ = new QLabel(this);
+ blueValuesLabel_ = new QLabel(this);
+ otherBluesLabel_ = new QLabel(this);
+ familyBluesLabel_ = new QLabel(this);
+ familyOtherBluesLabel_ = new QLabel(this);
+ blueScaleLabel_ = new QLabel(this);
+ blueShiftLabel_ = new QLabel(this);
+ blueFuzzLabel_ = new QLabel(this);
+ stdWidthsLabel_ = new QLabel(this);
+ stdHeightsLabel_ = new QLabel(this);
+ snapWidthsLabel_ = new QLabel(this);
+ snapHeightsLabel_ = new QLabel(this);
+ forceBoldLabel_ = new QLabel(this);
+ languageGroupLabel_ = new QLabel(this);
+ passwordLabel_ = new QLabel(this);
+ lenIVLabel_ = new QLabel(this);
+ minFeatureLabel_ = new QLabel(this);
+ roundStemUpLabel_ = new QLabel(this);
+ expansionFactorLabel_ = new QLabel(this);
+
+ setLabelSelectable( uniqueIDLabel_);
+ setLabelSelectable( blueValuesLabel_);
+ setLabelSelectable( otherBluesLabel_);
+ setLabelSelectable( familyBluesLabel_);
+ setLabelSelectable(familyOtherBluesLabel_);
+ setLabelSelectable( blueScaleLabel_);
+ setLabelSelectable( blueShiftLabel_);
+ setLabelSelectable( blueFuzzLabel_);
+ setLabelSelectable( stdWidthsLabel_);
+ setLabelSelectable( stdHeightsLabel_);
+ setLabelSelectable( snapWidthsLabel_);
+ setLabelSelectable( snapHeightsLabel_);
+ setLabelSelectable( forceBoldLabel_);
+ setLabelSelectable( languageGroupLabel_);
+ setLabelSelectable( passwordLabel_);
+ setLabelSelectable( lenIVLabel_);
+ setLabelSelectable( minFeatureLabel_);
+ setLabelSelectable( roundStemUpLabel_);
+ setLabelSelectable( expansionFactorLabel_);
+
+ noticeLabel_->setWordWrap(true);
+ familyBluesLabel_->setWordWrap(true);
+ blueValuesLabel_->setWordWrap(true);
+ otherBluesLabel_->setWordWrap(true);
+ familyOtherBluesLabel_->setWordWrap(true);
+ stdWidthsLabel_->setWordWrap(true);
+ stdHeightsLabel_->setWordWrap(true);
+ snapWidthsLabel_->setWordWrap(true);
+ snapHeightsLabel_->setWordWrap(true);
+ minFeatureLabel_->setWordWrap(true);
+
+ infoGroupBox_ = new QGroupBox(tr("PostScript /FontInfo dictionary"), this);
+ privateGroupBox_ = new QGroupBox(tr("PostScript /Private dictionary"), this);
+
+ infoWidget_ = new QWidget(this);
+ privateWidget_ = new QWidget(this);
+
+ infoScrollArea_ = new UnboundScrollArea(this);
+ infoScrollArea_->setWidget(infoWidget_);
+ infoScrollArea_->setWidgetResizable(true);
+ infoScrollArea_->setStyleSheet("QScrollArea
{background-color:transparent;}");
+ infoWidget_->setStyleSheet("background-color:transparent;");
+ infoWidget_->setContentsMargins(0, 0, 0, 0);
+
+ privateScrollArea_ = new UnboundScrollArea(this);
+ privateScrollArea_->setWidget(privateWidget_);
+ privateScrollArea_->setWidgetResizable(true);
+ privateScrollArea_->setStyleSheet("QScrollArea
{background-color:transparent;}");
+ privateWidget_->setStyleSheet("background-color:transparent;");
+ privateWidget_->setContentsMargins(0, 0, 0, 0);
+
+ infoLayout_ = new QGridLayout;
+ privateLayout_ = new QGridLayout;
+ infoGroupBoxLayout_ = new QHBoxLayout;
+ privateGroupBoxLayout_ = new QHBoxLayout;
+
+#define PSI2Row(w) GL2CRow(infoLayout_, w)
+#define PSP2Row(w) GL2CRow(privateLayout_, w)
+
+ PSI2Row( version);
+ PSI2Row( notice);
+ PSI2Row( fullName);
+ PSI2Row( familyName);
+ PSI2Row( weight);
+ PSI2Row(italicAngle);
+ PSI2Row( fixedPitch);
+ PSI2Row( ulPos);
+ PSI2Row(ulThickness);
+
+ PSP2Row( uniqueID);
+ PSP2Row( blueValues);
+ PSP2Row( otherBlues);
+ PSP2Row( familyBlues);
+ PSP2Row(familyOtherBlues);
+ PSP2Row( blueScale);
+ PSP2Row( blueShift);
+ PSP2Row( blueFuzz);
+ PSP2Row( stdWidths);
+ PSP2Row( stdHeights);
+ PSP2Row( snapWidths);
+ PSP2Row( snapHeights);
+ PSP2Row( forceBold);
+ PSP2Row( languageGroup);
+ PSP2Row( password);
+ PSP2Row( lenIV);
+ PSP2Row( minFeature);
+ PSP2Row( roundStemUp);
+ PSP2Row( expansionFactor);
+
+ infoLayout_->addItem(new QSpacerItem(0, 0,
+ QSizePolicy::Preferred,
+ QSizePolicy::Expanding),
+ infoLayout_->rowCount(), 0, 1, 2);
+ privateLayout_->addItem(new QSpacerItem(0, 0,
+ QSizePolicy::Preferred,
+ QSizePolicy::Expanding),
+ privateLayout_->rowCount(), 0, 1, 2);
+
+ infoLayout_->setColumnStretch(1, 1);
+ privateLayout_->setColumnStretch(1, 1);
+
+ infoWidget_->setLayout(infoLayout_);
+ privateWidget_->setLayout(privateLayout_);
+ infoGroupBox_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Expanding);
+ privateGroupBox_->setSizePolicy(QSizePolicy::Ignored,
QSizePolicy::Expanding);
+
+ infoGroupBoxLayout_->addWidget(infoScrollArea_);
+ privateGroupBoxLayout_->addWidget(privateScrollArea_);
+ infoGroupBox_->setLayout(infoGroupBoxLayout_);
+ privateGroupBox_->setLayout(privateGroupBoxLayout_);
+
+ mainLayout_ = new QHBoxLayout;
+ mainLayout_->addWidget(infoGroupBox_);
+ mainLayout_->addWidget(privateGroupBox_);
+ setLayout(mainLayout_);
+}
+
+
+MMGXInfoTab::MMGXInfoTab(QWidget* parent,
+ Engine* engine)
+: QWidget(parent), engine_(engine)
+{
+ createLayout();
+}
+
+
+void
+MMGXInfoTab::reloadFont()
+{
+ auto state = engine_->currentFontMMGXState();
+ axesGroupBox_->setEnabled(state != MMGXState::NoMMGX);
+ switch (state)
+ {
+ case MMGXState::NoMMGX:
+ mmgxTypeLabel_->setText("No MM/GX");
+ break;
+ case MMGXState::MM:
+ mmgxTypeLabel_->setText("Adobe Multiple Master");
+ break;
+ case MMGXState::GX_OVF:
+ mmgxTypeLabel_->setText("TrueType GX or OpenType Variable Font");
+ break;
+ default:
+ mmgxTypeLabel_->setText("Unknown");
+ }
+
+ if (engine_->currentFontMMGXAxes() != axesModel_->storage())
+ {
+ axesModel_->beginModelUpdate();
+ axesModel_->storage() = engine_->currentFontMMGXAxes();
+ axesModel_->endModelUpdate();
+ }
+
+ setEnabled(state != MMGXState::NoMMGX);
+}
+
+
+void
+MMGXInfoTab::createLayout()
+{
+ mmgxTypePromptLabel_ = new QLabel(tr("MM/GX Type:"));
+ mmgxTypeLabel_ = new QLabel(this);
+ setLabelSelectable(mmgxTypeLabel_);
+
+ axesTable_ = new QTableView(this);
+
+ axesModel_ = new MMGXAxisInfoModel(this);
+ axesTable_->setModel(axesModel_);
+ auto header = axesTable_->verticalHeader();
+ // This will force the minimal size to be used
+ header->setDefaultSectionSize(0);
+ header->setSectionResizeMode(QHeaderView::Fixed);
+ axesTable_->horizontalHeader()->setStretchLastSection(true);
+
+ axesGroupBox_ = new QGroupBox("MM/GX Axes");
+
+ axesLayout_ = new QHBoxLayout;
+ axesLayout_->addWidget(axesTable_);
+
+ axesGroupBox_->setLayout(axesLayout_);
+
+ infoLayout_ = new QGridLayout;
+#define MMGXI2Row(w) GL2CRow(infoLayout_, w)
+ auto r = MMGXI2Row(mmgxType);
+
+ infoLayout_->addItem(new QSpacerItem(0, 0,
+ QSizePolicy::Expanding,
+ QSizePolicy::Preferred),
+ r, 2);
+
+ mainLayout_ = new QVBoxLayout;
+ mainLayout_->addLayout(infoLayout_);
+ mainLayout_->addWidget(axesGroupBox_, 1);
+
+ setLayout(mainLayout_);
+}
+
+
+CompositeGlyphsTab::CompositeGlyphsTab(QWidget* parent,
+ Engine* engine)
+: QWidget(parent), engine_(engine)
+{
+ createLayout();
+ createConnections();
+}
+
+
+void
+CompositeGlyphsTab::reloadFont()
+{
+ if (engine_->fontFileManager().currentReloadDueToPeriodicUpdate())
+ return;
+ forceReloadFont();
+}
+
+
+void
+CompositeGlyphsTab::createLayout()
+{
+ compositeGlyphCountPromptLabel_ = new QLabel(tr("Composite Glyphs Count:"));
+ compositeGlyphCountLabel_ = new QLabel(this);
+ forceRefreshButton_ = new QPushButton(tr("Force Refresh"), this);
+ compositeTreeView_ = new QTreeView(this);
+
+ compositeModel_ = new CompositeGlyphsInfoModel(this, engine_);
+ compositeTreeView_->setModel(compositeModel_);
+
+ forceRefreshButton_->setToolTip(tr(
+ "Force refresh the tree view.\n"
+ "Note that periodic reloading of fonts loaded from symbolic links won't\n"
+ "trigger automatically refreshing, so you need to manually reload."));
+
+ // Layouting
+ countLayout_ = new QHBoxLayout;
+ countLayout_->addWidget(compositeGlyphCountPromptLabel_);
+ countLayout_->addWidget(compositeGlyphCountLabel_);
+ countLayout_->addWidget(forceRefreshButton_);
+ countLayout_->addStretch(1);
+
+ mainLayout_ = new QVBoxLayout;
+ mainLayout_->addLayout(countLayout_);
+ mainLayout_->addWidget(compositeTreeView_);
+
+ setLayout(mainLayout_);
+}
+
+
+void
+CompositeGlyphsTab::createConnections()
+{
+ connect(forceRefreshButton_, &QPushButton::clicked,
+ this, &CompositeGlyphsTab::forceReloadFont);
+ connect(compositeTreeView_, &QTreeView::doubleClicked,
+ this, &CompositeGlyphsTab::treeRowDoubleClicked);
+}
+
+
+void
+CompositeGlyphsTab::forceReloadFont()
+{
+ engine_->loadDefaults(); // this would reload the font
+ auto face = engine_->currentFallbackFtFace();
+
+ std::vector<CompositeGlyphInfo> list;
+ CompositeGlyphInfo::get(engine_, list);
+ if (list != compositeModel_->storage())
+ {
+ compositeModel_->beginModelUpdate();
+ compositeModel_->storage() = std::move(list);
+ compositeModel_->endModelUpdate();
+ }
+
+ if (!face || !FT_IS_SFNT(face))
+ {
+ compositeGlyphCountPromptLabel_->setVisible(false);
+ compositeGlyphCountLabel_->setText(tr("Not a SFNT font."));
+ }
+ else if (compositeModel_->storage().empty())
+ {
+ compositeGlyphCountPromptLabel_->setVisible(false);
+ compositeGlyphCountLabel_->setText(
+ tr("No composite glyphs in the 'glyf' table."));
+ }
+ else
+ {
+ compositeGlyphCountPromptLabel_->setVisible(true);
+ compositeGlyphCountLabel_->setText(
+ QString::number(compositeModel_->storage().size()));
+ }
+}
+
+
+void
+CompositeGlyphsTab::treeRowDoubleClicked(const QModelIndex& idx)
+{
+ auto gidx = compositeModel_->glyphIndexFromIndex(idx);
+ if (gidx < 0)
+ return;
+ emit switchToSingular(gidx);
+}
+
+
+// end of info.cpp
diff --git a/src/ftinspect/panels/info.hpp b/src/ftinspect/panels/info.hpp
new file mode 100644
index 0000000..6890d23
--- /dev/null
+++ b/src/ftinspect/panels/info.hpp
@@ -0,0 +1,327 @@
+// info.hpp
+
+// Copyright (C) 2022 by Charlie Jiang.
+
+#pragma once
+
+#include "abstracttab.hpp"
+#include "../engine/fontinfo.hpp"
+#include "../models/fontinfomodels.hpp"
+#include "../widgets/customwidgets.hpp"
+
+#include <vector>
+#include <QWidget>
+#include <QTabWidget>
+#include <QBoxLayout>
+#include <QTextEdit>
+#include <QDialog>
+#include <QGridLayout>
+#include <QVector>
+#include <QLabel>
+#include <QGroupBox>
+#include <QTableView>
+#include <QTreeView>
+
+class Engine;
+class GeneralInfoTab;
+class SFNTInfoTab;
+class PostScriptInfoTab;
+class MMGXInfoTab;
+class CompositeGlyphsTab;
+
+class InfoTab
+: public QWidget, public AbstractTab
+{
+ Q_OBJECT
+public:
+ InfoTab(QWidget* parent, Engine* engine);
+ ~InfoTab() override = default;
+
+ void repaintGlyph() override {}
+ void reloadFont() override;
+
+signals:
+ void switchToSingular(int glyphIndex);
+
+private:
+ Engine* engine_;
+
+ QVector<AbstractTab*> tabs_;
+ GeneralInfoTab* generalTab_;
+ SFNTInfoTab* sfntTab_;
+ PostScriptInfoTab* postScriptTab_;
+ MMGXInfoTab* mmgxTab_;
+ CompositeGlyphsTab* compositeGlyphsTab_;
+
+ QTabWidget* tab_;
+ QHBoxLayout* layout_;
+
+ void createLayout();
+ void createConnections();
+};
+
+
+#define LabelPair(name) \
+ QLabel* name##Label_; \
+ QLabel* name##PromptLabel_;
+
+
+class GeneralInfoTab
+: public QWidget, public AbstractTab
+{
+ Q_OBJECT
+public:
+ GeneralInfoTab(QWidget* parent, Engine* engine);
+ ~GeneralInfoTab() override = default;
+
+ void repaintGlyph() override {}
+ void reloadFont() override;
+
+private:
+ Engine* engine_;
+
+ LabelPair( numFaces)
+ LabelPair( family)
+ LabelPair( style)
+ LabelPair( postscript)
+ LabelPair( created)
+ LabelPair( modified)
+ LabelPair( revision)
+ LabelPair( copyright)
+ LabelPair( trademark)
+ LabelPair(manufacturer)
+
+ LabelPair(driverName)
+ LabelPair( sfnt)
+ LabelPair( fontType)
+ LabelPair( direction)
+ LabelPair(fixedWidth)
+ LabelPair(glyphNames)
+
+ LabelPair( emSize)
+ LabelPair( bbox)
+ LabelPair( ascender)
+ LabelPair( descender)
+ LabelPair( maxAdvanceWidth)
+ LabelPair(maxAdvanceHeight)
+ LabelPair( ulPos)
+ LabelPair( ulThickness)
+
+ QGroupBox* basicGroupBox_;
+ QGroupBox* typeEntriesGroupBox_;
+ QGroupBox* charMapGroupBox_;
+ QGroupBox* fixedSizesGroupBox_;
+
+ QTableView* charMapsTable_;
+ QTableView* fixedSizesTable_;
+
+ FixedSizeInfoModel* fixedSizeInfoModel_;
+ CharMapInfoModel* charMapInfoModel_;
+
+ UnboundScrollArea* leftScrollArea_;
+
+ QWidget* leftWidget_;
+ QHBoxLayout* mainLayout_;
+ QVBoxLayout* leftLayout_;
+ QVBoxLayout* rightLayout_;
+ QGridLayout* basicLayout_;
+ QGridLayout* typeEntriesLayout_;
+ QHBoxLayout* charMapLayout_;
+ QHBoxLayout* fixedSizesLayout_;
+
+ std::vector<QLabel*> scalableOnlyLabels_;
+
+ FontBasicInfo oldFontBasicInfo_ = {};
+ FontTypeEntries oldFontTypeEntries_ = {};
+
+ void createLayout();
+};
+
+
+class StringViewDialog
+: public QDialog
+{
+ Q_OBJECT
+public:
+ StringViewDialog(QWidget* parent);
+ ~StringViewDialog() override = default;
+
+ void updateString(QByteArray const& rawArray, QString const& str);
+
+private:
+ QLabel* textLabel_;
+ QLabel* hexTextLabel_;
+
+ QTextEdit* textEdit_;
+ QTextEdit* hexTextEdit_;
+
+ QVBoxLayout* layout_;
+
+ void createLayout();
+};
+
+
+class SFNTInfoTab
+: public QWidget, public AbstractTab
+{
+ Q_OBJECT
+public:
+ SFNTInfoTab(QWidget* parent, Engine* engine);
+ ~SFNTInfoTab() override = default;
+
+ void repaintGlyph() override {}
+ void reloadFont() override;
+
+private:
+ Engine* engine_;
+
+ QGroupBox* sfntNamesGroupBox_;
+ QGroupBox* sfntTablesGroupBox_;
+
+ QTableView* sfntNamesTable_;
+ QTableView* sfntTablesTable_;
+
+ SFNTNameModel* sfntNamesModel_;
+ SFNTTableInfoModel* sfntTablesModel_;
+
+ QHBoxLayout* sfntNamesLayout_;
+ QHBoxLayout* sfntTablesLayout_;
+ QHBoxLayout* mainLayout_;
+
+ StringViewDialog* stringViewDialog_;
+
+ void createLayout();
+ void createConnections();
+
+ void nameTableDoubleClicked(QModelIndex const& index);
+};
+
+
+class PostScriptInfoTab
+: public QWidget, public AbstractTab
+{
+ Q_OBJECT
+public:
+ PostScriptInfoTab(QWidget* parent, Engine* engine);
+ ~PostScriptInfoTab() override = default;
+
+ void repaintGlyph() override {}
+ void reloadFont() override;
+
+private:
+ Engine* engine_;
+
+ LabelPair( version)
+ LabelPair( notice)
+ LabelPair( fullName)
+ LabelPair( familyName)
+ LabelPair( weight)
+ LabelPair(italicAngle)
+ LabelPair( fixedPitch)
+ LabelPair( ulPos)
+ LabelPair(ulThickness)
+
+ LabelPair( uniqueID)
+ LabelPair( blueValues)
+ LabelPair( otherBlues)
+ LabelPair( familyBlues)
+ LabelPair(familyOtherBlues)
+ LabelPair( blueScale)
+ LabelPair( blueShift)
+ LabelPair( blueFuzz)
+ LabelPair( stdWidths)
+ LabelPair( stdHeights)
+ LabelPair( snapWidths)
+ LabelPair( snapHeights)
+ LabelPair( forceBold)
+ LabelPair( languageGroup)
+ LabelPair( password)
+ LabelPair( lenIV)
+ LabelPair( minFeature)
+ LabelPair( roundStemUp)
+ LabelPair( expansionFactor)
+
+ QGroupBox* infoGroupBox_;
+ QGroupBox* privateGroupBox_;
+
+ QWidget* infoWidget_;
+ QWidget* privateWidget_;
+
+ UnboundScrollArea* infoScrollArea_;
+ UnboundScrollArea* privateScrollArea_;
+
+ QGridLayout* infoLayout_;
+ QGridLayout* privateLayout_;
+ QHBoxLayout* infoGroupBoxLayout_;
+ QHBoxLayout* privateGroupBoxLayout_;
+ QHBoxLayout* mainLayout_;
+
+ PS_PrivateRec oldFontPrivate_;
+
+ void createLayout();
+};
+
+
+class MMGXInfoTab
+: public QWidget, public AbstractTab
+{
+ Q_OBJECT
+public:
+ MMGXInfoTab(QWidget* parent, Engine* engine);
+ ~MMGXInfoTab() override = default;
+
+ void repaintGlyph() override {}
+ void reloadFont() override;
+
+private:
+ Engine* engine_;
+
+ LabelPair(mmgxType)
+
+ QGroupBox* axesGroupBox_;
+ QTableView* axesTable_;
+
+ QGridLayout* infoLayout_;
+ QHBoxLayout* axesLayout_;
+ QVBoxLayout* mainLayout_;
+
+ MMGXAxisInfoModel* axesModel_;
+
+ void createLayout();
+};
+
+
+class CompositeGlyphsTab
+: public QWidget, public AbstractTab
+{
+ Q_OBJECT
+public:
+ CompositeGlyphsTab(QWidget* parent, Engine* engine);
+ ~CompositeGlyphsTab() override = default;
+
+ void repaintGlyph() override {}
+ void reloadFont() override;
+
+signals:
+ void switchToSingular(int glyphIndex);
+
+private:
+ Engine* engine_;
+
+ LabelPair(compositeGlyphCount)
+ QPushButton* forceRefreshButton_;
+ QTreeView* compositeTreeView_;
+ CompositeGlyphsInfoModel* compositeModel_;
+
+ QHBoxLayout* countLayout_;
+ QVBoxLayout* mainLayout_;
+
+ void createLayout();
+ void createConnections();
+
+ void forceReloadFont();
+ void treeRowDoubleClicked(const QModelIndex& idx);
+};
+
+
+// end of info.hpp
diff --git a/src/ftinspect/panels/settingpanel.cpp
b/src/ftinspect/panels/settingpanel.cpp
index 3db0bde..dda3873 100644
--- a/src/ftinspect/panels/settingpanel.cpp
+++ b/src/ftinspect/panels/settingpanel.cpp
@@ -298,11 +298,11 @@ SettingPanel::populatePalettes()
QSignalBlocker blocker(paletteComboBox_);
paletteComboBox_->clear();
for (int i = 0; i < newSize; ++i)
- paletteComboBox_->addItem(
- QString("%1: %2")
- .arg(i)
- .arg(newPalettes[i].name),
- newPalettes[i].name);
+ {
+ auto str = QString("%1: %2").arg(i).arg(newPalettes[i].name);
+ paletteComboBox_->addItem(str, newPalettes[i].name);
+ paletteComboBox_->setItemData(i, str, Qt::ToolTipRole);
+ }
}
emit fontReloadNeeded();
diff --git a/src/ftinspect/panels/settingpanelmmgx.cpp
b/src/ftinspect/panels/settingpanelmmgx.cpp
index f80c318..a1a81dc 100644
--- a/src/ftinspect/panels/settingpanelmmgx.cpp
+++ b/src/ftinspect/panels/settingpanelmmgx.cpp
@@ -95,6 +95,7 @@ SettingPanelMMGX::createLayout()
itemsListWidget_ = new QWidget(this);
scrollArea_ = new UnboundScrollArea(this);
+ scrollArea_->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Ignored);
scrollArea_->setWidget(itemsListWidget_);
scrollArea_->setWidgetResizable(true);
itemsListWidget_->setAutoFillBackground(false);
diff --git a/src/ftinspect/widgets/charmapcombobox.cpp
b/src/ftinspect/widgets/charmapcombobox.cpp
index b9caa91..dadb483 100644
--- a/src/ftinspect/widgets/charmapcombobox.cpp
+++ b/src/ftinspect/widgets/charmapcombobox.cpp
@@ -77,23 +77,24 @@ CharMapComboBox::repopulate(std::vector<CharMapInfo>&
charMaps)
addItem(tr("Glyph Order"));
setItemData(0, 0u, EncodingRole);
}
-
- int i = 0;
+
int newIndex = 0;
for (auto& map : charMaps)
{
- addItem(tr("%1: %2 (platform %3, encoding %4)")
- .arg(i)
- .arg(*map.encodingName)
- .arg(map.platformID)
- .arg(map.encodingID));
+ auto i = count();
+ auto str = tr("%1: %2 (platform %3, encoding %4)")
+ .arg(i)
+ .arg(*map.encodingName)
+ .arg(map.platformID)
+ .arg(map.encodingID);
+ addItem(str);
+ setItemData(i, str, Qt::ToolTipRole);
+
auto encoding = static_cast<unsigned>(map.encoding);
- setItemData(haveGlyphOrder_ ? i + 1 : i, encoding, EncodingRole);
+ setItemData(i, encoding, EncodingRole);
if (encoding == oldEncoding && i == oldIndex)
newIndex = i;
-
- i++;
}
// this shouldn't emit any event either, because force repainting
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [freetype2-demos] master bf88f4d 26/41: [ftinspect] Add "Font Info" tab.,
Werner Lemberg <=