gecko-dev/gfx/thebes/gfxFontEntry.cpp

1919 lines
62 KiB
C++

/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/DebugOnly.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/Logging.h"
#include "gfxFontEntry.h"
#include "gfxTextRun.h"
#include "gfxPlatform.h"
#include "nsGkAtoms.h"
#include "gfxTypes.h"
#include "gfxContext.h"
#include "gfxFontConstants.h"
#include "gfxHarfBuzzShaper.h"
#include "gfxUserFontSet.h"
#include "gfxPlatformFontList.h"
#include "nsUnicodeProperties.h"
#include "nsMathUtils.h"
#include "nsBidiUtils.h"
#include "nsUnicodeRange.h"
#include "nsStyleConsts.h"
#include "mozilla/AppUnits.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/Likely.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/Telemetry.h"
#include "gfxSVGGlyphs.h"
#include "gfx2DGlue.h"
#include "cairo.h"
#include "harfbuzz/hb.h"
#include "harfbuzz/hb-ot.h"
#include "graphite2/Font.h"
#include <algorithm>
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::unicode;
using mozilla::services::GetObserverService;
void
gfxCharacterMap::NotifyReleased()
{
gfxPlatformFontList *fontlist = gfxPlatformFontList::PlatformFontList();
if (mShared) {
fontlist->RemoveCmap(this);
}
delete this;
}
gfxFontEntry::gfxFontEntry() :
mStyle(NS_FONT_STYLE_NORMAL), mFixedPitch(false),
mIsBadUnderlineFont(false),
mIsUserFontContainer(false),
mIsDataUserFont(false),
mIsLocalUserFont(false),
mStandardFace(false),
mIgnoreGDEF(false),
mIgnoreGSUB(false),
mSVGInitialized(false),
mHasSpaceFeaturesInitialized(false),
mHasSpaceFeatures(false),
mHasSpaceFeaturesKerning(false),
mHasSpaceFeaturesNonKerning(false),
mSkipDefaultFeatureSpaceCheck(false),
mGraphiteSpaceContextualsInitialized(false),
mHasGraphiteSpaceContextuals(false),
mSpaceGlyphIsInvisible(false),
mSpaceGlyphIsInvisibleInitialized(false),
mCheckedForGraphiteTables(false),
mHasCmapTable(false),
mGrFaceInitialized(false),
mCheckedForColorGlyph(false),
mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
mUVSOffset(0), mUVSData(nullptr),
mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
mCOLR(nullptr),
mCPAL(nullptr),
mUnitsPerEm(0),
mHBFace(nullptr),
mGrFace(nullptr),
mGrFaceRefCnt(0),
mComputedSizeOfUserFont(0)
{
memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
}
gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
mName(aName), mStyle(NS_FONT_STYLE_NORMAL), mFixedPitch(false),
mIsBadUnderlineFont(false),
mIsUserFontContainer(false),
mIsDataUserFont(false),
mIsLocalUserFont(false), mStandardFace(aIsStandardFace),
mIgnoreGDEF(false),
mIgnoreGSUB(false),
mSVGInitialized(false),
mHasSpaceFeaturesInitialized(false),
mHasSpaceFeatures(false),
mHasSpaceFeaturesKerning(false),
mHasSpaceFeaturesNonKerning(false),
mSkipDefaultFeatureSpaceCheck(false),
mGraphiteSpaceContextualsInitialized(false),
mHasGraphiteSpaceContextuals(false),
mSpaceGlyphIsInvisible(false),
mSpaceGlyphIsInvisibleInitialized(false),
mCheckedForGraphiteTables(false),
mHasCmapTable(false),
mGrFaceInitialized(false),
mCheckedForColorGlyph(false),
mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
mUVSOffset(0), mUVSData(nullptr),
mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
mCOLR(nullptr),
mCPAL(nullptr),
mUnitsPerEm(0),
mHBFace(nullptr),
mGrFace(nullptr),
mGrFaceRefCnt(0),
mComputedSizeOfUserFont(0)
{
memset(&mDefaultSubSpaceFeatures, 0, sizeof(mDefaultSubSpaceFeatures));
memset(&mNonDefaultSubSpaceFeatures, 0, sizeof(mNonDefaultSubSpaceFeatures));
}
gfxFontEntry::~gfxFontEntry()
{
// Should not be dropped by stylo
MOZ_ASSERT(NS_IsMainThread());
if (mCOLR) {
hb_blob_destroy(mCOLR);
}
if (mCPAL) {
hb_blob_destroy(mCPAL);
}
// For downloaded fonts, we need to tell the user font cache that this
// entry is being deleted.
if (mIsDataUserFont) {
gfxUserFontSet::UserFontCache::ForgetFont(this);
}
if (mFeatureInputs) {
for (auto iter = mFeatureInputs->Iter(); !iter.Done(); iter.Next()) {
hb_set_t*& set = iter.Data();
hb_set_destroy(set);
}
}
// By the time the entry is destroyed, all font instances that were
// using it should already have been deleted, and so the HB and/or Gr
// face objects should have been released.
MOZ_ASSERT(!mHBFace);
MOZ_ASSERT(!mGrFaceInitialized);
}
bool gfxFontEntry::TestCharacterMap(uint32_t aCh)
{
if (!mCharacterMap) {
ReadCMAP();
NS_ASSERTION(mCharacterMap, "failed to initialize character map");
}
return mCharacterMap->test(aCh);
}
nsresult gfxFontEntry::InitializeUVSMap()
{
// mUVSOffset will not be initialized
// until cmap is initialized.
if (!mCharacterMap) {
ReadCMAP();
NS_ASSERTION(mCharacterMap, "failed to initialize character map");
}
if (!mUVSOffset) {
return NS_ERROR_FAILURE;
}
if (!mUVSData) {
const uint32_t kCmapTag = TRUETYPE_TAG('c','m','a','p');
AutoTable cmapTable(this, kCmapTag);
if (!cmapTable) {
mUVSOffset = 0; // don't bother to read the table again
return NS_ERROR_FAILURE;
}
UniquePtr<uint8_t[]> uvsData;
unsigned int cmapLen;
const char* cmapData = hb_blob_get_data(cmapTable, &cmapLen);
nsresult rv = gfxFontUtils::ReadCMAPTableFormat14(
(const uint8_t*)cmapData + mUVSOffset,
cmapLen - mUVSOffset, uvsData);
if (NS_FAILED(rv)) {
mUVSOffset = 0; // don't bother to read the table again
return rv;
}
mUVSData = Move(uvsData);
}
return NS_OK;
}
uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS)
{
InitializeUVSMap();
if (mUVSData) {
return gfxFontUtils::MapUVSToGlyphFormat14(mUVSData.get(), aCh, aVS);
}
return 0;
}
bool gfxFontEntry::SupportsScriptInGSUB(const hb_tag_t* aScriptTags)
{
hb_face_t *face = GetHBFace();
if (!face) {
return false;
}
unsigned int index;
hb_tag_t chosenScript;
bool found =
hb_ot_layout_table_choose_script(face, TRUETYPE_TAG('G','S','U','B'),
aScriptTags, &index, &chosenScript);
hb_face_destroy(face);
return found && chosenScript != TRUETYPE_TAG('D','F','L','T');
}
nsresult gfxFontEntry::ReadCMAP(FontInfoData *aFontInfoData)
{
NS_ASSERTION(false, "using default no-op implementation of ReadCMAP");
mCharacterMap = new gfxCharacterMap();
return NS_OK;
}
nsString
gfxFontEntry::RealFaceName()
{
AutoTable nameTable(this, TRUETYPE_TAG('n','a','m','e'));
if (nameTable) {
nsAutoString name;
nsresult rv = gfxFontUtils::GetFullNameFromTable(nameTable, name);
if (NS_SUCCEEDED(rv)) {
return name;
}
}
return Name();
}
gfxFont*
gfxFontEntry::FindOrMakeFont(const gfxFontStyle *aStyle,
bool aNeedsBold,
gfxCharacterMap* aUnicodeRangeMap)
{
// the font entry name is the psname, not the family name
gfxFont* font =
gfxFontCache::GetCache()->Lookup(this, aStyle, aUnicodeRangeMap);
if (!font) {
gfxFont *newFont = CreateFontInstance(aStyle, aNeedsBold);
if (!newFont) {
return nullptr;
}
if (!newFont->Valid()) {
delete newFont;
return nullptr;
}
font = newFont;
font->SetUnicodeRangeMap(aUnicodeRangeMap);
gfxFontCache::GetCache()->AddNew(font);
}
return font;
}
uint16_t
gfxFontEntry::UnitsPerEm()
{
if (!mUnitsPerEm) {
AutoTable headTable(this, TRUETYPE_TAG('h','e','a','d'));
if (headTable) {
uint32_t len;
const HeadTable* head =
reinterpret_cast<const HeadTable*>(hb_blob_get_data(headTable,
&len));
if (len >= sizeof(HeadTable)) {
mUnitsPerEm = head->unitsPerEm;
}
}
// if we didn't find a usable 'head' table, or if the value was
// outside the valid range, record it as invalid
if (mUnitsPerEm < kMinUPEM || mUnitsPerEm > kMaxUPEM) {
mUnitsPerEm = kInvalidUPEM;
}
}
return mUnitsPerEm;
}
bool
gfxFontEntry::HasSVGGlyph(uint32_t aGlyphId)
{
NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first.");
return mSVGGlyphs->HasSVGGlyph(aGlyphId);
}
bool
gfxFontEntry::GetSVGGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphId,
gfxRect *aResult)
{
MOZ_ASSERT(mSVGInitialized,
"SVG data has not yet been loaded. TryGetSVGData() first.");
MOZ_ASSERT(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM,
"font has invalid unitsPerEm");
cairo_matrix_t fontMatrix;
cairo_get_font_matrix(gfxFont::RefCairo(aDrawTarget), &fontMatrix);
gfxMatrix svgToAppSpace(fontMatrix.xx, fontMatrix.yx,
fontMatrix.xy, fontMatrix.yy,
fontMatrix.x0, fontMatrix.y0);
svgToAppSpace.PreScale(1.0f / mUnitsPerEm, 1.0f / mUnitsPerEm);
return mSVGGlyphs->GetGlyphExtents(aGlyphId, svgToAppSpace, aResult);
}
void
gfxFontEntry::RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId,
SVGContextPaint* aContextPaint)
{
NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first.");
mSVGGlyphs->RenderGlyph(aContext, aGlyphId, aContextPaint);
}
bool
gfxFontEntry::TryGetSVGData(gfxFont* aFont)
{
if (!gfxPlatform::GetPlatform()->OpenTypeSVGEnabled()) {
return false;
}
if (!mSVGInitialized) {
mSVGInitialized = true;
// If UnitsPerEm is not known/valid, we can't use SVG glyphs
if (UnitsPerEm() == kInvalidUPEM) {
return false;
}
// We don't use AutoTable here because we'll pass ownership of this
// blob to the gfxSVGGlyphs, once we've confirmed the table exists
hb_blob_t *svgTable = GetFontTable(TRUETYPE_TAG('S','V','G',' '));
if (!svgTable) {
return false;
}
// gfxSVGGlyphs will hb_blob_destroy() the table when it is finished
// with it.
mSVGGlyphs = MakeUnique<gfxSVGGlyphs>(svgTable, this);
}
if (mSVGGlyphs && !mFontsUsingSVGGlyphs.Contains(aFont)) {
mFontsUsingSVGGlyphs.AppendElement(aFont);
}
return !!mSVGGlyphs;
}
void
gfxFontEntry::NotifyFontDestroyed(gfxFont* aFont)
{
mFontsUsingSVGGlyphs.RemoveElement(aFont);
}
void
gfxFontEntry::NotifyGlyphsChanged()
{
for (uint32_t i = 0, count = mFontsUsingSVGGlyphs.Length(); i < count; ++i) {
gfxFont* font = mFontsUsingSVGGlyphs[i];
font->NotifyGlyphsChanged();
}
}
bool
gfxFontEntry::TryGetColorGlyphs()
{
if (mCheckedForColorGlyph) {
return (mCOLR && mCPAL);
}
mCheckedForColorGlyph = true;
mCOLR = GetFontTable(TRUETYPE_TAG('C', 'O', 'L', 'R'));
if (!mCOLR) {
return false;
}
mCPAL = GetFontTable(TRUETYPE_TAG('C', 'P', 'A', 'L'));
if (!mCPAL) {
hb_blob_destroy(mCOLR);
mCOLR = nullptr;
return false;
}
// validation COLR and CPAL table
if (gfxFontUtils::ValidateColorGlyphs(mCOLR, mCPAL)) {
return true;
}
hb_blob_destroy(mCOLR);
hb_blob_destroy(mCPAL);
mCOLR = nullptr;
mCPAL = nullptr;
return false;
}
/**
* FontTableBlobData
*
* See FontTableHashEntry for the general strategy.
*/
class gfxFontEntry::FontTableBlobData {
public:
explicit FontTableBlobData(nsTArray<uint8_t>&& aBuffer)
: mTableData(Move(aBuffer))
, mHashtable(nullptr)
, mHashKey(0)
{
MOZ_COUNT_CTOR(FontTableBlobData);
}
~FontTableBlobData() {
MOZ_COUNT_DTOR(FontTableBlobData);
if (mHashtable && mHashKey) {
mHashtable->RemoveEntry(mHashKey);
}
}
// Useful for creating blobs
const char *GetTable() const
{
return reinterpret_cast<const char*>(mTableData.Elements());
}
uint32_t GetTableLength() const { return mTableData.Length(); }
// Tell this FontTableBlobData to remove the HashEntry when this is
// destroyed.
void ManageHashEntry(nsTHashtable<FontTableHashEntry> *aHashtable,
uint32_t aHashKey)
{
mHashtable = aHashtable;
mHashKey = aHashKey;
}
// Disconnect from the HashEntry (because the blob has already been
// removed from the hashtable).
void ForgetHashEntry()
{
mHashtable = nullptr;
mHashKey = 0;
}
size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
return mTableData.ShallowSizeOfExcludingThis(aMallocSizeOf);
}
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
}
private:
// The font table data block
nsTArray<uint8_t> mTableData;
// The blob destroy function needs to know the owning hashtable
// and the hashtable key, so that it can remove the entry.
nsTHashtable<FontTableHashEntry> *mHashtable;
uint32_t mHashKey;
// not implemented
FontTableBlobData(const FontTableBlobData&);
};
hb_blob_t *
gfxFontEntry::FontTableHashEntry::
ShareTableAndGetBlob(nsTArray<uint8_t>&& aTable,
nsTHashtable<FontTableHashEntry> *aHashtable)
{
Clear();
// adopts elements of aTable
mSharedBlobData = new FontTableBlobData(Move(aTable));
mBlob = hb_blob_create(mSharedBlobData->GetTable(),
mSharedBlobData->GetTableLength(),
HB_MEMORY_MODE_READONLY,
mSharedBlobData, DeleteFontTableBlobData);
if (mBlob == hb_blob_get_empty() ) {
// The FontTableBlobData was destroyed during hb_blob_create().
// The (empty) blob is still be held in the hashtable with a strong
// reference.
return hb_blob_reference(mBlob);
}
// Tell the FontTableBlobData to remove this hash entry when destroyed.
// The hashtable does not keep a strong reference.
mSharedBlobData->ManageHashEntry(aHashtable, GetKey());
return mBlob;
}
void
gfxFontEntry::FontTableHashEntry::Clear()
{
// If the FontTableBlobData is managing the hash entry, then the blob is
// not owned by this HashEntry; otherwise there is strong reference to the
// blob that must be removed.
if (mSharedBlobData) {
mSharedBlobData->ForgetHashEntry();
mSharedBlobData = nullptr;
} else if (mBlob) {
hb_blob_destroy(mBlob);
}
mBlob = nullptr;
}
// a hb_destroy_func for hb_blob_create
/* static */ void
gfxFontEntry::FontTableHashEntry::DeleteFontTableBlobData(void *aBlobData)
{
delete static_cast<FontTableBlobData*>(aBlobData);
}
hb_blob_t *
gfxFontEntry::FontTableHashEntry::GetBlob() const
{
return hb_blob_reference(mBlob);
}
bool
gfxFontEntry::GetExistingFontTable(uint32_t aTag, hb_blob_t **aBlob)
{
if (!mFontTableCache) {
// we do this here rather than on fontEntry construction
// because not all shapers will access the table cache at all
mFontTableCache = MakeUnique<nsTHashtable<FontTableHashEntry>>(8);
}
FontTableHashEntry *entry = mFontTableCache->GetEntry(aTag);
if (!entry) {
return false;
}
*aBlob = entry->GetBlob();
return true;
}
hb_blob_t *
gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag,
nsTArray<uint8_t>* aBuffer)
{
if (MOZ_UNLIKELY(!mFontTableCache)) {
// we do this here rather than on fontEntry construction
// because not all shapers will access the table cache at all
mFontTableCache = MakeUnique<nsTHashtable<FontTableHashEntry>>(8);
}
FontTableHashEntry *entry = mFontTableCache->PutEntry(aTag);
if (MOZ_UNLIKELY(!entry)) { // OOM
return nullptr;
}
if (!aBuffer) {
// ensure the entry is null
entry->Clear();
return nullptr;
}
return entry->ShareTableAndGetBlob(Move(*aBuffer), mFontTableCache.get());
}
already_AddRefed<gfxCharacterMap>
gfxFontEntry::GetCMAPFromFontInfo(FontInfoData *aFontInfoData,
uint32_t& aUVSOffset)
{
if (!aFontInfoData || !aFontInfoData->mLoadCmaps) {
return nullptr;
}
return aFontInfoData->GetCMAP(mName, aUVSOffset);
}
hb_blob_t *
gfxFontEntry::GetFontTable(uint32_t aTag)
{
hb_blob_t *blob;
if (GetExistingFontTable(aTag, &blob)) {
return blob;
}
nsTArray<uint8_t> buffer;
bool haveTable = NS_SUCCEEDED(CopyFontTable(aTag, buffer));
return ShareFontTableAndGetBlob(aTag, haveTable ? &buffer : nullptr);
}
// callback for HarfBuzz to get a font table (in hb_blob_t form)
// from the font entry (passed as aUserData)
/*static*/ hb_blob_t *
gfxFontEntry::HBGetTable(hb_face_t *face, uint32_t aTag, void *aUserData)
{
gfxFontEntry *fontEntry = static_cast<gfxFontEntry*>(aUserData);
// bug 589682 - ignore the GDEF table in buggy fonts (applies to
// Italic and BoldItalic faces of Times New Roman)
if (aTag == TRUETYPE_TAG('G','D','E','F') &&
fontEntry->IgnoreGDEF()) {
return nullptr;
}
// bug 721719 - ignore the GSUB table in buggy fonts (applies to Roboto,
// at least on some Android ICS devices; set in gfxFT2FontList.cpp)
if (aTag == TRUETYPE_TAG('G','S','U','B') &&
fontEntry->IgnoreGSUB()) {
return nullptr;
}
return fontEntry->GetFontTable(aTag);
}
/*static*/ void
gfxFontEntry::HBFaceDeletedCallback(void *aUserData)
{
gfxFontEntry *fe = static_cast<gfxFontEntry*>(aUserData);
fe->ForgetHBFace();
}
void
gfxFontEntry::ForgetHBFace()
{
mHBFace = nullptr;
}
hb_face_t*
gfxFontEntry::GetHBFace()
{
if (!mHBFace) {
mHBFace = hb_face_create_for_tables(HBGetTable, this,
HBFaceDeletedCallback);
return mHBFace;
}
return hb_face_reference(mHBFace);
}
/*static*/ const void*
gfxFontEntry::GrGetTable(const void *aAppFaceHandle, unsigned int aName,
size_t *aLen)
{
gfxFontEntry *fontEntry =
static_cast<gfxFontEntry*>(const_cast<void*>(aAppFaceHandle));
hb_blob_t *blob = fontEntry->GetFontTable(aName);
if (blob) {
unsigned int blobLength;
const void *tableData = hb_blob_get_data(blob, &blobLength);
fontEntry->mGrTableMap->Put(tableData, blob);
*aLen = blobLength;
return tableData;
}
*aLen = 0;
return nullptr;
}
/*static*/ void
gfxFontEntry::GrReleaseTable(const void *aAppFaceHandle,
const void *aTableBuffer)
{
gfxFontEntry *fontEntry =
static_cast<gfxFontEntry*>(const_cast<void*>(aAppFaceHandle));
void* value;
if (fontEntry->mGrTableMap->Remove(aTableBuffer, &value)) {
hb_blob_destroy(static_cast<hb_blob_t*>(value));
}
}
gr_face*
gfxFontEntry::GetGrFace()
{
if (!mGrFaceInitialized) {
gr_face_ops faceOps = {
sizeof(gr_face_ops),
GrGetTable,
GrReleaseTable
};
mGrTableMap = new nsDataHashtable<nsPtrHashKey<const void>,void*>;
mGrFace = gr_make_face_with_ops(this, &faceOps, gr_face_default);
mGrFaceInitialized = true;
}
++mGrFaceRefCnt;
return mGrFace;
}
void
gfxFontEntry::ReleaseGrFace(gr_face *aFace)
{
MOZ_ASSERT(aFace == mGrFace); // sanity-check
MOZ_ASSERT(mGrFaceRefCnt > 0);
if (--mGrFaceRefCnt == 0) {
gr_face_destroy(mGrFace);
mGrFace = nullptr;
mGrFaceInitialized = false;
delete mGrTableMap;
mGrTableMap = nullptr;
}
}
void
gfxFontEntry::DisconnectSVG()
{
if (mSVGInitialized && mSVGGlyphs) {
mSVGGlyphs = nullptr;
mSVGInitialized = false;
}
}
bool
gfxFontEntry::HasFontTable(uint32_t aTableTag)
{
AutoTable table(this, aTableTag);
return table && hb_blob_get_length(table) > 0;
}
void
gfxFontEntry::CheckForGraphiteTables()
{
mHasGraphiteTables = HasFontTable(TRUETYPE_TAG('S','i','l','f'));
}
bool
gfxFontEntry::HasGraphiteSpaceContextuals()
{
if (!mGraphiteSpaceContextualsInitialized) {
gr_face* face = GetGrFace();
if (face) {
const gr_faceinfo* faceInfo = gr_face_info(face, 0);
mHasGraphiteSpaceContextuals =
faceInfo->space_contextuals != gr_faceinfo::gr_space_none;
}
ReleaseGrFace(face); // always balance GetGrFace, even if face is null
mGraphiteSpaceContextualsInitialized = true;
}
return mHasGraphiteSpaceContextuals;
}
#define FEATURE_SCRIPT_MASK 0x000000ff // script index replaces low byte of tag
static_assert(int(Script::NUM_SCRIPT_CODES) <= FEATURE_SCRIPT_MASK, "Too many script codes");
// high-order three bytes of tag with script in low-order byte
#define SCRIPT_FEATURE(s,tag) (((~FEATURE_SCRIPT_MASK) & (tag)) | \
((FEATURE_SCRIPT_MASK) & static_cast<uint32_t>(s)))
bool
gfxFontEntry::SupportsOpenTypeFeature(Script aScript, uint32_t aFeatureTag)
{
if (!mSupportedFeatures) {
mSupportedFeatures = MakeUnique<nsDataHashtable<nsUint32HashKey,bool>>();
}
// note: high-order three bytes *must* be unique for each feature
// listed below (see SCRIPT_FEATURE macro def'n)
NS_ASSERTION(aFeatureTag == HB_TAG('s','m','c','p') ||
aFeatureTag == HB_TAG('c','2','s','c') ||
aFeatureTag == HB_TAG('p','c','a','p') ||
aFeatureTag == HB_TAG('c','2','p','c') ||
aFeatureTag == HB_TAG('s','u','p','s') ||
aFeatureTag == HB_TAG('s','u','b','s') ||
aFeatureTag == HB_TAG('v','e','r','t'),
"use of unknown feature tag");
// note: graphite feature support uses the last script index
NS_ASSERTION(int(aScript) < FEATURE_SCRIPT_MASK - 1,
"need to bump the size of the feature shift");
uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
bool result;
if (mSupportedFeatures->Get(scriptFeature, &result)) {
return result;
}
result = false;
hb_face_t *face = GetHBFace();
if (hb_ot_layout_has_substitution(face)) {
hb_script_t hbScript =
gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript);
// Get the OpenType tag(s) that match this script code
hb_tag_t scriptTags[4] = {
HB_TAG_NONE,
HB_TAG_NONE,
HB_TAG_NONE,
HB_TAG_NONE
};
hb_ot_tags_from_script(hbScript, &scriptTags[0], &scriptTags[1]);
// Replace the first remaining NONE with DEFAULT
hb_tag_t* scriptTag = &scriptTags[0];
while (*scriptTag != HB_TAG_NONE) {
++scriptTag;
}
*scriptTag = HB_OT_TAG_DEFAULT_SCRIPT;
// Now check for 'smcp' under the first of those scripts that is present
const hb_tag_t kGSUB = HB_TAG('G','S','U','B');
scriptTag = &scriptTags[0];
while (*scriptTag != HB_TAG_NONE) {
unsigned int scriptIndex;
if (hb_ot_layout_table_find_script(face, kGSUB, *scriptTag,
&scriptIndex)) {
if (hb_ot_layout_language_find_feature(face, kGSUB,
scriptIndex,
HB_OT_LAYOUT_DEFAULT_LANGUAGE_INDEX,
aFeatureTag, nullptr)) {
result = true;
}
break;
}
++scriptTag;
}
}
hb_face_destroy(face);
mSupportedFeatures->Put(scriptFeature, result);
return result;
}
const hb_set_t*
gfxFontEntry::InputsForOpenTypeFeature(Script aScript, uint32_t aFeatureTag)
{
if (!mFeatureInputs) {
mFeatureInputs = MakeUnique<nsDataHashtable<nsUint32HashKey,hb_set_t*>>();
}
NS_ASSERTION(aFeatureTag == HB_TAG('s','u','p','s') ||
aFeatureTag == HB_TAG('s','u','b','s'),
"use of unknown feature tag");
uint32_t scriptFeature = SCRIPT_FEATURE(aScript, aFeatureTag);
hb_set_t *inputGlyphs;
if (mFeatureInputs->Get(scriptFeature, &inputGlyphs)) {
return inputGlyphs;
}
inputGlyphs = hb_set_create();
hb_face_t *face = GetHBFace();
if (hb_ot_layout_has_substitution(face)) {
hb_script_t hbScript =
gfxHarfBuzzShaper::GetHBScriptUsedForShaping(aScript);
// Get the OpenType tag(s) that match this script code
hb_tag_t scriptTags[4] = {
HB_TAG_NONE,
HB_TAG_NONE,
HB_TAG_NONE,
HB_TAG_NONE
};
hb_ot_tags_from_script(hbScript, &scriptTags[0], &scriptTags[1]);
// Replace the first remaining NONE with DEFAULT
hb_tag_t* scriptTag = &scriptTags[0];
while (*scriptTag != HB_TAG_NONE) {
++scriptTag;
}
*scriptTag = HB_OT_TAG_DEFAULT_SCRIPT;
const hb_tag_t kGSUB = HB_TAG('G','S','U','B');
hb_tag_t features[2] = { aFeatureTag, HB_TAG_NONE };
hb_set_t *featurelookups = hb_set_create();
hb_ot_layout_collect_lookups(face, kGSUB, scriptTags, nullptr,
features, featurelookups);
hb_codepoint_t index = -1;
while (hb_set_next(featurelookups, &index)) {
hb_ot_layout_lookup_collect_glyphs(face, kGSUB, index,
nullptr, inputGlyphs,
nullptr, nullptr);
}
hb_set_destroy(featurelookups);
}
hb_face_destroy(face);
mFeatureInputs->Put(scriptFeature, inputGlyphs);
return inputGlyphs;
}
bool
gfxFontEntry::SupportsGraphiteFeature(uint32_t aFeatureTag)
{
if (!mSupportedFeatures) {
mSupportedFeatures = MakeUnique<nsDataHashtable<nsUint32HashKey,bool>>();
}
// note: high-order three bytes *must* be unique for each feature
// listed below (see SCRIPT_FEATURE macro def'n)
NS_ASSERTION(aFeatureTag == HB_TAG('s','m','c','p') ||
aFeatureTag == HB_TAG('c','2','s','c') ||
aFeatureTag == HB_TAG('p','c','a','p') ||
aFeatureTag == HB_TAG('c','2','p','c') ||
aFeatureTag == HB_TAG('s','u','p','s') ||
aFeatureTag == HB_TAG('s','u','b','s'),
"use of unknown feature tag");
// graphite feature check uses the last script slot
uint32_t scriptFeature = SCRIPT_FEATURE(FEATURE_SCRIPT_MASK, aFeatureTag);
bool result;
if (mSupportedFeatures->Get(scriptFeature, &result)) {
return result;
}
gr_face* face = GetGrFace();
result = face ? gr_face_find_fref(face, aFeatureTag) != nullptr : false;
ReleaseGrFace(face);
mSupportedFeatures->Put(scriptFeature, result);
return result;
}
bool
gfxFontEntry::GetColorLayersInfo(uint32_t aGlyphId,
const mozilla::gfx::Color& aDefaultColor,
nsTArray<uint16_t>& aLayerGlyphs,
nsTArray<mozilla::gfx::Color>& aLayerColors)
{
return gfxFontUtils::GetColorGlyphLayers(mCOLR,
mCPAL,
aGlyphId,
aDefaultColor,
aLayerGlyphs,
aLayerColors);
}
size_t
gfxFontEntry::FontTableHashEntry::SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
{
size_t n = 0;
if (mBlob) {
n += aMallocSizeOf(mBlob);
}
if (mSharedBlobData) {
n += mSharedBlobData->SizeOfIncludingThis(aMallocSizeOf);
}
return n;
}
void
gfxFontEntry::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
FontListSizes* aSizes) const
{
aSizes->mFontListSize += mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
// cmaps are shared so only non-shared cmaps are included here
if (mCharacterMap && mCharacterMap->mBuildOnTheFly) {
aSizes->mCharMapsSize +=
mCharacterMap->SizeOfIncludingThis(aMallocSizeOf);
}
if (mFontTableCache) {
aSizes->mFontTableCacheSize +=
mFontTableCache->SizeOfIncludingThis(aMallocSizeOf);
}
// If the font has UVS data, we count that as part of the character map.
if (mUVSData) {
aSizes->mCharMapsSize += aMallocSizeOf(mUVSData.get());
}
// The following, if present, are essentially cached forms of font table
// data, so we'll accumulate them together with the basic table cache.
if (mUserFontData) {
aSizes->mFontTableCacheSize +=
mUserFontData->SizeOfIncludingThis(aMallocSizeOf);
}
if (mSVGGlyphs) {
aSizes->mFontTableCacheSize +=
mSVGGlyphs->SizeOfIncludingThis(aMallocSizeOf);
}
if (mSupportedFeatures) {
aSizes->mFontTableCacheSize +=
mSupportedFeatures->ShallowSizeOfIncludingThis(aMallocSizeOf);
}
if (mFeatureInputs) {
aSizes->mFontTableCacheSize +=
mFeatureInputs->ShallowSizeOfIncludingThis(aMallocSizeOf);
for (auto iter = mFeatureInputs->ConstIter(); !iter.Done();
iter.Next()) {
// There's no API to get the real size of an hb_set, so we'll use
// an approximation based on knowledge of the implementation.
aSizes->mFontTableCacheSize += 8192; // vector of 64K bits
}
}
// We don't include the size of mCOLR/mCPAL here, because (depending on the
// font backend implementation) they will either wrap blocks of data owned
// by the system (and potentially shared), or tables that are in our font
// table cache and therefore already counted.
}
void
gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
FontListSizes* aSizes) const
{
aSizes->mFontListSize += aMallocSizeOf(this);
AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
}
// This is used to report the size of an individual downloaded font in the
// user font cache. (Fonts that are part of the platform font list accumulate
// their sizes to the font list's reporter using the AddSizeOf... methods
// above.)
size_t
gfxFontEntry::ComputedSizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
{
FontListSizes s = { 0 };
AddSizeOfExcludingThis(aMallocSizeOf, &s);
// When reporting memory used for the main platform font list,
// where we're typically summing the totals for a few hundred font faces,
// we report the fields of FontListSizes separately.
// But for downloaded user fonts, the actual resource data (added below)
// will dominate, and the minor overhead of these pieces isn't worth
// splitting out for an individual font.
size_t result = s.mFontListSize + s.mFontTableCacheSize + s.mCharMapsSize;
if (mIsDataUserFont) {
MOZ_ASSERT(mComputedSizeOfUserFont > 0, "user font with no data?");
result += mComputedSizeOfUserFont;
}
return result;
}
//////////////////////////////////////////////////////////////////////////////
//
// class gfxFontFamily
//
//////////////////////////////////////////////////////////////////////////////
// we consider faces with mStandardFace == true to be "less than" those with false,
// because during style matching, earlier entries are tried first
class FontEntryStandardFaceComparator {
public:
bool Equals(const RefPtr<gfxFontEntry>& a, const RefPtr<gfxFontEntry>& b) const {
return a->mStandardFace == b->mStandardFace;
}
bool LessThan(const RefPtr<gfxFontEntry>& a, const RefPtr<gfxFontEntry>& b) const {
return (a->mStandardFace == true && b->mStandardFace == false);
}
};
void
gfxFontFamily::SortAvailableFonts()
{
mAvailableFonts.Sort(FontEntryStandardFaceComparator());
}
bool
gfxFontFamily::HasOtherFamilyNames()
{
// need to read in other family names to determine this
if (!mOtherFamilyNamesInitialized) {
ReadOtherFamilyNames(gfxPlatformFontList::PlatformFontList()); // sets mHasOtherFamilyNames
}
return mHasOtherFamilyNames;
}
gfxFontEntry*
gfxFontFamily::FindFontForStyle(const gfxFontStyle& aFontStyle,
bool& aNeedsSyntheticBold,
bool aIgnoreSizeTolerance)
{
AutoTArray<gfxFontEntry*,4> matched;
FindAllFontsForStyle(aFontStyle, matched, aNeedsSyntheticBold,
aIgnoreSizeTolerance);
if (!matched.IsEmpty()) {
return matched[0];
}
return nullptr;
}
#define STYLE_SHIFT 2 // number of bits to contain style distance
// style distance ==> [0,2]
static inline uint32_t
StyleDistance(uint32_t aFontStyle, uint32_t aTargetStyle)
{
if (aFontStyle == aTargetStyle) {
return 0; // styles match exactly ==> 0
}
if (aFontStyle == NS_FONT_STYLE_NORMAL ||
aTargetStyle == NS_FONT_STYLE_NORMAL) {
return 2; // one is normal (but not the other) ==> 2
}
return 1; // neither is normal; must be italic vs oblique ==> 1
}
#define REVERSE_STRETCH_DISTANCE 5
// stretch distance ==> [0,13]
static inline uint32_t
StretchDistance(int16_t aFontStretch, int16_t aTargetStretch)
{
int32_t distance = 0;
if (aTargetStretch != aFontStretch) {
// stretch values are in the range -4 .. +4
// if aTargetStretch is positive, we prefer more-positive values;
// if zero or negative, prefer more-negative
if (aTargetStretch > 0) {
distance = (aFontStretch - aTargetStretch);
} else {
distance = (aTargetStretch - aFontStretch);
}
// if the computed "distance" here is negative, it means that
// aFontEntry lies in the "non-preferred" direction from aTargetStretch,
// so we treat that as larger than any preferred-direction distance
// (max possible is 4) by adding an extra 5 to the absolute value
if (distance < 0) {
distance = -distance + REVERSE_STRETCH_DISTANCE;
}
}
return uint32_t(distance);
}
// CSS currently limits font weights to multiples of 100 but the weight
// matching code below does not assume this.
//
// Calculate weight distance with values in the range (0..1000). In general,
// heavier weights match towards even heavier weights while lighter weights
// match towards even lighter weights. Target weight values in the range
// [400..500] are special, since they will first match up to 500, then down
// towards 0, then up again towards 999.
//
// Example: with target 600 and font weight 800, distance will be 200. With
// target 300 and font weight 600, distance will be 900, since heavier
// weights are farther away than lighter weights. If the target is 5 and the
// font weight 995, the distance would be 1590 for the same reason.
#define REVERSE_WEIGHT_DISTANCE 600
#define WEIGHT_SHIFT 11 // number of bits to contain weight distance
// weight distance ==> [0,1598]
static inline uint32_t
WeightDistance(uint32_t aFontWeight, uint32_t aTargetWeight)
{
// Compute a measure of the "distance" between the requested
// weight and the given fontEntry
int32_t distance = 0, addedDistance = 0;
if (aTargetWeight != aFontWeight) {
if (aTargetWeight > 500) {
distance = aFontWeight - aTargetWeight;
} else if (aTargetWeight < 400) {
distance = aTargetWeight - aFontWeight;
} else {
// special case - target is between 400 and 500
// font weights between 400 and 500 are close
if (aFontWeight >= 400 && aFontWeight <= 500) {
if (aFontWeight < aTargetWeight) {
distance = 500 - aFontWeight;
} else {
distance = aFontWeight - aTargetWeight;
}
} else {
// font weights outside use rule for target weights < 400 with
// added distance to separate from font weights in
// the [400..500] range
distance = aTargetWeight - aFontWeight;
addedDistance = 100;
}
}
if (distance < 0) {
distance = -distance + REVERSE_WEIGHT_DISTANCE;
}
distance += addedDistance;
}
return uint32_t(distance);
}
#define MAX_DISTANCE 0xffffffff
static inline uint32_t
WeightStyleStretchDistance(gfxFontEntry* aFontEntry,
const gfxFontStyle& aTargetStyle)
{
// weight/style/stretch priority: stretch >> style >> weight
uint32_t stretchDist =
StretchDistance(aFontEntry->mStretch, aTargetStyle.stretch);
uint32_t styleDist = StyleDistance(aFontEntry->mStyle, aTargetStyle.style);
uint32_t weightDist =
WeightDistance(aFontEntry->Weight(), aTargetStyle.weight);
NS_ASSERTION(weightDist < (1 << WEIGHT_SHIFT), "weight value out of bounds");
NS_ASSERTION(styleDist < (1 << STYLE_SHIFT), "slope value out of bounds");
return (stretchDist << (STYLE_SHIFT + WEIGHT_SHIFT)) |
(styleDist << WEIGHT_SHIFT) |
weightDist;
}
void
gfxFontFamily::FindAllFontsForStyle(const gfxFontStyle& aFontStyle,
nsTArray<gfxFontEntry*>& aFontEntryList,
bool& aNeedsSyntheticBold,
bool aIgnoreSizeTolerance)
{
if (!mHasStyles) {
FindStyleVariations(); // collect faces for the family, if not already done
}
NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!");
NS_ASSERTION(aFontEntryList.IsEmpty(), "non-empty fontlist passed in");
aNeedsSyntheticBold = false;
int8_t baseWeight = aFontStyle.ComputeWeight();
bool wantBold = baseWeight >= 6;
gfxFontEntry *fe = nullptr;
// If the family has only one face, we simply return it; no further
// checking needed
uint32_t count = mAvailableFonts.Length();
if (count == 1) {
fe = mAvailableFonts[0];
aNeedsSyntheticBold =
wantBold && !fe->IsBold() && aFontStyle.allowSyntheticWeight;
aFontEntryList.AppendElement(fe);
return;
}
// Most families are "simple", having just Regular/Bold/Italic/BoldItalic,
// or some subset of these. In this case, we have exactly 4 entries in mAvailableFonts,
// stored in the above order; note that some of the entries may be nullptr.
// We can then pick the required entry based on whether the request is for
// bold or non-bold, italic or non-italic, without running the more complex
// matching algorithm used for larger families with many weights and/or widths.
if (mIsSimpleFamily) {
// Family has no more than the "standard" 4 faces, at fixed indexes;
// calculate which one we want.
// Note that we cannot simply return it as not all 4 faces are necessarily present.
bool wantItalic = (aFontStyle.style != NS_FONT_STYLE_NORMAL);
uint8_t faceIndex = (wantItalic ? kItalicMask : 0) |
(wantBold ? kBoldMask : 0);
// if the desired style is available, return it directly
fe = mAvailableFonts[faceIndex];
if (fe) {
// no need to set aNeedsSyntheticBold here as we matched the boldness request
aFontEntryList.AppendElement(fe);
return;
}
// order to check fallback faces in a simple family, depending on requested style
static const uint8_t simpleFallbacks[4][3] = {
{ kBoldFaceIndex, kItalicFaceIndex, kBoldItalicFaceIndex }, // fallbacks for Regular
{ kRegularFaceIndex, kBoldItalicFaceIndex, kItalicFaceIndex },// Bold
{ kBoldItalicFaceIndex, kRegularFaceIndex, kBoldFaceIndex }, // Italic
{ kItalicFaceIndex, kBoldFaceIndex, kRegularFaceIndex } // BoldItalic
};
const uint8_t *order = simpleFallbacks[faceIndex];
for (uint8_t trial = 0; trial < 3; ++trial) {
// check remaining faces in order of preference to find the first that actually exists
fe = mAvailableFonts[order[trial]];
if (fe) {
aNeedsSyntheticBold =
wantBold && !fe->IsBold() &&
aFontStyle.allowSyntheticWeight;
aFontEntryList.AppendElement(fe);
return;
}
}
// this can't happen unless we have totally broken the font-list manager!
NS_NOTREACHED("no face found in simple font family!");
}
// Pick the font(s) that are closest to the desired weight, style, and
// stretch. Iterate over all fonts, measuring the weight/style distance.
// Because of unicode-range values, there may be more than one font for a
// given but the 99% use case is only a single font entry per
// weight/style/stretch distance value. To optimize this, only add entries
// to the matched font array when another entry already has the same
// weight/style/stretch distance and add the last matched font entry. For
// normal platform fonts with a single font entry for each
// weight/style/stretch combination, only the last matched font entry will
// be added.
uint32_t minDistance = MAX_DISTANCE;
gfxFontEntry* matched = nullptr;
// iterate in forward order so that faces like 'Bold' are matched before
// matching style distance faces such as 'Bold Outline' (see bug 1185812)
for (uint32_t i = 0; i < count; i++) {
fe = mAvailableFonts[i];
// weight/style/stretch priority: stretch >> style >> weight
uint32_t distance = WeightStyleStretchDistance(fe, aFontStyle);
if (distance < minDistance) {
matched = fe;
if (!aFontEntryList.IsEmpty()) {
aFontEntryList.Clear();
}
minDistance = distance;
} else if (distance == minDistance) {
if (matched) {
aFontEntryList.AppendElement(matched);
}
matched = fe;
}
}
NS_ASSERTION(matched, "didn't match a font within a family");
if (matched) {
aFontEntryList.AppendElement(matched);
if (!matched->IsBold() && aFontStyle.weight >= 600 &&
aFontStyle.allowSyntheticWeight) {
aNeedsSyntheticBold = true;
}
}
}
void
gfxFontFamily::CheckForSimpleFamily()
{
// already checked this family
if (mIsSimpleFamily) {
return;
}
uint32_t count = mAvailableFonts.Length();
if (count > 4 || count == 0) {
return; // can't be "simple" if there are >4 faces;
// if none then the family is unusable anyway
}
if (count == 1) {
mIsSimpleFamily = true;
return;
}
int16_t firstStretch = mAvailableFonts[0]->Stretch();
gfxFontEntry *faces[4] = { 0 };
for (uint8_t i = 0; i < count; ++i) {
gfxFontEntry *fe = mAvailableFonts[i];
if (fe->Stretch() != firstStretch || fe->IsOblique()) {
// simple families don't have varying font-stretch or oblique
return;
}
uint8_t faceIndex = (fe->IsItalic() ? kItalicMask : 0) |
(fe->Weight() >= 600 ? kBoldMask : 0);
if (faces[faceIndex]) {
return; // two faces resolve to the same slot; family isn't "simple"
}
faces[faceIndex] = fe;
}
// we have successfully slotted the available faces into the standard
// 4-face framework
mAvailableFonts.SetLength(4);
for (uint8_t i = 0; i < 4; ++i) {
if (mAvailableFonts[i].get() != faces[i]) {
mAvailableFonts[i].swap(faces[i]);
}
}
mIsSimpleFamily = true;
}
#ifdef DEBUG
bool
gfxFontFamily::ContainsFace(gfxFontEntry* aFontEntry) {
uint32_t i, numFonts = mAvailableFonts.Length();
for (i = 0; i < numFonts; i++) {
if (mAvailableFonts[i] == aFontEntry) {
return true;
}
// userfonts contain the actual real font entry
if (mAvailableFonts[i] && mAvailableFonts[i]->mIsUserFontContainer) {
gfxUserFontEntry* ufe =
static_cast<gfxUserFontEntry*>(mAvailableFonts[i].get());
if (ufe->GetPlatformFontEntry() == aFontEntry) {
return true;
}
}
}
return false;
}
#endif
void gfxFontFamily::LocalizedName(nsAString& aLocalizedName)
{
// just return the primary name; subclasses should override
aLocalizedName = mName;
}
// metric for how close a given font matches a style
static int32_t
CalcStyleMatch(gfxFontEntry *aFontEntry, const gfxFontStyle *aStyle)
{
int32_t rank = 0;
if (aStyle) {
// italics
bool wantUpright = (aStyle->style == NS_FONT_STYLE_NORMAL);
if (aFontEntry->IsUpright() == wantUpright) {
rank += 10;
}
// measure of closeness of weight to the desired value
rank += 9 - DeprecatedAbs(aFontEntry->Weight() / 100 - aStyle->ComputeWeight());
} else {
// if no font to match, prefer non-bold, non-italic fonts
if (aFontEntry->IsUpright()) {
rank += 3;
}
if (!aFontEntry->IsBold()) {
rank += 2;
}
}
return rank;
}
#define RANK_MATCHED_CMAP 20
void
gfxFontFamily::FindFontForChar(GlobalFontMatch *aMatchData)
{
if (mFamilyCharacterMapInitialized && !TestCharacterMap(aMatchData->mCh)) {
// none of the faces in the family support the required char,
// so bail out immediately
return;
}
bool needsBold;
gfxFontEntry *fe =
FindFontForStyle(aMatchData->mStyle ? *aMatchData->mStyle
: gfxFontStyle(),
needsBold, true);
if (fe && !fe->SkipDuringSystemFallback()) {
int32_t rank = 0;
if (fe->HasCharacter(aMatchData->mCh)) {
rank += RANK_MATCHED_CMAP;
aMatchData->mCount++;
LogModule* log = gfxPlatform::GetLog(eGfxLog_textrun);
if (MOZ_UNLIKELY(MOZ_LOG_TEST(log, LogLevel::Debug))) {
uint32_t unicodeRange = FindCharUnicodeRange(aMatchData->mCh);
Script script = GetScriptCode(aMatchData->mCh);
MOZ_LOG(log, LogLevel::Debug,\
("(textrun-systemfallback-fonts) char: u+%6.6x "
"unicode-range: %d script: %d match: [%s]\n",
aMatchData->mCh,
unicodeRange, int(script),
NS_ConvertUTF16toUTF8(fe->Name()).get()));
}
// omitting from original windows code -- family name, lang group, pitch
// not available in current FontEntry implementation
rank += CalcStyleMatch(fe, aMatchData->mStyle);
} else if (!fe->IsNormalStyle()) {
// If style/weight/stretch was not Normal, see if we can
// fall back to a next-best face (e.g. Arial Black -> Bold,
// or Arial Narrow -> Regular).
GlobalFontMatch data(aMatchData->mCh, aMatchData->mStyle);
SearchAllFontsForChar(&data);
if (data.mMatchRank >= RANK_MATCHED_CMAP) {
fe = data.mBestMatch;
rank = data.mMatchRank;
} else {
return;
}
} else {
return;
}
aMatchData->mCmapsTested++;
// xxx - add whether AAT font with morphing info for specific lang groups
if (rank > aMatchData->mMatchRank
|| (rank == aMatchData->mMatchRank &&
Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0))
{
aMatchData->mBestMatch = fe;
aMatchData->mMatchedFamily = this;
aMatchData->mMatchRank = rank;
}
}
}
void
gfxFontFamily::SearchAllFontsForChar(GlobalFontMatch *aMatchData)
{
uint32_t i, numFonts = mAvailableFonts.Length();
for (i = 0; i < numFonts; i++) {
gfxFontEntry *fe = mAvailableFonts[i];
if (fe && fe->HasCharacter(aMatchData->mCh)) {
int32_t rank = RANK_MATCHED_CMAP;
rank += CalcStyleMatch(fe, aMatchData->mStyle);
if (rank > aMatchData->mMatchRank
|| (rank == aMatchData->mMatchRank &&
Compare(fe->Name(), aMatchData->mBestMatch->Name()) > 0))
{
aMatchData->mBestMatch = fe;
aMatchData->mMatchedFamily = this;
aMatchData->mMatchRank = rank;
}
}
}
}
/*virtual*/
gfxFontFamily::~gfxFontFamily()
{
// Should not be dropped by stylo
MOZ_ASSERT(NS_IsMainThread());
}
/*static*/ void
gfxFontFamily::ReadOtherFamilyNamesForFace(const nsAString& aFamilyName,
const char *aNameData,
uint32_t aDataLength,
nsTArray<nsString>& aOtherFamilyNames,
bool useFullName)
{
const gfxFontUtils::NameHeader *nameHeader =
reinterpret_cast<const gfxFontUtils::NameHeader*>(aNameData);
uint32_t nameCount = nameHeader->count;
if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) {
NS_WARNING("invalid font (name records)");
return;
}
const gfxFontUtils::NameRecord *nameRecord =
reinterpret_cast<const gfxFontUtils::NameRecord*>(aNameData + sizeof(gfxFontUtils::NameHeader));
uint32_t stringsBase = uint32_t(nameHeader->stringOffset);
for (uint32_t i = 0; i < nameCount; i++, nameRecord++) {
uint32_t nameLen = nameRecord->length;
uint32_t nameOff = nameRecord->offset; // offset from base of string storage
if (stringsBase + nameOff + nameLen > aDataLength) {
NS_WARNING("invalid font (name table strings)");
return;
}
uint16_t nameID = nameRecord->nameID;
if ((useFullName && nameID == gfxFontUtils::NAME_ID_FULL) ||
(!useFullName && (nameID == gfxFontUtils::NAME_ID_FAMILY ||
nameID == gfxFontUtils::NAME_ID_PREFERRED_FAMILY))) {
nsAutoString otherFamilyName;
bool ok = gfxFontUtils::DecodeFontName(aNameData + stringsBase + nameOff,
nameLen,
uint32_t(nameRecord->platformID),
uint32_t(nameRecord->encodingID),
uint32_t(nameRecord->languageID),
otherFamilyName);
// add if not same as canonical family name
if (ok && otherFamilyName != aFamilyName) {
aOtherFamilyNames.AppendElement(otherFamilyName);
}
}
}
}
// returns true if other names were found, false otherwise
bool
gfxFontFamily::ReadOtherFamilyNamesForFace(gfxPlatformFontList *aPlatformFontList,
hb_blob_t *aNameTable,
bool useFullName)
{
uint32_t dataLength;
const char *nameData = hb_blob_get_data(aNameTable, &dataLength);
AutoTArray<nsString,4> otherFamilyNames;
ReadOtherFamilyNamesForFace(mName, nameData, dataLength,
otherFamilyNames, useFullName);
uint32_t n = otherFamilyNames.Length();
for (uint32_t i = 0; i < n; i++) {
aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]);
}
return n != 0;
}
void
gfxFontFamily::ReadOtherFamilyNames(gfxPlatformFontList *aPlatformFontList)
{
if (mOtherFamilyNamesInitialized)
return;
mOtherFamilyNamesInitialized = true;
FindStyleVariations();
// read in other family names for the first face in the list
uint32_t i, numFonts = mAvailableFonts.Length();
const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e');
for (i = 0; i < numFonts; ++i) {
gfxFontEntry *fe = mAvailableFonts[i];
if (!fe) {
continue;
}
gfxFontEntry::AutoTable nameTable(fe, kNAME);
if (!nameTable) {
continue;
}
mHasOtherFamilyNames = ReadOtherFamilyNamesForFace(aPlatformFontList,
nameTable);
break;
}
// read in other names for the first face in the list with the assumption
// that if extra names don't exist in that face then they don't exist in
// other faces for the same font
if (!mHasOtherFamilyNames)
return;
// read in names for all faces, needed to catch cases where fonts have
// family names for individual weights (e.g. Hiragino Kaku Gothic Pro W6)
for ( ; i < numFonts; i++) {
gfxFontEntry *fe = mAvailableFonts[i];
if (!fe) {
continue;
}
gfxFontEntry::AutoTable nameTable(fe, kNAME);
if (!nameTable) {
continue;
}
ReadOtherFamilyNamesForFace(aPlatformFontList, nameTable);
}
}
static bool
LookForLegacyFamilyName(const nsAString& aCanonicalName,
const char* aNameData,
uint32_t aDataLength,
nsAString& aLegacyName /* outparam */)
{
const gfxFontUtils::NameHeader* nameHeader =
reinterpret_cast<const gfxFontUtils::NameHeader*>(aNameData);
uint32_t nameCount = nameHeader->count;
if (nameCount * sizeof(gfxFontUtils::NameRecord) > aDataLength) {
NS_WARNING("invalid font (name records)");
return false;
}
const gfxFontUtils::NameRecord* nameRecord =
reinterpret_cast<const gfxFontUtils::NameRecord*>
(aNameData + sizeof(gfxFontUtils::NameHeader));
uint32_t stringsBase = uint32_t(nameHeader->stringOffset);
for (uint32_t i = 0; i < nameCount; i++, nameRecord++) {
uint32_t nameLen = nameRecord->length;
uint32_t nameOff = nameRecord->offset;
if (stringsBase + nameOff + nameLen > aDataLength) {
NS_WARNING("invalid font (name table strings)");
return false;
}
if (uint16_t(nameRecord->nameID) == gfxFontUtils::NAME_ID_FAMILY) {
bool ok =
gfxFontUtils::DecodeFontName(aNameData + stringsBase + nameOff,
nameLen,
uint32_t(nameRecord->platformID),
uint32_t(nameRecord->encodingID),
uint32_t(nameRecord->languageID),
aLegacyName);
// it's only a legacy name if it differs from the canonical name
if (ok && aLegacyName != aCanonicalName) {
return true;
}
}
}
return false;
}
bool
gfxFontFamily::CheckForLegacyFamilyNames(gfxPlatformFontList* aFontList)
{
if (mCheckedForLegacyFamilyNames) {
// we already did this, so there's nothing more to add
return false;
}
mCheckedForLegacyFamilyNames = true;
bool added = false;
const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e');
for (auto& fe : mAvailableFonts) {
if (!fe) {
continue;
}
gfxFontEntry::AutoTable nameTable(fe, kNAME);
if (!nameTable) {
continue;
}
nsAutoString legacyName;
uint32_t dataLength;
const char* nameData = hb_blob_get_data(nameTable, &dataLength);
if (LookForLegacyFamilyName(Name(), nameData, dataLength,
legacyName)) {
if (aFontList->AddWithLegacyFamilyName(legacyName, fe)) {
added = true;
}
}
}
return added;
}
void
gfxFontFamily::ReadFaceNames(gfxPlatformFontList *aPlatformFontList,
bool aNeedFullnamePostscriptNames,
FontInfoData *aFontInfoData)
{
// if all needed names have already been read, skip
if (mOtherFamilyNamesInitialized &&
(mFaceNamesInitialized || !aNeedFullnamePostscriptNames))
return;
bool asyncFontLoaderDisabled = false;
if (!mOtherFamilyNamesInitialized &&
aFontInfoData &&
aFontInfoData->mLoadOtherNames &&
!asyncFontLoaderDisabled)
{
AutoTArray<nsString,4> otherFamilyNames;
bool foundOtherNames =
aFontInfoData->GetOtherFamilyNames(mName, otherFamilyNames);
if (foundOtherNames) {
uint32_t i, n = otherFamilyNames.Length();
for (i = 0; i < n; i++) {
aPlatformFontList->AddOtherFamilyName(this, otherFamilyNames[i]);
}
}
mOtherFamilyNamesInitialized = true;
}
// if all needed data has been initialized, return
if (mOtherFamilyNamesInitialized &&
(mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
return;
}
FindStyleVariations(aFontInfoData);
// check again, as style enumeration code may have loaded names
if (mOtherFamilyNamesInitialized &&
(mFaceNamesInitialized || !aNeedFullnamePostscriptNames)) {
return;
}
uint32_t i, numFonts = mAvailableFonts.Length();
const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e');
bool firstTime = true, readAllFaces = false;
for (i = 0; i < numFonts; ++i) {
gfxFontEntry *fe = mAvailableFonts[i];
if (!fe) {
continue;
}
nsAutoString fullname, psname;
bool foundFaceNames = false;
if (!mFaceNamesInitialized &&
aNeedFullnamePostscriptNames &&
aFontInfoData &&
aFontInfoData->mLoadFaceNames) {
aFontInfoData->GetFaceNames(fe->Name(), fullname, psname);
if (!fullname.IsEmpty()) {
aPlatformFontList->AddFullname(fe, fullname);
}
if (!psname.IsEmpty()) {
aPlatformFontList->AddPostscriptName(fe, psname);
}
foundFaceNames = true;
// found everything needed? skip to next font
if (mOtherFamilyNamesInitialized) {
continue;
}
}
// load directly from the name table
gfxFontEntry::AutoTable nameTable(fe, kNAME);
if (!nameTable) {
continue;
}
if (aNeedFullnamePostscriptNames && !foundFaceNames) {
if (gfxFontUtils::ReadCanonicalName(
nameTable, gfxFontUtils::NAME_ID_FULL, fullname) == NS_OK)
{
aPlatformFontList->AddFullname(fe, fullname);
}
if (gfxFontUtils::ReadCanonicalName(
nameTable, gfxFontUtils::NAME_ID_POSTSCRIPT, psname) == NS_OK)
{
aPlatformFontList->AddPostscriptName(fe, psname);
}
}
if (!mOtherFamilyNamesInitialized && (firstTime || readAllFaces)) {
bool foundOtherName = ReadOtherFamilyNamesForFace(aPlatformFontList,
nameTable);
// if the first face has a different name, scan all faces, otherwise
// assume the family doesn't have other names
if (firstTime && foundOtherName) {
mHasOtherFamilyNames = true;
readAllFaces = true;
}
firstTime = false;
}
// if not reading in any more names, skip other faces
if (!readAllFaces && !aNeedFullnamePostscriptNames) {
break;
}
}
mFaceNamesInitialized = true;
mOtherFamilyNamesInitialized = true;
}
gfxFontEntry*
gfxFontFamily::FindFont(const nsAString& aPostscriptName)
{
// find the font using a simple linear search
uint32_t numFonts = mAvailableFonts.Length();
for (uint32_t i = 0; i < numFonts; i++) {
gfxFontEntry *fe = mAvailableFonts[i].get();
if (fe && fe->Name() == aPostscriptName)
return fe;
}
return nullptr;
}
void
gfxFontFamily::ReadAllCMAPs(FontInfoData *aFontInfoData)
{
FindStyleVariations(aFontInfoData);
uint32_t i, numFonts = mAvailableFonts.Length();
for (i = 0; i < numFonts; i++) {
gfxFontEntry *fe = mAvailableFonts[i];
// don't try to load cmaps for downloadable fonts not yet loaded
if (!fe || fe->mIsUserFontContainer) {
continue;
}
fe->ReadCMAP(aFontInfoData);
mFamilyCharacterMap.Union(*(fe->mCharacterMap));
}
mFamilyCharacterMap.Compact();
mFamilyCharacterMapInitialized = true;
}
void
gfxFontFamily::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
FontListSizes* aSizes) const
{
aSizes->mFontListSize +=
mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
aSizes->mCharMapsSize +=
mFamilyCharacterMap.SizeOfExcludingThis(aMallocSizeOf);
aSizes->mFontListSize +=
mAvailableFonts.ShallowSizeOfExcludingThis(aMallocSizeOf);
for (uint32_t i = 0; i < mAvailableFonts.Length(); ++i) {
gfxFontEntry *fe = mAvailableFonts[i];
if (fe) {
fe->AddSizeOfIncludingThis(aMallocSizeOf, aSizes);
}
}
}
void
gfxFontFamily::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
FontListSizes* aSizes) const
{
aSizes->mFontListSize += aMallocSizeOf(this);
AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
}