Bug 290125 - Create a pref to treat floated ::first-letter more like webkit/blink, not tightly wrapping the glyph extents. r=emilio

Historically, Gecko implemented the behavior allowed by CSS2 whereby a floated ::first-letter is "boxed"
tightly around the glyph shape, rather than using constant font-ascent and -descent metrics which may
leave a lot of blank space depending whether the character has any ascender/descender or not.

However, neither webkit nor blink do this, which leads to webcompat pain when sites are constructed
assuming their behavior.

Eventually, I think we should ideally reimplement ::first-letter entirely at frame-construction time,
rather than during reflow. But in the interest of minimizing risk here, and making it easy to flip
between our existing "legacy" behavior and the new "compatible" behavior, this patch leaves the
overall implementation unchanged and just alters the metrics used for the resulting first-letter
frame.

This patch creates an integer pref layout.css.floating-first-letter.tight-glyph-bounds to allow us
to choose between three behaviors:

    1: Use tight glyph bounds, and ignore line-height; the baseline of the floated letter automatically
       adjusts to wrap text around the "ink box" of the glyph. This is the existing Gecko behavior.

    0: Don't use tight glyph bounds, respect line-height: the floated letter acts like a normal <span>
       with float positioning; baseline position and vertical size are based on font metrics but not
       the specific shape of the individual glyph. This gives a similar result to webkit/blink.

   -1: Automatically choose between (1) and (0) based on heuristics to try and detect whether the page
       was written with the webkit/blink behavior (0) in mind; specifically, if there is a line-height
       of less than 1em, or a negative block-start margin, we assume the author was trying to eliminate
       excess blank space that behavior (0) tends to produce, and so we use that model.

Initially, this patch leaves the behavior unchanged for Beta/Release builds, but enables option -1 (use
heuristics to choose which layout model to apply) on Nightly so we can see how that works in practice.

Differential Revision: https://phabricator.services.mozilla.com/D165008
This commit is contained in:
Jonathan Kew
2022-12-20 07:55:24 +00:00
parent d5e646d506
commit 445ced8d77
5 changed files with 107 additions and 31 deletions

View File

@@ -52,6 +52,7 @@
#include "nsDisplayList.h"
#include "nsIFrame.h"
#include "nsIMathMLFrame.h"
#include "nsFirstLetterFrame.h"
#include "nsPlaceholderFrame.h"
#include "nsTextFrameUtils.h"
#include "nsTextRunTransformations.h"
@@ -2233,17 +2234,19 @@ static gfxFontGroup* GetFontGroupForFrame(
return fontGroup;
}
nsFontMetrics* nsTextFrame::InflatedFontMetrics() const {
if (!mFontMetrics) {
float inflation = nsLayoutUtils::FontSizeInflationFor(this);
mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(this, inflation);
}
return mFontMetrics;
}
static gfxFontGroup* GetInflatedFontGroupForFrame(nsTextFrame* aFrame) {
gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated);
if (textRun) {
return textRun->GetFontGroup();
}
if (!aFrame->InflatedFontMetrics()) {
float inflation = nsLayoutUtils::FontSizeInflationFor(aFrame);
RefPtr<nsFontMetrics> metrics =
nsLayoutUtils::GetFontMetricsForFrame(aFrame, inflation);
aFrame->SetInflatedFontMetrics(metrics);
}
return aFrame->InflatedFontMetrics()->GetThebesFontGroup();
}
@@ -3932,13 +3935,7 @@ void nsTextFrame::PropertyProvider::SetupJustificationSpacing(
void nsTextFrame::PropertyProvider::InitFontGroupAndFontMetrics() const {
if (!mFontMetrics) {
if (mWhichTextRun == nsTextFrame::eInflated) {
if (!mFrame->InflatedFontMetrics()) {
float inflation = mFrame->GetFontSizeInflation();
mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, inflation);
mFrame->SetInflatedFontMetrics(mFontMetrics);
} else {
mFontMetrics = mFrame->InflatedFontMetrics();
}
mFontMetrics = mFrame->InflatedFontMetrics();
} else {
mFontMetrics = nsLayoutUtils::GetFontMetricsForFrame(mFrame, 1.0f);
}
@@ -4921,10 +4918,6 @@ gfxTextRun* nsTextFrame::GetUninflatedTextRun() const {
return GetProperty(UninflatedTextRunProperty());
}
void nsTextFrame::SetInflatedFontMetrics(nsFontMetrics* aFontMetrics) {
mFontMetrics = aFontMetrics;
}
void nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
float aInflation) {
NS_ASSERTION(aTextRun, "must have text run");
@@ -5871,13 +5864,16 @@ void nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
}
nscoord nsTextFrame::ComputeLineHeight() const {
return ReflowInput::CalcLineHeight(GetContent(), Style(), PresContext(),
NS_UNCONSTRAINEDSIZE,
GetFontSizeInflation());
}
gfxFloat nsTextFrame::ComputeDescentLimitForSelectionUnderline(
nsPresContext* aPresContext, const gfxFont::Metrics& aFontMetrics) {
gfxFloat app = aPresContext->AppUnitsPerDevPixel();
nscoord lineHeightApp =
ReflowInput::CalcLineHeight(GetContent(), Style(), PresContext(),
NS_UNCONSTRAINEDSIZE, GetFontSizeInflation());
gfxFloat lineHeight = gfxFloat(lineHeightApp) / app;
const gfxFloat lineHeight =
gfxFloat(ComputeLineHeight()) / aPresContext->AppUnitsPerDevPixel();
if (lineHeight <= aFontMetrics.maxHeight) {
return aFontMetrics.maxDescent;
}
@@ -9707,10 +9703,14 @@ void nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
// The metrics for the text go in here
gfxTextRun::Metrics textMetrics;
gfxFont::BoundingBoxType boundingBoxType =
IsFloatingFirstLetterChild() || IsInitialLetterChild()
? gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
: gfxFont::LOOSE_INK_EXTENTS;
gfxFont::BoundingBoxType boundingBoxType = gfxFont::LOOSE_INK_EXTENTS;
if (IsFloatingFirstLetterChild() || IsInitialLetterChild()) {
if (nsFirstLetterFrame* firstLetter = do_QueryFrame(GetParent())) {
if (firstLetter->UseTightBounds()) {
boundingBoxType = gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS;
}
}
}
int32_t limitLength = length;
int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this);