Charlie Jiang pushed to branch gsoc-2022-chariri-3 at FreeType / FreeType Demo Programs
Commits:
-
1afa70cb
by Charlie Jiang at 2022-08-19T00:52:36+08:00
8 changed files:
- src/ftinspect/engine/engine.cpp
- src/ftinspect/engine/engine.hpp
- src/ftinspect/engine/fontinfo.cpp
- src/ftinspect/engine/fontinfo.hpp
- src/ftinspect/models/fontinfomodels.cpp
- src/ftinspect/models/fontinfomodels.hpp
- src/ftinspect/panels/info.cpp
- src/ftinspect/panels/info.hpp
Changes:
... | ... | @@ -282,6 +282,7 @@ Engine::loadFont(int fontIndex, |
282 | 282 | fontType_ = FontType_Other;
|
283 | 283 | |
284 | 284 | update();
|
285 | + curSFNTTablesValid_ = false;
|
|
285 | 286 | |
286 | 287 | curFontIndex_ = fontIndex;
|
287 | 288 | auto id = FaceID(fontIndex, faceIndex, namedInstanceIndex);
|
... | ... | @@ -491,6 +492,19 @@ Engine::currentFontPSPrivateInfo(PS_PrivateRec& outInfo) |
491 | 492 | }
|
492 | 493 | |
493 | 494 | |
495 | +std::vector<SFNTTableInfo>&
|
|
496 | +Engine::currentFontSFNTTableInfo()
|
|
497 | +{
|
|
498 | + if (!curSFNTTablesValid_)
|
|
499 | + {
|
|
500 | + SFNTTableInfo::getForAll(this, curSFNTTables_);
|
|
501 | + curSFNTTablesValid_ = true;
|
|
502 | + }
|
|
503 | + |
|
504 | + return curSFNTTables_;
|
|
505 | +}
|
|
506 | + |
|
507 | + |
|
494 | 508 | QString
|
495 | 509 | Engine::glyphName(int index)
|
496 | 510 | {
|
... | ... | @@ -161,6 +161,7 @@ public: |
161 | 161 | std::vector<SFNTName>& currentFontSFNTNames() { return curSFNTNames_; }
|
162 | 162 | MMGXState currentFontMMGXState() { return curMMGXState_; }
|
163 | 163 | std::vector<MMGXAxisInfo>& currentFontMMGXAxes() { return curMMGXAxes_; }
|
164 | + std::vector<SFNTTableInfo>& currentFontSFNTTableInfo();
|
|
164 | 165 | FontFileManager& fontFileManager() { return fontFileManager_; }
|
165 | 166 | EngineDefaultValues& engineDefaults() { return engineDefaults_; }
|
166 | 167 | bool antiAliasingEnabled() { return antiAliasingEnabled_; }
|
... | ... | @@ -233,6 +234,9 @@ private: |
233 | 234 | std::vector<CharMapInfo> curCharMaps_;
|
234 | 235 | std::vector<PaletteInfo> curPaletteInfos_;
|
235 | 236 | std::vector<MMGXAxisInfo> curMMGXAxes_;
|
237 | + |
|
238 | + bool curSFNTTablesValid_ = false;
|
|
239 | + std::vector<SFNTTableInfo> curSFNTTables_;
|
|
236 | 240 | MMGXState curMMGXState_ = MMGXState::NoMMGX;
|
237 | 241 | |
238 | 242 | FT_Library library_;
|
... | ... | @@ -6,7 +6,9 @@ |
6 | 6 | |
7 | 7 | #include "engine.hpp"
|
8 | 8 | |
9 | +#include <map>
|
|
9 | 10 | #include <unordered_map>
|
11 | +#include <memory>
|
|
10 | 12 | #include <utility>
|
11 | 13 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
12 | 14 | #include <QTextCodec>
|
... | ... | @@ -17,11 +19,18 @@ |
17 | 19 | #include <freetype/ftmodapi.h>
|
18 | 20 | #include <freetype/ttnameid.h>
|
19 | 21 | #include <freetype/tttables.h>
|
22 | +#include <freetype/tttags.h>
|
|
23 | + |
|
24 | +#ifdef _MSC_VER // To use intrin
|
|
25 | +#define WIN32_LEAN_AND_MEAN
|
|
26 | +#include <Windows.h>
|
|
27 | +#include <intrin.h>
|
|
28 | +#endif
|
|
20 | 29 | |
21 | 30 | |
22 | 31 | void
|
23 | 32 | SFNTName::get(Engine* engine,
|
24 | - std::vector<SFNTName>& list)
|
|
33 | + std::vector<SFNTName>& list)
|
|
25 | 34 | {
|
26 | 35 | auto size = engine->currentFtSize();
|
27 | 36 | if (!size || !FT_IS_SFNT(size->face))
|
... | ... | @@ -369,4 +378,202 @@ FontFixedSize::get(Engine* engine, |
369 | 378 | }
|
370 | 379 | |
371 | 380 | |
381 | +struct TTCHeaderRec
|
|
382 | +{
|
|
383 | + uint32_t ttcTag;
|
|
384 | + uint16_t majorVersion;
|
|
385 | + uint16_t minorVersion;
|
|
386 | + uint32_t numFonts;
|
|
387 | +};
|
|
388 | + |
|
389 | + |
|
390 | +struct SFNTHeaderRec
|
|
391 | +{
|
|
392 | + uint32_t formatTag;
|
|
393 | + uint16_t numTables;
|
|
394 | + // There'll be some padding, but it doesn't matter.
|
|
395 | +};
|
|
396 | + |
|
397 | + |
|
398 | +struct TTTableRec
|
|
399 | +{
|
|
400 | + uint32_t tag;
|
|
401 | + uint32_t checksum;
|
|
402 | + uint32_t offset;
|
|
403 | + uint32_t length;
|
|
404 | +};
|
|
405 | + |
|
406 | + |
|
407 | +uint32_t
|
|
408 | +bigEndianToNative(uint32_t n)
|
|
409 | +{
|
|
410 | +#ifdef _MSC_VER
|
|
411 | + #if REG_DWORD == REG_DWORD_LITTLE_ENDIAN
|
|
412 | + return _byteswap_ulong(n);
|
|
413 | + #else
|
|
414 | + return n;
|
|
415 | + #endif
|
|
416 | +#else
|
|
417 | + auto np = reinterpret_cast<unsigned char*>(&n);
|
|
418 | + |
|
419 | + return (static_cast<uint32_t>(np[0]) << 24)
|
|
420 | + | (static_cast<uint32_t>(np[1]) << 16)
|
|
421 | + | (static_cast<uint32_t>(np[2]) << 8)
|
|
422 | + | (static_cast<uint32_t>(np[3]));
|
|
423 | +#endif
|
|
424 | +}
|
|
425 | + |
|
426 | + |
|
427 | +uint16_t
|
|
428 | +bigEndianToNative(uint16_t n)
|
|
429 | +{
|
|
430 | +#ifdef _MSC_VER
|
|
431 | +#if REG_DWORD == REG_DWORD_LITTLE_ENDIAN
|
|
432 | + return _byteswap_ushort(n);
|
|
433 | +#else
|
|
434 | + return n;
|
|
435 | +#endif
|
|
436 | +#else
|
|
437 | + auto np = reinterpret_cast<unsigned char*>(&n);
|
|
438 | + |
|
439 | + return static_cast<uint16_t>((static_cast<uint16_t>(np[0]) << 8)
|
|
440 | + | (static_cast<uint16_t>(np[1])));
|
|
441 | +#endif
|
|
442 | +}
|
|
443 | + |
|
444 | + |
|
445 | +void readSingleFace(QFile& file,
|
|
446 | + uint32_t offset,
|
|
447 | + unsigned faceIndex,
|
|
448 | + std::vector<TTTableRec>& tempTables,
|
|
449 | + std::map<unsigned long, SFNTTableInfo>& result)
|
|
450 | +{
|
|
451 | + if (!file.seek(offset))
|
|
452 | + return;
|
|
453 | + |
|
454 | + SFNTHeaderRec sfntHeader = {};
|
|
455 | + if (file.read(reinterpret_cast<char*>(&sfntHeader),
|
|
456 | + sizeof(SFNTHeaderRec))
|
|
457 | + != sizeof(SFNTHeaderRec))
|
|
458 | + return;
|
|
459 | + sfntHeader.formatTag = bigEndianToNative(sfntHeader.formatTag);
|
|
460 | + sfntHeader.numTables = bigEndianToNative(sfntHeader.numTables);
|
|
461 | + |
|
462 | + unsigned short validEntries = sfntHeader.numTables;
|
|
463 | +
|
|
464 | + if (sfntHeader.formatTag != TTAG_OTTO)
|
|
465 | + {
|
|
466 | + // TODO check SFNT Header
|
|
467 | + //checkSFNTHeader();
|
|
468 | + }
|
|
469 | + |
|
470 | + if (!file.seek(offset + 12))
|
|
471 | + return;
|
|
472 | + |
|
473 | + tempTables.resize(validEntries);
|
|
474 | + auto desiredLen = static_cast<long long>(validEntries * sizeof(TTTableRec));
|
|
475 | + auto readLen = file.read(reinterpret_cast<char*>(tempTables.data()), desiredLen);
|
|
476 | + if (readLen != desiredLen)
|
|
477 | + return;
|
|
478 | + |
|
479 | + for (auto& t : tempTables)
|
|
480 | + {
|
|
481 | + t.tag = bigEndianToNative(t.tag);
|
|
482 | + t.offset = bigEndianToNative(t.offset);
|
|
483 | + t.checksum = bigEndianToNative(t.checksum);
|
|
484 | + t.length = bigEndianToNative(t.length);
|
|
485 | + |
|
486 | + auto it = result.find(t.offset);
|
|
487 | + if (it == result.end())
|
|
488 | + {
|
|
489 | + auto emplaced = result.emplace(t.offset, SFNTTableInfo());
|
|
490 | + it = emplaced.first;
|
|
491 | + |
|
492 | + auto& info = it->second;
|
|
493 | + info.tag = t.tag;
|
|
494 | + info.length = t.length;
|
|
495 | + info.offset = t.offset;
|
|
496 | + info.sharedFaces.emplace(faceIndex);
|
|
497 | + info.valid = true;
|
|
498 | + }
|
|
499 | + else
|
|
500 | + {
|
|
501 | + it->second.sharedFaces.emplace(faceIndex);
|
|
502 | + // TODO check
|
|
503 | + }
|
|
504 | + }
|
|
505 | +}
|
|
506 | + |
|
507 | + |
|
508 | +void
|
|
509 | +SFNTTableInfo::getForAll(Engine* engine,
|
|
510 | + std::vector<SFNTTableInfo>& infos)
|
|
511 | +{
|
|
512 | + infos.clear();
|
|
513 | + auto size = engine->currentFtSize();
|
|
514 | + if (!size || !FT_IS_SFNT(size->face))
|
|
515 | + return;
|
|
516 | + |
|
517 | + auto index = engine->currentFontIndex();
|
|
518 | + auto& mgr = engine->fontFileManager();
|
|
519 | + if (index < 0 || index >= mgr.size())
|
|
520 | + return;
|
|
521 | + |
|
522 | + auto& fileInfo = mgr[index];
|
|
523 | + QFile file(fileInfo.filePath());
|
|
524 | + if (!file.open(QIODevice::ReadOnly))
|
|
525 | + return;
|
|
526 | + |
|
527 | + auto fileSize = file.size();
|
|
528 | + if (fileSize < 12)
|
|
529 | + return;
|
|
530 | + |
|
531 | + std::vector<TTTableRec> tables;
|
|
532 | + std::map<unsigned long, SFNTTableInfo> result;
|
|
533 | + |
|
534 | + TTCHeaderRec ttcHeader = {};
|
|
535 | + auto readLen = file.read(reinterpret_cast<char*>(&ttcHeader),
|
|
536 | + sizeof(TTCHeaderRec));
|
|
537 | + |
|
538 | + if (readLen != sizeof(TTCHeaderRec))
|
|
539 | + return;
|
|
540 | + |
|
541 | + ttcHeader.ttcTag = bigEndianToNative(ttcHeader.ttcTag);
|
|
542 | + ttcHeader.majorVersion = bigEndianToNative(ttcHeader.majorVersion);
|
|
543 | + ttcHeader.minorVersion = bigEndianToNative(ttcHeader.minorVersion);
|
|
544 | + ttcHeader.numFonts = bigEndianToNative(ttcHeader.numFonts);
|
|
545 | + |
|
546 | + if (ttcHeader.ttcTag == TTAG_ttcf
|
|
547 | + && (ttcHeader.majorVersion == 2 || ttcHeader.majorVersion == 1))
|
|
548 | + {
|
|
549 | + // Valid TTC file
|
|
550 | + std::unique_ptr<unsigned> offsets(new unsigned[ttcHeader.numFonts]);
|
|
551 | + auto desiredLen = static_cast<long long>(ttcHeader.numFonts
|
|
552 | + * sizeof(unsigned));
|
|
553 | + readLen = file.read(reinterpret_cast<char*>(offsets.get()), desiredLen);
|
|
554 | + if (readLen != desiredLen)
|
|
555 | + return;
|
|
556 | +
|
|
557 | + for (unsigned faceIndex = 0;
|
|
558 | + faceIndex < ttcHeader.numFonts;
|
|
559 | + faceIndex++)
|
|
560 | + {
|
|
561 | + auto offset = bigEndianToNative(offsets.get()[faceIndex]);
|
|
562 | + readSingleFace(file, offset, faceIndex, tables, result);
|
|
563 | + }
|
|
564 | + }
|
|
565 | + else
|
|
566 | + {
|
|
567 | + // Not TTC file, try single SFNT
|
|
568 | + if (!file.seek(0))
|
|
569 | + return;
|
|
570 | + readSingleFace(file, 0, 0, tables, result);
|
|
571 | + }
|
|
572 | + |
|
573 | + infos.reserve(result.size());
|
|
574 | + for (auto& pr : result)
|
|
575 | + infos.emplace_back(std::move(pr.second));
|
|
576 | +}
|
|
577 | + |
|
578 | + |
|
372 | 579 | // end of fontinfo.cpp |
... | ... | @@ -4,6 +4,7 @@ |
4 | 4 | |
5 | 5 | #pragma once
|
6 | 6 | |
7 | +#include <set>
|
|
7 | 8 | #include <QDateTime>
|
8 | 9 | #include <QByteArray>
|
9 | 10 | #include <QString>
|
... | ... | @@ -13,6 +14,38 @@ |
13 | 14 | |
14 | 15 | class Engine;
|
15 | 16 | |
17 | +struct SFNTTableInfo
|
|
18 | +{
|
|
19 | + unsigned long tag = 0;
|
|
20 | + unsigned long offset = 0;
|
|
21 | + unsigned long length = 0;
|
|
22 | + bool valid = false;
|
|
23 | + std::set<unsigned long> sharedFaces;
|
|
24 | + |
|
25 | + static void getForAll(Engine* engine, std::vector<SFNTTableInfo>& infos);
|
|
26 | + |
|
27 | + |
|
28 | + friend bool
|
|
29 | + operator==(const SFNTTableInfo& lhs,
|
|
30 | + const SFNTTableInfo& rhs)
|
|
31 | + {
|
|
32 | + return lhs.tag == rhs.tag
|
|
33 | + && lhs.offset == rhs.offset
|
|
34 | + && lhs.length == rhs.length
|
|
35 | + && lhs.valid == rhs.valid
|
|
36 | + && lhs.sharedFaces == rhs.sharedFaces;
|
|
37 | + }
|
|
38 | + |
|
39 | + |
|
40 | + friend bool
|
|
41 | + operator!=(const SFNTTableInfo& lhs,
|
|
42 | + const SFNTTableInfo& rhs)
|
|
43 | + {
|
|
44 | + return !(lhs == rhs);
|
|
45 | + }
|
|
46 | +};
|
|
47 | + |
|
48 | + |
|
16 | 49 | struct SFNTName
|
17 | 50 | {
|
18 | 51 | unsigned short nameID;
|
... | ... | @@ -270,6 +270,113 @@ SFNTNameModel::headerData(int section, |
270 | 270 | }
|
271 | 271 | |
272 | 272 | |
273 | +QString
|
|
274 | +tagToString(unsigned long tag)
|
|
275 | +{
|
|
276 | + QString str(4, '0');
|
|
277 | + str[0] = static_cast<char>(tag >> 24);
|
|
278 | + str[1] = static_cast<char>(tag >> 16);
|
|
279 | + str[2] = static_cast<char>(tag >> 8);
|
|
280 | + str[3] = static_cast<char>(tag);
|
|
281 | + return str;
|
|
282 | +}
|
|
283 | + |
|
284 | + |
|
285 | +int
|
|
286 | +SFNTTableInfoModel::rowCount(const QModelIndex& parent) const
|
|
287 | +{
|
|
288 | + if (parent.isValid())
|
|
289 | + return 0;
|
|
290 | + return static_cast<int>(storage_.size());
|
|
291 | +}
|
|
292 | + |
|
293 | + |
|
294 | +int
|
|
295 | +SFNTTableInfoModel::columnCount(const QModelIndex& parent) const
|
|
296 | +{
|
|
297 | + if (parent.isValid())
|
|
298 | + return 0;
|
|
299 | + return STIM_Max;
|
|
300 | +}
|
|
301 | + |
|
302 | + |
|
303 | +QVariant
|
|
304 | +SFNTTableInfoModel::data(const QModelIndex& index,
|
|
305 | + int role) const
|
|
306 | +{
|
|
307 | + if (index.row() < 0 || index.column() < 0)
|
|
308 | + return {};
|
|
309 | + auto r = static_cast<size_t>(index.row());
|
|
310 | + if (role != Qt::DisplayRole || r > storage_.size())
|
|
311 | + return {};
|
|
312 | + |
|
313 | + auto& obj = storage_[r];
|
|
314 | + switch (static_cast<Columns>(index.column()))
|
|
315 | + {
|
|
316 | + case STIM_Tag:
|
|
317 | + return tagToString(obj.tag);
|
|
318 | + case STIM_Offset:
|
|
319 | + return static_cast<unsigned long long>(obj.offset);
|
|
320 | + case STIM_Length:
|
|
321 | + return static_cast<unsigned long long>(obj.length);
|
|
322 | + case STIM_Valid:
|
|
323 | + return obj.valid;
|
|
324 | + case STIM_SharedFaces:
|
|
325 | + if (obj.sharedFaces.empty())
|
|
326 | + return "[]";
|
|
327 | + {
|
|
328 | + auto result = QString('[') + QString::number(*obj.sharedFaces.begin());
|
|
329 | + for (auto it = std::next(obj.sharedFaces.begin());
|
|
330 | + it != obj.sharedFaces.end();
|
|
331 | + ++it)
|
|
332 | + {
|
|
333 | + auto xStr = QString::number(*it);
|
|
334 | + result.reserve(result.length() + xStr.length() + 2);
|
|
335 | + result += ", ";
|
|
336 | + result += xStr;
|
|
337 | + }
|
|
338 | + result += ']';
|
|
339 | + return result;
|
|
340 | + }
|
|
341 | + default:
|
|
342 | + break;
|
|
343 | + }
|
|
344 | + |
|
345 | + return {};
|
|
346 | +}
|
|
347 | + |
|
348 | + |
|
349 | +QVariant
|
|
350 | +SFNTTableInfoModel::headerData(int section,
|
|
351 | + Qt::Orientation orientation,
|
|
352 | + int role) const
|
|
353 | +{
|
|
354 | + if (role != Qt::DisplayRole)
|
|
355 | + return {};
|
|
356 | + if (orientation == Qt::Vertical)
|
|
357 | + return section;
|
|
358 | + if (orientation != Qt::Horizontal)
|
|
359 | + return {};
|
|
360 | + |
|
361 | + switch (static_cast<Columns>(section))
|
|
362 | + {
|
|
363 | + case STIM_Tag:
|
|
364 | + return "Tag";
|
|
365 | + case STIM_Offset:
|
|
366 | + return "Offset";
|
|
367 | + case STIM_Length:
|
|
368 | + return "Length";
|
|
369 | + case STIM_Valid:
|
|
370 | + return "Valid";
|
|
371 | + case STIM_SharedFaces:
|
|
372 | + return "Subfont Indices";
|
|
373 | + default:;
|
|
374 | + }
|
|
375 | + |
|
376 | + return {};
|
|
377 | +}
|
|
378 | + |
|
379 | + |
|
273 | 380 | int
|
274 | 381 | MMGXAxisInfoModel::rowCount(const QModelIndex& parent) const
|
275 | 382 | {
|
... | ... | @@ -302,11 +409,7 @@ MMGXAxisInfoModel::data(const QModelIndex& index, |
302 | 409 | switch (static_cast<Columns>(index.column()))
|
303 | 410 | {
|
304 | 411 | case MAIM_Tag:
|
305 | - {
|
|
306 | - auto str = QString::fromUtf8(reinterpret_cast<const char*>(&obj.tag), 4);
|
|
307 | - std::reverse(str.begin(), str.end());
|
|
308 | - return str;
|
|
309 | - }
|
|
412 | + return tagToString(obj.tag);
|
|
310 | 413 | case MAIM_Minimum:
|
311 | 414 | return obj.minimum;
|
312 | 415 | case MAIM_Default:
|
... | ... | @@ -128,6 +128,43 @@ private: |
128 | 128 | };
|
129 | 129 | |
130 | 130 | |
131 | +class SFNTTableInfoModel
|
|
132 | +: public QAbstractTableModel
|
|
133 | +{
|
|
134 | + Q_OBJECT
|
|
135 | +public:
|
|
136 | + explicit SFNTTableInfoModel(QObject* parent) : QAbstractTableModel(parent) {}
|
|
137 | + ~SFNTTableInfoModel() override = default;
|
|
138 | + |
|
139 | + int rowCount(const QModelIndex& parent) const override;
|
|
140 | + int columnCount(const QModelIndex& parent) const override;
|
|
141 | + QVariant data(const QModelIndex& index,
|
|
142 | + int role) const override;
|
|
143 | + QVariant headerData(int section,
|
|
144 | + Qt::Orientation orientation,
|
|
145 | + int role) const override;
|
|
146 | + |
|
147 | + // Same to `FixedSizeInfoModel`
|
|
148 | + void beginModelUpdate() { beginResetModel(); }
|
|
149 | + void endModelUpdate() { endResetModel(); }
|
|
150 | + std::vector<SFNTTableInfo>& storage() { return storage_; }
|
|
151 | + |
|
152 | + enum Columns : int
|
|
153 | + {
|
|
154 | + STIM_Tag = 0,
|
|
155 | + STIM_Offset,
|
|
156 | + STIM_Length,
|
|
157 | + STIM_Valid,
|
|
158 | + STIM_SharedFaces,
|
|
159 | + STIM_Max
|
|
160 | + };
|
|
161 | + |
|
162 | +private:
|
|
163 | + // Don't let the item count exceed INT_MAX!
|
|
164 | + std::vector<SFNTTableInfo> storage_;
|
|
165 | +};
|
|
166 | + |
|
167 | + |
|
131 | 168 | class MMGXAxisInfoModel
|
132 | 169 | : public QAbstractTableModel
|
133 | 170 | {
|
... | ... | @@ -424,6 +424,13 @@ SFNTInfoTab::reloadFont() |
424 | 424 | sfntNamesModel_->storage() = engine_->currentFontSFNTNames();
|
425 | 425 | sfntNamesModel_->endModelUpdate();
|
426 | 426 | }
|
427 | + |
|
428 | + if (engine_->currentFontSFNTTableInfo() != sfntTablesModel_->storage())
|
|
429 | + {
|
|
430 | + sfntTablesModel_->beginModelUpdate();
|
|
431 | + sfntTablesModel_->storage() = engine_->currentFontSFNTTableInfo();
|
|
432 | + sfntTablesModel_->endModelUpdate();
|
|
433 | + }
|
|
427 | 434 | }
|
428 | 435 | |
429 | 436 | |
... | ... | @@ -444,10 +451,13 @@ SFNTInfoTab::createLayout() |
444 | 451 | header->setSectionResizeMode(QHeaderView::Fixed);
|
445 | 452 | sfntNamesTable_->horizontalHeader()->setStretchLastSection(true);
|
446 | 453 | |
454 | + sfntTablesModel_ = new SFNTTableInfoModel(this);
|
|
455 | + sfntTablesTable_->setModel(sfntTablesModel_);
|
|
447 | 456 | header = sfntTablesTable_->verticalHeader();
|
448 | 457 | // This will force the minimal size to be used
|
449 | 458 | header->setDefaultSectionSize(0);
|
450 | 459 | header->setSectionResizeMode(QHeaderView::Fixed);
|
460 | + sfntTablesTable_->horizontalHeader()->setStretchLastSection(true);
|
|
451 | 461 | |
452 | 462 | sfntNamesLayout_ = new QHBoxLayout;
|
453 | 463 | sfntTablesLayout_ = new QHBoxLayout;
|
... | ... | @@ -172,6 +172,7 @@ private: |
172 | 172 | QTableView* sfntTablesTable_;
|
173 | 173 | |
174 | 174 | SFNTNameModel* sfntNamesModel_;
|
175 | + SFNTTableInfoModel* sfntTablesModel_;
|
|
175 | 176 | |
176 | 177 | QHBoxLayout* sfntNamesLayout_;
|
177 | 178 | QHBoxLayout* sfntTablesLayout_;
|