nsFontMetrics.cpp (15290B)
1 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* This Source Code Form is subject to the terms of the Mozilla Public 3 * License, v. 2.0. If a copy of the MPL was not distributed with this 4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 5 6 #include "nsFontMetrics.h" 7 #include <math.h> // for floor, ceil 8 #include <algorithm> // for max 9 #include "gfxContext.h" // for gfxContext 10 #include "gfxFontConstants.h" // for NS_FONT_{SUB,SUPER}SCRIPT_OFFSET_RATIO 11 #include "gfxPlatform.h" // for gfxPlatform 12 #include "gfxPoint.h" // for gfxPoint 13 #include "gfxRect.h" // for gfxRect 14 #include "gfxTextRun.h" // for gfxFontGroup 15 #include "gfxTypes.h" // for gfxFloat 16 #include "nsAtom.h" // for nsAtom 17 #include "nsBoundingMetrics.h" // for nsBoundingMetrics 18 #include "nsDebug.h" // for NS_ERROR 19 #include "nsDeviceContext.h" // for nsDeviceContext 20 #include "nsMathUtils.h" // for NS_round 21 #include "nsPresContext.h" // for nsPresContext 22 #include "nsString.h" // for nsString 23 #include "nsStyleConsts.h" // for StyleHyphens::None 24 #include "mozilla/Assertions.h" // for MOZ_ASSERT 25 #include "mozilla/UniquePtr.h" // for UniquePtr 26 27 class gfxUserFontSet; 28 using namespace mozilla; 29 30 namespace { 31 32 class AutoTextRun { 33 public: 34 typedef mozilla::gfx::DrawTarget DrawTarget; 35 36 AutoTextRun(const nsFontMetrics* aMetrics, DrawTarget* aDrawTarget, 37 const char* aString, uint32_t aLength) { 38 mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun( 39 reinterpret_cast<const uint8_t*>(aString), aLength, aDrawTarget, 40 aMetrics->AppUnitsPerDevPixel(), ComputeFlags(aMetrics), 41 nsTextFrameUtils::Flags(), nullptr); 42 } 43 44 AutoTextRun(const nsFontMetrics* aMetrics, DrawTarget* aDrawTarget, 45 const char16_t* aString, uint32_t aLength) { 46 mTextRun = aMetrics->GetThebesFontGroup()->MakeTextRun( 47 aString, aLength, aDrawTarget, aMetrics->AppUnitsPerDevPixel(), 48 ComputeFlags(aMetrics), nsTextFrameUtils::Flags(), nullptr); 49 } 50 51 gfxTextRun* get() const { return mTextRun.get(); } 52 gfxTextRun* operator->() const { return mTextRun.get(); } 53 54 private: 55 static gfx::ShapedTextFlags ComputeFlags(const nsFontMetrics* aMetrics) { 56 gfx::ShapedTextFlags flags = gfx::ShapedTextFlags(); 57 if (aMetrics->GetTextRunRTL()) { 58 flags |= gfx::ShapedTextFlags::TEXT_IS_RTL; 59 } 60 if (aMetrics->GetVertical()) { 61 switch (aMetrics->GetTextOrientation()) { 62 case StyleTextOrientation::Mixed: 63 flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED; 64 break; 65 case StyleTextOrientation::Upright: 66 flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT; 67 break; 68 case StyleTextOrientation::Sideways: 69 flags |= gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT; 70 break; 71 } 72 } 73 return flags; 74 } 75 76 RefPtr<gfxTextRun> mTextRun; 77 }; 78 79 class StubPropertyProvider final : public gfxTextRun::PropertyProvider { 80 public: 81 void GetHyphenationBreaks( 82 gfxTextRun::Range aRange, 83 gfxTextRun::HyphenType* aBreakBefore) const override { 84 NS_ERROR( 85 "This shouldn't be called because we never call BreakAndMeasureText"); 86 } 87 mozilla::StyleHyphens GetHyphensOption() const override { 88 NS_ERROR( 89 "This shouldn't be called because we never call BreakAndMeasureText"); 90 return mozilla::StyleHyphens::None; 91 } 92 gfxFloat GetHyphenWidth() const override { 93 NS_ERROR("This shouldn't be called because we never enable hyphens"); 94 return 0; 95 } 96 already_AddRefed<mozilla::gfx::DrawTarget> GetDrawTarget() const override { 97 NS_ERROR("This shouldn't be called because we never enable hyphens"); 98 return nullptr; 99 } 100 uint32_t GetAppUnitsPerDevUnit() const override { 101 NS_ERROR("This shouldn't be called because we never enable hyphens"); 102 return 60; 103 } 104 bool GetSpacing(gfxTextRun::Range aRange, Spacing* aSpacing) const override { 105 NS_ERROR("This shouldn't be called because we never enable spacing"); 106 return false; 107 } 108 gfx::ShapedTextFlags GetShapedTextFlags() const override { 109 NS_ERROR("This shouldn't be called because we never enable hyphens"); 110 return gfx::ShapedTextFlags(); 111 } 112 }; 113 114 } // namespace 115 116 nsFontMetrics::nsFontMetrics(const nsFont& aFont, const Params& aParams, 117 nsPresContext* aContext) 118 : mFont(aFont), 119 mLanguage(aParams.language), 120 mPresContext(aContext), 121 mP2A(aContext->DeviceContext()->AppUnitsPerDevPixel()), 122 mOrientation(aParams.orientation), 123 mExplicitLanguage(aParams.explicitLanguage), 124 #ifdef XP_WIN 125 mAllowForceGDIClassic(aParams.allowForceGDIClassic), 126 #endif 127 mTextRunRTL(false), 128 mVertical(false), 129 mTextOrientation(mozilla::StyleTextOrientation::Mixed) { 130 gfxFontStyle style(aFont.style, aFont.weight, aFont.stretch, 131 gfxFloat(aFont.size.ToAppUnits()) / mP2A, aFont.sizeAdjust, 132 aFont.family.is_system_font, 133 aContext->DeviceContext()->IsPrinterContext(), 134 #ifdef XP_WIN 135 mAllowForceGDIClassic, 136 #endif 137 aFont.synthesisWeight == StyleFontSynthesis::Auto, 138 aFont.synthesisStyle, 139 aFont.synthesisSmallCaps == StyleFontSynthesis::Auto, 140 aFont.synthesisPosition == StyleFontSynthesis::Auto, 141 aFont.languageOverride); 142 143 aFont.AddFontFeaturesToStyle(&style, mOrientation == eVertical); 144 style.featureValueLookup = aParams.featureValueLookup; 145 146 aFont.AddFontVariationsToStyle(&style); 147 148 gfxFloat devToCssSize = gfxFloat(mP2A) / gfxFloat(AppUnitsPerCSSPixel()); 149 mFontGroup = new gfxFontGroup( 150 mPresContext, aFont.family.families, &style, mLanguage, mExplicitLanguage, 151 aParams.textPerf, aParams.userFontSet, devToCssSize, aFont.variantEmoji); 152 } 153 154 nsFontMetrics::~nsFontMetrics() { 155 // Should not be dropped by stylo 156 MOZ_ASSERT(NS_IsMainThread()); 157 if (mPresContext) { 158 mPresContext->FontMetricsDeleted(this); 159 } 160 } 161 162 void nsFontMetrics::Destroy() { mPresContext = nullptr; } 163 164 // XXXTODO get rid of this macro 165 #define ROUND_TO_TWIPS(x) (nscoord) floor(((x) * mP2A) + 0.5) 166 #define CEIL_TO_TWIPS(x) (nscoord) ceil((x) * mP2A) 167 168 static const gfxFont::Metrics& GetMetrics( 169 const nsFontMetrics* aFontMetrics, 170 nsFontMetrics::FontOrientation aOrientation) { 171 RefPtr<gfxFont> font = 172 aFontMetrics->GetThebesFontGroup()->GetFirstValidFont(); 173 return font->GetMetrics(aOrientation); 174 } 175 176 static const gfxFont::Metrics& GetMetrics(const nsFontMetrics* aFontMetrics) { 177 return GetMetrics(aFontMetrics, aFontMetrics->Orientation()); 178 } 179 180 nscoord nsFontMetrics::XHeight() const { 181 return ROUND_TO_TWIPS(GetMetrics(this).xHeight); 182 } 183 184 nscoord nsFontMetrics::CapHeight() const { 185 return ROUND_TO_TWIPS(GetMetrics(this).capHeight); 186 } 187 188 nscoord nsFontMetrics::SuperscriptOffset() const { 189 return ROUND_TO_TWIPS(GetMetrics(this).emHeight * 190 NS_FONT_SUPERSCRIPT_OFFSET_RATIO); 191 } 192 193 nscoord nsFontMetrics::SubscriptOffset() const { 194 return ROUND_TO_TWIPS(GetMetrics(this).emHeight * 195 NS_FONT_SUBSCRIPT_OFFSET_RATIO); 196 } 197 198 void nsFontMetrics::GetStrikeout(nscoord& aOffset, nscoord& aSize) const { 199 aOffset = ROUND_TO_TWIPS(GetMetrics(this).strikeoutOffset); 200 aSize = ROUND_TO_TWIPS(GetMetrics(this).strikeoutSize); 201 } 202 203 void nsFontMetrics::GetUnderline(nscoord& aOffset, nscoord& aSize) const { 204 aOffset = ROUND_TO_TWIPS(mFontGroup->GetUnderlineOffset()); 205 aSize = ROUND_TO_TWIPS(GetMetrics(this).underlineSize); 206 } 207 208 // GetMaxAscent/GetMaxDescent/GetMaxHeight must contain the 209 // text-decoration lines drawable area. See bug 421353. 210 // BE CAREFUL for rounding each values. The logic MUST be same as 211 // nsCSSRendering::GetTextDecorationRectInternal's. 212 213 static gfxFloat ComputeMaxDescent(const gfxFont::Metrics& aMetrics, 214 gfxFontGroup* aFontGroup) { 215 gfxFloat offset = floor(-aFontGroup->GetUnderlineOffset() + 0.5); 216 gfxFloat size = NS_round(aMetrics.underlineSize); 217 gfxFloat minDescent = offset + size; 218 return floor(std::max(minDescent, aMetrics.maxDescent) + 0.5); 219 } 220 221 static gfxFloat ComputeMaxAscent(const gfxFont::Metrics& aMetrics) { 222 return floor(aMetrics.maxAscent + 0.5); 223 } 224 225 nscoord nsFontMetrics::InternalLeading() const { 226 return ROUND_TO_TWIPS(GetMetrics(this).internalLeading); 227 } 228 229 nscoord nsFontMetrics::ExternalLeading() const { 230 return ROUND_TO_TWIPS(GetMetrics(this).externalLeading); 231 } 232 233 nscoord nsFontMetrics::EmHeight() const { 234 return ROUND_TO_TWIPS(GetMetrics(this).emHeight); 235 } 236 237 nscoord nsFontMetrics::TrimmedAscent() const { 238 const auto& m = GetMetrics(this); 239 return ROUND_TO_TWIPS(std::max(0.0, m.maxAscent - m.internalLeading / 2)); 240 } 241 242 nscoord nsFontMetrics::TrimmedDescent() const { 243 const auto& m = GetMetrics(this); 244 return ROUND_TO_TWIPS(std::max(0.0, m.maxDescent - m.internalLeading / 2)); 245 } 246 247 nscoord nsFontMetrics::MaxHeight() const { 248 return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this))) + 249 CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup)); 250 } 251 252 nscoord nsFontMetrics::MaxAscent() const { 253 return CEIL_TO_TWIPS(ComputeMaxAscent(GetMetrics(this))); 254 } 255 256 nscoord nsFontMetrics::MaxDescent() const { 257 return CEIL_TO_TWIPS(ComputeMaxDescent(GetMetrics(this), mFontGroup)); 258 } 259 260 nscoord nsFontMetrics::MaxAdvance() const { 261 return CEIL_TO_TWIPS(GetMetrics(this).maxAdvance); 262 } 263 264 nscoord nsFontMetrics::AveCharWidth() const { 265 // Use CEIL instead of ROUND for consistency with GetMaxAdvance 266 return CEIL_TO_TWIPS(GetMetrics(this).aveCharWidth); 267 } 268 269 nscoord nsFontMetrics::ZeroOrAveCharWidth() const { 270 return CEIL_TO_TWIPS(GetMetrics(this).ZeroOrAveCharWidth()); 271 } 272 273 nscoord nsFontMetrics::SpaceWidth() const { 274 // For vertical text with mixed or sideways orientation, we want the 275 // width of a horizontal space (even if we're using vertical line-spacing 276 // metrics, as with "writing-mode:vertical-*;text-orientation:mixed"). 277 return CEIL_TO_TWIPS( 278 GetMetrics(this, 279 mVertical && mTextOrientation == StyleTextOrientation::Upright 280 ? eVertical 281 : eHorizontal) 282 .spaceWidth); 283 } 284 285 nscoord nsFontMetrics::InterScriptSpacingWidth() const { 286 const auto& m = GetMetrics(this); 287 // If there is no advance measure of the CJK water ideograph, use 1em instead. 288 // https://drafts.csswg.org/css-values-4/#ic 289 LayoutDeviceDoubleCoord ic = 290 m.ideographicWidth >= 0.0 ? m.ideographicWidth : m.emHeight; 291 292 // The inter-script spacing is defined as 1/8 of the CJK advance measure, i.e. 293 // 0.125ic: https://drafts.csswg.org/css-text-4/#inter-script-spacing 294 constexpr double kFraction = 0.125; 295 return LayoutDevicePixel::ToAppUnits(ic * kFraction, AppUnitsPerDevPixel()); 296 } 297 298 int32_t nsFontMetrics::GetMaxStringLength() const { 299 const double x = 32767.0 / std::max(1.0, GetMetrics(this).maxAdvance); 300 int32_t len = (int32_t)floor(x); 301 return std::max(1, len); 302 } 303 304 nscoord nsFontMetrics::GetWidth(const char* aString, uint32_t aLength, 305 DrawTarget* aDrawTarget) const { 306 if (aLength == 0) { 307 return 0; 308 } 309 if (aLength == 1 && aString[0] == ' ') { 310 return SpaceWidth(); 311 } 312 StubPropertyProvider provider; 313 AutoTextRun textRun(this, aDrawTarget, aString, aLength); 314 if (textRun.get()) { 315 return NSToCoordRound( 316 textRun->GetAdvanceWidth(gfxTextRun::Range(0, aLength), &provider)); 317 } 318 return 0; 319 } 320 321 nscoord nsFontMetrics::GetWidth(const char16_t* aString, uint32_t aLength, 322 DrawTarget* aDrawTarget) const { 323 if (aLength == 0) { 324 return 0; 325 } 326 if (aLength == 1 && aString[0] == ' ') { 327 return SpaceWidth(); 328 } 329 StubPropertyProvider provider; 330 AutoTextRun textRun(this, aDrawTarget, aString, aLength); 331 if (textRun.get()) { 332 return NSToCoordRound( 333 textRun->GetAdvanceWidth(gfxTextRun::Range(0, aLength), &provider)); 334 } 335 return 0; 336 } 337 338 // Draw a string using this font handle on the surface passed in. 339 void nsFontMetrics::DrawString(const char* aString, uint32_t aLength, 340 nscoord aX, nscoord aY, 341 gfxContext* aContext) const { 342 if (aLength == 0) { 343 return; 344 } 345 StubPropertyProvider provider; 346 AutoTextRun textRun(this, aContext->GetDrawTarget(), aString, aLength); 347 if (!textRun.get()) { 348 return; 349 } 350 gfx::Point pt(aX, aY); 351 gfxTextRun::Range range(0, aLength); 352 if (mTextRunRTL) { 353 if (mVertical) { 354 pt.y += textRun->GetAdvanceWidth(range, &provider); 355 } else { 356 pt.x += textRun->GetAdvanceWidth(range, &provider); 357 } 358 } 359 mozilla::gfx::PaletteCache paletteCache; 360 gfxTextRun::DrawParams params(aContext, paletteCache); 361 params.provider = &provider; 362 textRun->Draw(range, pt, params); 363 } 364 365 void nsFontMetrics::DrawString( 366 const char16_t* aString, uint32_t aLength, nscoord aX, nscoord aY, 367 gfxContext* aContext, DrawTarget* aTextRunConstructionDrawTarget) const { 368 if (aLength == 0) { 369 return; 370 } 371 StubPropertyProvider provider; 372 AutoTextRun textRun(this, aTextRunConstructionDrawTarget, aString, aLength); 373 if (!textRun.get()) { 374 return; 375 } 376 gfx::Point pt(aX, aY); 377 gfxTextRun::Range range(0, aLength); 378 if (mTextRunRTL) { 379 if (mVertical) { 380 pt.y += textRun->GetAdvanceWidth(range, &provider); 381 } else { 382 pt.x += textRun->GetAdvanceWidth(range, &provider); 383 } 384 } 385 mozilla::gfx::PaletteCache paletteCache; 386 gfxTextRun::DrawParams params(aContext, paletteCache); 387 params.provider = &provider; 388 textRun->Draw(range, pt, params); 389 } 390 391 static nsBoundingMetrics GetTextBoundingMetrics( 392 const nsFontMetrics* aMetrics, const char16_t* aString, uint32_t aLength, 393 mozilla::gfx::DrawTarget* aDrawTarget, gfxFont::BoundingBoxType aType) { 394 if (aLength == 0) { 395 return nsBoundingMetrics(); 396 } 397 StubPropertyProvider provider; 398 AutoTextRun textRun(aMetrics, aDrawTarget, aString, aLength); 399 nsBoundingMetrics m; 400 if (textRun.get()) { 401 gfxTextRun::Metrics theMetrics = textRun->MeasureText( 402 gfxTextRun::Range(0, aLength), aType, aDrawTarget, &provider); 403 404 m.leftBearing = NSToCoordFloor(theMetrics.mBoundingBox.X()); 405 m.rightBearing = NSToCoordCeil(theMetrics.mBoundingBox.XMost()); 406 m.ascent = NSToCoordCeil(-theMetrics.mBoundingBox.Y()); 407 m.descent = NSToCoordCeil(theMetrics.mBoundingBox.YMost()); 408 m.width = NSToCoordRound(theMetrics.mAdvanceWidth); 409 } 410 return m; 411 } 412 413 nsBoundingMetrics nsFontMetrics::GetBoundingMetrics( 414 const char16_t* aString, uint32_t aLength, DrawTarget* aDrawTarget) const { 415 return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget, 416 gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS); 417 } 418 419 nsBoundingMetrics nsFontMetrics::GetInkBoundsForInkOverflow( 420 const char16_t* aString, uint32_t aLength, DrawTarget* aDrawTarget) const { 421 return GetTextBoundingMetrics(this, aString, aLength, aDrawTarget, 422 gfxFont::LOOSE_INK_EXTENTS); 423 } 424 425 gfxUserFontSet* nsFontMetrics::GetUserFontSet() const { 426 return mFontGroup->GetUserFontSet(); 427 }