mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
6220f98ec6
Backed out changeset 85486c4aa3d8 (bug 936964) Backed out changeset 25312eb71998 (bug 936964) Backed out changeset 6dbb8333960c (bug 936964) Backed out changeset da6465ad476f (bug 936964) Backed out changeset a87ffc992f38 (bug 936964) Backed out changeset 4ae3a61182db (bug 936964) Backed out changeset 34e9c3137804 (bug 936964) Backed out changeset fd1459e71585 (bug 936964) Backed out changeset 3e8a701d8bdc (bug 943660) Landed on a CLOSED TREE --HG-- rename : content/canvas/src/WebGLMemoryTracker.h => content/canvas/src/WebGLMemoryReporterWrapper.h
6923 lines
243 KiB
C++
6923 lines
243 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"
|
|
|
|
#ifdef MOZ_LOGGING
|
|
#define FORCE_PR_LOG /* Allow logging in the release build */
|
|
#endif
|
|
#include "prlog.h"
|
|
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "nsExpirationTracker.h"
|
|
#include "nsILanguageAtomService.h"
|
|
#include "nsITimer.h"
|
|
|
|
#include "gfxFont.h"
|
|
#include "gfxPlatform.h"
|
|
#include "nsGkAtoms.h"
|
|
|
|
#include "gfxTypes.h"
|
|
#include "gfxContext.h"
|
|
#include "gfxFontMissingGlyphs.h"
|
|
#include "gfxUserFontSet.h"
|
|
#include "gfxPlatformFontList.h"
|
|
#include "gfxScriptItemizer.h"
|
|
#include "nsUnicodeProperties.h"
|
|
#include "nsMathUtils.h"
|
|
#include "nsBidiUtils.h"
|
|
#include "nsUnicodeRange.h"
|
|
#include "nsStyleConsts.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 "gfxFontTest.h"
|
|
|
|
#include "harfbuzz/hb.h"
|
|
#include "harfbuzz/hb-ot.h"
|
|
#include "graphite2/Font.h"
|
|
|
|
#include "nsCRT.h"
|
|
#include "GeckoProfiler.h"
|
|
#include "gfxFontConstants.h"
|
|
|
|
#include <algorithm>
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::gfx;
|
|
using namespace mozilla::unicode;
|
|
using mozilla::services::GetObserverService;
|
|
|
|
gfxFontCache *gfxFontCache::gGlobalCache = nullptr;
|
|
|
|
static const PRUnichar kEllipsisChar[] = { 0x2026, 0x0 };
|
|
static const PRUnichar kASCIIPeriodsChar[] = { '.', '.', '.', 0x0 };
|
|
|
|
#ifdef DEBUG_roc
|
|
#define DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
#endif
|
|
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
static uint32_t gTextRunStorageHighWaterMark = 0;
|
|
static uint32_t gTextRunStorage = 0;
|
|
static uint32_t gFontCount = 0;
|
|
static uint32_t gGlyphExtentsCount = 0;
|
|
static uint32_t gGlyphExtentsWidthsTotalSize = 0;
|
|
static uint32_t gGlyphExtentsSetupEagerSimple = 0;
|
|
static uint32_t gGlyphExtentsSetupEagerTight = 0;
|
|
static uint32_t gGlyphExtentsSetupLazyTight = 0;
|
|
static uint32_t gGlyphExtentsSetupFallBackToTight = 0;
|
|
#endif
|
|
|
|
void
|
|
gfxCharacterMap::NotifyReleased()
|
|
{
|
|
gfxPlatformFontList *fontlist = gfxPlatformFontList::PlatformFontList();
|
|
if (mShared) {
|
|
fontlist->RemoveCmap(this);
|
|
}
|
|
delete this;
|
|
}
|
|
|
|
gfxFontEntry::gfxFontEntry() :
|
|
mItalic(false), mFixedPitch(false),
|
|
mIsProxy(false), mIsValid(true),
|
|
mIsBadUnderlineFont(false),
|
|
mIsUserFont(false),
|
|
mIsLocalUserFont(false),
|
|
mStandardFace(false),
|
|
mSymbolFont(false),
|
|
mIgnoreGDEF(false),
|
|
mIgnoreGSUB(false),
|
|
mSVGInitialized(false),
|
|
mHasSpaceFeaturesInitialized(false),
|
|
mHasSpaceFeatures(false),
|
|
mHasSpaceFeaturesKerning(false),
|
|
mHasSpaceFeaturesNonKerning(false),
|
|
mHasSpaceFeaturesSubDefault(false),
|
|
mCheckedForGraphiteTables(false),
|
|
mHasCmapTable(false),
|
|
mGrFaceInitialized(false),
|
|
mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
|
|
mUVSOffset(0), mUVSData(nullptr),
|
|
mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
|
|
mUnitsPerEm(0),
|
|
mHBFace(nullptr),
|
|
mGrFace(nullptr),
|
|
mGrFaceRefCnt(0)
|
|
{
|
|
}
|
|
|
|
gfxFontEntry::gfxFontEntry(const nsAString& aName, bool aIsStandardFace) :
|
|
mName(aName), mItalic(false), mFixedPitch(false),
|
|
mIsProxy(false), mIsValid(true),
|
|
mIsBadUnderlineFont(false), mIsUserFont(false),
|
|
mIsLocalUserFont(false), mStandardFace(aIsStandardFace),
|
|
mSymbolFont(false),
|
|
mIgnoreGDEF(false),
|
|
mIgnoreGSUB(false),
|
|
mSVGInitialized(false),
|
|
mHasSpaceFeaturesInitialized(false),
|
|
mHasSpaceFeatures(false),
|
|
mHasSpaceFeaturesKerning(false),
|
|
mHasSpaceFeaturesNonKerning(false),
|
|
mHasSpaceFeaturesSubDefault(false),
|
|
mCheckedForGraphiteTables(false),
|
|
mHasCmapTable(false),
|
|
mGrFaceInitialized(false),
|
|
mWeight(500), mStretch(NS_FONT_STRETCH_NORMAL),
|
|
mUVSOffset(0), mUVSData(nullptr),
|
|
mLanguageOverride(NO_FONT_LANGUAGE_OVERRIDE),
|
|
mUnitsPerEm(0),
|
|
mHBFace(nullptr),
|
|
mGrFace(nullptr),
|
|
mGrFaceRefCnt(0)
|
|
{
|
|
memset(&mHasSpaceFeaturesSub, 0, sizeof(mHasSpaceFeaturesSub));
|
|
}
|
|
|
|
gfxFontEntry::~gfxFontEntry()
|
|
{
|
|
// For downloaded fonts, we need to tell the user font cache that this
|
|
// entry is being deleted.
|
|
if (!mIsProxy && IsUserFont() && !IsLocalUserFont()) {
|
|
gfxUserFontSet::UserFontCache::ForgetFont(this);
|
|
}
|
|
|
|
// 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::IsSymbolFont()
|
|
{
|
|
return mSymbolFont;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 = uvsData;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
uint16_t gfxFontEntry::GetUVSGlyph(uint32_t aCh, uint32_t aVS)
|
|
{
|
|
InitializeUVSMap();
|
|
|
|
if (mUVSData) {
|
|
return gfxFontUtils::MapUVSToGlyphFormat14(mUVSData, aCh, aVS);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
nsresult gfxFontEntry::ReadCMAP()
|
|
{
|
|
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();
|
|
}
|
|
|
|
already_AddRefed<gfxFont>
|
|
gfxFontEntry::FindOrMakeFont(const gfxFontStyle *aStyle, bool aNeedsBold)
|
|
{
|
|
// the font entry name is the psname, not the family name
|
|
nsRefPtr<gfxFont> font = gfxFontCache::GetCache()->Lookup(this, aStyle);
|
|
|
|
if (!font) {
|
|
gfxFont *newFont = CreateFontInstance(aStyle, aNeedsBold);
|
|
if (!newFont)
|
|
return nullptr;
|
|
if (!newFont->Valid()) {
|
|
delete newFont;
|
|
return nullptr;
|
|
}
|
|
font = newFont;
|
|
gfxFontCache::GetCache()->AddNew(font);
|
|
}
|
|
return font.forget();
|
|
}
|
|
|
|
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(gfxContext *aContext, uint32_t aGlyphId,
|
|
gfxRect *aResult)
|
|
{
|
|
NS_ABORT_IF_FALSE(mSVGInitialized,
|
|
"SVG data has not yet been loaded. TryGetSVGData() first.");
|
|
NS_ABORT_IF_FALSE(mUnitsPerEm >= kMinUPEM && mUnitsPerEm <= kMaxUPEM,
|
|
"font has invalid unitsPerEm");
|
|
|
|
gfxContextAutoSaveRestore matrixRestore(aContext);
|
|
cairo_matrix_t fontMatrix;
|
|
cairo_get_font_matrix(aContext->GetCairo(), &fontMatrix);
|
|
|
|
gfxMatrix svgToAppSpace = *reinterpret_cast<gfxMatrix*>(&fontMatrix);
|
|
svgToAppSpace.Scale(1.0f / mUnitsPerEm, 1.0f / mUnitsPerEm);
|
|
|
|
return mSVGGlyphs->GetGlyphExtents(aGlyphId, svgToAppSpace, aResult);
|
|
}
|
|
|
|
bool
|
|
gfxFontEntry::RenderSVGGlyph(gfxContext *aContext, uint32_t aGlyphId,
|
|
int aDrawMode, gfxTextContextPaint *aContextPaint)
|
|
{
|
|
NS_ASSERTION(mSVGInitialized, "SVG data has not yet been loaded. TryGetSVGData() first.");
|
|
return mSVGGlyphs->RenderGlyph(aContext, aGlyphId, DrawMode(aDrawMode),
|
|
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 = new gfxSVGGlyphs(svgTable, this);
|
|
}
|
|
|
|
if (!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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* FontTableBlobData
|
|
*
|
|
* See FontTableHashEntry for the general strategy.
|
|
*/
|
|
|
|
class gfxFontEntry::FontTableBlobData {
|
|
public:
|
|
// Adopts the content of aBuffer.
|
|
FontTableBlobData(FallibleTArray<uint8_t>& aBuffer)
|
|
: mHashtable(nullptr), mHashKey(0)
|
|
{
|
|
MOZ_COUNT_CTOR(FontTableBlobData);
|
|
mTableData.SwapElements(aBuffer);
|
|
}
|
|
|
|
~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.SizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
|
|
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
private:
|
|
// The font table data block, owned (via adoption)
|
|
FallibleTArray<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(FallibleTArray<uint8_t>& aTable,
|
|
nsTHashtable<FontTableHashEntry> *aHashtable)
|
|
{
|
|
Clear();
|
|
// adopts elements of aTable
|
|
mSharedBlobData = new FontTableBlobData(aTable);
|
|
mBlob = hb_blob_create(mSharedBlobData->GetTable(),
|
|
mSharedBlobData->GetTableLength(),
|
|
HB_MEMORY_MODE_READONLY,
|
|
mSharedBlobData, DeleteFontTableBlobData);
|
|
if (!mSharedBlobData) {
|
|
// 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 = new nsTHashtable<FontTableHashEntry>(10);
|
|
}
|
|
|
|
FontTableHashEntry *entry = mFontTableCache->GetEntry(aTag);
|
|
if (!entry) {
|
|
return false;
|
|
}
|
|
|
|
*aBlob = entry->GetBlob();
|
|
return true;
|
|
}
|
|
|
|
hb_blob_t *
|
|
gfxFontEntry::ShareFontTableAndGetBlob(uint32_t aTag,
|
|
FallibleTArray<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 = new nsTHashtable<FontTableHashEntry>(10);
|
|
}
|
|
|
|
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(*aBuffer, mFontTableCache);
|
|
}
|
|
|
|
static int
|
|
DirEntryCmp(const void* aKey, const void* aItem)
|
|
{
|
|
int32_t tag = *static_cast<const int32_t*>(aKey);
|
|
const TableDirEntry* entry = static_cast<const TableDirEntry*>(aItem);
|
|
return tag - int32_t(entry->tag);
|
|
}
|
|
|
|
hb_blob_t*
|
|
gfxFontEntry::GetTableFromFontData(const void* aFontData, uint32_t aTableTag)
|
|
{
|
|
const SFNTHeader* header =
|
|
reinterpret_cast<const SFNTHeader*>(aFontData);
|
|
const TableDirEntry* dir =
|
|
reinterpret_cast<const TableDirEntry*>(header + 1);
|
|
dir = static_cast<const TableDirEntry*>
|
|
(bsearch(&aTableTag, dir, uint16_t(header->numTables),
|
|
sizeof(TableDirEntry), DirEntryCmp));
|
|
if (dir) {
|
|
return hb_blob_create(reinterpret_cast<const char*>(aFontData) +
|
|
dir->offset, dir->length,
|
|
HB_MEMORY_MODE_READONLY, nullptr, nullptr);
|
|
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
hb_blob_t *
|
|
gfxFontEntry::GetFontTable(uint32_t aTag)
|
|
{
|
|
hb_blob_t *blob;
|
|
if (GetExistingFontTable(aTag, &blob)) {
|
|
return blob;
|
|
}
|
|
|
|
FallibleTArray<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 *data;
|
|
if (fontEntry->mGrTableMap->Get(aTableBuffer, &data)) {
|
|
fontEntry->mGrTableMap->Remove(aTableBuffer);
|
|
hb_blob_destroy(static_cast<hb_blob_t*>(data));
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxFontEntry::CheckForGraphiteTables()
|
|
{
|
|
AutoTable silfTable(this, TRUETYPE_TAG('S','i','l','f'));
|
|
mHasGraphiteTables = silfTable && hb_blob_get_length(silfTable) > 0;
|
|
}
|
|
|
|
/* static */ size_t
|
|
gfxFontEntry::FontTableHashEntry::SizeOfEntryExcludingThis
|
|
(FontTableHashEntry *aEntry,
|
|
MallocSizeOf aMallocSizeOf,
|
|
void* aUserArg)
|
|
{
|
|
size_t n = 0;
|
|
if (aEntry->mBlob) {
|
|
n += aMallocSizeOf(aEntry->mBlob);
|
|
}
|
|
if (aEntry->mSharedBlobData) {
|
|
n += aEntry->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->SizeOfExcludingThis(
|
|
FontTableHashEntry::SizeOfEntryExcludingThis,
|
|
aMallocSizeOf);
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxFontEntry::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
|
|
FontListSizes* aSizes) const
|
|
{
|
|
aSizes->mFontListSize += aMallocSizeOf(this);
|
|
AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// class gfxFontFamily
|
|
//
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
|
|
// we consider faces with mStandardFace == true to be "greater than" those with false,
|
|
// because during style matching, later entries will replace earlier ones
|
|
class FontEntryStandardFaceComparator {
|
|
public:
|
|
bool Equals(const nsRefPtr<gfxFontEntry>& a, const nsRefPtr<gfxFontEntry>& b) const {
|
|
return a->mStandardFace == b->mStandardFace;
|
|
}
|
|
bool LessThan(const nsRefPtr<gfxFontEntry>& a, const nsRefPtr<gfxFontEntry>& b) const {
|
|
return (a->mStandardFace == false && b->mStandardFace == true);
|
|
}
|
|
};
|
|
|
|
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)
|
|
{
|
|
if (!mHasStyles)
|
|
FindStyleVariations(); // collect faces for the family, if not already done
|
|
|
|
NS_ASSERTION(mAvailableFonts.Length() > 0, "font family with no faces!");
|
|
|
|
aNeedsSyntheticBold = false;
|
|
|
|
int8_t baseWeight = aFontStyle.ComputeWeight();
|
|
bool wantBold = baseWeight >= 6;
|
|
|
|
// If the family has only one face, we simply return it; no further checking needed
|
|
if (mAvailableFonts.Length() == 1) {
|
|
gfxFontEntry *fe = mAvailableFonts[0];
|
|
aNeedsSyntheticBold = wantBold && !fe->IsBold();
|
|
return fe;
|
|
}
|
|
|
|
bool wantItalic = (aFontStyle.style &
|
|
(NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0;
|
|
|
|
// 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.
|
|
uint8_t faceIndex = (wantItalic ? kItalicMask : 0) |
|
|
(wantBold ? kBoldMask : 0);
|
|
|
|
// if the desired style is available, return it directly
|
|
gfxFontEntry *fe = mAvailableFonts[faceIndex];
|
|
if (fe) {
|
|
// no need to set aNeedsSyntheticBold here as we matched the boldness request
|
|
return fe;
|
|
}
|
|
|
|
// 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();
|
|
return fe;
|
|
}
|
|
}
|
|
|
|
// this can't happen unless we have totally broken the font-list manager!
|
|
NS_NOTREACHED("no face found in simple font family!");
|
|
return nullptr;
|
|
}
|
|
|
|
// This is a large/rich font family, so we do full style- and weight-matching:
|
|
// first collect a list of weights that are the best match for the requested
|
|
// font-stretch and font-style, then pick the best weight match among those
|
|
// available.
|
|
|
|
gfxFontEntry *weightList[10] = { 0 };
|
|
bool foundWeights = FindWeightsForStyle(weightList, wantItalic, aFontStyle.stretch);
|
|
if (!foundWeights) {
|
|
return nullptr;
|
|
}
|
|
|
|
// First find a match for the best weight
|
|
int8_t matchBaseWeight = 0;
|
|
int8_t i = baseWeight;
|
|
|
|
// Need to special case when normal face doesn't exist but medium does.
|
|
// In that case, use medium otherwise weights < 400
|
|
if (baseWeight == 4 && !weightList[4]) {
|
|
i = 5; // medium
|
|
}
|
|
|
|
// Loop through weights, since one exists loop will terminate
|
|
int8_t direction = (baseWeight > 5) ? 1 : -1;
|
|
for (; ; i += direction) {
|
|
if (weightList[i]) {
|
|
matchBaseWeight = i;
|
|
break;
|
|
}
|
|
|
|
// If we've reached one side without finding a font,
|
|
// start over and go the other direction until we find a match
|
|
if (i == 1 || i == 9) {
|
|
i = baseWeight;
|
|
direction = -direction;
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(matchBaseWeight != 0,
|
|
"weight mapping should always find at least one font in a family");
|
|
|
|
gfxFontEntry *matchFE = weightList[matchBaseWeight];
|
|
|
|
NS_ASSERTION(matchFE,
|
|
"weight mapping should always find at least one font in a family");
|
|
|
|
if (!matchFE->IsBold() && baseWeight >= 6)
|
|
{
|
|
aNeedsSyntheticBold = true;
|
|
}
|
|
|
|
return matchFE;
|
|
}
|
|
|
|
void
|
|
gfxFontFamily::CheckForSimpleFamily()
|
|
{
|
|
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) {
|
|
return; // font-stretch doesn't match, don't treat as simple family
|
|
}
|
|
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;
|
|
}
|
|
|
|
static inline uint32_t
|
|
StyleDistance(gfxFontEntry *aFontEntry,
|
|
bool anItalic, int16_t aStretch)
|
|
{
|
|
// Compute a measure of the "distance" between the requested style
|
|
// and the given fontEntry,
|
|
// considering italicness and font-stretch but not weight.
|
|
|
|
int32_t distance = 0;
|
|
if (aStretch != aFontEntry->mStretch) {
|
|
// stretch values are in the range -4 .. +4
|
|
// if aStretch is positive, we prefer more-positive values;
|
|
// if zero or negative, prefer more-negative
|
|
if (aStretch > 0) {
|
|
distance = (aFontEntry->mStretch - aStretch) * 2;
|
|
} else {
|
|
distance = (aStretch - aFontEntry->mStretch) * 2;
|
|
}
|
|
// if the computed "distance" here is negative, it means that
|
|
// aFontEntry lies in the "non-preferred" direction from aStretch,
|
|
// so we treat that as larger than any preferred-direction distance
|
|
// (max possible is 8) by adding an extra 10 to the absolute value
|
|
if (distance < 0) {
|
|
distance = -distance + 10;
|
|
}
|
|
}
|
|
if (aFontEntry->IsItalic() != anItalic) {
|
|
distance += 1;
|
|
}
|
|
return uint32_t(distance);
|
|
}
|
|
|
|
bool
|
|
gfxFontFamily::FindWeightsForStyle(gfxFontEntry* aFontsForWeights[],
|
|
bool anItalic, int16_t aStretch)
|
|
{
|
|
uint32_t foundWeights = 0;
|
|
uint32_t bestMatchDistance = 0xffffffff;
|
|
|
|
uint32_t count = mAvailableFonts.Length();
|
|
for (uint32_t i = 0; i < count; i++) {
|
|
// this is not called for "simple" families, and therefore it does not
|
|
// need to check the mAvailableFonts entries for nullptr.
|
|
gfxFontEntry *fe = mAvailableFonts[i];
|
|
uint32_t distance = StyleDistance(fe, anItalic, aStretch);
|
|
if (distance <= bestMatchDistance) {
|
|
int8_t wt = fe->mWeight / 100;
|
|
NS_ASSERTION(wt >= 1 && wt < 10, "invalid weight in fontEntry");
|
|
if (!aFontsForWeights[wt]) {
|
|
// record this as a possible candidate for weight matching
|
|
aFontsForWeights[wt] = fe;
|
|
++foundWeights;
|
|
} else {
|
|
uint32_t prevDistance =
|
|
StyleDistance(aFontsForWeights[wt], anItalic, aStretch);
|
|
if (prevDistance >= distance) {
|
|
// replacing a weight we already found,
|
|
// so don't increment foundWeights
|
|
aFontsForWeights[wt] = fe;
|
|
}
|
|
}
|
|
bestMatchDistance = distance;
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(foundWeights > 0, "Font family containing no faces?");
|
|
|
|
if (foundWeights == 1) {
|
|
// no need to cull entries if we only found one weight
|
|
return true;
|
|
}
|
|
|
|
// we might have recorded some faces that were a partial style match, but later found
|
|
// others that were closer; in this case, we need to cull the poorer matches from the
|
|
// weight list we'll return
|
|
for (uint32_t i = 0; i < 10; ++i) {
|
|
if (aFontsForWeights[i] &&
|
|
StyleDistance(aFontsForWeights[i], anItalic, aStretch) > bestMatchDistance)
|
|
{
|
|
aFontsForWeights[i] = 0;
|
|
}
|
|
}
|
|
|
|
return (foundWeights > 0);
|
|
}
|
|
|
|
|
|
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 wantItalic =
|
|
(aStyle->style & (NS_FONT_STYLE_ITALIC | NS_FONT_STYLE_OBLIQUE)) != 0;
|
|
if (aFontEntry->IsItalic() == wantItalic) {
|
|
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->IsItalic()) {
|
|
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;
|
|
gfxFontStyle normal;
|
|
gfxFontEntry *fe = FindFontForStyle(
|
|
(aMatchData->mStyle == nullptr) ? *aMatchData->mStyle : normal,
|
|
needsBold);
|
|
|
|
if (fe && !fe->SkipDuringSystemFallback()) {
|
|
int32_t rank = 0;
|
|
|
|
if (fe->TestCharacterMap(aMatchData->mCh)) {
|
|
rank += RANK_MATCHED_CMAP;
|
|
aMatchData->mCount++;
|
|
#ifdef PR_LOGGING
|
|
PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun);
|
|
|
|
if (MOZ_UNLIKELY(log)) {
|
|
uint32_t unicodeRange = FindCharUnicodeRange(aMatchData->mCh);
|
|
uint32_t script = GetScriptCode(aMatchData->mCh);
|
|
PR_LOG(log, PR_LOG_DEBUG,\
|
|
("(textrun-systemfallback-fonts) char: u+%6.6x "
|
|
"unicode-range: %d script: %d match: [%s]\n",
|
|
aMatchData->mCh,
|
|
unicodeRange, script,
|
|
NS_ConvertUTF16toUTF8(fe->Name()).get()));
|
|
}
|
|
#endif
|
|
}
|
|
|
|
aMatchData->mCmapsTested++;
|
|
if (rank == 0) {
|
|
return;
|
|
}
|
|
|
|
// omitting from original windows code -- family name, lang group, pitch
|
|
// not available in current FontEntry implementation
|
|
rank += CalcStyleMatch(fe, aMatchData->mStyle);
|
|
|
|
// 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->TestCharacterMap(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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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);
|
|
const gfxFontUtils::NameHeader *nameHeader =
|
|
reinterpret_cast<const gfxFontUtils::NameHeader*>(nameData);
|
|
|
|
uint32_t nameCount = nameHeader->count;
|
|
if (nameCount * sizeof(gfxFontUtils::NameRecord) > dataLength) {
|
|
NS_WARNING("invalid font (name records)");
|
|
return false;
|
|
}
|
|
|
|
const gfxFontUtils::NameRecord *nameRecord =
|
|
reinterpret_cast<const gfxFontUtils::NameRecord*>(nameData + sizeof(gfxFontUtils::NameHeader));
|
|
uint32_t stringsBase = uint32_t(nameHeader->stringOffset);
|
|
|
|
bool foundNames = false;
|
|
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 > dataLength) {
|
|
NS_WARNING("invalid font (name table strings)");
|
|
return false;
|
|
}
|
|
|
|
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(nameData + 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 != mName) {
|
|
aPlatformFontList->AddOtherFamilyName(this, otherFamilyName);
|
|
foundNames = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return foundNames;
|
|
}
|
|
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxFontFamily::ReadFaceNames(gfxPlatformFontList *aPlatformFontList,
|
|
bool aNeedFullnamePostscriptNames)
|
|
{
|
|
// if all needed names have already been read, skip
|
|
if (mOtherFamilyNamesInitialized &&
|
|
(mFaceNamesInitialized || !aNeedFullnamePostscriptNames))
|
|
return;
|
|
|
|
FindStyleVariations();
|
|
|
|
uint32_t i, numFonts = mAvailableFonts.Length();
|
|
const uint32_t kNAME = TRUETYPE_TAG('n','a','m','e');
|
|
nsAutoString fullname, psname;
|
|
|
|
bool firstTime = true, readAllFaces = false;
|
|
for (i = 0; i < numFonts; ++i) {
|
|
gfxFontEntry *fe = mAvailableFonts[i];
|
|
if (!fe) {
|
|
continue;
|
|
}
|
|
gfxFontEntry::AutoTable nameTable(fe, kNAME);
|
|
if (!nameTable) {
|
|
continue;
|
|
}
|
|
if (aNeedFullnamePostscriptNames) {
|
|
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::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
|
|
FontListSizes* aSizes) const
|
|
{
|
|
aSizes->mFontListSize +=
|
|
mName.SizeOfExcludingThisIfUnshared(aMallocSizeOf);
|
|
aSizes->mCharMapsSize +=
|
|
mFamilyCharacterMap.SizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
aSizes->mFontListSize +=
|
|
mAvailableFonts.SizeOfExcludingThis(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);
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
|
|
NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(FontCacheMallocSizeOf)
|
|
|
|
NS_IMETHODIMP
|
|
gfxFontCache::MemoryReporter::CollectReports
|
|
(nsIMemoryReporterCallback* aCb,
|
|
nsISupports* aClosure)
|
|
{
|
|
FontCacheSizes sizes;
|
|
|
|
gfxFontCache::GetCache()->AddSizeOfIncludingThis(&FontCacheMallocSizeOf,
|
|
&sizes);
|
|
|
|
aCb->Callback(EmptyCString(),
|
|
NS_LITERAL_CSTRING("explicit/gfx/font-cache"),
|
|
nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
|
|
sizes.mFontInstances,
|
|
NS_LITERAL_CSTRING("Memory used for active font instances."),
|
|
aClosure);
|
|
|
|
aCb->Callback(EmptyCString(),
|
|
NS_LITERAL_CSTRING("explicit/gfx/font-shaped-words"),
|
|
nsIMemoryReporter::KIND_HEAP, nsIMemoryReporter::UNITS_BYTES,
|
|
sizes.mShapedWords,
|
|
NS_LITERAL_CSTRING("Memory used to cache shaped glyph data."),
|
|
aClosure);
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS1(gfxFontCache::Observer, nsIObserver)
|
|
|
|
NS_IMETHODIMP
|
|
gfxFontCache::Observer::Observe(nsISupports *aSubject,
|
|
const char *aTopic,
|
|
const PRUnichar *someData)
|
|
{
|
|
if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
|
|
gfxFontCache *fontCache = gfxFontCache::GetCache();
|
|
if (fontCache) {
|
|
fontCache->FlushShapedWordCaches();
|
|
}
|
|
} else {
|
|
NS_NOTREACHED("unexpected notification topic");
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
gfxFontCache::Init()
|
|
{
|
|
NS_ASSERTION(!gGlobalCache, "Where did this come from?");
|
|
gGlobalCache = new gfxFontCache();
|
|
if (!gGlobalCache) {
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
}
|
|
RegisterStrongMemoryReporter(new MemoryReporter());
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
gfxFontCache::Shutdown()
|
|
{
|
|
delete gGlobalCache;
|
|
gGlobalCache = nullptr;
|
|
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
printf("Textrun storage high water mark=%d\n", gTextRunStorageHighWaterMark);
|
|
printf("Total number of fonts=%d\n", gFontCount);
|
|
printf("Total glyph extents allocated=%d (size %d)\n", gGlyphExtentsCount,
|
|
int(gGlyphExtentsCount*sizeof(gfxGlyphExtents)));
|
|
printf("Total glyph extents width-storage size allocated=%d\n", gGlyphExtentsWidthsTotalSize);
|
|
printf("Number of simple glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerSimple);
|
|
printf("Number of tight glyph extents eagerly requested=%d\n", gGlyphExtentsSetupEagerTight);
|
|
printf("Number of tight glyph extents lazily requested=%d\n", gGlyphExtentsSetupLazyTight);
|
|
printf("Number of simple glyph extent setups that fell back to tight=%d\n", gGlyphExtentsSetupFallBackToTight);
|
|
#endif
|
|
}
|
|
|
|
gfxFontCache::gfxFontCache()
|
|
: nsExpirationTracker<gfxFont,3>(FONT_TIMEOUT_SECONDS * 1000)
|
|
{
|
|
nsCOMPtr<nsIObserverService> obs = GetObserverService();
|
|
if (obs) {
|
|
obs->AddObserver(new Observer, "memory-pressure", false);
|
|
}
|
|
|
|
#ifndef RELEASE_BUILD
|
|
// Currently disabled for release builds, due to unexplained crashes
|
|
// during expiration; see bug 717175 & 894798.
|
|
mWordCacheExpirationTimer = do_CreateInstance("@mozilla.org/timer;1");
|
|
if (mWordCacheExpirationTimer) {
|
|
mWordCacheExpirationTimer->
|
|
InitWithFuncCallback(WordCacheExpirationTimerCallback, this,
|
|
SHAPED_WORD_TIMEOUT_SECONDS * 1000,
|
|
nsITimer::TYPE_REPEATING_SLACK);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
gfxFontCache::~gfxFontCache()
|
|
{
|
|
// Ensure the user font cache releases its references to font entries,
|
|
// so they aren't kept alive after the font instances and font-list
|
|
// have been shut down.
|
|
gfxUserFontSet::UserFontCache::Shutdown();
|
|
|
|
if (mWordCacheExpirationTimer) {
|
|
mWordCacheExpirationTimer->Cancel();
|
|
mWordCacheExpirationTimer = nullptr;
|
|
}
|
|
|
|
// Expire everything that has a zero refcount, so we don't leak them.
|
|
AgeAllGenerations();
|
|
// All fonts should be gone.
|
|
NS_WARN_IF_FALSE(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
|
|
{
|
|
return aKey->mFontEntry == mFont->GetFontEntry() &&
|
|
aKey->mStyle->Equals(*mFont->GetStyle());
|
|
}
|
|
|
|
already_AddRefed<gfxFont>
|
|
gfxFontCache::Lookup(const gfxFontEntry *aFontEntry,
|
|
const gfxFontStyle *aStyle)
|
|
{
|
|
Key key(aFontEntry, aStyle);
|
|
HashEntry *entry = mFonts.GetEntry(key);
|
|
|
|
Telemetry::Accumulate(Telemetry::FONT_CACHE_HIT, entry != nullptr);
|
|
if (!entry)
|
|
return nullptr;
|
|
|
|
nsRefPtr<gfxFont> font = entry->mFont;
|
|
return font.forget();
|
|
}
|
|
|
|
void
|
|
gfxFontCache::AddNew(gfxFont *aFont)
|
|
{
|
|
Key key(aFont->GetFontEntry(), aFont->GetStyle());
|
|
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());
|
|
HashEntry *entry = mFonts.GetEntry(key);
|
|
if (entry && entry->mFont == aFont) {
|
|
mFonts.RemoveEntry(key);
|
|
}
|
|
NS_ASSERTION(aFont->GetRefCount() == 0,
|
|
"Destroying with non-zero ref count!");
|
|
delete aFont;
|
|
}
|
|
|
|
/*static*/
|
|
PLDHashOperator
|
|
gfxFontCache::AgeCachedWordsForFont(HashEntry* aHashEntry, void* aUserData)
|
|
{
|
|
aHashEntry->mFont->AgeCachedWords();
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
/*static*/
|
|
void
|
|
gfxFontCache::WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache)
|
|
{
|
|
gfxFontCache* cache = static_cast<gfxFontCache*>(aCache);
|
|
cache->mFonts.EnumerateEntries(AgeCachedWordsForFont, nullptr);
|
|
}
|
|
|
|
/*static*/
|
|
PLDHashOperator
|
|
gfxFontCache::ClearCachedWordsForFont(HashEntry* aHashEntry, void* aUserData)
|
|
{
|
|
aHashEntry->mFont->ClearCachedWords();
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
/*static*/
|
|
size_t
|
|
gfxFontCache::AddSizeOfFontEntryExcludingThis(HashEntry* aHashEntry,
|
|
MallocSizeOf aMallocSizeOf,
|
|
void* aUserArg)
|
|
{
|
|
HashEntry *entry = static_cast<HashEntry*>(aHashEntry);
|
|
FontCacheSizes *sizes = static_cast<FontCacheSizes*>(aUserArg);
|
|
entry->mFont->AddSizeOfExcludingThis(aMallocSizeOf, sizes);
|
|
|
|
// The entry's size is recorded in the |sizes| parameter, so we return zero
|
|
// here to the hashtable enumerator.
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
gfxFontCache::AddSizeOfExcludingThis(MallocSizeOf aMallocSizeOf,
|
|
FontCacheSizes* aSizes) const
|
|
{
|
|
// TODO: add the overhead of the expiration tracker (generation arrays)
|
|
|
|
aSizes->mFontInstances +=
|
|
mFonts.SizeOfExcludingThis(AddSizeOfFontEntryExcludingThis,
|
|
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);
|
|
nsAutoTArray<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 */ bool
|
|
gfxFontShaper::MergeFontFeatures(
|
|
const gfxFontStyle *aStyle,
|
|
const nsTArray<gfxFontFeature>& aFontFeatures,
|
|
bool aDisableLigatures,
|
|
const nsAString& aFamilyName,
|
|
nsDataHashtable<nsUint32HashKey,uint32_t>& aMergedFeatures)
|
|
{
|
|
uint32_t numAlts = aStyle->alternateValues.Length();
|
|
const nsTArray<gfxFontFeature>& styleRuleFeatures =
|
|
aStyle->featureSettings;
|
|
|
|
// bail immediately if nothing to do
|
|
if (styleRuleFeatures.IsEmpty() &&
|
|
aFontFeatures.IsEmpty() &&
|
|
!aDisableLigatures &&
|
|
numAlts == 0) {
|
|
return false;
|
|
}
|
|
|
|
// Ligature features are enabled by default in the generic shaper,
|
|
// so we explicitly turn them off if necessary (for letter-spacing)
|
|
if (aDisableLigatures) {
|
|
aMergedFeatures.Put(HB_TAG('l','i','g','a'), 0);
|
|
aMergedFeatures.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);
|
|
aMergedFeatures.Put(feature.mTag, feature.mValue);
|
|
}
|
|
|
|
// add font-specific feature values from style rules
|
|
if (aStyle->featureValueLookup && numAlts > 0) {
|
|
nsAutoTArray<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);
|
|
aMergedFeatures.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);
|
|
aMergedFeatures.Put(feature.mTag, feature.mValue);
|
|
}
|
|
|
|
return aMergedFeatures.Count() != 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(gfxFontEntry *aFontEntry, const gfxFontStyle *aFontStyle,
|
|
AntialiasOption anAAOption, cairo_scaled_font_t *aScaledFont) :
|
|
mScaledFont(aScaledFont),
|
|
mFontEntry(aFontEntry), mIsValid(true),
|
|
mApplySyntheticBold(false),
|
|
mStyle(*aFontStyle),
|
|
mAdjustedSize(0.0),
|
|
mFUnitsConvFactor(0.0f),
|
|
mAntialiasOption(anAAOption)
|
|
{
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
++gFontCount;
|
|
#endif
|
|
mKerningSet = HasFeatureSet(HB_TAG('k','e','r','n'), mKerningEnabled);
|
|
}
|
|
|
|
static PLDHashOperator
|
|
NotifyFontDestroyed(nsPtrHashKey<gfxFont::GlyphChangeObserver>* aKey,
|
|
void* aClosure)
|
|
{
|
|
aKey->GetKey()->ForgetFont();
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
gfxFont::~gfxFont()
|
|
{
|
|
uint32_t i, count = mGlyphExtentsArray.Length();
|
|
// We destroy the contents of mGlyphExtentsArray explicitly instead of
|
|
// using nsAutoPtr because VC++ can't deal with nsTArrays of nsAutoPtrs
|
|
// of classes that lack a proper copy constructor
|
|
for (i = 0; i < count; ++i) {
|
|
delete mGlyphExtentsArray[i];
|
|
}
|
|
|
|
mFontEntry->NotifyFontDestroyed(this);
|
|
|
|
if (mGlyphChangeObservers) {
|
|
mGlyphChangeObservers->EnumerateEntries(NotifyFontDestroyed, nullptr);
|
|
}
|
|
}
|
|
|
|
/*static*/
|
|
PLDHashOperator
|
|
gfxFont::AgeCacheEntry(CacheHashEntry *aEntry, void *aUserData)
|
|
{
|
|
if (!aEntry->mShapedWord) {
|
|
NS_ASSERTION(aEntry->mShapedWord, "cache entry has no gfxShapedWord!");
|
|
return PL_DHASH_REMOVE;
|
|
}
|
|
if (aEntry->mShapedWord->IncrementAge() == kShapedWordCacheMaxAge) {
|
|
return PL_DHASH_REMOVE;
|
|
}
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
static bool
|
|
HasLookupRuleWithGlyphByScript(hb_face_t *aFace, hb_tag_t aTableTag,
|
|
hb_tag_t aScript, uint16_t aGlyph)
|
|
{
|
|
hb_set_t *lookups = hb_set_create();
|
|
hb_set_t *glyphs = hb_set_create();
|
|
hb_tag_t scripts[2] = {0};
|
|
scripts[0] = aScript;
|
|
|
|
bool result = false;
|
|
hb_ot_layout_collect_lookups(aFace, aTableTag, scripts, nullptr, nullptr,
|
|
lookups);
|
|
|
|
hb_codepoint_t index = -1;
|
|
while (hb_set_next(lookups, &index)) {
|
|
hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
|
|
glyphs, glyphs, glyphs,
|
|
glyphs);
|
|
if (hb_set_has(glyphs, aGlyph)) {
|
|
result = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
hb_set_destroy(glyphs);
|
|
hb_set_destroy(lookups);
|
|
|
|
return result;
|
|
}
|
|
|
|
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,
|
|
hb_tag_t aExcludeFeature,
|
|
hb_set_t *aLookups, hb_set_t *aExcludedFeatureLookups,
|
|
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, aLookups);
|
|
}
|
|
|
|
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 = featureTag == aExcludeFeature ?
|
|
aExcludedFeatureLookups : aLookups;
|
|
CollectLookupsByFeature(aFace, aTableTag, featureIndex, lookups);
|
|
}
|
|
offset += len;
|
|
} while (len == ArrayLength(featureIndexes));
|
|
}
|
|
|
|
static void
|
|
HasLookupRuleWithGlyph(hb_face_t *aFace, hb_tag_t aTableTag, bool& aHasGlyph,
|
|
hb_tag_t aExcludeFeature, bool& aHasGlyphExcluded,
|
|
uint16_t aGlyph)
|
|
{
|
|
// iterate over the scripts in the font
|
|
uint32_t numScripts, numLangs, script, lang;
|
|
hb_set_t *lookups = hb_set_create();
|
|
hb_set_t *excludedFeatureLookups = hb_set_create();
|
|
|
|
numScripts = hb_ot_layout_table_get_script_tags(aFace, aTableTag, 0,
|
|
nullptr, nullptr);
|
|
|
|
for (script = 0; script < numScripts; script++) {
|
|
// default lang
|
|
CollectLookupsByLanguage(aFace, aTableTag, aExcludeFeature,
|
|
lookups, excludedFeatureLookups,
|
|
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, aExcludeFeature,
|
|
lookups, excludedFeatureLookups,
|
|
script, lang);
|
|
}
|
|
}
|
|
|
|
// look for the glyph among non-excluded lookups
|
|
hb_set_t *glyphs = hb_set_create();
|
|
hb_codepoint_t index = -1;
|
|
while (hb_set_next(lookups, &index)) {
|
|
hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
|
|
glyphs, glyphs, glyphs,
|
|
glyphs);
|
|
if (hb_set_has(glyphs, aGlyph)) {
|
|
aHasGlyph = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// look for the glyph among excluded lookups
|
|
hb_set_clear(glyphs);
|
|
index = -1;
|
|
while (hb_set_next(excludedFeatureLookups, &index)) {
|
|
hb_ot_layout_lookup_collect_glyphs(aFace, aTableTag, index,
|
|
glyphs, glyphs, glyphs,
|
|
glyphs);
|
|
if (hb_set_has(glyphs, aGlyph)) {
|
|
aHasGlyphExcluded = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
hb_set_destroy(glyphs);
|
|
hb_set_destroy(excludedFeatureLookups);
|
|
hb_set_destroy(lookups);
|
|
}
|
|
|
|
nsDataHashtable<nsUint32HashKey, int32_t> *gfxFont::sScriptTagToCode = nullptr;
|
|
|
|
void
|
|
gfxFont::CheckForFeaturesInvolvingSpace()
|
|
{
|
|
mFontEntry->mHasSpaceFeaturesInitialized = true;
|
|
|
|
bool result = false;
|
|
|
|
hb_face_t *face = GetFontEntry()->GetHBFace();
|
|
|
|
uint32_t i, len, offset;
|
|
uint32_t spaceGlyph = GetSpaceGlyph();
|
|
int32_t s;
|
|
|
|
mFontEntry->mHasSpaceFeaturesSubDefault = false;
|
|
|
|
// 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, int32_t>(MOZ_NUM_SCRIPT_CODES);
|
|
for (s = MOZ_SCRIPT_ARABIC; s < MOZ_NUM_SCRIPT_CODES; s++) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
// iterate over the scripts in the font
|
|
hb_tag_t scriptTags[8];
|
|
|
|
offset = 0;
|
|
do {
|
|
len = ArrayLength(scriptTags);
|
|
hb_ot_layout_table_get_script_tags(face, HB_OT_TAG_GSUB, offset,
|
|
&len, scriptTags);
|
|
for (i = 0; i < len; i++) {
|
|
if (HasLookupRuleWithGlyphByScript(face, HB_OT_TAG_GSUB,
|
|
scriptTags[i], spaceGlyph))
|
|
{
|
|
result = true;
|
|
if (scriptTags[i] == HB_TAG('D','F','L','T')) {
|
|
mFontEntry->mHasSpaceFeaturesSubDefault = true;
|
|
}
|
|
if (sScriptTagToCode->Get(scriptTags[i], &s)) {
|
|
uint32_t index = s >> 5;
|
|
uint32_t bit = s & 0x1f;
|
|
mFontEntry->mHasSpaceFeaturesSub[index] |= (1 << bit);
|
|
}
|
|
}
|
|
}
|
|
offset += len;
|
|
} while (len == ArrayLength(scriptTags));
|
|
}
|
|
|
|
// GPOS lookups - distinguish kerning from non-kerning features
|
|
mFontEntry->mHasSpaceFeaturesKerning = false;
|
|
mFontEntry->mHasSpaceFeaturesNonKerning = false;
|
|
|
|
if (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;
|
|
|
|
#ifdef DEBUG_SPACE_LOOKUPS
|
|
printf("font: %s - subst: %8.8x %8.8x %8.8x %8.8x "
|
|
"default: %s kerning: %s non-kerning: %s\n",
|
|
NS_ConvertUTF16toUTF8(mFontEntry->Name()).get(),
|
|
mFontEntry->mHasSpaceFeaturesSub[0],
|
|
mFontEntry->mHasSpaceFeaturesSub[1],
|
|
mFontEntry->mHasSpaceFeaturesSub[2],
|
|
mFontEntry->mHasSpaceFeaturesSub[3],
|
|
(mFontEntry->mHasSpaceFeaturesSubDefault ? "true" : "false"),
|
|
(mFontEntry->mHasSpaceFeaturesKerning ? "true" : "false"),
|
|
(mFontEntry->mHasSpaceFeaturesNonKerning ? "true" : "false")
|
|
);
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
gfxFont::HasFeatureSet(uint32_t aFeature, bool& aFeatureOn)
|
|
{
|
|
aFeatureOn = false;
|
|
|
|
if (mStyle.featureSettings.IsEmpty() &&
|
|
GetFontEntry()->mFeatureSettings.IsEmpty()) {
|
|
return false;
|
|
}
|
|
|
|
// add feature values from font
|
|
bool featureSet = false;
|
|
uint32_t i, count;
|
|
|
|
nsTArray<gfxFontFeature>& fontFeatures = GetFontEntry()->mFeatureSettings;
|
|
count = fontFeatures.Length();
|
|
for (i = 0; i < count; i++) {
|
|
const gfxFontFeature& feature = fontFeatures.ElementAt(i);
|
|
if (feature.mTag == aFeature) {
|
|
featureSet = true;
|
|
aFeatureOn = (feature.mValue != 0);
|
|
}
|
|
}
|
|
|
|
// add feature values from style rules
|
|
nsTArray<gfxFontFeature>& styleFeatures = mStyle.featureSettings;
|
|
count = styleFeatures.Length();
|
|
for (i = 0; i < count; i++) {
|
|
const gfxFontFeature& feature = styleFeatures.ElementAt(i);
|
|
if (feature.mTag == aFeature) {
|
|
featureSet = true;
|
|
aFeatureOn = (feature.mValue != 0);
|
|
}
|
|
}
|
|
|
|
return featureSet;
|
|
}
|
|
|
|
/**
|
|
* A helper function in case we need to do any rounding or other
|
|
* processing here.
|
|
*/
|
|
#define ToDeviceUnits(aAppUnits, aDevUnitsPerAppUnit) \
|
|
(double(aAppUnits)*double(aDevUnitsPerAppUnit))
|
|
|
|
struct GlyphBuffer {
|
|
#define GLYPH_BUFFER_SIZE (2048/sizeof(cairo_glyph_t))
|
|
cairo_glyph_t mGlyphBuffer[GLYPH_BUFFER_SIZE];
|
|
unsigned int mNumGlyphs;
|
|
|
|
GlyphBuffer()
|
|
: mNumGlyphs(0) { }
|
|
|
|
cairo_glyph_t *AppendGlyph() {
|
|
return &mGlyphBuffer[mNumGlyphs++];
|
|
}
|
|
|
|
void Flush(cairo_t *aCR, DrawMode aDrawMode, bool aReverse,
|
|
gfxTextContextPaint *aContextPaint,
|
|
const gfxMatrix& aGlobalMatrix, bool aFinish = false) {
|
|
// Ensure there's enough room for a glyph to be added to the buffer
|
|
// and we actually have glyphs to draw
|
|
if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) {
|
|
return;
|
|
}
|
|
|
|
if (aReverse) {
|
|
for (uint32_t i = 0; i < mNumGlyphs/2; ++i) {
|
|
cairo_glyph_t tmp = mGlyphBuffer[i];
|
|
mGlyphBuffer[i] = mGlyphBuffer[mNumGlyphs - 1 - i];
|
|
mGlyphBuffer[mNumGlyphs - 1 - i] = tmp;
|
|
}
|
|
}
|
|
|
|
if (aDrawMode == DrawMode::GLYPH_PATH) {
|
|
cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs);
|
|
} else {
|
|
if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) ==
|
|
(int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) {
|
|
FlushStroke(aCR, aContextPaint, aGlobalMatrix);
|
|
}
|
|
if (int(aDrawMode) & int(DrawMode::GLYPH_FILL)) {
|
|
PROFILER_LABEL("GlyphBuffer", "cairo_show_glyphs");
|
|
nsRefPtr<gfxPattern> pattern;
|
|
if (aContextPaint &&
|
|
!!(pattern = aContextPaint->GetFillPattern(aGlobalMatrix))) {
|
|
cairo_save(aCR);
|
|
cairo_set_source(aCR, pattern->CairoPattern());
|
|
}
|
|
|
|
cairo_show_glyphs(aCR, mGlyphBuffer, mNumGlyphs);
|
|
|
|
if (pattern) {
|
|
cairo_restore(aCR);
|
|
}
|
|
}
|
|
if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) ==
|
|
int(DrawMode::GLYPH_STROKE)) {
|
|
FlushStroke(aCR, aContextPaint, aGlobalMatrix);
|
|
}
|
|
}
|
|
|
|
mNumGlyphs = 0;
|
|
}
|
|
|
|
private:
|
|
void FlushStroke(cairo_t *aCR, gfxTextContextPaint *aContextPaint,
|
|
const gfxMatrix& aGlobalMatrix) {
|
|
nsRefPtr<gfxPattern> pattern;
|
|
if (aContextPaint &&
|
|
!!(pattern = aContextPaint->GetStrokePattern(aGlobalMatrix))) {
|
|
cairo_save(aCR);
|
|
cairo_set_source(aCR, pattern->CairoPattern());
|
|
}
|
|
|
|
cairo_new_path(aCR);
|
|
cairo_glyph_path(aCR, mGlyphBuffer, mNumGlyphs);
|
|
cairo_stroke(aCR);
|
|
|
|
if (pattern) {
|
|
cairo_restore(aCR);
|
|
}
|
|
}
|
|
|
|
#undef GLYPH_BUFFER_SIZE
|
|
};
|
|
|
|
static AntialiasMode Get2DAAMode(gfxFont::AntialiasOption aAAOption) {
|
|
switch (aAAOption) {
|
|
case gfxFont::kAntialiasSubpixel:
|
|
return AA_SUBPIXEL;
|
|
case gfxFont::kAntialiasGrayscale:
|
|
return AA_GRAY;
|
|
case gfxFont::kAntialiasNone:
|
|
return AA_NONE;
|
|
default:
|
|
return AA_DEFAULT;
|
|
}
|
|
}
|
|
|
|
struct GlyphBufferAzure {
|
|
#define GLYPH_BUFFER_SIZE (2048/sizeof(Glyph))
|
|
Glyph mGlyphBuffer[GLYPH_BUFFER_SIZE];
|
|
unsigned int mNumGlyphs;
|
|
|
|
GlyphBufferAzure()
|
|
: mNumGlyphs(0) { }
|
|
|
|
Glyph *AppendGlyph() {
|
|
return &mGlyphBuffer[mNumGlyphs++];
|
|
}
|
|
|
|
void Flush(DrawTarget *aDT, gfxTextContextPaint *aContextPaint, ScaledFont *aFont,
|
|
DrawMode aDrawMode, bool aReverse, const GlyphRenderingOptions *aOptions,
|
|
gfxContext *aThebesContext, const Matrix *aInvFontMatrix, const DrawOptions &aDrawOptions,
|
|
bool aFinish = false)
|
|
{
|
|
// Ensure there's enough room for a glyph to be added to the buffer
|
|
if ((!aFinish && mNumGlyphs < GLYPH_BUFFER_SIZE) || !mNumGlyphs) {
|
|
return;
|
|
}
|
|
|
|
if (aReverse) {
|
|
Glyph *begin = &mGlyphBuffer[0];
|
|
Glyph *end = &mGlyphBuffer[mNumGlyphs];
|
|
std::reverse(begin, end);
|
|
}
|
|
|
|
gfx::GlyphBuffer buf;
|
|
buf.mGlyphs = mGlyphBuffer;
|
|
buf.mNumGlyphs = mNumGlyphs;
|
|
|
|
gfxContext::AzureState state = aThebesContext->CurrentState();
|
|
if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) ==
|
|
(int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) {
|
|
FlushStroke(aDT, aContextPaint, aFont, aThebesContext, buf, state);
|
|
}
|
|
if (int(aDrawMode) & int(DrawMode::GLYPH_FILL)) {
|
|
if (state.pattern || aContextPaint) {
|
|
Pattern *pat;
|
|
|
|
nsRefPtr<gfxPattern> fillPattern;
|
|
if (!aContextPaint ||
|
|
!(fillPattern = aContextPaint->GetFillPattern(aThebesContext->CurrentMatrix()))) {
|
|
if (state.pattern) {
|
|
pat = state.pattern->GetPattern(aDT, state.patternTransformChanged ? &state.patternTransform : nullptr);
|
|
} else {
|
|
pat = nullptr;
|
|
}
|
|
} else {
|
|
pat = fillPattern->GetPattern(aDT);
|
|
}
|
|
|
|
if (pat) {
|
|
Matrix saved;
|
|
Matrix *mat = nullptr;
|
|
if (aInvFontMatrix) {
|
|
// The brush matrix needs to be multiplied with the inverted matrix
|
|
// as well, to move the brush into the space of the glyphs. Before
|
|
// the render target transformation
|
|
|
|
// This relies on the returned Pattern not to be reused by
|
|
// others, but regenerated on GetPattern calls. This is true!
|
|
if (pat->GetType() == PATTERN_LINEAR_GRADIENT) {
|
|
mat = &static_cast<LinearGradientPattern*>(pat)->mMatrix;
|
|
} else if (pat->GetType() == PATTERN_RADIAL_GRADIENT) {
|
|
mat = &static_cast<RadialGradientPattern*>(pat)->mMatrix;
|
|
} else if (pat->GetType() == PATTERN_SURFACE) {
|
|
mat = &static_cast<SurfacePattern*>(pat)->mMatrix;
|
|
}
|
|
|
|
if (mat) {
|
|
saved = *mat;
|
|
*mat = (*mat) * (*aInvFontMatrix);
|
|
}
|
|
}
|
|
|
|
aDT->FillGlyphs(aFont, buf, *pat,
|
|
aDrawOptions, aOptions);
|
|
|
|
if (mat) {
|
|
*mat = saved;
|
|
}
|
|
}
|
|
} else if (state.sourceSurface) {
|
|
aDT->FillGlyphs(aFont, buf, SurfacePattern(state.sourceSurface,
|
|
EXTEND_CLAMP,
|
|
state.surfTransform),
|
|
aDrawOptions, aOptions);
|
|
} else {
|
|
aDT->FillGlyphs(aFont, buf, ColorPattern(state.color),
|
|
aDrawOptions, aOptions);
|
|
}
|
|
}
|
|
if (int(aDrawMode) & int(DrawMode::GLYPH_PATH)) {
|
|
aThebesContext->EnsurePathBuilder();
|
|
Matrix mat = aDT->GetTransform();
|
|
aFont->CopyGlyphsToBuilder(buf, aThebesContext->mPathBuilder, &mat);
|
|
}
|
|
if ((int(aDrawMode) & (int(DrawMode::GLYPH_STROKE) | int(DrawMode::GLYPH_STROKE_UNDERNEATH))) ==
|
|
int(DrawMode::GLYPH_STROKE)) {
|
|
FlushStroke(aDT, aContextPaint, aFont, aThebesContext, buf, state);
|
|
}
|
|
|
|
mNumGlyphs = 0;
|
|
}
|
|
|
|
private:
|
|
void FlushStroke(DrawTarget *aDT, gfxTextContextPaint *aContextPaint,
|
|
ScaledFont *aFont, gfxContext *aThebesContext,
|
|
gfx::GlyphBuffer& aBuf, gfxContext::AzureState& aState)
|
|
{
|
|
RefPtr<Path> path = aFont->GetPathForGlyphs(aBuf, aDT);
|
|
if (aContextPaint) {
|
|
nsRefPtr<gfxPattern> strokePattern =
|
|
aContextPaint->GetStrokePattern(aThebesContext->CurrentMatrix());
|
|
if (strokePattern) {
|
|
aDT->Stroke(path, *strokePattern->GetPattern(aDT), aState.strokeOptions);
|
|
}
|
|
}
|
|
}
|
|
|
|
#undef GLYPH_BUFFER_SIZE
|
|
};
|
|
|
|
// Bug 674909. When synthetic bolding text by drawing twice, need to
|
|
// render using a pixel offset in device pixels, otherwise text
|
|
// doesn't appear bolded, it appears as if a bad text shadow exists
|
|
// when a non-identity transform exists. Use an offset factor so that
|
|
// the second draw occurs at a constant offset in device pixels.
|
|
|
|
double
|
|
gfxFont::CalcXScale(gfxContext *aContext)
|
|
{
|
|
// determine magnitude of a 1px x offset in device space
|
|
gfxSize t = aContext->UserToDevice(gfxSize(1.0, 0.0));
|
|
if (t.width == 1.0 && t.height == 0.0) {
|
|
// short-circuit the most common case to avoid sqrt() and division
|
|
return 1.0;
|
|
}
|
|
|
|
double m = sqrt(t.width * t.width + t.height * t.height);
|
|
|
|
NS_ASSERTION(m != 0.0, "degenerate transform while synthetic bolding");
|
|
if (m == 0.0) {
|
|
return 0.0; // effectively disables offset
|
|
}
|
|
|
|
// scale factor so that offsets are 1px in device pixels
|
|
return 1.0 / m;
|
|
}
|
|
|
|
static DrawMode
|
|
ForcePaintingDrawMode(DrawMode aDrawMode)
|
|
{
|
|
return aDrawMode == DrawMode::GLYPH_PATH ?
|
|
DrawMode(int(DrawMode::GLYPH_FILL) | int(DrawMode::GLYPH_STROKE)) :
|
|
aDrawMode;
|
|
}
|
|
|
|
void
|
|
gfxFont::Draw(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
|
|
gfxContext *aContext, DrawMode aDrawMode, gfxPoint *aPt,
|
|
Spacing *aSpacing, gfxTextContextPaint *aContextPaint,
|
|
gfxTextRunDrawCallbacks *aCallbacks)
|
|
{
|
|
NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !(int(aDrawMode) & int(DrawMode::GLYPH_PATH)),
|
|
"GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH");
|
|
|
|
if (aStart >= aEnd)
|
|
return;
|
|
|
|
const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
|
|
const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
|
|
const double devUnitsPerAppUnit = 1.0/double(appUnitsPerDevUnit);
|
|
bool isRTL = aTextRun->IsRightToLeft();
|
|
double direction = aTextRun->GetDirection();
|
|
gfxMatrix globalMatrix = aContext->CurrentMatrix();
|
|
|
|
bool haveSVGGlyphs = GetFontEntry()->TryGetSVGData(this);
|
|
nsAutoPtr<gfxTextContextPaint> contextPaint;
|
|
if (haveSVGGlyphs && !aContextPaint) {
|
|
// If no pattern is specified for fill, use the current pattern
|
|
NS_ASSERTION((int(aDrawMode) & int(DrawMode::GLYPH_STROKE)) == 0, "no pattern supplied for stroking text");
|
|
nsRefPtr<gfxPattern> fillPattern = aContext->GetPattern();
|
|
contextPaint = new SimpleTextContextPaint(fillPattern, nullptr,
|
|
aContext->CurrentMatrix());
|
|
aContextPaint = contextPaint;
|
|
}
|
|
|
|
// synthetic-bold strikes are each offset one device pixel in run direction
|
|
// (these values are only needed if IsSyntheticBold() is true)
|
|
double synBoldOnePixelOffset = 0;
|
|
int32_t strikes = 1;
|
|
if (IsSyntheticBold()) {
|
|
double xscale = CalcXScale(aContext);
|
|
synBoldOnePixelOffset = direction * xscale;
|
|
if (xscale != 0.0) {
|
|
// use as many strikes as needed for the the increased advance
|
|
strikes = NS_lroundf(GetSyntheticBoldOffset() / xscale);
|
|
}
|
|
}
|
|
|
|
uint32_t i;
|
|
// Current position in appunits
|
|
double x = aPt->x;
|
|
double y = aPt->y;
|
|
|
|
cairo_t *cr = aContext->GetCairo();
|
|
RefPtr<DrawTarget> dt = aContext->GetDrawTarget();
|
|
|
|
bool paintSVGGlyphs = !aCallbacks || aCallbacks->mShouldPaintSVGGlyphs;
|
|
bool emittedGlyphs = false;
|
|
|
|
if (aContext->IsCairo()) {
|
|
bool success = SetupCairoFont(aContext);
|
|
if (MOZ_UNLIKELY(!success))
|
|
return;
|
|
|
|
::GlyphBuffer glyphs;
|
|
cairo_glyph_t *glyph;
|
|
|
|
if (aSpacing) {
|
|
x += direction*aSpacing[0].mBefore;
|
|
}
|
|
for (i = aStart; i < aEnd; ++i) {
|
|
const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i];
|
|
if (glyphData->IsSimpleGlyph()) {
|
|
double advance = glyphData->GetSimpleAdvance();
|
|
double glyphX;
|
|
if (isRTL) {
|
|
x -= advance;
|
|
glyphX = x;
|
|
} else {
|
|
glyphX = x;
|
|
x += advance;
|
|
}
|
|
|
|
if (haveSVGGlyphs) {
|
|
if (!paintSVGGlyphs) {
|
|
continue;
|
|
}
|
|
gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit),
|
|
ToDeviceUnits(y, devUnitsPerAppUnit));
|
|
DrawMode mode = ForcePaintingDrawMode(aDrawMode);
|
|
if (RenderSVGGlyph(aContext, point, mode,
|
|
glyphData->GetSimpleGlyph(), aContextPaint,
|
|
aCallbacks, emittedGlyphs)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Perhaps we should put a scale in the cairo context instead of
|
|
// doing this scaling here...
|
|
// Multiplying by the reciprocal may introduce tiny error here,
|
|
// but we assume cairo is going to round coordinates at some stage
|
|
// and this is faster
|
|
glyph = glyphs.AppendGlyph();
|
|
glyph->index = glyphData->GetSimpleGlyph();
|
|
glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit);
|
|
glyph->y = ToDeviceUnits(y, devUnitsPerAppUnit);
|
|
glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix);
|
|
|
|
// synthetic bolding by multi-striking with 1-pixel offsets
|
|
// at least once, more if there's room (large font sizes)
|
|
if (IsSyntheticBold()) {
|
|
double strikeOffset = synBoldOnePixelOffset;
|
|
int32_t strikeCount = strikes;
|
|
do {
|
|
cairo_glyph_t *doubleglyph;
|
|
doubleglyph = glyphs.AppendGlyph();
|
|
doubleglyph->index = glyph->index;
|
|
doubleglyph->x =
|
|
ToDeviceUnits(glyphX + strikeOffset * appUnitsPerDevUnit,
|
|
devUnitsPerAppUnit);
|
|
doubleglyph->y = glyph->y;
|
|
strikeOffset += synBoldOnePixelOffset;
|
|
glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix);
|
|
} while (--strikeCount > 0);
|
|
}
|
|
emittedGlyphs = true;
|
|
} else {
|
|
uint32_t glyphCount = glyphData->GetGlyphCount();
|
|
if (glyphCount > 0) {
|
|
const gfxTextRun::DetailedGlyph *details =
|
|
aTextRun->GetDetailedGlyphs(i);
|
|
NS_ASSERTION(details, "detailedGlyph should not be missing!");
|
|
double advance;
|
|
for (uint32_t j = 0; j < glyphCount; ++j, ++details, x += direction * advance) {
|
|
advance = details->mAdvance;
|
|
if (glyphData->IsMissing()) {
|
|
// default ignorable characters will have zero advance width.
|
|
// we don't have to draw the hexbox for them
|
|
if (aDrawMode != DrawMode::GLYPH_PATH && advance > 0) {
|
|
double glyphX = x;
|
|
if (isRTL) {
|
|
glyphX -= advance;
|
|
}
|
|
gfxPoint pt(ToDeviceUnits(glyphX, devUnitsPerAppUnit),
|
|
ToDeviceUnits(y, devUnitsPerAppUnit));
|
|
gfxFloat advanceDevUnits = ToDeviceUnits(advance, devUnitsPerAppUnit);
|
|
gfxFloat height = GetMetrics().maxAscent;
|
|
gfxRect glyphRect(pt.x, pt.y - height, advanceDevUnits, height);
|
|
gfxFontMissingGlyphs::DrawMissingGlyph(aContext,
|
|
glyphRect,
|
|
details->mGlyphID,
|
|
appUnitsPerDevUnit);
|
|
}
|
|
} else {
|
|
double glyphX = x + details->mXOffset;
|
|
if (isRTL) {
|
|
glyphX -= advance;
|
|
}
|
|
|
|
gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit),
|
|
ToDeviceUnits(y, devUnitsPerAppUnit));
|
|
|
|
if (haveSVGGlyphs) {
|
|
if (!paintSVGGlyphs) {
|
|
continue;
|
|
}
|
|
DrawMode mode = ForcePaintingDrawMode(aDrawMode);
|
|
if (RenderSVGGlyph(aContext, point, mode,
|
|
details->mGlyphID,
|
|
aContextPaint, aCallbacks,
|
|
emittedGlyphs)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
glyph = glyphs.AppendGlyph();
|
|
glyph->index = details->mGlyphID;
|
|
glyph->x = ToDeviceUnits(glyphX, devUnitsPerAppUnit);
|
|
glyph->y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit);
|
|
glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix);
|
|
|
|
if (IsSyntheticBold()) {
|
|
double strikeOffset = synBoldOnePixelOffset;
|
|
int32_t strikeCount = strikes;
|
|
do {
|
|
cairo_glyph_t *doubleglyph;
|
|
doubleglyph = glyphs.AppendGlyph();
|
|
doubleglyph->index = glyph->index;
|
|
doubleglyph->x =
|
|
ToDeviceUnits(glyphX + strikeOffset *
|
|
appUnitsPerDevUnit,
|
|
devUnitsPerAppUnit);
|
|
doubleglyph->y = glyph->y;
|
|
strikeOffset += synBoldOnePixelOffset;
|
|
glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix);
|
|
} while (--strikeCount > 0);
|
|
}
|
|
emittedGlyphs = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aSpacing) {
|
|
double space = aSpacing[i - aStart].mAfter;
|
|
if (i + 1 < aEnd) {
|
|
space += aSpacing[i + 1 - aStart].mBefore;
|
|
}
|
|
x += direction*space;
|
|
}
|
|
}
|
|
|
|
if (gfxFontTestStore::CurrentStore()) {
|
|
/* This assumes that the tests won't have anything that results
|
|
* in more than GLYPH_BUFFER_SIZE glyphs. Do this before we
|
|
* flush, since that'll blow away the num_glyphs.
|
|
*/
|
|
gfxFontTestStore::CurrentStore()->AddItem(GetName(),
|
|
glyphs.mGlyphBuffer,
|
|
glyphs.mNumGlyphs);
|
|
}
|
|
|
|
// draw any remaining glyphs
|
|
glyphs.Flush(cr, aDrawMode, isRTL, aContextPaint, globalMatrix, true);
|
|
if (aCallbacks && emittedGlyphs) {
|
|
aCallbacks->NotifyGlyphPathEmitted();
|
|
}
|
|
|
|
} else {
|
|
RefPtr<ScaledFont> scaledFont = GetScaledFont(dt);
|
|
|
|
if (!scaledFont) {
|
|
return;
|
|
}
|
|
|
|
bool oldSubpixelAA = dt->GetPermitSubpixelAA();
|
|
|
|
if (!AllowSubpixelAA()) {
|
|
dt->SetPermitSubpixelAA(false);
|
|
}
|
|
|
|
GlyphBufferAzure glyphs;
|
|
Glyph *glyph;
|
|
|
|
Matrix mat, matInv;
|
|
Matrix oldMat = dt->GetTransform();
|
|
|
|
// This is nullptr when we have inverse-transformed glyphs and we need
|
|
// to transform the Brush inside flush.
|
|
Matrix *passedInvMatrix = nullptr;
|
|
|
|
RefPtr<GlyphRenderingOptions> renderingOptions =
|
|
GetGlyphRenderingOptions();
|
|
|
|
DrawOptions drawOptions;
|
|
drawOptions.mAntialiasMode = Get2DAAMode(mAntialiasOption);
|
|
|
|
// The cairo DrawTarget backend uses the cairo_scaled_font directly
|
|
// and so has the font skew matrix applied already.
|
|
if (mScaledFont &&
|
|
dt->GetType() != BACKEND_CAIRO) {
|
|
cairo_matrix_t matrix;
|
|
cairo_scaled_font_get_font_matrix(mScaledFont, &matrix);
|
|
if (matrix.xy != 0) {
|
|
// If this matrix applies a skew, which can happen when drawing
|
|
// oblique fonts, we will set the DrawTarget matrix to apply the
|
|
// skew. We'll need to move the glyphs by the inverse of the skew to
|
|
// get the glyphs positioned correctly in the new device space
|
|
// though, since the font matrix should only be applied to drawing
|
|
// the glyphs, and not to their position.
|
|
mat = ToMatrix(*reinterpret_cast<gfxMatrix*>(&matrix));
|
|
|
|
mat._11 = mat._22 = 1.0;
|
|
float adjustedSize = mAdjustedSize > 0 ? mAdjustedSize : GetStyle()->size;
|
|
mat._21 /= adjustedSize;
|
|
|
|
dt->SetTransform(mat * oldMat);
|
|
|
|
matInv = mat;
|
|
matInv.Invert();
|
|
|
|
passedInvMatrix = &matInv;
|
|
}
|
|
}
|
|
|
|
if (aSpacing) {
|
|
x += direction*aSpacing[0].mBefore;
|
|
}
|
|
for (i = aStart; i < aEnd; ++i) {
|
|
const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i];
|
|
if (glyphData->IsSimpleGlyph()) {
|
|
double advance = glyphData->GetSimpleAdvance();
|
|
double glyphX;
|
|
if (isRTL) {
|
|
x -= advance;
|
|
glyphX = x;
|
|
} else {
|
|
glyphX = x;
|
|
x += advance;
|
|
}
|
|
|
|
if (haveSVGGlyphs) {
|
|
if (!paintSVGGlyphs) {
|
|
continue;
|
|
}
|
|
gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit),
|
|
ToDeviceUnits(y, devUnitsPerAppUnit));
|
|
DrawMode mode = ForcePaintingDrawMode(aDrawMode);
|
|
if (RenderSVGGlyph(aContext, point, mode,
|
|
glyphData->GetSimpleGlyph(), aContextPaint,
|
|
aCallbacks, emittedGlyphs)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Perhaps we should put a scale in the cairo context instead of
|
|
// doing this scaling here...
|
|
// Multiplying by the reciprocal may introduce tiny error here,
|
|
// but we assume cairo is going to round coordinates at some stage
|
|
// and this is faster
|
|
glyph = glyphs.AppendGlyph();
|
|
glyph->mIndex = glyphData->GetSimpleGlyph();
|
|
glyph->mPosition.x = ToDeviceUnits(glyphX, devUnitsPerAppUnit);
|
|
glyph->mPosition.y = ToDeviceUnits(y, devUnitsPerAppUnit);
|
|
glyph->mPosition = matInv * glyph->mPosition;
|
|
glyphs.Flush(dt, aContextPaint, scaledFont,
|
|
aDrawMode, isRTL, renderingOptions,
|
|
aContext, passedInvMatrix,
|
|
drawOptions);
|
|
|
|
// synthetic bolding by multi-striking with 1-pixel offsets
|
|
// at least once, more if there's room (large font sizes)
|
|
if (IsSyntheticBold()) {
|
|
double strikeOffset = synBoldOnePixelOffset;
|
|
int32_t strikeCount = strikes;
|
|
do {
|
|
Glyph *doubleglyph;
|
|
doubleglyph = glyphs.AppendGlyph();
|
|
doubleglyph->mIndex = glyph->mIndex;
|
|
doubleglyph->mPosition.x =
|
|
ToDeviceUnits(glyphX + strikeOffset * appUnitsPerDevUnit,
|
|
devUnitsPerAppUnit);
|
|
doubleglyph->mPosition.y = glyph->mPosition.y;
|
|
doubleglyph->mPosition = matInv * doubleglyph->mPosition;
|
|
strikeOffset += synBoldOnePixelOffset;
|
|
glyphs.Flush(dt, aContextPaint, scaledFont,
|
|
aDrawMode, isRTL, renderingOptions,
|
|
aContext, passedInvMatrix,
|
|
drawOptions);
|
|
} while (--strikeCount > 0);
|
|
}
|
|
emittedGlyphs = true;
|
|
} else {
|
|
uint32_t glyphCount = glyphData->GetGlyphCount();
|
|
if (glyphCount > 0) {
|
|
const gfxTextRun::DetailedGlyph *details =
|
|
aTextRun->GetDetailedGlyphs(i);
|
|
NS_ASSERTION(details, "detailedGlyph should not be missing!");
|
|
double advance;
|
|
for (uint32_t j = 0; j < glyphCount; ++j, ++details, x += direction * advance) {
|
|
advance = details->mAdvance;
|
|
if (glyphData->IsMissing()) {
|
|
// default ignorable characters will have zero advance width.
|
|
// we don't have to draw the hexbox for them
|
|
if (aDrawMode != DrawMode::GLYPH_PATH && advance > 0) {
|
|
double glyphX = x;
|
|
if (isRTL) {
|
|
glyphX -= advance;
|
|
}
|
|
gfxPoint pt(ToDeviceUnits(glyphX, devUnitsPerAppUnit),
|
|
ToDeviceUnits(y, devUnitsPerAppUnit));
|
|
gfxFloat advanceDevUnits = ToDeviceUnits(advance, devUnitsPerAppUnit);
|
|
gfxFloat height = GetMetrics().maxAscent;
|
|
gfxRect glyphRect(pt.x, pt.y - height, advanceDevUnits, height);
|
|
gfxFontMissingGlyphs::DrawMissingGlyph(aContext,
|
|
glyphRect,
|
|
details->mGlyphID,
|
|
appUnitsPerDevUnit);
|
|
}
|
|
} else {
|
|
double glyphX = x + details->mXOffset;
|
|
if (isRTL) {
|
|
glyphX -= advance;
|
|
}
|
|
|
|
gfxPoint point(ToDeviceUnits(glyphX, devUnitsPerAppUnit),
|
|
ToDeviceUnits(y, devUnitsPerAppUnit));
|
|
|
|
if (haveSVGGlyphs) {
|
|
if (!paintSVGGlyphs) {
|
|
continue;
|
|
}
|
|
DrawMode mode = ForcePaintingDrawMode(aDrawMode);
|
|
if (RenderSVGGlyph(aContext, point, mode,
|
|
details->mGlyphID,
|
|
aContextPaint, aCallbacks,
|
|
emittedGlyphs)) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
glyph = glyphs.AppendGlyph();
|
|
glyph->mIndex = details->mGlyphID;
|
|
glyph->mPosition.x = ToDeviceUnits(glyphX, devUnitsPerAppUnit);
|
|
glyph->mPosition.y = ToDeviceUnits(y + details->mYOffset, devUnitsPerAppUnit);
|
|
glyph->mPosition = matInv * glyph->mPosition;
|
|
glyphs.Flush(dt, aContextPaint, scaledFont, aDrawMode,
|
|
isRTL, renderingOptions, aContext, passedInvMatrix,
|
|
drawOptions);
|
|
|
|
if (IsSyntheticBold()) {
|
|
double strikeOffset = synBoldOnePixelOffset;
|
|
int32_t strikeCount = strikes;
|
|
do {
|
|
Glyph *doubleglyph;
|
|
doubleglyph = glyphs.AppendGlyph();
|
|
doubleglyph->mIndex = glyph->mIndex;
|
|
doubleglyph->mPosition.x =
|
|
ToDeviceUnits(glyphX + strikeOffset *
|
|
appUnitsPerDevUnit,
|
|
devUnitsPerAppUnit);
|
|
doubleglyph->mPosition.y = glyph->mPosition.y;
|
|
strikeOffset += synBoldOnePixelOffset;
|
|
doubleglyph->mPosition = matInv * doubleglyph->mPosition;
|
|
glyphs.Flush(dt, aContextPaint, scaledFont,
|
|
aDrawMode, isRTL, renderingOptions,
|
|
aContext, passedInvMatrix, drawOptions);
|
|
} while (--strikeCount > 0);
|
|
}
|
|
emittedGlyphs = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (aSpacing) {
|
|
double space = aSpacing[i - aStart].mAfter;
|
|
if (i + 1 < aEnd) {
|
|
space += aSpacing[i + 1 - aStart].mBefore;
|
|
}
|
|
x += direction*space;
|
|
}
|
|
}
|
|
|
|
glyphs.Flush(dt, aContextPaint, scaledFont, aDrawMode, isRTL,
|
|
renderingOptions, aContext, passedInvMatrix,
|
|
drawOptions, true);
|
|
if (aCallbacks && emittedGlyphs) {
|
|
aCallbacks->NotifyGlyphPathEmitted();
|
|
}
|
|
|
|
dt->SetTransform(oldMat);
|
|
|
|
dt->SetPermitSubpixelAA(oldSubpixelAA);
|
|
}
|
|
|
|
*aPt = gfxPoint(x, y);
|
|
}
|
|
|
|
bool
|
|
gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMode,
|
|
uint32_t aGlyphId, gfxTextContextPaint *aContextPaint)
|
|
{
|
|
if (!GetFontEntry()->HasSVGGlyph(aGlyphId)) {
|
|
return false;
|
|
}
|
|
|
|
const gfxFloat devUnitsPerSVGUnit =
|
|
GetAdjustedSize() / GetFontEntry()->UnitsPerEm();
|
|
gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
|
|
|
|
aContext->Translate(gfxPoint(aPoint.x, aPoint.y));
|
|
aContext->Scale(devUnitsPerSVGUnit, devUnitsPerSVGUnit);
|
|
|
|
aContextPaint->InitStrokeGeometry(aContext, devUnitsPerSVGUnit);
|
|
|
|
return GetFontEntry()->RenderSVGGlyph(aContext, aGlyphId, int(aDrawMode),
|
|
aContextPaint);
|
|
}
|
|
|
|
bool
|
|
gfxFont::RenderSVGGlyph(gfxContext *aContext, gfxPoint aPoint, DrawMode aDrawMode,
|
|
uint32_t aGlyphId, gfxTextContextPaint *aContextPaint,
|
|
gfxTextRunDrawCallbacks *aCallbacks,
|
|
bool& aEmittedGlyphs)
|
|
{
|
|
if (aCallbacks) {
|
|
if (aEmittedGlyphs) {
|
|
aCallbacks->NotifyGlyphPathEmitted();
|
|
aEmittedGlyphs = false;
|
|
}
|
|
aCallbacks->NotifyBeforeSVGGlyphPainted();
|
|
}
|
|
bool rendered = RenderSVGGlyph(aContext, aPoint, aDrawMode, aGlyphId,
|
|
aContextPaint);
|
|
if (aCallbacks) {
|
|
aCallbacks->NotifyAfterSVGGlyphPainted();
|
|
}
|
|
return rendered;
|
|
}
|
|
|
|
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, gfxTextRun *aTextRun)
|
|
{
|
|
return (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX) ||
|
|
aFont->GetFontEntry()->IsUserFont();
|
|
}
|
|
|
|
static bool
|
|
NeedsGlyphExtents(gfxTextRun *aTextRun)
|
|
{
|
|
if (aTextRun->GetFlags() & gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX)
|
|
return true;
|
|
uint32_t numRuns;
|
|
const gfxTextRun::GlyphRun *glyphRuns = aTextRun->GetGlyphRuns(&numRuns);
|
|
for (uint32_t i = 0; i < numRuns; ++i) {
|
|
if (glyphRuns[i].mFont->GetFontEntry()->IsUserFont())
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
gfxFont::RunMetrics
|
|
gfxFont::Measure(gfxTextRun *aTextRun,
|
|
uint32_t aStart, uint32_t aEnd,
|
|
BoundingBoxType aBoundingBoxType,
|
|
gfxContext *aRefContext,
|
|
Spacing *aSpacing)
|
|
{
|
|
// 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,
|
|
aRefContext, aSpacing);
|
|
}
|
|
}
|
|
|
|
const int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
|
|
// Current position in appunits
|
|
const gfxFont::Metrics& fontMetrics = GetMetrics();
|
|
|
|
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.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()) ? nullptr
|
|
: GetOrCreateGlyphExtents(aTextRun->GetAppUnitsPerDevUnit());
|
|
double x = 0;
|
|
if (aSpacing) {
|
|
x += direction*aSpacing[0].mBefore;
|
|
}
|
|
uint32_t i;
|
|
for (i = aStart; i < aEnd; ++i) {
|
|
const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[i];
|
|
if (glyphData->IsSimpleGlyph()) {
|
|
double advance = glyphData->GetSimpleAdvance();
|
|
// 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) {
|
|
uint32_t glyphIndex = glyphData->GetSimpleGlyph();
|
|
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,
|
|
aRefContext, glyphIndex, &glyphRect)) {
|
|
glyphRect = gfxRect(0, metrics.mBoundingBox.Y(),
|
|
advance, metrics.mBoundingBox.Height());
|
|
}
|
|
if (isRTL) {
|
|
glyphRect -= gfxPoint(advance, 0);
|
|
}
|
|
glyphRect += gfxPoint(x, 0);
|
|
metrics.mBoundingBox = metrics.mBoundingBox.Union(glyphRect);
|
|
}
|
|
}
|
|
x += direction*advance;
|
|
} else {
|
|
uint32_t glyphCount = glyphData->GetGlyphCount();
|
|
if (glyphCount > 0) {
|
|
const gfxTextRun::DetailedGlyph *details =
|
|
aTextRun->GetDetailedGlyphs(i);
|
|
NS_ASSERTION(details != nullptr,
|
|
"detaiedGlyph record should not be missing!");
|
|
uint32_t j;
|
|
for (j = 0; j < glyphCount; ++j, ++details) {
|
|
uint32_t glyphIndex = details->mGlyphID;
|
|
gfxPoint glyphPt(x + details->mXOffset, details->mYOffset);
|
|
double advance = details->mAdvance;
|
|
gfxRect glyphRect;
|
|
if (glyphData->IsMissing() || !extents ||
|
|
!extents->GetTightGlyphExtentsAppUnits(this,
|
|
aRefContext, 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 -= gfxPoint(advance, 0);
|
|
}
|
|
glyphRect += gfxPoint(x, 0);
|
|
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 (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);
|
|
}
|
|
|
|
metrics.mAdvanceWidth = x*direction;
|
|
return metrics;
|
|
}
|
|
|
|
static PLDHashOperator
|
|
NotifyGlyphChangeObservers(nsPtrHashKey<gfxFont::GlyphChangeObserver>* aKey,
|
|
void* aClosure)
|
|
{
|
|
aKey->GetKey()->NotifyGlyphsChanged();
|
|
return PL_DHASH_NEXT;
|
|
}
|
|
|
|
void
|
|
gfxFont::NotifyGlyphsChanged()
|
|
{
|
|
uint32_t i, count = mGlyphExtentsArray.Length();
|
|
for (i = 0; i < count; ++i) {
|
|
// Flush cached extents array
|
|
mGlyphExtentsArray[i]->NotifyGlyphsChanged();
|
|
}
|
|
|
|
if (mGlyphChangeObservers) {
|
|
mGlyphChangeObservers->EnumerateEntries(NotifyGlyphChangeObservers, nullptr);
|
|
}
|
|
}
|
|
|
|
static bool
|
|
IsBoundarySpace(PRUnichar aChar, PRUnichar aNextChar)
|
|
{
|
|
return (aChar == ' ' || aChar == 0x00A0) && !IsClusterExtender(aNextChar);
|
|
}
|
|
|
|
static inline uint32_t
|
|
HashMix(uint32_t aHash, PRUnichar aCh)
|
|
{
|
|
return (aHash >> 28) ^ (aHash << 4) ^ aCh;
|
|
}
|
|
|
|
#ifdef __GNUC__
|
|
#define GFX_MAYBE_UNUSED __attribute__((unused))
|
|
#else
|
|
#define GFX_MAYBE_UNUSED
|
|
#endif
|
|
|
|
template<typename T>
|
|
gfxShapedWord*
|
|
gfxFont::GetShapedWord(gfxContext *aContext,
|
|
const T *aText,
|
|
uint32_t aLength,
|
|
uint32_t aHash,
|
|
int32_t aRunScript,
|
|
int32_t aAppUnitsPerDevUnit,
|
|
uint32_t aFlags,
|
|
gfxTextPerfMetrics *aTextPerf GFX_MAYBE_UNUSED)
|
|
{
|
|
// if the cache is getting too big, flush it and start over
|
|
uint32_t wordCacheMaxEntries =
|
|
gfxPlatform::GetPlatform()->WordCacheMaxEntries();
|
|
if (mWordCache->Count() > wordCacheMaxEntries) {
|
|
NS_WARNING("flushing shaped-word cache");
|
|
ClearCachedWords();
|
|
}
|
|
|
|
// if there's a cached entry for this word, just return it
|
|
CacheHashKey key(aText, aLength, aHash,
|
|
aRunScript,
|
|
aAppUnitsPerDevUnit,
|
|
aFlags);
|
|
|
|
CacheHashEntry *entry = mWordCache->PutEntry(key);
|
|
if (!entry) {
|
|
NS_WARNING("failed to create word cache entry - expect missing text");
|
|
return nullptr;
|
|
}
|
|
gfxShapedWord *sw = entry->mShapedWord;
|
|
|
|
bool isContent = !mStyle.systemFont;
|
|
|
|
if (sw) {
|
|
sw->ResetAge();
|
|
Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_HITS_CONTENT :
|
|
Telemetry::WORD_CACHE_HITS_CHROME),
|
|
aLength);
|
|
#ifndef RELEASE_BUILD
|
|
if (aTextPerf) {
|
|
aTextPerf->current.wordCacheHit++;
|
|
}
|
|
#endif
|
|
return sw;
|
|
}
|
|
|
|
Telemetry::Accumulate((isContent ? Telemetry::WORD_CACHE_MISSES_CONTENT :
|
|
Telemetry::WORD_CACHE_MISSES_CHROME),
|
|
aLength);
|
|
#ifndef RELEASE_BUILD
|
|
if (aTextPerf) {
|
|
aTextPerf->current.wordCacheMiss++;
|
|
}
|
|
#endif
|
|
|
|
sw = entry->mShapedWord = gfxShapedWord::Create(aText, aLength,
|
|
aRunScript,
|
|
aAppUnitsPerDevUnit,
|
|
aFlags);
|
|
if (!sw) {
|
|
NS_WARNING("failed to create gfxShapedWord - expect missing text");
|
|
return nullptr;
|
|
}
|
|
|
|
DebugOnly<bool> ok =
|
|
ShapeText(aContext, aText, 0, aLength, aRunScript, sw);
|
|
|
|
NS_WARN_IF_FALSE(ok, "failed to shape word - expect garbled text");
|
|
|
|
return sw;
|
|
}
|
|
|
|
bool
|
|
gfxFont::CacheHashEntry::KeyEquals(const KeyTypePointer aKey) const
|
|
{
|
|
const gfxShapedWord *sw = mShapedWord;
|
|
if (!sw) {
|
|
return false;
|
|
}
|
|
if (sw->GetLength() != aKey->mLength ||
|
|
sw->Flags() != aKey->mFlags ||
|
|
sw->GetAppUnitsPerDevUnit() != aKey->mAppUnitsPerDevUnit ||
|
|
sw->Script() != 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 PRUnichar *s2 = aKey->mText.mDouble;
|
|
const PRUnichar *s2end = s2 + aKey->mLength;
|
|
while (s2 < s2end) {
|
|
if (*s1++ != *s2++) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
NS_ASSERTION((aKey->mFlags & gfxTextRunFactory::TEXT_IS_8BIT) == 0 &&
|
|
!aKey->mTextIs8Bit, "didn't expect 8-bit text here");
|
|
return (0 == memcmp(sw->TextUnicode(), aKey->mText.mDouble,
|
|
aKey->mLength * sizeof(PRUnichar)));
|
|
}
|
|
|
|
bool
|
|
gfxFont::ShapeText(gfxContext *aContext,
|
|
const uint8_t *aText,
|
|
uint32_t aOffset,
|
|
uint32_t aLength,
|
|
int32_t aScript,
|
|
gfxShapedText *aShapedText,
|
|
bool aPreferPlatformShaping)
|
|
{
|
|
nsDependentCSubstring ascii((const char*)aText, aLength);
|
|
nsAutoString utf16;
|
|
AppendASCIItoUTF16(ascii, utf16);
|
|
if (utf16.Length() != aLength) {
|
|
return false;
|
|
}
|
|
return ShapeText(aContext, utf16.BeginReading(), aOffset, aLength,
|
|
aScript, aShapedText, aPreferPlatformShaping);
|
|
}
|
|
|
|
bool
|
|
gfxFont::ShapeText(gfxContext *aContext,
|
|
const PRUnichar *aText,
|
|
uint32_t aOffset,
|
|
uint32_t aLength,
|
|
int32_t aScript,
|
|
gfxShapedText *aShapedText,
|
|
bool aPreferPlatformShaping)
|
|
{
|
|
bool ok = false;
|
|
|
|
if (mGraphiteShaper && gfxPlatform::GetPlatform()->UseGraphiteShaping()) {
|
|
ok = mGraphiteShaper->ShapeText(aContext, aText, aOffset, aLength,
|
|
aScript, aShapedText);
|
|
}
|
|
|
|
if (!ok && mHarfBuzzShaper && !aPreferPlatformShaping) {
|
|
if (gfxPlatform::GetPlatform()->UseHarfBuzzForScript(aScript)) {
|
|
ok = mHarfBuzzShaper->ShapeText(aContext, aText, aOffset, aLength,
|
|
aScript, aShapedText);
|
|
}
|
|
}
|
|
|
|
if (!ok) {
|
|
if (!mPlatformShaper) {
|
|
CreatePlatformShaper();
|
|
NS_ASSERTION(mPlatformShaper, "no platform shaper available!");
|
|
}
|
|
if (mPlatformShaper) {
|
|
ok = mPlatformShaper->ShapeText(aContext, aText, aOffset, aLength,
|
|
aScript, aShapedText);
|
|
}
|
|
}
|
|
|
|
PostShapingFixup(aContext, aText, aOffset, aLength, aShapedText);
|
|
|
|
return ok;
|
|
}
|
|
|
|
void
|
|
gfxFont::PostShapingFixup(gfxContext *aContext,
|
|
const PRUnichar *aText,
|
|
uint32_t aOffset,
|
|
uint32_t aLength,
|
|
gfxShapedText *aShapedText)
|
|
{
|
|
if (IsSyntheticBold()) {
|
|
float synBoldOffset =
|
|
GetSyntheticBoldOffset() * CalcXScale(aContext);
|
|
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(gfxContext *aContext,
|
|
const T *aText,
|
|
uint32_t aOffset,
|
|
uint32_t aLength,
|
|
int32_t aScript,
|
|
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(PRUnichar)) {
|
|
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(aContext, aText, aOffset, fragLen, aScript, 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 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(gfxContext *aContext,
|
|
const T *aText,
|
|
uint32_t aOffset,
|
|
uint32_t aLength,
|
|
int32_t aScript,
|
|
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(aContext, aText + fragStart,
|
|
aOffset + fragStart, length,
|
|
aScript, aTextRun);
|
|
}
|
|
|
|
if (i == aLength) {
|
|
break;
|
|
}
|
|
|
|
// fragment was terminated by an invalid char: skip it,
|
|
// unless it's a control char that we want to show as a hexbox,
|
|
// but record where TAB or NEWLINE occur
|
|
if (ch == '\t') {
|
|
aTextRun->SetIsTab(aOffset + i);
|
|
} else if (ch == '\n') {
|
|
aTextRun->SetIsNewline(aOffset + i);
|
|
} else if (IsInvalidControlChar(ch)) {
|
|
aTextRun->SetMissingGlyph(aOffset + i, ch, this);
|
|
}
|
|
fragStart = i + 1;
|
|
}
|
|
|
|
NS_WARN_IF_FALSE(ok, "failed to shape text - expect garbled text");
|
|
return ok;
|
|
}
|
|
|
|
#ifndef RELEASE_BUILD
|
|
#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(PRUnichar aCh) { return aCh < 0x100; }
|
|
|
|
template<typename T>
|
|
bool
|
|
gfxFont::SplitAndInitTextRun(gfxContext *aContext,
|
|
gfxTextRun *aTextRun,
|
|
const T *aString,
|
|
uint32_t aRunStart,
|
|
uint32_t aRunLength,
|
|
int32_t aRunScript)
|
|
{
|
|
if (aRunLength == 0) {
|
|
return true;
|
|
}
|
|
|
|
gfxTextPerfMetrics *tp = nullptr;
|
|
|
|
#ifndef RELEASE_BUILD
|
|
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
|
|
|
|
if (BypassShapedWordCache(aRunScript)) {
|
|
TEXT_PERF_INCR(tp, wordCacheSpaceRules);
|
|
return ShapeTextWithoutWordCache(aContext, aString + aRunStart,
|
|
aRunStart, aRunLength, aRunScript,
|
|
aTextRun);
|
|
}
|
|
|
|
InitWordCache();
|
|
uint32_t wordCacheCharLimit =
|
|
gfxPlatform::GetPlatform()->WordCacheCharLimit();
|
|
|
|
// the only flags we care about for ShapedWord construction/caching
|
|
uint32_t flags = aTextRun->GetFlags();
|
|
flags &= (gfxTextRunFactory::TEXT_IS_RTL |
|
|
gfxTextRunFactory::TEXT_DISABLE_OPTIONAL_LIGATURES);
|
|
if (sizeof(T) == sizeof(uint8_t)) {
|
|
flags |= gfxTextRunFactory::TEXT_IS_8BIT;
|
|
}
|
|
|
|
const T *text = aString + aRunStart;
|
|
uint32_t wordStart = 0;
|
|
uint32_t hash = 0;
|
|
bool wordIs8Bit = true;
|
|
int32_t appUnitsPerDevUnit = aTextRun->GetAppUnitsPerDevUnit();
|
|
|
|
T nextCh = text[0];
|
|
for (uint32_t i = 0; i <= aRunLength; ++i) {
|
|
T ch = nextCh;
|
|
nextCh = (i < aRunLength - 1) ? text[i + 1] : '\n';
|
|
bool 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 = 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(aContext,
|
|
text + wordStart,
|
|
aRunStart + wordStart,
|
|
length,
|
|
aRunScript,
|
|
aTextRun);
|
|
if (!ok) {
|
|
return false;
|
|
}
|
|
} else if (length > 0) {
|
|
uint32_t wordFlags = flags;
|
|
// in the 8-bit version of this method, TEXT_IS_8BIT was
|
|
// already set as part of |flags|, so no need for a per-word
|
|
// adjustment here
|
|
if (sizeof(T) == sizeof(PRUnichar)) {
|
|
if (wordIs8Bit) {
|
|
wordFlags |= gfxTextRunFactory::TEXT_IS_8BIT;
|
|
}
|
|
}
|
|
gfxShapedWord *sw = GetShapedWord(aContext,
|
|
text + wordStart, length,
|
|
hash, aRunScript,
|
|
appUnitsPerDevUnit,
|
|
wordFlags, tp);
|
|
if (sw) {
|
|
aTextRun->CopyGlyphDataFrom(sw, aRunStart + wordStart);
|
|
} else {
|
|
return false; // failed, presumably out of memory?
|
|
}
|
|
}
|
|
|
|
if (boundary) {
|
|
// word was terminated by a space: add that to the textrun
|
|
if (!aTextRun->SetSpaceGlyphIfSimple(this, aContext,
|
|
aRunStart + i, ch))
|
|
{
|
|
static const uint8_t space = ' ';
|
|
gfxShapedWord *sw =
|
|
GetShapedWord(aContext,
|
|
&space, 1,
|
|
HashMix(0, ' '), aRunScript,
|
|
appUnitsPerDevUnit,
|
|
flags | gfxTextRunFactory::TEXT_IS_8BIT, tp);
|
|
if (sw) {
|
|
aTextRun->CopyGlyphDataFrom(sw, aRunStart + i);
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
hash = 0;
|
|
wordStart = i + 1;
|
|
wordIs8Bit = true;
|
|
continue;
|
|
}
|
|
|
|
if (i == aRunLength) {
|
|
break;
|
|
}
|
|
|
|
NS_ASSERTION(invalid,
|
|
"how did we get here except via an invalid char?");
|
|
|
|
// word was terminated by an invalid char: skip it,
|
|
// unless it's a control char that we want to show as a hexbox,
|
|
// but record where TAB or NEWLINE occur
|
|
if (ch == '\t') {
|
|
aTextRun->SetIsTab(aRunStart + i);
|
|
} else if (ch == '\n') {
|
|
aTextRun->SetIsNewline(aRunStart + i);
|
|
} else if (IsInvalidControlChar(ch)) {
|
|
aTextRun->SetMissingGlyph(aRunStart + i, ch, this);
|
|
}
|
|
|
|
hash = 0;
|
|
wordStart = i + 1;
|
|
wordIs8Bit = true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
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];
|
|
}
|
|
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(gfxContext *aContext, uint32_t aGlyphID, bool aNeedTight,
|
|
gfxGlyphExtents *aExtents)
|
|
{
|
|
gfxContextMatrixAutoSaveRestore matrixRestore(aContext);
|
|
aContext->IdentityMatrix();
|
|
|
|
gfxRect svgBounds;
|
|
if (mFontEntry->TryGetSVGData(this) && mFontEntry->HasSVGGlyph(aGlyphID) &&
|
|
mFontEntry->GetSVGGlyphExtents(aContext, aGlyphID, &svgBounds)) {
|
|
gfxFloat d2a = aExtents->GetAppUnitsPerDevUnit();
|
|
aExtents->SetTightGlyphExtents(aGlyphID,
|
|
gfxRect(svgBounds.x * d2a,
|
|
svgBounds.y * d2a,
|
|
svgBounds.width * d2a,
|
|
svgBounds.height * d2a));
|
|
return;
|
|
}
|
|
|
|
cairo_glyph_t glyph;
|
|
glyph.index = aGlyphID;
|
|
glyph.x = 0;
|
|
glyph.y = 0;
|
|
cairo_text_extents_t extents;
|
|
cairo_glyph_extents(aContext->GetCairo(), &glyph, 1, &extents);
|
|
|
|
const Metrics& fontMetrics = GetMetrics();
|
|
int32_t appUnitsPerDevUnit = aExtents->GetAppUnitsPerDevUnit();
|
|
if (!aNeedTight && extents.x_bearing >= 0 &&
|
|
extents.y_bearing >= -fontMetrics.maxAscent &&
|
|
extents.height + extents.y_bearing <= fontMetrics.maxDescent) {
|
|
uint32_t appUnitsWidth =
|
|
uint32_t(ceil((extents.x_bearing + extents.width)*appUnitsPerDevUnit));
|
|
if (appUnitsWidth < gfxGlyphExtents::INVALID_WIDTH) {
|
|
aExtents->SetContainedGlyphWidthAppUnits(aGlyphID, uint16_t(appUnitsWidth));
|
|
return;
|
|
}
|
|
}
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
if (!aNeedTight) {
|
|
++gGlyphExtentsSetupFallBackToTight;
|
|
}
|
|
#endif
|
|
|
|
gfxFloat d2a = appUnitsPerDevUnit;
|
|
gfxRect bounds(extents.x_bearing*d2a, extents.y_bearing*d2a,
|
|
extents.width*d2a, extents.height*d2a);
|
|
aExtents->SetTightGlyphExtents(aGlyphID, bounds);
|
|
}
|
|
|
|
// Try to initialize font metrics by reading sfnt tables directly;
|
|
// set mIsValid=TRUE and return TRUE on success.
|
|
// Return FALSE if the gfxFontEntry subclass does not
|
|
// implement GetFontTable(), or for non-sfnt fonts where tables are
|
|
// not available.
|
|
// If this returns TRUE without setting the mIsValid flag, then we -did-
|
|
// apparently find an sfnt, but it was too broken to be used.
|
|
bool
|
|
gfxFont::InitMetricsFromSfntTables(Metrics& aMetrics)
|
|
{
|
|
mIsValid = false; // font is NOT valid in case of early return
|
|
|
|
const uint32_t kHheaTableTag = TRUETYPE_TAG('h','h','e','a');
|
|
const uint32_t kPostTableTag = TRUETYPE_TAG('p','o','s','t');
|
|
const uint32_t kOS_2TableTag = TRUETYPE_TAG('O','S','/','2');
|
|
|
|
uint32_t len;
|
|
|
|
if (mFUnitsConvFactor == 0.0) {
|
|
// If the conversion factor from FUnits is not yet set,
|
|
// get the unitsPerEm from the 'head' table via the font entry
|
|
uint16_t unitsPerEm = GetFontEntry()->UnitsPerEm();
|
|
if (unitsPerEm == gfxFontEntry::kInvalidUPEM) {
|
|
return false;
|
|
}
|
|
mFUnitsConvFactor = mAdjustedSize / 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 HheaTable* hhea =
|
|
reinterpret_cast<const HheaTable*>(hb_blob_get_data(hheaTable, &len));
|
|
if (len < sizeof(HheaTable)) {
|
|
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));
|
|
if (len >= offsetof(OS2Table, sxHeight) + sizeof(int16_t) &&
|
|
uint16_t(os2->version) >= 2) {
|
|
// version 2 and later includes the x-height field
|
|
SET_SIGNED(xHeight, os2->sxHeight);
|
|
// Abs because of negative xHeight seen in Kokonor (Tibetan) font
|
|
aMetrics.xHeight = Abs(aMetrics.xHeight);
|
|
}
|
|
// this should always be present
|
|
if (len >= offsetof(OS2Table, yStrikeoutPosition) + sizeof(int16_t)) {
|
|
SET_SIGNED(aveCharWidth, os2->xAvgCharWidth);
|
|
SET_SIGNED(subscriptOffset, os2->ySubscriptYOffset);
|
|
SET_SIGNED(superscriptOffset, os2->ySuperscriptYOffset);
|
|
SET_SIGNED(strikeoutSize, os2->yStrikeoutSize);
|
|
SET_SIGNED(strikeoutOffset, os2->yStrikeoutPosition);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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.subscriptOffset) {
|
|
aMetrics.subscriptOffset = aMetrics.xHeight;
|
|
}
|
|
if (!aMetrics.superscriptOffset) {
|
|
aMetrics.superscriptOffset = aMetrics.xHeight;
|
|
}
|
|
|
|
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) {
|
|
memset(aMetrics, 0, sizeof(gfxFont::Metrics));
|
|
return;
|
|
}
|
|
|
|
// MS (P)Gothic and MS (P)Mincho are not having suitable values in their super script offset.
|
|
// If the values are not suitable, we should use x-height instead of them.
|
|
// See https://bugzilla.mozilla.org/show_bug.cgi?id=353632
|
|
if (aMetrics->superscriptOffset <= 0 ||
|
|
aMetrics->superscriptOffset >= aMetrics->maxAscent) {
|
|
aMetrics->superscriptOffset = aMetrics->xHeight;
|
|
}
|
|
// And also checking the case of sub script offset. The old gfx for win has checked this too.
|
|
if (aMetrics->subscriptOffset <= 0 ||
|
|
aMetrics->subscriptOffset >= aMetrics->maxAscent) {
|
|
aMetrics->subscriptOffset = aMetrics->xHeight;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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().zeroOrAveCharWidth; // figure space
|
|
case 0x2008: return GetMetrics().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;
|
|
}
|
|
}
|
|
|
|
/*static*/ size_t
|
|
gfxFont::WordCacheEntrySizeOfExcludingThis(CacheHashEntry* aHashEntry,
|
|
MallocSizeOf aMallocSizeOf,
|
|
void* aUserArg)
|
|
{
|
|
return aMallocSizeOf(aHashEntry->mShapedWord.get());
|
|
}
|
|
|
|
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->SizeOfExcludingThis(WordCacheEntrySizeOfExcludingThis,
|
|
aMallocSizeOf);
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxFont::AddSizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
|
|
FontCacheSizes* aSizes) const
|
|
{
|
|
aSizes->mFontInstances += aMallocSizeOf(this);
|
|
AddSizeOfExcludingThis(aMallocSizeOf, aSizes);
|
|
}
|
|
|
|
void
|
|
gfxFont::AddGlyphChangeObserver(GlyphChangeObserver *aObserver)
|
|
{
|
|
if (!mGlyphChangeObservers) {
|
|
mGlyphChangeObservers = new nsTHashtable<nsPtrHashKey<GlyphChangeObserver> >;
|
|
}
|
|
mGlyphChangeObservers->PutEntry(aObserver);
|
|
}
|
|
|
|
void
|
|
gfxFont::RemoveGlyphChangeObserver(GlyphChangeObserver *aObserver)
|
|
{
|
|
NS_ASSERTION(mGlyphChangeObservers, "No observers registered");
|
|
NS_ASSERTION(mGlyphChangeObservers->Contains(aObserver), "Observer not registered");
|
|
mGlyphChangeObservers->RemoveEntry(aObserver);
|
|
}
|
|
|
|
gfxGlyphExtents::~gfxGlyphExtents()
|
|
{
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
gGlyphExtentsWidthsTotalSize +=
|
|
mContainedGlyphWidths.SizeOfExcludingThis(&FontCacheMallocSizeOf);
|
|
gGlyphExtentsCount++;
|
|
#endif
|
|
MOZ_COUNT_DTOR(gfxGlyphExtents);
|
|
}
|
|
|
|
bool
|
|
gfxGlyphExtents::GetTightGlyphExtentsAppUnits(gfxFont *aFont,
|
|
gfxContext *aContext, uint32_t aGlyphID, gfxRect *aExtents)
|
|
{
|
|
HashEntry *entry = mTightGlyphExtents.GetEntry(aGlyphID);
|
|
if (!entry) {
|
|
if (!aContext) {
|
|
NS_WARNING("Could not get glyph extents (no aContext)");
|
|
return false;
|
|
}
|
|
|
|
if (aFont->SetupCairoFont(aContext)) {
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
++gGlyphExtentsSetupLazyTight;
|
|
#endif
|
|
aFont->SetupGlyphExtents(aContext, aGlyphID, true, this);
|
|
entry = mTightGlyphExtents.GetEntry(aGlyphID);
|
|
}
|
|
if (!entry) {
|
|
NS_WARNING("Could not get glyph extents");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
*aExtents = gfxRect(entry->x, entry->y, entry->width, entry->height);
|
|
return true;
|
|
}
|
|
|
|
gfxGlyphExtents::GlyphWidths::~GlyphWidths()
|
|
{
|
|
uint32_t i, count = mBlocks.Length();
|
|
for (i = 0; i < count; ++i) {
|
|
uintptr_t bits = mBlocks[i];
|
|
if (bits && !(bits & 0x1)) {
|
|
delete[] reinterpret_cast<uint16_t *>(bits);
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t
|
|
gfxGlyphExtents::GlyphWidths::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
|
{
|
|
uint32_t i;
|
|
uint32_t size = mBlocks.SizeOfExcludingThis(aMallocSizeOf);
|
|
for (i = 0; i < mBlocks.Length(); ++i) {
|
|
uintptr_t bits = mBlocks[i];
|
|
if (bits && !(bits & 0x1)) {
|
|
size += aMallocSizeOf(reinterpret_cast<void*>(bits));
|
|
}
|
|
}
|
|
return size;
|
|
}
|
|
|
|
void
|
|
gfxGlyphExtents::GlyphWidths::Set(uint32_t aGlyphID, uint16_t aWidth)
|
|
{
|
|
uint32_t block = aGlyphID >> BLOCK_SIZE_BITS;
|
|
uint32_t len = mBlocks.Length();
|
|
if (block >= len) {
|
|
uintptr_t *elems = mBlocks.AppendElements(block + 1 - len);
|
|
if (!elems)
|
|
return;
|
|
memset(elems, 0, sizeof(uintptr_t)*(block + 1 - len));
|
|
}
|
|
|
|
uintptr_t bits = mBlocks[block];
|
|
uint32_t glyphOffset = aGlyphID & (BLOCK_SIZE - 1);
|
|
if (!bits) {
|
|
mBlocks[block] = MakeSingle(glyphOffset, aWidth);
|
|
return;
|
|
}
|
|
|
|
uint16_t *newBlock;
|
|
if (bits & 0x1) {
|
|
// Expand the block to a real block. We could avoid this by checking
|
|
// glyphOffset == GetGlyphOffset(bits), but that never happens so don't bother
|
|
newBlock = new uint16_t[BLOCK_SIZE];
|
|
if (!newBlock)
|
|
return;
|
|
uint32_t i;
|
|
for (i = 0; i < BLOCK_SIZE; ++i) {
|
|
newBlock[i] = INVALID_WIDTH;
|
|
}
|
|
newBlock[GetGlyphOffset(bits)] = GetWidth(bits);
|
|
mBlocks[block] = reinterpret_cast<uintptr_t>(newBlock);
|
|
} else {
|
|
newBlock = reinterpret_cast<uint16_t *>(bits);
|
|
}
|
|
newBlock[glyphOffset] = aWidth;
|
|
}
|
|
|
|
void
|
|
gfxGlyphExtents::SetTightGlyphExtents(uint32_t aGlyphID, const gfxRect& aExtentsAppUnits)
|
|
{
|
|
HashEntry *entry = mTightGlyphExtents.PutEntry(aGlyphID);
|
|
if (!entry)
|
|
return;
|
|
entry->x = aExtentsAppUnits.X();
|
|
entry->y = aExtentsAppUnits.Y();
|
|
entry->width = aExtentsAppUnits.Width();
|
|
entry->height = aExtentsAppUnits.Height();
|
|
}
|
|
|
|
size_t
|
|
gfxGlyphExtents::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
|
|
{
|
|
return mContainedGlyphWidths.SizeOfExcludingThis(aMallocSizeOf) +
|
|
mTightGlyphExtents.SizeOfExcludingThis(nullptr, aMallocSizeOf);
|
|
}
|
|
|
|
size_t
|
|
gfxGlyphExtents::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
|
|
{
|
|
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
gfxFontGroup::gfxFontGroup(const nsAString& aFamilies,
|
|
const gfxFontStyle *aStyle,
|
|
gfxUserFontSet *aUserFontSet)
|
|
: mFamilies(aFamilies)
|
|
, mStyle(*aStyle)
|
|
, mUnderlineOffset(UNDERLINE_OFFSET_NOT_SET)
|
|
, mHyphenWidth(-1)
|
|
, mTextPerf(nullptr)
|
|
{
|
|
mUserFontSet = nullptr;
|
|
SetUserFontSet(aUserFontSet);
|
|
|
|
mSkipDrawing = false;
|
|
|
|
mPageLang = gfxPlatform::GetFontPrefLangFor(mStyle.language);
|
|
BuildFontList();
|
|
}
|
|
|
|
void
|
|
gfxFontGroup::BuildFontList()
|
|
{
|
|
// "#if" to be removed once all platforms are moved to gfxPlatformFontList interface
|
|
// and subclasses of gfxFontGroup eliminated
|
|
#if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID)
|
|
ForEachFont(FindPlatformFont, this);
|
|
|
|
if (mFonts.Length() == 0) {
|
|
bool needsBold;
|
|
gfxPlatformFontList *pfl = gfxPlatformFontList::PlatformFontList();
|
|
gfxFontFamily *defaultFamily = pfl->GetDefaultFont(&mStyle);
|
|
NS_ASSERTION(defaultFamily,
|
|
"invalid default font returned by GetDefaultFont");
|
|
|
|
if (defaultFamily) {
|
|
gfxFontEntry *fe = defaultFamily->FindFontForStyle(mStyle,
|
|
needsBold);
|
|
if (fe) {
|
|
nsRefPtr<gfxFont> font = fe->FindOrMakeFont(&mStyle,
|
|
needsBold);
|
|
if (font) {
|
|
mFonts.AppendElement(FamilyFace(defaultFamily, font));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mFonts.Length() == 0) {
|
|
// Try for a "font of last resort...."
|
|
// Because an empty font list would be Really Bad for later code
|
|
// that assumes it will be able to get valid metrics for layout,
|
|
// just look for the first usable font and put in the list.
|
|
// (see bug 554544)
|
|
nsAutoTArray<nsRefPtr<gfxFontFamily>,200> families;
|
|
pfl->GetFontFamilyList(families);
|
|
uint32_t count = families.Length();
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
gfxFontEntry *fe = families[i]->FindFontForStyle(mStyle,
|
|
needsBold);
|
|
if (fe) {
|
|
nsRefPtr<gfxFont> font = fe->FindOrMakeFont(&mStyle,
|
|
needsBold);
|
|
if (font) {
|
|
mFonts.AppendElement(FamilyFace(families[i], font));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (mFonts.Length() == 0) {
|
|
// an empty font list at this point is fatal; we're not going to
|
|
// be able to do even the most basic layout operations
|
|
char msg[256]; // CHECK buffer length if revising message below
|
|
sprintf(msg, "unable to find a usable font (%.220s)",
|
|
NS_ConvertUTF16toUTF8(mFamilies).get());
|
|
NS_RUNTIMEABORT(msg);
|
|
}
|
|
}
|
|
|
|
if (!mStyle.systemFont) {
|
|
uint32_t count = mFonts.Length();
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
gfxFont* font = mFonts[i].Font();
|
|
if (font->GetFontEntry()->mIsBadUnderlineFont) {
|
|
gfxFloat first = mFonts[0].Font()->GetMetrics().underlineOffset;
|
|
gfxFloat bad = font->GetMetrics().underlineOffset;
|
|
mUnderlineOffset = std::min(first, bad);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
gfxFontGroup::FindPlatformFont(const nsAString& aName,
|
|
const nsACString& aGenericName,
|
|
bool aUseFontSet,
|
|
void *aClosure)
|
|
{
|
|
gfxFontGroup *fontGroup = static_cast<gfxFontGroup*>(aClosure);
|
|
const gfxFontStyle *fontStyle = fontGroup->GetStyle();
|
|
|
|
bool needsBold;
|
|
gfxFontFamily *family = nullptr;
|
|
gfxFontEntry *fe = nullptr;
|
|
|
|
if (aUseFontSet) {
|
|
// First, look up in the user font set...
|
|
// If the fontSet matches the family, we must not look for a platform
|
|
// font of the same name, even if we fail to actually get a fontEntry
|
|
// here; we'll fall back to the next name in the CSS font-family list.
|
|
gfxUserFontSet *fs = fontGroup->GetUserFontSet();
|
|
if (fs) {
|
|
// If the fontSet matches the family, but the font has not yet finished
|
|
// loading (nor has its load timeout fired), the fontGroup should wait
|
|
// for the download, and not actually draw its text yet.
|
|
family = fs->GetFamily(aName);
|
|
if (family) {
|
|
bool waitForUserFont = false;
|
|
fe = fs->FindFontEntry(family, *fontStyle,
|
|
needsBold, waitForUserFont);
|
|
if (!fe && waitForUserFont) {
|
|
fontGroup->mSkipDrawing = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Not known in the user font set ==> check system fonts
|
|
if (!family) {
|
|
gfxPlatformFontList *fontList = gfxPlatformFontList::PlatformFontList();
|
|
family = fontList->FindFamily(aName);
|
|
if (family) {
|
|
fe = family->FindFontForStyle(*fontStyle, needsBold);
|
|
}
|
|
}
|
|
|
|
// add to the font group, unless it's already there
|
|
if (fe && !fontGroup->HasFont(fe)) {
|
|
nsRefPtr<gfxFont> font = fe->FindOrMakeFont(fontStyle, needsBold);
|
|
if (font) {
|
|
fontGroup->mFonts.AppendElement(FamilyFace(family, font));
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
gfxFontGroup::HasFont(const gfxFontEntry *aFontEntry)
|
|
{
|
|
uint32_t count = mFonts.Length();
|
|
for (uint32_t i = 0; i < count; ++i) {
|
|
if (mFonts[i].Font()->GetFontEntry() == aFontEntry)
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
gfxFontGroup::~gfxFontGroup()
|
|
{
|
|
mFonts.Clear();
|
|
SetUserFontSet(nullptr);
|
|
}
|
|
|
|
gfxFontGroup *
|
|
gfxFontGroup::Copy(const gfxFontStyle *aStyle)
|
|
{
|
|
gfxFontGroup *fg = new gfxFontGroup(mFamilies, aStyle, mUserFontSet);
|
|
fg->SetTextPerfMetrics(mTextPerf);
|
|
return fg;
|
|
}
|
|
|
|
bool
|
|
gfxFontGroup::IsInvalidChar(uint8_t ch)
|
|
{
|
|
return ((ch & 0x7f) < 0x20 || ch == 0x7f);
|
|
}
|
|
|
|
bool
|
|
gfxFontGroup::IsInvalidChar(PRUnichar ch)
|
|
{
|
|
// All printable 7-bit ASCII values are OK
|
|
if (ch >= ' ' && ch < 0x7f) {
|
|
return false;
|
|
}
|
|
// No point in sending non-printing control chars through font shaping
|
|
if (ch <= 0x9f) {
|
|
return true;
|
|
}
|
|
return (((ch & 0xFF00) == 0x2000 /* Unicode control character */ &&
|
|
(ch == 0x200B/*ZWSP*/ || ch == 0x2028/*LSEP*/ || ch == 0x2029/*PSEP*/)) ||
|
|
IsBidiControl(ch));
|
|
}
|
|
|
|
bool
|
|
gfxFontGroup::ForEachFont(FontCreationCallback fc,
|
|
void *closure)
|
|
{
|
|
return ForEachFontInternal(mFamilies, mStyle.language,
|
|
true, true, true, fc, closure);
|
|
}
|
|
|
|
bool
|
|
gfxFontGroup::ForEachFont(const nsAString& aFamilies,
|
|
nsIAtom *aLanguage,
|
|
FontCreationCallback fc,
|
|
void *closure)
|
|
{
|
|
return ForEachFontInternal(aFamilies, aLanguage,
|
|
false, true, true, fc, closure);
|
|
}
|
|
|
|
struct ResolveData {
|
|
ResolveData(gfxFontGroup::FontCreationCallback aCallback,
|
|
nsACString& aGenericFamily,
|
|
bool aUseFontSet,
|
|
void *aClosure) :
|
|
mCallback(aCallback),
|
|
mGenericFamily(aGenericFamily),
|
|
mUseFontSet(aUseFontSet),
|
|
mClosure(aClosure) {
|
|
}
|
|
gfxFontGroup::FontCreationCallback mCallback;
|
|
nsCString mGenericFamily;
|
|
bool mUseFontSet;
|
|
void *mClosure;
|
|
};
|
|
|
|
bool
|
|
gfxFontGroup::ForEachFontInternal(const nsAString& aFamilies,
|
|
nsIAtom *aLanguage,
|
|
bool aResolveGeneric,
|
|
bool aResolveFontName,
|
|
bool aUseFontSet,
|
|
FontCreationCallback fc,
|
|
void *closure)
|
|
{
|
|
const PRUnichar kSingleQuote = PRUnichar('\'');
|
|
const PRUnichar kDoubleQuote = PRUnichar('\"');
|
|
const PRUnichar kComma = PRUnichar(',');
|
|
|
|
nsIAtom *groupAtom = nullptr;
|
|
nsAutoCString groupString;
|
|
if (aLanguage) {
|
|
if (!gLangService) {
|
|
CallGetService(NS_LANGUAGEATOMSERVICE_CONTRACTID, &gLangService);
|
|
}
|
|
if (gLangService) {
|
|
nsresult rv;
|
|
groupAtom = gLangService->GetLanguageGroup(aLanguage, &rv);
|
|
}
|
|
}
|
|
if (!groupAtom) {
|
|
groupAtom = nsGkAtoms::Unicode;
|
|
}
|
|
groupAtom->ToUTF8String(groupString);
|
|
|
|
nsPromiseFlatString families(aFamilies);
|
|
const PRUnichar *p, *p_end;
|
|
families.BeginReading(p);
|
|
families.EndReading(p_end);
|
|
nsAutoString family;
|
|
nsAutoCString lcFamily;
|
|
nsAutoString genericFamily;
|
|
|
|
while (p < p_end) {
|
|
while (nsCRT::IsAsciiSpace(*p) || *p == kComma)
|
|
if (++p == p_end)
|
|
return true;
|
|
|
|
bool generic;
|
|
if (*p == kSingleQuote || *p == kDoubleQuote) {
|
|
// quoted font family
|
|
PRUnichar quoteMark = *p;
|
|
if (++p == p_end)
|
|
return true;
|
|
const PRUnichar *nameStart = p;
|
|
|
|
// XXX What about CSS character escapes?
|
|
while (*p != quoteMark)
|
|
if (++p == p_end)
|
|
return true;
|
|
|
|
family = Substring(nameStart, p);
|
|
generic = false;
|
|
genericFamily.SetIsVoid(true);
|
|
|
|
while (++p != p_end && *p != kComma)
|
|
/* nothing */ ;
|
|
|
|
} else {
|
|
// unquoted font family
|
|
const PRUnichar *nameStart = p;
|
|
while (++p != p_end && *p != kComma)
|
|
/* nothing */ ;
|
|
|
|
family = Substring(nameStart, p);
|
|
family.CompressWhitespace(false, true);
|
|
|
|
if (aResolveGeneric &&
|
|
(family.LowerCaseEqualsLiteral("serif") ||
|
|
family.LowerCaseEqualsLiteral("sans-serif") ||
|
|
family.LowerCaseEqualsLiteral("monospace") ||
|
|
family.LowerCaseEqualsLiteral("cursive") ||
|
|
family.LowerCaseEqualsLiteral("fantasy")))
|
|
{
|
|
generic = true;
|
|
|
|
ToLowerCase(NS_LossyConvertUTF16toASCII(family), lcFamily);
|
|
|
|
nsAutoCString prefName("font.name.");
|
|
prefName.Append(lcFamily);
|
|
prefName.AppendLiteral(".");
|
|
prefName.Append(groupString);
|
|
|
|
nsAdoptingString value = Preferences::GetString(prefName.get());
|
|
if (value) {
|
|
CopyASCIItoUTF16(lcFamily, genericFamily);
|
|
family = value;
|
|
}
|
|
} else {
|
|
generic = false;
|
|
genericFamily.SetIsVoid(true);
|
|
}
|
|
}
|
|
|
|
NS_LossyConvertUTF16toASCII gf(genericFamily);
|
|
if (generic) {
|
|
ForEachFontInternal(family, groupAtom, false,
|
|
aResolveFontName, false,
|
|
fc, closure);
|
|
} else if (!family.IsEmpty()) {
|
|
if (aResolveFontName) {
|
|
ResolveData data(fc, gf, aUseFontSet, closure);
|
|
bool aborted = false, needsBold;
|
|
nsresult rv = NS_OK;
|
|
bool foundFamily = false;
|
|
bool waitForUserFont = false;
|
|
gfxFontEntry *fe = nullptr;
|
|
if (aUseFontSet && mUserFontSet) {
|
|
gfxFontFamily *fam = mUserFontSet->GetFamily(family);
|
|
if (fam) {
|
|
fe = mUserFontSet->FindFontEntry(fam, mStyle,
|
|
needsBold,
|
|
waitForUserFont);
|
|
}
|
|
}
|
|
if (fe) {
|
|
gfxFontGroup::FontResolverProc(family, &data);
|
|
} else {
|
|
if (waitForUserFont) {
|
|
mSkipDrawing = true;
|
|
}
|
|
if (!foundFamily) {
|
|
gfxPlatform *pf = gfxPlatform::GetPlatform();
|
|
rv = pf->ResolveFontName(family,
|
|
gfxFontGroup::FontResolverProc,
|
|
&data, aborted);
|
|
}
|
|
}
|
|
if (NS_FAILED(rv) || aborted)
|
|
return false;
|
|
}
|
|
else {
|
|
if (!fc(family, gf, aUseFontSet, closure))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (generic && aResolveGeneric) {
|
|
nsAutoCString prefName("font.name-list.");
|
|
prefName.Append(lcFamily);
|
|
prefName.AppendLiteral(".");
|
|
prefName.Append(groupString);
|
|
nsAdoptingString value = Preferences::GetString(prefName.get());
|
|
if (value) {
|
|
ForEachFontInternal(value, groupAtom, false,
|
|
aResolveFontName, false,
|
|
fc, closure);
|
|
}
|
|
}
|
|
|
|
++p; // may advance past p_end
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
gfxFontGroup::FontResolverProc(const nsAString& aName, void *aClosure)
|
|
{
|
|
ResolveData *data = reinterpret_cast<ResolveData*>(aClosure);
|
|
return (data->mCallback)(aName, data->mGenericFamily, data->mUseFontSet,
|
|
data->mClosure);
|
|
}
|
|
|
|
gfxTextRun *
|
|
gfxFontGroup::MakeEmptyTextRun(const Parameters *aParams, uint32_t aFlags)
|
|
{
|
|
aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT;
|
|
return gfxTextRun::Create(aParams, 0, this, aFlags);
|
|
}
|
|
|
|
gfxTextRun *
|
|
gfxFontGroup::MakeSpaceTextRun(const Parameters *aParams, uint32_t aFlags)
|
|
{
|
|
aFlags |= TEXT_IS_8BIT | TEXT_IS_ASCII | TEXT_IS_PERSISTENT;
|
|
|
|
gfxTextRun *textRun = gfxTextRun::Create(aParams, 1, this, aFlags);
|
|
if (!textRun) {
|
|
return nullptr;
|
|
}
|
|
|
|
gfxFont *font = GetFontAt(0);
|
|
if (MOZ_UNLIKELY(GetStyle()->size == 0)) {
|
|
// Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
|
|
// them, and always create at least size 1 fonts, i.e. they still
|
|
// render something for size 0 fonts.
|
|
textRun->AddGlyphRun(font, gfxTextRange::kFontGroup, 0, false);
|
|
}
|
|
else {
|
|
textRun->SetSpaceGlyph(font, aParams->mContext, 0);
|
|
}
|
|
|
|
// Note that the gfxGlyphExtents glyph bounds storage for the font will
|
|
// always contain an entry for the font's space glyph, so we don't have
|
|
// to call FetchGlyphExtents here.
|
|
return textRun;
|
|
}
|
|
|
|
gfxTextRun *
|
|
gfxFontGroup::MakeBlankTextRun(uint32_t aLength,
|
|
const Parameters *aParams, uint32_t aFlags)
|
|
{
|
|
gfxTextRun *textRun =
|
|
gfxTextRun::Create(aParams, aLength, this, aFlags);
|
|
if (!textRun) {
|
|
return nullptr;
|
|
}
|
|
|
|
textRun->AddGlyphRun(GetFontAt(0), gfxTextRange::kFontGroup, 0, false);
|
|
return textRun;
|
|
}
|
|
|
|
gfxTextRun *
|
|
gfxFontGroup::MakeHyphenTextRun(gfxContext *aCtx, uint32_t aAppUnitsPerDevUnit)
|
|
{
|
|
// only use U+2010 if it is supported by the first font in the group;
|
|
// it's better to use ASCII '-' from the primary font than to fall back to
|
|
// U+2010 from some other, possibly poorly-matching face
|
|
static const PRUnichar hyphen = 0x2010;
|
|
gfxFont *font = GetFontAt(0);
|
|
if (font && font->HasCharacter(hyphen)) {
|
|
return MakeTextRun(&hyphen, 1, aCtx, aAppUnitsPerDevUnit,
|
|
gfxFontGroup::TEXT_IS_PERSISTENT);
|
|
}
|
|
|
|
static const uint8_t dash = '-';
|
|
return MakeTextRun(&dash, 1, aCtx, aAppUnitsPerDevUnit,
|
|
gfxFontGroup::TEXT_IS_PERSISTENT);
|
|
}
|
|
|
|
gfxFloat
|
|
gfxFontGroup::GetHyphenWidth(gfxContext *aCtx, uint32_t aAppUnitsPerDevUnit)
|
|
{
|
|
if (mHyphenWidth < 0) {
|
|
nsAutoPtr<gfxTextRun> hyphRun(MakeHyphenTextRun(aCtx,
|
|
aAppUnitsPerDevUnit));
|
|
mHyphenWidth = hyphRun.get() ?
|
|
hyphRun->GetAdvanceWidth(0, hyphRun->GetLength(), nullptr) : 0;
|
|
}
|
|
return mHyphenWidth;
|
|
}
|
|
|
|
gfxTextRun *
|
|
gfxFontGroup::MakeTextRun(const uint8_t *aString, uint32_t aLength,
|
|
const Parameters *aParams, uint32_t aFlags)
|
|
{
|
|
if (aLength == 0) {
|
|
return MakeEmptyTextRun(aParams, aFlags);
|
|
}
|
|
if (aLength == 1 && aString[0] == ' ') {
|
|
return MakeSpaceTextRun(aParams, aFlags);
|
|
}
|
|
|
|
aFlags |= TEXT_IS_8BIT;
|
|
|
|
if (GetStyle()->size == 0) {
|
|
// Short-circuit for size-0 fonts, as Windows and ATSUI can't handle
|
|
// them, and always create at least size 1 fonts, i.e. they still
|
|
// render something for size 0 fonts.
|
|
return MakeBlankTextRun(aLength, aParams, aFlags);
|
|
}
|
|
|
|
gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength,
|
|
this, aFlags);
|
|
if (!textRun) {
|
|
return nullptr;
|
|
}
|
|
|
|
InitTextRun(aParams->mContext, textRun, aString, aLength);
|
|
|
|
textRun->FetchGlyphExtents(aParams->mContext);
|
|
|
|
return textRun;
|
|
}
|
|
|
|
gfxTextRun *
|
|
gfxFontGroup::MakeTextRun(const PRUnichar *aString, uint32_t aLength,
|
|
const Parameters *aParams, uint32_t aFlags)
|
|
{
|
|
if (aLength == 0) {
|
|
return MakeEmptyTextRun(aParams, aFlags);
|
|
}
|
|
if (aLength == 1 && aString[0] == ' ') {
|
|
return MakeSpaceTextRun(aParams, aFlags);
|
|
}
|
|
if (GetStyle()->size == 0) {
|
|
return MakeBlankTextRun(aLength, aParams, aFlags);
|
|
}
|
|
|
|
gfxTextRun *textRun = gfxTextRun::Create(aParams, aLength,
|
|
this, aFlags);
|
|
if (!textRun) {
|
|
return nullptr;
|
|
}
|
|
|
|
InitTextRun(aParams->mContext, textRun, aString, aLength);
|
|
|
|
textRun->FetchGlyphExtents(aParams->mContext);
|
|
|
|
return textRun;
|
|
}
|
|
|
|
template<typename T>
|
|
void
|
|
gfxFontGroup::InitTextRun(gfxContext *aContext,
|
|
gfxTextRun *aTextRun,
|
|
const T *aString,
|
|
uint32_t aLength)
|
|
{
|
|
NS_ASSERTION(aLength > 0, "don't call InitTextRun for a zero-length run");
|
|
|
|
// we need to do numeral processing even on 8-bit text,
|
|
// in case we're converting Western to Hindi/Arabic digits
|
|
int32_t numOption = gfxPlatform::GetPlatform()->GetBidiNumeralOption();
|
|
nsAutoArrayPtr<PRUnichar> transformedString;
|
|
if (numOption != IBMBIDI_NUMERAL_NOMINAL) {
|
|
// scan the string for numerals that may need to be transformed;
|
|
// if we find any, we'll make a local copy here and use that for
|
|
// font matching and glyph generation/shaping
|
|
bool prevIsArabic =
|
|
(aTextRun->GetFlags() & gfxTextRunFactory::TEXT_INCOMING_ARABICCHAR) != 0;
|
|
for (uint32_t i = 0; i < aLength; ++i) {
|
|
PRUnichar origCh = aString[i];
|
|
PRUnichar newCh = HandleNumberInChar(origCh, prevIsArabic, numOption);
|
|
if (newCh != origCh) {
|
|
if (!transformedString) {
|
|
transformedString = new PRUnichar[aLength];
|
|
if (sizeof(T) == sizeof(PRUnichar)) {
|
|
memcpy(transformedString.get(), aString, i * sizeof(PRUnichar));
|
|
} else {
|
|
for (uint32_t j = 0; j < i; ++j) {
|
|
transformedString[j] = aString[j];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (transformedString) {
|
|
transformedString[i] = newCh;
|
|
}
|
|
prevIsArabic = IS_ARABIC_CHAR(newCh);
|
|
}
|
|
}
|
|
|
|
#ifdef PR_LOGGING
|
|
PRLogModuleInfo *log = (mStyle.systemFont ?
|
|
gfxPlatform::GetLog(eGfxLog_textrunui) :
|
|
gfxPlatform::GetLog(eGfxLog_textrun));
|
|
#endif
|
|
|
|
if (sizeof(T) == sizeof(uint8_t) && !transformedString) {
|
|
|
|
#ifdef PR_LOGGING
|
|
if (MOZ_UNLIKELY(log)) {
|
|
nsAutoCString lang;
|
|
mStyle.language->ToUTF8String(lang);
|
|
nsAutoCString str((const char*)aString, aLength);
|
|
PR_LOG(log, PR_LOG_WARNING,\
|
|
("(%s) fontgroup: [%s] lang: %s script: %d len %d "
|
|
"weight: %d width: %d style: %s size: %6.2f %d-byte "
|
|
"TEXTRUN [%s] ENDTEXTRUN\n",
|
|
(mStyle.systemFont ? "textrunui" : "textrun"),
|
|
NS_ConvertUTF16toUTF8(mFamilies).get(),
|
|
lang.get(), MOZ_SCRIPT_LATIN, aLength,
|
|
uint32_t(mStyle.weight), uint32_t(mStyle.stretch),
|
|
(mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" :
|
|
(mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" :
|
|
"normal")),
|
|
mStyle.size,
|
|
sizeof(T),
|
|
str.get()));
|
|
}
|
|
#endif
|
|
|
|
// the text is still purely 8-bit; bypass the script-run itemizer
|
|
// and treat it as a single Latin run
|
|
InitScriptRun(aContext, aTextRun, aString,
|
|
0, aLength, MOZ_SCRIPT_LATIN);
|
|
} else {
|
|
const PRUnichar *textPtr;
|
|
if (transformedString) {
|
|
textPtr = transformedString.get();
|
|
} else {
|
|
// typecast to avoid compilation error for the 8-bit version,
|
|
// even though this is dead code in that case
|
|
textPtr = reinterpret_cast<const PRUnichar*>(aString);
|
|
}
|
|
|
|
// split into script runs so that script can potentially influence
|
|
// the font matching process below
|
|
gfxScriptItemizer scriptRuns(textPtr, aLength);
|
|
|
|
uint32_t runStart = 0, runLimit = aLength;
|
|
int32_t runScript = MOZ_SCRIPT_LATIN;
|
|
while (scriptRuns.Next(runStart, runLimit, runScript)) {
|
|
|
|
#ifdef PR_LOGGING
|
|
if (MOZ_UNLIKELY(log)) {
|
|
nsAutoCString lang;
|
|
mStyle.language->ToUTF8String(lang);
|
|
uint32_t runLen = runLimit - runStart;
|
|
PR_LOG(log, PR_LOG_WARNING,\
|
|
("(%s) fontgroup: [%s] lang: %s script: %d len %d "
|
|
"weight: %d width: %d style: %s size: %6.2f %d-byte "
|
|
"TEXTRUN [%s] ENDTEXTRUN\n",
|
|
(mStyle.systemFont ? "textrunui" : "textrun"),
|
|
NS_ConvertUTF16toUTF8(mFamilies).get(),
|
|
lang.get(), runScript, runLen,
|
|
uint32_t(mStyle.weight), uint32_t(mStyle.stretch),
|
|
(mStyle.style & NS_FONT_STYLE_ITALIC ? "italic" :
|
|
(mStyle.style & NS_FONT_STYLE_OBLIQUE ? "oblique" :
|
|
"normal")),
|
|
mStyle.size,
|
|
sizeof(T),
|
|
NS_ConvertUTF16toUTF8(textPtr + runStart, runLen).get()));
|
|
}
|
|
#endif
|
|
|
|
InitScriptRun(aContext, aTextRun, textPtr,
|
|
runStart, runLimit, runScript);
|
|
}
|
|
}
|
|
|
|
if (sizeof(T) == sizeof(PRUnichar) && aLength > 0) {
|
|
gfxTextRun::CompressedGlyph *glyph = aTextRun->GetCharacterGlyphs();
|
|
if (!glyph->IsSimpleGlyph()) {
|
|
glyph->SetClusterStart(true);
|
|
}
|
|
}
|
|
|
|
// It's possible for CoreText to omit glyph runs if it decides they contain
|
|
// only invisibles (e.g., U+FEFF, see reftest 474417-1). In this case, we
|
|
// need to eliminate them from the glyph run array to avoid drawing "partial
|
|
// ligatures" with the wrong font.
|
|
// We don't do this during InitScriptRun (or gfxFont::InitTextRun) because
|
|
// it will iterate back over all glyphruns in the textrun, which leads to
|
|
// pathologically-bad perf in the case where a textrun contains many script
|
|
// changes (see bug 680402) - we'd end up re-sanitizing all the earlier runs
|
|
// every time a new script subrun is processed.
|
|
aTextRun->SanitizeGlyphRuns();
|
|
|
|
aTextRun->SortGlyphRuns();
|
|
}
|
|
|
|
template<typename T>
|
|
void
|
|
gfxFontGroup::InitScriptRun(gfxContext *aContext,
|
|
gfxTextRun *aTextRun,
|
|
const T *aString,
|
|
uint32_t aScriptRunStart,
|
|
uint32_t aScriptRunEnd,
|
|
int32_t aRunScript)
|
|
{
|
|
NS_ASSERTION(aScriptRunEnd > aScriptRunStart,
|
|
"don't call InitScriptRun for a zero-length run");
|
|
|
|
gfxFont *mainFont = GetFontAt(0);
|
|
|
|
uint32_t runStart = aScriptRunStart;
|
|
nsAutoTArray<gfxTextRange,3> fontRanges;
|
|
ComputeRanges(fontRanges, aString + aScriptRunStart,
|
|
aScriptRunEnd - aScriptRunStart, aRunScript);
|
|
uint32_t numRanges = fontRanges.Length();
|
|
|
|
for (uint32_t r = 0; r < numRanges; r++) {
|
|
const gfxTextRange& range = fontRanges[r];
|
|
uint32_t matchedLength = range.Length();
|
|
gfxFont *matchedFont = range.font;
|
|
|
|
// create the glyph run for this range
|
|
if (matchedFont) {
|
|
aTextRun->AddGlyphRun(matchedFont, range.matchType,
|
|
runStart, (matchedLength > 0));
|
|
// do glyph layout and record the resulting positioned glyphs
|
|
if (!matchedFont->SplitAndInitTextRun(aContext, aTextRun, aString,
|
|
runStart, matchedLength,
|
|
aRunScript)) {
|
|
// glyph layout failed! treat as missing glyphs
|
|
matchedFont = nullptr;
|
|
}
|
|
} else {
|
|
aTextRun->AddGlyphRun(mainFont, gfxTextRange::kFontGroup,
|
|
runStart, (matchedLength > 0));
|
|
}
|
|
|
|
if (!matchedFont) {
|
|
// We need to set cluster boundaries (and mark spaces) so that
|
|
// surrogate pairs, combining characters, etc behave properly,
|
|
// even if we don't have glyphs for them
|
|
aTextRun->SetupClusterBoundaries(runStart, aString + runStart,
|
|
matchedLength);
|
|
|
|
// various "missing" characters may need special handling,
|
|
// so we check for them here
|
|
uint32_t runLimit = runStart + matchedLength;
|
|
for (uint32_t index = runStart; index < runLimit; index++) {
|
|
T ch = aString[index];
|
|
|
|
// tab and newline are not to be displayed as hexboxes,
|
|
// but do need to be recorded in the textrun
|
|
if (ch == '\n') {
|
|
aTextRun->SetIsNewline(index);
|
|
continue;
|
|
}
|
|
if (ch == '\t') {
|
|
aTextRun->SetIsTab(index);
|
|
continue;
|
|
}
|
|
|
|
// for 16-bit textruns only, check for surrogate pairs and
|
|
// special Unicode spaces; omit these checks in 8-bit runs
|
|
if (sizeof(T) == sizeof(PRUnichar)) {
|
|
if (NS_IS_HIGH_SURROGATE(ch) &&
|
|
index + 1 < aScriptRunEnd &&
|
|
NS_IS_LOW_SURROGATE(aString[index + 1]))
|
|
{
|
|
aTextRun->SetMissingGlyph(index,
|
|
SURROGATE_TO_UCS4(ch,
|
|
aString[index + 1]),
|
|
mainFont);
|
|
index++;
|
|
continue;
|
|
}
|
|
|
|
// check if this is a known Unicode whitespace character that
|
|
// we can render using the space glyph with a custom width
|
|
gfxFloat wid = mainFont->SynthesizeSpaceWidth(ch);
|
|
if (wid >= 0.0) {
|
|
nscoord advance =
|
|
aTextRun->GetAppUnitsPerDevUnit() * floor(wid + 0.5);
|
|
if (gfxShapedText::CompressedGlyph::IsSimpleAdvance(advance)) {
|
|
aTextRun->GetCharacterGlyphs()[index].
|
|
SetSimpleGlyph(advance,
|
|
mainFont->GetSpaceGlyph());
|
|
} else {
|
|
gfxTextRun::DetailedGlyph detailedGlyph;
|
|
detailedGlyph.mGlyphID = mainFont->GetSpaceGlyph();
|
|
detailedGlyph.mAdvance = advance;
|
|
detailedGlyph.mXOffset = detailedGlyph.mYOffset = 0;
|
|
gfxShapedText::CompressedGlyph g;
|
|
g.SetComplex(true, true, 1);
|
|
aTextRun->SetGlyphs(index,
|
|
g, &detailedGlyph);
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (IsInvalidChar(ch)) {
|
|
// invalid chars are left as zero-width/invisible
|
|
continue;
|
|
}
|
|
|
|
// record char code so we can draw a box with the Unicode value
|
|
aTextRun->SetMissingGlyph(index, ch, mainFont);
|
|
}
|
|
}
|
|
|
|
runStart += matchedLength;
|
|
}
|
|
}
|
|
|
|
gfxTextRun *
|
|
gfxFontGroup::GetEllipsisTextRun(int32_t aAppUnitsPerDevPixel,
|
|
LazyReferenceContextGetter& aRefContextGetter)
|
|
{
|
|
if (mCachedEllipsisTextRun &&
|
|
mCachedEllipsisTextRun->GetAppUnitsPerDevUnit() == aAppUnitsPerDevPixel) {
|
|
return mCachedEllipsisTextRun;
|
|
}
|
|
|
|
// Use a Unicode ellipsis if the font supports it,
|
|
// otherwise use three ASCII periods as fallback.
|
|
gfxFont* firstFont = GetFontAt(0);
|
|
nsString ellipsis = firstFont->HasCharacter(kEllipsisChar[0])
|
|
? nsDependentString(kEllipsisChar,
|
|
ArrayLength(kEllipsisChar) - 1)
|
|
: nsDependentString(kASCIIPeriodsChar,
|
|
ArrayLength(kASCIIPeriodsChar) - 1);
|
|
|
|
nsRefPtr<gfxContext> refCtx = aRefContextGetter.GetRefContext();
|
|
Parameters params = {
|
|
refCtx, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevPixel
|
|
};
|
|
gfxTextRun* textRun =
|
|
MakeTextRun(ellipsis.get(), ellipsis.Length(), ¶ms, TEXT_IS_PERSISTENT);
|
|
if (!textRun) {
|
|
return nullptr;
|
|
}
|
|
mCachedEllipsisTextRun = textRun;
|
|
textRun->ReleaseFontGroup(); // don't let the presence of a cached ellipsis
|
|
// textrun prolong the fontgroup's life
|
|
return textRun;
|
|
}
|
|
|
|
already_AddRefed<gfxFont>
|
|
gfxFontGroup::TryAllFamilyMembers(gfxFontFamily* aFamily, uint32_t aCh)
|
|
{
|
|
if (!aFamily->TestCharacterMap(aCh)) {
|
|
return nullptr;
|
|
}
|
|
|
|
// Note that we don't need the actual runScript in matchData for
|
|
// gfxFontFamily::SearchAllFontsForChar, it's only used for the
|
|
// system-fallback case. So we can just set it to 0 here.
|
|
GlobalFontMatch matchData(aCh, 0, &mStyle);
|
|
aFamily->SearchAllFontsForChar(&matchData);
|
|
gfxFontEntry *fe = matchData.mBestMatch;
|
|
if (!fe) {
|
|
return nullptr;
|
|
}
|
|
|
|
bool needsBold = mStyle.weight >= 600 && !fe->IsBold();
|
|
nsRefPtr<gfxFont> font = fe->FindOrMakeFont(&mStyle, needsBold);
|
|
return font.forget();
|
|
}
|
|
|
|
already_AddRefed<gfxFont>
|
|
gfxFontGroup::FindFontForChar(uint32_t aCh, uint32_t aPrevCh,
|
|
int32_t aRunScript, gfxFont *aPrevMatchedFont,
|
|
uint8_t *aMatchType)
|
|
{
|
|
// To optimize common cases, try the first font in the font-group
|
|
// before going into the more detailed checks below
|
|
uint32_t nextIndex = 0;
|
|
bool isJoinControl = gfxFontUtils::IsJoinControl(aCh);
|
|
bool wasJoinCauser = gfxFontUtils::IsJoinCauser(aPrevCh);
|
|
bool isVarSelector = gfxFontUtils::IsVarSelector(aCh);
|
|
|
|
if (!isJoinControl && !wasJoinCauser && !isVarSelector) {
|
|
nsRefPtr<gfxFont> firstFont = mFonts[0].Font();
|
|
if (firstFont->HasCharacter(aCh)) {
|
|
*aMatchType = gfxTextRange::kFontGroup;
|
|
return firstFont.forget();
|
|
}
|
|
// It's possible that another font in the family (e.g. regular face,
|
|
// where the requested style was italic) will support the character
|
|
nsRefPtr<gfxFont> font = TryAllFamilyMembers(mFonts[0].Family(), aCh);
|
|
if (font) {
|
|
*aMatchType = gfxTextRange::kFontGroup;
|
|
return font.forget();
|
|
}
|
|
// we don't need to check the first font again below
|
|
++nextIndex;
|
|
}
|
|
|
|
if (aPrevMatchedFont) {
|
|
// Don't switch fonts for control characters, regardless of
|
|
// whether they are present in the current font, as they won't
|
|
// actually be rendered (see bug 716229)
|
|
uint8_t category = GetGeneralCategory(aCh);
|
|
if (category == HB_UNICODE_GENERAL_CATEGORY_CONTROL) {
|
|
nsRefPtr<gfxFont> ret = aPrevMatchedFont;
|
|
return ret.forget();
|
|
}
|
|
|
|
// if this character is a join-control or the previous is a join-causer,
|
|
// use the same font as the previous range if we can
|
|
if (isJoinControl || wasJoinCauser) {
|
|
if (aPrevMatchedFont->HasCharacter(aCh)) {
|
|
nsRefPtr<gfxFont> ret = aPrevMatchedFont;
|
|
return ret.forget();
|
|
}
|
|
}
|
|
}
|
|
|
|
// if this character is a variation selector,
|
|
// use the previous font regardless of whether it supports VS or not.
|
|
// otherwise the text run will be divided.
|
|
if (isVarSelector) {
|
|
if (aPrevMatchedFont) {
|
|
nsRefPtr<gfxFont> ret = aPrevMatchedFont;
|
|
return ret.forget();
|
|
}
|
|
// VS alone. it's meaningless to search different fonts
|
|
return nullptr;
|
|
}
|
|
|
|
// 1. check remaining fonts in the font group
|
|
uint32_t fontListLength = FontListLength();
|
|
for (uint32_t i = nextIndex; i < fontListLength; i++) {
|
|
nsRefPtr<gfxFont> font = mFonts[i].Font();
|
|
if (font->HasCharacter(aCh)) {
|
|
*aMatchType = gfxTextRange::kFontGroup;
|
|
return font.forget();
|
|
}
|
|
|
|
font = TryAllFamilyMembers(mFonts[i].Family(), aCh);
|
|
if (font) {
|
|
*aMatchType = gfxTextRange::kFontGroup;
|
|
return font.forget();
|
|
}
|
|
}
|
|
|
|
// if character is in Private Use Area, don't do matching against pref or system fonts
|
|
if ((aCh >= 0xE000 && aCh <= 0xF8FF) || (aCh >= 0xF0000 && aCh <= 0x10FFFD))
|
|
return nullptr;
|
|
|
|
// 2. search pref fonts
|
|
nsRefPtr<gfxFont> font = WhichPrefFontSupportsChar(aCh);
|
|
if (font) {
|
|
*aMatchType = gfxTextRange::kPrefsFallback;
|
|
return font.forget();
|
|
}
|
|
|
|
// 3. use fallback fonts
|
|
// -- before searching for something else check the font used for the previous character
|
|
if (aPrevMatchedFont && aPrevMatchedFont->HasCharacter(aCh)) {
|
|
*aMatchType = gfxTextRange::kSystemFallback;
|
|
nsRefPtr<gfxFont> ret = aPrevMatchedFont;
|
|
return ret.forget();
|
|
}
|
|
|
|
// never fall back for characters from unknown scripts
|
|
if (aRunScript == HB_SCRIPT_UNKNOWN) {
|
|
return nullptr;
|
|
}
|
|
|
|
// for known "space" characters, don't do a full system-fallback search;
|
|
// we'll synthesize appropriate-width spaces instead of missing-glyph boxes
|
|
if (GetGeneralCategory(aCh) ==
|
|
HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR &&
|
|
GetFontAt(0)->SynthesizeSpaceWidth(aCh) >= 0.0)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
// -- otherwise look for other stuff
|
|
*aMatchType = gfxTextRange::kSystemFallback;
|
|
font = WhichSystemFontSupportsChar(aCh, aRunScript);
|
|
return font.forget();
|
|
}
|
|
|
|
template<typename T>
|
|
void gfxFontGroup::ComputeRanges(nsTArray<gfxTextRange>& aRanges,
|
|
const T *aString, uint32_t aLength,
|
|
int32_t aRunScript)
|
|
{
|
|
NS_ASSERTION(aRanges.Length() == 0, "aRanges must be initially empty");
|
|
NS_ASSERTION(aLength > 0, "don't call ComputeRanges for zero-length text");
|
|
|
|
uint32_t prevCh = 0;
|
|
uint8_t matchType = 0;
|
|
int32_t lastRangeIndex = -1;
|
|
|
|
// initialize prevFont to the group's primary font, so that this will be
|
|
// used for string-initial control chars, etc rather than risk hitting font
|
|
// fallback for these (bug 716229)
|
|
gfxFont *prevFont = GetFontAt(0);
|
|
|
|
for (uint32_t i = 0; i < aLength; i++) {
|
|
|
|
const uint32_t origI = i; // save off in case we increase for surrogate
|
|
|
|
// set up current ch
|
|
uint32_t ch = aString[i];
|
|
|
|
// in 16-bit case only, check for surrogate pair
|
|
if (sizeof(T) == sizeof(PRUnichar)) {
|
|
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 = ' ';
|
|
}
|
|
|
|
// find the font for this char
|
|
nsRefPtr<gfxFont> font =
|
|
FindFontForChar(ch, prevCh, aRunScript, prevFont, &matchType);
|
|
|
|
#ifndef RELEASE_BUILD
|
|
if (MOZ_UNLIKELY(mTextPerf)) {
|
|
if (matchType == gfxTextRange::kPrefsFallback) {
|
|
mTextPerf->current.fallbackPrefs++;
|
|
} else if (matchType == gfxTextRange::kSystemFallback) {
|
|
mTextPerf->current.fallbackSystem++;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
prevCh = ch;
|
|
|
|
if (lastRangeIndex == -1) {
|
|
// first char ==> make a new range
|
|
aRanges.AppendElement(gfxTextRange(0, 1, font, matchType));
|
|
lastRangeIndex++;
|
|
prevFont = font;
|
|
} else {
|
|
// if font has changed, make a new range
|
|
gfxTextRange& prevRange = aRanges[lastRangeIndex];
|
|
if (prevRange.font != font || prevRange.matchType != matchType) {
|
|
// close out the previous range
|
|
prevRange.end = origI;
|
|
aRanges.AppendElement(gfxTextRange(origI, i + 1,
|
|
font, matchType));
|
|
lastRangeIndex++;
|
|
|
|
// update prevFont for the next match, *unless* we switched
|
|
// fonts on a ZWJ, in which case propagating the changed font
|
|
// is probably not a good idea (see bug 619511)
|
|
if (sizeof(T) == sizeof(uint8_t) ||
|
|
!gfxFontUtils::IsJoinCauser(ch))
|
|
{
|
|
prevFont = font;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
aRanges[lastRangeIndex].end = aLength;
|
|
}
|
|
|
|
gfxUserFontSet*
|
|
gfxFontGroup::GetUserFontSet()
|
|
{
|
|
return mUserFontSet;
|
|
}
|
|
|
|
void
|
|
gfxFontGroup::SetUserFontSet(gfxUserFontSet *aUserFontSet)
|
|
{
|
|
NS_IF_RELEASE(mUserFontSet);
|
|
mUserFontSet = aUserFontSet;
|
|
NS_IF_ADDREF(mUserFontSet);
|
|
mCurrGeneration = GetGeneration();
|
|
}
|
|
|
|
uint64_t
|
|
gfxFontGroup::GetGeneration()
|
|
{
|
|
if (!mUserFontSet)
|
|
return 0;
|
|
return mUserFontSet->GetGeneration();
|
|
}
|
|
|
|
void
|
|
gfxFontGroup::UpdateFontList()
|
|
{
|
|
if (mUserFontSet && mCurrGeneration != GetGeneration()) {
|
|
// xxx - can probably improve this to detect when all fonts were found, so no need to update list
|
|
mFonts.Clear();
|
|
mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET;
|
|
mSkipDrawing = false;
|
|
|
|
// bug 548184 - need to clean up FT2, OS/2 platform code to use BuildFontList
|
|
#if defined(XP_MACOSX) || defined(XP_WIN) || defined(ANDROID)
|
|
BuildFontList();
|
|
#else
|
|
ForEachFont(FindPlatformFont, this);
|
|
#endif
|
|
mCurrGeneration = GetGeneration();
|
|
mCachedEllipsisTextRun = nullptr;
|
|
}
|
|
}
|
|
|
|
struct PrefFontCallbackData {
|
|
PrefFontCallbackData(nsTArray<nsRefPtr<gfxFontFamily> >& aFamiliesArray)
|
|
: mPrefFamilies(aFamiliesArray)
|
|
{}
|
|
|
|
nsTArray<nsRefPtr<gfxFontFamily> >& mPrefFamilies;
|
|
|
|
static bool AddFontFamilyEntry(eFontPrefLang aLang, const nsAString& aName, void *aClosure)
|
|
{
|
|
PrefFontCallbackData *prefFontData = static_cast<PrefFontCallbackData*>(aClosure);
|
|
|
|
gfxFontFamily *family = gfxPlatformFontList::PlatformFontList()->FindFamily(aName);
|
|
if (family) {
|
|
prefFontData->mPrefFamilies.AppendElement(family);
|
|
}
|
|
return true;
|
|
}
|
|
};
|
|
|
|
already_AddRefed<gfxFont>
|
|
gfxFontGroup::WhichPrefFontSupportsChar(uint32_t aCh)
|
|
{
|
|
nsRefPtr<gfxFont> font;
|
|
|
|
// get the pref font list if it hasn't been set up already
|
|
uint32_t unicodeRange = FindCharUnicodeRange(aCh);
|
|
eFontPrefLang charLang = gfxPlatform::GetPlatform()->GetFontPrefLangFor(unicodeRange);
|
|
|
|
// if the last pref font was the first family in the pref list, no need to recheck through a list of families
|
|
if (mLastPrefFont && charLang == mLastPrefLang &&
|
|
mLastPrefFirstFont && mLastPrefFont->HasCharacter(aCh)) {
|
|
font = mLastPrefFont;
|
|
return font.forget();
|
|
}
|
|
|
|
// based on char lang and page lang, set up list of pref lang fonts to check
|
|
eFontPrefLang prefLangs[kMaxLenPrefLangList];
|
|
uint32_t i, numLangs = 0;
|
|
|
|
gfxPlatform::GetPlatform()->GetLangPrefs(prefLangs, numLangs, charLang, mPageLang);
|
|
|
|
for (i = 0; i < numLangs; i++) {
|
|
nsAutoTArray<nsRefPtr<gfxFontFamily>, 5> families;
|
|
eFontPrefLang currentLang = prefLangs[i];
|
|
|
|
gfxPlatformFontList *fontList = gfxPlatformFontList::PlatformFontList();
|
|
|
|
// get the pref families for a single pref lang
|
|
if (!fontList->GetPrefFontFamilyEntries(currentLang, &families)) {
|
|
eFontPrefLang prefLangsToSearch[1] = { currentLang };
|
|
PrefFontCallbackData prefFontData(families);
|
|
gfxPlatform::ForEachPrefFont(prefLangsToSearch, 1, PrefFontCallbackData::AddFontFamilyEntry,
|
|
&prefFontData);
|
|
fontList->SetPrefFontFamilyEntries(currentLang, families);
|
|
}
|
|
|
|
// find the first pref font that includes the character
|
|
uint32_t j, numPrefs;
|
|
numPrefs = families.Length();
|
|
for (j = 0; j < numPrefs; j++) {
|
|
// look up the appropriate face
|
|
gfxFontFamily *family = families[j];
|
|
if (!family) continue;
|
|
|
|
// if a pref font is used, it's likely to be used again in the same text run.
|
|
// the style doesn't change so the face lookup can be cached rather than calling
|
|
// FindOrMakeFont repeatedly. speeds up FindFontForChar lookup times for subsequent
|
|
// pref font lookups
|
|
if (family == mLastPrefFamily && mLastPrefFont->HasCharacter(aCh)) {
|
|
font = mLastPrefFont;
|
|
return font.forget();
|
|
}
|
|
|
|
bool needsBold;
|
|
gfxFontEntry *fe = family->FindFontForStyle(mStyle, needsBold);
|
|
// if ch in cmap, create and return a gfxFont
|
|
if (fe && fe->TestCharacterMap(aCh)) {
|
|
nsRefPtr<gfxFont> prefFont = fe->FindOrMakeFont(&mStyle, needsBold);
|
|
if (!prefFont) continue;
|
|
mLastPrefFamily = family;
|
|
mLastPrefFont = prefFont;
|
|
mLastPrefLang = charLang;
|
|
mLastPrefFirstFont = (i == 0 && j == 0);
|
|
return prefFont.forget();
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
already_AddRefed<gfxFont>
|
|
gfxFontGroup::WhichSystemFontSupportsChar(uint32_t aCh, int32_t aRunScript)
|
|
{
|
|
gfxFontEntry *fe =
|
|
gfxPlatformFontList::PlatformFontList()->
|
|
SystemFindFontForChar(aCh, aRunScript, &mStyle);
|
|
if (fe) {
|
|
bool wantBold = mStyle.ComputeWeight() >= 6;
|
|
nsRefPtr<gfxFont> font =
|
|
fe->FindOrMakeFont(&mStyle, wantBold && !fe->IsBold());
|
|
return font.forget();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/*static*/ void
|
|
gfxFontGroup::Shutdown()
|
|
{
|
|
NS_IF_RELEASE(gLangService);
|
|
}
|
|
|
|
nsILanguageAtomService* gfxFontGroup::gLangService = nullptr;
|
|
|
|
|
|
#define DEFAULT_PIXEL_FONT_SIZE 16.0f
|
|
|
|
/*static*/ uint32_t
|
|
gfxFontStyle::ParseFontLanguageOverride(const nsString& aLangTag)
|
|
{
|
|
if (!aLangTag.Length() || aLangTag.Length() > 4) {
|
|
return NO_FONT_LANGUAGE_OVERRIDE;
|
|
}
|
|
uint32_t index, result = 0;
|
|
for (index = 0; index < aLangTag.Length(); ++index) {
|
|
PRUnichar ch = aLangTag[index];
|
|
if (!nsCRT::IsAscii(ch)) { // valid tags are pure ASCII
|
|
return NO_FONT_LANGUAGE_OVERRIDE;
|
|
}
|
|
result = (result << 8) + ch;
|
|
}
|
|
while (index++ < 4) {
|
|
result = (result << 8) + 0x20;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
gfxFontStyle::gfxFontStyle() :
|
|
language(nsGkAtoms::x_western),
|
|
size(DEFAULT_PIXEL_FONT_SIZE), sizeAdjust(0.0f),
|
|
languageOverride(NO_FONT_LANGUAGE_OVERRIDE),
|
|
weight(NS_FONT_WEIGHT_NORMAL), stretch(NS_FONT_STRETCH_NORMAL),
|
|
systemFont(true), printerFont(false), useGrayscaleAntialiasing(false),
|
|
style(NS_FONT_STYLE_NORMAL)
|
|
{
|
|
}
|
|
|
|
gfxFontStyle::gfxFontStyle(uint8_t aStyle, uint16_t aWeight, int16_t aStretch,
|
|
gfxFloat aSize, nsIAtom *aLanguage,
|
|
float aSizeAdjust, bool aSystemFont,
|
|
bool aPrinterFont,
|
|
const nsString& aLanguageOverride):
|
|
language(aLanguage),
|
|
size(aSize), sizeAdjust(aSizeAdjust),
|
|
languageOverride(ParseFontLanguageOverride(aLanguageOverride)),
|
|
weight(aWeight), stretch(aStretch),
|
|
systemFont(aSystemFont), printerFont(aPrinterFont),
|
|
useGrayscaleAntialiasing(false), style(aStyle)
|
|
{
|
|
MOZ_ASSERT(!mozilla::IsNaN(size));
|
|
MOZ_ASSERT(!mozilla::IsNaN(sizeAdjust));
|
|
|
|
if (weight > 900)
|
|
weight = 900;
|
|
if (weight < 100)
|
|
weight = 100;
|
|
|
|
if (size >= FONT_MAX_SIZE) {
|
|
size = FONT_MAX_SIZE;
|
|
sizeAdjust = 0.0;
|
|
} else if (size < 0.0) {
|
|
NS_WARNING("negative font size");
|
|
size = 0.0;
|
|
}
|
|
|
|
if (!language) {
|
|
NS_WARNING("null language");
|
|
language = nsGkAtoms::x_western;
|
|
}
|
|
}
|
|
|
|
gfxFontStyle::gfxFontStyle(const gfxFontStyle& aStyle) :
|
|
language(aStyle.language),
|
|
featureValueLookup(aStyle.featureValueLookup),
|
|
size(aStyle.size), sizeAdjust(aStyle.sizeAdjust),
|
|
languageOverride(aStyle.languageOverride),
|
|
weight(aStyle.weight), stretch(aStyle.stretch),
|
|
systemFont(aStyle.systemFont), printerFont(aStyle.printerFont),
|
|
useGrayscaleAntialiasing(aStyle.useGrayscaleAntialiasing),
|
|
style(aStyle.style)
|
|
{
|
|
featureSettings.AppendElements(aStyle.featureSettings);
|
|
alternateValues.AppendElements(aStyle.alternateValues);
|
|
}
|
|
|
|
int8_t
|
|
gfxFontStyle::ComputeWeight() const
|
|
{
|
|
int8_t baseWeight = (weight + 50) / 100;
|
|
|
|
if (baseWeight < 0)
|
|
baseWeight = 0;
|
|
if (baseWeight > 9)
|
|
baseWeight = 9;
|
|
|
|
return baseWeight;
|
|
}
|
|
|
|
void
|
|
gfxShapedText::SetupClusterBoundaries(uint32_t aOffset,
|
|
const PRUnichar *aString,
|
|
uint32_t aLength)
|
|
{
|
|
CompressedGlyph *glyphs = GetCharacterGlyphs() + aOffset;
|
|
|
|
gfxTextRun::CompressedGlyph extendCluster;
|
|
extendCluster.SetComplex(false, true, 0);
|
|
|
|
ClusterIterator iter(aString, aLength);
|
|
|
|
// the ClusterIterator won't be able to tell us if the string
|
|
// _begins_ with a cluster-extender, so we handle that here
|
|
if (aLength && IsClusterExtender(*aString)) {
|
|
*glyphs = extendCluster;
|
|
}
|
|
|
|
while (!iter.AtEnd()) {
|
|
if (*iter == PRUnichar(' ')) {
|
|
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;
|
|
if (NS_IS_LOW_SURROGATE(*aString)) {
|
|
glyphs->SetIsLowSurrogate();
|
|
}
|
|
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 = new DetailedGlyphStore();
|
|
}
|
|
|
|
DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount);
|
|
if (!details) {
|
|
GetCharacterGlyphs()[aIndex].SetMissing(0);
|
|
return nullptr;
|
|
}
|
|
|
|
return details;
|
|
}
|
|
|
|
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);
|
|
if (!details) {
|
|
return;
|
|
}
|
|
memcpy(details, aGlyphs, sizeof(DetailedGlyph)*glyphCount);
|
|
}
|
|
GetCharacterGlyphs()[aIndex] = aGlyph;
|
|
}
|
|
|
|
#define ZWNJ 0x200C
|
|
#define ZWJ 0x200D
|
|
// U+061C ARABIC LETTER MARK is expected to be added to XIDMOD_DEFAULT_IGNORABLE
|
|
// in a future Unicode update. Add it manually for now
|
|
#define ALM 0x061C
|
|
static inline bool
|
|
IsDefaultIgnorable(uint32_t aChar)
|
|
{
|
|
return GetIdentifierModification(aChar) == XIDMOD_DEFAULT_IGNORABLE ||
|
|
aChar == ZWNJ || aChar == ZWJ || aChar == ALM;
|
|
}
|
|
|
|
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);
|
|
if (!details) {
|
|
return;
|
|
}
|
|
|
|
details->mGlyphID = aChar;
|
|
if (IsDefaultIgnorable(aChar)) {
|
|
// Setting advance width to zero will prevent drawing the hexbox
|
|
details->mAdvance = 0;
|
|
} else {
|
|
gfxFloat width =
|
|
std::max(aFont->GetMetrics().aveCharWidth,
|
|
gfxFontMissingGlyphs::GetDesiredMinWidth(aChar,
|
|
mAppUnitsPerDevUnit));
|
|
details->mAdvance = uint32_t(width * mAppUnitsPerDevUnit);
|
|
}
|
|
details->mXOffset = 0;
|
|
details->mYOffset = 0;
|
|
GetCharacterGlyphs()[aIndex].SetMissing(1);
|
|
}
|
|
|
|
bool
|
|
gfxShapedText::FilterIfIgnorable(uint32_t aIndex, uint32_t aCh)
|
|
{
|
|
if (IsDefaultIgnorable(aCh)) {
|
|
DetailedGlyph *details = AllocateDetailedGlyphs(aIndex, 1);
|
|
if (details) {
|
|
details->mGlyphID = aCh;
|
|
details->mAdvance = 0;
|
|
details->mXOffset = 0;
|
|
details->mYOffset = 0;
|
|
GetCharacterGlyphs()[aIndex].SetMissing(1);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void
|
|
gfxShapedText::AdjustAdvancesForSyntheticBold(float aSynBoldOffset,
|
|
uint32_t aOffset,
|
|
uint32_t aLength)
|
|
{
|
|
uint32_t synAppUnitOffset = aSynBoldOffset * mAppUnitsPerDevUnit;
|
|
CompressedGlyph *charGlyphs = GetCharacterGlyphs();
|
|
for (uint32_t i = aOffset; i < aOffset + aLength; ++i) {
|
|
CompressedGlyph *glyphData = charGlyphs + i;
|
|
if (glyphData->IsSimpleGlyph()) {
|
|
// simple glyphs ==> just add the advance
|
|
int32_t advance = glyphData->GetSimpleAdvance() + synAppUnitOffset;
|
|
if (CompressedGlyph::IsSimpleAdvance(advance)) {
|
|
glyphData->SetSimpleGlyph(advance, glyphData->GetSimpleGlyph());
|
|
} else {
|
|
// rare case, tested by making this the default
|
|
uint32_t glyphIndex = glyphData->GetSimpleGlyph();
|
|
glyphData->SetComplex(true, true, 1);
|
|
DetailedGlyph detail = {glyphIndex, advance, 0, 0};
|
|
SetGlyphs(i, *glyphData, &detail);
|
|
}
|
|
} else {
|
|
// complex glyphs ==> add offset at cluster/ligature boundaries
|
|
uint32_t detailedLength = glyphData->GetGlyphCount();
|
|
if (detailedLength) {
|
|
DetailedGlyph *details = GetDetailedGlyphs(i);
|
|
if (!details) {
|
|
continue;
|
|
}
|
|
if (IsRightToLeft()) {
|
|
details[0].mAdvance += synAppUnitOffset;
|
|
} else {
|
|
details[detailedLength - 1].mAdvance += synAppUnitOffset;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool
|
|
gfxTextRun::GlyphRunIterator::NextRun() {
|
|
if (mNextIndex >= mTextRun->mGlyphRuns.Length())
|
|
return false;
|
|
mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex];
|
|
if (mGlyphRun->mCharacterOffset >= mEndOffset)
|
|
return false;
|
|
|
|
mStringStart = std::max(mStartOffset, mGlyphRun->mCharacterOffset);
|
|
uint32_t last = mNextIndex + 1 < mTextRun->mGlyphRuns.Length()
|
|
? mTextRun->mGlyphRuns[mNextIndex + 1].mCharacterOffset : mTextRun->GetLength();
|
|
mStringEnd = std::min(mEndOffset, last);
|
|
|
|
++mNextIndex;
|
|
return true;
|
|
}
|
|
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
static void
|
|
AccountStorageForTextRun(gfxTextRun *aTextRun, int32_t aSign)
|
|
{
|
|
// Ignores detailed glyphs... we don't know when those have been constructed
|
|
// Also ignores gfxSkipChars dynamic storage (which won't be anything
|
|
// for preformatted text)
|
|
// Also ignores GlyphRun array, again because it hasn't been constructed
|
|
// by the time this gets called. If there's only one glyphrun that's stored
|
|
// directly in the textrun anyway so no additional overhead.
|
|
uint32_t length = aTextRun->GetLength();
|
|
int32_t bytes = length * sizeof(gfxTextRun::CompressedGlyph);
|
|
bytes += sizeof(gfxTextRun);
|
|
gTextRunStorage += bytes*aSign;
|
|
gTextRunStorageHighWaterMark = std::max(gTextRunStorageHighWaterMark, gTextRunStorage);
|
|
}
|
|
#endif
|
|
|
|
// Helper for textRun creation to preallocate storage for glyph records;
|
|
// this function returns a pointer to the newly-allocated glyph storage.
|
|
// Returns nullptr if allocation fails.
|
|
void *
|
|
gfxTextRun::AllocateStorageForTextRun(size_t aSize, uint32_t aLength)
|
|
{
|
|
// Allocate the storage we need, returning nullptr on failure rather than
|
|
// throwing an exception (because web content can create huge runs).
|
|
void *storage = moz_malloc(aSize + aLength * sizeof(CompressedGlyph));
|
|
if (!storage) {
|
|
NS_WARNING("failed to allocate storage for text run!");
|
|
return nullptr;
|
|
}
|
|
|
|
// Initialize the glyph storage (beyond aSize) to zero
|
|
memset(reinterpret_cast<char*>(storage) + aSize, 0,
|
|
aLength * sizeof(CompressedGlyph));
|
|
|
|
return storage;
|
|
}
|
|
|
|
gfxTextRun *
|
|
gfxTextRun::Create(const gfxTextRunFactory::Parameters *aParams,
|
|
uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags)
|
|
{
|
|
void *storage = AllocateStorageForTextRun(sizeof(gfxTextRun), aLength);
|
|
if (!storage) {
|
|
return nullptr;
|
|
}
|
|
|
|
return new (storage) gfxTextRun(aParams, aLength, aFontGroup, aFlags);
|
|
}
|
|
|
|
gfxTextRun::gfxTextRun(const gfxTextRunFactory::Parameters *aParams,
|
|
uint32_t aLength, gfxFontGroup *aFontGroup, uint32_t aFlags)
|
|
: gfxShapedText(aLength, aFlags, aParams->mAppUnitsPerDevUnit)
|
|
, mUserData(aParams->mUserData)
|
|
, mFontGroup(aFontGroup)
|
|
, mReleasedFontGroup(false)
|
|
{
|
|
NS_ASSERTION(mAppUnitsPerDevUnit > 0, "Invalid app unit scale");
|
|
MOZ_COUNT_CTOR(gfxTextRun);
|
|
NS_ADDREF(mFontGroup);
|
|
|
|
#ifndef RELEASE_BUILD
|
|
gfxTextPerfMetrics *tp = aFontGroup->GetTextPerfMetrics();
|
|
if (tp) {
|
|
tp->current.textrunConst++;
|
|
}
|
|
#endif
|
|
|
|
mCharacterGlyphs = reinterpret_cast<CompressedGlyph*>(this + 1);
|
|
|
|
if (aParams->mSkipChars) {
|
|
mSkipChars.TakeFrom(aParams->mSkipChars);
|
|
}
|
|
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
AccountStorageForTextRun(this, 1);
|
|
#endif
|
|
|
|
mSkipDrawing = mFontGroup->ShouldSkipDrawing();
|
|
}
|
|
|
|
gfxTextRun::~gfxTextRun()
|
|
{
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
AccountStorageForTextRun(this, -1);
|
|
#endif
|
|
#ifdef DEBUG
|
|
// Make it easy to detect a dead text run
|
|
mFlags = 0xFFFFFFFF;
|
|
#endif
|
|
|
|
// The cached ellipsis textrun (if any) in a fontgroup will have already
|
|
// been told to release its reference to the group, so we mustn't do that
|
|
// again here.
|
|
if (!mReleasedFontGroup) {
|
|
#ifndef RELEASE_BUILD
|
|
gfxTextPerfMetrics *tp = mFontGroup->GetTextPerfMetrics();
|
|
if (tp) {
|
|
tp->current.textrunDestr++;
|
|
}
|
|
#endif
|
|
NS_RELEASE(mFontGroup);
|
|
}
|
|
|
|
MOZ_COUNT_DTOR(gfxTextRun);
|
|
}
|
|
|
|
void
|
|
gfxTextRun::ReleaseFontGroup()
|
|
{
|
|
NS_ASSERTION(!mReleasedFontGroup, "doubly released!");
|
|
NS_RELEASE(mFontGroup);
|
|
mReleasedFontGroup = true;
|
|
}
|
|
|
|
bool
|
|
gfxTextRun::SetPotentialLineBreaks(uint32_t aStart, uint32_t aLength,
|
|
uint8_t *aBreakBefore,
|
|
gfxContext *aRefContext)
|
|
{
|
|
NS_ASSERTION(aStart + aLength <= GetLength(), "Overflow");
|
|
|
|
uint32_t changed = 0;
|
|
uint32_t i;
|
|
CompressedGlyph *charGlyphs = mCharacterGlyphs + aStart;
|
|
for (i = 0; i < aLength; ++i) {
|
|
uint8_t canBreak = aBreakBefore[i];
|
|
if (canBreak && !charGlyphs[i].IsClusterStart()) {
|
|
// This can happen ... there is no guarantee that our linebreaking rules
|
|
// align with the platform's idea of what constitutes a cluster.
|
|
NS_WARNING("Break suggested inside cluster!");
|
|
canBreak = CompressedGlyph::FLAG_BREAK_TYPE_NONE;
|
|
}
|
|
changed |= charGlyphs[i].SetCanBreakBefore(canBreak);
|
|
}
|
|
return changed != 0;
|
|
}
|
|
|
|
gfxTextRun::LigatureData
|
|
gfxTextRun::ComputeLigatureData(uint32_t aPartStart, uint32_t aPartEnd,
|
|
PropertyProvider *aProvider)
|
|
{
|
|
NS_ASSERTION(aPartStart < aPartEnd, "Computing ligature data for empty range");
|
|
NS_ASSERTION(aPartEnd <= GetLength(), "Character length overflow");
|
|
|
|
LigatureData result;
|
|
CompressedGlyph *charGlyphs = mCharacterGlyphs;
|
|
|
|
uint32_t i;
|
|
for (i = aPartStart; !charGlyphs[i].IsLigatureGroupStart(); --i) {
|
|
NS_ASSERTION(i > 0, "Ligature at the start of the run??");
|
|
}
|
|
result.mLigatureStart = i;
|
|
for (i = aPartStart + 1; i < GetLength() && !charGlyphs[i].IsLigatureGroupStart(); ++i) {
|
|
}
|
|
result.mLigatureEnd = i;
|
|
|
|
int32_t ligatureWidth =
|
|
GetAdvanceForGlyphs(result.mLigatureStart, result.mLigatureEnd);
|
|
// Count the number of started clusters we have seen
|
|
uint32_t totalClusterCount = 0;
|
|
uint32_t partClusterIndex = 0;
|
|
uint32_t partClusterCount = 0;
|
|
for (i = result.mLigatureStart; i < result.mLigatureEnd; ++i) {
|
|
// Treat the first character of the ligature as the start of a
|
|
// cluster for our purposes of allocating ligature width to its
|
|
// characters.
|
|
if (i == result.mLigatureStart || charGlyphs[i].IsClusterStart()) {
|
|
++totalClusterCount;
|
|
if (i < aPartStart) {
|
|
++partClusterIndex;
|
|
} else if (i < aPartEnd) {
|
|
++partClusterCount;
|
|
}
|
|
}
|
|
}
|
|
NS_ASSERTION(totalClusterCount > 0, "Ligature involving no clusters??");
|
|
result.mPartAdvance = partClusterIndex * (ligatureWidth / totalClusterCount);
|
|
result.mPartWidth = partClusterCount * (ligatureWidth / totalClusterCount);
|
|
|
|
// Any rounding errors are apportioned to the final part of the ligature,
|
|
// so that measuring all parts of a ligature and summing them is equal to
|
|
// the ligature width.
|
|
if (aPartEnd == result.mLigatureEnd) {
|
|
gfxFloat allParts = totalClusterCount * (ligatureWidth / totalClusterCount);
|
|
result.mPartWidth += ligatureWidth - allParts;
|
|
}
|
|
|
|
if (partClusterCount == 0) {
|
|
// nothing to draw
|
|
result.mClipBeforePart = result.mClipAfterPart = true;
|
|
} else {
|
|
// Determine whether we should clip before or after this part when
|
|
// drawing its slice of the ligature.
|
|
// We need to clip before the part if any cluster is drawn before
|
|
// this part.
|
|
result.mClipBeforePart = partClusterIndex > 0;
|
|
// We need to clip after the part if any cluster is drawn after
|
|
// this part.
|
|
result.mClipAfterPart = partClusterIndex + partClusterCount < totalClusterCount;
|
|
}
|
|
|
|
if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) {
|
|
gfxFont::Spacing spacing;
|
|
if (aPartStart == result.mLigatureStart) {
|
|
aProvider->GetSpacing(aPartStart, 1, &spacing);
|
|
result.mPartWidth += spacing.mBefore;
|
|
}
|
|
if (aPartEnd == result.mLigatureEnd) {
|
|
aProvider->GetSpacing(aPartEnd - 1, 1, &spacing);
|
|
result.mPartWidth += spacing.mAfter;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
gfxFloat
|
|
gfxTextRun::ComputePartialLigatureWidth(uint32_t aPartStart, uint32_t aPartEnd,
|
|
PropertyProvider *aProvider)
|
|
{
|
|
if (aPartStart >= aPartEnd)
|
|
return 0;
|
|
LigatureData data = ComputeLigatureData(aPartStart, aPartEnd, aProvider);
|
|
return data.mPartWidth;
|
|
}
|
|
|
|
int32_t
|
|
gfxTextRun::GetAdvanceForGlyphs(uint32_t aStart, uint32_t aEnd)
|
|
{
|
|
const CompressedGlyph *glyphData = mCharacterGlyphs + aStart;
|
|
int32_t advance = 0;
|
|
uint32_t i;
|
|
for (i = aStart; i < aEnd; ++i, ++glyphData) {
|
|
if (glyphData->IsSimpleGlyph()) {
|
|
advance += glyphData->GetSimpleAdvance();
|
|
} else {
|
|
uint32_t glyphCount = glyphData->GetGlyphCount();
|
|
if (glyphCount == 0) {
|
|
continue;
|
|
}
|
|
const DetailedGlyph *details = GetDetailedGlyphs(i);
|
|
if (details) {
|
|
uint32_t j;
|
|
for (j = 0; j < glyphCount; ++j, ++details) {
|
|
advance += details->mAdvance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return advance;
|
|
}
|
|
|
|
static void
|
|
GetAdjustedSpacing(gfxTextRun *aTextRun, uint32_t aStart, uint32_t aEnd,
|
|
gfxTextRun::PropertyProvider *aProvider,
|
|
gfxTextRun::PropertyProvider::Spacing *aSpacing)
|
|
{
|
|
if (aStart >= aEnd)
|
|
return;
|
|
|
|
aProvider->GetSpacing(aStart, aEnd - aStart, aSpacing);
|
|
|
|
#ifdef DEBUG
|
|
// Check to see if we have spacing inside ligatures
|
|
|
|
const gfxTextRun::CompressedGlyph *charGlyphs = aTextRun->GetCharacterGlyphs();
|
|
uint32_t i;
|
|
|
|
for (i = aStart; i < aEnd; ++i) {
|
|
if (!charGlyphs[i].IsLigatureGroupStart()) {
|
|
NS_ASSERTION(i == aStart || aSpacing[i - aStart].mBefore == 0,
|
|
"Before-spacing inside a ligature!");
|
|
NS_ASSERTION(i - 1 <= aStart || aSpacing[i - 1 - aStart].mAfter == 0,
|
|
"After-spacing inside a ligature!");
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
bool
|
|
gfxTextRun::GetAdjustedSpacingArray(uint32_t aStart, uint32_t aEnd,
|
|
PropertyProvider *aProvider,
|
|
uint32_t aSpacingStart, uint32_t aSpacingEnd,
|
|
nsTArray<PropertyProvider::Spacing> *aSpacing)
|
|
{
|
|
if (!aProvider || !(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING))
|
|
return false;
|
|
if (!aSpacing->AppendElements(aEnd - aStart))
|
|
return false;
|
|
memset(aSpacing->Elements(), 0, sizeof(gfxFont::Spacing)*(aSpacingStart - aStart));
|
|
GetAdjustedSpacing(this, aSpacingStart, aSpacingEnd, aProvider,
|
|
aSpacing->Elements() + aSpacingStart - aStart);
|
|
memset(aSpacing->Elements() + aSpacingEnd - aStart, 0, sizeof(gfxFont::Spacing)*(aEnd - aSpacingEnd));
|
|
return true;
|
|
}
|
|
|
|
void
|
|
gfxTextRun::ShrinkToLigatureBoundaries(uint32_t *aStart, uint32_t *aEnd)
|
|
{
|
|
if (*aStart >= *aEnd)
|
|
return;
|
|
|
|
CompressedGlyph *charGlyphs = mCharacterGlyphs;
|
|
|
|
while (*aStart < *aEnd && !charGlyphs[*aStart].IsLigatureGroupStart()) {
|
|
++(*aStart);
|
|
}
|
|
if (*aEnd < GetLength()) {
|
|
while (*aEnd > *aStart && !charGlyphs[*aEnd].IsLigatureGroupStart()) {
|
|
--(*aEnd);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxTextRun::DrawGlyphs(gfxFont *aFont, gfxContext *aContext,
|
|
DrawMode aDrawMode, gfxPoint *aPt,
|
|
gfxTextContextPaint *aContextPaint,
|
|
uint32_t aStart, uint32_t aEnd,
|
|
PropertyProvider *aProvider,
|
|
uint32_t aSpacingStart, uint32_t aSpacingEnd,
|
|
gfxTextRunDrawCallbacks *aCallbacks)
|
|
{
|
|
nsAutoTArray<PropertyProvider::Spacing,200> spacingBuffer;
|
|
bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider,
|
|
aSpacingStart, aSpacingEnd, &spacingBuffer);
|
|
aFont->Draw(this, aStart, aEnd, aContext, aDrawMode, aPt,
|
|
haveSpacing ? spacingBuffer.Elements() : nullptr, aContextPaint,
|
|
aCallbacks);
|
|
}
|
|
|
|
static void
|
|
ClipPartialLigature(gfxTextRun *aTextRun, gfxFloat *aLeft, gfxFloat *aRight,
|
|
gfxFloat aXOrigin, gfxTextRun::LigatureData *aLigature)
|
|
{
|
|
if (aLigature->mClipBeforePart) {
|
|
if (aTextRun->IsRightToLeft()) {
|
|
*aRight = std::min(*aRight, aXOrigin);
|
|
} else {
|
|
*aLeft = std::max(*aLeft, aXOrigin);
|
|
}
|
|
}
|
|
if (aLigature->mClipAfterPart) {
|
|
gfxFloat endEdge = aXOrigin + aTextRun->GetDirection()*aLigature->mPartWidth;
|
|
if (aTextRun->IsRightToLeft()) {
|
|
*aLeft = std::max(*aLeft, endEdge);
|
|
} else {
|
|
*aRight = std::min(*aRight, endEdge);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxTextRun::DrawPartialLigature(gfxFont *aFont, gfxContext *aCtx,
|
|
uint32_t aStart, uint32_t aEnd,
|
|
gfxPoint *aPt,
|
|
PropertyProvider *aProvider,
|
|
gfxTextRunDrawCallbacks *aCallbacks)
|
|
{
|
|
if (aStart >= aEnd)
|
|
return;
|
|
|
|
// Draw partial ligature. We hack this by clipping the ligature.
|
|
LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider);
|
|
gfxRect clipExtents = aCtx->GetClipExtents();
|
|
gfxFloat left = clipExtents.X()*mAppUnitsPerDevUnit;
|
|
gfxFloat right = clipExtents.XMost()*mAppUnitsPerDevUnit;
|
|
ClipPartialLigature(this, &left, &right, aPt->x, &data);
|
|
|
|
{
|
|
// Need to preserve the path, otherwise this can break canvas text-on-path;
|
|
// in general it seems like a good thing, as naive callers probably won't
|
|
// expect gfxTextRun::Draw to implicitly destroy the current path.
|
|
gfxContextPathAutoSaveRestore savePath(aCtx);
|
|
|
|
// use division here to ensure that when the rect is aligned on multiples
|
|
// of mAppUnitsPerDevUnit, we clip to true device unit boundaries.
|
|
// Also, make sure we snap the rectangle to device pixels.
|
|
aCtx->Save();
|
|
aCtx->NewPath();
|
|
aCtx->Rectangle(gfxRect(left / mAppUnitsPerDevUnit,
|
|
clipExtents.Y(),
|
|
(right - left) / mAppUnitsPerDevUnit,
|
|
clipExtents.Height()), true);
|
|
aCtx->Clip();
|
|
}
|
|
|
|
gfxFloat direction = GetDirection();
|
|
gfxPoint pt(aPt->x - direction*data.mPartAdvance, aPt->y);
|
|
DrawGlyphs(aFont, aCtx,
|
|
aCallbacks ? DrawMode::GLYPH_PATH : DrawMode::GLYPH_FILL, &pt,
|
|
nullptr, data.mLigatureStart, data.mLigatureEnd, aProvider,
|
|
aStart, aEnd, aCallbacks);
|
|
aCtx->Restore();
|
|
|
|
aPt->x += direction*data.mPartWidth;
|
|
}
|
|
|
|
// returns true if a glyph run is using a font with synthetic bolding enabled, false otherwise
|
|
static bool
|
|
HasSyntheticBold(gfxTextRun *aRun, uint32_t aStart, uint32_t aLength)
|
|
{
|
|
gfxTextRun::GlyphRunIterator iter(aRun, aStart, aLength);
|
|
while (iter.NextRun()) {
|
|
gfxFont *font = iter.GetGlyphRun()->mFont;
|
|
if (font && font->IsSyntheticBold()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// returns true if color is non-opaque (i.e. alpha != 1.0) or completely transparent, false otherwise
|
|
// if true, color is set on output
|
|
static bool
|
|
HasNonOpaqueColor(gfxContext *aContext, gfxRGBA& aCurrentColor)
|
|
{
|
|
if (aContext->GetDeviceColor(aCurrentColor)) {
|
|
if (aCurrentColor.a < 1.0 && aCurrentColor.a > 0.0) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
// helper class for double-buffering drawing with non-opaque color
|
|
struct BufferAlphaColor {
|
|
BufferAlphaColor(gfxContext *aContext)
|
|
: mContext(aContext)
|
|
{
|
|
|
|
}
|
|
|
|
~BufferAlphaColor() {}
|
|
|
|
void PushSolidColor(const gfxRect& aBounds, const gfxRGBA& aAlphaColor, uint32_t appsPerDevUnit)
|
|
{
|
|
mContext->Save();
|
|
mContext->NewPath();
|
|
mContext->Rectangle(gfxRect(aBounds.X() / appsPerDevUnit,
|
|
aBounds.Y() / appsPerDevUnit,
|
|
aBounds.Width() / appsPerDevUnit,
|
|
aBounds.Height() / appsPerDevUnit), true);
|
|
mContext->Clip();
|
|
mContext->SetColor(gfxRGBA(aAlphaColor.r, aAlphaColor.g, aAlphaColor.b));
|
|
mContext->PushGroup(GFX_CONTENT_COLOR_ALPHA);
|
|
mAlpha = aAlphaColor.a;
|
|
}
|
|
|
|
void PopAlpha()
|
|
{
|
|
// pop the text, using the color alpha as the opacity
|
|
mContext->PopGroupToSource();
|
|
mContext->SetOperator(gfxContext::OPERATOR_OVER);
|
|
mContext->Paint(mAlpha);
|
|
mContext->Restore();
|
|
}
|
|
|
|
gfxContext *mContext;
|
|
gfxFloat mAlpha;
|
|
};
|
|
|
|
void
|
|
gfxTextRun::Draw(gfxContext *aContext, gfxPoint aPt, DrawMode aDrawMode,
|
|
uint32_t aStart, uint32_t aLength,
|
|
PropertyProvider *aProvider, gfxFloat *aAdvanceWidth,
|
|
gfxTextContextPaint *aContextPaint,
|
|
gfxTextRunDrawCallbacks *aCallbacks)
|
|
{
|
|
NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range");
|
|
NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !(int(aDrawMode) & int(DrawMode::GLYPH_PATH)),
|
|
"GLYPH_PATH cannot be used with GLYPH_FILL, GLYPH_STROKE or GLYPH_STROKE_UNDERNEATH");
|
|
NS_ASSERTION(aDrawMode == DrawMode::GLYPH_PATH || !aCallbacks, "callback must not be specified unless using GLYPH_PATH");
|
|
|
|
bool skipDrawing = mSkipDrawing;
|
|
if (aDrawMode == DrawMode::GLYPH_FILL) {
|
|
gfxRGBA currentColor;
|
|
if (aContext->GetDeviceColor(currentColor) && currentColor.a == 0) {
|
|
skipDrawing = true;
|
|
}
|
|
}
|
|
|
|
gfxFloat direction = GetDirection();
|
|
|
|
if (skipDrawing) {
|
|
// We don't need to draw anything;
|
|
// but if the caller wants advance width, we need to compute it here
|
|
if (aAdvanceWidth) {
|
|
gfxTextRun::Metrics metrics = MeasureText(aStart, aLength,
|
|
gfxFont::LOOSE_INK_EXTENTS,
|
|
aContext, aProvider);
|
|
*aAdvanceWidth = metrics.mAdvanceWidth * direction;
|
|
}
|
|
|
|
// return without drawing
|
|
return;
|
|
}
|
|
|
|
gfxPoint pt = aPt;
|
|
|
|
// synthetic bolding draws glyphs twice ==> colors with opacity won't draw correctly unless first drawn without alpha
|
|
BufferAlphaColor syntheticBoldBuffer(aContext);
|
|
gfxRGBA currentColor;
|
|
bool needToRestore = false;
|
|
|
|
if (aDrawMode == DrawMode::GLYPH_FILL && HasNonOpaqueColor(aContext, currentColor)
|
|
&& HasSyntheticBold(this, aStart, aLength)) {
|
|
needToRestore = true;
|
|
// measure text, use the bounding box
|
|
gfxTextRun::Metrics metrics = MeasureText(aStart, aLength, gfxFont::LOOSE_INK_EXTENTS,
|
|
aContext, aProvider);
|
|
metrics.mBoundingBox.MoveBy(aPt);
|
|
syntheticBoldBuffer.PushSolidColor(metrics.mBoundingBox, currentColor, GetAppUnitsPerDevUnit());
|
|
}
|
|
|
|
GlyphRunIterator iter(this, aStart, aLength);
|
|
while (iter.NextRun()) {
|
|
gfxFont *font = iter.GetGlyphRun()->mFont;
|
|
uint32_t start = iter.GetStringStart();
|
|
uint32_t end = iter.GetStringEnd();
|
|
uint32_t ligatureRunStart = start;
|
|
uint32_t ligatureRunEnd = end;
|
|
ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd);
|
|
|
|
bool drawPartial = aDrawMode == DrawMode::GLYPH_FILL ||
|
|
(aDrawMode == DrawMode::GLYPH_PATH && aCallbacks);
|
|
|
|
if (drawPartial) {
|
|
DrawPartialLigature(font, aContext, start, ligatureRunStart, &pt,
|
|
aProvider, aCallbacks);
|
|
}
|
|
|
|
DrawGlyphs(font, aContext, aDrawMode, &pt, aContextPaint, ligatureRunStart,
|
|
ligatureRunEnd, aProvider, ligatureRunStart, ligatureRunEnd,
|
|
aCallbacks);
|
|
|
|
if (drawPartial) {
|
|
DrawPartialLigature(font, aContext, ligatureRunEnd, end, &pt,
|
|
aProvider, aCallbacks);
|
|
}
|
|
}
|
|
|
|
// composite result when synthetic bolding used
|
|
if (needToRestore) {
|
|
syntheticBoldBuffer.PopAlpha();
|
|
}
|
|
|
|
if (aAdvanceWidth) {
|
|
*aAdvanceWidth = (pt.x - aPt.x)*direction;
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxTextRun::AccumulateMetricsForRun(gfxFont *aFont,
|
|
uint32_t aStart, uint32_t aEnd,
|
|
gfxFont::BoundingBoxType aBoundingBoxType,
|
|
gfxContext *aRefContext,
|
|
PropertyProvider *aProvider,
|
|
uint32_t aSpacingStart, uint32_t aSpacingEnd,
|
|
Metrics *aMetrics)
|
|
{
|
|
nsAutoTArray<PropertyProvider::Spacing,200> spacingBuffer;
|
|
bool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider,
|
|
aSpacingStart, aSpacingEnd, &spacingBuffer);
|
|
Metrics metrics = aFont->Measure(this, aStart, aEnd, aBoundingBoxType, aRefContext,
|
|
haveSpacing ? spacingBuffer.Elements() : nullptr);
|
|
aMetrics->CombineWith(metrics, IsRightToLeft());
|
|
}
|
|
|
|
void
|
|
gfxTextRun::AccumulatePartialLigatureMetrics(gfxFont *aFont,
|
|
uint32_t aStart, uint32_t aEnd,
|
|
gfxFont::BoundingBoxType aBoundingBoxType, gfxContext *aRefContext,
|
|
PropertyProvider *aProvider, Metrics *aMetrics)
|
|
{
|
|
if (aStart >= aEnd)
|
|
return;
|
|
|
|
// Measure partial ligature. We hack this by clipping the metrics in the
|
|
// same way we clip the drawing.
|
|
LigatureData data = ComputeLigatureData(aStart, aEnd, aProvider);
|
|
|
|
// First measure the complete ligature
|
|
Metrics metrics;
|
|
AccumulateMetricsForRun(aFont, data.mLigatureStart, data.mLigatureEnd,
|
|
aBoundingBoxType, aRefContext,
|
|
aProvider, aStart, aEnd, &metrics);
|
|
|
|
// Clip the bounding box to the ligature part
|
|
gfxFloat bboxLeft = metrics.mBoundingBox.X();
|
|
gfxFloat bboxRight = metrics.mBoundingBox.XMost();
|
|
// Where we are going to start "drawing" relative to our left baseline origin
|
|
gfxFloat origin = IsRightToLeft() ? metrics.mAdvanceWidth - data.mPartAdvance : 0;
|
|
ClipPartialLigature(this, &bboxLeft, &bboxRight, origin, &data);
|
|
metrics.mBoundingBox.x = bboxLeft;
|
|
metrics.mBoundingBox.width = bboxRight - bboxLeft;
|
|
|
|
// mBoundingBox is now relative to the left baseline origin for the entire
|
|
// ligature. Shift it left.
|
|
metrics.mBoundingBox.x -=
|
|
IsRightToLeft() ? metrics.mAdvanceWidth - (data.mPartAdvance + data.mPartWidth)
|
|
: data.mPartAdvance;
|
|
metrics.mAdvanceWidth = data.mPartWidth;
|
|
|
|
aMetrics->CombineWith(metrics, IsRightToLeft());
|
|
}
|
|
|
|
gfxTextRun::Metrics
|
|
gfxTextRun::MeasureText(uint32_t aStart, uint32_t aLength,
|
|
gfxFont::BoundingBoxType aBoundingBoxType,
|
|
gfxContext *aRefContext,
|
|
PropertyProvider *aProvider)
|
|
{
|
|
NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range");
|
|
|
|
Metrics accumulatedMetrics;
|
|
GlyphRunIterator iter(this, aStart, aLength);
|
|
while (iter.NextRun()) {
|
|
gfxFont *font = iter.GetGlyphRun()->mFont;
|
|
uint32_t start = iter.GetStringStart();
|
|
uint32_t end = iter.GetStringEnd();
|
|
uint32_t ligatureRunStart = start;
|
|
uint32_t ligatureRunEnd = end;
|
|
ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd);
|
|
|
|
AccumulatePartialLigatureMetrics(font, start, ligatureRunStart,
|
|
aBoundingBoxType, aRefContext, aProvider, &accumulatedMetrics);
|
|
|
|
// XXX This sucks. We have to get glyph extents just so we can detect
|
|
// glyphs outside the font box, even when aBoundingBoxType is LOOSE,
|
|
// even though in almost all cases we could get correct results just
|
|
// by getting some ascent/descent from the font and using our stored
|
|
// advance widths.
|
|
AccumulateMetricsForRun(font,
|
|
ligatureRunStart, ligatureRunEnd, aBoundingBoxType,
|
|
aRefContext, aProvider, ligatureRunStart, ligatureRunEnd,
|
|
&accumulatedMetrics);
|
|
|
|
AccumulatePartialLigatureMetrics(font, ligatureRunEnd, end,
|
|
aBoundingBoxType, aRefContext, aProvider, &accumulatedMetrics);
|
|
}
|
|
|
|
return accumulatedMetrics;
|
|
}
|
|
|
|
#define MEASUREMENT_BUFFER_SIZE 100
|
|
|
|
uint32_t
|
|
gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
|
|
bool aLineBreakBefore, gfxFloat aWidth,
|
|
PropertyProvider *aProvider,
|
|
bool aSuppressInitialBreak,
|
|
gfxFloat *aTrimWhitespace,
|
|
Metrics *aMetrics,
|
|
gfxFont::BoundingBoxType aBoundingBoxType,
|
|
gfxContext *aRefContext,
|
|
bool *aUsedHyphenation,
|
|
uint32_t *aLastBreak,
|
|
bool aCanWordWrap,
|
|
gfxBreakPriority *aBreakPriority)
|
|
{
|
|
aMaxLength = std::min(aMaxLength, GetLength() - aStart);
|
|
|
|
NS_ASSERTION(aStart + aMaxLength <= GetLength(), "Substring out of range");
|
|
|
|
uint32_t bufferStart = aStart;
|
|
uint32_t bufferLength = std::min<uint32_t>(aMaxLength, MEASUREMENT_BUFFER_SIZE);
|
|
PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE];
|
|
bool haveSpacing = aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) != 0;
|
|
if (haveSpacing) {
|
|
GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider,
|
|
spacingBuffer);
|
|
}
|
|
bool hyphenBuffer[MEASUREMENT_BUFFER_SIZE];
|
|
bool haveHyphenation = aProvider &&
|
|
(aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_AUTO ||
|
|
(aProvider->GetHyphensOption() == NS_STYLE_HYPHENS_MANUAL &&
|
|
(mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0));
|
|
if (haveHyphenation) {
|
|
aProvider->GetHyphenationBreaks(bufferStart, bufferLength,
|
|
hyphenBuffer);
|
|
}
|
|
|
|
gfxFloat width = 0;
|
|
gfxFloat advance = 0;
|
|
// The number of space characters that can be trimmed
|
|
uint32_t trimmableChars = 0;
|
|
// The amount of space removed by ignoring trimmableChars
|
|
gfxFloat trimmableAdvance = 0;
|
|
int32_t lastBreak = -1;
|
|
int32_t lastBreakTrimmableChars = -1;
|
|
gfxFloat lastBreakTrimmableAdvance = -1;
|
|
bool aborted = false;
|
|
uint32_t end = aStart + aMaxLength;
|
|
bool lastBreakUsedHyphenation = false;
|
|
|
|
uint32_t ligatureRunStart = aStart;
|
|
uint32_t ligatureRunEnd = end;
|
|
ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd);
|
|
|
|
uint32_t i;
|
|
for (i = aStart; i < end; ++i) {
|
|
if (i >= bufferStart + bufferLength) {
|
|
// Fetch more spacing and hyphenation data
|
|
bufferStart = i;
|
|
bufferLength = std::min(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE) - i;
|
|
if (haveSpacing) {
|
|
GetAdjustedSpacing(this, bufferStart, bufferStart + bufferLength, aProvider,
|
|
spacingBuffer);
|
|
}
|
|
if (haveHyphenation) {
|
|
aProvider->GetHyphenationBreaks(bufferStart, bufferLength,
|
|
hyphenBuffer);
|
|
}
|
|
}
|
|
|
|
// There can't be a word-wrap break opportunity at the beginning of the
|
|
// line: if the width is too small for even one character to fit, it
|
|
// could be the first and last break opportunity on the line, and that
|
|
// would trigger an infinite loop.
|
|
if (!aSuppressInitialBreak || i > aStart) {
|
|
bool lineBreakHere = mCharacterGlyphs[i].CanBreakBefore() == 1;
|
|
bool hyphenation = haveHyphenation && hyphenBuffer[i - bufferStart];
|
|
bool wordWrapping =
|
|
aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() &&
|
|
*aBreakPriority <= eWordWrapBreak;
|
|
|
|
if (lineBreakHere || hyphenation || wordWrapping) {
|
|
gfxFloat hyphenatedAdvance = advance;
|
|
if (!lineBreakHere && !wordWrapping) {
|
|
hyphenatedAdvance += aProvider->GetHyphenWidth();
|
|
}
|
|
|
|
if (lastBreak < 0 || width + hyphenatedAdvance - trimmableAdvance <= aWidth) {
|
|
// We can break here.
|
|
lastBreak = i;
|
|
lastBreakTrimmableChars = trimmableChars;
|
|
lastBreakTrimmableAdvance = trimmableAdvance;
|
|
lastBreakUsedHyphenation = !lineBreakHere && !wordWrapping;
|
|
*aBreakPriority = hyphenation || lineBreakHere ?
|
|
eNormalBreak : eWordWrapBreak;
|
|
}
|
|
|
|
width += advance;
|
|
advance = 0;
|
|
if (width - trimmableAdvance > aWidth) {
|
|
// No more text fits. Abort
|
|
aborted = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
gfxFloat charAdvance;
|
|
if (i >= ligatureRunStart && i < ligatureRunEnd) {
|
|
charAdvance = GetAdvanceForGlyphs(i, i + 1);
|
|
if (haveSpacing) {
|
|
PropertyProvider::Spacing *space = &spacingBuffer[i - bufferStart];
|
|
charAdvance += space->mBefore + space->mAfter;
|
|
}
|
|
} else {
|
|
charAdvance = ComputePartialLigatureWidth(i, i + 1, aProvider);
|
|
}
|
|
|
|
advance += charAdvance;
|
|
if (aTrimWhitespace) {
|
|
if (mCharacterGlyphs[i].CharIsSpace()) {
|
|
++trimmableChars;
|
|
trimmableAdvance += charAdvance;
|
|
} else {
|
|
trimmableAdvance = 0;
|
|
trimmableChars = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!aborted) {
|
|
width += advance;
|
|
}
|
|
|
|
// There are three possibilities:
|
|
// 1) all the text fit (width <= aWidth)
|
|
// 2) some of the text fit up to a break opportunity (width > aWidth && lastBreak >= 0)
|
|
// 3) none of the text fits before a break opportunity (width > aWidth && lastBreak < 0)
|
|
uint32_t charsFit;
|
|
bool usedHyphenation = false;
|
|
if (width - trimmableAdvance <= aWidth) {
|
|
charsFit = aMaxLength;
|
|
} else if (lastBreak >= 0) {
|
|
charsFit = lastBreak - aStart;
|
|
trimmableChars = lastBreakTrimmableChars;
|
|
trimmableAdvance = lastBreakTrimmableAdvance;
|
|
usedHyphenation = lastBreakUsedHyphenation;
|
|
} else {
|
|
charsFit = aMaxLength;
|
|
}
|
|
|
|
if (aMetrics) {
|
|
*aMetrics = MeasureText(aStart, charsFit - trimmableChars,
|
|
aBoundingBoxType, aRefContext, aProvider);
|
|
}
|
|
if (aTrimWhitespace) {
|
|
*aTrimWhitespace = trimmableAdvance;
|
|
}
|
|
if (aUsedHyphenation) {
|
|
*aUsedHyphenation = usedHyphenation;
|
|
}
|
|
if (aLastBreak && charsFit == aMaxLength) {
|
|
if (lastBreak < 0) {
|
|
*aLastBreak = UINT32_MAX;
|
|
} else {
|
|
*aLastBreak = lastBreak - aStart;
|
|
}
|
|
}
|
|
|
|
return charsFit;
|
|
}
|
|
|
|
gfxFloat
|
|
gfxTextRun::GetAdvanceWidth(uint32_t aStart, uint32_t aLength,
|
|
PropertyProvider *aProvider)
|
|
{
|
|
NS_ASSERTION(aStart + aLength <= GetLength(), "Substring out of range");
|
|
|
|
uint32_t ligatureRunStart = aStart;
|
|
uint32_t ligatureRunEnd = aStart + aLength;
|
|
ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd);
|
|
|
|
gfxFloat result = ComputePartialLigatureWidth(aStart, ligatureRunStart, aProvider) +
|
|
ComputePartialLigatureWidth(ligatureRunEnd, aStart + aLength, aProvider);
|
|
|
|
// Account for all remaining spacing here. This is more efficient than
|
|
// processing it along with the glyphs.
|
|
if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) {
|
|
uint32_t i;
|
|
nsAutoTArray<PropertyProvider::Spacing,200> spacingBuffer;
|
|
if (spacingBuffer.AppendElements(aLength)) {
|
|
GetAdjustedSpacing(this, ligatureRunStart, ligatureRunEnd, aProvider,
|
|
spacingBuffer.Elements());
|
|
for (i = 0; i < ligatureRunEnd - ligatureRunStart; ++i) {
|
|
PropertyProvider::Spacing *space = &spacingBuffer[i];
|
|
result += space->mBefore + space->mAfter;
|
|
}
|
|
}
|
|
}
|
|
|
|
return result + GetAdvanceForGlyphs(ligatureRunStart, ligatureRunEnd);
|
|
}
|
|
|
|
bool
|
|
gfxTextRun::SetLineBreaks(uint32_t aStart, uint32_t aLength,
|
|
bool aLineBreakBefore, bool aLineBreakAfter,
|
|
gfxFloat *aAdvanceWidthDelta,
|
|
gfxContext *aRefContext)
|
|
{
|
|
// Do nothing because our shaping does not currently take linebreaks into
|
|
// account. There is no change in advance width.
|
|
if (aAdvanceWidthDelta) {
|
|
*aAdvanceWidthDelta = 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
uint32_t
|
|
gfxTextRun::FindFirstGlyphRunContaining(uint32_t aOffset)
|
|
{
|
|
NS_ASSERTION(aOffset <= GetLength(), "Bad offset looking for glyphrun");
|
|
NS_ASSERTION(GetLength() == 0 || mGlyphRuns.Length() > 0,
|
|
"non-empty text but no glyph runs present!");
|
|
if (aOffset == GetLength())
|
|
return mGlyphRuns.Length();
|
|
uint32_t start = 0;
|
|
uint32_t end = mGlyphRuns.Length();
|
|
while (end - start > 1) {
|
|
uint32_t mid = (start + end)/2;
|
|
if (mGlyphRuns[mid].mCharacterOffset <= aOffset) {
|
|
start = mid;
|
|
} else {
|
|
end = mid;
|
|
}
|
|
}
|
|
NS_ASSERTION(mGlyphRuns[start].mCharacterOffset <= aOffset,
|
|
"Hmm, something went wrong, aOffset should have been found");
|
|
return start;
|
|
}
|
|
|
|
nsresult
|
|
gfxTextRun::AddGlyphRun(gfxFont *aFont, uint8_t aMatchType,
|
|
uint32_t aUTF16Offset, bool aForceNewRun)
|
|
{
|
|
NS_ASSERTION(aFont, "adding glyph run for null font!");
|
|
if (!aFont) {
|
|
return NS_OK;
|
|
}
|
|
uint32_t numGlyphRuns = mGlyphRuns.Length();
|
|
if (!aForceNewRun && numGlyphRuns > 0) {
|
|
GlyphRun *lastGlyphRun = &mGlyphRuns[numGlyphRuns - 1];
|
|
|
|
NS_ASSERTION(lastGlyphRun->mCharacterOffset <= aUTF16Offset,
|
|
"Glyph runs out of order (and run not forced)");
|
|
|
|
// Don't append a run if the font is already the one we want
|
|
if (lastGlyphRun->mFont == aFont &&
|
|
lastGlyphRun->mMatchType == aMatchType)
|
|
{
|
|
return NS_OK;
|
|
}
|
|
|
|
// If the offset has not changed, avoid leaving a zero-length run
|
|
// by overwriting the last entry instead of appending...
|
|
if (lastGlyphRun->mCharacterOffset == aUTF16Offset) {
|
|
|
|
// ...except that if the run before the last entry had the same
|
|
// font as the new one wants, merge with it instead of creating
|
|
// adjacent runs with the same font
|
|
if (numGlyphRuns > 1 &&
|
|
mGlyphRuns[numGlyphRuns - 2].mFont == aFont &&
|
|
mGlyphRuns[numGlyphRuns - 2].mMatchType == aMatchType)
|
|
{
|
|
mGlyphRuns.TruncateLength(numGlyphRuns - 1);
|
|
return NS_OK;
|
|
}
|
|
|
|
lastGlyphRun->mFont = aFont;
|
|
lastGlyphRun->mMatchType = aMatchType;
|
|
return NS_OK;
|
|
}
|
|
}
|
|
|
|
NS_ASSERTION(aForceNewRun || numGlyphRuns > 0 || aUTF16Offset == 0,
|
|
"First run doesn't cover the first character (and run not forced)?");
|
|
|
|
GlyphRun *glyphRun = mGlyphRuns.AppendElement();
|
|
if (!glyphRun)
|
|
return NS_ERROR_OUT_OF_MEMORY;
|
|
glyphRun->mFont = aFont;
|
|
glyphRun->mCharacterOffset = aUTF16Offset;
|
|
glyphRun->mMatchType = aMatchType;
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
gfxTextRun::SortGlyphRuns()
|
|
{
|
|
if (mGlyphRuns.Length() <= 1)
|
|
return;
|
|
|
|
nsTArray<GlyphRun> runs(mGlyphRuns);
|
|
GlyphRunOffsetComparator comp;
|
|
runs.Sort(comp);
|
|
|
|
// Now copy back, coalescing adjacent glyph runs that have the same font
|
|
mGlyphRuns.Clear();
|
|
uint32_t i, count = runs.Length();
|
|
for (i = 0; i < count; ++i) {
|
|
// a GlyphRun with the same font as the previous GlyphRun can just
|
|
// be skipped; the last GlyphRun will cover its character range.
|
|
if (i == 0 || runs[i].mFont != runs[i - 1].mFont) {
|
|
mGlyphRuns.AppendElement(runs[i]);
|
|
// If two fonts have the same character offset, Sort() will have
|
|
// randomized the order.
|
|
NS_ASSERTION(i == 0 ||
|
|
runs[i].mCharacterOffset !=
|
|
runs[i - 1].mCharacterOffset,
|
|
"Two fonts for the same run, glyph indices may not match the font");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Note that SanitizeGlyphRuns scans all glyph runs in the textrun;
|
|
// therefore we only call it once, at the end of textrun construction,
|
|
// NOT incrementally as each glyph run is added (bug 680402).
|
|
void
|
|
gfxTextRun::SanitizeGlyphRuns()
|
|
{
|
|
if (mGlyphRuns.Length() <= 1)
|
|
return;
|
|
|
|
// If any glyph run starts with ligature-continuation characters, we need to advance it
|
|
// to the first "real" character to avoid drawing partial ligature glyphs from wrong font
|
|
// (seen with U+FEFF in reftest 474417-1, as Core Text eliminates the glyph, which makes
|
|
// it appear as if a ligature has been formed)
|
|
int32_t i, lastRunIndex = mGlyphRuns.Length() - 1;
|
|
const CompressedGlyph *charGlyphs = mCharacterGlyphs;
|
|
for (i = lastRunIndex; i >= 0; --i) {
|
|
GlyphRun& run = mGlyphRuns[i];
|
|
while (charGlyphs[run.mCharacterOffset].IsLigatureContinuation() &&
|
|
run.mCharacterOffset < GetLength()) {
|
|
run.mCharacterOffset++;
|
|
}
|
|
// if the run has become empty, eliminate it
|
|
if ((i < lastRunIndex &&
|
|
run.mCharacterOffset >= mGlyphRuns[i+1].mCharacterOffset) ||
|
|
(i == lastRunIndex && run.mCharacterOffset == GetLength())) {
|
|
mGlyphRuns.RemoveElementAt(i);
|
|
--lastRunIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint32_t
|
|
gfxTextRun::CountMissingGlyphs()
|
|
{
|
|
uint32_t i;
|
|
uint32_t count = 0;
|
|
for (i = 0; i < GetLength(); ++i) {
|
|
if (mCharacterGlyphs[i].IsMissing()) {
|
|
++count;
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
gfxTextRun::DetailedGlyph *
|
|
gfxTextRun::AllocateDetailedGlyphs(uint32_t aIndex, uint32_t aCount)
|
|
{
|
|
NS_ASSERTION(aIndex < GetLength(), "Index out of range");
|
|
|
|
if (!mDetailedGlyphs) {
|
|
mDetailedGlyphs = new DetailedGlyphStore();
|
|
}
|
|
|
|
DetailedGlyph *details = mDetailedGlyphs->Allocate(aIndex, aCount);
|
|
if (!details) {
|
|
mCharacterGlyphs[aIndex].SetMissing(0);
|
|
return nullptr;
|
|
}
|
|
|
|
return details;
|
|
}
|
|
|
|
void
|
|
gfxTextRun::CopyGlyphDataFrom(gfxShapedWord *aShapedWord, uint32_t aOffset)
|
|
{
|
|
uint32_t wordLen = aShapedWord->GetLength();
|
|
NS_ASSERTION(aOffset + wordLen <= GetLength(),
|
|
"word overruns end of textrun!");
|
|
|
|
CompressedGlyph *charGlyphs = GetCharacterGlyphs();
|
|
const CompressedGlyph *wordGlyphs = aShapedWord->GetCharacterGlyphs();
|
|
if (aShapedWord->HasDetailedGlyphs()) {
|
|
for (uint32_t i = 0; i < wordLen; ++i, ++aOffset) {
|
|
const CompressedGlyph& g = wordGlyphs[i];
|
|
if (g.IsSimpleGlyph()) {
|
|
charGlyphs[aOffset] = g;
|
|
} else {
|
|
const DetailedGlyph *details =
|
|
g.GetGlyphCount() > 0 ?
|
|
aShapedWord->GetDetailedGlyphs(i) : nullptr;
|
|
SetGlyphs(aOffset, g, details);
|
|
}
|
|
}
|
|
} else {
|
|
memcpy(charGlyphs + aOffset, wordGlyphs,
|
|
wordLen * sizeof(CompressedGlyph));
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxTextRun::CopyGlyphDataFrom(gfxTextRun *aSource, uint32_t aStart,
|
|
uint32_t aLength, uint32_t aDest)
|
|
{
|
|
NS_ASSERTION(aStart + aLength <= aSource->GetLength(),
|
|
"Source substring out of range");
|
|
NS_ASSERTION(aDest + aLength <= GetLength(),
|
|
"Destination substring out of range");
|
|
|
|
if (aSource->mSkipDrawing) {
|
|
mSkipDrawing = true;
|
|
}
|
|
|
|
// Copy base glyph data, and DetailedGlyph data where present
|
|
const CompressedGlyph *srcGlyphs = aSource->mCharacterGlyphs + aStart;
|
|
CompressedGlyph *dstGlyphs = mCharacterGlyphs + aDest;
|
|
for (uint32_t i = 0; i < aLength; ++i) {
|
|
CompressedGlyph g = srcGlyphs[i];
|
|
g.SetCanBreakBefore(!g.IsClusterStart() ?
|
|
CompressedGlyph::FLAG_BREAK_TYPE_NONE :
|
|
dstGlyphs[i].CanBreakBefore());
|
|
if (!g.IsSimpleGlyph()) {
|
|
uint32_t count = g.GetGlyphCount();
|
|
if (count > 0) {
|
|
DetailedGlyph *dst = AllocateDetailedGlyphs(i + aDest, count);
|
|
if (dst) {
|
|
DetailedGlyph *src = aSource->GetDetailedGlyphs(i + aStart);
|
|
if (src) {
|
|
::memcpy(dst, src, count * sizeof(DetailedGlyph));
|
|
} else {
|
|
g.SetMissing(0);
|
|
}
|
|
} else {
|
|
g.SetMissing(0);
|
|
}
|
|
}
|
|
}
|
|
dstGlyphs[i] = g;
|
|
}
|
|
|
|
// Copy glyph runs
|
|
GlyphRunIterator iter(aSource, aStart, aLength);
|
|
#ifdef DEBUG
|
|
gfxFont *lastFont = nullptr;
|
|
#endif
|
|
while (iter.NextRun()) {
|
|
gfxFont *font = iter.GetGlyphRun()->mFont;
|
|
NS_ASSERTION(font != lastFont, "Glyphruns not coalesced?");
|
|
#ifdef DEBUG
|
|
lastFont = font;
|
|
uint32_t end = iter.GetStringEnd();
|
|
#endif
|
|
uint32_t start = iter.GetStringStart();
|
|
|
|
// These used to be NS_ASSERTION()s, but WARNING is more appropriate.
|
|
// Although it's unusual (and not desirable), it's possible for us to assign
|
|
// different fonts to a base character and a following diacritic.
|
|
// Example on OSX 10.5/10.6 with default fonts installed:
|
|
// data:text/html,<p style="font-family:helvetica, arial, sans-serif;">
|
|
// &%23x043E;&%23x0486;&%23x20;&%23x043E;&%23x0486;
|
|
// This means the rendering of the cluster will probably not be very good,
|
|
// but it's the best we can do for now if the specified font only covered the
|
|
// initial base character and not its applied marks.
|
|
NS_WARN_IF_FALSE(aSource->IsClusterStart(start),
|
|
"Started font run in the middle of a cluster");
|
|
NS_WARN_IF_FALSE(end == aSource->GetLength() || aSource->IsClusterStart(end),
|
|
"Ended font run in the middle of a cluster");
|
|
|
|
nsresult rv = AddGlyphRun(font, iter.GetGlyphRun()->mMatchType,
|
|
start - aStart + aDest, false);
|
|
if (NS_FAILED(rv))
|
|
return;
|
|
}
|
|
}
|
|
|
|
void
|
|
gfxTextRun::SetSpaceGlyph(gfxFont *aFont, gfxContext *aContext,
|
|
uint32_t aCharIndex)
|
|
{
|
|
if (SetSpaceGlyphIfSimple(aFont, aContext, aCharIndex, ' ')) {
|
|
return;
|
|
}
|
|
|
|
aFont->InitWordCache();
|
|
static const uint8_t space = ' ';
|
|
gfxShapedWord *sw = aFont->GetShapedWord(aContext,
|
|
&space, 1,
|
|
HashMix(0, ' '),
|
|
MOZ_SCRIPT_LATIN,
|
|
mAppUnitsPerDevUnit,
|
|
gfxTextRunFactory::TEXT_IS_8BIT |
|
|
gfxTextRunFactory::TEXT_IS_ASCII |
|
|
gfxTextRunFactory::TEXT_IS_PERSISTENT,
|
|
nullptr);
|
|
if (sw) {
|
|
AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false);
|
|
CopyGlyphDataFrom(sw, aCharIndex);
|
|
}
|
|
}
|
|
|
|
bool
|
|
gfxTextRun::SetSpaceGlyphIfSimple(gfxFont *aFont, gfxContext *aContext,
|
|
uint32_t aCharIndex, PRUnichar aSpaceChar)
|
|
{
|
|
uint32_t spaceGlyph = aFont->GetSpaceGlyph();
|
|
if (!spaceGlyph || !CompressedGlyph::IsSimpleGlyphID(spaceGlyph)) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t spaceWidthAppUnits =
|
|
NS_lroundf(aFont->GetMetrics().spaceWidth * mAppUnitsPerDevUnit);
|
|
if (!CompressedGlyph::IsSimpleAdvance(spaceWidthAppUnits)) {
|
|
return false;
|
|
}
|
|
|
|
AddGlyphRun(aFont, gfxTextRange::kFontGroup, aCharIndex, false);
|
|
CompressedGlyph g;
|
|
g.SetSimpleGlyph(spaceWidthAppUnits, spaceGlyph);
|
|
if (aSpaceChar == ' ') {
|
|
g.SetIsSpace();
|
|
}
|
|
GetCharacterGlyphs()[aCharIndex] = g;
|
|
return true;
|
|
}
|
|
|
|
void
|
|
gfxTextRun::FetchGlyphExtents(gfxContext *aRefContext)
|
|
{
|
|
bool needsGlyphExtents = NeedsGlyphExtents(this);
|
|
if (!needsGlyphExtents && !mDetailedGlyphs)
|
|
return;
|
|
|
|
uint32_t i, runCount = mGlyphRuns.Length();
|
|
CompressedGlyph *charGlyphs = mCharacterGlyphs;
|
|
for (i = 0; i < runCount; ++i) {
|
|
const GlyphRun& run = mGlyphRuns[i];
|
|
gfxFont *font = run.mFont;
|
|
uint32_t start = run.mCharacterOffset;
|
|
uint32_t end = i + 1 < runCount ?
|
|
mGlyphRuns[i + 1].mCharacterOffset : GetLength();
|
|
bool fontIsSetup = false;
|
|
uint32_t j;
|
|
gfxGlyphExtents *extents = font->GetOrCreateGlyphExtents(mAppUnitsPerDevUnit);
|
|
|
|
for (j = start; j < end; ++j) {
|
|
const gfxTextRun::CompressedGlyph *glyphData = &charGlyphs[j];
|
|
if (glyphData->IsSimpleGlyph()) {
|
|
// If we're in speed mode, don't set up glyph extents here; we'll
|
|
// just return "optimistic" glyph bounds later
|
|
if (needsGlyphExtents) {
|
|
uint32_t glyphIndex = glyphData->GetSimpleGlyph();
|
|
if (!extents->IsGlyphKnown(glyphIndex)) {
|
|
if (!fontIsSetup) {
|
|
if (!font->SetupCairoFont(aRefContext)) {
|
|
NS_WARNING("failed to set up font for glyph extents");
|
|
break;
|
|
}
|
|
fontIsSetup = true;
|
|
}
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
++gGlyphExtentsSetupEagerSimple;
|
|
#endif
|
|
font->SetupGlyphExtents(aRefContext, glyphIndex, false, extents);
|
|
}
|
|
}
|
|
} else if (!glyphData->IsMissing()) {
|
|
uint32_t glyphCount = glyphData->GetGlyphCount();
|
|
if (glyphCount == 0) {
|
|
continue;
|
|
}
|
|
const gfxTextRun::DetailedGlyph *details = GetDetailedGlyphs(j);
|
|
if (!details) {
|
|
continue;
|
|
}
|
|
for (uint32_t k = 0; k < glyphCount; ++k, ++details) {
|
|
uint32_t glyphIndex = details->mGlyphID;
|
|
if (!extents->IsGlyphKnownWithTightExtents(glyphIndex)) {
|
|
if (!fontIsSetup) {
|
|
if (!font->SetupCairoFont(aRefContext)) {
|
|
NS_WARNING("failed to set up font for glyph extents");
|
|
break;
|
|
}
|
|
fontIsSetup = true;
|
|
}
|
|
#ifdef DEBUG_TEXT_RUN_STORAGE_METRICS
|
|
++gGlyphExtentsSetupEagerTight;
|
|
#endif
|
|
font->SetupGlyphExtents(aRefContext, glyphIndex, true, extents);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
gfxTextRun::ClusterIterator::ClusterIterator(gfxTextRun *aTextRun)
|
|
: mTextRun(aTextRun), mCurrentChar(uint32_t(-1))
|
|
{
|
|
}
|
|
|
|
void
|
|
gfxTextRun::ClusterIterator::Reset()
|
|
{
|
|
mCurrentChar = uint32_t(-1);
|
|
}
|
|
|
|
bool
|
|
gfxTextRun::ClusterIterator::NextCluster()
|
|
{
|
|
uint32_t len = mTextRun->GetLength();
|
|
while (++mCurrentChar < len) {
|
|
if (mTextRun->IsClusterStart(mCurrentChar)) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
mCurrentChar = uint32_t(-1);
|
|
return false;
|
|
}
|
|
|
|
uint32_t
|
|
gfxTextRun::ClusterIterator::ClusterLength() const
|
|
{
|
|
if (mCurrentChar == uint32_t(-1)) {
|
|
return 0;
|
|
}
|
|
|
|
uint32_t i = mCurrentChar,
|
|
len = mTextRun->GetLength();
|
|
while (++i < len) {
|
|
if (mTextRun->IsClusterStart(i)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
return i - mCurrentChar;
|
|
}
|
|
|
|
gfxFloat
|
|
gfxTextRun::ClusterIterator::ClusterAdvance(PropertyProvider *aProvider) const
|
|
{
|
|
if (mCurrentChar == uint32_t(-1)) {
|
|
return 0;
|
|
}
|
|
|
|
return mTextRun->GetAdvanceWidth(mCurrentChar, ClusterLength(), aProvider);
|
|
}
|
|
|
|
size_t
|
|
gfxTextRun::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf)
|
|
{
|
|
// The second arg is how much gfxTextRun::AllocateStorage would have
|
|
// allocated.
|
|
size_t total = mGlyphRuns.SizeOfExcludingThis(aMallocSizeOf);
|
|
|
|
if (mDetailedGlyphs) {
|
|
total += mDetailedGlyphs->SizeOfIncludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
size_t
|
|
gfxTextRun::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf)
|
|
{
|
|
return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG
|
|
void
|
|
gfxTextRun::Dump(FILE* aOutput) {
|
|
if (!aOutput) {
|
|
aOutput = stdout;
|
|
}
|
|
|
|
uint32_t i;
|
|
fputc('[', aOutput);
|
|
for (i = 0; i < mGlyphRuns.Length(); ++i) {
|
|
if (i > 0) {
|
|
fputc(',', aOutput);
|
|
}
|
|
gfxFont* font = mGlyphRuns[i].mFont;
|
|
const gfxFontStyle* style = font->GetStyle();
|
|
NS_ConvertUTF16toUTF8 fontName(font->GetName());
|
|
nsAutoCString lang;
|
|
style->language->ToUTF8String(lang);
|
|
fprintf(aOutput, "%d: %s %f/%d/%d/%s", mGlyphRuns[i].mCharacterOffset,
|
|
fontName.get(), style->size,
|
|
style->weight, style->style, lang.get());
|
|
}
|
|
fputc(']', aOutput);
|
|
}
|
|
#endif
|