michael@0: /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsFontMetrics.h" michael@0: #include // for floor, ceil michael@0: #include // for max michael@0: #include "gfxPlatform.h" // for gfxPlatform michael@0: #include "gfxPoint.h" // for gfxPoint michael@0: #include "gfxRect.h" // for gfxRect michael@0: #include "gfxTypes.h" // for gfxFloat michael@0: #include "nsBoundingMetrics.h" // for nsBoundingMetrics michael@0: #include "nsDebug.h" // for NS_ERROR, NS_ABORT_IF_FALSE michael@0: #include "nsDeviceContext.h" // for nsDeviceContext michael@0: #include "nsIAtom.h" // for nsIAtom michael@0: #include "nsMathUtils.h" // for NS_round michael@0: #include "nsRenderingContext.h" // for nsRenderingContext michael@0: #include "nsString.h" // for nsString michael@0: #include "nsStyleConsts.h" // for NS_STYLE_HYPHENS_NONE michael@0: michael@0: class gfxUserFontSet; michael@0: michael@0: namespace { michael@0: michael@0: class AutoTextRun { michael@0: public: michael@0: AutoTextRun(nsFontMetrics* aMetrics, nsRenderingContext* aRC, michael@0: const char* aString, int32_t aLength) michael@0: { michael@0: mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun( michael@0: reinterpret_cast(aString), aLength, michael@0: aRC->ThebesContext(), michael@0: aMetrics->AppUnitsPerDevPixel(), michael@0: ComputeFlags(aMetrics)); michael@0: } michael@0: michael@0: AutoTextRun(nsFontMetrics* aMetrics, nsRenderingContext* aRC, michael@0: const char16_t* aString, int32_t aLength) michael@0: { michael@0: mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun( michael@0: aString, aLength, michael@0: aRC->ThebesContext(), michael@0: aMetrics->AppUnitsPerDevPixel(), michael@0: ComputeFlags(aMetrics)); michael@0: } michael@0: michael@0: gfxTextRun *get() { return mTextRun; } michael@0: gfxTextRun *operator->() { return mTextRun; } michael@0: michael@0: private: michael@0: static uint32_t ComputeFlags(nsFontMetrics* aMetrics) { michael@0: uint32_t flags = 0; michael@0: if (aMetrics->GetTextRunRTL()) { michael@0: flags |= gfxTextRunFactory::TEXT_IS_RTL; michael@0: } michael@0: return flags; michael@0: } michael@0: michael@0: nsAutoPtr mTextRun; michael@0: }; michael@0: michael@0: class StubPropertyProvider : public gfxTextRun::PropertyProvider { michael@0: public: michael@0: virtual void GetHyphenationBreaks(uint32_t aStart, uint32_t aLength, michael@0: bool* aBreakBefore) { michael@0: NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText"); michael@0: } michael@0: virtual int8_t GetHyphensOption() { michael@0: NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText"); michael@0: return NS_STYLE_HYPHENS_NONE; michael@0: } michael@0: virtual gfxFloat GetHyphenWidth() { michael@0: NS_ERROR("This shouldn't be called because we never enable hyphens"); michael@0: return 0; michael@0: } michael@0: virtual already_AddRefed GetContext() { michael@0: NS_ERROR("This shouldn't be called because we never enable hyphens"); michael@0: return nullptr; michael@0: } michael@0: virtual uint32_t GetAppUnitsPerDevUnit() { michael@0: NS_ERROR("This shouldn't be called because we never enable hyphens"); michael@0: return 60; michael@0: } michael@0: virtual void GetSpacing(uint32_t aStart, uint32_t aLength, michael@0: Spacing* aSpacing) { michael@0: NS_ERROR("This shouldn't be called because we never enable spacing"); michael@0: } michael@0: }; michael@0: michael@0: } // anon namespace michael@0: michael@0: nsFontMetrics::nsFontMetrics() michael@0: : mDeviceContext(nullptr), mP2A(0), mTextRunRTL(false) michael@0: { michael@0: } michael@0: michael@0: nsFontMetrics::~nsFontMetrics() michael@0: { michael@0: if (mDeviceContext) michael@0: mDeviceContext->FontMetricsDeleted(this); michael@0: } michael@0: michael@0: nsresult michael@0: nsFontMetrics::Init(const nsFont& aFont, nsIAtom* aLanguage, michael@0: nsDeviceContext *aContext, michael@0: gfxUserFontSet *aUserFontSet, michael@0: gfxTextPerfMetrics *aTextPerf) michael@0: { michael@0: NS_ABORT_IF_FALSE(mP2A == 0, "already initialized"); michael@0: michael@0: mFont = aFont; michael@0: mLanguage = aLanguage; michael@0: mDeviceContext = aContext; michael@0: mP2A = mDeviceContext->AppUnitsPerDevPixel(); michael@0: michael@0: gfxFontStyle style(aFont.style, michael@0: aFont.weight, michael@0: aFont.stretch, michael@0: gfxFloat(aFont.size) / mP2A, michael@0: aLanguage, michael@0: aFont.sizeAdjust, michael@0: aFont.systemFont, michael@0: mDeviceContext->IsPrinterSurface(), michael@0: aFont.languageOverride); michael@0: michael@0: aFont.AddFontFeaturesToStyle(&style); michael@0: michael@0: mFontGroup = gfxPlatform::GetPlatform()-> michael@0: CreateFontGroup(aFont.name, &style, aUserFontSet); michael@0: mFontGroup->SetTextPerfMetrics(aTextPerf); michael@0: if (mFontGroup->FontListLength() < 1) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFontMetrics::Destroy() michael@0: { michael@0: mDeviceContext = nullptr; michael@0: } michael@0: michael@0: // XXXTODO get rid of this macro michael@0: #define ROUND_TO_TWIPS(x) (nscoord)floor(((x) * mP2A) + 0.5) michael@0: #define CEIL_TO_TWIPS(x) (nscoord)ceil((x) * mP2A) michael@0: michael@0: const gfxFont::Metrics& nsFontMetrics::GetMetrics() const michael@0: { michael@0: return mFontGroup->GetFontAt(0)->GetMetrics(); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::XHeight() michael@0: { michael@0: return ROUND_TO_TWIPS(GetMetrics().xHeight); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::SuperscriptOffset() michael@0: { michael@0: return ROUND_TO_TWIPS(GetMetrics().superscriptOffset); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::SubscriptOffset() michael@0: { michael@0: return ROUND_TO_TWIPS(GetMetrics().subscriptOffset); michael@0: } michael@0: michael@0: void michael@0: nsFontMetrics::GetStrikeout(nscoord& aOffset, nscoord& aSize) michael@0: { michael@0: aOffset = ROUND_TO_TWIPS(GetMetrics().strikeoutOffset); michael@0: aSize = ROUND_TO_TWIPS(GetMetrics().strikeoutSize); michael@0: } michael@0: michael@0: void michael@0: nsFontMetrics::GetUnderline(nscoord& aOffset, nscoord& aSize) michael@0: { michael@0: aOffset = ROUND_TO_TWIPS(mFontGroup->GetUnderlineOffset()); michael@0: aSize = ROUND_TO_TWIPS(GetMetrics().underlineSize); michael@0: } michael@0: michael@0: // GetMaxAscent/GetMaxDescent/GetMaxHeight must contain the michael@0: // text-decoration lines drawable area. See bug 421353. michael@0: // BE CAREFUL for rounding each values. The logic MUST be same as michael@0: // nsCSSRendering::GetTextDecorationRectInternal's. michael@0: michael@0: static gfxFloat ComputeMaxDescent(const gfxFont::Metrics& aMetrics, michael@0: gfxFontGroup* aFontGroup) michael@0: { michael@0: gfxFloat offset = floor(-aFontGroup->GetUnderlineOffset() + 0.5); michael@0: gfxFloat size = NS_round(aMetrics.underlineSize); michael@0: gfxFloat minDescent = floor(offset + size + 0.5); michael@0: return std::max(minDescent, aMetrics.maxDescent); michael@0: } michael@0: michael@0: static gfxFloat ComputeMaxAscent(const gfxFont::Metrics& aMetrics) michael@0: { michael@0: return floor(aMetrics.maxAscent + 0.5); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::InternalLeading() michael@0: { michael@0: return ROUND_TO_TWIPS(GetMetrics().internalLeading); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::ExternalLeading() michael@0: { michael@0: return ROUND_TO_TWIPS(GetMetrics().externalLeading); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::EmHeight() michael@0: { michael@0: return ROUND_TO_TWIPS(GetMetrics().emHeight); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::EmAscent() michael@0: { michael@0: return ROUND_TO_TWIPS(GetMetrics().emAscent); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::EmDescent() michael@0: { michael@0: return ROUND_TO_TWIPS(GetMetrics().emDescent); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::MaxHeight() michael@0: { michael@0: return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics())) + michael@0: CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(), mFontGroup)); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::MaxAscent() michael@0: { michael@0: return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics())); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::MaxDescent() michael@0: { michael@0: return CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(), mFontGroup)); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::MaxAdvance() michael@0: { michael@0: return CEIL_TO_TWIPS(GetMetrics().maxAdvance); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::AveCharWidth() michael@0: { michael@0: // Use CEIL instead of ROUND for consistency with GetMaxAdvance michael@0: return CEIL_TO_TWIPS(GetMetrics().aveCharWidth); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::SpaceWidth() michael@0: { michael@0: return CEIL_TO_TWIPS(GetMetrics().spaceWidth); michael@0: } michael@0: michael@0: int32_t michael@0: nsFontMetrics::GetMaxStringLength() michael@0: { michael@0: const gfxFont::Metrics& m = GetMetrics(); michael@0: const double x = 32767.0 / m.maxAdvance; michael@0: int32_t len = (int32_t)floor(x); michael@0: return std::max(1, len); michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::GetWidth(const char* aString, uint32_t aLength, michael@0: nsRenderingContext *aContext) michael@0: { michael@0: if (aLength == 0) michael@0: return 0; michael@0: michael@0: if (aLength == 1 && aString[0] == ' ') michael@0: return SpaceWidth(); michael@0: michael@0: StubPropertyProvider provider; michael@0: AutoTextRun textRun(this, aContext, aString, aLength); michael@0: return textRun.get() ? michael@0: NSToCoordRound(textRun->GetAdvanceWidth(0, aLength, &provider)) : 0; michael@0: } michael@0: michael@0: nscoord michael@0: nsFontMetrics::GetWidth(const char16_t* aString, uint32_t aLength, michael@0: nsRenderingContext *aContext) michael@0: { michael@0: if (aLength == 0) michael@0: return 0; michael@0: michael@0: if (aLength == 1 && aString[0] == ' ') michael@0: return SpaceWidth(); michael@0: michael@0: StubPropertyProvider provider; michael@0: AutoTextRun textRun(this, aContext, aString, aLength); michael@0: return textRun.get() ? michael@0: NSToCoordRound(textRun->GetAdvanceWidth(0, aLength, &provider)) : 0; michael@0: } michael@0: michael@0: // Draw a string using this font handle on the surface passed in. michael@0: void michael@0: nsFontMetrics::DrawString(const char *aString, uint32_t aLength, michael@0: nscoord aX, nscoord aY, michael@0: nsRenderingContext *aContext) michael@0: { michael@0: if (aLength == 0) michael@0: return; michael@0: michael@0: StubPropertyProvider provider; michael@0: AutoTextRun textRun(this, aContext, aString, aLength); michael@0: if (!textRun.get()) { michael@0: return; michael@0: } michael@0: gfxPoint pt(aX, aY); michael@0: if (mTextRunRTL) { michael@0: pt.x += textRun->GetAdvanceWidth(0, aLength, &provider); michael@0: } michael@0: textRun->Draw(aContext->ThebesContext(), pt, DrawMode::GLYPH_FILL, 0, aLength, michael@0: &provider, nullptr, nullptr); michael@0: } michael@0: michael@0: void michael@0: nsFontMetrics::DrawString(const char16_t* aString, uint32_t aLength, michael@0: nscoord aX, nscoord aY, michael@0: nsRenderingContext *aContext, michael@0: nsRenderingContext *aTextRunConstructionContext) michael@0: { michael@0: if (aLength == 0) michael@0: return; michael@0: michael@0: StubPropertyProvider provider; michael@0: AutoTextRun textRun(this, aTextRunConstructionContext, aString, aLength); michael@0: if (!textRun.get()) { michael@0: return; michael@0: } michael@0: gfxPoint pt(aX, aY); michael@0: if (mTextRunRTL) { michael@0: pt.x += textRun->GetAdvanceWidth(0, aLength, &provider); michael@0: } michael@0: textRun->Draw(aContext->ThebesContext(), pt, DrawMode::GLYPH_FILL, 0, aLength, michael@0: &provider, nullptr, nullptr); michael@0: } michael@0: michael@0: static nsBoundingMetrics michael@0: GetTextBoundingMetrics(nsFontMetrics* aMetrics, const char16_t *aString, uint32_t aLength, michael@0: nsRenderingContext *aContext, gfxFont::BoundingBoxType aType) michael@0: { michael@0: if (aLength == 0) michael@0: return nsBoundingMetrics(); michael@0: michael@0: StubPropertyProvider provider; michael@0: AutoTextRun textRun(aMetrics, aContext, aString, aLength); michael@0: nsBoundingMetrics m; michael@0: if (textRun.get()) { michael@0: gfxTextRun::Metrics theMetrics = michael@0: textRun->MeasureText(0, aLength, michael@0: aType, michael@0: aContext->ThebesContext(), &provider); michael@0: michael@0: m.leftBearing = NSToCoordFloor( theMetrics.mBoundingBox.X()); michael@0: m.rightBearing = NSToCoordCeil( theMetrics.mBoundingBox.XMost()); michael@0: m.ascent = NSToCoordCeil( -theMetrics.mBoundingBox.Y()); michael@0: m.descent = NSToCoordCeil( theMetrics.mBoundingBox.YMost()); michael@0: m.width = NSToCoordRound( theMetrics.mAdvanceWidth); michael@0: } michael@0: return m; michael@0: } michael@0: michael@0: nsBoundingMetrics michael@0: nsFontMetrics::GetBoundingMetrics(const char16_t *aString, uint32_t aLength, michael@0: nsRenderingContext *aContext) michael@0: { michael@0: return GetTextBoundingMetrics(this, aString, aLength, aContext, gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS); michael@0: michael@0: } michael@0: michael@0: nsBoundingMetrics michael@0: nsFontMetrics::GetInkBoundsForVisualOverflow(const char16_t *aString, uint32_t aLength, michael@0: nsRenderingContext *aContext) michael@0: { michael@0: return GetTextBoundingMetrics(this, aString, aLength, aContext, gfxFont::LOOSE_INK_EXTENTS); michael@0: } michael@0: