Ignore:
Timestamp:
Oct 16, 2009, 3:22:12 PM (16 years ago)
Author:
Dmitry A. Kuminov
Message:

gui: Implemented simple font caching through the QSettings registry. This gets upto 10x faster font population times at application startup and when unknown families are requested.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/gui/text/qfontdatabase_pm.cpp

    r228 r230  
    4646#include "qfontengine_pm_p.h"
    4747
     48#include "qsettings.h"
     49#include "qfileinfo.h"
     50#include "qdatetime.h"
     51#include "qhash.h"
     52
    4853#include <ft2build.h>
    4954#include FT_FREETYPE_H
     
    5358
    5459QT_BEGIN_NAMESPACE
     60
     61struct FaceData
     62{
     63    int index;
     64    QString familyName;
     65    QtFontStyle::Key styleKey;
     66    QList<QFontDatabase::WritingSystem> systems;
     67    bool fixedPitch;
     68    bool smoothScalable;
     69    QList<unsigned short> pixelSizes;
     70};
     71
     72static QDataStream &operator<<(QDataStream &data, const QFontDatabase::WritingSystem &ws)
     73{
     74    data << (int)ws;
     75    return data;
     76}
     77
     78static QDataStream &operator>>(QDataStream &data, QFontDatabase::WritingSystem &ws)
     79{
     80    data >> (int&)ws;
     81    return data;
     82}
     83
     84static QDataStream &operator<<(QDataStream &data, const FaceData &cached)
     85{
     86    data << cached.familyName;
     87    data << cached.styleKey.style << cached.styleKey.weight
     88         << cached.styleKey.stretch;
     89    data << cached.systems;
     90    data << cached.fixedPitch << cached.smoothScalable;
     91    data << cached.pixelSizes;
     92    return data;
     93}
     94
     95static QDataStream &operator>>(QDataStream &data, FaceData &cached)
     96{
     97    data >> cached.familyName;
     98    uint style;
     99    int weight, stretch;
     100    data >> style; cached.styleKey.style = style;
     101    data >> weight; cached.styleKey.weight = weight;
     102    data >> stretch; cached.styleKey.stretch = stretch;
     103    data >> cached.systems;
     104    data >> cached.fixedPitch >> cached.smoothScalable;
     105    data >> cached.pixelSizes;
     106    return data;
     107}
     108
     109struct FileData
     110{
     111    FileData() : seen(false) {}
     112    FileData(const QFileInfo &fi, bool s) : fileInfo(fi), seen(s) {}
     113
     114    QFileInfo fileInfo;
     115    bool seen;
     116};
     117
     118typedef QHash<QString, FileData> FontFileHash;
     119static FontFileHash knownFontFiles;
     120static bool knownFontFilesInitialized = false;
    55121
    56122static void populateDatabase(const QString& fam)
     
    72138    const QString foundryName;
    73139
    74     QList<QByteArray> fontFiles;
     140#ifdef QFONTDATABASE_DEBUG
     141    QTime timer;
     142    timer.start();
     143#endif
     144
     145    QSettings fontCache(QSettings::UserScope, QLatin1String("Trolltech"));
     146    fontCache.beginGroup(QLatin1String("Qt/Fonts/Cache 1.0"));
     147
     148    if (!knownFontFilesInitialized) {
     149        // get the initial list of know font files from the cache (necessary to
     150        // detect deleted font files)
     151        knownFontFilesInitialized = true;
     152        QStringList files = fontCache.childGroups();
     153        foreach(QString file, files) {
     154            file.replace("|", "/");
     155            knownFontFiles.insert(file, FileData());
     156            // note that QFileInfo is empty so the file will be considered as
     157            // NEW which is necessary for the font to get into the database
     158        }
     159    } else {
     160        // reset the 'seen' flag
     161        for (FontFileHash::iterator it = knownFontFiles.begin();
     162             it != knownFontFiles.end(); ++it)
     163            it.value().seen = false;
     164    }
     165
     166    QList<QFileInfo> fontFiles;
    75167
    76168    ULONG bufSize = 0;
     
    101193                                file.append(".PFB");
    102194                            }
    103                             fontFiles << file;
     195                            QFileInfo fileInfo(QFile::decodeName(file));
     196                            QString fileName = fileInfo.canonicalFilePath().toLower();
     197                            if (!fileName.isEmpty()) { // file may have been deleted
     198                                fileInfo.setFile(fileName);
     199                                // check the in-process file name cache
     200                                FileData &cached = knownFontFiles[fileName];
     201                                if (cached.fileInfo.filePath().isEmpty() ||
     202                                    cached.fileInfo.lastModified() != fileInfo.lastModified() ||
     203                                    cached.fileInfo.size() != fileInfo.size()) {
     204                                    // no cache entry or outdated, process it
     205                                    cached.fileInfo = fileInfo;
     206                                    cached.seen = true;
     207                                    fontFiles << fileInfo;
     208                                    FD_DEBUG("populateDatabase: NEW/UPDATED font file %s",
     209                                             qPrintable(fileName));
     210                                } else {
     211                                    // just set the 'seen' flag and skip this font
     212                                    // (it's already in the database)
     213                                    knownFontFiles[fileName].seen = true;
     214                                    FD_DEBUG("populateDatabase: UNCHANGED font file %s",
     215                                             qPrintable(fileName));
     216                                }
     217                            }
    104218                        }
    105219                    }
     
    114228    FT_Library lib = qt_getFreetype();
    115229
    116     foreach(const QByteArray &file, fontFiles) {
    117         FT_Face face;
    118         FT_Error rc = FT_New_Face(lib, file, -1, &face);
    119 
    120         FD_DEBUG("populateDatabase: Font file %s: FT error %d, has %ld faces",
    121                  file.constData(), (int) rc, rc ? -1 : face->num_faces);
    122         if (rc != 0)
    123             continue;
    124 
    125         FT_Long numFaces = face->num_faces;
    126         FT_Done_Face(face);
    127 
    128         // go throuhg each face
    129         for (FT_Long idx = 0; idx < numFaces; ++idx) {
    130             rc = FT_New_Face(lib, file, idx, &face);
    131             if (rc != 0)
    132                 continue;
    133 
    134             QString familyName = QString::fromLatin1(face->family_name);
    135 
    136             // familyName may contain extra spaces (at least this is true for
    137             // TNR.PFB that is reported as "Times New Roman ". Trim them.
    138             familyName = familyName.trimmed();
    139 
    140             QtFontStyle::Key styleKey;
    141 
    142             styleKey.style = face->style_flags & FT_STYLE_FLAG_ITALIC ?
    143                 QFont::StyleItalic : QFont::StyleNormal;
    144 
    145             QList<QFontDatabase::WritingSystem> systems;
    146 
    147             TT_OS2 *os2_table = 0;
    148             if (face->face_flags & FT_FACE_FLAG_SFNT) {
    149                 os2_table = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
    150             }
    151             if (os2_table) {
    152                 // map weight and width values
    153                 if (os2_table->usWeightClass < 400)
    154                     styleKey.weight = QFont::Light;
    155                 else if (os2_table->usWeightClass < 600)
    156                     styleKey.weight = QFont::Normal;
    157                 else if (os2_table->usWeightClass < 700)
    158                     styleKey.weight = QFont::DemiBold;
    159                 else if (os2_table->usWeightClass < 800)
    160                     styleKey.weight = QFont::Bold;
    161                 else
    162                     styleKey.weight = QFont::Black;
    163 
    164                 switch (os2_table->usWidthClass) {
    165                     case 1: styleKey.stretch = QFont::UltraCondensed; break;
    166                     case 2: styleKey.stretch = QFont::ExtraCondensed; break;
    167                     case 3: styleKey.stretch = QFont::Condensed; break;
    168                     case 4: styleKey.stretch = QFont::SemiCondensed; break;
    169                     case 5: styleKey.stretch = QFont::Unstretched; break;
    170                     case 6: styleKey.stretch = QFont::SemiExpanded; break;
    171                     case 7: styleKey.stretch = QFont::Expanded; break;
    172                     case 8: styleKey.stretch = QFont::ExtraExpanded; break;
    173                     case 9: styleKey.stretch = QFont::UltraExpanded; break;
    174                     default: styleKey.stretch = QFont::Unstretched; break;
     230    // go through each font file and get available faces
     231    foreach(const QFileInfo &fileInfo, fontFiles) {
     232        QString fileKey = fileInfo.absoluteFilePath().toLower();
     233        QByteArray file = QFile::encodeName(fileKey);
     234
     235        // QSettings uses / for splitting into groups, suppress it
     236        fileKey.replace("/", "|");
     237
     238        QList<FaceData> cachedFaces;
     239
     240        // first, look up the cached data
     241        fontCache.beginGroup(fileKey);
     242
     243        if (fontCache.value(QLatin1String("DateTime")).toDateTime() != fileInfo.lastModified() ||
     244            fontCache.value(QLatin1String("Size")).toUInt() != fileInfo.size()) {
     245            // the cache is outdated or doesn't exist, query the font file
     246
     247            FT_Long numFaces = 0;
     248            FT_Face face;
     249
     250            FT_Error rc = FT_New_Face(lib, file, -1, &face);
     251            if (rc == 0) {
     252                numFaces = face->num_faces;
     253                FT_Done_Face(face);
     254            } else {
     255                // invalid/unsupported font file, numFaces is left 0 so that
     256                // only DateTime and Size will be cached indicating that this
     257                // file is not recognized
     258            }
     259
     260            FD_DEBUG("populateDatabase: Font file %s: FT error %d, has %ld faces",
     261                     file.constData(), (int) rc, numFaces);
     262
     263            // go throuhg each face
     264            for (FT_Long idx = 0; idx < numFaces; ++idx) {
     265                rc = FT_New_Face(lib, file, idx, &face);
     266                if (rc != 0)
     267                    continue;
     268
     269                FaceData cached;
     270
     271                cached.index = idx;
     272                cached.familyName = QString::fromLatin1(face->family_name);
     273
     274                // familyName may contain extra spaces (at least this is true for
     275                // TNR.PFB that is reported as "Times New Roman ". Trim them.
     276                cached.familyName = cached.familyName.trimmed();
     277
     278                cached.styleKey.style = face->style_flags & FT_STYLE_FLAG_ITALIC ?
     279                    QFont::StyleItalic : QFont::StyleNormal;
     280
     281                TT_OS2 *os2_table = 0;
     282                if (face->face_flags & FT_FACE_FLAG_SFNT) {
     283                    os2_table = (TT_OS2 *)FT_Get_Sfnt_Table(face, ft_sfnt_os2);
    175284                }
    176 
    177                 quint32 unicodeRange[4] = {
    178                     os2_table->ulUnicodeRange1, os2_table->ulUnicodeRange2,
    179                     os2_table->ulUnicodeRange3, os2_table->ulUnicodeRange4
    180                 };
    181                 quint32 codePageRange[2] = {
    182                     os2_table->ulCodePageRange1, os2_table->ulCodePageRange2
    183                 };
    184                 systems = determineWritingSystemsFromTrueTypeBits(unicodeRange, codePageRange);
    185             } else {
    186                 // we've only got simple weight information and no stretch
    187                 styleKey.weight = face->style_flags & FT_STYLE_FLAG_BOLD ?
    188                     QFont::Bold : QFont::Normal;
    189                 styleKey.stretch = QFont::Unstretched;
    190             }
    191 
    192             QtFontFamily *family = privateDb()->family(familyName, true);
     285                if (os2_table) {
     286                    // map weight and width values
     287                    if (os2_table->usWeightClass < 400)
     288                        cached.styleKey.weight = QFont::Light;
     289                    else if (os2_table->usWeightClass < 600)
     290                        cached.styleKey.weight = QFont::Normal;
     291                    else if (os2_table->usWeightClass < 700)
     292                        cached.styleKey.weight = QFont::DemiBold;
     293                    else if (os2_table->usWeightClass < 800)
     294                        cached.styleKey.weight = QFont::Bold;
     295                    else
     296                        cached.styleKey.weight = QFont::Black;
     297
     298                    switch (os2_table->usWidthClass) {
     299                        case 1: cached.styleKey.stretch = QFont::UltraCondensed; break;
     300                        case 2: cached.styleKey.stretch = QFont::ExtraCondensed; break;
     301                        case 3: cached.styleKey.stretch = QFont::Condensed; break;
     302                        case 4: cached.styleKey.stretch = QFont::SemiCondensed; break;
     303                        case 5: cached.styleKey.stretch = QFont::Unstretched; break;
     304                        case 6: cached.styleKey.stretch = QFont::SemiExpanded; break;
     305                        case 7: cached.styleKey.stretch = QFont::Expanded; break;
     306                        case 8: cached.styleKey.stretch = QFont::ExtraExpanded; break;
     307                        case 9: cached.styleKey.stretch = QFont::UltraExpanded; break;
     308                        default: cached.styleKey.stretch = QFont::Unstretched; break;
     309                    }
     310
     311                    quint32 unicodeRange[4] = {
     312                        os2_table->ulUnicodeRange1, os2_table->ulUnicodeRange2,
     313                        os2_table->ulUnicodeRange3, os2_table->ulUnicodeRange4
     314                    };
     315                    quint32 codePageRange[2] = {
     316                        os2_table->ulCodePageRange1, os2_table->ulCodePageRange2
     317                    };
     318                    cached.systems =
     319                        determineWritingSystemsFromTrueTypeBits(unicodeRange, codePageRange);
     320                } else {
     321                    // we've only got simple weight information and no stretch
     322                    cached.styleKey.weight = face->style_flags & FT_STYLE_FLAG_BOLD ?
     323                        QFont::Bold : QFont::Normal;
     324                    cached.styleKey.stretch = QFont::Unstretched;
     325                }
     326
     327                cached.fixedPitch = face->face_flags & FT_FACE_FLAG_FIXED_WIDTH;
     328
     329                cached.smoothScalable = face->face_flags & FT_FACE_FLAG_SCALABLE;
     330
     331                // the font may both be scalable and contain fixed size bitmaps
     332                if (face->face_flags & FT_FACE_FLAG_FIXED_SIZES) {
     333                    for (FT_Int i = 0; i < face->num_fixed_sizes; ++i) {
     334                        cached.pixelSizes << face->available_sizes[i].height;
     335                    }
     336                }
     337
     338                cachedFaces << cached;
     339
     340                FT_Done_Face(face);
     341            }
     342
     343            // store the data into the cache
     344            fontCache.setValue(QLatin1String("DateTime"), fileInfo.lastModified());
     345            fontCache.setValue(QLatin1String("Size"), fileInfo.size());
     346            foreach(FaceData cached, cachedFaces) {
     347                QByteArray rawData;
     348                QDataStream data(&rawData, QIODevice::WriteOnly);
     349                data << cached;
     350
     351                QString face = QString::number(cached.index);
     352                fontCache.beginGroup(face);
     353                fontCache.setValue(QLatin1String("Info"), rawData);
     354                fontCache.endGroup();
     355            }
     356        } else {
     357            // take the face data from the cache
     358
     359            QStringList faces = fontCache.childGroups();
     360
     361            FD_DEBUG("populateDatabase: Font file %s: IN CACHE, has %d faces",
     362                     file.constData(), faces.count());
     363
     364            foreach(QString face, faces) {
     365                bool ok = false;
     366                FaceData cached;
     367                cached.index = face.toInt(&ok);
     368                if (!ok || cached.index < 0) // not a valid index
     369                    continue;
     370
     371                fontCache.beginGroup(face);
     372                QByteArray rawData =
     373                    fontCache.value(QLatin1String("Info")).toByteArray();
     374                QDataStream data(rawData);
     375                data >> cached;
     376                fontCache.endGroup();
     377
     378                cachedFaces << cached;
     379            }
     380        }
     381
     382        fontCache.endGroup();
     383
     384        // go throuhg each cached face and add it to the database
     385        foreach(FaceData cached, cachedFaces) {
     386
     387            QtFontFamily *family = privateDb()->family(cached.familyName, true);
    193388
    194389            // @todo is it possible that the same family is both fixed and not?
    195             Q_ASSERT(!family->fixedPitch || face->face_flags & FT_FACE_FLAG_FIXED_WIDTH);
    196             family->fixedPitch = face->face_flags & FT_FACE_FLAG_FIXED_WIDTH;
    197 
    198             if (systems.isEmpty() || familyName.compare("Workplace Sans")) {
     390            Q_ASSERT(!family->fixedPitch || cached.fixedPitch);
     391            family->fixedPitch = cached.fixedPitch;
     392
     393            if (cached.systems.isEmpty() || cached.familyName.compare("Workplace Sans")) {
    199394                // it was hard or impossible to determine the actual writing system
    200395                // of the font (as in case of OS/2 bitmap and PFB fonts for which it is
     
    210405                    family->writingSystems[ws] = QtFontFamily::Supported;
    211406            } else {
    212                 for (int i = 0; i < systems.count(); ++i)
    213                     family->writingSystems[systems.at(i)] = QtFontFamily::Supported;
     407                for (int i = 0; i < cached.systems.count(); ++i)
     408                    family->writingSystems[cached.systems.at(i)] = QtFontFamily::Supported;
    214409            }
    215410
    216411            QtFontFoundry *foundry = family->foundry(foundryName, true);
    217             QtFontStyle *style = foundry->style(styleKey, true);
     412            QtFontStyle *style = foundry->style(cached.styleKey, true);
    218413
    219414            // so far, all recognized fonts are antialiased
    220415            style->antialiased = true;
    221416
    222             if ((face->face_flags & FT_FACE_FLAG_SCALABLE) &&
    223                 !style->smoothScalable) {
     417            if (cached.smoothScalable && !style->smoothScalable) {
    224418                // add new scalable style only if it hasn't been already added --
    225419                // the first one of two duplicate (in Qt terms) non-bitmap font
     
    229423                    style->pixelSize(SMOOTH_SCALABLE, true);
    230424                size->fileName = file;
    231                 size->fileIndex = idx;
    232             }
    233 
    234             // the font may both be scalable and contain fixed size bitmaps
    235             if (face->face_flags & FT_FACE_FLAG_FIXED_SIZES) {
    236                 for (FT_Int i = 0; i < face->num_fixed_sizes; ++i) {
    237                     QtFontSize *size =
    238                         style->pixelSize(face->available_sizes[i].height, true);
    239                     // the first bitmap style with a given pixel and point size wins
    240                     if (!size->fileName.isEmpty())
    241                         continue;
    242                     size->fileName = file;
    243                     size->fileIndex = idx;
    244                 }
    245             }
    246 
    247             FT_Done_Face(face);
    248         }
    249     }
     425                size->fileIndex = cached.index;
     426            }
     427
     428            foreach(unsigned short pixelSize, cached.pixelSizes) {
     429                QtFontSize *size = style->pixelSize(pixelSize, true);
     430                // the first bitmap style with a given pixel and point size wins
     431                if (!size->fileName.isEmpty())
     432                    continue;
     433                size->fileName = file;
     434                size->fileIndex = cached.index;
     435            }
     436        }
     437    }
     438
     439    // go through the known file list to detect what files have been removed
     440    for (FontFileHash::iterator it = knownFontFiles.begin();
     441         it != knownFontFiles.end();) {
     442        if (!it.value().seen) {
     443            FD_DEBUG("populateDatabase: DELETED font file %s",
     444                     qPrintable(it.key()));
     445            // remove from the both caches
     446            QString fileKey = it.key();
     447            fileKey.replace("/", "|");
     448            fontCache.remove(fileKey);
     449            it = knownFontFiles.erase(it);
     450            // @todo should we remove all references to this file from the
     451            // font database? My concern is that this font may be in use by Qt
     452            // and its glyphs may be still cached when file deletion happens
     453        } else {
     454            ++it;
     455        }
     456    }
     457
     458#ifdef QFONTDATABASE_DEBUG
     459    FD_DEBUG("populateDatabase: took %d ms", timer.elapsed());
     460#endif
    250461}
    251462
Note: See TracChangeset for help on using the changeset viewer.