Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | #include "mozilla/DebugOnly.h" |
michael@0 | 7 | #include "mozilla/MathAlgorithms.h" |
michael@0 | 8 | |
michael@0 | 9 | #ifdef MOZ_LOGGING |
michael@0 | 10 | #define FORCE_PR_LOG /* Allow logging in the release build */ |
michael@0 | 11 | #endif |
michael@0 | 12 | #include "prlog.h" |
michael@0 | 13 | |
michael@0 | 14 | #include "nsServiceManagerUtils.h" |
michael@0 | 15 | #include "nsExpirationTracker.h" |
michael@0 | 16 | #include "nsILanguageAtomService.h" |
michael@0 | 17 | #include "nsITimer.h" |
michael@0 | 18 | |
michael@0 | 19 | #include "gfxFont.h" |
michael@0 | 20 | #include "gfxPlatform.h" |
michael@0 | 21 | #include "nsGkAtoms.h" |
michael@0 | 22 | |
michael@0 | 23 | #include "gfxTypes.h" |
michael@0 | 24 | #include "gfxContext.h" |
michael@0 | 25 | #include "gfxFontMissingGlyphs.h" |
michael@0 | 26 | #include "gfxHarfBuzzShaper.h" |
michael@0 | 27 | #include "gfxUserFontSet.h" |
michael@0 | 28 | #include "gfxPlatformFontList.h" |
michael@0 | 29 | #include "gfxScriptItemizer.h" |
michael@0 | 30 | #include "nsUnicodeProperties.h" |
michael@0 | 31 | #include "nsMathUtils.h" |
michael@0 | 32 | #include "nsBidiUtils.h" |
michael@0 | 33 | #include "nsUnicodeRange.h" |
michael@0 | 34 | #include "nsStyleConsts.h" |
michael@0 | 35 | #include "mozilla/FloatingPoint.h" |
michael@0 | 36 | #include "mozilla/Likely.h" |
michael@0 | 37 | #include "mozilla/MemoryReporting.h" |
michael@0 | 38 | #include "mozilla/Preferences.h" |
michael@0 | 39 | #include "mozilla/Services.h" |
michael@0 | 40 | #include "mozilla/Telemetry.h" |
michael@0 | 41 | #include "gfxSVGGlyphs.h" |
michael@0 | 42 | #include "gfxMathTable.h" |
michael@0 | 43 | #include "gfx2DGlue.h" |
michael@0 | 44 | |
michael@0 | 45 | #if defined(XP_MACOSX) |
michael@0 | 46 | #include "nsCocoaFeatures.h" |
michael@0 | 47 | #endif |
michael@0 | 48 | |
michael@0 | 49 | #include "cairo.h" |
michael@0 | 50 | #include "gfxFontTest.h" |
michael@0 | 51 | |
michael@0 | 52 | #include "harfbuzz/hb.h" |
michael@0 | 53 | #include "harfbuzz/hb-ot.h" |
michael@0 | 54 | #include "graphite2/Font.h" |
michael@0 | 55 | |
michael@0 | 56 | #include "nsCRT.h" |
michael@0 | 57 | #include "GeckoProfiler.h" |
michael@0 | 58 | #include "gfxFontConstants.h" |
michael@0 | 59 | |
michael@0 | 60 | #include <algorithm> |
michael@0 | 61 | |
michael@0 | 62 | using namespace mozilla; |
michael@0 | 63 | using namespace mozilla::gfx; |
michael@0 | 64 | using namespace mozilla::unicode; |
michael@0 | 65 | using mozilla::services::GetObserverService; |
michael@0 | 66 | |
michael@0 | 67 | gfxFontCache *gfxFontCache::gGlobalCache = nullptr; |
michael@0 | 68 | |
michael@0 | 69 | static const char16_t kEllipsisChar[] = { 0x2026, 0x0 }; |
michael@0 | 70 | static const char16_t kASCIIPeriodsChar[] = { '.', '.', '.', 0x0 }; |
michael@0 | 71 | |
michael@0 | 72 | #ifdef DEBUG_roc |
michael@0 | 73 | #define DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 74 | #endif |
michael@0 | 75 | |
michael@0 | 76 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 77 | static uint32_t gTextRunStorageHighWaterMark = 0; |
michael@0 | 78 | static uint32_t gTextRunStorage = 0; |
michael@0 | 79 | static uint32_t gFontCount = 0; |
michael@0 | 80 | static uint32_t gGlyphExtentsCount = 0; |
michael@0 | 81 | static uint32_t gGlyphExtentsWidthsTotalSize = 0; |
michael@0 | 82 | static uint32_t gGlyphExtentsSetupEagerSimple = 0; |
michael@0 | 83 | static uint32_t gGlyphExtentsSetupEagerTight = 0; |
michael@0 | 84 | static uint32_t gGlyphExtentsSetupLazyTight = 0; |
michael@0 | 85 | static uint32_t gGlyphExtentsSetupFallBackToTight = 0; |
michael@0 | 86 | #endif |
michael@0 | 87 | |
michael@0 | 88 | #ifdef PR_LOGGING |
michael@0 | 89 | #define LOG_FONTINIT(args) PR_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), \ |
michael@0 | 90 | PR_LOG_DEBUG, args) |
michael@0 | 91 | #define LOG_FONTINIT_ENABLED() PR_LOG_TEST( \ |
michael@0 | 92 | gfxPlatform::GetLog(eGfxLog_fontinit), \ |
michael@0 | 93 | PR_LOG_DEBUG) |
michael@0 | 94 | #endif // PR_LOGGING |
michael@0 | 95 | |
michael@0 | 96 | void |
michael@0 | 97 | gfxCharacterMap::NotifyReleased() |
michael@0 | 98 | { |
michael@0 | 99 | gfxPlatformFontList *fontlist = gfxPlatformFontList::PlatformFontList(); |
michael@0 | 100 | if (mShared) { |
michael@0 | 101 | fontlist->RemoveCmap(this); |
michael@0 | 102 | } |
michael@0 | 103 | delete this; |
michael@0 | 104 | } |
michael@0 | 105 | |
michael@0 | 106 | gfxFontEntry::gfxFontEntry() : |
michael@0 | 107 | mItalic(false), mFixedPitch(false), |
michael@0 | 108 | mIsProxy(false), mIsValid(true), |
michael@0 | 109 | mIsBadUnderlineFont(false), |
michael@0 | 110 | mIsUserFont(false), |
michael@0 | 111 | mIsLocalUserFont(false), |
michael@0 | 112 | mStandardFace(false), |
michael@0 | 113 | mSymbolFont(false), |
michael@0 | 114 | mIgnoreGDEF(false), |
michael@0 | 115 | mIgnoreGSUB(false), |
michael@0 | 116 | mSVGInitialized(false), |
michael@0 | 117 | mMathInitialized(false), |
michael@0 | 118 | mHasSpaceFeaturesInitialized(false), |
michael@0 | 119 | mHasSpaceFeatures(false), |
michael@0 | 120 | mHasSpaceFeaturesKerning(false), |
michael@0 | 121 | mHasSpaceFeaturesNonKerning(false), |
michael@0 | 122 | mSkipDefaultFeatureSpaceCheck(false), |
michael@0 | 123 | mCheckedForGraphiteTables(false), |
michael@0 | 124 | mHasCmapTable(false), |
michael@0 | 125 | mGrFaceInitialized(false), |
michael@0 | 126 | mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL), |
michael@0 | 127 | mUVSOffset(0), mUVSData(nullptr), |
michael@0 | 128 | mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE), |
michael@0 | 129 | mUnitsPerEm(0), |
michael@0 | 130 | mHBFace(nullptr), |
michael@0 | 131 | mGrFace(nullptr), |
michael@0 | 132 | mGrFaceRefCnt(0) |
michael@0 | 133 | { |
michael@0 | 134 | memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); |
michael@0 | 135 | memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); |
michael@0 | 136 | } |
michael@0 | 137 | |
michael@0 | 138 | gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) : |
michael@0 | 139 | mName(aName), mItalic(false), mFixedPitch(false), |
michael@0 | 140 | mIsProxy(false), mIsValid(true), |
michael@0 | 141 | mIsBadUnderlineFont(false), mIsUserFont(false), |
michael@0 | 142 | mIsLocalUserFont(false), mStandardFace(aIsStandardFace), |
michael@0 | 143 | mSymbolFont(false), |
michael@0 | 144 | mIgnoreGDEF(false), |
michael@0 | 145 | mIgnoreGSUB(false), |
michael@0 | 146 | mSVGInitialized(false), |
michael@0 | 147 | mMathInitialized(false), |
michael@0 | 148 | mHasSpaceFeaturesInitialized(false), |
michael@0 | 149 | mHasSpaceFeatures(false), |
michael@0 | 150 | mHasSpaceFeaturesKerning(false), |
michael@0 | 151 | mHasSpaceFeaturesNonKerning(false), |
michael@0 | 152 | mSkipDefaultFeatureSpaceCheck(false), |
michael@0 | 153 | mCheckedForGraphiteTables(false), |
michael@0 | 154 | mHasCmapTable(false), |
michael@0 | 155 | mGrFaceInitialized(false), |
michael@0 | 156 | mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL), |
michael@0 | 157 | mUVSOffset(0), mUVSData(nullptr), |
michael@0 | 158 | mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE), |
michael@0 | 159 | mUnitsPerEm(0), |
michael@0 | 160 | mHBFace(nullptr), |
michael@0 | 161 | mGrFace(nullptr), |
michael@0 | 162 | mGrFaceRefCnt(0) |
michael@0 | 163 | { |
michael@0 | 164 | memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures)); |
michael@0 | 165 | memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures)); |
michael@0 | 166 | } |
michael@0 | 167 | |
michael@0 | 168 | gfxFontEntry::~gfxFontEntry() |
michael@0 | 169 | { |
michael@0 | 170 | // For downloaded fonts, we need to tell the user font cache that this |
michael@0 | 171 | // entry is being deleted. |
michael@0 | 172 | if (!mIsProxy && IsUserFont() && !IsLocalUserFont()) { |
michael@0 | 173 | gfxUserFontSet::UserFontCache::ForgetFont(this); |
michael@0 | 174 | } |
michael@0 | 175 | |
michael@0 | 176 | // By the time the entry is destroyed, all font instances that were |
michael@0 | 177 | // using it should already have been deleted, and so the HB and/or Gr |
michael@0 | 178 | // face objects should have been released. |
michael@0 | 179 | MOZ_ASSERT(!mHBFace); |
michael@0 | 180 | MOZ_ASSERT(!mGrFaceInitialized); |
michael@0 | 181 | } |
michael@0 | 182 | |
michael@0 | 183 | bool gfxFontEntry::IsSymbolFont() |
michael@0 | 184 | { |
michael@0 | 185 | return mSymbolFont; |
michael@0 | 186 | } |
michael@0 | 187 | |
michael@0 | 188 | bool gfxFontEntry::TestCharacterMap(uint32_t aCh) |
michael@0 | 189 | { |
michael@0 | 190 | if (!mCharacterMap) { |
michael@0 | 191 | ReadCMAP(); |
michael@0 | 192 | NS_ASSERTION(mCharacterMap, "failed to initialize character map"); |
michael@0 | 193 | } |
michael@0 | 194 | return mCharacterMap->test(aCh); |
michael@0 | 195 | } |
michael@0 | 196 | |
michael@0 | 197 | nsresult gfxFontEntry::InitializeUVSMap() |
michael@0 | 198 | { |
michael@0 | 199 | // mUVSOffset will not be initialized |
michael@0 | 200 | // until cmap is initialized. |
michael@0 | 201 | if (!mCharacterMap) { |
michael@0 | 202 | ReadCMAP(); |
michael@0 | 203 | NS_ASSERTION(mCharacterMap, "failed to initialize character map"); |
michael@0 | 204 | } |
michael@0 | 205 | |
michael@0 | 206 | if (!mUVSOffset) { |
michael@0 | 207 | return NS_ERROR_FAILURE; |
michael@0 | 208 | } |
michael@0 | 209 | |
michael@0 | 210 | if (!mUVSData) { |
michael@0 | 211 | const uint32_t kCmapTag = TRUETYPE_TAG('c','m','a','p'); |
michael@0 | 212 | AutoTable cmapTable(this, kCmapTag); |
michael@0 | 213 | if (!cmapTable) { |
michael@0 | 214 | mUVSOffset = 0; // don't bother to read the table again |
michael@0 | 215 | return NS_ERROR_FAILURE; |
michael@0 | 216 | } |
michael@0 | 217 | |
michael@0 | 218 | uint8_t* uvsData; |
michael@0 | 219 | unsigned int cmapLen; |
michael@0 | 220 | const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen); |
michael@0 | 221 | nsresult rv = gfxFontUtils::ReadCMAPTableFormat14( |
michael@0 | 222 | (const uint8_t*)cmapData + mUVSOffset, |
michael@0 | 223 | cmapLen - mUVSOffset, uvsData); |
michael@0 | 224 | |
michael@0 | 225 | if (NS_FAILED(rv)) { |
michael@0 | 226 | mUVSOffset = 0; // don't bother to read the table again |
michael@0 | 227 | return rv; |
michael@0 | 228 | } |
michael@0 | 229 | |
michael@0 | 230 | mUVSData = uvsData; |
michael@0 | 231 | } |
michael@0 | 232 | |
michael@0 | 233 | return NS_OK; |
michael@0 | 234 | } |
michael@0 | 235 | |
michael@0 | 236 | uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS) |
michael@0 | 237 | { |
michael@0 | 238 | InitializeUVSMap(); |
michael@0 | 239 | |
michael@0 | 240 | if (mUVSData) { |
michael@0 | 241 | return gfxFontUtils::MapUVSToGlyphFormat14(mUVSData, aCh, aVS); |
michael@0 | 242 | } |
michael@0 | 243 | |
michael@0 | 244 | return 0; |
michael@0 | 245 | } |
michael@0 | 246 | |
michael@0 | 247 | bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags) |
michael@0 | 248 | { |
michael@0 | 249 | hb_face_t *face = GetHBFace(); |
michael@0 | 250 | if (!face) { |
michael@0 | 251 | return false; |
michael@0 | 252 | } |
michael@0 | 253 | |
michael@0 | 254 | unsigned int index; |
michael@0 | 255 | hb_tag_t chosenScript; |
michael@0 | 256 | bool found = |
michael@0 | 257 | hb_ot_layout_table_choose_script(face, TRUETYPE_TAG('G','S','U','B'), |
michael@0 | 258 | aScriptTags, &index, &chosenScript); |
michael@0 | 259 | hb_face_destroy(face); |
michael@0 | 260 | |
michael@0 | 261 | return found && chosenScript != TRUETYPE_TAG('D','F','L','T'); |
michael@0 | 262 | } |
michael@0 | 263 | |
michael@0 | 264 | nsresult gfxFontEntry::ReadCMAP(FontInfoData *aFontInfoData) |
michael@0 | 265 | { |
michael@0 | 266 | NS_ASSERTION(false, "using default no-op implementation of ReadCMAP"); |
michael@0 | 267 | mCharacterMap = new gfxCharacterMap(); |
michael@0 | 268 | return NS_OK; |
michael@0 | 269 | } |
michael@0 | 270 | |
michael@0 | 271 | nsString |
michael@0 | 272 | gfxFontEntry::RealFaceName() |
michael@0 | 273 | { |
michael@0 | 274 | AutoTable nameTable(this, TRUETYPE_TAG('n','a','m','e')); |
michael@0 | 275 | if (nameTable) { |
michael@0 | 276 | nsAutoString name; |
michael@0 | 277 | nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name); |
michael@0 | 278 | if (NS_SUCCEEDED(rv)) { |
michael@0 | 279 | return name; |
michael@0 | 280 | } |
michael@0 | 281 | } |
michael@0 | 282 | return Name(); |
michael@0 | 283 | } |
michael@0 | 284 | |
michael@0 | 285 | already_AddRefed<gfxFont> |
michael@0 | 286 | gfxFontEntry::FindOrMakeFont(const gfxFontStyle *aStyle, bool aNeedsBold) |
michael@0 | 287 | { |
michael@0 | 288 | // the font entry name is the psname, not the family name |
michael@0 | 289 | nsRefPtr<gfxFont> font = gfxFontCache::GetCache()->Lookup(this, aStyle); |
michael@0 | 290 | |
michael@0 | 291 | if (!font) { |
michael@0 | 292 | gfxFont *newFont = CreateFontInstance(aStyle, aNeedsBold); |
michael@0 | 293 | if (!newFont) |
michael@0 | 294 | return nullptr; |
michael@0 | 295 | if (!newFont->Valid()) { |
michael@0 | 296 | delete newFont; |
michael@0 | 297 | return nullptr; |
michael@0 | 298 | } |
michael@0 | 299 | font = newFont; |
michael@0 | 300 | gfxFontCache::GetCache()->AddNew(font); |
michael@0 | 301 | } |
michael@0 | 302 | return font.forget(); |
michael@0 | 303 | } |
michael@0 | 304 | |
michael@0 | 305 | uint16_t |
michael@0 | 306 | gfxFontEntry::UnitsPerEm() |
michael@0 | 307 | { |
michael@0 | 308 | if (!mUnitsPerEm) { |
michael@0 | 309 | AutoTable headTable(this, TRUETYPE_TAG('h','e','a','d')); |
michael@0 | 310 | if (headTable) { |
michael@0 | 311 | uint32_t len; |
michael@0 | 312 | const HeadTable* head = |
michael@0 | 313 | reinterpret_cast<const HeadTable*>(hb_blob_get_data(headTable, |
michael@0 | 314 | &len)); |
michael@0 | 315 | if (len >= sizeof(HeadTable)) { |
michael@0 | 316 | mUnitsPerEm = head->unitsPerEm; |
michael@0 | 317 | } |
michael@0 | 318 | } |
michael@0 | 319 | |
michael@0 | 320 | // if we didn't find a usable 'head' table, or if the value was |
michael@0 | 321 | // outside the valid range, record it as invalid |
michael@0 | 322 | if (mUnitsPerEm < kMinUPEM || mUnitsPerEm > kMaxUPEM) { |
michael@0 | 323 | mUnitsPerEm = kInvalidUPEM; |
michael@0 | 324 | } |
michael@0 | 325 | } |
michael@0 | 326 | return mUnitsPerEm; |
michael@0 | 327 | } |
michael@0 | 328 | |
michael@0 | 329 | bool |
michael@0 | 330 | gfxFontEntry::HasSVGGlyph(uint32_t aGlyphId) |
michael@0 | 331 | { |
michael@0 | 332 | NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first."); |
michael@0 | 333 | return mSVGGlyphs->HasSVGGlyph(aGlyphId); |
michael@0 | 334 | } |
michael@0 | 335 | |
michael@0 | 336 | bool |
michael@0 | 337 | gfxFontEntry::GetSVGGlyphExtents(gfxContext *aContext, uint32_t aGlyphId, |
michael@0 | 338 | gfxRect *aResult) |
michael@0 | 339 | { |
michael@0 | 340 | NS_ABORT_IF_FALSE(mSVGInitialized, |
michael@0 | 341 | "SVG data has not yet been loaded. TryGetSVGData() first."); |
michael@0 | 342 | NS_ABORT_IF_FALSE(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM, |
michael@0 | 343 | "font has invalid unitsPerEm"); |
michael@0 | 344 | |
michael@0 | 345 | gfxContextAutoSaveRestore matrixRestore(aContext); |
michael@0 | 346 | cairo_matrix_t fontMatrix; |
michael@0 | 347 | cairo_get_font_matrix(aContext->GetCairo(), &fontMatrix); |
michael@0 | 348 | |
michael@0 | 349 | gfxMatrix svgToAppSpace = *reinterpret_cast<gfxMatrix*>(&fontMatrix); |
michael@0 | 350 | svgToAppSpace.Scale(1.0f / mUnitsPerEm, 1.0f / mUnitsPerEm); |
michael@0 | 351 | |
michael@0 | 352 | return mSVGGlyphs->GetGlyphExtents(aGlyphId, svgToAppSpace, aResult); |
michael@0 | 353 | } |
michael@0 | 354 | |
michael@0 | 355 | bool |
michael@0 | 356 | gfxFontEntry::RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId, |
michael@0 | 357 | int aDrawMode, gfxTextContextPaint *aContextPaint) |
michael@0 | 358 | { |
michael@0 | 359 | NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first."); |
michael@0 | 360 | return mSVGGlyphs->RenderGlyph(aContext, aGlyphId, DrawMode(aDrawMode), |
michael@0 | 361 | aContextPaint); |
michael@0 | 362 | } |
michael@0 | 363 | |
michael@0 | 364 | bool |
michael@0 | 365 | gfxFontEntry::TryGetSVGData(gfxFont* aFont) |
michael@0 | 366 | { |
michael@0 | 367 | if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) { |
michael@0 | 368 | return false; |
michael@0 | 369 | } |
michael@0 | 370 | |
michael@0 | 371 | if (!mSVGInitialized) { |
michael@0 | 372 | mSVGInitialized = true; |
michael@0 | 373 | |
michael@0 | 374 | // If UnitsPerEm is not known/valid, we can't use SVG glyphs |
michael@0 | 375 | if (UnitsPerEm() == kInvalidUPEM) { |
michael@0 | 376 | return false; |
michael@0 | 377 | } |
michael@0 | 378 | |
michael@0 | 379 | // We don't use AutoTable here because we'll pass ownership of this |
michael@0 | 380 | // blob to the gfxSVGGlyphs, once we've confirmed the table exists |
michael@0 | 381 | hb_blob_t *svgTable = GetFontTable(TRUETYPE_TAG('S','V','G',' ')); |
michael@0 | 382 | if (!svgTable) { |
michael@0 | 383 | return false; |
michael@0 | 384 | } |
michael@0 | 385 | |
michael@0 | 386 | // gfxSVGGlyphs will hb_blob_destroy() the table when it is finished |
michael@0 | 387 | // with it. |
michael@0 | 388 | mSVGGlyphs = new gfxSVGGlyphs(svgTable, this); |
michael@0 | 389 | } |
michael@0 | 390 | |
michael@0 | 391 | if (!mFontsUsingSVGGlyphs.Contains(aFont)) { |
michael@0 | 392 | mFontsUsingSVGGlyphs.AppendElement(aFont); |
michael@0 | 393 | } |
michael@0 | 394 | |
michael@0 | 395 | return !!mSVGGlyphs; |
michael@0 | 396 | } |
michael@0 | 397 | |
michael@0 | 398 | void |
michael@0 | 399 | gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont) |
michael@0 | 400 | { |
michael@0 | 401 | mFontsUsingSVGGlyphs.RemoveElement(aFont); |
michael@0 | 402 | } |
michael@0 | 403 | |
michael@0 | 404 | void |
michael@0 | 405 | gfxFontEntry::NotifyGlyphsChanged() |
michael@0 | 406 | { |
michael@0 | 407 | for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) { |
michael@0 | 408 | gfxFont* font = mFontsUsingSVGGlyphs[i]; |
michael@0 | 409 | font->NotifyGlyphsChanged(); |
michael@0 | 410 | } |
michael@0 | 411 | } |
michael@0 | 412 | |
michael@0 | 413 | bool |
michael@0 | 414 | gfxFontEntry::TryGetMathTable(gfxFont* aFont) |
michael@0 | 415 | { |
michael@0 | 416 | if (!mMathInitialized) { |
michael@0 | 417 | mMathInitialized = true; |
michael@0 | 418 | |
michael@0 | 419 | // If UnitsPerEm is not known/valid, we can't use MATH table |
michael@0 | 420 | if (UnitsPerEm() == kInvalidUPEM) { |
michael@0 | 421 | return false; |
michael@0 | 422 | } |
michael@0 | 423 | |
michael@0 | 424 | // We don't use AutoTable here because we'll pass ownership of this |
michael@0 | 425 | // blob to the gfxMathTable, once we've confirmed the table exists |
michael@0 | 426 | hb_blob_t *mathTable = GetFontTable(TRUETYPE_TAG('M','A','T','H')); |
michael@0 | 427 | if (!mathTable) { |
michael@0 | 428 | return false; |
michael@0 | 429 | } |
michael@0 | 430 | |
michael@0 | 431 | // gfxMathTable will hb_blob_destroy() the table when it is finished |
michael@0 | 432 | // with it. |
michael@0 | 433 | mMathTable = new gfxMathTable(mathTable); |
michael@0 | 434 | if (!mMathTable->HasValidHeaders()) { |
michael@0 | 435 | mMathTable = nullptr; |
michael@0 | 436 | return false; |
michael@0 | 437 | } |
michael@0 | 438 | } |
michael@0 | 439 | |
michael@0 | 440 | return !!mMathTable; |
michael@0 | 441 | } |
michael@0 | 442 | |
michael@0 | 443 | gfxFloat |
michael@0 | 444 | gfxFontEntry::GetMathConstant(gfxFontEntry::MathConstant aConstant) |
michael@0 | 445 | { |
michael@0 | 446 | NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); |
michael@0 | 447 | gfxFloat value = mMathTable->GetMathConstant(aConstant); |
michael@0 | 448 | if (aConstant == gfxFontEntry::ScriptPercentScaleDown || |
michael@0 | 449 | aConstant == gfxFontEntry::ScriptScriptPercentScaleDown || |
michael@0 | 450 | aConstant == gfxFontEntry::RadicalDegreeBottomRaisePercent) { |
michael@0 | 451 | return value / 100.0; |
michael@0 | 452 | } |
michael@0 | 453 | return value / mUnitsPerEm; |
michael@0 | 454 | } |
michael@0 | 455 | |
michael@0 | 456 | bool |
michael@0 | 457 | gfxFontEntry::GetMathItalicsCorrection(uint32_t aGlyphID, |
michael@0 | 458 | gfxFloat* aItalicCorrection) |
michael@0 | 459 | { |
michael@0 | 460 | NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); |
michael@0 | 461 | int16_t italicCorrection; |
michael@0 | 462 | if (!mMathTable->GetMathItalicsCorrection(aGlyphID, &italicCorrection)) { |
michael@0 | 463 | return false; |
michael@0 | 464 | } |
michael@0 | 465 | *aItalicCorrection = gfxFloat(italicCorrection) / mUnitsPerEm; |
michael@0 | 466 | return true; |
michael@0 | 467 | } |
michael@0 | 468 | |
michael@0 | 469 | uint32_t |
michael@0 | 470 | gfxFontEntry::GetMathVariantsSize(uint32_t aGlyphID, bool aVertical, |
michael@0 | 471 | uint16_t aSize) |
michael@0 | 472 | { |
michael@0 | 473 | NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); |
michael@0 | 474 | return mMathTable->GetMathVariantsSize(aGlyphID, aVertical, aSize); |
michael@0 | 475 | } |
michael@0 | 476 | |
michael@0 | 477 | bool |
michael@0 | 478 | gfxFontEntry::GetMathVariantsParts(uint32_t aGlyphID, bool aVertical, |
michael@0 | 479 | uint32_t aGlyphs[4]) |
michael@0 | 480 | { |
michael@0 | 481 | NS_ASSERTION(mMathTable, "Math data has not yet been loaded. TryGetMathData() first."); |
michael@0 | 482 | return mMathTable->GetMathVariantsParts(aGlyphID, aVertical, aGlyphs); |
michael@0 | 483 | } |
michael@0 | 484 | |
michael@0 | 485 | /** |
michael@0 | 486 | * FontTableBlobData |
michael@0 | 487 | * |
michael@0 | 488 | * See FontTableHashEntry for the general strategy. |
michael@0 | 489 | */ |
michael@0 | 490 | |
michael@0 | 491 | class gfxFontEntry::FontTableBlobData { |
michael@0 | 492 | public: |
michael@0 | 493 | // Adopts the content of aBuffer. |
michael@0 | 494 | FontTableBlobData(FallibleTArray<uint8_t>& aBuffer) |
michael@0 | 495 | : mHashtable(nullptr), mHashKey(0) |
michael@0 | 496 | { |
michael@0 | 497 | MOZ_COUNT_CTOR(FontTableBlobData); |
michael@0 | 498 | mTableData.SwapElements(aBuffer); |
michael@0 | 499 | } |
michael@0 | 500 | |
michael@0 | 501 | ~FontTableBlobData() { |
michael@0 | 502 | MOZ_COUNT_DTOR(FontTableBlobData); |
michael@0 | 503 | if (mHashtable && mHashKey) { |
michael@0 | 504 | mHashtable->RemoveEntry(mHashKey); |
michael@0 | 505 | } |
michael@0 | 506 | } |
michael@0 | 507 | |
michael@0 | 508 | // Useful for creating blobs |
michael@0 | 509 | const char *GetTable() const |
michael@0 | 510 | { |
michael@0 | 511 | return reinterpret_cast<const char*>(mTableData.Elements()); |
michael@0 | 512 | } |
michael@0 | 513 | uint32_t GetTableLength() const { return mTableData.Length(); } |
michael@0 | 514 | |
michael@0 | 515 | // Tell this FontTableBlobData to remove the HashEntry when this is |
michael@0 | 516 | // destroyed. |
michael@0 | 517 | void ManageHashEntry(nsTHashtable<FontTableHashEntry> *aHashtable, |
michael@0 | 518 | uint32_t aHashKey) |
michael@0 | 519 | { |
michael@0 | 520 | mHashtable = aHashtable; |
michael@0 | 521 | mHashKey = aHashKey; |
michael@0 | 522 | } |
michael@0 | 523 | |
michael@0 | 524 | // Disconnect from the HashEntry (because the blob has already been |
michael@0 | 525 | // removed from the hashtable). |
michael@0 | 526 | void ForgetHashEntry() |
michael@0 | 527 | { |
michael@0 | 528 | mHashtable = nullptr; |
michael@0 | 529 | mHashKey = 0; |
michael@0 | 530 | } |
michael@0 | 531 | |
michael@0 | 532 | size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const { |
michael@0 | 533 | return mTableData.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 534 | } |
michael@0 | 535 | size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const { |
michael@0 | 536 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 537 | } |
michael@0 | 538 | |
michael@0 | 539 | private: |
michael@0 | 540 | // The font table data block, owned (via adoption) |
michael@0 | 541 | FallibleTArray<uint8_t> mTableData; |
michael@0 | 542 | |
michael@0 | 543 | // The blob destroy function needs to know the owning hashtable |
michael@0 | 544 | // and the hashtable key, so that it can remove the entry. |
michael@0 | 545 | nsTHashtable<FontTableHashEntry> *mHashtable; |
michael@0 | 546 | uint32_t mHashKey; |
michael@0 | 547 | |
michael@0 | 548 | // not implemented |
michael@0 | 549 | FontTableBlobData(const FontTableBlobData&); |
michael@0 | 550 | }; |
michael@0 | 551 | |
michael@0 | 552 | hb_blob_t * |
michael@0 | 553 | gfxFontEntry::FontTableHashEntry:: |
michael@0 | 554 | ShareTableAndGetBlob(FallibleTArray<uint8_t>& aTable, |
michael@0 | 555 | nsTHashtable<FontTableHashEntry> *aHashtable) |
michael@0 | 556 | { |
michael@0 | 557 | Clear(); |
michael@0 | 558 | // adopts elements of aTable |
michael@0 | 559 | mSharedBlobData = new FontTableBlobData(aTable); |
michael@0 | 560 | mBlob = hb_blob_create(mSharedBlobData->GetTable(), |
michael@0 | 561 | mSharedBlobData->GetTableLength(), |
michael@0 | 562 | HB_MEMORY_MODE_READONLY, |
michael@0 | 563 | mSharedBlobData, DeleteFontTableBlobData); |
michael@0 | 564 | if (!mSharedBlobData) { |
michael@0 | 565 | // The FontTableBlobData was destroyed during hb_blob_create(). |
michael@0 | 566 | // The (empty) blob is still be held in the hashtable with a strong |
michael@0 | 567 | // reference. |
michael@0 | 568 | return hb_blob_reference(mBlob); |
michael@0 | 569 | } |
michael@0 | 570 | |
michael@0 | 571 | // Tell the FontTableBlobData to remove this hash entry when destroyed. |
michael@0 | 572 | // The hashtable does not keep a strong reference. |
michael@0 | 573 | mSharedBlobData->ManageHashEntry(aHashtable, GetKey()); |
michael@0 | 574 | return mBlob; |
michael@0 | 575 | } |
michael@0 | 576 | |
michael@0 | 577 | void |
michael@0 | 578 | gfxFontEntry::FontTableHashEntry::Clear() |
michael@0 | 579 | { |
michael@0 | 580 | // If the FontTableBlobData is managing the hash entry, then the blob is |
michael@0 | 581 | // not owned by this HashEntry; otherwise there is strong reference to the |
michael@0 | 582 | // blob that must be removed. |
michael@0 | 583 | if (mSharedBlobData) { |
michael@0 | 584 | mSharedBlobData->ForgetHashEntry(); |
michael@0 | 585 | mSharedBlobData = nullptr; |
michael@0 | 586 | } else if (mBlob) { |
michael@0 | 587 | hb_blob_destroy(mBlob); |
michael@0 | 588 | } |
michael@0 | 589 | mBlob = nullptr; |
michael@0 | 590 | } |
michael@0 | 591 | |
michael@0 | 592 | // a hb_destroy_func for hb_blob_create |
michael@0 | 593 | |
michael@0 | 594 | /* static */ void |
michael@0 | 595 | gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData(void *aBlobData) |
michael@0 | 596 | { |
michael@0 | 597 | delete static_cast<FontTableBlobData*>(aBlobData); |
michael@0 | 598 | } |
michael@0 | 599 | |
michael@0 | 600 | hb_blob_t * |
michael@0 | 601 | gfxFontEntry::FontTableHashEntry::GetBlob() const |
michael@0 | 602 | { |
michael@0 | 603 | return hb_blob_reference(mBlob); |
michael@0 | 604 | } |
michael@0 | 605 | |
michael@0 | 606 | bool |
michael@0 | 607 | gfxFontEntry::GetExistingFontTable(uint32_t aTag, hb_blob_t **aBlob) |
michael@0 | 608 | { |
michael@0 | 609 | if (!mFontTableCache) { |
michael@0 | 610 | // we do this here rather than on fontEntry construction |
michael@0 | 611 | // because not all shapers will access the table cache at all |
michael@0 | 612 | mFontTableCache = new nsTHashtable<FontTableHashEntry>(10); |
michael@0 | 613 | } |
michael@0 | 614 | |
michael@0 | 615 | FontTableHashEntry *entry = mFontTableCache->GetEntry(aTag); |
michael@0 | 616 | if (!entry) { |
michael@0 | 617 | return false; |
michael@0 | 618 | } |
michael@0 | 619 | |
michael@0 | 620 | *aBlob = entry->GetBlob(); |
michael@0 | 621 | return true; |
michael@0 | 622 | } |
michael@0 | 623 | |
michael@0 | 624 | hb_blob_t * |
michael@0 | 625 | gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag, |
michael@0 | 626 | FallibleTArray<uint8_t>* aBuffer) |
michael@0 | 627 | { |
michael@0 | 628 | if (MOZ_UNLIKELY(!mFontTableCache)) { |
michael@0 | 629 | // we do this here rather than on fontEntry construction |
michael@0 | 630 | // because not all shapers will access the table cache at all |
michael@0 | 631 | mFontTableCache = new nsTHashtable<FontTableHashEntry>(10); |
michael@0 | 632 | } |
michael@0 | 633 | |
michael@0 | 634 | FontTableHashEntry *entry = mFontTableCache->PutEntry(aTag); |
michael@0 | 635 | if (MOZ_UNLIKELY(!entry)) { // OOM |
michael@0 | 636 | return nullptr; |
michael@0 | 637 | } |
michael@0 | 638 | |
michael@0 | 639 | if (!aBuffer) { |
michael@0 | 640 | // ensure the entry is null |
michael@0 | 641 | entry->Clear(); |
michael@0 | 642 | return nullptr; |
michael@0 | 643 | } |
michael@0 | 644 | |
michael@0 | 645 | return entry->ShareTableAndGetBlob(*aBuffer, mFontTableCache); |
michael@0 | 646 | } |
michael@0 | 647 | |
michael@0 | 648 | static int |
michael@0 | 649 | DirEntryCmp(const void* aKey, const void* aItem) |
michael@0 | 650 | { |
michael@0 | 651 | int32_t tag = *static_cast<const int32_t*>(aKey); |
michael@0 | 652 | const TableDirEntry* entry = static_cast<const TableDirEntry*>(aItem); |
michael@0 | 653 | return tag - int32_t(entry->tag); |
michael@0 | 654 | } |
michael@0 | 655 | |
michael@0 | 656 | hb_blob_t* |
michael@0 | 657 | gfxFontEntry::GetTableFromFontData(const void* aFontData, uint32_t aTableTag) |
michael@0 | 658 | { |
michael@0 | 659 | const SFNTHeader* header = |
michael@0 | 660 | reinterpret_cast<const SFNTHeader*>(aFontData); |
michael@0 | 661 | const TableDirEntry* dir = |
michael@0 | 662 | reinterpret_cast<const TableDirEntry*>(header + 1); |
michael@0 | 663 | dir = static_cast<const TableDirEntry*> |
michael@0 | 664 | (bsearch(&aTableTag, dir, uint16_t(header->numTables), |
michael@0 | 665 | sizeof(TableDirEntry), DirEntryCmp)); |
michael@0 | 666 | if (dir) { |
michael@0 | 667 | return hb_blob_create(reinterpret_cast<const char*>(aFontData) + |
michael@0 | 668 | dir->offset, dir->length, |
michael@0 | 669 | HB_MEMORY_MODE_READONLY, nullptr, nullptr); |
michael@0 | 670 | |
michael@0 | 671 | } |
michael@0 | 672 | return nullptr; |
michael@0 | 673 | } |
michael@0 | 674 | |
michael@0 | 675 | already_AddRefed<gfxCharacterMap> |
michael@0 | 676 | gfxFontEntry::GetCMAPFromFontInfo(FontInfoData *aFontInfoData, |
michael@0 | 677 | uint32_t& aUVSOffset, |
michael@0 | 678 | bool& aSymbolFont) |
michael@0 | 679 | { |
michael@0 | 680 | if (!aFontInfoData || !aFontInfoData->mLoadCmaps) { |
michael@0 | 681 | return nullptr; |
michael@0 | 682 | } |
michael@0 | 683 | |
michael@0 | 684 | return aFontInfoData->GetCMAP(mName, aUVSOffset, aSymbolFont); |
michael@0 | 685 | } |
michael@0 | 686 | |
michael@0 | 687 | hb_blob_t * |
michael@0 | 688 | gfxFontEntry::GetFontTable(uint32_t aTag) |
michael@0 | 689 | { |
michael@0 | 690 | hb_blob_t *blob; |
michael@0 | 691 | if (GetExistingFontTable(aTag, &blob)) { |
michael@0 | 692 | return blob; |
michael@0 | 693 | } |
michael@0 | 694 | |
michael@0 | 695 | FallibleTArray<uint8_t> buffer; |
michael@0 | 696 | bool haveTable = NS_SUCCEEDED(CopyFontTable(aTag, buffer)); |
michael@0 | 697 | |
michael@0 | 698 | return ShareFontTableAndGetBlob(aTag, haveTable ? &buffer : nullptr); |
michael@0 | 699 | } |
michael@0 | 700 | |
michael@0 | 701 | // callback for HarfBuzz to get a font table (in hb_blob_t form) |
michael@0 | 702 | // from the font entry (passed as aUserData) |
michael@0 | 703 | /*static*/ hb_blob_t * |
michael@0 | 704 | gfxFontEntry::HBGetTable(hb_face_t *face, uint32_t aTag, void *aUserData) |
michael@0 | 705 | { |
michael@0 | 706 | gfxFontEntry *fontEntry = static_cast<gfxFontEntry*>(aUserData); |
michael@0 | 707 | |
michael@0 | 708 | // bug 589682 - ignore the GDEF table in buggy fonts (applies to |
michael@0 | 709 | // Italic and BoldItalic faces of Times New Roman) |
michael@0 | 710 | if (aTag == TRUETYPE_TAG('G','D','E','F') && |
michael@0 | 711 | fontEntry->IgnoreGDEF()) { |
michael@0 | 712 | return nullptr; |
michael@0 | 713 | } |
michael@0 | 714 | |
michael@0 | 715 | // bug 721719 - ignore the GSUB table in buggy fonts (applies to Roboto, |
michael@0 | 716 | // at least on some Android ICS devices; set in gfxFT2FontList.cpp) |
michael@0 | 717 | if (aTag == TRUETYPE_TAG('G','S','U','B') && |
michael@0 | 718 | fontEntry->IgnoreGSUB()) { |
michael@0 | 719 | return nullptr; |
michael@0 | 720 | } |
michael@0 | 721 | |
michael@0 | 722 | return fontEntry->GetFontTable(aTag); |
michael@0 | 723 | } |
michael@0 | 724 | |
michael@0 | 725 | /*static*/ void |
michael@0 | 726 | gfxFontEntry::HBFaceDeletedCallback(void *aUserData) |
michael@0 | 727 | { |
michael@0 | 728 | gfxFontEntry *fe = static_cast<gfxFontEntry*>(aUserData); |
michael@0 | 729 | fe->ForgetHBFace(); |
michael@0 | 730 | } |
michael@0 | 731 | |
michael@0 | 732 | void |
michael@0 | 733 | gfxFontEntry::ForgetHBFace() |
michael@0 | 734 | { |
michael@0 | 735 | mHBFace = nullptr; |
michael@0 | 736 | } |
michael@0 | 737 | |
michael@0 | 738 | hb_face_t* |
michael@0 | 739 | gfxFontEntry::GetHBFace() |
michael@0 | 740 | { |
michael@0 | 741 | if (!mHBFace) { |
michael@0 | 742 | mHBFace = hb_face_create_for_tables(HBGetTable, this, |
michael@0 | 743 | HBFaceDeletedCallback); |
michael@0 | 744 | return mHBFace; |
michael@0 | 745 | } |
michael@0 | 746 | return hb_face_reference(mHBFace); |
michael@0 | 747 | } |
michael@0 | 748 | |
michael@0 | 749 | /*static*/ const void* |
michael@0 | 750 | gfxFontEntry::GrGetTable(const void *aAppFaceHandle, unsigned int aName, |
michael@0 | 751 | size_t *aLen) |
michael@0 | 752 | { |
michael@0 | 753 | gfxFontEntry *fontEntry = |
michael@0 | 754 | static_cast<gfxFontEntry*>(const_cast<void*>(aAppFaceHandle)); |
michael@0 | 755 | hb_blob_t *blob = fontEntry->GetFontTable(aName); |
michael@0 | 756 | if (blob) { |
michael@0 | 757 | unsigned int blobLength; |
michael@0 | 758 | const void *tableData = hb_blob_get_data(blob, &blobLength); |
michael@0 | 759 | fontEntry->mGrTableMap->Put(tableData, blob); |
michael@0 | 760 | *aLen = blobLength; |
michael@0 | 761 | return tableData; |
michael@0 | 762 | } |
michael@0 | 763 | *aLen = 0; |
michael@0 | 764 | return nullptr; |
michael@0 | 765 | } |
michael@0 | 766 | |
michael@0 | 767 | /*static*/ void |
michael@0 | 768 | gfxFontEntry::GrReleaseTable(const void *aAppFaceHandle, |
michael@0 | 769 | const void *aTableBuffer) |
michael@0 | 770 | { |
michael@0 | 771 | gfxFontEntry *fontEntry = |
michael@0 | 772 | static_cast<gfxFontEntry*>(const_cast<void*>(aAppFaceHandle)); |
michael@0 | 773 | void *data; |
michael@0 | 774 | if (fontEntry->mGrTableMap->Get(aTableBuffer, &data)) { |
michael@0 | 775 | fontEntry->mGrTableMap->Remove(aTableBuffer); |
michael@0 | 776 | hb_blob_destroy(static_cast<hb_blob_t*>(data)); |
michael@0 | 777 | } |
michael@0 | 778 | } |
michael@0 | 779 | |
michael@0 | 780 | gr_face* |
michael@0 | 781 | gfxFontEntry::GetGrFace() |
michael@0 | 782 | { |
michael@0 | 783 | if (!mGrFaceInitialized) { |
michael@0 | 784 | gr_face_ops faceOps = { |
michael@0 | 785 | sizeof(gr_face_ops), |
michael@0 | 786 | GrGetTable, |
michael@0 | 787 | GrReleaseTable |
michael@0 | 788 | }; |
michael@0 | 789 | mGrTableMap = new nsDataHashtable<nsPtrHashKey<const void>,void*>; |
michael@0 | 790 | mGrFace = gr_make_face_with_ops(this, &faceOps, gr_face_default); |
michael@0 | 791 | mGrFaceInitialized = true; |
michael@0 | 792 | } |
michael@0 | 793 | ++mGrFaceRefCnt; |
michael@0 | 794 | return mGrFace; |
michael@0 | 795 | } |
michael@0 | 796 | |
michael@0 | 797 | void |
michael@0 | 798 | gfxFontEntry::ReleaseGrFace(gr_face *aFace) |
michael@0 | 799 | { |
michael@0 | 800 | MOZ_ASSERT(aFace == mGrFace); // sanity-check |
michael@0 | 801 | MOZ_ASSERT(mGrFaceRefCnt > 0); |
michael@0 | 802 | if (--mGrFaceRefCnt == 0) { |
michael@0 | 803 | gr_face_destroy(mGrFace); |
michael@0 | 804 | mGrFace = nullptr; |
michael@0 | 805 | mGrFaceInitialized = false; |
michael@0 | 806 | delete mGrTableMap; |
michael@0 | 807 | mGrTableMap = nullptr; |
michael@0 | 808 | } |
michael@0 | 809 | } |
michael@0 | 810 | |
michael@0 | 811 | void |
michael@0 | 812 | gfxFontEntry::DisconnectSVG() |
michael@0 | 813 | { |
michael@0 | 814 | if (mSVGInitialized && mSVGGlyphs) { |
michael@0 | 815 | mSVGGlyphs = nullptr; |
michael@0 | 816 | mSVGInitialized = false; |
michael@0 | 817 | } |
michael@0 | 818 | } |
michael@0 | 819 | |
michael@0 | 820 | bool |
michael@0 | 821 | gfxFontEntry::HasFontTable(uint32_t aTableTag) |
michael@0 | 822 | { |
michael@0 | 823 | AutoTable table(this, aTableTag); |
michael@0 | 824 | return table && hb_blob_get_length(table) > 0; |
michael@0 | 825 | } |
michael@0 | 826 | |
michael@0 | 827 | void |
michael@0 | 828 | gfxFontEntry::CheckForGraphiteTables() |
michael@0 | 829 | { |
michael@0 | 830 | mHasGraphiteTables = HasFontTable(TRUETYPE_TAG('S','i','l','f')); |
michael@0 | 831 | } |
michael@0 | 832 | |
michael@0 | 833 | /* static */ size_t |
michael@0 | 834 | gfxFontEntry::FontTableHashEntry::SizeOfEntryExcludingThis |
michael@0 | 835 | (FontTableHashEntry *aEntry, |
michael@0 | 836 | MallocSizeOf aMallocSizeOf, |
michael@0 | 837 | void* aUserArg) |
michael@0 | 838 | { |
michael@0 | 839 | size_t n = 0; |
michael@0 | 840 | if (aEntry->mBlob) { |
michael@0 | 841 | n += aMallocSizeOf(aEntry->mBlob); |
michael@0 | 842 | } |
michael@0 | 843 | if (aEntry->mSharedBlobData) { |
michael@0 | 844 | n += aEntry->mSharedBlobData->SizeOfIncludingThis(aMallocSizeOf); |
michael@0 | 845 | } |
michael@0 | 846 | return n; |
michael@0 | 847 | } |
michael@0 | 848 | |
michael@0 | 849 | void |
michael@0 | 850 | gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, |
michael@0 | 851 | FontListSizes* aSizes) const |
michael@0 | 852 | { |
michael@0 | 853 | aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); |
michael@0 | 854 | |
michael@0 | 855 | // cmaps are shared so only non-shared cmaps are included here |
michael@0 | 856 | if (mCharacterMap && mCharacterMap->mBuildOnTheFly) { |
michael@0 | 857 | aSizes->mCharMapsSize += |
michael@0 | 858 | mCharacterMap->SizeOfIncludingThis(aMallocSizeOf); |
michael@0 | 859 | } |
michael@0 | 860 | if (mFontTableCache) { |
michael@0 | 861 | aSizes->mFontTableCacheSize += |
michael@0 | 862 | mFontTableCache->SizeOfExcludingThis( |
michael@0 | 863 | FontTableHashEntry::SizeOfEntryExcludingThis, |
michael@0 | 864 | aMallocSizeOf); |
michael@0 | 865 | } |
michael@0 | 866 | } |
michael@0 | 867 | |
michael@0 | 868 | void |
michael@0 | 869 | gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, |
michael@0 | 870 | FontListSizes* aSizes) const |
michael@0 | 871 | { |
michael@0 | 872 | aSizes->mFontListSize += aMallocSizeOf(this); |
michael@0 | 873 | AddSizeOfExcludingThis(aMallocSizeOf, aSizes); |
michael@0 | 874 | } |
michael@0 | 875 | |
michael@0 | 876 | ////////////////////////////////////////////////////////////////////////////// |
michael@0 | 877 | // |
michael@0 | 878 | // class gfxFontFamily |
michael@0 | 879 | // |
michael@0 | 880 | ////////////////////////////////////////////////////////////////////////////// |
michael@0 | 881 | |
michael@0 | 882 | // we consider faces with mStandardFace == true to be "greater than" those with false, |
michael@0 | 883 | // because during style matching, later entries will replace earlier ones |
michael@0 | 884 | class FontEntryStandardFaceComparator { |
michael@0 | 885 | public: |
michael@0 | 886 | bool Equals(const nsRefPtr<gfxFontEntry>& a, const nsRefPtr<gfxFontEntry>& b) const { |
michael@0 | 887 | return a->mStandardFace == b->mStandardFace; |
michael@0 | 888 | } |
michael@0 | 889 | bool LessThan(const nsRefPtr<gfxFontEntry>& a, const nsRefPtr<gfxFontEntry>& b) const { |
michael@0 | 890 | return (a->mStandardFace == false && b->mStandardFace == true); |
michael@0 | 891 | } |
michael@0 | 892 | }; |
michael@0 | 893 | |
michael@0 | 894 | void |
michael@0 | 895 | gfxFontFamily::SortAvailableFonts() |
michael@0 | 896 | { |
michael@0 | 897 | mAvailableFonts.Sort(FontEntryStandardFaceComparator()); |
michael@0 | 898 | } |
michael@0 | 899 | |
michael@0 | 900 | bool |
michael@0 | 901 | gfxFontFamily::HasOtherFamilyNames() |
michael@0 | 902 | { |
michael@0 | 903 | // need to read in other family names to determine this |
michael@0 | 904 | if (!mOtherFamilyNamesInitialized) { |
michael@0 | 905 | ReadOtherFamilyNames(gfxPlatformFontList::PlatformFontList()); // sets mHasOtherFamilyNames |
michael@0 | 906 | } |
michael@0 | 907 | return mHasOtherFamilyNames; |
michael@0 | 908 | } |
michael@0 | 909 | |
michael@0 | 910 | gfxFontEntry* |
michael@0 | 911 | gfxFontFamily::FindFontForStyle(const gfxFontStyle& aFontStyle, |
michael@0 | 912 | bool& aNeedsSyntheticBold) |
michael@0 | 913 | { |
michael@0 | 914 | if (!mHasStyles) |
michael@0 | 915 | FindStyleVariations(); // collect faces for the family, if not already done |
michael@0 | 916 | |
michael@0 | 917 | NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!"); |
michael@0 | 918 | |
michael@0 | 919 | aNeedsSyntheticBold = false; |
michael@0 | 920 | |
michael@0 | 921 | int8_t baseWeight = aFontStyle.ComputeWeight(); |
michael@0 | 922 | bool wantBold = baseWeight >= 6; |
michael@0 | 923 | |
michael@0 | 924 | // If the family has only one face, we simply return it; no further checking needed |
michael@0 | 925 | if (mAvailableFonts.Length() == 1) { |
michael@0 | 926 | gfxFontEntry *fe = mAvailableFonts[0]; |
michael@0 | 927 | aNeedsSyntheticBold = wantBold && !fe->IsBold(); |
michael@0 | 928 | return fe; |
michael@0 | 929 | } |
michael@0 | 930 | |
michael@0 | 931 | bool wantItalic = (aFontStyle.style & |
michael@0 | 932 | (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; |
michael@0 | 933 | |
michael@0 | 934 | // Most families are "simple", having just Regular/Bold/Italic/BoldItalic, |
michael@0 | 935 | // or some subset of these. In this case, we have exactly 4 entries in mAvailableFonts, |
michael@0 | 936 | // stored in the above order; note that some of the entries may be nullptr. |
michael@0 | 937 | // We can then pick the required entry based on whether the request is for |
michael@0 | 938 | // bold or non-bold, italic or non-italic, without running the more complex |
michael@0 | 939 | // matching algorithm used for larger families with many weights and/or widths. |
michael@0 | 940 | |
michael@0 | 941 | if (mIsSimpleFamily) { |
michael@0 | 942 | // Family has no more than the "standard" 4 faces, at fixed indexes; |
michael@0 | 943 | // calculate which one we want. |
michael@0 | 944 | // Note that we cannot simply return it as not all 4 faces are necessarily present. |
michael@0 | 945 | uint8_t faceIndex = (wantItalic ? kItalicMask : 0) | |
michael@0 | 946 | (wantBold ? kBoldMask : 0); |
michael@0 | 947 | |
michael@0 | 948 | // if the desired style is available, return it directly |
michael@0 | 949 | gfxFontEntry *fe = mAvailableFonts[faceIndex]; |
michael@0 | 950 | if (fe) { |
michael@0 | 951 | // no need to set aNeedsSyntheticBold here as we matched the boldness request |
michael@0 | 952 | return fe; |
michael@0 | 953 | } |
michael@0 | 954 | |
michael@0 | 955 | // order to check fallback faces in a simple family, depending on requested style |
michael@0 | 956 | static const uint8_t simpleFallbacks[4][3] = { |
michael@0 | 957 | { kBoldFaceIndex, kItalicFaceIndex, kBoldItalicFaceIndex }, // fallbacks for Regular |
michael@0 | 958 | { kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex },// Bold |
michael@0 | 959 | { kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex }, // Italic |
michael@0 | 960 | { kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex } // BoldItalic |
michael@0 | 961 | }; |
michael@0 | 962 | const uint8_t *order = simpleFallbacks[faceIndex]; |
michael@0 | 963 | |
michael@0 | 964 | for (uint8_t trial = 0; trial < 3; ++trial) { |
michael@0 | 965 | // check remaining faces in order of preference to find the first that actually exists |
michael@0 | 966 | fe = mAvailableFonts[order[trial]]; |
michael@0 | 967 | if (fe) { |
michael@0 | 968 | aNeedsSyntheticBold = wantBold && !fe->IsBold(); |
michael@0 | 969 | return fe; |
michael@0 | 970 | } |
michael@0 | 971 | } |
michael@0 | 972 | |
michael@0 | 973 | // this can't happen unless we have totally broken the font-list manager! |
michael@0 | 974 | NS_NOTREACHED("no face found in simple font family!"); |
michael@0 | 975 | return nullptr; |
michael@0 | 976 | } |
michael@0 | 977 | |
michael@0 | 978 | // This is a large/rich font family, so we do full style- and weight-matching: |
michael@0 | 979 | // first collect a list of weights that are the best match for the requested |
michael@0 | 980 | // font-stretch and font-style, then pick the best weight match among those |
michael@0 | 981 | // available. |
michael@0 | 982 | |
michael@0 | 983 | gfxFontEntry *weightList[10] = { 0 }; |
michael@0 | 984 | bool foundWeights = FindWeightsForStyle(weightList, wantItalic, aFontStyle.stretch); |
michael@0 | 985 | if (!foundWeights) { |
michael@0 | 986 | return nullptr; |
michael@0 | 987 | } |
michael@0 | 988 | |
michael@0 | 989 | // First find a match for the best weight |
michael@0 | 990 | int8_t matchBaseWeight = 0; |
michael@0 | 991 | int8_t i = baseWeight; |
michael@0 | 992 | |
michael@0 | 993 | // Need to special case when normal face doesn't exist but medium does. |
michael@0 | 994 | // In that case, use medium otherwise weights < 400 |
michael@0 | 995 | if (baseWeight == 4 && !weightList[4]) { |
michael@0 | 996 | i = 5; // medium |
michael@0 | 997 | } |
michael@0 | 998 | |
michael@0 | 999 | // Loop through weights, since one exists loop will terminate |
michael@0 | 1000 | int8_t direction = (baseWeight > 5) ? 1 : -1; |
michael@0 | 1001 | for (; ; i += direction) { |
michael@0 | 1002 | if (weightList[i]) { |
michael@0 | 1003 | matchBaseWeight = i; |
michael@0 | 1004 | break; |
michael@0 | 1005 | } |
michael@0 | 1006 | |
michael@0 | 1007 | // If we've reached one side without finding a font, |
michael@0 | 1008 | // start over and go the other direction until we find a match |
michael@0 | 1009 | if (i == 1 || i == 9) { |
michael@0 | 1010 | i = baseWeight; |
michael@0 | 1011 | direction = -direction; |
michael@0 | 1012 | } |
michael@0 | 1013 | } |
michael@0 | 1014 | |
michael@0 | 1015 | NS_ASSERTION(matchBaseWeight != 0, |
michael@0 | 1016 | "weight mapping should always find at least one font in a family"); |
michael@0 | 1017 | |
michael@0 | 1018 | gfxFontEntry *matchFE = weightList[matchBaseWeight]; |
michael@0 | 1019 | |
michael@0 | 1020 | NS_ASSERTION(matchFE, |
michael@0 | 1021 | "weight mapping should always find at least one font in a family"); |
michael@0 | 1022 | |
michael@0 | 1023 | if (!matchFE->IsBold() && baseWeight >= 6) |
michael@0 | 1024 | { |
michael@0 | 1025 | aNeedsSyntheticBold = true; |
michael@0 | 1026 | } |
michael@0 | 1027 | |
michael@0 | 1028 | return matchFE; |
michael@0 | 1029 | } |
michael@0 | 1030 | |
michael@0 | 1031 | void |
michael@0 | 1032 | gfxFontFamily::CheckForSimpleFamily() |
michael@0 | 1033 | { |
michael@0 | 1034 | // already checked this family |
michael@0 | 1035 | if (mIsSimpleFamily) { |
michael@0 | 1036 | return; |
michael@0 | 1037 | }; |
michael@0 | 1038 | |
michael@0 | 1039 | uint32_t count = mAvailableFonts.Length(); |
michael@0 | 1040 | if (count > 4 || count == 0) { |
michael@0 | 1041 | return; // can't be "simple" if there are >4 faces; |
michael@0 | 1042 | // if none then the family is unusable anyway |
michael@0 | 1043 | } |
michael@0 | 1044 | |
michael@0 | 1045 | if (count == 1) { |
michael@0 | 1046 | mIsSimpleFamily = true; |
michael@0 | 1047 | return; |
michael@0 | 1048 | } |
michael@0 | 1049 | |
michael@0 | 1050 | int16_t firstStretch = mAvailableFonts[0]->Stretch(); |
michael@0 | 1051 | |
michael@0 | 1052 | gfxFontEntry *faces[4] = { 0 }; |
michael@0 | 1053 | for (uint8_t i = 0; i < count; ++i) { |
michael@0 | 1054 | gfxFontEntry *fe = mAvailableFonts[i]; |
michael@0 | 1055 | if (fe->Stretch() != firstStretch) { |
michael@0 | 1056 | return; // font-stretch doesn't match, don't treat as simple family |
michael@0 | 1057 | } |
michael@0 | 1058 | uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) | |
michael@0 | 1059 | (fe->Weight() >= 600 ? kBoldMask : 0); |
michael@0 | 1060 | if (faces[faceIndex]) { |
michael@0 | 1061 | return; // two faces resolve to the same slot; family isn't "simple" |
michael@0 | 1062 | } |
michael@0 | 1063 | faces[faceIndex] = fe; |
michael@0 | 1064 | } |
michael@0 | 1065 | |
michael@0 | 1066 | // we have successfully slotted the available faces into the standard |
michael@0 | 1067 | // 4-face framework |
michael@0 | 1068 | mAvailableFonts.SetLength(4); |
michael@0 | 1069 | for (uint8_t i = 0; i < 4; ++i) { |
michael@0 | 1070 | if (mAvailableFonts[i].get() != faces[i]) { |
michael@0 | 1071 | mAvailableFonts[i].swap(faces[i]); |
michael@0 | 1072 | } |
michael@0 | 1073 | } |
michael@0 | 1074 | |
michael@0 | 1075 | mIsSimpleFamily = true; |
michael@0 | 1076 | } |
michael@0 | 1077 | |
michael@0 | 1078 | static inline uint32_t |
michael@0 | 1079 | StyleDistance(gfxFontEntry *aFontEntry, |
michael@0 | 1080 | bool anItalic, int16_t aStretch) |
michael@0 | 1081 | { |
michael@0 | 1082 | // Compute a measure of the "distance" between the requested style |
michael@0 | 1083 | // and the given fontEntry, |
michael@0 | 1084 | // considering italicness and font-stretch but not weight. |
michael@0 | 1085 | |
michael@0 | 1086 | int32_t distance = 0; |
michael@0 | 1087 | if (aStretch != aFontEntry->mStretch) { |
michael@0 | 1088 | // stretch values are in the range -4 .. +4 |
michael@0 | 1089 | // if aStretch is positive, we prefer more-positive values; |
michael@0 | 1090 | // if zero or negative, prefer more-negative |
michael@0 | 1091 | if (aStretch > 0) { |
michael@0 | 1092 | distance = (aFontEntry->mStretch - aStretch) * 2; |
michael@0 | 1093 | } else { |
michael@0 | 1094 | distance = (aStretch - aFontEntry->mStretch) * 2; |
michael@0 | 1095 | } |
michael@0 | 1096 | // if the computed "distance" here is negative, it means that |
michael@0 | 1097 | // aFontEntry lies in the "non-preferred" direction from aStretch, |
michael@0 | 1098 | // so we treat that as larger than any preferred-direction distance |
michael@0 | 1099 | // (max possible is 8) by adding an extra 10 to the absolute value |
michael@0 | 1100 | if (distance < 0) { |
michael@0 | 1101 | distance = -distance + 10; |
michael@0 | 1102 | } |
michael@0 | 1103 | } |
michael@0 | 1104 | if (aFontEntry->IsItalic() != anItalic) { |
michael@0 | 1105 | distance += 1; |
michael@0 | 1106 | } |
michael@0 | 1107 | return uint32_t(distance); |
michael@0 | 1108 | } |
michael@0 | 1109 | |
michael@0 | 1110 | bool |
michael@0 | 1111 | gfxFontFamily::FindWeightsForStyle(gfxFontEntry* aFontsForWeights[], |
michael@0 | 1112 | bool anItalic, int16_t aStretch) |
michael@0 | 1113 | { |
michael@0 | 1114 | uint32_t foundWeights = 0; |
michael@0 | 1115 | uint32_t bestMatchDistance = 0xffffffff; |
michael@0 | 1116 | |
michael@0 | 1117 | uint32_t count = mAvailableFonts.Length(); |
michael@0 | 1118 | for (uint32_t i = 0; i < count; i++) { |
michael@0 | 1119 | // this is not called for "simple" families, and therefore it does not |
michael@0 | 1120 | // need to check the mAvailableFonts entries for nullptr. |
michael@0 | 1121 | gfxFontEntry *fe = mAvailableFonts[i]; |
michael@0 | 1122 | uint32_t distance = StyleDistance(fe, anItalic, aStretch); |
michael@0 | 1123 | if (distance <= bestMatchDistance) { |
michael@0 | 1124 | int8_t wt = fe->mWeight / 100; |
michael@0 | 1125 | NS_ASSERTION(wt >= 1 && wt < 10, "invalid weight in fontEntry"); |
michael@0 | 1126 | if (!aFontsForWeights[wt]) { |
michael@0 | 1127 | // record this as a possible candidate for weight matching |
michael@0 | 1128 | aFontsForWeights[wt] = fe; |
michael@0 | 1129 | ++foundWeights; |
michael@0 | 1130 | } else { |
michael@0 | 1131 | uint32_t prevDistance = |
michael@0 | 1132 | StyleDistance(aFontsForWeights[wt], anItalic, aStretch); |
michael@0 | 1133 | if (prevDistance >= distance) { |
michael@0 | 1134 | // replacing a weight we already found, |
michael@0 | 1135 | // so don't increment foundWeights |
michael@0 | 1136 | aFontsForWeights[wt] = fe; |
michael@0 | 1137 | } |
michael@0 | 1138 | } |
michael@0 | 1139 | bestMatchDistance = distance; |
michael@0 | 1140 | } |
michael@0 | 1141 | } |
michael@0 | 1142 | |
michael@0 | 1143 | NS_ASSERTION(foundWeights > 0, "Font family containing no faces?"); |
michael@0 | 1144 | |
michael@0 | 1145 | if (foundWeights == 1) { |
michael@0 | 1146 | // no need to cull entries if we only found one weight |
michael@0 | 1147 | return true; |
michael@0 | 1148 | } |
michael@0 | 1149 | |
michael@0 | 1150 | // we might have recorded some faces that were a partial style match, but later found |
michael@0 | 1151 | // others that were closer; in this case, we need to cull the poorer matches from the |
michael@0 | 1152 | // weight list we'll return |
michael@0 | 1153 | for (uint32_t i = 0; i < 10; ++i) { |
michael@0 | 1154 | if (aFontsForWeights[i] && |
michael@0 | 1155 | StyleDistance(aFontsForWeights[i], anItalic, aStretch) > bestMatchDistance) |
michael@0 | 1156 | { |
michael@0 | 1157 | aFontsForWeights[i] = 0; |
michael@0 | 1158 | } |
michael@0 | 1159 | } |
michael@0 | 1160 | |
michael@0 | 1161 | return (foundWeights > 0); |
michael@0 | 1162 | } |
michael@0 | 1163 | |
michael@0 | 1164 | |
michael@0 | 1165 | void gfxFontFamily::LocalizedName(nsAString& aLocalizedName) |
michael@0 | 1166 | { |
michael@0 | 1167 | // just return the primary name; subclasses should override |
michael@0 | 1168 | aLocalizedName = mName; |
michael@0 | 1169 | } |
michael@0 | 1170 | |
michael@0 | 1171 | // metric for how close a given font matches a style |
michael@0 | 1172 | static int32_t |
michael@0 | 1173 | CalcStyleMatch(gfxFontEntry *aFontEntry, const gfxFontStyle *aStyle) |
michael@0 | 1174 | { |
michael@0 | 1175 | int32_t rank = 0; |
michael@0 | 1176 | if (aStyle) { |
michael@0 | 1177 | // italics |
michael@0 | 1178 | bool wantItalic = |
michael@0 | 1179 | (aStyle->style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0; |
michael@0 | 1180 | if (aFontEntry->IsItalic() == wantItalic) { |
michael@0 | 1181 | rank += 10; |
michael@0 | 1182 | } |
michael@0 | 1183 | |
michael@0 | 1184 | // measure of closeness of weight to the desired value |
michael@0 | 1185 | rank += 9 - DeprecatedAbs(aFontEntry->Weight() / 100 - aStyle->ComputeWeight()); |
michael@0 | 1186 | } else { |
michael@0 | 1187 | // if no font to match, prefer non-bold, non-italic fonts |
michael@0 | 1188 | if (!aFontEntry->IsItalic()) { |
michael@0 | 1189 | rank += 3; |
michael@0 | 1190 | } |
michael@0 | 1191 | if (!aFontEntry->IsBold()) { |
michael@0 | 1192 | rank += 2; |
michael@0 | 1193 | } |
michael@0 | 1194 | } |
michael@0 | 1195 | |
michael@0 | 1196 | return rank; |
michael@0 | 1197 | } |
michael@0 | 1198 | |
michael@0 | 1199 | #define RANK_MATCHED_CMAP 20 |
michael@0 | 1200 | |
michael@0 | 1201 | void |
michael@0 | 1202 | gfxFontFamily::FindFontForChar(GlobalFontMatch *aMatchData) |
michael@0 | 1203 | { |
michael@0 | 1204 | if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) { |
michael@0 | 1205 | // none of the faces in the family support the required char, |
michael@0 | 1206 | // so bail out immediately |
michael@0 | 1207 | return; |
michael@0 | 1208 | } |
michael@0 | 1209 | |
michael@0 | 1210 | bool needsBold; |
michael@0 | 1211 | gfxFontStyle normal; |
michael@0 | 1212 | gfxFontEntry *fe = FindFontForStyle( |
michael@0 | 1213 | (aMatchData->mStyle == nullptr) ? *aMatchData->mStyle : normal, |
michael@0 | 1214 | needsBold); |
michael@0 | 1215 | |
michael@0 | 1216 | if (fe && !fe->SkipDuringSystemFallback()) { |
michael@0 | 1217 | int32_t rank = 0; |
michael@0 | 1218 | |
michael@0 | 1219 | if (fe->TestCharacterMap(aMatchData->mCh)) { |
michael@0 | 1220 | rank += RANK_MATCHED_CMAP; |
michael@0 | 1221 | aMatchData->mCount++; |
michael@0 | 1222 | #ifdef PR_LOGGING |
michael@0 | 1223 | PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun); |
michael@0 | 1224 | |
michael@0 | 1225 | if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_DEBUG))) { |
michael@0 | 1226 | uint32_t unicodeRange = FindCharUnicodeRange(aMatchData->mCh); |
michael@0 | 1227 | uint32_t script = GetScriptCode(aMatchData->mCh); |
michael@0 | 1228 | PR_LOG(log, PR_LOG_DEBUG,\ |
michael@0 | 1229 | ("(textrun-systemfallback-fonts) char: u+%6.6x " |
michael@0 | 1230 | "unicode-range: %d script: %d match: [%s]\n", |
michael@0 | 1231 | aMatchData->mCh, |
michael@0 | 1232 | unicodeRange, script, |
michael@0 | 1233 | NS_ConvertUTF16toUTF8(fe->Name()).get())); |
michael@0 | 1234 | } |
michael@0 | 1235 | #endif |
michael@0 | 1236 | } |
michael@0 | 1237 | |
michael@0 | 1238 | aMatchData->mCmapsTested++; |
michael@0 | 1239 | if (rank == 0) { |
michael@0 | 1240 | return; |
michael@0 | 1241 | } |
michael@0 | 1242 | |
michael@0 | 1243 | // omitting from original windows code -- family name, lang group, pitch |
michael@0 | 1244 | // not available in current FontEntry implementation |
michael@0 | 1245 | rank += CalcStyleMatch(fe, aMatchData->mStyle); |
michael@0 | 1246 | |
michael@0 | 1247 | // xxx - add whether AAT font with morphing info for specific lang groups |
michael@0 | 1248 | |
michael@0 | 1249 | if (rank > aMatchData->mMatchRank |
michael@0 | 1250 | || (rank == aMatchData->mMatchRank && |
michael@0 | 1251 | Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) |
michael@0 | 1252 | { |
michael@0 | 1253 | aMatchData->mBestMatch = fe; |
michael@0 | 1254 | aMatchData->mMatchedFamily = this; |
michael@0 | 1255 | aMatchData->mMatchRank = rank; |
michael@0 | 1256 | } |
michael@0 | 1257 | } |
michael@0 | 1258 | } |
michael@0 | 1259 | |
michael@0 | 1260 | void |
michael@0 | 1261 | gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch *aMatchData) |
michael@0 | 1262 | { |
michael@0 | 1263 | uint32_t i, numFonts = mAvailableFonts.Length(); |
michael@0 | 1264 | for (i = 0; i < numFonts; i++) { |
michael@0 | 1265 | gfxFontEntry *fe = mAvailableFonts[i]; |
michael@0 | 1266 | if (fe && fe->TestCharacterMap(aMatchData->mCh)) { |
michael@0 | 1267 | int32_t rank = RANK_MATCHED_CMAP; |
michael@0 | 1268 | rank += CalcStyleMatch(fe, aMatchData->mStyle); |
michael@0 | 1269 | if (rank > aMatchData->mMatchRank |
michael@0 | 1270 | || (rank == aMatchData->mMatchRank && |
michael@0 | 1271 | Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0)) |
michael@0 | 1272 | { |
michael@0 | 1273 | aMatchData->mBestMatch = fe; |
michael@0 | 1274 | aMatchData->mMatchedFamily = this; |
michael@0 | 1275 | aMatchData->mMatchRank = rank; |
michael@0 | 1276 | } |
michael@0 | 1277 | } |
michael@0 | 1278 | } |
michael@0 | 1279 | } |
michael@0 | 1280 | |
michael@0 | 1281 | /*static*/ void |
michael@0 | 1282 | gfxFontFamily::ReadOtherFamilyNamesForFace(const nsAString& aFamilyName, |
michael@0 | 1283 | const char *aNameData, |
michael@0 | 1284 | uint32_t aDataLength, |
michael@0 | 1285 | nsTArray<nsString>& aOtherFamilyNames, |
michael@0 | 1286 | bool useFullName) |
michael@0 | 1287 | { |
michael@0 | 1288 | const gfxFontUtils::NameHeader *nameHeader = |
michael@0 | 1289 | reinterpret_cast<const gfxFontUtils::NameHeader*>(aNameData); |
michael@0 | 1290 | |
michael@0 | 1291 | uint32_t nameCount = nameHeader->count; |
michael@0 | 1292 | if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) { |
michael@0 | 1293 | NS_WARNING("invalid font (name records)"); |
michael@0 | 1294 | return; |
michael@0 | 1295 | } |
michael@0 | 1296 | |
michael@0 | 1297 | const gfxFontUtils::NameRecord *nameRecord = |
michael@0 | 1298 | reinterpret_cast<const gfxFontUtils::NameRecord*>(aNameData + sizeof(gfxFontUtils::NameHeader)); |
michael@0 | 1299 | uint32_t stringsBase = uint32_t(nameHeader->stringOffset); |
michael@0 | 1300 | |
michael@0 | 1301 | for (uint32_t i = 0; i < nameCount; i++, nameRecord++) { |
michael@0 | 1302 | uint32_t nameLen = nameRecord->length; |
michael@0 | 1303 | uint32_t nameOff = nameRecord->offset; // offset from base of string storage |
michael@0 | 1304 | |
michael@0 | 1305 | if (stringsBase + nameOff + nameLen > aDataLength) { |
michael@0 | 1306 | NS_WARNING("invalid font (name table strings)"); |
michael@0 | 1307 | return; |
michael@0 | 1308 | } |
michael@0 | 1309 | |
michael@0 | 1310 | uint16_t nameID = nameRecord->nameID; |
michael@0 | 1311 | if ((useFullName && nameID == gfxFontUtils::NAME_ID_FULL) || |
michael@0 | 1312 | (!useFullName && (nameID == gfxFontUtils::NAME_ID_FAMILY || |
michael@0 | 1313 | nameID == gfxFontUtils::NAME_ID_PREFERRED_FAMILY))) { |
michael@0 | 1314 | nsAutoString otherFamilyName; |
michael@0 | 1315 | bool ok = gfxFontUtils::DecodeFontName(aNameData + stringsBase + nameOff, |
michael@0 | 1316 | nameLen, |
michael@0 | 1317 | uint32_t(nameRecord->platformID), |
michael@0 | 1318 | uint32_t(nameRecord->encodingID), |
michael@0 | 1319 | uint32_t(nameRecord->languageID), |
michael@0 | 1320 | otherFamilyName); |
michael@0 | 1321 | // add if not same as canonical family name |
michael@0 | 1322 | if (ok && otherFamilyName != aFamilyName) { |
michael@0 | 1323 | aOtherFamilyNames.AppendElement(otherFamilyName); |
michael@0 | 1324 | } |
michael@0 | 1325 | } |
michael@0 | 1326 | } |
michael@0 | 1327 | } |
michael@0 | 1328 | |
michael@0 | 1329 | // returns true if other names were found, false otherwise |
michael@0 | 1330 | bool |
michael@0 | 1331 | gfxFontFamily::ReadOtherFamilyNamesForFace(gfxPlatformFontList *aPlatformFontList, |
michael@0 | 1332 | hb_blob_t *aNameTable, |
michael@0 | 1333 | bool useFullName) |
michael@0 | 1334 | { |
michael@0 | 1335 | uint32_t dataLength; |
michael@0 | 1336 | const char *nameData = hb_blob_get_data(aNameTable, &dataLength); |
michael@0 | 1337 | nsAutoTArray<nsString,4> otherFamilyNames; |
michael@0 | 1338 | |
michael@0 | 1339 | ReadOtherFamilyNamesForFace(mName, nameData, dataLength, |
michael@0 | 1340 | otherFamilyNames, useFullName); |
michael@0 | 1341 | |
michael@0 | 1342 | uint32_t n = otherFamilyNames.Length(); |
michael@0 | 1343 | for (uint32_t i = 0; i < n; i++) { |
michael@0 | 1344 | aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]); |
michael@0 | 1345 | } |
michael@0 | 1346 | |
michael@0 | 1347 | return n != 0; |
michael@0 | 1348 | } |
michael@0 | 1349 | |
michael@0 | 1350 | void |
michael@0 | 1351 | gfxFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList) |
michael@0 | 1352 | { |
michael@0 | 1353 | if (mOtherFamilyNamesInitialized) |
michael@0 | 1354 | return; |
michael@0 | 1355 | mOtherFamilyNamesInitialized = true; |
michael@0 | 1356 | |
michael@0 | 1357 | FindStyleVariations(); |
michael@0 | 1358 | |
michael@0 | 1359 | // read in other family names for the first face in the list |
michael@0 | 1360 | uint32_t i, numFonts = mAvailableFonts.Length(); |
michael@0 | 1361 | const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e'); |
michael@0 | 1362 | |
michael@0 | 1363 | for (i = 0; i < numFonts; ++i) { |
michael@0 | 1364 | gfxFontEntry *fe = mAvailableFonts[i]; |
michael@0 | 1365 | if (!fe) { |
michael@0 | 1366 | continue; |
michael@0 | 1367 | } |
michael@0 | 1368 | gfxFontEntry::AutoTable nameTable(fe, kNAME); |
michael@0 | 1369 | if (!nameTable) { |
michael@0 | 1370 | continue; |
michael@0 | 1371 | } |
michael@0 | 1372 | mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList, |
michael@0 | 1373 | nameTable); |
michael@0 | 1374 | break; |
michael@0 | 1375 | } |
michael@0 | 1376 | |
michael@0 | 1377 | // read in other names for the first face in the list with the assumption |
michael@0 | 1378 | // that if extra names don't exist in that face then they don't exist in |
michael@0 | 1379 | // other faces for the same font |
michael@0 | 1380 | if (!mHasOtherFamilyNames) |
michael@0 | 1381 | return; |
michael@0 | 1382 | |
michael@0 | 1383 | // read in names for all faces, needed to catch cases where fonts have |
michael@0 | 1384 | // family names for individual weights (e.g. Hiragino Kaku Gothic Pro W6) |
michael@0 | 1385 | for ( ; i < numFonts; i++) { |
michael@0 | 1386 | gfxFontEntry *fe = mAvailableFonts[i]; |
michael@0 | 1387 | if (!fe) { |
michael@0 | 1388 | continue; |
michael@0 | 1389 | } |
michael@0 | 1390 | gfxFontEntry::AutoTable nameTable(fe, kNAME); |
michael@0 | 1391 | if (!nameTable) { |
michael@0 | 1392 | continue; |
michael@0 | 1393 | } |
michael@0 | 1394 | ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable); |
michael@0 | 1395 | } |
michael@0 | 1396 | } |
michael@0 | 1397 | |
michael@0 | 1398 | void |
michael@0 | 1399 | gfxFontFamily::ReadFaceNames(gfxPlatformFontList *aPlatformFontList, |
michael@0 | 1400 | bool aNeedFullnamePostscriptNames, |
michael@0 | 1401 | FontInfoData *aFontInfoData) |
michael@0 | 1402 | { |
michael@0 | 1403 | // if all needed names have already been read, skip |
michael@0 | 1404 | if (mOtherFamilyNamesInitialized && |
michael@0 | 1405 | (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) |
michael@0 | 1406 | return; |
michael@0 | 1407 | |
michael@0 | 1408 | bool asyncFontLoaderDisabled = false; |
michael@0 | 1409 | |
michael@0 | 1410 | #if defined(XP_MACOSX) |
michael@0 | 1411 | // bug 975460 - async font loader crashes sometimes under 10.6, disable |
michael@0 | 1412 | if (!nsCocoaFeatures::OnLionOrLater()) { |
michael@0 | 1413 | asyncFontLoaderDisabled = true; |
michael@0 | 1414 | } |
michael@0 | 1415 | #endif |
michael@0 | 1416 | |
michael@0 | 1417 | if (!mOtherFamilyNamesInitialized && |
michael@0 | 1418 | aFontInfoData && |
michael@0 | 1419 | aFontInfoData->mLoadOtherNames && |
michael@0 | 1420 | !asyncFontLoaderDisabled) |
michael@0 | 1421 | { |
michael@0 | 1422 | nsAutoTArray<nsString,4> otherFamilyNames; |
michael@0 | 1423 | bool foundOtherNames = |
michael@0 | 1424 | aFontInfoData->GetOtherFamilyNames(mName, otherFamilyNames); |
michael@0 | 1425 | if (foundOtherNames) { |
michael@0 | 1426 | uint32_t i, n = otherFamilyNames.Length(); |
michael@0 | 1427 | for (i = 0; i < n; i++) { |
michael@0 | 1428 | aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]); |
michael@0 | 1429 | } |
michael@0 | 1430 | } |
michael@0 | 1431 | mOtherFamilyNamesInitialized = true; |
michael@0 | 1432 | } |
michael@0 | 1433 | |
michael@0 | 1434 | // if all needed data has been initialized, return |
michael@0 | 1435 | if (mOtherFamilyNamesInitialized && |
michael@0 | 1436 | (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { |
michael@0 | 1437 | return; |
michael@0 | 1438 | } |
michael@0 | 1439 | |
michael@0 | 1440 | FindStyleVariations(aFontInfoData); |
michael@0 | 1441 | |
michael@0 | 1442 | // check again, as style enumeration code may have loaded names |
michael@0 | 1443 | if (mOtherFamilyNamesInitialized && |
michael@0 | 1444 | (mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) { |
michael@0 | 1445 | return; |
michael@0 | 1446 | } |
michael@0 | 1447 | |
michael@0 | 1448 | uint32_t i, numFonts = mAvailableFonts.Length(); |
michael@0 | 1449 | const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e'); |
michael@0 | 1450 | |
michael@0 | 1451 | bool firstTime = true, readAllFaces = false; |
michael@0 | 1452 | for (i = 0; i < numFonts; ++i) { |
michael@0 | 1453 | gfxFontEntry *fe = mAvailableFonts[i]; |
michael@0 | 1454 | if (!fe) { |
michael@0 | 1455 | continue; |
michael@0 | 1456 | } |
michael@0 | 1457 | |
michael@0 | 1458 | nsAutoString fullname, psname; |
michael@0 | 1459 | bool foundFaceNames = false; |
michael@0 | 1460 | if (!mFaceNamesInitialized && |
michael@0 | 1461 | aNeedFullnamePostscriptNames && |
michael@0 | 1462 | aFontInfoData && |
michael@0 | 1463 | aFontInfoData->mLoadFaceNames) { |
michael@0 | 1464 | aFontInfoData->GetFaceNames(fe->Name(), fullname, psname); |
michael@0 | 1465 | if (!fullname.IsEmpty()) { |
michael@0 | 1466 | aPlatformFontList->AddFullname(fe, fullname); |
michael@0 | 1467 | } |
michael@0 | 1468 | if (!psname.IsEmpty()) { |
michael@0 | 1469 | aPlatformFontList->AddPostscriptName(fe, psname); |
michael@0 | 1470 | } |
michael@0 | 1471 | foundFaceNames = true; |
michael@0 | 1472 | |
michael@0 | 1473 | // found everything needed? skip to next font |
michael@0 | 1474 | if (mOtherFamilyNamesInitialized) { |
michael@0 | 1475 | continue; |
michael@0 | 1476 | } |
michael@0 | 1477 | } |
michael@0 | 1478 | |
michael@0 | 1479 | // load directly from the name table |
michael@0 | 1480 | gfxFontEntry::AutoTable nameTable(fe, kNAME); |
michael@0 | 1481 | if (!nameTable) { |
michael@0 | 1482 | continue; |
michael@0 | 1483 | } |
michael@0 | 1484 | |
michael@0 | 1485 | if (aNeedFullnamePostscriptNames && !foundFaceNames) { |
michael@0 | 1486 | if (gfxFontUtils::ReadCanonicalName( |
michael@0 | 1487 | nameTable, gfxFontUtils::NAME_ID_FULL, fullname) == NS_OK) |
michael@0 | 1488 | { |
michael@0 | 1489 | aPlatformFontList->AddFullname(fe, fullname); |
michael@0 | 1490 | } |
michael@0 | 1491 | |
michael@0 | 1492 | if (gfxFontUtils::ReadCanonicalName( |
michael@0 | 1493 | nameTable, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK) |
michael@0 | 1494 | { |
michael@0 | 1495 | aPlatformFontList->AddPostscriptName(fe, psname); |
michael@0 | 1496 | } |
michael@0 | 1497 | } |
michael@0 | 1498 | |
michael@0 | 1499 | if (!mOtherFamilyNamesInitialized && (firstTime || readAllFaces)) { |
michael@0 | 1500 | bool foundOtherName = ReadOtherFamilyNamesForFace(aPlatformFontList, |
michael@0 | 1501 | nameTable); |
michael@0 | 1502 | |
michael@0 | 1503 | // if the first face has a different name, scan all faces, otherwise |
michael@0 | 1504 | // assume the family doesn't have other names |
michael@0 | 1505 | if (firstTime && foundOtherName) { |
michael@0 | 1506 | mHasOtherFamilyNames = true; |
michael@0 | 1507 | readAllFaces = true; |
michael@0 | 1508 | } |
michael@0 | 1509 | firstTime = false; |
michael@0 | 1510 | } |
michael@0 | 1511 | |
michael@0 | 1512 | // if not reading in any more names, skip other faces |
michael@0 | 1513 | if (!readAllFaces && !aNeedFullnamePostscriptNames) { |
michael@0 | 1514 | break; |
michael@0 | 1515 | } |
michael@0 | 1516 | } |
michael@0 | 1517 | |
michael@0 | 1518 | mFaceNamesInitialized = true; |
michael@0 | 1519 | mOtherFamilyNamesInitialized = true; |
michael@0 | 1520 | } |
michael@0 | 1521 | |
michael@0 | 1522 | |
michael@0 | 1523 | gfxFontEntry* |
michael@0 | 1524 | gfxFontFamily::FindFont(const nsAString& aPostscriptName) |
michael@0 | 1525 | { |
michael@0 | 1526 | // find the font using a simple linear search |
michael@0 | 1527 | uint32_t numFonts = mAvailableFonts.Length(); |
michael@0 | 1528 | for (uint32_t i = 0; i < numFonts; i++) { |
michael@0 | 1529 | gfxFontEntry *fe = mAvailableFonts[i].get(); |
michael@0 | 1530 | if (fe && fe->Name() == aPostscriptName) |
michael@0 | 1531 | return fe; |
michael@0 | 1532 | } |
michael@0 | 1533 | return nullptr; |
michael@0 | 1534 | } |
michael@0 | 1535 | |
michael@0 | 1536 | void |
michael@0 | 1537 | gfxFontFamily::ReadAllCMAPs(FontInfoData *aFontInfoData) |
michael@0 | 1538 | { |
michael@0 | 1539 | FindStyleVariations(aFontInfoData); |
michael@0 | 1540 | |
michael@0 | 1541 | uint32_t i, numFonts = mAvailableFonts.Length(); |
michael@0 | 1542 | for (i = 0; i < numFonts; i++) { |
michael@0 | 1543 | gfxFontEntry *fe = mAvailableFonts[i]; |
michael@0 | 1544 | // don't try to load cmaps for downloadable fonts not yet loaded |
michael@0 | 1545 | if (!fe || fe->mIsProxy) { |
michael@0 | 1546 | continue; |
michael@0 | 1547 | } |
michael@0 | 1548 | fe->ReadCMAP(aFontInfoData); |
michael@0 | 1549 | mFamilyCharacterMap.Union(*(fe->mCharacterMap)); |
michael@0 | 1550 | } |
michael@0 | 1551 | mFamilyCharacterMap.Compact(); |
michael@0 | 1552 | mFamilyCharacterMapInitialized = true; |
michael@0 | 1553 | } |
michael@0 | 1554 | |
michael@0 | 1555 | void |
michael@0 | 1556 | gfxFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, |
michael@0 | 1557 | FontListSizes* aSizes) const |
michael@0 | 1558 | { |
michael@0 | 1559 | aSizes->mFontListSize += |
michael@0 | 1560 | mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf); |
michael@0 | 1561 | aSizes->mCharMapsSize += |
michael@0 | 1562 | mFamilyCharacterMap.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1563 | |
michael@0 | 1564 | aSizes->mFontListSize += |
michael@0 | 1565 | mAvailableFonts.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 1566 | for (uint32_t i = 0; i < mAvailableFonts.Length(); ++i) { |
michael@0 | 1567 | gfxFontEntry *fe = mAvailableFonts[i]; |
michael@0 | 1568 | if (fe) { |
michael@0 | 1569 | fe->AddSizeOfIncludingThis(aMallocSizeOf, aSizes); |
michael@0 | 1570 | } |
michael@0 | 1571 | } |
michael@0 | 1572 | } |
michael@0 | 1573 | |
michael@0 | 1574 | void |
michael@0 | 1575 | gfxFontFamily::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, |
michael@0 | 1576 | FontListSizes* aSizes) const |
michael@0 | 1577 | { |
michael@0 | 1578 | aSizes->mFontListSize += aMallocSizeOf(this); |
michael@0 | 1579 | AddSizeOfExcludingThis(aMallocSizeOf, aSizes); |
michael@0 | 1580 | } |
michael@0 | 1581 | |
michael@0 | 1582 | /* |
michael@0 | 1583 | * gfxFontCache - global cache of gfxFont instances. |
michael@0 | 1584 | * Expires unused fonts after a short interval; |
michael@0 | 1585 | * notifies fonts to age their cached shaped-word records; |
michael@0 | 1586 | * observes memory-pressure notification and tells fonts to clear their |
michael@0 | 1587 | * shaped-word caches to free up memory. |
michael@0 | 1588 | */ |
michael@0 | 1589 | |
michael@0 | 1590 | MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf) |
michael@0 | 1591 | |
michael@0 | 1592 | NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter) |
michael@0 | 1593 | |
michael@0 | 1594 | NS_IMETHODIMP |
michael@0 | 1595 | gfxFontCache::MemoryReporter::CollectReports |
michael@0 | 1596 | (nsIMemoryReporterCallback* aCb, |
michael@0 | 1597 | nsISupports* aClosure) |
michael@0 | 1598 | { |
michael@0 | 1599 | FontCacheSizes sizes; |
michael@0 | 1600 | |
michael@0 | 1601 | gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf, |
michael@0 | 1602 | &sizes); |
michael@0 | 1603 | |
michael@0 | 1604 | aCb->Callback(EmptyCString(), |
michael@0 | 1605 | NS_LITERAL_CSTRING("explicit/gfx/font-cache"), |
michael@0 | 1606 | KIND_HEAP, UNITS_BYTES, sizes.mFontInstances, |
michael@0 | 1607 | NS_LITERAL_CSTRING("Memory used for active font instances."), |
michael@0 | 1608 | aClosure); |
michael@0 | 1609 | |
michael@0 | 1610 | aCb->Callback(EmptyCString(), |
michael@0 | 1611 | NS_LITERAL_CSTRING("explicit/gfx/font-shaped-words"), |
michael@0 | 1612 | KIND_HEAP, UNITS_BYTES, sizes.mShapedWords, |
michael@0 | 1613 | NS_LITERAL_CSTRING("Memory used to cache shaped glyph data."), |
michael@0 | 1614 | aClosure); |
michael@0 | 1615 | |
michael@0 | 1616 | return NS_OK; |
michael@0 | 1617 | } |
michael@0 | 1618 | |
michael@0 | 1619 | NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver) |
michael@0 | 1620 | |
michael@0 | 1621 | NS_IMETHODIMP |
michael@0 | 1622 | gfxFontCache::Observer::Observe(nsISupports *aSubject, |
michael@0 | 1623 | const char *aTopic, |
michael@0 | 1624 | const char16_t *someData) |
michael@0 | 1625 | { |
michael@0 | 1626 | if (!nsCRT::strcmp(aTopic, "memory-pressure")) { |
michael@0 | 1627 | gfxFontCache *fontCache = gfxFontCache::GetCache(); |
michael@0 | 1628 | if (fontCache) { |
michael@0 | 1629 | fontCache->FlushShapedWordCaches(); |
michael@0 | 1630 | } |
michael@0 | 1631 | } else { |
michael@0 | 1632 | NS_NOTREACHED("unexpected notification topic"); |
michael@0 | 1633 | } |
michael@0 | 1634 | return NS_OK; |
michael@0 | 1635 | } |
michael@0 | 1636 | |
michael@0 | 1637 | nsresult |
michael@0 | 1638 | gfxFontCache::Init() |
michael@0 | 1639 | { |
michael@0 | 1640 | NS_ASSERTION(!gGlobalCache, "Where did this come from?"); |
michael@0 | 1641 | gGlobalCache = new gfxFontCache(); |
michael@0 | 1642 | if (!gGlobalCache) { |
michael@0 | 1643 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 1644 | } |
michael@0 | 1645 | RegisterStrongMemoryReporter(new MemoryReporter()); |
michael@0 | 1646 | return NS_OK; |
michael@0 | 1647 | } |
michael@0 | 1648 | |
michael@0 | 1649 | void |
michael@0 | 1650 | gfxFontCache::Shutdown() |
michael@0 | 1651 | { |
michael@0 | 1652 | delete gGlobalCache; |
michael@0 | 1653 | gGlobalCache = nullptr; |
michael@0 | 1654 | |
michael@0 | 1655 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 1656 | printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark); |
michael@0 | 1657 | printf("Total number of fonts=%d\n", gFontCount); |
michael@0 | 1658 | printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount, |
michael@0 | 1659 | int(gGlyphExtentsCount*sizeof(gfxGlyphExtents))); |
michael@0 | 1660 | printf("Total glyph extents width-storage size allocated=%d\n", gGlyphExtentsWidthsTotalSize); |
michael@0 | 1661 | printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple); |
michael@0 | 1662 | printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight); |
michael@0 | 1663 | printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight); |
michael@0 | 1664 | printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight); |
michael@0 | 1665 | #endif |
michael@0 | 1666 | } |
michael@0 | 1667 | |
michael@0 | 1668 | gfxFontCache::gfxFontCache() |
michael@0 | 1669 | : nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000) |
michael@0 | 1670 | { |
michael@0 | 1671 | nsCOMPtr<nsIObserverService> obs = GetObserverService(); |
michael@0 | 1672 | if (obs) { |
michael@0 | 1673 | obs->AddObserver(new Observer, "memory-pressure", false); |
michael@0 | 1674 | } |
michael@0 | 1675 | |
michael@0 | 1676 | #ifndef RELEASE_BUILD |
michael@0 | 1677 | // Currently disabled for release builds, due to unexplained crashes |
michael@0 | 1678 | // during expiration; see bug 717175 & 894798. |
michael@0 | 1679 | mWordCacheExpirationTimer = do_CreateInstance("@mozilla.org/timer;1"); |
michael@0 | 1680 | if (mWordCacheExpirationTimer) { |
michael@0 | 1681 | mWordCacheExpirationTimer-> |
michael@0 | 1682 | InitWithFuncCallback(WordCacheExpirationTimerCallback, this, |
michael@0 | 1683 | SHAPED_WORD_TIMEOUT_SECONDS * 1000, |
michael@0 | 1684 | nsITimer::TYPE_REPEATING_SLACK); |
michael@0 | 1685 | } |
michael@0 | 1686 | #endif |
michael@0 | 1687 | } |
michael@0 | 1688 | |
michael@0 | 1689 | gfxFontCache::~gfxFontCache() |
michael@0 | 1690 | { |
michael@0 | 1691 | // Ensure the user font cache releases its references to font entries, |
michael@0 | 1692 | // so they aren't kept alive after the font instances and font-list |
michael@0 | 1693 | // have been shut down. |
michael@0 | 1694 | gfxUserFontSet::UserFontCache::Shutdown(); |
michael@0 | 1695 | |
michael@0 | 1696 | if (mWordCacheExpirationTimer) { |
michael@0 | 1697 | mWordCacheExpirationTimer->Cancel(); |
michael@0 | 1698 | mWordCacheExpirationTimer = nullptr; |
michael@0 | 1699 | } |
michael@0 | 1700 | |
michael@0 | 1701 | // Expire everything that has a zero refcount, so we don't leak them. |
michael@0 | 1702 | AgeAllGenerations(); |
michael@0 | 1703 | // All fonts should be gone. |
michael@0 | 1704 | NS_WARN_IF_FALSE(mFonts.Count() == 0, |
michael@0 | 1705 | "Fonts still alive while shutting down gfxFontCache"); |
michael@0 | 1706 | // Note that we have to delete everything through the expiration |
michael@0 | 1707 | // tracker, since there might be fonts not in the hashtable but in |
michael@0 | 1708 | // the tracker. |
michael@0 | 1709 | } |
michael@0 | 1710 | |
michael@0 | 1711 | bool |
michael@0 | 1712 | gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const |
michael@0 | 1713 | { |
michael@0 | 1714 | return aKey->mFontEntry == mFont->GetFontEntry() && |
michael@0 | 1715 | aKey->mStyle->Equals(*mFont->GetStyle()); |
michael@0 | 1716 | } |
michael@0 | 1717 | |
michael@0 | 1718 | already_AddRefed<gfxFont> |
michael@0 | 1719 | gfxFontCache::Lookup(const gfxFontEntry *aFontEntry, |
michael@0 | 1720 | const gfxFontStyle *aStyle) |
michael@0 | 1721 | { |
michael@0 | 1722 | Key key(aFontEntry, aStyle); |
michael@0 | 1723 | HashEntry *entry = mFonts.GetEntry(key); |
michael@0 | 1724 | |
michael@0 | 1725 | Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT, entry != nullptr); |
michael@0 | 1726 | if (!entry) |
michael@0 | 1727 | return nullptr; |
michael@0 | 1728 | |
michael@0 | 1729 | nsRefPtr<gfxFont> font = entry->mFont; |
michael@0 | 1730 | return font.forget(); |
michael@0 | 1731 | } |
michael@0 | 1732 | |
michael@0 | 1733 | void |
michael@0 | 1734 | gfxFontCache::AddNew(gfxFont *aFont) |
michael@0 | 1735 | { |
michael@0 | 1736 | Key key(aFont->GetFontEntry(), aFont->GetStyle()); |
michael@0 | 1737 | HashEntry *entry = mFonts.PutEntry(key); |
michael@0 | 1738 | if (!entry) |
michael@0 | 1739 | return; |
michael@0 | 1740 | gfxFont *oldFont = entry->mFont; |
michael@0 | 1741 | entry->mFont = aFont; |
michael@0 | 1742 | // Assert that we can find the entry we just put in (this fails if the key |
michael@0 | 1743 | // has a NaN float value in it, e.g. 'sizeAdjust'). |
michael@0 | 1744 | MOZ_ASSERT(entry == mFonts.GetEntry(key)); |
michael@0 | 1745 | // If someone's asked us to replace an existing font entry, then that's a |
michael@0 | 1746 | // bit weird, but let it happen, and expire the old font if it's not used. |
michael@0 | 1747 | if (oldFont && oldFont->GetExpirationState()->IsTracked()) { |
michael@0 | 1748 | // if oldFont == aFont, recount should be > 0, |
michael@0 | 1749 | // so we shouldn't be here. |
michael@0 | 1750 | NS_ASSERTION(aFont != oldFont, "new font is tracked for expiry!"); |
michael@0 | 1751 | NotifyExpired(oldFont); |
michael@0 | 1752 | } |
michael@0 | 1753 | } |
michael@0 | 1754 | |
michael@0 | 1755 | void |
michael@0 | 1756 | gfxFontCache::NotifyReleased(gfxFont *aFont) |
michael@0 | 1757 | { |
michael@0 | 1758 | nsresult rv = AddObject(aFont); |
michael@0 | 1759 | if (NS_FAILED(rv)) { |
michael@0 | 1760 | // We couldn't track it for some reason. Kill it now. |
michael@0 | 1761 | DestroyFont(aFont); |
michael@0 | 1762 | } |
michael@0 | 1763 | // Note that we might have fonts that aren't in the hashtable, perhaps because |
michael@0 | 1764 | // of OOM adding to the hashtable or because someone did an AddNew where |
michael@0 | 1765 | // we already had a font. These fonts are added to the expiration tracker |
michael@0 | 1766 | // anyway, even though Lookup can't resurrect them. Eventually they will |
michael@0 | 1767 | // expire and be deleted. |
michael@0 | 1768 | } |
michael@0 | 1769 | |
michael@0 | 1770 | void |
michael@0 | 1771 | gfxFontCache::NotifyExpired(gfxFont *aFont) |
michael@0 | 1772 | { |
michael@0 | 1773 | aFont->ClearCachedWords(); |
michael@0 | 1774 | RemoveObject(aFont); |
michael@0 | 1775 | DestroyFont(aFont); |
michael@0 | 1776 | } |
michael@0 | 1777 | |
michael@0 | 1778 | void |
michael@0 | 1779 | gfxFontCache::DestroyFont(gfxFont *aFont) |
michael@0 | 1780 | { |
michael@0 | 1781 | Key key(aFont->GetFontEntry(), aFont->GetStyle()); |
michael@0 | 1782 | HashEntry *entry = mFonts.GetEntry(key); |
michael@0 | 1783 | if (entry && entry->mFont == aFont) { |
michael@0 | 1784 | mFonts.RemoveEntry(key); |
michael@0 | 1785 | } |
michael@0 | 1786 | NS_ASSERTION(aFont->GetRefCount() == 0, |
michael@0 | 1787 | "Destroying with non-zero ref count!"); |
michael@0 | 1788 | delete aFont; |
michael@0 | 1789 | } |
michael@0 | 1790 | |
michael@0 | 1791 | /*static*/ |
michael@0 | 1792 | PLDHashOperator |
michael@0 | 1793 | gfxFontCache::AgeCachedWordsForFont(HashEntry* aHashEntry, void* aUserData) |
michael@0 | 1794 | { |
michael@0 | 1795 | aHashEntry->mFont->AgeCachedWords(); |
michael@0 | 1796 | return PL_DHASH_NEXT; |
michael@0 | 1797 | } |
michael@0 | 1798 | |
michael@0 | 1799 | /*static*/ |
michael@0 | 1800 | void |
michael@0 | 1801 | gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache) |
michael@0 | 1802 | { |
michael@0 | 1803 | gfxFontCache* cache = static_cast<gfxFontCache*>(aCache); |
michael@0 | 1804 | cache->mFonts.EnumerateEntries(AgeCachedWordsForFont, nullptr); |
michael@0 | 1805 | } |
michael@0 | 1806 | |
michael@0 | 1807 | /*static*/ |
michael@0 | 1808 | PLDHashOperator |
michael@0 | 1809 | gfxFontCache::ClearCachedWordsForFont(HashEntry* aHashEntry, void* aUserData) |
michael@0 | 1810 | { |
michael@0 | 1811 | aHashEntry->mFont->ClearCachedWords(); |
michael@0 | 1812 | return PL_DHASH_NEXT; |
michael@0 | 1813 | } |
michael@0 | 1814 | |
michael@0 | 1815 | /*static*/ |
michael@0 | 1816 | size_t |
michael@0 | 1817 | gfxFontCache::AddSizeOfFontEntryExcludingThis(HashEntry* aHashEntry, |
michael@0 | 1818 | MallocSizeOf aMallocSizeOf, |
michael@0 | 1819 | void* aUserArg) |
michael@0 | 1820 | { |
michael@0 | 1821 | HashEntry *entry = static_cast<HashEntry*>(aHashEntry); |
michael@0 | 1822 | FontCacheSizes *sizes = static_cast<FontCacheSizes*>(aUserArg); |
michael@0 | 1823 | entry->mFont->AddSizeOfExcludingThis(aMallocSizeOf, sizes); |
michael@0 | 1824 | |
michael@0 | 1825 | // The entry's size is recorded in the |sizes| parameter, so we return zero |
michael@0 | 1826 | // here to the hashtable enumerator. |
michael@0 | 1827 | return 0; |
michael@0 | 1828 | } |
michael@0 | 1829 | |
michael@0 | 1830 | void |
michael@0 | 1831 | gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, |
michael@0 | 1832 | FontCacheSizes* aSizes) const |
michael@0 | 1833 | { |
michael@0 | 1834 | // TODO: add the overhead of the expiration tracker (generation arrays) |
michael@0 | 1835 | |
michael@0 | 1836 | aSizes->mFontInstances += |
michael@0 | 1837 | mFonts.SizeOfExcludingThis(AddSizeOfFontEntryExcludingThis, |
michael@0 | 1838 | aMallocSizeOf, aSizes); |
michael@0 | 1839 | } |
michael@0 | 1840 | |
michael@0 | 1841 | void |
michael@0 | 1842 | gfxFontCache::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, |
michael@0 | 1843 | FontCacheSizes* aSizes) const |
michael@0 | 1844 | { |
michael@0 | 1845 | aSizes->mFontInstances += aMallocSizeOf(this); |
michael@0 | 1846 | AddSizeOfExcludingThis(aMallocSizeOf, aSizes); |
michael@0 | 1847 | } |
michael@0 | 1848 | |
michael@0 | 1849 | #define MAX_SSXX_VALUE 99 |
michael@0 | 1850 | #define MAX_CVXX_VALUE 99 |
michael@0 | 1851 | |
michael@0 | 1852 | static void |
michael@0 | 1853 | LookupAlternateValues(gfxFontFeatureValueSet *featureLookup, |
michael@0 | 1854 | const nsAString& aFamily, |
michael@0 | 1855 | const nsTArray<gfxAlternateValue>& altValue, |
michael@0 | 1856 | nsTArray<gfxFontFeature>& aFontFeatures) |
michael@0 | 1857 | { |
michael@0 | 1858 | uint32_t numAlternates = altValue.Length(); |
michael@0 | 1859 | for (uint32_t i = 0; i < numAlternates; i++) { |
michael@0 | 1860 | const gfxAlternateValue& av = altValue.ElementAt(i); |
michael@0 | 1861 | nsAutoTArray<uint32_t,4> values; |
michael@0 | 1862 | |
michael@0 | 1863 | // map <family, name, feature> ==> <values> |
michael@0 | 1864 | bool found = |
michael@0 | 1865 | featureLookup->GetFontFeatureValuesFor(aFamily, av.alternate, |
michael@0 | 1866 | av.value, values); |
michael@0 | 1867 | uint32_t numValues = values.Length(); |
michael@0 | 1868 | |
michael@0 | 1869 | // nothing defined, skip |
michael@0 | 1870 | if (!found || numValues == 0) { |
michael@0 | 1871 | continue; |
michael@0 | 1872 | } |
michael@0 | 1873 | |
michael@0 | 1874 | gfxFontFeature feature; |
michael@0 | 1875 | if (av.alternate == NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT) { |
michael@0 | 1876 | NS_ASSERTION(numValues <= 2, |
michael@0 | 1877 | "too many values allowed for character-variant"); |
michael@0 | 1878 | // character-variant(12 3) ==> 'cv12' = 3 |
michael@0 | 1879 | uint32_t nn = values.ElementAt(0); |
michael@0 | 1880 | // ignore values greater than 99 |
michael@0 | 1881 | if (nn == 0 || nn > MAX_CVXX_VALUE) { |
michael@0 | 1882 | continue; |
michael@0 | 1883 | } |
michael@0 | 1884 | feature.mValue = 1; |
michael@0 | 1885 | if (numValues > 1) { |
michael@0 | 1886 | feature.mValue = values.ElementAt(1); |
michael@0 | 1887 | } |
michael@0 | 1888 | feature.mTag = HB_TAG('c','v',('0' + nn / 10), ('0' + nn % 10)); |
michael@0 | 1889 | aFontFeatures.AppendElement(feature); |
michael@0 | 1890 | |
michael@0 | 1891 | } else if (av.alternate == NS_FONT_VARIANT_ALTERNATES_STYLESET) { |
michael@0 | 1892 | // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1 |
michael@0 | 1893 | feature.mValue = 1; |
michael@0 | 1894 | for (uint32_t v = 0; v < numValues; v++) { |
michael@0 | 1895 | uint32_t nn = values.ElementAt(v); |
michael@0 | 1896 | if (nn == 0 || nn > MAX_SSXX_VALUE) { |
michael@0 | 1897 | continue; |
michael@0 | 1898 | } |
michael@0 | 1899 | feature.mTag = HB_TAG('s','s',('0' + nn / 10), ('0' + nn % 10)); |
michael@0 | 1900 | aFontFeatures.AppendElement(feature); |
michael@0 | 1901 | } |
michael@0 | 1902 | |
michael@0 | 1903 | } else { |
michael@0 | 1904 | NS_ASSERTION(numValues == 1, |
michael@0 | 1905 | "too many values for font-specific font-variant-alternates"); |
michael@0 | 1906 | feature.mValue = values.ElementAt(0); |
michael@0 | 1907 | |
michael@0 | 1908 | switch (av.alternate) { |
michael@0 | 1909 | case NS_FONT_VARIANT_ALTERNATES_STYLISTIC: // salt |
michael@0 | 1910 | feature.mTag = HB_TAG('s','a','l','t'); |
michael@0 | 1911 | break; |
michael@0 | 1912 | case NS_FONT_VARIANT_ALTERNATES_SWASH: // swsh, cswh |
michael@0 | 1913 | feature.mTag = HB_TAG('s','w','s','h'); |
michael@0 | 1914 | aFontFeatures.AppendElement(feature); |
michael@0 | 1915 | feature.mTag = HB_TAG('c','s','w','h'); |
michael@0 | 1916 | break; |
michael@0 | 1917 | case NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: // ornm |
michael@0 | 1918 | feature.mTag = HB_TAG('o','r','n','m'); |
michael@0 | 1919 | break; |
michael@0 | 1920 | case NS_FONT_VARIANT_ALTERNATES_ANNOTATION: // nalt |
michael@0 | 1921 | feature.mTag = HB_TAG('n','a','l','t'); |
michael@0 | 1922 | break; |
michael@0 | 1923 | default: |
michael@0 | 1924 | feature.mTag = 0; |
michael@0 | 1925 | break; |
michael@0 | 1926 | } |
michael@0 | 1927 | |
michael@0 | 1928 | NS_ASSERTION(feature.mTag, "unsupported alternate type"); |
michael@0 | 1929 | if (!feature.mTag) { |
michael@0 | 1930 | continue; |
michael@0 | 1931 | } |
michael@0 | 1932 | aFontFeatures.AppendElement(feature); |
michael@0 | 1933 | } |
michael@0 | 1934 | } |
michael@0 | 1935 | } |
michael@0 | 1936 | |
michael@0 | 1937 | /* static */ bool |
michael@0 | 1938 | gfxFontShaper::MergeFontFeatures( |
michael@0 | 1939 | const gfxFontStyle *aStyle, |
michael@0 | 1940 | const nsTArray<gfxFontFeature>& aFontFeatures, |
michael@0 | 1941 | bool aDisableLigatures, |
michael@0 | 1942 | const nsAString& aFamilyName, |
michael@0 | 1943 | nsDataHashtable<nsUint32HashKey,uint32_t>& aMergedFeatures) |
michael@0 | 1944 | { |
michael@0 | 1945 | uint32_t numAlts = aStyle->alternateValues.Length(); |
michael@0 | 1946 | const nsTArray<gfxFontFeature>& styleRuleFeatures = |
michael@0 | 1947 | aStyle->featureSettings; |
michael@0 | 1948 | |
michael@0 | 1949 | // bail immediately if nothing to do |
michael@0 | 1950 | if (styleRuleFeatures.IsEmpty() && |
michael@0 | 1951 | aFontFeatures.IsEmpty() && |
michael@0 | 1952 | !aDisableLigatures && |
michael@0 | 1953 | numAlts == 0) { |
michael@0 | 1954 | return false; |
michael@0 | 1955 | } |
michael@0 | 1956 | |
michael@0 | 1957 | // Ligature features are enabled by default in the generic shaper, |
michael@0 | 1958 | // so we explicitly turn them off if necessary (for letter-spacing) |
michael@0 | 1959 | if (aDisableLigatures) { |
michael@0 | 1960 | aMergedFeatures.Put(HB_TAG('l','i','g','a'), 0); |
michael@0 | 1961 | aMergedFeatures.Put(HB_TAG('c','l','i','g'), 0); |
michael@0 | 1962 | } |
michael@0 | 1963 | |
michael@0 | 1964 | // add feature values from font |
michael@0 | 1965 | uint32_t i, count; |
michael@0 | 1966 | |
michael@0 | 1967 | count = aFontFeatures.Length(); |
michael@0 | 1968 | for (i = 0; i < count; i++) { |
michael@0 | 1969 | const gfxFontFeature& feature = aFontFeatures.ElementAt(i); |
michael@0 | 1970 | aMergedFeatures.Put(feature.mTag, feature.mValue); |
michael@0 | 1971 | } |
michael@0 | 1972 | |
michael@0 | 1973 | // add font-specific feature values from style rules |
michael@0 | 1974 | if (aStyle->featureValueLookup && numAlts > 0) { |
michael@0 | 1975 | nsAutoTArray<gfxFontFeature,4> featureList; |
michael@0 | 1976 | |
michael@0 | 1977 | // insert list of alternate feature settings |
michael@0 | 1978 | LookupAlternateValues(aStyle->featureValueLookup, aFamilyName, |
michael@0 | 1979 | aStyle->alternateValues, featureList); |
michael@0 | 1980 | |
michael@0 | 1981 | count = featureList.Length(); |
michael@0 | 1982 | for (i = 0; i < count; i++) { |
michael@0 | 1983 | const gfxFontFeature& feature = featureList.ElementAt(i); |
michael@0 | 1984 | aMergedFeatures.Put(feature.mTag, feature.mValue); |
michael@0 | 1985 | } |
michael@0 | 1986 | } |
michael@0 | 1987 | |
michael@0 | 1988 | // add feature values from style rules |
michael@0 | 1989 | count = styleRuleFeatures.Length(); |
michael@0 | 1990 | for (i = 0; i < count; i++) { |
michael@0 | 1991 | const gfxFontFeature& feature = styleRuleFeatures.ElementAt(i); |
michael@0 | 1992 | aMergedFeatures.Put(feature.mTag, feature.mValue); |
michael@0 | 1993 | } |
michael@0 | 1994 | |
michael@0 | 1995 | return aMergedFeatures.Count() != 0; |
michael@0 | 1996 | } |
michael@0 | 1997 | |
michael@0 | 1998 | void |
michael@0 | 1999 | gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft) |
michael@0 | 2000 | { |
michael@0 | 2001 | mAscent = std::max(mAscent, aOther.mAscent); |
michael@0 | 2002 | mDescent = std::max(mDescent, aOther.mDescent); |
michael@0 | 2003 | if (aOtherIsOnLeft) { |
michael@0 | 2004 | mBoundingBox = |
michael@0 | 2005 | (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)).Union(aOther.mBoundingBox); |
michael@0 | 2006 | } else { |
michael@0 | 2007 | mBoundingBox = |
michael@0 | 2008 | mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0)); |
michael@0 | 2009 | } |
michael@0 | 2010 | mAdvanceWidth += aOther.mAdvanceWidth; |
michael@0 | 2011 | } |
michael@0 | 2012 | |
michael@0 | 2013 | gfxFont::gfxFont(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle, |
michael@0 | 2014 | AntialiasOption anAAOption, cairo_scaled_font_t *aScaledFont) : |
michael@0 | 2015 | mScaledFont(aScaledFont), |
michael@0 | 2016 | mFontEntry(aFontEntry), mIsValid(true), |
michael@0 | 2017 | mApplySyntheticBold(false), |
michael@0 | 2018 | mStyle(*aFontStyle), |
michael@0 | 2019 | mAdjustedSize(0.0), |
michael@0 | 2020 | mFUnitsConvFactor(0.0f), |
michael@0 | 2021 | mAntialiasOption(anAAOption) |
michael@0 | 2022 | { |
michael@0 | 2023 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 2024 | ++gFontCount; |
michael@0 | 2025 | #endif |
michael@0 | 2026 | mKerningSet = HasFeatureSet(HB_TAG('k','e','r','n'), mKerningEnabled); |
michael@0 | 2027 | } |
michael@0 | 2028 | |
michael@0 | 2029 | static PLDHashOperator |
michael@0 | 2030 | NotifyFontDestroyed(nsPtrHashKey<gfxFont::GlyphChangeObserver>* aKey, |
michael@0 | 2031 | void* aClosure) |
michael@0 | 2032 | { |
michael@0 | 2033 | aKey->GetKey()->ForgetFont(); |
michael@0 | 2034 | return PL_DHASH_NEXT; |
michael@0 | 2035 | } |
michael@0 | 2036 | |
michael@0 | 2037 | gfxFont::~gfxFont() |
michael@0 | 2038 | { |
michael@0 | 2039 | uint32_t i, count = mGlyphExtentsArray.Length(); |
michael@0 | 2040 | // We destroy the contents of mGlyphExtentsArray explicitly instead of |
michael@0 | 2041 | // using nsAutoPtr because VC++ can't deal with nsTArrays of nsAutoPtrs |
michael@0 | 2042 | // of classes that lack a proper copy constructor |
michael@0 | 2043 | for (i = 0; i < count; ++i) { |
michael@0 | 2044 | delete mGlyphExtentsArray[i]; |
michael@0 | 2045 | } |
michael@0 | 2046 | |
michael@0 | 2047 | mFontEntry->NotifyFontDestroyed(this); |
michael@0 | 2048 | |
michael@0 | 2049 | if (mGlyphChangeObservers) { |
michael@0 | 2050 | mGlyphChangeObservers->EnumerateEntries(NotifyFontDestroyed, nullptr); |
michael@0 | 2051 | } |
michael@0 | 2052 | } |
michael@0 | 2053 | |
michael@0 | 2054 | gfxFloat |
michael@0 | 2055 | gfxFont::GetGlyphHAdvance(gfxContext *aCtx, uint16_t aGID) |
michael@0 | 2056 | { |
michael@0 | 2057 | if (!SetupCairoFont(aCtx)) { |
michael@0 | 2058 | return 0; |
michael@0 | 2059 | } |
michael@0 | 2060 | if (ProvidesGlyphWidths()) { |
michael@0 | 2061 | return GetGlyphWidth(aCtx, aGID) / 65536.0; |
michael@0 | 2062 | } |
michael@0 | 2063 | if (mFUnitsConvFactor == 0.0f) { |
michael@0 | 2064 | GetMetrics(); |
michael@0 | 2065 | } |
michael@0 | 2066 | NS_ASSERTION(mFUnitsConvFactor > 0.0f, |
michael@0 | 2067 | "missing font unit conversion factor"); |
michael@0 | 2068 | if (!mHarfBuzzShaper) { |
michael@0 | 2069 | mHarfBuzzShaper = new gfxHarfBuzzShaper(this); |
michael@0 | 2070 | } |
michael@0 | 2071 | gfxHarfBuzzShaper* shaper = |
michael@0 | 2072 | static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get()); |
michael@0 | 2073 | if (!shaper->Initialize()) { |
michael@0 | 2074 | return 0; |
michael@0 | 2075 | } |
michael@0 | 2076 | return shaper->GetGlyphHAdvance(aCtx, aGID) / 65536.0; |
michael@0 | 2077 | } |
michael@0 | 2078 | |
michael@0 | 2079 | /*static*/ |
michael@0 | 2080 | PLDHashOperator |
michael@0 | 2081 | gfxFont::AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData) |
michael@0 | 2082 | { |
michael@0 | 2083 | if (!aEntry->mShapedWord) { |
michael@0 | 2084 | NS_ASSERTION(aEntry->mShapedWord, "cache entry has no gfxShapedWord!"); |
michael@0 | 2085 | return PL_DHASH_REMOVE; |
michael@0 | 2086 | } |
michael@0 | 2087 | if (aEntry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) { |
michael@0 | 2088 | return PL_DHASH_REMOVE; |
michael@0 | 2089 | } |
michael@0 | 2090 | return PL_DHASH_NEXT; |
michael@0 | 2091 | } |
michael@0 | 2092 | |
michael@0 | 2093 | static void |
michael@0 | 2094 | CollectLookupsByFeature(hb_face_t *aFace, hb_tag_t aTableTag, |
michael@0 | 2095 | uint32_t aFeatureIndex, hb_set_t *aLookups) |
michael@0 | 2096 | { |
michael@0 | 2097 | uint32_t lookups[32]; |
michael@0 | 2098 | uint32_t i, len, offset; |
michael@0 | 2099 | |
michael@0 | 2100 | offset = 0; |
michael@0 | 2101 | do { |
michael@0 | 2102 | len = ArrayLength(lookups); |
michael@0 | 2103 | hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, |
michael@0 | 2104 | offset, &len, lookups); |
michael@0 | 2105 | for (i = 0; i < len; i++) { |
michael@0 | 2106 | hb_set_add(aLookups, lookups[i]); |
michael@0 | 2107 | } |
michael@0 | 2108 | offset += len; |
michael@0 | 2109 | } while (len == ArrayLength(lookups)); |
michael@0 | 2110 | } |
michael@0 | 2111 | |
michael@0 | 2112 | static void |
michael@0 | 2113 | CollectLookupsByLanguage(hb_face_t *aFace, hb_tag_t aTableTag, |
michael@0 | 2114 | const nsTHashtable<nsUint32HashKey>& |
michael@0 | 2115 | aSpecificFeatures, |
michael@0 | 2116 | hb_set_t *aOtherLookups, |
michael@0 | 2117 | hb_set_t *aSpecificFeatureLookups, |
michael@0 | 2118 | uint32_t aScriptIndex, uint32_t aLangIndex) |
michael@0 | 2119 | { |
michael@0 | 2120 | uint32_t reqFeatureIndex; |
michael@0 | 2121 | if (hb_ot_layout_language_get_required_feature_index(aFace, aTableTag, |
michael@0 | 2122 | aScriptIndex, |
michael@0 | 2123 | aLangIndex, |
michael@0 | 2124 | &reqFeatureIndex)) { |
michael@0 | 2125 | CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, |
michael@0 | 2126 | aOtherLookups); |
michael@0 | 2127 | } |
michael@0 | 2128 | |
michael@0 | 2129 | uint32_t featureIndexes[32]; |
michael@0 | 2130 | uint32_t i, len, offset; |
michael@0 | 2131 | |
michael@0 | 2132 | offset = 0; |
michael@0 | 2133 | do { |
michael@0 | 2134 | len = ArrayLength(featureIndexes); |
michael@0 | 2135 | hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, |
michael@0 | 2136 | aScriptIndex, aLangIndex, |
michael@0 | 2137 | offset, &len, featureIndexes); |
michael@0 | 2138 | |
michael@0 | 2139 | for (i = 0; i < len; i++) { |
michael@0 | 2140 | uint32_t featureIndex = featureIndexes[i]; |
michael@0 | 2141 | |
michael@0 | 2142 | // get the feature tag |
michael@0 | 2143 | hb_tag_t featureTag; |
michael@0 | 2144 | uint32_t tagLen = 1; |
michael@0 | 2145 | hb_ot_layout_language_get_feature_tags(aFace, aTableTag, |
michael@0 | 2146 | aScriptIndex, aLangIndex, |
michael@0 | 2147 | offset + i, &tagLen, |
michael@0 | 2148 | &featureTag); |
michael@0 | 2149 | |
michael@0 | 2150 | // collect lookups |
michael@0 | 2151 | hb_set_t *lookups = aSpecificFeatures.GetEntry(featureTag) ? |
michael@0 | 2152 | aSpecificFeatureLookups : aOtherLookups; |
michael@0 | 2153 | CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups); |
michael@0 | 2154 | } |
michael@0 | 2155 | offset += len; |
michael@0 | 2156 | } while (len == ArrayLength(featureIndexes)); |
michael@0 | 2157 | } |
michael@0 | 2158 | |
michael@0 | 2159 | static bool |
michael@0 | 2160 | HasLookupRuleWithGlyphByScript(hb_face_t *aFace, hb_tag_t aTableTag, |
michael@0 | 2161 | hb_tag_t aScriptTag, uint32_t aScriptIndex, |
michael@0 | 2162 | uint16_t aGlyph, |
michael@0 | 2163 | const nsTHashtable<nsUint32HashKey>& |
michael@0 | 2164 | aDefaultFeatures, |
michael@0 | 2165 | bool& aHasDefaultFeatureWithGlyph) |
michael@0 | 2166 | { |
michael@0 | 2167 | uint32_t numLangs, lang; |
michael@0 | 2168 | hb_set_t *defaultFeatureLookups = hb_set_create(); |
michael@0 | 2169 | hb_set_t *nonDefaultFeatureLookups = hb_set_create(); |
michael@0 | 2170 | |
michael@0 | 2171 | // default lang |
michael@0 | 2172 | CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures, |
michael@0 | 2173 | nonDefaultFeatureLookups, defaultFeatureLookups, |
michael@0 | 2174 | aScriptIndex, |
michael@0 | 2175 | HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX); |
michael@0 | 2176 | |
michael@0 | 2177 | // iterate over langs |
michael@0 | 2178 | numLangs = hb_ot_layout_script_get_language_tags(aFace, aTableTag, |
michael@0 | 2179 | aScriptIndex, 0, |
michael@0 | 2180 | nullptr, nullptr); |
michael@0 | 2181 | for (lang = 0; lang < numLangs; lang++) { |
michael@0 | 2182 | CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures, |
michael@0 | 2183 | nonDefaultFeatureLookups, |
michael@0 | 2184 | defaultFeatureLookups, |
michael@0 | 2185 | aScriptIndex, lang); |
michael@0 | 2186 | } |
michael@0 | 2187 | |
michael@0 | 2188 | // look for the glyph among default feature lookups |
michael@0 | 2189 | aHasDefaultFeatureWithGlyph = false; |
michael@0 | 2190 | hb_set_t *glyphs = hb_set_create(); |
michael@0 | 2191 | hb_codepoint_t index = -1; |
michael@0 | 2192 | while (hb_set_next(defaultFeatureLookups, &index)) { |
michael@0 | 2193 | hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, |
michael@0 | 2194 | glyphs, glyphs, glyphs, |
michael@0 | 2195 | glyphs); |
michael@0 | 2196 | if (hb_set_has(glyphs, aGlyph)) { |
michael@0 | 2197 | aHasDefaultFeatureWithGlyph = true; |
michael@0 | 2198 | break; |
michael@0 | 2199 | } |
michael@0 | 2200 | } |
michael@0 | 2201 | |
michael@0 | 2202 | // look for the glyph among non-default feature lookups |
michael@0 | 2203 | // if no default feature lookups contained spaces |
michael@0 | 2204 | bool hasNonDefaultFeatureWithGlyph = false; |
michael@0 | 2205 | if (!aHasDefaultFeatureWithGlyph) { |
michael@0 | 2206 | hb_set_clear(glyphs); |
michael@0 | 2207 | index = -1; |
michael@0 | 2208 | while (hb_set_next(nonDefaultFeatureLookups, &index)) { |
michael@0 | 2209 | hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, |
michael@0 | 2210 | glyphs, glyphs, glyphs, |
michael@0 | 2211 | glyphs); |
michael@0 | 2212 | if (hb_set_has(glyphs, aGlyph)) { |
michael@0 | 2213 | hasNonDefaultFeatureWithGlyph = true; |
michael@0 | 2214 | break; |
michael@0 | 2215 | } |
michael@0 | 2216 | } |
michael@0 | 2217 | } |
michael@0 | 2218 | |
michael@0 | 2219 | hb_set_destroy(glyphs); |
michael@0 | 2220 | hb_set_destroy(defaultFeatureLookups); |
michael@0 | 2221 | hb_set_destroy(nonDefaultFeatureLookups); |
michael@0 | 2222 | |
michael@0 | 2223 | return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph; |
michael@0 | 2224 | } |
michael@0 | 2225 | |
michael@0 | 2226 | static void |
michael@0 | 2227 | HasLookupRuleWithGlyph(hb_face_t *aFace, hb_tag_t aTableTag, bool& aHasGlyph, |
michael@0 | 2228 | hb_tag_t aSpecificFeature, bool& aHasGlyphSpecific, |
michael@0 | 2229 | uint16_t aGlyph) |
michael@0 | 2230 | { |
michael@0 | 2231 | // iterate over the scripts in the font |
michael@0 | 2232 | uint32_t numScripts, numLangs, script, lang; |
michael@0 | 2233 | hb_set_t *otherLookups = hb_set_create(); |
michael@0 | 2234 | hb_set_t *specificFeatureLookups = hb_set_create(); |
michael@0 | 2235 | nsTHashtable<nsUint32HashKey> specificFeature; |
michael@0 | 2236 | |
michael@0 | 2237 | specificFeature.PutEntry(aSpecificFeature); |
michael@0 | 2238 | |
michael@0 | 2239 | numScripts = hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, |
michael@0 | 2240 | nullptr, nullptr); |
michael@0 | 2241 | |
michael@0 | 2242 | for (script = 0; script < numScripts; script++) { |
michael@0 | 2243 | // default lang |
michael@0 | 2244 | CollectLookupsByLanguage(aFace, aTableTag, specificFeature, |
michael@0 | 2245 | otherLookups, specificFeatureLookups, |
michael@0 | 2246 | script, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX); |
michael@0 | 2247 | |
michael@0 | 2248 | // iterate over langs |
michael@0 | 2249 | numLangs = hb_ot_layout_script_get_language_tags(aFace, HB_OT_TAG_GPOS, |
michael@0 | 2250 | script, 0, |
michael@0 | 2251 | nullptr, nullptr); |
michael@0 | 2252 | for (lang = 0; lang < numLangs; lang++) { |
michael@0 | 2253 | CollectLookupsByLanguage(aFace, aTableTag, specificFeature, |
michael@0 | 2254 | otherLookups, specificFeatureLookups, |
michael@0 | 2255 | script, lang); |
michael@0 | 2256 | } |
michael@0 | 2257 | } |
michael@0 | 2258 | |
michael@0 | 2259 | // look for the glyph among non-specific feature lookups |
michael@0 | 2260 | hb_set_t *glyphs = hb_set_create(); |
michael@0 | 2261 | hb_codepoint_t index = -1; |
michael@0 | 2262 | while (hb_set_next(otherLookups, &index)) { |
michael@0 | 2263 | hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, |
michael@0 | 2264 | glyphs, glyphs, glyphs, |
michael@0 | 2265 | glyphs); |
michael@0 | 2266 | if (hb_set_has(glyphs, aGlyph)) { |
michael@0 | 2267 | aHasGlyph = true; |
michael@0 | 2268 | break; |
michael@0 | 2269 | } |
michael@0 | 2270 | } |
michael@0 | 2271 | |
michael@0 | 2272 | // look for the glyph among specific feature lookups |
michael@0 | 2273 | hb_set_clear(glyphs); |
michael@0 | 2274 | index = -1; |
michael@0 | 2275 | while (hb_set_next(specificFeatureLookups, &index)) { |
michael@0 | 2276 | hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, |
michael@0 | 2277 | glyphs, glyphs, glyphs, |
michael@0 | 2278 | glyphs); |
michael@0 | 2279 | if (hb_set_has(glyphs, aGlyph)) { |
michael@0 | 2280 | aHasGlyphSpecific = true; |
michael@0 | 2281 | break; |
michael@0 | 2282 | } |
michael@0 | 2283 | } |
michael@0 | 2284 | |
michael@0 | 2285 | hb_set_destroy(glyphs); |
michael@0 | 2286 | hb_set_destroy(specificFeatureLookups); |
michael@0 | 2287 | hb_set_destroy(otherLookups); |
michael@0 | 2288 | } |
michael@0 | 2289 | |
michael@0 | 2290 | nsDataHashtable<nsUint32HashKey, int32_t> *gfxFont::sScriptTagToCode = nullptr; |
michael@0 | 2291 | nsTHashtable<nsUint32HashKey> *gfxFont::sDefaultFeatures = nullptr; |
michael@0 | 2292 | |
michael@0 | 2293 | static inline bool |
michael@0 | 2294 | HasSubstitution(uint32_t *aBitVector, uint32_t aBit) { |
michael@0 | 2295 | return (aBitVector[aBit >> 5] & (1 << (aBit & 0x1f))) != 0; |
michael@0 | 2296 | } |
michael@0 | 2297 | |
michael@0 | 2298 | // union of all default substitution features across scripts |
michael@0 | 2299 | static const hb_tag_t defaultFeatures[] = { |
michael@0 | 2300 | HB_TAG('a','b','v','f'), |
michael@0 | 2301 | HB_TAG('a','b','v','s'), |
michael@0 | 2302 | HB_TAG('a','k','h','n'), |
michael@0 | 2303 | HB_TAG('b','l','w','f'), |
michael@0 | 2304 | HB_TAG('b','l','w','s'), |
michael@0 | 2305 | HB_TAG('c','a','l','t'), |
michael@0 | 2306 | HB_TAG('c','c','m','p'), |
michael@0 | 2307 | HB_TAG('c','f','a','r'), |
michael@0 | 2308 | HB_TAG('c','j','c','t'), |
michael@0 | 2309 | HB_TAG('c','l','i','g'), |
michael@0 | 2310 | HB_TAG('f','i','n','2'), |
michael@0 | 2311 | HB_TAG('f','i','n','3'), |
michael@0 | 2312 | HB_TAG('f','i','n','a'), |
michael@0 | 2313 | HB_TAG('h','a','l','f'), |
michael@0 | 2314 | HB_TAG('h','a','l','n'), |
michael@0 | 2315 | HB_TAG('i','n','i','t'), |
michael@0 | 2316 | HB_TAG('i','s','o','l'), |
michael@0 | 2317 | HB_TAG('l','i','g','a'), |
michael@0 | 2318 | HB_TAG('l','j','m','o'), |
michael@0 | 2319 | HB_TAG('l','o','c','l'), |
michael@0 | 2320 | HB_TAG('l','t','r','a'), |
michael@0 | 2321 | HB_TAG('l','t','r','m'), |
michael@0 | 2322 | HB_TAG('m','e','d','2'), |
michael@0 | 2323 | HB_TAG('m','e','d','i'), |
michael@0 | 2324 | HB_TAG('m','s','e','t'), |
michael@0 | 2325 | HB_TAG('n','u','k','t'), |
michael@0 | 2326 | HB_TAG('p','r','e','f'), |
michael@0 | 2327 | HB_TAG('p','r','e','s'), |
michael@0 | 2328 | HB_TAG('p','s','t','f'), |
michael@0 | 2329 | HB_TAG('p','s','t','s'), |
michael@0 | 2330 | HB_TAG('r','c','l','t'), |
michael@0 | 2331 | HB_TAG('r','l','i','g'), |
michael@0 | 2332 | HB_TAG('r','k','r','f'), |
michael@0 | 2333 | HB_TAG('r','p','h','f'), |
michael@0 | 2334 | HB_TAG('r','t','l','a'), |
michael@0 | 2335 | HB_TAG('r','t','l','m'), |
michael@0 | 2336 | HB_TAG('t','j','m','o'), |
michael@0 | 2337 | HB_TAG('v','a','t','u'), |
michael@0 | 2338 | HB_TAG('v','e','r','t'), |
michael@0 | 2339 | HB_TAG('v','j','m','o') |
michael@0 | 2340 | }; |
michael@0 | 2341 | |
michael@0 | 2342 | void |
michael@0 | 2343 | gfxFont::CheckForFeaturesInvolvingSpace() |
michael@0 | 2344 | { |
michael@0 | 2345 | mFontEntry->mHasSpaceFeaturesInitialized = true; |
michael@0 | 2346 | |
michael@0 | 2347 | #ifdef PR_LOGGING |
michael@0 | 2348 | bool log = LOG_FONTINIT_ENABLED(); |
michael@0 | 2349 | TimeStamp start; |
michael@0 | 2350 | if (MOZ_UNLIKELY(log)) { |
michael@0 | 2351 | start = TimeStamp::Now(); |
michael@0 | 2352 | } |
michael@0 | 2353 | #endif |
michael@0 | 2354 | |
michael@0 | 2355 | bool result = false; |
michael@0 | 2356 | |
michael@0 | 2357 | uint32_t spaceGlyph = GetSpaceGlyph(); |
michael@0 | 2358 | if (!spaceGlyph) { |
michael@0 | 2359 | return; |
michael@0 | 2360 | } |
michael@0 | 2361 | |
michael@0 | 2362 | hb_face_t *face = GetFontEntry()->GetHBFace(); |
michael@0 | 2363 | |
michael@0 | 2364 | // GSUB lookups - examine per script |
michael@0 | 2365 | if (hb_ot_layout_has_substitution(face)) { |
michael@0 | 2366 | |
michael@0 | 2367 | // set up the script ==> code hashtable if needed |
michael@0 | 2368 | if (!sScriptTagToCode) { |
michael@0 | 2369 | sScriptTagToCode = |
michael@0 | 2370 | new nsDataHashtable<nsUint32HashKey, |
michael@0 | 2371 | int32_t>(MOZ_NUM_SCRIPT_CODES); |
michael@0 | 2372 | sScriptTagToCode->Put(HB_TAG('D','F','L','T'), MOZ_SCRIPT_COMMON); |
michael@0 | 2373 | for (int32_t s = MOZ_SCRIPT_ARABIC; s < MOZ_NUM_SCRIPT_CODES; s++) { |
michael@0 | 2374 | hb_script_t scriptTag = hb_script_t(GetScriptTagForCode(s)); |
michael@0 | 2375 | hb_tag_t s1, s2; |
michael@0 | 2376 | hb_ot_tags_from_script(scriptTag, &s1, &s2); |
michael@0 | 2377 | sScriptTagToCode->Put(s1, s); |
michael@0 | 2378 | if (s2 != HB_OT_TAG_DEFAULT_SCRIPT) { |
michael@0 | 2379 | sScriptTagToCode->Put(s2, s); |
michael@0 | 2380 | } |
michael@0 | 2381 | } |
michael@0 | 2382 | |
michael@0 | 2383 | uint32_t numDefaultFeatures = ArrayLength(defaultFeatures); |
michael@0 | 2384 | sDefaultFeatures = |
michael@0 | 2385 | new nsTHashtable<nsUint32HashKey>(numDefaultFeatures); |
michael@0 | 2386 | for (uint32_t i = 0; i < numDefaultFeatures; i++) { |
michael@0 | 2387 | sDefaultFeatures->PutEntry(defaultFeatures[i]); |
michael@0 | 2388 | } |
michael@0 | 2389 | } |
michael@0 | 2390 | |
michael@0 | 2391 | // iterate over the scripts in the font |
michael@0 | 2392 | hb_tag_t scriptTags[8]; |
michael@0 | 2393 | |
michael@0 | 2394 | uint32_t len, offset = 0; |
michael@0 | 2395 | do { |
michael@0 | 2396 | len = ArrayLength(scriptTags); |
michael@0 | 2397 | hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, |
michael@0 | 2398 | &len, scriptTags); |
michael@0 | 2399 | for (uint32_t i = 0; i < len; i++) { |
michael@0 | 2400 | bool isDefaultFeature = false; |
michael@0 | 2401 | int32_t s; |
michael@0 | 2402 | if (!HasLookupRuleWithGlyphByScript(face, HB_OT_TAG_GSUB, |
michael@0 | 2403 | scriptTags[i], offset + i, |
michael@0 | 2404 | spaceGlyph, |
michael@0 | 2405 | *sDefaultFeatures, |
michael@0 | 2406 | isDefaultFeature) || |
michael@0 | 2407 | !sScriptTagToCode->Get(scriptTags[i], &s)) |
michael@0 | 2408 | { |
michael@0 | 2409 | continue; |
michael@0 | 2410 | } |
michael@0 | 2411 | result = true; |
michael@0 | 2412 | uint32_t index = s >> 5; |
michael@0 | 2413 | uint32_t bit = s & 0x1f; |
michael@0 | 2414 | if (isDefaultFeature) { |
michael@0 | 2415 | mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit); |
michael@0 | 2416 | } else { |
michael@0 | 2417 | mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit); |
michael@0 | 2418 | } |
michael@0 | 2419 | } |
michael@0 | 2420 | offset += len; |
michael@0 | 2421 | } while (len == ArrayLength(scriptTags)); |
michael@0 | 2422 | } |
michael@0 | 2423 | |
michael@0 | 2424 | // spaces in default features of default script? |
michael@0 | 2425 | // ==> can't use word cache, skip GPOS analysis |
michael@0 | 2426 | bool canUseWordCache = true; |
michael@0 | 2427 | if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, |
michael@0 | 2428 | MOZ_SCRIPT_COMMON)) { |
michael@0 | 2429 | canUseWordCache = false; |
michael@0 | 2430 | } |
michael@0 | 2431 | |
michael@0 | 2432 | // GPOS lookups - distinguish kerning from non-kerning features |
michael@0 | 2433 | mFontEntry->mHasSpaceFeaturesKerning = false; |
michael@0 | 2434 | mFontEntry->mHasSpaceFeaturesNonKerning = false; |
michael@0 | 2435 | |
michael@0 | 2436 | if (canUseWordCache && hb_ot_layout_has_positioning(face)) { |
michael@0 | 2437 | bool hasKerning = false, hasNonKerning = false; |
michael@0 | 2438 | HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning, |
michael@0 | 2439 | HB_TAG('k','e','r','n'), hasKerning, spaceGlyph); |
michael@0 | 2440 | if (hasKerning || hasNonKerning) { |
michael@0 | 2441 | result = true; |
michael@0 | 2442 | } |
michael@0 | 2443 | mFontEntry->mHasSpaceFeaturesKerning = hasKerning; |
michael@0 | 2444 | mFontEntry->mHasSpaceFeaturesNonKerning = hasNonKerning; |
michael@0 | 2445 | } |
michael@0 | 2446 | |
michael@0 | 2447 | hb_face_destroy(face); |
michael@0 | 2448 | mFontEntry->mHasSpaceFeatures = result; |
michael@0 | 2449 | |
michael@0 | 2450 | #ifdef PR_LOGGING |
michael@0 | 2451 | if (MOZ_UNLIKELY(log)) { |
michael@0 | 2452 | TimeDuration elapsed = TimeStamp::Now() - start; |
michael@0 | 2453 | LOG_FONTINIT(( |
michael@0 | 2454 | "(fontinit-spacelookups) font: %s - " |
michael@0 | 2455 | "subst default: %8.8x %8.8x %8.8x %8.8x " |
michael@0 | 2456 | "subst non-default: %8.8x %8.8x %8.8x %8.8x " |
michael@0 | 2457 | "kerning: %s non-kerning: %s time: %6.3f\n", |
michael@0 | 2458 | NS_ConvertUTF16toUTF8(mFontEntry->Name()).get(), |
michael@0 | 2459 | mFontEntry->mDefaultSubSpaceFeatures[3], |
michael@0 | 2460 | mFontEntry->mDefaultSubSpaceFeatures[2], |
michael@0 | 2461 | mFontEntry->mDefaultSubSpaceFeatures[1], |
michael@0 | 2462 | mFontEntry->mDefaultSubSpaceFeatures[0], |
michael@0 | 2463 | mFontEntry->mNonDefaultSubSpaceFeatures[3], |
michael@0 | 2464 | mFontEntry->mNonDefaultSubSpaceFeatures[2], |
michael@0 | 2465 | mFontEntry->mNonDefaultSubSpaceFeatures[1], |
michael@0 | 2466 | mFontEntry->mNonDefaultSubSpaceFeatures[0], |
michael@0 | 2467 | (mFontEntry->mHasSpaceFeaturesKerning ? "true" : "false"), |
michael@0 | 2468 | (mFontEntry->mHasSpaceFeaturesNonKerning ? "true" : "false"), |
michael@0 | 2469 | elapsed.ToMilliseconds() |
michael@0 | 2470 | )); |
michael@0 | 2471 | } |
michael@0 | 2472 | #endif |
michael@0 | 2473 | } |
michael@0 | 2474 | |
michael@0 | 2475 | bool |
michael@0 | 2476 | gfxFont::HasSubstitutionRulesWithSpaceLookups(int32_t aRunScript) |
michael@0 | 2477 | { |
michael@0 | 2478 | NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized, |
michael@0 | 2479 | "need to initialize space lookup flags"); |
michael@0 | 2480 | NS_ASSERTION(aRunScript < MOZ_NUM_SCRIPT_CODES, "weird script code"); |
michael@0 | 2481 | if (aRunScript == MOZ_SCRIPT_INVALID || |
michael@0 | 2482 | aRunScript >= MOZ_NUM_SCRIPT_CODES) { |
michael@0 | 2483 | return false; |
michael@0 | 2484 | } |
michael@0 | 2485 | |
michael@0 | 2486 | // default features have space lookups ==> true |
michael@0 | 2487 | if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, |
michael@0 | 2488 | MOZ_SCRIPT_COMMON) || |
michael@0 | 2489 | HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, |
michael@0 | 2490 | aRunScript)) |
michael@0 | 2491 | { |
michael@0 | 2492 | return true; |
michael@0 | 2493 | } |
michael@0 | 2494 | |
michael@0 | 2495 | // non-default features have space lookups and some type of |
michael@0 | 2496 | // font feature, in font or style is specified ==> true |
michael@0 | 2497 | if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, |
michael@0 | 2498 | MOZ_SCRIPT_COMMON) || |
michael@0 | 2499 | HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, |
michael@0 | 2500 | aRunScript)) && |
michael@0 | 2501 | (!mStyle.featureSettings.IsEmpty() || |
michael@0 | 2502 | !mFontEntry->mFeatureSettings.IsEmpty())) |
michael@0 | 2503 | { |
michael@0 | 2504 | return true; |
michael@0 | 2505 | } |
michael@0 | 2506 | |
michael@0 | 2507 | return false; |
michael@0 | 2508 | } |
michael@0 | 2509 | |
michael@0 | 2510 | bool |
michael@0 | 2511 | gfxFont::SpaceMayParticipateInShaping(int32_t aRunScript) |
michael@0 | 2512 | { |
michael@0 | 2513 | // avoid checking fonts known not to include default space-dependent features |
michael@0 | 2514 | if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) { |
michael@0 | 2515 | if (!mKerningSet && mStyle.featureSettings.IsEmpty() && |
michael@0 | 2516 | mFontEntry->mFeatureSettings.IsEmpty()) { |
michael@0 | 2517 | return false; |
michael@0 | 2518 | } |
michael@0 | 2519 | } |
michael@0 | 2520 | |
michael@0 | 2521 | // We record the presence of space-dependent features in the font entry |
michael@0 | 2522 | // so that subsequent instantiations for the same font face won't |
michael@0 | 2523 | // require us to re-check the tables; however, the actual check is done |
michael@0 | 2524 | // by gfxFont because not all font entry subclasses know how to create |
michael@0 | 2525 | // a harfbuzz face for introspection. |
michael@0 | 2526 | if (!mFontEntry->mHasSpaceFeaturesInitialized) { |
michael@0 | 2527 | CheckForFeaturesInvolvingSpace(); |
michael@0 | 2528 | } |
michael@0 | 2529 | |
michael@0 | 2530 | if (!mFontEntry->mHasSpaceFeatures) { |
michael@0 | 2531 | return false; |
michael@0 | 2532 | } |
michael@0 | 2533 | |
michael@0 | 2534 | // if font has substitution rules or non-kerning positioning rules |
michael@0 | 2535 | // that involve spaces, bypass |
michael@0 | 2536 | if (HasSubstitutionRulesWithSpaceLookups(aRunScript) || |
michael@0 | 2537 | mFontEntry->mHasSpaceFeaturesNonKerning) { |
michael@0 | 2538 | return true; |
michael@0 | 2539 | } |
michael@0 | 2540 | |
michael@0 | 2541 | // if kerning explicitly enabled/disabled via font-feature-settings or |
michael@0 | 2542 | // font-kerning and kerning rules use spaces, only bypass when enabled |
michael@0 | 2543 | if (mKerningSet && mFontEntry->mHasSpaceFeaturesKerning) { |
michael@0 | 2544 | return mKerningEnabled; |
michael@0 | 2545 | } |
michael@0 | 2546 | |
michael@0 | 2547 | return false; |
michael@0 | 2548 | } |
michael@0 | 2549 | |
michael@0 | 2550 | bool |
michael@0 | 2551 | gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) |
michael@0 | 2552 | { |
michael@0 | 2553 | aFeatureOn = false; |
michael@0 | 2554 | |
michael@0 | 2555 | if (mStyle.featureSettings.IsEmpty() && |
michael@0 | 2556 | GetFontEntry()->mFeatureSettings.IsEmpty()) { |
michael@0 | 2557 | return false; |
michael@0 | 2558 | } |
michael@0 | 2559 | |
michael@0 | 2560 | // add feature values from font |
michael@0 | 2561 | bool featureSet = false; |
michael@0 | 2562 | uint32_t i, count; |
michael@0 | 2563 | |
michael@0 | 2564 | nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings; |
michael@0 | 2565 | count = fontFeatures.Length(); |
michael@0 | 2566 | for (i = 0; i < count; i++) { |
michael@0 | 2567 | const gfxFontFeature& feature = fontFeatures.ElementAt(i); |
michael@0 | 2568 | if (feature.mTag == aFeature) { |
michael@0 | 2569 | featureSet = true; |
michael@0 | 2570 | aFeatureOn = (feature.mValue != 0); |
michael@0 | 2571 | } |
michael@0 | 2572 | } |
michael@0 | 2573 | |
michael@0 | 2574 | // add feature values from style rules |
michael@0 | 2575 | nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings; |
michael@0 | 2576 | count = styleFeatures.Length(); |
michael@0 | 2577 | for (i = 0; i < count; i++) { |
michael@0 | 2578 | const gfxFontFeature& feature = styleFeatures.ElementAt(i); |
michael@0 | 2579 | if (feature.mTag == aFeature) { |
michael@0 | 2580 | featureSet = true; |
michael@0 | 2581 | aFeatureOn = (feature.mValue != 0); |
michael@0 | 2582 | } |
michael@0 | 2583 | } |
michael@0 | 2584 | |
michael@0 | 2585 | return featureSet; |
michael@0 | 2586 | } |
michael@0 | 2587 | |
michael@0 | 2588 | /** |
michael@0 | 2589 | * A helper function in case we need to do any rounding or other |
michael@0 | 2590 | * processing here. |
michael@0 | 2591 | */ |
michael@0 | 2592 | #define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \ |
michael@0 | 2593 | (double(aAppUnits)*double(aDevUnitsPerAppUnit)) |
michael@0 | 2594 | |
michael@0 | 2595 | struct GlyphBuffer { |
michael@0 | 2596 | #define GLYPH_BUFFER_SIZE (2048/sizeof(cairo_glyph_t)) |
michael@0 | 2597 | cairo_glyph_t mGlyphBuffer[GLYPH_BUFFER_SIZE]; |
michael@0 | 2598 | unsigned int mNumGlyphs; |
michael@0 | 2599 | |
michael@0 | 2600 | GlyphBuffer() |
michael@0 | 2601 | : mNumGlyphs(0) { } |
michael@0 | 2602 | |
michael@0 | 2603 | cairo_glyph_t *AppendGlyph() { |
michael@0 | 2604 | return &mGlyphBuffer[mNumGlyphs++]; |
michael@0 | 2605 | } |
michael@0 | 2606 | |
michael@0 | 2607 | void Flush(cairo_t *aCR, DrawMode aDrawMode, bool aReverse, |
michael@0 | 2608 | gfxTextContextPaint *aContextPaint, |
michael@0 | 2609 | const gfxMatrix& aGlobalMatrix, bool aFinish = false) { |
michael@0 | 2610 | // Ensure there's enough room for a glyph to be added to the buffer |
michael@0 | 2611 | // and we actually have glyphs to draw |
michael@0 | 2612 | if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) { |
michael@0 | 2613 | return; |
michael@0 | 2614 | } |
michael@0 | 2615 | |
michael@0 | 2616 | if (aReverse) { |
michael@0 | 2617 | for (uint32_t i = 0; i < mNumGlyphs/2; ++i) { |
michael@0 | 2618 | cairo_glyph_t tmp = mGlyphBuffer[i]; |
michael@0 | 2619 | mGlyphBuffer[i] = mGlyphBuffer[mNumGlyphs - 1 - i]; |
michael@0 | 2620 | mGlyphBuffer[mNumGlyphs - 1 - i] = tmp; |
michael@0 | 2621 | } |
michael@0 | 2622 | } |
michael@0 | 2623 | |
michael@0 | 2624 | if (aDrawMode == DrawMode::GLYPH_PATH) { |
michael@0 | 2625 | cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs); |
michael@0 | 2626 | } else { |
michael@0 | 2627 | if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) == |
michael@0 | 2628 | (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) { |
michael@0 | 2629 | FlushStroke(aCR, aContextPaint, aGlobalMatrix); |
michael@0 | 2630 | } |
michael@0 | 2631 | if (int(aDrawMode) & int(DrawMode::GLYPH_FILL)) { |
michael@0 | 2632 | PROFILER_LABEL("GlyphBuffer", "cairo_show_glyphs"); |
michael@0 | 2633 | nsRefPtr<gfxPattern> pattern; |
michael@0 | 2634 | if (aContextPaint && |
michael@0 | 2635 | !!(pattern = aContextPaint->GetFillPattern(aGlobalMatrix))) { |
michael@0 | 2636 | cairo_save(aCR); |
michael@0 | 2637 | cairo_set_source(aCR, pattern->CairoPattern()); |
michael@0 | 2638 | } |
michael@0 | 2639 | |
michael@0 | 2640 | cairo_show_glyphs(aCR, mGlyphBuffer, mNumGlyphs); |
michael@0 | 2641 | |
michael@0 | 2642 | if (pattern) { |
michael@0 | 2643 | cairo_restore(aCR); |
michael@0 | 2644 | } |
michael@0 | 2645 | } |
michael@0 | 2646 | if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) == |
michael@0 | 2647 | int(DrawMode::GLYPH_STROKE)) { |
michael@0 | 2648 | FlushStroke(aCR, aContextPaint, aGlobalMatrix); |
michael@0 | 2649 | } |
michael@0 | 2650 | } |
michael@0 | 2651 | |
michael@0 | 2652 | mNumGlyphs = 0; |
michael@0 | 2653 | } |
michael@0 | 2654 | |
michael@0 | 2655 | private: |
michael@0 | 2656 | void FlushStroke(cairo_t *aCR, gfxTextContextPaint *aContextPaint, |
michael@0 | 2657 | const gfxMatrix& aGlobalMatrix) { |
michael@0 | 2658 | nsRefPtr<gfxPattern> pattern; |
michael@0 | 2659 | if (aContextPaint && |
michael@0 | 2660 | !!(pattern = aContextPaint->GetStrokePattern(aGlobalMatrix))) { |
michael@0 | 2661 | cairo_save(aCR); |
michael@0 | 2662 | cairo_set_source(aCR, pattern->CairoPattern()); |
michael@0 | 2663 | } |
michael@0 | 2664 | |
michael@0 | 2665 | cairo_new_path(aCR); |
michael@0 | 2666 | cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs); |
michael@0 | 2667 | cairo_stroke(aCR); |
michael@0 | 2668 | |
michael@0 | 2669 | if (pattern) { |
michael@0 | 2670 | cairo_restore(aCR); |
michael@0 | 2671 | } |
michael@0 | 2672 | } |
michael@0 | 2673 | |
michael@0 | 2674 | #undef GLYPH_BUFFER_SIZE |
michael@0 | 2675 | }; |
michael@0 | 2676 | |
michael@0 | 2677 | static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) { |
michael@0 | 2678 | switch (aAAOption) { |
michael@0 | 2679 | case gfxFont::kAntialiasSubpixel: |
michael@0 | 2680 | return AntialiasMode::SUBPIXEL; |
michael@0 | 2681 | case gfxFont::kAntialiasGrayscale: |
michael@0 | 2682 | return AntialiasMode::GRAY; |
michael@0 | 2683 | case gfxFont::kAntialiasNone: |
michael@0 | 2684 | return AntialiasMode::NONE; |
michael@0 | 2685 | default: |
michael@0 | 2686 | return AntialiasMode::DEFAULT; |
michael@0 | 2687 | } |
michael@0 | 2688 | } |
michael@0 | 2689 | |
michael@0 | 2690 | struct GlyphBufferAzure { |
michael@0 | 2691 | #define GLYPH_BUFFER_SIZE (2048/sizeof(Glyph)) |
michael@0 | 2692 | Glyph mGlyphBuffer[GLYPH_BUFFER_SIZE]; |
michael@0 | 2693 | unsigned int mNumGlyphs; |
michael@0 | 2694 | |
michael@0 | 2695 | GlyphBufferAzure() |
michael@0 | 2696 | : mNumGlyphs(0) { } |
michael@0 | 2697 | |
michael@0 | 2698 | Glyph *AppendGlyph() { |
michael@0 | 2699 | return &mGlyphBuffer[mNumGlyphs++]; |
michael@0 | 2700 | } |
michael@0 | 2701 | |
michael@0 | 2702 | void Flush(DrawTarget *aDT, gfxTextContextPaint *aContextPaint, ScaledFont *aFont, |
michael@0 | 2703 | DrawMode aDrawMode, bool aReverse, const GlyphRenderingOptions *aOptions, |
michael@0 | 2704 | gfxContext *aThebesContext, const Matrix *aInvFontMatrix, const DrawOptions &aDrawOptions, |
michael@0 | 2705 | bool aFinish = false) |
michael@0 | 2706 | { |
michael@0 | 2707 | // Ensure there's enough room for a glyph to be added to the buffer |
michael@0 | 2708 | if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) { |
michael@0 | 2709 | return; |
michael@0 | 2710 | } |
michael@0 | 2711 | |
michael@0 | 2712 | if (aReverse) { |
michael@0 | 2713 | Glyph *begin = &mGlyphBuffer[0]; |
michael@0 | 2714 | Glyph *end = &mGlyphBuffer[mNumGlyphs]; |
michael@0 | 2715 | std::reverse(begin, end); |
michael@0 | 2716 | } |
michael@0 | 2717 | |
michael@0 | 2718 | gfx::GlyphBuffer buf; |
michael@0 | 2719 | buf.mGlyphs = mGlyphBuffer; |
michael@0 | 2720 | buf.mNumGlyphs = mNumGlyphs; |
michael@0 | 2721 | |
michael@0 | 2722 | gfxContext::AzureState state = aThebesContext->CurrentState(); |
michael@0 | 2723 | if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) == |
michael@0 | 2724 | (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) { |
michael@0 | 2725 | FlushStroke(aDT, aContextPaint, aFont, aThebesContext, buf, state); |
michael@0 | 2726 | } |
michael@0 | 2727 | if (int(aDrawMode) & int(DrawMode::GLYPH_FILL)) { |
michael@0 | 2728 | if (state.pattern || aContextPaint) { |
michael@0 | 2729 | Pattern *pat; |
michael@0 | 2730 | |
michael@0 | 2731 | nsRefPtr<gfxPattern> fillPattern; |
michael@0 | 2732 | if (!aContextPaint || |
michael@0 | 2733 | !(fillPattern = aContextPaint->GetFillPattern(aThebesContext->CurrentMatrix()))) { |
michael@0 | 2734 | if (state.pattern) { |
michael@0 | 2735 | pat = state.pattern->GetPattern(aDT, state.patternTransformChanged ? &state.patternTransform : nullptr); |
michael@0 | 2736 | } else { |
michael@0 | 2737 | pat = nullptr; |
michael@0 | 2738 | } |
michael@0 | 2739 | } else { |
michael@0 | 2740 | pat = fillPattern->GetPattern(aDT); |
michael@0 | 2741 | } |
michael@0 | 2742 | |
michael@0 | 2743 | if (pat) { |
michael@0 | 2744 | Matrix saved; |
michael@0 | 2745 | Matrix *mat = nullptr; |
michael@0 | 2746 | if (aInvFontMatrix) { |
michael@0 | 2747 | // The brush matrix needs to be multiplied with the inverted matrix |
michael@0 | 2748 | // as well, to move the brush into the space of the glyphs. Before |
michael@0 | 2749 | // the render target transformation |
michael@0 | 2750 | |
michael@0 | 2751 | // This relies on the returned Pattern not to be reused by |
michael@0 | 2752 | // others, but regenerated on GetPattern calls. This is true! |
michael@0 | 2753 | if (pat->GetType() == PatternType::LINEAR_GRADIENT) { |
michael@0 | 2754 | mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix; |
michael@0 | 2755 | } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) { |
michael@0 | 2756 | mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix; |
michael@0 | 2757 | } else if (pat->GetType() == PatternType::SURFACE) { |
michael@0 | 2758 | mat = &static_cast<SurfacePattern*>(pat)->mMatrix; |
michael@0 | 2759 | } |
michael@0 | 2760 | |
michael@0 | 2761 | if (mat) { |
michael@0 | 2762 | saved = *mat; |
michael@0 | 2763 | *mat = (*mat) * (*aInvFontMatrix); |
michael@0 | 2764 | } |
michael@0 | 2765 | } |
michael@0 | 2766 | |
michael@0 | 2767 | aDT->FillGlyphs(aFont, buf, *pat, |
michael@0 | 2768 | aDrawOptions, aOptions); |
michael@0 | 2769 | |
michael@0 | 2770 | if (mat) { |
michael@0 | 2771 | *mat = saved; |
michael@0 | 2772 | } |
michael@0 | 2773 | } |
michael@0 | 2774 | } else if (state.sourceSurface) { |
michael@0 | 2775 | aDT->FillGlyphs(aFont, buf, SurfacePattern(state.sourceSurface, |
michael@0 | 2776 | ExtendMode::CLAMP, |
michael@0 | 2777 | state.surfTransform), |
michael@0 | 2778 | aDrawOptions, aOptions); |
michael@0 | 2779 | } else { |
michael@0 | 2780 | aDT->FillGlyphs(aFont, buf, ColorPattern(state.color), |
michael@0 | 2781 | aDrawOptions, aOptions); |
michael@0 | 2782 | } |
michael@0 | 2783 | } |
michael@0 | 2784 | if (int(aDrawMode) & int(DrawMode::GLYPH_PATH)) { |
michael@0 | 2785 | aThebesContext->EnsurePathBuilder(); |
michael@0 | 2786 | Matrix mat = aDT->GetTransform(); |
michael@0 | 2787 | aFont->CopyGlyphsToBuilder(buf, aThebesContext->mPathBuilder, aDT->GetType(), &mat); |
michael@0 | 2788 | } |
michael@0 | 2789 | if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) == |
michael@0 | 2790 | int(DrawMode::GLYPH_STROKE)) { |
michael@0 | 2791 | FlushStroke(aDT, aContextPaint, aFont, aThebesContext, buf, state); |
michael@0 | 2792 | } |
michael@0 | 2793 | |
michael@0 | 2794 | mNumGlyphs = 0; |
michael@0 | 2795 | } |
michael@0 | 2796 | |
michael@0 | 2797 | private: |
michael@0 | 2798 | void FlushStroke(DrawTarget *aDT, gfxTextContextPaint *aContextPaint, |
michael@0 | 2799 | ScaledFont *aFont, gfxContext *aThebesContext, |
michael@0 | 2800 | gfx::GlyphBuffer& aBuf, gfxContext::AzureState& aState) |
michael@0 | 2801 | { |
michael@0 | 2802 | RefPtr<Path> path = aFont->GetPathForGlyphs(aBuf, aDT); |
michael@0 | 2803 | if (aContextPaint) { |
michael@0 | 2804 | nsRefPtr<gfxPattern> strokePattern = |
michael@0 | 2805 | aContextPaint->GetStrokePattern(aThebesContext->CurrentMatrix()); |
michael@0 | 2806 | if (strokePattern) { |
michael@0 | 2807 | aDT->Stroke(path, *strokePattern->GetPattern(aDT), aState.strokeOptions); |
michael@0 | 2808 | } |
michael@0 | 2809 | } |
michael@0 | 2810 | } |
michael@0 | 2811 | |
michael@0 | 2812 | #undef GLYPH_BUFFER_SIZE |
michael@0 | 2813 | }; |
michael@0 | 2814 | |
michael@0 | 2815 | // Bug 674909. When synthetic bolding text by drawing twice, need to |
michael@0 | 2816 | // render using a pixel offset in device pixels, otherwise text |
michael@0 | 2817 | // doesn't appear bolded, it appears as if a bad text shadow exists |
michael@0 | 2818 | // when a non-identity transform exists. Use an offset factor so that |
michael@0 | 2819 | // the second draw occurs at a constant offset in device pixels. |
michael@0 | 2820 | |
michael@0 | 2821 | double |
michael@0 | 2822 | gfxFont::CalcXScale(gfxContext *aContext) |
michael@0 | 2823 | { |
michael@0 | 2824 | // determine magnitude of a 1px x offset in device space |
michael@0 | 2825 | gfxSize t = aContext->UserToDevice(gfxSize(1.0, 0.0)); |
michael@0 | 2826 | if (t.width == 1.0 && t.height == 0.0) { |
michael@0 | 2827 | // short-circuit the most common case to avoid sqrt() and division |
michael@0 | 2828 | return 1.0; |
michael@0 | 2829 | } |
michael@0 | 2830 | |
michael@0 | 2831 | double m = sqrt(t.width * t.width + t.height * t.height); |
michael@0 | 2832 | |
michael@0 | 2833 | NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding"); |
michael@0 | 2834 | if (m == 0.0) { |
michael@0 | 2835 | return 0.0; // effectively disables offset |
michael@0 | 2836 | } |
michael@0 | 2837 | |
michael@0 | 2838 | // scale factor so that offsets are 1px in device pixels |
michael@0 | 2839 | return 1.0 / m; |
michael@0 | 2840 | } |
michael@0 | 2841 | |
michael@0 | 2842 | static DrawMode |
michael@0 | 2843 | ForcePaintingDrawMode(DrawMode aDrawMode) |
michael@0 | 2844 | { |
michael@0 | 2845 | return aDrawMode == DrawMode::GLYPH_PATH ? |
michael@0 | 2846 | DrawMode(int(DrawMode::GLYPH_FILL) | int(DrawMode::GLYPH_STROKE)) : |
michael@0 | 2847 | aDrawMode; |
michael@0 | 2848 | } |
michael@0 | 2849 | |
michael@0 | 2850 | void |
michael@0 | 2851 | gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd, |
michael@0 | 2852 | gfxContext *aContext, DrawMode aDrawMode, gfxPoint *aPt, |
michael@0 | 2853 | Spacing *aSpacing, gfxTextContextPaint *aContextPaint, |
michael@0 | 2854 | gfxTextRunDrawCallbacks *aCallbacks) |
michael@0 | 2855 | { |
michael@0 | 2856 | NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !(int(aDrawMode) & int(DrawMode::GLYPH_PATH)), |
michael@0 | 2857 | "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH"); |
michael@0 | 2858 | |
michael@0 | 2859 | if (aStart >= aEnd) |
michael@0 | 2860 | return; |
michael@0 | 2861 | |
michael@0 | 2862 | const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); |
michael@0 | 2863 | const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); |
michael@0 | 2864 | const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit); |
michael@0 | 2865 | bool isRTL = aTextRun->IsRightToLeft(); |
michael@0 | 2866 | double direction = aTextRun->GetDirection(); |
michael@0 | 2867 | gfxMatrix globalMatrix = aContext->CurrentMatrix(); |
michael@0 | 2868 | |
michael@0 | 2869 | bool haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this); |
michael@0 | 2870 | nsAutoPtr<gfxTextContextPaint> contextPaint; |
michael@0 | 2871 | if (haveSVGGlyphs && !aContextPaint) { |
michael@0 | 2872 | // If no pattern is specified for fill, use the current pattern |
michael@0 | 2873 | NS_ASSERTION((int(aDrawMode) & int(DrawMode::GLYPH_STROKE)) == 0, "no pattern supplied for stroking text"); |
michael@0 | 2874 | nsRefPtr<gfxPattern> fillPattern = aContext->GetPattern(); |
michael@0 | 2875 | contextPaint = new SimpleTextContextPaint(fillPattern, nullptr, |
michael@0 | 2876 | aContext->CurrentMatrix()); |
michael@0 | 2877 | aContextPaint = contextPaint; |
michael@0 | 2878 | } |
michael@0 | 2879 | |
michael@0 | 2880 | // synthetic-bold strikes are each offset one device pixel in run direction |
michael@0 | 2881 | // (these values are only needed if IsSyntheticBold() is true) |
michael@0 | 2882 | double synBoldOnePixelOffset = 0; |
michael@0 | 2883 | int32_t strikes = 1; |
michael@0 | 2884 | if (IsSyntheticBold()) { |
michael@0 | 2885 | double xscale = CalcXScale(aContext); |
michael@0 | 2886 | synBoldOnePixelOffset = direction * xscale; |
michael@0 | 2887 | if (xscale != 0.0) { |
michael@0 | 2888 | // use as many strikes as needed for the the increased advance |
michael@0 | 2889 | strikes = NS_lroundf(GetSyntheticBoldOffset() / xscale); |
michael@0 | 2890 | } |
michael@0 | 2891 | } |
michael@0 | 2892 | |
michael@0 | 2893 | uint32_t i; |
michael@0 | 2894 | // Current position in appunits |
michael@0 | 2895 | double x = aPt->x; |
michael@0 | 2896 | double y = aPt->y; |
michael@0 | 2897 | |
michael@0 | 2898 | cairo_t *cr = aContext->GetCairo(); |
michael@0 | 2899 | RefPtr<DrawTarget> dt = aContext->GetDrawTarget(); |
michael@0 | 2900 | |
michael@0 | 2901 | bool paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs; |
michael@0 | 2902 | bool emittedGlyphs = false; |
michael@0 | 2903 | |
michael@0 | 2904 | if (aContext->IsCairo()) { |
michael@0 | 2905 | bool success = SetupCairoFont(aContext); |
michael@0 | 2906 | if (MOZ_UNLIKELY(!success)) |
michael@0 | 2907 | return; |
michael@0 | 2908 | |
michael@0 | 2909 | ::GlyphBuffer glyphs; |
michael@0 | 2910 | cairo_glyph_t *glyph; |
michael@0 | 2911 | |
michael@0 | 2912 | if (aSpacing) { |
michael@0 | 2913 | x += direction*aSpacing[0].mBefore; |
michael@0 | 2914 | } |
michael@0 | 2915 | for (i = aStart; i < aEnd; ++i) { |
michael@0 | 2916 | const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i]; |
michael@0 | 2917 | if (glyphData->IsSimpleGlyph()) { |
michael@0 | 2918 | double advance = glyphData->GetSimpleAdvance(); |
michael@0 | 2919 | double glyphX; |
michael@0 | 2920 | if (isRTL) { |
michael@0 | 2921 | x -= advance; |
michael@0 | 2922 | glyphX = x; |
michael@0 | 2923 | } else { |
michael@0 | 2924 | glyphX = x; |
michael@0 | 2925 | x += advance; |
michael@0 | 2926 | } |
michael@0 | 2927 | |
michael@0 | 2928 | if (haveSVGGlyphs) { |
michael@0 | 2929 | if (!paintSVGGlyphs) { |
michael@0 | 2930 | continue; |
michael@0 | 2931 | } |
michael@0 | 2932 | gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit), |
michael@0 | 2933 | ToDeviceUnits(y, devUnitsPerAppUnit)); |
michael@0 | 2934 | DrawMode mode = ForcePaintingDrawMode(aDrawMode); |
michael@0 | 2935 | if (RenderSVGGlyph(aContext, point, mode, |
michael@0 | 2936 | glyphData->GetSimpleGlyph(), aContextPaint, |
michael@0 | 2937 | aCallbacks, emittedGlyphs)) { |
michael@0 | 2938 | continue; |
michael@0 | 2939 | } |
michael@0 | 2940 | } |
michael@0 | 2941 | |
michael@0 | 2942 | // Perhaps we should put a scale in the cairo context instead of |
michael@0 | 2943 | // doing this scaling here... |
michael@0 | 2944 | // Multiplying by the reciprocal may introduce tiny error here, |
michael@0 | 2945 | // but we assume cairo is going to round coordinates at some stage |
michael@0 | 2946 | // and this is faster |
michael@0 | 2947 | glyph = glyphs.AppendGlyph(); |
michael@0 | 2948 | glyph->index = glyphData->GetSimpleGlyph(); |
michael@0 | 2949 | glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit); |
michael@0 | 2950 | glyph->y = ToDeviceUnits(y, devUnitsPerAppUnit); |
michael@0 | 2951 | glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix); |
michael@0 | 2952 | |
michael@0 | 2953 | // synthetic bolding by multi-striking with 1-pixel offsets |
michael@0 | 2954 | // at least once, more if there's room (large font sizes) |
michael@0 | 2955 | if (IsSyntheticBold()) { |
michael@0 | 2956 | double strikeOffset = synBoldOnePixelOffset; |
michael@0 | 2957 | int32_t strikeCount = strikes; |
michael@0 | 2958 | do { |
michael@0 | 2959 | cairo_glyph_t *doubleglyph; |
michael@0 | 2960 | doubleglyph = glyphs.AppendGlyph(); |
michael@0 | 2961 | doubleglyph->index = glyph->index; |
michael@0 | 2962 | doubleglyph->x = |
michael@0 | 2963 | ToDeviceUnits(glyphX + strikeOffset * appUnitsPerDevUnit, |
michael@0 | 2964 | devUnitsPerAppUnit); |
michael@0 | 2965 | doubleglyph->y = glyph->y; |
michael@0 | 2966 | strikeOffset += synBoldOnePixelOffset; |
michael@0 | 2967 | glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix); |
michael@0 | 2968 | } while (--strikeCount > 0); |
michael@0 | 2969 | } |
michael@0 | 2970 | emittedGlyphs = true; |
michael@0 | 2971 | } else { |
michael@0 | 2972 | uint32_t glyphCount = glyphData->GetGlyphCount(); |
michael@0 | 2973 | if (glyphCount > 0) { |
michael@0 | 2974 | const gfxTextRun::DetailedGlyph *details = |
michael@0 | 2975 | aTextRun->GetDetailedGlyphs(i); |
michael@0 | 2976 | NS_ASSERTION(details, "detailedGlyph should not be missing!"); |
michael@0 | 2977 | double advance; |
michael@0 | 2978 | for (uint32_t j = 0; j < glyphCount; ++j, ++details, x += direction * advance) { |
michael@0 | 2979 | advance = details->mAdvance; |
michael@0 | 2980 | if (glyphData->IsMissing()) { |
michael@0 | 2981 | // default ignorable characters will have zero advance width. |
michael@0 | 2982 | // we don't have to draw the hexbox for them |
michael@0 | 2983 | if (aDrawMode != DrawMode::GLYPH_PATH && advance > 0) { |
michael@0 | 2984 | double glyphX = x; |
michael@0 | 2985 | if (isRTL) { |
michael@0 | 2986 | glyphX -= advance; |
michael@0 | 2987 | } |
michael@0 | 2988 | gfxPoint pt(ToDeviceUnits(glyphX, devUnitsPerAppUnit), |
michael@0 | 2989 | ToDeviceUnits(y, devUnitsPerAppUnit)); |
michael@0 | 2990 | gfxFloat advanceDevUnits = ToDeviceUnits(advance, devUnitsPerAppUnit); |
michael@0 | 2991 | gfxFloat height = GetMetrics().maxAscent; |
michael@0 | 2992 | gfxRect glyphRect(pt.x, pt.y - height, advanceDevUnits, height); |
michael@0 | 2993 | gfxFontMissingGlyphs::DrawMissingGlyph(aContext, |
michael@0 | 2994 | glyphRect, |
michael@0 | 2995 | details->mGlyphID, |
michael@0 | 2996 | appUnitsPerDevUnit); |
michael@0 | 2997 | } |
michael@0 | 2998 | } else { |
michael@0 | 2999 | double glyphX = x + details->mXOffset; |
michael@0 | 3000 | if (isRTL) { |
michael@0 | 3001 | glyphX -= advance; |
michael@0 | 3002 | } |
michael@0 | 3003 | |
michael@0 | 3004 | gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit), |
michael@0 | 3005 | ToDeviceUnits(y, devUnitsPerAppUnit)); |
michael@0 | 3006 | |
michael@0 | 3007 | if (haveSVGGlyphs) { |
michael@0 | 3008 | if (!paintSVGGlyphs) { |
michael@0 | 3009 | continue; |
michael@0 | 3010 | } |
michael@0 | 3011 | DrawMode mode = ForcePaintingDrawMode(aDrawMode); |
michael@0 | 3012 | if (RenderSVGGlyph(aContext, point, mode, |
michael@0 | 3013 | details->mGlyphID, |
michael@0 | 3014 | aContextPaint, aCallbacks, |
michael@0 | 3015 | emittedGlyphs)) { |
michael@0 | 3016 | continue; |
michael@0 | 3017 | } |
michael@0 | 3018 | } |
michael@0 | 3019 | |
michael@0 | 3020 | glyph = glyphs.AppendGlyph(); |
michael@0 | 3021 | glyph->index = details->mGlyphID; |
michael@0 | 3022 | glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit); |
michael@0 | 3023 | glyph->y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit); |
michael@0 | 3024 | glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix); |
michael@0 | 3025 | |
michael@0 | 3026 | if (IsSyntheticBold()) { |
michael@0 | 3027 | double strikeOffset = synBoldOnePixelOffset; |
michael@0 | 3028 | int32_t strikeCount = strikes; |
michael@0 | 3029 | do { |
michael@0 | 3030 | cairo_glyph_t *doubleglyph; |
michael@0 | 3031 | doubleglyph = glyphs.AppendGlyph(); |
michael@0 | 3032 | doubleglyph->index = glyph->index; |
michael@0 | 3033 | doubleglyph->x = |
michael@0 | 3034 | ToDeviceUnits(glyphX + strikeOffset * |
michael@0 | 3035 | appUnitsPerDevUnit, |
michael@0 | 3036 | devUnitsPerAppUnit); |
michael@0 | 3037 | doubleglyph->y = glyph->y; |
michael@0 | 3038 | strikeOffset += synBoldOnePixelOffset; |
michael@0 | 3039 | glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix); |
michael@0 | 3040 | } while (--strikeCount > 0); |
michael@0 | 3041 | } |
michael@0 | 3042 | emittedGlyphs = true; |
michael@0 | 3043 | } |
michael@0 | 3044 | } |
michael@0 | 3045 | } |
michael@0 | 3046 | } |
michael@0 | 3047 | |
michael@0 | 3048 | if (aSpacing) { |
michael@0 | 3049 | double space = aSpacing[i - aStart].mAfter; |
michael@0 | 3050 | if (i + 1 < aEnd) { |
michael@0 | 3051 | space += aSpacing[i + 1 - aStart].mBefore; |
michael@0 | 3052 | } |
michael@0 | 3053 | x += direction*space; |
michael@0 | 3054 | } |
michael@0 | 3055 | } |
michael@0 | 3056 | |
michael@0 | 3057 | if (gfxFontTestStore::CurrentStore()) { |
michael@0 | 3058 | /* This assumes that the tests won't have anything that results |
michael@0 | 3059 | * in more than GLYPH_BUFFER_SIZE glyphs. Do this before we |
michael@0 | 3060 | * flush, since that'll blow away the num_glyphs. |
michael@0 | 3061 | */ |
michael@0 | 3062 | gfxFontTestStore::CurrentStore()->AddItem(GetName(), |
michael@0 | 3063 | glyphs.mGlyphBuffer, |
michael@0 | 3064 | glyphs.mNumGlyphs); |
michael@0 | 3065 | } |
michael@0 | 3066 | |
michael@0 | 3067 | // draw any remaining glyphs |
michael@0 | 3068 | glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix, true); |
michael@0 | 3069 | if (aCallbacks && emittedGlyphs) { |
michael@0 | 3070 | aCallbacks->NotifyGlyphPathEmitted(); |
michael@0 | 3071 | } |
michael@0 | 3072 | |
michael@0 | 3073 | } else { |
michael@0 | 3074 | RefPtr<ScaledFont> scaledFont = GetScaledFont(dt); |
michael@0 | 3075 | |
michael@0 | 3076 | if (!scaledFont) { |
michael@0 | 3077 | return; |
michael@0 | 3078 | } |
michael@0 | 3079 | |
michael@0 | 3080 | bool oldSubpixelAA = dt->GetPermitSubpixelAA(); |
michael@0 | 3081 | |
michael@0 | 3082 | if (!AllowSubpixelAA()) { |
michael@0 | 3083 | dt->SetPermitSubpixelAA(false); |
michael@0 | 3084 | } |
michael@0 | 3085 | |
michael@0 | 3086 | GlyphBufferAzure glyphs; |
michael@0 | 3087 | Glyph *glyph; |
michael@0 | 3088 | |
michael@0 | 3089 | Matrix mat, matInv; |
michael@0 | 3090 | Matrix oldMat = dt->GetTransform(); |
michael@0 | 3091 | |
michael@0 | 3092 | // This is nullptr when we have inverse-transformed glyphs and we need |
michael@0 | 3093 | // to transform the Brush inside flush. |
michael@0 | 3094 | Matrix *passedInvMatrix = nullptr; |
michael@0 | 3095 | |
michael@0 | 3096 | RefPtr<GlyphRenderingOptions> renderingOptions = |
michael@0 | 3097 | GetGlyphRenderingOptions(); |
michael@0 | 3098 | |
michael@0 | 3099 | DrawOptions drawOptions; |
michael@0 | 3100 | drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption); |
michael@0 | 3101 | |
michael@0 | 3102 | // The cairo DrawTarget backend uses the cairo_scaled_font directly |
michael@0 | 3103 | // and so has the font skew matrix applied already. |
michael@0 | 3104 | if (mScaledFont && |
michael@0 | 3105 | dt->GetType() != BackendType::CAIRO) { |
michael@0 | 3106 | cairo_matrix_t matrix; |
michael@0 | 3107 | cairo_scaled_font_get_font_matrix(mScaledFont, &matrix); |
michael@0 | 3108 | if (matrix.xy != 0) { |
michael@0 | 3109 | // If this matrix applies a skew, which can happen when drawing |
michael@0 | 3110 | // oblique fonts, we will set the DrawTarget matrix to apply the |
michael@0 | 3111 | // skew. We'll need to move the glyphs by the inverse of the skew to |
michael@0 | 3112 | // get the glyphs positioned correctly in the new device space |
michael@0 | 3113 | // though, since the font matrix should only be applied to drawing |
michael@0 | 3114 | // the glyphs, and not to their position. |
michael@0 | 3115 | mat = ToMatrix(*reinterpret_cast<gfxMatrix*>(&matrix)); |
michael@0 | 3116 | |
michael@0 | 3117 | mat._11 = mat._22 = 1.0; |
michael@0 | 3118 | float adjustedSize = mAdjustedSize > 0 ? mAdjustedSize : GetStyle()->size; |
michael@0 | 3119 | mat._21 /= adjustedSize; |
michael@0 | 3120 | |
michael@0 | 3121 | dt->SetTransform(mat * oldMat); |
michael@0 | 3122 | |
michael@0 | 3123 | matInv = mat; |
michael@0 | 3124 | matInv.Invert(); |
michael@0 | 3125 | |
michael@0 | 3126 | passedInvMatrix = &matInv; |
michael@0 | 3127 | } |
michael@0 | 3128 | } |
michael@0 | 3129 | |
michael@0 | 3130 | if (aSpacing) { |
michael@0 | 3131 | x += direction*aSpacing[0].mBefore; |
michael@0 | 3132 | } |
michael@0 | 3133 | for (i = aStart; i < aEnd; ++i) { |
michael@0 | 3134 | const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i]; |
michael@0 | 3135 | if (glyphData->IsSimpleGlyph()) { |
michael@0 | 3136 | double advance = glyphData->GetSimpleAdvance(); |
michael@0 | 3137 | double glyphX; |
michael@0 | 3138 | if (isRTL) { |
michael@0 | 3139 | x -= advance; |
michael@0 | 3140 | glyphX = x; |
michael@0 | 3141 | } else { |
michael@0 | 3142 | glyphX = x; |
michael@0 | 3143 | x += advance; |
michael@0 | 3144 | } |
michael@0 | 3145 | |
michael@0 | 3146 | if (haveSVGGlyphs) { |
michael@0 | 3147 | if (!paintSVGGlyphs) { |
michael@0 | 3148 | continue; |
michael@0 | 3149 | } |
michael@0 | 3150 | gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit), |
michael@0 | 3151 | ToDeviceUnits(y, devUnitsPerAppUnit)); |
michael@0 | 3152 | DrawMode mode = ForcePaintingDrawMode(aDrawMode); |
michael@0 | 3153 | if (RenderSVGGlyph(aContext, point, mode, |
michael@0 | 3154 | glyphData->GetSimpleGlyph(), aContextPaint, |
michael@0 | 3155 | aCallbacks, emittedGlyphs)) { |
michael@0 | 3156 | continue; |
michael@0 | 3157 | } |
michael@0 | 3158 | } |
michael@0 | 3159 | |
michael@0 | 3160 | // Perhaps we should put a scale in the cairo context instead of |
michael@0 | 3161 | // doing this scaling here... |
michael@0 | 3162 | // Multiplying by the reciprocal may introduce tiny error here, |
michael@0 | 3163 | // but we assume cairo is going to round coordinates at some stage |
michael@0 | 3164 | // and this is faster |
michael@0 | 3165 | glyph = glyphs.AppendGlyph(); |
michael@0 | 3166 | glyph->mIndex = glyphData->GetSimpleGlyph(); |
michael@0 | 3167 | glyph->mPosition.x = ToDeviceUnits(glyphX, devUnitsPerAppUnit); |
michael@0 | 3168 | glyph->mPosition.y = ToDeviceUnits(y, devUnitsPerAppUnit); |
michael@0 | 3169 | glyph->mPosition = matInv * glyph->mPosition; |
michael@0 | 3170 | glyphs.Flush(dt, aContextPaint, scaledFont, |
michael@0 | 3171 | aDrawMode, isRTL, renderingOptions, |
michael@0 | 3172 | aContext, passedInvMatrix, |
michael@0 | 3173 | drawOptions); |
michael@0 | 3174 | |
michael@0 | 3175 | // synthetic bolding by multi-striking with 1-pixel offsets |
michael@0 | 3176 | // at least once, more if there's room (large font sizes) |
michael@0 | 3177 | if (IsSyntheticBold()) { |
michael@0 | 3178 | double strikeOffset = synBoldOnePixelOffset; |
michael@0 | 3179 | int32_t strikeCount = strikes; |
michael@0 | 3180 | do { |
michael@0 | 3181 | Glyph *doubleglyph; |
michael@0 | 3182 | doubleglyph = glyphs.AppendGlyph(); |
michael@0 | 3183 | doubleglyph->mIndex = glyph->mIndex; |
michael@0 | 3184 | doubleglyph->mPosition.x = |
michael@0 | 3185 | ToDeviceUnits(glyphX + strikeOffset * appUnitsPerDevUnit, |
michael@0 | 3186 | devUnitsPerAppUnit); |
michael@0 | 3187 | doubleglyph->mPosition.y = glyph->mPosition.y; |
michael@0 | 3188 | doubleglyph->mPosition = matInv * doubleglyph->mPosition; |
michael@0 | 3189 | strikeOffset += synBoldOnePixelOffset; |
michael@0 | 3190 | glyphs.Flush(dt, aContextPaint, scaledFont, |
michael@0 | 3191 | aDrawMode, isRTL, renderingOptions, |
michael@0 | 3192 | aContext, passedInvMatrix, |
michael@0 | 3193 | drawOptions); |
michael@0 | 3194 | } while (--strikeCount > 0); |
michael@0 | 3195 | } |
michael@0 | 3196 | emittedGlyphs = true; |
michael@0 | 3197 | } else { |
michael@0 | 3198 | uint32_t glyphCount = glyphData->GetGlyphCount(); |
michael@0 | 3199 | if (glyphCount > 0) { |
michael@0 | 3200 | const gfxTextRun::DetailedGlyph *details = |
michael@0 | 3201 | aTextRun->GetDetailedGlyphs(i); |
michael@0 | 3202 | NS_ASSERTION(details, "detailedGlyph should not be missing!"); |
michael@0 | 3203 | double advance; |
michael@0 | 3204 | for (uint32_t j = 0; j < glyphCount; ++j, ++details, x += direction * advance) { |
michael@0 | 3205 | advance = details->mAdvance; |
michael@0 | 3206 | if (glyphData->IsMissing()) { |
michael@0 | 3207 | // default ignorable characters will have zero advance width. |
michael@0 | 3208 | // we don't have to draw the hexbox for them |
michael@0 | 3209 | if (aDrawMode != DrawMode::GLYPH_PATH && advance > 0) { |
michael@0 | 3210 | double glyphX = x; |
michael@0 | 3211 | if (isRTL) { |
michael@0 | 3212 | glyphX -= advance; |
michael@0 | 3213 | } |
michael@0 | 3214 | gfxPoint pt(ToDeviceUnits(glyphX, devUnitsPerAppUnit), |
michael@0 | 3215 | ToDeviceUnits(y, devUnitsPerAppUnit)); |
michael@0 | 3216 | gfxFloat advanceDevUnits = ToDeviceUnits(advance, devUnitsPerAppUnit); |
michael@0 | 3217 | gfxFloat height = GetMetrics().maxAscent; |
michael@0 | 3218 | gfxRect glyphRect(pt.x, pt.y - height, advanceDevUnits, height); |
michael@0 | 3219 | gfxFontMissingGlyphs::DrawMissingGlyph(aContext, |
michael@0 | 3220 | glyphRect, |
michael@0 | 3221 | details->mGlyphID, |
michael@0 | 3222 | appUnitsPerDevUnit); |
michael@0 | 3223 | } |
michael@0 | 3224 | } else { |
michael@0 | 3225 | double glyphX = x + details->mXOffset; |
michael@0 | 3226 | if (isRTL) { |
michael@0 | 3227 | glyphX -= advance; |
michael@0 | 3228 | } |
michael@0 | 3229 | |
michael@0 | 3230 | gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit), |
michael@0 | 3231 | ToDeviceUnits(y, devUnitsPerAppUnit)); |
michael@0 | 3232 | |
michael@0 | 3233 | if (haveSVGGlyphs) { |
michael@0 | 3234 | if (!paintSVGGlyphs) { |
michael@0 | 3235 | continue; |
michael@0 | 3236 | } |
michael@0 | 3237 | DrawMode mode = ForcePaintingDrawMode(aDrawMode); |
michael@0 | 3238 | if (RenderSVGGlyph(aContext, point, mode, |
michael@0 | 3239 | details->mGlyphID, |
michael@0 | 3240 | aContextPaint, aCallbacks, |
michael@0 | 3241 | emittedGlyphs)) { |
michael@0 | 3242 | continue; |
michael@0 | 3243 | } |
michael@0 | 3244 | } |
michael@0 | 3245 | |
michael@0 | 3246 | glyph = glyphs.AppendGlyph(); |
michael@0 | 3247 | glyph->mIndex = details->mGlyphID; |
michael@0 | 3248 | glyph->mPosition.x = ToDeviceUnits(glyphX, devUnitsPerAppUnit); |
michael@0 | 3249 | glyph->mPosition.y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit); |
michael@0 | 3250 | glyph->mPosition = matInv * glyph->mPosition; |
michael@0 | 3251 | glyphs.Flush(dt, aContextPaint, scaledFont, aDrawMode, |
michael@0 | 3252 | isRTL, renderingOptions, aContext, passedInvMatrix, |
michael@0 | 3253 | drawOptions); |
michael@0 | 3254 | |
michael@0 | 3255 | if (IsSyntheticBold()) { |
michael@0 | 3256 | double strikeOffset = synBoldOnePixelOffset; |
michael@0 | 3257 | int32_t strikeCount = strikes; |
michael@0 | 3258 | do { |
michael@0 | 3259 | Glyph *doubleglyph; |
michael@0 | 3260 | doubleglyph = glyphs.AppendGlyph(); |
michael@0 | 3261 | doubleglyph->mIndex = glyph->mIndex; |
michael@0 | 3262 | doubleglyph->mPosition.x = |
michael@0 | 3263 | ToDeviceUnits(glyphX + strikeOffset * |
michael@0 | 3264 | appUnitsPerDevUnit, |
michael@0 | 3265 | devUnitsPerAppUnit); |
michael@0 | 3266 | doubleglyph->mPosition.y = glyph->mPosition.y; |
michael@0 | 3267 | strikeOffset += synBoldOnePixelOffset; |
michael@0 | 3268 | doubleglyph->mPosition = matInv * doubleglyph->mPosition; |
michael@0 | 3269 | glyphs.Flush(dt, aContextPaint, scaledFont, |
michael@0 | 3270 | aDrawMode, isRTL, renderingOptions, |
michael@0 | 3271 | aContext, passedInvMatrix, drawOptions); |
michael@0 | 3272 | } while (--strikeCount > 0); |
michael@0 | 3273 | } |
michael@0 | 3274 | emittedGlyphs = true; |
michael@0 | 3275 | } |
michael@0 | 3276 | } |
michael@0 | 3277 | } |
michael@0 | 3278 | } |
michael@0 | 3279 | |
michael@0 | 3280 | if (aSpacing) { |
michael@0 | 3281 | double space = aSpacing[i - aStart].mAfter; |
michael@0 | 3282 | if (i + 1 < aEnd) { |
michael@0 | 3283 | space += aSpacing[i + 1 - aStart].mBefore; |
michael@0 | 3284 | } |
michael@0 | 3285 | x += direction*space; |
michael@0 | 3286 | } |
michael@0 | 3287 | } |
michael@0 | 3288 | |
michael@0 | 3289 | glyphs.Flush(dt, aContextPaint, scaledFont, aDrawMode, isRTL, |
michael@0 | 3290 | renderingOptions, aContext, passedInvMatrix, |
michael@0 | 3291 | drawOptions, true); |
michael@0 | 3292 | if (aCallbacks && emittedGlyphs) { |
michael@0 | 3293 | aCallbacks->NotifyGlyphPathEmitted(); |
michael@0 | 3294 | } |
michael@0 | 3295 | |
michael@0 | 3296 | dt->SetTransform(oldMat); |
michael@0 | 3297 | |
michael@0 | 3298 | dt->SetPermitSubpixelAA(oldSubpixelAA); |
michael@0 | 3299 | } |
michael@0 | 3300 | |
michael@0 | 3301 | *aPt = gfxPoint(x, y); |
michael@0 | 3302 | } |
michael@0 | 3303 | |
michael@0 | 3304 | bool |
michael@0 | 3305 | gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMode, |
michael@0 | 3306 | uint32_t aGlyphId, gfxTextContextPaint *aContextPaint) |
michael@0 | 3307 | { |
michael@0 | 3308 | if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) { |
michael@0 | 3309 | return false; |
michael@0 | 3310 | } |
michael@0 | 3311 | |
michael@0 | 3312 | const gfxFloat devUnitsPerSVGUnit = |
michael@0 | 3313 | GetAdjustedSize() / GetFontEntry()->UnitsPerEm(); |
michael@0 | 3314 | gfxContextMatrixAutoSaveRestore matrixRestore(aContext); |
michael@0 | 3315 | |
michael@0 | 3316 | aContext->Translate(gfxPoint(aPoint.x, aPoint.y)); |
michael@0 | 3317 | aContext->Scale(devUnitsPerSVGUnit, devUnitsPerSVGUnit); |
michael@0 | 3318 | |
michael@0 | 3319 | aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit); |
michael@0 | 3320 | |
michael@0 | 3321 | return GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, int(aDrawMode), |
michael@0 | 3322 | aContextPaint); |
michael@0 | 3323 | } |
michael@0 | 3324 | |
michael@0 | 3325 | bool |
michael@0 | 3326 | gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMode, |
michael@0 | 3327 | uint32_t aGlyphId, gfxTextContextPaint *aContextPaint, |
michael@0 | 3328 | gfxTextRunDrawCallbacks *aCallbacks, |
michael@0 | 3329 | bool& aEmittedGlyphs) |
michael@0 | 3330 | { |
michael@0 | 3331 | if (aCallbacks) { |
michael@0 | 3332 | if (aEmittedGlyphs) { |
michael@0 | 3333 | aCallbacks->NotifyGlyphPathEmitted(); |
michael@0 | 3334 | aEmittedGlyphs = false; |
michael@0 | 3335 | } |
michael@0 | 3336 | aCallbacks->NotifyBeforeSVGGlyphPainted(); |
michael@0 | 3337 | } |
michael@0 | 3338 | bool rendered = RenderSVGGlyph(aContext, aPoint, aDrawMode, aGlyphId, |
michael@0 | 3339 | aContextPaint); |
michael@0 | 3340 | if (aCallbacks) { |
michael@0 | 3341 | aCallbacks->NotifyAfterSVGGlyphPainted(); |
michael@0 | 3342 | } |
michael@0 | 3343 | return rendered; |
michael@0 | 3344 | } |
michael@0 | 3345 | |
michael@0 | 3346 | static void |
michael@0 | 3347 | UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax) |
michael@0 | 3348 | { |
michael@0 | 3349 | *aDestMin = std::min(*aDestMin, aX); |
michael@0 | 3350 | *aDestMax = std::max(*aDestMax, aX); |
michael@0 | 3351 | } |
michael@0 | 3352 | |
michael@0 | 3353 | // We get precise glyph extents if the textrun creator requested them, or |
michael@0 | 3354 | // if the font is a user font --- in which case the author may be relying |
michael@0 | 3355 | // on overflowing glyphs. |
michael@0 | 3356 | static bool |
michael@0 | 3357 | NeedsGlyphExtents(gfxFont *aFont, gfxTextRun *aTextRun) |
michael@0 | 3358 | { |
michael@0 | 3359 | return (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) || |
michael@0 | 3360 | aFont->GetFontEntry()->IsUserFont(); |
michael@0 | 3361 | } |
michael@0 | 3362 | |
michael@0 | 3363 | static bool |
michael@0 | 3364 | NeedsGlyphExtents(gfxTextRun *aTextRun) |
michael@0 | 3365 | { |
michael@0 | 3366 | if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) |
michael@0 | 3367 | return true; |
michael@0 | 3368 | uint32_t numRuns; |
michael@0 | 3369 | const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&numRuns); |
michael@0 | 3370 | for (uint32_t i = 0; i < numRuns; ++i) { |
michael@0 | 3371 | if (glyphRuns[i].mFont->GetFontEntry()->IsUserFont()) |
michael@0 | 3372 | return true; |
michael@0 | 3373 | } |
michael@0 | 3374 | return false; |
michael@0 | 3375 | } |
michael@0 | 3376 | |
michael@0 | 3377 | gfxFont::RunMetrics |
michael@0 | 3378 | gfxFont::Measure(gfxTextRun *aTextRun, |
michael@0 | 3379 | uint32_t aStart, uint32_t aEnd, |
michael@0 | 3380 | BoundingBoxType aBoundingBoxType, |
michael@0 | 3381 | gfxContext *aRefContext, |
michael@0 | 3382 | Spacing *aSpacing) |
michael@0 | 3383 | { |
michael@0 | 3384 | // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS |
michael@0 | 3385 | // and the underlying cairo font may be antialiased, |
michael@0 | 3386 | // we need to create a copy in order to avoid getting cached extents. |
michael@0 | 3387 | // This is only used by MathML layout at present. |
michael@0 | 3388 | if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS && |
michael@0 | 3389 | mAntialiasOption != kAntialiasNone) { |
michael@0 | 3390 | if (!mNonAAFont) { |
michael@0 | 3391 | mNonAAFont = CopyWithAntialiasOption(kAntialiasNone); |
michael@0 | 3392 | } |
michael@0 | 3393 | // if font subclass doesn't implement CopyWithAntialiasOption(), |
michael@0 | 3394 | // it will return null and we'll proceed to use the existing font |
michael@0 | 3395 | if (mNonAAFont) { |
michael@0 | 3396 | return mNonAAFont->Measure(aTextRun, aStart, aEnd, |
michael@0 | 3397 | TIGHT_HINTED_OUTLINE_EXTENTS, |
michael@0 | 3398 | aRefContext, aSpacing); |
michael@0 | 3399 | } |
michael@0 | 3400 | } |
michael@0 | 3401 | |
michael@0 | 3402 | const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); |
michael@0 | 3403 | // Current position in appunits |
michael@0 | 3404 | const gfxFont::Metrics& fontMetrics = GetMetrics(); |
michael@0 | 3405 | |
michael@0 | 3406 | RunMetrics metrics; |
michael@0 | 3407 | metrics.mAscent = fontMetrics.maxAscent*appUnitsPerDevUnit; |
michael@0 | 3408 | metrics.mDescent = fontMetrics.maxDescent*appUnitsPerDevUnit; |
michael@0 | 3409 | if (aStart == aEnd) { |
michael@0 | 3410 | // exit now before we look at aSpacing[0], which is undefined |
michael@0 | 3411 | metrics.mBoundingBox = gfxRect(0, -metrics.mAscent, 0, metrics.mAscent + metrics.mDescent); |
michael@0 | 3412 | return metrics; |
michael@0 | 3413 | } |
michael@0 | 3414 | |
michael@0 | 3415 | gfxFloat advanceMin = 0, advanceMax = 0; |
michael@0 | 3416 | const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); |
michael@0 | 3417 | bool isRTL = aTextRun->IsRightToLeft(); |
michael@0 | 3418 | double direction = aTextRun->GetDirection(); |
michael@0 | 3419 | bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun); |
michael@0 | 3420 | gfxGlyphExtents *extents = |
michael@0 | 3421 | (aBoundingBoxType == LOOSE_INK_EXTENTS && |
michael@0 | 3422 | !needsGlyphExtents && |
michael@0 | 3423 | !aTextRun->HasDetailedGlyphs()) ? nullptr |
michael@0 | 3424 | : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit()); |
michael@0 | 3425 | double x = 0; |
michael@0 | 3426 | if (aSpacing) { |
michael@0 | 3427 | x += direction*aSpacing[0].mBefore; |
michael@0 | 3428 | } |
michael@0 | 3429 | uint32_t i; |
michael@0 | 3430 | for (i = aStart; i < aEnd; ++i) { |
michael@0 | 3431 | const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i]; |
michael@0 | 3432 | if (glyphData->IsSimpleGlyph()) { |
michael@0 | 3433 | double advance = glyphData->GetSimpleAdvance(); |
michael@0 | 3434 | // Only get the real glyph horizontal extent if we were asked |
michael@0 | 3435 | // for the tight bounding box or we're in quality mode |
michael@0 | 3436 | if ((aBoundingBoxType != LOOSE_INK_EXTENTS || needsGlyphExtents) && |
michael@0 | 3437 | extents) { |
michael@0 | 3438 | uint32_t glyphIndex = glyphData->GetSimpleGlyph(); |
michael@0 | 3439 | uint16_t extentsWidth = extents->GetContainedGlyphWidthAppUnits(glyphIndex); |
michael@0 | 3440 | if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH && |
michael@0 | 3441 | aBoundingBoxType == LOOSE_INK_EXTENTS) { |
michael@0 | 3442 | UnionRange(x, &advanceMin, &advanceMax); |
michael@0 | 3443 | UnionRange(x + direction*extentsWidth, &advanceMin, &advanceMax); |
michael@0 | 3444 | } else { |
michael@0 | 3445 | gfxRect glyphRect; |
michael@0 | 3446 | if (!extents->GetTightGlyphExtentsAppUnits(this, |
michael@0 | 3447 | aRefContext, glyphIndex, &glyphRect)) { |
michael@0 | 3448 | glyphRect = gfxRect(0, metrics.mBoundingBox.Y(), |
michael@0 | 3449 | advance, metrics.mBoundingBox.Height()); |
michael@0 | 3450 | } |
michael@0 | 3451 | if (isRTL) { |
michael@0 | 3452 | glyphRect -= gfxPoint(advance, 0); |
michael@0 | 3453 | } |
michael@0 | 3454 | glyphRect += gfxPoint(x, 0); |
michael@0 | 3455 | metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect); |
michael@0 | 3456 | } |
michael@0 | 3457 | } |
michael@0 | 3458 | x += direction*advance; |
michael@0 | 3459 | } else { |
michael@0 | 3460 | uint32_t glyphCount = glyphData->GetGlyphCount(); |
michael@0 | 3461 | if (glyphCount > 0) { |
michael@0 | 3462 | const gfxTextRun::DetailedGlyph *details = |
michael@0 | 3463 | aTextRun->GetDetailedGlyphs(i); |
michael@0 | 3464 | NS_ASSERTION(details != nullptr, |
michael@0 | 3465 | "detaiedGlyph record should not be missing!"); |
michael@0 | 3466 | uint32_t j; |
michael@0 | 3467 | for (j = 0; j < glyphCount; ++j, ++details) { |
michael@0 | 3468 | uint32_t glyphIndex = details->mGlyphID; |
michael@0 | 3469 | gfxPoint glyphPt(x + details->mXOffset, details->mYOffset); |
michael@0 | 3470 | double advance = details->mAdvance; |
michael@0 | 3471 | gfxRect glyphRect; |
michael@0 | 3472 | if (glyphData->IsMissing() || !extents || |
michael@0 | 3473 | !extents->GetTightGlyphExtentsAppUnits(this, |
michael@0 | 3474 | aRefContext, glyphIndex, &glyphRect)) { |
michael@0 | 3475 | // We might have failed to get glyph extents due to |
michael@0 | 3476 | // OOM or something |
michael@0 | 3477 | glyphRect = gfxRect(0, -metrics.mAscent, |
michael@0 | 3478 | advance, metrics.mAscent + metrics.mDescent); |
michael@0 | 3479 | } |
michael@0 | 3480 | if (isRTL) { |
michael@0 | 3481 | glyphRect -= gfxPoint(advance, 0); |
michael@0 | 3482 | } |
michael@0 | 3483 | glyphRect += gfxPoint(x, 0); |
michael@0 | 3484 | metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect); |
michael@0 | 3485 | x += direction*advance; |
michael@0 | 3486 | } |
michael@0 | 3487 | } |
michael@0 | 3488 | } |
michael@0 | 3489 | // Every other glyph type is ignored |
michael@0 | 3490 | if (aSpacing) { |
michael@0 | 3491 | double space = aSpacing[i - aStart].mAfter; |
michael@0 | 3492 | if (i + 1 < aEnd) { |
michael@0 | 3493 | space += aSpacing[i + 1 - aStart].mBefore; |
michael@0 | 3494 | } |
michael@0 | 3495 | x += direction*space; |
michael@0 | 3496 | } |
michael@0 | 3497 | } |
michael@0 | 3498 | |
michael@0 | 3499 | if (aBoundingBoxType == LOOSE_INK_EXTENTS) { |
michael@0 | 3500 | UnionRange(x, &advanceMin, &advanceMax); |
michael@0 | 3501 | gfxRect fontBox(advanceMin, -metrics.mAscent, |
michael@0 | 3502 | advanceMax - advanceMin, metrics.mAscent + metrics.mDescent); |
michael@0 | 3503 | metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox); |
michael@0 | 3504 | } |
michael@0 | 3505 | if (isRTL) { |
michael@0 | 3506 | metrics.mBoundingBox -= gfxPoint(x, 0); |
michael@0 | 3507 | } |
michael@0 | 3508 | |
michael@0 | 3509 | metrics.mAdvanceWidth = x*direction; |
michael@0 | 3510 | return metrics; |
michael@0 | 3511 | } |
michael@0 | 3512 | |
michael@0 | 3513 | static PLDHashOperator |
michael@0 | 3514 | NotifyGlyphChangeObservers(nsPtrHashKey<gfxFont::GlyphChangeObserver>* aKey, |
michael@0 | 3515 | void* aClosure) |
michael@0 | 3516 | { |
michael@0 | 3517 | aKey->GetKey()->NotifyGlyphsChanged(); |
michael@0 | 3518 | return PL_DHASH_NEXT; |
michael@0 | 3519 | } |
michael@0 | 3520 | |
michael@0 | 3521 | void |
michael@0 | 3522 | gfxFont::NotifyGlyphsChanged() |
michael@0 | 3523 | { |
michael@0 | 3524 | uint32_t i, count = mGlyphExtentsArray.Length(); |
michael@0 | 3525 | for (i = 0; i < count; ++i) { |
michael@0 | 3526 | // Flush cached extents array |
michael@0 | 3527 | mGlyphExtentsArray[i]->NotifyGlyphsChanged(); |
michael@0 | 3528 | } |
michael@0 | 3529 | |
michael@0 | 3530 | if (mGlyphChangeObservers) { |
michael@0 | 3531 | mGlyphChangeObservers->EnumerateEntries(NotifyGlyphChangeObservers, nullptr); |
michael@0 | 3532 | } |
michael@0 | 3533 | } |
michael@0 | 3534 | |
michael@0 | 3535 | static bool |
michael@0 | 3536 | IsBoundarySpace(char16_t aChar, char16_t aNextChar) |
michael@0 | 3537 | { |
michael@0 | 3538 | return (aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar); |
michael@0 | 3539 | } |
michael@0 | 3540 | |
michael@0 | 3541 | static inline uint32_t |
michael@0 | 3542 | HashMix(uint32_t aHash, char16_t aCh) |
michael@0 | 3543 | { |
michael@0 | 3544 | return (aHash >> 28) ^ (aHash << 4) ^ aCh; |
michael@0 | 3545 | } |
michael@0 | 3546 | |
michael@0 | 3547 | #ifdef __GNUC__ |
michael@0 | 3548 | #define GFX_MAYBE_UNUSED __attribute__((unused)) |
michael@0 | 3549 | #else |
michael@0 | 3550 | #define GFX_MAYBE_UNUSED |
michael@0 | 3551 | #endif |
michael@0 | 3552 | |
michael@0 | 3553 | template<typename T> |
michael@0 | 3554 | gfxShapedWord* |
michael@0 | 3555 | gfxFont::GetShapedWord(gfxContext *aContext, |
michael@0 | 3556 | const T *aText, |
michael@0 | 3557 | uint32_t aLength, |
michael@0 | 3558 | uint32_t aHash, |
michael@0 | 3559 | int32_t aRunScript, |
michael@0 | 3560 | int32_t aAppUnitsPerDevUnit, |
michael@0 | 3561 | uint32_t aFlags, |
michael@0 | 3562 | gfxTextPerfMetrics *aTextPerf GFX_MAYBE_UNUSED) |
michael@0 | 3563 | { |
michael@0 | 3564 | // if the cache is getting too big, flush it and start over |
michael@0 | 3565 | uint32_t wordCacheMaxEntries = |
michael@0 | 3566 | gfxPlatform::GetPlatform()->WordCacheMaxEntries(); |
michael@0 | 3567 | if (mWordCache->Count() > wordCacheMaxEntries) { |
michael@0 | 3568 | NS_WARNING("flushing shaped-word cache"); |
michael@0 | 3569 | ClearCachedWords(); |
michael@0 | 3570 | } |
michael@0 | 3571 | |
michael@0 | 3572 | // if there's a cached entry for this word, just return it |
michael@0 | 3573 | CacheHashKey key(aText, aLength, aHash, |
michael@0 | 3574 | aRunScript, |
michael@0 | 3575 | aAppUnitsPerDevUnit, |
michael@0 | 3576 | aFlags); |
michael@0 | 3577 | |
michael@0 | 3578 | CacheHashEntry *entry = mWordCache->PutEntry(key); |
michael@0 | 3579 | if (!entry) { |
michael@0 | 3580 | NS_WARNING("failed to create word cache entry - expect missing text"); |
michael@0 | 3581 | return nullptr; |
michael@0 | 3582 | } |
michael@0 | 3583 | gfxShapedWord *sw = entry->mShapedWord; |
michael@0 | 3584 | |
michael@0 | 3585 | bool isContent = !mStyle.systemFont; |
michael@0 | 3586 | |
michael@0 | 3587 | if (sw) { |
michael@0 | 3588 | sw->ResetAge(); |
michael@0 | 3589 | Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_HITS_CONTENT : |
michael@0 | 3590 | Telemetry::WORD_CACHE_HITS_CHROME), |
michael@0 | 3591 | aLength); |
michael@0 | 3592 | #ifndef RELEASE_BUILD |
michael@0 | 3593 | if (aTextPerf) { |
michael@0 | 3594 | aTextPerf->current.wordCacheHit++; |
michael@0 | 3595 | } |
michael@0 | 3596 | #endif |
michael@0 | 3597 | return sw; |
michael@0 | 3598 | } |
michael@0 | 3599 | |
michael@0 | 3600 | Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_MISSES_CONTENT : |
michael@0 | 3601 | Telemetry::WORD_CACHE_MISSES_CHROME), |
michael@0 | 3602 | aLength); |
michael@0 | 3603 | #ifndef RELEASE_BUILD |
michael@0 | 3604 | if (aTextPerf) { |
michael@0 | 3605 | aTextPerf->current.wordCacheMiss++; |
michael@0 | 3606 | } |
michael@0 | 3607 | #endif |
michael@0 | 3608 | |
michael@0 | 3609 | sw = entry->mShapedWord = gfxShapedWord::Create(aText, aLength, |
michael@0 | 3610 | aRunScript, |
michael@0 | 3611 | aAppUnitsPerDevUnit, |
michael@0 | 3612 | aFlags); |
michael@0 | 3613 | if (!sw) { |
michael@0 | 3614 | NS_WARNING("failed to create gfxShapedWord - expect missing text"); |
michael@0 | 3615 | return nullptr; |
michael@0 | 3616 | } |
michael@0 | 3617 | |
michael@0 | 3618 | DebugOnly<bool> ok = |
michael@0 | 3619 | ShapeText(aContext, aText, 0, aLength, aRunScript, sw); |
michael@0 | 3620 | |
michael@0 | 3621 | NS_WARN_IF_FALSE(ok, "failed to shape word - expect garbled text"); |
michael@0 | 3622 | |
michael@0 | 3623 | return sw; |
michael@0 | 3624 | } |
michael@0 | 3625 | |
michael@0 | 3626 | bool |
michael@0 | 3627 | gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const |
michael@0 | 3628 | { |
michael@0 | 3629 | const gfxShapedWord *sw = mShapedWord; |
michael@0 | 3630 | if (!sw) { |
michael@0 | 3631 | return false; |
michael@0 | 3632 | } |
michael@0 | 3633 | if (sw->GetLength() != aKey->mLength || |
michael@0 | 3634 | sw->Flags() != aKey->mFlags || |
michael@0 | 3635 | sw->GetAppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit || |
michael@0 | 3636 | sw->Script() != aKey->mScript) { |
michael@0 | 3637 | return false; |
michael@0 | 3638 | } |
michael@0 | 3639 | if (sw->TextIs8Bit()) { |
michael@0 | 3640 | if (aKey->mTextIs8Bit) { |
michael@0 | 3641 | return (0 == memcmp(sw->Text8Bit(), aKey->mText.mSingle, |
michael@0 | 3642 | aKey->mLength * sizeof(uint8_t))); |
michael@0 | 3643 | } |
michael@0 | 3644 | // The key has 16-bit text, even though all the characters are < 256, |
michael@0 | 3645 | // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're |
michael@0 | 3646 | // comparing with will have 8-bit text. |
michael@0 | 3647 | const uint8_t *s1 = sw->Text8Bit(); |
michael@0 | 3648 | const char16_t *s2 = aKey->mText.mDouble; |
michael@0 | 3649 | const char16_t *s2end = s2 + aKey->mLength; |
michael@0 | 3650 | while (s2 < s2end) { |
michael@0 | 3651 | if (*s1++ != *s2++) { |
michael@0 | 3652 | return false; |
michael@0 | 3653 | } |
michael@0 | 3654 | } |
michael@0 | 3655 | return true; |
michael@0 | 3656 | } |
michael@0 | 3657 | NS_ASSERTION((aKey->mFlags & gfxTextRunFactory::TEXT_IS_8BIT) == 0 && |
michael@0 | 3658 | !aKey->mTextIs8Bit, "didn't expect 8-bit text here"); |
michael@0 | 3659 | return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble, |
michael@0 | 3660 | aKey->mLength * sizeof(char16_t))); |
michael@0 | 3661 | } |
michael@0 | 3662 | |
michael@0 | 3663 | bool |
michael@0 | 3664 | gfxFont::ShapeText(gfxContext *aContext, |
michael@0 | 3665 | const uint8_t *aText, |
michael@0 | 3666 | uint32_t aOffset, |
michael@0 | 3667 | uint32_t aLength, |
michael@0 | 3668 | int32_t aScript, |
michael@0 | 3669 | gfxShapedText *aShapedText, |
michael@0 | 3670 | bool aPreferPlatformShaping) |
michael@0 | 3671 | { |
michael@0 | 3672 | nsDependentCSubstring ascii((const char*)aText, aLength); |
michael@0 | 3673 | nsAutoString utf16; |
michael@0 | 3674 | AppendASCIItoUTF16(ascii, utf16); |
michael@0 | 3675 | if (utf16.Length() != aLength) { |
michael@0 | 3676 | return false; |
michael@0 | 3677 | } |
michael@0 | 3678 | return ShapeText(aContext, utf16.BeginReading(), aOffset, aLength, |
michael@0 | 3679 | aScript, aShapedText, aPreferPlatformShaping); |
michael@0 | 3680 | } |
michael@0 | 3681 | |
michael@0 | 3682 | bool |
michael@0 | 3683 | gfxFont::ShapeText(gfxContext *aContext, |
michael@0 | 3684 | const char16_t *aText, |
michael@0 | 3685 | uint32_t aOffset, |
michael@0 | 3686 | uint32_t aLength, |
michael@0 | 3687 | int32_t aScript, |
michael@0 | 3688 | gfxShapedText *aShapedText, |
michael@0 | 3689 | bool aPreferPlatformShaping) |
michael@0 | 3690 | { |
michael@0 | 3691 | bool ok = false; |
michael@0 | 3692 | |
michael@0 | 3693 | if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) { |
michael@0 | 3694 | ok = mGraphiteShaper->ShapeText(aContext, aText, aOffset, aLength, |
michael@0 | 3695 | aScript, aShapedText); |
michael@0 | 3696 | } |
michael@0 | 3697 | |
michael@0 | 3698 | if (!ok && mHarfBuzzShaper && !aPreferPlatformShaping) { |
michael@0 | 3699 | if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aScript)) { |
michael@0 | 3700 | ok = mHarfBuzzShaper->ShapeText(aContext, aText, aOffset, aLength, |
michael@0 | 3701 | aScript, aShapedText); |
michael@0 | 3702 | } |
michael@0 | 3703 | } |
michael@0 | 3704 | |
michael@0 | 3705 | if (!ok) { |
michael@0 | 3706 | if (!mPlatformShaper) { |
michael@0 | 3707 | CreatePlatformShaper(); |
michael@0 | 3708 | NS_ASSERTION(mPlatformShaper, "no platform shaper available!"); |
michael@0 | 3709 | } |
michael@0 | 3710 | if (mPlatformShaper) { |
michael@0 | 3711 | ok = mPlatformShaper->ShapeText(aContext, aText, aOffset, aLength, |
michael@0 | 3712 | aScript, aShapedText); |
michael@0 | 3713 | } |
michael@0 | 3714 | } |
michael@0 | 3715 | |
michael@0 | 3716 | PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText); |
michael@0 | 3717 | |
michael@0 | 3718 | return ok; |
michael@0 | 3719 | } |
michael@0 | 3720 | |
michael@0 | 3721 | void |
michael@0 | 3722 | gfxFont::PostShapingFixup(gfxContext *aContext, |
michael@0 | 3723 | const char16_t *aText, |
michael@0 | 3724 | uint32_t aOffset, |
michael@0 | 3725 | uint32_t aLength, |
michael@0 | 3726 | gfxShapedText *aShapedText) |
michael@0 | 3727 | { |
michael@0 | 3728 | if (IsSyntheticBold()) { |
michael@0 | 3729 | float synBoldOffset = |
michael@0 | 3730 | GetSyntheticBoldOffset() * CalcXScale(aContext); |
michael@0 | 3731 | aShapedText->AdjustAdvancesForSyntheticBold(synBoldOffset, |
michael@0 | 3732 | aOffset, aLength); |
michael@0 | 3733 | } |
michael@0 | 3734 | } |
michael@0 | 3735 | |
michael@0 | 3736 | #define MAX_SHAPING_LENGTH 32760 // slightly less than 32K, trying to avoid |
michael@0 | 3737 | // over-stressing platform shapers |
michael@0 | 3738 | #define BACKTRACK_LIMIT 16 // backtrack this far looking for a good place |
michael@0 | 3739 | // to split into fragments for separate shaping |
michael@0 | 3740 | |
michael@0 | 3741 | template<typename T> |
michael@0 | 3742 | bool |
michael@0 | 3743 | gfxFont::ShapeFragmentWithoutWordCache(gfxContext *aContext, |
michael@0 | 3744 | const T *aText, |
michael@0 | 3745 | uint32_t aOffset, |
michael@0 | 3746 | uint32_t aLength, |
michael@0 | 3747 | int32_t aScript, |
michael@0 | 3748 | gfxTextRun *aTextRun) |
michael@0 | 3749 | { |
michael@0 | 3750 | aTextRun->SetupClusterBoundaries(aOffset, aText, aLength); |
michael@0 | 3751 | |
michael@0 | 3752 | bool ok = true; |
michael@0 | 3753 | |
michael@0 | 3754 | while (ok && aLength > 0) { |
michael@0 | 3755 | uint32_t fragLen = aLength; |
michael@0 | 3756 | |
michael@0 | 3757 | // limit the length of text we pass to shapers in a single call |
michael@0 | 3758 | if (fragLen > MAX_SHAPING_LENGTH) { |
michael@0 | 3759 | fragLen = MAX_SHAPING_LENGTH; |
michael@0 | 3760 | |
michael@0 | 3761 | // in the 8-bit case, there are no multi-char clusters, |
michael@0 | 3762 | // so we don't need to do this check |
michael@0 | 3763 | if (sizeof(T) == sizeof(char16_t)) { |
michael@0 | 3764 | uint32_t i; |
michael@0 | 3765 | for (i = 0; i < BACKTRACK_LIMIT; ++i) { |
michael@0 | 3766 | if (aTextRun->IsClusterStart(aOffset + fragLen - i)) { |
michael@0 | 3767 | fragLen -= i; |
michael@0 | 3768 | break; |
michael@0 | 3769 | } |
michael@0 | 3770 | } |
michael@0 | 3771 | if (i == BACKTRACK_LIMIT) { |
michael@0 | 3772 | // if we didn't find any cluster start while backtracking, |
michael@0 | 3773 | // just check that we're not in the middle of a surrogate |
michael@0 | 3774 | // pair; back up by one code unit if we are. |
michael@0 | 3775 | if (NS_IS_LOW_SURROGATE(aText[fragLen]) && |
michael@0 | 3776 | NS_IS_HIGH_SURROGATE(aText[fragLen - 1])) { |
michael@0 | 3777 | --fragLen; |
michael@0 | 3778 | } |
michael@0 | 3779 | } |
michael@0 | 3780 | } |
michael@0 | 3781 | } |
michael@0 | 3782 | |
michael@0 | 3783 | ok = ShapeText(aContext, aText, aOffset, fragLen, aScript, aTextRun); |
michael@0 | 3784 | |
michael@0 | 3785 | aText += fragLen; |
michael@0 | 3786 | aOffset += fragLen; |
michael@0 | 3787 | aLength -= fragLen; |
michael@0 | 3788 | } |
michael@0 | 3789 | |
michael@0 | 3790 | return ok; |
michael@0 | 3791 | } |
michael@0 | 3792 | |
michael@0 | 3793 | // Check if aCh is an unhandled control character that should be displayed |
michael@0 | 3794 | // as a hexbox rather than rendered by some random font on the system. |
michael@0 | 3795 | // We exclude \r as stray s are rather common (bug 941940). |
michael@0 | 3796 | // Note that \n and \t don't come through here, as they have specific |
michael@0 | 3797 | // meanings that have already been handled. |
michael@0 | 3798 | static bool |
michael@0 | 3799 | IsInvalidControlChar(uint32_t aCh) |
michael@0 | 3800 | { |
michael@0 | 3801 | return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f); |
michael@0 | 3802 | } |
michael@0 | 3803 | |
michael@0 | 3804 | template<typename T> |
michael@0 | 3805 | bool |
michael@0 | 3806 | gfxFont::ShapeTextWithoutWordCache(gfxContext *aContext, |
michael@0 | 3807 | const T *aText, |
michael@0 | 3808 | uint32_t aOffset, |
michael@0 | 3809 | uint32_t aLength, |
michael@0 | 3810 | int32_t aScript, |
michael@0 | 3811 | gfxTextRun *aTextRun) |
michael@0 | 3812 | { |
michael@0 | 3813 | uint32_t fragStart = 0; |
michael@0 | 3814 | bool ok = true; |
michael@0 | 3815 | |
michael@0 | 3816 | for (uint32_t i = 0; i <= aLength && ok; ++i) { |
michael@0 | 3817 | T ch = (i < aLength) ? aText[i] : '\n'; |
michael@0 | 3818 | bool invalid = gfxFontGroup::IsInvalidChar(ch); |
michael@0 | 3819 | uint32_t length = i - fragStart; |
michael@0 | 3820 | |
michael@0 | 3821 | // break into separate fragments when we hit an invalid char |
michael@0 | 3822 | if (!invalid) { |
michael@0 | 3823 | continue; |
michael@0 | 3824 | } |
michael@0 | 3825 | |
michael@0 | 3826 | if (length > 0) { |
michael@0 | 3827 | ok = ShapeFragmentWithoutWordCache(aContext, aText + fragStart, |
michael@0 | 3828 | aOffset + fragStart, length, |
michael@0 | 3829 | aScript, aTextRun); |
michael@0 | 3830 | } |
michael@0 | 3831 | |
michael@0 | 3832 | if (i == aLength) { |
michael@0 | 3833 | break; |
michael@0 | 3834 | } |
michael@0 | 3835 | |
michael@0 | 3836 | // fragment was terminated by an invalid char: skip it, |
michael@0 | 3837 | // unless it's a control char that we want to show as a hexbox, |
michael@0 | 3838 | // but record where TAB or NEWLINE occur |
michael@0 | 3839 | if (ch == '\t') { |
michael@0 | 3840 | aTextRun->SetIsTab(aOffset + i); |
michael@0 | 3841 | } else if (ch == '\n') { |
michael@0 | 3842 | aTextRun->SetIsNewline(aOffset + i); |
michael@0 | 3843 | } else if (IsInvalidControlChar(ch) && |
michael@0 | 3844 | !(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS)) { |
michael@0 | 3845 | aTextRun->SetMissingGlyph(aOffset + i, ch, this); |
michael@0 | 3846 | } |
michael@0 | 3847 | fragStart = i + 1; |
michael@0 | 3848 | } |
michael@0 | 3849 | |
michael@0 | 3850 | NS_WARN_IF_FALSE(ok, "failed to shape text - expect garbled text"); |
michael@0 | 3851 | return ok; |
michael@0 | 3852 | } |
michael@0 | 3853 | |
michael@0 | 3854 | #ifndef RELEASE_BUILD |
michael@0 | 3855 | #define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0) |
michael@0 | 3856 | #else |
michael@0 | 3857 | #define TEXT_PERF_INCR(tp, m) |
michael@0 | 3858 | #endif |
michael@0 | 3859 | |
michael@0 | 3860 | inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; } |
michael@0 | 3861 | inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; } |
michael@0 | 3862 | |
michael@0 | 3863 | inline static bool HasSpaces(const uint8_t *aString, uint32_t aLen) |
michael@0 | 3864 | { |
michael@0 | 3865 | return memchr(aString, 0x20, aLen) != nullptr; |
michael@0 | 3866 | } |
michael@0 | 3867 | |
michael@0 | 3868 | inline static bool HasSpaces(const char16_t *aString, uint32_t aLen) |
michael@0 | 3869 | { |
michael@0 | 3870 | for (const char16_t *ch = aString; ch < aString + aLen; ch++) { |
michael@0 | 3871 | if (*ch == 0x20) { |
michael@0 | 3872 | return true; |
michael@0 | 3873 | } |
michael@0 | 3874 | } |
michael@0 | 3875 | return false; |
michael@0 | 3876 | } |
michael@0 | 3877 | |
michael@0 | 3878 | template<typename T> |
michael@0 | 3879 | bool |
michael@0 | 3880 | gfxFont::SplitAndInitTextRun(gfxContext *aContext, |
michael@0 | 3881 | gfxTextRun *aTextRun, |
michael@0 | 3882 | const T *aString, |
michael@0 | 3883 | uint32_t aRunStart, |
michael@0 | 3884 | uint32_t aRunLength, |
michael@0 | 3885 | int32_t aRunScript) |
michael@0 | 3886 | { |
michael@0 | 3887 | if (aRunLength == 0) { |
michael@0 | 3888 | return true; |
michael@0 | 3889 | } |
michael@0 | 3890 | |
michael@0 | 3891 | gfxTextPerfMetrics *tp = nullptr; |
michael@0 | 3892 | |
michael@0 | 3893 | #ifndef RELEASE_BUILD |
michael@0 | 3894 | tp = aTextRun->GetFontGroup()->GetTextPerfMetrics(); |
michael@0 | 3895 | if (tp) { |
michael@0 | 3896 | if (mStyle.systemFont) { |
michael@0 | 3897 | tp->current.numChromeTextRuns++; |
michael@0 | 3898 | } else { |
michael@0 | 3899 | tp->current.numContentTextRuns++; |
michael@0 | 3900 | } |
michael@0 | 3901 | tp->current.numChars += aRunLength; |
michael@0 | 3902 | if (aRunLength > tp->current.maxTextRunLen) { |
michael@0 | 3903 | tp->current.maxTextRunLen = aRunLength; |
michael@0 | 3904 | } |
michael@0 | 3905 | } |
michael@0 | 3906 | #endif |
michael@0 | 3907 | |
michael@0 | 3908 | uint32_t wordCacheCharLimit = |
michael@0 | 3909 | gfxPlatform::GetPlatform()->WordCacheCharLimit(); |
michael@0 | 3910 | |
michael@0 | 3911 | // If spaces can participate in shaping (e.g. within lookups for automatic |
michael@0 | 3912 | // fractions), need to shape without using the word cache which segments |
michael@0 | 3913 | // textruns on space boundaries. Word cache can be used if the textrun |
michael@0 | 3914 | // is short enough to fit in the word cache and it lacks spaces. |
michael@0 | 3915 | if (SpaceMayParticipateInShaping(aRunScript)) { |
michael@0 | 3916 | if (aRunLength > wordCacheCharLimit || |
michael@0 | 3917 | HasSpaces(aString + aRunStart, aRunLength)) { |
michael@0 | 3918 | TEXT_PERF_INCR(tp, wordCacheSpaceRules); |
michael@0 | 3919 | return ShapeTextWithoutWordCache(aContext, aString + aRunStart, |
michael@0 | 3920 | aRunStart, aRunLength, aRunScript, |
michael@0 | 3921 | aTextRun); |
michael@0 | 3922 | } |
michael@0 | 3923 | } |
michael@0 | 3924 | |
michael@0 | 3925 | InitWordCache(); |
michael@0 | 3926 | |
michael@0 | 3927 | // the only flags we care about for ShapedWord construction/caching |
michael@0 | 3928 | uint32_t flags = aTextRun->GetFlags(); |
michael@0 | 3929 | flags &= (gfxTextRunFactory::TEXT_IS_RTL | |
michael@0 | 3930 | gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES | |
michael@0 | 3931 | gfxTextRunFactory::TEXT_USE_MATH_SCRIPT); |
michael@0 | 3932 | if (sizeof(T) == sizeof(uint8_t)) { |
michael@0 | 3933 | flags |= gfxTextRunFactory::TEXT_IS_8BIT; |
michael@0 | 3934 | } |
michael@0 | 3935 | |
michael@0 | 3936 | const T *text = aString + aRunStart; |
michael@0 | 3937 | uint32_t wordStart = 0; |
michael@0 | 3938 | uint32_t hash = 0; |
michael@0 | 3939 | bool wordIs8Bit = true; |
michael@0 | 3940 | int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit(); |
michael@0 | 3941 | |
michael@0 | 3942 | T nextCh = text[0]; |
michael@0 | 3943 | for (uint32_t i = 0; i <= aRunLength; ++i) { |
michael@0 | 3944 | T ch = nextCh; |
michael@0 | 3945 | nextCh = (i < aRunLength - 1) ? text[i + 1] : '\n'; |
michael@0 | 3946 | bool boundary = IsBoundarySpace(ch, nextCh); |
michael@0 | 3947 | bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch); |
michael@0 | 3948 | uint32_t length = i - wordStart; |
michael@0 | 3949 | |
michael@0 | 3950 | // break into separate ShapedWords when we hit an invalid char, |
michael@0 | 3951 | // or a boundary space (always handled individually), |
michael@0 | 3952 | // or the first non-space after a space |
michael@0 | 3953 | if (!boundary && !invalid) { |
michael@0 | 3954 | if (!IsChar8Bit(ch)) { |
michael@0 | 3955 | wordIs8Bit = false; |
michael@0 | 3956 | } |
michael@0 | 3957 | // include this character in the hash, and move on to next |
michael@0 | 3958 | hash = HashMix(hash, ch); |
michael@0 | 3959 | continue; |
michael@0 | 3960 | } |
michael@0 | 3961 | |
michael@0 | 3962 | // We've decided to break here (i.e. we're at the end of a "word"); |
michael@0 | 3963 | // shape the word and add it to the textrun. |
michael@0 | 3964 | // For words longer than the limit, we don't use the |
michael@0 | 3965 | // font's word cache but just shape directly into the textrun. |
michael@0 | 3966 | if (length > wordCacheCharLimit) { |
michael@0 | 3967 | TEXT_PERF_INCR(tp, wordCacheLong); |
michael@0 | 3968 | bool ok = ShapeFragmentWithoutWordCache(aContext, |
michael@0 | 3969 | text + wordStart, |
michael@0 | 3970 | aRunStart + wordStart, |
michael@0 | 3971 | length, |
michael@0 | 3972 | aRunScript, |
michael@0 | 3973 | aTextRun); |
michael@0 | 3974 | if (!ok) { |
michael@0 | 3975 | return false; |
michael@0 | 3976 | } |
michael@0 | 3977 | } else if (length > 0) { |
michael@0 | 3978 | uint32_t wordFlags = flags; |
michael@0 | 3979 | // in the 8-bit version of this method, TEXT_IS_8BIT was |
michael@0 | 3980 | // already set as part of |flags|, so no need for a per-word |
michael@0 | 3981 | // adjustment here |
michael@0 | 3982 | if (sizeof(T) == sizeof(char16_t)) { |
michael@0 | 3983 | if (wordIs8Bit) { |
michael@0 | 3984 | wordFlags |= gfxTextRunFactory::TEXT_IS_8BIT; |
michael@0 | 3985 | } |
michael@0 | 3986 | } |
michael@0 | 3987 | gfxShapedWord *sw = GetShapedWord(aContext, |
michael@0 | 3988 | text + wordStart, length, |
michael@0 | 3989 | hash, aRunScript, |
michael@0 | 3990 | appUnitsPerDevUnit, |
michael@0 | 3991 | wordFlags, tp); |
michael@0 | 3992 | if (sw) { |
michael@0 | 3993 | aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart); |
michael@0 | 3994 | } else { |
michael@0 | 3995 | return false; // failed, presumably out of memory? |
michael@0 | 3996 | } |
michael@0 | 3997 | } |
michael@0 | 3998 | |
michael@0 | 3999 | if (boundary) { |
michael@0 | 4000 | // word was terminated by a space: add that to the textrun |
michael@0 | 4001 | if (!aTextRun->SetSpaceGlyphIfSimple(this, aContext, |
michael@0 | 4002 | aRunStart + i, ch)) |
michael@0 | 4003 | { |
michael@0 | 4004 | static const uint8_t space = ' '; |
michael@0 | 4005 | gfxShapedWord *sw = |
michael@0 | 4006 | GetShapedWord(aContext, |
michael@0 | 4007 | &space, 1, |
michael@0 | 4008 | HashMix(0, ' '), aRunScript, |
michael@0 | 4009 | appUnitsPerDevUnit, |
michael@0 | 4010 | flags | gfxTextRunFactory::TEXT_IS_8BIT, tp); |
michael@0 | 4011 | if (sw) { |
michael@0 | 4012 | aTextRun->CopyGlyphDataFrom(sw, aRunStart + i); |
michael@0 | 4013 | } else { |
michael@0 | 4014 | return false; |
michael@0 | 4015 | } |
michael@0 | 4016 | } |
michael@0 | 4017 | hash = 0; |
michael@0 | 4018 | wordStart = i + 1; |
michael@0 | 4019 | wordIs8Bit = true; |
michael@0 | 4020 | continue; |
michael@0 | 4021 | } |
michael@0 | 4022 | |
michael@0 | 4023 | if (i == aRunLength) { |
michael@0 | 4024 | break; |
michael@0 | 4025 | } |
michael@0 | 4026 | |
michael@0 | 4027 | NS_ASSERTION(invalid, |
michael@0 | 4028 | "how did we get here except via an invalid char?"); |
michael@0 | 4029 | |
michael@0 | 4030 | // word was terminated by an invalid char: skip it, |
michael@0 | 4031 | // unless it's a control char that we want to show as a hexbox, |
michael@0 | 4032 | // but record where TAB or NEWLINE occur |
michael@0 | 4033 | if (ch == '\t') { |
michael@0 | 4034 | aTextRun->SetIsTab(aRunStart + i); |
michael@0 | 4035 | } else if (ch == '\n') { |
michael@0 | 4036 | aTextRun->SetIsNewline(aRunStart + i); |
michael@0 | 4037 | } else if (IsInvalidControlChar(ch) && |
michael@0 | 4038 | !(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS)) { |
michael@0 | 4039 | aTextRun->SetMissingGlyph(aRunStart + i, ch, this); |
michael@0 | 4040 | } |
michael@0 | 4041 | |
michael@0 | 4042 | hash = 0; |
michael@0 | 4043 | wordStart = i + 1; |
michael@0 | 4044 | wordIs8Bit = true; |
michael@0 | 4045 | } |
michael@0 | 4046 | |
michael@0 | 4047 | return true; |
michael@0 | 4048 | } |
michael@0 | 4049 | |
michael@0 | 4050 | gfxGlyphExtents * |
michael@0 | 4051 | gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) { |
michael@0 | 4052 | uint32_t i, count = mGlyphExtentsArray.Length(); |
michael@0 | 4053 | for (i = 0; i < count; ++i) { |
michael@0 | 4054 | if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit) |
michael@0 | 4055 | return mGlyphExtentsArray[i]; |
michael@0 | 4056 | } |
michael@0 | 4057 | gfxGlyphExtents *glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit); |
michael@0 | 4058 | if (glyphExtents) { |
michael@0 | 4059 | mGlyphExtentsArray.AppendElement(glyphExtents); |
michael@0 | 4060 | // Initialize the extents of a space glyph, assuming that spaces don't |
michael@0 | 4061 | // render anything! |
michael@0 | 4062 | glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0); |
michael@0 | 4063 | } |
michael@0 | 4064 | return glyphExtents; |
michael@0 | 4065 | } |
michael@0 | 4066 | |
michael@0 | 4067 | void |
michael@0 | 4068 | gfxFont::SetupGlyphExtents(gfxContext *aContext, uint32_t aGlyphID, bool aNeedTight, |
michael@0 | 4069 | gfxGlyphExtents *aExtents) |
michael@0 | 4070 | { |
michael@0 | 4071 | gfxContextMatrixAutoSaveRestore matrixRestore(aContext); |
michael@0 | 4072 | aContext->IdentityMatrix(); |
michael@0 | 4073 | |
michael@0 | 4074 | gfxRect svgBounds; |
michael@0 | 4075 | if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) && |
michael@0 | 4076 | mFontEntry->GetSVGGlyphExtents(aContext, aGlyphID, &svgBounds)) { |
michael@0 | 4077 | gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit(); |
michael@0 | 4078 | aExtents->SetTightGlyphExtents(aGlyphID, |
michael@0 | 4079 | gfxRect(svgBounds.x * d2a, |
michael@0 | 4080 | svgBounds.y * d2a, |
michael@0 | 4081 | svgBounds.width * d2a, |
michael@0 | 4082 | svgBounds.height * d2a)); |
michael@0 | 4083 | return; |
michael@0 | 4084 | } |
michael@0 | 4085 | |
michael@0 | 4086 | cairo_glyph_t glyph; |
michael@0 | 4087 | glyph.index = aGlyphID; |
michael@0 | 4088 | glyph.x = 0; |
michael@0 | 4089 | glyph.y = 0; |
michael@0 | 4090 | cairo_text_extents_t extents; |
michael@0 | 4091 | cairo_glyph_extents(aContext->GetCairo(), &glyph, 1, &extents); |
michael@0 | 4092 | |
michael@0 | 4093 | const Metrics& fontMetrics = GetMetrics(); |
michael@0 | 4094 | int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit(); |
michael@0 | 4095 | if (!aNeedTight && extents.x_bearing >= 0 && |
michael@0 | 4096 | extents.y_bearing >= -fontMetrics.maxAscent && |
michael@0 | 4097 | extents.height + extents.y_bearing <= fontMetrics.maxDescent) { |
michael@0 | 4098 | uint32_t appUnitsWidth = |
michael@0 | 4099 | uint32_t(ceil((extents.x_bearing + extents.width)*appUnitsPerDevUnit)); |
michael@0 | 4100 | if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) { |
michael@0 | 4101 | aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, uint16_t(appUnitsWidth)); |
michael@0 | 4102 | return; |
michael@0 | 4103 | } |
michael@0 | 4104 | } |
michael@0 | 4105 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 4106 | if (!aNeedTight) { |
michael@0 | 4107 | ++gGlyphExtentsSetupFallBackToTight; |
michael@0 | 4108 | } |
michael@0 | 4109 | #endif |
michael@0 | 4110 | |
michael@0 | 4111 | gfxFloat d2a = appUnitsPerDevUnit; |
michael@0 | 4112 | gfxRect bounds(extents.x_bearing*d2a, extents.y_bearing*d2a, |
michael@0 | 4113 | extents.width*d2a, extents.height*d2a); |
michael@0 | 4114 | aExtents->SetTightGlyphExtents(aGlyphID, bounds); |
michael@0 | 4115 | } |
michael@0 | 4116 | |
michael@0 | 4117 | // Try to initialize font metrics by reading sfnt tables directly; |
michael@0 | 4118 | // set mIsValid=TRUE and return TRUE on success. |
michael@0 | 4119 | // Return FALSE if the gfxFontEntry subclass does not |
michael@0 | 4120 | // implement GetFontTable(), or for non-sfnt fonts where tables are |
michael@0 | 4121 | // not available. |
michael@0 | 4122 | // If this returns TRUE without setting the mIsValid flag, then we -did- |
michael@0 | 4123 | // apparently find an sfnt, but it was too broken to be used. |
michael@0 | 4124 | bool |
michael@0 | 4125 | gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics) |
michael@0 | 4126 | { |
michael@0 | 4127 | mIsValid = false; // font is NOT valid in case of early return |
michael@0 | 4128 | |
michael@0 | 4129 | const uint32_t kHheaTableTag = TRUETYPE_TAG('h','h','e','a'); |
michael@0 | 4130 | const uint32_t kPostTableTag = TRUETYPE_TAG('p','o','s','t'); |
michael@0 | 4131 | const uint32_t kOS_2TableTag = TRUETYPE_TAG('O','S','/','2'); |
michael@0 | 4132 | |
michael@0 | 4133 | uint32_t len; |
michael@0 | 4134 | |
michael@0 | 4135 | if (mFUnitsConvFactor == 0.0) { |
michael@0 | 4136 | // If the conversion factor from FUnits is not yet set, |
michael@0 | 4137 | // get the unitsPerEm from the 'head' table via the font entry |
michael@0 | 4138 | uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm(); |
michael@0 | 4139 | if (unitsPerEm == gfxFontEntry::kInvalidUPEM) { |
michael@0 | 4140 | return false; |
michael@0 | 4141 | } |
michael@0 | 4142 | mFUnitsConvFactor = mAdjustedSize / unitsPerEm; |
michael@0 | 4143 | } |
michael@0 | 4144 | |
michael@0 | 4145 | // 'hhea' table is required to get vertical extents |
michael@0 | 4146 | gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag); |
michael@0 | 4147 | if (!hheaTable) { |
michael@0 | 4148 | return false; // no 'hhea' table -> not an sfnt |
michael@0 | 4149 | } |
michael@0 | 4150 | const HheaTable* hhea = |
michael@0 | 4151 | reinterpret_cast<const HheaTable*>(hb_blob_get_data(hheaTable, &len)); |
michael@0 | 4152 | if (len < sizeof(HheaTable)) { |
michael@0 | 4153 | return false; |
michael@0 | 4154 | } |
michael@0 | 4155 | |
michael@0 | 4156 | #define SET_UNSIGNED(field,src) aMetrics.field = uint16_t(src) * mFUnitsConvFactor |
michael@0 | 4157 | #define SET_SIGNED(field,src) aMetrics.field = int16_t(src) * mFUnitsConvFactor |
michael@0 | 4158 | |
michael@0 | 4159 | SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax); |
michael@0 | 4160 | SET_SIGNED(maxAscent, hhea->ascender); |
michael@0 | 4161 | SET_SIGNED(maxDescent, -int16_t(hhea->descender)); |
michael@0 | 4162 | SET_SIGNED(externalLeading, hhea->lineGap); |
michael@0 | 4163 | |
michael@0 | 4164 | // 'post' table is required for underline metrics |
michael@0 | 4165 | gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag); |
michael@0 | 4166 | if (!postTable) { |
michael@0 | 4167 | return true; // no 'post' table -> sfnt is not valid |
michael@0 | 4168 | } |
michael@0 | 4169 | const PostTable *post = |
michael@0 | 4170 | reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len)); |
michael@0 | 4171 | if (len < offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) { |
michael@0 | 4172 | return true; // bad post table -> sfnt is not valid |
michael@0 | 4173 | } |
michael@0 | 4174 | |
michael@0 | 4175 | SET_SIGNED(underlineOffset, post->underlinePosition); |
michael@0 | 4176 | SET_UNSIGNED(underlineSize, post->underlineThickness); |
michael@0 | 4177 | |
michael@0 | 4178 | // 'OS/2' table is optional, if not found we'll estimate xHeight |
michael@0 | 4179 | // and aveCharWidth by measuring glyphs |
michael@0 | 4180 | gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag); |
michael@0 | 4181 | if (os2Table) { |
michael@0 | 4182 | const OS2Table *os2 = |
michael@0 | 4183 | reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len)); |
michael@0 | 4184 | if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) && |
michael@0 | 4185 | uint16_t(os2->version) >= 2) { |
michael@0 | 4186 | // version 2 and later includes the x-height field |
michael@0 | 4187 | SET_SIGNED(xHeight, os2->sxHeight); |
michael@0 | 4188 | // Abs because of negative xHeight seen in Kokonor (Tibetan) font |
michael@0 | 4189 | aMetrics.xHeight = Abs(aMetrics.xHeight); |
michael@0 | 4190 | } |
michael@0 | 4191 | // this should always be present in any valid OS/2 of any version |
michael@0 | 4192 | if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) { |
michael@0 | 4193 | SET_SIGNED(aveCharWidth, os2->xAvgCharWidth); |
michael@0 | 4194 | SET_SIGNED(subscriptOffset, os2->ySubscriptYOffset); |
michael@0 | 4195 | SET_SIGNED(superscriptOffset, os2->ySuperscriptYOffset); |
michael@0 | 4196 | SET_SIGNED(strikeoutSize, os2->yStrikeoutSize); |
michael@0 | 4197 | SET_SIGNED(strikeoutOffset, os2->yStrikeoutPosition); |
michael@0 | 4198 | |
michael@0 | 4199 | // for fonts with USE_TYPO_METRICS set in the fsSelection field, |
michael@0 | 4200 | // and for all OpenType math fonts (having a 'MATH' table), |
michael@0 | 4201 | // let the OS/2 sTypo* metrics override those from the hhea table |
michael@0 | 4202 | // (see http://www.microsoft.com/typography/otspec/os2.htm#fss) |
michael@0 | 4203 | const uint16_t kUseTypoMetricsMask = 1 << 7; |
michael@0 | 4204 | if ((uint16_t(os2->fsSelection) & kUseTypoMetricsMask) || |
michael@0 | 4205 | mFontEntry->HasFontTable(TRUETYPE_TAG('M','A','T','H'))) { |
michael@0 | 4206 | SET_SIGNED(maxAscent, os2->sTypoAscender); |
michael@0 | 4207 | SET_SIGNED(maxDescent, - int16_t(os2->sTypoDescender)); |
michael@0 | 4208 | SET_SIGNED(externalLeading, os2->sTypoLineGap); |
michael@0 | 4209 | } |
michael@0 | 4210 | } |
michael@0 | 4211 | } |
michael@0 | 4212 | |
michael@0 | 4213 | mIsValid = true; |
michael@0 | 4214 | |
michael@0 | 4215 | return true; |
michael@0 | 4216 | } |
michael@0 | 4217 | |
michael@0 | 4218 | static double |
michael@0 | 4219 | RoundToNearestMultiple(double aValue, double aFraction) |
michael@0 | 4220 | { |
michael@0 | 4221 | return floor(aValue/aFraction + 0.5) * aFraction; |
michael@0 | 4222 | } |
michael@0 | 4223 | |
michael@0 | 4224 | void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics) |
michael@0 | 4225 | { |
michael@0 | 4226 | aMetrics.maxAscent = |
michael@0 | 4227 | ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1/1024.0)); |
michael@0 | 4228 | aMetrics.maxDescent = |
michael@0 | 4229 | ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1/1024.0)); |
michael@0 | 4230 | |
michael@0 | 4231 | if (aMetrics.xHeight <= 0) { |
michael@0 | 4232 | // only happens if we couldn't find either font metrics |
michael@0 | 4233 | // or a char to measure; |
michael@0 | 4234 | // pick an arbitrary value that's better than zero |
michael@0 | 4235 | aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR; |
michael@0 | 4236 | } |
michael@0 | 4237 | |
michael@0 | 4238 | aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent; |
michael@0 | 4239 | |
michael@0 | 4240 | if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) { |
michael@0 | 4241 | aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight; |
michael@0 | 4242 | } else { |
michael@0 | 4243 | aMetrics.internalLeading = 0.0; |
michael@0 | 4244 | } |
michael@0 | 4245 | |
michael@0 | 4246 | aMetrics.emAscent = aMetrics.maxAscent * aMetrics.emHeight |
michael@0 | 4247 | / aMetrics.maxHeight; |
michael@0 | 4248 | aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent; |
michael@0 | 4249 | |
michael@0 | 4250 | if (GetFontEntry()->IsFixedPitch()) { |
michael@0 | 4251 | // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger |
michael@0 | 4252 | // advance than the average character width... this forces |
michael@0 | 4253 | // those fonts to be recognized like fixed pitch fonts by layout. |
michael@0 | 4254 | aMetrics.maxAdvance = aMetrics.aveCharWidth; |
michael@0 | 4255 | } |
michael@0 | 4256 | |
michael@0 | 4257 | if (!aMetrics.subscriptOffset) { |
michael@0 | 4258 | aMetrics.subscriptOffset = aMetrics.xHeight; |
michael@0 | 4259 | } |
michael@0 | 4260 | if (!aMetrics.superscriptOffset) { |
michael@0 | 4261 | aMetrics.superscriptOffset = aMetrics.xHeight; |
michael@0 | 4262 | } |
michael@0 | 4263 | |
michael@0 | 4264 | if (!aMetrics.strikeoutOffset) { |
michael@0 | 4265 | aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5; |
michael@0 | 4266 | } |
michael@0 | 4267 | if (!aMetrics.strikeoutSize) { |
michael@0 | 4268 | aMetrics.strikeoutSize = aMetrics.underlineSize; |
michael@0 | 4269 | } |
michael@0 | 4270 | } |
michael@0 | 4271 | |
michael@0 | 4272 | void |
michael@0 | 4273 | gfxFont::SanitizeMetrics(gfxFont::Metrics *aMetrics, bool aIsBadUnderlineFont) |
michael@0 | 4274 | { |
michael@0 | 4275 | // Even if this font size is zero, this font is created with non-zero size. |
michael@0 | 4276 | // However, for layout and others, we should return the metrics of zero size font. |
michael@0 | 4277 | if (mStyle.size == 0.0) { |
michael@0 | 4278 | memset(aMetrics, 0, sizeof(gfxFont::Metrics)); |
michael@0 | 4279 | return; |
michael@0 | 4280 | } |
michael@0 | 4281 | |
michael@0 | 4282 | // MS (P)Gothic and MS (P)Mincho are not having suitable values in their super script offset. |
michael@0 | 4283 | // If the values are not suitable, we should use x-height instead of them. |
michael@0 | 4284 | // See https://bugzilla.mozilla.org/show_bug.cgi?id=353632 |
michael@0 | 4285 | if (aMetrics->superscriptOffset <= 0 || |
michael@0 | 4286 | aMetrics->superscriptOffset >= aMetrics->maxAscent) { |
michael@0 | 4287 | aMetrics->superscriptOffset = aMetrics->xHeight; |
michael@0 | 4288 | } |
michael@0 | 4289 | // And also checking the case of sub script offset. The old gfx for win has checked this too. |
michael@0 | 4290 | if (aMetrics->subscriptOffset <= 0 || |
michael@0 | 4291 | aMetrics->subscriptOffset >= aMetrics->maxAscent) { |
michael@0 | 4292 | aMetrics->subscriptOffset = aMetrics->xHeight; |
michael@0 | 4293 | } |
michael@0 | 4294 | |
michael@0 | 4295 | aMetrics->underlineSize = std::max(1.0, aMetrics->underlineSize); |
michael@0 | 4296 | aMetrics->strikeoutSize = std::max(1.0, aMetrics->strikeoutSize); |
michael@0 | 4297 | |
michael@0 | 4298 | aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -1.0); |
michael@0 | 4299 | |
michael@0 | 4300 | if (aMetrics->maxAscent < 1.0) { |
michael@0 | 4301 | // We cannot draw strikeout line and overline in the ascent... |
michael@0 | 4302 | aMetrics->underlineSize = 0; |
michael@0 | 4303 | aMetrics->underlineOffset = 0; |
michael@0 | 4304 | aMetrics->strikeoutSize = 0; |
michael@0 | 4305 | aMetrics->strikeoutOffset = 0; |
michael@0 | 4306 | return; |
michael@0 | 4307 | } |
michael@0 | 4308 | |
michael@0 | 4309 | /** |
michael@0 | 4310 | * Some CJK fonts have bad underline offset. Therefore, if this is such font, |
michael@0 | 4311 | * we need to lower the underline offset to bottom of *em* descent. |
michael@0 | 4312 | * However, if this is system font, we should not do this for the rendering compatibility with |
michael@0 | 4313 | * another application's UI on the platform. |
michael@0 | 4314 | * XXX Should not use this hack if the font size is too small? |
michael@0 | 4315 | * Such text cannot be read, this might be used for tight CSS rendering? (E.g., Acid2) |
michael@0 | 4316 | */ |
michael@0 | 4317 | if (!mStyle.systemFont && aIsBadUnderlineFont) { |
michael@0 | 4318 | // First, we need 2 pixels between baseline and underline at least. Because many CJK characters |
michael@0 | 4319 | // put their glyphs on the baseline, so, 1 pixel is too close for CJK characters. |
michael@0 | 4320 | aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -2.0); |
michael@0 | 4321 | |
michael@0 | 4322 | // Next, we put the underline to bottom of below of the descent space. |
michael@0 | 4323 | if (aMetrics->internalLeading + aMetrics->externalLeading > aMetrics->underlineSize) { |
michael@0 | 4324 | aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -aMetrics->emDescent); |
michael@0 | 4325 | } else { |
michael@0 | 4326 | aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, |
michael@0 | 4327 | aMetrics->underlineSize - aMetrics->emDescent); |
michael@0 | 4328 | } |
michael@0 | 4329 | } |
michael@0 | 4330 | // If underline positioned is too far from the text, descent position is preferred so that underline |
michael@0 | 4331 | // will stay within the boundary. |
michael@0 | 4332 | else if (aMetrics->underlineSize - aMetrics->underlineOffset > aMetrics->maxDescent) { |
michael@0 | 4333 | if (aMetrics->underlineSize > aMetrics->maxDescent) |
michael@0 | 4334 | aMetrics->underlineSize = std::max(aMetrics->maxDescent, 1.0); |
michael@0 | 4335 | // The max underlineOffset is 1px (the min underlineSize is 1px, and min maxDescent is 0px.) |
michael@0 | 4336 | aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent; |
michael@0 | 4337 | } |
michael@0 | 4338 | |
michael@0 | 4339 | // If strikeout line is overflowed from the ascent, the line should be resized and moved for |
michael@0 | 4340 | // that being in the ascent space. |
michael@0 | 4341 | // Note that the strikeoutOffset is *middle* of the strikeout line position. |
michael@0 | 4342 | gfxFloat halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5); |
michael@0 | 4343 | if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) { |
michael@0 | 4344 | if (aMetrics->strikeoutSize > aMetrics->maxAscent) { |
michael@0 | 4345 | aMetrics->strikeoutSize = std::max(aMetrics->maxAscent, 1.0); |
michael@0 | 4346 | halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5); |
michael@0 | 4347 | } |
michael@0 | 4348 | gfxFloat ascent = floor(aMetrics->maxAscent + 0.5); |
michael@0 | 4349 | aMetrics->strikeoutOffset = std::max(halfOfStrikeoutSize, ascent / 2.0); |
michael@0 | 4350 | } |
michael@0 | 4351 | |
michael@0 | 4352 | // If overline is larger than the ascent, the line should be resized. |
michael@0 | 4353 | if (aMetrics->underlineSize > aMetrics->maxAscent) { |
michael@0 | 4354 | aMetrics->underlineSize = aMetrics->maxAscent; |
michael@0 | 4355 | } |
michael@0 | 4356 | } |
michael@0 | 4357 | |
michael@0 | 4358 | gfxFloat |
michael@0 | 4359 | gfxFont::SynthesizeSpaceWidth(uint32_t aCh) |
michael@0 | 4360 | { |
michael@0 | 4361 | // return an appropriate width for various Unicode space characters |
michael@0 | 4362 | // that we "fake" if they're not actually present in the font; |
michael@0 | 4363 | // returns negative value if the char is not a known space. |
michael@0 | 4364 | switch (aCh) { |
michael@0 | 4365 | case 0x2000: // en quad |
michael@0 | 4366 | case 0x2002: return GetAdjustedSize() / 2; // en space |
michael@0 | 4367 | case 0x2001: // em quad |
michael@0 | 4368 | case 0x2003: return GetAdjustedSize(); // em space |
michael@0 | 4369 | case 0x2004: return GetAdjustedSize() / 3; // three-per-em space |
michael@0 | 4370 | case 0x2005: return GetAdjustedSize() / 4; // four-per-em space |
michael@0 | 4371 | case 0x2006: return GetAdjustedSize() / 6; // six-per-em space |
michael@0 | 4372 | case 0x2007: return GetMetrics().zeroOrAveCharWidth; // figure space |
michael@0 | 4373 | case 0x2008: return GetMetrics().spaceWidth; // punctuation space |
michael@0 | 4374 | case 0x2009: return GetAdjustedSize() / 5; // thin space |
michael@0 | 4375 | case 0x200a: return GetAdjustedSize() / 10; // hair space |
michael@0 | 4376 | case 0x202f: return GetAdjustedSize() / 5; // narrow no-break space |
michael@0 | 4377 | default: return -1.0; |
michael@0 | 4378 | } |
michael@0 | 4379 | } |
michael@0 | 4380 | |
michael@0 | 4381 | /*static*/ size_t |
michael@0 | 4382 | gfxFont::WordCacheEntrySizeOfExcludingThis(CacheHashEntry* aHashEntry, |
michael@0 | 4383 | MallocSizeOf aMallocSizeOf, |
michael@0 | 4384 | void* aUserArg) |
michael@0 | 4385 | { |
michael@0 | 4386 | return aMallocSizeOf(aHashEntry->mShapedWord.get()); |
michael@0 | 4387 | } |
michael@0 | 4388 | |
michael@0 | 4389 | void |
michael@0 | 4390 | gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf, |
michael@0 | 4391 | FontCacheSizes* aSizes) const |
michael@0 | 4392 | { |
michael@0 | 4393 | for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) { |
michael@0 | 4394 | aSizes->mFontInstances += |
michael@0 | 4395 | mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf); |
michael@0 | 4396 | } |
michael@0 | 4397 | if (mWordCache) { |
michael@0 | 4398 | aSizes->mShapedWords += |
michael@0 | 4399 | mWordCache->SizeOfExcludingThis(WordCacheEntrySizeOfExcludingThis, |
michael@0 | 4400 | aMallocSizeOf); |
michael@0 | 4401 | } |
michael@0 | 4402 | } |
michael@0 | 4403 | |
michael@0 | 4404 | void |
michael@0 | 4405 | gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf, |
michael@0 | 4406 | FontCacheSizes* aSizes) const |
michael@0 | 4407 | { |
michael@0 | 4408 | aSizes->mFontInstances += aMallocSizeOf(this); |
michael@0 | 4409 | AddSizeOfExcludingThis(aMallocSizeOf, aSizes); |
michael@0 | 4410 | } |
michael@0 | 4411 | |
michael@0 | 4412 | void |
michael@0 | 4413 | gfxFont::AddGlyphChangeObserver(GlyphChangeObserver *aObserver) |
michael@0 | 4414 | { |
michael@0 | 4415 | if (!mGlyphChangeObservers) { |
michael@0 | 4416 | mGlyphChangeObservers = new nsTHashtable<nsPtrHashKey<GlyphChangeObserver> >; |
michael@0 | 4417 | } |
michael@0 | 4418 | mGlyphChangeObservers->PutEntry(aObserver); |
michael@0 | 4419 | } |
michael@0 | 4420 | |
michael@0 | 4421 | void |
michael@0 | 4422 | gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver) |
michael@0 | 4423 | { |
michael@0 | 4424 | NS_ASSERTION(mGlyphChangeObservers, "No observers registered"); |
michael@0 | 4425 | NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), "Observer not registered"); |
michael@0 | 4426 | mGlyphChangeObservers->RemoveEntry(aObserver); |
michael@0 | 4427 | } |
michael@0 | 4428 | |
michael@0 | 4429 | gfxGlyphExtents::~gfxGlyphExtents() |
michael@0 | 4430 | { |
michael@0 | 4431 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 4432 | gGlyphExtentsWidthsTotalSize += |
michael@0 | 4433 | mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf); |
michael@0 | 4434 | gGlyphExtentsCount++; |
michael@0 | 4435 | #endif |
michael@0 | 4436 | MOZ_COUNT_DTOR(gfxGlyphExtents); |
michael@0 | 4437 | } |
michael@0 | 4438 | |
michael@0 | 4439 | bool |
michael@0 | 4440 | gfxGlyphExtents::GetTightGlyphExtentsAppUnits(gfxFont *aFont, |
michael@0 | 4441 | gfxContext *aContext, uint32_t aGlyphID, gfxRect *aExtents) |
michael@0 | 4442 | { |
michael@0 | 4443 | HashEntry *entry = mTightGlyphExtents.GetEntry(aGlyphID); |
michael@0 | 4444 | if (!entry) { |
michael@0 | 4445 | if (!aContext) { |
michael@0 | 4446 | NS_WARNING("Could not get glyph extents (no aContext)"); |
michael@0 | 4447 | return false; |
michael@0 | 4448 | } |
michael@0 | 4449 | |
michael@0 | 4450 | if (aFont->SetupCairoFont(aContext)) { |
michael@0 | 4451 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 4452 | ++gGlyphExtentsSetupLazyTight; |
michael@0 | 4453 | #endif |
michael@0 | 4454 | aFont->SetupGlyphExtents(aContext, aGlyphID, true, this); |
michael@0 | 4455 | entry = mTightGlyphExtents.GetEntry(aGlyphID); |
michael@0 | 4456 | } |
michael@0 | 4457 | if (!entry) { |
michael@0 | 4458 | NS_WARNING("Could not get glyph extents"); |
michael@0 | 4459 | return false; |
michael@0 | 4460 | } |
michael@0 | 4461 | } |
michael@0 | 4462 | |
michael@0 | 4463 | *aExtents = gfxRect(entry->x, entry->y, entry->width, entry->height); |
michael@0 | 4464 | return true; |
michael@0 | 4465 | } |
michael@0 | 4466 | |
michael@0 | 4467 | gfxGlyphExtents::GlyphWidths::~GlyphWidths() |
michael@0 | 4468 | { |
michael@0 | 4469 | uint32_t i, count = mBlocks.Length(); |
michael@0 | 4470 | for (i = 0; i < count; ++i) { |
michael@0 | 4471 | uintptr_t bits = mBlocks[i]; |
michael@0 | 4472 | if (bits && !(bits & 0x1)) { |
michael@0 | 4473 | delete[] reinterpret_cast<uint16_t *>(bits); |
michael@0 | 4474 | } |
michael@0 | 4475 | } |
michael@0 | 4476 | } |
michael@0 | 4477 | |
michael@0 | 4478 | uint32_t |
michael@0 | 4479 | gfxGlyphExtents::GlyphWidths::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 4480 | { |
michael@0 | 4481 | uint32_t i; |
michael@0 | 4482 | uint32_t size = mBlocks.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 4483 | for (i = 0; i < mBlocks.Length(); ++i) { |
michael@0 | 4484 | uintptr_t bits = mBlocks[i]; |
michael@0 | 4485 | if (bits && !(bits & 0x1)) { |
michael@0 | 4486 | size += aMallocSizeOf(reinterpret_cast<void*>(bits)); |
michael@0 | 4487 | } |
michael@0 | 4488 | } |
michael@0 | 4489 | return size; |
michael@0 | 4490 | } |
michael@0 | 4491 | |
michael@0 | 4492 | void |
michael@0 | 4493 | gfxGlyphExtents::GlyphWidths::Set(uint32_t aGlyphID, uint16_t aWidth) |
michael@0 | 4494 | { |
michael@0 | 4495 | uint32_t block = aGlyphID >> BLOCK_SIZE_BITS; |
michael@0 | 4496 | uint32_t len = mBlocks.Length(); |
michael@0 | 4497 | if (block >= len) { |
michael@0 | 4498 | uintptr_t *elems = mBlocks.AppendElements(block + 1 - len); |
michael@0 | 4499 | if (!elems) |
michael@0 | 4500 | return; |
michael@0 | 4501 | memset(elems, 0, sizeof(uintptr_t)*(block + 1 - len)); |
michael@0 | 4502 | } |
michael@0 | 4503 | |
michael@0 | 4504 | uintptr_t bits = mBlocks[block]; |
michael@0 | 4505 | uint32_t glyphOffset = aGlyphID & (BLOCK_SIZE - 1); |
michael@0 | 4506 | if (!bits) { |
michael@0 | 4507 | mBlocks[block] = MakeSingle(glyphOffset, aWidth); |
michael@0 | 4508 | return; |
michael@0 | 4509 | } |
michael@0 | 4510 | |
michael@0 | 4511 | uint16_t *newBlock; |
michael@0 | 4512 | if (bits & 0x1) { |
michael@0 | 4513 | // Expand the block to a real block. We could avoid this by checking |
michael@0 | 4514 | // glyphOffset == GetGlyphOffset(bits), but that never happens so don't bother |
michael@0 | 4515 | newBlock = new uint16_t[BLOCK_SIZE]; |
michael@0 | 4516 | if (!newBlock) |
michael@0 | 4517 | return; |
michael@0 | 4518 | uint32_t i; |
michael@0 | 4519 | for (i = 0; i < BLOCK_SIZE; ++i) { |
michael@0 | 4520 | newBlock[i] = INVALID_WIDTH; |
michael@0 | 4521 | } |
michael@0 | 4522 | newBlock[GetGlyphOffset(bits)] = GetWidth(bits); |
michael@0 | 4523 | mBlocks[block] = reinterpret_cast<uintptr_t>(newBlock); |
michael@0 | 4524 | } else { |
michael@0 | 4525 | newBlock = reinterpret_cast<uint16_t *>(bits); |
michael@0 | 4526 | } |
michael@0 | 4527 | newBlock[glyphOffset] = aWidth; |
michael@0 | 4528 | } |
michael@0 | 4529 | |
michael@0 | 4530 | void |
michael@0 | 4531 | gfxGlyphExtents::SetTightGlyphExtents(uint32_t aGlyphID, const gfxRect& aExtentsAppUnits) |
michael@0 | 4532 | { |
michael@0 | 4533 | HashEntry *entry = mTightGlyphExtents.PutEntry(aGlyphID); |
michael@0 | 4534 | if (!entry) |
michael@0 | 4535 | return; |
michael@0 | 4536 | entry->x = aExtentsAppUnits.X(); |
michael@0 | 4537 | entry->y = aExtentsAppUnits.Y(); |
michael@0 | 4538 | entry->width = aExtentsAppUnits.Width(); |
michael@0 | 4539 | entry->height = aExtentsAppUnits.Height(); |
michael@0 | 4540 | } |
michael@0 | 4541 | |
michael@0 | 4542 | size_t |
michael@0 | 4543 | gfxGlyphExtents::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 4544 | { |
michael@0 | 4545 | return mContainedGlyphWidths.SizeOfExcludingThis(aMallocSizeOf) + |
michael@0 | 4546 | mTightGlyphExtents.SizeOfExcludingThis(nullptr, aMallocSizeOf); |
michael@0 | 4547 | } |
michael@0 | 4548 | |
michael@0 | 4549 | size_t |
michael@0 | 4550 | gfxGlyphExtents::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
michael@0 | 4551 | { |
michael@0 | 4552 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 4553 | } |
michael@0 | 4554 | |
michael@0 | 4555 | gfxFontGroup::gfxFontGroup(const nsAString& aFamilies, |
michael@0 | 4556 | const gfxFontStyle *aStyle, |
michael@0 | 4557 | gfxUserFontSet *aUserFontSet) |
michael@0 | 4558 | : mFamilies(aFamilies) |
michael@0 | 4559 | , mStyle(*aStyle) |
michael@0 | 4560 | , mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET) |
michael@0 | 4561 | , mHyphenWidth(-1) |
michael@0 | 4562 | , mUserFontSet(aUserFontSet) |
michael@0 | 4563 | , mTextPerf(nullptr) |
michael@0 | 4564 | , mPageLang(gfxPlatform::GetFontPrefLangFor(aStyle->language)) |
michael@0 | 4565 | , mSkipDrawing(false) |
michael@0 | 4566 | { |
michael@0 | 4567 | // We don't use SetUserFontSet() here, as we want to unconditionally call |
michael@0 | 4568 | // BuildFontList() rather than only do UpdateFontList() if it changed. |
michael@0 | 4569 | mCurrGeneration = GetGeneration(); |
michael@0 | 4570 | BuildFontList(); |
michael@0 | 4571 | } |
michael@0 | 4572 | |
michael@0 | 4573 | void |
michael@0 | 4574 | gfxFontGroup::BuildFontList() |
michael@0 | 4575 | { |
michael@0 | 4576 | // "#if" to be removed once all platforms are moved to gfxPlatformFontList interface |
michael@0 | 4577 | // and subclasses of gfxFontGroup eliminated |
michael@0 | 4578 | #if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID) |
michael@0 | 4579 | ForEachFont(FindPlatformFont, this); |
michael@0 | 4580 | |
michael@0 | 4581 | if (mFonts.Length() == 0) { |
michael@0 | 4582 | bool needsBold; |
michael@0 | 4583 | gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList(); |
michael@0 | 4584 | gfxFontFamily *defaultFamily = pfl->GetDefaultFont(&mStyle); |
michael@0 | 4585 | NS_ASSERTION(defaultFamily, |
michael@0 | 4586 | "invalid default font returned by GetDefaultFont"); |
michael@0 | 4587 | |
michael@0 | 4588 | if (defaultFamily) { |
michael@0 | 4589 | gfxFontEntry *fe = defaultFamily->FindFontForStyle(mStyle, |
michael@0 | 4590 | needsBold); |
michael@0 | 4591 | if (fe) { |
michael@0 | 4592 | nsRefPtr<gfxFont> font = fe->FindOrMakeFont(&mStyle, |
michael@0 | 4593 | needsBold); |
michael@0 | 4594 | if (font) { |
michael@0 | 4595 | mFonts.AppendElement(FamilyFace(defaultFamily, font)); |
michael@0 | 4596 | } |
michael@0 | 4597 | } |
michael@0 | 4598 | } |
michael@0 | 4599 | |
michael@0 | 4600 | if (mFonts.Length() == 0) { |
michael@0 | 4601 | // Try for a "font of last resort...." |
michael@0 | 4602 | // Because an empty font list would be Really Bad for later code |
michael@0 | 4603 | // that assumes it will be able to get valid metrics for layout, |
michael@0 | 4604 | // just look for the first usable font and put in the list. |
michael@0 | 4605 | // (see bug 554544) |
michael@0 | 4606 | nsAutoTArray<nsRefPtr<gfxFontFamily>,200> families; |
michael@0 | 4607 | pfl->GetFontFamilyList(families); |
michael@0 | 4608 | uint32_t count = families.Length(); |
michael@0 | 4609 | for (uint32_t i = 0; i < count; ++i) { |
michael@0 | 4610 | gfxFontEntry *fe = families[i]->FindFontForStyle(mStyle, |
michael@0 | 4611 | needsBold); |
michael@0 | 4612 | if (fe) { |
michael@0 | 4613 | nsRefPtr<gfxFont> font = fe->FindOrMakeFont(&mStyle, |
michael@0 | 4614 | needsBold); |
michael@0 | 4615 | if (font) { |
michael@0 | 4616 | mFonts.AppendElement(FamilyFace(families[i], font)); |
michael@0 | 4617 | break; |
michael@0 | 4618 | } |
michael@0 | 4619 | } |
michael@0 | 4620 | } |
michael@0 | 4621 | } |
michael@0 | 4622 | |
michael@0 | 4623 | if (mFonts.Length() == 0) { |
michael@0 | 4624 | // an empty font list at this point is fatal; we're not going to |
michael@0 | 4625 | // be able to do even the most basic layout operations |
michael@0 | 4626 | char msg[256]; // CHECK buffer length if revising message below |
michael@0 | 4627 | sprintf(msg, "unable to find a usable font (%.220s)", |
michael@0 | 4628 | NS_ConvertUTF16toUTF8(mFamilies).get()); |
michael@0 | 4629 | NS_RUNTIMEABORT(msg); |
michael@0 | 4630 | } |
michael@0 | 4631 | } |
michael@0 | 4632 | |
michael@0 | 4633 | if (!mStyle.systemFont) { |
michael@0 | 4634 | uint32_t count = mFonts.Length(); |
michael@0 | 4635 | for (uint32_t i = 0; i < count; ++i) { |
michael@0 | 4636 | gfxFont* font = mFonts[i].Font(); |
michael@0 | 4637 | if (font->GetFontEntry()->mIsBadUnderlineFont) { |
michael@0 | 4638 | gfxFloat first = mFonts[0].Font()->GetMetrics().underlineOffset; |
michael@0 | 4639 | gfxFloat bad = font->GetMetrics().underlineOffset; |
michael@0 | 4640 | mUnderlineOffset = std::min(first, bad); |
michael@0 | 4641 | break; |
michael@0 | 4642 | } |
michael@0 | 4643 | } |
michael@0 | 4644 | } |
michael@0 | 4645 | #endif |
michael@0 | 4646 | } |
michael@0 | 4647 | |
michael@0 | 4648 | bool |
michael@0 | 4649 | gfxFontGroup::FindPlatformFont(const nsAString& aName, |
michael@0 | 4650 | const nsACString& aGenericName, |
michael@0 | 4651 | bool aUseFontSet, |
michael@0 | 4652 | void *aClosure) |
michael@0 | 4653 | { |
michael@0 | 4654 | gfxFontGroup *fontGroup = static_cast<gfxFontGroup*>(aClosure); |
michael@0 | 4655 | const gfxFontStyle *fontStyle = fontGroup->GetStyle(); |
michael@0 | 4656 | |
michael@0 | 4657 | bool needsBold; |
michael@0 | 4658 | gfxFontFamily *family = nullptr; |
michael@0 | 4659 | gfxFontEntry *fe = nullptr; |
michael@0 | 4660 | |
michael@0 | 4661 | if (aUseFontSet) { |
michael@0 | 4662 | // First, look up in the user font set... |
michael@0 | 4663 | // If the fontSet matches the family, we must not look for a platform |
michael@0 | 4664 | // font of the same name, even if we fail to actually get a fontEntry |
michael@0 | 4665 | // here; we'll fall back to the next name in the CSS font-family list. |
michael@0 | 4666 | gfxUserFontSet *fs = fontGroup->GetUserFontSet(); |
michael@0 | 4667 | if (fs) { |
michael@0 | 4668 | // If the fontSet matches the family, but the font has not yet finished |
michael@0 | 4669 | // loading (nor has its load timeout fired), the fontGroup should wait |
michael@0 | 4670 | // for the download, and not actually draw its text yet. |
michael@0 | 4671 | family = fs->GetFamily(aName); |
michael@0 | 4672 | if (family) { |
michael@0 | 4673 | bool waitForUserFont = false; |
michael@0 | 4674 | fe = fs->FindFontEntry(family, *fontStyle, |
michael@0 | 4675 | needsBold, waitForUserFont); |
michael@0 | 4676 | if (!fe && waitForUserFont) { |
michael@0 | 4677 | fontGroup->mSkipDrawing = true; |
michael@0 | 4678 | } |
michael@0 | 4679 | } |
michael@0 | 4680 | } |
michael@0 | 4681 | } |
michael@0 | 4682 | |
michael@0 | 4683 | // Not known in the user font set ==> check system fonts |
michael@0 | 4684 | // XXX: Fallback is bad.. |
michael@0 | 4685 | if (!family) { |
michael@0 | 4686 | gfxPlatformFontList *fontList = gfxPlatformFontList::PlatformFontList(); |
michael@0 | 4687 | family = fontList->FindFamily(aName); |
michael@0 | 4688 | if (family) { |
michael@0 | 4689 | fe = family->FindFontForStyle(*fontStyle, needsBold); |
michael@0 | 4690 | } |
michael@0 | 4691 | } |
michael@0 | 4692 | |
michael@0 | 4693 | // add to the font group, unless it's already there |
michael@0 | 4694 | if (fe && !fontGroup->HasFont(fe)) { |
michael@0 | 4695 | nsRefPtr<gfxFont> font = fe->FindOrMakeFont(fontStyle, needsBold); |
michael@0 | 4696 | if (font) { |
michael@0 | 4697 | fontGroup->mFonts.AppendElement(FamilyFace(family, font)); |
michael@0 | 4698 | } |
michael@0 | 4699 | } |
michael@0 | 4700 | |
michael@0 | 4701 | return true; |
michael@0 | 4702 | } |
michael@0 | 4703 | |
michael@0 | 4704 | bool |
michael@0 | 4705 | gfxFontGroup::HasFont(const gfxFontEntry *aFontEntry) |
michael@0 | 4706 | { |
michael@0 | 4707 | uint32_t count = mFonts.Length(); |
michael@0 | 4708 | for (uint32_t i = 0; i < count; ++i) { |
michael@0 | 4709 | if (mFonts[i].Font()->GetFontEntry() == aFontEntry) |
michael@0 | 4710 | return true; |
michael@0 | 4711 | } |
michael@0 | 4712 | return false; |
michael@0 | 4713 | } |
michael@0 | 4714 | |
michael@0 | 4715 | gfxFontGroup::~gfxFontGroup() |
michael@0 | 4716 | { |
michael@0 | 4717 | mFonts.Clear(); |
michael@0 | 4718 | } |
michael@0 | 4719 | |
michael@0 | 4720 | gfxFontGroup * |
michael@0 | 4721 | gfxFontGroup::Copy(const gfxFontStyle *aStyle) |
michael@0 | 4722 | { |
michael@0 | 4723 | gfxFontGroup *fg = new gfxFontGroup(mFamilies, aStyle, mUserFontSet); |
michael@0 | 4724 | fg->SetTextPerfMetrics(mTextPerf); |
michael@0 | 4725 | return fg; |
michael@0 | 4726 | } |
michael@0 | 4727 | |
michael@0 | 4728 | bool |
michael@0 | 4729 | gfxFontGroup::IsInvalidChar(uint8_t ch) |
michael@0 | 4730 | { |
michael@0 | 4731 | return ((ch & 0x7f) < 0x20 || ch == 0x7f); |
michael@0 | 4732 | } |
michael@0 | 4733 | |
michael@0 | 4734 | bool |
michael@0 | 4735 | gfxFontGroup::IsInvalidChar(char16_t ch) |
michael@0 | 4736 | { |
michael@0 | 4737 | // All printable 7-bit ASCII values are OK |
michael@0 | 4738 | if (ch >= ' ' && ch < 0x7f) { |
michael@0 | 4739 | return false; |
michael@0 | 4740 | } |
michael@0 | 4741 | // No point in sending non-printing control chars through font shaping |
michael@0 | 4742 | if (ch <= 0x9f) { |
michael@0 | 4743 | return true; |
michael@0 | 4744 | } |
michael@0 | 4745 | return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ && |
michael@0 | 4746 | (ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/)) || |
michael@0 | 4747 | IsBidiControl(ch)); |
michael@0 | 4748 | } |
michael@0 | 4749 | |
michael@0 | 4750 | bool |
michael@0 | 4751 | gfxFontGroup::ForEachFont(FontCreationCallback fc, |
michael@0 | 4752 | void *closure) |
michael@0 | 4753 | { |
michael@0 | 4754 | return ForEachFontInternal(mFamilies, mStyle.language, |
michael@0 | 4755 | true, true, true, fc, closure); |
michael@0 | 4756 | } |
michael@0 | 4757 | |
michael@0 | 4758 | bool |
michael@0 | 4759 | gfxFontGroup::ForEachFont(const nsAString& aFamilies, |
michael@0 | 4760 | nsIAtom *aLanguage, |
michael@0 | 4761 | FontCreationCallback fc, |
michael@0 | 4762 | void *closure) |
michael@0 | 4763 | { |
michael@0 | 4764 | return ForEachFontInternal(aFamilies, aLanguage, |
michael@0 | 4765 | false, true, true, fc, closure); |
michael@0 | 4766 | } |
michael@0 | 4767 | |
michael@0 | 4768 | struct ResolveData { |
michael@0 | 4769 | ResolveData(gfxFontGroup::FontCreationCallback aCallback, |
michael@0 | 4770 | nsACString& aGenericFamily, |
michael@0 | 4771 | bool aUseFontSet, |
michael@0 | 4772 | void *aClosure) : |
michael@0 | 4773 | mCallback(aCallback), |
michael@0 | 4774 | mGenericFamily(aGenericFamily), |
michael@0 | 4775 | mUseFontSet(aUseFontSet), |
michael@0 | 4776 | mClosure(aClosure) { |
michael@0 | 4777 | } |
michael@0 | 4778 | gfxFontGroup::FontCreationCallback mCallback; |
michael@0 | 4779 | nsCString mGenericFamily; |
michael@0 | 4780 | bool mUseFontSet; |
michael@0 | 4781 | void *mClosure; |
michael@0 | 4782 | }; |
michael@0 | 4783 | |
michael@0 | 4784 | bool |
michael@0 | 4785 | gfxFontGroup::ForEachFontInternal(const nsAString& aFamilies, |
michael@0 | 4786 | nsIAtom *aLanguage, |
michael@0 | 4787 | bool aResolveGeneric, |
michael@0 | 4788 | bool aResolveFontName, |
michael@0 | 4789 | bool aUseFontSet, |
michael@0 | 4790 | FontCreationCallback fc, |
michael@0 | 4791 | void *closure) |
michael@0 | 4792 | { |
michael@0 | 4793 | const char16_t kSingleQuote = char16_t('\''); |
michael@0 | 4794 | const char16_t kDoubleQuote = char16_t('\"'); |
michael@0 | 4795 | const char16_t kComma = char16_t(','); |
michael@0 | 4796 | |
michael@0 | 4797 | nsIAtom *groupAtom = nullptr; |
michael@0 | 4798 | nsAutoCString groupString; |
michael@0 | 4799 | if (aLanguage) { |
michael@0 | 4800 | if (!gLangService) { |
michael@0 | 4801 | CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService); |
michael@0 | 4802 | } |
michael@0 | 4803 | if (gLangService) { |
michael@0 | 4804 | nsresult rv; |
michael@0 | 4805 | groupAtom = gLangService->GetLanguageGroup(aLanguage, &rv); |
michael@0 | 4806 | } |
michael@0 | 4807 | } |
michael@0 | 4808 | if (!groupAtom) { |
michael@0 | 4809 | groupAtom = nsGkAtoms::Unicode; |
michael@0 | 4810 | } |
michael@0 | 4811 | groupAtom->ToUTF8String(groupString); |
michael@0 | 4812 | |
michael@0 | 4813 | nsPromiseFlatString families(aFamilies); |
michael@0 | 4814 | const char16_t *p, *p_end; |
michael@0 | 4815 | families.BeginReading(p); |
michael@0 | 4816 | families.EndReading(p_end); |
michael@0 | 4817 | nsAutoString family; |
michael@0 | 4818 | nsAutoCString lcFamily; |
michael@0 | 4819 | nsAutoString genericFamily; |
michael@0 | 4820 | |
michael@0 | 4821 | while (p < p_end) { |
michael@0 | 4822 | while (nsCRT::IsAsciiSpace(*p) || *p == kComma) |
michael@0 | 4823 | if (++p == p_end) |
michael@0 | 4824 | return true; |
michael@0 | 4825 | |
michael@0 | 4826 | bool generic; |
michael@0 | 4827 | if (*p == kSingleQuote || *p == kDoubleQuote) { |
michael@0 | 4828 | // quoted font family |
michael@0 | 4829 | char16_t quoteMark = *p; |
michael@0 | 4830 | if (++p == p_end) |
michael@0 | 4831 | return true; |
michael@0 | 4832 | const char16_t *nameStart = p; |
michael@0 | 4833 | |
michael@0 | 4834 | // XXX What about CSS character escapes? |
michael@0 | 4835 | while (*p != quoteMark) |
michael@0 | 4836 | if (++p == p_end) |
michael@0 | 4837 | return true; |
michael@0 | 4838 | |
michael@0 | 4839 | family = Substring(nameStart, p); |
michael@0 | 4840 | generic = false; |
michael@0 | 4841 | genericFamily.SetIsVoid(true); |
michael@0 | 4842 | |
michael@0 | 4843 | while (++p != p_end && *p != kComma) |
michael@0 | 4844 | /* nothing */ ; |
michael@0 | 4845 | |
michael@0 | 4846 | } else { |
michael@0 | 4847 | // unquoted font family |
michael@0 | 4848 | const char16_t *nameStart = p; |
michael@0 | 4849 | while (++p != p_end && *p != kComma) |
michael@0 | 4850 | /* nothing */ ; |
michael@0 | 4851 | |
michael@0 | 4852 | family = Substring(nameStart, p); |
michael@0 | 4853 | family.CompressWhitespace(false, true); |
michael@0 | 4854 | |
michael@0 | 4855 | if (aResolveGeneric && |
michael@0 | 4856 | (family.LowerCaseEqualsLiteral("serif") || |
michael@0 | 4857 | family.LowerCaseEqualsLiteral("sans-serif") || |
michael@0 | 4858 | family.LowerCaseEqualsLiteral("monospace") || |
michael@0 | 4859 | family.LowerCaseEqualsLiteral("cursive") || |
michael@0 | 4860 | family.LowerCaseEqualsLiteral("fantasy"))) |
michael@0 | 4861 | { |
michael@0 | 4862 | generic = true; |
michael@0 | 4863 | |
michael@0 | 4864 | ToLowerCase(NS_LossyConvertUTF16toASCII(family), lcFamily); |
michael@0 | 4865 | |
michael@0 | 4866 | nsAutoCString prefName("font.name."); |
michael@0 | 4867 | prefName.Append(lcFamily); |
michael@0 | 4868 | prefName.AppendLiteral("."); |
michael@0 | 4869 | prefName.Append(groupString); |
michael@0 | 4870 | |
michael@0 | 4871 | nsAdoptingString value = Preferences::GetString(prefName.get()); |
michael@0 | 4872 | if (value) { |
michael@0 | 4873 | CopyASCIItoUTF16(lcFamily, genericFamily); |
michael@0 | 4874 | family = value; |
michael@0 | 4875 | } |
michael@0 | 4876 | } else { |
michael@0 | 4877 | generic = false; |
michael@0 | 4878 | genericFamily.SetIsVoid(true); |
michael@0 | 4879 | } |
michael@0 | 4880 | } |
michael@0 | 4881 | |
michael@0 | 4882 | NS_LossyConvertUTF16toASCII gf(genericFamily); |
michael@0 | 4883 | if (generic) { |
michael@0 | 4884 | ForEachFontInternal(family, groupAtom, false, |
michael@0 | 4885 | aResolveFontName, false, |
michael@0 | 4886 | fc, closure); |
michael@0 | 4887 | } else if (!family.IsEmpty()) { |
michael@0 | 4888 | if (aResolveFontName) { |
michael@0 | 4889 | ResolveData data(fc, gf, aUseFontSet, closure); |
michael@0 | 4890 | bool aborted = false, needsBold; |
michael@0 | 4891 | nsresult rv = NS_OK; |
michael@0 | 4892 | bool foundFamily = false; |
michael@0 | 4893 | bool waitForUserFont = false; |
michael@0 | 4894 | gfxFontEntry *fe = nullptr; |
michael@0 | 4895 | if (aUseFontSet && mUserFontSet) { |
michael@0 | 4896 | gfxFontFamily *fam = mUserFontSet->GetFamily(family); |
michael@0 | 4897 | if (fam) { |
michael@0 | 4898 | fe = mUserFontSet->FindFontEntry(fam, mStyle, |
michael@0 | 4899 | needsBold, |
michael@0 | 4900 | waitForUserFont); |
michael@0 | 4901 | } |
michael@0 | 4902 | } |
michael@0 | 4903 | if (fe) { |
michael@0 | 4904 | gfxFontGroup::FontResolverProc(family, &data); |
michael@0 | 4905 | } else { |
michael@0 | 4906 | if (waitForUserFont) { |
michael@0 | 4907 | mSkipDrawing = true; |
michael@0 | 4908 | } |
michael@0 | 4909 | if (!foundFamily) { |
michael@0 | 4910 | gfxPlatform *pf = gfxPlatform::GetPlatform(); |
michael@0 | 4911 | // XXX: Fallback is bad |
michael@0 | 4912 | rv = pf->ResolveFontName(family, |
michael@0 | 4913 | gfxFontGroup::FontResolverProc, |
michael@0 | 4914 | &data, aborted); |
michael@0 | 4915 | } |
michael@0 | 4916 | } |
michael@0 | 4917 | if (NS_FAILED(rv) || aborted) |
michael@0 | 4918 | return false; |
michael@0 | 4919 | } |
michael@0 | 4920 | else { |
michael@0 | 4921 | if (!fc(family, gf, aUseFontSet, closure)) |
michael@0 | 4922 | return false; |
michael@0 | 4923 | } |
michael@0 | 4924 | } |
michael@0 | 4925 | |
michael@0 | 4926 | if (generic && aResolveGeneric) { |
michael@0 | 4927 | nsAutoCString prefName("font.name-list."); |
michael@0 | 4928 | prefName.Append(lcFamily); |
michael@0 | 4929 | prefName.AppendLiteral("."); |
michael@0 | 4930 | prefName.Append(groupString); |
michael@0 | 4931 | nsAdoptingString value = Preferences::GetString(prefName.get()); |
michael@0 | 4932 | if (value) { |
michael@0 | 4933 | ForEachFontInternal(value, groupAtom, false, |
michael@0 | 4934 | aResolveFontName, false, |
michael@0 | 4935 | fc, closure); |
michael@0 | 4936 | } |
michael@0 | 4937 | } |
michael@0 | 4938 | |
michael@0 | 4939 | ++p; // may advance past p_end |
michael@0 | 4940 | } |
michael@0 | 4941 | |
michael@0 | 4942 | return true; |
michael@0 | 4943 | } |
michael@0 | 4944 | |
michael@0 | 4945 | bool |
michael@0 | 4946 | gfxFontGroup::FontResolverProc(const nsAString& aName, void *aClosure) |
michael@0 | 4947 | { |
michael@0 | 4948 | ResolveData *data = reinterpret_cast<ResolveData*>(aClosure); |
michael@0 | 4949 | return (data->mCallback)(aName, data->mGenericFamily, data->mUseFontSet, |
michael@0 | 4950 | data->mClosure); |
michael@0 | 4951 | } |
michael@0 | 4952 | |
michael@0 | 4953 | gfxTextRun * |
michael@0 | 4954 | gfxFontGroup::MakeEmptyTextRun(const Parameters *aParams, uint32_t aFlags) |
michael@0 | 4955 | { |
michael@0 | 4956 | aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; |
michael@0 | 4957 | return gfxTextRun::Create(aParams, 0, this, aFlags); |
michael@0 | 4958 | } |
michael@0 | 4959 | |
michael@0 | 4960 | gfxTextRun * |
michael@0 | 4961 | gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, uint32_t aFlags) |
michael@0 | 4962 | { |
michael@0 | 4963 | aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT; |
michael@0 | 4964 | |
michael@0 | 4965 | gfxTextRun *textRun = gfxTextRun::Create(aParams, 1, this, aFlags); |
michael@0 | 4966 | if (!textRun) { |
michael@0 | 4967 | return nullptr; |
michael@0 | 4968 | } |
michael@0 | 4969 | |
michael@0 | 4970 | gfxFont *font = GetFontAt(0); |
michael@0 | 4971 | if (MOZ_UNLIKELY(GetStyle()->size == 0)) { |
michael@0 | 4972 | // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle |
michael@0 | 4973 | // them, and always create at least size 1 fonts, i.e. they still |
michael@0 | 4974 | // render something for size 0 fonts. |
michael@0 | 4975 | textRun->AddGlyphRun(font, gfxTextRange::kFontGroup, 0, false); |
michael@0 | 4976 | } |
michael@0 | 4977 | else { |
michael@0 | 4978 | if (font->GetSpaceGlyph()) { |
michael@0 | 4979 | // Normally, the font has a cached space glyph, so we can avoid |
michael@0 | 4980 | // the cost of calling FindFontForChar. |
michael@0 | 4981 | textRun->SetSpaceGlyph(font, aParams->mContext, 0); |
michael@0 | 4982 | } else { |
michael@0 | 4983 | // In case the primary font doesn't have <space> (bug 970891), |
michael@0 | 4984 | // find one that does. |
michael@0 | 4985 | uint8_t matchType; |
michael@0 | 4986 | nsRefPtr<gfxFont> spaceFont = |
michael@0 | 4987 | FindFontForChar(' ', 0, MOZ_SCRIPT_LATIN, nullptr, &matchType); |
michael@0 | 4988 | if (spaceFont) { |
michael@0 | 4989 | textRun->SetSpaceGlyph(spaceFont, aParams->mContext, 0); |
michael@0 | 4990 | } |
michael@0 | 4991 | } |
michael@0 | 4992 | } |
michael@0 | 4993 | |
michael@0 | 4994 | // Note that the gfxGlyphExtents glyph bounds storage for the font will |
michael@0 | 4995 | // always contain an entry for the font's space glyph, so we don't have |
michael@0 | 4996 | // to call FetchGlyphExtents here. |
michael@0 | 4997 | return textRun; |
michael@0 | 4998 | } |
michael@0 | 4999 | |
michael@0 | 5000 | gfxTextRun * |
michael@0 | 5001 | gfxFontGroup::MakeBlankTextRun(uint32_t aLength, |
michael@0 | 5002 | const Parameters *aParams, uint32_t aFlags) |
michael@0 | 5003 | { |
michael@0 | 5004 | gfxTextRun *textRun = |
michael@0 | 5005 | gfxTextRun::Create(aParams, aLength, this, aFlags); |
michael@0 | 5006 | if (!textRun) { |
michael@0 | 5007 | return nullptr; |
michael@0 | 5008 | } |
michael@0 | 5009 | |
michael@0 | 5010 | textRun->AddGlyphRun(GetFontAt(0), gfxTextRange::kFontGroup, 0, false); |
michael@0 | 5011 | return textRun; |
michael@0 | 5012 | } |
michael@0 | 5013 | |
michael@0 | 5014 | gfxTextRun * |
michael@0 | 5015 | gfxFontGroup::MakeHyphenTextRun(gfxContext *aCtx, uint32_t aAppUnitsPerDevUnit) |
michael@0 | 5016 | { |
michael@0 | 5017 | // only use U+2010 if it is supported by the first font in the group; |
michael@0 | 5018 | // it's better to use ASCII '-' from the primary font than to fall back to |
michael@0 | 5019 | // U+2010 from some other, possibly poorly-matching face |
michael@0 | 5020 | static const char16_t hyphen = 0x2010; |
michael@0 | 5021 | gfxFont *font = GetFontAt(0); |
michael@0 | 5022 | if (font && font->HasCharacter(hyphen)) { |
michael@0 | 5023 | return MakeTextRun(&hyphen, 1, aCtx, aAppUnitsPerDevUnit, |
michael@0 | 5024 | gfxFontGroup::TEXT_IS_PERSISTENT); |
michael@0 | 5025 | } |
michael@0 | 5026 | |
michael@0 | 5027 | static const uint8_t dash = '-'; |
michael@0 | 5028 | return MakeTextRun(&dash, 1, aCtx, aAppUnitsPerDevUnit, |
michael@0 | 5029 | gfxFontGroup::TEXT_IS_PERSISTENT); |
michael@0 | 5030 | } |
michael@0 | 5031 | |
michael@0 | 5032 | gfxFloat |
michael@0 | 5033 | gfxFontGroup::GetHyphenWidth(gfxTextRun::PropertyProvider *aProvider) |
michael@0 | 5034 | { |
michael@0 | 5035 | if (mHyphenWidth < 0) { |
michael@0 | 5036 | nsRefPtr<gfxContext> ctx(aProvider->GetContext()); |
michael@0 | 5037 | if (ctx) { |
michael@0 | 5038 | nsAutoPtr<gfxTextRun> |
michael@0 | 5039 | hyphRun(MakeHyphenTextRun(ctx, |
michael@0 | 5040 | aProvider->GetAppUnitsPerDevUnit())); |
michael@0 | 5041 | mHyphenWidth = hyphRun.get() ? |
michael@0 | 5042 | hyphRun->GetAdvanceWidth(0, hyphRun->GetLength(), nullptr) : 0; |
michael@0 | 5043 | } |
michael@0 | 5044 | } |
michael@0 | 5045 | return mHyphenWidth; |
michael@0 | 5046 | } |
michael@0 | 5047 | |
michael@0 | 5048 | gfxTextRun * |
michael@0 | 5049 | gfxFontGroup::MakeTextRun(const uint8_t *aString, uint32_t aLength, |
michael@0 | 5050 | const Parameters *aParams, uint32_t aFlags) |
michael@0 | 5051 | { |
michael@0 | 5052 | if (aLength == 0) { |
michael@0 | 5053 | return MakeEmptyTextRun(aParams, aFlags); |
michael@0 | 5054 | } |
michael@0 | 5055 | if (aLength == 1 && aString[0] == ' ') { |
michael@0 | 5056 | return MakeSpaceTextRun(aParams, aFlags); |
michael@0 | 5057 | } |
michael@0 | 5058 | |
michael@0 | 5059 | aFlags |= TEXT_IS_8BIT; |
michael@0 | 5060 | |
michael@0 | 5061 | if (GetStyle()->size == 0) { |
michael@0 | 5062 | // Short-circuit for size-0 fonts, as Windows and ATSUI can't handle |
michael@0 | 5063 | // them, and always create at least size 1 fonts, i.e. they still |
michael@0 | 5064 | // render something for size 0 fonts. |
michael@0 | 5065 | return MakeBlankTextRun(aLength, aParams, aFlags); |
michael@0 | 5066 | } |
michael@0 | 5067 | |
michael@0 | 5068 | gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength, |
michael@0 | 5069 | this, aFlags); |
michael@0 | 5070 | if (!textRun) { |
michael@0 | 5071 | return nullptr; |
michael@0 | 5072 | } |
michael@0 | 5073 | |
michael@0 | 5074 | InitTextRun(aParams->mContext, textRun, aString, aLength); |
michael@0 | 5075 | |
michael@0 | 5076 | textRun->FetchGlyphExtents(aParams->mContext); |
michael@0 | 5077 | |
michael@0 | 5078 | return textRun; |
michael@0 | 5079 | } |
michael@0 | 5080 | |
michael@0 | 5081 | gfxTextRun * |
michael@0 | 5082 | gfxFontGroup::MakeTextRun(const char16_t *aString, uint32_t aLength, |
michael@0 | 5083 | const Parameters *aParams, uint32_t aFlags) |
michael@0 | 5084 | { |
michael@0 | 5085 | if (aLength == 0) { |
michael@0 | 5086 | return MakeEmptyTextRun(aParams, aFlags); |
michael@0 | 5087 | } |
michael@0 | 5088 | if (aLength == 1 && aString[0] == ' ') { |
michael@0 | 5089 | return MakeSpaceTextRun(aParams, aFlags); |
michael@0 | 5090 | } |
michael@0 | 5091 | if (GetStyle()->size == 0) { |
michael@0 | 5092 | return MakeBlankTextRun(aLength, aParams, aFlags); |
michael@0 | 5093 | } |
michael@0 | 5094 | |
michael@0 | 5095 | gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength, |
michael@0 | 5096 | this, aFlags); |
michael@0 | 5097 | if (!textRun) { |
michael@0 | 5098 | return nullptr; |
michael@0 | 5099 | } |
michael@0 | 5100 | |
michael@0 | 5101 | InitTextRun(aParams->mContext, textRun, aString, aLength); |
michael@0 | 5102 | |
michael@0 | 5103 | textRun->FetchGlyphExtents(aParams->mContext); |
michael@0 | 5104 | |
michael@0 | 5105 | return textRun; |
michael@0 | 5106 | } |
michael@0 | 5107 | |
michael@0 | 5108 | template<typename T> |
michael@0 | 5109 | void |
michael@0 | 5110 | gfxFontGroup::InitTextRun(gfxContext *aContext, |
michael@0 | 5111 | gfxTextRun *aTextRun, |
michael@0 | 5112 | const T *aString, |
michael@0 | 5113 | uint32_t aLength) |
michael@0 | 5114 | { |
michael@0 | 5115 | NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run"); |
michael@0 | 5116 | |
michael@0 | 5117 | // we need to do numeral processing even on 8-bit text, |
michael@0 | 5118 | // in case we're converting Western to Hindi/Arabic digits |
michael@0 | 5119 | int32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption(); |
michael@0 | 5120 | nsAutoArrayPtr<char16_t> transformedString; |
michael@0 | 5121 | if (numOption != IBMBIDI_NUMERAL_NOMINAL) { |
michael@0 | 5122 | // scan the string for numerals that may need to be transformed; |
michael@0 | 5123 | // if we find any, we'll make a local copy here and use that for |
michael@0 | 5124 | // font matching and glyph generation/shaping |
michael@0 | 5125 | bool prevIsArabic = |
michael@0 | 5126 | (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0; |
michael@0 | 5127 | for (uint32_t i = 0; i < aLength; ++i) { |
michael@0 | 5128 | char16_t origCh = aString[i]; |
michael@0 | 5129 | char16_t newCh = HandleNumberInChar(origCh, prevIsArabic, numOption); |
michael@0 | 5130 | if (newCh != origCh) { |
michael@0 | 5131 | if (!transformedString) { |
michael@0 | 5132 | transformedString = new char16_t[aLength]; |
michael@0 | 5133 | if (sizeof(T) == sizeof(char16_t)) { |
michael@0 | 5134 | memcpy(transformedString.get(), aString, i * sizeof(char16_t)); |
michael@0 | 5135 | } else { |
michael@0 | 5136 | for (uint32_t j = 0; j < i; ++j) { |
michael@0 | 5137 | transformedString[j] = aString[j]; |
michael@0 | 5138 | } |
michael@0 | 5139 | } |
michael@0 | 5140 | } |
michael@0 | 5141 | } |
michael@0 | 5142 | if (transformedString) { |
michael@0 | 5143 | transformedString[i] = newCh; |
michael@0 | 5144 | } |
michael@0 | 5145 | prevIsArabic = IS_ARABIC_CHAR(newCh); |
michael@0 | 5146 | } |
michael@0 | 5147 | } |
michael@0 | 5148 | |
michael@0 | 5149 | #ifdef PR_LOGGING |
michael@0 | 5150 | PRLogModuleInfo *log = (mStyle.systemFont ? |
michael@0 | 5151 | gfxPlatform::GetLog(eGfxLog_textrunui) : |
michael@0 | 5152 | gfxPlatform::GetLog(eGfxLog_textrun)); |
michael@0 | 5153 | #endif |
michael@0 | 5154 | |
michael@0 | 5155 | if (sizeof(T) == sizeof(uint8_t) && !transformedString) { |
michael@0 | 5156 | |
michael@0 | 5157 | #ifdef PR_LOGGING |
michael@0 | 5158 | if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) { |
michael@0 | 5159 | nsAutoCString lang; |
michael@0 | 5160 | mStyle.language->ToUTF8String(lang); |
michael@0 | 5161 | nsAutoCString str((const char*)aString, aLength); |
michael@0 | 5162 | PR_LOG(log, PR_LOG_WARNING,\ |
michael@0 | 5163 | ("(%s) fontgroup: [%s] lang: %s script: %d len %d " |
michael@0 | 5164 | "weight: %d width: %d style: %s size: %6.2f %d-byte " |
michael@0 | 5165 | "TEXTRUN [%s] ENDTEXTRUN\n", |
michael@0 | 5166 | (mStyle.systemFont ? "textrunui" : "textrun"), |
michael@0 | 5167 | NS_ConvertUTF16toUTF8(mFamilies).get(), |
michael@0 | 5168 | lang.get(), MOZ_SCRIPT_LATIN, aLength, |
michael@0 | 5169 | uint32_t(mStyle.weight), uint32_t(mStyle.stretch), |
michael@0 | 5170 | (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" : |
michael@0 | 5171 | (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" : |
michael@0 | 5172 | "normal")), |
michael@0 | 5173 | mStyle.size, |
michael@0 | 5174 | sizeof(T), |
michael@0 | 5175 | str.get())); |
michael@0 | 5176 | } |
michael@0 | 5177 | #endif |
michael@0 | 5178 | |
michael@0 | 5179 | // the text is still purely 8-bit; bypass the script-run itemizer |
michael@0 | 5180 | // and treat it as a single Latin run |
michael@0 | 5181 | InitScriptRun(aContext, aTextRun, aString, |
michael@0 | 5182 | 0, aLength, MOZ_SCRIPT_LATIN); |
michael@0 | 5183 | } else { |
michael@0 | 5184 | const char16_t *textPtr; |
michael@0 | 5185 | if (transformedString) { |
michael@0 | 5186 | textPtr = transformedString.get(); |
michael@0 | 5187 | } else { |
michael@0 | 5188 | // typecast to avoid compilation error for the 8-bit version, |
michael@0 | 5189 | // even though this is dead code in that case |
michael@0 | 5190 | textPtr = reinterpret_cast<const char16_t*>(aString); |
michael@0 | 5191 | } |
michael@0 | 5192 | |
michael@0 | 5193 | // split into script runs so that script can potentially influence |
michael@0 | 5194 | // the font matching process below |
michael@0 | 5195 | gfxScriptItemizer scriptRuns(textPtr, aLength); |
michael@0 | 5196 | |
michael@0 | 5197 | uint32_t runStart = 0, runLimit = aLength; |
michael@0 | 5198 | int32_t runScript = MOZ_SCRIPT_LATIN; |
michael@0 | 5199 | while (scriptRuns.Next(runStart, runLimit, runScript)) { |
michael@0 | 5200 | |
michael@0 | 5201 | #ifdef PR_LOGGING |
michael@0 | 5202 | if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) { |
michael@0 | 5203 | nsAutoCString lang; |
michael@0 | 5204 | mStyle.language->ToUTF8String(lang); |
michael@0 | 5205 | uint32_t runLen = runLimit - runStart; |
michael@0 | 5206 | PR_LOG(log, PR_LOG_WARNING,\ |
michael@0 | 5207 | ("(%s) fontgroup: [%s] lang: %s script: %d len %d " |
michael@0 | 5208 | "weight: %d width: %d style: %s size: %6.2f %d-byte " |
michael@0 | 5209 | "TEXTRUN [%s] ENDTEXTRUN\n", |
michael@0 | 5210 | (mStyle.systemFont ? "textrunui" : "textrun"), |
michael@0 | 5211 | NS_ConvertUTF16toUTF8(mFamilies).get(), |
michael@0 | 5212 | lang.get(), runScript, runLen, |
michael@0 | 5213 | uint32_t(mStyle.weight), uint32_t(mStyle.stretch), |
michael@0 | 5214 | (mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" : |
michael@0 | 5215 | (mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" : |
michael@0 | 5216 | "normal")), |
michael@0 | 5217 | mStyle.size, |
michael@0 | 5218 | sizeof(T), |
michael@0 | 5219 | NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get())); |
michael@0 | 5220 | } |
michael@0 | 5221 | #endif |
michael@0 | 5222 | |
michael@0 | 5223 | InitScriptRun(aContext, aTextRun, textPtr, |
michael@0 | 5224 | runStart, runLimit, runScript); |
michael@0 | 5225 | } |
michael@0 | 5226 | } |
michael@0 | 5227 | |
michael@0 | 5228 | if (sizeof(T) == sizeof(char16_t) && aLength > 0) { |
michael@0 | 5229 | gfxTextRun::CompressedGlyph *glyph = aTextRun->GetCharacterGlyphs(); |
michael@0 | 5230 | if (!glyph->IsSimpleGlyph()) { |
michael@0 | 5231 | glyph->SetClusterStart(true); |
michael@0 | 5232 | } |
michael@0 | 5233 | } |
michael@0 | 5234 | |
michael@0 | 5235 | // It's possible for CoreText to omit glyph runs if it decides they contain |
michael@0 | 5236 | // only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we |
michael@0 | 5237 | // need to eliminate them from the glyph run array to avoid drawing "partial |
michael@0 | 5238 | // ligatures" with the wrong font. |
michael@0 | 5239 | // We don't do this during InitScriptRun (or gfxFont::InitTextRun) because |
michael@0 | 5240 | // it will iterate back over all glyphruns in the textrun, which leads to |
michael@0 | 5241 | // pathologically-bad perf in the case where a textrun contains many script |
michael@0 | 5242 | // changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs |
michael@0 | 5243 | // every time a new script subrun is processed. |
michael@0 | 5244 | aTextRun->SanitizeGlyphRuns(); |
michael@0 | 5245 | |
michael@0 | 5246 | aTextRun->SortGlyphRuns(); |
michael@0 | 5247 | } |
michael@0 | 5248 | |
michael@0 | 5249 | template<typename T> |
michael@0 | 5250 | void |
michael@0 | 5251 | gfxFontGroup::InitScriptRun(gfxContext *aContext, |
michael@0 | 5252 | gfxTextRun *aTextRun, |
michael@0 | 5253 | const T *aString, |
michael@0 | 5254 | uint32_t aScriptRunStart, |
michael@0 | 5255 | uint32_t aScriptRunEnd, |
michael@0 | 5256 | int32_t aRunScript) |
michael@0 | 5257 | { |
michael@0 | 5258 | NS_ASSERTION(aScriptRunEnd > aScriptRunStart, |
michael@0 | 5259 | "don't call InitScriptRun for a zero-length run"); |
michael@0 | 5260 | |
michael@0 | 5261 | gfxFont *mainFont = GetFontAt(0); |
michael@0 | 5262 | |
michael@0 | 5263 | uint32_t runStart = aScriptRunStart; |
michael@0 | 5264 | nsAutoTArray<gfxTextRange,3> fontRanges; |
michael@0 | 5265 | ComputeRanges(fontRanges, aString + aScriptRunStart, |
michael@0 | 5266 | aScriptRunEnd - aScriptRunStart, aRunScript); |
michael@0 | 5267 | uint32_t numRanges = fontRanges.Length(); |
michael@0 | 5268 | |
michael@0 | 5269 | for (uint32_t r = 0; r < numRanges; r++) { |
michael@0 | 5270 | const gfxTextRange& range = fontRanges[r]; |
michael@0 | 5271 | uint32_t matchedLength = range.Length(); |
michael@0 | 5272 | gfxFont *matchedFont = range.font; |
michael@0 | 5273 | |
michael@0 | 5274 | // create the glyph run for this range |
michael@0 | 5275 | if (matchedFont) { |
michael@0 | 5276 | aTextRun->AddGlyphRun(matchedFont, range.matchType, |
michael@0 | 5277 | runStart, (matchedLength > 0)); |
michael@0 | 5278 | // do glyph layout and record the resulting positioned glyphs |
michael@0 | 5279 | if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, aString, |
michael@0 | 5280 | runStart, matchedLength, |
michael@0 | 5281 | aRunScript)) { |
michael@0 | 5282 | // glyph layout failed! treat as missing glyphs |
michael@0 | 5283 | matchedFont = nullptr; |
michael@0 | 5284 | } |
michael@0 | 5285 | } else { |
michael@0 | 5286 | aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup, |
michael@0 | 5287 | runStart, (matchedLength > 0)); |
michael@0 | 5288 | } |
michael@0 | 5289 | |
michael@0 | 5290 | if (!matchedFont) { |
michael@0 | 5291 | // We need to set cluster boundaries (and mark spaces) so that |
michael@0 | 5292 | // surrogate pairs, combining characters, etc behave properly, |
michael@0 | 5293 | // even if we don't have glyphs for them |
michael@0 | 5294 | aTextRun->SetupClusterBoundaries(runStart, aString + runStart, |
michael@0 | 5295 | matchedLength); |
michael@0 | 5296 | |
michael@0 | 5297 | // various "missing" characters may need special handling, |
michael@0 | 5298 | // so we check for them here |
michael@0 | 5299 | uint32_t runLimit = runStart + matchedLength; |
michael@0 | 5300 | for (uint32_t index = runStart; index < runLimit; index++) { |
michael@0 | 5301 | T ch = aString[index]; |
michael@0 | 5302 | |
michael@0 | 5303 | // tab and newline are not to be displayed as hexboxes, |
michael@0 | 5304 | // but do need to be recorded in the textrun |
michael@0 | 5305 | if (ch == '\n') { |
michael@0 | 5306 | aTextRun->SetIsNewline(index); |
michael@0 | 5307 | continue; |
michael@0 | 5308 | } |
michael@0 | 5309 | if (ch == '\t') { |
michael@0 | 5310 | aTextRun->SetIsTab(index); |
michael@0 | 5311 | continue; |
michael@0 | 5312 | } |
michael@0 | 5313 | |
michael@0 | 5314 | // for 16-bit textruns only, check for surrogate pairs and |
michael@0 | 5315 | // special Unicode spaces; omit these checks in 8-bit runs |
michael@0 | 5316 | if (sizeof(T) == sizeof(char16_t)) { |
michael@0 | 5317 | if (NS_IS_HIGH_SURROGATE(ch) && |
michael@0 | 5318 | index + 1 < aScriptRunEnd && |
michael@0 | 5319 | NS_IS_LOW_SURROGATE(aString[index + 1])) |
michael@0 | 5320 | { |
michael@0 | 5321 | aTextRun->SetMissingGlyph(index, |
michael@0 | 5322 | SURROGATE_TO_UCS4(ch, |
michael@0 | 5323 | aString[index + 1]), |
michael@0 | 5324 | mainFont); |
michael@0 | 5325 | index++; |
michael@0 | 5326 | continue; |
michael@0 | 5327 | } |
michael@0 | 5328 | |
michael@0 | 5329 | // check if this is a known Unicode whitespace character that |
michael@0 | 5330 | // we can render using the space glyph with a custom width |
michael@0 | 5331 | gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch); |
michael@0 | 5332 | if (wid >= 0.0) { |
michael@0 | 5333 | nscoord advance = |
michael@0 | 5334 | aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5); |
michael@0 | 5335 | if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) { |
michael@0 | 5336 | aTextRun->GetCharacterGlyphs()[index]. |
michael@0 | 5337 | SetSimpleGlyph(advance, |
michael@0 | 5338 | mainFont->GetSpaceGlyph()); |
michael@0 | 5339 | } else { |
michael@0 | 5340 | gfxTextRun::DetailedGlyph detailedGlyph; |
michael@0 | 5341 | detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph(); |
michael@0 | 5342 | detailedGlyph.mAdvance = advance; |
michael@0 | 5343 | detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0; |
michael@0 | 5344 | gfxShapedText::CompressedGlyph g; |
michael@0 | 5345 | g.SetComplex(true, true, 1); |
michael@0 | 5346 | aTextRun->SetGlyphs(index, |
michael@0 | 5347 | g, &detailedGlyph); |
michael@0 | 5348 | } |
michael@0 | 5349 | continue; |
michael@0 | 5350 | } |
michael@0 | 5351 | } |
michael@0 | 5352 | |
michael@0 | 5353 | if (IsInvalidChar(ch)) { |
michael@0 | 5354 | // invalid chars are left as zero-width/invisible |
michael@0 | 5355 | continue; |
michael@0 | 5356 | } |
michael@0 | 5357 | |
michael@0 | 5358 | // record char code so we can draw a box with the Unicode value |
michael@0 | 5359 | aTextRun->SetMissingGlyph(index, ch, mainFont); |
michael@0 | 5360 | } |
michael@0 | 5361 | } |
michael@0 | 5362 | |
michael@0 | 5363 | runStart += matchedLength; |
michael@0 | 5364 | } |
michael@0 | 5365 | } |
michael@0 | 5366 | |
michael@0 | 5367 | gfxTextRun * |
michael@0 | 5368 | gfxFontGroup::GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel, |
michael@0 | 5369 | LazyReferenceContextGetter& aRefContextGetter) |
michael@0 | 5370 | { |
michael@0 | 5371 | if (mCachedEllipsisTextRun && |
michael@0 | 5372 | mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) { |
michael@0 | 5373 | return mCachedEllipsisTextRun; |
michael@0 | 5374 | } |
michael@0 | 5375 | |
michael@0 | 5376 | // Use a Unicode ellipsis if the font supports it, |
michael@0 | 5377 | // otherwise use three ASCII periods as fallback. |
michael@0 | 5378 | gfxFont* firstFont = GetFontAt(0); |
michael@0 | 5379 | nsString ellipsis = firstFont->HasCharacter(kEllipsisChar[0]) |
michael@0 | 5380 | ? nsDependentString(kEllipsisChar, |
michael@0 | 5381 | ArrayLength(kEllipsisChar) - 1) |
michael@0 | 5382 | : nsDependentString(kASCIIPeriodsChar, |
michael@0 | 5383 | ArrayLength(kASCIIPeriodsChar) - 1); |
michael@0 | 5384 | |
michael@0 | 5385 | nsRefPtr<gfxContext> refCtx = aRefContextGetter.GetRefContext(); |
michael@0 | 5386 | Parameters params = { |
michael@0 | 5387 | refCtx, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel |
michael@0 | 5388 | }; |
michael@0 | 5389 | gfxTextRun* textRun = |
michael@0 | 5390 | MakeTextRun(ellipsis.get(), ellipsis.Length(), ¶ms, TEXT_IS_PERSISTENT); |
michael@0 | 5391 | if (!textRun) { |
michael@0 | 5392 | return nullptr; |
michael@0 | 5393 | } |
michael@0 | 5394 | mCachedEllipsisTextRun = textRun; |
michael@0 | 5395 | textRun->ReleaseFontGroup(); // don't let the presence of a cached ellipsis |
michael@0 | 5396 | // textrun prolong the fontgroup's life |
michael@0 | 5397 | return textRun; |
michael@0 | 5398 | } |
michael@0 | 5399 | |
michael@0 | 5400 | already_AddRefed<gfxFont> |
michael@0 | 5401 | gfxFontGroup::TryAllFamilyMembers(gfxFontFamily* aFamily, uint32_t aCh) |
michael@0 | 5402 | { |
michael@0 | 5403 | if (!aFamily->TestCharacterMap(aCh)) { |
michael@0 | 5404 | return nullptr; |
michael@0 | 5405 | } |
michael@0 | 5406 | |
michael@0 | 5407 | // Note that we don't need the actual runScript in matchData for |
michael@0 | 5408 | // gfxFontFamily::SearchAllFontsForChar, it's only used for the |
michael@0 | 5409 | // system-fallback case. So we can just set it to 0 here. |
michael@0 | 5410 | GlobalFontMatch matchData(aCh, 0, &mStyle); |
michael@0 | 5411 | aFamily->SearchAllFontsForChar(&matchData); |
michael@0 | 5412 | gfxFontEntry *fe = matchData.mBestMatch; |
michael@0 | 5413 | if (!fe) { |
michael@0 | 5414 | return nullptr; |
michael@0 | 5415 | } |
michael@0 | 5416 | |
michael@0 | 5417 | bool needsBold = mStyle.weight >= 600 && !fe->IsBold(); |
michael@0 | 5418 | nsRefPtr<gfxFont> font = fe->FindOrMakeFont(&mStyle, needsBold); |
michael@0 | 5419 | return font.forget(); |
michael@0 | 5420 | } |
michael@0 | 5421 | |
michael@0 | 5422 | already_AddRefed<gfxFont> |
michael@0 | 5423 | gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh, |
michael@0 | 5424 | int32_t aRunScript, gfxFont *aPrevMatchedFont, |
michael@0 | 5425 | uint8_t *aMatchType) |
michael@0 | 5426 | { |
michael@0 | 5427 | // To optimize common cases, try the first font in the font-group |
michael@0 | 5428 | // before going into the more detailed checks below |
michael@0 | 5429 | uint32_t nextIndex = 0; |
michael@0 | 5430 | bool isJoinControl = gfxFontUtils::IsJoinControl(aCh); |
michael@0 | 5431 | bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh); |
michael@0 | 5432 | bool isVarSelector = gfxFontUtils::IsVarSelector(aCh); |
michael@0 | 5433 | |
michael@0 | 5434 | if (!isJoinControl && !wasJoinCauser && !isVarSelector) { |
michael@0 | 5435 | nsRefPtr<gfxFont> firstFont = mFonts[0].Font(); |
michael@0 | 5436 | if (firstFont->HasCharacter(aCh)) { |
michael@0 | 5437 | *aMatchType = gfxTextRange::kFontGroup; |
michael@0 | 5438 | return firstFont.forget(); |
michael@0 | 5439 | } |
michael@0 | 5440 | // It's possible that another font in the family (e.g. regular face, |
michael@0 | 5441 | // where the requested style was italic) will support the character |
michael@0 | 5442 | nsRefPtr<gfxFont> font = TryAllFamilyMembers(mFonts[0].Family(), aCh); |
michael@0 | 5443 | if (font) { |
michael@0 | 5444 | *aMatchType = gfxTextRange::kFontGroup; |
michael@0 | 5445 | return font.forget(); |
michael@0 | 5446 | } |
michael@0 | 5447 | // we don't need to check the first font again below |
michael@0 | 5448 | ++nextIndex; |
michael@0 | 5449 | } |
michael@0 | 5450 | |
michael@0 | 5451 | if (aPrevMatchedFont) { |
michael@0 | 5452 | // Don't switch fonts for control characters, regardless of |
michael@0 | 5453 | // whether they are present in the current font, as they won't |
michael@0 | 5454 | // actually be rendered (see bug 716229) |
michael@0 | 5455 | if (isJoinControl || |
michael@0 | 5456 | GetGeneralCategory(aCh) == HB_UNICODE_GENERAL_CATEGORY_CONTROL) { |
michael@0 | 5457 | nsRefPtr<gfxFont> ret = aPrevMatchedFont; |
michael@0 | 5458 | return ret.forget(); |
michael@0 | 5459 | } |
michael@0 | 5460 | |
michael@0 | 5461 | // if previous character was a join-causer (ZWJ), |
michael@0 | 5462 | // use the same font as the previous range if we can |
michael@0 | 5463 | if (wasJoinCauser) { |
michael@0 | 5464 | if (aPrevMatchedFont->HasCharacter(aCh)) { |
michael@0 | 5465 | nsRefPtr<gfxFont> ret = aPrevMatchedFont; |
michael@0 | 5466 | return ret.forget(); |
michael@0 | 5467 | } |
michael@0 | 5468 | } |
michael@0 | 5469 | } |
michael@0 | 5470 | |
michael@0 | 5471 | // if this character is a variation selector, |
michael@0 | 5472 | // use the previous font regardless of whether it supports VS or not. |
michael@0 | 5473 | // otherwise the text run will be divided. |
michael@0 | 5474 | if (isVarSelector) { |
michael@0 | 5475 | if (aPrevMatchedFont) { |
michael@0 | 5476 | nsRefPtr<gfxFont> ret = aPrevMatchedFont; |
michael@0 | 5477 | return ret.forget(); |
michael@0 | 5478 | } |
michael@0 | 5479 | // VS alone. it's meaningless to search different fonts |
michael@0 | 5480 | return nullptr; |
michael@0 | 5481 | } |
michael@0 | 5482 | |
michael@0 | 5483 | // 1. check remaining fonts in the font group |
michael@0 | 5484 | uint32_t fontListLength = FontListLength(); |
michael@0 | 5485 | for (uint32_t i = nextIndex; i < fontListLength; i++) { |
michael@0 | 5486 | nsRefPtr<gfxFont> font = mFonts[i].Font(); |
michael@0 | 5487 | if (font->HasCharacter(aCh)) { |
michael@0 | 5488 | *aMatchType = gfxTextRange::kFontGroup; |
michael@0 | 5489 | return font.forget(); |
michael@0 | 5490 | } |
michael@0 | 5491 | |
michael@0 | 5492 | font = TryAllFamilyMembers(mFonts[i].Family(), aCh); |
michael@0 | 5493 | if (font) { |
michael@0 | 5494 | *aMatchType = gfxTextRange::kFontGroup; |
michael@0 | 5495 | return font.forget(); |
michael@0 | 5496 | } |
michael@0 | 5497 | } |
michael@0 | 5498 | |
michael@0 | 5499 | // if character is in Private Use Area, don't do matching against pref or system fonts |
michael@0 | 5500 | if ((aCh >= 0xE000 && aCh <= 0xF8FF) || (aCh >= 0xF0000 && aCh <= 0x10FFFD)) |
michael@0 | 5501 | return nullptr; |
michael@0 | 5502 | |
michael@0 | 5503 | // 2. search pref fonts |
michael@0 | 5504 | nsRefPtr<gfxFont> font = WhichPrefFontSupportsChar(aCh); |
michael@0 | 5505 | if (font) { |
michael@0 | 5506 | *aMatchType = gfxTextRange::kPrefsFallback; |
michael@0 | 5507 | return font.forget(); |
michael@0 | 5508 | } |
michael@0 | 5509 | |
michael@0 | 5510 | // 3. use fallback fonts |
michael@0 | 5511 | // -- before searching for something else check the font used for the previous character |
michael@0 | 5512 | if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) { |
michael@0 | 5513 | *aMatchType = gfxTextRange::kSystemFallback; |
michael@0 | 5514 | nsRefPtr<gfxFont> ret = aPrevMatchedFont; |
michael@0 | 5515 | return ret.forget(); |
michael@0 | 5516 | } |
michael@0 | 5517 | |
michael@0 | 5518 | // never fall back for characters from unknown scripts |
michael@0 | 5519 | if (aRunScript == HB_SCRIPT_UNKNOWN) { |
michael@0 | 5520 | return nullptr; |
michael@0 | 5521 | } |
michael@0 | 5522 | |
michael@0 | 5523 | // for known "space" characters, don't do a full system-fallback search; |
michael@0 | 5524 | // we'll synthesize appropriate-width spaces instead of missing-glyph boxes |
michael@0 | 5525 | if (GetGeneralCategory(aCh) == |
michael@0 | 5526 | HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR && |
michael@0 | 5527 | GetFontAt(0)->SynthesizeSpaceWidth(aCh) >= 0.0) |
michael@0 | 5528 | { |
michael@0 | 5529 | return nullptr; |
michael@0 | 5530 | } |
michael@0 | 5531 | |
michael@0 | 5532 | // -- otherwise look for other stuff |
michael@0 | 5533 | *aMatchType = gfxTextRange::kSystemFallback; |
michael@0 | 5534 | font = WhichSystemFontSupportsChar(aCh, aRunScript); |
michael@0 | 5535 | return font.forget(); |
michael@0 | 5536 | } |
michael@0 | 5537 | |
michael@0 | 5538 | template<typename T> |
michael@0 | 5539 | void gfxFontGroup::ComputeRanges(nsTArray<gfxTextRange>& aRanges, |
michael@0 | 5540 | const T *aString, uint32_t aLength, |
michael@0 | 5541 | int32_t aRunScript) |
michael@0 | 5542 | { |
michael@0 | 5543 | NS_ASSERTION(aRanges.Length() == 0, "aRanges must be initially empty"); |
michael@0 | 5544 | NS_ASSERTION(aLength > 0, "don't call ComputeRanges for zero-length text"); |
michael@0 | 5545 | |
michael@0 | 5546 | uint32_t prevCh = 0; |
michael@0 | 5547 | int32_t lastRangeIndex = -1; |
michael@0 | 5548 | |
michael@0 | 5549 | // initialize prevFont to the group's primary font, so that this will be |
michael@0 | 5550 | // used for string-initial control chars, etc rather than risk hitting font |
michael@0 | 5551 | // fallback for these (bug 716229) |
michael@0 | 5552 | gfxFont *prevFont = GetFontAt(0); |
michael@0 | 5553 | |
michael@0 | 5554 | // if we use the initial value of prevFont, we treat this as a match from |
michael@0 | 5555 | // the font group; fixes bug 978313 |
michael@0 | 5556 | uint8_t matchType = gfxTextRange::kFontGroup; |
michael@0 | 5557 | |
michael@0 | 5558 | for (uint32_t i = 0; i < aLength; i++) { |
michael@0 | 5559 | |
michael@0 | 5560 | const uint32_t origI = i; // save off in case we increase for surrogate |
michael@0 | 5561 | |
michael@0 | 5562 | // set up current ch |
michael@0 | 5563 | uint32_t ch = aString[i]; |
michael@0 | 5564 | |
michael@0 | 5565 | // in 16-bit case only, check for surrogate pair |
michael@0 | 5566 | if (sizeof(T) == sizeof(char16_t)) { |
michael@0 | 5567 | if ((i + 1 < aLength) && NS_IS_HIGH_SURROGATE(ch) && |
michael@0 | 5568 | NS_IS_LOW_SURROGATE(aString[i + 1])) { |
michael@0 | 5569 | i++; |
michael@0 | 5570 | ch = SURROGATE_TO_UCS4(ch, aString[i]); |
michael@0 | 5571 | } |
michael@0 | 5572 | } |
michael@0 | 5573 | |
michael@0 | 5574 | if (ch == 0xa0) { |
michael@0 | 5575 | ch = ' '; |
michael@0 | 5576 | } |
michael@0 | 5577 | |
michael@0 | 5578 | // find the font for this char |
michael@0 | 5579 | nsRefPtr<gfxFont> font = |
michael@0 | 5580 | FindFontForChar(ch, prevCh, aRunScript, prevFont, &matchType); |
michael@0 | 5581 | |
michael@0 | 5582 | #ifndef RELEASE_BUILD |
michael@0 | 5583 | if (MOZ_UNLIKELY(mTextPerf)) { |
michael@0 | 5584 | if (matchType == gfxTextRange::kPrefsFallback) { |
michael@0 | 5585 | mTextPerf->current.fallbackPrefs++; |
michael@0 | 5586 | } else if (matchType == gfxTextRange::kSystemFallback) { |
michael@0 | 5587 | mTextPerf->current.fallbackSystem++; |
michael@0 | 5588 | } |
michael@0 | 5589 | } |
michael@0 | 5590 | #endif |
michael@0 | 5591 | |
michael@0 | 5592 | prevCh = ch; |
michael@0 | 5593 | |
michael@0 | 5594 | if (lastRangeIndex == -1) { |
michael@0 | 5595 | // first char ==> make a new range |
michael@0 | 5596 | aRanges.AppendElement(gfxTextRange(0, 1, font, matchType)); |
michael@0 | 5597 | lastRangeIndex++; |
michael@0 | 5598 | prevFont = font; |
michael@0 | 5599 | } else { |
michael@0 | 5600 | // if font has changed, make a new range |
michael@0 | 5601 | gfxTextRange& prevRange = aRanges[lastRangeIndex]; |
michael@0 | 5602 | if (prevRange.font != font || prevRange.matchType != matchType) { |
michael@0 | 5603 | // close out the previous range |
michael@0 | 5604 | prevRange.end = origI; |
michael@0 | 5605 | aRanges.AppendElement(gfxTextRange(origI, i + 1, |
michael@0 | 5606 | font, matchType)); |
michael@0 | 5607 | lastRangeIndex++; |
michael@0 | 5608 | |
michael@0 | 5609 | // update prevFont for the next match, *unless* we switched |
michael@0 | 5610 | // fonts on a ZWJ, in which case propagating the changed font |
michael@0 | 5611 | // is probably not a good idea (see bug 619511) |
michael@0 | 5612 | if (sizeof(T) == sizeof(uint8_t) || |
michael@0 | 5613 | !gfxFontUtils::IsJoinCauser(ch)) |
michael@0 | 5614 | { |
michael@0 | 5615 | prevFont = font; |
michael@0 | 5616 | } |
michael@0 | 5617 | } |
michael@0 | 5618 | } |
michael@0 | 5619 | } |
michael@0 | 5620 | |
michael@0 | 5621 | aRanges[lastRangeIndex].end = aLength; |
michael@0 | 5622 | } |
michael@0 | 5623 | |
michael@0 | 5624 | gfxUserFontSet* |
michael@0 | 5625 | gfxFontGroup::GetUserFontSet() |
michael@0 | 5626 | { |
michael@0 | 5627 | return mUserFontSet; |
michael@0 | 5628 | } |
michael@0 | 5629 | |
michael@0 | 5630 | void |
michael@0 | 5631 | gfxFontGroup::SetUserFontSet(gfxUserFontSet *aUserFontSet) |
michael@0 | 5632 | { |
michael@0 | 5633 | if (aUserFontSet == mUserFontSet) { |
michael@0 | 5634 | return; |
michael@0 | 5635 | } |
michael@0 | 5636 | mUserFontSet = aUserFontSet; |
michael@0 | 5637 | mCurrGeneration = GetGeneration() - 1; |
michael@0 | 5638 | UpdateFontList(); |
michael@0 | 5639 | } |
michael@0 | 5640 | |
michael@0 | 5641 | uint64_t |
michael@0 | 5642 | gfxFontGroup::GetGeneration() |
michael@0 | 5643 | { |
michael@0 | 5644 | if (!mUserFontSet) |
michael@0 | 5645 | return 0; |
michael@0 | 5646 | return mUserFontSet->GetGeneration(); |
michael@0 | 5647 | } |
michael@0 | 5648 | |
michael@0 | 5649 | void |
michael@0 | 5650 | gfxFontGroup::UpdateFontList() |
michael@0 | 5651 | { |
michael@0 | 5652 | if (mCurrGeneration != GetGeneration()) { |
michael@0 | 5653 | // xxx - can probably improve this to detect when all fonts were found, so no need to update list |
michael@0 | 5654 | mFonts.Clear(); |
michael@0 | 5655 | mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET; |
michael@0 | 5656 | mSkipDrawing = false; |
michael@0 | 5657 | |
michael@0 | 5658 | // bug 548184 - need to clean up FT2, OS/2 platform code to use BuildFontList |
michael@0 | 5659 | #if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID) |
michael@0 | 5660 | BuildFontList(); |
michael@0 | 5661 | #else |
michael@0 | 5662 | ForEachFont(FindPlatformFont, this); |
michael@0 | 5663 | #endif |
michael@0 | 5664 | mCurrGeneration = GetGeneration(); |
michael@0 | 5665 | mCachedEllipsisTextRun = nullptr; |
michael@0 | 5666 | } |
michael@0 | 5667 | } |
michael@0 | 5668 | |
michael@0 | 5669 | struct PrefFontCallbackData { |
michael@0 | 5670 | PrefFontCallbackData(nsTArray<nsRefPtr<gfxFontFamily> >& aFamiliesArray) |
michael@0 | 5671 | : mPrefFamilies(aFamiliesArray) |
michael@0 | 5672 | {} |
michael@0 | 5673 | |
michael@0 | 5674 | nsTArray<nsRefPtr<gfxFontFamily> >& mPrefFamilies; |
michael@0 | 5675 | |
michael@0 | 5676 | static bool AddFontFamilyEntry(eFontPrefLang aLang, const nsAString& aName, void *aClosure) |
michael@0 | 5677 | { |
michael@0 | 5678 | PrefFontCallbackData *prefFontData = static_cast<PrefFontCallbackData*>(aClosure); |
michael@0 | 5679 | |
michael@0 | 5680 | gfxFontFamily *family = gfxPlatformFontList::PlatformFontList()->FindFamily(aName); |
michael@0 | 5681 | if (family) { |
michael@0 | 5682 | prefFontData->mPrefFamilies.AppendElement(family); |
michael@0 | 5683 | } |
michael@0 | 5684 | return true; |
michael@0 | 5685 | } |
michael@0 | 5686 | }; |
michael@0 | 5687 | |
michael@0 | 5688 | already_AddRefed<gfxFont> |
michael@0 | 5689 | gfxFontGroup::WhichPrefFontSupportsChar(uint32_t aCh) |
michael@0 | 5690 | { |
michael@0 | 5691 | nsRefPtr<gfxFont> font; |
michael@0 | 5692 | |
michael@0 | 5693 | // get the pref font list if it hasn't been set up already |
michael@0 | 5694 | uint32_t unicodeRange = FindCharUnicodeRange(aCh); |
michael@0 | 5695 | eFontPrefLang charLang = gfxPlatform::GetPlatform()->GetFontPrefLangFor(unicodeRange); |
michael@0 | 5696 | |
michael@0 | 5697 | // if the last pref font was the first family in the pref list, no need to recheck through a list of families |
michael@0 | 5698 | if (mLastPrefFont && charLang == mLastPrefLang && |
michael@0 | 5699 | mLastPrefFirstFont && mLastPrefFont->HasCharacter(aCh)) { |
michael@0 | 5700 | font = mLastPrefFont; |
michael@0 | 5701 | return font.forget(); |
michael@0 | 5702 | } |
michael@0 | 5703 | |
michael@0 | 5704 | // based on char lang and page lang, set up list of pref lang fonts to check |
michael@0 | 5705 | eFontPrefLang prefLangs[kMaxLenPrefLangList]; |
michael@0 | 5706 | uint32_t i, numLangs = 0; |
michael@0 | 5707 | |
michael@0 | 5708 | gfxPlatform::GetPlatform()->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang); |
michael@0 | 5709 | |
michael@0 | 5710 | for (i = 0; i < numLangs; i++) { |
michael@0 | 5711 | nsAutoTArray<nsRefPtr<gfxFontFamily>, 5> families; |
michael@0 | 5712 | eFontPrefLang currentLang = prefLangs[i]; |
michael@0 | 5713 | |
michael@0 | 5714 | gfxPlatformFontList *fontList = gfxPlatformFontList::PlatformFontList(); |
michael@0 | 5715 | |
michael@0 | 5716 | // get the pref families for a single pref lang |
michael@0 | 5717 | if (!fontList->GetPrefFontFamilyEntries(currentLang, &families)) { |
michael@0 | 5718 | eFontPrefLang prefLangsToSearch[1] = { currentLang }; |
michael@0 | 5719 | PrefFontCallbackData prefFontData(families); |
michael@0 | 5720 | gfxPlatform::ForEachPrefFont(prefLangsToSearch, 1, PrefFontCallbackData::AddFontFamilyEntry, |
michael@0 | 5721 | &prefFontData); |
michael@0 | 5722 | fontList->SetPrefFontFamilyEntries(currentLang, families); |
michael@0 | 5723 | } |
michael@0 | 5724 | |
michael@0 | 5725 | // find the first pref font that includes the character |
michael@0 | 5726 | uint32_t j, numPrefs; |
michael@0 | 5727 | numPrefs = families.Length(); |
michael@0 | 5728 | for (j = 0; j < numPrefs; j++) { |
michael@0 | 5729 | // look up the appropriate face |
michael@0 | 5730 | gfxFontFamily *family = families[j]; |
michael@0 | 5731 | if (!family) continue; |
michael@0 | 5732 | |
michael@0 | 5733 | // if a pref font is used, it's likely to be used again in the same text run. |
michael@0 | 5734 | // the style doesn't change so the face lookup can be cached rather than calling |
michael@0 | 5735 | // FindOrMakeFont repeatedly. speeds up FindFontForChar lookup times for subsequent |
michael@0 | 5736 | // pref font lookups |
michael@0 | 5737 | if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) { |
michael@0 | 5738 | font = mLastPrefFont; |
michael@0 | 5739 | return font.forget(); |
michael@0 | 5740 | } |
michael@0 | 5741 | |
michael@0 | 5742 | bool needsBold; |
michael@0 | 5743 | gfxFontEntry *fe = family->FindFontForStyle(mStyle, needsBold); |
michael@0 | 5744 | // if ch in cmap, create and return a gfxFont |
michael@0 | 5745 | if (fe && fe->TestCharacterMap(aCh)) { |
michael@0 | 5746 | nsRefPtr<gfxFont> prefFont = fe->FindOrMakeFont(&mStyle, needsBold); |
michael@0 | 5747 | if (!prefFont) continue; |
michael@0 | 5748 | mLastPrefFamily = family; |
michael@0 | 5749 | mLastPrefFont = prefFont; |
michael@0 | 5750 | mLastPrefLang = charLang; |
michael@0 | 5751 | mLastPrefFirstFont = (i == 0 && j == 0); |
michael@0 | 5752 | return prefFont.forget(); |
michael@0 | 5753 | } |
michael@0 | 5754 | |
michael@0 | 5755 | } |
michael@0 | 5756 | } |
michael@0 | 5757 | |
michael@0 | 5758 | return nullptr; |
michael@0 | 5759 | } |
michael@0 | 5760 | |
michael@0 | 5761 | already_AddRefed<gfxFont> |
michael@0 | 5762 | gfxFontGroup::WhichSystemFontSupportsChar(uint32_t aCh, int32_t aRunScript) |
michael@0 | 5763 | { |
michael@0 | 5764 | gfxFontEntry *fe = |
michael@0 | 5765 | gfxPlatformFontList::PlatformFontList()-> |
michael@0 | 5766 | SystemFindFontForChar(aCh, aRunScript, &mStyle); |
michael@0 | 5767 | if (fe) { |
michael@0 | 5768 | bool wantBold = mStyle.ComputeWeight() >= 6; |
michael@0 | 5769 | nsRefPtr<gfxFont> font = |
michael@0 | 5770 | fe->FindOrMakeFont(&mStyle, wantBold && !fe->IsBold()); |
michael@0 | 5771 | return font.forget(); |
michael@0 | 5772 | } |
michael@0 | 5773 | |
michael@0 | 5774 | return nullptr; |
michael@0 | 5775 | } |
michael@0 | 5776 | |
michael@0 | 5777 | /*static*/ void |
michael@0 | 5778 | gfxFontGroup::Shutdown() |
michael@0 | 5779 | { |
michael@0 | 5780 | NS_IF_RELEASE(gLangService); |
michael@0 | 5781 | } |
michael@0 | 5782 | |
michael@0 | 5783 | nsILanguageAtomService* gfxFontGroup::gLangService = nullptr; |
michael@0 | 5784 | |
michael@0 | 5785 | |
michael@0 | 5786 | #define DEFAULT_PIXEL_FONT_SIZE 16.0f |
michael@0 | 5787 | |
michael@0 | 5788 | /*static*/ uint32_t |
michael@0 | 5789 | gfxFontStyle::ParseFontLanguageOverride(const nsString& aLangTag) |
michael@0 | 5790 | { |
michael@0 | 5791 | if (!aLangTag.Length() || aLangTag.Length() > 4) { |
michael@0 | 5792 | return NO_FONT_LANGUAGE_OVERRIDE; |
michael@0 | 5793 | } |
michael@0 | 5794 | uint32_t index, result = 0; |
michael@0 | 5795 | for (index = 0; index < aLangTag.Length(); ++index) { |
michael@0 | 5796 | char16_t ch = aLangTag[index]; |
michael@0 | 5797 | if (!nsCRT::IsAscii(ch)) { // valid tags are pure ASCII |
michael@0 | 5798 | return NO_FONT_LANGUAGE_OVERRIDE; |
michael@0 | 5799 | } |
michael@0 | 5800 | result = (result << 8) + ch; |
michael@0 | 5801 | } |
michael@0 | 5802 | while (index++ < 4) { |
michael@0 | 5803 | result = (result << 8) + 0x20; |
michael@0 | 5804 | } |
michael@0 | 5805 | return result; |
michael@0 | 5806 | } |
michael@0 | 5807 | |
michael@0 | 5808 | gfxFontStyle::gfxFontStyle() : |
michael@0 | 5809 | language(nsGkAtoms::x_western), |
michael@0 | 5810 | size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(0.0f), |
michael@0 | 5811 | languageOverride(NO_FONT_LANGUAGE_OVERRIDE), |
michael@0 | 5812 | weight(NS_FONT_WEIGHT_NORMAL), stretch(NS_FONT_STRETCH_NORMAL), |
michael@0 | 5813 | systemFont(true), printerFont(false), useGrayscaleAntialiasing(false), |
michael@0 | 5814 | style(NS_FONT_STYLE_NORMAL) |
michael@0 | 5815 | { |
michael@0 | 5816 | } |
michael@0 | 5817 | |
michael@0 | 5818 | gfxFontStyle::gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch, |
michael@0 | 5819 | gfxFloat aSize, nsIAtom *aLanguage, |
michael@0 | 5820 | float aSizeAdjust, bool aSystemFont, |
michael@0 | 5821 | bool aPrinterFont, |
michael@0 | 5822 | const nsString& aLanguageOverride): |
michael@0 | 5823 | language(aLanguage), |
michael@0 | 5824 | size(aSize), sizeAdjust(aSizeAdjust), |
michael@0 | 5825 | languageOverride(ParseFontLanguageOverride(aLanguageOverride)), |
michael@0 | 5826 | weight(aWeight), stretch(aStretch), |
michael@0 | 5827 | systemFont(aSystemFont), printerFont(aPrinterFont), |
michael@0 | 5828 | useGrayscaleAntialiasing(false), style(aStyle) |
michael@0 | 5829 | { |
michael@0 | 5830 | MOZ_ASSERT(!mozilla::IsNaN(size)); |
michael@0 | 5831 | MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust)); |
michael@0 | 5832 | |
michael@0 | 5833 | if (weight > 900) |
michael@0 | 5834 | weight = 900; |
michael@0 | 5835 | if (weight < 100) |
michael@0 | 5836 | weight = 100; |
michael@0 | 5837 | |
michael@0 | 5838 | if (size >= FONT_MAX_SIZE) { |
michael@0 | 5839 | size = FONT_MAX_SIZE; |
michael@0 | 5840 | sizeAdjust = 0.0; |
michael@0 | 5841 | } else if (size < 0.0) { |
michael@0 | 5842 | NS_WARNING("negative font size"); |
michael@0 | 5843 | size = 0.0; |
michael@0 | 5844 | } |
michael@0 | 5845 | |
michael@0 | 5846 | if (!language) { |
michael@0 | 5847 | NS_WARNING("null language"); |
michael@0 | 5848 | language = nsGkAtoms::x_western; |
michael@0 | 5849 | } |
michael@0 | 5850 | } |
michael@0 | 5851 | |
michael@0 | 5852 | gfxFontStyle::gfxFontStyle(const gfxFontStyle& aStyle) : |
michael@0 | 5853 | language(aStyle.language), |
michael@0 | 5854 | featureValueLookup(aStyle.featureValueLookup), |
michael@0 | 5855 | size(aStyle.size), sizeAdjust(aStyle.sizeAdjust), |
michael@0 | 5856 | languageOverride(aStyle.languageOverride), |
michael@0 | 5857 | weight(aStyle.weight), stretch(aStyle.stretch), |
michael@0 | 5858 | systemFont(aStyle.systemFont), printerFont(aStyle.printerFont), |
michael@0 | 5859 | useGrayscaleAntialiasing(aStyle.useGrayscaleAntialiasing), |
michael@0 | 5860 | style(aStyle.style) |
michael@0 | 5861 | { |
michael@0 | 5862 | featureSettings.AppendElements(aStyle.featureSettings); |
michael@0 | 5863 | alternateValues.AppendElements(aStyle.alternateValues); |
michael@0 | 5864 | } |
michael@0 | 5865 | |
michael@0 | 5866 | int8_t |
michael@0 | 5867 | gfxFontStyle::ComputeWeight() const |
michael@0 | 5868 | { |
michael@0 | 5869 | int8_t baseWeight = (weight + 50) / 100; |
michael@0 | 5870 | |
michael@0 | 5871 | if (baseWeight < 0) |
michael@0 | 5872 | baseWeight = 0; |
michael@0 | 5873 | if (baseWeight > 9) |
michael@0 | 5874 | baseWeight = 9; |
michael@0 | 5875 | |
michael@0 | 5876 | return baseWeight; |
michael@0 | 5877 | } |
michael@0 | 5878 | |
michael@0 | 5879 | void |
michael@0 | 5880 | gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, |
michael@0 | 5881 | const char16_t *aString, |
michael@0 | 5882 | uint32_t aLength) |
michael@0 | 5883 | { |
michael@0 | 5884 | CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset; |
michael@0 | 5885 | |
michael@0 | 5886 | gfxTextRun::CompressedGlyph extendCluster; |
michael@0 | 5887 | extendCluster.SetComplex(false, true, 0); |
michael@0 | 5888 | |
michael@0 | 5889 | ClusterIterator iter(aString, aLength); |
michael@0 | 5890 | |
michael@0 | 5891 | // the ClusterIterator won't be able to tell us if the string |
michael@0 | 5892 | // _begins_ with a cluster-extender, so we handle that here |
michael@0 | 5893 | if (aLength && IsClusterExtender(*aString)) { |
michael@0 | 5894 | *glyphs = extendCluster; |
michael@0 | 5895 | } |
michael@0 | 5896 | |
michael@0 | 5897 | while (!iter.AtEnd()) { |
michael@0 | 5898 | if (*iter == char16_t(' ')) { |
michael@0 | 5899 | glyphs->SetIsSpace(); |
michael@0 | 5900 | } |
michael@0 | 5901 | // advance iter to the next cluster-start (or end of text) |
michael@0 | 5902 | iter.Next(); |
michael@0 | 5903 | // step past the first char of the cluster |
michael@0 | 5904 | aString++; |
michael@0 | 5905 | glyphs++; |
michael@0 | 5906 | // mark all the rest as cluster-continuations |
michael@0 | 5907 | while (aString < iter) { |
michael@0 | 5908 | *glyphs = extendCluster; |
michael@0 | 5909 | if (NS_IS_LOW_SURROGATE(*aString)) { |
michael@0 | 5910 | glyphs->SetIsLowSurrogate(); |
michael@0 | 5911 | } |
michael@0 | 5912 | glyphs++; |
michael@0 | 5913 | aString++; |
michael@0 | 5914 | } |
michael@0 | 5915 | } |
michael@0 | 5916 | } |
michael@0 | 5917 | |
michael@0 | 5918 | void |
michael@0 | 5919 | gfxShapedText::SetupClusterBoundaries(uint32_t aOffset, |
michael@0 | 5920 | const uint8_t *aString, |
michael@0 | 5921 | uint32_t aLength) |
michael@0 | 5922 | { |
michael@0 | 5923 | CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset; |
michael@0 | 5924 | const uint8_t *limit = aString + aLength; |
michael@0 | 5925 | |
michael@0 | 5926 | while (aString < limit) { |
michael@0 | 5927 | if (*aString == uint8_t(' ')) { |
michael@0 | 5928 | glyphs->SetIsSpace(); |
michael@0 | 5929 | } |
michael@0 | 5930 | aString++; |
michael@0 | 5931 | glyphs++; |
michael@0 | 5932 | } |
michael@0 | 5933 | } |
michael@0 | 5934 | |
michael@0 | 5935 | gfxShapedText::DetailedGlyph * |
michael@0 | 5936 | gfxShapedText::AllocateDetailedGlyphs(uint32_t aIndex, uint32_t aCount) |
michael@0 | 5937 | { |
michael@0 | 5938 | NS_ASSERTION(aIndex < GetLength(), "Index out of range"); |
michael@0 | 5939 | |
michael@0 | 5940 | if (!mDetailedGlyphs) { |
michael@0 | 5941 | mDetailedGlyphs = new DetailedGlyphStore(); |
michael@0 | 5942 | } |
michael@0 | 5943 | |
michael@0 | 5944 | DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount); |
michael@0 | 5945 | if (!details) { |
michael@0 | 5946 | GetCharacterGlyphs()[aIndex].SetMissing(0); |
michael@0 | 5947 | return nullptr; |
michael@0 | 5948 | } |
michael@0 | 5949 | |
michael@0 | 5950 | return details; |
michael@0 | 5951 | } |
michael@0 | 5952 | |
michael@0 | 5953 | void |
michael@0 | 5954 | gfxShapedText::SetGlyphs(uint32_t aIndex, CompressedGlyph aGlyph, |
michael@0 | 5955 | const DetailedGlyph *aGlyphs) |
michael@0 | 5956 | { |
michael@0 | 5957 | NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here"); |
michael@0 | 5958 | NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(), |
michael@0 | 5959 | "First character can't be a ligature continuation!"); |
michael@0 | 5960 | |
michael@0 | 5961 | uint32_t glyphCount = aGlyph.GetGlyphCount(); |
michael@0 | 5962 | if (glyphCount > 0) { |
michael@0 | 5963 | DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount); |
michael@0 | 5964 | if (!details) { |
michael@0 | 5965 | return; |
michael@0 | 5966 | } |
michael@0 | 5967 | memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount); |
michael@0 | 5968 | } |
michael@0 | 5969 | GetCharacterGlyphs()[aIndex] = aGlyph; |
michael@0 | 5970 | } |
michael@0 | 5971 | |
michael@0 | 5972 | #define ZWNJ 0x200C |
michael@0 | 5973 | #define ZWJ 0x200D |
michael@0 | 5974 | // U+061C ARABIC LETTER MARK is expected to be added to XIDMOD_DEFAULT_IGNORABLE |
michael@0 | 5975 | // in a future Unicode update. Add it manually for now |
michael@0 | 5976 | #define ALM 0x061C |
michael@0 | 5977 | static inline bool |
michael@0 | 5978 | IsDefaultIgnorable(uint32_t aChar) |
michael@0 | 5979 | { |
michael@0 | 5980 | return GetIdentifierModification(aChar) == XIDMOD_DEFAULT_IGNORABLE || |
michael@0 | 5981 | aChar == ZWNJ || aChar == ZWJ || aChar == ALM; |
michael@0 | 5982 | } |
michael@0 | 5983 | |
michael@0 | 5984 | void |
michael@0 | 5985 | gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont) |
michael@0 | 5986 | { |
michael@0 | 5987 | uint8_t category = GetGeneralCategory(aChar); |
michael@0 | 5988 | if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK && |
michael@0 | 5989 | category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) |
michael@0 | 5990 | { |
michael@0 | 5991 | GetCharacterGlyphs()[aIndex].SetComplex(false, true, 0); |
michael@0 | 5992 | } |
michael@0 | 5993 | |
michael@0 | 5994 | DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); |
michael@0 | 5995 | if (!details) { |
michael@0 | 5996 | return; |
michael@0 | 5997 | } |
michael@0 | 5998 | |
michael@0 | 5999 | details->mGlyphID = aChar; |
michael@0 | 6000 | if (IsDefaultIgnorable(aChar)) { |
michael@0 | 6001 | // Setting advance width to zero will prevent drawing the hexbox |
michael@0 | 6002 | details->mAdvance = 0; |
michael@0 | 6003 | } else { |
michael@0 | 6004 | gfxFloat width = |
michael@0 | 6005 | std::max(aFont->GetMetrics().aveCharWidth, |
michael@0 | 6006 | gfxFontMissingGlyphs::GetDesiredMinWidth(aChar, |
michael@0 | 6007 | mAppUnitsPerDevUnit)); |
michael@0 | 6008 | details->mAdvance = uint32_t(width * mAppUnitsPerDevUnit); |
michael@0 | 6009 | } |
michael@0 | 6010 | details->mXOffset = 0; |
michael@0 | 6011 | details->mYOffset = 0; |
michael@0 | 6012 | GetCharacterGlyphs()[aIndex].SetMissing(1); |
michael@0 | 6013 | } |
michael@0 | 6014 | |
michael@0 | 6015 | bool |
michael@0 | 6016 | gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh) |
michael@0 | 6017 | { |
michael@0 | 6018 | if (IsDefaultIgnorable(aCh)) { |
michael@0 | 6019 | DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1); |
michael@0 | 6020 | if (details) { |
michael@0 | 6021 | details->mGlyphID = aCh; |
michael@0 | 6022 | details->mAdvance = 0; |
michael@0 | 6023 | details->mXOffset = 0; |
michael@0 | 6024 | details->mYOffset = 0; |
michael@0 | 6025 | GetCharacterGlyphs()[aIndex].SetMissing(1); |
michael@0 | 6026 | return true; |
michael@0 | 6027 | } |
michael@0 | 6028 | } |
michael@0 | 6029 | return false; |
michael@0 | 6030 | } |
michael@0 | 6031 | |
michael@0 | 6032 | void |
michael@0 | 6033 | gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset, |
michael@0 | 6034 | uint32_t aOffset, |
michael@0 | 6035 | uint32_t aLength) |
michael@0 | 6036 | { |
michael@0 | 6037 | uint32_t synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit; |
michael@0 | 6038 | CompressedGlyph *charGlyphs = GetCharacterGlyphs(); |
michael@0 | 6039 | for (uint32_t i = aOffset; i < aOffset + aLength; ++i) { |
michael@0 | 6040 | CompressedGlyph *glyphData = charGlyphs + i; |
michael@0 | 6041 | if (glyphData->IsSimpleGlyph()) { |
michael@0 | 6042 | // simple glyphs ==> just add the advance |
michael@0 | 6043 | int32_t advance = glyphData->GetSimpleAdvance() + synAppUnitOffset; |
michael@0 | 6044 | if (CompressedGlyph::IsSimpleAdvance(advance)) { |
michael@0 | 6045 | glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph()); |
michael@0 | 6046 | } else { |
michael@0 | 6047 | // rare case, tested by making this the default |
michael@0 | 6048 | uint32_t glyphIndex = glyphData->GetSimpleGlyph(); |
michael@0 | 6049 | glyphData->SetComplex(true, true, 1); |
michael@0 | 6050 | DetailedGlyph detail = {glyphIndex, advance, 0, 0}; |
michael@0 | 6051 | SetGlyphs(i, *glyphData, &detail); |
michael@0 | 6052 | } |
michael@0 | 6053 | } else { |
michael@0 | 6054 | // complex glyphs ==> add offset at cluster/ligature boundaries |
michael@0 | 6055 | uint32_t detailedLength = glyphData->GetGlyphCount(); |
michael@0 | 6056 | if (detailedLength) { |
michael@0 | 6057 | DetailedGlyph *details = GetDetailedGlyphs(i); |
michael@0 | 6058 | if (!details) { |
michael@0 | 6059 | continue; |
michael@0 | 6060 | } |
michael@0 | 6061 | if (IsRightToLeft()) { |
michael@0 | 6062 | details[0].mAdvance += synAppUnitOffset; |
michael@0 | 6063 | } else { |
michael@0 | 6064 | details[detailedLength - 1].mAdvance += synAppUnitOffset; |
michael@0 | 6065 | } |
michael@0 | 6066 | } |
michael@0 | 6067 | } |
michael@0 | 6068 | } |
michael@0 | 6069 | } |
michael@0 | 6070 | |
michael@0 | 6071 | bool |
michael@0 | 6072 | gfxTextRun::GlyphRunIterator::NextRun() { |
michael@0 | 6073 | if (mNextIndex >= mTextRun->mGlyphRuns.Length()) |
michael@0 | 6074 | return false; |
michael@0 | 6075 | mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex]; |
michael@0 | 6076 | if (mGlyphRun->mCharacterOffset >= mEndOffset) |
michael@0 | 6077 | return false; |
michael@0 | 6078 | |
michael@0 | 6079 | mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset); |
michael@0 | 6080 | uint32_t last = mNextIndex + 1 < mTextRun->mGlyphRuns.Length() |
michael@0 | 6081 | ? mTextRun->mGlyphRuns[mNextIndex + 1].mCharacterOffset : mTextRun->GetLength(); |
michael@0 | 6082 | mStringEnd = std::min(mEndOffset, last); |
michael@0 | 6083 | |
michael@0 | 6084 | ++mNextIndex; |
michael@0 | 6085 | return true; |
michael@0 | 6086 | } |
michael@0 | 6087 | |
michael@0 | 6088 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 6089 | static void |
michael@0 | 6090 | AccountStorageForTextRun(gfxTextRun *aTextRun, int32_t aSign) |
michael@0 | 6091 | { |
michael@0 | 6092 | // Ignores detailed glyphs... we don't know when those have been constructed |
michael@0 | 6093 | // Also ignores gfxSkipChars dynamic storage (which won't be anything |
michael@0 | 6094 | // for preformatted text) |
michael@0 | 6095 | // Also ignores GlyphRun array, again because it hasn't been constructed |
michael@0 | 6096 | // by the time this gets called. If there's only one glyphrun that's stored |
michael@0 | 6097 | // directly in the textrun anyway so no additional overhead. |
michael@0 | 6098 | uint32_t length = aTextRun->GetLength(); |
michael@0 | 6099 | int32_t bytes = length * sizeof(gfxTextRun::CompressedGlyph); |
michael@0 | 6100 | bytes += sizeof(gfxTextRun); |
michael@0 | 6101 | gTextRunStorage += bytes*aSign; |
michael@0 | 6102 | gTextRunStorageHighWaterMark = std::max(gTextRunStorageHighWaterMark, gTextRunStorage); |
michael@0 | 6103 | } |
michael@0 | 6104 | #endif |
michael@0 | 6105 | |
michael@0 | 6106 | // Helper for textRun creation to preallocate storage for glyph records; |
michael@0 | 6107 | // this function returns a pointer to the newly-allocated glyph storage. |
michael@0 | 6108 | // Returns nullptr if allocation fails. |
michael@0 | 6109 | void * |
michael@0 | 6110 | gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength) |
michael@0 | 6111 | { |
michael@0 | 6112 | // Allocate the storage we need, returning nullptr on failure rather than |
michael@0 | 6113 | // throwing an exception (because web content can create huge runs). |
michael@0 | 6114 | void *storage = moz_malloc(aSize + aLength * sizeof(CompressedGlyph)); |
michael@0 | 6115 | if (!storage) { |
michael@0 | 6116 | NS_WARNING("failed to allocate storage for text run!"); |
michael@0 | 6117 | return nullptr; |
michael@0 | 6118 | } |
michael@0 | 6119 | |
michael@0 | 6120 | // Initialize the glyph storage (beyond aSize) to zero |
michael@0 | 6121 | memset(reinterpret_cast<char*>(storage) + aSize, 0, |
michael@0 | 6122 | aLength * sizeof(CompressedGlyph)); |
michael@0 | 6123 | |
michael@0 | 6124 | return storage; |
michael@0 | 6125 | } |
michael@0 | 6126 | |
michael@0 | 6127 | gfxTextRun * |
michael@0 | 6128 | gfxTextRun::Create(const gfxTextRunFactory::Parameters *aParams, |
michael@0 | 6129 | uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags) |
michael@0 | 6130 | { |
michael@0 | 6131 | void *storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength); |
michael@0 | 6132 | if (!storage) { |
michael@0 | 6133 | return nullptr; |
michael@0 | 6134 | } |
michael@0 | 6135 | |
michael@0 | 6136 | return new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags); |
michael@0 | 6137 | } |
michael@0 | 6138 | |
michael@0 | 6139 | gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams, |
michael@0 | 6140 | uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags) |
michael@0 | 6141 | : gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit) |
michael@0 | 6142 | , mUserData(aParams->mUserData) |
michael@0 | 6143 | , mFontGroup(aFontGroup) |
michael@0 | 6144 | , mReleasedFontGroup(false) |
michael@0 | 6145 | { |
michael@0 | 6146 | NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale"); |
michael@0 | 6147 | MOZ_COUNT_CTOR(gfxTextRun); |
michael@0 | 6148 | NS_ADDREF(mFontGroup); |
michael@0 | 6149 | |
michael@0 | 6150 | #ifndef RELEASE_BUILD |
michael@0 | 6151 | gfxTextPerfMetrics *tp = aFontGroup->GetTextPerfMetrics(); |
michael@0 | 6152 | if (tp) { |
michael@0 | 6153 | tp->current.textrunConst++; |
michael@0 | 6154 | } |
michael@0 | 6155 | #endif |
michael@0 | 6156 | |
michael@0 | 6157 | mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1); |
michael@0 | 6158 | |
michael@0 | 6159 | if (aParams->mSkipChars) { |
michael@0 | 6160 | mSkipChars.TakeFrom(aParams->mSkipChars); |
michael@0 | 6161 | } |
michael@0 | 6162 | |
michael@0 | 6163 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 6164 | AccountStorageForTextRun(this, 1); |
michael@0 | 6165 | #endif |
michael@0 | 6166 | |
michael@0 | 6167 | mSkipDrawing = mFontGroup->ShouldSkipDrawing(); |
michael@0 | 6168 | } |
michael@0 | 6169 | |
michael@0 | 6170 | gfxTextRun::~gfxTextRun() |
michael@0 | 6171 | { |
michael@0 | 6172 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 6173 | AccountStorageForTextRun(this, -1); |
michael@0 | 6174 | #endif |
michael@0 | 6175 | #ifdef DEBUG |
michael@0 | 6176 | // Make it easy to detect a dead text run |
michael@0 | 6177 | mFlags = 0xFFFFFFFF; |
michael@0 | 6178 | #endif |
michael@0 | 6179 | |
michael@0 | 6180 | // The cached ellipsis textrun (if any) in a fontgroup will have already |
michael@0 | 6181 | // been told to release its reference to the group, so we mustn't do that |
michael@0 | 6182 | // again here. |
michael@0 | 6183 | if (!mReleasedFontGroup) { |
michael@0 | 6184 | #ifndef RELEASE_BUILD |
michael@0 | 6185 | gfxTextPerfMetrics *tp = mFontGroup->GetTextPerfMetrics(); |
michael@0 | 6186 | if (tp) { |
michael@0 | 6187 | tp->current.textrunDestr++; |
michael@0 | 6188 | } |
michael@0 | 6189 | #endif |
michael@0 | 6190 | NS_RELEASE(mFontGroup); |
michael@0 | 6191 | } |
michael@0 | 6192 | |
michael@0 | 6193 | MOZ_COUNT_DTOR(gfxTextRun); |
michael@0 | 6194 | } |
michael@0 | 6195 | |
michael@0 | 6196 | void |
michael@0 | 6197 | gfxTextRun::ReleaseFontGroup() |
michael@0 | 6198 | { |
michael@0 | 6199 | NS_ASSERTION(!mReleasedFontGroup, "doubly released!"); |
michael@0 | 6200 | NS_RELEASE(mFontGroup); |
michael@0 | 6201 | mReleasedFontGroup = true; |
michael@0 | 6202 | } |
michael@0 | 6203 | |
michael@0 | 6204 | bool |
michael@0 | 6205 | gfxTextRun::SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength, |
michael@0 | 6206 | uint8_t *aBreakBefore, |
michael@0 | 6207 | gfxContext *aRefContext) |
michael@0 | 6208 | { |
michael@0 | 6209 | NS_ASSERTION(aStart + aLength <= GetLength(), "Overflow"); |
michael@0 | 6210 | |
michael@0 | 6211 | uint32_t changed = 0; |
michael@0 | 6212 | uint32_t i; |
michael@0 | 6213 | CompressedGlyph *charGlyphs = mCharacterGlyphs + aStart; |
michael@0 | 6214 | for (i = 0; i < aLength; ++i) { |
michael@0 | 6215 | uint8_t canBreak = aBreakBefore[i]; |
michael@0 | 6216 | if (canBreak && !charGlyphs[i].IsClusterStart()) { |
michael@0 | 6217 | // This can happen ... there is no guarantee that our linebreaking rules |
michael@0 | 6218 | // align with the platform's idea of what constitutes a cluster. |
michael@0 | 6219 | NS_WARNING("Break suggested inside cluster!"); |
michael@0 | 6220 | canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE; |
michael@0 | 6221 | } |
michael@0 | 6222 | changed |= charGlyphs[i].SetCanBreakBefore(canBreak); |
michael@0 | 6223 | } |
michael@0 | 6224 | return changed != 0; |
michael@0 | 6225 | } |
michael@0 | 6226 | |
michael@0 | 6227 | gfxTextRun::LigatureData |
michael@0 | 6228 | gfxTextRun::ComputeLigatureData(uint32_t aPartStart, uint32_t aPartEnd, |
michael@0 | 6229 | PropertyProvider *aProvider) |
michael@0 | 6230 | { |
michael@0 | 6231 | NS_ASSERTION(aPartStart < aPartEnd, "Computing ligature data for empty range"); |
michael@0 | 6232 | NS_ASSERTION(aPartEnd <= GetLength(), "Character length overflow"); |
michael@0 | 6233 | |
michael@0 | 6234 | LigatureData result; |
michael@0 | 6235 | CompressedGlyph *charGlyphs = mCharacterGlyphs; |
michael@0 | 6236 | |
michael@0 | 6237 | uint32_t i; |
michael@0 | 6238 | for (i = aPartStart; !charGlyphs[i].IsLigatureGroupStart(); --i) { |
michael@0 | 6239 | NS_ASSERTION(i > 0, "Ligature at the start of the run??"); |
michael@0 | 6240 | } |
michael@0 | 6241 | result.mLigatureStart = i; |
michael@0 | 6242 | for (i = aPartStart + 1; i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) { |
michael@0 | 6243 | } |
michael@0 | 6244 | result.mLigatureEnd = i; |
michael@0 | 6245 | |
michael@0 | 6246 | int32_t ligatureWidth = |
michael@0 | 6247 | GetAdvanceForGlyphs(result.mLigatureStart, result.mLigatureEnd); |
michael@0 | 6248 | // Count the number of started clusters we have seen |
michael@0 | 6249 | uint32_t totalClusterCount = 0; |
michael@0 | 6250 | uint32_t partClusterIndex = 0; |
michael@0 | 6251 | uint32_t partClusterCount = 0; |
michael@0 | 6252 | for (i = result.mLigatureStart; i < result.mLigatureEnd; ++i) { |
michael@0 | 6253 | // Treat the first character of the ligature as the start of a |
michael@0 | 6254 | // cluster for our purposes of allocating ligature width to its |
michael@0 | 6255 | // characters. |
michael@0 | 6256 | if (i == result.mLigatureStart || charGlyphs[i].IsClusterStart()) { |
michael@0 | 6257 | ++totalClusterCount; |
michael@0 | 6258 | if (i < aPartStart) { |
michael@0 | 6259 | ++partClusterIndex; |
michael@0 | 6260 | } else if (i < aPartEnd) { |
michael@0 | 6261 | ++partClusterCount; |
michael@0 | 6262 | } |
michael@0 | 6263 | } |
michael@0 | 6264 | } |
michael@0 | 6265 | NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??"); |
michael@0 | 6266 | result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount); |
michael@0 | 6267 | result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount); |
michael@0 | 6268 | |
michael@0 | 6269 | // Any rounding errors are apportioned to the final part of the ligature, |
michael@0 | 6270 | // so that measuring all parts of a ligature and summing them is equal to |
michael@0 | 6271 | // the ligature width. |
michael@0 | 6272 | if (aPartEnd == result.mLigatureEnd) { |
michael@0 | 6273 | gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount); |
michael@0 | 6274 | result.mPartWidth += ligatureWidth - allParts; |
michael@0 | 6275 | } |
michael@0 | 6276 | |
michael@0 | 6277 | if (partClusterCount == 0) { |
michael@0 | 6278 | // nothing to draw |
michael@0 | 6279 | result.mClipBeforePart = result.mClipAfterPart = true; |
michael@0 | 6280 | } else { |
michael@0 | 6281 | // Determine whether we should clip before or after this part when |
michael@0 | 6282 | // drawing its slice of the ligature. |
michael@0 | 6283 | // We need to clip before the part if any cluster is drawn before |
michael@0 | 6284 | // this part. |
michael@0 | 6285 | result.mClipBeforePart = partClusterIndex > 0; |
michael@0 | 6286 | // We need to clip after the part if any cluster is drawn after |
michael@0 | 6287 | // this part. |
michael@0 | 6288 | result.mClipAfterPart = partClusterIndex + partClusterCount < totalClusterCount; |
michael@0 | 6289 | } |
michael@0 | 6290 | |
michael@0 | 6291 | if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { |
michael@0 | 6292 | gfxFont::Spacing spacing; |
michael@0 | 6293 | if (aPartStart == result.mLigatureStart) { |
michael@0 | 6294 | aProvider->GetSpacing(aPartStart, 1, &spacing); |
michael@0 | 6295 | result.mPartWidth += spacing.mBefore; |
michael@0 | 6296 | } |
michael@0 | 6297 | if (aPartEnd == result.mLigatureEnd) { |
michael@0 | 6298 | aProvider->GetSpacing(aPartEnd - 1, 1, &spacing); |
michael@0 | 6299 | result.mPartWidth += spacing.mAfter; |
michael@0 | 6300 | } |
michael@0 | 6301 | } |
michael@0 | 6302 | |
michael@0 | 6303 | return result; |
michael@0 | 6304 | } |
michael@0 | 6305 | |
michael@0 | 6306 | gfxFloat |
michael@0 | 6307 | gfxTextRun::ComputePartialLigatureWidth(uint32_t aPartStart, uint32_t aPartEnd, |
michael@0 | 6308 | PropertyProvider *aProvider) |
michael@0 | 6309 | { |
michael@0 | 6310 | if (aPartStart >= aPartEnd) |
michael@0 | 6311 | return 0; |
michael@0 | 6312 | LigatureData data = ComputeLigatureData(aPartStart, aPartEnd, aProvider); |
michael@0 | 6313 | return data.mPartWidth; |
michael@0 | 6314 | } |
michael@0 | 6315 | |
michael@0 | 6316 | int32_t |
michael@0 | 6317 | gfxTextRun::GetAdvanceForGlyphs(uint32_t aStart, uint32_t aEnd) |
michael@0 | 6318 | { |
michael@0 | 6319 | const CompressedGlyph *glyphData = mCharacterGlyphs + aStart; |
michael@0 | 6320 | int32_t advance = 0; |
michael@0 | 6321 | uint32_t i; |
michael@0 | 6322 | for (i = aStart; i < aEnd; ++i, ++glyphData) { |
michael@0 | 6323 | if (glyphData->IsSimpleGlyph()) { |
michael@0 | 6324 | advance += glyphData->GetSimpleAdvance(); |
michael@0 | 6325 | } else { |
michael@0 | 6326 | uint32_t glyphCount = glyphData->GetGlyphCount(); |
michael@0 | 6327 | if (glyphCount == 0) { |
michael@0 | 6328 | continue; |
michael@0 | 6329 | } |
michael@0 | 6330 | const DetailedGlyph *details = GetDetailedGlyphs(i); |
michael@0 | 6331 | if (details) { |
michael@0 | 6332 | uint32_t j; |
michael@0 | 6333 | for (j = 0; j < glyphCount; ++j, ++details) { |
michael@0 | 6334 | advance += details->mAdvance; |
michael@0 | 6335 | } |
michael@0 | 6336 | } |
michael@0 | 6337 | } |
michael@0 | 6338 | } |
michael@0 | 6339 | return advance; |
michael@0 | 6340 | } |
michael@0 | 6341 | |
michael@0 | 6342 | static void |
michael@0 | 6343 | GetAdjustedSpacing(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd, |
michael@0 | 6344 | gfxTextRun::PropertyProvider *aProvider, |
michael@0 | 6345 | gfxTextRun::PropertyProvider::Spacing *aSpacing) |
michael@0 | 6346 | { |
michael@0 | 6347 | if (aStart >= aEnd) |
michael@0 | 6348 | return; |
michael@0 | 6349 | |
michael@0 | 6350 | aProvider->GetSpacing(aStart, aEnd - aStart, aSpacing); |
michael@0 | 6351 | |
michael@0 | 6352 | #ifdef DEBUG |
michael@0 | 6353 | // Check to see if we have spacing inside ligatures |
michael@0 | 6354 | |
michael@0 | 6355 | const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs(); |
michael@0 | 6356 | uint32_t i; |
michael@0 | 6357 | |
michael@0 | 6358 | for (i = aStart; i < aEnd; ++i) { |
michael@0 | 6359 | if (!charGlyphs[i].IsLigatureGroupStart()) { |
michael@0 | 6360 | NS_ASSERTION(i == aStart || aSpacing[i - aStart].mBefore == 0, |
michael@0 | 6361 | "Before-spacing inside a ligature!"); |
michael@0 | 6362 | NS_ASSERTION(i - 1 <= aStart || aSpacing[i - 1 - aStart].mAfter == 0, |
michael@0 | 6363 | "After-spacing inside a ligature!"); |
michael@0 | 6364 | } |
michael@0 | 6365 | } |
michael@0 | 6366 | #endif |
michael@0 | 6367 | } |
michael@0 | 6368 | |
michael@0 | 6369 | bool |
michael@0 | 6370 | gfxTextRun::GetAdjustedSpacingArray(uint32_t aStart, uint32_t aEnd, |
michael@0 | 6371 | PropertyProvider *aProvider, |
michael@0 | 6372 | uint32_t aSpacingStart, uint32_t aSpacingEnd, |
michael@0 | 6373 | nsTArray<PropertyProvider::Spacing> *aSpacing) |
michael@0 | 6374 | { |
michael@0 | 6375 | if (!aProvider || !(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) |
michael@0 | 6376 | return false; |
michael@0 | 6377 | if (!aSpacing->AppendElements(aEnd - aStart)) |
michael@0 | 6378 | return false; |
michael@0 | 6379 | memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing)*(aSpacingStart - aStart)); |
michael@0 | 6380 | GetAdjustedSpacing(this, aSpacingStart, aSpacingEnd, aProvider, |
michael@0 | 6381 | aSpacing->Elements() + aSpacingStart - aStart); |
michael@0 | 6382 | memset(aSpacing->Elements() + aSpacingEnd - aStart, 0, sizeof(gfxFont::Spacing)*(aEnd - aSpacingEnd)); |
michael@0 | 6383 | return true; |
michael@0 | 6384 | } |
michael@0 | 6385 | |
michael@0 | 6386 | void |
michael@0 | 6387 | gfxTextRun::ShrinkToLigatureBoundaries(uint32_t *aStart, uint32_t *aEnd) |
michael@0 | 6388 | { |
michael@0 | 6389 | if (*aStart >= *aEnd) |
michael@0 | 6390 | return; |
michael@0 | 6391 | |
michael@0 | 6392 | CompressedGlyph *charGlyphs = mCharacterGlyphs; |
michael@0 | 6393 | |
michael@0 | 6394 | while (*aStart < *aEnd && !charGlyphs[*aStart].IsLigatureGroupStart()) { |
michael@0 | 6395 | ++(*aStart); |
michael@0 | 6396 | } |
michael@0 | 6397 | if (*aEnd < GetLength()) { |
michael@0 | 6398 | while (*aEnd > *aStart && !charGlyphs[*aEnd].IsLigatureGroupStart()) { |
michael@0 | 6399 | --(*aEnd); |
michael@0 | 6400 | } |
michael@0 | 6401 | } |
michael@0 | 6402 | } |
michael@0 | 6403 | |
michael@0 | 6404 | void |
michael@0 | 6405 | gfxTextRun::DrawGlyphs(gfxFont *aFont, gfxContext *aContext, |
michael@0 | 6406 | DrawMode aDrawMode, gfxPoint *aPt, |
michael@0 | 6407 | gfxTextContextPaint *aContextPaint, |
michael@0 | 6408 | uint32_t aStart, uint32_t aEnd, |
michael@0 | 6409 | PropertyProvider *aProvider, |
michael@0 | 6410 | uint32_t aSpacingStart, uint32_t aSpacingEnd, |
michael@0 | 6411 | gfxTextRunDrawCallbacks *aCallbacks) |
michael@0 | 6412 | { |
michael@0 | 6413 | nsAutoTArray<PropertyProvider::Spacing,200> spacingBuffer; |
michael@0 | 6414 | bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, |
michael@0 | 6415 | aSpacingStart, aSpacingEnd, &spacingBuffer); |
michael@0 | 6416 | aFont->Draw(this, aStart, aEnd, aContext, aDrawMode, aPt, |
michael@0 | 6417 | haveSpacing ? spacingBuffer.Elements() : nullptr, aContextPaint, |
michael@0 | 6418 | aCallbacks); |
michael@0 | 6419 | } |
michael@0 | 6420 | |
michael@0 | 6421 | static void |
michael@0 | 6422 | ClipPartialLigature(gfxTextRun *aTextRun, gfxFloat *aLeft, gfxFloat *aRight, |
michael@0 | 6423 | gfxFloat aXOrigin, gfxTextRun::LigatureData *aLigature) |
michael@0 | 6424 | { |
michael@0 | 6425 | if (aLigature->mClipBeforePart) { |
michael@0 | 6426 | if (aTextRun->IsRightToLeft()) { |
michael@0 | 6427 | *aRight = std::min(*aRight, aXOrigin); |
michael@0 | 6428 | } else { |
michael@0 | 6429 | *aLeft = std::max(*aLeft, aXOrigin); |
michael@0 | 6430 | } |
michael@0 | 6431 | } |
michael@0 | 6432 | if (aLigature->mClipAfterPart) { |
michael@0 | 6433 | gfxFloat endEdge = aXOrigin + aTextRun->GetDirection()*aLigature->mPartWidth; |
michael@0 | 6434 | if (aTextRun->IsRightToLeft()) { |
michael@0 | 6435 | *aLeft = std::max(*aLeft, endEdge); |
michael@0 | 6436 | } else { |
michael@0 | 6437 | *aRight = std::min(*aRight, endEdge); |
michael@0 | 6438 | } |
michael@0 | 6439 | } |
michael@0 | 6440 | } |
michael@0 | 6441 | |
michael@0 | 6442 | void |
michael@0 | 6443 | gfxTextRun::DrawPartialLigature(gfxFont *aFont, gfxContext *aCtx, |
michael@0 | 6444 | uint32_t aStart, uint32_t aEnd, |
michael@0 | 6445 | gfxPoint *aPt, |
michael@0 | 6446 | PropertyProvider *aProvider, |
michael@0 | 6447 | gfxTextRunDrawCallbacks *aCallbacks) |
michael@0 | 6448 | { |
michael@0 | 6449 | if (aStart >= aEnd) |
michael@0 | 6450 | return; |
michael@0 | 6451 | |
michael@0 | 6452 | // Draw partial ligature. We hack this by clipping the ligature. |
michael@0 | 6453 | LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider); |
michael@0 | 6454 | gfxRect clipExtents = aCtx->GetClipExtents(); |
michael@0 | 6455 | gfxFloat left = clipExtents.X()*mAppUnitsPerDevUnit; |
michael@0 | 6456 | gfxFloat right = clipExtents.XMost()*mAppUnitsPerDevUnit; |
michael@0 | 6457 | ClipPartialLigature(this, &left, &right, aPt->x, &data); |
michael@0 | 6458 | |
michael@0 | 6459 | { |
michael@0 | 6460 | // Need to preserve the path, otherwise this can break canvas text-on-path; |
michael@0 | 6461 | // in general it seems like a good thing, as naive callers probably won't |
michael@0 | 6462 | // expect gfxTextRun::Draw to implicitly destroy the current path. |
michael@0 | 6463 | gfxContextPathAutoSaveRestore savePath(aCtx); |
michael@0 | 6464 | |
michael@0 | 6465 | // use division here to ensure that when the rect is aligned on multiples |
michael@0 | 6466 | // of mAppUnitsPerDevUnit, we clip to true device unit boundaries. |
michael@0 | 6467 | // Also, make sure we snap the rectangle to device pixels. |
michael@0 | 6468 | aCtx->Save(); |
michael@0 | 6469 | aCtx->NewPath(); |
michael@0 | 6470 | aCtx->Rectangle(gfxRect(left / mAppUnitsPerDevUnit, |
michael@0 | 6471 | clipExtents.Y(), |
michael@0 | 6472 | (right - left) / mAppUnitsPerDevUnit, |
michael@0 | 6473 | clipExtents.Height()), true); |
michael@0 | 6474 | aCtx->Clip(); |
michael@0 | 6475 | } |
michael@0 | 6476 | |
michael@0 | 6477 | gfxFloat direction = GetDirection(); |
michael@0 | 6478 | gfxPoint pt(aPt->x - direction*data.mPartAdvance, aPt->y); |
michael@0 | 6479 | DrawGlyphs(aFont, aCtx, |
michael@0 | 6480 | aCallbacks ? DrawMode::GLYPH_PATH : DrawMode::GLYPH_FILL, &pt, |
michael@0 | 6481 | nullptr, data.mLigatureStart, data.mLigatureEnd, aProvider, |
michael@0 | 6482 | aStart, aEnd, aCallbacks); |
michael@0 | 6483 | aCtx->Restore(); |
michael@0 | 6484 | |
michael@0 | 6485 | aPt->x += direction*data.mPartWidth; |
michael@0 | 6486 | } |
michael@0 | 6487 | |
michael@0 | 6488 | // returns true if a glyph run is using a font with synthetic bolding enabled, false otherwise |
michael@0 | 6489 | static bool |
michael@0 | 6490 | HasSyntheticBold(gfxTextRun *aRun, uint32_t aStart, uint32_t aLength) |
michael@0 | 6491 | { |
michael@0 | 6492 | gfxTextRun::GlyphRunIterator iter(aRun, aStart, aLength); |
michael@0 | 6493 | while (iter.NextRun()) { |
michael@0 | 6494 | gfxFont *font = iter.GetGlyphRun()->mFont; |
michael@0 | 6495 | if (font && font->IsSyntheticBold()) { |
michael@0 | 6496 | return true; |
michael@0 | 6497 | } |
michael@0 | 6498 | } |
michael@0 | 6499 | |
michael@0 | 6500 | return false; |
michael@0 | 6501 | } |
michael@0 | 6502 | |
michael@0 | 6503 | // returns true if color is non-opaque (i.e. alpha != 1.0) or completely transparent, false otherwise |
michael@0 | 6504 | // if true, color is set on output |
michael@0 | 6505 | static bool |
michael@0 | 6506 | HasNonOpaqueColor(gfxContext *aContext, gfxRGBA& aCurrentColor) |
michael@0 | 6507 | { |
michael@0 | 6508 | if (aContext->GetDeviceColor(aCurrentColor)) { |
michael@0 | 6509 | if (aCurrentColor.a < 1.0 && aCurrentColor.a > 0.0) { |
michael@0 | 6510 | return true; |
michael@0 | 6511 | } |
michael@0 | 6512 | } |
michael@0 | 6513 | |
michael@0 | 6514 | return false; |
michael@0 | 6515 | } |
michael@0 | 6516 | |
michael@0 | 6517 | // helper class for double-buffering drawing with non-opaque color |
michael@0 | 6518 | struct BufferAlphaColor { |
michael@0 | 6519 | BufferAlphaColor(gfxContext *aContext) |
michael@0 | 6520 | : mContext(aContext) |
michael@0 | 6521 | { |
michael@0 | 6522 | |
michael@0 | 6523 | } |
michael@0 | 6524 | |
michael@0 | 6525 | ~BufferAlphaColor() {} |
michael@0 | 6526 | |
michael@0 | 6527 | void PushSolidColor(const gfxRect& aBounds, const gfxRGBA& aAlphaColor, uint32_t appsPerDevUnit) |
michael@0 | 6528 | { |
michael@0 | 6529 | mContext->Save(); |
michael@0 | 6530 | mContext->NewPath(); |
michael@0 | 6531 | mContext->Rectangle(gfxRect(aBounds.X() / appsPerDevUnit, |
michael@0 | 6532 | aBounds.Y() / appsPerDevUnit, |
michael@0 | 6533 | aBounds.Width() / appsPerDevUnit, |
michael@0 | 6534 | aBounds.Height() / appsPerDevUnit), true); |
michael@0 | 6535 | mContext->Clip(); |
michael@0 | 6536 | mContext->SetColor(gfxRGBA(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b)); |
michael@0 | 6537 | mContext->PushGroup(gfxContentType::COLOR_ALPHA); |
michael@0 | 6538 | mAlpha = aAlphaColor.a; |
michael@0 | 6539 | } |
michael@0 | 6540 | |
michael@0 | 6541 | void PopAlpha() |
michael@0 | 6542 | { |
michael@0 | 6543 | // pop the text, using the color alpha as the opacity |
michael@0 | 6544 | mContext->PopGroupToSource(); |
michael@0 | 6545 | mContext->SetOperator(gfxContext::OPERATOR_OVER); |
michael@0 | 6546 | mContext->Paint(mAlpha); |
michael@0 | 6547 | mContext->Restore(); |
michael@0 | 6548 | } |
michael@0 | 6549 | |
michael@0 | 6550 | gfxContext *mContext; |
michael@0 | 6551 | gfxFloat mAlpha; |
michael@0 | 6552 | }; |
michael@0 | 6553 | |
michael@0 | 6554 | void |
michael@0 | 6555 | gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, DrawMode aDrawMode, |
michael@0 | 6556 | uint32_t aStart, uint32_t aLength, |
michael@0 | 6557 | PropertyProvider *aProvider, gfxFloat *aAdvanceWidth, |
michael@0 | 6558 | gfxTextContextPaint *aContextPaint, |
michael@0 | 6559 | gfxTextRunDrawCallbacks *aCallbacks) |
michael@0 | 6560 | { |
michael@0 | 6561 | NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); |
michael@0 | 6562 | NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !(int(aDrawMode) & int(DrawMode::GLYPH_PATH)), |
michael@0 | 6563 | "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH"); |
michael@0 | 6564 | NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !aCallbacks, "callback must not be specified unless using GLYPH_PATH"); |
michael@0 | 6565 | |
michael@0 | 6566 | bool skipDrawing = mSkipDrawing; |
michael@0 | 6567 | if (aDrawMode == DrawMode::GLYPH_FILL) { |
michael@0 | 6568 | gfxRGBA currentColor; |
michael@0 | 6569 | if (aContext->GetDeviceColor(currentColor) && currentColor.a == 0) { |
michael@0 | 6570 | skipDrawing = true; |
michael@0 | 6571 | } |
michael@0 | 6572 | } |
michael@0 | 6573 | |
michael@0 | 6574 | gfxFloat direction = GetDirection(); |
michael@0 | 6575 | |
michael@0 | 6576 | if (skipDrawing) { |
michael@0 | 6577 | // We don't need to draw anything; |
michael@0 | 6578 | // but if the caller wants advance width, we need to compute it here |
michael@0 | 6579 | if (aAdvanceWidth) { |
michael@0 | 6580 | gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, |
michael@0 | 6581 | gfxFont::LOOSE_INK_EXTENTS, |
michael@0 | 6582 | aContext, aProvider); |
michael@0 | 6583 | *aAdvanceWidth = metrics.mAdvanceWidth * direction; |
michael@0 | 6584 | } |
michael@0 | 6585 | |
michael@0 | 6586 | // return without drawing |
michael@0 | 6587 | return; |
michael@0 | 6588 | } |
michael@0 | 6589 | |
michael@0 | 6590 | gfxPoint pt = aPt; |
michael@0 | 6591 | |
michael@0 | 6592 | // synthetic bolding draws glyphs twice ==> colors with opacity won't draw correctly unless first drawn without alpha |
michael@0 | 6593 | BufferAlphaColor syntheticBoldBuffer(aContext); |
michael@0 | 6594 | gfxRGBA currentColor; |
michael@0 | 6595 | bool needToRestore = false; |
michael@0 | 6596 | |
michael@0 | 6597 | if (aDrawMode == DrawMode::GLYPH_FILL && HasNonOpaqueColor(aContext, currentColor) |
michael@0 | 6598 | && HasSyntheticBold(this, aStart, aLength)) { |
michael@0 | 6599 | needToRestore = true; |
michael@0 | 6600 | // measure text, use the bounding box |
michael@0 | 6601 | gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, gfxFont::LOOSE_INK_EXTENTS, |
michael@0 | 6602 | aContext, aProvider); |
michael@0 | 6603 | metrics.mBoundingBox.MoveBy(aPt); |
michael@0 | 6604 | syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, GetAppUnitsPerDevUnit()); |
michael@0 | 6605 | } |
michael@0 | 6606 | |
michael@0 | 6607 | GlyphRunIterator iter(this, aStart, aLength); |
michael@0 | 6608 | while (iter.NextRun()) { |
michael@0 | 6609 | gfxFont *font = iter.GetGlyphRun()->mFont; |
michael@0 | 6610 | uint32_t start = iter.GetStringStart(); |
michael@0 | 6611 | uint32_t end = iter.GetStringEnd(); |
michael@0 | 6612 | uint32_t ligatureRunStart = start; |
michael@0 | 6613 | uint32_t ligatureRunEnd = end; |
michael@0 | 6614 | ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); |
michael@0 | 6615 | |
michael@0 | 6616 | bool drawPartial = aDrawMode == DrawMode::GLYPH_FILL || |
michael@0 | 6617 | (aDrawMode == DrawMode::GLYPH_PATH && aCallbacks); |
michael@0 | 6618 | |
michael@0 | 6619 | if (drawPartial) { |
michael@0 | 6620 | DrawPartialLigature(font, aContext, start, ligatureRunStart, &pt, |
michael@0 | 6621 | aProvider, aCallbacks); |
michael@0 | 6622 | } |
michael@0 | 6623 | |
michael@0 | 6624 | DrawGlyphs(font, aContext, aDrawMode, &pt, aContextPaint, ligatureRunStart, |
michael@0 | 6625 | ligatureRunEnd, aProvider, ligatureRunStart, ligatureRunEnd, |
michael@0 | 6626 | aCallbacks); |
michael@0 | 6627 | |
michael@0 | 6628 | if (drawPartial) { |
michael@0 | 6629 | DrawPartialLigature(font, aContext, ligatureRunEnd, end, &pt, |
michael@0 | 6630 | aProvider, aCallbacks); |
michael@0 | 6631 | } |
michael@0 | 6632 | } |
michael@0 | 6633 | |
michael@0 | 6634 | // composite result when synthetic bolding used |
michael@0 | 6635 | if (needToRestore) { |
michael@0 | 6636 | syntheticBoldBuffer.PopAlpha(); |
michael@0 | 6637 | } |
michael@0 | 6638 | |
michael@0 | 6639 | if (aAdvanceWidth) { |
michael@0 | 6640 | *aAdvanceWidth = (pt.x - aPt.x)*direction; |
michael@0 | 6641 | } |
michael@0 | 6642 | } |
michael@0 | 6643 | |
michael@0 | 6644 | void |
michael@0 | 6645 | gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont, |
michael@0 | 6646 | uint32_t aStart, uint32_t aEnd, |
michael@0 | 6647 | gfxFont::BoundingBoxType aBoundingBoxType, |
michael@0 | 6648 | gfxContext *aRefContext, |
michael@0 | 6649 | PropertyProvider *aProvider, |
michael@0 | 6650 | uint32_t aSpacingStart, uint32_t aSpacingEnd, |
michael@0 | 6651 | Metrics *aMetrics) |
michael@0 | 6652 | { |
michael@0 | 6653 | nsAutoTArray<PropertyProvider::Spacing,200> spacingBuffer; |
michael@0 | 6654 | bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, |
michael@0 | 6655 | aSpacingStart, aSpacingEnd, &spacingBuffer); |
michael@0 | 6656 | Metrics metrics = aFont->Measure(this, aStart, aEnd, aBoundingBoxType, aRefContext, |
michael@0 | 6657 | haveSpacing ? spacingBuffer.Elements() : nullptr); |
michael@0 | 6658 | aMetrics->CombineWith(metrics, IsRightToLeft()); |
michael@0 | 6659 | } |
michael@0 | 6660 | |
michael@0 | 6661 | void |
michael@0 | 6662 | gfxTextRun::AccumulatePartialLigatureMetrics(gfxFont *aFont, |
michael@0 | 6663 | uint32_t aStart, uint32_t aEnd, |
michael@0 | 6664 | gfxFont::BoundingBoxType aBoundingBoxType, gfxContext *aRefContext, |
michael@0 | 6665 | PropertyProvider *aProvider, Metrics *aMetrics) |
michael@0 | 6666 | { |
michael@0 | 6667 | if (aStart >= aEnd) |
michael@0 | 6668 | return; |
michael@0 | 6669 | |
michael@0 | 6670 | // Measure partial ligature. We hack this by clipping the metrics in the |
michael@0 | 6671 | // same way we clip the drawing. |
michael@0 | 6672 | LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider); |
michael@0 | 6673 | |
michael@0 | 6674 | // First measure the complete ligature |
michael@0 | 6675 | Metrics metrics; |
michael@0 | 6676 | AccumulateMetricsForRun(aFont, data.mLigatureStart, data.mLigatureEnd, |
michael@0 | 6677 | aBoundingBoxType, aRefContext, |
michael@0 | 6678 | aProvider, aStart, aEnd, &metrics); |
michael@0 | 6679 | |
michael@0 | 6680 | // Clip the bounding box to the ligature part |
michael@0 | 6681 | gfxFloat bboxLeft = metrics.mBoundingBox.X(); |
michael@0 | 6682 | gfxFloat bboxRight = metrics.mBoundingBox.XMost(); |
michael@0 | 6683 | // Where we are going to start "drawing" relative to our left baseline origin |
michael@0 | 6684 | gfxFloat origin = IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0; |
michael@0 | 6685 | ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data); |
michael@0 | 6686 | metrics.mBoundingBox.x = bboxLeft; |
michael@0 | 6687 | metrics.mBoundingBox.width = bboxRight - bboxLeft; |
michael@0 | 6688 | |
michael@0 | 6689 | // mBoundingBox is now relative to the left baseline origin for the entire |
michael@0 | 6690 | // ligature. Shift it left. |
michael@0 | 6691 | metrics.mBoundingBox.x -= |
michael@0 | 6692 | IsRightToLeft() ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth) |
michael@0 | 6693 | : data.mPartAdvance; |
michael@0 | 6694 | metrics.mAdvanceWidth = data.mPartWidth; |
michael@0 | 6695 | |
michael@0 | 6696 | aMetrics->CombineWith(metrics, IsRightToLeft()); |
michael@0 | 6697 | } |
michael@0 | 6698 | |
michael@0 | 6699 | gfxTextRun::Metrics |
michael@0 | 6700 | gfxTextRun::MeasureText(uint32_t aStart, uint32_t aLength, |
michael@0 | 6701 | gfxFont::BoundingBoxType aBoundingBoxType, |
michael@0 | 6702 | gfxContext *aRefContext, |
michael@0 | 6703 | PropertyProvider *aProvider) |
michael@0 | 6704 | { |
michael@0 | 6705 | NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); |
michael@0 | 6706 | |
michael@0 | 6707 | Metrics accumulatedMetrics; |
michael@0 | 6708 | GlyphRunIterator iter(this, aStart, aLength); |
michael@0 | 6709 | while (iter.NextRun()) { |
michael@0 | 6710 | gfxFont *font = iter.GetGlyphRun()->mFont; |
michael@0 | 6711 | uint32_t start = iter.GetStringStart(); |
michael@0 | 6712 | uint32_t end = iter.GetStringEnd(); |
michael@0 | 6713 | uint32_t ligatureRunStart = start; |
michael@0 | 6714 | uint32_t ligatureRunEnd = end; |
michael@0 | 6715 | ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); |
michael@0 | 6716 | |
michael@0 | 6717 | AccumulatePartialLigatureMetrics(font, start, ligatureRunStart, |
michael@0 | 6718 | aBoundingBoxType, aRefContext, aProvider, &accumulatedMetrics); |
michael@0 | 6719 | |
michael@0 | 6720 | // XXX This sucks. We have to get glyph extents just so we can detect |
michael@0 | 6721 | // glyphs outside the font box, even when aBoundingBoxType is LOOSE, |
michael@0 | 6722 | // even though in almost all cases we could get correct results just |
michael@0 | 6723 | // by getting some ascent/descent from the font and using our stored |
michael@0 | 6724 | // advance widths. |
michael@0 | 6725 | AccumulateMetricsForRun(font, |
michael@0 | 6726 | ligatureRunStart, ligatureRunEnd, aBoundingBoxType, |
michael@0 | 6727 | aRefContext, aProvider, ligatureRunStart, ligatureRunEnd, |
michael@0 | 6728 | &accumulatedMetrics); |
michael@0 | 6729 | |
michael@0 | 6730 | AccumulatePartialLigatureMetrics(font, ligatureRunEnd, end, |
michael@0 | 6731 | aBoundingBoxType, aRefContext, aProvider, &accumulatedMetrics); |
michael@0 | 6732 | } |
michael@0 | 6733 | |
michael@0 | 6734 | return accumulatedMetrics; |
michael@0 | 6735 | } |
michael@0 | 6736 | |
michael@0 | 6737 | #define MEASUREMENT_BUFFER_SIZE 100 |
michael@0 | 6738 | |
michael@0 | 6739 | uint32_t |
michael@0 | 6740 | gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength, |
michael@0 | 6741 | bool aLineBreakBefore, gfxFloat aWidth, |
michael@0 | 6742 | PropertyProvider *aProvider, |
michael@0 | 6743 | bool aSuppressInitialBreak, |
michael@0 | 6744 | gfxFloat *aTrimWhitespace, |
michael@0 | 6745 | Metrics *aMetrics, |
michael@0 | 6746 | gfxFont::BoundingBoxType aBoundingBoxType, |
michael@0 | 6747 | gfxContext *aRefContext, |
michael@0 | 6748 | bool *aUsedHyphenation, |
michael@0 | 6749 | uint32_t *aLastBreak, |
michael@0 | 6750 | bool aCanWordWrap, |
michael@0 | 6751 | gfxBreakPriority *aBreakPriority) |
michael@0 | 6752 | { |
michael@0 | 6753 | aMaxLength = std::min(aMaxLength, GetLength() - aStart); |
michael@0 | 6754 | |
michael@0 | 6755 | NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range"); |
michael@0 | 6756 | |
michael@0 | 6757 | uint32_t bufferStart = aStart; |
michael@0 | 6758 | uint32_t bufferLength = std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE); |
michael@0 | 6759 | PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE]; |
michael@0 | 6760 | bool haveSpacing = aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) != 0; |
michael@0 | 6761 | if (haveSpacing) { |
michael@0 | 6762 | GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider, |
michael@0 | 6763 | spacingBuffer); |
michael@0 | 6764 | } |
michael@0 | 6765 | bool hyphenBuffer[MEASUREMENT_BUFFER_SIZE]; |
michael@0 | 6766 | bool haveHyphenation = aProvider && |
michael@0 | 6767 | (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_AUTO || |
michael@0 | 6768 | (aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_MANUAL && |
michael@0 | 6769 | (mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0)); |
michael@0 | 6770 | if (haveHyphenation) { |
michael@0 | 6771 | aProvider->GetHyphenationBreaks(bufferStart, bufferLength, |
michael@0 | 6772 | hyphenBuffer); |
michael@0 | 6773 | } |
michael@0 | 6774 | |
michael@0 | 6775 | gfxFloat width = 0; |
michael@0 | 6776 | gfxFloat advance = 0; |
michael@0 | 6777 | // The number of space characters that can be trimmed |
michael@0 | 6778 | uint32_t trimmableChars = 0; |
michael@0 | 6779 | // The amount of space removed by ignoring trimmableChars |
michael@0 | 6780 | gfxFloat trimmableAdvance = 0; |
michael@0 | 6781 | int32_t lastBreak = -1; |
michael@0 | 6782 | int32_t lastBreakTrimmableChars = -1; |
michael@0 | 6783 | gfxFloat lastBreakTrimmableAdvance = -1; |
michael@0 | 6784 | bool aborted = false; |
michael@0 | 6785 | uint32_t end = aStart + aMaxLength; |
michael@0 | 6786 | bool lastBreakUsedHyphenation = false; |
michael@0 | 6787 | |
michael@0 | 6788 | uint32_t ligatureRunStart = aStart; |
michael@0 | 6789 | uint32_t ligatureRunEnd = end; |
michael@0 | 6790 | ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); |
michael@0 | 6791 | |
michael@0 | 6792 | uint32_t i; |
michael@0 | 6793 | for (i = aStart; i < end; ++i) { |
michael@0 | 6794 | if (i >= bufferStart + bufferLength) { |
michael@0 | 6795 | // Fetch more spacing and hyphenation data |
michael@0 | 6796 | bufferStart = i; |
michael@0 | 6797 | bufferLength = std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE) - i; |
michael@0 | 6798 | if (haveSpacing) { |
michael@0 | 6799 | GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider, |
michael@0 | 6800 | spacingBuffer); |
michael@0 | 6801 | } |
michael@0 | 6802 | if (haveHyphenation) { |
michael@0 | 6803 | aProvider->GetHyphenationBreaks(bufferStart, bufferLength, |
michael@0 | 6804 | hyphenBuffer); |
michael@0 | 6805 | } |
michael@0 | 6806 | } |
michael@0 | 6807 | |
michael@0 | 6808 | // There can't be a word-wrap break opportunity at the beginning of the |
michael@0 | 6809 | // line: if the width is too small for even one character to fit, it |
michael@0 | 6810 | // could be the first and last break opportunity on the line, and that |
michael@0 | 6811 | // would trigger an infinite loop. |
michael@0 | 6812 | if (!aSuppressInitialBreak || i > aStart) { |
michael@0 | 6813 | bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1; |
michael@0 | 6814 | bool atHyphenationBreak = |
michael@0 | 6815 | !atNaturalBreak && haveHyphenation && hyphenBuffer[i - bufferStart]; |
michael@0 | 6816 | bool atBreak = atNaturalBreak || atHyphenationBreak; |
michael@0 | 6817 | bool wordWrapping = |
michael@0 | 6818 | aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() && |
michael@0 | 6819 | *aBreakPriority <= gfxBreakPriority::eWordWrapBreak; |
michael@0 | 6820 | |
michael@0 | 6821 | if (atBreak || wordWrapping) { |
michael@0 | 6822 | gfxFloat hyphenatedAdvance = advance; |
michael@0 | 6823 | if (atHyphenationBreak) { |
michael@0 | 6824 | hyphenatedAdvance += aProvider->GetHyphenWidth(); |
michael@0 | 6825 | } |
michael@0 | 6826 | |
michael@0 | 6827 | if (lastBreak < 0 || width + hyphenatedAdvance - trimmableAdvance <= aWidth) { |
michael@0 | 6828 | // We can break here. |
michael@0 | 6829 | lastBreak = i; |
michael@0 | 6830 | lastBreakTrimmableChars = trimmableChars; |
michael@0 | 6831 | lastBreakTrimmableAdvance = trimmableAdvance; |
michael@0 | 6832 | lastBreakUsedHyphenation = atHyphenationBreak; |
michael@0 | 6833 | *aBreakPriority = atBreak ? gfxBreakPriority::eNormalBreak |
michael@0 | 6834 | : gfxBreakPriority::eWordWrapBreak; |
michael@0 | 6835 | } |
michael@0 | 6836 | |
michael@0 | 6837 | width += advance; |
michael@0 | 6838 | advance = 0; |
michael@0 | 6839 | if (width - trimmableAdvance > aWidth) { |
michael@0 | 6840 | // No more text fits. Abort |
michael@0 | 6841 | aborted = true; |
michael@0 | 6842 | break; |
michael@0 | 6843 | } |
michael@0 | 6844 | } |
michael@0 | 6845 | } |
michael@0 | 6846 | |
michael@0 | 6847 | gfxFloat charAdvance; |
michael@0 | 6848 | if (i >= ligatureRunStart && i < ligatureRunEnd) { |
michael@0 | 6849 | charAdvance = GetAdvanceForGlyphs(i, i + 1); |
michael@0 | 6850 | if (haveSpacing) { |
michael@0 | 6851 | PropertyProvider::Spacing *space = &spacingBuffer[i - bufferStart]; |
michael@0 | 6852 | charAdvance += space->mBefore + space->mAfter; |
michael@0 | 6853 | } |
michael@0 | 6854 | } else { |
michael@0 | 6855 | charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider); |
michael@0 | 6856 | } |
michael@0 | 6857 | |
michael@0 | 6858 | advance += charAdvance; |
michael@0 | 6859 | if (aTrimWhitespace) { |
michael@0 | 6860 | if (mCharacterGlyphs[i].CharIsSpace()) { |
michael@0 | 6861 | ++trimmableChars; |
michael@0 | 6862 | trimmableAdvance += charAdvance; |
michael@0 | 6863 | } else { |
michael@0 | 6864 | trimmableAdvance = 0; |
michael@0 | 6865 | trimmableChars = 0; |
michael@0 | 6866 | } |
michael@0 | 6867 | } |
michael@0 | 6868 | } |
michael@0 | 6869 | |
michael@0 | 6870 | if (!aborted) { |
michael@0 | 6871 | width += advance; |
michael@0 | 6872 | } |
michael@0 | 6873 | |
michael@0 | 6874 | // There are three possibilities: |
michael@0 | 6875 | // 1) all the text fit (width <= aWidth) |
michael@0 | 6876 | // 2) some of the text fit up to a break opportunity (width > aWidth && lastBreak >= 0) |
michael@0 | 6877 | // 3) none of the text fits before a break opportunity (width > aWidth && lastBreak < 0) |
michael@0 | 6878 | uint32_t charsFit; |
michael@0 | 6879 | bool usedHyphenation = false; |
michael@0 | 6880 | if (width - trimmableAdvance <= aWidth) { |
michael@0 | 6881 | charsFit = aMaxLength; |
michael@0 | 6882 | } else if (lastBreak >= 0) { |
michael@0 | 6883 | charsFit = lastBreak - aStart; |
michael@0 | 6884 | trimmableChars = lastBreakTrimmableChars; |
michael@0 | 6885 | trimmableAdvance = lastBreakTrimmableAdvance; |
michael@0 | 6886 | usedHyphenation = lastBreakUsedHyphenation; |
michael@0 | 6887 | } else { |
michael@0 | 6888 | charsFit = aMaxLength; |
michael@0 | 6889 | } |
michael@0 | 6890 | |
michael@0 | 6891 | if (aMetrics) { |
michael@0 | 6892 | *aMetrics = MeasureText(aStart, charsFit - trimmableChars, |
michael@0 | 6893 | aBoundingBoxType, aRefContext, aProvider); |
michael@0 | 6894 | } |
michael@0 | 6895 | if (aTrimWhitespace) { |
michael@0 | 6896 | *aTrimWhitespace = trimmableAdvance; |
michael@0 | 6897 | } |
michael@0 | 6898 | if (aUsedHyphenation) { |
michael@0 | 6899 | *aUsedHyphenation = usedHyphenation; |
michael@0 | 6900 | } |
michael@0 | 6901 | if (aLastBreak && charsFit == aMaxLength) { |
michael@0 | 6902 | if (lastBreak < 0) { |
michael@0 | 6903 | *aLastBreak = UINT32_MAX; |
michael@0 | 6904 | } else { |
michael@0 | 6905 | *aLastBreak = lastBreak - aStart; |
michael@0 | 6906 | } |
michael@0 | 6907 | } |
michael@0 | 6908 | |
michael@0 | 6909 | return charsFit; |
michael@0 | 6910 | } |
michael@0 | 6911 | |
michael@0 | 6912 | gfxFloat |
michael@0 | 6913 | gfxTextRun::GetAdvanceWidth(uint32_t aStart, uint32_t aLength, |
michael@0 | 6914 | PropertyProvider *aProvider) |
michael@0 | 6915 | { |
michael@0 | 6916 | NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range"); |
michael@0 | 6917 | |
michael@0 | 6918 | uint32_t ligatureRunStart = aStart; |
michael@0 | 6919 | uint32_t ligatureRunEnd = aStart + aLength; |
michael@0 | 6920 | ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); |
michael@0 | 6921 | |
michael@0 | 6922 | gfxFloat result = ComputePartialLigatureWidth(aStart, ligatureRunStart, aProvider) + |
michael@0 | 6923 | ComputePartialLigatureWidth(ligatureRunEnd, aStart + aLength, aProvider); |
michael@0 | 6924 | |
michael@0 | 6925 | // Account for all remaining spacing here. This is more efficient than |
michael@0 | 6926 | // processing it along with the glyphs. |
michael@0 | 6927 | if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { |
michael@0 | 6928 | uint32_t i; |
michael@0 | 6929 | nsAutoTArray<PropertyProvider::Spacing,200> spacingBuffer; |
michael@0 | 6930 | if (spacingBuffer.AppendElements(aLength)) { |
michael@0 | 6931 | GetAdjustedSpacing(this, ligatureRunStart, ligatureRunEnd, aProvider, |
michael@0 | 6932 | spacingBuffer.Elements()); |
michael@0 | 6933 | for (i = 0; i < ligatureRunEnd - ligatureRunStart; ++i) { |
michael@0 | 6934 | PropertyProvider::Spacing *space = &spacingBuffer[i]; |
michael@0 | 6935 | result += space->mBefore + space->mAfter; |
michael@0 | 6936 | } |
michael@0 | 6937 | } |
michael@0 | 6938 | } |
michael@0 | 6939 | |
michael@0 | 6940 | return result + GetAdvanceForGlyphs(ligatureRunStart, ligatureRunEnd); |
michael@0 | 6941 | } |
michael@0 | 6942 | |
michael@0 | 6943 | bool |
michael@0 | 6944 | gfxTextRun::SetLineBreaks(uint32_t aStart, uint32_t aLength, |
michael@0 | 6945 | bool aLineBreakBefore, bool aLineBreakAfter, |
michael@0 | 6946 | gfxFloat *aAdvanceWidthDelta, |
michael@0 | 6947 | gfxContext *aRefContext) |
michael@0 | 6948 | { |
michael@0 | 6949 | // Do nothing because our shaping does not currently take linebreaks into |
michael@0 | 6950 | // account. There is no change in advance width. |
michael@0 | 6951 | if (aAdvanceWidthDelta) { |
michael@0 | 6952 | *aAdvanceWidthDelta = 0; |
michael@0 | 6953 | } |
michael@0 | 6954 | return false; |
michael@0 | 6955 | } |
michael@0 | 6956 | |
michael@0 | 6957 | uint32_t |
michael@0 | 6958 | gfxTextRun::FindFirstGlyphRunContaining(uint32_t aOffset) |
michael@0 | 6959 | { |
michael@0 | 6960 | NS_ASSERTION(aOffset <= GetLength(), "Bad offset looking for glyphrun"); |
michael@0 | 6961 | NS_ASSERTION(GetLength() == 0 || mGlyphRuns.Length() > 0, |
michael@0 | 6962 | "non-empty text but no glyph runs present!"); |
michael@0 | 6963 | if (aOffset == GetLength()) |
michael@0 | 6964 | return mGlyphRuns.Length(); |
michael@0 | 6965 | uint32_t start = 0; |
michael@0 | 6966 | uint32_t end = mGlyphRuns.Length(); |
michael@0 | 6967 | while (end - start > 1) { |
michael@0 | 6968 | uint32_t mid = (start + end)/2; |
michael@0 | 6969 | if (mGlyphRuns[mid].mCharacterOffset <= aOffset) { |
michael@0 | 6970 | start = mid; |
michael@0 | 6971 | } else { |
michael@0 | 6972 | end = mid; |
michael@0 | 6973 | } |
michael@0 | 6974 | } |
michael@0 | 6975 | NS_ASSERTION(mGlyphRuns[start].mCharacterOffset <= aOffset, |
michael@0 | 6976 | "Hmm, something went wrong, aOffset should have been found"); |
michael@0 | 6977 | return start; |
michael@0 | 6978 | } |
michael@0 | 6979 | |
michael@0 | 6980 | nsresult |
michael@0 | 6981 | gfxTextRun::AddGlyphRun(gfxFont *aFont, uint8_t aMatchType, |
michael@0 | 6982 | uint32_t aUTF16Offset, bool aForceNewRun) |
michael@0 | 6983 | { |
michael@0 | 6984 | NS_ASSERTION(aFont, "adding glyph run for null font!"); |
michael@0 | 6985 | if (!aFont) { |
michael@0 | 6986 | return NS_OK; |
michael@0 | 6987 | } |
michael@0 | 6988 | uint32_t numGlyphRuns = mGlyphRuns.Length(); |
michael@0 | 6989 | if (!aForceNewRun && numGlyphRuns > 0) { |
michael@0 | 6990 | GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1]; |
michael@0 | 6991 | |
michael@0 | 6992 | NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset, |
michael@0 | 6993 | "Glyph runs out of order (and run not forced)"); |
michael@0 | 6994 | |
michael@0 | 6995 | // Don't append a run if the font is already the one we want |
michael@0 | 6996 | if (lastGlyphRun->mFont == aFont && |
michael@0 | 6997 | lastGlyphRun->mMatchType == aMatchType) |
michael@0 | 6998 | { |
michael@0 | 6999 | return NS_OK; |
michael@0 | 7000 | } |
michael@0 | 7001 | |
michael@0 | 7002 | // If the offset has not changed, avoid leaving a zero-length run |
michael@0 | 7003 | // by overwriting the last entry instead of appending... |
michael@0 | 7004 | if (lastGlyphRun->mCharacterOffset == aUTF16Offset) { |
michael@0 | 7005 | |
michael@0 | 7006 | // ...except that if the run before the last entry had the same |
michael@0 | 7007 | // font as the new one wants, merge with it instead of creating |
michael@0 | 7008 | // adjacent runs with the same font |
michael@0 | 7009 | if (numGlyphRuns > 1 && |
michael@0 | 7010 | mGlyphRuns[numGlyphRuns - 2].mFont == aFont && |
michael@0 | 7011 | mGlyphRuns[numGlyphRuns - 2].mMatchType == aMatchType) |
michael@0 | 7012 | { |
michael@0 | 7013 | mGlyphRuns.TruncateLength(numGlyphRuns - 1); |
michael@0 | 7014 | return NS_OK; |
michael@0 | 7015 | } |
michael@0 | 7016 | |
michael@0 | 7017 | lastGlyphRun->mFont = aFont; |
michael@0 | 7018 | lastGlyphRun->mMatchType = aMatchType; |
michael@0 | 7019 | return NS_OK; |
michael@0 | 7020 | } |
michael@0 | 7021 | } |
michael@0 | 7022 | |
michael@0 | 7023 | NS_ASSERTION(aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0, |
michael@0 | 7024 | "First run doesn't cover the first character (and run not forced)?"); |
michael@0 | 7025 | |
michael@0 | 7026 | GlyphRun *glyphRun = mGlyphRuns.AppendElement(); |
michael@0 | 7027 | if (!glyphRun) |
michael@0 | 7028 | return NS_ERROR_OUT_OF_MEMORY; |
michael@0 | 7029 | glyphRun->mFont = aFont; |
michael@0 | 7030 | glyphRun->mCharacterOffset = aUTF16Offset; |
michael@0 | 7031 | glyphRun->mMatchType = aMatchType; |
michael@0 | 7032 | return NS_OK; |
michael@0 | 7033 | } |
michael@0 | 7034 | |
michael@0 | 7035 | void |
michael@0 | 7036 | gfxTextRun::SortGlyphRuns() |
michael@0 | 7037 | { |
michael@0 | 7038 | if (mGlyphRuns.Length() <= 1) |
michael@0 | 7039 | return; |
michael@0 | 7040 | |
michael@0 | 7041 | nsTArray<GlyphRun> runs(mGlyphRuns); |
michael@0 | 7042 | GlyphRunOffsetComparator comp; |
michael@0 | 7043 | runs.Sort(comp); |
michael@0 | 7044 | |
michael@0 | 7045 | // Now copy back, coalescing adjacent glyph runs that have the same font |
michael@0 | 7046 | mGlyphRuns.Clear(); |
michael@0 | 7047 | uint32_t i, count = runs.Length(); |
michael@0 | 7048 | for (i = 0; i < count; ++i) { |
michael@0 | 7049 | // a GlyphRun with the same font as the previous GlyphRun can just |
michael@0 | 7050 | // be skipped; the last GlyphRun will cover its character range. |
michael@0 | 7051 | if (i == 0 || runs[i].mFont != runs[i - 1].mFont) { |
michael@0 | 7052 | mGlyphRuns.AppendElement(runs[i]); |
michael@0 | 7053 | // If two fonts have the same character offset, Sort() will have |
michael@0 | 7054 | // randomized the order. |
michael@0 | 7055 | NS_ASSERTION(i == 0 || |
michael@0 | 7056 | runs[i].mCharacterOffset != |
michael@0 | 7057 | runs[i - 1].mCharacterOffset, |
michael@0 | 7058 | "Two fonts for the same run, glyph indices may not match the font"); |
michael@0 | 7059 | } |
michael@0 | 7060 | } |
michael@0 | 7061 | } |
michael@0 | 7062 | |
michael@0 | 7063 | // Note that SanitizeGlyphRuns scans all glyph runs in the textrun; |
michael@0 | 7064 | // therefore we only call it once, at the end of textrun construction, |
michael@0 | 7065 | // NOT incrementally as each glyph run is added (bug 680402). |
michael@0 | 7066 | void |
michael@0 | 7067 | gfxTextRun::SanitizeGlyphRuns() |
michael@0 | 7068 | { |
michael@0 | 7069 | if (mGlyphRuns.Length() <= 1) |
michael@0 | 7070 | return; |
michael@0 | 7071 | |
michael@0 | 7072 | // If any glyph run starts with ligature-continuation characters, we need to advance it |
michael@0 | 7073 | // to the first "real" character to avoid drawing partial ligature glyphs from wrong font |
michael@0 | 7074 | // (seen with U+FEFF in reftest 474417-1, as Core Text eliminates the glyph, which makes |
michael@0 | 7075 | // it appear as if a ligature has been formed) |
michael@0 | 7076 | int32_t i, lastRunIndex = mGlyphRuns.Length() - 1; |
michael@0 | 7077 | const CompressedGlyph *charGlyphs = mCharacterGlyphs; |
michael@0 | 7078 | for (i = lastRunIndex; i >= 0; --i) { |
michael@0 | 7079 | GlyphRun& run = mGlyphRuns[i]; |
michael@0 | 7080 | while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() && |
michael@0 | 7081 | run.mCharacterOffset < GetLength()) { |
michael@0 | 7082 | run.mCharacterOffset++; |
michael@0 | 7083 | } |
michael@0 | 7084 | // if the run has become empty, eliminate it |
michael@0 | 7085 | if ((i < lastRunIndex && |
michael@0 | 7086 | run.mCharacterOffset >= mGlyphRuns[i+1].mCharacterOffset) || |
michael@0 | 7087 | (i == lastRunIndex && run.mCharacterOffset == GetLength())) { |
michael@0 | 7088 | mGlyphRuns.RemoveElementAt(i); |
michael@0 | 7089 | --lastRunIndex; |
michael@0 | 7090 | } |
michael@0 | 7091 | } |
michael@0 | 7092 | } |
michael@0 | 7093 | |
michael@0 | 7094 | uint32_t |
michael@0 | 7095 | gfxTextRun::CountMissingGlyphs() |
michael@0 | 7096 | { |
michael@0 | 7097 | uint32_t i; |
michael@0 | 7098 | uint32_t count = 0; |
michael@0 | 7099 | for (i = 0; i < GetLength(); ++i) { |
michael@0 | 7100 | if (mCharacterGlyphs[i].IsMissing()) { |
michael@0 | 7101 | ++count; |
michael@0 | 7102 | } |
michael@0 | 7103 | } |
michael@0 | 7104 | return count; |
michael@0 | 7105 | } |
michael@0 | 7106 | |
michael@0 | 7107 | gfxTextRun::DetailedGlyph * |
michael@0 | 7108 | gfxTextRun::AllocateDetailedGlyphs(uint32_t aIndex, uint32_t aCount) |
michael@0 | 7109 | { |
michael@0 | 7110 | NS_ASSERTION(aIndex < GetLength(), "Index out of range"); |
michael@0 | 7111 | |
michael@0 | 7112 | if (!mDetailedGlyphs) { |
michael@0 | 7113 | mDetailedGlyphs = new DetailedGlyphStore(); |
michael@0 | 7114 | } |
michael@0 | 7115 | |
michael@0 | 7116 | DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount); |
michael@0 | 7117 | if (!details) { |
michael@0 | 7118 | mCharacterGlyphs[aIndex].SetMissing(0); |
michael@0 | 7119 | return nullptr; |
michael@0 | 7120 | } |
michael@0 | 7121 | |
michael@0 | 7122 | return details; |
michael@0 | 7123 | } |
michael@0 | 7124 | |
michael@0 | 7125 | void |
michael@0 | 7126 | gfxTextRun::CopyGlyphDataFrom(gfxShapedWord *aShapedWord, uint32_t aOffset) |
michael@0 | 7127 | { |
michael@0 | 7128 | uint32_t wordLen = aShapedWord->GetLength(); |
michael@0 | 7129 | NS_ASSERTION(aOffset + wordLen <= GetLength(), |
michael@0 | 7130 | "word overruns end of textrun!"); |
michael@0 | 7131 | |
michael@0 | 7132 | CompressedGlyph *charGlyphs = GetCharacterGlyphs(); |
michael@0 | 7133 | const CompressedGlyph *wordGlyphs = aShapedWord->GetCharacterGlyphs(); |
michael@0 | 7134 | if (aShapedWord->HasDetailedGlyphs()) { |
michael@0 | 7135 | for (uint32_t i = 0; i < wordLen; ++i, ++aOffset) { |
michael@0 | 7136 | const CompressedGlyph& g = wordGlyphs[i]; |
michael@0 | 7137 | if (g.IsSimpleGlyph()) { |
michael@0 | 7138 | charGlyphs[aOffset] = g; |
michael@0 | 7139 | } else { |
michael@0 | 7140 | const DetailedGlyph *details = |
michael@0 | 7141 | g.GetGlyphCount() > 0 ? |
michael@0 | 7142 | aShapedWord->GetDetailedGlyphs(i) : nullptr; |
michael@0 | 7143 | SetGlyphs(aOffset, g, details); |
michael@0 | 7144 | } |
michael@0 | 7145 | } |
michael@0 | 7146 | } else { |
michael@0 | 7147 | memcpy(charGlyphs + aOffset, wordGlyphs, |
michael@0 | 7148 | wordLen * sizeof(CompressedGlyph)); |
michael@0 | 7149 | } |
michael@0 | 7150 | } |
michael@0 | 7151 | |
michael@0 | 7152 | void |
michael@0 | 7153 | gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, uint32_t aStart, |
michael@0 | 7154 | uint32_t aLength, uint32_t aDest) |
michael@0 | 7155 | { |
michael@0 | 7156 | NS_ASSERTION(aStart + aLength <= aSource->GetLength(), |
michael@0 | 7157 | "Source substring out of range"); |
michael@0 | 7158 | NS_ASSERTION(aDest + aLength <= GetLength(), |
michael@0 | 7159 | "Destination substring out of range"); |
michael@0 | 7160 | |
michael@0 | 7161 | if (aSource->mSkipDrawing) { |
michael@0 | 7162 | mSkipDrawing = true; |
michael@0 | 7163 | } |
michael@0 | 7164 | |
michael@0 | 7165 | // Copy base glyph data, and DetailedGlyph data where present |
michael@0 | 7166 | const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aStart; |
michael@0 | 7167 | CompressedGlyph *dstGlyphs = mCharacterGlyphs + aDest; |
michael@0 | 7168 | for (uint32_t i = 0; i < aLength; ++i) { |
michael@0 | 7169 | CompressedGlyph g = srcGlyphs[i]; |
michael@0 | 7170 | g.SetCanBreakBefore(!g.IsClusterStart() ? |
michael@0 | 7171 | CompressedGlyph::FLAG_BREAK_TYPE_NONE : |
michael@0 | 7172 | dstGlyphs[i].CanBreakBefore()); |
michael@0 | 7173 | if (!g.IsSimpleGlyph()) { |
michael@0 | 7174 | uint32_t count = g.GetGlyphCount(); |
michael@0 | 7175 | if (count > 0) { |
michael@0 | 7176 | DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count); |
michael@0 | 7177 | if (dst) { |
michael@0 | 7178 | DetailedGlyph *src = aSource->GetDetailedGlyphs(i + aStart); |
michael@0 | 7179 | if (src) { |
michael@0 | 7180 | ::memcpy(dst, src, count * sizeof(DetailedGlyph)); |
michael@0 | 7181 | } else { |
michael@0 | 7182 | g.SetMissing(0); |
michael@0 | 7183 | } |
michael@0 | 7184 | } else { |
michael@0 | 7185 | g.SetMissing(0); |
michael@0 | 7186 | } |
michael@0 | 7187 | } |
michael@0 | 7188 | } |
michael@0 | 7189 | dstGlyphs[i] = g; |
michael@0 | 7190 | } |
michael@0 | 7191 | |
michael@0 | 7192 | // Copy glyph runs |
michael@0 | 7193 | GlyphRunIterator iter(aSource, aStart, aLength); |
michael@0 | 7194 | #ifdef DEBUG |
michael@0 | 7195 | gfxFont *lastFont = nullptr; |
michael@0 | 7196 | #endif |
michael@0 | 7197 | while (iter.NextRun()) { |
michael@0 | 7198 | gfxFont *font = iter.GetGlyphRun()->mFont; |
michael@0 | 7199 | NS_ASSERTION(font != lastFont, "Glyphruns not coalesced?"); |
michael@0 | 7200 | #ifdef DEBUG |
michael@0 | 7201 | lastFont = font; |
michael@0 | 7202 | uint32_t end = iter.GetStringEnd(); |
michael@0 | 7203 | #endif |
michael@0 | 7204 | uint32_t start = iter.GetStringStart(); |
michael@0 | 7205 | |
michael@0 | 7206 | // These used to be NS_ASSERTION()s, but WARNING is more appropriate. |
michael@0 | 7207 | // Although it's unusual (and not desirable), it's possible for us to assign |
michael@0 | 7208 | // different fonts to a base character and a following diacritic. |
michael@0 | 7209 | // Example on OSX 10.5/10.6 with default fonts installed: |
michael@0 | 7210 | // data:text/html,<p style="font-family:helvetica, arial, sans-serif;"> |
michael@0 | 7211 | // &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486; |
michael@0 | 7212 | // This means the rendering of the cluster will probably not be very good, |
michael@0 | 7213 | // but it's the best we can do for now if the specified font only covered the |
michael@0 | 7214 | // initial base character and not its applied marks. |
michael@0 | 7215 | NS_WARN_IF_FALSE(aSource->IsClusterStart(start), |
michael@0 | 7216 | "Started font run in the middle of a cluster"); |
michael@0 | 7217 | NS_WARN_IF_FALSE(end == aSource->GetLength() || aSource->IsClusterStart(end), |
michael@0 | 7218 | "Ended font run in the middle of a cluster"); |
michael@0 | 7219 | |
michael@0 | 7220 | nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType, |
michael@0 | 7221 | start - aStart + aDest, false); |
michael@0 | 7222 | if (NS_FAILED(rv)) |
michael@0 | 7223 | return; |
michael@0 | 7224 | } |
michael@0 | 7225 | } |
michael@0 | 7226 | |
michael@0 | 7227 | void |
michael@0 | 7228 | gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext, |
michael@0 | 7229 | uint32_t aCharIndex) |
michael@0 | 7230 | { |
michael@0 | 7231 | if (SetSpaceGlyphIfSimple(aFont, aContext, aCharIndex, ' ')) { |
michael@0 | 7232 | return; |
michael@0 | 7233 | } |
michael@0 | 7234 | |
michael@0 | 7235 | aFont->InitWordCache(); |
michael@0 | 7236 | static const uint8_t space = ' '; |
michael@0 | 7237 | gfxShapedWord *sw = aFont->GetShapedWord(aContext, |
michael@0 | 7238 | &space, 1, |
michael@0 | 7239 | HashMix(0, ' '), |
michael@0 | 7240 | MOZ_SCRIPT_LATIN, |
michael@0 | 7241 | mAppUnitsPerDevUnit, |
michael@0 | 7242 | gfxTextRunFactory::TEXT_IS_8BIT | |
michael@0 | 7243 | gfxTextRunFactory::TEXT_IS_ASCII | |
michael@0 | 7244 | gfxTextRunFactory::TEXT_IS_PERSISTENT, |
michael@0 | 7245 | nullptr); |
michael@0 | 7246 | if (sw) { |
michael@0 | 7247 | AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false); |
michael@0 | 7248 | CopyGlyphDataFrom(sw, aCharIndex); |
michael@0 | 7249 | } |
michael@0 | 7250 | } |
michael@0 | 7251 | |
michael@0 | 7252 | bool |
michael@0 | 7253 | gfxTextRun::SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext, |
michael@0 | 7254 | uint32_t aCharIndex, char16_t aSpaceChar) |
michael@0 | 7255 | { |
michael@0 | 7256 | uint32_t spaceGlyph = aFont->GetSpaceGlyph(); |
michael@0 | 7257 | if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) { |
michael@0 | 7258 | return false; |
michael@0 | 7259 | } |
michael@0 | 7260 | |
michael@0 | 7261 | uint32_t spaceWidthAppUnits = |
michael@0 | 7262 | NS_lroundf(aFont->GetMetrics().spaceWidth * mAppUnitsPerDevUnit); |
michael@0 | 7263 | if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) { |
michael@0 | 7264 | return false; |
michael@0 | 7265 | } |
michael@0 | 7266 | |
michael@0 | 7267 | AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false); |
michael@0 | 7268 | CompressedGlyph g; |
michael@0 | 7269 | g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph); |
michael@0 | 7270 | if (aSpaceChar == ' ') { |
michael@0 | 7271 | g.SetIsSpace(); |
michael@0 | 7272 | } |
michael@0 | 7273 | GetCharacterGlyphs()[aCharIndex] = g; |
michael@0 | 7274 | return true; |
michael@0 | 7275 | } |
michael@0 | 7276 | |
michael@0 | 7277 | void |
michael@0 | 7278 | gfxTextRun::FetchGlyphExtents(gfxContext *aRefContext) |
michael@0 | 7279 | { |
michael@0 | 7280 | bool needsGlyphExtents = NeedsGlyphExtents(this); |
michael@0 | 7281 | if (!needsGlyphExtents && !mDetailedGlyphs) |
michael@0 | 7282 | return; |
michael@0 | 7283 | |
michael@0 | 7284 | uint32_t i, runCount = mGlyphRuns.Length(); |
michael@0 | 7285 | CompressedGlyph *charGlyphs = mCharacterGlyphs; |
michael@0 | 7286 | for (i = 0; i < runCount; ++i) { |
michael@0 | 7287 | const GlyphRun& run = mGlyphRuns[i]; |
michael@0 | 7288 | gfxFont *font = run.mFont; |
michael@0 | 7289 | uint32_t start = run.mCharacterOffset; |
michael@0 | 7290 | uint32_t end = i + 1 < runCount ? |
michael@0 | 7291 | mGlyphRuns[i + 1].mCharacterOffset : GetLength(); |
michael@0 | 7292 | bool fontIsSetup = false; |
michael@0 | 7293 | uint32_t j; |
michael@0 | 7294 | gfxGlyphExtents *extents = font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit); |
michael@0 | 7295 | |
michael@0 | 7296 | for (j = start; j < end; ++j) { |
michael@0 | 7297 | const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[j]; |
michael@0 | 7298 | if (glyphData->IsSimpleGlyph()) { |
michael@0 | 7299 | // If we're in speed mode, don't set up glyph extents here; we'll |
michael@0 | 7300 | // just return "optimistic" glyph bounds later |
michael@0 | 7301 | if (needsGlyphExtents) { |
michael@0 | 7302 | uint32_t glyphIndex = glyphData->GetSimpleGlyph(); |
michael@0 | 7303 | if (!extents->IsGlyphKnown(glyphIndex)) { |
michael@0 | 7304 | if (!fontIsSetup) { |
michael@0 | 7305 | if (!font->SetupCairoFont(aRefContext)) { |
michael@0 | 7306 | NS_WARNING("failed to set up font for glyph extents"); |
michael@0 | 7307 | break; |
michael@0 | 7308 | } |
michael@0 | 7309 | fontIsSetup = true; |
michael@0 | 7310 | } |
michael@0 | 7311 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 7312 | ++gGlyphExtentsSetupEagerSimple; |
michael@0 | 7313 | #endif |
michael@0 | 7314 | font->SetupGlyphExtents(aRefContext, glyphIndex, false, extents); |
michael@0 | 7315 | } |
michael@0 | 7316 | } |
michael@0 | 7317 | } else if (!glyphData->IsMissing()) { |
michael@0 | 7318 | uint32_t glyphCount = glyphData->GetGlyphCount(); |
michael@0 | 7319 | if (glyphCount == 0) { |
michael@0 | 7320 | continue; |
michael@0 | 7321 | } |
michael@0 | 7322 | const gfxTextRun::DetailedGlyph *details = GetDetailedGlyphs(j); |
michael@0 | 7323 | if (!details) { |
michael@0 | 7324 | continue; |
michael@0 | 7325 | } |
michael@0 | 7326 | for (uint32_t k = 0; k < glyphCount; ++k, ++details) { |
michael@0 | 7327 | uint32_t glyphIndex = details->mGlyphID; |
michael@0 | 7328 | if (!extents->IsGlyphKnownWithTightExtents(glyphIndex)) { |
michael@0 | 7329 | if (!fontIsSetup) { |
michael@0 | 7330 | if (!font->SetupCairoFont(aRefContext)) { |
michael@0 | 7331 | NS_WARNING("failed to set up font for glyph extents"); |
michael@0 | 7332 | break; |
michael@0 | 7333 | } |
michael@0 | 7334 | fontIsSetup = true; |
michael@0 | 7335 | } |
michael@0 | 7336 | #ifdef DEBUG_TEXT_RUN_STORAGE_METRICS |
michael@0 | 7337 | ++gGlyphExtentsSetupEagerTight; |
michael@0 | 7338 | #endif |
michael@0 | 7339 | font->SetupGlyphExtents(aRefContext, glyphIndex, true, extents); |
michael@0 | 7340 | } |
michael@0 | 7341 | } |
michael@0 | 7342 | } |
michael@0 | 7343 | } |
michael@0 | 7344 | } |
michael@0 | 7345 | } |
michael@0 | 7346 | |
michael@0 | 7347 | |
michael@0 | 7348 | gfxTextRun::ClusterIterator::ClusterIterator(gfxTextRun *aTextRun) |
michael@0 | 7349 | : mTextRun(aTextRun), mCurrentChar(uint32_t(-1)) |
michael@0 | 7350 | { |
michael@0 | 7351 | } |
michael@0 | 7352 | |
michael@0 | 7353 | void |
michael@0 | 7354 | gfxTextRun::ClusterIterator::Reset() |
michael@0 | 7355 | { |
michael@0 | 7356 | mCurrentChar = uint32_t(-1); |
michael@0 | 7357 | } |
michael@0 | 7358 | |
michael@0 | 7359 | bool |
michael@0 | 7360 | gfxTextRun::ClusterIterator::NextCluster() |
michael@0 | 7361 | { |
michael@0 | 7362 | uint32_t len = mTextRun->GetLength(); |
michael@0 | 7363 | while (++mCurrentChar < len) { |
michael@0 | 7364 | if (mTextRun->IsClusterStart(mCurrentChar)) { |
michael@0 | 7365 | return true; |
michael@0 | 7366 | } |
michael@0 | 7367 | } |
michael@0 | 7368 | |
michael@0 | 7369 | mCurrentChar = uint32_t(-1); |
michael@0 | 7370 | return false; |
michael@0 | 7371 | } |
michael@0 | 7372 | |
michael@0 | 7373 | uint32_t |
michael@0 | 7374 | gfxTextRun::ClusterIterator::ClusterLength() const |
michael@0 | 7375 | { |
michael@0 | 7376 | if (mCurrentChar == uint32_t(-1)) { |
michael@0 | 7377 | return 0; |
michael@0 | 7378 | } |
michael@0 | 7379 | |
michael@0 | 7380 | uint32_t i = mCurrentChar, |
michael@0 | 7381 | len = mTextRun->GetLength(); |
michael@0 | 7382 | while (++i < len) { |
michael@0 | 7383 | if (mTextRun->IsClusterStart(i)) { |
michael@0 | 7384 | break; |
michael@0 | 7385 | } |
michael@0 | 7386 | } |
michael@0 | 7387 | |
michael@0 | 7388 | return i - mCurrentChar; |
michael@0 | 7389 | } |
michael@0 | 7390 | |
michael@0 | 7391 | gfxFloat |
michael@0 | 7392 | gfxTextRun::ClusterIterator::ClusterAdvance(PropertyProvider *aProvider) const |
michael@0 | 7393 | { |
michael@0 | 7394 | if (mCurrentChar == uint32_t(-1)) { |
michael@0 | 7395 | return 0; |
michael@0 | 7396 | } |
michael@0 | 7397 | |
michael@0 | 7398 | return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider); |
michael@0 | 7399 | } |
michael@0 | 7400 | |
michael@0 | 7401 | size_t |
michael@0 | 7402 | gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) |
michael@0 | 7403 | { |
michael@0 | 7404 | // The second arg is how much gfxTextRun::AllocateStorage would have |
michael@0 | 7405 | // allocated. |
michael@0 | 7406 | size_t total = mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 7407 | |
michael@0 | 7408 | if (mDetailedGlyphs) { |
michael@0 | 7409 | total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf); |
michael@0 | 7410 | } |
michael@0 | 7411 | |
michael@0 | 7412 | return total; |
michael@0 | 7413 | } |
michael@0 | 7414 | |
michael@0 | 7415 | size_t |
michael@0 | 7416 | gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) |
michael@0 | 7417 | { |
michael@0 | 7418 | return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf); |
michael@0 | 7419 | } |
michael@0 | 7420 | |
michael@0 | 7421 | |
michael@0 | 7422 | #ifdef DEBUG |
michael@0 | 7423 | void |
michael@0 | 7424 | gfxTextRun::Dump(FILE* aOutput) { |
michael@0 | 7425 | if (!aOutput) { |
michael@0 | 7426 | aOutput = stdout; |
michael@0 | 7427 | } |
michael@0 | 7428 | |
michael@0 | 7429 | uint32_t i; |
michael@0 | 7430 | fputc('[', aOutput); |
michael@0 | 7431 | for (i = 0; i < mGlyphRuns.Length(); ++i) { |
michael@0 | 7432 | if (i > 0) { |
michael@0 | 7433 | fputc(',', aOutput); |
michael@0 | 7434 | } |
michael@0 | 7435 | gfxFont* font = mGlyphRuns[i].mFont; |
michael@0 | 7436 | const gfxFontStyle* style = font->GetStyle(); |
michael@0 | 7437 | NS_ConvertUTF16toUTF8 fontName(font->GetName()); |
michael@0 | 7438 | nsAutoCString lang; |
michael@0 | 7439 | style->language->ToUTF8String(lang); |
michael@0 | 7440 | fprintf(aOutput, "%d: %s %f/%d/%d/%s", mGlyphRuns[i].mCharacterOffset, |
michael@0 | 7441 | fontName.get(), style->size, |
michael@0 | 7442 | style->weight, style->style, lang.get()); |
michael@0 | 7443 | } |
michael@0 | 7444 | fputc(']', aOutput); |
michael@0 | 7445 | } |
michael@0 | 7446 | #endif |