/* -*- 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_TEXTRUN_H #define GFX_TEXTRUN_H #include #include "gfxTypes.h" #include "gfxPoint.h" #include "gfxFont.h" #include "gfxFontConstants.h" #include "gfxSkipChars.h" #include "gfxPlatform.h" #include "mozilla/MemoryReporting.h" #include "mozilla/RefPtr.h" #include "nsPoint.h" #include "nsString.h" #include "nsTArray.h" #include "nsTextFrameUtils.h" #include "DrawMode.h" #include "harfbuzz/hb.h" #include "nsUnicodeScriptCodes.h" #include "nsColor.h" #include "X11UndefineNone.h" #ifdef DEBUG #include #endif class gfxContext; class gfxFontGroup; class gfxUserFontEntry; class gfxUserFontSet; class nsAtom; class nsLanguageAtomService; class gfxMissingFontRecorder; namespace mozilla { class SVGContextPaint; enum class StyleHyphens : uint8_t; }; // namespace mozilla /** * Callback for Draw() to use when drawing text with mode * DrawMode::GLYPH_PATH. */ struct gfxTextRunDrawCallbacks { /** * Constructs a new DrawCallbacks object. * * @param aShouldPaintSVGGlyphs If true, SVG glyphs will be painted. If * false, SVG glyphs will not be painted; fallback plain glyphs are not * emitted either. */ explicit gfxTextRunDrawCallbacks(bool aShouldPaintSVGGlyphs = false) : mShouldPaintSVGGlyphs(aShouldPaintSVGGlyphs) {} /** * Called when a path has been emitted to the gfxContext when * painting a text run. This can be called any number of times, * due to partial ligatures and intervening SVG glyphs. */ virtual void NotifyGlyphPathEmitted() = 0; bool mShouldPaintSVGGlyphs; }; /** * gfxTextRun is an abstraction for drawing and measuring substrings of a run * of text. It stores runs of positioned glyph data, each run having a single * gfxFont. The glyphs are associated with a string of source text, and the * gfxTextRun APIs take parameters that are offsets into that source text. * * gfxTextRuns are mostly immutable. The only things that can change are * inter-cluster spacing and line break placement. Spacing is always obtained * lazily by methods that need it, it is not cached. Line breaks are stored * persistently (insofar as they affect the shaping of glyphs; gfxTextRun does * not actually do anything to explicitly account for line breaks). Initially * there are no line breaks. The textrun can record line breaks before or after * any given cluster. (Line breaks specified inside clusters are ignored.) * * It is important that zero-length substrings are handled correctly. This will * be on the test! */ class gfxTextRun : public gfxShapedText { NS_INLINE_DECL_REFCOUNTING(gfxTextRun); protected: // Override operator delete to properly free the object that was // allocated via malloc. void operator delete(void* p) { free(p); } virtual ~gfxTextRun(); public: typedef gfxFont::RunMetrics Metrics; typedef mozilla::gfx::DrawTarget DrawTarget; // Public textrun API for general use bool IsClusterStart(uint32_t aPos) const { MOZ_ASSERT(aPos < GetLength()); return mCharacterGlyphs[aPos].IsClusterStart(); } bool IsLigatureGroupStart(uint32_t aPos) const { MOZ_ASSERT(aPos < GetLength()); return mCharacterGlyphs[aPos].IsLigatureGroupStart(); } bool CanBreakLineBefore(uint32_t aPos) const { return CanBreakBefore(aPos) == CompressedGlyph::FLAG_BREAK_TYPE_NORMAL; } bool CanHyphenateBefore(uint32_t aPos) const { return CanBreakBefore(aPos) == CompressedGlyph::FLAG_BREAK_TYPE_HYPHEN; } // Returns a gfxShapedText::CompressedGlyph::FLAG_BREAK_TYPE_* value // as defined in gfxFont.h (may be NONE, NORMAL or HYPHEN). uint8_t CanBreakBefore(uint32_t aPos) const { MOZ_ASSERT(aPos < GetLength()); return mCharacterGlyphs[aPos].CanBreakBefore(); } bool CharIsSpace(uint32_t aPos) const { MOZ_ASSERT(aPos < GetLength()); return mCharacterGlyphs[aPos].CharIsSpace(); } bool CharIsTab(uint32_t aPos) const { MOZ_ASSERT(aPos < GetLength()); return mCharacterGlyphs[aPos].CharIsTab(); } bool CharIsNewline(uint32_t aPos) const { MOZ_ASSERT(aPos < GetLength()); return mCharacterGlyphs[aPos].CharIsNewline(); } bool CharMayHaveEmphasisMark(uint32_t aPos) const { MOZ_ASSERT(aPos < GetLength()); return mCharacterGlyphs[aPos].CharMayHaveEmphasisMark(); } bool CharIsFormattingControl(uint32_t aPos) const { MOZ_ASSERT(aPos < GetLength()); return mCharacterGlyphs[aPos].CharIsFormattingControl(); } // All offsets are in terms of the string passed into MakeTextRun. // Describe range [start, end) of a text run. The range is // restricted to grapheme cluster boundaries. struct Range { uint32_t start; uint32_t end; uint32_t Length() const { return end - start; } Range() : start(0), end(0) {} Range(uint32_t aStart, uint32_t aEnd) : start(aStart), end(aEnd) {} explicit Range(const gfxTextRun* aTextRun) : start(0), end(aTextRun->GetLength()) {} }; // All coordinates are in layout/app units /** * Set the potential linebreaks for a substring of the textrun. These are * the "allow break before" points. Initially, there are no potential * linebreaks. * * This can change glyphs and/or geometry! Some textruns' shapes * depend on potential line breaks (e.g., title-case-converting textruns). * This function is virtual so that those textruns can reshape themselves. * * @return true if this changed the linebreaks, false if the new line * breaks are the same as the old */ virtual bool SetPotentialLineBreaks(Range aRange, const uint8_t* aBreakBefore); enum class HyphenType : uint8_t { None, Explicit, Soft, AutoWithManualInSameWord, AutoWithoutManualInSameWord }; struct HyphenationState { uint32_t mostRecentBoundary = 0; bool hasManualHyphen = false; bool hasExplicitHyphen = false; bool hasAutoHyphen = false; }; /** * Layout provides PropertyProvider objects. These allow detection of * potential line break points and computation of spacing. We pass the data * this way to allow lazy data acquisition; for example BreakAndMeasureText * will want to only ask for properties of text it's actually looking at. * * NOTE that requested spacing may not actually be applied, if the textrun * is unable to apply it in some context. Exception: spacing around a * whitespace character MUST always be applied. */ class PropertyProvider { public: // Detect hyphenation break opportunities in the given range; breaks // not at cluster boundaries will be ignored. virtual void GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore) const = 0; // Returns the provider's hyphenation setting, so callers can decide // whether it is necessary to call GetHyphenationBreaks. // Result is an StyleHyphens value. virtual mozilla::StyleHyphens GetHyphensOption() const = 0; // Returns the extra width that will be consumed by a hyphen. This should // be constant for a given textrun. virtual gfxFloat GetHyphenWidth() const = 0; typedef gfxFont::Spacing Spacing; /** * Get the spacing around the indicated characters. Spacing must be zero * inside clusters. In other words, if character i is not * CLUSTER_START, then character i-1 must have zero after-spacing and * character i must have zero before-spacing. */ virtual void GetSpacing(Range aRange, Spacing* aSpacing) const = 0; // Returns a gfxContext that can be used to measure the hyphen glyph. // Only called if the hyphen width is requested. virtual already_AddRefed GetDrawTarget() const = 0; // Return the appUnitsPerDevUnit value to be used when measuring. // Only called if the hyphen width is requested. virtual uint32_t GetAppUnitsPerDevUnit() const = 0; }; struct MOZ_STACK_CLASS DrawParams { gfxContext* context; DrawMode drawMode = DrawMode::GLYPH_FILL; nscolor textStrokeColor = 0; gfxPattern* textStrokePattern = nullptr; const mozilla::gfx::StrokeOptions* strokeOpts = nullptr; const mozilla::gfx::DrawOptions* drawOpts = nullptr; PropertyProvider* provider = nullptr; // If non-null, the advance width of the substring is set. gfxFloat* advanceWidth = nullptr; mozilla::SVGContextPaint* contextPaint = nullptr; gfxTextRunDrawCallbacks* callbacks = nullptr; explicit DrawParams(gfxContext* aContext) : context(aContext) {} }; /** * Draws a substring. Uses only GetSpacing from aBreakProvider. * The provided point is the baseline origin on the left of the string * for LTR, on the right of the string for RTL. * * Drawing should respect advance widths in the sense that for LTR runs, * Draw(Range(start, middle), pt, ...) followed by * Draw(Range(middle, end), gfxPoint(pt.x + advance, pt.y), ...) * should have the same effect as * Draw(Range(start, end), pt, ...) * * For RTL runs the rule is: * Draw(Range(middle, end), pt, ...) followed by * Draw(Range(start, middle), gfxPoint(pt.x + advance, pt.y), ...) * should have the same effect as * Draw(Range(start, end), pt, ...) * * Glyphs should be drawn in logical content order, which can be significant * if they overlap (perhaps due to negative spacing). */ void Draw(Range aRange, mozilla::gfx::Point aPt, const DrawParams& aParams) const; /** * Draws the emphasis marks for this text run. Uses only GetSpacing * from aProvider. The provided point is the baseline origin of the * line of emphasis marks. */ void DrawEmphasisMarks(gfxContext* aContext, gfxTextRun* aMark, gfxFloat aMarkAdvance, mozilla::gfx::Point aPt, Range aRange, PropertyProvider* aProvider) const; /** * Computes the ReflowMetrics for a substring. * Uses GetSpacing from aBreakProvider. * @param aBoundingBoxType which kind of bounding box (loose/tight) */ Metrics MeasureText(Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aDrawTargetForTightBoundingBox, PropertyProvider* aProvider) const; Metrics MeasureText(gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aDrawTargetForTightBoundingBox, PropertyProvider* aProvider = nullptr) const { return MeasureText(Range(this), aBoundingBoxType, aDrawTargetForTightBoundingBox, aProvider); } /** * Computes just the advance width for a substring. * Uses GetSpacing from aBreakProvider. * If aSpacing is not null, the spacing attached before and after * the substring would be returned in it. NOTE: the spacing is * included in the advance width. */ gfxFloat GetAdvanceWidth(Range aRange, PropertyProvider* aProvider, PropertyProvider::Spacing* aSpacing = nullptr) const; gfxFloat GetAdvanceWidth() const { return GetAdvanceWidth(Range(this), nullptr); } /** * Computes the minimum advance width for a substring assuming line * breaking is allowed everywhere. */ gfxFloat GetMinAdvanceWidth(Range aRange); /** * Clear all stored line breaks for the given range (both before and after), * and then set the line-break state before aRange.start to aBreakBefore and * after the last cluster to aBreakAfter. * * We require that before and after line breaks be consistent. For clusters * i and i+1, we require that if there is a break after cluster i, a break * will be specified before cluster i+1. This may be temporarily violated * (e.g. after reflowing line L and before reflowing line L+1); to handle * these temporary violations, we say that there is a break betwen i and i+1 * if a break is specified after i OR a break is specified before i+1. * * This can change textrun geometry! The existence of a linebreak can affect * the advance width of the cluster before the break (when kerning) or the * geometry of one cluster before the break or any number of clusters * after the break. (The one-cluster-before-the-break limit is somewhat * arbitrary; if some scripts require breaking it, then we need to * alter nsTextFrame::TrimTrailingWhitespace, perhaps drastically becase * it could affect the layout of frames before it...) * * We return true if glyphs or geometry changed, false otherwise. This * function is virtual so that gfxTextRun subclasses can reshape * properly. * * @param aAdvanceWidthDelta if non-null, returns the change in advance * width of the given range. */ virtual bool SetLineBreaks(Range aRange, bool aLineBreakBefore, bool aLineBreakAfter, gfxFloat* aAdvanceWidthDelta); enum SuppressBreak { eNoSuppressBreak, // Measure the range of text as if there is no break before it. eSuppressInitialBreak, // Measure the range of text as if it contains no break eSuppressAllBreaks }; void ClassifyAutoHyphenations(uint32_t aStart, Range aRange, nsTArray& aHyphenBuffer, HyphenationState* aWordState); /** * Finds the longest substring that will fit into the given width. * Uses GetHyphenationBreaks and GetSpacing from aProvider. * Guarantees the following: * -- 0 <= result <= aMaxLength * -- result is the maximal value of N such that either * N < aMaxLength && line break at N && * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth * OR N < aMaxLength && hyphen break at N && * GetAdvanceWidth(Range(aStart, N), aProvider) + * GetHyphenWidth() <= aWidth * OR N == aMaxLength && * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth * where GetAdvanceWidth assumes the effect of * SetLineBreaks(Range(aStart, N), * aLineBreakBefore, N < aMaxLength, aProvider) * -- if no such N exists, then result is the smallest N such that * N < aMaxLength && line break at N * OR N < aMaxLength && hyphen break at N * OR N == aMaxLength * * The call has the effect of * SetLineBreaks(Range(aStart, result), aLineBreakBefore, * result < aMaxLength, aProvider) * and the returned metrics and the invariants above reflect this. * * @param aMaxLength this can be UINT32_MAX, in which case the length used * is up to the end of the string * @param aLineBreakBefore set to true if and only if there is an actual * line break at the start of this string. * @param aSuppressBreak what break should be suppressed. * @param aTrimWhitespace if non-null, then we allow a trailing run of * spaces to be trimmed; the width of the space(s) will not be included in * the measured string width for comparison with the limit aWidth, and * trimmed spaces will not be included in returned metrics. The width * of the trimmed spaces will be returned in aTrimWhitespace. * Trimmed spaces are still counted in the "characters fit" result. * @param aHangWhitespace true if we allow whitespace to overflow the * container at a soft-wrap * @param aMetrics if non-null, we fill this in for the returned substring. * If a hyphenation break was used, the hyphen is NOT included in the returned * metrics. * @param aBoundingBoxType whether to make the bounding box in aMetrics tight * @param aDrawTargetForTightBoundingbox a reference DrawTarget to get the * tight bounding box, if requested * @param aUsedHyphenation if non-null, records if we selected a hyphenation * break * @param aLastBreak if non-null and result is aMaxLength, we set this to * the maximal N such that * N < aMaxLength && line break at N && * GetAdvanceWidth(Range(aStart, N), aProvider) <= aWidth * OR N < aMaxLength && hyphen break at N && * GetAdvanceWidth(Range(aStart, N), aProvider) + * GetHyphenWidth() <= aWidth * or UINT32_MAX if no such N exists, where GetAdvanceWidth assumes * the effect of * SetLineBreaks(Range(aStart, N), aLineBreakBefore, * N < aMaxLength, aProvider) * * @param aCanWordWrap true if we can break between any two grapheme * clusters. This is set by overflow-wrap|word-wrap: break-word * * @param aBreakPriority in/out the priority of the break opportunity * saved in the line. If we are prioritizing break opportunities, we will * not set a break with a lower priority. @see gfxBreakPriority. * * Note that negative advance widths are possible especially if negative * spacing is provided. */ uint32_t BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength, bool aLineBreakBefore, gfxFloat aWidth, PropertyProvider* aProvider, SuppressBreak aSuppressBreak, gfxFloat* aTrimWhitespace, bool aHangWhitespace, Metrics* aMetrics, gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aDrawTargetForTightBoundingBox, bool* aUsedHyphenation, uint32_t* aLastBreak, bool aCanWordWrap, gfxBreakPriority* aBreakPriority); // Utility getters void* GetUserData() const { return mUserData; } void SetUserData(void* aUserData) { mUserData = aUserData; } void SetFlagBits(nsTextFrameUtils::Flags aFlags) { mFlags2 |= aFlags; } void ClearFlagBits(nsTextFrameUtils::Flags aFlags) { mFlags2 &= ~aFlags; } const gfxSkipChars& GetSkipChars() const { return mSkipChars; } gfxFontGroup* GetFontGroup() const { return mFontGroup; } // Call this, don't call "new gfxTextRun" directly. This does custom // allocation and initialization static already_AddRefed Create( const gfxTextRunFactory::Parameters* aParams, uint32_t aLength, gfxFontGroup* aFontGroup, mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2); // The text is divided into GlyphRuns as necessary. (In the vast majority // of cases, a gfxTextRun contains just a single GlyphRun.) struct GlyphRun { RefPtr mFont; // never null in a valid GlyphRun uint32_t mCharacterOffset; // into original UTF16 string mozilla::gfx::ShapedTextFlags mOrientation; // gfxTextRunFactory::TEXT_ORIENT_* value gfxTextRange::MatchType mMatchType; }; class MOZ_STACK_CLASS GlyphRunIterator { public: GlyphRunIterator(const gfxTextRun* aTextRun, Range aRange) : mTextRun(aTextRun), mStartOffset(aRange.start), mEndOffset(aRange.end) { mNextIndex = mTextRun->FindFirstGlyphRunContaining(aRange.start); } bool NextRun(); const GlyphRun* GetGlyphRun() const { return mGlyphRun; } uint32_t GetStringStart() const { return mStringStart; } uint32_t GetStringEnd() const { return mStringEnd; } private: const gfxTextRun* mTextRun; MOZ_INIT_OUTSIDE_CTOR const GlyphRun* mGlyphRun; MOZ_INIT_OUTSIDE_CTOR uint32_t mStringStart; MOZ_INIT_OUTSIDE_CTOR uint32_t mStringEnd; uint32_t mNextIndex; uint32_t mStartOffset; uint32_t mEndOffset; }; class GlyphRunOffsetComparator { public: bool Equals(const GlyphRun& a, const GlyphRun& b) const { return a.mCharacterOffset == b.mCharacterOffset; } bool LessThan(const GlyphRun& a, const GlyphRun& b) const { return a.mCharacterOffset < b.mCharacterOffset; } }; friend class GlyphRunIterator; friend class FontSelector; // API for setting up the textrun glyphs. Should only be called by // things that construct textruns. /** * We've found a run of text that should use a particular font. Call this * only during initialization when font substitution has been computed. * Call it before setting up the glyphs for the characters in this run; * SetMissingGlyph requires that the correct glyphrun be installed. * * If aForceNewRun, a new glyph run will be added, even if the * previously added run uses the same font. If glyph runs are * added out of strictly increasing aStartCharIndex order (via * force), then SortGlyphRuns must be called after all glyph runs * are added before any further operations are performed with this * TextRun. */ nsresult AddGlyphRun(gfxFont* aFont, gfxTextRange::MatchType aMatchType, uint32_t aStartCharIndex, bool aForceNewRun, mozilla::gfx::ShapedTextFlags aOrientation); void ResetGlyphRuns() { if (mHasGlyphRunArray) { MOZ_ASSERT(mGlyphRunArray.Length() > 1); // Discard all but the first GlyphRun... mGlyphRunArray.TruncateLength(1); // ...and then convert to the single-run representation. ConvertFromGlyphRunArray(); } // Clear out the one remaining GlyphRun. mSingleGlyphRun.mFont = nullptr; } void SortGlyphRuns(); void SanitizeGlyphRuns(); const CompressedGlyph* GetCharacterGlyphs() const final { MOZ_ASSERT(mCharacterGlyphs, "failed to initialize mCharacterGlyphs"); return mCharacterGlyphs; } CompressedGlyph* GetCharacterGlyphs() final { MOZ_ASSERT(mCharacterGlyphs, "failed to initialize mCharacterGlyphs"); return mCharacterGlyphs; } // clean out results from shaping in progress, used for fallback scenarios void ClearGlyphsAndCharacters(); void SetSpaceGlyph(gfxFont* aFont, DrawTarget* aDrawTarget, uint32_t aCharIndex, mozilla::gfx::ShapedTextFlags aOrientation); // Set the glyph data for the given character index to the font's // space glyph, IF this can be done as a "simple" glyph record // (not requiring a DetailedGlyph entry). This avoids the need to call // the font shaper and go through the shaped-word cache for most spaces. // // The parameter aSpaceChar is the original character code for which // this space glyph is being used; if this is U+0020, we need to record // that it could be trimmed at a run edge, whereas other kinds of space // (currently just U+00A0) would not be trimmable/breakable. // // Returns true if it was able to set simple glyph data for the space; // if it returns false, the caller needs to fall back to some other // means to create the necessary (detailed) glyph data. bool SetSpaceGlyphIfSimple(gfxFont* aFont, uint32_t aCharIndex, char16_t aSpaceChar, mozilla::gfx::ShapedTextFlags aOrientation); // Record the positions of specific characters that layout may need to // detect in the textrun, even though it doesn't have an explicit copy // of the original text. These are recorded using flag bits in the // CompressedGlyph record; if necessary, we convert "simple" glyph records // to "complex" ones as the Tab and Newline flags are not present in // simple CompressedGlyph records. void SetIsTab(uint32_t aIndex) { EnsureComplexGlyph(aIndex).SetIsTab(); } void SetIsNewline(uint32_t aIndex) { EnsureComplexGlyph(aIndex).SetIsNewline(); } void SetNoEmphasisMark(uint32_t aIndex) { EnsureComplexGlyph(aIndex).SetNoEmphasisMark(); } void SetIsFormattingControl(uint32_t aIndex) { EnsureComplexGlyph(aIndex).SetIsFormattingControl(); } /** * Prefetch all the glyph extents needed to ensure that Measure calls * on this textrun not requesting tight boundingBoxes will succeed. Note * that some glyph extents might not be fetched due to OOM or other * errors. */ void FetchGlyphExtents(DrawTarget* aRefDrawTarget); uint32_t CountMissingGlyphs() const; const GlyphRun* GetGlyphRuns(uint32_t* aNumGlyphRuns) const { if (mHasGlyphRunArray) { *aNumGlyphRuns = mGlyphRunArray.Length(); return mGlyphRunArray.Elements(); } else { *aNumGlyphRuns = mSingleGlyphRun.mFont ? 1 : 0; return &mSingleGlyphRun; } } // Returns the index of the GlyphRun containing the given offset. // Returns mGlyphRuns.Length() when aOffset is mCharacterCount. uint32_t FindFirstGlyphRunContaining(uint32_t aOffset) const; // Copy glyph data from a ShapedWord into this textrun. void CopyGlyphDataFrom(gfxShapedWord* aSource, uint32_t aStart); // Copy glyph data for a range of characters from aSource to this // textrun. void CopyGlyphDataFrom(gfxTextRun* aSource, Range aRange, uint32_t aDest); // Tell the textrun to release its reference to its creating gfxFontGroup // immediately, rather than on destruction. This is used for textruns // that are actually owned by a gfxFontGroup, so that they don't keep it // permanently alive due to a circular reference. (The caller of this is // taking responsibility for ensuring the textrun will not outlive its // mFontGroup.) void ReleaseFontGroup(); struct LigatureData { // textrun range of the containing ligature Range mRange; // appunits advance to the start of the ligature part within the ligature; // never includes any spacing gfxFloat mPartAdvance; // appunits width of the ligature part; includes before-spacing // when the part is at the start of the ligature, and after-spacing // when the part is as the end of the ligature gfxFloat mPartWidth; bool mClipBeforePart; bool mClipAfterPart; }; // return storage used by this run, for memory reporter; // nsTransformedTextRun needs to override this as it holds additional data virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) MOZ_MUST_OVERRIDE; virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) MOZ_MUST_OVERRIDE; nsTextFrameUtils::Flags GetFlags2() const { return mFlags2; } // Get the size, if it hasn't already been gotten, marking as it goes. size_t MaybeSizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) { if (mFlags2 & nsTextFrameUtils::Flags::TEXT_RUN_SIZE_ACCOUNTED) { return 0; } mFlags2 |= nsTextFrameUtils::Flags::TEXT_RUN_SIZE_ACCOUNTED; return SizeOfIncludingThis(aMallocSizeOf); } void ResetSizeOfAccountingFlags() { mFlags2 &= ~nsTextFrameUtils::Flags::TEXT_RUN_SIZE_ACCOUNTED; } // shaping state - for some font features, fallback is required that // affects the entire run. for example, fallback for one script/font // portion of a textrun requires fallback to be applied to the entire run enum ShapingState : uint8_t { eShapingState_Normal, // default state eShapingState_ShapingWithFeature, // have shaped with feature eShapingState_ShapingWithFallback, // have shaped with fallback eShapingState_Aborted, // abort initial iteration eShapingState_ForceFallbackFeature // redo with fallback forced on }; ShapingState GetShapingState() const { return mShapingState; } void SetShapingState(ShapingState aShapingState) { mShapingState = aShapingState; } int32_t GetAdvanceForGlyph(uint32_t aIndex) const { const CompressedGlyph& glyphData = mCharacterGlyphs[aIndex]; if (glyphData.IsSimpleGlyph()) { return glyphData.GetSimpleAdvance(); } uint32_t glyphCount = glyphData.GetGlyphCount(); if (!glyphCount) { return 0; } const DetailedGlyph* details = GetDetailedGlyphs(aIndex); int32_t advance = 0; for (uint32_t j = 0; j < glyphCount; ++j, ++details) { advance += details->mAdvance; } return advance; } #ifdef DEBUG void Dump(FILE* aOutput); #endif protected: /** * Create a textrun, and set its mCharacterGlyphs to point immediately * after the base object; this is ONLY used in conjunction with placement * new, after allocating a block large enough for the glyph records to * follow the base textrun object. */ gfxTextRun(const gfxTextRunFactory::Parameters* aParams, uint32_t aLength, gfxFontGroup* aFontGroup, mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2); /** * Helper for the Create() factory method to allocate the required * glyph storage for a textrun object with the basic size aSize, * plus room for aLength glyph records. */ static void* AllocateStorageForTextRun(size_t aSize, uint32_t aLength); // Pointer to the array of CompressedGlyph records; must be initialized // when the object is constructed. CompressedGlyph* mCharacterGlyphs; private: // **** general helpers **** // Get the total advance for a range of glyphs. int32_t GetAdvanceForGlyphs(Range aRange) const; // Spacing for characters outside the range aSpacingStart/aSpacingEnd // is assumed to be zero; such characters are not passed to aProvider. // This is useful to protect aProvider from being passed character indices // it is not currently able to handle. bool GetAdjustedSpacingArray( Range aRange, PropertyProvider* aProvider, Range aSpacingRange, nsTArray* aSpacing) const; CompressedGlyph& EnsureComplexGlyph(uint32_t aIndex) { gfxShapedText::EnsureComplexGlyph(aIndex, mCharacterGlyphs[aIndex]); return mCharacterGlyphs[aIndex]; } // **** ligature helpers **** // (Platforms do the actual ligaturization, but we need to do a bunch of stuff // to handle requests that begin or end inside a ligature) // if aProvider is null then mBeforeSpacing and mAfterSpacing are set to zero LigatureData ComputeLigatureData(Range aPartRange, PropertyProvider* aProvider) const; gfxFloat ComputePartialLigatureWidth(Range aPartRange, PropertyProvider* aProvider) const; void DrawPartialLigature(gfxFont* aFont, Range aRange, mozilla::gfx::Point* aPt, PropertyProvider* aProvider, TextRunDrawParams& aParams, mozilla::gfx::ShapedTextFlags aOrientation) const; // Advance aRange.start to the start of the nearest ligature, back // up aRange.end to the nearest ligature end; may result in // aRange->start == aRange->end. void ShrinkToLigatureBoundaries(Range* aRange) const; // result in appunits gfxFloat GetPartialLigatureWidth(Range aRange, PropertyProvider* aProvider) const; void AccumulatePartialLigatureMetrics( gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aRefDrawTarget, PropertyProvider* aProvider, mozilla::gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const; // **** measurement helper **** void AccumulateMetricsForRun(gfxFont* aFont, Range aRange, gfxFont::BoundingBoxType aBoundingBoxType, DrawTarget* aRefDrawTarget, PropertyProvider* aProvider, Range aSpacingRange, mozilla::gfx::ShapedTextFlags aOrientation, Metrics* aMetrics) const; // **** drawing helper **** void DrawGlyphs(gfxFont* aFont, Range aRange, mozilla::gfx::Point* aPt, PropertyProvider* aProvider, Range aSpacingRange, TextRunDrawParams& aParams, mozilla::gfx::ShapedTextFlags aOrientation) const; // The textrun holds either a single GlyphRun -or- an array; // the flag mHasGlyphRunArray tells us which is present. union { GlyphRun mSingleGlyphRun; nsTArray mGlyphRunArray; }; void ConvertToGlyphRunArray() { MOZ_ASSERT(!mHasGlyphRunArray && mSingleGlyphRun.mFont); GlyphRun tmp = std::move(mSingleGlyphRun); mSingleGlyphRun.~GlyphRun(); new (&mGlyphRunArray) nsTArray(2); mGlyphRunArray.AppendElement(std::move(tmp)); mHasGlyphRunArray = true; } void ConvertFromGlyphRunArray() { MOZ_ASSERT(mHasGlyphRunArray && mGlyphRunArray.Length() == 1); GlyphRun tmp = std::move(mGlyphRunArray[0]); mGlyphRunArray.~nsTArray(); new (&mSingleGlyphRun) GlyphRun(std::move(tmp)); mHasGlyphRunArray = false; } void* mUserData; // mFontGroup is usually a strong reference, but refcounting is managed // manually because it may be explicitly released by ReleaseFontGroup() // in the case where the font group actually owns the textrun. gfxFontGroup* MOZ_OWNING_REF mFontGroup; gfxSkipChars mSkipChars; nsTextFrameUtils::Flags mFlags2; // additional flags (see also gfxShapedText::mFlags) bool mSkipDrawing; // true if the font group we used had a user font // download that's in progress, so we should hide text // until the download completes (or timeout fires) bool mReleasedFontGroup; // we already called NS_RELEASE on // mFontGroup, so don't do it again bool mHasGlyphRunArray; // whether we're using an array or // just storing a single glyphrun // shaping state for handling variant fallback features // such as subscript/superscript variant glyphs ShapingState mShapingState; }; class gfxFontGroup final : public gfxTextRunFactory { public: typedef mozilla::unicode::Script Script; typedef gfxShapedText::CompressedGlyph CompressedGlyph; static void Shutdown(); // platform must call this to release the languageAtomService gfxFontGroup(const mozilla::FontFamilyList& aFontFamilyList, const gfxFontStyle* aStyle, gfxTextPerfMetrics* aTextPerf, gfxUserFontSet* aUserFontSet, gfxFloat aDevToCssSize); virtual ~gfxFontGroup(); // Returns first valid font in the fontlist or default font. // Initiates userfont loads if userfont not loaded. // aGeneric: if non-null, returns the CSS generic type that was mapped to // this font gfxFont* GetFirstValidFont(uint32_t aCh = 0x20, mozilla::FontFamilyType* aGeneric = nullptr); // Returns the first font in the font-group that has an OpenType MATH table, // or null if no such font is available. The GetMathConstant methods may be // called on the returned font. gfxFont* GetFirstMathFont(); const gfxFontStyle* GetStyle() const { return &mStyle; } gfxFontGroup* Copy(const gfxFontStyle* aStyle); /** * The listed characters should be treated as invisible and zero-width * when creating textruns. */ static bool IsInvalidChar(uint8_t ch); static bool IsInvalidChar(char16_t ch); /** * Make a textrun for a given string. * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the * textrun will copy it. * This calls FetchGlyphExtents on the textrun. */ already_AddRefed MakeTextRun(const char16_t* aString, uint32_t aLength, const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, gfxMissingFontRecorder* aMFR); /** * Make a textrun for a given string. * If aText is not persistent (aFlags & TEXT_IS_PERSISTENT), the * textrun will copy it. * This calls FetchGlyphExtents on the textrun. */ already_AddRefed MakeTextRun(const uint8_t* aString, uint32_t aLength, const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, gfxMissingFontRecorder* aMFR); /** * Textrun creation helper for clients that don't want to pass * a full Parameters record. */ template already_AddRefed MakeTextRun(const T* aString, uint32_t aLength, DrawTarget* aRefDrawTarget, int32_t aAppUnitsPerDevUnit, mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2, gfxMissingFontRecorder* aMFR) { gfxTextRunFactory::Parameters params = { aRefDrawTarget, nullptr, nullptr, nullptr, 0, aAppUnitsPerDevUnit}; return MakeTextRun(aString, aLength, ¶ms, aFlags, aFlags2, aMFR); } // Get the (possibly-cached) width of the hyphen character. gfxFloat GetHyphenWidth(const gfxTextRun::PropertyProvider* aProvider); /** * Make a text run representing a single hyphen character. * This will use U+2010 HYPHEN if available in the first font, * otherwise fall back to U+002D HYPHEN-MINUS. * The caller is responsible for deleting the returned text run * when no longer required. */ already_AddRefed MakeHyphenTextRun(DrawTarget* aDrawTarget, uint32_t aAppUnitsPerDevUnit); /** * Check whether a given font (specified by its gfxFontEntry) * is already in the fontgroup's list of actual fonts */ bool HasFont(const gfxFontEntry* aFontEntry); // This returns the preferred underline for this font group. // Some CJK fonts have wrong underline offset in its metrics. // If this group has such "bad" font, each platform's gfxFontGroup // initialized mUnderlineOffset. The value should be lower value of // first font's metrics and the bad font's metrics. Otherwise, this // returns from first font's metrics. enum { UNDERLINE_OFFSET_NOT_SET = INT16_MAX }; gfxFloat GetUnderlineOffset(); gfxFont* FindFontForChar(uint32_t ch, uint32_t prevCh, uint32_t aNextCh, Script aRunScript, gfxFont* aPrevMatchedFont, gfxTextRange::MatchType* aMatchType); gfxUserFontSet* GetUserFontSet(); // With downloadable fonts, the composition of the font group can change as // fonts are downloaded for each change in state of the user font set, the // generation value is bumped to avoid picking up previously created text runs // in the text run word cache. For font groups based on stylesheets with no // @font-face rule, this always returns 0. uint64_t GetGeneration(); // generation of the latest fontset rebuild, 0 when no fontset present uint64_t GetRebuildGeneration(); // used when logging text performance gfxTextPerfMetrics* GetTextPerfMetrics() { return mTextPerf; } // This will call UpdateUserFonts() if the user font set is changed. void SetUserFontSet(gfxUserFontSet* aUserFontSet); void ClearCachedData() { mUnderlineOffset = UNDERLINE_OFFSET_NOT_SET; mSkipDrawing = false; mHyphenWidth = -1; mCachedEllipsisTextRun = nullptr; } // If there is a user font set, check to see whether the font list or any // caches need updating. void UpdateUserFonts(); // search for a specific userfont in the list of fonts bool ContainsUserFont(const gfxUserFontEntry* aUserFont); bool ShouldSkipDrawing() const { return mSkipDrawing; } class LazyReferenceDrawTargetGetter { public: virtual already_AddRefed GetRefDrawTarget() = 0; }; // The gfxFontGroup keeps ownership of this textrun. // It is only guaranteed to exist until the next call to GetEllipsisTextRun // (which might use a different appUnitsPerDev value or flags) for the font // group, or until UpdateUserFonts is called, or the fontgroup is destroyed. // Get it/use it/forget it :) - don't keep a reference that might go stale. gfxTextRun* GetEllipsisTextRun( int32_t aAppUnitsPerDevPixel, mozilla::gfx::ShapedTextFlags aFlags, LazyReferenceDrawTargetGetter& aRefDrawTargetGetter); protected: // search through pref fonts for a character, return nullptr if no matching // pref font gfxFont* WhichPrefFontSupportsChar(uint32_t aCh, uint32_t aNextCh); gfxFont* WhichSystemFontSupportsChar(uint32_t aCh, uint32_t aNextCh, Script aRunScript); template void ComputeRanges(nsTArray& mRanges, const T* aString, uint32_t aLength, Script aRunScript, mozilla::gfx::ShapedTextFlags aOrientation); class FamilyFace { public: FamilyFace() : mFamily(nullptr), mFontEntry(nullptr), mGeneric(mozilla::eFamily_none), mFontCreated(false), mLoading(false), mInvalid(false), mCheckForFallbackFaces(false) {} FamilyFace(gfxFontFamily* aFamily, gfxFont* aFont, mozilla::FontFamilyType aGeneric) : mFamily(aFamily), mGeneric(aGeneric), mFontCreated(true), mLoading(false), mInvalid(false), mCheckForFallbackFaces(false) { NS_ASSERTION(aFont, "font pointer must not be null"); NS_ASSERTION(!aFamily || aFamily->ContainsFace(aFont->GetFontEntry()), "font is not a member of the given family"); mFont = aFont; NS_ADDREF(aFont); } FamilyFace(gfxFontFamily* aFamily, gfxFontEntry* aFontEntry, mozilla::FontFamilyType aGeneric) : mFamily(aFamily), mGeneric(aGeneric), mFontCreated(false), mLoading(false), mInvalid(false), mCheckForFallbackFaces(false) { NS_ASSERTION(aFontEntry, "font entry pointer must not be null"); NS_ASSERTION(!aFamily || aFamily->ContainsFace(aFontEntry), "font is not a member of the given family"); mFontEntry = aFontEntry; NS_ADDREF(aFontEntry); } FamilyFace(const FamilyFace& aOtherFamilyFace) : mFamily(aOtherFamilyFace.mFamily), mGeneric(aOtherFamilyFace.mGeneric), mFontCreated(aOtherFamilyFace.mFontCreated), mLoading(aOtherFamilyFace.mLoading), mInvalid(aOtherFamilyFace.mInvalid), mCheckForFallbackFaces(aOtherFamilyFace.mCheckForFallbackFaces) { if (mFontCreated) { mFont = aOtherFamilyFace.mFont; NS_ADDREF(mFont); } else { mFontEntry = aOtherFamilyFace.mFontEntry; NS_IF_ADDREF(mFontEntry); } } ~FamilyFace() { if (mFontCreated) { NS_RELEASE(mFont); } else { NS_IF_RELEASE(mFontEntry); } } FamilyFace& operator=(const FamilyFace& aOther) { if (mFontCreated) { NS_RELEASE(mFont); } else { NS_IF_RELEASE(mFontEntry); } mFamily = aOther.mFamily; mGeneric = aOther.mGeneric; mFontCreated = aOther.mFontCreated; mLoading = aOther.mLoading; mInvalid = aOther.mInvalid; if (mFontCreated) { mFont = aOther.mFont; NS_ADDREF(mFont); } else { mFontEntry = aOther.mFontEntry; NS_IF_ADDREF(mFontEntry); } return *this; } gfxFontFamily* Family() const { return mFamily.get(); } gfxFont* Font() const { return mFontCreated ? mFont : nullptr; } gfxFontEntry* FontEntry() const { return mFontCreated ? mFont->GetFontEntry() : mFontEntry; } mozilla::FontFamilyType Generic() const { return mGeneric; } bool IsUserFontContainer() const { return FontEntry()->mIsUserFontContainer; } bool IsLoading() const { return mLoading; } bool IsInvalid() const { return mInvalid; } void CheckState(bool& aSkipDrawing); void SetLoading(bool aIsLoading) { mLoading = aIsLoading; } void SetInvalid() { mInvalid = true; } bool CheckForFallbackFaces() const { return mCheckForFallbackFaces; } void SetCheckForFallbackFaces() { mCheckForFallbackFaces = true; } void SetFont(gfxFont* aFont) { NS_ASSERTION(aFont, "font pointer must not be null"); NS_ADDREF(aFont); if (mFontCreated) { NS_RELEASE(mFont); } else { NS_IF_RELEASE(mFontEntry); } mFont = aFont; mFontCreated = true; mLoading = false; } bool EqualsUserFont(const gfxUserFontEntry* aUserFont) const; private: RefPtr mFamily; // either a font or a font entry exists union { // Whichever of these fields is actually present will be a strong // reference, with refcounting handled manually. gfxFont* MOZ_OWNING_REF mFont; gfxFontEntry* MOZ_OWNING_REF mFontEntry; }; mozilla::FontFamilyType mGeneric; bool mFontCreated : 1; bool mLoading : 1; bool mInvalid : 1; bool mCheckForFallbackFaces : 1; }; // List of font families, either named or generic. // Generic names map to system pref fonts based on language. mozilla::FontFamilyList mFamilyList; // Fontlist containing a font entry for each family found. gfxFont objects // are created as needed and userfont loads are initiated when needed. // Code should be careful about addressing this array directly. nsTArray mFonts; RefPtr mDefaultFont; gfxFontStyle mStyle; gfxFloat mUnderlineOffset; gfxFloat mHyphenWidth; gfxFloat mDevToCssSize; RefPtr mUserFontSet; uint64_t mCurrGeneration; // track the current user font set generation, // rebuild font list if needed gfxTextPerfMetrics* mTextPerf; // Cache a textrun representing an ellipsis (useful for CSS text-overflow) // at a specific appUnitsPerDevPixel size and orientation RefPtr mCachedEllipsisTextRun; // cache the most recent pref font to avoid general pref font lookup RefPtr mLastPrefFamily; RefPtr mLastPrefFont; eFontPrefLang mLastPrefLang; // lang group for last pref font eFontPrefLang mPageLang; bool mLastPrefFirstFont; // is this the first font in the list of pref fonts // for this lang group? bool mSkipDrawing; // hide text while waiting for a font // download to complete (or fallback // timer to fire) /** * Textrun creation short-cuts for special cases where we don't need to * call a font shaper to generate glyphs. */ already_AddRefed MakeEmptyTextRun( const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2); already_AddRefed MakeSpaceTextRun( const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2); already_AddRefed MakeBlankTextRun( uint32_t aLength, const Parameters* aParams, mozilla::gfx::ShapedTextFlags aFlags, nsTextFrameUtils::Flags aFlags2); // Initialize the list of fonts void BuildFontList(); // Get the font at index i within the fontlist. // Will initiate userfont load if not already loaded. // May return null if userfont not loaded or if font invalid gfxFont* GetFontAt(int32_t i, uint32_t aCh = 0x20); // Whether there's a font loading for a given family in the fontlist // for a given character bool FontLoadingForFamily(gfxFontFamily* aFamily, uint32_t aCh) const; // will always return a font or force a shutdown gfxFont* GetDefaultFont(); // Init this font group's font metrics. If there no bad fonts, you don't need // to call this. But if there are one or more bad fonts which have bad // underline offset, you should call this with the *first* bad font. void InitMetricsForBadFont(gfxFont* aBadFont); // Set up the textrun glyphs for an entire text run: // find script runs, and then call InitScriptRun for each template void InitTextRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const T* aString, uint32_t aLength, gfxMissingFontRecorder* aMFR); // InitTextRun helper to handle a single script run, by finding font ranges // and calling each font's InitTextRun() as appropriate template void InitScriptRun(DrawTarget* aDrawTarget, gfxTextRun* aTextRun, const T* aString, uint32_t aScriptRunStart, uint32_t aScriptRunEnd, Script aRunScript, gfxMissingFontRecorder* aMFR); // Helper for font-matching: // search all faces in a family for a fallback in cases where it's unclear // whether the family might have a font for a given character gfxFont* FindFallbackFaceForChar(gfxFontFamily* aFamily, uint32_t aCh); // helper methods for looking up fonts // lookup and add a font with a given name (i.e. *not* a generic!) void AddPlatformFont(const nsACString& aName, nsTArray& aFamilyList); // do style selection and add entries to list void AddFamilyToFontList(gfxFontFamily* aFamily, mozilla::FontFamilyType aGeneric); }; // A "missing font recorder" is to be used during text-run creation to keep // a record of any scripts encountered for which font coverage was lacking; // when Flush() is called, it sends a notification that front-end code can use // to download fonts on demand (or whatever else it wants to do). #define GFX_MISSING_FONTS_NOTIFY_PREF "gfx.missing_fonts.notify" class gfxMissingFontRecorder { public: gfxMissingFontRecorder() { MOZ_COUNT_CTOR(gfxMissingFontRecorder); memset(&mMissingFonts, 0, sizeof(mMissingFonts)); } ~gfxMissingFontRecorder() { #ifdef DEBUG for (uint32_t i = 0; i < kNumScriptBitsWords; i++) { NS_ASSERTION(mMissingFonts[i] == 0, "failed to flush the missing-font recorder"); } #endif MOZ_COUNT_DTOR(gfxMissingFontRecorder); } // record this script code in our mMissingFonts bitset void RecordScript(mozilla::unicode::Script aScriptCode) { mMissingFonts[static_cast(aScriptCode) >> 5] |= (1 << (static_cast(aScriptCode) & 0x1f)); } // send a notification of any missing-scripts that have been // recorded, and clear the mMissingFonts set for re-use void Flush(); // forget any missing-scripts that have been recorded up to now; // called before discarding a recorder we no longer care about void Clear() { memset(&mMissingFonts, 0, sizeof(mMissingFonts)); } private: // Number of 32-bit words needed for the missing-script flags static const uint32_t kNumScriptBitsWords = ((static_cast(mozilla::unicode::Script::NUM_SCRIPT_CODES) + 31) / 32); uint32_t mMissingFonts[kNumScriptBitsWords]; }; #endif