gecko-dev/gfx/thebes/gfxFont.cpp

4315 lines
162 KiB
C++
Raw Normal View History

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2012-05-21 11:12:37 +00:00
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "gfxFont.h"
#include "mozilla/BinarySearch.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/FontPropertyTypes.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/SVGContextPaint.h"
#include "mozilla/Logging.h"
#include "nsITimer.h"
#include "gfxGlyphExtents.h"
#include "gfxPlatform.h"
#include "gfxTextRun.h"
2012-03-31 06:08:46 +00:00
#include "nsGkAtoms.h"
#include "gfxTypes.h"
#include "gfxContext.h"
#include "gfxFontMissingGlyphs.h"
#include "gfxGraphiteShaper.h"
#include "gfxHarfBuzzShaper.h"
#include "gfxUserFontSet.h"
#include "nsSpecialCasingData.h"
#include "nsTextRunTransformations.h"
#include "nsUGenCategory.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 "TextDrawTarget.h"
#include "GreekCasing.h"
#include "cairo.h"
#ifdef XP_WIN
#include "cairo-win32.h"
#include "gfxWindowsPlatform.h"
#endif
#include "harfbuzz/hb.h"
#include "harfbuzz/hb-ot.h"
#include <algorithm>
#include <limits>
#include <cmath>
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::unicode;
using mozilla::services::GetObserverService;
gfxFontCache *gfxFontCache::gGlobalCache = nullptr;
#ifdef DEBUG_roc
#define DEBUG_TEXT_RUN_STORAGE_METRICS
#endif
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
uint32_t gTextRunStorageHighWaterMark = 0;
uint32_t gTextRunStorage = 0;
uint32_t gFontCount = 0;
uint32_t gGlyphExtentsCount = 0;
uint32_t gGlyphExtentsWidthsTotalSize = 0;
uint32_t gGlyphExtentsSetupEagerSimple = 0;
uint32_t gGlyphExtentsSetupEagerTight = 0;
uint32_t gGlyphExtentsSetupLazyTight = 0;
uint32_t gGlyphExtentsSetupFallBackToTight = 0;
#endif
#define LOG_FONTINIT(args) MOZ_LOG(gfxPlatform::GetLog(eGfxLog_fontinit), \
LogLevel::Debug, args)
#define LOG_FONTINIT_ENABLED() MOZ_LOG_TEST( \
gfxPlatform::GetLog(eGfxLog_fontinit), \
LogLevel::Debug)
/*
* gfxFontCache - global cache of gfxFont instances.
* Expires unused fonts after a short interval;
* notifies fonts to age their cached shaped-word records;
* observes memory-pressure notification and tells fonts to clear their
* shaped-word caches to free up memory.
*/
MOZ_DEFINE_MALLOC_SIZE_OF(FontCacheMallocSizeOf)
NS_IMPL_ISUPPORTS(gfxFontCache::MemoryReporter, nsIMemoryReporter)
/*virtual*/
gfxTextRunFactory::~gfxTextRunFactory()
{
// Should not be dropped by stylo
MOZ_ASSERT(NS_IsMainThread());
}
NS_IMETHODIMP
gfxFontCache::MemoryReporter::CollectReports(
nsIHandleReportCallback* aHandleReport, nsISupports* aData, bool aAnonymize)
{
FontCacheSizes sizes;
gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf,
&sizes);
MOZ_COLLECT_REPORT(
"explicit/gfx/font-cache", KIND_HEAP, UNITS_BYTES,
sizes.mFontInstances,
"Memory used for active font instances.");
MOZ_COLLECT_REPORT(
"explicit/gfx/font-shaped-words", KIND_HEAP, UNITS_BYTES,
sizes.mShapedWords,
"Memory used to cache shaped glyph data.");
return NS_OK;
}
NS_IMPL_ISUPPORTS(gfxFontCache::Observer, nsIObserver)
NS_IMETHODIMP
gfxFontCache::Observer::Observe(nsISupports *aSubject,
const char *aTopic,
const char16_t *someData)
{
if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
gfxFontCache *fontCache = gfxFontCache::GetCache();
if (fontCache) {
fontCache->FlushShapedWordCaches();
}
} else {
MOZ_ASSERT_UNREACHABLE("unexpected notification topic");
}
return NS_OK;
}
nsresult
gfxFontCache::Init()
{
NS_ASSERTION(!gGlobalCache, "Where did this come from?");
gGlobalCache = new gfxFontCache(SystemGroup::EventTargetFor(TaskCategory::Other));
if (!gGlobalCache) {
return NS_ERROR_OUT_OF_MEMORY;
}
RegisterStrongMemoryReporter(new MemoryReporter());
return NS_OK;
}
void
gfxFontCache::Shutdown()
{
delete gGlobalCache;
gGlobalCache = nullptr;
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark);
printf("Total number of fonts=%d\n", gFontCount);
printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount,
int(gGlyphExtentsCount*sizeof(gfxGlyphExtents)));
printf("Total glyph extents width-storage size allocated=%d\n", gGlyphExtentsWidthsTotalSize);
printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple);
printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight);
printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight);
printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight);
#endif
}
gfxFontCache::gfxFontCache(nsIEventTarget* aEventTarget)
: gfxFontCacheExpirationTracker(aEventTarget)
{
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.
nsIEventTarget* target = nullptr;
if (XRE_IsContentProcess() && NS_IsMainThread()) {
target = aEventTarget;
}
NS_NewTimerWithFuncCallback(getter_AddRefs(mWordCacheExpirationTimer),
WordCacheExpirationTimerCallback,
this,
SHAPED_WORD_TIMEOUT_SECONDS * 1000,
nsITimer::TYPE_REPEATING_SLACK,
"gfxFontCache::gfxFontCache",
target);
#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)));
}
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;
return entry->mFont;
}
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::NotifyGlyphsChanged()
{
for (auto it = mFonts.Iter(); !it.Done(); it.Next()) {
it.Get()->mFont->NotifyGlyphsChanged();
}
}
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);
}
}
}
void
gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
const char16_t *aString,
uint32_t aLength)
{
CompressedGlyph* glyphs = GetCharacterGlyphs() + aOffset;
CompressedGlyph extendCluster =
CompressedGlyph::MakeComplex(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
IsIgnorable(uint32_t aChar)
{
return (IsDefaultIgnorable(aChar)) || 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 (IsIgnorable(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);
}
GetCharacterGlyphs()[aIndex].SetMissing(1);
}
bool
gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh)
{
if (IsIgnorable(aCh)) {
// There are a few default-ignorables of Letter category (currently,
// just the Hangul filler characters) that we'd better not discard
// if they're followed by additional characters in the same cluster.
// Some fonts use them to carry the width of a whole cluster of
// combining jamos; see bug 1238243.
if (GetGenCategory(aCh) == nsUGenCategory::kLetter &&
aIndex + 1 < GetLength() &&
!GetCharacterGlyphs()[aIndex + 1].IsClusterStart()) {
return false;
}
DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
details->mGlyphID = aCh;
details->mAdvance = 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, gfx::Point() };
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;
}
}
}
}
}
float
gfxFont::AngleForSyntheticOblique() const
{
// If the style doesn't call for italic/oblique, or if the face already
// provides it, no synthetic style should be added.
if (mStyle.style == FontSlantStyle::Normal() ||
!mStyle.allowSyntheticStyle ||
!mFontEntry->IsUpright()) {
return 0.0f;
}
// If style calls for italic, and face doesn't support it, use default
// oblique angle as a simulation.
if (mStyle.style.IsItalic()) {
return mFontEntry->SupportsItalic() ? 0.0f : FontSlantStyle::kDefaultAngle;
}
// Default or custom oblique angle
return mStyle.style.ObliqueAngle();
}
float
gfxFont::SkewForSyntheticOblique() const
{
// Precomputed value of tan(kDefaultAngle), the default italic/oblique slant;
// avoids calling tan() at runtime except for custom oblique values.
static const float kTanDefaultAngle =
tan(FontSlantStyle::kDefaultAngle * (M_PI / 180.0));
float angle = AngleForSyntheticOblique();
if (angle == 0.0f) {
return 0.0f;
} else if (angle == FontSlantStyle::kDefaultAngle) {
return kTanDefaultAngle;
} else {
return tan(angle * (M_PI / 180.0));
}
}
void
gfxFont::RunMetrics::CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft)
{
mAscent = std::max(mAscent, aOther.mAscent);
mDescent = std::max(mDescent, aOther.mDescent);
if (aOtherIsOnLeft) {
mBoundingBox =
(mBoundingBox + gfxPoint(aOther.mAdvanceWidth, 0)).Union(aOther.mBoundingBox);
} else {
mBoundingBox =
mBoundingBox.Union(aOther.mBoundingBox + gfxPoint(mAdvanceWidth, 0));
}
mAdvanceWidth += aOther.mAdvanceWidth;
}
gfxFont::gfxFont(const RefPtr<UnscaledFont>& aUnscaledFont,
gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
AntialiasOption anAAOption, cairo_scaled_font_t *aScaledFont) :
mScaledFont(aScaledFont),
mFontEntry(aFontEntry),
mUnscaledFont(aUnscaledFont),
mStyle(*aFontStyle),
mAdjustedSize(0.0),
mFUnitsConvFactor(-1.0f), // negative to indicate "not yet initialized"
mAntialiasOption(anAAOption),
mIsValid(true),
mApplySyntheticBold(false),
mKerningEnabled(false),
mMathInitialized(false)
{
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
++gFontCount;
#endif
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();
}
}
}
// Work out whether cairo will snap inter-glyph spacing to pixels.
//
// Layout does not align text to pixel boundaries, so, with font drawing
// backends that snap glyph positions to pixels, it is important that
// inter-glyph spacing within words is always an integer number of pixels.
// This ensures that the drawing backend snaps all of the word's glyphs in the
// same direction and so inter-glyph spacing remains the same.
//
gfxFont::RoundingFlags
gfxFont::GetRoundOffsetsToPixels(DrawTarget* aDrawTarget)
{
RoundingFlags result = RoundingFlags(0);
// 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()) {
return result;
}
// All raster backends snap glyphs to pixels vertically.
// Print backends set CAIRO_HINT_METRICS_OFF.
result |= RoundingFlags::kRoundY;
// If we can't set up the cairo font, bail out.
if (!SetupCairoFont(aDrawTarget)) {
return result;
}
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) {
result |= RoundingFlags::kRoundX; // default to the same as the fallback path below
return result;
}
// Sometimes hint metrics gets set for us, most notably for printing.
#ifdef MOZ_TREE_CAIRO
cairo_hint_metrics_t hint_metrics =
cairo_scaled_font_get_hint_metrics(scaled_font);
#else
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);
#endif
switch (hint_metrics) {
case CAIRO_HINT_METRICS_OFF:
result &= ~RoundingFlags::kRoundY;
return result;
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 result;
}
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 result;
}
break;
default:
break;
}
break;
case CAIRO_HINT_METRICS_ON:
break;
}
result |= RoundingFlags::kRoundX;
return result;
}
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);
// Ensure that we don't try to look at script codes beyond what the
// current version of ICU (at runtime -- in case of system ICU)
// knows about.
Script scriptCount =
Script(std::min<int>(u_getIntPropertyMaxValue(UCHAR_SCRIPT) + 1,
int(Script::NUM_SCRIPT_CODES)));
for (Script s = Script::ARABIC; s < scriptCount;
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::FeatureWillHandleChar(Script aRunScript, uint32_t aFeature,
uint32_t aUnicode)
{
if (!SupportsFeature(aRunScript, aFeature)) {
return false;
}
// xxx - for graphite, don't really know how to sniff lookups so bail
if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
return true;
}
if (!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, aFeature);
if (aUnicode == 0xa0) {
aUnicode = ' ';
}
hb_codepoint_t gid = shaper->GetNominalGlyph(aUnicode);
return hb_set_has(inputGlyphs, gid);
}
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;
}
void
gfxFont::InitializeScaledFont()
{
if (!mAzureScaledFont) {
return;
}
float angle = AngleForSyntheticOblique();
if (angle != 0.0f) {
mAzureScaledFont->SetSyntheticObliqueAngle(angle);
}
}
/**
* A helper function in case we need to do any rounding or other
* processing here.
*/
#define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \
(double(aAppUnits)*double(aDevUnitsPerAppUnit))
static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) {
switch (aAAOption) {
case gfxFont::kAntialiasSubpixel:
return AntialiasMode::SUBPIXEL;
case gfxFont::kAntialiasGrayscale:
return AntialiasMode::GRAY;
case gfxFont::kAntialiasNone:
return AntialiasMode::NONE;
default:
return AntialiasMode::DEFAULT;
}
}
class GlyphBufferAzure
{
#define AUTO_BUFFER_SIZE (2048/sizeof(Glyph))
typedef mozilla::image::imgDrawingParams imgDrawingParams;
public:
GlyphBufferAzure(const TextRunDrawParams& aRunParams,
const FontDrawParams& aFontParams)
: mRunParams(aRunParams)
, mFontParams(aFontParams)
, mBuffer(*mAutoBuffer.addr())
, mBufSize(AUTO_BUFFER_SIZE)
, mCapacity(0)
, mNumGlyphs(0)
{
}
~GlyphBufferAzure()
{
if (mNumGlyphs > 0) {
FlushGlyphs();
}
if (mBuffer != *mAutoBuffer.addr()) {
free(mBuffer);
}
}
// Ensure the buffer has enough space for aGlyphCount glyphs to be added.
// This MUST be called before OutputGlyph is used to actually store glyph
// records in the buffer. It may be called repeated to add further capacity
// in case we don't know up-front exactly what will be needed.
void AddCapacity(uint32_t aGlyphCount)
{
// See if the required capacity fits within the already-allocated space
if (mCapacity + aGlyphCount <= mBufSize) {
mCapacity += aGlyphCount;
return;
}
// We need to grow the buffer: determine a new size, allocate, and
// copy the existing data over if we didn't use realloc (which would
// do it automatically).
mBufSize = std::max(mCapacity + aGlyphCount, mBufSize * 2);
if (mBuffer == *mAutoBuffer.addr()) {
// switching from autobuffer to malloc, so we need to copy
mBuffer =
reinterpret_cast<Glyph*>(moz_xmalloc(mBufSize * sizeof(Glyph)));
std::memcpy(mBuffer, *mAutoBuffer.addr(),
mNumGlyphs * sizeof(Glyph));
} else {
mBuffer =
reinterpret_cast<Glyph*>(moz_xrealloc(mBuffer,
mBufSize * sizeof(Glyph)));
}
mCapacity += aGlyphCount;
}
void OutputGlyph(uint32_t aGlyphID, const gfx::Point& aPt)
{
// Check that AddCapacity has been used appropriately!
MOZ_ASSERT(mNumGlyphs < mCapacity);
Glyph* glyph = mBuffer + mNumGlyphs++;
glyph->mIndex = aGlyphID;
glyph->mPosition = aPt;
}
void Flush()
{
if (mNumGlyphs > 0) {
FlushGlyphs();
mNumGlyphs = 0;
}
}
const TextRunDrawParams& mRunParams;
const FontDrawParams& mFontParams;
private:
static DrawMode
GetStrokeMode(DrawMode aMode)
{
return aMode & (DrawMode::GLYPH_STROKE |
DrawMode::GLYPH_STROKE_UNDERNEATH);
}
// Render the buffered glyphs to the draw target.
void FlushGlyphs()
{
if (mRunParams.isRTL) {
std::reverse(mBuffer, mBuffer + mNumGlyphs);
}
gfx::GlyphBuffer buf;
buf.mGlyphs = mBuffer;
buf.mNumGlyphs = mNumGlyphs;
const gfxContext::AzureState &state = mRunParams.context->CurrentState();
// Draw stroke first if the UNDERNEATH flag is set in drawMode.
if (mRunParams.strokeOpts &&
GetStrokeMode(mRunParams.drawMode) ==
(DrawMode::GLYPH_STROKE | DrawMode::GLYPH_STROKE_UNDERNEATH)) {
DrawStroke(state, buf);
}
if (mRunParams.drawMode & DrawMode::GLYPH_FILL) {
if (state.pattern || mFontParams.contextPaint) {
Pattern *pat;
Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat The bulk of this commit was generated with a script, executed at the top level of a typical source code checkout. The only non-machine-generated part was modifying MFBT's moz.build to reflect the new naming. CLOSED TREE makes big refactorings like this a piece of cake. # The main substitution. find . -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.mm' -o -name '*.idl'| \ xargs perl -p -i -e ' s/nsRefPtr\.h/RefPtr\.h/g; # handle includes s/nsRefPtr ?</RefPtr</g; # handle declarations and variables ' # Handle a special friend declaration in gfx/layers/AtomicRefCountedWithFinalize.h. perl -p -i -e 's/::nsRefPtr;/::RefPtr;/' gfx/layers/AtomicRefCountedWithFinalize.h # Handle nsRefPtr.h itself, a couple places that define constructors # from nsRefPtr, and code generators specially. We do this here, rather # than indiscriminantly s/nsRefPtr/RefPtr/, because that would rename # things like nsRefPtrHashtable. perl -p -i -e 's/nsRefPtr/RefPtr/g' \ mfbt/nsRefPtr.h \ xpcom/glue/nsCOMPtr.h \ xpcom/base/OwningNonNull.h \ ipc/ipdl/ipdl/lower.py \ ipc/ipdl/ipdl/builtin.py \ dom/bindings/Codegen.py \ python/lldbutils/lldbutils/utils.py # In our indiscriminate substitution above, we renamed # nsRefPtrGetterAddRefs, the class behind getter_AddRefs. Fix that up. find . -name '*.cpp' -o -name '*.h' -o -name '*.idl' | \ xargs perl -p -i -e 's/nsRefPtrGetterAddRefs/RefPtrGetterAddRefs/g' if [ -d .git ]; then git mv mfbt/nsRefPtr.h mfbt/RefPtr.h else hg mv mfbt/nsRefPtr.h mfbt/RefPtr.h fi --HG-- rename : mfbt/nsRefPtr.h => mfbt/RefPtr.h
2015-10-18 05:24:48 +00:00
RefPtr<gfxPattern> fillPattern;
if (mFontParams.contextPaint) {
imgDrawingParams imgParams;
fillPattern =
mFontParams.contextPaint->GetFillPattern(
mRunParams.context->GetDrawTarget(),
mRunParams.context->CurrentMatrixDouble(),
imgParams);
}
if (!fillPattern) {
if (state.pattern) {
RefPtr<gfxPattern> statePattern =
mRunParams.context->CurrentState().pattern;
pat = statePattern->GetPattern(mRunParams.dt,
state.patternTransformChanged ?
&state.patternTransform : nullptr);
} else {
pat = nullptr;
}
} else {
pat = fillPattern->GetPattern(mRunParams.dt);
}
if (pat) {
mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
*pat, mFontParams.drawOptions);
}
} else {
mRunParams.dt->FillGlyphs(mFontParams.scaledFont, buf,
ColorPattern(state.color),
mFontParams.drawOptions);
}
}
// Draw stroke if the UNDERNEATH flag is not set.
if (mRunParams.strokeOpts &&
GetStrokeMode(mRunParams.drawMode) == DrawMode::GLYPH_STROKE) {
DrawStroke(state, buf);
}
if (mRunParams.drawMode & DrawMode::GLYPH_PATH) {
mRunParams.context->EnsurePathBuilder();
Matrix mat = mRunParams.dt->GetTransform();
mFontParams.scaledFont->CopyGlyphsToBuilder(
buf, mRunParams.context->mPathBuilder, &mat);
}
}
void DrawStroke(const gfxContext::AzureState& aState,
gfx::GlyphBuffer& aBuffer)
{
if (mRunParams.textStrokePattern) {
Pattern* pat = mRunParams.textStrokePattern->GetPattern(
mRunParams.dt, aState.patternTransformChanged
? &aState.patternTransform
: nullptr);
if (pat) {
FlushStroke(aBuffer, *pat);
}
} else {
FlushStroke(aBuffer, ColorPattern(
Color::FromABGR(mRunParams.textStrokeColor)));
}
}
void FlushStroke(gfx::GlyphBuffer& aBuf, const Pattern& aPattern)
{
mRunParams.dt->StrokeGlyphs(mFontParams.scaledFont, aBuf,
aPattern,
*mRunParams.strokeOpts,
mFontParams.drawOptions);
}
// We use an "inline" buffer automatically allocated (on the stack) as part
// of the GlyphBufferAzure object to hold the glyphs in most cases, falling
// back to a separately-allocated heap buffer if the count of buffered
// glyphs gets too big.
//
// This is basically a rudimentary AutoTArray; so why not use AutoTArray
// itself?
//
// If we used an AutoTArray, we'd want to avoid using SetLength or
// AppendElements to allocate the space we actually need, because those
// methods would default-construct the new elements.
//
// Could we use SetCapacity to reserve the necessary buffer space without
// default-constructing all the Glyph records? No, because of a failure
// that could occur when we need to grow the buffer, which happens when we
// encounter a DetailedGlyph in the textrun that refers to a sequence of
// several real glyphs. At that point, we need to add some extra capacity
// to the buffer we initially allocated based on the length of the textrun
// range we're rendering.
//
// This buffer growth would work fine as long as it still fits within the
// array's inline buffer (we just use a bit more of it), or if the buffer
// was already heap-allocated (in which case AutoTArray will use realloc(),
// preserving its contents). But a problem will arise when the initial
// capacity we allocated (based on the length of the run) fits within the
// array's inline buffer, but subsequently we need to extend the buffer
// beyond the inline buffer size, so we reallocate to the heap. Because we
// haven't "officially" filled the array with SetLength or AppendElements,
// its mLength is still zero; as far as it's concerned the buffer is just
// uninitialized space, and when it switches to use a malloc'd buffer it
// won't copy the existing contents.
// Allocate space for a buffer of Glyph records, without initializing them.
AlignedStorage2<Glyph[AUTO_BUFFER_SIZE]> mAutoBuffer;
// Pointer to the buffer we're currently using -- initially mAutoBuffer,
// but may be changed to a malloc'd buffer, in which case that buffer must
// be free'd on destruction.
Glyph* mBuffer;
uint32_t mBufSize; // size of allocated buffer; capacity can grow to
// this before reallocation is needed
uint32_t mCapacity; // amount of buffer size reserved
uint32_t mNumGlyphs; // number of glyphs actually present in the buffer
#undef AUTO_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.
gfx::Float
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;
}
gfx::Float m = sqrtf(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 a run of CharacterGlyph records from the given offset in aShapedText.
// Returns true if glyph paths were actually emitted.
template<gfxFont::FontComplexityT FC, gfxFont::SpacingT S>
bool
gfxFont::DrawGlyphs(const gfxShapedText* aShapedText,
uint32_t aOffset, // offset in the textrun
uint32_t aCount, // length of run to draw
gfx::Point* aPt,
GlyphBufferAzure& aBuffer)
{
float& inlineCoord = aBuffer.mFontParams.isVerticalFont ? aPt->y : aPt->x;
const gfxShapedText::CompressedGlyph *glyphData =
&aShapedText->GetCharacterGlyphs()[aOffset];
if (S == SpacingT::HasSpacing) {
float space = aBuffer.mRunParams.spacing[0].mBefore * aBuffer.mFontParams.advanceDirection;
inlineCoord += space;
}
// Allocate buffer space for the run, assuming all simple glyphs.
uint32_t capacityMult = 1 + aBuffer.mFontParams.extraStrikes;
aBuffer.AddCapacity(capacityMult * aCount);
bool emittedGlyphs = false;
for (uint32_t i = 0; i < aCount; ++i, ++glyphData) {
if (glyphData->IsSimpleGlyph()) {
float advance = glyphData->GetSimpleAdvance() * aBuffer.mFontParams.advanceDirection;
if (aBuffer.mRunParams.isRTL) {
inlineCoord += advance;
}
DrawOneGlyph<FC>(glyphData->GetSimpleGlyph(), *aPt, aBuffer,
&emittedGlyphs);
if (!aBuffer.mRunParams.isRTL) {
inlineCoord += advance;
}
} else {
uint32_t glyphCount = glyphData->GetGlyphCount();
if (glyphCount > 0) {
// Add extra buffer capacity to allow for multiple-glyph entry.
aBuffer.AddCapacity(capacityMult * (glyphCount - 1));
const gfxShapedText::DetailedGlyph *details =
aShapedText->GetDetailedGlyphs(aOffset + i);
MOZ_ASSERT(details, "missing DetailedGlyph!");
for (uint32_t j = 0; j < glyphCount; ++j, ++details) {
float advance = details->mAdvance * aBuffer.mFontParams.advanceDirection;
if (aBuffer.mRunParams.isRTL) {
inlineCoord += advance;
}
if (glyphData->IsMissing()) {
if (!DrawMissingGlyph(aBuffer.mRunParams,
aBuffer.mFontParams,
details, *aPt)) {
return false;
}
} else {
gfx::Point glyphPt(*aPt + details->mOffset);
DrawOneGlyph<FC>(details->mGlyphID, glyphPt, aBuffer,
&emittedGlyphs);
}
if (!aBuffer.mRunParams.isRTL) {
inlineCoord += advance;
}
}
}
}
if (S == SpacingT::HasSpacing) {
float space = aBuffer.mRunParams.spacing[i].mAfter;
if (i + 1 < aCount) {
space += aBuffer.mRunParams.spacing[i + 1].mBefore;
}
space *= aBuffer.mFontParams.advanceDirection;
inlineCoord += space;
}
}
return emittedGlyphs;
}
// Draw an individual glyph at a specific location.
// *aPt is the glyph position in appUnits; it is converted to device
// coordinates (devPt) here.
template<gfxFont::FontComplexityT FC>
void
gfxFont::DrawOneGlyph(uint32_t aGlyphID, const gfx::Point& aPt,
GlyphBufferAzure& aBuffer, bool *aEmittedGlyphs) const
{
const TextRunDrawParams& runParams(aBuffer.mRunParams);
gfx::Point devPt(ToDeviceUnits(aPt.x, runParams.devPerApp),
ToDeviceUnits(aPt.y, runParams.devPerApp));
if (FC == FontComplexityT::ComplexFont) {
const FontDrawParams& fontParams(aBuffer.mFontParams);
auto* textDrawer = runParams.context->GetTextDrawer();
gfxContextMatrixAutoSaveRestore matrixRestore;
if (fontParams.obliqueSkew != 0.0f &&
fontParams.isVerticalFont && !textDrawer) {
// We have to flush each glyph individually when doing
// synthetic-oblique for vertical-upright text, because
// the skew transform needs to be applied to a separate
// origin for each glyph, not once for the whole run.
aBuffer.Flush();
matrixRestore.SetContext(runParams.context);
gfx::Matrix mat =
runParams.context->CurrentMatrix().
PreTranslate(devPt).
PreMultiply(gfx::Matrix(1, 0, -fontParams.obliqueSkew, 1, 0, 0)).
PreTranslate(-devPt);
runParams.context->SetMatrix(mat);
}
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 &&
!gfxPlatform::GetPlatform()->HasNativeColrFontSupport() &&
RenderColorGlyph(runParams.dt, runParams.context,
fontParams.scaledFont,
fontParams.drawOptions,
devPt,
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);
}
if (fontParams.obliqueSkew != 0.0f &&
fontParams.isVerticalFont && !textDrawer) {
aBuffer.Flush();
}
} else {
aBuffer.OutputGlyph(aGlyphID, devPt);
}
*aEmittedGlyphs = true;
}
bool
gfxFont::DrawMissingGlyph(const TextRunDrawParams& aRunParams,
const FontDrawParams& aFontParams,
const gfxShapedText::DetailedGlyph* aDetails,
const gfx::Point& aPt)
{
// Default-ignorable chars will have zero advance width;
// we don't have to draw the hexbox for them.
float advance = aDetails->mAdvance;
if (aRunParams.drawMode != DrawMode::GLYPH_PATH && advance > 0) {
auto* textDrawer = aRunParams.context->GetTextDrawer();
const Matrix* matPtr = nullptr;
Matrix mat;
if (textDrawer) {
// Generate an orientation matrix for the current writing mode
wr::FontInstanceFlags flags = textDrawer->GetWRGlyphFlags();
if (flags.bits & wr::FontInstanceFlags::TRANSPOSE) {
std::swap(mat._11, mat._12);
std::swap(mat._21, mat._22);
}
mat.PostScale(flags.bits & wr::FontInstanceFlags::FLIP_X ? -1.0f : 1.0f,
flags.bits & wr::FontInstanceFlags::FLIP_Y ? -1.0f : 1.0f);
matPtr = &mat;
}
Point pt(Float(ToDeviceUnits(aPt.x, aRunParams.devPerApp)),
Float(ToDeviceUnits(aPt.y, aRunParams.devPerApp)));
Float advanceDevUnits =
Float(ToDeviceUnits(advance, aRunParams.devPerApp));
Float height = GetMetrics(eHorizontal).maxAscent;
// Horizontally center if drawing vertically upright with no sideways transform.
Rect glyphRect = aFontParams.isVerticalFont && !mat.HasNonAxisAlignedTransform() ?
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 undo
// this before drawing the hexbox. (Bug 983985)
gfxContextMatrixAutoSaveRestore matrixRestore;
if (aFontParams.obliqueSkew != 0.0f &&
!aFontParams.isVerticalFont && !textDrawer) {
matrixRestore.SetContext(aRunParams.context);
gfx::Matrix mat =
aRunParams.context->CurrentMatrix().
PreTranslate(pt).
PreMultiply(gfx::Matrix(1, 0, aFontParams.obliqueSkew, 1, 0, 0)).
PreTranslate(-pt);
aRunParams.context->SetMatrix(mat);
}
gfxFontMissingGlyphs::DrawMissingGlyph(
aDetails->mGlyphID, glyphRect, *aRunParams.dt,
PatternFromState(aRunParams.context),
1.0 / aRunParams.devPerApp, matPtr);
}
return true;
}
// This method is mostly parallel to DrawGlyphs.
void
gfxFont::DrawEmphasisMarks(const gfxTextRun* aShapedText, gfx::Point* aPt,
uint32_t aOffset, uint32_t aCount,
const EmphasisMarkDrawParams& aParams)
{
float& inlineCoord = aParams.isVertical ? aPt->y : aPt->x;
gfxTextRun::Range markRange(aParams.mark);
gfxTextRun::DrawParams params(aParams.context);
float clusterStart = -std::numeric_limits<float>::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<float>::infinity()) {
clusterStart = inlineCoord;
}
if (aShapedText->CharMayHaveEmphasisMark(idx)) {
shouldDrawEmphasisMark = true;
}
inlineCoord += aParams.direction * aShapedText->GetAdvanceForGlyph(idx);
if (shouldDrawEmphasisMark &&
(i + 1 == aCount || aShapedText->IsClusterStart(idx + 1))) {
float clusterAdvance = inlineCoord - clusterStart;
// Move the coord backward to get the needed start point.
float 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,
gfx::Point* aPt, const TextRunDrawParams& aRunParams,
gfx::ShapedTextFlags 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;
}
auto* textDrawer = aRunParams.context->GetTextDrawer();
fontParams.obliqueSkew = SkewForSyntheticOblique();
fontParams.haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
fontParams.haveColorGlyphs = GetFontEntry()->TryGetColorGlyphs();
fontParams.contextPaint = aRunParams.runContextPaint;
if (textDrawer) {
Color color;
if (fontParams.haveSVGGlyphs ||
(fontParams.haveColorGlyphs &&
aRunParams.context->HasNonOpaqueNonTransparentColor(color))) {
textDrawer->FoundUnsupportedFeature();
return;
}
fontParams.isVerticalFont = aRunParams.isVerticalRun;
} else {
fontParams.isVerticalFont =
aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
}
gfxContextMatrixAutoSaveRestore matrixRestore;
layout::TextDrawTarget::AutoRestoreWRGlyphFlags glyphFlagsRestore;
// Save the current baseline offset for restoring later, in case it is modified.
float& baseline = fontParams.isVerticalFont ? aPt->x : aPt->y;
float origBaseline = baseline;
// The point may be advanced in local-space, while the resulting point on return
// must be advanced in transformed space. So save the original point so we can
// properly transform the advance later.
gfx::Point origPt = *aPt;
// Default to advancing along the +X direction (-X if RTL).
fontParams.advanceDirection = aRunParams.isRTL ? -1.0f : 1.0f;
// Default to offsetting baseline downward along the +Y direction.
float baselineDir = 1.0f;
// The direction of sideways rotation, if applicable.
// -1 for rotating left/counter-clockwise
// 1 for rotating right/clockwise
// 0 for no rotation
float sidewaysDir =
(aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT ?
-1.0f :
(aOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT ?
1.0f : 0.0f));
// If we're rendering a sideways run, we need to push a rotation transform to the context.
if (sidewaysDir != 0.0f) {
if (textDrawer) {
// For WebRender, we can't use a DrawTarget transform and must instead use flags
// that locally transform the glyph, without affecting the glyph origin. The glyph
// origins must thus be offset in the transformed directions (instead of local-space
// directions). Modify the advance and baseline directions to account for the
// indicated transform.
// The default text orientation is down being +Y and right being +X.
// Rotating 90 degrees left/CCW makes down be +X and right be -Y.
// Rotating 90 degrees right/CW makes down be -X and right be +Y.
// Thus the advance direction (moving right) is just sidewaysDir,
// i.e. negative along Y axis if rotated left and positive if
// rotated right.
fontParams.advanceDirection *= sidewaysDir;
// The baseline direction (moving down) is negated relative to the
// advance direction for sideways transforms.
baselineDir *= -sidewaysDir;
glyphFlagsRestore.Save(textDrawer);
// Set the transform flags accordingly. Both sideways rotations transpose X and Y,
// while left rotation flips the resulting Y axis, and right rotation flips the
// resulting X axis.
textDrawer->SetWRGlyphFlags(textDrawer->GetWRGlyphFlags() |
wr::FontInstanceFlags::TRANSPOSE |
(aOrientation ==
gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT ?
wr::FontInstanceFlags::FLIP_Y :
wr::FontInstanceFlags::FLIP_X));
} else {
// For non-WebRender targets, just push a rotation transform.
matrixRestore.SetContext(aRunParams.context);
gfxPoint p(aPt->x * aRunParams.devPerApp,
aPt->y * aRunParams.devPerApp);
// Get a matrix we can use to draw the (horizontally-shaped) textrun
// with 90-degree CW rotation.
const gfxFloat rotation = sidewaysDir * M_PI / 2.0f;
gfxMatrix mat =
aRunParams.context->CurrentMatrixDouble().
PreTranslate(p). // translate origin for rotation
PreRotate(rotation). // turn 90deg CCW (sideways-left) or CW (*-right)
PreTranslate(-p); // undo the translation
aRunParams.context->SetMatrixDouble(mat);
}
// 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()) {
const Metrics& metrics = GetMetrics(eHorizontal);
float baseAdj = (metrics.emAscent - metrics.emDescent) / 2;
baseline += baseAdj * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
}
}
if (fontParams.obliqueSkew != 0.0f && !fontParams.isVerticalFont && !textDrawer) {
// Adjust matrix for synthetic-oblique, except if we're doing vertical-
// upright text, in which case this will be handled for each glyph
// individually in DrawOneGlyph.
if (!matrixRestore.HasMatrix()) {
matrixRestore.SetContext(aRunParams.context);
}
gfx::Point p(aPt->x * aRunParams.devPerApp,
aPt->y * aRunParams.devPerApp);
gfx::Matrix mat =
aRunParams.context->CurrentMatrix().
PreTranslate(p).
PreMultiply(gfx::Matrix(1, 0, -fontParams.obliqueSkew, 1, 0, 0)).
PreTranslate(-p);
aRunParams.context->SetMatrix(mat);
}
RefPtr<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");
Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat The bulk of this commit was generated with a script, executed at the top level of a typical source code checkout. The only non-machine-generated part was modifying MFBT's moz.build to reflect the new naming. CLOSED TREE makes big refactorings like this a piece of cake. # The main substitution. find . -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.mm' -o -name '*.idl'| \ xargs perl -p -i -e ' s/nsRefPtr\.h/RefPtr\.h/g; # handle includes s/nsRefPtr ?</RefPtr</g; # handle declarations and variables ' # Handle a special friend declaration in gfx/layers/AtomicRefCountedWithFinalize.h. perl -p -i -e 's/::nsRefPtr;/::RefPtr;/' gfx/layers/AtomicRefCountedWithFinalize.h # Handle nsRefPtr.h itself, a couple places that define constructors # from nsRefPtr, and code generators specially. We do this here, rather # than indiscriminantly s/nsRefPtr/RefPtr/, because that would rename # things like nsRefPtrHashtable. perl -p -i -e 's/nsRefPtr/RefPtr/g' \ mfbt/nsRefPtr.h \ xpcom/glue/nsCOMPtr.h \ xpcom/base/OwningNonNull.h \ ipc/ipdl/ipdl/lower.py \ ipc/ipdl/ipdl/builtin.py \ dom/bindings/Codegen.py \ python/lldbutils/lldbutils/utils.py # In our indiscriminate substitution above, we renamed # nsRefPtrGetterAddRefs, the class behind getter_AddRefs. Fix that up. find . -name '*.cpp' -o -name '*.h' -o -name '*.idl' | \ xargs perl -p -i -e 's/nsRefPtrGetterAddRefs/RefPtrGetterAddRefs/g' if [ -d .git ]; then git mv mfbt/nsRefPtr.h mfbt/RefPtr.h else hg mv mfbt/nsRefPtr.h mfbt/RefPtr.h fi --HG-- rename : mfbt/nsRefPtr.h => mfbt/RefPtr.h
2015-10-18 05:24:48 +00:00
RefPtr<gfxPattern> fillPattern = aRunParams.context->GetPattern();
contextPaint =
new SimpleTextContextPaint(fillPattern, nullptr,
aRunParams.context->CurrentMatrixDouble());
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)
// WebRender handles synthetic bold independently via FontInstanceFlags,
// so just ignore requests in that case.
if (IsSyntheticBold() && !textDrawer) {
gfx::Float 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();
fontParams.drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
if (mStyle.baselineOffset != 0.0) {
baseline +=
mStyle.baselineOffset * aTextRun->GetAppUnitsPerDevUnit() * baselineDir;
}
bool emittedGlyphs;
{
// Select appropriate version of the templated DrawGlyphs method
// to output glyphs to the buffer, depending on complexity needed
// for the type of font, and whether added inter-glyph spacing
// is specified.
GlyphBufferAzure buffer(aRunParams, fontParams);
if (fontParams.haveSVGGlyphs || fontParams.haveColorGlyphs ||
fontParams.extraStrikes ||
(fontParams.obliqueSkew != 0.0f &&
fontParams.isVerticalFont && !textDrawer)) {
if (aRunParams.spacing) {
emittedGlyphs =
DrawGlyphs<FontComplexityT::ComplexFont,
SpacingT::HasSpacing>(aTextRun, aStart,
aEnd - aStart, aPt,
buffer);
} else {
emittedGlyphs =
DrawGlyphs<FontComplexityT::ComplexFont,
SpacingT::NoSpacing>(aTextRun, aStart,
aEnd - aStart, aPt,
buffer);
}
} else {
if (aRunParams.spacing) {
emittedGlyphs =
DrawGlyphs<FontComplexityT::SimpleFont,
SpacingT::HasSpacing>(aTextRun, aStart,
aEnd - aStart, aPt,
buffer);
} else {
emittedGlyphs =
DrawGlyphs<FontComplexityT::SimpleFont,
SpacingT::NoSpacing>(aTextRun, aStart,
aEnd - aStart, aPt,
buffer);
}
}
}
baseline = origBaseline;
if (aRunParams.callbacks && emittedGlyphs) {
aRunParams.callbacks->NotifyGlyphPathEmitted();
}
aRunParams.dt->SetTransform(oldMat);
aRunParams.dt->SetPermitSubpixelAA(oldSubpixelAA);
if (sidewaysDir != 0.0f && !textDrawer) {
// Adjust updated aPt to account for the transform we were using.
// The advance happened horizontally in local-space, but the transformed
// sideways advance is actually vertical, with sign depending on the
// direction of rotation.
float advance = aPt->x - origPt.x;
*aPt = gfx::Point(origPt.x, origPt.y + advance * sidewaysDir);
}
}
bool
gfxFont::RenderSVGGlyph(gfxContext *aContext, gfx::Point aPoint,
uint32_t aGlyphId, SVGContextPaint* aContextPaint) const
{
if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
return false;
}
const gfxFloat devUnitsPerSVGUnit =
GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
aContext->SetMatrix(
aContext->CurrentMatrix().PreTranslate(aPoint.x, aPoint.y).
PreScale(devUnitsPerSVGUnit, devUnitsPerSVGUnit));
aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit);
GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, aContextPaint);
aContext->NewPath();
return true;
}
bool
gfxFont::RenderSVGGlyph(gfxContext *aContext, gfx::Point 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,
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);
}
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() & gfx::ShapedTextFlags::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,
gfx::ShapedTextFlags 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 = 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 == gfx::ShapedTextFlags::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 (isRTL) {
glyphRect.MoveByX(-advance);
}
glyphRect.MoveByX(x);
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;
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 (isRTL) {
glyphRect.MoveByX(-advance);
}
glyphRect.MoveByX(x + details->mOffset.x);
glyphRect.MoveByY(details->mOffset.y);
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.
gfx::Float obliqueSkew = SkewForSyntheticOblique();
if (obliqueSkew != 0.0f) {
gfxFloat extendLeftEdge =
obliqueSkew < 0.0f
? ceil(-obliqueSkew * -metrics.mBoundingBox.Y())
: ceil(obliqueSkew * metrics.mBoundingBox.YMost());
gfxFloat extendRightEdge =
obliqueSkew < 0.0f
? ceil(-obliqueSkew * metrics.mBoundingBox.YMost())
: ceil(obliqueSkew * -metrics.mBoundingBox.Y());
metrics.mBoundingBox.SetWidth(metrics.mBoundingBox.Width() +
extendLeftEdge + extendRightEdge);
metrics.mBoundingBox.MoveByX(-extendLeftEdge);
}
if (baselineOffset != 0) {
metrics.mAscent -= baselineOffset;
metrics.mDescent += baselineOffset;
metrics.mBoundingBox.MoveByY(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,
gfx::ShapedTextFlags aFlags,
RoundingFlags aRounding,
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, aRounding);
CacheHashEntry* entry = mWordCache->PutEntry(key, fallible);
if (!entry) {
NS_WARNING("failed to create word cache entry - expect missing text");
return nullptr;
}
gfxShapedWord* sw = entry->mShapedWord.get();
if (sw) {
sw->ResetAge();
#ifndef RELEASE_OR_BETA
if (aTextPerf) {
aTextPerf->current.wordCacheHit++;
}
#endif
return sw;
}
#ifndef RELEASE_OR_BETA
if (aTextPerf) {
aTextPerf->current.wordCacheMiss++;
}
#endif
sw = gfxShapedWord::Create(aText, aLength, aRunScript, aAppUnitsPerDevUnit,
aFlags, aRounding);
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,
aRounding, sw);
NS_WARNING_ASSERTION(ok, "failed to shape word - expect garbled text");
return sw;
}
template gfxShapedWord*
gfxFont::GetShapedWord(DrawTarget *aDrawTarget,
const uint8_t *aText,
uint32_t aLength,
uint32_t aHash,
Script aRunScript,
bool aVertical,
int32_t aAppUnitsPerDevUnit,
gfx::ShapedTextFlags aFlags,
RoundingFlags aRounding,
gfxTextPerfMetrics *aTextPerf);
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->GetRounding() != aKey->mRounding ||
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 & gfx::ShapedTextFlags::TEXT_IS_8BIT) &&
!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,
RoundingFlags aRounding,
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, aRounding, aShapedText);
}
bool
gfxFont::ShapeText(DrawTarget *aDrawTarget,
const char16_t *aText,
uint32_t aOffset,
uint32_t aLength,
Script aScript,
bool aVertical,
RoundingFlags aRounding,
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);
Telemetry::ScalarAdd(Telemetry::ScalarID::BROWSER_USAGE_GRAPHITE, 1);
}
ok = mGraphiteShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
aScript, aVertical, aRounding,
aShapedText);
}
}
if (!ok) {
if (!mHarfBuzzShaper) {
mHarfBuzzShaper = MakeUnique<gfxHarfBuzzShaper>(this);
}
ok = mHarfBuzzShaper->ShapeText(aDrawTarget, aText, aOffset, aLength,
aScript, aVertical, aRounding,
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,
RoundingFlags aRounding,
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, aRounding, 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,
RoundingFlags aRounding,
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, aRounding,
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 (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
aTextRun->SetIsFormattingControl(aOffset + i);
} else if (IsInvalidControlChar(ch) &&
!(aTextRun->GetFlags() & gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) {
if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
ShapeFragmentWithoutWordCache(aDrawTarget, aText + i,
aOffset + i, 1,
aScript, aVertical, aRounding,
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,
ShapedTextFlags aOrientation)
{
if (aRunLength == 0) {
return true;
}
gfxTextPerfMetrics *tp = nullptr;
RoundingFlags rounding = GetRoundOffsetsToPixels(aDrawTarget);
#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();
bool vertical =
aOrientation == ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
// 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, vertical,
rounding, aTextRun);
}
}
InitWordCache();
// the only flags we care about for ShapedWord construction/caching
gfx::ShapedTextFlags flags = aTextRun->GetFlags();
flags &= (gfx::ShapedTextFlags::TEXT_IS_RTL |
gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES |
gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT |
gfx::ShapedTextFlags::TEXT_ORIENT_MASK);
if (sizeof(T) == sizeof(uint8_t)) {
flags |= gfx::ShapedTextFlags::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,
vertical,
rounding,
aTextRun);
if (!ok) {
return false;
}
} else if (length > 0) {
gfx::ShapedTextFlags 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 |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
}
}
gfxShapedWord* sw = GetShapedWord(aDrawTarget,
aString + wordStart, length,
hash, aRunScript, vertical,
appUnitsPerDevUnit,
wordFlags, rounding, 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
MOZ_ASSERT(aOrientation !=
ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED,
"text-orientation:mixed should be resolved earlier");
if (boundary != ' ' ||
!aTextRun->SetSpaceGlyphIfSimple(this, aRunStart + i, ch,
aOrientation)) {
// 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, vertical, appUnitsPerDevUnit,
flags | gfx::ShapedTextFlags::TEXT_IS_8BIT,
rounding, 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 (GetGeneralCategory(ch) == HB_UNICODE_GENERAL_CATEGORY_FORMAT) {
aTextRun->SetIsFormattingControl(aRunStart + i);
} else if (IsInvalidControlChar(ch) &&
!(aTextRun->GetFlags() & gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS)) {
if (GetFontEntry()->IsUserFont() && HasCharacter(ch)) {
ShapeFragmentWithoutWordCache(aDrawTarget, aString + i,
aRunStart + i, 1,
aRunScript, vertical,
rounding, 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,
ShapedTextFlags aOrientation);
template bool
gfxFont::SplitAndInitTextRun(DrawTarget *aDrawTarget,
gfxTextRun *aTextRun,
const char16_t *aString,
uint32_t aRunStart,
uint32_t aRunLength,
Script aRunScript,
ShapedTextFlags aOrientation);
template<>
bool
gfxFont::InitFakeSmallCapsRun(DrawTarget *aDrawTarget,
gfxTextRun *aTextRun,
const char16_t *aText,
uint32_t aOffset,
uint32_t aLength,
gfxTextRange::MatchType aMatchType,
gfx::ShapedTextFlags aOrientation,
Script aScript,
bool aSyntheticLower,
bool aSyntheticUpper)
{
bool ok = true;
Bug 1207245 - part 6 - rename nsRefPtr<T> to RefPtr<T>; r=ehsan; a=Tomcat The bulk of this commit was generated with a script, executed at the top level of a typical source code checkout. The only non-machine-generated part was modifying MFBT's moz.build to reflect the new naming. CLOSED TREE makes big refactorings like this a piece of cake. # The main substitution. find . -name '*.cpp' -o -name '*.cc' -o -name '*.h' -o -name '*.mm' -o -name '*.idl'| \ xargs perl -p -i -e ' s/nsRefPtr\.h/RefPtr\.h/g; # handle includes s/nsRefPtr ?</RefPtr</g; # handle declarations and variables ' # Handle a special friend declaration in gfx/layers/AtomicRefCountedWithFinalize.h. perl -p -i -e 's/::nsRefPtr;/::RefPtr;/' gfx/layers/AtomicRefCountedWithFinalize.h # Handle nsRefPtr.h itself, a couple places that define constructors # from nsRefPtr, and code generators specially. We do this here, rather # than indiscriminantly s/nsRefPtr/RefPtr/, because that would rename # things like nsRefPtrHashtable. perl -p -i -e 's/nsRefPtr/RefPtr/g' \ mfbt/nsRefPtr.h \ xpcom/glue/nsCOMPtr.h \ xpcom/base/OwningNonNull.h \ ipc/ipdl/ipdl/lower.py \ ipc/ipdl/ipdl/builtin.py \ dom/bindings/Codegen.py \ python/lldbutils/lldbutils/utils.py # In our indiscriminate substitution above, we renamed # nsRefPtrGetterAddRefs, the class behind getter_AddRefs. Fix that up. find . -name '*.cpp' -o -name '*.h' -o -name '*.idl' | \ xargs perl -p -i -e 's/nsRefPtrGetterAddRefs/RefPtrGetterAddRefs/g' if [ -d .git ]; then git mv mfbt/nsRefPtr.h mfbt/RefPtr.h else hg mv mfbt/nsRefPtr.h mfbt/RefPtr.h fi --HG-- rename : mfbt/nsRefPtr.h => mfbt/RefPtr.h
2015-10-18 05:24:48 +00:00
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;
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;
bool markEta, updateEta;
uint32_t ch2 =
mozilla::GreekCasing::UpperCase(ch, state, markEta,
updateEta);
if ((ch != ch2 || markEta) && !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, aOrientation)) {
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(),
gfx::ShapedTextFlags(),
nsTextFrameUtils::Flags()));
tempRun->AddGlyphRun(f, aMatchType, 0, true, aOrientation);
if (!f->SplitAndInitTextRun(aDrawTarget, tempRun.get(),
convertedString.BeginReading(),
0, convertedString.Length(),
aScript, aOrientation)) {
ok = false;
} else {
RefPtr<gfxTextRun> mergedRun(
gfxTextRun::Create(&params, runLength,
aTextRun->GetFontGroup(),
gfx::ShapedTextFlags(),
nsTextFrameUtils::Flags()));
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, aOrientation)) {
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,
gfxTextRange::MatchType aMatchType,
gfx::ShapedTextFlags 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);
}
gfxFont*
gfxFont::GetSmallCapsFont()
{
gfxFontStyle style(*GetStyle());
style.size *= SMALL_CAPS_SCALE_FACTOR;
style.variantCaps = NS_FONT_VARIANT_CAPS_NORMAL;
gfxFontEntry* fe = GetFontEntry();
Bug 1449605 - part 1 - Rearrange thebes font code so that the decision whether to apply synthetic-bold is deferred until actually instantiating a font, not made during the font-matching process. r=jwatt This rearranges how synthetic-bold use is determined in the font selection & rendering code. Previously, we would decide during the font-selection algorithm whether we need to apply synthetic-bold to the chosen face, and then pass that decision through the fontgroup (storing it in the FamilyFace entries of the mFonts array there) down to the actual rendering code that instantiates fonts from the faces (font entries) we've selected. That became a problem for variation fonts because in the case of a user font, we may not have downloaded the resource yet, so we just have a "user font container" entry, which carries the descriptors from the @font-face rule and will fetch the actual resource when needed. But in the case of a @font-face rule without a weight descriptor, we don't actually know at font-selection time whether the face will support "true" bold (via a variation axis) or not, so we can't reliably make the right decision about applying synthetic bold. So we now defer that decision until we actually instantiate a platform font object to shape/measure/draw text. At that point, we have the requested style and we also have the real font resource, so we can easily determine whether fake-bold is required. (This patch should not result in any visible behavior change; that will come in a second patch now that the architecture supports it.)
2018-05-01 09:30:50 +00:00
return fe->FindOrMakeFont(&style, mUnicodeRangeMap);
}
gfxFont*
gfxFont::GetSubSuperscriptFont(int32_t aAppUnitsPerDevPixel)
{
gfxFontStyle style(*GetStyle());
style.AdjustForSubSuperscript(aAppUnitsPerDevPixel);
gfxFontEntry* fe = GetFontEntry();
Bug 1449605 - part 1 - Rearrange thebes font code so that the decision whether to apply synthetic-bold is deferred until actually instantiating a font, not made during the font-matching process. r=jwatt This rearranges how synthetic-bold use is determined in the font selection & rendering code. Previously, we would decide during the font-selection algorithm whether we need to apply synthetic-bold to the chosen face, and then pass that decision through the fontgroup (storing it in the FamilyFace entries of the mFonts array there) down to the actual rendering code that instantiates fonts from the faces (font entries) we've selected. That became a problem for variation fonts because in the case of a user font, we may not have downloaded the resource yet, so we just have a "user font container" entry, which carries the descriptors from the @font-face rule and will fetch the actual resource when needed. But in the case of a @font-face rule without a weight descriptor, we don't actually know at font-selection time whether the face will support "true" bold (via a variation axis) or not, so we can't reliably make the right decision about applying synthetic bold. So we now defer that decision until we actually instantiate a platform font object to shape/measure/draw text. At that point, we have the requested style and we also have the real font resource, so we can easily determine whether fake-bold is required. (This patch should not result in any visible behavior change; that will come in a second patch now that the architecture supports it.)
2018-05-01 09:30:50 +00:00
return fe->FindOrMakeFont(&style, 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,
GetAdjustedSize(), &svgBounds)) {
gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
aExtents->SetTightGlyphExtents(aGlyphID,
gfxRect(svgBounds.X() * d2a,
svgBounds.Y() * d2a,
svgBounds.Width() * d2a,
svgBounds.Height() * d2a));
return;
}
RefPtr<ScaledFont> sf = GetScaledFont(aDrawTarget);
uint16_t glyphIndex = aGlyphID;
GlyphMetrics metrics;
if (mAntialiasOption == kAntialiasNone) {
sf->GetGlyphDesignMetrics(&glyphIndex, 1, &metrics);
} else {
aDrawTarget->GetGlyphRasterizationMetrics(sf, &glyphIndex, 1, &metrics);
}
const Metrics& fontMetrics = GetMetrics(eHorizontal);
int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
if (!aNeedTight && metrics.mXBearing >= 0.0 &&
metrics.mYBearing >= -fontMetrics.maxAscent &&
metrics.mHeight + metrics.mYBearing <= fontMetrics.maxDescent) {
uint32_t appUnitsWidth =
uint32_t(ceil((metrics.mXBearing + metrics.mWidth)*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(metrics.mXBearing * d2a, metrics.mYBearing * d2a,
metrics.mWidth * d2a, metrics.mHeight * 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).
//
// We also prefer OS/2 metrics if the hhea table gave us a negative
// value for maxDescent, which almost certainly indicates a sign
// error in the font. (See bug 1402413 for an example.)
const uint16_t kUseTypoMetricsMask = 1 << 7;
if ((uint16_t(os2->fsSelection) & kUseTypoMetricsMask) ||
aMetrics.maxDescent < 0) {
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).
UniquePtr<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;
UniquePtr<Metrics> metrics = MakeUnique<Metrics>();
::memset(metrics.get(), 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 std::move(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 =
MakeUnique<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
gfxFontStyle::gfxFontStyle() :
language(nsGkAtoms::x_western),
size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(-1.0f), baselineOffset(0.0f),
languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)),
weight(FontWeight::Normal()),
stretch(FontStretch::Normal()),
style(FontSlantStyle::Normal()),
variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
systemFont(true), printerFont(false), useGrayscaleAntialiasing(false),
allowSyntheticWeight(true), allowSyntheticStyle(true),
noFallbackVariantFeatures(true),
explicitLanguage(false)
{
}
gfxFontStyle::gfxFontStyle(FontSlantStyle aStyle,
FontWeight aWeight,
FontStretch aStretch,
gfxFloat aSize,
nsAtom *aLanguage, bool aExplicitLanguage,
float aSizeAdjust, bool aSystemFont,
bool aPrinterFont,
bool aAllowWeightSynthesis,
bool aAllowStyleSynthesis,
uint32_t aLanguageOverride):
language(aLanguage),
size(aSize), sizeAdjust(aSizeAdjust), baselineOffset(0.0f),
languageOverride(aLanguageOverride),
fontSmoothingBackgroundColor(NS_RGBA(0, 0, 0, 0)),
weight(aWeight),
stretch(aStretch),
style(aStyle),
variantCaps(NS_FONT_VARIANT_CAPS_NORMAL),
variantSubSuper(NS_FONT_VARIANT_POSITION_NORMAL),
systemFont(aSystemFont), printerFont(aPrinterFont),
useGrayscaleAntialiasing(false),
allowSyntheticWeight(aAllowWeightSynthesis),
allowSyntheticStyle(aAllowStyleSynthesis),
noFallbackVariantFeatures(true),
explicitLanguage(aExplicitLanguage)
{
MOZ_ASSERT(!mozilla::IsNaN(size));
MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust));
if (weight > FontWeight(900)) {
weight = FontWeight(900);
}
if (weight < FontWeight(100)) {
weight = FontWeight(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;
}
}
PLDHashNumber
gfxFontStyle::Hash() const
{
uint32_t hash =
variationSettings.IsEmpty()
? 0
: mozilla::HashBytes(variationSettings.Elements(),
variationSettings.Length() *
sizeof(gfxFontVariation));
return mozilla::AddToHash(hash, systemFont, style.ForHash(),
stretch.ForHash(), weight.ForHash(),
size, int32_t(sizeAdjust * 1000.0f),
nsRefPtrHashKey<nsAtom>::HashKey(language));
}
void
gfxFontStyle::AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel)
{
MOZ_ASSERT(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;
}
/* static */ void
SharedFontList::Initialize()
{
sEmpty = new SharedFontList();
}
/* static */ void
SharedFontList::Shutdown()
{
sEmpty = nullptr;
}
StaticRefPtr<SharedFontList> SharedFontList::sEmpty;