summaryrefslogtreecommitdiffstats
path: root/gfx/src/nsFontMetrics.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'gfx/src/nsFontMetrics.cpp')
-rw-r--r--gfx/src/nsFontMetrics.cpp405
1 files changed, 405 insertions, 0 deletions
diff --git a/gfx/src/nsFontMetrics.cpp b/gfx/src/nsFontMetrics.cpp
new file mode 100644
index 0000000000..93fd1264b2
--- /dev/null
+++ b/gfx/src/nsFontMetrics.cpp
@@ -0,0 +1,405 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsFontMetrics.h"
+#include <math.h> // for floor, ceil
+#include <algorithm> // for max
+#include "gfxContext.h" // for gfxContext
+#include "gfxFontConstants.h" // for NS_FONT_SYNTHESIS_*
+#include "gfxPlatform.h" // for gfxPlatform
+#include "gfxPoint.h" // for gfxPoint
+#include "gfxRect.h" // for gfxRect
+#include "gfxTextRun.h" // for gfxFontGroup
+#include "gfxTypes.h" // for gfxFloat
+#include "nsAtom.h" // for nsAtom
+#include "nsBoundingMetrics.h" // for nsBoundingMetrics
+#include "nsDebug.h" // for NS_ERROR
+#include "nsDeviceContext.h" // for nsDeviceContext
+#include "nsMathUtils.h" // for NS_round
+#include "nsPresContext.h" // for nsPresContext
+#include "nsString.h" // for nsString
+#include "nsStyleConsts.h" // for StyleHyphens::None
+#include "mozilla/Assertions.h" // for MOZ_ASSERT
+#include "mozilla/UniquePtr.h" // for UniquePtr
+
+class gfxUserFontSet;
+using namespace mozilla;
+
+namespace {
+
+class AutoTextRun {
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ AutoTextRun(const nsFontMetrics* aMetrics, DrawTarget* aDrawTarget,
+ const char* aString, uint32_t aLength) {
+ mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
+ reinterpret_cast<const uint8_t*>(aString), aLength, aDrawTarget,
+ aMetrics->AppUnitsPerDevPixel(), ComputeFlags(aMetrics),
+ nsTextFrameUtils::Flags(), nullptr);
+ }
+
+ AutoTextRun(const nsFontMetrics* aMetrics, DrawTarget* aDrawTarget,
+ const char16_t* aString, uint32_t aLength) {
+ mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun(
+ aString, aLength, aDrawTarget, aMetrics->AppUnitsPerDevPixel(),
+ ComputeFlags(aMetrics), nsTextFrameUtils::Flags(), nullptr);
+ }
+
+ gfxTextRun* get() const { return mTextRun.get(); }
+ gfxTextRun* operator->() const { return mTextRun.get(); }
+
+ private:
+ static gfx::ShapedTextFlags ComputeFlags(const nsFontMetrics* aMetrics) {
+ gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
+ if (aMetrics->GetTextRunRTL()) {
+ flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
+ }
+ if (aMetrics->GetVertical()) {
+ switch (aMetrics->GetTextOrientation()) {
+ case StyleTextOrientation::Mixed:
+ flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED;
+ break;
+ case StyleTextOrientation::Upright:
+ flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ break;
+ case StyleTextOrientation::Sideways:
+ flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ break;
+ }
+ }
+ return flags;
+ }
+
+ RefPtr<gfxTextRun> mTextRun;
+};
+
+class StubPropertyProvider final : public gfxTextRun::PropertyProvider {
+ public:
+ void GetHyphenationBreaks(
+ gfxTextRun::Range aRange,
+ gfxTextRun::HyphenType* aBreakBefore) const override {
+ NS_ERROR(
+ "This shouldn't be called because we never call BreakAndMeasureText");
+ }
+ mozilla::StyleHyphens GetHyphensOption() const override {
+ NS_ERROR(
+ "This shouldn't be called because we never call BreakAndMeasureText");
+ return mozilla::StyleHyphens::None;
+ }
+ gfxFloat GetHyphenWidth() const override {
+ NS_ERROR("This shouldn't be called because we never enable hyphens");
+ return 0;
+ }
+ already_AddRefed<mozilla::gfx::DrawTarget> GetDrawTarget() const override {
+ NS_ERROR("This shouldn't be called because we never enable hyphens");
+ return nullptr;
+ }
+ uint32_t GetAppUnitsPerDevUnit() const override {
+ NS_ERROR("This shouldn't be called because we never enable hyphens");
+ return 60;
+ }
+ void GetSpacing(gfxTextRun::Range aRange, Spacing* aSpacing) const override {
+ NS_ERROR("This shouldn't be called because we never enable spacing");
+ }
+ gfx::ShapedTextFlags GetShapedTextFlags() const override {
+ NS_ERROR("This shouldn't be called because we never enable hyphens");
+ return gfx::ShapedTextFlags();
+ }
+};
+
+} // namespace
+
+nsFontMetrics::nsFontMetrics(const nsFont& aFont, const Params& aParams,
+ nsPresContext* aContext)
+ : mFont(aFont),
+ mLanguage(aParams.language),
+ mPresContext(aContext),
+ mP2A(aContext->DeviceContext()->AppUnitsPerDevPixel()),
+ mOrientation(aParams.orientation),
+ mExplicitLanguage(aParams.explicitLanguage),
+ mTextRunRTL(false),
+ mVertical(false),
+ mTextOrientation(mozilla::StyleTextOrientation::Mixed) {
+ gfxFontStyle style(aFont.style, aFont.weight, aFont.stretch,
+ gfxFloat(aFont.size.ToAppUnits()) / mP2A, aFont.sizeAdjust,
+ aFont.family.is_system_font,
+ aContext->DeviceContext()->IsPrinterContext(),
+ aFont.synthesisWeight == StyleFontSynthesis::Auto,
+ aFont.synthesisStyle == StyleFontSynthesis::Auto,
+ aFont.synthesisSmallCaps == StyleFontSynthesis::Auto,
+ aFont.synthesisPosition == StyleFontSynthesis::Auto,
+ aFont.languageOverride);
+
+ aFont.AddFontFeaturesToStyle(&style, mOrientation == eVertical);
+ style.featureValueLookup = aParams.featureValueLookup;
+
+ aFont.AddFontVariationsToStyle(&style);
+
+ gfxFloat devToCssSize = gfxFloat(mP2A) / gfxFloat(AppUnitsPerCSSPixel());
+ mFontGroup = new gfxFontGroup(
+ mPresContext, aFont.family.families, &style, mLanguage, mExplicitLanguage,
+ aParams.textPerf, aParams.userFontSet, devToCssSize, aFont.variantEmoji);
+}
+
+nsFontMetrics::~nsFontMetrics() {
+ // Should not be dropped by stylo
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mPresContext) {
+ mPresContext->FontMetricsDeleted(this);
+ }
+}
+
+void nsFontMetrics::Destroy() { mPresContext = nullptr; }
+
+// XXXTODO get rid of this macro
+#define ROUND_TO_TWIPS(x) (nscoord) floor(((x) * mP2A) + 0.5)
+#define CEIL_TO_TWIPS(x) (nscoord) ceil((x) * mP2A)
+
+static const gfxFont::Metrics& GetMetrics(
+ const nsFontMetrics* aFontMetrics,
+ nsFontMetrics::FontOrientation aOrientation) {
+ RefPtr<gfxFont> font =
+ aFontMetrics->GetThebesFontGroup()->GetFirstValidFont();
+ return font->GetMetrics(aOrientation);
+}
+
+static const gfxFont::Metrics& GetMetrics(const nsFontMetrics* aFontMetrics) {
+ return GetMetrics(aFontMetrics, aFontMetrics->Orientation());
+}
+
+nscoord nsFontMetrics::XHeight() const {
+ return ROUND_TO_TWIPS(GetMetrics(this).xHeight);
+}
+
+nscoord nsFontMetrics::CapHeight() const {
+ return ROUND_TO_TWIPS(GetMetrics(this).capHeight);
+}
+
+nscoord nsFontMetrics::SuperscriptOffset() const {
+ return ROUND_TO_TWIPS(GetMetrics(this).emHeight *
+ NS_FONT_SUPERSCRIPT_OFFSET_RATIO);
+}
+
+nscoord nsFontMetrics::SubscriptOffset() const {
+ return ROUND_TO_TWIPS(GetMetrics(this).emHeight *
+ NS_FONT_SUBSCRIPT_OFFSET_RATIO);
+}
+
+void nsFontMetrics::GetStrikeout(nscoord& aOffset, nscoord& aSize) const {
+ aOffset = ROUND_TO_TWIPS(GetMetrics(this).strikeoutOffset);
+ aSize = ROUND_TO_TWIPS(GetMetrics(this).strikeoutSize);
+}
+
+void nsFontMetrics::GetUnderline(nscoord& aOffset, nscoord& aSize) const {
+ aOffset = ROUND_TO_TWIPS(mFontGroup->GetUnderlineOffset());
+ aSize = ROUND_TO_TWIPS(GetMetrics(this).underlineSize);
+}
+
+// GetMaxAscent/GetMaxDescent/GetMaxHeight must contain the
+// text-decoration lines drawable area. See bug 421353.
+// BE CAREFUL for rounding each values. The logic MUST be same as
+// nsCSSRendering::GetTextDecorationRectInternal's.
+
+static gfxFloat ComputeMaxDescent(const gfxFont::Metrics& aMetrics,
+ gfxFontGroup* aFontGroup) {
+ gfxFloat offset = floor(-aFontGroup->GetUnderlineOffset() + 0.5);
+ gfxFloat size = NS_round(aMetrics.underlineSize);
+ gfxFloat minDescent = offset + size;
+ return floor(std::max(minDescent, aMetrics.maxDescent) + 0.5);
+}
+
+static gfxFloat ComputeMaxAscent(const gfxFont::Metrics& aMetrics) {
+ return floor(aMetrics.maxAscent + 0.5);
+}
+
+nscoord nsFontMetrics::InternalLeading() const {
+ return ROUND_TO_TWIPS(GetMetrics(this).internalLeading);
+}
+
+nscoord nsFontMetrics::ExternalLeading() const {
+ return ROUND_TO_TWIPS(GetMetrics(this).externalLeading);
+}
+
+nscoord nsFontMetrics::EmHeight() const {
+ return ROUND_TO_TWIPS(GetMetrics(this).emHeight);
+}
+
+nscoord nsFontMetrics::EmAscent() const {
+ return ROUND_TO_TWIPS(GetMetrics(this).emAscent);
+}
+
+nscoord nsFontMetrics::EmDescent() const {
+ return ROUND_TO_TWIPS(GetMetrics(this).emDescent);
+}
+
+nscoord nsFontMetrics::MaxHeight() const {
+ return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this))) +
+ CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup));
+}
+
+nscoord nsFontMetrics::MaxAscent() const {
+ return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this)));
+}
+
+nscoord nsFontMetrics::MaxDescent() const {
+ return CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup));
+}
+
+nscoord nsFontMetrics::MaxAdvance() const {
+ return CEIL_TO_TWIPS(GetMetrics(this).maxAdvance);
+}
+
+nscoord nsFontMetrics::AveCharWidth() const {
+ // Use CEIL instead of ROUND for consistency with GetMaxAdvance
+ return CEIL_TO_TWIPS(GetMetrics(this).aveCharWidth);
+}
+
+nscoord nsFontMetrics::ZeroOrAveCharWidth() const {
+ return CEIL_TO_TWIPS(GetMetrics(this).ZeroOrAveCharWidth());
+}
+
+nscoord nsFontMetrics::SpaceWidth() const {
+ // For vertical text with mixed or sideways orientation, we want the
+ // width of a horizontal space (even if we're using vertical line-spacing
+ // metrics, as with "writing-mode:vertical-*;text-orientation:mixed").
+ return CEIL_TO_TWIPS(
+ GetMetrics(this,
+ mVertical && mTextOrientation == StyleTextOrientation::Upright
+ ? eVertical
+ : eHorizontal)
+ .spaceWidth);
+}
+
+int32_t nsFontMetrics::GetMaxStringLength() const {
+ const double x = 32767.0 / std::max(1.0, GetMetrics(this).maxAdvance);
+ int32_t len = (int32_t)floor(x);
+ return std::max(1, len);
+}
+
+nscoord nsFontMetrics::GetWidth(const char* aString, uint32_t aLength,
+ DrawTarget* aDrawTarget) const {
+ if (aLength == 0) {
+ return 0;
+ }
+ if (aLength == 1 && aString[0] == ' ') {
+ return SpaceWidth();
+ }
+ StubPropertyProvider provider;
+ AutoTextRun textRun(this, aDrawTarget, aString, aLength);
+ if (textRun.get()) {
+ return NSToCoordRound(
+ textRun->GetAdvanceWidth(gfxTextRun::Range(0, aLength), &provider));
+ }
+ return 0;
+}
+
+nscoord nsFontMetrics::GetWidth(const char16_t* aString, uint32_t aLength,
+ DrawTarget* aDrawTarget) const {
+ if (aLength == 0) {
+ return 0;
+ }
+ if (aLength == 1 && aString[0] == ' ') {
+ return SpaceWidth();
+ }
+ StubPropertyProvider provider;
+ AutoTextRun textRun(this, aDrawTarget, aString, aLength);
+ if (textRun.get()) {
+ return NSToCoordRound(
+ textRun->GetAdvanceWidth(gfxTextRun::Range(0, aLength), &provider));
+ }
+ return 0;
+}
+
+// Draw a string using this font handle on the surface passed in.
+void nsFontMetrics::DrawString(const char* aString, uint32_t aLength,
+ nscoord aX, nscoord aY,
+ gfxContext* aContext) const {
+ if (aLength == 0) {
+ return;
+ }
+ StubPropertyProvider provider;
+ AutoTextRun textRun(this, aContext->GetDrawTarget(), aString, aLength);
+ if (!textRun.get()) {
+ return;
+ }
+ gfx::Point pt(aX, aY);
+ gfxTextRun::Range range(0, aLength);
+ if (mTextRunRTL) {
+ if (mVertical) {
+ pt.y += textRun->GetAdvanceWidth(range, &provider);
+ } else {
+ pt.x += textRun->GetAdvanceWidth(range, &provider);
+ }
+ }
+ mozilla::gfx::PaletteCache paletteCache;
+ gfxTextRun::DrawParams params(aContext, paletteCache);
+ params.provider = &provider;
+ textRun->Draw(range, pt, params);
+}
+
+void nsFontMetrics::DrawString(
+ const char16_t* aString, uint32_t aLength, nscoord aX, nscoord aY,
+ gfxContext* aContext, DrawTarget* aTextRunConstructionDrawTarget) const {
+ if (aLength == 0) {
+ return;
+ }
+ StubPropertyProvider provider;
+ AutoTextRun textRun(this, aTextRunConstructionDrawTarget, aString, aLength);
+ if (!textRun.get()) {
+ return;
+ }
+ gfx::Point pt(aX, aY);
+ gfxTextRun::Range range(0, aLength);
+ if (mTextRunRTL) {
+ if (mVertical) {
+ pt.y += textRun->GetAdvanceWidth(range, &provider);
+ } else {
+ pt.x += textRun->GetAdvanceWidth(range, &provider);
+ }
+ }
+ mozilla::gfx::PaletteCache paletteCache;
+ gfxTextRun::DrawParams params(aContext, paletteCache);
+ params.provider = &provider;
+ textRun->Draw(range, pt, params);
+}
+
+static nsBoundingMetrics GetTextBoundingMetrics(
+ const nsFontMetrics* aMetrics, const char16_t* aString, uint32_t aLength,
+ mozilla::gfx::DrawTarget* aDrawTarget, gfxFont::BoundingBoxType aType) {
+ if (aLength == 0) {
+ return nsBoundingMetrics();
+ }
+ StubPropertyProvider provider;
+ AutoTextRun textRun(aMetrics, aDrawTarget, aString, aLength);
+ nsBoundingMetrics m;
+ if (textRun.get()) {
+ gfxTextRun::Metrics theMetrics = textRun->MeasureText(
+ gfxTextRun::Range(0, aLength), aType, aDrawTarget, &provider);
+
+ m.leftBearing = NSToCoordFloor(theMetrics.mBoundingBox.X());
+ m.rightBearing = NSToCoordCeil(theMetrics.mBoundingBox.XMost());
+ m.ascent = NSToCoordCeil(-theMetrics.mBoundingBox.Y());
+ m.descent = NSToCoordCeil(theMetrics.mBoundingBox.YMost());
+ m.width = NSToCoordRound(theMetrics.mAdvanceWidth);
+ }
+ return m;
+}
+
+nsBoundingMetrics nsFontMetrics::GetBoundingMetrics(
+ const char16_t* aString, uint32_t aLength, DrawTarget* aDrawTarget) const {
+ return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget,
+ gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS);
+}
+
+nsBoundingMetrics nsFontMetrics::GetInkBoundsForInkOverflow(
+ const char16_t* aString, uint32_t aLength, DrawTarget* aDrawTarget) const {
+ return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget,
+ gfxFont::LOOSE_INK_EXTENTS);
+}
+
+gfxUserFontSet* nsFontMetrics::GetUserFontSet() const {
+ return mFontGroup->GetUserFontSet();
+}