diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /gfx/thebes/gfxFont.h | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'gfx/thebes/gfxFont.h')
-rw-r--r-- | gfx/thebes/gfxFont.h | 2375 |
1 files changed, 2375 insertions, 0 deletions
diff --git a/gfx/thebes/gfxFont.h b/gfx/thebes/gfxFont.h new file mode 100644 index 0000000000..3a3d13c810 --- /dev/null +++ b/gfx/thebes/gfxFont.h @@ -0,0 +1,2375 @@ +/* -*- 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/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/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, + 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 + // (see http://www.microsoft.com/typography/otspec/languagetags.htm). + // 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 estimated background color behind the text. Enables a special + // rendering mode when NS_GET_A(.) > 0. Only used for text in the chrome. + nscolor fontSmoothingBackgroundColor; + + // 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 + bool allowSyntheticWeight : 1; + bool allowSyntheticStyle : 1; + bool allowSyntheticSmallCaps : 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) && + (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) && + (fontSmoothingBackgroundColor == other.fontSmoothingBackgroundColor); + } +}; + +/** + * 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(¤t, 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)(const 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, + + 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 <= 2, "Bogus break-before 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 AdjustAdvancesForSyntheticBold(float aSynBoldOffset, 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; + } + + 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)); + } + + 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; + + 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; + } + + /** + * 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; + + struct CacheHashKey { + union { + const uint8_t* mSingle; + const char16_t* mDouble; + } mText; + uint32_t mLength; + mozilla::gfx::ShapedTextFlags mFlags; + Script mScript; + RefPtr<nsAtom> mLanguage; + int32_t mAppUnitsPerDevUnit; + PLDHashNumber mHashKey; + bool mTextIs8Bit; + RoundingFlags mRounding; + + CacheHashKey(const uint8_t* aText, uint32_t aLength, uint32_t aStringHash, + Script aScriptCode, nsAtom* aLanguage, + int32_t aAppUnitsPerDevUnit, + mozilla::gfx::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 & mozilla::gfx::ShapedTextFlags::TEXT_IS_8BIT, + "8-bit flag should have been set"); + mText.mSingle = aText; + } + + CacheHashKey(const char16_t* aText, uint32_t aLength, uint32_t aStringHash, + Script aScriptCode, nsAtom* aLanguage, + int32_t aAppUnitsPerDevUnit, + mozilla::gfx::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; + } + }; + + class CacheHashEntry : public PLDHashEntryHdr { + public: + typedef const CacheHashKey& KeyType; + typedef const CacheHashKey* KeyTypePointer; + + // When constructing a new entry in the hashtable, the caller of Put() + // will fill us in. + explicit CacheHashEntry(KeyTypePointer aKey) {} + CacheHashEntry(const CacheHashEntry&) = delete; + CacheHashEntry& operator=(const CacheHashEntry&) = delete; + CacheHashEntry(CacheHashEntry&&) = default; + CacheHashEntry& operator=(CacheHashEntry&&) = default; + + bool KeyEquals(const KeyTypePointer aKey) const; + + static KeyTypePointer KeyToPointer(KeyType aKey) { return &aKey; } + + static PLDHashNumber HashKey(const KeyTypePointer aKey) { + return aKey->mHashKey; + } + + size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const { + return aMallocSizeOf(mShapedWord.get()); + } + + enum { ALLOW_MEMMOVE = true }; + + mozilla::UniquePtr<gfxShapedWord> mShapedWord; + }; + + mozilla::UniquePtr<nsTHashtable<CacheHashEntry>> 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; + + // 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); + + // 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 { + 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; + const mozilla::gfx::FontPaletteValueSet* paletteValueSet = nullptr; + nsAtom* fontPalette = nullptr; + DrawMode drawMode = DrawMode::GLYPH_FILL; + bool isVerticalRun = false; + bool isRTL = false; + bool paintSVGGlyphs = true; + bool allowGDI = true; + + // MRU cache of color-font palettes being used by fonts in the run. We cache + // these in the TextRunDrawParams so that we can avoid re-creating a new + // palette (which can be quite expensive) for each individual glyph run. + using CacheKey = const gfxFont*; + + struct CacheData { + CacheKey mKey; + mozilla::UniquePtr<nsTArray<mozilla::gfx::sRGBColor>> mPalette; + }; + + class PaletteCache + : public mozilla::MruCache<CacheKey, CacheData, PaletteCache> { + public: + static mozilla::HashNumber Hash(const CacheKey& aKey) { + return mozilla::HashGeneric(aKey); + } + static bool Match(const CacheKey& aKey, const CacheData& aVal) { + return aVal.mKey == aKey; + } + }; + + PaletteCache mPaletteCache; + + // Returns a pointer to a palette owned by the PaletteCache. This is only + // valid until the next call to GetPaletteFor (which might evict it) or + // until the TextRunDrawParams goes out of scope. + nsTArray<mozilla::gfx::sRGBColor>* GetPaletteFor(const gfxFont* aFont); +}; + +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; + nsTArray<mozilla::gfx::sRGBColor>* palette; // owned by TextRunDrawParams + mozilla::gfx::Rect fontExtents; + bool isVerticalFont; + bool haveSVGGlyphs; + bool haveColorGlyphs; +}; + +struct MOZ_STACK_CLASS EmphasisMarkDrawParams { + gfxContext* context; + gfxFont::Spacing* spacing; + gfxTextRun* mark; + gfxFloat advance; + gfxFloat direction; + bool isVertical; +}; + +#endif |