summaryrefslogtreecommitdiffstats
path: root/gfx/thebes/gfxFont.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/thebes/gfxFont.cpp')
-rw-r--r--gfx/thebes/gfxFont.cpp4028
1 files changed, 4028 insertions, 0 deletions
diff --git a/gfx/thebes/gfxFont.cpp b/gfx/thebes/gfxFont.cpp
new file mode 100644
index 000000000..d0b747fff
--- /dev/null
+++ b/gfx/thebes/gfxFont.cpp
@@ -0,0 +1,4028 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/gfx/2D.h"
+#include "mozilla/MathAlgorithms.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 "nsIUGenCategory.h"
+#include "nsSpecialCasingData.h"
+#include "nsTextRunTransformations.h"
+#include "nsUnicodeProperties.h"
+#include "nsStyleConsts.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
+#include "gfxMathTable.h"
+#include "gfxSVGGlyphs.h"
+#include "gfx2DGlue.h"
+
+#include "GreekCasing.h"
+
+#include "cairo.h"
+
+#include "harfbuzz/hb.h"
+#include "harfbuzz/hb-ot.h"
+#include "graphite2/Font.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)
+
+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 {
+ NS_NOTREACHED("unexpected notification topic");
+ }
+ return NS_OK;
+}
+
+nsresult
+gfxFontCache::Init()
+{
+ NS_ASSERTION(!gGlobalCache, "Where did this come from?");
+ gGlobalCache = new gfxFontCache();
+ 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()
+ : nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000,
+ "gfxFontCache")
+{
+ nsCOMPtr<nsIObserverService> obs = GetObserverService();
+ if (obs) {
+ obs->AddObserver(new Observer, "memory-pressure", false);
+ }
+
+#ifndef RELEASE_OR_BETA
+ // Currently disabled for release builds, due to unexplained crashes
+ // during expiration; see bug 717175 & 894798.
+ mWordCacheExpirationTimer = do_CreateInstance("@mozilla.org/timer;1");
+ if (mWordCacheExpirationTimer) {
+ mWordCacheExpirationTimer->
+ InitWithFuncCallback(WordCacheExpirationTimerCallback, this,
+ SHAPED_WORD_TIMEOUT_SECONDS * 1000,
+ nsITimer::TYPE_REPEATING_SLACK);
+ }
+#endif
+}
+
+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 that has a zero refcount, so we don't leak them.
+ AgeAllGenerations();
+ // All fonts should be gone.
+ NS_WARNING_ASSERTION(mFonts.Count() == 0,
+ "Fonts still alive while shutting down gfxFontCache");
+ // Note that we have to delete everything through the expiration
+ // tracker, since there might be fonts not in the hashtable but in
+ // the tracker.
+}
+
+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)
+{
+ Key key(aFontEntry, aStyle, aUnicodeRangeMap);
+ HashEntry *entry = mFonts.GetEntry(key);
+
+ Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT, entry != nullptr);
+ if (!entry)
+ return nullptr;
+
+ RefPtr<gfxFont> font = entry->mFont;
+ return font.forget();
+}
+
+void
+gfxFontCache::AddNew(gfxFont *aFont)
+{
+ Key key(aFont->GetFontEntry(), aFont->GetStyle(),
+ aFont->GetUnicodeRangeMap());
+ HashEntry *entry = mFonts.PutEntry(key);
+ if (!entry)
+ return;
+ gfxFont *oldFont = 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));
+ // If someone's asked us to replace an existing font entry, then that's a
+ // bit weird, but let it happen, and expire the old font if it's not used.
+ if (oldFont && oldFont->GetExpirationState()->IsTracked()) {
+ // if oldFont == aFont, recount should be > 0,
+ // so we shouldn't be here.
+ NS_ASSERTION(aFont != oldFont, "new font is tracked for expiry!");
+ NotifyExpired(oldFont);
+ }
+}
+
+void
+gfxFontCache::NotifyReleased(gfxFont *aFont)
+{
+ nsresult rv = AddObject(aFont);
+ if (NS_FAILED(rv)) {
+ // We couldn't track it for some reason. Kill it now.
+ DestroyFont(aFont);
+ }
+ // Note that we might have fonts that aren't in the hashtable, perhaps because
+ // of OOM adding to the hashtable or because someone did an AddNew where
+ // we already had a font. These fonts are added to the expiration tracker
+ // anyway, even though Lookup can't resurrect them. Eventually they will
+ // expire and be deleted.
+}
+
+void
+gfxFontCache::NotifyExpired(gfxFont *aFont)
+{
+ aFont->ClearCachedWords();
+ RemoveObject(aFont);
+ DestroyFont(aFont);
+}
+
+void
+gfxFontCache::DestroyFont(gfxFont *aFont)
+{
+ Key key(aFont->GetFontEntry(), aFont->GetStyle(),
+ aFont->GetUnicodeRangeMap());
+ HashEntry *entry = mFonts.GetEntry(key);
+ if (entry && entry->mFont == aFont) {
+ mFonts.RemoveEntry(entry);
+ }
+ NS_ASSERTION(aFont->GetRefCount() == 0,
+ "Destroying with non-zero ref count!");
+ delete aFont;
+}
+
+/*static*/
+void
+gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache)
+{
+ gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
+ for (auto it = cache->mFonts.Iter(); !it.Done(); it.Next()) {
+ it.Get()->mFont->AgeCachedWords();
+ }
+}
+
+void
+gfxFontCache::FlushShapedWordCaches()
+{
+ for (auto it = mFonts.Iter(); !it.Done(); it.Next()) {
+ it.Get()->mFont->ClearCachedWords();
+ }
+}
+
+void
+gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ // TODO: add the overhead of the expiration tracker (generation arrays)
+
+ aSizes->mFontInstances += mFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (auto iter = mFonts.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Get()->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(gfxFontFeatureValueSet *featureLookup,
+ const nsAString& aFamily,
+ const nsTArray<gfxAlternateValue>& altValue,
+ nsTArray<gfxFontFeature>& aFontFeatures)
+{
+ uint32_t numAlternates = altValue.Length();
+ for (uint32_t i = 0; i < numAlternates; i++) {
+ const gfxAlternateValue& av = altValue.ElementAt(i);
+ AutoTArray<uint32_t,4> values;
+
+ // map <family, name, feature> ==> <values>
+ bool found =
+ featureLookup->GetFontFeatureValuesFor(aFamily, av.alternate,
+ av.value, values);
+ uint32_t numValues = values.Length();
+
+ // nothing defined, skip
+ if (!found || numValues == 0) {
+ continue;
+ }
+
+ gfxFontFeature feature;
+ if (av.alternate == NS_FONT_VARIANT_ALTERNATES_CHARACTER_VARIANT) {
+ NS_ASSERTION(numValues <= 2,
+ "too many values allowed for character-variant");
+ // character-variant(12 3) ==> 'cv12' = 3
+ uint32_t nn = values.ElementAt(0);
+ // ignore values greater than 99
+ if (nn == 0 || nn > MAX_CVXX_VALUE) {
+ continue;
+ }
+ feature.mValue = 1;
+ if (numValues > 1) {
+ feature.mValue = values.ElementAt(1);
+ }
+ feature.mTag = HB_TAG('c','v',('0' + nn / 10), ('0' + nn % 10));
+ aFontFeatures.AppendElement(feature);
+
+ } else if (av.alternate == NS_FONT_VARIANT_ALTERNATES_STYLESET) {
+ // styleset(1 2 7) ==> 'ss01' = 1, 'ss02' = 1, 'ss07' = 1
+ feature.mValue = 1;
+ for (uint32_t v = 0; v < numValues; v++) {
+ uint32_t nn = values.ElementAt(v);
+ if (nn == 0 || nn > MAX_SSXX_VALUE) {
+ continue;
+ }
+ feature.mTag = HB_TAG('s','s',('0' + nn / 10), ('0' + nn % 10));
+ aFontFeatures.AppendElement(feature);
+ }
+
+ } else {
+ NS_ASSERTION(numValues == 1,
+ "too many values for font-specific font-variant-alternates");
+ feature.mValue = values.ElementAt(0);
+
+ switch (av.alternate) {
+ case NS_FONT_VARIANT_ALTERNATES_STYLISTIC: // salt
+ feature.mTag = HB_TAG('s','a','l','t');
+ break;
+ case NS_FONT_VARIANT_ALTERNATES_SWASH: // swsh, cswh
+ feature.mTag = HB_TAG('s','w','s','h');
+ aFontFeatures.AppendElement(feature);
+ feature.mTag = HB_TAG('c','s','w','h');
+ break;
+ case NS_FONT_VARIANT_ALTERNATES_ORNAMENTS: // ornm
+ feature.mTag = HB_TAG('o','r','n','m');
+ break;
+ case NS_FONT_VARIANT_ALTERNATES_ANNOTATION: // nalt
+ feature.mTag = HB_TAG('n','a','l','t');
+ break;
+ default:
+ feature.mTag = 0;
+ break;
+ }
+
+ NS_ASSERTION(feature.mTag, "unsupported alternate type");
+ if (!feature.mTag) {
+ continue;
+ }
+ aFontFeatures.AppendElement(feature);
+ }
+ }
+}
+
+/* static */ void
+gfxFontShaper::MergeFontFeatures(
+ const gfxFontStyle *aStyle,
+ const nsTArray<gfxFontFeature>& aFontFeatures,
+ bool aDisableLigatures,
+ const nsAString& aFamilyName,
+ bool aAddSmallCaps,
+ void (*aHandleFeature)(const uint32_t&, uint32_t&, void*),
+ void* aHandleFeatureData)
+{
+ uint32_t numAlts = aStyle->alternateValues.Length();
+ 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 &&
+ numAlts == 0) {
+ return;
+ }
+
+ nsDataHashtable<nsUint32HashKey,uint32_t> mergedFeatures;
+
+ // Ligature features are enabled by default in the generic shaper,
+ // so we explicitly turn them off if necessary (for letter-spacing)
+ if (aDisableLigatures) {
+ mergedFeatures.Put(HB_TAG('l','i','g','a'), 0);
+ mergedFeatures.Put(HB_TAG('c','l','i','g'), 0);
+ }
+
+ // add feature values from font
+ uint32_t i, count;
+
+ count = aFontFeatures.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = aFontFeatures.ElementAt(i);
+ mergedFeatures.Put(feature.mTag, feature.mValue);
+ }
+
+ // 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:
+ mergedFeatures.Put(HB_TAG('c','2','s','c'), 1);
+ // fall through to the small-caps case
+ MOZ_FALLTHROUGH;
+
+ case NS_FONT_VARIANT_CAPS_SMALLCAPS:
+ mergedFeatures.Put(HB_TAG('s','m','c','p'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_ALLPETITE:
+ mergedFeatures.Put(aAddSmallCaps ? HB_TAG('c','2','s','c') :
+ HB_TAG('c','2','p','c'), 1);
+ // fall through to the petite-caps case
+ MOZ_FALLTHROUGH;
+
+ case NS_FONT_VARIANT_CAPS_PETITECAPS:
+ mergedFeatures.Put(aAddSmallCaps ? HB_TAG('s','m','c','p') :
+ HB_TAG('p','c','a','p'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_TITLING:
+ mergedFeatures.Put(HB_TAG('t','i','t','l'), 1);
+ break;
+
+ case NS_FONT_VARIANT_CAPS_UNICASE:
+ mergedFeatures.Put(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:
+ mergedFeatures.Put(HB_TAG('s','u','p','s'), 1);
+ break;
+ case NS_FONT_VARIANT_POSITION_SUB:
+ mergedFeatures.Put(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 && numAlts > 0) {
+ AutoTArray<gfxFontFeature,4> featureList;
+
+ // insert list of alternate feature settings
+ LookupAlternateValues(aStyle->featureValueLookup, aFamilyName,
+ aStyle->alternateValues, featureList);
+
+ count = featureList.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = featureList.ElementAt(i);
+ mergedFeatures.Put(feature.mTag, feature.mValue);
+ }
+ }
+
+ // add feature values from style rules
+ count = styleRuleFeatures.Length();
+ for (i = 0; i < count; i++) {
+ const gfxFontFeature& feature = styleRuleFeatures.ElementAt(i);
+ mergedFeatures.Put(feature.mTag, feature.mValue);
+ }
+
+ if (mergedFeatures.Count() != 0) {
+ for (auto iter = mergedFeatures.Iter(); !iter.Done(); iter.Next()) {
+ aHandleFeature(iter.Key(), iter.Data(), aHandleFeatureData);
+ }
+ }
+}
+
+// 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.
+//
+/* static */ void
+gfxFontShaper::GetRoundOffsetsToPixels(DrawTarget* aDrawTarget,
+ bool* aRoundX, bool* aRoundY)
+{
+ *aRoundX = false;
+ // 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 return false for non-cairo contexts.
+ if (aDrawTarget->GetTransform().HasNonTranslation()) {
+ *aRoundY = false;
+ return;
+ }
+
+ // All raster backends snap glyphs to pixels vertically.
+ // Print backends set CAIRO_HINT_METRICS_OFF.
+ *aRoundY = true;
+
+ cairo_t* cr = gfxFont::RefCairo(aDrawTarget);
+ cairo_scaled_font_t *scaled_font = cairo_get_scaled_font(cr);
+
+ // bug 1198921 - this sometimes fails under Windows for whatver reason
+ NS_ASSERTION(scaled_font, "null cairo scaled font should never be returned "
+ "by cairo_get_scaled_font");
+ if (!scaled_font) {
+ *aRoundX = true; // default to the same as the fallback path below
+ return;
+ }
+
+ // Sometimes hint metrics gets set for us, most notably for printing.
+ cairo_font_options_t *font_options = cairo_font_options_create();
+ cairo_scaled_font_get_font_options(scaled_font, font_options);
+ cairo_hint_metrics_t hint_metrics =
+ cairo_font_options_get_hint_metrics(font_options);
+ cairo_font_options_destroy(font_options);
+
+ switch (hint_metrics) {
+ case CAIRO_HINT_METRICS_OFF:
+ *aRoundY = false;
+ return;
+ case CAIRO_HINT_METRICS_DEFAULT:
+ // Here we mimic what cairo surface/font backends do. Printing
+ // surfaces have already been handled by hint_metrics. The
+ // fallback show_glyphs implementation composites pixel-aligned
+ // glyph surfaces, so we just pick surface/font combinations that
+ // override this.
+ switch (cairo_scaled_font_get_type(scaled_font)) {
+#if CAIRO_HAS_DWRITE_FONT // dwrite backend is not in std cairo releases yet
+ case CAIRO_FONT_TYPE_DWRITE:
+ // show_glyphs is implemented on the font and so is used for
+ // all surface types; however, it may pixel-snap depending on
+ // the dwrite rendering mode
+ if (!cairo_dwrite_scaled_font_get_force_GDI_classic(scaled_font) &&
+ gfxWindowsPlatform::GetPlatform()->DWriteMeasuringMode() ==
+ DWRITE_MEASURING_MODE_NATURAL) {
+ return;
+ }
+ MOZ_FALLTHROUGH;
+#endif
+ case CAIRO_FONT_TYPE_QUARTZ:
+ // Quartz surfaces implement show_glyphs for Quartz fonts
+ if (cairo_surface_get_type(cairo_get_target(cr)) ==
+ CAIRO_SURFACE_TYPE_QUARTZ) {
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case CAIRO_HINT_METRICS_ON:
+ break;
+ }
+ *aRoundX = true;
+}
+
+void
+gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
+ const char16_t *aString,
+ uint32_t aLength)
+{
+ CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset;
+
+ gfxTextRun::CompressedGlyph extendCluster;
+ extendCluster.SetComplex(false, true, 0);
+
+ ClusterIterator iter(aString, aLength);
+
+ // the ClusterIterator won't be able to tell us if the string
+ // _begins_ with a cluster-extender, so we handle that here
+ if (aLength) {
+ uint32_t ch = *aString;
+ if (aLength > 1 && NS_IS_HIGH_SURROGATE(ch) &&
+ NS_IS_LOW_SURROGATE(aString[1])) {
+ ch = SURROGATE_TO_UCS4(ch, aString[1]);
+ }
+ if (IsClusterExtender(ch)) {
+ *glyphs = extendCluster;
+ }
+ }
+
+ while (!iter.AtEnd()) {
+ if (*iter == char16_t(' ')) {
+ glyphs->SetIsSpace();
+ }
+ // advance iter to the next cluster-start (or end of text)
+ iter.Next();
+ // step past the first char of the cluster
+ aString++;
+ glyphs++;
+ // mark all the rest as cluster-continuations
+ while (aString < iter) {
+ *glyphs = extendCluster;
+ glyphs++;
+ aString++;
+ }
+ }
+}
+
+void
+gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
+ const uint8_t *aString,
+ uint32_t aLength)
+{
+ CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset;
+ const uint8_t *limit = aString + aLength;
+
+ while (aString < limit) {
+ if (*aString == uint8_t(' ')) {
+ glyphs->SetIsSpace();
+ }
+ aString++;
+ 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::SetGlyphs(uint32_t aIndex, CompressedGlyph aGlyph,
+ const DetailedGlyph *aGlyphs)
+{
+ NS_ASSERTION(!aGlyph.IsSimpleGlyph(), "Simple glyphs not handled here");
+ NS_ASSERTION(aIndex > 0 || aGlyph.IsLigatureGroupStart(),
+ "First character can't be a ligature continuation!");
+
+ uint32_t glyphCount = aGlyph.GetGlyphCount();
+ if (glyphCount > 0) {
+ DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, glyphCount);
+ memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount);
+ }
+ GetCharacterGlyphs()[aIndex] = aGlyph;
+}
+
+#define ZWNJ 0x200C
+#define ZWJ 0x200D
+static inline bool
+IsDefaultIgnorable(uint32_t aChar)
+{
+ return GetIdentifierModification(aChar) == XIDMOD_DEFAULT_IGNORABLE ||
+ aChar == ZWNJ || aChar == ZWJ;
+}
+
+void
+gfxShapedText::SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont *aFont)
+{
+ uint8_t category = GetGeneralCategory(aChar);
+ if (category >= HB_UNICODE_GENERAL_CATEGORY_SPACING_MARK &&
+ category <= HB_UNICODE_GENERAL_CATEGORY_NON_SPACING_MARK)
+ {
+ GetCharacterGlyphs()[aIndex].SetComplex(false, true, 0);
+ }
+
+ DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+
+ details->mGlyphID = aChar;
+ if (IsDefaultIgnorable(aChar)) {
+ // Setting advance width to zero will prevent drawing the hexbox
+ details->mAdvance = 0;
+ } else {
+ gfxFloat width =
+ std::max(aFont->GetMetrics(gfxFont::eHorizontal).aveCharWidth,
+ gfxFloat(gfxFontMissingGlyphs::GetDesiredMinWidth(aChar,
+ mAppUnitsPerDevUnit)));
+ details->mAdvance = uint32_t(width * mAppUnitsPerDevUnit);
+ }
+ details->mXOffset = 0;
+ details->mYOffset = 0;
+ GetCharacterGlyphs()[aIndex].SetMissing(1);
+}
+
+bool
+gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh)
+{
+ if (IsDefaultIgnorable(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.
+ if (GetGenCategory(aCh) == nsIUGenCategory::kLetter &&
+ aIndex + 1 < GetLength() &&
+ !GetCharacterGlyphs()[aIndex + 1].IsClusterStart()) {
+ return false;
+ }
+ DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
+ details->mGlyphID = aCh;
+ details->mAdvance = 0;
+ details->mXOffset = 0;
+ details->mYOffset = 0;
+ GetCharacterGlyphs()[aIndex].SetMissing(1);
+ return true;
+ }
+ return false;
+}
+
+void
+gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset,
+ uint32_t aOffset,
+ uint32_t aLength)
+{
+ uint32_t synAppUnitOffset = aSynBoldOffset * 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() + synAppUnitOffset;
+ if (CompressedGlyph::IsSimpleAdvance(advance)) {
+ glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
+ } else {
+ // rare case, tested by making this the default
+ uint32_t glyphIndex = glyphData->GetSimpleGlyph();
+ glyphData->SetComplex(true, true, 1);
+ DetailedGlyph detail = {glyphIndex, advance, 0, 0};
+ SetGlyphs(i, *glyphData, &detail);
+ }
+ } else {
+ // complex glyphs ==> add offset at cluster/ligature boundaries
+ uint32_t detailedLength = glyphData->GetGlyphCount();
+ if (detailedLength) {
+ DetailedGlyph *details = GetDetailedGlyphs(i);
+ if (!details) {
+ continue;
+ }
+ if (IsRightToLeft()) {
+ details[0].mAdvance += synAppUnitOffset;
+ } else {
+ details[detailedLength - 1].mAdvance += synAppUnitOffset;
+ }
+ }
+ }
+ }
+}
+
+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(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
+ AntialiasOption anAAOption, cairo_scaled_font_t *aScaledFont) :
+ mScaledFont(aScaledFont),
+ mFontEntry(aFontEntry), mIsValid(true),
+ mApplySyntheticBold(false),
+ mMathInitialized(false),
+ mStyle(*aFontStyle),
+ mAdjustedSize(0.0),
+ mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized"
+ mAntialiasOption(anAAOption)
+{
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ ++gFontCount;
+#endif
+ mKerningSet = HasFeatureSet(HB_TAG('k','e','r','n'), mKerningEnabled);
+}
+
+gfxFont::~gfxFont()
+{
+ mFontEntry->NotifyFontDestroyed(this);
+
+ if (mGlyphChangeObservers) {
+ for (auto it = mGlyphChangeObservers->Iter(); !it.Done(); it.Next()) {
+ it.Get()->GetKey()->ForgetFont();
+ }
+ }
+}
+
+gfxFloat
+gfxFont::GetGlyphHAdvance(DrawTarget* aDrawTarget, uint16_t aGID)
+{
+ if (!SetupCairoFont(aDrawTarget)) {
+ return 0;
+ }
+ if (ProvidesGlyphWidths()) {
+ return GetGlyphWidth(*aDrawTarget, aGID) / 65536.0;
+ }
+ if (mFUnitsConvFactor < 0.0f) {
+ GetMetrics(eHorizontal);
+ }
+ NS_ASSERTION(mFUnitsConvFactor >= 0.0f,
+ "missing font unit conversion factor");
+ if (!mHarfBuzzShaper) {
+ mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
+ }
+ gfxHarfBuzzShaper* shaper =
+ static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
+ if (!shaper->Initialize()) {
+ return 0;
+ }
+ return shaper->GetGlyphHAdvance(aGID) / 65536.0;
+}
+
+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 = ArrayLength(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 == ArrayLength(lookups));
+}
+
+static void
+CollectLookupsByLanguage(hb_face_t *aFace, hb_tag_t aTableTag,
+ const nsTHashtable<nsUint32HashKey>&
+ 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 = ArrayLength(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.GetEntry(featureTag) ?
+ aSpecificFeatureLookups : aOtherLookups;
+ CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups);
+ }
+ offset += len;
+ } while (len == ArrayLength(featureIndexes));
+}
+
+static bool
+HasLookupRuleWithGlyphByScript(hb_face_t *aFace, hb_tag_t aTableTag,
+ hb_tag_t aScriptTag, uint32_t aScriptIndex,
+ uint16_t aGlyph,
+ const nsTHashtable<nsUint32HashKey>&
+ 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();
+ nsTHashtable<nsUint32HashKey> specificFeature;
+
+ specificFeature.PutEntry(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);
+}
+
+nsDataHashtable<nsUint32HashKey,Script> *gfxFont::sScriptTagToCode = nullptr;
+nsTHashtable<nsUint32HashKey> *gfxFont::sDefaultFeatures = nullptr;
+
+static inline bool
+HasSubstitution(uint32_t *aBitVector, 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()
+{
+ mFontEntry->mHasSpaceFeaturesInitialized = true;
+
+ bool log = LOG_FONTINIT_ENABLED();
+ TimeStamp start;
+ if (MOZ_UNLIKELY(log)) {
+ start = TimeStamp::Now();
+ }
+
+ bool result = false;
+
+ uint32_t spaceGlyph = GetSpaceGlyph();
+ if (!spaceGlyph) {
+ return;
+ }
+
+ hb_face_t *face = GetFontEntry()->GetHBFace();
+
+ // GSUB lookups - examine per script
+ if (hb_ot_layout_has_substitution(face)) {
+
+ // set up the script ==> code hashtable if needed
+ if (!sScriptTagToCode) {
+ sScriptTagToCode =
+ new nsDataHashtable<nsUint32HashKey,
+ Script>(size_t(Script::NUM_SCRIPT_CODES));
+ sScriptTagToCode->Put(HB_TAG('D','F','L','T'), Script::COMMON);
+ for (Script s = Script::ARABIC; s < Script::NUM_SCRIPT_CODES;
+ s = Script(static_cast<int>(s) + 1)) {
+ hb_script_t scriptTag = hb_script_t(GetScriptTagForCode(s));
+ hb_tag_t s1, s2;
+ hb_ot_tags_from_script(scriptTag, &s1, &s2);
+ sScriptTagToCode->Put(s1, s);
+ if (s2 != HB_OT_TAG_DEFAULT_SCRIPT) {
+ sScriptTagToCode->Put(s2, s);
+ }
+ }
+
+ uint32_t numDefaultFeatures = ArrayLength(defaultFeatures);
+ sDefaultFeatures =
+ new nsTHashtable<nsUint32HashKey>(numDefaultFeatures);
+ for (uint32_t i = 0; i < numDefaultFeatures; i++) {
+ sDefaultFeatures->PutEntry(defaultFeatures[i]);
+ }
+ }
+
+ // iterate over the scripts in the font
+ hb_tag_t scriptTags[8];
+
+ uint32_t len, offset = 0;
+ do {
+ len = ArrayLength(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) ||
+ !sScriptTagToCode->Get(scriptTags[i], &s))
+ {
+ continue;
+ }
+ result = true;
+ 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 == ArrayLength(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
+ mFontEntry->mHasSpaceFeaturesKerning = false;
+ mFontEntry->mHasSpaceFeaturesNonKerning = false;
+
+ 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 || hasNonKerning) {
+ result = true;
+ }
+ mFontEntry->mHasSpaceFeaturesKerning = hasKerning;
+ mFontEntry->mHasSpaceFeaturesNonKerning = hasNonKerning;
+ }
+
+ hb_face_destroy(face);
+ mFontEntry->mHasSpaceFeatures = result;
+
+ 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",
+ NS_ConvertUTF16toUTF8(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->mHasSpaceFeaturesKerning ? "true" : "false"),
+ (mFontEntry->mHasSpaceFeaturesNonKerning ? "true" : "false"),
+ elapsed.ToMilliseconds()
+ ));
+ }
+}
+
+bool
+gfxFont::HasSubstitutionRulesWithSpaceLookups(Script aRunScript)
+{
+ NS_ASSERTION(GetFontEntry()->mHasSpaceFeaturesInitialized,
+ "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;
+}
+
+bool
+gfxFont::SpaceMayParticipateInShaping(Script aRunScript)
+{
+ // 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.
+ if (!mFontEntry->mHasSpaceFeaturesInitialized) {
+ CheckForFeaturesInvolvingSpace();
+ }
+
+ if (!mFontEntry->mHasSpaceFeatures) {
+ return false;
+ }
+
+ // if font has substitution rules or non-kerning positioning rules
+ // that involve spaces, bypass
+ if (HasSubstitutionRulesWithSpaceLookups(aRunScript) ||
+ mFontEntry->mHasSpaceFeaturesNonKerning) {
+ 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 && mFontEntry->mHasSpaceFeaturesKerning) {
+ 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;
+ }
+
+ if (!mHarfBuzzShaper) {
+ mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
+ }
+ gfxHarfBuzzShaper* shaper =
+ static_cast<gfxHarfBuzzShaper*>(mHarfBuzzShaper.get());
+ if (!shaper->Initialize()) {
+ 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_HIGH_SURROGATE(ch) &&
+ NS_IS_LOW_SURROGATE(aString[i + 1])) {
+ i++;
+ ch = SURROGATE_TO_UCS4(ch, aString[i]);
+ }
+
+ if (ch == 0xa0) {
+ ch = ' ';
+ }
+
+ 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::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;
+}
+
+/**
+ * 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
+{
+public:
+ GlyphBufferAzure(const TextRunDrawParams& aRunParams,
+ const FontDrawParams& aFontParams)
+ : mRunParams(aRunParams)
+ , mFontParams(aFontParams)
+ , mNumGlyphs(0)
+ {
+ }
+
+ ~GlyphBufferAzure()
+ {
+ Flush(true); // flush any remaining buffered glyphs
+ }
+
+ void OutputGlyph(uint32_t aGlyphID, const gfxPoint& aPt)
+ {
+ Glyph *glyph = AppendGlyph();
+ glyph->mIndex = aGlyphID;
+ glyph->mPosition.x = aPt.x;
+ glyph->mPosition.y = aPt.y;
+ glyph->mPosition = mFontParams.matInv.TransformPoint(glyph->mPosition);
+ Flush(false); // this will flush only if the buffer is full
+ }
+
+ const TextRunDrawParams& mRunParams;
+ const FontDrawParams& mFontParams;
+
+private:
+#define GLYPH_BUFFER_SIZE (2048/sizeof(Glyph))
+
+ Glyph *AppendGlyph()
+ {
+ return &mGlyphBuffer[mNumGlyphs++];
+ }
+
+ static DrawMode
+ GetStrokeMode(DrawMode aMode)
+ {
+ return aMode & (DrawMode::GLYPH_STROKE |
+ DrawMode::GLYPH_STROKE_UNDERNEATH);
+ }
+
+ // Render the buffered glyphs to the draw target and clear the buffer.
+ // This actually flushes the glyphs only if the buffer is full, or if the
+ // aFinish parameter is true; otherwise it simply returns.
+ void Flush(bool aFinish)
+ {
+ // Ensure there's enough room for a glyph to be added to the buffer
+ if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) {
+ return;
+ }
+
+ if (mRunParams.isRTL) {
+ Glyph *begin = &mGlyphBuffer[0];
+ Glyph *end = &mGlyphBuffer[mNumGlyphs];
+ std::reverse(begin, end);
+ }
+
+ gfx::GlyphBuffer buf;
+ buf.mGlyphs = mGlyphBuffer;
+ buf.mNumGlyphs = mNumGlyphs;
+
+ gfxContext::AzureState state = mRunParams.context->CurrentState();
+ if (mRunParams.drawMode & DrawMode::GLYPH_FILL) {
+ if (state.pattern || mFontParams.contextPaint) {
+ Pattern *pat;
+
+ RefPtr<gfxPattern> fillPattern;
+ if (!mFontParams.contextPaint ||
+ !(fillPattern = mFontParams.contextPaint->GetFillPattern(
+ mRunParams.context->GetDrawTarget(),
+ mRunParams.context->CurrentMatrix()))) {
+ if (state.pattern) {
+ pat = state.pattern->GetPattern(mRunParams.dt,
+ state.patternTransformChanged ?
+ &state.patternTransform : nullptr);
+ } else {
+ pat = nullptr;
+ }
+ } else {
+ pat = fillPattern->GetPattern(mRunParams.dt);
+ }
+
+ if (pat) {
+ Matrix saved;
+ Matrix *mat = nullptr;
+ if (mFontParams.passedInvMatrix) {
+ // The brush matrix needs to be multiplied with the
+ // inverted matrix as well, to move the brush into the
+ // space of the glyphs.
+
+ // This relies on the returned Pattern not to be reused
+ // by others, but regenerated on GetPattern calls. This
+ // is true!
+ if (pat->GetType() == PatternType::LINEAR_GRADIENT) {
+ mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) {
+ mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::SURFACE) {
+ mat = &static_cast<SurfacePattern*>(pat)->mMatrix;
+ }
+
+ if (mat) {
+ saved = *mat;
+ *mat = (*mat) * (*mFontParams.passedInvMatrix);
+ }
+ }
+
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
+ *pat, mFontParams.drawOptions,
+ mFontParams.renderingOptions);
+
+ if (mat) {
+ *mat = saved;
+ }
+ }
+ } else if (state.sourceSurface) {
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
+ SurfacePattern(state.sourceSurface,
+ ExtendMode::CLAMP,
+ state.surfTransform),
+ mFontParams.drawOptions,
+ mFontParams.renderingOptions);
+ } else {
+ mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
+ ColorPattern(state.color),
+ mFontParams.drawOptions,
+ mFontParams.renderingOptions);
+ }
+ }
+ if (GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE &&
+ mRunParams.strokeOpts) {
+ Pattern *pat;
+ if (mRunParams.textStrokePattern) {
+ pat = mRunParams.textStrokePattern->GetPattern(
+ mRunParams.dt, state.patternTransformChanged
+ ? &state.patternTransform
+ : nullptr);
+
+ if (pat) {
+ Matrix saved;
+ Matrix *mat = nullptr;
+ if (mFontParams.passedInvMatrix) {
+ // The brush matrix needs to be multiplied with the
+ // inverted matrix as well, to move the brush into the
+ // space of the glyphs.
+
+ // This relies on the returned Pattern not to be reused
+ // by others, but regenerated on GetPattern calls. This
+ // is true!
+ if (pat->GetType() == PatternType::LINEAR_GRADIENT) {
+ mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::RADIAL_GRADIENT) {
+ mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix;
+ } else if (pat->GetType() == PatternType::SURFACE) {
+ mat = &static_cast<SurfacePattern*>(pat)->mMatrix;
+ }
+
+ if (mat) {
+ saved = *mat;
+ *mat = (*mat) * (*mFontParams.passedInvMatrix);
+ }
+ }
+ FlushStroke(buf, *pat);
+
+ if (mat) {
+ *mat = saved;
+ }
+ }
+ } else {
+ FlushStroke(buf,
+ ColorPattern(
+ Color::FromABGR(mRunParams.textStrokeColor)));
+ }
+ }
+ if (mRunParams.drawMode & DrawMode::GLYPH_PATH) {
+ mRunParams.context->EnsurePathBuilder();
+ Matrix mat = mRunParams.dt->GetTransform();
+ mFontParams.scaledFont->CopyGlyphsToBuilder(
+ buf, mRunParams.context->mPathBuilder, &mat);
+ }
+
+ mNumGlyphs = 0;
+ }
+
+ void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern)
+ {
+ RefPtr<Path> path =
+ mFontParams.scaledFont->GetPathForGlyphs(aBuf, mRunParams.dt);
+ mRunParams.dt->Stroke(path, aPattern, *mRunParams.strokeOpts,
+ (mRunParams.drawOpts) ? *mRunParams.drawOpts
+ : DrawOptions());
+ }
+
+ Glyph mGlyphBuffer[GLYPH_BUFFER_SIZE];
+ unsigned int mNumGlyphs;
+
+#undef GLYPH_BUFFER_SIZE
+};
+
+// Bug 674909. When synthetic bolding text by drawing twice, need to
+// render using a pixel offset in device pixels, otherwise text
+// doesn't appear bolded, it appears as if a bad text shadow exists
+// when a non-identity transform exists. Use an offset factor so that
+// the second draw occurs at a constant offset in device pixels.
+
+double
+gfxFont::CalcXScale(DrawTarget* aDrawTarget)
+{
+ // determine magnitude of a 1px x offset in device space
+ Size t = aDrawTarget->GetTransform().TransformSize(Size(1.0, 0.0));
+ if (t.width == 1.0 && t.height == 0.0) {
+ // short-circuit the most common case to avoid sqrt() and division
+ return 1.0;
+ }
+
+ double m = sqrt(t.width * t.width + t.height * t.height);
+
+ NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding");
+ if (m == 0.0) {
+ return 0.0; // effectively disables offset
+ }
+
+ // scale factor so that offsets are 1px in device pixels
+ return 1.0 / m;
+}
+
+// Draw an individual glyph at a specific location.
+// *aPt is the glyph position in appUnits; it is converted to device
+// coordinates (devPt) here.
+void
+gfxFont::DrawOneGlyph(uint32_t aGlyphID, double aAdvance, gfxPoint *aPt,
+ GlyphBufferAzure& aBuffer, bool *aEmittedGlyphs) const
+{
+ const TextRunDrawParams& runParams(aBuffer.mRunParams);
+ const FontDrawParams& fontParams(aBuffer.mFontParams);
+
+ double glyphX, glyphY;
+ if (fontParams.isVerticalFont) {
+ glyphX = aPt->x;
+ if (runParams.isRTL) {
+ aPt->y -= aAdvance;
+ glyphY = aPt->y;
+ } else {
+ glyphY = aPt->y;
+ aPt->y += aAdvance;
+ }
+ } else {
+ glyphY = aPt->y;
+ if (runParams.isRTL) {
+ aPt->x -= aAdvance;
+ glyphX = aPt->x;
+ } else {
+ glyphX = aPt->x;
+ aPt->x += aAdvance;
+ }
+ }
+ gfxPoint devPt(ToDeviceUnits(glyphX, runParams.devPerApp),
+ ToDeviceUnits(glyphY, runParams.devPerApp));
+
+ if (fontParams.haveSVGGlyphs) {
+ if (!runParams.paintSVGGlyphs) {
+ return;
+ }
+ NS_WARNING_ASSERTION(
+ runParams.drawMode != DrawMode::GLYPH_PATH,
+ "Rendering SVG glyph despite request for glyph path");
+ if (RenderSVGGlyph(runParams.context, devPt,
+ aGlyphID, fontParams.contextPaint,
+ runParams.callbacks, *aEmittedGlyphs)) {
+ return;
+ }
+ }
+
+ if (fontParams.haveColorGlyphs &&
+ RenderColorGlyph(runParams.dt, runParams.context,
+ fontParams.scaledFont, fontParams.renderingOptions,
+ fontParams.drawOptions,
+ fontParams.matInv.TransformPoint(gfx::Point(devPt.x, devPt.y)),
+ aGlyphID)) {
+ return;
+ }
+
+ aBuffer.OutputGlyph(aGlyphID, devPt);
+
+ // Synthetic bolding (if required) by multi-striking.
+ for (int32_t i = 0; i < fontParams.extraStrikes; ++i) {
+ if (fontParams.isVerticalFont) {
+ devPt.y += fontParams.synBoldOnePixelOffset;
+ } else {
+ devPt.x += fontParams.synBoldOnePixelOffset;
+ }
+ aBuffer.OutputGlyph(aGlyphID, devPt);
+ }
+
+ *aEmittedGlyphs = true;
+}
+
+// Draw a run of CharacterGlyph records from the given offset in aShapedText.
+// Returns true if glyph paths were actually emitted.
+bool
+gfxFont::DrawGlyphs(const gfxShapedText *aShapedText,
+ uint32_t aOffset, // offset in the textrun
+ uint32_t aCount, // length of run to draw
+ gfxPoint *aPt,
+ const TextRunDrawParams& aRunParams,
+ const FontDrawParams& aFontParams)
+{
+ bool emittedGlyphs = false;
+ GlyphBufferAzure buffer(aRunParams, aFontParams);
+
+ gfxFloat& inlineCoord = aFontParams.isVerticalFont ? aPt->y : aPt->x;
+
+ if (aRunParams.spacing) {
+ inlineCoord += aRunParams.isRTL ? -aRunParams.spacing[0].mBefore
+ : aRunParams.spacing[0].mBefore;
+ }
+
+ const gfxShapedText::CompressedGlyph *glyphData =
+ &aShapedText->GetCharacterGlyphs()[aOffset];
+
+ for (uint32_t i = 0; i < aCount; ++i, ++glyphData) {
+ if (glyphData->IsSimpleGlyph()) {
+ DrawOneGlyph(glyphData->GetSimpleGlyph(),
+ glyphData->GetSimpleAdvance(),
+ aPt, buffer, &emittedGlyphs);
+ } else {
+ uint32_t glyphCount = glyphData->GetGlyphCount();
+ if (glyphCount > 0) {
+ const gfxShapedText::DetailedGlyph *details =
+ aShapedText->GetDetailedGlyphs(aOffset + i);
+ NS_ASSERTION(details, "detailedGlyph should not be missing!");
+ for (uint32_t j = 0; j < glyphCount; ++j, ++details) {
+ double advance = details->mAdvance;
+
+ if (glyphData->IsMissing()) {
+ // Default-ignorable chars will have zero advance width;
+ // we don't have to draw the hexbox for them.
+ if (aRunParams.drawMode != DrawMode::GLYPH_PATH &&
+ advance > 0) {
+ double glyphX = aPt->x;
+ double glyphY = aPt->y;
+ if (aRunParams.isRTL) {
+ if (aFontParams.isVerticalFont) {
+ glyphY -= advance;
+ } else {
+ glyphX -= advance;
+ }
+ }
+ Point pt(Float(ToDeviceUnits(glyphX, aRunParams.devPerApp)),
+ Float(ToDeviceUnits(glyphY, aRunParams.devPerApp)));
+ Float advanceDevUnits =
+ Float(ToDeviceUnits(advance, aRunParams.devPerApp));
+ Float height = GetMetrics(eHorizontal).maxAscent;
+ Rect glyphRect = aFontParams.isVerticalFont ?
+ Rect(pt.x - height / 2, pt.y,
+ height, advanceDevUnits) :
+ Rect(pt.x, pt.y - height,
+ advanceDevUnits, height);
+
+ // If there's a fake-italic skew in effect as part
+ // of the drawTarget's transform, we need to remove
+ // this before drawing the hexbox. (Bug 983985)
+ Matrix oldMat;
+ if (aFontParams.passedInvMatrix) {
+ oldMat = aRunParams.dt->GetTransform();
+ aRunParams.dt->SetTransform(
+ *aFontParams.passedInvMatrix * oldMat);
+ }
+
+ gfxFontMissingGlyphs::DrawMissingGlyph(
+ details->mGlyphID, glyphRect, *aRunParams.dt,
+ PatternFromState(aRunParams.context),
+ aShapedText->GetAppUnitsPerDevUnit());
+
+ // Restore the matrix, if we modified it before
+ // drawing the hexbox.
+ if (aFontParams.passedInvMatrix) {
+ aRunParams.dt->SetTransform(oldMat);
+ }
+ }
+ } else {
+ gfxPoint glyphXY(*aPt);
+ if (aFontParams.isVerticalFont) {
+ glyphXY.x += details->mYOffset;
+ glyphXY.y += details->mXOffset;
+ } else {
+ glyphXY.x += details->mXOffset;
+ glyphXY.y += details->mYOffset;
+ }
+ DrawOneGlyph(details->mGlyphID, advance, &glyphXY,
+ buffer, &emittedGlyphs);
+ }
+
+ inlineCoord += aRunParams.isRTL ? -advance : advance;
+ }
+ }
+ }
+
+ if (aRunParams.spacing) {
+ double space = aRunParams.spacing[i].mAfter;
+ if (i + 1 < aCount) {
+ space += aRunParams.spacing[i + 1].mBefore;
+ }
+ inlineCoord += aRunParams.isRTL ? -space : space;
+ }
+ }
+
+ return emittedGlyphs;
+}
+
+// This method is mostly parallel to DrawGlyphs.
+void
+gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfxPoint* aPt,
+ uint32_t aOffset, uint32_t aCount,
+ const EmphasisMarkDrawParams& aParams)
+{
+ gfxFloat& inlineCoord = aParams.isVertical ? aPt->y : aPt->x;
+ gfxTextRun::Range markRange(aParams.mark);
+ gfxTextRun::DrawParams params(aParams.context);
+
+ gfxFloat clusterStart = -std::numeric_limits<gfxFloat>::infinity();
+ bool shouldDrawEmphasisMark = false;
+ for (uint32_t i = 0, idx = aOffset; i < aCount; ++i, ++idx) {
+ if (aParams.spacing) {
+ inlineCoord += aParams.direction * aParams.spacing[i].mBefore;
+ }
+ if (aShapedText->IsClusterStart(idx) ||
+ clusterStart == -std::numeric_limits<gfxFloat>::infinity()) {
+ clusterStart = inlineCoord;
+ }
+ if (aShapedText->CharMayHaveEmphasisMark(idx)) {
+ shouldDrawEmphasisMark = true;
+ }
+ inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx);
+ if (shouldDrawEmphasisMark &&
+ (i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) {
+ gfxFloat clusterAdvance = inlineCoord - clusterStart;
+ // Move the coord backward to get the needed start point.
+ gfxFloat delta = (clusterAdvance + aParams.advance) / 2;
+ inlineCoord -= delta;
+ aParams.mark->Draw(markRange, *aPt, params);
+ inlineCoord += delta;
+ shouldDrawEmphasisMark = false;
+ }
+ if (aParams.spacing) {
+ inlineCoord += aParams.direction * aParams.spacing[i].mAfter;
+ }
+ }
+}
+
+void
+gfxFont::Draw(const gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
+ gfxPoint *aPt, const TextRunDrawParams& aRunParams,
+ uint16_t aOrientation)
+{
+ NS_ASSERTION(aRunParams.drawMode == DrawMode::GLYPH_PATH ||
+ !(int(aRunParams.drawMode) & int(DrawMode::GLYPH_PATH)),
+ "GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH");
+
+ if (aStart >= aEnd) {
+ return;
+ }
+
+ FontDrawParams fontParams;
+
+ if (aRunParams.drawOpts) {
+ fontParams.drawOptions = *aRunParams.drawOpts;
+ }
+
+ fontParams.scaledFont = GetScaledFont(aRunParams.dt);
+ if (!fontParams.scaledFont) {
+ return;
+ }
+
+ fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
+ fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
+ fontParams.contextPaint = aRunParams.runContextPaint;
+ fontParams.isVerticalFont =
+ aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+
+ bool sideways = false;
+ gfxPoint origPt = *aPt;
+ if (aRunParams.isVerticalRun && !fontParams.isVerticalFont) {
+ sideways = true;
+ aRunParams.context->Save();
+ gfxPoint p(aPt->x * aRunParams.devPerApp,
+ aPt->y * aRunParams.devPerApp);
+ const Metrics& metrics = GetMetrics(eHorizontal);
+ // Get a matrix we can use to draw the (horizontally-shaped) textrun
+ // with 90-degree CW rotation.
+ const gfxFloat
+ rotation = (aOrientation ==
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT)
+ ? -M_PI / 2.0 : M_PI / 2.0;
+ gfxMatrix mat =
+ aRunParams.context->CurrentMatrix().
+ Translate(p). // translate origin for rotation
+ Rotate(rotation). // turn 90deg CCW (sideways-left) or CW (*-right)
+ Translate(-p); // undo the translation
+
+ // If we're drawing rotated horizontal text for an element styled
+ // text-orientation:mixed, the dominant baseline will be vertical-
+ // centered. So in this case, we need to adjust the position so that
+ // the rotated horizontal text (which uses an alphabetic baseline) will
+ // look OK when juxtaposed with upright glyphs (rendered on a centered
+ // vertical baseline). The adjustment here is somewhat ad hoc; we
+ // should eventually look for baseline tables[1] in the fonts and use
+ // those if available.
+ // [1] See http://www.microsoft.com/typography/otspec/base.htm
+ if (aTextRun->UseCenterBaseline()) {
+ gfxPoint baseAdj(0, (metrics.emAscent - metrics.emDescent) / 2);
+ mat.Translate(baseAdj);
+ }
+
+ aRunParams.context->SetMatrix(mat);
+ }
+
+ UniquePtr<SVGContextPaint> contextPaint;
+ if (fontParams.haveSVGGlyphs && !fontParams.contextPaint) {
+ // If no pattern is specified for fill, use the current pattern
+ NS_ASSERTION((int(aRunParams.drawMode) & int(DrawMode::GLYPH_STROKE)) == 0,
+ "no pattern supplied for stroking text");
+ RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern();
+ contextPaint.reset(
+ new SimpleTextContextPaint(fillPattern, nullptr,
+ aRunParams.context->CurrentMatrix()));
+ fontParams.contextPaint = contextPaint.get();
+ }
+
+ // Synthetic-bold strikes are each offset one device pixel in run direction.
+ // (these values are only needed if IsSyntheticBold() is true)
+ if (IsSyntheticBold()) {
+ double xscale = CalcXScale(aRunParams.context->GetDrawTarget());
+ fontParams.synBoldOnePixelOffset = aRunParams.direction * xscale;
+ if (xscale != 0.0) {
+ // use as many strikes as needed for the the increased advance
+ fontParams.extraStrikes =
+ std::max(1, NS_lroundf(GetSyntheticBoldOffset() / xscale));
+ }
+ } else {
+ fontParams.synBoldOnePixelOffset = 0;
+ fontParams.extraStrikes = 0;
+ }
+
+ bool oldSubpixelAA = aRunParams.dt->GetPermitSubpixelAA();
+ if (!AllowSubpixelAA()) {
+ aRunParams.dt->SetPermitSubpixelAA(false);
+ }
+
+ Matrix mat;
+ Matrix oldMat = aRunParams.dt->GetTransform();
+
+ // This is nullptr when we have inverse-transformed glyphs and we need
+ // to transform the Brush inside flush.
+ fontParams.passedInvMatrix = nullptr;
+
+ fontParams.renderingOptions = GetGlyphRenderingOptions(&aRunParams);
+ fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
+
+ // The cairo DrawTarget backend uses the cairo_scaled_font directly
+ // and so has the font skew matrix applied already.
+ if (mScaledFont &&
+ aRunParams.dt->GetBackendType() != BackendType::CAIRO) {
+ cairo_matrix_t matrix;
+ cairo_scaled_font_get_font_matrix(mScaledFont, &matrix);
+ if (matrix.xy != 0) {
+ // If this matrix applies a skew, which can happen when drawing
+ // oblique fonts, we will set the DrawTarget matrix to apply the
+ // skew. We'll need to move the glyphs by the inverse of the skew to
+ // get the glyphs positioned correctly in the new device space
+ // though, since the font matrix should only be applied to drawing
+ // the glyphs, and not to their position.
+ mat = Matrix(matrix.xx, matrix.yx,
+ matrix.xy, matrix.yy,
+ matrix.x0, matrix.y0);
+
+ mat._11 = mat._22 = 1.0;
+ mat._21 /= GetAdjustedSize();
+
+ aRunParams.dt->SetTransform(mat * oldMat);
+
+ fontParams.matInv = mat;
+ fontParams.matInv.Invert();
+
+ fontParams.passedInvMatrix = &fontParams.matInv;
+ }
+ }
+
+ gfxFloat& baseline = fontParams.isVerticalFont ? aPt->x : aPt->y;
+ gfxFloat origBaseline = baseline;
+ if (mStyle.baselineOffset != 0.0) {
+ baseline +=
+ mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit();
+ }
+
+ bool emittedGlyphs =
+ DrawGlyphs(aTextRun, aStart, aEnd - aStart, aPt,
+ aRunParams, fontParams);
+
+ baseline = origBaseline;
+
+ if (aRunParams.callbacks && emittedGlyphs) {
+ aRunParams.callbacks->NotifyGlyphPathEmitted();
+ }
+
+ aRunParams.dt->SetTransform(oldMat);
+ aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA);
+
+ if (sideways) {
+ aRunParams.context->Restore();
+ // adjust updated aPt to account for the transform we were using
+ gfxFloat advance = aPt->x - origPt.x;
+ if (aOrientation ==
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT) {
+ *aPt = gfxPoint(origPt.x, origPt.y - advance);
+ } else {
+ *aPt = gfxPoint(origPt.x, origPt.y + advance);
+ }
+ }
+}
+
+bool
+gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint,
+ uint32_t aGlyphId, SVGContextPaint* aContextPaint) const
+{
+ if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
+ return false;
+ }
+
+ const gfxFloat devUnitsPerSVGUnit =
+ GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
+ gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
+
+ aContext->Save();
+ aContext->SetMatrix(
+ aContext->CurrentMatrix().Translate(aPoint.x, aPoint.y).
+ Scale(devUnitsPerSVGUnit, devUnitsPerSVGUnit));
+
+ aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit);
+
+ bool rv = GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId,
+ aContextPaint);
+ aContext->Restore();
+ aContext->NewPath();
+ return rv;
+}
+
+bool
+gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint,
+ uint32_t aGlyphId, SVGContextPaint* aContextPaint,
+ gfxTextRunDrawCallbacks *aCallbacks,
+ bool& aEmittedGlyphs) const
+{
+ if (aCallbacks && aEmittedGlyphs) {
+ aCallbacks->NotifyGlyphPathEmitted();
+ aEmittedGlyphs = false;
+ }
+ return RenderSVGGlyph(aContext, aPoint, aGlyphId, aContextPaint);
+}
+
+bool
+gfxFont::RenderColorGlyph(DrawTarget* aDrawTarget,
+ gfxContext* aContext,
+ mozilla::gfx::ScaledFont* scaledFont,
+ GlyphRenderingOptions* aRenderingOptions,
+ mozilla::gfx::DrawOptions aDrawOptions,
+ const mozilla::gfx::Point& aPoint,
+ uint32_t aGlyphId) const
+{
+ AutoTArray<uint16_t, 8> layerGlyphs;
+ AutoTArray<mozilla::gfx::Color, 8> layerColors;
+
+ mozilla::gfx::Color defaultColor;
+ if (!aContext->GetDeviceColor(defaultColor)) {
+ defaultColor = mozilla::gfx::Color(0, 0, 0);
+ }
+ if (!GetFontEntry()->GetColorLayersInfo(aGlyphId, defaultColor,
+ layerGlyphs, layerColors)) {
+ return false;
+ }
+
+ for (uint32_t layerIndex = 0; layerIndex < layerGlyphs.Length();
+ layerIndex++) {
+ Glyph glyph;
+ glyph.mIndex = layerGlyphs[layerIndex];
+ glyph.mPosition = aPoint;
+
+ mozilla::gfx::GlyphBuffer buffer;
+ buffer.mGlyphs = &glyph;
+ buffer.mNumGlyphs = 1;
+
+ aDrawTarget->FillGlyphs(scaledFont, buffer,
+ ColorPattern(layerColors[layerIndex]),
+ aDrawOptions, aRenderingOptions);
+ }
+ return true;
+}
+
+static void
+UnionRange(gfxFloat aX, gfxFloat* aDestMin, gfxFloat* aDestMax)
+{
+ *aDestMin = std::min(*aDestMin, aX);
+ *aDestMax = std::max(*aDestMax, aX);
+}
+
+// We get precise glyph extents if the textrun creator requested them, or
+// if the font is a user font --- in which case the author may be relying
+// on overflowing glyphs.
+static bool
+NeedsGlyphExtents(gfxFont *aFont, const gfxTextRun *aTextRun)
+{
+ return (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) ||
+ aFont->GetFontEntry()->IsUserFont();
+}
+
+bool
+gfxFont::IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget,
+ const gfxTextRun* aTextRun)
+{
+ if (!mFontEntry->mSpaceGlyphIsInvisibleInitialized &&
+ GetAdjustedSize() >= 1.0) {
+ gfxGlyphExtents *extents =
+ GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
+ gfxRect glyphExtents;
+ mFontEntry->mSpaceGlyphIsInvisible =
+ extents->GetTightGlyphExtentsAppUnits(this, aRefDrawTarget,
+ GetSpaceGlyph(), &glyphExtents) &&
+ glyphExtents.IsEmpty();
+ mFontEntry->mSpaceGlyphIsInvisibleInitialized = true;
+ }
+ return mFontEntry->mSpaceGlyphIsInvisible;
+}
+
+gfxFont::RunMetrics
+gfxFont::Measure(const gfxTextRun *aTextRun,
+ uint32_t aStart, uint32_t aEnd,
+ BoundingBoxType aBoundingBoxType,
+ DrawTarget* aRefDrawTarget,
+ Spacing *aSpacing,
+ uint16_t aOrientation)
+{
+ // If aBoundingBoxType is TIGHT_HINTED_OUTLINE_EXTENTS
+ // and the underlying cairo font may be antialiased,
+ // we need to create a copy in order to avoid getting cached extents.
+ // This is only used by MathML layout at present.
+ if (aBoundingBoxType == TIGHT_HINTED_OUTLINE_EXTENTS &&
+ mAntialiasOption != kAntialiasNone) {
+ if (!mNonAAFont) {
+ mNonAAFont.reset(CopyWithAntialiasOption(kAntialiasNone));
+ }
+ // if font subclass doesn't implement CopyWithAntialiasOption(),
+ // it will return null and we'll proceed to use the existing font
+ if (mNonAAFont) {
+ return mNonAAFont->Measure(aTextRun, aStart, aEnd,
+ TIGHT_HINTED_OUTLINE_EXTENTS,
+ aRefDrawTarget, aSpacing, aOrientation);
+ }
+ }
+
+ const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+ // Current position in appunits
+ gfxFont::Orientation orientation =
+ aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT
+ ? eVertical : eHorizontal;
+ const gfxFont::Metrics& fontMetrics = GetMetrics(orientation);
+
+ gfxFloat baselineOffset = 0;
+ if (aTextRun->UseCenterBaseline() && orientation == eHorizontal) {
+ // For a horizontal font being used in vertical writing mode with
+ // text-orientation:mixed, the overall metrics we're accumulating
+ // will be aimed at a center baseline. But this font's metrics were
+ // based on the alphabetic baseline. So we compute a baseline offset
+ // that will be applied to ascent/descent values and glyph rects
+ // to effectively shift them relative to the baseline.
+ // XXX Eventually we should probably use the BASE table, if present.
+ // But it usually isn't, so we need an ad hoc adjustment for now.
+ baselineOffset = appUnitsPerDevUnit *
+ (fontMetrics.emAscent - fontMetrics.emDescent) / 2;
+ }
+
+ RunMetrics metrics;
+ metrics.mAscent = fontMetrics.maxAscent * appUnitsPerDevUnit;
+ metrics.mDescent = fontMetrics.maxDescent * appUnitsPerDevUnit;
+
+ if (aStart == aEnd) {
+ // exit now before we look at aSpacing[0], which is undefined
+ metrics.mAscent -= baselineOffset;
+ metrics.mDescent += baselineOffset;
+ metrics.mBoundingBox = gfxRect(0, -metrics.mAscent,
+ 0, metrics.mAscent + metrics.mDescent);
+ return metrics;
+ }
+
+ gfxFloat advanceMin = 0, advanceMax = 0;
+ const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
+ bool isRTL = aTextRun->IsRightToLeft();
+ double direction = aTextRun->GetDirection();
+ bool needsGlyphExtents = NeedsGlyphExtents(this, aTextRun);
+ gfxGlyphExtents *extents =
+ ((aBoundingBoxType == LOOSE_INK_EXTENTS &&
+ !needsGlyphExtents &&
+ !aTextRun->HasDetailedGlyphs()) ||
+ (MOZ_UNLIKELY(GetStyle()->sizeAdjust == 0.0)) ||
+ (MOZ_UNLIKELY(GetStyle()->size == 0))) ? nullptr
+ : GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
+ double x = 0;
+ if (aSpacing) {
+ x += direction*aSpacing[0].mBefore;
+ }
+ uint32_t spaceGlyph = GetSpaceGlyph();
+ bool allGlyphsInvisible = true;
+ uint32_t i;
+ for (i = aStart; i < aEnd; ++i) {
+ const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i];
+ if (glyphData->IsSimpleGlyph()) {
+ double advance = glyphData->GetSimpleAdvance();
+ uint32_t glyphIndex = glyphData->GetSimpleGlyph();
+ if (glyphIndex != spaceGlyph ||
+ !IsSpaceGlyphInvisible(aRefDrawTarget, aTextRun)) {
+ allGlyphsInvisible = false;
+ }
+ // Only get the real glyph horizontal extent if we were asked
+ // for the tight bounding box or we're in quality mode
+ if ((aBoundingBoxType != LOOSE_INK_EXTENTS || needsGlyphExtents) &&
+ extents){
+ uint16_t extentsWidth = extents->GetContainedGlyphWidthAppUnits(glyphIndex);
+ if (extentsWidth != gfxGlyphExtents::INVALID_WIDTH &&
+ aBoundingBoxType == LOOSE_INK_EXTENTS) {
+ UnionRange(x, &advanceMin, &advanceMax);
+ UnionRange(x + direction*extentsWidth, &advanceMin, &advanceMax);
+ } else {
+ gfxRect glyphRect;
+ if (!extents->GetTightGlyphExtentsAppUnits(this,
+ aRefDrawTarget, glyphIndex, &glyphRect)) {
+ glyphRect = gfxRect(0, metrics.mBoundingBox.Y(),
+ advance, metrics.mBoundingBox.Height());
+ }
+ if (orientation == eVertical) {
+ Swap(glyphRect.x, glyphRect.y);
+ Swap(glyphRect.width, glyphRect.height);
+ }
+ if (isRTL) {
+ glyphRect -= gfxPoint(advance, 0);
+ }
+ glyphRect += gfxPoint(x, 0);
+ metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
+ }
+ }
+ x += direction*advance;
+ } else {
+ allGlyphsInvisible = false;
+ uint32_t glyphCount = glyphData->GetGlyphCount();
+ if (glyphCount > 0) {
+ const gfxTextRun::DetailedGlyph *details =
+ aTextRun->GetDetailedGlyphs(i);
+ NS_ASSERTION(details != nullptr,
+ "detailedGlyph record should not be missing!");
+ uint32_t j;
+ for (j = 0; j < glyphCount; ++j, ++details) {
+ uint32_t glyphIndex = details->mGlyphID;
+ gfxPoint glyphPt(x + details->mXOffset, details->mYOffset);
+ double advance = details->mAdvance;
+ gfxRect glyphRect;
+ if (glyphData->IsMissing() || !extents ||
+ !extents->GetTightGlyphExtentsAppUnits(this,
+ aRefDrawTarget, glyphIndex, &glyphRect)) {
+ // We might have failed to get glyph extents due to
+ // OOM or something
+ glyphRect = gfxRect(0, -metrics.mAscent,
+ advance, metrics.mAscent + metrics.mDescent);
+ }
+ if (orientation == eVertical) {
+ Swap(glyphRect.x, glyphRect.y);
+ Swap(glyphRect.width, glyphRect.height);
+ }
+ if (isRTL) {
+ glyphRect -= gfxPoint(advance, 0);
+ }
+ glyphRect += glyphPt;
+ metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
+ x += direction*advance;
+ }
+ }
+ }
+ // Every other glyph type is ignored
+ if (aSpacing) {
+ double space = aSpacing[i - aStart].mAfter;
+ if (i + 1 < aEnd) {
+ space += aSpacing[i + 1 - aStart].mBefore;
+ }
+ x += direction*space;
+ }
+ }
+
+ if (allGlyphsInvisible) {
+ metrics.mBoundingBox.SetEmpty();
+ } else {
+ if (aBoundingBoxType == LOOSE_INK_EXTENTS) {
+ UnionRange(x, &advanceMin, &advanceMax);
+ gfxRect fontBox(advanceMin, -metrics.mAscent,
+ advanceMax - advanceMin, metrics.mAscent + metrics.mDescent);
+ metrics.mBoundingBox = metrics.mBoundingBox.Union(fontBox);
+ }
+ if (isRTL) {
+ metrics.mBoundingBox -= gfxPoint(x, 0);
+ }
+ }
+
+ // If the font may be rendered with a fake-italic effect, we need to allow
+ // for the top-right of the glyphs being skewed to the right, and the
+ // bottom-left being skewed further left.
+ if (mStyle.style != NS_FONT_STYLE_NORMAL &&
+ mFontEntry->IsUpright() &&
+ mStyle.allowSyntheticStyle) {
+ gfxFloat extendLeftEdge =
+ ceil(OBLIQUE_SKEW_FACTOR * metrics.mBoundingBox.YMost());
+ gfxFloat extendRightEdge =
+ ceil(OBLIQUE_SKEW_FACTOR * -metrics.mBoundingBox.y);
+ metrics.mBoundingBox.width += extendLeftEdge + extendRightEdge;
+ metrics.mBoundingBox.x -= extendLeftEdge;
+ }
+
+ if (baselineOffset != 0) {
+ metrics.mAscent -= baselineOffset;
+ metrics.mDescent += baselineOffset;
+ metrics.mBoundingBox.y += baselineOffset;
+ }
+
+ metrics.mAdvanceWidth = x*direction;
+ return metrics;
+}
+
+void
+gfxFont::AgeCachedWords()
+{
+ if (mWordCache) {
+ for (auto it = mWordCache->Iter(); !it.Done(); it.Next()) {
+ CacheHashEntry *entry = it.Get();
+ if (!entry->mShapedWord) {
+ NS_ASSERTION(entry->mShapedWord,
+ "cache entry has no gfxShapedWord!");
+ it.Remove();
+ } else if (entry->mShapedWord->IncrementAge() ==
+ kShapedWordCacheMaxAge) {
+ it.Remove();
+ }
+ }
+ }
+}
+
+void
+gfxFont::NotifyGlyphsChanged()
+{
+ uint32_t i, count = mGlyphExtentsArray.Length();
+ for (i = 0; i < count; ++i) {
+ // Flush cached extents array
+ mGlyphExtentsArray[i]->NotifyGlyphsChanged();
+ }
+
+ if (mGlyphChangeObservers) {
+ for (auto it = mGlyphChangeObservers->Iter(); !it.Done(); it.Next()) {
+ it.Get()->GetKey()->NotifyGlyphsChanged();
+ }
+ }
+}
+
+// If aChar is a "word boundary" for shaped-word caching purposes, return it;
+// else return 0.
+static char16_t
+IsBoundarySpace(char16_t aChar, char16_t aNextChar)
+{
+ if ((aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar)) {
+ return aChar;
+ }
+ return 0;
+}
+
+#ifdef __GNUC__
+#define GFX_MAYBE_UNUSED __attribute__((unused))
+#else
+#define GFX_MAYBE_UNUSED
+#endif
+
+template<typename T>
+gfxShapedWord*
+gfxFont::GetShapedWord(DrawTarget *aDrawTarget,
+ const T *aText,
+ uint32_t aLength,
+ uint32_t aHash,
+ Script aRunScript,
+ bool aVertical,
+ int32_t aAppUnitsPerDevUnit,
+ uint32_t aFlags,
+ gfxTextPerfMetrics *aTextPerf GFX_MAYBE_UNUSED)
+{
+ // if the cache is getting too big, flush it and start over
+ uint32_t wordCacheMaxEntries =
+ gfxPlatform::GetPlatform()->WordCacheMaxEntries();
+ if (mWordCache->Count() > wordCacheMaxEntries) {
+ NS_WARNING("flushing shaped-word cache");
+ ClearCachedWords();
+ }
+
+ // if there's a cached entry for this word, just return it
+ CacheHashKey key(aText, aLength, aHash,
+ aRunScript,
+ aAppUnitsPerDevUnit,
+ aFlags);
+
+ CacheHashEntry *entry = mWordCache->PutEntry(key);
+ if (!entry) {
+ NS_WARNING("failed to create word cache entry - expect missing text");
+ return nullptr;
+ }
+ gfxShapedWord* sw = entry->mShapedWord.get();
+
+ bool isContent = !mStyle.systemFont;
+
+ if (sw) {
+ sw->ResetAge();
+ Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_HITS_CONTENT :
+ Telemetry::WORD_CACHE_HITS_CHROME),
+ aLength);
+#ifndef RELEASE_OR_BETA
+ if (aTextPerf) {
+ aTextPerf->current.wordCacheHit++;
+ }
+#endif
+ return sw;
+ }
+
+ Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_MISSES_CONTENT :
+ Telemetry::WORD_CACHE_MISSES_CHROME),
+ aLength);
+#ifndef RELEASE_OR_BETA
+ if (aTextPerf) {
+ aTextPerf->current.wordCacheMiss++;
+ }
+#endif
+
+ sw = gfxShapedWord::Create(aText, aLength, aRunScript, aAppUnitsPerDevUnit,
+ aFlags);
+ entry->mShapedWord.reset(sw);
+ if (!sw) {
+ NS_WARNING("failed to create gfxShapedWord - expect missing text");
+ return nullptr;
+ }
+
+ DebugOnly<bool> ok =
+ ShapeText(aDrawTarget, aText, 0, aLength, aRunScript, aVertical, sw);
+
+ NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text");
+
+ return sw;
+}
+
+bool
+gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
+{
+ const gfxShapedWord* sw = mShapedWord.get();
+ if (!sw) {
+ return false;
+ }
+ if (sw->GetLength() != aKey->mLength ||
+ sw->GetFlags() != aKey->mFlags ||
+ sw->GetAppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit ||
+ sw->GetScript() != aKey->mScript) {
+ return false;
+ }
+ if (sw->TextIs8Bit()) {
+ if (aKey->mTextIs8Bit) {
+ return (0 == memcmp(sw->Text8Bit(), aKey->mText.mSingle,
+ aKey->mLength * sizeof(uint8_t)));
+ }
+ // The key has 16-bit text, even though all the characters are < 256,
+ // so the TEXT_IS_8BIT flag was set and the cached ShapedWord we're
+ // comparing with will have 8-bit text.
+ const uint8_t *s1 = sw->Text8Bit();
+ const char16_t *s2 = aKey->mText.mDouble;
+ const char16_t *s2end = s2 + aKey->mLength;
+ while (s2 < s2end) {
+ if (*s1++ != *s2++) {
+ return false;
+ }
+ }
+ return true;
+ }
+ NS_ASSERTION((aKey->mFlags & gfxTextRunFactory::TEXT_IS_8BIT) == 0 &&
+ !aKey->mTextIs8Bit, "didn't expect 8-bit text here");
+ return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble,
+ aKey->mLength * sizeof(char16_t)));
+}
+
+bool
+gfxFont::ShapeText(DrawTarget *aDrawTarget,
+ const uint8_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ nsDependentCSubstring ascii((const char*)aText, aLength);
+ nsAutoString utf16;
+ AppendASCIItoUTF16(ascii, utf16);
+ if (utf16.Length() != aLength) {
+ return false;
+ }
+ return ShapeText(aDrawTarget, utf16.BeginReading(), aOffset, aLength,
+ aScript, aVertical, aShapedText);
+}
+
+bool
+gfxFont::ShapeText(DrawTarget *aDrawTarget,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxShapedText *aShapedText)
+{
+ bool ok = false;
+
+ // XXX Currently, we do all vertical shaping through harfbuzz.
+ // Vertical graphite support may be wanted as a future enhancement.
+ if (FontCanSupportGraphite() && !aVertical) {
+ if (gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
+ if (!mGraphiteShaper) {
+ mGraphiteShaper = MakeUnique<gfxGraphiteShaper>(this);
+ }
+ ok = mGraphiteShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
+ aScript, aVertical, aShapedText);
+ }
+ }
+
+ if (!ok) {
+ if (!mHarfBuzzShaper) {
+ mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
+ }
+ ok = mHarfBuzzShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
+ aScript, aVertical, aShapedText);
+ }
+
+ NS_WARNING_ASSERTION(ok, "shaper failed, expect scrambled or missing text");
+
+ PostShapingFixup(aDrawTarget, aText, aOffset, aLength,
+ aVertical, aShapedText);
+
+ return ok;
+}
+
+void
+gfxFont::PostShapingFixup(DrawTarget* aDrawTarget,
+ const char16_t* aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ bool aVertical,
+ gfxShapedText* aShapedText)
+{
+ if (IsSyntheticBold()) {
+ const Metrics& metrics =
+ GetMetrics(aVertical ? eVertical : eHorizontal);
+ if (metrics.maxAdvance > metrics.aveCharWidth) {
+ float synBoldOffset =
+ GetSyntheticBoldOffset() * CalcXScale(aDrawTarget);
+ aShapedText->AdjustAdvancesForSyntheticBold(synBoldOffset,
+ aOffset, aLength);
+ }
+ }
+}
+
+#define MAX_SHAPING_LENGTH 32760 // slightly less than 32K, trying to avoid
+ // over-stressing platform shapers
+#define BACKTRACK_LIMIT 16 // backtrack this far looking for a good place
+ // to split into fragments for separate shaping
+
+template<typename T>
+bool
+gfxFont::ShapeFragmentWithoutWordCache(DrawTarget *aDrawTarget,
+ const T *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxTextRun *aTextRun)
+{
+ aTextRun->SetupClusterBoundaries(aOffset, aText, aLength);
+
+ bool ok = true;
+
+ while (ok && aLength > 0) {
+ uint32_t fragLen = aLength;
+
+ // limit the length of text we pass to shapers in a single call
+ if (fragLen > MAX_SHAPING_LENGTH) {
+ fragLen = MAX_SHAPING_LENGTH;
+
+ // in the 8-bit case, there are no multi-char clusters,
+ // so we don't need to do this check
+ if (sizeof(T) == sizeof(char16_t)) {
+ uint32_t i;
+ for (i = 0; i < BACKTRACK_LIMIT; ++i) {
+ if (aTextRun->IsClusterStart(aOffset + fragLen - i)) {
+ fragLen -= i;
+ break;
+ }
+ }
+ if (i == BACKTRACK_LIMIT) {
+ // if we didn't find any cluster start while backtracking,
+ // just check that we're not in the middle of a surrogate
+ // pair; back up by one code unit if we are.
+ if (NS_IS_LOW_SURROGATE(aText[fragLen]) &&
+ NS_IS_HIGH_SURROGATE(aText[fragLen - 1])) {
+ --fragLen;
+ }
+ }
+ }
+ }
+
+ ok = ShapeText(aDrawTarget, aText, aOffset, fragLen, aScript, aVertical,
+ aTextRun);
+
+ aText += fragLen;
+ aOffset += fragLen;
+ aLength -= fragLen;
+ }
+
+ return ok;
+}
+
+// Check if aCh is an unhandled control character that should be displayed
+// as a hexbox rather than rendered by some random font on the system.
+// We exclude \r as stray &#13;s are rather common (bug 941940).
+// Note that \n and \t don't come through here, as they have specific
+// meanings that have already been handled.
+static bool
+IsInvalidControlChar(uint32_t aCh)
+{
+ return aCh != '\r' && ((aCh & 0x7f) < 0x20 || aCh == 0x7f);
+}
+
+template<typename T>
+bool
+gfxFont::ShapeTextWithoutWordCache(DrawTarget *aDrawTarget,
+ const T *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ Script aScript,
+ bool aVertical,
+ gfxTextRun *aTextRun)
+{
+ uint32_t fragStart = 0;
+ bool ok = true;
+
+ for (uint32_t i = 0; i <= aLength && ok; ++i) {
+ T ch = (i < aLength) ? aText[i] : '\n';
+ bool invalid = gfxFontGroup::IsInvalidChar(ch);
+ uint32_t length = i - fragStart;
+
+ // break into separate fragments when we hit an invalid char
+ if (!invalid) {
+ continue;
+ }
+
+ if (length > 0) {
+ ok = ShapeFragmentWithoutWordCache(aDrawTarget, aText + fragStart,
+ aOffset + fragStart, length,
+ aScript, aVertical, aTextRun);
+ }
+
+ if (i == aLength) {
+ break;
+ }
+
+ // fragment was terminated by an invalid char: skip it,
+ // unless it's a control char that we want to show as a hexbox,
+ // but record where TAB or NEWLINE occur
+ if (ch == '\t') {
+ aTextRun->SetIsTab(aOffset + i);
+ } else if (ch == '\n') {
+ aTextRun->SetIsNewline(aOffset + i);
+ } else if (IsInvalidControlChar(ch) &&
+ !(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS)) {
+ if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
+ ShapeFragmentWithoutWordCache(aDrawTarget, aText + i,
+ aOffset + i, 1,
+ aScript, aVertical, aTextRun);
+ } else {
+ aTextRun->SetMissingGlyph(aOffset + i, ch, this);
+ }
+ }
+ fragStart = i + 1;
+ }
+
+ NS_WARNING_ASSERTION(ok, "failed to shape text - expect garbled text");
+ return ok;
+}
+
+#ifndef RELEASE_OR_BETA
+#define TEXT_PERF_INCR(tp, m) (tp ? (tp)->current.m++ : 0)
+#else
+#define TEXT_PERF_INCR(tp, m)
+#endif
+
+inline static bool IsChar8Bit(uint8_t /*aCh*/) { return true; }
+inline static bool IsChar8Bit(char16_t aCh) { return aCh < 0x100; }
+
+inline static bool HasSpaces(const uint8_t *aString, uint32_t aLen)
+{
+ return memchr(aString, 0x20, aLen) != nullptr;
+}
+
+inline static bool HasSpaces(const char16_t *aString, uint32_t aLen)
+{
+ for (const char16_t *ch = aString; ch < aString + aLen; ch++) {
+ if (*ch == 0x20) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template<typename T>
+bool
+gfxFont::SplitAndInitTextRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const T *aString, // text for this font run
+ uint32_t aRunStart, // position in the textrun
+ uint32_t aRunLength,
+ Script aRunScript,
+ bool aVertical)
+{
+ if (aRunLength == 0) {
+ return true;
+ }
+
+ gfxTextPerfMetrics *tp = nullptr;
+
+#ifndef RELEASE_OR_BETA
+ tp = aTextRun->GetFontGroup()->GetTextPerfMetrics();
+ if (tp) {
+ if (mStyle.systemFont) {
+ tp->current.numChromeTextRuns++;
+ } else {
+ tp->current.numContentTextRuns++;
+ }
+ tp->current.numChars += aRunLength;
+ if (aRunLength > tp->current.maxTextRunLen) {
+ tp->current.maxTextRunLen = aRunLength;
+ }
+ }
+#endif
+
+ uint32_t wordCacheCharLimit =
+ gfxPlatform::GetPlatform()->WordCacheCharLimit();
+
+ // If spaces can participate in shaping (e.g. within lookups for automatic
+ // fractions), need to shape without using the word cache which segments
+ // textruns on space boundaries. Word cache can be used if the textrun
+ // is short enough to fit in the word cache and it lacks spaces.
+ if (SpaceMayParticipateInShaping(aRunScript)) {
+ if (aRunLength > wordCacheCharLimit ||
+ HasSpaces(aString, aRunLength)) {
+ TEXT_PERF_INCR(tp, wordCacheSpaceRules);
+ return ShapeTextWithoutWordCache(aDrawTarget, aString,
+ aRunStart, aRunLength,
+ aRunScript, aVertical,
+ aTextRun);
+ }
+ }
+
+ InitWordCache();
+
+ // the only flags we care about for ShapedWord construction/caching
+ uint32_t flags = aTextRun->GetFlags();
+ flags &= (gfxTextRunFactory::TEXT_IS_RTL |
+ gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES |
+ gfxTextRunFactory::TEXT_USE_MATH_SCRIPT |
+ gfxTextRunFactory::TEXT_ORIENT_MASK);
+ if (sizeof(T) == sizeof(uint8_t)) {
+ flags |= gfxTextRunFactory::TEXT_IS_8BIT;
+ }
+
+ uint32_t wordStart = 0;
+ uint32_t hash = 0;
+ bool wordIs8Bit = true;
+ int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
+
+ T nextCh = aString[0];
+ for (uint32_t i = 0; i <= aRunLength; ++i) {
+ T ch = nextCh;
+ nextCh = (i < aRunLength - 1) ? aString[i + 1] : '\n';
+ T boundary = IsBoundarySpace(ch, nextCh);
+ bool invalid = !boundary && gfxFontGroup::IsInvalidChar(ch);
+ uint32_t length = i - wordStart;
+
+ // break into separate ShapedWords when we hit an invalid char,
+ // or a boundary space (always handled individually),
+ // or the first non-space after a space
+ if (!boundary && !invalid) {
+ if (!IsChar8Bit(ch)) {
+ wordIs8Bit = false;
+ }
+ // include this character in the hash, and move on to next
+ hash = gfxShapedWord::HashMix(hash, ch);
+ continue;
+ }
+
+ // We've decided to break here (i.e. we're at the end of a "word");
+ // shape the word and add it to the textrun.
+ // For words longer than the limit, we don't use the
+ // font's word cache but just shape directly into the textrun.
+ if (length > wordCacheCharLimit) {
+ TEXT_PERF_INCR(tp, wordCacheLong);
+ bool ok = ShapeFragmentWithoutWordCache(aDrawTarget,
+ aString + wordStart,
+ aRunStart + wordStart,
+ length,
+ aRunScript,
+ aVertical,
+ aTextRun);
+ if (!ok) {
+ return false;
+ }
+ } else if (length > 0) {
+ uint32_t wordFlags = flags;
+ // in the 8-bit version of this method, TEXT_IS_8BIT was
+ // already set as part of |flags|, so no need for a per-word
+ // adjustment here
+ if (sizeof(T) == sizeof(char16_t)) {
+ if (wordIs8Bit) {
+ wordFlags |= gfxTextRunFactory::TEXT_IS_8BIT;
+ }
+ }
+ gfxShapedWord* sw = GetShapedWord(aDrawTarget,
+ aString + wordStart, length,
+ hash, aRunScript, aVertical,
+ appUnitsPerDevUnit,
+ wordFlags, tp);
+ if (sw) {
+ aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart);
+ } else {
+ return false; // failed, presumably out of memory?
+ }
+ }
+
+ if (boundary) {
+ // word was terminated by a space: add that to the textrun
+ uint16_t orientation = flags & gfxTextRunFactory::TEXT_ORIENT_MASK;
+ if (orientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_MIXED) {
+ orientation = aVertical ?
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT :
+ gfxTextRunFactory::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ }
+ if (boundary != ' ' ||
+ !aTextRun->SetSpaceGlyphIfSimple(this, aRunStart + i, ch,
+ orientation)) {
+ // Currently, the only "boundary" characters we recognize are
+ // space and no-break space, which are both 8-bit, so we force
+ // that flag (below). If we ever change IsBoundarySpace, we
+ // may need to revise this.
+ // Avoid tautological-constant-out-of-range-compare in 8-bit:
+ DebugOnly<char16_t> boundary16 = boundary;
+ NS_ASSERTION(boundary16 < 256, "unexpected boundary!");
+ gfxShapedWord *sw =
+ GetShapedWord(aDrawTarget, &boundary, 1,
+ gfxShapedWord::HashMix(0, boundary),
+ aRunScript, aVertical, appUnitsPerDevUnit,
+ flags | gfxTextRunFactory::TEXT_IS_8BIT, tp);
+ if (sw) {
+ aTextRun->CopyGlyphDataFrom(sw, aRunStart + i);
+ } else {
+ return false;
+ }
+ }
+ hash = 0;
+ wordStart = i + 1;
+ wordIs8Bit = true;
+ continue;
+ }
+
+ if (i == aRunLength) {
+ break;
+ }
+
+ NS_ASSERTION(invalid,
+ "how did we get here except via an invalid char?");
+
+ // word was terminated by an invalid char: skip it,
+ // unless it's a control char that we want to show as a hexbox,
+ // but record where TAB or NEWLINE occur
+ if (ch == '\t') {
+ aTextRun->SetIsTab(aRunStart + i);
+ } else if (ch == '\n') {
+ aTextRun->SetIsNewline(aRunStart + i);
+ } else if (IsInvalidControlChar(ch) &&
+ !(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_HIDE_CONTROL_CHARACTERS)) {
+ if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
+ ShapeFragmentWithoutWordCache(aDrawTarget, aString + i,
+ aRunStart + i, 1,
+ aRunScript, aVertical, aTextRun);
+ } else {
+ aTextRun->SetMissingGlyph(aRunStart + i, ch, this);
+ }
+ }
+
+ hash = 0;
+ wordStart = i + 1;
+ wordIs8Bit = true;
+ }
+
+ return true;
+}
+
+// Explicit instantiations of SplitAndInitTextRun, to avoid libxul link failure
+template bool
+gfxFont::SplitAndInitTextRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const uint8_t *aString,
+ uint32_t aRunStart,
+ uint32_t aRunLength,
+ Script aRunScript,
+ bool aVertical);
+template bool
+gfxFont::SplitAndInitTextRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const char16_t *aString,
+ uint32_t aRunStart,
+ uint32_t aRunLength,
+ Script aRunScript,
+ bool aVertical);
+
+template<>
+bool
+gfxFont::InitFakeSmallCapsRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const char16_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ uint8_t aMatchType,
+ uint16_t aOrientation,
+ Script aScript,
+ bool aSyntheticLower,
+ bool aSyntheticUpper)
+{
+ bool ok = true;
+
+ RefPtr<gfxFont> smallCapsFont = GetSmallCapsFont();
+ if (!smallCapsFont) {
+ NS_WARNING("failed to get reduced-size font for smallcaps!");
+ smallCapsFont = this;
+ }
+
+ enum RunCaseAction {
+ kNoChange,
+ kUppercaseReduce,
+ kUppercase
+ };
+
+ RunCaseAction runAction = kNoChange;
+ uint32_t runStart = 0;
+ bool vertical =
+ aOrientation == gfxTextRunFactory::TEXT_ORIENT_VERTICAL_UPRIGHT;
+
+ for (uint32_t i = 0; i <= aLength; ++i) {
+ uint32_t extraCodeUnits = 0; // Will be set to 1 if we need to consume
+ // a trailing surrogate as well as the
+ // current code unit.
+ RunCaseAction chAction = kNoChange;
+ // Unless we're at the end, figure out what treatment the current
+ // character will need.
+ if (i < aLength) {
+ uint32_t ch = aText[i];
+ if (NS_IS_HIGH_SURROGATE(ch) && i < aLength - 1 &&
+ NS_IS_LOW_SURROGATE(aText[i + 1])) {
+ ch = SURROGATE_TO_UCS4(ch, aText[i + 1]);
+ extraCodeUnits = 1;
+ }
+ // Characters that aren't the start of a cluster are ignored here.
+ // They get added to whatever lowercase/non-lowercase run we're in.
+ if (IsClusterExtender(ch)) {
+ chAction = runAction;
+ } else {
+ if (ch != ToUpperCase(ch) || SpecialUpper(ch)) {
+ // ch is lower case
+ chAction = (aSyntheticLower ? kUppercaseReduce : kNoChange);
+ } else if (ch != ToLowerCase(ch)) {
+ // ch is upper case
+ chAction = (aSyntheticUpper ? kUppercaseReduce : kNoChange);
+ if (mStyle.explicitLanguage &&
+ mStyle.language == nsGkAtoms::el) {
+ // In Greek, check for characters that will be modified by
+ // the GreekUpperCase mapping - this catches accented
+ // capitals where the accent is to be removed (bug 307039).
+ // These are handled by using the full-size font with the
+ // uppercasing transform.
+ mozilla::GreekCasing::State state;
+ uint32_t ch2 = mozilla::GreekCasing::UpperCase(ch, state);
+ if (ch != ch2 && !aSyntheticUpper) {
+ chAction = kUppercase;
+ }
+ }
+ }
+ }
+ }
+
+ // At the end of the text or when the current character needs different
+ // casing treatment from the current run, finish the run-in-progress
+ // and prepare to accumulate a new run.
+ // Note that we do not look at any source data for offset [i] here,
+ // as that would be invalid in the case where i==length.
+ if ((i == aLength || runAction != chAction) && runStart < i) {
+ uint32_t runLength = i - runStart;
+ gfxFont* f = this;
+ switch (runAction) {
+ case kNoChange:
+ // just use the current font and the existing string
+ aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart, true,
+ aOrientation);
+ if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
+ aText + runStart,
+ aOffset + runStart, runLength,
+ aScript, vertical)) {
+ ok = false;
+ }
+ break;
+
+ case kUppercaseReduce:
+ // use reduced-size font, then fall through to uppercase the text
+ f = smallCapsFont;
+ MOZ_FALLTHROUGH;
+
+ case kUppercase:
+ // apply uppercase transform to the string
+ nsDependentSubstring origString(aText + runStart, runLength);
+ nsAutoString convertedString;
+ AutoTArray<bool,50> charsToMergeArray;
+ AutoTArray<bool,50> deletedCharsArray;
+
+ bool mergeNeeded = nsCaseTransformTextRunFactory::
+ TransformString(origString,
+ convertedString,
+ true,
+ mStyle.explicitLanguage
+ ? mStyle.language.get() : nullptr,
+ charsToMergeArray,
+ deletedCharsArray);
+
+ if (mergeNeeded) {
+ // This is the hard case: the transformation caused chars
+ // to be inserted or deleted, so we can't shape directly
+ // into the destination textrun but have to handle the
+ // mismatch of character positions.
+ gfxTextRunFactory::Parameters params = {
+ aDrawTarget, nullptr, nullptr, nullptr, 0,
+ aTextRun->GetAppUnitsPerDevUnit()
+ };
+ RefPtr<gfxTextRun> tempRun(
+ gfxTextRun::Create(&params, convertedString.Length(),
+ aTextRun->GetFontGroup(), 0));
+ tempRun->AddGlyphRun(f, aMatchType, 0, true, aOrientation);
+ if (!f->SplitAndInitTextRun(aDrawTarget, tempRun.get(),
+ convertedString.BeginReading(),
+ 0, convertedString.Length(),
+ aScript, vertical)) {
+ ok = false;
+ } else {
+ RefPtr<gfxTextRun> mergedRun(
+ gfxTextRun::Create(&params, runLength,
+ aTextRun->GetFontGroup(), 0));
+ MergeCharactersInTextRun(mergedRun.get(), tempRun.get(),
+ charsToMergeArray.Elements(),
+ deletedCharsArray.Elements());
+ gfxTextRun::Range runRange(0, runLength);
+ aTextRun->CopyGlyphDataFrom(mergedRun.get(), runRange,
+ aOffset + runStart);
+ }
+ } else {
+ aTextRun->AddGlyphRun(f, aMatchType, aOffset + runStart,
+ true, aOrientation);
+ if (!f->SplitAndInitTextRun(aDrawTarget, aTextRun,
+ convertedString.BeginReading(),
+ aOffset + runStart, runLength,
+ aScript, vertical)) {
+ ok = false;
+ }
+ }
+ break;
+ }
+
+ runStart = i;
+ }
+
+ i += extraCodeUnits;
+ if (i < aLength) {
+ runAction = chAction;
+ }
+ }
+
+ return ok;
+}
+
+template<>
+bool
+gfxFont::InitFakeSmallCapsRun(DrawTarget *aDrawTarget,
+ gfxTextRun *aTextRun,
+ const uint8_t *aText,
+ uint32_t aOffset,
+ uint32_t aLength,
+ uint8_t aMatchType,
+ uint16_t aOrientation,
+ Script aScript,
+ bool aSyntheticLower,
+ bool aSyntheticUpper)
+{
+ NS_ConvertASCIItoUTF16 unicodeString(reinterpret_cast<const char*>(aText),
+ aLength);
+ return InitFakeSmallCapsRun(aDrawTarget, aTextRun, static_cast<const char16_t*>(unicodeString.get()),
+ aOffset, aLength, aMatchType, aOrientation,
+ aScript, aSyntheticLower, aSyntheticUpper);
+}
+
+already_AddRefed<gfxFont>
+gfxFont::GetSmallCapsFont()
+{
+ gfxFontStyle style(*GetStyle());
+ style.size *= SMALL_CAPS_SCALE_FACTOR;
+ style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
+ gfxFontEntry* fe = GetFontEntry();
+ bool needsBold = style.weight >= 600 && !fe->IsBold();
+ return fe->FindOrMakeFont(&style, needsBold, mUnicodeRangeMap);
+}
+
+already_AddRefed<gfxFont>
+gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel)
+{
+ gfxFontStyle style(*GetStyle());
+ style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
+ gfxFontEntry* fe = GetFontEntry();
+ bool needsBold = style.weight >= 600 && !fe->IsBold();
+ return fe->FindOrMakeFont(&style, needsBold, mUnicodeRangeMap);
+}
+
+static void
+DestroyRefCairo(void* aData)
+{
+ cairo_t* refCairo = static_cast<cairo_t*>(aData);
+ MOZ_ASSERT(refCairo);
+ cairo_destroy(refCairo);
+}
+
+/* static */ cairo_t *
+gfxFont::RefCairo(DrawTarget* aDT)
+{
+ // DrawTargets that don't use a Cairo backend can be given a 1x1 "reference"
+ // |cairo_t*|, stored in the DrawTarget's user data, for doing font-related
+ // operations.
+ static UserDataKey sRefCairo;
+
+ cairo_t* refCairo = nullptr;
+ if (aDT->GetBackendType() == BackendType::CAIRO) {
+ refCairo = static_cast<cairo_t*>
+ (aDT->GetNativeSurface(NativeSurfaceType::CAIRO_CONTEXT));
+ if (refCairo) {
+ return refCairo;
+ }
+ }
+
+ refCairo = static_cast<cairo_t*>(aDT->GetUserData(&sRefCairo));
+ if (!refCairo) {
+ refCairo = cairo_create(gfxPlatform::GetPlatform()->ScreenReferenceSurface()->CairoSurface());
+ aDT->AddUserData(&sRefCairo, refCairo, DestroyRefCairo);
+ }
+
+ return refCairo;
+}
+
+gfxGlyphExtents *
+gfxFont::GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit) {
+ uint32_t i, count = mGlyphExtentsArray.Length();
+ for (i = 0; i < count; ++i) {
+ if (mGlyphExtentsArray[i]->GetAppUnitsPerDevUnit() == aAppUnitsPerDevUnit)
+ return mGlyphExtentsArray[i].get();
+ }
+ gfxGlyphExtents *glyphExtents = new gfxGlyphExtents(aAppUnitsPerDevUnit);
+ if (glyphExtents) {
+ mGlyphExtentsArray.AppendElement(glyphExtents);
+ // Initialize the extents of a space glyph, assuming that spaces don't
+ // render anything!
+ glyphExtents->SetContainedGlyphWidthAppUnits(GetSpaceGlyph(), 0);
+ }
+ return glyphExtents;
+}
+
+void
+gfxFont::SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID,
+ bool aNeedTight, gfxGlyphExtents *aExtents)
+{
+ gfxRect svgBounds;
+ if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) &&
+ mFontEntry->GetSVGGlyphExtents(aDrawTarget, aGlyphID, &svgBounds)) {
+ gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
+ aExtents->SetTightGlyphExtents(aGlyphID,
+ gfxRect(svgBounds.x * d2a,
+ svgBounds.y * d2a,
+ svgBounds.width * d2a,
+ svgBounds.height * d2a));
+ return;
+ }
+
+ cairo_glyph_t glyph;
+ glyph.index = aGlyphID;
+ glyph.x = 0;
+ glyph.y = 0;
+ cairo_text_extents_t extents;
+ cairo_glyph_extents(gfxFont::RefCairo(aDrawTarget), &glyph, 1, &extents);
+
+ const Metrics& fontMetrics = GetMetrics(eHorizontal);
+ int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
+ if (!aNeedTight && extents.x_bearing >= 0 &&
+ extents.y_bearing >= -fontMetrics.maxAscent &&
+ extents.height + extents.y_bearing <= fontMetrics.maxDescent) {
+ uint32_t appUnitsWidth =
+ uint32_t(ceil((extents.x_bearing + extents.width)*appUnitsPerDevUnit));
+ if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
+ aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, uint16_t(appUnitsWidth));
+ return;
+ }
+ }
+#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
+ if (!aNeedTight) {
+ ++gGlyphExtentsSetupFallBackToTight;
+ }
+#endif
+
+ gfxFloat d2a = appUnitsPerDevUnit;
+ gfxRect bounds(extents.x_bearing*d2a, extents.y_bearing*d2a,
+ extents.width*d2a, extents.height*d2a);
+ aExtents->SetTightGlyphExtents(aGlyphID, bounds);
+}
+
+// Try to initialize font metrics by reading sfnt tables directly;
+// set mIsValid=TRUE and return TRUE on success.
+// Return FALSE if the gfxFontEntry subclass does not
+// implement GetFontTable(), or for non-sfnt fonts where tables are
+// not available.
+// If this returns TRUE without setting the mIsValid flag, then we -did-
+// apparently find an sfnt, but it was too broken to be used.
+bool
+gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics)
+{
+ mIsValid = false; // font is NOT valid in case of early return
+
+ const uint32_t kHheaTableTag = TRUETYPE_TAG('h','h','e','a');
+ const uint32_t kPostTableTag = TRUETYPE_TAG('p','o','s','t');
+ const uint32_t kOS_2TableTag = TRUETYPE_TAG('O','S','/','2');
+
+ uint32_t len;
+
+ if (mFUnitsConvFactor < 0.0) {
+ // If the conversion factor from FUnits is not yet set,
+ // get the unitsPerEm from the 'head' table via the font entry
+ uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm();
+ if (unitsPerEm == gfxFontEntry::kInvalidUPEM) {
+ return false;
+ }
+ mFUnitsConvFactor = GetAdjustedSize() / unitsPerEm;
+ }
+
+ // 'hhea' table is required to get vertical extents
+ gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
+ if (!hheaTable) {
+ return false; // no 'hhea' table -> not an sfnt
+ }
+ const MetricsHeader* hhea =
+ reinterpret_cast<const MetricsHeader*>
+ (hb_blob_get_data(hheaTable, &len));
+ if (len < sizeof(MetricsHeader)) {
+ return false;
+ }
+
+#define SET_UNSIGNED(field,src) aMetrics.field = uint16_t(src) * mFUnitsConvFactor
+#define SET_SIGNED(field,src) aMetrics.field = int16_t(src) * mFUnitsConvFactor
+
+ SET_UNSIGNED(maxAdvance, hhea->advanceWidthMax);
+ SET_SIGNED(maxAscent, hhea->ascender);
+ SET_SIGNED(maxDescent, -int16_t(hhea->descender));
+ SET_SIGNED(externalLeading, hhea->lineGap);
+
+ // 'post' table is required for underline metrics
+ gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag);
+ if (!postTable) {
+ return true; // no 'post' table -> sfnt is not valid
+ }
+ const PostTable *post =
+ reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable, &len));
+ if (len < offsetof(PostTable, underlineThickness) + sizeof(uint16_t)) {
+ return true; // bad post table -> sfnt is not valid
+ }
+
+ SET_SIGNED(underlineOffset, post->underlinePosition);
+ SET_UNSIGNED(underlineSize, post->underlineThickness);
+
+ // 'OS/2' table is optional, if not found we'll estimate xHeight
+ // and aveCharWidth by measuring glyphs
+ gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
+ if (os2Table) {
+ const OS2Table *os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
+ // although sxHeight and sCapHeight are signed fields, we consider
+ // negative values to be erroneous and just ignore them
+ if (uint16_t(os2->version) >= 2) {
+ // version 2 and later includes the x-height and cap-height fields
+ if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) &&
+ int16_t(os2->sxHeight) > 0) {
+ SET_SIGNED(xHeight, os2->sxHeight);
+ }
+ if (len >= offsetof(OS2Table, sCapHeight) + sizeof(int16_t) &&
+ int16_t(os2->sCapHeight) > 0) {
+ SET_SIGNED(capHeight, os2->sCapHeight);
+ }
+ }
+ // this should always be present in any valid OS/2 of any version
+ if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) {
+ SET_SIGNED(aveCharWidth, os2->xAvgCharWidth);
+ SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
+ SET_SIGNED(strikeoutOffset, os2->yStrikeoutPosition);
+
+ // for fonts with USE_TYPO_METRICS set in the fsSelection field,
+ // let the OS/2 sTypo* metrics override those from the hhea table
+ // (see http://www.microsoft.com/typography/otspec/os2.htm#fss)
+ const uint16_t kUseTypoMetricsMask = 1 << 7;
+ if (uint16_t(os2->fsSelection) & kUseTypoMetricsMask) {
+ SET_SIGNED(maxAscent, os2->sTypoAscender);
+ SET_SIGNED(maxDescent, - int16_t(os2->sTypoDescender));
+ SET_SIGNED(externalLeading, os2->sTypoLineGap);
+ }
+ }
+ }
+
+#undef SET_SIGNED
+#undef SET_UNSIGNED
+
+ mIsValid = true;
+
+ return true;
+}
+
+static double
+RoundToNearestMultiple(double aValue, double aFraction)
+{
+ return floor(aValue/aFraction + 0.5) * aFraction;
+}
+
+void gfxFont::CalculateDerivedMetrics(Metrics& aMetrics)
+{
+ aMetrics.maxAscent =
+ ceil(RoundToNearestMultiple(aMetrics.maxAscent, 1/1024.0));
+ aMetrics.maxDescent =
+ ceil(RoundToNearestMultiple(aMetrics.maxDescent, 1/1024.0));
+
+ if (aMetrics.xHeight <= 0) {
+ // only happens if we couldn't find either font metrics
+ // or a char to measure;
+ // pick an arbitrary value that's better than zero
+ aMetrics.xHeight = aMetrics.maxAscent * DEFAULT_XHEIGHT_FACTOR;
+ }
+
+ // If we have a font that doesn't provide a capHeight value, use maxAscent
+ // as a reasonable fallback.
+ if (aMetrics.capHeight <= 0) {
+ aMetrics.capHeight = aMetrics.maxAscent;
+ }
+
+ aMetrics.maxHeight = aMetrics.maxAscent + aMetrics.maxDescent;
+
+ if (aMetrics.maxHeight - aMetrics.emHeight > 0.0) {
+ aMetrics.internalLeading = aMetrics.maxHeight - aMetrics.emHeight;
+ } else {
+ aMetrics.internalLeading = 0.0;
+ }
+
+ aMetrics.emAscent = aMetrics.maxAscent * aMetrics.emHeight
+ / aMetrics.maxHeight;
+ aMetrics.emDescent = aMetrics.emHeight - aMetrics.emAscent;
+
+ if (GetFontEntry()->IsFixedPitch()) {
+ // Some Quartz fonts are fixed pitch, but there's some glyph with a bigger
+ // advance than the average character width... this forces
+ // those fonts to be recognized like fixed pitch fonts by layout.
+ aMetrics.maxAdvance = aMetrics.aveCharWidth;
+ }
+
+ if (!aMetrics.strikeoutOffset) {
+ aMetrics.strikeoutOffset = aMetrics.xHeight * 0.5;
+ }
+ if (!aMetrics.strikeoutSize) {
+ aMetrics.strikeoutSize = aMetrics.underlineSize;
+ }
+}
+
+void
+gfxFont::SanitizeMetrics(gfxFont::Metrics *aMetrics, bool aIsBadUnderlineFont)
+{
+ // Even if this font size is zero, this font is created with non-zero size.
+ // However, for layout and others, we should return the metrics of zero size font.
+ if (mStyle.size == 0.0 || mStyle.sizeAdjust == 0.0) {
+ memset(aMetrics, 0, sizeof(gfxFont::Metrics));
+ return;
+ }
+
+ aMetrics->underlineSize = std::max(1.0, aMetrics->underlineSize);
+ aMetrics->strikeoutSize = std::max(1.0, aMetrics->strikeoutSize);
+
+ aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -1.0);
+
+ if (aMetrics->maxAscent < 1.0) {
+ // We cannot draw strikeout line and overline in the ascent...
+ aMetrics->underlineSize = 0;
+ aMetrics->underlineOffset = 0;
+ aMetrics->strikeoutSize = 0;
+ aMetrics->strikeoutOffset = 0;
+ return;
+ }
+
+ /**
+ * Some CJK fonts have bad underline offset. Therefore, if this is such font,
+ * we need to lower the underline offset to bottom of *em* descent.
+ * However, if this is system font, we should not do this for the rendering compatibility with
+ * another application's UI on the platform.
+ * XXX Should not use this hack if the font size is too small?
+ * Such text cannot be read, this might be used for tight CSS rendering? (E.g., Acid2)
+ */
+ if (!mStyle.systemFont && aIsBadUnderlineFont) {
+ // First, we need 2 pixels between baseline and underline at least. Because many CJK characters
+ // put their glyphs on the baseline, so, 1 pixel is too close for CJK characters.
+ aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -2.0);
+
+ // Next, we put the underline to bottom of below of the descent space.
+ if (aMetrics->internalLeading + aMetrics->externalLeading > aMetrics->underlineSize) {
+ aMetrics->underlineOffset = std::min(aMetrics->underlineOffset, -aMetrics->emDescent);
+ } else {
+ aMetrics->underlineOffset = std::min(aMetrics->underlineOffset,
+ aMetrics->underlineSize - aMetrics->emDescent);
+ }
+ }
+ // If underline positioned is too far from the text, descent position is preferred so that underline
+ // will stay within the boundary.
+ else if (aMetrics->underlineSize - aMetrics->underlineOffset > aMetrics->maxDescent) {
+ if (aMetrics->underlineSize > aMetrics->maxDescent)
+ aMetrics->underlineSize = std::max(aMetrics->maxDescent, 1.0);
+ // The max underlineOffset is 1px (the min underlineSize is 1px, and min maxDescent is 0px.)
+ aMetrics->underlineOffset = aMetrics->underlineSize - aMetrics->maxDescent;
+ }
+
+ // If strikeout line is overflowed from the ascent, the line should be resized and moved for
+ // that being in the ascent space.
+ // Note that the strikeoutOffset is *middle* of the strikeout line position.
+ gfxFloat halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
+ if (halfOfStrikeoutSize + aMetrics->strikeoutOffset > aMetrics->maxAscent) {
+ if (aMetrics->strikeoutSize > aMetrics->maxAscent) {
+ aMetrics->strikeoutSize = std::max(aMetrics->maxAscent, 1.0);
+ halfOfStrikeoutSize = floor(aMetrics->strikeoutSize / 2.0 + 0.5);
+ }
+ gfxFloat ascent = floor(aMetrics->maxAscent + 0.5);
+ aMetrics->strikeoutOffset = std::max(halfOfStrikeoutSize, ascent / 2.0);
+ }
+
+ // If overline is larger than the ascent, the line should be resized.
+ if (aMetrics->underlineSize > aMetrics->maxAscent) {
+ aMetrics->underlineSize = aMetrics->maxAscent;
+ }
+}
+
+// Create a Metrics record to be used for vertical layout. This should never
+// fail, as we've already decided this is a valid font. We do not have the
+// option of marking it invalid (as can happen if we're unable to read
+// horizontal metrics), because that could break a font that we're already
+// using for horizontal text.
+// So we will synthesize *something* usable here even if there aren't any of the
+// usual font tables (which can happen in the case of a legacy bitmap or Type1
+// font for which the platform-specific backend used platform APIs instead of
+// sfnt tables to create the horizontal metrics).
+const gfxFont::Metrics*
+gfxFont::CreateVerticalMetrics()
+{
+ const uint32_t kHheaTableTag = TRUETYPE_TAG('h','h','e','a');
+ const uint32_t kVheaTableTag = TRUETYPE_TAG('v','h','e','a');
+ const uint32_t kPostTableTag = TRUETYPE_TAG('p','o','s','t');
+ const uint32_t kOS_2TableTag = TRUETYPE_TAG('O','S','/','2');
+ uint32_t len;
+
+ Metrics* metrics = new Metrics;
+ ::memset(metrics, 0, sizeof(Metrics));
+
+ // Some basic defaults, in case the font lacks any real metrics tables.
+ // TODO: consider what rounding (if any) we should apply to these.
+ metrics->emHeight = GetAdjustedSize();
+ metrics->emAscent = metrics->emHeight / 2;
+ metrics->emDescent = metrics->emHeight - metrics->emAscent;
+
+ metrics->maxAscent = metrics->emAscent;
+ metrics->maxDescent = metrics->emDescent;
+
+ const float UNINITIALIZED_LEADING = -10000.0f;
+ metrics->externalLeading = UNINITIALIZED_LEADING;
+
+ if (mFUnitsConvFactor < 0.0) {
+ uint16_t upem = GetFontEntry()->UnitsPerEm();
+ if (upem != gfxFontEntry::kInvalidUPEM) {
+ mFUnitsConvFactor = GetAdjustedSize() / upem;
+ }
+ }
+
+#define SET_UNSIGNED(field,src) metrics->field = uint16_t(src) * mFUnitsConvFactor
+#define SET_SIGNED(field,src) metrics->field = int16_t(src) * mFUnitsConvFactor
+
+ gfxFontEntry::AutoTable os2Table(mFontEntry, kOS_2TableTag);
+ if (os2Table && mFUnitsConvFactor >= 0.0) {
+ const OS2Table *os2 =
+ reinterpret_cast<const OS2Table*>(hb_blob_get_data(os2Table, &len));
+ // These fields should always be present in any valid OS/2 table
+ if (len >= offsetof(OS2Table, sTypoLineGap) + sizeof(int16_t)) {
+ SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
+ // Use ascent+descent from the horizontal metrics as the default
+ // advance (aveCharWidth) in vertical mode
+ gfxFloat ascentDescent = gfxFloat(mFUnitsConvFactor) *
+ (int16_t(os2->sTypoAscender) - int16_t(os2->sTypoDescender));
+ metrics->aveCharWidth =
+ std::max(metrics->emHeight, ascentDescent);
+ // Use xAvgCharWidth from horizontal metrics as minimum font extent
+ // for vertical layout, applying half of it to ascent and half to
+ // descent (to work with a default centered baseline).
+ gfxFloat halfCharWidth =
+ int16_t(os2->xAvgCharWidth) * gfxFloat(mFUnitsConvFactor) / 2;
+ metrics->maxAscent = std::max(metrics->maxAscent, halfCharWidth);
+ metrics->maxDescent = std::max(metrics->maxDescent, halfCharWidth);
+ }
+ }
+
+ // If we didn't set aveCharWidth from OS/2, try to read 'hhea' metrics
+ // and use the line height from its ascent/descent.
+ if (!metrics->aveCharWidth) {
+ gfxFontEntry::AutoTable hheaTable(mFontEntry, kHheaTableTag);
+ if (hheaTable && mFUnitsConvFactor >= 0.0) {
+ const MetricsHeader* hhea =
+ reinterpret_cast<const MetricsHeader*>
+ (hb_blob_get_data(hheaTable, &len));
+ if (len >= sizeof(MetricsHeader)) {
+ SET_SIGNED(aveCharWidth, int16_t(hhea->ascender) -
+ int16_t(hhea->descender));
+ metrics->maxAscent = metrics->aveCharWidth / 2;
+ metrics->maxDescent =
+ metrics->aveCharWidth - metrics->maxAscent;
+ }
+ }
+ }
+
+ // Read real vertical metrics if available.
+ gfxFontEntry::AutoTable vheaTable(mFontEntry, kVheaTableTag);
+ if (vheaTable && mFUnitsConvFactor >= 0.0) {
+ const MetricsHeader* vhea =
+ reinterpret_cast<const MetricsHeader*>
+ (hb_blob_get_data(vheaTable, &len));
+ if (len >= sizeof(MetricsHeader)) {
+ SET_UNSIGNED(maxAdvance, vhea->advanceWidthMax);
+ // Redistribute space between ascent/descent because we want a
+ // centered vertical baseline by default.
+ gfxFloat halfExtent = 0.5 * gfxFloat(mFUnitsConvFactor) *
+ (int16_t(vhea->ascender) + std::abs(int16_t(vhea->descender)));
+ // Some bogus fonts have ascent and descent set to zero in 'vhea'.
+ // In that case we just ignore them and keep our synthetic values
+ // from above.
+ if (halfExtent > 0) {
+ metrics->maxAscent = halfExtent;
+ metrics->maxDescent = halfExtent;
+ SET_SIGNED(externalLeading, vhea->lineGap);
+ }
+ }
+ }
+
+ // If we didn't set aveCharWidth above, we must be dealing with a non-sfnt
+ // font of some kind (Type1, bitmap, vector, ...), so fall back to using
+ // whatever the platform backend figured out for horizontal layout.
+ // And if we haven't set externalLeading yet, then copy that from the
+ // horizontal metrics as well, to help consistency of CSS line-height.
+ if (!metrics->aveCharWidth ||
+ metrics->externalLeading == UNINITIALIZED_LEADING) {
+ const Metrics& horizMetrics = GetHorizontalMetrics();
+ if (!metrics->aveCharWidth) {
+ metrics->aveCharWidth = horizMetrics.maxAscent + horizMetrics.maxDescent;
+ }
+ if (metrics->externalLeading == UNINITIALIZED_LEADING) {
+ metrics->externalLeading = horizMetrics.externalLeading;
+ }
+ }
+
+ // Get underline thickness from the 'post' table if available.
+ gfxFontEntry::AutoTable postTable(mFontEntry, kPostTableTag);
+ if (postTable) {
+ const PostTable *post =
+ reinterpret_cast<const PostTable*>(hb_blob_get_data(postTable,
+ &len));
+ if (len >= offsetof(PostTable, underlineThickness) +
+ sizeof(uint16_t)) {
+ SET_UNSIGNED(underlineSize, post->underlineThickness);
+ // Also use for strikeout if we didn't find that in OS/2 above.
+ if (!metrics->strikeoutSize) {
+ metrics->strikeoutSize = metrics->underlineSize;
+ }
+ }
+ }
+
+#undef SET_UNSIGNED
+#undef SET_SIGNED
+
+ // If we didn't read this from a vhea table, it will still be zero.
+ // In any case, let's make sure it is not less than the value we've
+ // come up with for aveCharWidth.
+ metrics->maxAdvance = std::max(metrics->maxAdvance, metrics->aveCharWidth);
+
+ // Thickness of underline and strikeout may have been read from tables,
+ // but in case they were not present, ensure a minimum of 1 pixel.
+ // We synthesize our own positions, as font metrics don't provide these
+ // for vertical layout.
+ metrics->underlineSize = std::max(1.0, metrics->underlineSize);
+ metrics->underlineOffset = - metrics->maxDescent - metrics->underlineSize;
+
+ metrics->strikeoutSize = std::max(1.0, metrics->strikeoutSize);
+ metrics->strikeoutOffset = - 0.5 * metrics->strikeoutSize;
+
+ // Somewhat arbitrary values for now, subject to future refinement...
+ metrics->spaceWidth = metrics->aveCharWidth;
+ metrics->zeroOrAveCharWidth = metrics->aveCharWidth;
+ metrics->maxHeight = metrics->maxAscent + metrics->maxDescent;
+ metrics->xHeight = metrics->emHeight / 2;
+ metrics->capHeight = metrics->maxAscent;
+
+ return metrics;
+}
+
+gfxFloat
+gfxFont::SynthesizeSpaceWidth(uint32_t aCh)
+{
+ // return an appropriate width for various Unicode space characters
+ // that we "fake" if they're not actually present in the font;
+ // returns negative value if the char is not a known space.
+ switch (aCh) {
+ case 0x2000: // en quad
+ case 0x2002: return GetAdjustedSize() / 2; // en space
+ case 0x2001: // em quad
+ case 0x2003: return GetAdjustedSize(); // em space
+ case 0x2004: return GetAdjustedSize() / 3; // three-per-em space
+ case 0x2005: return GetAdjustedSize() / 4; // four-per-em space
+ case 0x2006: return GetAdjustedSize() / 6; // six-per-em space
+ case 0x2007: return GetMetrics(eHorizontal).zeroOrAveCharWidth; // figure space
+ case 0x2008: return GetMetrics(eHorizontal).spaceWidth; // punctuation space
+ case 0x2009: return GetAdjustedSize() / 5; // thin space
+ case 0x200a: return GetAdjustedSize() / 10; // hair space
+ case 0x202f: return GetAdjustedSize() / 5; // narrow no-break space
+ default: return -1.0;
+ }
+}
+
+void
+gfxFont::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ for (uint32_t i = 0; i < mGlyphExtentsArray.Length(); ++i) {
+ aSizes->mFontInstances +=
+ mGlyphExtentsArray[i]->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ if (mWordCache) {
+ aSizes->mShapedWords += mWordCache->SizeOfIncludingThis(aMallocSizeOf);
+ }
+}
+
+void
+gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+ FontCacheSizes* aSizes) const
+{
+ aSizes->mFontInstances += aMallocSizeOf(this);
+ AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
+}
+
+void
+gfxFont::AddGlyphChangeObserver(GlyphChangeObserver *aObserver)
+{
+ if (!mGlyphChangeObservers) {
+ mGlyphChangeObservers.reset(
+ new nsTHashtable<nsPtrHashKey<GlyphChangeObserver>>);
+ }
+ mGlyphChangeObservers->PutEntry(aObserver);
+}
+
+void
+gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver)
+{
+ NS_ASSERTION(mGlyphChangeObservers, "No observers registered");
+ NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), "Observer not registered");
+ mGlyphChangeObservers->RemoveEntry(aObserver);
+}
+
+
+#define DEFAULT_PIXEL_FONT_SIZE 16.0f
+
+/*static*/ uint32_t
+gfxFontStyle::ParseFontLanguageOverride(const nsString& aLangTag)
+{
+ if (!aLangTag.Length() || aLangTag.Length() > 4) {
+ return NO_FONT_LANGUAGE_OVERRIDE;
+ }
+ uint32_t index, result = 0;
+ for (index = 0; index < aLangTag.Length(); ++index) {
+ char16_t ch = aLangTag[index];
+ if (!nsCRT::IsAscii(ch)) { // valid tags are pure ASCII
+ return NO_FONT_LANGUAGE_OVERRIDE;
+ }
+ result = (result << 8) + ch;
+ }
+ while (index++ < 4) {
+ result = (result << 8) + 0x20;
+ }
+ return result;
+}
+
+gfxFontStyle::gfxFontStyle() :
+ language(nsGkAtoms::x_western),
+ size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(-1.0f), baselineOffset(0.0f),
+ languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
+ weight(NS_FONT_WEIGHT_NORMAL), stretch(NS_FONT_STRETCH_NORMAL),
+ systemFont(true), printerFont(false), useGrayscaleAntialiasing(false),
+ style(NS_FONT_STYLE_NORMAL),
+ allowSyntheticWeight(true), allowSyntheticStyle(true),
+ noFallbackVariantFeatures(true),
+ explicitLanguage(false),
+ variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+ variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL)
+{
+}
+
+gfxFontStyle::gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
+ gfxFloat aSize,
+ nsIAtom *aLanguage, bool aExplicitLanguage,
+ float aSizeAdjust, bool aSystemFont,
+ bool aPrinterFont,
+ bool aAllowWeightSynthesis,
+ bool aAllowStyleSynthesis,
+ const nsString& aLanguageOverride):
+ language(aLanguage),
+ size(aSize), sizeAdjust(aSizeAdjust), baselineOffset(0.0f),
+ languageOverride(ParseFontLanguageOverride(aLanguageOverride)),
+ weight(aWeight), stretch(aStretch),
+ systemFont(aSystemFont), printerFont(aPrinterFont),
+ useGrayscaleAntialiasing(false),
+ style(aStyle),
+ allowSyntheticWeight(aAllowWeightSynthesis),
+ allowSyntheticStyle(aAllowStyleSynthesis),
+ noFallbackVariantFeatures(true),
+ explicitLanguage(aExplicitLanguage),
+ variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
+ variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL)
+{
+ MOZ_ASSERT(!mozilla::IsNaN(size));
+ MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust));
+
+ if (weight > 900)
+ weight = 900;
+ if (weight < 100)
+ weight = 100;
+
+ if (size >= FONT_MAX_SIZE) {
+ size = FONT_MAX_SIZE;
+ sizeAdjust = -1.0f;
+ } else if (size < 0.0) {
+ NS_WARNING("negative font size");
+ size = 0.0;
+ }
+
+ if (!language) {
+ NS_WARNING("null language");
+ language = nsGkAtoms::x_western;
+ }
+}
+
+gfxFontStyle::gfxFontStyle(const gfxFontStyle& aStyle) :
+ language(aStyle.language),
+ featureValueLookup(aStyle.featureValueLookup),
+ size(aStyle.size), sizeAdjust(aStyle.sizeAdjust),
+ baselineOffset(aStyle.baselineOffset),
+ languageOverride(aStyle.languageOverride),
+ weight(aStyle.weight), stretch(aStyle.stretch),
+ systemFont(aStyle.systemFont), printerFont(aStyle.printerFont),
+ useGrayscaleAntialiasing(aStyle.useGrayscaleAntialiasing),
+ style(aStyle.style),
+ allowSyntheticWeight(aStyle.allowSyntheticWeight),
+ allowSyntheticStyle(aStyle.allowSyntheticStyle),
+ noFallbackVariantFeatures(aStyle.noFallbackVariantFeatures),
+ explicitLanguage(aStyle.explicitLanguage),
+ variantCaps(aStyle.variantCaps),
+ variantSubSuper(aStyle.variantSubSuper)
+{
+ featureSettings.AppendElements(aStyle.featureSettings);
+ alternateValues.AppendElements(aStyle.alternateValues);
+}
+
+int8_t
+gfxFontStyle::ComputeWeight() const
+{
+ int8_t baseWeight = (weight + 50) / 100;
+
+ if (baseWeight < 0)
+ baseWeight = 0;
+ if (baseWeight > 9)
+ baseWeight = 9;
+
+ return baseWeight;
+}
+
+void
+gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel)
+{
+ NS_PRECONDITION(variantSubSuper != NS_FONT_VARIANT_POSITION_NORMAL &&
+ baselineOffset == 0,
+ "can't adjust this style for sub/superscript");
+
+ // calculate the baseline offset (before changing the size)
+ if (variantSubSuper == NS_FONT_VARIANT_POSITION_SUPER) {
+ baselineOffset = size * -NS_FONT_SUPERSCRIPT_OFFSET_RATIO;
+ } else {
+ baselineOffset = size * NS_FONT_SUBSCRIPT_OFFSET_RATIO;
+ }
+
+ // calculate reduced size, roughly mimicing behavior of font-size: smaller
+ float cssSize = size * aAppUnitsPerDevPixel / AppUnitsPerCSSPixel();
+ if (cssSize < NS_FONT_SUB_SUPER_SMALL_SIZE) {
+ size *= NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL;
+ } else if (cssSize >= NS_FONT_SUB_SUPER_LARGE_SIZE) {
+ size *= NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
+ } else {
+ gfxFloat t = (cssSize - NS_FONT_SUB_SUPER_SMALL_SIZE) /
+ (NS_FONT_SUB_SUPER_LARGE_SIZE -
+ NS_FONT_SUB_SUPER_SMALL_SIZE);
+ size *= (1.0 - t) * NS_FONT_SUB_SUPER_SIZE_RATIO_SMALL +
+ t * NS_FONT_SUB_SUPER_SIZE_RATIO_LARGE;
+ }
+
+ // clear the variant field
+ variantSubSuper = NS_FONT_VARIANT_POSITION_NORMAL;
+}
+
+bool
+gfxFont::TryGetMathTable()
+{
+ if (!mMathInitialized) {
+ mMathInitialized = true;
+
+ hb_face_t *face = GetFontEntry()->GetHBFace();
+ if (face) {
+ if (hb_ot_math_has_data(face)) {
+ mMathTable = MakeUnique<gfxMathTable>(face, GetAdjustedSize());
+ }
+ hb_face_destroy(face);
+ }
+ }
+
+ return !!mMathTable;
+}