Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Firefox/gfx/thebes/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 181 kB image not shown  

Quelle  gfxFont.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


#include "gfxFont.h"

#include "mozilla/BinarySearch.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FontPropertyTypes.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/IntegerRange.h"
#include "mozilla/intl/Segmenter.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/SVGContextPaint.h"

#include "mozilla/Logging.h"

#include "nsITimer.h"

#include "gfxGlyphExtents.h"
#include "gfxPlatform.h"
#include "gfxTextRun.h"
#include "nsGkAtoms.h"

#include "gfxTypes.h"
#include "gfxContext.h"
#include "gfxFontMissingGlyphs.h"
#include "gfxGraphiteShaper.h"
#include "gfxHarfBuzzShaper.h"
#include "gfxUserFontSet.h"
#include "nsCRT.h"
#include "nsContentUtils.h"
#include "nsSpecialCasingData.h"
#include "nsTextRunTransformations.h"
#include "nsUGenCategory.h"
#include "nsUnicodeProperties.h"
#include "nsStyleConsts.h"
#include "mozilla/AppUnits.h"
#include "mozilla/HashTable.h"
#include "mozilla/Likely.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/glean/GfxMetrics.h"
#include "gfxMathTable.h"
#include "gfxSVGGlyphs.h"
#include "gfx2DGlue.h"
#include "TextDrawTarget.h"

#include "ThebesRLBox.h"

#include "GreekCasing.h"

#include "cairo.h"
#ifdef XP_WIN
#  include "cairo-win32.h"
#  include "gfxWindowsPlatform.h"
#endif

#include "harfbuzz/hb.h"
#include "harfbuzz/hb-ot.h"

#include <algorithm>
#include <limits>
#include <cmath>

using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::unicode;
using mozilla::services::GetObserverService;

gfxFontCache* gfxFontCache::gGlobalCache = nullptr;

#ifdef DEBUG_roc
#  define DEBUG_TEXT_RUN_STORAGE_METRICS
#endif

#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
uint32_t gTextRunStorageHighWaterMark = 0;
uint32_t gTextRunStorage = 0;
uint32_t gFontCount = 0;
uint32_t gGlyphExtentsCount = 0;
uint32_t gGlyphExtentsWidthsTotalSize = 0;
uint32_t gGlyphExtentsSetupEagerSimple = 0;
uint32_t gGlyphExtentsSetupEagerTight = 0;
uint32_t gGlyphExtentsSetupLazyTight = 0;
uint32_t gGlyphExtentsSetupFallBackToTight = 0;
#endif

#define LOG_FONTINIT(args) \
  MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug, args)
#define LOG_FONTINIT_ENABLED() \
  MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_fontinit), LogLevel::Debug)

/*
 * gfxFontCache - global cache of gfxFont instances.
 * Expires unused fonts after a short interval;
 * notifies fonts to age their cached shaped-word records;
 * observes memory-pressure notification and tells fonts to clear their
 * shaped-word caches to free up memory.
 */


MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf)

NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter)

/*virtual*/
gfxTextRunFactory::~gfxTextRunFactory() {
  // Should not be dropped by stylo
  MOZ_ASSERT(!Servo_IsWorkerThread());
}

NS_IMETHODIMP
gfxFontCache::MemoryReporter::CollectReports(
    nsIHandleReportCallback* aHandleReport, nsISupports* aData,
    bool aAnonymize) {
  FontCacheSizes sizes;

  gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf,
                                                   &sizes);

  MOZ_COLLECT_REPORT("explicit/gfx/font-cache", KIND_HEAP, UNITS_BYTES,
                     sizes.mFontInstances,
                     "Memory used for active font instances.");

  MOZ_COLLECT_REPORT("explicit/gfx/font-shaped-words", KIND_HEAP, UNITS_BYTES,
                     sizes.mShapedWords,
                     "Memory used to cache shaped glyph data.");

  return NS_OK;
}

NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver)

NS_IMETHODIMP
gfxFontCache::Observer::Observe(nsISupports* aSubject, const char* aTopic,
                                const char16_t* someData) {
  if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
    gfxFontCache* fontCache = gfxFontCache::GetCache();
    if (fontCache) {
      fontCache->FlushShapedWordCaches();
    }
  } else {
    MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
  }
  return NS_OK;
}

nsresult gfxFontCache::Init() {
  NS_ASSERTION(!gGlobalCache, "Where did this come from?");
  gGlobalCache = new gfxFontCache(GetMainThreadSerialEventTarget());
  if (!gGlobalCache) {
    return NS_ERROR_OUT_OF_MEMORY;
  }
  RegisterStrongMemoryReporter(new MemoryReporter());
  return NS_OK;
}

void gfxFontCache::Shutdown() {
  delete gGlobalCache;
  gGlobalCache = nullptr;

#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
  printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark);
  printf("Total number of fonts=%d\n", gFontCount);
  printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount,
         int(gGlyphExtentsCount * sizeof(gfxGlyphExtents)));
  printf("Total glyph extents width-storage size allocated=%d\n",
         gGlyphExtentsWidthsTotalSize);
  printf("Number of simple glyph extents eagerly requested=%d\n",
         gGlyphExtentsSetupEagerSimple);
  printf("Number of tight glyph extents eagerly requested=%d\n",
         gGlyphExtentsSetupEagerTight);
  printf("Number of tight glyph extents lazily requested=%d\n",
         gGlyphExtentsSetupLazyTight);
  printf("Number of simple glyph extent setups that fell back to tight=%d\n",
         gGlyphExtentsSetupFallBackToTight);
#endif
}

gfxFontCache::gfxFontCache(nsIEventTarget* aEventTarget)
    : ExpirationTrackerImpl<gfxFont, 3, Lock, AutoLock>(
          FONT_TIMEOUT_SECONDS * 1000, "gfxFontCache", aEventTarget) {
  nsCOMPtr<nsIObserverService> obs = GetObserverService();
  if (obs) {
    obs->AddObserver(new Observer, "memory-pressure"false);
  }

  nsIEventTarget* target = nullptr;
  if (XRE_IsContentProcess() && NS_IsMainThread()) {
    target = aEventTarget;
  }

  // Create the timer used to expire shaped-word records from each font's
  // cache after a short period of non-use. We have a single timer in
  // gfxFontCache that loops over all fonts known to the cache, to avoid
  // the overhead of individual timers in each font instance.
  // The timer will be started any time shaped word records are cached
  // (and pauses itself when all caches become empty).
  mWordCacheExpirationTimer = NS_NewTimer(target);
}

gfxFontCache::~gfxFontCache() {
  // Ensure the user font cache releases its references to font entries,
  // so they aren't kept alive after the font instances and font-list
  // have been shut down.
  gfxUserFontSet::UserFontCache::Shutdown();

  if (mWordCacheExpirationTimer) {
    mWordCacheExpirationTimer->Cancel();
    mWordCacheExpirationTimer = nullptr;
  }

  // Expire everything manually so we don't leak them.
  Flush();
}

bool gfxFontCache::HashEntry::KeyEquals(const KeyTypePointer aKey) const {
  const gfxCharacterMap* fontUnicodeRangeMap = mFont->GetUnicodeRangeMap();
  return aKey->mFontEntry == mFont->GetFontEntry() &&
         aKey->mStyle->Equals(*mFont->GetStyle()) &&
         ((!aKey->mUnicodeRangeMap && !fontUnicodeRangeMap) ||
          (aKey->mUnicodeRangeMap && fontUnicodeRangeMap &&
           aKey->mUnicodeRangeMap->Equals(fontUnicodeRangeMap)));
}

already_AddRefed<gfxFont> gfxFontCache::Lookup(
    const gfxFontEntry* aFontEntry, const gfxFontStyle* aStyle,
    const gfxCharacterMap* aUnicodeRangeMap) {
  MutexAutoLock lock(mMutex);

  Key key(aFontEntry, aStyle, aUnicodeRangeMap);
  HashEntry* entry = mFonts.GetEntry(key);

  glean::fontlist::font_cache_hit
      .EnumGet(
          static_cast<glean::fontlist::FontCacheHitLabel>(entry != nullptr))
      .Add();

  if (!entry) {
    return nullptr;
  }

  RefPtr<gfxFont> font = entry->mFont;
  if (font->GetExpirationState()->IsTracked()) {
    RemoveObjectLocked(font, lock);
  }
  return font.forget();
}

already_AddRefed<gfxFont> gfxFontCache::MaybeInsert(gfxFont* aFont) {
  MOZ_ASSERT(aFont);
  MutexAutoLock lock(mMutex);

  Key key(aFont->GetFontEntry(), aFont->GetStyle(),
          aFont->GetUnicodeRangeMap());
  HashEntry* entry = mFonts.PutEntry(key);
  if (!entry) {
    return do_AddRef(aFont);
  }

  // If it is null, then we are inserting a new entry. Otherwise we are
  // attempting to replace an existing font, probably due to a thread race, in
  // which case stick with the original font.
  if (!entry->mFont) {
    entry->mFont = aFont;
    // Assert that we can find the entry we just put in (this fails if the key
    // has a NaN float value in it, e.g. 'sizeAdjust').
    MOZ_ASSERT(entry == mFonts.GetEntry(key));
  } else {
    MOZ_ASSERT(entry->mFont != aFont);
    aFont->Destroy();
    if (entry->mFont->GetExpirationState()->IsTracked()) {
      RemoveObjectLocked(entry->mFont, lock);
    }
  }

  return do_AddRef(entry->mFont);
}

bool gfxFontCache::MaybeDestroy(gfxFont* aFont) {
  MOZ_ASSERT(aFont);
  MutexAutoLock lock(mMutex);

  // If the font has a non-zero refcount, then we must have lost the race with
  // gfxFontCache::Lookup and the same font was reacquired.
  if (aFont->GetRefCount() > 0) {
    return false;
  }

  Key key(aFont->GetFontEntry(), aFont->GetStyle(),
          aFont->GetUnicodeRangeMap());
  HashEntry* entry = mFonts.GetEntry(key);
  if (!entry || entry->mFont != aFont) {
    MOZ_ASSERT(!aFont->GetExpirationState()->IsTracked());
    return true;
  }

  // If the font is being tracked, we must have then also lost another race with
  // gfxFontCache::MaybeDestroy which re-added it to the tracker.
  if (aFont->GetExpirationState()->IsTracked()) {
    return false;
  }

  // Typically this won't fail, but it may during startup/shutdown if the timer
  // service is not available.
  nsresult rv = AddObjectLocked(aFont, lock);
  if (NS_SUCCEEDED(rv)) {
    return false;
  }

  mFonts.RemoveEntry(entry);
  return true;
}

void gfxFontCache::NotifyExpiredLocked(gfxFont* aFont, const AutoLock& aLock) {
  MOZ_ASSERT(aFont->GetRefCount() == 0);

  RemoveObjectLocked(aFont, aLock);
  mTrackerDiscard.AppendElement(aFont);

  Key key(aFont->GetFontEntry(), aFont->GetStyle(),
          aFont->GetUnicodeRangeMap());
  HashEntry* entry = mFonts.GetEntry(key);
  if (!entry || entry->mFont != aFont) {
    MOZ_ASSERT_UNREACHABLE("Invalid font?");
    return;
  }

  mFonts.RemoveEntry(entry);
}

void gfxFontCache::NotifyHandlerEnd() {
  nsTArray<gfxFont*> discard;
  {
    MutexAutoLock lock(mMutex);
    discard = std::move(mTrackerDiscard);
  }
  DestroyDiscard(discard);
}

void gfxFontCache::DestroyDiscard(nsTArray<gfxFont*>& aDiscard) {
  for (auto& font : aDiscard) {
    NS_ASSERTION(font->GetRefCount() == 0,
                 "Destroying with refs outside cache!");
    font->ClearCachedWords();
    font->Destroy();
  }
  aDiscard.Clear();
}

void gfxFontCache::Flush() {
  nsTArray<gfxFont*> discard;
  {
    MutexAutoLock lock(mMutex);
    discard.SetCapacity(mFonts.Count());
    for (auto iter = mFonts.Iter(); !iter.Done(); iter.Next()) {
      HashEntry* entry = static_cast<HashEntry*>(iter.Get());
      if (!entry || !entry->mFont) {
        MOZ_ASSERT_UNREACHABLE("Invalid font?");
        continue;
      }

      if (entry->mFont->GetRefCount() == 0) {
        // If we are not tracked, then we must have won the race with
        // gfxFont::MaybeDestroy and it is waiting on the mutex. To avoid a
        // double free, we let gfxFont::MaybeDestroy handle the freeing when it
        // acquires the mutex and discovers there is no matching entry in the
        // hashtable.
        if (entry->mFont->GetExpirationState()->IsTracked()) {
          RemoveObjectLocked(entry->mFont, lock);
          discard.AppendElement(entry->mFont);
        }
      } else {
        MOZ_ASSERT(!entry->mFont->GetExpirationState()->IsTracked());
      }
    }
    MOZ_ASSERT(IsEmptyLocked(lock),
               "Cache tracker still has fonts after flush!");
    mFonts.Clear();
  }
  DestroyDiscard(discard);
}

/*static*/
void gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer,
                                                    void* aCache) {
  gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
  cache->AgeCachedWords();
}

void gfxFontCache::AgeCachedWords() {
  bool allEmpty = true;
  {
    MutexAutoLock lock(mMutex);
    for (const auto& entry : mFonts) {
      allEmpty = entry.mFont->AgeCachedWords() && allEmpty;
    }
  }
  if (allEmpty) {
    PauseWordCacheExpirationTimer();
  }
}

void gfxFontCache::FlushShapedWordCaches() {
  {
    MutexAutoLock lock(mMutex);
    for (const auto& entry : mFonts) {
      entry.mFont->ClearCachedWords();
    }
  }
  PauseWordCacheExpirationTimer();
}

void gfxFontCache::NotifyGlyphsChanged() {
  MutexAutoLock lock(mMutex);
  for (const auto& entry : mFonts) {
    entry.mFont->NotifyGlyphsChanged();
  }
}

void gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
                                          FontCacheSizes* aSizes) const {
  // TODO: add the overhead of the expiration tracker (generation arrays)

  MutexAutoLock lock(*const_cast<Mutex*>(&mMutex));
  aSizes->mFontInstances += mFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
  for (const auto& entry : mFonts) {
    entry.mFont->AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
  }
}

void gfxFontCache::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
                                          FontCacheSizes* aSizes) const {
  aSizes->mFontInstances += aMallocSizeOf(this);
  AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
}

#define MAX_SSXX_VALUE 99
#define MAX_CVXX_VALUE 99

static void LookupAlternateValues(const gfxFontFeatureValueSet& aFeatureLookup,
                                  const nsACString& aFamily,
                                  const StyleVariantAlternates& aAlternates,
                                  nsTArray<gfxFontFeature>& aFontFeatures) {
  using Tag = StyleVariantAlternates::Tag;

  // historical-forms gets handled in nsFont::AddFontFeaturesToStyle.
  if (aAlternates.IsHistoricalForms()) {
    return;
  }

  gfxFontFeature feature;
  if (aAlternates.IsCharacterVariant()) {
    for (auto& ident : aAlternates.AsCharacterVariant().AsSpan()) {
      Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor(
          aFamily, NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT,
          ident.AsAtom());
      // nothing defined, skip
      if (values.IsEmpty()) {
        continue;
      }
      NS_ASSERTION(values.Length() <= 2,
                   "too many values allowed for character-variant");
      // character-variant(12 3) ==> 'cv12' = 3
      uint32_t nn = values[0];
      // ignore values greater than 99
      if (nn == 0 || nn > MAX_CVXX_VALUE) {
        continue;
      }
      feature.mValue = values.Length() > 1 ? values[1] : 1;
      feature.mTag = HB_TAG('c''v', ('0' + nn / 10), ('0' + nn % 10));
      aFontFeatures.AppendElement(feature);
    }
    return;
  }

  if (aAlternates.IsStyleset()) {
    for (auto& ident : aAlternates.AsStyleset().AsSpan()) {
      Span<const uint32_t> values = aFeatureLookup.GetFontFeatureValuesFor(
          aFamily, NS_FONT_VARIANT_ALTERNATES_STYLESET, ident.AsAtom());

      // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1
      feature.mValue = 1;
      for (uint32_t nn : values) {
        if (nn == 0 || nn > MAX_SSXX_VALUE) {
          continue;
        }
        feature.mTag = HB_TAG('s''s', ('0' + nn / 10), ('0' + nn % 10));
        aFontFeatures.AppendElement(feature);
      }
    }
    return;
  }

  uint32_t constant = 0;
  nsAtom* name = nullptr;
  switch (aAlternates.tag) {
    case Tag::Swash:
      constant = NS_FONT_VARIANT_ALTERNATES_SWASH;
      name = aAlternates.AsSwash().AsAtom();
      break;
    case Tag::Stylistic:
      constant = NS_FONT_VARIANT_ALTERNATES_STYLISTIC;
      name = aAlternates.AsStylistic().AsAtom();
      break;
    case Tag::Ornaments:
      constant = NS_FONT_VARIANT_ALTERNATES_ORNAMENTS;
      name = aAlternates.AsOrnaments().AsAtom();
      break;
    case Tag::Annotation:
      constant = NS_FONT_VARIANT_ALTERNATES_ANNOTATION;
      name = aAlternates.AsAnnotation().AsAtom();
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Unknown font-variant-alternates value!");
      return;
  }

  Span<const uint32_t> values =
      aFeatureLookup.GetFontFeatureValuesFor(aFamily, constant, name);
  if (values.IsEmpty()) {
    return;
  }
  MOZ_ASSERT(values.Length() == 1,
             "too many values for font-specific font-variant-alternates");

  feature.mValue = values[0];
  switch (aAlternates.tag) {
    case Tag::Swash:  // swsh, cswh
      feature.mTag = HB_TAG('s''w''s''h');
      aFontFeatures.AppendElement(feature);
      feature.mTag = HB_TAG('c''s''w''h');
      break;
    case Tag::Stylistic:  // salt
      feature.mTag = HB_TAG('s''a''l''t');
      break;
    case Tag::Ornaments:  // ornm
      feature.mTag = HB_TAG('o''r''n''m');
      break;
    case Tag::Annotation:  // nalt
      feature.mTag = HB_TAG('n''a''l''t');
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("how?");
      return;
  }
  aFontFeatures.AppendElement(feature);
}

/* static */
void gfxFontShaper::MergeFontFeatures(
    const gfxFontStyle* aStyle, const nsTArray<gfxFontFeature>& aFontFeatures,
    bool aDisableLigatures, const nsACString& aFamilyName, bool aAddSmallCaps,
    void (*aHandleFeature)(uint32_t, uint32_t, void*),
    void* aHandleFeatureData) {
  const nsTArray<gfxFontFeature>& styleRuleFeatures = aStyle->featureSettings;

  // Bail immediately if nothing to do, which is the common case.
  if (styleRuleFeatures.IsEmpty() && aFontFeatures.IsEmpty() &&
      !aDisableLigatures &&
      aStyle->variantCaps == NS_FONT_VARIANT_CAPS_NORMAL &&
      aStyle->variantSubSuper == NS_FONT_VARIANT_POSITION_NORMAL &&
      aStyle->variantAlternates.IsEmpty()) {
    return;
  }

  AutoTArray<gfxFontFeature, 32> mergedFeatures;

  struct FeatureTagCmp {
    bool Equals(const gfxFontFeature& a, const gfxFontFeature& b) const {
      return a.mTag == b.mTag;
    }
    bool LessThan(const gfxFontFeature& a, const gfxFontFeature& b) const {
      return a.mTag < b.mTag;
    }
  } cmp;

  auto addOrReplace = [&](const gfxFontFeature& aFeature) {
    auto index = mergedFeatures.BinaryIndexOf(aFeature, cmp);
    if (index == nsTArray<gfxFontFeature>::NoIndex) {
      mergedFeatures.InsertElementSorted(aFeature, cmp);
    } else {
      mergedFeatures[index].mValue = aFeature.mValue;
    }
  };

  // add feature values from font
  for (const gfxFontFeature& feature : aFontFeatures) {
    addOrReplace(feature);
  }

  // font-variant-caps - handled here due to the need for fallback handling
  // petite caps cases can fallback to appropriate smallcaps
  uint32_t variantCaps = aStyle->variantCaps;
  switch (variantCaps) {
    case NS_FONT_VARIANT_CAPS_NORMAL:
      break;

    case NS_FONT_VARIANT_CAPS_ALLSMALL:
      addOrReplace(gfxFontFeature{HB_TAG('c''2''s''c'), 1});
      // fall through to the small-caps case
      [[fallthrough]];

    case NS_FONT_VARIANT_CAPS_SMALLCAPS:
      addOrReplace(gfxFontFeature{HB_TAG('s''m''c''p'), 1});
      break;

    case NS_FONT_VARIANT_CAPS_ALLPETITE:
      addOrReplace(gfxFontFeature{aAddSmallCaps ? HB_TAG('c''2''s''c')
                                                : HB_TAG('c''2''p''c'),
                                  1});
      // fall through to the petite-caps case
      [[fallthrough]];

    case NS_FONT_VARIANT_CAPS_PETITECAPS:
      addOrReplace(gfxFontFeature{aAddSmallCaps ? HB_TAG('s''m''c''p')
                                                : HB_TAG('p''c''a''p'),
                                  1});
      break;

    case NS_FONT_VARIANT_CAPS_TITLING:
      addOrReplace(gfxFontFeature{HB_TAG('t''i''t''l'), 1});
      break;

    case NS_FONT_VARIANT_CAPS_UNICASE:
      addOrReplace(gfxFontFeature{HB_TAG('u''n''i''c'), 1});
      break;

    default:
      MOZ_ASSERT_UNREACHABLE("Unexpected variantCaps");
      break;
  }

  // font-variant-position - handled here due to the need for fallback
  switch (aStyle->variantSubSuper) {
    case NS_FONT_VARIANT_POSITION_NORMAL:
      break;
    case NS_FONT_VARIANT_POSITION_SUPER:
      addOrReplace(gfxFontFeature{HB_TAG('s''u''p''s'), 1});
      break;
    case NS_FONT_VARIANT_POSITION_SUB:
      addOrReplace(gfxFontFeature{HB_TAG('s''u''b''s'), 1});
      break;
    default:
      MOZ_ASSERT_UNREACHABLE("Unexpected variantSubSuper");
      break;
  }

  // add font-specific feature values from style rules
  if (aStyle->featureValueLookup && !aStyle->variantAlternates.IsEmpty()) {
    AutoTArray<gfxFontFeature, 4> featureList;

    // insert list of alternate feature settings
    for (auto& alternate : aStyle->variantAlternates.AsSpan()) {
      LookupAlternateValues(*aStyle->featureValueLookup, aFamilyName, alternate,
                            featureList);
    }

    for (const gfxFontFeature& feature : featureList) {
      addOrReplace(gfxFontFeature{feature.mTag, feature.mValue});
    }
  }

  auto disableOptionalLigatures = [&]() -> void {
    addOrReplace(gfxFontFeature{HB_TAG('l''i''g''a'), 0});
    addOrReplace(gfxFontFeature{HB_TAG('c''l''i''g'), 0});
    addOrReplace(gfxFontFeature{HB_TAG('d''l''i''g'), 0});
    addOrReplace(gfxFontFeature{HB_TAG('h''l''i''g'), 0});
  };

  // Add features that are already resolved to tags & values in the style.
  if (styleRuleFeatures.IsEmpty()) {
    // Disable optional ligatures if non-zero letter-spacing is in effect.
    if (aDisableLigatures) {
      disableOptionalLigatures();
    }
  } else {
    for (const gfxFontFeature& feature : styleRuleFeatures) {
      // A dummy feature (0,0) is used as a sentinel to separate features
      // originating from font-variant-* or other high-level properties from
      // those directly specified as font-feature-settings. The high-level
      // features may be overridden by aDisableLigatures, while low-level
      // features specified directly as tags will come last and therefore
      // take precedence over everything else.
      if (feature.mTag) {
        addOrReplace(gfxFontFeature{feature.mTag, feature.mValue});
      } else if (aDisableLigatures) {
        // Handle ligature-disabling setting at the boundary between high-
        // and low-level features.
        disableOptionalLigatures();
      }
    }
  }

  for (const auto& f : mergedFeatures) {
    aHandleFeature(f.mTag, f.mValue, aHandleFeatureData);
  }
}

void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
                                           const char16_t* aString,
                                           uint32_t aLength) {
  if (aLength == 0) {
    return;
  }

  CompressedGlyph* const glyphs = GetCharacterGlyphs() + aOffset;
  CompressedGlyph extendCluster = CompressedGlyph::MakeComplex(falsetrue);

  // GraphemeClusterBreakIteratorUtf16 won't be able to tell us if the string
  // _begins_ with a cluster-extender, so we handle that here
  uint32_t ch = aString[0];
  if (aLength > 1 && NS_IS_SURROGATE_PAIR(ch, aString[1])) {
    ch = SURROGATE_TO_UCS4(ch, aString[1]);
  }
  if (IsClusterExtender(ch)) {
    glyphs[0] = extendCluster;
  }

  intl::GraphemeClusterBreakIteratorUtf16 iter(
      Span<const char16_t>(aString, aLength));
  uint32_t pos = 0;

  const char16_t kIdeographicSpace = 0x3000;
  // Special case for Bengali: although Virama normally clusters with the
  // preceding letter, we *also* want to cluster it with a following Ya
  // so that when the Virama+Ya form ya-phala, this is not separated from the
  // preceding letter by any letter-spacing or justification.
  const char16_t kBengaliVirama = 0x09CD;
  const char16_t kBengaliYa = 0x09AF;
  bool prevWasHyphen = false;
  while (pos < aLength) {
    const char16_t ch = aString[pos];
    if (prevWasHyphen) {
      if (nsContentUtils::IsAlphanumeric(ch)) {
        glyphs[pos].SetCanBreakBefore(
            CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP);
      }
      prevWasHyphen = false;
    }
    if (ch == char16_t(' ') || ch == kIdeographicSpace) {
      glyphs[pos].SetIsSpace();
    } else if (nsContentUtils::IsHyphen(ch) && pos &&
               nsContentUtils::IsAlphanumeric(aString[pos - 1])) {
      prevWasHyphen = true;
    } else if (ch == kBengaliYa) {
      // Unless we're at the start, check for a preceding virama.
      if (pos > 0 && aString[pos - 1] == kBengaliVirama) {
        glyphs[pos] = extendCluster;
      }
    }
    // advance iter to the next cluster-start (or end of text)
    const uint32_t nextPos = *iter.Next();
    // step past the first char of the cluster
    ++pos;
    // mark all the rest as cluster-continuations
    for (; pos < nextPos; ++pos) {
      glyphs[pos] = extendCluster;
    }
  }
}

void gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
                                           const uint8_t* aString,
                                           uint32_t aLength) {
  CompressedGlyph* glyphs = GetCharacterGlyphs() + aOffset;
  uint32_t pos = 0;
  bool prevWasHyphen = false;
  while (pos < aLength) {
    uint8_t ch = aString[pos];
    if (prevWasHyphen) {
      if (nsContentUtils::IsAlphanumeric(ch)) {
        glyphs->SetCanBreakBefore(
            CompressedGlyph::FLAG_BREAK_TYPE_EMERGENCY_WRAP);
      }
      prevWasHyphen = false;
    }
    if (ch == uint8_t(' ')) {
      glyphs->SetIsSpace();
    } else if (ch == uint8_t('-') && pos &&
               nsContentUtils::IsAlphanumeric(aString[pos - 1])) {
      prevWasHyphen = true;
    }
    ++pos;
    ++glyphs;
  }
}

gfxShapedText::DetailedGlyph* gfxShapedText::AllocateDetailedGlyphs(
    uint32_t aIndex, uint32_t aCount) {
  NS_ASSERTION(aIndex < GetLength(), "Index out of range");

  if (!mDetailedGlyphs) {
    mDetailedGlyphs = MakeUnique<DetailedGlyphStore>();
  }

  return mDetailedGlyphs->Allocate(aIndex, aCount);
}

void gfxShapedText::SetDetailedGlyphs(uint32_t aIndex, uint32_t aGlyphCount,
                                      const DetailedGlyph* aGlyphs) {
  CompressedGlyph& g = GetCharacterGlyphs()[aIndex];

  MOZ_ASSERT(aIndex > 0 || g.IsLigatureGroupStart(),
             "First character can't be a ligature continuation!");

  if (aGlyphCount > 0) {
    DetailedGlyph* details = AllocateDetailedGlyphs(aIndex, aGlyphCount);
    memcpy(details, aGlyphs, sizeof(DetailedGlyph) * aGlyphCount);
  }

  g.SetGlyphCount(aGlyphCount);
}

#define ZWNJ 0x200C
#define ZWJ 0x200D
static inline bool IsIgnorable(uint32_t aChar) {
  return (IsDefaultIgnorable(aChar)) || aChar == ZWNJ || aChar == ZWJ;
}

void gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar,
                                    gfxFont* aFont) {
  CompressedGlyph& g = GetCharacterGlyphs()[aIndex];
  uint8_t category = GetGeneralCategory(aChar);
  if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK &&
      category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK) {
    g.SetComplex(falsetrue);
  }

  // Leaving advance as zero will prevent drawing the hexbox for ignorables.
  int32_t advance = 0;
  if (!IsIgnorable(aChar)) {
    gfxFloat width =
        std::max(aFont->GetMetrics(nsFontMetrics::eHorizontal).aveCharWidth,
                 gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth(
                     aChar, mAppUnitsPerDevUnit)));
    advance = int32_t(width * mAppUnitsPerDevUnit);
  }
  DetailedGlyph detail = {aChar, advance, gfx::Point()};
  SetDetailedGlyphs(aIndex, 1, &detail);
  g.SetMissing();
}

bool gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh) {
  if (IsIgnorable(aCh)) {
    // There are a few default-ignorables of Letter category (currently,
    // just the Hangul filler characters) that we'd better not discard
    // if they're followed by additional characters in the same cluster.
    // Some fonts use them to carry the width of a whole cluster of
    // combining jamos; see bug 1238243.
    auto* charGlyphs = GetCharacterGlyphs();
    if (GetGenCategory(aCh) == nsUGenCategory::kLetter &&
        aIndex + 1 < GetLength() && !charGlyphs[aIndex + 1].IsClusterStart()) {
      return false;
    }
    // A compressedGlyph that is set to MISSING but has no DetailedGlyphs list
    // will be zero-width/invisible, which is what we want here.
    CompressedGlyph& g = charGlyphs[aIndex];
    g.SetComplex(g.IsClusterStart(), g.IsLigatureGroupStart()).SetMissing();
    return true;
  }
  return false;
}

void gfxShapedText::ApplyTrackingToClusters(gfxFloat aTrackingAdjustment,
                                            uint32_t aOffset,
                                            uint32_t aLength) {
  int32_t appUnitAdjustment =
      NS_round(aTrackingAdjustment * gfxFloat(mAppUnitsPerDevUnit));
  CompressedGlyph* charGlyphs = GetCharacterGlyphs();
  for (uint32_t i = aOffset; i < aOffset + aLength; ++i) {
    CompressedGlyph* glyphData = charGlyphs + i;
    if (glyphData->IsSimpleGlyph()) {
      // simple glyphs ==> just add the advance
      int32_t advance = glyphData->GetSimpleAdvance();
      if (advance > 0) {
        advance = std::max(0, advance + appUnitAdjustment);
        if (CompressedGlyph::IsSimpleAdvance(advance)) {
          glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
        } else {
          // rare case, tested by making this the default
          uint32_t glyphIndex = glyphData->GetSimpleGlyph();
          // convert the simple CompressedGlyph to an empty complex record
          glyphData->SetComplex(truetrue);
          // then set its details (glyph ID with its new advance)
          DetailedGlyph detail = {glyphIndex, advance, gfx::Point()};
          SetDetailedGlyphs(i, 1, &detail);
        }
      }
    } else {
      // complex glyphs ==> add offset at cluster/ligature boundaries
      uint32_t detailedLength = glyphData->GetGlyphCount();
      if (detailedLength) {
        DetailedGlyph* details = GetDetailedGlyphs(i);
        if (!details) {
          continue;
        }
        auto& advance = IsRightToLeft() ? details[0].mAdvance
                                        : details[detailedLength - 1].mAdvance;
        if (advance > 0) {
          advance = std::max(0, advance + appUnitAdjustment);
        }
      }
    }
  }
}

size_t gfxShapedWord::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
  size_t total = aMallocSizeOf(this);
  if (mDetailedGlyphs) {
    total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf);
  }
  return total;
}

float gfxFont::AngleForSyntheticOblique() const {
  // First check conditions that mean no synthetic slant should be used:
  if (mStyle.style == FontSlantStyle::NORMAL) {
    return 0.0f;  // Requested style is 'normal'.
  }
  if (!mStyle.allowSyntheticStyle) {
    return 0.0f;  // Synthetic obliquing is disabled.
  }
  if (!mFontEntry->MayUseSyntheticSlant()) {
    return 0.0f;  // The resource supports "real" slant, so don't synthesize.
  }

  // If style calls for italic, and face doesn't support it, use default
  // oblique angle as a simulation.
  if (mStyle.style.IsItalic()) {
    return mFontEntry->SupportsItalic()
               ? 0.0f
               : FontSlantStyle::DEFAULT_OBLIQUE_DEGREES;
  }

  // OK, we're going to use synthetic oblique: return the requested angle.
  return mStyle.style.ObliqueAngle();
}

float gfxFont::SkewForSyntheticOblique() const {
  // Precomputed value of tan(kDefaultAngle), the default italic/oblique slant;
  // avoids calling tan() at runtime except for custom oblique values.
  static const float kTanDefaultAngle =
      tan(FontSlantStyle::DEFAULT_OBLIQUE_DEGREES * (M_PI / 180.0));

  float angle = AngleForSyntheticOblique();
  if (angle == 0.0f) {
    return 0.0f;
  } else if (angle == FontSlantStyle::DEFAULT_OBLIQUE_DEGREES) {
    return kTanDefaultAngle;
  } else {
    return tan(angle * (M_PI / 180.0));
  }
}

void gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther,
                                      bool aOtherIsOnLeft) {
  mAscent = std::max(mAscent, aOther.mAscent);
  mDescent = std::max(mDescent, aOther.mDescent);
  if (aOtherIsOnLeft) {
    mBoundingBox = (mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0))
                       .Union(aOther.mBoundingBox);
  } else {
    mBoundingBox =
        mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0));
  }
  mAdvanceWidth += aOther.mAdvanceWidth;
}

gfxFont::gfxFont(const RefPtr<UnscaledFont>& aUnscaledFont,
                 gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
                 AntialiasOption anAAOption)
    : mFontEntry(aFontEntry),
      mLock("gfxFont lock"),
      mUnscaledFont(aUnscaledFont),
      mStyle(*aFontStyle),
      mAdjustedSize(-1.0),       // negative to indicate "not yet initialized"
      mFUnitsConvFactor(-1.0f),  // negative to indicate "not yet initialized"
      mAntialiasOption(anAAOption),
      mIsValid(true),
      mApplySyntheticBold(false),
      mKerningEnabled(false),
      mMathInitialized(false) {
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
  ++gFontCount;
#endif

  if (MOZ_UNLIKELY(StaticPrefs::gfx_text_disable_aa_AtStartup())) {
    mAntialiasOption = kAntialiasNone;
  }

  // Turn off AA for Ahem for testing purposes when requested.
  if (MOZ_UNLIKELY(StaticPrefs::gfx_font_rendering_ahem_antialias_none() &&
                   mFontEntry->FamilyName().EqualsLiteral("Ahem"))) {
    mAntialiasOption = kAntialiasNone;
  }

  mKerningSet = HasFeatureSet(HB_TAG('k''e''r''n'), mKerningEnabled);

  // Ensure the gfxFontEntry's unitsPerEm and extents fields are initialized,
  // so that GetFontExtents can use them without risk of races.
  Unused << mFontEntry->UnitsPerEm();
}

gfxFont::~gfxFont() {
  mFontEntry->NotifyFontDestroyed(this);

  // Delete objects owned through atomic pointers. (Some of these may be null,
  // but that's OK.)
  delete mVerticalMetrics.exchange(nullptr);
  delete mHarfBuzzShaper.exchange(nullptr);
  delete mGraphiteShaper.exchange(nullptr);
  delete mMathTable.exchange(nullptr);
  delete mNonAAFont.exchange(nullptr);

  if (auto* scaledFont = mAzureScaledFont.exchange(nullptr)) {
    scaledFont->Release();
  }

  if (mGlyphChangeObservers) {
    for (const auto& key : *mGlyphChangeObservers) {
      key->ForgetFont();
    }
  }
}

// Work out whether cairo will snap inter-glyph spacing to pixels.
//
// Layout does not align text to pixel boundaries, so, with font drawing
// backends that snap glyph positions to pixels, it is important that
// inter-glyph spacing within words is always an integer number of pixels.
// This ensures that the drawing backend snaps all of the word's glyphs in the
// same direction and so inter-glyph spacing remains the same.
//
gfxFont::RoundingFlags gfxFont::GetRoundOffsetsToPixels(
    DrawTarget* aDrawTarget) {
  // Could do something fancy here for ScaleFactors of
  // AxisAlignedTransforms, but we leave things simple.
  // Not much point rounding if a matrix will mess things up anyway.
  // Also check if the font already knows hint metrics is off...
  if (aDrawTarget->GetTransform().HasNonTranslation() || !ShouldHintMetrics()) {
    return RoundingFlags(0);
  }

  cairo_t* cr = static_cast<cairo_t*>(
      aDrawTarget->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
  if (cr) {
    cairo_surface_t* target = cairo_get_target(cr);

    // Check whether the cairo surface's font options hint metrics.
    cairo_font_options_t* fontOptions = cairo_font_options_create();
    cairo_surface_get_font_options(target, fontOptions);
    cairo_hint_metrics_t hintMetrics =
        cairo_font_options_get_hint_metrics(fontOptions);
    cairo_font_options_destroy(fontOptions);

    switch (hintMetrics) {
      case CAIRO_HINT_METRICS_OFF:
        return RoundingFlags(0);
      case CAIRO_HINT_METRICS_ON:
        return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
      default:
        break;
    }
  }

  if (ShouldRoundXOffset(cr)) {
    return RoundingFlags::kRoundX | RoundingFlags::kRoundY;
  } else {
    return RoundingFlags::kRoundY;
  }
}

gfxHarfBuzzShaper* gfxFont::GetHarfBuzzShaper() {
  if (!mHarfBuzzShaper) {
    auto* shaper = new gfxHarfBuzzShaper(this);
    shaper->Initialize();
    if (!mHarfBuzzShaper.compareExchange(nullptr, shaper)) {
      delete shaper;
    }
  }
  gfxHarfBuzzShaper* shaper = mHarfBuzzShaper;
  return shaper->IsInitialized() ? shaper : nullptr;
}

gfxFloat gfxFont::GetGlyphAdvance(uint16_t aGID, bool aVertical) {
  if (!aVertical && ProvidesGlyphWidths()) {
    return GetGlyphWidth(aGID) / 65536.0;
  }
  if (mFUnitsConvFactor < 0.0f) {
    // Metrics haven't been initialized; lock while we do that.
    AutoWriteLock lock(mLock);
    if (mFUnitsConvFactor < 0.0f) {
      GetMetrics(nsFontMetrics::eHorizontal);
    }
  }
  NS_ASSERTION(mFUnitsConvFactor >= 0.0f,
               "missing font unit conversion factor");
  if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
    if (aVertical) {
      // Note that GetGlyphVAdvance may return -1 to indicate it was unable
      // to retrieve vertical metrics; in that case we fall back to the
      // aveCharWidth value as a default advance.
      int32_t advance = shaper->GetGlyphVAdvance(aGID);
      if (advance < 0) {
        return GetMetrics(nsFontMetrics::eVertical).aveCharWidth;
      }
      return advance / 65536.0;
    }
    return shaper->GetGlyphHAdvance(aGID) / 65536.0;
  }
  return 0.0;
}

gfxFloat gfxFont::GetCharAdvance(uint32_t aUnicode, bool aVertical) {
  uint32_t gid = 0;
  if (ProvidesGetGlyph()) {
    gid = GetGlyph(aUnicode, 0);
  } else {
    if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
      gid = shaper->GetNominalGlyph(aUnicode);
    }
  }
  if (!gid) {
    return -1.0;
  }
  return GetGlyphAdvance(gid, aVertical);
}

static void CollectLookupsByFeature(hb_face_t* aFace, hb_tag_t aTableTag,
                                    uint32_t aFeatureIndex,
                                    hb_set_t* aLookups) {
  uint32_t lookups[32];
  uint32_t i, len, offset;

  offset = 0;
  do {
    len = std::size(lookups);
    hb_ot_layout_feature_get_lookups(aFace, aTableTag, aFeatureIndex, offset,
                                     &len, lookups);
    for (i = 0; i < len; i++) {
      hb_set_add(aLookups, lookups[i]);
    }
    offset += len;
  } while (len == std::size(lookups));
}

static void CollectLookupsByLanguage(
    hb_face_t* aFace, hb_tag_t aTableTag,
    const nsTHashSet<uint32_t>& aSpecificFeatures, hb_set_t* aOtherLookups,
    hb_set_t* aSpecificFeatureLookups, uint32_t aScriptIndex,
    uint32_t aLangIndex) {
  uint32_t reqFeatureIndex;
  if (hb_ot_layout_language_get_required_feature_index(
          aFace, aTableTag, aScriptIndex, aLangIndex, &reqFeatureIndex)) {
    CollectLookupsByFeature(aFace, aTableTag, reqFeatureIndex, aOtherLookups);
  }

  uint32_t featureIndexes[32];
  uint32_t i, len, offset;

  offset = 0;
  do {
    len = std::size(featureIndexes);
    hb_ot_layout_language_get_feature_indexes(aFace, aTableTag, aScriptIndex,
                                              aLangIndex, offset, &len,
                                              featureIndexes);

    for (i = 0; i < len; i++) {
      uint32_t featureIndex = featureIndexes[i];

      // get the feature tag
      hb_tag_t featureTag;
      uint32_t tagLen = 1;
      hb_ot_layout_language_get_feature_tags(aFace, aTableTag, aScriptIndex,
                                             aLangIndex, offset + i, &tagLen,
                                             &featureTag);

      // collect lookups
      hb_set_t* lookups = aSpecificFeatures.Contains(featureTag)
                              ? aSpecificFeatureLookups
                              : aOtherLookups;
      CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups);
    }
    offset += len;
  } while (len == std::size(featureIndexes));
}

static bool HasLookupRuleWithGlyphByScript(
    hb_face_t* aFace, hb_tag_t aTableTag, hb_tag_t aScriptTag,
    uint32_t aScriptIndex, uint16_t aGlyph,
    const nsTHashSet<uint32_t>& aDefaultFeatures,
    bool& aHasDefaultFeatureWithGlyph) {
  uint32_t numLangs, lang;
  hb_set_t* defaultFeatureLookups = hb_set_create();
  hb_set_t* nonDefaultFeatureLookups = hb_set_create();

  // default lang
  CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
                           nonDefaultFeatureLookups, defaultFeatureLookups,
                           aScriptIndex, HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);

  // iterate over langs
  numLangs = hb_ot_layout_script_get_language_tags(
      aFace, aTableTag, aScriptIndex, 0, nullptr, nullptr);
  for (lang = 0; lang < numLangs; lang++) {
    CollectLookupsByLanguage(aFace, aTableTag, aDefaultFeatures,
                             nonDefaultFeatureLookups, defaultFeatureLookups,
                             aScriptIndex, lang);
  }

  // look for the glyph among default feature lookups
  aHasDefaultFeatureWithGlyph = false;
  hb_set_t* glyphs = hb_set_create();
  hb_codepoint_t index = -1;
  while (hb_set_next(defaultFeatureLookups, &index)) {
    hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
                                       glyphs, nullptr);
    if (hb_set_has(glyphs, aGlyph)) {
      aHasDefaultFeatureWithGlyph = true;
      break;
    }
  }

  // look for the glyph among non-default feature lookups
  // if no default feature lookups contained spaces
  bool hasNonDefaultFeatureWithGlyph = false;
  if (!aHasDefaultFeatureWithGlyph) {
    hb_set_clear(glyphs);
    index = -1;
    while (hb_set_next(nonDefaultFeatureLookups, &index)) {
      hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs,
                                         glyphs, glyphs, nullptr);
      if (hb_set_has(glyphs, aGlyph)) {
        hasNonDefaultFeatureWithGlyph = true;
        break;
      }
    }
  }

  hb_set_destroy(glyphs);
  hb_set_destroy(defaultFeatureLookups);
  hb_set_destroy(nonDefaultFeatureLookups);

  return aHasDefaultFeatureWithGlyph || hasNonDefaultFeatureWithGlyph;
}

static void HasLookupRuleWithGlyph(hb_face_t* aFace, hb_tag_t aTableTag,
                                   bool& aHasGlyph, hb_tag_t aSpecificFeature,
                                   bool& aHasGlyphSpecific, uint16_t aGlyph) {
  // iterate over the scripts in the font
  uint32_t numScripts, numLangs, script, lang;
  hb_set_t* otherLookups = hb_set_create();
  hb_set_t* specificFeatureLookups = hb_set_create();
  nsTHashSet<uint32_t> specificFeature(1);

  specificFeature.Insert(aSpecificFeature);

  numScripts =
      hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0, nullptr, nullptr);

  for (script = 0; script < numScripts; script++) {
    // default lang
    CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups,
                             specificFeatureLookups, script,
                             HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX);

    // iterate over langs
    numLangs = hb_ot_layout_script_get_language_tags(
        aFace, HB_OT_TAG_GPOS, script, 0, nullptr, nullptr);
    for (lang = 0; lang < numLangs; lang++) {
      CollectLookupsByLanguage(aFace, aTableTag, specificFeature, otherLookups,
                               specificFeatureLookups, script, lang);
    }
  }

  // look for the glyph among non-specific feature lookups
  hb_set_t* glyphs = hb_set_create();
  hb_codepoint_t index = -1;
  while (hb_set_next(otherLookups, &index)) {
    hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
                                       glyphs, nullptr);
    if (hb_set_has(glyphs, aGlyph)) {
      aHasGlyph = true;
      break;
    }
  }

  // look for the glyph among specific feature lookups
  hb_set_clear(glyphs);
  index = -1;
  while (hb_set_next(specificFeatureLookups, &index)) {
    hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index, glyphs, glyphs,
                                       glyphs, nullptr);
    if (hb_set_has(glyphs, aGlyph)) {
      aHasGlyphSpecific = true;
      break;
    }
  }

  hb_set_destroy(glyphs);
  hb_set_destroy(specificFeatureLookups);
  hb_set_destroy(otherLookups);
}

Atomic<nsTHashMap<nsUint32HashKey, intl::Script>*> gfxFont::sScriptTagToCode;
Atomic<nsTHashSet<uint32_t>*> gfxFont::sDefaultFeatures;

static inline bool HasSubstitution(uint32_t* aBitVector, intl::Script aScript) {
  return (aBitVector[static_cast<uint32_t>(aScript) >> 5] &
          (1 << (static_cast<uint32_t>(aScript) & 0x1f))) != 0;
}

// union of all default substitution features across scripts
static const hb_tag_t defaultFeatures[] = {
    HB_TAG('a''b''v''f'), HB_TAG('a''b''v''s'),
    HB_TAG('a''k''h''n'), HB_TAG('b''l''w''f'),
    HB_TAG('b''l''w''s'), HB_TAG('c''a''l''t'),
    HB_TAG('c''c''m''p'), HB_TAG('c''f''a''r'),
    HB_TAG('c''j''c''t'), HB_TAG('c''l''i''g'),
    HB_TAG('f''i''n''2'), HB_TAG('f''i''n''3'),
    HB_TAG('f''i''n''a'), HB_TAG('h''a''l''f'),
    HB_TAG('h''a''l''n'), HB_TAG('i''n''i''t'),
    HB_TAG('i''s''o''l'), HB_TAG('l''i''g''a'),
    HB_TAG('l''j''m''o'), HB_TAG('l''o''c''l'),
    HB_TAG('l''t''r''a'), HB_TAG('l''t''r''m'),
    HB_TAG('m''e''d''2'), HB_TAG('m''e''d''i'),
    HB_TAG('m''s''e''t'), HB_TAG('n''u''k''t'),
    HB_TAG('p''r''e''f'), HB_TAG('p''r''e''s'),
    HB_TAG('p''s''t''f'), HB_TAG('p''s''t''s'),
    HB_TAG('r''c''l''t'), HB_TAG('r''l''i''g'),
    HB_TAG('r''k''r''f'), HB_TAG('r''p''h''f'),
    HB_TAG('r''t''l''a'), HB_TAG('r''t''l''m'),
    HB_TAG('t''j''m''o'), HB_TAG('v''a''t''u'),
    HB_TAG('v''e''r''t'), HB_TAG('v''j''m''o')};

void gfxFont::CheckForFeaturesInvolvingSpace() const {
  gfxFontEntry::SpaceFeatures flags = gfxFontEntry::SpaceFeatures::None;

  auto setFlags =
      MakeScopeExit([&]() { mFontEntry->mHasSpaceFeatures = flags; });

  bool log = LOG_FONTINIT_ENABLED();
  TimeStamp start;
  if (MOZ_UNLIKELY(log)) {
    start = TimeStamp::Now();
  }

  uint32_t spaceGlyph = GetSpaceGlyph();
  if (!spaceGlyph) {
    return;
  }

  auto face(GetFontEntry()->GetHBFace());

  // GSUB lookups - examine per script
  if (hb_ot_layout_has_substitution(face)) {
    // Get the script ==> code hashtable, creating it on first use.
    nsTHashMap<nsUint32HashKey, Script>* tagToCode = sScriptTagToCode;
    if (!tagToCode) {
      tagToCode = new nsTHashMap<nsUint32HashKey, Script>(
          size_t(Script::NUM_SCRIPT_CODES));
      tagToCode->InsertOrUpdate(HB_TAG('D''F''L''T'), Script::COMMON);
      // Ensure that we don't try to look at script codes beyond what the
      // current version of ICU (at runtime -- in case of system ICU)
      // knows about.
      Script scriptCount = Script(
          std::min<int>(intl::UnicodeProperties::GetMaxNumberOfScripts() + 1,
                        int(Script::NUM_SCRIPT_CODES)));
      for (Script s = Script::ARABIC; s < scriptCount;
           s = Script(static_cast<int>(s) + 1)) {
        hb_script_t script = hb_script_t(GetScriptTagForCode(s));
        unsigned int scriptCount = 4;
        hb_tag_t scriptTags[4];
        hb_ot_tags_from_script_and_language(script, HB_LANGUAGE_INVALID,
                                            &scriptCount, scriptTags, nullptr,
                                            nullptr);
        for (unsigned int i = 0; i < scriptCount; i++) {
          tagToCode->InsertOrUpdate(scriptTags[i], s);
        }
      }
      if (!sScriptTagToCode.compareExchange(nullptr, tagToCode)) {
        // We lost a race! Discard our new table and use the winner.
        delete tagToCode;
        tagToCode = sScriptTagToCode;
      }
    }

    // Set up the default-features hashset on first use.
    if (!sDefaultFeatures) {
      uint32_t numDefaultFeatures = std::size(defaultFeatures);
      auto* set = new nsTHashSet<uint32_t>(numDefaultFeatures);
      for (uint32_t i = 0; i < numDefaultFeatures; i++) {
        set->Insert(defaultFeatures[i]);
      }
      if (!sDefaultFeatures.compareExchange(nullptr, set)) {
        delete set;
      }
    }

    // iterate over the scripts in the font
    hb_tag_t scriptTags[8];

    uint32_t len, offset = 0;
    do {
      len = std::size(scriptTags);
      hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset, &len,
                                         scriptTags);
      for (uint32_t i = 0; i < len; i++) {
        bool isDefaultFeature = false;
        Script s;
        if (!HasLookupRuleWithGlyphByScript(
                face, HB_OT_TAG_GSUB, scriptTags[i], offset + i, spaceGlyph,
                *sDefaultFeatures, isDefaultFeature) ||
            !tagToCode->Get(scriptTags[i], &s)) {
          continue;
        }
        flags = flags | gfxFontEntry::SpaceFeatures::HasFeatures;
        uint32_t index = static_cast<uint32_t>(s) >> 5;
        uint32_t bit = static_cast<uint32_t>(s) & 0x1f;
        if (isDefaultFeature) {
          mFontEntry->mDefaultSubSpaceFeatures[index] |= (1 << bit);
        } else {
          mFontEntry->mNonDefaultSubSpaceFeatures[index] |= (1 << bit);
        }
      }
      offset += len;
    } while (len == std::size(scriptTags));
  }

  // spaces in default features of default script?
  // ==> can't use word cache, skip GPOS analysis
  bool canUseWordCache = true;
  if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON)) {
    canUseWordCache = false;
  }

  // GPOS lookups - distinguish kerning from non-kerning features
  if (canUseWordCache && hb_ot_layout_has_positioning(face)) {
    bool hasKerning = false, hasNonKerning = false;
    HasLookupRuleWithGlyph(face, HB_OT_TAG_GPOS, hasNonKerning,
                           HB_TAG('k''e''r''n'), hasKerning, spaceGlyph);
    if (hasKerning) {
      flags |= gfxFontEntry::SpaceFeatures::HasFeatures |
               gfxFontEntry::SpaceFeatures::Kerning;
    }
    if (hasNonKerning) {
      flags |= gfxFontEntry::SpaceFeatures::HasFeatures |
               gfxFontEntry::SpaceFeatures::NonKerning;
    }
  }

  if (MOZ_UNLIKELY(log)) {
    TimeDuration elapsed = TimeStamp::Now() - start;
    LOG_FONTINIT((
        "(fontinit-spacelookups) font: %s - "
        "subst default: %8.8x %8.8x %8.8x %8.8x "
        "subst non-default: %8.8x %8.8x %8.8x %8.8x "
        "kerning: %s non-kerning: %s time: %6.3f\n",
        mFontEntry->Name().get(), mFontEntry->mDefaultSubSpaceFeatures[3],
        mFontEntry->mDefaultSubSpaceFeatures[2],
        mFontEntry->mDefaultSubSpaceFeatures[1],
        mFontEntry->mDefaultSubSpaceFeatures[0],
        mFontEntry->mNonDefaultSubSpaceFeatures[3],
        mFontEntry->mNonDefaultSubSpaceFeatures[2],
        mFontEntry->mNonDefaultSubSpaceFeatures[1],
        mFontEntry->mNonDefaultSubSpaceFeatures[0],
        (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::Kerning
             ? "true"
             : "false"),
        (mFontEntry->mHasSpaceFeatures & gfxFontEntry::SpaceFeatures::NonKerning
             ? "true"
             : "false"),
        elapsed.ToMilliseconds()));
  }
}

bool gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const {
  NS_ASSERTION(GetFontEntry()->mHasSpaceFeatures !=
                   gfxFontEntry::SpaceFeatures::Uninitialized,
               "need to initialize space lookup flags");
  NS_ASSERTION(aRunScript < Script::NUM_SCRIPT_CODES, "weird script code");
  if (aRunScript == Script::INVALID || aRunScript >= Script::NUM_SCRIPT_CODES) {
    return false;
  }

  // default features have space lookups ==> true
  if (HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, Script::COMMON) ||
      HasSubstitution(mFontEntry->mDefaultSubSpaceFeatures, aRunScript)) {
    return true;
  }

  // non-default features have space lookups and some type of
  // font feature, in font or style is specified ==> true
  if ((HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures,
                       Script::COMMON) ||
       HasSubstitution(mFontEntry->mNonDefaultSubSpaceFeatures, aRunScript)) &&
      (!mStyle.featureSettings.IsEmpty() ||
       !mFontEntry->mFeatureSettings.IsEmpty())) {
    return true;
  }

  return false;
}

tainted_boolean_hint gfxFont::SpaceMayParticipateInShaping(
    Script aRunScript) const {
  // avoid checking fonts known not to include default space-dependent features
  if (MOZ_UNLIKELY(mFontEntry->mSkipDefaultFeatureSpaceCheck)) {
    if (!mKerningSet && mStyle.featureSettings.IsEmpty() &&
        mFontEntry->mFeatureSettings.IsEmpty()) {
      return false;
    }
  }

  if (FontCanSupportGraphite()) {
    if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
      return mFontEntry->HasGraphiteSpaceContextuals();
    }
  }

  // We record the presence of space-dependent features in the font entry
  // so that subsequent instantiations for the same font face won't
  // require us to re-check the tables; however, the actual check is done
  // by gfxFont because not all font entry subclasses know how to create
  // a harfbuzz face for introspection.
  gfxFontEntry::SpaceFeatures flags = mFontEntry->mHasSpaceFeatures;
  if (flags == gfxFontEntry::SpaceFeatures::Uninitialized) {
    CheckForFeaturesInvolvingSpace();
    flags = mFontEntry->mHasSpaceFeatures;
  }

  if (!(flags & gfxFontEntry::SpaceFeatures::HasFeatures)) {
    return false;
  }

  // if font has substitution rules or non-kerning positioning rules
  // that involve spaces, bypass
  if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
      (flags & gfxFontEntry::SpaceFeatures::NonKerning)) {
    return true;
  }

  // if kerning explicitly enabled/disabled via font-feature-settings or
  // font-kerning and kerning rules use spaces, only bypass when enabled
  if (mKerningSet && (flags & gfxFontEntry::SpaceFeatures::Kerning)) {
    return mKerningEnabled;
  }

  return false;
}

bool gfxFont::SupportsFeature(Script aScript, uint32_t aFeatureTag) {
  if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
    return GetFontEntry()->SupportsGraphiteFeature(aFeatureTag);
  }
  return GetFontEntry()->SupportsOpenTypeFeature(aScript, aFeatureTag);
}

bool gfxFont::SupportsVariantCaps(Script aScript, uint32_t aVariantCaps,
                                  bool& aFallbackToSmallCaps,
                                  bool& aSyntheticLowerToSmallCaps,
                                  bool& aSyntheticUpperToSmallCaps) {
  bool ok = true;  // cases without fallback are fine
  aFallbackToSmallCaps = false;
  aSyntheticLowerToSmallCaps = false;
  aSyntheticUpperToSmallCaps = false;
  switch (aVariantCaps) {
    case NS_FONT_VARIANT_CAPS_SMALLCAPS:
      ok = SupportsFeature(aScript, HB_TAG('s''m''c''p'));
      if (!ok) {
        aSyntheticLowerToSmallCaps = true;
      }
      break;
    case NS_FONT_VARIANT_CAPS_ALLSMALL:
      ok = SupportsFeature(aScript, HB_TAG('s''m''c''p')) &&
           SupportsFeature(aScript, HB_TAG('c''2''s''c'));
      if (!ok) {
        aSyntheticLowerToSmallCaps = true;
        aSyntheticUpperToSmallCaps = true;
      }
      break;
    case NS_FONT_VARIANT_CAPS_PETITECAPS:
      ok = SupportsFeature(aScript, HB_TAG('p''c''a''p'));
      if (!ok) {
        ok = SupportsFeature(aScript, HB_TAG('s''m''c''p'));
        aFallbackToSmallCaps = ok;
      }
      if (!ok) {
        aSyntheticLowerToSmallCaps = true;
      }
      break;
    case NS_FONT_VARIANT_CAPS_ALLPETITE:
      ok = SupportsFeature(aScript, HB_TAG('p''c''a''p')) &&
           SupportsFeature(aScript, HB_TAG('c''2''p''c'));
      if (!ok) {
        ok = SupportsFeature(aScript, HB_TAG('s''m''c''p')) &&
             SupportsFeature(aScript, HB_TAG('c''2''s''c'));
        aFallbackToSmallCaps = ok;
      }
      if (!ok) {
        aSyntheticLowerToSmallCaps = true;
        aSyntheticUpperToSmallCaps = true;
      }
      break;
    default:
      break;
  }

  NS_ASSERTION(
      !(ok && (aSyntheticLowerToSmallCaps || aSyntheticUpperToSmallCaps)),
      "shouldn't use synthetic features if we found real ones");

  NS_ASSERTION(!(!ok && aFallbackToSmallCaps),
               "if we found a usable fallback, that counts as ok");

  return ok;
}

bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
                                     const uint8_t* aString, uint32_t aLength,
                                     Script aRunScript) {
  NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aString),
                                       aLength);
  return SupportsSubSuperscript(aSubSuperscript, unicodeString.get(), aLength,
                                aRunScript);
}

bool gfxFont::SupportsSubSuperscript(uint32_t aSubSuperscript,
                                     const char16_t* aString, uint32_t aLength,
                                     Script aRunScript) {
  NS_ASSERTION(aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER ||
                   aSubSuperscript == NS_FONT_VARIANT_POSITION_SUB,
               "unknown value of font-variant-position");

  uint32_t feature = aSubSuperscript == NS_FONT_VARIANT_POSITION_SUPER
                         ? HB_TAG('s''u''p''s')
                         : HB_TAG('s''u''b''s');

  if (!SupportsFeature(aRunScript, feature)) {
    return false;
  }

  // xxx - for graphite, don't really know how to sniff lookups so bail
  if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
    return true;
  }

  gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper();
  if (!shaper) {
    return false;
  }

  // get the hbset containing input glyphs for the feature
  const hb_set_t* inputGlyphs =
      mFontEntry->InputsForOpenTypeFeature(aRunScript, feature);

  // create an hbset containing default glyphs for the script run
  hb_set_t* defaultGlyphsInRun = hb_set_create();

  // for each character, get the glyph id
  for (uint32_t i = 0; i < aLength; i++) {
    uint32_t ch = aString[i];

    if (i + 1 < aLength && NS_IS_SURROGATE_PAIR(ch, aString[i + 1])) {
      i++;
      ch = SURROGATE_TO_UCS4(ch, aString[i]);
    }

    hb_codepoint_t gid = shaper->GetNominalGlyph(ch);
    hb_set_add(defaultGlyphsInRun, gid);
  }

  // intersect with input glyphs, if size is not the same ==> fallback
  uint32_t origSize = hb_set_get_population(defaultGlyphsInRun);
  hb_set_intersect(defaultGlyphsInRun, inputGlyphs);
  uint32_t intersectionSize = hb_set_get_population(defaultGlyphsInRun);
  hb_set_destroy(defaultGlyphsInRun);

  return origSize == intersectionSize;
}

bool gfxFont::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature,
                                    uint32_t aUnicode) {
  if (!SupportsFeature(aRunScript, aFeature)) {
    return false;
  }

  // xxx - for graphite, don't really know how to sniff lookups so bail
  if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
    return true;
  }

  if (gfxHarfBuzzShaper* shaper = GetHarfBuzzShaper()) {
    // get the hbset containing input glyphs for the feature
    const hb_set_t* inputGlyphs =
        mFontEntry->InputsForOpenTypeFeature(aRunScript, aFeature);

    hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode);
    return hb_set_has(inputGlyphs, gid);
  }

  return false;
}

bool gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn) {
  aFeatureOn = false;

  if (mStyle.featureSettings.IsEmpty() &&
      GetFontEntry()->mFeatureSettings.IsEmpty()) {
    return false;
  }

  // add feature values from font
  bool featureSet = false;
  uint32_t i, count;

  nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings;
  count = fontFeatures.Length();
  for (i = 0; i < count; i++) {
    const gfxFontFeature& feature = fontFeatures.ElementAt(i);
    if (feature.mTag == aFeature) {
      featureSet = true;
      aFeatureOn = (feature.mValue != 0);
    }
  }

  // add feature values from style rules
  nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings;
  count = styleFeatures.Length();
  for (i = 0; i < count; i++) {
    const gfxFontFeature& feature = styleFeatures.ElementAt(i);
    if (feature.mTag == aFeature) {
      featureSet = true;
      aFeatureOn = (feature.mValue != 0);
    }
  }

  return featureSet;
}

already_AddRefed<mozilla::gfx::ScaledFont> gfxFont::GetScaledFont(
    mozilla::gfx::DrawTarget* aDrawTarget) {
  mozilla::gfx::PaletteCache dummy;
  TextRunDrawParams params(dummy);
  return GetScaledFont(params);
}

void gfxFont::InitializeScaledFont(
    const RefPtr<mozilla::gfx::ScaledFont>& aScaledFont) {
  if (!aScaledFont) {
    return;
  }

  float angle = AngleForSyntheticOblique();
  if (angle != 0.0f) {
    aScaledFont->SetSyntheticObliqueAngle(angle);
  }
}

/**
 * A helper function in case we need to do any rounding or other
 * processing here.
 */

#define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \
  (double(aAppUnits) * double(aDevUnitsPerAppUnit))

static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) {
  switch (aAAOption) {
    case gfxFont::kAntialiasSubpixel:
      return AntialiasMode::SUBPIXEL;
    case gfxFont::kAntialiasGrayscale:
      return AntialiasMode::GRAY;
    case gfxFont::kAntialiasNone:
      return AntialiasMode::NONE;
    default:
      return AntialiasMode::DEFAULT;
  }
}

class GlyphBufferAzure {
#define AUTO_BUFFER_SIZE (2048 / sizeof(Glyph))

  typedef mozilla::image::imgDrawingParams imgDrawingParams;

 public:
  GlyphBufferAzure(const TextRunDrawParams& aRunParams,
                   const FontDrawParams& aFontParams)
      : mRunParams(aRunParams),
        mFontParams(aFontParams),
        mBuffer(*mAutoBuffer.addr()),
        mBufSize(AUTO_BUFFER_SIZE),
        mCapacity(0),
        mNumGlyphs(0) {}

  ~GlyphBufferAzure() {
    if (mNumGlyphs > 0) {
      FlushGlyphs();
    }

    if (mBuffer != *mAutoBuffer.addr()) {
      free(mBuffer);
    }
  }

  // Ensure the buffer has enough space for aGlyphCount glyphs to be added,
  // considering the supplied strike multipler aStrikeCount.
  // This MUST be called before OutputGlyph is used to actually store glyph
  // records in the buffer. It may be called repeated to add further capacity
  // in case we don't know up-front exactly what will be needed.
  void AddCapacity(uint32_t aGlyphCount, uint32_t aStrikeCount) {
    // Calculate the new capacity and ensure it will fit within the maximum
    // allowed capacity.
    static const uint64_t kMaxCapacity = 64 * 1024;
    mCapacity = uint32_t(std::min(
        kMaxCapacity,
        uint64_t(mCapacity) + uint64_t(aGlyphCount) * uint64_t(aStrikeCount)));
    // See if the required capacity fits within the already-allocated space
    if (mCapacity <= mBufSize) {
      return;
    }
    // We need to grow the buffer: determine a new size, allocate, and
    // copy the existing data over if we didn't use realloc (which would
    // do it automatically).
    mBufSize = std::max(mCapacity, mBufSize * 2);
    if (mBuffer == *mAutoBuffer.addr()) {
      // switching from autobuffer to malloc, so we need to copy
      mBuffer = reinterpret_cast<Glyph*>(moz_xmalloc(mBufSize * sizeof(Glyph)));
      std::memcpy(mBuffer, *mAutoBuffer.addr(), mNumGlyphs * sizeof(Glyph));
    } else {
      mBuffer = reinterpret_cast<Glyph*>(
          moz_xrealloc(mBuffer, mBufSize * sizeof(Glyph)));
    }
  }

  void OutputGlyph(uint32_t aGlyphID, const gfx::Point& aPt) {
    // If the buffer is full, flush to make room for the new glyph.
    if (mNumGlyphs >= mCapacity) {
      // Check that AddCapacity has been used appropriately!
      MOZ_ASSERT(mCapacity > 0 && mNumGlyphs == mCapacity);
      Flush();
    }
    Glyph* glyph = mBuffer + mNumGlyphs++;
    glyph->mIndex = aGlyphID;
    glyph->mPosition = aPt;
  }

  void Flush() {
    if (mNumGlyphs > 0) {
      FlushGlyphs();
      mNumGlyphs = 0;
    }
  }

  const TextRunDrawParams& mRunParams;
  const FontDrawParams& mFontParams;

 private:
  static DrawMode GetStrokeMode(DrawMode aMode) {
    return aMode & (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH);
  }

  // Render the buffered glyphs to the draw target.
  void FlushGlyphs() {
    gfx::GlyphBuffer buf;
    buf.mGlyphs = mBuffer;
    buf.mNumGlyphs = mNumGlyphs;

    const gfxContext::AzureState& state = mRunParams.context->CurrentState();

    // Draw stroke first if the UNDERNEATH flag is set in drawMode.
    if (mRunParams.strokeOpts &&
        GetStrokeMode(mRunParams.drawMode) ==
            (DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) {
      DrawStroke(state, buf);
    }

    if (mRunParams.drawMode & DrawMode::GLYPH_FILL) {
      if (state.pattern || mFontParams.contextPaint) {
        Pattern* pat;

        RefPtr<gfxPattern> fillPattern;
        if (mFontParams.contextPaint) {
          imgDrawingParams imgParams;
          fillPattern = mFontParams.contextPaint->GetFillPattern(
              mRunParams.context->GetDrawTarget(),
              mRunParams.context->CurrentMatrixDouble(), imgParams);
        }
        if (!fillPattern) {
          if (state.pattern) {
--> --------------------

--> maximum size reached

--> --------------------

89%


¤ Dauer der Verarbeitung: 0.11 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.