| 1 | /****************************************************************************
|
|---|
| 2 | **
|
|---|
| 3 | ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
|
|---|
| 4 | ** All rights reserved.
|
|---|
| 5 | ** Contact: Nokia Corporation ([email protected])
|
|---|
| 6 | **
|
|---|
| 7 | ** This file is part of the QtGui module of the Qt Toolkit.
|
|---|
| 8 | **
|
|---|
| 9 | ** $QT_BEGIN_LICENSE:LGPL$
|
|---|
| 10 | ** Commercial Usage
|
|---|
| 11 | ** Licensees holding valid Qt Commercial licenses may use this file in
|
|---|
| 12 | ** accordance with the Qt Commercial License Agreement provided with the
|
|---|
| 13 | ** Software or, alternatively, in accordance with the terms contained in
|
|---|
| 14 | ** a written agreement between you and Nokia.
|
|---|
| 15 | **
|
|---|
| 16 | ** GNU Lesser General Public License Usage
|
|---|
| 17 | ** Alternatively, this file may be used under the terms of the GNU Lesser
|
|---|
| 18 | ** General Public License version 2.1 as published by the Free Software
|
|---|
| 19 | ** Foundation and appearing in the file LICENSE.LGPL included in the
|
|---|
| 20 | ** packaging of this file. Please review the following information to
|
|---|
| 21 | ** ensure the GNU Lesser General Public License version 2.1 requirements
|
|---|
| 22 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|---|
| 23 | **
|
|---|
| 24 | ** In addition, as a special exception, Nokia gives you certain additional
|
|---|
| 25 | ** rights. These rights are described in the Nokia Qt LGPL Exception
|
|---|
| 26 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
|
|---|
| 27 | **
|
|---|
| 28 | ** GNU General Public License Usage
|
|---|
| 29 | ** Alternatively, this file may be used under the terms of the GNU
|
|---|
| 30 | ** General Public License version 3.0 as published by the Free Software
|
|---|
| 31 | ** Foundation and appearing in the file LICENSE.GPL included in the
|
|---|
| 32 | ** packaging of this file. Please review the following information to
|
|---|
| 33 | ** ensure the GNU General Public License version 3.0 requirements will be
|
|---|
| 34 | ** met: http://www.gnu.org/copyleft/gpl.html.
|
|---|
| 35 | **
|
|---|
| 36 | ** If you have questions regarding the use of this file, please contact
|
|---|
| 37 | ** Nokia at [email protected].
|
|---|
| 38 | ** $QT_END_LICENSE$
|
|---|
| 39 | **
|
|---|
| 40 | ****************************************************************************/
|
|---|
| 41 |
|
|---|
| 42 | #include <private/qapplication_p.h>
|
|---|
| 43 | #include "qdir.h"
|
|---|
| 44 | #include "qfont_p.h"
|
|---|
| 45 | #include "qfontengine_s60_p.h"
|
|---|
| 46 | #include "qabstractfileengine.h"
|
|---|
| 47 | #include "qdesktopservices.h"
|
|---|
| 48 | #include "qtemporaryfile.h"
|
|---|
| 49 | #include "qtextcodec.h"
|
|---|
| 50 | #include <private/qpixmap_s60_p.h>
|
|---|
| 51 | #include <private/qt_s60_p.h>
|
|---|
| 52 | #include "qendian.h"
|
|---|
| 53 | #include <private/qcore_symbian_p.h>
|
|---|
| 54 | #ifdef QT_NO_FREETYPE
|
|---|
| 55 | #include <openfont.h>
|
|---|
| 56 | #ifdef SYMBIAN_ENABLE_SPLIT_HEADERS
|
|---|
| 57 | #include <graphics/openfontrasterizer.h> // COpenFontRasterizer has moved to a new header file
|
|---|
| 58 | #endif // SYMBIAN_ENABLE_SPLIT_HEADERS
|
|---|
| 59 | #endif // QT_NO_FREETYPE
|
|---|
| 60 |
|
|---|
| 61 | QT_BEGIN_NAMESPACE
|
|---|
| 62 |
|
|---|
| 63 | QStringList qt_symbian_fontFamiliesOnFontServer() // Also used in qfont_s60.cpp
|
|---|
| 64 | {
|
|---|
| 65 | QStringList result;
|
|---|
| 66 | QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock);
|
|---|
| 67 | const int numTypeFaces = S60->screenDevice()->NumTypefaces();
|
|---|
| 68 | for (int i = 0; i < numTypeFaces; i++) {
|
|---|
| 69 | TTypefaceSupport typefaceSupport;
|
|---|
| 70 | S60->screenDevice()->TypefaceSupport(typefaceSupport, i);
|
|---|
| 71 | const QString familyName((const QChar *)typefaceSupport.iTypeface.iName.Ptr(), typefaceSupport.iTypeface.iName.Length());
|
|---|
| 72 | result.append(familyName);
|
|---|
| 73 | }
|
|---|
| 74 | lock.relock();
|
|---|
| 75 | return result;
|
|---|
| 76 | }
|
|---|
| 77 |
|
|---|
| 78 | QFileInfoList alternativeFilePaths(const QString &path, const QStringList &nameFilters,
|
|---|
| 79 | QDir::Filters filters = QDir::NoFilter, QDir::SortFlags sort = QDir::NoSort,
|
|---|
| 80 | bool uniqueFileNames = true)
|
|---|
| 81 | {
|
|---|
| 82 | QFileInfoList result;
|
|---|
| 83 |
|
|---|
| 84 | // Prepare a 'soft to hard' drive list: W:, X: ... A:, Z:
|
|---|
| 85 | QStringList driveStrings;
|
|---|
| 86 | foreach (const QFileInfo &drive, QDir::drives())
|
|---|
| 87 | driveStrings.append(drive.absolutePath());
|
|---|
| 88 | driveStrings.sort();
|
|---|
| 89 | const QString zDriveString(QLatin1String("Z:/"));
|
|---|
| 90 | driveStrings.removeAll(zDriveString);
|
|---|
| 91 | driveStrings.prepend(zDriveString);
|
|---|
| 92 |
|
|---|
| 93 | QStringList uniqueFileNameList;
|
|---|
| 94 | for (int i = driveStrings.count() - 1; i >= 0; --i) {
|
|---|
| 95 | const QDir dirOnDrive(driveStrings.at(i) + path);
|
|---|
| 96 | const QFileInfoList entriesOnDrive = dirOnDrive.entryInfoList(nameFilters, filters, sort);
|
|---|
| 97 | if (uniqueFileNames) {
|
|---|
| 98 | foreach(const QFileInfo &entry, entriesOnDrive) {
|
|---|
| 99 | if (!uniqueFileNameList.contains(entry.fileName())) {
|
|---|
| 100 | uniqueFileNameList.append(entry.fileName());
|
|---|
| 101 | result.append(entry);
|
|---|
| 102 | }
|
|---|
| 103 | }
|
|---|
| 104 | } else {
|
|---|
| 105 | result.append(entriesOnDrive);
|
|---|
| 106 | }
|
|---|
| 107 | }
|
|---|
| 108 | return result;
|
|---|
| 109 | }
|
|---|
| 110 |
|
|---|
| 111 | #ifdef QT_NO_FREETYPE
|
|---|
| 112 | class QSymbianFontDatabaseExtrasImplementation : public QSymbianFontDatabaseExtras
|
|---|
| 113 | {
|
|---|
| 114 | public:
|
|---|
| 115 | QSymbianFontDatabaseExtrasImplementation();
|
|---|
| 116 | ~QSymbianFontDatabaseExtrasImplementation();
|
|---|
| 117 |
|
|---|
| 118 | const QSymbianTypeFaceExtras *extras(const QString &typeface, bool bold, bool italic) const;
|
|---|
| 119 | void removeAppFontData(QFontDatabasePrivate::ApplicationFont *fnt);
|
|---|
| 120 | static inline bool appFontLimitReached();
|
|---|
| 121 | TUid addFontFileToFontStore(const QFileInfo &fontFileInfo);
|
|---|
| 122 | static void clear();
|
|---|
| 123 |
|
|---|
| 124 | static inline QString tempAppFontFolder();
|
|---|
| 125 | static const QString appFontMarkerPrefix;
|
|---|
| 126 | static QString appFontMarker(); // 'qaf<shortUid[+shortPid]>'
|
|---|
| 127 |
|
|---|
| 128 | struct CFontFromFontStoreReleaser {
|
|---|
| 129 | static inline void cleanup(CFont *font)
|
|---|
| 130 | {
|
|---|
| 131 | if (!font)
|
|---|
| 132 | return;
|
|---|
| 133 | const QSymbianFontDatabaseExtrasImplementation *dbExtras =
|
|---|
| 134 | static_cast<const QSymbianFontDatabaseExtrasImplementation*>(privateDb()->symbianExtras);
|
|---|
| 135 | dbExtras->m_store->ReleaseFont(font);
|
|---|
| 136 | }
|
|---|
| 137 | };
|
|---|
| 138 |
|
|---|
| 139 | struct CFontFromScreenDeviceReleaser {
|
|---|
| 140 | static inline void cleanup(CFont *font)
|
|---|
| 141 | {
|
|---|
| 142 | if (!font)
|
|---|
| 143 | return;
|
|---|
| 144 | S60->screenDevice()->ReleaseFont(font);
|
|---|
| 145 | }
|
|---|
| 146 | };
|
|---|
| 147 |
|
|---|
| 148 | // m_heap, m_store, m_rasterizer and m_extras are used if Symbian
|
|---|
| 149 | // does not provide the Font Table API
|
|---|
| 150 | RHeap* m_heap;
|
|---|
| 151 | CFontStore *m_store;
|
|---|
| 152 | COpenFontRasterizer *m_rasterizer;
|
|---|
| 153 | mutable QList<const QSymbianTypeFaceExtras *> m_extras;
|
|---|
| 154 |
|
|---|
| 155 | mutable QHash<QString, const QSymbianTypeFaceExtras *> m_extrasHash;
|
|---|
| 156 | mutable QSet<QString> m_applicationFontFamilies;
|
|---|
| 157 | };
|
|---|
| 158 |
|
|---|
| 159 | const QString QSymbianFontDatabaseExtrasImplementation::appFontMarkerPrefix =
|
|---|
| 160 | QLatin1String("Q");
|
|---|
| 161 |
|
|---|
| 162 | inline QString QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder()
|
|---|
| 163 | {
|
|---|
| 164 | return QDir::toNativeSeparators(QDir::tempPath()) + QLatin1Char('\\');
|
|---|
| 165 | }
|
|---|
| 166 |
|
|---|
| 167 | QString QSymbianFontDatabaseExtrasImplementation::appFontMarker()
|
|---|
| 168 | {
|
|---|
| 169 | static QString result;
|
|---|
| 170 | if (result.isEmpty()) {
|
|---|
| 171 | quint16 id = 0;
|
|---|
| 172 | if (QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) {
|
|---|
| 173 | // We are allowed to load app fonts even from previous, crashed runs
|
|---|
| 174 | // of this application, since we can access the font tables.
|
|---|
| 175 | const quint32 uid = RProcess().Type().MostDerived().iUid;
|
|---|
| 176 | id = static_cast<quint16>(uid + (uid >> 16));
|
|---|
| 177 | } else {
|
|---|
| 178 | // If no font table Api is available, we must not even load a font
|
|---|
| 179 | // from a previous (crashed) run of this application. Reason: we
|
|---|
| 180 | // won't get the font tables, they are not in the CFontStore.
|
|---|
| 181 | // So, we use the pid, for more uniqueness.
|
|---|
| 182 | id = static_cast<quint16>(RProcess().Id().Id());
|
|---|
| 183 | }
|
|---|
| 184 | result = appFontMarkerPrefix + QString::fromLatin1("%1").arg(id & 0x7fff, 3, 32, QLatin1Char('0'));
|
|---|
| 185 | Q_ASSERT(appFontMarkerPrefix.length() == 1 && result.length() == 4);
|
|---|
| 186 | }
|
|---|
| 187 | return result;
|
|---|
| 188 | }
|
|---|
| 189 |
|
|---|
| 190 | static inline bool qt_symbian_fontNameHasAppFontMarker(const QString &fontName)
|
|---|
| 191 | {
|
|---|
| 192 | const int idLength = 3; // Keep in sync with id length in appFontMarker().
|
|---|
| 193 | const QString &prefix = QSymbianFontDatabaseExtrasImplementation::appFontMarkerPrefix;
|
|---|
| 194 | if (fontName.length() < prefix.length() + idLength
|
|---|
| 195 | || fontName.mid(fontName.length() - idLength - prefix.length(), prefix.length()) != prefix)
|
|---|
| 196 | return false;
|
|---|
| 197 | // Testing if the the id is base32 data
|
|---|
| 198 | for (int i = fontName.length() - idLength; i < fontName.length(); ++i) {
|
|---|
| 199 | const QChar &c = fontName.at(i);
|
|---|
| 200 | if (!(c >= QLatin1Char('0') && c <= QLatin1Char('9')
|
|---|
| 201 | || c >= QLatin1Char('a') && c <= QLatin1Char('v')))
|
|---|
| 202 | return false;
|
|---|
| 203 | }
|
|---|
| 204 | return true;
|
|---|
| 205 | }
|
|---|
| 206 |
|
|---|
| 207 | // If fontName is an application font of this app, prepend the app font marker
|
|---|
| 208 | QString qt_symbian_fontNameWithAppFontMarker(const QString &fontName)
|
|---|
| 209 | {
|
|---|
| 210 | QFontDatabasePrivate *db = privateDb();
|
|---|
| 211 | Q_ASSERT(db);
|
|---|
| 212 | const QSymbianFontDatabaseExtrasImplementation *dbExtras =
|
|---|
| 213 | static_cast<const QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras);
|
|---|
| 214 | return dbExtras->m_applicationFontFamilies.contains(fontName) ?
|
|---|
| 215 | fontName + QSymbianFontDatabaseExtrasImplementation::appFontMarker()
|
|---|
| 216 | : fontName;
|
|---|
| 217 | }
|
|---|
| 218 |
|
|---|
| 219 | static inline QString qt_symbian_appFontNameWithoutMarker(const QString &markedFontName)
|
|---|
| 220 | {
|
|---|
| 221 | return markedFontName.left(markedFontName.length()
|
|---|
| 222 | - QSymbianFontDatabaseExtrasImplementation::appFontMarker().length());
|
|---|
| 223 | }
|
|---|
| 224 |
|
|---|
| 225 | QSymbianFontDatabaseExtrasImplementation::QSymbianFontDatabaseExtrasImplementation()
|
|---|
| 226 | {
|
|---|
| 227 | if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) {
|
|---|
| 228 | QStringList filters;
|
|---|
| 229 | filters.append(QLatin1String("*.ttf"));
|
|---|
| 230 | filters.append(QLatin1String("*.ccc"));
|
|---|
| 231 | filters.append(QLatin1String("*.ltt"));
|
|---|
| 232 | const QFileInfoList fontFiles = alternativeFilePaths(QLatin1String("resource\\Fonts"), filters);
|
|---|
| 233 |
|
|---|
| 234 | const TInt heapMinLength = 0x1000;
|
|---|
| 235 | const TInt heapMaxLength = qMax(0x20000 * fontFiles.count(), heapMinLength);
|
|---|
| 236 | m_heap = User::ChunkHeap(NULL, heapMinLength, heapMaxLength);
|
|---|
| 237 | QT_TRAP_THROWING(
|
|---|
| 238 | m_store = CFontStore::NewL(m_heap);
|
|---|
| 239 | m_rasterizer = COpenFontRasterizer::NewL(TUid::Uid(0x101F7F5E));
|
|---|
| 240 | CleanupStack::PushL(m_rasterizer);
|
|---|
| 241 | m_store->InstallRasterizerL(m_rasterizer);
|
|---|
| 242 | CleanupStack::Pop(m_rasterizer););
|
|---|
| 243 |
|
|---|
| 244 | foreach (const QFileInfo &fontFileInfo, fontFiles)
|
|---|
| 245 | addFontFileToFontStore(fontFileInfo);
|
|---|
| 246 | }
|
|---|
| 247 | }
|
|---|
| 248 |
|
|---|
| 249 | void QSymbianFontDatabaseExtrasImplementation::clear()
|
|---|
| 250 | {
|
|---|
| 251 | QFontDatabasePrivate *db = privateDb();
|
|---|
| 252 | if (!db)
|
|---|
| 253 | return;
|
|---|
| 254 | const QSymbianFontDatabaseExtrasImplementation *dbExtras =
|
|---|
| 255 | static_cast<const QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras);
|
|---|
| 256 | if (!dbExtras)
|
|---|
| 257 | return; // initializeDb() has never been called
|
|---|
| 258 | if (QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) {
|
|---|
| 259 | qDeleteAll(dbExtras->m_extrasHash);
|
|---|
| 260 | } else {
|
|---|
| 261 | typedef QList<const QSymbianTypeFaceExtras *>::iterator iterator;
|
|---|
| 262 | for (iterator p = dbExtras->m_extras.begin(); p != dbExtras->m_extras.end(); ++p) {
|
|---|
| 263 | dbExtras->m_store->ReleaseFont((*p)->fontOwner());
|
|---|
| 264 | delete *p;
|
|---|
| 265 | }
|
|---|
| 266 | dbExtras->m_extras.clear();
|
|---|
| 267 | }
|
|---|
| 268 | dbExtras->m_extrasHash.clear();
|
|---|
| 269 | }
|
|---|
| 270 |
|
|---|
| 271 | void qt_cleanup_symbianFontDatabase()
|
|---|
| 272 | {
|
|---|
| 273 | QFontDatabasePrivate *db = privateDb();
|
|---|
| 274 | if (!db)
|
|---|
| 275 | return;
|
|---|
| 276 |
|
|---|
| 277 | QSymbianFontDatabaseExtrasImplementation::clear();
|
|---|
| 278 |
|
|---|
| 279 | if (!db->applicationFonts.isEmpty()) {
|
|---|
| 280 | QFontDatabase::removeAllApplicationFonts();
|
|---|
| 281 | // We remove the left over temporary font files of Qt application.
|
|---|
| 282 | // Active fonts are undeletable since the font server holds a handle
|
|---|
| 283 | // on them, so we do not need to worry to delete other running
|
|---|
| 284 | // applications' fonts.
|
|---|
| 285 | const QDir dir(QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder());
|
|---|
| 286 | const QStringList filter(
|
|---|
| 287 | QSymbianFontDatabaseExtrasImplementation::appFontMarkerPrefix + QLatin1String("*.ttf"));
|
|---|
| 288 | foreach (const QFileInfo &ttfFile, dir.entryInfoList(filter))
|
|---|
| 289 | QFile(ttfFile.absoluteFilePath()).remove();
|
|---|
| 290 | db->applicationFonts.clear();
|
|---|
| 291 | }
|
|---|
| 292 | }
|
|---|
| 293 |
|
|---|
| 294 | QSymbianFontDatabaseExtrasImplementation::~QSymbianFontDatabaseExtrasImplementation()
|
|---|
| 295 | {
|
|---|
| 296 | qt_cleanup_symbianFontDatabase();
|
|---|
| 297 | if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) {
|
|---|
| 298 | delete m_store;
|
|---|
| 299 | m_heap->Close();
|
|---|
| 300 | }
|
|---|
| 301 | }
|
|---|
| 302 |
|
|---|
| 303 | #ifndef FNTSTORE_H_INLINES_SUPPORT_FMM
|
|---|
| 304 | /*
|
|---|
| 305 | Workaround: fntstore.h has an inlined function 'COpenFont* CBitmapFont::OpenFont()'
|
|---|
| 306 | that returns a private data member. The header will change between SDKs. But Qt has
|
|---|
| 307 | to build on any SDK version and run on other versions of Symbian OS.
|
|---|
| 308 | This function performs the needed pointer arithmetic to get the right COpenFont*
|
|---|
| 309 | */
|
|---|
| 310 | COpenFont* OpenFontFromBitmapFont(const CBitmapFont* aBitmapFont)
|
|---|
| 311 | {
|
|---|
| 312 | const TInt offsetIOpenFont = 92; // '_FOFF(CBitmapFont, iOpenFont)' ..if iOpenFont weren't private
|
|---|
| 313 | const TUint valueIOpenFont = *(TUint*)PtrAdd(aBitmapFont, offsetIOpenFont);
|
|---|
| 314 | return (valueIOpenFont & 1) ?
|
|---|
| 315 | (COpenFont*)PtrAdd(aBitmapFont, valueIOpenFont & ~1) : // New behavior: iOpenFont is offset
|
|---|
| 316 | (COpenFont*)valueIOpenFont; // Old behavior: iOpenFont is pointer
|
|---|
| 317 | }
|
|---|
| 318 | #endif // FNTSTORE_H_INLINES_SUPPORT_FMM
|
|---|
| 319 |
|
|---|
| 320 | const QSymbianTypeFaceExtras *QSymbianFontDatabaseExtrasImplementation::extras(const QString &aTypeface,
|
|---|
| 321 | bool bold, bool italic) const
|
|---|
| 322 | {
|
|---|
| 323 | const QString typeface = qt_symbian_fontNameWithAppFontMarker(aTypeface);
|
|---|
| 324 | const QString searchKey = typeface + QString::number(int(bold)) + QString::number(int(italic));
|
|---|
| 325 | if (!m_extrasHash.contains(searchKey)) {
|
|---|
| 326 | TFontSpec searchSpec(qt_QString2TPtrC(typeface), 1);
|
|---|
| 327 | if (bold)
|
|---|
| 328 | searchSpec.iFontStyle.SetStrokeWeight(EStrokeWeightBold);
|
|---|
| 329 | if (italic)
|
|---|
| 330 | searchSpec.iFontStyle.SetPosture(EPostureItalic);
|
|---|
| 331 |
|
|---|
| 332 | CFont* font = NULL;
|
|---|
| 333 | if (QSymbianTypeFaceExtras::symbianFontTableApiAvailable()) {
|
|---|
| 334 | const TInt err = S60->screenDevice()->GetNearestFontToDesignHeightInPixels(font, searchSpec);
|
|---|
| 335 | Q_ASSERT(err == KErrNone && font);
|
|---|
| 336 | QScopedPointer<CFont, CFontFromScreenDeviceReleaser> sFont(font);
|
|---|
| 337 | QSymbianTypeFaceExtras *extras = new QSymbianTypeFaceExtras(font);
|
|---|
| 338 | sFont.take();
|
|---|
| 339 | m_extrasHash.insert(searchKey, extras);
|
|---|
| 340 | } else {
|
|---|
| 341 | const TInt err = m_store->GetNearestFontToDesignHeightInPixels(font, searchSpec);
|
|---|
| 342 | Q_ASSERT(err == KErrNone && font);
|
|---|
| 343 | const CBitmapFont *bitmapFont = static_cast<CBitmapFont*>(font);
|
|---|
| 344 | COpenFont *openFont =
|
|---|
| 345 | #ifdef FNTSTORE_H_INLINES_SUPPORT_FMM
|
|---|
| 346 | bitmapFont->OpenFont();
|
|---|
| 347 | #else // FNTSTORE_H_INLINES_SUPPORT_FMM
|
|---|
| 348 | OpenFontFromBitmapFont(bitmapFont);
|
|---|
| 349 | #endif // FNTSTORE_H_INLINES_SUPPORT_FMM
|
|---|
| 350 | const TOpenFontFaceAttrib* const attrib = openFont->FaceAttrib();
|
|---|
| 351 | const QString foundKey =
|
|---|
| 352 | QString((const QChar*)attrib->FullName().Ptr(), attrib->FullName().Length());
|
|---|
| 353 | if (!m_extrasHash.contains(foundKey)) {
|
|---|
| 354 | QScopedPointer<CFont, CFontFromFontStoreReleaser> sFont(font);
|
|---|
| 355 | QSymbianTypeFaceExtras *extras = new QSymbianTypeFaceExtras(font, openFont);
|
|---|
| 356 | sFont.take();
|
|---|
| 357 | m_extras.append(extras);
|
|---|
| 358 | m_extrasHash.insert(searchKey, extras);
|
|---|
| 359 | m_extrasHash.insert(foundKey, extras);
|
|---|
| 360 | } else {
|
|---|
| 361 | m_store->ReleaseFont(font);
|
|---|
| 362 | m_extrasHash.insert(searchKey, m_extrasHash.value(foundKey));
|
|---|
| 363 | }
|
|---|
| 364 | }
|
|---|
| 365 | }
|
|---|
| 366 | return m_extrasHash.value(searchKey);
|
|---|
| 367 | }
|
|---|
| 368 |
|
|---|
| 369 | void QSymbianFontDatabaseExtrasImplementation::removeAppFontData(
|
|---|
| 370 | QFontDatabasePrivate::ApplicationFont *fnt)
|
|---|
| 371 | {
|
|---|
| 372 | clear();
|
|---|
| 373 | if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable()
|
|---|
| 374 | && fnt->fontStoreFontFileUid.iUid != 0)
|
|---|
| 375 | m_store->RemoveFile(fnt->fontStoreFontFileUid);
|
|---|
| 376 | if (!fnt->families.isEmpty())
|
|---|
| 377 | m_applicationFontFamilies.remove(fnt->families.first());
|
|---|
| 378 | if (fnt->screenDeviceFontFileId != 0)
|
|---|
| 379 | S60->screenDevice()->RemoveFile(fnt->screenDeviceFontFileId);
|
|---|
| 380 | QFile::remove(fnt->temporaryFileName);
|
|---|
| 381 | *fnt = QFontDatabasePrivate::ApplicationFont();
|
|---|
| 382 | }
|
|---|
| 383 |
|
|---|
| 384 | bool QSymbianFontDatabaseExtrasImplementation::appFontLimitReached()
|
|---|
| 385 | {
|
|---|
| 386 | QFontDatabasePrivate *db = privateDb();
|
|---|
| 387 | if (!db)
|
|---|
| 388 | return false;
|
|---|
| 389 | const int maxAppFonts = 5;
|
|---|
| 390 | int registeredAppFonts = 0;
|
|---|
| 391 | foreach (const QFontDatabasePrivate::ApplicationFont &appFont, db->applicationFonts)
|
|---|
| 392 | if (!appFont.families.isEmpty() && ++registeredAppFonts == maxAppFonts)
|
|---|
| 393 | return true;
|
|---|
| 394 | return false;
|
|---|
| 395 | }
|
|---|
| 396 |
|
|---|
| 397 | TUid QSymbianFontDatabaseExtrasImplementation::addFontFileToFontStore(const QFileInfo &fontFileInfo)
|
|---|
| 398 | {
|
|---|
| 399 | Q_ASSERT(!QSymbianTypeFaceExtras::symbianFontTableApiAvailable());
|
|---|
| 400 | const QString fontFile = QDir::toNativeSeparators(fontFileInfo.absoluteFilePath());
|
|---|
| 401 | const TPtrC fontFilePtr(qt_QString2TPtrC(fontFile));
|
|---|
| 402 | TUid fontUid = {0};
|
|---|
| 403 | TRAP_IGNORE(fontUid = m_store->AddFileL(fontFilePtr));
|
|---|
| 404 | return fontUid;
|
|---|
| 405 | }
|
|---|
| 406 |
|
|---|
| 407 | #else // QT_NO_FREETYPE
|
|---|
| 408 | class QFontEngineFTS60 : public QFontEngineFT
|
|---|
| 409 | {
|
|---|
| 410 | public:
|
|---|
| 411 | QFontEngineFTS60(const QFontDef &fd);
|
|---|
| 412 | };
|
|---|
| 413 |
|
|---|
| 414 | QFontEngineFTS60::QFontEngineFTS60(const QFontDef &fd)
|
|---|
| 415 | : QFontEngineFT(fd)
|
|---|
| 416 | {
|
|---|
| 417 | default_hint_style = HintFull;
|
|---|
| 418 | }
|
|---|
| 419 | #endif // QT_NO_FREETYPE
|
|---|
| 420 |
|
|---|
| 421 | /*
|
|---|
| 422 | QFontEngineS60::pixelsToPoints, QFontEngineS60::pointsToPixels, QFontEngineMultiS60::QFontEngineMultiS60
|
|---|
| 423 | and QFontEngineMultiS60::QFontEngineMultiS60 should be in qfontengine_s60.cpp. But since also the
|
|---|
| 424 | Freetype based font rendering need them, they are here.
|
|---|
| 425 | */
|
|---|
| 426 | qreal QFontEngineS60::pixelsToPoints(qreal pixels, Qt::Orientation orientation)
|
|---|
| 427 | {
|
|---|
| 428 | CWsScreenDevice* device = S60->screenDevice();
|
|---|
| 429 | return (orientation == Qt::Horizontal?
|
|---|
| 430 | device->HorizontalPixelsToTwips(pixels)
|
|---|
| 431 | :device->VerticalPixelsToTwips(pixels)) / KTwipsPerPoint;
|
|---|
| 432 | }
|
|---|
| 433 |
|
|---|
| 434 | qreal QFontEngineS60::pointsToPixels(qreal points, Qt::Orientation orientation)
|
|---|
| 435 | {
|
|---|
| 436 | CWsScreenDevice* device = S60->screenDevice();
|
|---|
| 437 | const int twips = points * KTwipsPerPoint;
|
|---|
| 438 | return orientation == Qt::Horizontal?
|
|---|
| 439 | device->HorizontalTwipsToPixels(twips)
|
|---|
| 440 | :device->VerticalTwipsToPixels(twips);
|
|---|
| 441 | }
|
|---|
| 442 |
|
|---|
| 443 | QFontEngineMultiS60::QFontEngineMultiS60(QFontEngine *first, int script, const QStringList &fallbackFamilies)
|
|---|
| 444 | : QFontEngineMulti(fallbackFamilies.size() + 1)
|
|---|
| 445 | , m_script(script)
|
|---|
| 446 | , m_fallbackFamilies(fallbackFamilies)
|
|---|
| 447 | {
|
|---|
| 448 | engines[0] = first;
|
|---|
| 449 | first->ref.ref();
|
|---|
| 450 | fontDef = engines[0]->fontDef;
|
|---|
| 451 | }
|
|---|
| 452 |
|
|---|
| 453 | void QFontEngineMultiS60::loadEngine(int at)
|
|---|
| 454 | {
|
|---|
| 455 | Q_ASSERT(at < engines.size());
|
|---|
| 456 | Q_ASSERT(engines.at(at) == 0);
|
|---|
| 457 |
|
|---|
| 458 | QFontDef request = fontDef;
|
|---|
| 459 | request.styleStrategy |= QFont::NoFontMerging;
|
|---|
| 460 | request.family = m_fallbackFamilies.at(at-1);
|
|---|
| 461 | engines[at] = QFontDatabase::findFont(m_script,
|
|---|
| 462 | /*fontprivate*/0,
|
|---|
| 463 | request);
|
|---|
| 464 | Q_ASSERT(engines[at]);
|
|---|
| 465 | }
|
|---|
| 466 |
|
|---|
| 467 | static bool registerScreenDeviceFont(int screenDeviceFontIndex,
|
|---|
| 468 | const QSymbianFontDatabaseExtrasImplementation *dbExtras)
|
|---|
| 469 | {
|
|---|
| 470 | TTypefaceSupport typefaceSupport;
|
|---|
| 471 | S60->screenDevice()->TypefaceSupport(typefaceSupport, screenDeviceFontIndex);
|
|---|
| 472 |
|
|---|
| 473 | QString familyName((const QChar*)typefaceSupport.iTypeface.iName.Ptr(), typefaceSupport.iTypeface.iName.Length());
|
|---|
| 474 | if (qt_symbian_fontNameHasAppFontMarker(familyName)) {
|
|---|
| 475 | const QString &marker = QSymbianFontDatabaseExtrasImplementation::appFontMarker();
|
|---|
| 476 | if (familyName.endsWith(marker)) {
|
|---|
| 477 | familyName = qt_symbian_appFontNameWithoutMarker(familyName);
|
|---|
| 478 | dbExtras->m_applicationFontFamilies.insert(familyName);
|
|---|
| 479 | } else {
|
|---|
| 480 | return false; // This was somebody else's application font. Skip it.
|
|---|
| 481 | }
|
|---|
| 482 | }
|
|---|
| 483 |
|
|---|
| 484 | CFont *font; // We have to get a font instance in order to know all the details
|
|---|
| 485 | TFontSpec fontSpec(typefaceSupport.iTypeface.iName, 11);
|
|---|
| 486 | if (S60->screenDevice()->GetNearestFontInPixels(font, fontSpec) != KErrNone)
|
|---|
| 487 | return false;
|
|---|
| 488 | QScopedPointer<CFont, QSymbianFontDatabaseExtrasImplementation::CFontFromScreenDeviceReleaser> sFont(font);
|
|---|
| 489 | if (font->TypeUid() != KCFbsFontUid)
|
|---|
| 490 | return false;
|
|---|
| 491 | TOpenFontFaceAttrib faceAttrib;
|
|---|
| 492 | const CFbsFont *cfbsFont = static_cast<const CFbsFont *>(font);
|
|---|
| 493 | cfbsFont->GetFaceAttrib(faceAttrib);
|
|---|
| 494 |
|
|---|
| 495 | QtFontStyle::Key styleKey;
|
|---|
| 496 | styleKey.style = faceAttrib.IsItalic()?QFont::StyleItalic:QFont::StyleNormal;
|
|---|
| 497 | styleKey.weight = faceAttrib.IsBold()?QFont::Bold:QFont::Normal;
|
|---|
| 498 |
|
|---|
| 499 | QtFontFamily *family = privateDb()->family(familyName, true);
|
|---|
| 500 | family->fixedPitch = faceAttrib.IsMonoWidth();
|
|---|
| 501 | QtFontFoundry *foundry = family->foundry(QString(), true);
|
|---|
| 502 | QtFontStyle *style = foundry->style(styleKey, true);
|
|---|
| 503 | style->smoothScalable = typefaceSupport.iIsScalable;
|
|---|
| 504 | style->pixelSize(0, true);
|
|---|
| 505 |
|
|---|
| 506 | const QSymbianTypeFaceExtras *typeFaceExtras =
|
|---|
| 507 | dbExtras->extras(familyName, faceAttrib.IsBold(), faceAttrib.IsItalic());
|
|---|
| 508 | const QByteArray os2Table = typeFaceExtras->getSfntTable(MAKE_TAG('O', 'S', '/', '2'));
|
|---|
| 509 | const unsigned char* data = reinterpret_cast<const unsigned char*>(os2Table.constData());
|
|---|
| 510 | const unsigned char* ulUnicodeRange = data + 42;
|
|---|
| 511 | quint32 unicodeRange[4] = {
|
|---|
| 512 | qFromBigEndian<quint32>(ulUnicodeRange),
|
|---|
| 513 | qFromBigEndian<quint32>(ulUnicodeRange + 4),
|
|---|
| 514 | qFromBigEndian<quint32>(ulUnicodeRange + 8),
|
|---|
| 515 | qFromBigEndian<quint32>(ulUnicodeRange + 12)
|
|---|
| 516 | };
|
|---|
| 517 | const unsigned char* ulCodePageRange = data + 78;
|
|---|
| 518 | quint32 codePageRange[2] = {
|
|---|
| 519 | qFromBigEndian<quint32>(ulCodePageRange),
|
|---|
| 520 | qFromBigEndian<quint32>(ulCodePageRange + 4)
|
|---|
| 521 | };
|
|---|
| 522 | const QList<QFontDatabase::WritingSystem> writingSystems =
|
|---|
| 523 | determineWritingSystemsFromTrueTypeBits(unicodeRange, codePageRange);
|
|---|
| 524 | foreach (const QFontDatabase::WritingSystem system, writingSystems)
|
|---|
| 525 | family->writingSystems[system] = QtFontFamily::Supported;
|
|---|
| 526 | return true;
|
|---|
| 527 | }
|
|---|
| 528 |
|
|---|
| 529 | static void initializeDb()
|
|---|
| 530 | {
|
|---|
| 531 | QFontDatabasePrivate *db = privateDb();
|
|---|
| 532 | if(!db || db->count)
|
|---|
| 533 | return;
|
|---|
| 534 |
|
|---|
| 535 | #ifdef QT_NO_FREETYPE
|
|---|
| 536 | if (!db->symbianExtras)
|
|---|
| 537 | db->symbianExtras = new QSymbianFontDatabaseExtrasImplementation;
|
|---|
| 538 |
|
|---|
| 539 | QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock);
|
|---|
| 540 |
|
|---|
| 541 | const int numTypeFaces = S60->screenDevice()->NumTypefaces();
|
|---|
| 542 | const QSymbianFontDatabaseExtrasImplementation *dbExtras =
|
|---|
| 543 | static_cast<const QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras);
|
|---|
| 544 | for (int i = 0; i < numTypeFaces; i++)
|
|---|
| 545 | registerScreenDeviceFont(i, dbExtras);
|
|---|
| 546 |
|
|---|
| 547 | // We have to clear/release all CFonts, here, in case one of the fonts is
|
|---|
| 548 | // an application font of another running Qt app. Otherwise the other Qt app
|
|---|
| 549 | // cannot remove it's application font, anymore -> "Zombie Font".
|
|---|
| 550 | QSymbianFontDatabaseExtrasImplementation::clear();
|
|---|
| 551 |
|
|---|
| 552 | lock.relock();
|
|---|
| 553 |
|
|---|
| 554 | #else // QT_NO_FREETYPE
|
|---|
| 555 | QDir dir(QDesktopServices::storageLocation(QDesktopServices::FontsLocation));
|
|---|
| 556 | dir.setNameFilters(QStringList() << QLatin1String("*.ttf")
|
|---|
| 557 | << QLatin1String("*.ttc") << QLatin1String("*.pfa")
|
|---|
| 558 | << QLatin1String("*.pfb"));
|
|---|
| 559 | for (int i = 0; i < int(dir.count()); ++i) {
|
|---|
| 560 | const QByteArray file = QFile::encodeName(dir.absoluteFilePath(dir[i]));
|
|---|
| 561 | db->addTTFile(file);
|
|---|
| 562 | }
|
|---|
| 563 | #endif // QT_NO_FREETYPE
|
|---|
| 564 | }
|
|---|
| 565 |
|
|---|
| 566 | static inline void load(const QString &family = QString(), int script = -1)
|
|---|
| 567 | {
|
|---|
| 568 | Q_UNUSED(family)
|
|---|
| 569 | Q_UNUSED(script)
|
|---|
| 570 | initializeDb();
|
|---|
| 571 | }
|
|---|
| 572 |
|
|---|
| 573 | struct OffsetTable {
|
|---|
| 574 | quint32 sfntVersion;
|
|---|
| 575 | quint16 numTables, searchRange, entrySelector, rangeShift;
|
|---|
| 576 | };
|
|---|
| 577 |
|
|---|
| 578 | struct TableRecord {
|
|---|
| 579 | quint32 tag, checkSum, offset, length;
|
|---|
| 580 | };
|
|---|
| 581 |
|
|---|
| 582 | struct NameTableHead {
|
|---|
| 583 | quint16 format, count, stringOffset;
|
|---|
| 584 | };
|
|---|
| 585 |
|
|---|
| 586 | struct NameRecord {
|
|---|
| 587 | quint16 platformID, encodingID, languageID, nameID, length, offset;
|
|---|
| 588 | };
|
|---|
| 589 |
|
|---|
| 590 | static quint32 ttfCalcChecksum(const char *data, quint32 bytesCount)
|
|---|
| 591 | {
|
|---|
| 592 | quint32 result = 0;
|
|---|
| 593 | const quint32 *ptr = reinterpret_cast<const quint32*>(data);
|
|---|
| 594 | const quint32 *endPtr =
|
|---|
| 595 | ptr + (bytesCount + sizeof(quint32) - 1) / sizeof(quint32);
|
|---|
| 596 | while (ptr < endPtr) {
|
|---|
| 597 | const quint32 unit32Value = *ptr++;
|
|---|
| 598 | result += qFromBigEndian(unit32Value);
|
|---|
| 599 | }
|
|---|
| 600 | return result;
|
|---|
| 601 | }
|
|---|
| 602 |
|
|---|
| 603 | static inline quint32 toDWordBoundary(quint32 value)
|
|---|
| 604 | {
|
|---|
| 605 | return (value + 3) & ~3;
|
|---|
| 606 | }
|
|---|
| 607 |
|
|---|
| 608 | static inline quint32 dWordPadding(quint32 value)
|
|---|
| 609 | {
|
|---|
| 610 | return (4 - (value & 3)) & 3;
|
|---|
| 611 | }
|
|---|
| 612 |
|
|---|
| 613 | static inline bool ttfMarkNameTable(QByteArray &table, const QString &marker)
|
|---|
| 614 | {
|
|---|
| 615 | const quint32 tableLength = static_cast<quint32>(table.size());
|
|---|
| 616 |
|
|---|
| 617 | if (tableLength > 50000 // hard limit
|
|---|
| 618 | || tableLength < sizeof(NameTableHead)) // corrupt name table
|
|---|
| 619 | return false;
|
|---|
| 620 |
|
|---|
| 621 | const NameTableHead *head = reinterpret_cast<const NameTableHead*>(table.constData());
|
|---|
| 622 | const quint16 count = qFromBigEndian(head->count);
|
|---|
| 623 | const quint16 stringOffset = qFromBigEndian(head->stringOffset);
|
|---|
| 624 | if (count > 200 // hard limit
|
|---|
| 625 | || stringOffset >= tableLength // corrupt name table
|
|---|
| 626 | || sizeof(NameTableHead) + count * sizeof(NameRecord) >= tableLength) // corrupt name table
|
|---|
| 627 | return false;
|
|---|
| 628 |
|
|---|
| 629 | QTextEncoder encoder(QTextCodec::codecForName("UTF-16BE"), QTextCodec::IgnoreHeader);
|
|---|
| 630 | const QByteArray markerUtf16BE = encoder.fromUnicode(marker);
|
|---|
| 631 | const QByteArray markerAscii = marker.toAscii();
|
|---|
| 632 |
|
|---|
| 633 | QByteArray markedTable;
|
|---|
| 634 | markedTable.reserve(tableLength + marker.length() * 20); // Original size plus some extra
|
|---|
| 635 | markedTable.append(table, stringOffset);
|
|---|
| 636 | QByteArray markedStrings;
|
|---|
| 637 | quint32 stringDataCount = stringOffset;
|
|---|
| 638 | for (quint16 i = 0; i < count; ++i) {
|
|---|
| 639 | const quint32 nameRecordOffset = sizeof(NameTableHead) + sizeof(NameRecord) * i;
|
|---|
| 640 | NameRecord *nameRecord =
|
|---|
| 641 | reinterpret_cast<NameRecord*>(markedTable.data() + nameRecordOffset);
|
|---|
| 642 | const quint16 nameID = qFromBigEndian(nameRecord->nameID);
|
|---|
| 643 | const quint16 platformID = qFromBigEndian(nameRecord->platformID);
|
|---|
| 644 | const quint16 encodingID = qFromBigEndian(nameRecord->encodingID);
|
|---|
| 645 | const quint16 offset = qFromBigEndian(nameRecord->offset);
|
|---|
| 646 | const quint16 length = qFromBigEndian(nameRecord->length);
|
|---|
| 647 | stringDataCount += length;
|
|---|
| 648 | if (stringDataCount > 80000 // hard limit. String data may be > name table size. Multiple records can reference the same string.
|
|---|
| 649 | || static_cast<quint32>(stringOffset + offset + length) > tableLength) // String outside bounds
|
|---|
| 650 | return false;
|
|---|
| 651 | const bool needsMarker =
|
|---|
| 652 | nameID == 1 || nameID == 3 || nameID == 4 || nameID == 16 || nameID == 21;
|
|---|
| 653 | const bool isUnicode =
|
|---|
| 654 | platformID == 0 || platformID == 3 && encodingID == 1;
|
|---|
| 655 | const QByteArray originalString =
|
|---|
| 656 | QByteArray::fromRawData(table.constData() + stringOffset + offset, length);
|
|---|
| 657 | QByteArray markedString;
|
|---|
| 658 | if (needsMarker) {
|
|---|
| 659 | const int maxBytesLength = (KMaxTypefaceNameLength - marker.length()) * (isUnicode ? 2 : 1);
|
|---|
| 660 | markedString = originalString.left(maxBytesLength) + (isUnicode ? markerUtf16BE : markerAscii);
|
|---|
| 661 | } else {
|
|---|
| 662 | markedString = originalString;
|
|---|
| 663 | }
|
|---|
| 664 | nameRecord->offset = qToBigEndian(static_cast<quint16>(markedStrings.length()));
|
|---|
| 665 | nameRecord->length = qToBigEndian(static_cast<quint16>(markedString.length()));
|
|---|
| 666 | markedStrings.append(markedString);
|
|---|
| 667 | }
|
|---|
| 668 | markedTable.append(markedStrings);
|
|---|
| 669 | table = markedTable;
|
|---|
| 670 | return true;
|
|---|
| 671 | }
|
|---|
| 672 |
|
|---|
| 673 | const quint32 ttfMaxFileSize = 3500000;
|
|---|
| 674 |
|
|---|
| 675 | static inline bool ttfMarkAppFont(QByteArray &ttf, const QString &marker)
|
|---|
| 676 | {
|
|---|
| 677 | const quint32 ttfChecksumNumber = 0xb1b0afba;
|
|---|
| 678 | const quint32 alignment = 4;
|
|---|
| 679 | const quint32 ttfLength = static_cast<quint32>(ttf.size());
|
|---|
| 680 | if (ttfLength > ttfMaxFileSize // hard limit
|
|---|
| 681 | || ttfLength % alignment != 0 // ttf sizes are always factors of 4
|
|---|
| 682 | || ttfLength <= sizeof(OffsetTable) // ttf too short
|
|---|
| 683 | || ttfCalcChecksum(ttf.constData(), ttf.size()) != ttfChecksumNumber) // ttf checksum is invalid
|
|---|
| 684 | return false;
|
|---|
| 685 |
|
|---|
| 686 | const OffsetTable *offsetTable = reinterpret_cast<const OffsetTable*>(ttf.constData());
|
|---|
| 687 | const quint16 numTables = qFromBigEndian(offsetTable->numTables);
|
|---|
| 688 | const quint32 recordsLength =
|
|---|
| 689 | toDWordBoundary(sizeof(OffsetTable) + numTables * sizeof(TableRecord));
|
|---|
| 690 | if (numTables > 30 // hard limit
|
|---|
| 691 | || recordsLength + numTables * alignment > ttfLength) // Corrupt ttf. Tables would not fit, even if empty.
|
|---|
| 692 | return false;
|
|---|
| 693 |
|
|---|
| 694 | QByteArray markedTtf;
|
|---|
| 695 | markedTtf.reserve(ttfLength + marker.length() * 20); // Original size plus some extra
|
|---|
| 696 | markedTtf.append(ttf.constData(), recordsLength);
|
|---|
| 697 |
|
|---|
| 698 | const quint32 ttfCheckSumAdjustmentOffset = 8; // Offset from the start of 'head'
|
|---|
| 699 | int indexOfHeadTable = -1;
|
|---|
| 700 | quint32 ttfDataSize = recordsLength;
|
|---|
| 701 | typedef QPair<quint32, quint32> Range;
|
|---|
| 702 | QList<Range> memoryRanges;
|
|---|
| 703 | memoryRanges.reserve(numTables);
|
|---|
| 704 | for (int i = 0; i < numTables; ++i) {
|
|---|
| 705 | TableRecord *tableRecord =
|
|---|
| 706 | reinterpret_cast<TableRecord*>(markedTtf.data() + sizeof(OffsetTable) + i * sizeof(TableRecord));
|
|---|
| 707 | const quint32 offset = qFromBigEndian(tableRecord->offset);
|
|---|
| 708 | const quint32 length = qFromBigEndian(tableRecord->length);
|
|---|
| 709 | const quint32 lengthAligned = toDWordBoundary(length);
|
|---|
| 710 | ttfDataSize += lengthAligned;
|
|---|
| 711 | if (offset < recordsLength // must not intersect ttf header/records
|
|---|
| 712 | || offset % alignment != 0 // must be aligned
|
|---|
| 713 | || offset > ttfLength - alignment // table out of bounds
|
|---|
| 714 | || offset + lengthAligned > ttfLength // table out of bounds
|
|---|
| 715 | || ttfDataSize > ttfLength) // tables would not fit into the ttf
|
|---|
| 716 | return false;
|
|---|
| 717 |
|
|---|
| 718 | foreach (const Range &range, memoryRanges)
|
|---|
| 719 | if (offset < range.first + range.second && offset + lengthAligned > range.first)
|
|---|
| 720 | return false; // Overlaps with another table
|
|---|
| 721 | memoryRanges.append(Range(offset, lengthAligned));
|
|---|
| 722 |
|
|---|
| 723 | quint32 checkSum = qFromBigEndian(tableRecord->checkSum);
|
|---|
| 724 | if (tableRecord->tag == qToBigEndian(static_cast<quint32>('head'))) {
|
|---|
| 725 | if (length < ttfCheckSumAdjustmentOffset + sizeof(quint32))
|
|---|
| 726 | return false; // Invalid 'head' table
|
|---|
| 727 | const quint32 *checkSumAdjustmentTag =
|
|---|
| 728 | reinterpret_cast<const quint32*>(ttf.constData() + offset + ttfCheckSumAdjustmentOffset);
|
|---|
| 729 | const quint32 checkSumAdjustment = qFromBigEndian(*checkSumAdjustmentTag);
|
|---|
| 730 | checkSum += checkSumAdjustment;
|
|---|
| 731 | indexOfHeadTable = i; // For the ttf checksum re-calculation, later
|
|---|
| 732 | }
|
|---|
| 733 | if (checkSum != ttfCalcChecksum(ttf.constData() + offset, length))
|
|---|
| 734 | return false; // Table checksum is invalid
|
|---|
| 735 |
|
|---|
| 736 | bool updateTableChecksum = false;
|
|---|
| 737 | QByteArray table;
|
|---|
| 738 | if (tableRecord->tag == qToBigEndian(static_cast<quint32>('name'))) {
|
|---|
| 739 | table = QByteArray(ttf.constData() + offset, length);
|
|---|
| 740 | if (!ttfMarkNameTable(table, marker))
|
|---|
| 741 | return false; // Name table was not markable.
|
|---|
| 742 | updateTableChecksum = true;
|
|---|
| 743 | } else {
|
|---|
| 744 | table = QByteArray::fromRawData(ttf.constData() + offset, length);
|
|---|
| 745 | }
|
|---|
| 746 |
|
|---|
| 747 | tableRecord->offset = qToBigEndian(markedTtf.size());
|
|---|
| 748 | tableRecord->length = qToBigEndian(table.size());
|
|---|
| 749 | markedTtf.append(table);
|
|---|
| 750 | markedTtf.append(QByteArray(dWordPadding(table.size()), 0)); // 0-padding
|
|---|
| 751 | if (updateTableChecksum) {
|
|---|
| 752 | TableRecord *tableRecord = // Need to recalculate, since markedTtf changed
|
|---|
| 753 | reinterpret_cast<TableRecord*>(markedTtf.data() + sizeof(OffsetTable) + i * sizeof(TableRecord));
|
|---|
| 754 | const quint32 offset = qFromBigEndian(tableRecord->offset);
|
|---|
| 755 | const quint32 length = qFromBigEndian(tableRecord->length);
|
|---|
| 756 | tableRecord->checkSum = qToBigEndian(ttfCalcChecksum(markedTtf.constData() + offset, length));
|
|---|
| 757 | }
|
|---|
| 758 | }
|
|---|
| 759 | if (indexOfHeadTable == -1 // 'head' table is mandatory
|
|---|
| 760 | || ttfDataSize != ttfLength) // We do not allow ttf data "holes". Neither does Symbian.
|
|---|
| 761 | return false;
|
|---|
| 762 | TableRecord *headRecord =
|
|---|
| 763 | reinterpret_cast<TableRecord*>(markedTtf.data() + sizeof(OffsetTable) + indexOfHeadTable * sizeof(TableRecord));
|
|---|
| 764 | quint32 *checkSumAdjustmentTag =
|
|---|
| 765 | reinterpret_cast<quint32*>(markedTtf.data() + qFromBigEndian(headRecord->offset) + ttfCheckSumAdjustmentOffset);
|
|---|
| 766 | *checkSumAdjustmentTag = 0;
|
|---|
| 767 | const quint32 ttfChecksum = ttfCalcChecksum(markedTtf.constData(), markedTtf.count());
|
|---|
| 768 | *checkSumAdjustmentTag = qToBigEndian(ttfChecksumNumber - ttfChecksum);
|
|---|
| 769 | ttf = markedTtf;
|
|---|
| 770 | return true;
|
|---|
| 771 | }
|
|---|
| 772 |
|
|---|
| 773 | static inline bool ttfCanSymbianLoadFont(const QByteArray &data, const QString &fileName)
|
|---|
| 774 | {
|
|---|
| 775 | bool result = false;
|
|---|
| 776 | QString ttfFileName;
|
|---|
| 777 | QFile tempFileGuard;
|
|---|
| 778 | QFileInfo info(fileName);
|
|---|
| 779 | if (!data.isEmpty()) {
|
|---|
| 780 | QTemporaryFile tempfile(QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder()
|
|---|
| 781 | + QSymbianFontDatabaseExtrasImplementation::appFontMarker()
|
|---|
| 782 | + QLatin1String("XXXXXX.ttf"));
|
|---|
| 783 | if (!tempfile.open() || tempfile.write(data) == -1)
|
|---|
| 784 | return false;
|
|---|
| 785 | ttfFileName = QDir::toNativeSeparators(QFileInfo(tempfile).canonicalFilePath());
|
|---|
| 786 | tempfile.setAutoRemove(false);
|
|---|
| 787 | tempfile.close();
|
|---|
| 788 | tempFileGuard.setFileName(ttfFileName);
|
|---|
| 789 | if (!tempFileGuard.open(QIODevice::ReadOnly))
|
|---|
| 790 | return false;
|
|---|
| 791 | } else if (info.isFile()) {
|
|---|
| 792 | ttfFileName = QDir::toNativeSeparators(info.canonicalFilePath());
|
|---|
| 793 | } else {
|
|---|
| 794 | return false;
|
|---|
| 795 | }
|
|---|
| 796 |
|
|---|
| 797 | CFontStore *store = 0;
|
|---|
| 798 | RHeap* heap = User::ChunkHeap(NULL, 0x1000, 0x20000);
|
|---|
| 799 | if (heap) {
|
|---|
| 800 | QT_TRAP_THROWING(
|
|---|
| 801 | CleanupClosePushL(*heap);
|
|---|
| 802 | store = CFontStore::NewL(heap);
|
|---|
| 803 | CleanupStack::PushL(store);
|
|---|
| 804 | COpenFontRasterizer *rasterizer = COpenFontRasterizer::NewL(TUid::Uid(0x101F7F5E));
|
|---|
| 805 | CleanupStack::PushL(rasterizer);
|
|---|
| 806 | store->InstallRasterizerL(rasterizer);
|
|---|
| 807 | CleanupStack::Pop(rasterizer);
|
|---|
| 808 | TUid fontUid = {-1};
|
|---|
| 809 | TRAP_IGNORE(fontUid = store->AddFileL(qt_QString2TPtrC(ttfFileName)));
|
|---|
| 810 | if (fontUid.iUid != -1)
|
|---|
| 811 | result = true;
|
|---|
| 812 | CleanupStack::PopAndDestroy(2, heap); // heap, store
|
|---|
| 813 | );
|
|---|
| 814 | }
|
|---|
| 815 |
|
|---|
| 816 | if (tempFileGuard.isOpen())
|
|---|
| 817 | tempFileGuard.remove();
|
|---|
| 818 |
|
|---|
| 819 | return result;
|
|---|
| 820 | }
|
|---|
| 821 |
|
|---|
| 822 | static void registerFont(QFontDatabasePrivate::ApplicationFont *fnt)
|
|---|
| 823 | {
|
|---|
| 824 | if (QSymbianFontDatabaseExtrasImplementation::appFontLimitReached()
|
|---|
| 825 | || fnt->data.size() > ttfMaxFileSize // hard limit
|
|---|
| 826 | || fnt->data.isEmpty() && (!fnt->fileName.endsWith(QLatin1String(".ttf"), Qt::CaseInsensitive) // Only buffer or .ttf
|
|---|
| 827 | || QFileInfo(fnt->fileName).size() > ttfMaxFileSize)) // hard limit
|
|---|
| 828 | return;
|
|---|
| 829 |
|
|---|
| 830 | // Using ttfCanSymbianLoadFont() causes crashes on app destruction (Symbian^3|PR1 and lower).
|
|---|
| 831 | // Therefore, not using it for now, but eventually in a later version.
|
|---|
| 832 | // if (!ttfCanSymbianLoadFont(fnt->data, fnt->fileName))
|
|---|
| 833 | // return;
|
|---|
| 834 |
|
|---|
| 835 | QFontDatabasePrivate *db = privateDb();
|
|---|
| 836 | if (!db)
|
|---|
| 837 | return;
|
|---|
| 838 |
|
|---|
| 839 | if (!db->count)
|
|---|
| 840 | initializeDb();
|
|---|
| 841 |
|
|---|
| 842 | QSymbianFontDatabaseExtrasImplementation *dbExtras =
|
|---|
| 843 | static_cast<QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras);
|
|---|
| 844 | if (!dbExtras)
|
|---|
| 845 | return;
|
|---|
| 846 |
|
|---|
| 847 | const QString &marker = QSymbianFontDatabaseExtrasImplementation::appFontMarker();
|
|---|
| 848 |
|
|---|
| 849 | // The QTemporaryFile object being used in the following section must be
|
|---|
| 850 | // destructed before letting Symbian load the TTF file. Symbian would not
|
|---|
| 851 | // load it otherwise, because QTemporaryFile will still keep some handle
|
|---|
| 852 | // on it. The scope is used to reduce the life time of the QTemporaryFile.
|
|---|
| 853 | // In order to prevent other processes from modifying the file between the
|
|---|
| 854 | // moment where the QTemporaryFile is destructed and the file is loaded by
|
|---|
| 855 | // Symbian, we have a QFile "tempFileGuard" outside the scope which opens
|
|---|
| 856 | // the file in ReadOnly mode while the QTemporaryFile is still alive.
|
|---|
| 857 | QFile tempFileGuard;
|
|---|
| 858 | {
|
|---|
| 859 | QTemporaryFile tempfile(QSymbianFontDatabaseExtrasImplementation::tempAppFontFolder()
|
|---|
| 860 | + marker + QLatin1String("XXXXXX.ttf"));
|
|---|
| 861 | if (!tempfile.open())
|
|---|
| 862 | return;
|
|---|
| 863 | const QString tempFileName = QFileInfo(tempfile).canonicalFilePath();
|
|---|
| 864 | if (fnt->data.isEmpty()) {
|
|---|
| 865 | QFile sourceFile(fnt->fileName);
|
|---|
| 866 | if (!sourceFile.open(QIODevice::ReadOnly))
|
|---|
| 867 | return;
|
|---|
| 868 | fnt->data = sourceFile.readAll();
|
|---|
| 869 | }
|
|---|
| 870 | if (!ttfMarkAppFont(fnt->data, marker) || tempfile.write(fnt->data) == -1)
|
|---|
| 871 | return;
|
|---|
| 872 | tempfile.setAutoRemove(false);
|
|---|
| 873 | tempfile.close(); // Tempfile still keeps a file handle, forbidding write access
|
|---|
| 874 | fnt->data.clear(); // The TTF data was marked and saved. Not needed in memory, anymore.
|
|---|
| 875 | tempFileGuard.setFileName(tempFileName);
|
|---|
| 876 | if (!tempFileGuard.open(QIODevice::ReadOnly))
|
|---|
| 877 | return;
|
|---|
| 878 | fnt->temporaryFileName = tempFileName;
|
|---|
| 879 | }
|
|---|
| 880 |
|
|---|
| 881 | const QString fullFileName = QDir::toNativeSeparators(fnt->temporaryFileName);
|
|---|
| 882 | QSymbianFbsHeapLock lock(QSymbianFbsHeapLock::Unlock);
|
|---|
| 883 | const QStringList fontsOnServerBefore = qt_symbian_fontFamiliesOnFontServer();
|
|---|
| 884 | const TInt err =
|
|---|
| 885 | S60->screenDevice()->AddFile(qt_QString2TPtrC(fullFileName), fnt->screenDeviceFontFileId);
|
|---|
| 886 | tempFileGuard.close(); // Did its job
|
|---|
| 887 | const QStringList fontsOnServerAfter = qt_symbian_fontFamiliesOnFontServer();
|
|---|
| 888 | if (err == KErrNone && fontsOnServerBefore.count() < fontsOnServerAfter.count()) { // Added to screen device?
|
|---|
| 889 | int fontOnServerIndex = fontsOnServerAfter.count() - 1;
|
|---|
| 890 | for (int i = 0; i < fontsOnServerBefore.count(); i++) {
|
|---|
| 891 | if (fontsOnServerBefore.at(i) != fontsOnServerAfter.at(i)) {
|
|---|
| 892 | fontOnServerIndex = i;
|
|---|
| 893 | break;
|
|---|
| 894 | }
|
|---|
| 895 | }
|
|---|
| 896 |
|
|---|
| 897 | // Must remove all font engines with their CFonts, first.
|
|---|
| 898 | QFontCache::instance()->clear();
|
|---|
| 899 | db->free();
|
|---|
| 900 | QSymbianFontDatabaseExtrasImplementation::clear();
|
|---|
| 901 |
|
|---|
| 902 | if (!QSymbianTypeFaceExtras::symbianFontTableApiAvailable())
|
|---|
| 903 | fnt->fontStoreFontFileUid = dbExtras->addFontFileToFontStore(QFileInfo(fullFileName));
|
|---|
| 904 |
|
|---|
| 905 | const QString &appFontName = fontsOnServerAfter.at(fontOnServerIndex);
|
|---|
| 906 | fnt->families.append(qt_symbian_appFontNameWithoutMarker(appFontName));
|
|---|
| 907 | if (!qt_symbian_fontNameHasAppFontMarker(appFontName)
|
|---|
| 908 | || !registerScreenDeviceFont(fontOnServerIndex, dbExtras))
|
|---|
| 909 | dbExtras->removeAppFontData(fnt);
|
|---|
| 910 | } else {
|
|---|
| 911 | if (fnt->screenDeviceFontFileId > 0)
|
|---|
| 912 | S60->screenDevice()->RemoveFile(fnt->screenDeviceFontFileId); // May still have the file open!
|
|---|
| 913 | QFile::remove(fnt->temporaryFileName);
|
|---|
| 914 | *fnt = QFontDatabasePrivate::ApplicationFont();
|
|---|
| 915 | }
|
|---|
| 916 | lock.relock();
|
|---|
| 917 | }
|
|---|
| 918 |
|
|---|
| 919 | bool QFontDatabase::removeApplicationFont(int handle)
|
|---|
| 920 | {
|
|---|
| 921 | QMutexLocker locker(fontDatabaseMutex());
|
|---|
| 922 |
|
|---|
| 923 | QFontDatabasePrivate *db = privateDb();
|
|---|
| 924 | if (!db || handle < 0 || handle >= db->applicationFonts.count())
|
|---|
| 925 | return false;
|
|---|
| 926 | QSymbianFontDatabaseExtrasImplementation *dbExtras =
|
|---|
| 927 | static_cast<QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras);
|
|---|
| 928 | if (!dbExtras)
|
|---|
| 929 | return false;
|
|---|
| 930 |
|
|---|
| 931 | QFontDatabasePrivate::ApplicationFont *fnt = &db->applicationFonts[handle];
|
|---|
| 932 | if (fnt->families.isEmpty())
|
|---|
| 933 | return true; // Nothing to remove. Return peacefully.
|
|---|
| 934 |
|
|---|
| 935 | // Must remove all font engines with their CFonts, first
|
|---|
| 936 | QFontCache::instance()->clear();
|
|---|
| 937 | db->free();
|
|---|
| 938 | dbExtras->removeAppFontData(fnt);
|
|---|
| 939 |
|
|---|
| 940 | db->invalidate(); // This will just emit 'fontDatabaseChanged()'
|
|---|
| 941 | return true;
|
|---|
| 942 | }
|
|---|
| 943 |
|
|---|
| 944 | bool QFontDatabase::removeAllApplicationFonts()
|
|---|
| 945 | {
|
|---|
| 946 | QMutexLocker locker(fontDatabaseMutex());
|
|---|
| 947 |
|
|---|
| 948 | const int applicationFontsCount = privateDb()->applicationFonts.count();
|
|---|
| 949 | for (int i = 0; i < applicationFontsCount; ++i)
|
|---|
| 950 | if (!removeApplicationFont(i))
|
|---|
| 951 | return false;
|
|---|
| 952 | return true;
|
|---|
| 953 | }
|
|---|
| 954 |
|
|---|
| 955 | bool QFontDatabase::supportsThreadedFontRendering()
|
|---|
| 956 | {
|
|---|
| 957 | return false;
|
|---|
| 958 | }
|
|---|
| 959 |
|
|---|
| 960 | static
|
|---|
| 961 | QFontDef cleanedFontDef(const QFontDef &req)
|
|---|
| 962 | {
|
|---|
| 963 | QFontDef result = req;
|
|---|
| 964 | if (result.pixelSize <= 0) {
|
|---|
| 965 | result.pixelSize = QFontEngineS60::pointsToPixels(qMax(qreal(1.0), result.pointSize));
|
|---|
| 966 | result.pointSize = 0;
|
|---|
| 967 | }
|
|---|
| 968 | return result;
|
|---|
| 969 | }
|
|---|
| 970 |
|
|---|
| 971 | QFontEngine *QFontDatabase::findFont(int script, const QFontPrivate *d, const QFontDef &req)
|
|---|
| 972 | {
|
|---|
| 973 | const QFontCache::Key key(cleanedFontDef(req), script);
|
|---|
| 974 |
|
|---|
| 975 | if (!privateDb()->count)
|
|---|
| 976 | initializeDb();
|
|---|
| 977 |
|
|---|
| 978 | QFontEngine *fe = QFontCache::instance()->findEngine(key);
|
|---|
| 979 | if (!fe) {
|
|---|
| 980 | // Making sure that fe->fontDef.family will be an existing font.
|
|---|
| 981 | initializeDb();
|
|---|
| 982 | QFontDatabasePrivate *db = privateDb();
|
|---|
| 983 | QtFontDesc desc;
|
|---|
| 984 | QList<int> blacklistedFamilies;
|
|---|
| 985 | match(script, key.def, key.def.family, QString(), -1, &desc, blacklistedFamilies);
|
|---|
| 986 | if (!desc.family) // falling back to application font
|
|---|
| 987 | desc.family = db->family(QApplication::font().defaultFamily());
|
|---|
| 988 | Q_ASSERT(desc.family);
|
|---|
| 989 |
|
|---|
| 990 | // Making sure that desc.family supports the requested script
|
|---|
| 991 | QtFontDesc mappedDesc;
|
|---|
| 992 | bool supportsScript = false;
|
|---|
| 993 | do {
|
|---|
| 994 | match(script, req, QString(), QString(), -1, &mappedDesc, blacklistedFamilies);
|
|---|
| 995 | if (mappedDesc.family == desc.family) {
|
|---|
| 996 | supportsScript = true;
|
|---|
| 997 | break;
|
|---|
| 998 | }
|
|---|
| 999 | blacklistedFamilies.append(mappedDesc.familyIndex);
|
|---|
| 1000 | } while (mappedDesc.family);
|
|---|
| 1001 | if (!supportsScript) {
|
|---|
| 1002 | blacklistedFamilies.clear();
|
|---|
| 1003 | match(script, req, QString(), QString(), -1, &mappedDesc, blacklistedFamilies);
|
|---|
| 1004 | if (mappedDesc.family)
|
|---|
| 1005 | desc = mappedDesc;
|
|---|
| 1006 | }
|
|---|
| 1007 |
|
|---|
| 1008 | const QString fontFamily = desc.family->name;
|
|---|
| 1009 | QFontDef request = req;
|
|---|
| 1010 | request.family = fontFamily;
|
|---|
| 1011 | #ifdef QT_NO_FREETYPE
|
|---|
| 1012 | const QSymbianFontDatabaseExtrasImplementation *dbExtras =
|
|---|
| 1013 | static_cast<const QSymbianFontDatabaseExtrasImplementation*>(db->symbianExtras);
|
|---|
| 1014 | const QSymbianTypeFaceExtras *typeFaceExtras =
|
|---|
| 1015 | dbExtras->extras(fontFamily, request.weight > QFont::Normal, request.style != QFont::StyleNormal);
|
|---|
| 1016 |
|
|---|
| 1017 | // We need a valid pixelSize, e.g. for lineThickness()
|
|---|
| 1018 | if (request.pixelSize < 0)
|
|---|
| 1019 | request.pixelSize = request.pointSize * d->dpi / 72;
|
|---|
| 1020 |
|
|---|
| 1021 | fe = new QFontEngineS60(request, typeFaceExtras);
|
|---|
| 1022 | #else // QT_NO_FREETYPE
|
|---|
| 1023 | Q_UNUSED(d)
|
|---|
| 1024 | QFontEngine::FaceId faceId;
|
|---|
| 1025 | const QtFontFamily * const reqQtFontFamily = db->family(fontFamily);
|
|---|
| 1026 | faceId.filename = reqQtFontFamily->fontFilename;
|
|---|
| 1027 | faceId.index = reqQtFontFamily->fontFileIndex;
|
|---|
| 1028 |
|
|---|
| 1029 | QFontEngineFTS60 *fte = new QFontEngineFTS60(cleanedFontDef(request));
|
|---|
| 1030 | if (fte->init(faceId, true, QFontEngineFT::Format_A8))
|
|---|
| 1031 | fe = fte;
|
|---|
| 1032 | else
|
|---|
| 1033 | delete fte;
|
|---|
| 1034 | #endif // QT_NO_FREETYPE
|
|---|
| 1035 |
|
|---|
| 1036 | Q_ASSERT(fe);
|
|---|
| 1037 | if (script == QUnicodeTables::Common
|
|---|
| 1038 | && !(req.styleStrategy & QFont::NoFontMerging)
|
|---|
| 1039 | && !fe->symbol) {
|
|---|
| 1040 |
|
|---|
| 1041 | QStringList commonFonts;
|
|---|
| 1042 | for (int ws = 1; ws < QFontDatabase::WritingSystemsCount; ++ws) {
|
|---|
| 1043 | if (scriptForWritingSystem[ws] != script)
|
|---|
| 1044 | continue;
|
|---|
| 1045 | for (int i = 0; i < db->count; ++i) {
|
|---|
| 1046 | if (db->families[i]->writingSystems[ws] & QtFontFamily::Supported)
|
|---|
| 1047 | commonFonts.append(db->families[i]->name);
|
|---|
| 1048 | }
|
|---|
| 1049 | }
|
|---|
| 1050 |
|
|---|
| 1051 | // Hack: Prioritize .ccc fonts
|
|---|
| 1052 | const QString niceEastAsianFont(QLatin1String("Sans MT 936_S60"));
|
|---|
| 1053 | if (commonFonts.removeAll(niceEastAsianFont) > 0)
|
|---|
| 1054 | commonFonts.prepend(niceEastAsianFont);
|
|---|
| 1055 |
|
|---|
| 1056 | fe = new QFontEngineMultiS60(fe, script, commonFonts);
|
|---|
| 1057 | }
|
|---|
| 1058 | }
|
|---|
| 1059 | fe->ref.ref();
|
|---|
| 1060 | QFontCache::instance()->insertEngine(key, fe);
|
|---|
| 1061 | return fe;
|
|---|
| 1062 | }
|
|---|
| 1063 |
|
|---|
| 1064 | void QFontDatabase::load(const QFontPrivate *d, int script)
|
|---|
| 1065 | {
|
|---|
| 1066 | QFontEngine *fe = 0;
|
|---|
| 1067 | QFontDef req = d->request;
|
|---|
| 1068 |
|
|---|
| 1069 | if (!d->engineData) {
|
|---|
| 1070 | const QFontCache::Key key(cleanedFontDef(req), script);
|
|---|
| 1071 | getEngineData(d, key);
|
|---|
| 1072 | }
|
|---|
| 1073 |
|
|---|
| 1074 | // the cached engineData could have already loaded the engine we want
|
|---|
| 1075 | if (d->engineData->engines[script])
|
|---|
| 1076 | fe = d->engineData->engines[script];
|
|---|
| 1077 |
|
|---|
| 1078 | if (!fe) {
|
|---|
| 1079 | if (qt_enable_test_font && req.family == QLatin1String("__Qt__Box__Engine__")) {
|
|---|
| 1080 | fe = new QTestFontEngine(req.pixelSize);
|
|---|
| 1081 | fe->fontDef = req;
|
|---|
| 1082 | } else {
|
|---|
| 1083 | fe = findFont(script, d, req);
|
|---|
| 1084 | }
|
|---|
| 1085 | d->engineData->engines[script] = fe;
|
|---|
| 1086 | }
|
|---|
| 1087 | }
|
|---|
| 1088 |
|
|---|
| 1089 | QT_END_NAMESPACE
|
|---|