Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=4 et sw=2 tw=80:
* 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/. */
#ifndef GFX_FONT_H
#define GFX_FONT_H
#include <new>
#include <utility>
#include <functional>
#include "PLDHashTable.h"
#include "ThebesRLBoxTypes.h"
#include "gfxFontVariations.h"
#include "gfxRect.h"
#include "gfxTypes.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/Attributes.h"
#include "mozilla/FontPropertyTypes.h"
#include "mozilla/HashTable.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/MruCache.h"
#include "mozilla/Mutex.h"
#include "mozilla/RefPtr.h"
#include "mozilla/RWLock.h"
#include "mozilla/TypedEnumBits.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/gfx/FontPaletteCache.h"
#include "mozilla/gfx/MatrixFwd.h"
#include "mozilla/gfx/Point.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/intl/UnicodeScriptCodes.h"
#include "nsCOMPtr.h"
#include "nsColor.h"
#include "nsTHashMap.h"
#include "nsTHashSet.h"
#include "nsExpirationTracker.h"
#include "nsFontMetrics.h"
#include "nsHashKeys.h"
#include "nsIMemoryReporter.h"
#include "nsIObserver.h"
#include "nsISupports.h"
#include "nsString.h"
#include "nsTArray.h"
#include "nsTHashtable.h"
#include "nscore.h"
#include "DrawMode.h"
// Only required for function bodies
#include "gfxFontEntry.h"
#include "gfxFontFeatures.h"
class gfxContext;
class gfxGraphiteShaper;
class gfxHarfBuzzShaper;
class gfxGlyphExtents;
class gfxMathTable;
class gfxPattern;
class gfxShapedText;
class gfxShapedWord;
class gfxSkipChars;
class gfxTextRun;
class nsIEventTarget;
class nsITimer;
struct gfxTextRunDrawCallbacks;
namespace mozilla {
class SVGContextPaint;
namespace layout {
class TextDrawTarget;
}
} // namespace mozilla
typedef struct _cairo cairo_t;
typedef struct _cairo_scaled_font cairo_scaled_font_t;
#define FONT_MAX_SIZE 2000.0
#define SMALL_CAPS_SCALE_FACTOR 0.8
// The skew factor used for synthetic-italic [oblique] fonts;
// we use a platform-dependent value to harmonize with the platform's own APIs.
#ifdef XP_WIN
# define OBLIQUE_SKEW_FACTOR 0.3f
#elif defined(MOZ_WIDGET_GTK)
# define OBLIQUE_SKEW_FACTOR 0.2f
#else
# define OBLIQUE_SKEW_FACTOR 0.25f
#endif
struct gfxFontStyle {
using FontStretch = mozilla::FontStretch;
using FontSlantStyle = mozilla::FontSlantStyle;
using FontWeight = mozilla::FontWeight;
using FontSizeAdjust = mozilla::StyleFontSizeAdjust;
gfxFontStyle();
gfxFontStyle(FontSlantStyle aStyle, FontWeight aWeight, FontStretch aStretch,
gfxFloat aSize, const FontSizeAdjust& aSizeAdjust,
bool aSystemFont, bool aPrinterFont, bool aWeightSynthesis,
bool aStyleSynthesis, bool aSmallCapsSynthesis,
bool aPositionSynthesis, uint32_t aLanguageOverride);
// Features are composed of (1) features from style rules (2) features
// from feature settings rules and (3) family-specific features. (1) and
// (3) are guaranteed to be mutually exclusive
// custom opentype feature settings
CopyableTArray<gfxFontFeature> featureSettings;
// Some font-variant property values require font-specific settings
// defined via @font-feature-values rules. These are resolved after
// font matching occurs.
// -- list of value tags for specific alternate features
mozilla::StyleFontVariantAlternates variantAlternates;
// -- object used to look these up once the font is matched
RefPtr<gfxFontFeatureValueSet> featureValueLookup;
// opentype variation settings
CopyableTArray<gfxFontVariation> variationSettings;
// The logical size of the font, in pixels
gfxFloat size;
// The optical size value to apply (if supported); negative means none.
float autoOpticalSize = -1.0f;
// The aspect-value (ie., the ratio actualsize:actualxheight) that any
// actual physical font created from this font structure must have when
// rendering or measuring a string. A value of -1.0 means no adjustment
// needs to be done; otherwise the value must be nonnegative.
float sizeAdjust;
// baseline offset, used when simulating sub/superscript glyphs
float baselineOffset;
// Language system tag, to override document language;
// an OpenType "language system" tag represented as a 32-bit integer
// Normally 0, so font rendering will use the document or element language
// (see above) to control any language-specific rendering, but the author
// can override this for cases where the options implemented in the font
// do not directly match the actual language. (E.g. lang may be Macedonian,
// but the font in use does not explicitly support this; the author can
// use font-language-override to request the Serbian option in the font
// in order to get correct glyph shapes.)
uint32_t languageOverride;
// The Font{Weight,Stretch,SlantStyle} fields are each a 16-bit type.
// The weight of the font: 100, 200, ... 900.
FontWeight weight;
// The stretch of the font
FontStretch stretch;
// The style of font
FontSlantStyle style;
// Whether face-selection properties weight/style/stretch are all 'normal'
bool IsNormalStyle() const {
return weight.IsNormal() && style.IsNormal() && stretch.IsNormal();
}
// We pack these three small-integer fields into a single byte to avoid
// overflowing an 8-byte boundary [in a 64-bit build] and ending up with
// 7 bytes of padding at the end of the struct.
// caps variant (small-caps, petite-caps, etc.)
uint8_t variantCaps : 3; // uses range 0..6
// sub/superscript variant
uint8_t variantSubSuper : 2; // uses range 0..2
// font metric used as basis of font-size-adjust
uint8_t sizeAdjustBasis : 3; // uses range 0..4
// Say that this font is a system font and therefore does not
// require certain fixup that we do for fonts from untrusted
// sources.
bool systemFont : 1;
// Say that this font is used for print or print preview.
bool printerFont : 1;
// Used to imitate -webkit-font-smoothing: antialiased
bool useGrayscaleAntialiasing : 1;
// Whether synthetic styles are allowed (required, in the case of position)
bool allowSyntheticWeight : 1;
bool allowSyntheticStyle : 1;
bool allowSyntheticSmallCaps : 1;
bool useSyntheticPosition : 1;
// some variant features require fallback which complicates the shaping
// code, so set up a bool to indicate when shaping with fallback is needed
bool noFallbackVariantFeatures : 1;
// Return the final adjusted font size for the given aspect ratio.
// Not meant to be called when sizeAdjustBasis is NONE.
gfxFloat GetAdjustedSize(gfxFloat aspect) const {
MOZ_ASSERT(
FontSizeAdjust::Tag(sizeAdjustBasis) != FontSizeAdjust::Tag::None,
"Not meant to be called when sizeAdjustBasis is none");
gfxFloat adjustedSize =
std::max(NS_round(size * (sizeAdjust / aspect)), 1.0);
return std::min(adjustedSize, FONT_MAX_SIZE);
}
// Some callers want to take a short-circuit path if they can be sure the
// adjusted size will be zero.
bool AdjustedSizeMustBeZero() const {
return size == 0.0 ||
(FontSizeAdjust::Tag(sizeAdjustBasis) != FontSizeAdjust::Tag::None &&
sizeAdjust == 0.0);
}
PLDHashNumber Hash() const;
// Adjust this style to simulate sub/superscript (as requested in the
// variantSubSuper field) using size and baselineOffset instead.
void AdjustForSubSuperscript(int32_t aAppUnitsPerDevPixel);
// Should this style cause the given font entry to use synthetic bold?
bool NeedsSyntheticBold(gfxFontEntry* aFontEntry) const {
return weight.IsBold() && allowSyntheticWeight &&
!aFontEntry->SupportsBold();
}
bool Equals(const gfxFontStyle& other) const {
return mozilla::NumbersAreBitwiseIdentical(size, other.size) &&
(style == other.style) && (weight == other.weight) &&
(stretch == other.stretch) && (variantCaps == other.variantCaps) &&
(variantSubSuper == other.variantSubSuper) &&
(allowSyntheticWeight == other.allowSyntheticWeight) &&
(allowSyntheticStyle == other.allowSyntheticStyle) &&
(allowSyntheticSmallCaps == other.allowSyntheticSmallCaps) &&
(useSyntheticPosition == other.useSyntheticPosition) &&
(systemFont == other.systemFont) &&
(printerFont == other.printerFont) &&
(useGrayscaleAntialiasing == other.useGrayscaleAntialiasing) &&
(baselineOffset == other.baselineOffset) &&
mozilla::NumbersAreBitwiseIdentical(sizeAdjust, other.sizeAdjust) &&
(sizeAdjustBasis == other.sizeAdjustBasis) &&
(featureSettings == other.featureSettings) &&
(variantAlternates == other.variantAlternates) &&
(featureValueLookup == other.featureValueLookup) &&
(variationSettings == other.variationSettings) &&
(languageOverride == other.languageOverride) &&
mozilla::NumbersAreBitwiseIdentical(autoOpticalSize,
other.autoOpticalSize);
}
};
/**
* Font cache design:
*
* The mFonts hashtable contains most fonts, indexed by (gfxFontEntry*, style).
* It maintains a strong reference to the fonts it contains.
* Whenever a font is accessed, it is marked as used to move it to a new
* generation in the tracker to avoid expiration.
* The expiration tracker will only expire fonts with a single reference, the
* cache itself. Fonts with more than one reference are marked as used.
*
* We're using 3 generations with a ten-second generation interval, so
* zero-refcount fonts will be deleted 20-30 seconds after their refcount
* goes to zero, if timer events fire in a timely manner.
*
* The font cache also handles timed expiration of cached ShapedWords
* for "persistent" fonts: it has a repeating timer, and notifies
* each cached font to "age" its shaped words. The words will be released
* by the fonts if they get aged three times without being re-used in the
* meantime.
*
* Note that the ShapedWord timeout is much larger than the font timeout,
* so that in the case of a short-lived font, we'll discard the gfxFont
* completely, with all its words, and avoid the cost of aging the words
* individually. That only happens with longer-lived fonts.
*/
struct FontCacheSizes {
FontCacheSizes() : mFontInstances(0), mShapedWords(0) {}
size_t mFontInstances; // memory used by instances of gfxFont subclasses
size_t mShapedWords; // memory used by the per-font shapedWord caches
};
class gfxFontCache final
: public ExpirationTrackerImpl<gfxFont, 3, mozilla::Mutex,
mozilla::MutexAutoLock> {
protected:
// Expiration tracker implementation.
enum { FONT_TIMEOUT_SECONDS = 10 };
typedef mozilla::Mutex Lock;
typedef mozilla::MutexAutoLock AutoLock;
// This protects the ExpirationTracker tables.
Lock mMutex = Lock("fontCacheExpirationMutex");
Lock& GetMutex() override { return mMutex; }
public:
explicit gfxFontCache(nsIEventTarget* aEventTarget);
~gfxFontCache();
enum { SHAPED_WORD_TIMEOUT_SECONDS = 60 };
/*
* Get the global gfxFontCache. You must call Init() before
* calling this method --- the result will not be null.
*/
static gfxFontCache* GetCache() { return gGlobalCache; }
static nsresult Init();
// It's OK to call this even if Init() has not been called.
static void Shutdown();
// Look up a font in the cache. Returns null if there's nothing matching
// in the cache
already_AddRefed<gfxFont> Lookup(const gfxFontEntry* aFontEntry,
const gfxFontStyle* aStyle,
const gfxCharacterMap* aUnicodeRangeMap);
// We created a new font (presumably because Lookup returned null);
// put it in the cache. The font's refcount should be nonzero. It is
// allowable to add a new font even if there is one already in the
// cache with the same key, as we may race with other threads to do
// the insertion -- in that case we will return the original font,
// and destroy the new one.
already_AddRefed<gfxFont> MaybeInsert(gfxFont* aFont);
bool MaybeDestroy(gfxFont* aFont);
// Cleans out the hashtable and removes expired fonts waiting for cleanup.
// Other gfxFont objects may be still in use but they will be pushed
// into the expiration queues and removed.
void Flush();
void FlushShapedWordCaches();
void NotifyGlyphsChanged();
void AgeCachedWords();
void RunWordCacheExpirationTimer() {
if (!mTimerRunning) {
mozilla::MutexAutoLock lock(mMutex);
if (!mTimerRunning && mWordCacheExpirationTimer) {
mWordCacheExpirationTimer->InitWithNamedFuncCallback(
WordCacheExpirationTimerCallback, this,
SHAPED_WORD_TIMEOUT_SECONDS * 1000, nsITimer::TYPE_REPEATING_SLACK,
"gfxFontCache::WordCacheExpiration");
mTimerRunning = true;
}
}
}
void PauseWordCacheExpirationTimer() {
if (mTimerRunning) {
mozilla::MutexAutoLock lock(mMutex);
if (mTimerRunning && mWordCacheExpirationTimer) {
mWordCacheExpirationTimer->Cancel();
mTimerRunning = false;
}
}
}
void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
FontCacheSizes* aSizes) const;
void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
FontCacheSizes* aSizes) const;
protected:
class MemoryReporter final : public nsIMemoryReporter {
~MemoryReporter() = default;
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIMEMORYREPORTER
};
// Observer for notifications that the font cache cares about
class Observer final : public nsIObserver {
~Observer() = default;
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIOBSERVER
};
nsresult AddObject(gfxFont* aFont) {
AutoLock lock(mMutex);
return AddObjectLocked(aFont, lock);
}
// This gets called when the timeout has expired on a single-refcount
// font; we just delete it.
void NotifyExpiredLocked(gfxFont* aFont, const AutoLock&)
MOZ_REQUIRES(mMutex) override;
void NotifyHandlerEnd() override;
void DestroyDiscard(nsTArray<gfxFont*>& aDiscard);
static gfxFontCache* gGlobalCache;
struct MOZ_STACK_CLASS Key {
const gfxFontEntry* mFontEntry;
const gfxFontStyle* mStyle;
const gfxCharacterMap* mUnicodeRangeMap;
Key(const gfxFontEntry* aFontEntry, const gfxFontStyle* aStyle,
const gfxCharacterMap* aUnicodeRangeMap)
: mFontEntry(aFontEntry),
mStyle(aStyle),
mUnicodeRangeMap(aUnicodeRangeMap) {}
};
class HashEntry : public PLDHashEntryHdr {
public:
typedef const Key& KeyType;
typedef const Key* KeyTypePointer;
// When constructing a new entry in the hashtable, we'll leave this
// blank. The caller of Put() will fill this in.
explicit HashEntry(KeyTypePointer aStr) {}
bool KeyEquals(const KeyTypePointer aKey) const;
static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; }
static PLDHashNumber HashKey(const KeyTypePointer aKey) {
return mozilla::HashGeneric(aKey->mStyle->Hash(), aKey->mFontEntry,
aKey->mUnicodeRangeMap);
}
enum { ALLOW_MEMMOVE = true };
gfxFont* MOZ_UNSAFE_REF("tracking for deferred deletion") mFont = nullptr;
};
nsTHashtable<HashEntry> mFonts MOZ_GUARDED_BY(mMutex);
nsTArray<gfxFont*> mTrackerDiscard MOZ_GUARDED_BY(mMutex);
static void WordCacheExpirationTimerCallback(nsITimer* aTimer, void* aCache);
nsCOMPtr<nsITimer> mWordCacheExpirationTimer MOZ_GUARDED_BY(mMutex);
std::atomic<bool> mTimerRunning = false;
};
class gfxTextPerfMetrics {
public:
struct TextCounts {
uint32_t numContentTextRuns;
uint32_t numChromeTextRuns;
uint32_t numChars;
uint32_t maxTextRunLen;
uint32_t wordCacheSpaceRules;
uint32_t wordCacheLong;
uint32_t wordCacheHit;
uint32_t wordCacheMiss;
uint32_t fallbackPrefs;
uint32_t fallbackSystem;
uint32_t textrunConst;
uint32_t textrunDestr;
uint32_t genericLookups;
};
uint32_t reflowCount;
// counts per reflow operation
TextCounts current;
// totals for the lifetime of a document
TextCounts cumulative;
gfxTextPerfMetrics() { memset(this, 0, sizeof(gfxTextPerfMetrics)); }
// add current totals to cumulative ones
void Accumulate() {
if (current.numChars == 0) {
return;
}
cumulative.numContentTextRuns += current.numContentTextRuns;
cumulative.numChromeTextRuns += current.numChromeTextRuns;
cumulative.numChars += current.numChars;
if (current.maxTextRunLen > cumulative.maxTextRunLen) {
cumulative.maxTextRunLen = current.maxTextRunLen;
}
cumulative.wordCacheSpaceRules += current.wordCacheSpaceRules;
cumulative.wordCacheLong += current.wordCacheLong;
cumulative.wordCacheHit += current.wordCacheHit;
cumulative.wordCacheMiss += current.wordCacheMiss;
cumulative.fallbackPrefs += current.fallbackPrefs;
cumulative.fallbackSystem += current.fallbackSystem;
cumulative.textrunConst += current.textrunConst;
cumulative.textrunDestr += current.textrunDestr;
cumulative.genericLookups += current.genericLookups;
memset(&current, 0, sizeof(current));
}
};
namespace mozilla {
namespace gfx {
class UnscaledFont;
// Flags that live in the gfxShapedText::mFlags field.
// (Note that gfxTextRun has an additional mFlags2 field for use
// by textrun clients like nsTextFrame.)
//
// If you add a flag, please add support for it in gfxTextRun::Dump.
enum class ShapedTextFlags : uint16_t {
/**
* When set, the text is RTL.
*/
TEXT_IS_RTL = 0x0001,
/**
* When set, spacing is enabled and the textrun needs to call GetSpacing
* on the spacing provider.
*/
TEXT_ENABLE_SPACING = 0x0002,
/**
* When set, the text has no characters above 255 and it is stored
* in the textrun in 8-bit format.
*/
TEXT_IS_8BIT = 0x0004,
/**
* When set, GetHyphenationBreaks may return true for some character
* positions, otherwise it will always return false for all characters.
*/
TEXT_ENABLE_HYPHEN_BREAKS = 0x0008,
/**
* When set, the RunMetrics::mBoundingBox field will be initialized
* properly based on glyph extents, in particular, glyph extents that
* overflow the standard font-box (the box defined by the ascent, descent
* and advance width of the glyph). When not set, it may just be the
* standard font-box even if glyphs overflow.
*/
TEXT_NEED_BOUNDING_BOX = 0x0010,
/**
* When set, optional ligatures are disabled. Ligatures that are
* required for legible text should still be enabled.
*/
TEXT_DISABLE_OPTIONAL_LIGATURES = 0x0020,
/**
* When set, the textrun should favour speed of construction over
* quality. This may involve disabling ligatures and/or kerning or
* other effects.
*/
TEXT_OPTIMIZE_SPEED = 0x0040,
/**
* When set, the textrun should discard control characters instead of
* turning them into hexboxes.
*/
TEXT_HIDE_CONTROL_CHARACTERS = 0x0080,
/**
* nsTextFrameThebes sets these, but they're defined here rather than
* in nsTextFrameUtils.h because ShapedWord creation/caching also needs
* to check the _INCOMING flag
*/
TEXT_TRAILING_ARABICCHAR = 0x0100,
/**
* When set, the previous character for this textrun was an Arabic
* character. This is used for the context detection necessary for
* bidi.numeral implementation.
*/
TEXT_INCOMING_ARABICCHAR = 0x0200,
/**
* Set if the textrun should use the OpenType 'math' script.
*/
TEXT_USE_MATH_SCRIPT = 0x0400,
/*
* Bit 0x0800 is currently unused.
*/
/**
* Field for orientation of the textrun and glyphs within it.
* Possible values of the TEXT_ORIENT_MASK field:
* TEXT_ORIENT_HORIZONTAL
* TEXT_ORIENT_VERTICAL_UPRIGHT
* TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT
* TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT
* TEXT_ORIENT_VERTICAL_MIXED
* For all VERTICAL settings, the x and y coordinates of glyph
* positions are exchanged, so that simple advances are vertical.
*
* The MIXED value indicates vertical textRuns for which the CSS
* text-orientation property is 'mixed', but is never used for
* individual glyphRuns; it will be resolved to either UPRIGHT
* or SIDEWAYS_RIGHT according to the UTR50 properties of the
* characters, and separate glyphRuns created for the resulting
* glyph orientations.
*/
TEXT_ORIENT_MASK = 0x7000,
TEXT_ORIENT_HORIZONTAL = 0x0000,
TEXT_ORIENT_VERTICAL_UPRIGHT = 0x1000,
TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT = 0x2000,
TEXT_ORIENT_VERTICAL_MIXED = 0x3000,
TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT = 0x4000,
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ShapedTextFlags)
} // namespace gfx
} // namespace mozilla
class gfxTextRunFactory {
// Used by stylo
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(gfxTextRunFactory)
public:
typedef mozilla::gfx::DrawTarget DrawTarget;
/**
* This record contains all the parameters needed to initialize a textrun.
*/
struct MOZ_STACK_CLASS Parameters {
// Shape text params suggesting where the textrun will be rendered
DrawTarget* mDrawTarget;
// Pointer to arbitrary user data (which should outlive the textrun)
void* mUserData;
// A description of which characters have been stripped from the original
// DOM string to produce the characters in the textrun. May be null
// if that information is not relevant.
gfxSkipChars* mSkipChars;
// A list of where linebreaks are currently placed in the textrun. May
// be null if mInitialBreakCount is zero.
uint32_t* mInitialBreaks;
uint32_t mInitialBreakCount;
// The ratio to use to convert device pixels to application layout units
int32_t mAppUnitsPerDevUnit;
};
protected:
// Protected destructor, to discourage deletion outside of Release():
virtual ~gfxTextRunFactory();
};
/**
* gfxFontShaper
*
* This class implements text shaping (character to glyph mapping and
* glyph layout). There is a gfxFontShaper subclass for each text layout
* technology (uniscribe, core text, harfbuzz,....) we support.
*
* The shaper is responsible for setting up glyph data in gfxTextRuns.
*
* A generic, platform-independent shaper relies only on the standard
* gfxFont interface and can work with any concrete subclass of gfxFont.
*
* Platform-specific implementations designed to interface to platform
* shaping APIs such as Uniscribe or CoreText may rely on features of a
* specific font subclass to access native font references
* (such as CTFont, HFONT, DWriteFont, etc).
*/
class gfxFontShaper {
public:
typedef mozilla::gfx::DrawTarget DrawTarget;
typedef mozilla::intl::Script Script;
enum class RoundingFlags : uint8_t { kRoundX = 0x01, kRoundY = 0x02 };
explicit gfxFontShaper(gfxFont* aFont) : mFont(aFont) {
NS_ASSERTION(aFont, "shaper requires a valid font!");
}
virtual ~gfxFontShaper() = default;
// Shape a piece of text and store the resulting glyph data into
// aShapedText. Parameters aOffset/aLength indicate the range of
// aShapedText to be updated; aLength is also the length of aText.
virtual bool ShapeText(DrawTarget* aDrawTarget, const char16_t* aText,
uint32_t aOffset, uint32_t aLength, Script aScript,
nsAtom* aLanguage, // may be null, indicating no
// lang-specific shaping to be
// applied
bool aVertical, RoundingFlags aRounding,
gfxShapedText* aShapedText) = 0;
gfxFont* GetFont() const { return mFont; }
static void MergeFontFeatures(
const gfxFontStyle* aStyle, const nsTArray<gfxFontFeature>& aFontFeatures,
bool aDisableLigatures, const nsACString& aFamilyName, bool aAddSmallCaps,
void (*aHandleFeature)(uint32_t, uint32_t, void*),
void* aHandleFeatureData);
protected:
// the font this shaper is working with. The font owns a UniquePtr reference
// to this object, and will destroy it before it dies. Thus, mFont will always
// be valid.
gfxFont* MOZ_NON_OWNING_REF mFont;
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(gfxFontShaper::RoundingFlags)
/*
* gfxShapedText is an abstract superclass for gfxShapedWord and gfxTextRun.
* These are objects that store a list of zero or more glyphs for each
* character. For each glyph we store the glyph ID, the advance, and possibly
* x/y-offsets. The idea is that a string is rendered by a loop that draws each
* glyph at its designated offset from the current point, then advances the
* current point by the glyph's advance in the direction of the textrun (LTR or
* RTL). Each glyph advance is always rounded to the nearest appunit; this
* ensures consistent results when dividing the text in a textrun into multiple
* text frames (frame boundaries are always aligned to appunits). We optimize
* for the case where a character has a single glyph and zero xoffset and
* yoffset, and the glyph ID and advance are in a reasonable range so we can
* pack all necessary data into 32 bits.
*
* gfxFontShaper can shape text into either a gfxShapedWord (cached by a
* gfxFont) or directly into a gfxTextRun (for cases where we want to shape
* textruns in their entirety rather than using cached words, because there may
* be layout features that depend on the inter-word spaces).
*/
class gfxShapedText {
public:
typedef mozilla::intl::Script Script;
gfxShapedText(uint32_t aLength, mozilla::gfx::ShapedTextFlags aFlags,
uint16_t aAppUnitsPerDevUnit)
: mLength(aLength),
mFlags(aFlags),
mAppUnitsPerDevUnit(aAppUnitsPerDevUnit) {}
virtual ~gfxShapedText() = default;
/**
* This class records the information associated with a character in the
* input string. It's optimized for the case where there is one glyph
* representing that character alone.
*
* A character can have zero or more associated glyphs. Each glyph
* has an advance width and an x and y offset.
* A character may be the start of a cluster.
* A character may be the start of a ligature group.
* A character can be "missing", indicating that the system is unable
* to render the character.
*
* All characters in a ligature group conceptually share all the glyphs
* associated with the characters in a group.
*/
class CompressedGlyph {
public:
enum {
// Indicates that a cluster and ligature group starts at this
// character; this character has a single glyph with a reasonable
// advance and zero offsets. A "reasonable" advance
// is one that fits in the available bits (currently 12) (specified
// in appunits).
FLAG_IS_SIMPLE_GLYPH = 0x80000000U,
// These flags are applicable to both "simple" and "complex" records.
COMMON_FLAGS_MASK = 0x70000000U,
// Indicates whether a linebreak is allowed before this character;
// this is a two-bit field that holds a FLAG_BREAK_TYPE_xxx value
// indicating the kind of linebreak (if any) allowed here.
FLAGS_CAN_BREAK_BEFORE = 0x60000000U,
FLAGS_CAN_BREAK_SHIFT = 29,
FLAG_BREAK_TYPE_NONE = 0,
FLAG_BREAK_TYPE_NORMAL = 1,
FLAG_BREAK_TYPE_HYPHEN = 2,
// Allow break before this position if needed to avoid overflow:
FLAG_BREAK_TYPE_EMERGENCY_WRAP = 3,
FLAG_CHAR_IS_SPACE = 0x10000000U,
// Fields present only when FLAG_IS_SIMPLE_GLYPH is /true/.
// The advance is stored in appunits as a 12-bit field:
ADVANCE_MASK = 0x0FFF0000U,
ADVANCE_SHIFT = 16,
// and the glyph ID is stored in the low 16 bits.
GLYPH_MASK = 0x0000FFFFU,
// Fields present only when FLAG_IS_SIMPLE_GLYPH is /false/.
// Non-simple glyphs may or may not have glyph data in the
// corresponding mDetailedGlyphs entry. They have a glyph count
// stored in the low 16 bits, and the following flag bits:
GLYPH_COUNT_MASK = 0x0000FFFFU,
// When NOT set, indicates that this character corresponds to a
// missing glyph and should be skipped (or possibly, render the character
// Unicode value in some special way). If there are glyphs,
// the mGlyphID is actually the UTF16 character code. The bit is
// inverted so we can memset the array to zero to indicate all missing.
FLAG_NOT_MISSING = 0x010000,
FLAG_NOT_CLUSTER_START = 0x020000,
FLAG_NOT_LIGATURE_GROUP_START = 0x040000,
// Flag bit 0x080000 is currently unused.
// Certain types of characters are marked so that they can be given
// special treatment in rendering. This may require use of a "complex"
// CompressedGlyph record even for a character that would otherwise be
// treated as "simple".
CHAR_TYPE_FLAGS_MASK = 0xF00000,
FLAG_CHAR_IS_TAB = 0x100000,
FLAG_CHAR_IS_NEWLINE = 0x200000,
// Per CSS Text Decoration Module Level 3, emphasis marks are not
// drawn for any character in Unicode categories Z*, Cc, Cf, and Cn
// which is not combined with any combining characters. This flag is
// set for all those characters except 0x20 whitespace.
FLAG_CHAR_NO_EMPHASIS_MARK = 0x400000,
// Per CSS Text, letter-spacing is not applied to formatting chars
// (category Cf). We mark those in the textrun so as to be able to
// skip them when setting up spacing in nsTextFrame.
FLAG_CHAR_IS_FORMATTING_CONTROL = 0x800000,
// The bits 0x0F000000 are currently unused in non-simple glyphs.
};
// "Simple glyphs" have a simple glyph ID, simple advance and their
// x and y offsets are zero. Also the glyph extents do not overflow
// the font-box defined by the font ascent, descent and glyph advance width.
// These case is optimized to avoid storing DetailedGlyphs.
// Returns true if the glyph ID aGlyph fits into the compressed
// representation
static bool IsSimpleGlyphID(uint32_t aGlyph) {
return (aGlyph & GLYPH_MASK) == aGlyph;
}
// Returns true if the advance aAdvance fits into the compressed
// representation. aAdvance is in appunits.
static bool IsSimpleAdvance(uint32_t aAdvance) {
return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance;
}
bool IsSimpleGlyph() const { return mValue & FLAG_IS_SIMPLE_GLYPH; }
uint32_t GetSimpleAdvance() const {
MOZ_ASSERT(IsSimpleGlyph());
return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT;
}
uint32_t GetSimpleGlyph() const {
MOZ_ASSERT(IsSimpleGlyph());
return mValue & GLYPH_MASK;
}
bool IsMissing() const {
return !(mValue & (FLAG_NOT_MISSING | FLAG_IS_SIMPLE_GLYPH));
}
bool IsClusterStart() const {
return IsSimpleGlyph() || !(mValue & FLAG_NOT_CLUSTER_START);
}
bool IsLigatureGroupStart() const {
return IsSimpleGlyph() || !(mValue & FLAG_NOT_LIGATURE_GROUP_START);
}
bool IsLigatureContinuation() const {
return !IsSimpleGlyph() &&
(mValue & (FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING)) ==
(FLAG_NOT_LIGATURE_GROUP_START | FLAG_NOT_MISSING);
}
// Return true if the original character was a normal (breakable,
// trimmable) space (U+0020). Not true for other characters that
// may happen to map to the space glyph (U+00A0).
bool CharIsSpace() const { return mValue & FLAG_CHAR_IS_SPACE; }
bool CharIsTab() const {
return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_TAB);
}
bool CharIsNewline() const {
return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_NEWLINE);
}
bool CharMayHaveEmphasisMark() const {
return !CharIsSpace() &&
(IsSimpleGlyph() || !(mValue & FLAG_CHAR_NO_EMPHASIS_MARK));
}
bool CharIsFormattingControl() const {
return !IsSimpleGlyph() && (mValue & FLAG_CHAR_IS_FORMATTING_CONTROL);
}
uint32_t CharTypeFlags() const {
return IsSimpleGlyph() ? 0 : (mValue & CHAR_TYPE_FLAGS_MASK);
}
void SetClusterStart(bool aIsClusterStart) {
MOZ_ASSERT(!IsSimpleGlyph());
if (aIsClusterStart) {
mValue &= ~FLAG_NOT_CLUSTER_START;
} else {
mValue |= FLAG_NOT_CLUSTER_START;
}
}
uint8_t CanBreakBefore() const {
return (mValue & FLAGS_CAN_BREAK_BEFORE) >> FLAGS_CAN_BREAK_SHIFT;
}
// Returns FLAGS_CAN_BREAK_BEFORE if the setting changed, 0 otherwise
uint32_t SetCanBreakBefore(uint8_t aCanBreakBefore) {
MOZ_ASSERT(aCanBreakBefore <= 3, "Bogus break-flags value!");
uint32_t breakMask = (uint32_t(aCanBreakBefore) << FLAGS_CAN_BREAK_SHIFT);
uint32_t toggle = breakMask ^ (mValue & FLAGS_CAN_BREAK_BEFORE);
mValue ^= toggle;
return toggle;
}
// Create a CompressedGlyph value representing a simple glyph with
// no extra flags (line-break or is_space) set.
static CompressedGlyph MakeSimpleGlyph(uint32_t aAdvanceAppUnits,
uint32_t aGlyph) {
MOZ_ASSERT(IsSimpleAdvance(aAdvanceAppUnits));
MOZ_ASSERT(IsSimpleGlyphID(aGlyph));
CompressedGlyph g;
g.mValue =
FLAG_IS_SIMPLE_GLYPH | (aAdvanceAppUnits << ADVANCE_SHIFT) | aGlyph;
return g;
}
// Assign a simple glyph value to an existing CompressedGlyph record,
// preserving line-break/is-space flags if present.
CompressedGlyph& SetSimpleGlyph(uint32_t aAdvanceAppUnits,
uint32_t aGlyph) {
MOZ_ASSERT(!CharTypeFlags(), "Char type flags lost");
mValue = (mValue & COMMON_FLAGS_MASK) |
MakeSimpleGlyph(aAdvanceAppUnits, aGlyph).mValue;
return *this;
}
// Create a CompressedGlyph value representing a complex glyph record,
// without any line-break or char-type flags.
static CompressedGlyph MakeComplex(bool aClusterStart,
bool aLigatureStart) {
CompressedGlyph g;
g.mValue = FLAG_NOT_MISSING |
(aClusterStart ? 0 : FLAG_NOT_CLUSTER_START) |
(aLigatureStart ? 0 : FLAG_NOT_LIGATURE_GROUP_START);
return g;
}
// Assign a complex glyph value to an existing CompressedGlyph record,
// preserving line-break/char-type flags if present.
// This sets the glyphCount to zero; it will be updated when we call
// gfxShapedText::SetDetailedGlyphs.
CompressedGlyph& SetComplex(bool aClusterStart, bool aLigatureStart) {
mValue = (mValue & COMMON_FLAGS_MASK) | CharTypeFlags() |
MakeComplex(aClusterStart, aLigatureStart).mValue;
return *this;
}
/**
* Mark a glyph record as being a missing-glyph.
* Missing glyphs are treated as ligature group starts; don't mess with
* the cluster-start flag (see bugs 618870 and 619286).
* We also preserve the glyph count here, as this is used after any
* required DetailedGlyphs (to store the char code for a hexbox) has been
* set up.
* This must be called *after* SetDetailedGlyphs is used for the relevant
* offset in the shaped-word, because that will mark it as not-missing.
*/
CompressedGlyph& SetMissing() {
MOZ_ASSERT(!IsSimpleGlyph());
mValue &= ~(FLAG_NOT_MISSING | FLAG_NOT_LIGATURE_GROUP_START);
return *this;
}
uint32_t GetGlyphCount() const {
MOZ_ASSERT(!IsSimpleGlyph());
return mValue & GLYPH_COUNT_MASK;
}
void SetGlyphCount(uint32_t aGlyphCount) {
MOZ_ASSERT(!IsSimpleGlyph());
MOZ_ASSERT(GetGlyphCount() == 0, "Glyph count already set");
MOZ_ASSERT(aGlyphCount <= 0xffff, "Glyph count out of range");
mValue |= FLAG_NOT_MISSING | aGlyphCount;
}
void SetIsSpace() { mValue |= FLAG_CHAR_IS_SPACE; }
void SetIsTab() {
MOZ_ASSERT(!IsSimpleGlyph());
mValue |= FLAG_CHAR_IS_TAB;
}
void SetIsNewline() {
MOZ_ASSERT(!IsSimpleGlyph());
mValue |= FLAG_CHAR_IS_NEWLINE;
}
void SetNoEmphasisMark() {
MOZ_ASSERT(!IsSimpleGlyph());
mValue |= FLAG_CHAR_NO_EMPHASIS_MARK;
}
void SetIsFormattingControl() {
MOZ_ASSERT(!IsSimpleGlyph());
mValue |= FLAG_CHAR_IS_FORMATTING_CONTROL;
}
private:
uint32_t mValue;
};
// Accessor for the array of CompressedGlyph records, which will be in
// a different place in gfxShapedWord vs gfxTextRun
virtual const CompressedGlyph* GetCharacterGlyphs() const = 0;
virtual CompressedGlyph* GetCharacterGlyphs() = 0;
/**
* When the glyphs for a character don't fit into a CompressedGlyph record
* in SimpleGlyph format, we use an array of DetailedGlyphs instead.
*/
struct DetailedGlyph {
// The glyphID, or the Unicode character if this is a missing glyph
uint32_t mGlyphID;
// The advance of the glyph, in appunits.
// mAdvance is in the text direction (RTL or LTR),
// and will normally be non-negative (although this is not guaranteed)
int32_t mAdvance;
// The offset from the glyph's default position, in line-relative
// coordinates (so mOffset.x is an offset in the line-right direction,
// and mOffset.y is an offset in line-downwards direction).
// These values are in floating-point appUnits.
mozilla::gfx::Point mOffset;
};
// Store DetailedGlyph records for the given index. (This does not modify
// the associated CompressedGlyph character-type or break flags.)
void SetDetailedGlyphs(uint32_t aIndex, uint32_t aGlyphCount,
const DetailedGlyph* aGlyphs);
void SetMissingGlyph(uint32_t aIndex, uint32_t aChar, gfxFont* aFont);
void SetIsSpace(uint32_t aIndex) {
GetCharacterGlyphs()[aIndex].SetIsSpace();
}
bool HasDetailedGlyphs() const { return mDetailedGlyphs != nullptr; }
bool IsLigatureGroupStart(uint32_t aPos) {
NS_ASSERTION(aPos < GetLength(), "aPos out of range");
return GetCharacterGlyphs()[aPos].IsLigatureGroupStart();
}
// NOTE that this must not be called for a character offset that does
// not have any DetailedGlyph records; callers must have verified that
// GetCharacterGlyphs()[aCharIndex].GetGlyphCount() is greater than zero.
DetailedGlyph* GetDetailedGlyphs(uint32_t aCharIndex) const {
NS_ASSERTION(GetCharacterGlyphs() && HasDetailedGlyphs() &&
!GetCharacterGlyphs()[aCharIndex].IsSimpleGlyph() &&
GetCharacterGlyphs()[aCharIndex].GetGlyphCount() > 0,
"invalid use of GetDetailedGlyphs; check the caller!");
return mDetailedGlyphs->Get(aCharIndex);
}
void ApplyTrackingToClusters(gfxFloat aTrackingAdjustment, uint32_t aOffset,
uint32_t aLength);
// Mark clusters in the CompressedGlyph records, starting at aOffset,
// based on the Unicode properties of the text in aString.
// This is also responsible to set the IsSpace flag for space characters.
void SetupClusterBoundaries(uint32_t aOffset, const char16_t* aString,
uint32_t aLength);
// In 8-bit text, there won't actually be any clusters, but we still need
// the space-marking functionality.
void SetupClusterBoundaries(uint32_t aOffset, const uint8_t* aString,
uint32_t aLength);
mozilla::gfx::ShapedTextFlags GetFlags() const { return mFlags; }
bool IsVertical() const {
return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) !=
mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL;
}
bool UseCenterBaseline() const {
mozilla::gfx::ShapedTextFlags orient =
GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK;
return orient ==
mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED ||
orient ==
mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
}
bool IsRightToLeft() const {
return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_IS_RTL) ==
mozilla::gfx::ShapedTextFlags::TEXT_IS_RTL;
}
bool IsSidewaysLeft() const {
return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_MASK) ==
mozilla::gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT;
}
// Return true if the logical inline direction is reversed compared to
// normal physical coordinates (i.e. if it is leftwards or upwards)
bool IsInlineReversed() const { return IsSidewaysLeft() != IsRightToLeft(); }
gfxFloat GetDirection() const { return IsInlineReversed() ? -1.0f : 1.0f; }
bool DisableLigatures() const {
return (GetFlags() &
mozilla::gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES) ==
mozilla::gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES;
}
bool TextIs8Bit() const {
return (GetFlags() & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT) ==
mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT;
}
int32_t GetAppUnitsPerDevUnit() const { return mAppUnitsPerDevUnit; }
uint32_t GetLength() const { return mLength; }
bool FilterIfIgnorable(uint32_t aIndex, uint32_t aCh);
protected:
// Allocate aCount DetailedGlyphs for the given index
DetailedGlyph* AllocateDetailedGlyphs(uint32_t aCharIndex, uint32_t aCount);
// Ensure the glyph on the given index is complex glyph so that we can use
// it to record specific characters that layout may need to detect.
void EnsureComplexGlyph(uint32_t aIndex, CompressedGlyph& aGlyph) {
MOZ_ASSERT(GetCharacterGlyphs() + aIndex == &aGlyph);
if (aGlyph.IsSimpleGlyph()) {
DetailedGlyph details = {aGlyph.GetSimpleGlyph(),
(int32_t)aGlyph.GetSimpleAdvance(),
mozilla::gfx::Point()};
aGlyph.SetComplex(true, true);
SetDetailedGlyphs(aIndex, 1, &details);
}
}
// For characters whose glyph data does not fit the "simple" glyph criteria
// in CompressedGlyph, we use a sorted array to store the association
// between the source character offset and an index into an array
// DetailedGlyphs. The CompressedGlyph record includes a count of
// the number of DetailedGlyph records that belong to the character,
// starting at the given index.
class DetailedGlyphStore {
public:
DetailedGlyphStore() = default;
// This is optimized for the most common calling patterns:
// we rarely need random access to the records, access is most commonly
// sequential through the textRun, so we record the last-used index
// and check whether the caller wants the same record again, or the
// next; if not, it's most likely we're starting over from the start
// of the run, so we check the first entry before resorting to binary
// search as a last resort.
// NOTE that this must not be called for a character offset that does
// not have any DetailedGlyph records; callers must have verified that
// mCharacterGlyphs[aOffset].GetGlyphCount() is greater than zero
// before calling this, otherwise the assertions here will fire (in a
// debug build), and we'll probably crash.
DetailedGlyph* Get(uint32_t aOffset) {
NS_ASSERTION(mOffsetToIndex.Length() > 0, "no detailed glyph records!");
DetailedGlyph* details = mDetails.Elements();
// check common cases (fwd iteration, initial entry, etc) first
if (mLastUsed < mOffsetToIndex.Length() - 1 &&
aOffset == mOffsetToIndex[mLastUsed + 1].mOffset) {
++mLastUsed;
} else if (aOffset == mOffsetToIndex[0].mOffset) {
mLastUsed = 0;
} else if (aOffset == mOffsetToIndex[mLastUsed].mOffset) {
// do nothing
} else if (mLastUsed > 0 &&
aOffset == mOffsetToIndex[mLastUsed - 1].mOffset) {
--mLastUsed;
} else {
mLastUsed = mOffsetToIndex.BinaryIndexOf(aOffset, CompareToOffset());
}
NS_ASSERTION(mLastUsed != nsTArray<DGRec>::NoIndex,
"detailed glyph record missing!");
return details + mOffsetToIndex[mLastUsed].mIndex;
}
DetailedGlyph* Allocate(uint32_t aOffset, uint32_t aCount) {
uint32_t detailIndex = mDetails.Length();
DetailedGlyph* details = mDetails.AppendElements(aCount);
// We normally set up glyph records sequentially, so the common case
// here is to append new records to the mOffsetToIndex array;
// test for that before falling back to the InsertElementSorted
// method.
if (mOffsetToIndex.Length() == 0 ||
aOffset > mOffsetToIndex[mOffsetToIndex.Length() - 1].mOffset) {
mOffsetToIndex.AppendElement(DGRec(aOffset, detailIndex));
} else {
mOffsetToIndex.InsertElementSorted(DGRec(aOffset, detailIndex),
CompareRecordOffsets());
}
return details;
}
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) {
return aMallocSizeOf(this) +
mDetails.ShallowSizeOfExcludingThis(aMallocSizeOf) +
mOffsetToIndex.ShallowSizeOfExcludingThis(aMallocSizeOf);
}
private:
struct DGRec {
DGRec(const uint32_t& aOffset, const uint32_t& aIndex)
: mOffset(aOffset), mIndex(aIndex) {}
uint32_t mOffset; // source character offset in the textrun
uint32_t mIndex; // index where this char's DetailedGlyphs begin
};
struct CompareToOffset {
bool Equals(const DGRec& a, const uint32_t& b) const {
return a.mOffset == b;
}
bool LessThan(const DGRec& a, const uint32_t& b) const {
return a.mOffset < b;
}
};
struct CompareRecordOffsets {
bool Equals(const DGRec& a, const DGRec& b) const {
return a.mOffset == b.mOffset;
}
bool LessThan(const DGRec& a, const DGRec& b) const {
return a.mOffset < b.mOffset;
}
};
// Concatenated array of all the DetailedGlyph records needed for the
// textRun; individual character offsets are associated with indexes
// into this array via the mOffsetToIndex table.
nsTArray<DetailedGlyph> mDetails;
// For each character offset that needs DetailedGlyphs, we record the
// index in mDetails where the list of glyphs begins. This array is
// sorted by mOffset.
nsTArray<DGRec> mOffsetToIndex;
// Records the most recently used index into mOffsetToIndex, so that
// we can support sequential access more quickly than just doing
// a binary search each time.
nsTArray<DGRec>::index_type mLastUsed = 0;
};
mozilla::UniquePtr<DetailedGlyphStore> mDetailedGlyphs;
// Number of char16_t characters and CompressedGlyph glyph records
uint32_t mLength;
// Shaping flags (direction, ligature-suppression)
mozilla::gfx::ShapedTextFlags mFlags;
uint16_t mAppUnitsPerDevUnit;
};
/*
* gfxShapedWord: an individual (space-delimited) run of text shaped with a
* particular font, without regard to external context.
*
* The glyph data is copied into gfxTextRuns as needed from the cache of
* ShapedWords associated with each gfxFont instance.
*/
class gfxShapedWord final : public gfxShapedText {
public:
typedef mozilla::intl::Script Script;
// Create a ShapedWord that can hold glyphs for aLength characters,
// with mCharacterGlyphs sized appropriately.
//
// Returns null on allocation failure (does NOT use infallible alloc)
// so caller must check for success.
//
// This does NOT perform shaping, so the returned word contains no
// glyph data; the caller must call gfxFont::ShapeText() with appropriate
// parameters to set up the glyphs.
static gfxShapedWord* Create(const uint8_t* aText, uint32_t aLength,
Script aRunScript, nsAtom* aLanguage,
uint16_t aAppUnitsPerDevUnit,
mozilla::gfx::ShapedTextFlags aFlags,
gfxFontShaper::RoundingFlags aRounding) {
NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(),
"excessive length for gfxShapedWord!");
// Compute size needed including the mCharacterGlyphs array
// and a copy of the original text
uint32_t size = offsetof(gfxShapedWord, mCharGlyphsStorage) +
aLength * (sizeof(CompressedGlyph) + sizeof(uint8_t));
void* storage = malloc(size);
if (!storage) {
return nullptr;
}
// Construct in the pre-allocated storage, using placement new
return new (storage) gfxShapedWord(aText, aLength, aRunScript, aLanguage,
aAppUnitsPerDevUnit, aFlags, aRounding);
}
static gfxShapedWord* Create(const char16_t* aText, uint32_t aLength,
Script aRunScript, nsAtom* aLanguage,
uint16_t aAppUnitsPerDevUnit,
mozilla::gfx::ShapedTextFlags aFlags,
gfxFontShaper::RoundingFlags aRounding) {
NS_ASSERTION(aLength <= gfxPlatform::GetPlatform()->WordCacheCharLimit(),
"excessive length for gfxShapedWord!");
// In the 16-bit version of Create, if the TEXT_IS_8BIT flag is set,
// then we convert the text to an 8-bit version and call the 8-bit
// Create function instead.
if (aFlags & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT) {
nsAutoCString narrowText;
LossyAppendUTF16toASCII(nsDependentSubstring(aText, aLength), narrowText);
return Create((const uint8_t*)(narrowText.BeginReading()), aLength,
aRunScript, aLanguage, aAppUnitsPerDevUnit, aFlags,
aRounding);
}
uint32_t size = offsetof(gfxShapedWord, mCharGlyphsStorage) +
aLength * (sizeof(CompressedGlyph) + sizeof(char16_t));
void* storage = malloc(size);
if (!storage) {
return nullptr;
}
return new (storage) gfxShapedWord(aText, aLength, aRunScript, aLanguage,
aAppUnitsPerDevUnit, aFlags, aRounding);
}
// Override operator delete to properly free the object that was
// allocated via malloc.
void operator delete(void* p) { free(p); }
const CompressedGlyph* GetCharacterGlyphs() const override {
return &mCharGlyphsStorage[0];
}
CompressedGlyph* GetCharacterGlyphs() override {
return &mCharGlyphsStorage[0];
}
const uint8_t* Text8Bit() const {
NS_ASSERTION(TextIs8Bit(), "invalid use of Text8Bit()");
return reinterpret_cast<const uint8_t*>(mCharGlyphsStorage + GetLength());
}
const char16_t* TextUnicode() const {
NS_ASSERTION(!TextIs8Bit(), "invalid use of TextUnicode()");
return reinterpret_cast<const char16_t*>(mCharGlyphsStorage + GetLength());
}
char16_t GetCharAt(uint32_t aOffset) const {
NS_ASSERTION(aOffset < GetLength(), "aOffset out of range");
return TextIs8Bit() ? char16_t(Text8Bit()[aOffset])
: TextUnicode()[aOffset];
}
Script GetScript() const { return mScript; }
nsAtom* GetLanguage() const { return mLanguage.get(); }
gfxFontShaper::RoundingFlags GetRounding() const { return mRounding; }
void ResetAge() { mAgeCounter = 0; }
uint32_t IncrementAge() { return ++mAgeCounter; }
// Helper used when hashing a word for the shaped-word caches
static uint32_t HashMix(uint32_t aHash, char16_t aCh) {
return (aHash >> 28) ^ (aHash << 4) ^ aCh;
}
size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
private:
// so that gfxTextRun can share our DetailedGlyphStore class
friend class gfxTextRun;
// Construct storage for a ShapedWord, ready to receive glyph data
gfxShapedWord(const uint8_t* aText, uint32_t aLength, Script aRunScript,
nsAtom* aLanguage, uint16_t aAppUnitsPerDevUnit,
mozilla::gfx::ShapedTextFlags aFlags,
gfxFontShaper::RoundingFlags aRounding)
: gfxShapedText(aLength,
aFlags | mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT,
aAppUnitsPerDevUnit),
mLanguage(aLanguage),
mScript(aRunScript),
mRounding(aRounding),
mAgeCounter(0) {
memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph));
uint8_t* text = reinterpret_cast<uint8_t*>(&mCharGlyphsStorage[aLength]);
memcpy(text, aText, aLength * sizeof(uint8_t));
SetupClusterBoundaries(0, aText, aLength);
}
gfxShapedWord(const char16_t* aText, uint32_t aLength, Script aRunScript,
nsAtom* aLanguage, uint16_t aAppUnitsPerDevUnit,
mozilla::gfx::ShapedTextFlags aFlags,
gfxFontShaper::RoundingFlags aRounding)
: gfxShapedText(aLength, aFlags, aAppUnitsPerDevUnit),
mLanguage(aLanguage),
mScript(aRunScript),
mRounding(aRounding),
mAgeCounter(0) {
memset(mCharGlyphsStorage, 0, aLength * sizeof(CompressedGlyph));
char16_t* text = reinterpret_cast<char16_t*>(&mCharGlyphsStorage[aLength]);
memcpy(text, aText, aLength * sizeof(char16_t));
SetupClusterBoundaries(0, aText, aLength);
}
RefPtr<nsAtom> mLanguage;
Script mScript;
gfxFontShaper::RoundingFlags mRounding;
// With multithreaded shaping, this may be updated by any thread.
std::atomic<uint32_t> mAgeCounter;
// The mCharGlyphsStorage array is actually a variable-size member;
// when the ShapedWord is created, its size will be increased as necessary
// to allow the proper number of glyphs to be stored.
// The original text, in either 8-bit or 16-bit form, will be stored
// immediately following the CompressedGlyphs.
CompressedGlyph mCharGlyphsStorage[1];
};
class GlyphBufferAzure;
struct TextRunDrawParams;
struct FontDrawParams;
struct EmphasisMarkDrawParams;
class gfxFont {
friend class gfxHarfBuzzShaper;
friend class gfxGraphiteShaper;
protected:
using DrawTarget = mozilla::gfx::DrawTarget;
using Script = mozilla::intl::Script;
using SVGContextPaint = mozilla::SVGContextPaint;
using RoundingFlags = gfxFontShaper::RoundingFlags;
using ShapedTextFlags = mozilla::gfx::ShapedTextFlags;
public:
using FontSlantStyle = mozilla::FontSlantStyle;
using FontSizeAdjust = mozilla::StyleFontSizeAdjust;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DESTROY(gfxFont, MaybeDestroy())
int32_t GetRefCount() { return int32_t(mRefCnt); }
// options to specify the kind of AA to be used when creating a font
typedef enum : uint8_t {
kAntialiasDefault,
kAntialiasNone,
kAntialiasGrayscale,
kAntialiasSubpixel
} AntialiasOption;
protected:
gfxFont(const RefPtr<mozilla::gfx::UnscaledFont>& aUnscaledFont,
gfxFontEntry* aFontEntry, const gfxFontStyle* aFontStyle,
AntialiasOption anAAOption = kAntialiasDefault);
virtual ~gfxFont();
void MaybeDestroy() {
bool destroy = true;
if (gfxFontCache* fc = gfxFontCache::GetCache()) {
destroy = fc->MaybeDestroy(this);
}
if (destroy) {
Destroy();
}
}
public:
void Destroy() {
MOZ_ASSERT(GetRefCount() == 0);
delete this;
}
bool Valid() const { return mIsValid; }
// options for the kind of bounding box to return from measurement
typedef enum {
LOOSE_INK_EXTENTS,
// A box that encloses all the painted pixels, and may
// include sidebearings and/or additional ascent/descent
// within the glyph cell even if the ink is smaller.
TIGHT_INK_EXTENTS,
// A box that tightly encloses all the painted pixels
// (although actually on Windows, at least, it may be
// slightly larger than strictly necessary because
// we can't get precise extents with ClearType).
TIGHT_HINTED_OUTLINE_EXTENTS
// A box that tightly encloses the glyph outline,
// ignoring possible antialiasing pixels that extend
// beyond this.
// NOTE: The default implementation of gfxFont::Measure(),
// which works with the glyph extents cache, does not
// differentiate between this and TIGHT_INK_EXTENTS.
// Whether the distinction is important depends on the
// antialiasing behavior of the platform; currently the
// distinction is only implemented in the gfxWindowsFont
// subclass, because of ClearType's tendency to paint
// outside the hinted outline.
// Also NOTE: it is relatively expensive to request this,
// as it does not use cached glyph extents in the font.
} BoundingBoxType;
const nsCString& GetName() const { return mFontEntry->Name(); }
const gfxFontStyle* GetStyle() const { return &mStyle; }
virtual gfxFont* CopyWithAntialiasOption(AntialiasOption anAAOption) const {
// platforms where this actually matters should override
return nullptr;
}
gfxFloat GetAdjustedSize() const {
// mAdjustedSize is cached here if not already set to a non-zero value;
// but it may be overridden by a value computed in metrics initialization
// from font-size-adjust.
if (mAdjustedSize < 0.0) {
mAdjustedSize = mStyle.AdjustedSizeMustBeZero()
? 0.0
: mStyle.size * mFontEntry->mSizeAdjust;
}
return mAdjustedSize;
}
float FUnitsToDevUnitsFactor() const {
// check this was set up during font initialization
NS_ASSERTION(mFUnitsConvFactor >= 0.0f, "mFUnitsConvFactor not valid");
return mFUnitsConvFactor;
}
// check whether this is an sfnt we can potentially use with harfbuzz
bool FontCanSupportHarfBuzz() const { return mFontEntry->HasCmapTable(); }
// check whether this is an sfnt we can potentially use with Graphite
bool FontCanSupportGraphite() const {
return mFontEntry->HasGraphiteTables();
}
// Whether this is a font that may be doing full-color rendering,
// and therefore needs us to use a mask for text-shadow even when
// we're not actually blurring.
bool AlwaysNeedsMaskForShadow() const {
return mFontEntry->TryGetColorGlyphs() || mFontEntry->TryGetSVGData(this) ||
mFontEntry->HasFontTable(TRUETYPE_TAG('C', 'B', 'D', 'T')) ||
mFontEntry->HasFontTable(TRUETYPE_TAG('s', 'b', 'i', 'x'));
}
// whether a feature is supported by the font (limited to a small set
// of features for which some form of fallback needs to be implemented)
bool SupportsFeature(Script aScript, uint32_t aFeatureTag);
// whether the font supports "real" small caps, petite caps etc.
// aFallbackToSmallCaps true when petite caps should fallback to small caps
bool SupportsVariantCaps(Script aScript, uint32_t aVariantCaps,
bool& aFallbackToSmallCaps,
bool& aSyntheticLowerToSmallCaps,
bool& aSyntheticUpperToSmallCaps);
// whether the font supports subscript/superscript feature
// for fallback, need to verify that all characters in the run
// have variant substitutions
bool SupportsSubSuperscript(uint32_t aSubSuperscript, const uint8_t* aString,
uint32_t aLength, Script aRunScript);
bool SupportsSubSuperscript(uint32_t aSubSuperscript, const char16_t* aString,
uint32_t aLength, Script aRunScript);
// whether the specified feature will apply to the given character
bool FeatureWillHandleChar(Script aRunScript, uint32_t aFeature,
uint32_t aUnicode);
// Subclasses may choose to look up glyph ids for characters.
// If they do not override this, gfxHarfBuzzShaper will fetch the cmap
// table and use that.
virtual bool ProvidesGetGlyph() const { return false; }
// Map unicode character to glyph ID.
// Only used if ProvidesGetGlyph() returns true.
virtual uint32_t GetGlyph(uint32_t unicode, uint32_t variation_selector) {
return 0;
}
// Return the advance of a glyph.
gfxFloat GetGlyphAdvance(uint16_t aGID, bool aVertical = false);
// Return the advance of a given Unicode char in isolation.
// Returns -1.0 if the char is not supported.
gfxFloat GetCharAdvance(uint32_t aUnicode, bool aVertical = false);
gfxFloat SynthesizeSpaceWidth(uint32_t aCh);
// Work out whether cairo will snap inter-glyph spacing to pixels
// when rendering to the given drawTarget.
RoundingFlags GetRoundOffsetsToPixels(DrawTarget* aDrawTarget);
virtual bool ShouldHintMetrics() const { return true; }
virtual bool ShouldRoundXOffset(cairo_t* aCairo) const { return true; }
// Return the font's owned harfbuzz shaper, creating and initializing it if
// necessary; returns null if shaper initialization has failed.
gfxHarfBuzzShaper* GetHarfBuzzShaper();
// Font metrics
struct Metrics {
gfxFloat capHeight;
gfxFloat xHeight;
gfxFloat strikeoutSize;
gfxFloat strikeoutOffset;
gfxFloat underlineSize;
gfxFloat underlineOffset;
gfxFloat internalLeading;
gfxFloat externalLeading;
gfxFloat emHeight;
gfxFloat emAscent;
gfxFloat emDescent;
gfxFloat maxHeight;
gfxFloat maxAscent;
gfxFloat maxDescent;
gfxFloat maxAdvance;
gfxFloat aveCharWidth;
gfxFloat spaceWidth;
gfxFloat zeroWidth; // -1 if there was no zero glyph
gfxFloat ideographicWidth; // -1 if kWaterIdeograph is not supported
gfxFloat ZeroOrAveCharWidth() const {
return zeroWidth >= 0 ? zeroWidth : aveCharWidth;
}
};
// Unicode character used as basis for 'ic' unit:
static constexpr uint32_t kWaterIdeograph = 0x6C34;
typedef nsFontMetrics::FontOrientation Orientation;
const Metrics& GetMetrics(Orientation aOrientation) {
if (aOrientation == nsFontMetrics::eHorizontal) {
return GetHorizontalMetrics();
}
if (!mVerticalMetrics) {
CreateVerticalMetrics();
}
return *mVerticalMetrics;
}
struct Baselines {
gfxFloat mAlphabetic;
gfxFloat mHanging;
gfxFloat mIdeographic;
};
Baselines GetBaselines(Orientation aOrientation);
/**
* We let layout specify spacing on either side of any
* character. We need to specify both before and after
* spacing so that substring measurement can do the right things.
* These values are in appunits. They're always an integral number of
* appunits, but we specify them in floats in case very large spacing
* values are required.
*/
struct Spacing {
gfxFloat mBefore;
gfxFloat mAfter;
};
/**
* Metrics for a particular string
*/
struct RunMetrics {
RunMetrics() { mAdvanceWidth = mAscent = mDescent = 0.0; }
void CombineWith(const RunMetrics& aOther, bool aOtherIsOnLeft);
// can be negative (partly due to negative spacing).
// Advance widths should be additive: the advance width of the
// (offset1, length1) plus the advance width of (offset1 + length1,
// length2) should be the advance width of (offset1, length1 + length2)
gfxFloat mAdvanceWidth;
// For zero-width substrings, these must be zero!
gfxFloat mAscent; // always non-negative
gfxFloat mDescent; // always non-negative
// Bounding box that is guaranteed to include everything drawn.
// If a tight boundingBox was requested when these metrics were
// generated, this will tightly wrap the glyphs, otherwise it is
// "loose" and may be larger than the true bounding box.
// Coordinates are relative to the baseline left origin, so typically
// mBoundingBox.y == -mAscent
gfxRect mBoundingBox;
};
/**
* Draw a series of glyphs to aContext. The direction of aTextRun must
* be honoured.
* @param aStart the first character to draw
* @param aEnd draw characters up to here
* @param aPt the baseline origin; the left end of the baseline
* for LTR textruns, the right end for RTL textruns.
* On return, this will be updated to the other end of the baseline.
* In application units, really!
* @param aRunParams record with drawing parameters, see TextRunDrawParams.
* Particular fields of interest include
* .spacing spacing to insert before and after characters (for RTL
* glyphs, before-spacing is inserted to the right of characters). There
* are aEnd - aStart elements in this array, unless it's null to indicate
* that there is no spacing.
* .drawMode specifies whether the fill or stroke of the glyph should be
* drawn, or if it should be drawn into the current path
* .contextPaint information about how to construct the fill and
* stroke pattern. Can be nullptr if we are not stroking the text, which
* indicates that the current source from context should be used for fill
* .context the Thebes graphics context to which we're drawing
* .dt Moz2D DrawTarget to which we're drawing
*
* Callers guarantee:
* -- aStart and aEnd are aligned to cluster and ligature boundaries
* -- all glyphs use this font
*/
void Draw(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
mozilla::gfx::Point* aPt, TextRunDrawParams& aRunParams,
mozilla::gfx::ShapedTextFlags aOrientation);
/**
* Draw the emphasis marks for the given text run. Its prerequisite
* and output are similiar to the method Draw().
* @param aPt the baseline origin of the emphasis marks.
* @param aParams some drawing parameters, see EmphasisMarkDrawParams.
*/
void DrawEmphasisMarks(const gfxTextRun* aShapedText,
mozilla::gfx::Point* aPt, uint32_t aOffset,
uint32_t aCount,
const EmphasisMarkDrawParams& aParams);
/**
* Measure a run of characters. See gfxTextRun::Metrics.
* @param aTight if false, then return the union of the glyph extents
* with the font-box for the characters (the rectangle with x=0,width=
* the advance width for the character run,y=-(font ascent), and height=
* font ascent + font descent). Otherwise, we must return as tight as possible
* an approximation to the area actually painted by glyphs.
* @param aDrawTargetForTightBoundingBox when aTight is true, this must
* be non-null.
* @param aSpacing spacing to insert before and after glyphs. The bounding box
* need not include the spacing itself, but the spacing affects the glyph
* positions. null if there is no spacing.
*
* Callers guarantee:
* -- aStart and aEnd are aligned to cluster and ligature boundaries
* -- all glyphs use this font
*
* The default implementation just uses font metrics and aTextRun's
* advances, and assumes no characters fall outside the font box. In
* general this is insufficient, because that assumption is not always true.
*/
virtual RunMetrics Measure(const gfxTextRun* aTextRun, uint32_t aStart,
uint32_t aEnd, BoundingBoxType aBoundingBoxType,
DrawTarget* aDrawTargetForTightBoundingBox,
Spacing* aSpacing,
mozilla::gfx::ShapedTextFlags aOrientation);
/**
* Line breaks have been changed at the beginning and/or end of a substring
* of the text. Reshaping may be required; glyph updating is permitted.
* @return true if anything was changed, false otherwise
*/
bool NotifyLineBreaksChanged(gfxTextRun* aTextRun, uint32_t aStart,
uint32_t aLength) {
return false;
}
// Expiration tracking
nsExpirationState* GetExpirationState() { return &mExpirationState; }
// Get the glyphID of a space
uint16_t GetSpaceGlyph() const { return mSpaceGlyph; }
gfxGlyphExtents* GetOrCreateGlyphExtents(int32_t aAppUnitsPerDevUnit);
void SetupGlyphExtents(DrawTarget* aDrawTarget, uint32_t aGlyphID,
bool aNeedTight, gfxGlyphExtents* aExtents);
virtual bool AllowSubpixelAA() const { return true; }
bool ApplySyntheticBold() const { return mApplySyntheticBold; }
float AngleForSyntheticOblique() const;
float SkewForSyntheticOblique() const;
// Amount by which synthetic bold "fattens" the glyphs:
// For size S up to a threshold size T, we use (0.25 + 3S / 4T),
// so that the result ranges from 0.25 to 1.0; thereafter,
// simply use (S / T).
gfxFloat GetSyntheticBoldOffset() const {
gfxFloat size = GetAdjustedSize();
const gfxFloat threshold = 48.0;
return size < threshold ? (0.25 + 0.75 * size / threshold)
: (size / threshold);
}
gfxFontEntry* GetFontEntry() const { return mFontEntry.get(); }
bool HasCharacter(uint32_t ch) const {
if (!mIsValid || (mUnicodeRangeMap && !mUnicodeRangeMap->test(ch))) {
return false;
}
return mFontEntry->HasCharacter(ch);
}
const gfxCharacterMap* GetUnicodeRangeMap() const {
return mUnicodeRangeMap.get();
}
void SetUnicodeRangeMap(gfxCharacterMap* aUnicodeRangeMap) {
mUnicodeRangeMap = aUnicodeRangeMap;
}
uint16_t GetUVSGlyph(uint32_t aCh, uint32_t aVS) const {
if (!mIsValid) {
return 0;
}
return mFontEntry->GetUVSGlyph(aCh, aVS);
}
template <typename T>
bool InitFakeSmallCapsRun(nsPresContext* aPresContext,
DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
const T* aText, uint32_t aOffset, uint32_t aLength,
FontMatchType aMatchType,
mozilla::gfx::ShapedTextFlags aOrientation,
Script aScript, nsAtom* aLanguage,
bool aSyntheticLower, bool aSyntheticUpper);
// call the (virtual) InitTextRun method to do glyph generation/shaping,
// limiting the length of text passed by processing the run in multiple
// segments if necessary
template <typename T>
bool SplitAndInitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun,
const T* aString, uint32_t aRunStart,
uint32_t aRunLength, Script aRunScript,
nsAtom* aLanguage,
mozilla::gfx::ShapedTextFlags aOrientation);
// Get a ShapedWord representing a single space for use in setting up a
// gfxTextRun.
bool ProcessSingleSpaceShapedWord(
DrawTarget* aDrawTarget, bool aVertical, int32_t aAppUnitsPerDevUnit,
mozilla::gfx::ShapedTextFlags aFlags, RoundingFlags aRounding,
const std::function<void(gfxShapedWord*)>& aCallback);
// Called by the gfxFontCache timer to increment the age of all the words,
// so that they'll expire after a sufficient period of non-use.
// Returns true if the cache is now empty, otherwise false.
bool AgeCachedWords();
// Discard all cached word records; called on memory-pressure notification.
void ClearCachedWords() {
mozilla::AutoWriteLock lock(mLock);
if (mWordCache) {
ClearCachedWordsLocked();
}
}
void ClearCachedWordsLocked() MOZ_REQUIRES(mLock) {
MOZ_ASSERT(mWordCache);
mWordCache->clear();
}
// Glyph rendering/geometry has changed, so invalidate data as necessary.
void NotifyGlyphsChanged() const;
virtual void AddSizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf,
FontCacheSizes* aSizes) const;
virtual void AddSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf,
FontCacheSizes* aSizes) const;
typedef enum {
FONT_TYPE_DWRITE,
FONT_TYPE_GDI,
FONT_TYPE_FT2,
FONT_TYPE_MAC,
FONT_TYPE_OS2,
FONT_TYPE_CAIRO,
FONT_TYPE_FONTCONFIG
} FontType;
virtual FontType GetType() const = 0;
const RefPtr<mozilla::gfx::UnscaledFont>& GetUnscaledFont() const {
return mUnscaledFont;
}
virtual already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
const TextRunDrawParams& aRunParams) = 0;
already_AddRefed<mozilla::gfx::ScaledFont> GetScaledFont(
mozilla::gfx::DrawTarget* aDrawTarget);
// gfxFont implementations may cache ScaledFont versions other than the
// default, so InitializeScaledFont must support explicitly specifying
// which ScaledFonts to initialize.
void InitializeScaledFont(
const RefPtr<mozilla::gfx::ScaledFont>& aScaledFont);
bool KerningDisabled() const { return mKerningSet && !mKerningEnabled; }
/**
* Subclass this object to be notified of glyph changes. Delete the object
* when no longer needed.
*/
class GlyphChangeObserver {
public:
virtual ~GlyphChangeObserver() {
if (mFont) {
mFont->RemoveGlyphChangeObserver(this);
}
}
// This gets called when the gfxFont dies.
void ForgetFont() { mFont = nullptr; }
virtual void NotifyGlyphsChanged() = 0;
protected:
explicit GlyphChangeObserver(gfxFont* aFont) : mFont(aFont) {
mFont->AddGlyphChangeObserver(this);
}
// This pointer is nulled by ForgetFont in the gfxFont's
// destructor. Before the gfxFont dies.
gfxFont* MOZ_NON_OWNING_REF mFont;
};
friend class GlyphChangeObserver;
bool GlyphsMayChange() const {
// Currently only fonts with SVG glyphs can have animated glyphs
return mFontEntry->TryGetSVGData(this);
}
static void DestroySingletons() {
delete sScriptTagToCode;
delete sDefaultFeatures;
}
// Call TryGetMathTable() to try and load the Open Type MATH table.
// If (and ONLY if) TryGetMathTable() has returned true, the MathTable()
// method may be called to access the gfxMathTable data.
bool TryGetMathTable();
gfxMathTable* MathTable() const {
MOZ_RELEASE_ASSERT(mMathTable,
"A successful call to TryGetMathTable() must be "
"performed before calling this function");
return mMathTable;
}
// Return a cloned font resized and offset to simulate sub/superscript
// glyphs. This does not add a reference to the returned font.
already_AddRefed<gfxFont> GetSubSuperscriptFont(
int32_t aAppUnitsPerDevPixel) const;
bool HasColorGlyphFor(uint32_t aCh, uint32_t aNextCh);
protected:
virtual const Metrics& GetHorizontalMetrics() const = 0;
void CreateVerticalMetrics();
bool MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
BoundingBoxType aBoundingBoxType,
DrawTarget* aRefDrawTarget, Spacing* aSpacing,
gfxGlyphExtents* aExtents, bool aIsRTL,
bool aNeedsGlyphExtents, RunMetrics& aMetrics,
gfxFloat* aAdvanceMin, gfxFloat* aAdvanceMax);
bool MeasureGlyphs(const gfxTextRun* aTextRun, uint32_t aStart, uint32_t aEnd,
BoundingBoxType aBoundingBoxType,
DrawTarget* aRefDrawTarget, Spacing* aSpacing, bool aIsRTL,
RunMetrics& aMetrics);
// Template parameters for DrawGlyphs/DrawOneGlyph, used to select
// simplified versions of the methods in the most common cases.
enum class FontComplexityT { SimpleFont, ComplexFont };
enum class SpacingT { NoSpacing, HasSpacing };
// Output a run of glyphs at *aPt, which is updated to follow the last glyph
// in the run. This method also takes account of any letter-spacing provided
// in aRunParams.
template <FontComplexityT FC, SpacingT S>
bool DrawGlyphs(const gfxShapedText* aShapedText,
uint32_t aOffset, // offset in the textrun
uint32_t aCount, // length of run to draw
mozilla::gfx::Point* aPt,
// transform for mOffset field in DetailedGlyph records,
// to account for rotations (may be null)
const mozilla::gfx::Matrix* aOffsetMatrix,
GlyphBufferAzure& aBuffer);
// Output a single glyph at *aPt.
// Normal glyphs are simply accumulated in aBuffer until it is full and
// gets flushed, but SVG or color-font glyphs will instead be rendered
// directly to the destination (found from the buffer's parameters).
template <FontComplexityT FC>
void DrawOneGlyph(uint32_t aGlyphID, const mozilla::gfx::Point& aPt,
GlyphBufferAzure& aBuffer, bool* aEmittedGlyphs);
// Helper for DrawOneGlyph to handle missing glyphs, rendering either
// nothing (for default-ignorables) or a missing-glyph hexbox.
bool DrawMissingGlyph(const TextRunDrawParams& aRunParams,
const FontDrawParams& aFontParams,
const gfxShapedText::DetailedGlyph* aDetails,
const mozilla::gfx::Point& aPt);
// set the font size and offset used for
// synthetic subscript/superscript glyphs
void CalculateSubSuperSizeAndOffset(int32_t aAppUnitsPerDevPixel,
gfxFloat& aSubSuperSizeRatio,
float& aBaselineOffset);
// Return a font that is a "clone" of this one, but reduced to 80% size
// (and with variantCaps set to normal). This does not add a reference to
// the returned font.
already_AddRefed<gfxFont> GetSmallCapsFont() const;
// subclasses may provide (possibly hinted) glyph widths (in font units);
// if they do not override this, harfbuzz will use unhinted widths
// derived from the font tables
virtual bool ProvidesGlyphWidths() const { return false; }
// The return value is interpreted as a horizontal advance in 16.16 fixed
// point format.
virtual int32_t GetGlyphWidth(uint16_t aGID) { return -1; }
virtual bool GetGlyphBounds(uint16_t aGID, gfxRect* aBounds,
bool aTight = false) {
return false;
}
bool IsSpaceGlyphInvisible(DrawTarget* aRefDrawTarget,
const gfxTextRun* aTextRun);
void AddGlyphChangeObserver(GlyphChangeObserver* aObserver);
void RemoveGlyphChangeObserver(GlyphChangeObserver* aObserver);
// whether font contains substitution lookups containing spaces
bool HasSubstitutionRulesWithSpaceLookups(Script aRunScript) const;
// do spaces participate in shaping rules? if so, can't used word cache
// Note that this function uses HasGraphiteSpaceContextuals, so it can only
// return a "hint" to the correct answer. The calling code must ensure it
// performs safe actions independent of the value returned.
tainted_boolean_hint SpaceMayParticipateInShaping(Script aRunScript) const;
// For 8-bit text, expand to 16-bit and then call the following method.
bool ShapeText(DrawTarget* aContext, const uint8_t* aText,
uint32_t aOffset, // dest offset in gfxShapedText
uint32_t aLength, Script aScript, nsAtom* aLanguage,
bool aVertical, RoundingFlags aRounding,
gfxShapedText* aShapedText); // where to store the result
// Call the appropriate shaper to generate glyphs for aText and store
// them into aShapedText.
virtual bool ShapeText(DrawTarget* aContext, const char16_t* aText,
uint32_t aOffset, uint32_t aLength, Script aScript,
nsAtom* aLanguage, bool aVertical,
RoundingFlags aRounding, gfxShapedText* aShapedText);
// Helper to adjust for synthetic bold and set character-type flags
// in the shaped text; implementations of ShapeText should call this
// after glyph shaping has been completed.
void PostShapingFixup(DrawTarget* aContext, const char16_t* aText,
uint32_t aOffset, // position within aShapedText
uint32_t aLength, bool aVertical,
gfxShapedText* aShapedText);
// Shape text directly into a range within a textrun, without using the
// font's word cache. Intended for use when the font has layout features
// that involve space, and therefore require shaping complete runs rather
// than isolated words, or for long strings that are inefficient to cache.
// This will split the text on "invalid" characters (tab/newline) that are
// not handled via normal shaping, but does not otherwise divide up the
// text.
template <typename T>
bool ShapeTextWithoutWordCache(DrawTarget* aDrawTarget, const T* aText,
uint32_t aOffset, uint32_t aLength,
Script aScript, nsAtom* aLanguage,
bool aVertical, RoundingFlags aRounding,
gfxTextRun* aTextRun);
// Shape a fragment of text (a run that is known to contain only
// "valid" characters, no newlines/tabs/other control chars).
// All non-wordcache shaping goes through here; this is the function
// that will ensure we don't pass excessively long runs to the various
// platform shapers.
template <typename T>
bool ShapeFragmentWithoutWordCache(DrawTarget* aDrawTarget, const T* aText,
uint32_t aOffset, uint32_t aLength,
Script aScript, nsAtom* aLanguage,
bool aVertical, RoundingFlags aRounding,
gfxTextRun* aTextRun);
void CheckForFeaturesInvolvingSpace() const;
// Get a ShapedWord representing the given text (either 8- or 16-bit)
// for use in setting up a gfxTextRun.
template <typename T, typename Func>
bool ProcessShapedWordInternal(DrawTarget* aDrawTarget, const T* aText,
uint32_t aLength, uint32_t aHash,
Script aRunScript, nsAtom* aLanguage,
bool aVertical, int32_t aAppUnitsPerDevUnit,
mozilla::gfx::ShapedTextFlags aFlags,
RoundingFlags aRounding,
gfxTextPerfMetrics* aTextPerf, Func aCallback);
// whether a given feature is included in feature settings from both the
// font and the style. aFeatureOn set if resolved feature value is non-zero
bool HasFeatureSet(uint32_t aFeature, bool& aFeatureOn);
// used when analyzing whether a font has space contextual lookups
static mozilla::Atomic<nsTHashMap<nsUint32HashKey, Script>*> sScriptTagToCode;
static mozilla::Atomic<nsTHashSet<uint32_t>*> sDefaultFeatures;
RefPtr<gfxFontEntry> mFontEntry;
mutable mozilla::RWLock mLock;
// Note that WordCacheKey contains a pointer to the text of the word, which
// must be valid for as long as the key is in use. When using for a Lookup,
// the string may be local/temporary, but when storing in the HashMap, we
// set the Key text pointer to reference the text in the associated
// gfxShapedWord that is being stored.
struct WordCacheKey {
union {
const uint8_t* mSingle;
const char16_t* mDouble;
} mText;
uint32_t mLength;
ShapedTextFlags mFlags;
Script mScript;
RefPtr<nsAtom> mLanguage;
int32_t mAppUnitsPerDevUnit;
PLDHashNumber mHashKey;
bool mTextIs8Bit;
RoundingFlags mRounding;
WordCacheKey(const uint8_t* aText, uint32_t aLength, uint32_t aStringHash,
Script aScriptCode, nsAtom* aLanguage,
int32_t aAppUnitsPerDevUnit, ShapedTextFlags aFlags,
RoundingFlags aRounding)
: mLength(aLength),
mFlags(aFlags),
mScript(aScriptCode),
mLanguage(aLanguage),
mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
mHashKey(aStringHash + static_cast<int32_t>(aScriptCode) +
aAppUnitsPerDevUnit * 0x100 + uint16_t(aFlags) * 0x10000 +
int(aRounding) + (aLanguage ? aLanguage->hash() : 0)),
mTextIs8Bit(true),
mRounding(aRounding) {
NS_ASSERTION(aFlags & ShapedTextFlags::TEXT_IS_8BIT,
"8-bit flag should have been set");
mText.mSingle = aText;
}
WordCacheKey(const char16_t* aText, uint32_t aLength, uint32_t aStringHash,
Script aScriptCode, nsAtom* aLanguage,
int32_t aAppUnitsPerDevUnit, ShapedTextFlags aFlags,
RoundingFlags aRounding)
: mLength(aLength),
mFlags(aFlags),
mScript(aScriptCode),
mLanguage(aLanguage),
mAppUnitsPerDevUnit(aAppUnitsPerDevUnit),
mHashKey(aStringHash + static_cast<int32_t>(aScriptCode) +
aAppUnitsPerDevUnit * 0x100 + uint16_t(aFlags) * 0x10000 +
int(aRounding)),
mTextIs8Bit(false),
mRounding(aRounding) {
// We can NOT assert that TEXT_IS_8BIT is false in aFlags here,
// because this might be an 8bit-only word from a 16-bit textrun,
// in which case the text we're passed is still in 16-bit form,
// and we'll have to use an 8-to-16bit comparison in KeyEquals.
mText.mDouble = aText;
}
bool Matches(const WordCacheKey& aLookup) const;
class HashPolicy {
public:
typedef WordCacheKey Key;
typedef WordCacheKey Lookup;
static mozilla::HashNumber hash(const Lookup& aLookup) {
return aLookup.mHashKey;
}
static bool match(const Key& aKey, const Lookup& aLookup);
};
};
mozilla::UniquePtr<
mozilla::HashMap<WordCacheKey, mozilla::UniquePtr<gfxShapedWord>,
WordCacheKey::HashPolicy>>
mWordCache MOZ_GUARDED_BY(mLock);
static const uint32_t kShapedWordCacheMaxAge = 3;
nsTArray<mozilla::UniquePtr<gfxGlyphExtents>> mGlyphExtentsArray
MOZ_GUARDED_BY(mLock);
mozilla::UniquePtr<nsTHashSet<GlyphChangeObserver*>> mGlyphChangeObservers
MOZ_GUARDED_BY(mLock);
// a copy of the font without antialiasing, if needed for separate
// measurement by mathml code
mozilla::Atomic<gfxFont*> mNonAAFont;
// we create either or both of these shapers when needed, depending
// whether the font has graphite tables, and whether graphite shaping
// is actually enabled
mozilla::Atomic<gfxHarfBuzzShaper*> mHarfBuzzShaper;
mozilla::Atomic<gfxGraphiteShaper*> mGraphiteShaper;
// If a userfont with unicode-range specified, contains map of *possible*
// ranges supported by font. This is set during user-font initialization,
// before the font is available to other threads, and thereafter is inert
// so no guard is needed.
RefPtr<gfxCharacterMap> mUnicodeRangeMap;
// This is immutable once initialized by the constructor, so does not need
// locking.
RefPtr<mozilla::gfx::UnscaledFont> mUnscaledFont;
mozilla::Atomic<mozilla::gfx::ScaledFont*> mAzureScaledFont;
// For vertical metrics, created on demand.
mozilla::Atomic<Metrics*> mVerticalMetrics;
// Table used for MathML layout.
mozilla::Atomic<gfxMathTable*> mMathTable;
gfxFontStyle mStyle;
mutable gfxFloat mAdjustedSize;
// Tracking adjustment to be applied for CSS px size mCachedTrackingSize.
gfxFloat mTracking = 0.0;
gfxFloat mCachedTrackingSize = -1.0;
// Conversion factor from font units to dev units; note that this may be
// zero (in the degenerate case where mAdjustedSize has become zero).
// This is OK because we only multiply by this factor, never divide.
float mFUnitsConvFactor;
// This is guarded by gfxFontCache::GetCache()->GetMutex() but it is difficult
// to annotate that fact.
nsExpirationState mExpirationState;
// Glyph ID of the font's <space> glyph, zero if missing
uint16_t mSpaceGlyph = 0;
// the AA setting requested for this font - may affect glyph bounds
AntialiasOption mAntialiasOption;
bool mIsValid;
// use synthetic bolding for environments where this is not supported
// by the platform
bool mApplySyntheticBold;
bool mKerningSet; // kerning explicitly set?
bool mKerningEnabled; // if set, on or off?
mozilla::Atomic<bool> mMathInitialized; // TryGetMathTable() called?
// Helper for subclasses that want to initialize standard metrics from the
// tables of sfnt (TrueType/OpenType) fonts.
// This will use mFUnitsConvFactor if it is already set, else compute it
// from mAdjustedSize and the unitsPerEm in the font's 'head' table.
// Returns TRUE and sets mIsValid=TRUE if successful;
// Returns TRUE but leaves mIsValid=FALSE if the font seems to be broken.
// Returns FALSE if the font does not appear to be an sfnt at all,
// and should be handled (if possible) using other APIs.
bool InitMetricsFromSfntTables(Metrics& aMetrics);
// Helper to calculate various derived metrics from the results of
// InitMetricsFromSfntTables or equivalent platform code
void CalculateDerivedMetrics(Metrics& aMetrics);
// some fonts have bad metrics, this method sanitize them.
// if this font has bad underline offset, aIsBadUnderlineFont should be true.
void SanitizeMetrics(Metrics* aMetrics, bool aIsBadUnderlineFont);
bool RenderSVGGlyph(gfxContext* aContext,
mozilla::layout::TextDrawTarget* aTextDrawer,
mozilla::gfx::Point aPoint, uint32_t aGlyphId,
SVGContextPaint* aContextPaint) const;
bool RenderSVGGlyph(gfxContext* aContext,
mozilla::layout::TextDrawTarget* aTextDrawer,
mozilla::gfx::Point aPoint, uint32_t aGlyphId,
SVGContextPaint* aContextPaint,
gfxTextRunDrawCallbacks* aCallbacks,
bool& aEmittedGlyphs) const;
bool RenderColorGlyph(DrawTarget* aDrawTarget, gfxContext* aContext,
mozilla::layout::TextDrawTarget* aTextDrawer,
const FontDrawParams& aFontParams,
const mozilla::gfx::Point& aPoint, uint32_t aGlyphId);
class ColorGlyphCache {
public:
ColorGlyphCache() = default;
~ColorGlyphCache() = default;
void SetColors(mozilla::gfx::sRGBColor aCurrentColor,
mozilla::gfx::FontPalette* aPalette);
mozilla::HashMap<uint32_t, RefPtr<mozilla::gfx::SourceSurface>> mCache;
private:
mozilla::gfx::sRGBColor mCurrentColor;
RefPtr<mozilla::gfx::FontPalette> mPalette;
};
mozilla::UniquePtr<ColorGlyphCache> mColorGlyphCache;
// Subclasses can override to return true if the platform is able to render
// COLR-font glyphs directly, instead of us painting the layers explicitly.
// (Currently used only for COLR.v0 fonts on macOS.)
virtual bool UseNativeColrFontSupport() const { return false; }
// 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.
// This helper calculates the scale factor we need to apply to the
// synthetic-bold offset.
static mozilla::gfx::Float CalcXScale(DrawTarget* aDrawTarget);
};
// proportion of ascent used for x-height, if unable to read value from font
#define DEFAULT_XHEIGHT_FACTOR 0.56f
// Parameters passed to gfxFont methods for drawing glyphs from a textrun.
// The TextRunDrawParams are set up once per textrun; the FontDrawParams
// are dependent on the specific font, so they are set per GlyphRun.
struct MOZ_STACK_CLASS TextRunDrawParams {
explicit TextRunDrawParams(mozilla::gfx::PaletteCache& aPaletteCache)
: paletteCache(aPaletteCache) {}
mozilla::gfx::PaletteCache& paletteCache;
RefPtr<mozilla::gfx::DrawTarget> dt;
gfxContext* context = nullptr;
gfxFont::Spacing* spacing = nullptr;
gfxTextRunDrawCallbacks* callbacks = nullptr;
mozilla::SVGContextPaint* runContextPaint = nullptr;
mozilla::layout::TextDrawTarget* textDrawer = nullptr;
mozilla::LayoutDeviceRect clipRect;
mozilla::gfx::Float direction = 1.0f;
double devPerApp = 1.0;
nscolor textStrokeColor = 0;
gfxPattern* textStrokePattern = nullptr;
const mozilla::gfx::StrokeOptions* strokeOpts = nullptr;
const mozilla::gfx::DrawOptions* drawOpts = nullptr;
nsAtom* fontPalette = nullptr;
DrawMode drawMode = DrawMode::GLYPH_FILL;
bool isVerticalRun = false;
bool isRTL = false;
bool paintSVGGlyphs = true;
bool allowGDI = true;
bool hasTextShadow = false;
};
struct MOZ_STACK_CLASS FontDrawParams {
RefPtr<mozilla::gfx::ScaledFont> scaledFont;
mozilla::SVGContextPaint* contextPaint;
mozilla::gfx::Float synBoldOnePixelOffset;
mozilla::gfx::Float obliqueSkew;
int32_t extraStrikes;
mozilla::gfx::DrawOptions drawOptions;
gfxFloat advanceDirection;
mozilla::gfx::sRGBColor currentColor;
RefPtr<mozilla::gfx::FontPalette> palette;
mozilla::gfx::Rect fontExtents;
bool isVerticalFont;
bool haveSVGGlyphs;
bool haveColorGlyphs;
bool hasTextShadow; // whether we're rendering with a text-shadow
};
struct MOZ_STACK_CLASS EmphasisMarkDrawParams {
EmphasisMarkDrawParams(gfxContext* aContext,
mozilla::gfx::PaletteCache& aPaletteCache)
: context(aContext), paletteCache(aPaletteCache) {}
gfxContext* context;
mozilla::gfx::PaletteCache& paletteCache;
gfxFont::Spacing* spacing;
gfxTextRun* mark;
gfxFloat advance;
gfxFloat direction;
bool isVertical;
};
#endif