Bug 1531223 - Add support for the 'ic' and 'cap' font-relative units. r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D132818
This commit is contained in:
Jonathan Kew 2021-12-06 12:22:09 +00:00
parent c922b5bf64
commit ed3fd395ba
24 changed files with 209 additions and 37 deletions

View File

@ -262,6 +262,21 @@ nscoord nsFontMetrics::SpaceWidth() {
.spaceWidth);
}
nscoord nsFontMetrics::IcWidth() {
if (mIcWidth < 0) {
const char16_t ch = 0x6C34;
FontMatchType matchType;
RefPtr<gfxFont> font = GetThebesFontGroup()->FindFontForChar(
ch, 0, 0, unicode::Script::HAN, nullptr, &matchType);
if (!font) {
mIcWidth = EmHeight();
} else {
mIcWidth = ROUND_TO_TWIPS(font->GetCharAdvance(ch, mOrientation));
}
}
return mIcWidth;
}
int32_t nsFontMetrics::GetMaxStringLength() {
const gfxFont::Metrics& m = GetMetrics(this);
const double x = 32767.0 / std::max(1.0, m.maxAdvance);

View File

@ -178,6 +178,11 @@ class nsFontMetrics final {
*/
nscoord SpaceWidth();
/**
* Return the typical width of ideographic characters.
*/
nscoord IcWidth();
/**
* Returns the font associated with these metrics. The return value
* is only defined after Init() has been called.
@ -253,6 +258,10 @@ class nsFontMetrics final {
nsPresContext* MOZ_NON_OWNING_REF mPresContext;
int32_t mP2A;
// Cached value for IcWidth(), as computing this requires a font fallback
// search that may be expensive.
nscoord mIcWidth = -1;
// The font orientation (horizontal or vertical) for which these metrics
// have been initialized. This determines which line metrics (ascent and
// descent) they will return.

View File

@ -214,8 +214,9 @@ void nsPresContext::ForceReflowForFontInfoUpdate(bool aNeedsReframe) {
// We also need to trigger restyling for ex/ch units changes to take effect,
// if needed.
auto restyleHint =
UsesExChUnits() ? RestyleHint::RecascadeSubtree() : RestyleHint{0};
auto restyleHint = UsesFontMetricDependentFontUnits()
? RestyleHint::RecascadeSubtree()
: RestyleHint{0};
RebuildAllStyleData(changeHint, restyleHint);
}
@ -271,7 +272,7 @@ nsPresContext::nsPresContext(dom::Document* aDocument, nsPresContextType aType)
mPendingUIResolutionChanged(false),
mPendingFontInfoUpdateReflowFromStyle(false),
mIsGlyph(false),
mUsesExChUnits(false),
mUsesFontMetricDependentFontUnits(false),
mCounterStylesDirty(true),
mFontFeatureValuesDirty(true),
mSuppressResizeReflow(false),
@ -658,7 +659,7 @@ void nsPresContext::PreferenceChanged(const char* aPrefName) {
// Changes to font_rendering prefs need to trigger a reflow
StringBeginsWith(prefName, "gfx.font_rendering."_ns)) {
changeHint |= NS_STYLE_HINT_REFLOW;
if (UsesExChUnits()) {
if (UsesFontMetricDependentFontUnits()) {
restyleHint |= RestyleHint::RecascadeSubtree();
}
}
@ -1772,7 +1773,7 @@ void nsPresContext::PostRebuildAllStyleDataEvent(
return;
}
if (aRestyleHint.DefinitelyRecascadesAllSubtree()) {
mUsesExChUnits = false;
mUsesFontMetricDependentFontUnits = false;
}
RestyleManager()->RebuildAllStyleData(aExtraHint, aRestyleHint);
}
@ -1970,8 +1971,9 @@ void nsPresContext::UserFontSetUpdated(gfxUserFontEntry* aUpdatedFont) {
// TODO(emilio): We could be more granular if we knew which families have
// potentially changed.
if (!aUpdatedFont) {
auto hint =
UsesExChUnits() ? RestyleHint::RecascadeSubtree() : RestyleHint{0};
auto hint = UsesFontMetricDependentFontUnits()
? RestyleHint::RecascadeSubtree()
: RestyleHint{0};
PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW, hint);
return;
}

View File

@ -1051,9 +1051,13 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
bool HasEverBuiltInvisibleText() const { return mHasEverBuiltInvisibleText; }
void SetBuiltInvisibleText() { mHasEverBuiltInvisibleText = true; }
bool UsesExChUnits() const { return mUsesExChUnits; }
bool UsesFontMetricDependentFontUnits() const {
return mUsesFontMetricDependentFontUnits;
}
void SetUsesExChUnits(bool aValue) { mUsesExChUnits = aValue; }
void SetUsesFontMetricDependentFontUnits(bool aValue) {
mUsesFontMetricDependentFontUnits = aValue;
}
bool IsDeviceSizePageSize();
@ -1330,7 +1334,7 @@ class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
//
// TODO(emilio): It's a bit weird that this lives here but all the other
// relevant bits live in Device on the rust side.
unsigned mUsesExChUnits : 1;
unsigned mUsesFontMetricDependentFontUnits : 1;
// Is the current mCounterStyleManager valid?
unsigned mCounterStylesDirty : 1;

View File

@ -199,7 +199,7 @@ nscoord nsMathMLFrame::CalcLength(nsPresContext* aPresContext,
}
if (eCSSUnit_XHeight == unit) {
aPresContext->SetUsesExChUnits(true);
aPresContext->SetUsesFontMetricDependentFontUnits(true);
RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetFontMetricsForComputedStyle(
aComputedStyle, aPresContext, aFontSizeInflation);
nscoord xHeight = fm->XHeight();

View File

@ -1426,7 +1426,7 @@ GeckoFontMetrics Gecko_GetFontMetrics(const nsPresContext* aPresContext,
// ArrayBuffer-backed FontFace objects are handled synchronously.
nsPresContext* presContext = const_cast<nsPresContext*>(aPresContext);
presContext->SetUsesExChUnits(true);
presContext->SetUsesFontMetricDependentFontUnits(true);
RefPtr<nsFontMetrics> fm = nsLayoutUtils::GetMetricsFor(
presContext, aIsVertical, aFont, aFontSize, aUseUserFontSet);
@ -1439,7 +1439,9 @@ GeckoFontMetrics Gecko_GetFontMetrics(const nsPresContext* aPresContext,
return Length::FromPixels(CSSPixel::FromAppUnits(aLen));
};
return {ToLength(NS_round(metrics.xHeight * d2a)),
ToLength(NS_round(metrics.zeroWidth * d2a))};
ToLength(NS_round(metrics.zeroWidth * d2a)),
ToLength(NS_round(metrics.capHeight * d2a)), ToLength(fm->IcWidth()),
ToLength(NS_round(metrics.maxAscent * d2a))};
}
NS_IMPL_THREADSAFE_FFI_REFCOUNTING(SheetLoadDataHolder, SheetLoadDataHolder);

View File

@ -483,7 +483,10 @@ mozilla::StyleDefaultFontSizes Gecko_GetBaseSize(nsAtom* lang);
struct GeckoFontMetrics {
mozilla::Length mXSize;
mozilla::Length mChSize; // negatives indicate not found.
mozilla::Length mChSize; // negatives indicate not found.
mozilla::Length mCapHeight; // negatives indicate not found.
mozilla::Length mIcWidth; // negatives indicate not found.
mozilla::Length mAscent;
};
GeckoFontMetrics Gecko_GetFontMetrics(const nsPresContext*, bool is_vertical,

View File

@ -210,7 +210,7 @@ void nsFontFaceUtils::MarkDirtyForFontChange(nsIFrame* aSubtreeRoot,
}
if (alreadyScheduled == ReflowAlreadyScheduled::No ||
pc->UsesExChUnits()) {
pc->UsesFontMetricDependentFontUnits()) {
if (f->IsPlaceholderFrame()) {
nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {

View File

@ -12,20 +12,41 @@ use crate::Atom;
/// Represents the font metrics that style needs from a font to compute the
/// value of certain CSS units like `ex`.
#[derive(Clone, Debug, Default, PartialEq)]
#[derive(Clone, Debug, PartialEq)]
pub struct FontMetrics {
/// The x-height of the font.
pub x_height: Option<Length>,
/// The zero advance. This is usually writing mode dependent
pub zero_advance_measure: Option<Length>,
/// The cap-height of the font.
pub cap_height: Option<Length>,
/// The ideographic-advance of the font.
pub ideographic_advance: Option<Length>,
/// The ascent of the font (a value is always available for this).
pub ascent: Length,
}
impl Default for FontMetrics {
fn default() -> Self {
FontMetrics {
x_height: None,
zero_advance_measure: None,
cap_height: None,
ideographic_advance: None,
ascent: Length::new(0.0),
}
}
}
/// Type of font metrics to retrieve.
#[derive(Clone, Debug, PartialEq)]
pub enum FontMetricsOrientation {
/// Get metrics for horizontal or vertical according to the Context's
/// writing mode.
MatchContext,
/// writing mode, using horizontal metrics for vertical/mixed
MatchContextPreferHorizontal,
/// Get metrics for horizontal or vertical according to the Context's
/// writing mode, using vertical metrics for vertical/mixed
MatchContextPreferVertical,
/// Force getting horizontal metrics.
Horizontal,
}

View File

@ -991,7 +991,8 @@ impl FontMetricsProvider for GeckoFontMetricsProvider {
};
let vertical_metrics = match orientation {
FontMetricsOrientation::MatchContext => wm.is_vertical() && wm.is_upright(),
FontMetricsOrientation::MatchContextPreferHorizontal => wm.is_vertical() && wm.is_upright(),
FontMetricsOrientation::MatchContextPreferVertical => wm.is_vertical() && !wm.is_sideways(),
FontMetricsOrientation::Horizontal => false,
};
let gecko_metrics = unsafe {
@ -1011,6 +1012,17 @@ impl FontMetricsProvider for GeckoFontMetricsProvider {
} else {
None
},
cap_height: if gecko_metrics.mCapHeight.px() >= 0. {
Some(gecko_metrics.mCapHeight)
} else {
None
},
ideographic_advance: if gecko_metrics.mIcWidth.px() >= 0. {
Some(gecko_metrics.mIcWidth)
} else {
None
},
ascent: gecko_metrics.mAscent,
}
}
}

View File

@ -42,10 +42,12 @@ pub enum MinMaxOp {
pub enum SortKey {
Number,
Percentage,
Cap,
Ch,
Deg,
Em,
Ex,
Ic,
Px,
Rem,
Sec,

View File

@ -184,6 +184,8 @@ impl generic::CalcNodeLeaf for Leaf {
FontRelativeLength::Ch(..) => SortKey::Ch,
FontRelativeLength::Em(..) => SortKey::Em,
FontRelativeLength::Ex(..) => SortKey::Ex,
FontRelativeLength::Cap(..) => SortKey::Cap,
FontRelativeLength::Ic(..) => SortKey::Ic,
FontRelativeLength::Rem(..) => SortKey::Rem,
},
NoCalcLength::ViewportPercentage(ref vp) => match *vp {

View File

@ -59,6 +59,12 @@ pub enum FontRelativeLength {
/// A "ch" value: https://drafts.csswg.org/css-values/#ch
#[css(dimension)]
Ch(CSSFloat),
/// A "cap" value: https://drafts.csswg.org/css-values/#cap
#[css(dimension)]
Cap(CSSFloat),
/// An "ic" value: https://drafts.csswg.org/css-values-4/#ic
#[css(dimension)]
Ic(CSSFloat),
/// A "rem" value: https://drafts.csswg.org/css-values/#rem
#[css(dimension)]
Rem(CSSFloat),
@ -92,6 +98,8 @@ impl FontRelativeLength {
FontRelativeLength::Em(v) |
FontRelativeLength::Ex(v) |
FontRelativeLength::Ch(v) |
FontRelativeLength::Cap(v) |
FontRelativeLength::Ic(v) |
FontRelativeLength::Rem(v) => v == 0.,
}
}
@ -101,6 +109,8 @@ impl FontRelativeLength {
FontRelativeLength::Em(v) |
FontRelativeLength::Ex(v) |
FontRelativeLength::Ch(v) |
FontRelativeLength::Cap(v) |
FontRelativeLength::Ic(v) |
FontRelativeLength::Rem(v) => v < 0.,
}
}
@ -116,12 +126,14 @@ impl FontRelativeLength {
(&Em(one), &Em(other)) => Em(one + other),
(&Ex(one), &Ex(other)) => Ex(one + other),
(&Ch(one), &Ch(other)) => Ch(one + other),
(&Cap(one), &Cap(other)) => Cap(one + other),
(&Ic(one), &Ic(other)) => Ic(one + other),
(&Rem(one), &Rem(other)) => Rem(one + other),
// See https://github.com/rust-lang/rust/issues/68867. rustc isn't
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Em(..) | Ex(..) | Ch(..) | Rem(..) => {},
Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {},
}
debug_unreachable!("Forgot to handle unit in try_sum()")
},
@ -210,7 +222,7 @@ impl FontRelativeLength {
// whichever is in the inline axis of the element.)
//
let metrics =
query_font_metrics(context, base_size, FontMetricsOrientation::MatchContext);
query_font_metrics(context, base_size, FontMetricsOrientation::MatchContextPreferHorizontal);
let reference_size = metrics.zero_advance_measure.unwrap_or_else(|| {
// https://drafts.csswg.org/css-values/#ch
//
@ -231,6 +243,47 @@ impl FontRelativeLength {
});
(reference_size, length)
},
FontRelativeLength::Cap(length) => {
if context.for_non_inherited_property.is_some() {
context.rule_cache_conditions.borrow_mut().set_uncacheable();
}
context.builder.add_flags(font_metrics_flag);
let metrics =
query_font_metrics(context, base_size, FontMetricsOrientation::Horizontal);
let reference_size = metrics.cap_height.unwrap_or_else(|| {
// https://drafts.csswg.org/css-values/#cap
//
// In the cases where it is impossible or impractical to
// determine the cap-height, the fonts ascent must be used.
//
metrics.ascent
});
(reference_size, length)
},
FontRelativeLength::Ic(length) => {
if context.for_non_inherited_property.is_some() {
context.rule_cache_conditions.borrow_mut().set_uncacheable();
}
context.builder.add_flags(font_metrics_flag);
// https://drafts.csswg.org/css-values/#ic:
//
// Equal to the used advance measure of the CJK water ideograph
// (U+6C34) glyph in the font used to render it. (The advance
// measure of a glyph is its advance width or height,
// whichever is in the inline axis of the element.)
//
let metrics =
query_font_metrics(context, base_size, FontMetricsOrientation::MatchContextPreferVertical);
let reference_size = metrics.ideographic_advance.unwrap_or_else(|| {
// https://drafts.csswg.org/css-values/#ic
//
// In the cases where it is impossible or impractical to determine
// the ideographic advance measure, it must be assumed to be 1em.
//
reference_font_size
});
(reference_size, length)
},
FontRelativeLength::Rem(length) => {
// https://drafts.csswg.org/css-values/#rem:
//
@ -542,6 +595,8 @@ impl NoCalcLength {
"em" => NoCalcLength::FontRelative(FontRelativeLength::Em(value)),
"ex" => NoCalcLength::FontRelative(FontRelativeLength::Ex(value)),
"ch" => NoCalcLength::FontRelative(FontRelativeLength::Ch(value)),
"cap" => NoCalcLength::FontRelative(FontRelativeLength::Cap(value)),
"ic" => NoCalcLength::FontRelative(FontRelativeLength::Ic(value)),
"rem" => NoCalcLength::FontRelative(FontRelativeLength::Rem(value)),
// viewport percentages
"vw" if !context.in_page_rule() => {
@ -701,12 +756,14 @@ impl PartialOrd for FontRelativeLength {
(&Em(ref one), &Em(ref other)) => one.partial_cmp(other),
(&Ex(ref one), &Ex(ref other)) => one.partial_cmp(other),
(&Ch(ref one), &Ch(ref other)) => one.partial_cmp(other),
(&Cap(ref one), &Cap(ref other)) => one.partial_cmp(other),
(&Ic(ref one), &Ic(ref other)) => one.partial_cmp(other),
(&Rem(ref one), &Rem(ref other)) => one.partial_cmp(other),
// See https://github.com/rust-lang/rust/issues/68867. rustc isn't
// able to figure it own on its own so we help.
_ => unsafe {
match *self {
Em(..) | Ex(..) | Ch(..) | Rem(..) => {},
Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {},
}
debug_unreachable!("Forgot an arm in partial_cmp?")
},
@ -723,6 +780,8 @@ impl Mul<CSSFloat> for FontRelativeLength {
FontRelativeLength::Em(v) => FontRelativeLength::Em(v * scalar),
FontRelativeLength::Ex(v) => FontRelativeLength::Ex(v * scalar),
FontRelativeLength::Ch(v) => FontRelativeLength::Ch(v * scalar),
FontRelativeLength::Cap(v) => FontRelativeLength::Cap(v * scalar),
FontRelativeLength::Ic(v) => FontRelativeLength::Ic(v * scalar),
FontRelativeLength::Rem(v) => FontRelativeLength::Rem(v * scalar),
}
}

View File

@ -1,2 +0,0 @@
[ic-unit-001.html]
expected: FAIL

View File

@ -1,2 +1,3 @@
[ic-unit-002.html]
expected: FAIL
fuzzy:
if os == "mac": maxDifference=0-8;totalPixels=0-2

View File

@ -1,2 +1,3 @@
[ic-unit-003.html]
expected: FAIL
fuzzy:
if os == "mac": maxDifference=0-8;totalPixels=0-2

View File

@ -1,2 +1,3 @@
[ic-unit-004.html]
expected: FAIL
fuzzy:
if os == "mac": maxDifference=0-4;totalPixels=0-2

View File

@ -1,2 +0,0 @@
[ic-unit-008.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[ic-unit-009.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[ic-unit-010.html]
expected: FAIL

View File

@ -1,2 +0,0 @@
[ic-unit-011.html]
expected: FAIL

View File

@ -1,2 +1,3 @@
[ic-unit-012.html]
expected: FAIL
[ic-unit-003.html]
fuzzy:
if os == "win": maxDifference=0-1;totalPixels=0-45

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Values and Units Test: support for the cap unit</title>
<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
<link rel="help" href="https://drafts.csswg.org/css-values-4/#font-relative-lengths">
<link rel="match" href="reference/cap-unit-001-ref.html">
<meta name="assert" content="The cap unit is equal to the used cap-height of the first available font.">
<link rel="stylesheet" type="text/css" href="/fonts/ahem.css">
<style>
span {
background: green;
color: green;
position: absolute;
}
div {
font: 50px Ahem; /* cap-height of Ahem is 0.8em */
background: red;
position: relative;
height: 180px;
height: calc(180px - 2cap); /* reduce to 100px if cap correctly supported */
width: 100px;
}
div span {
width: 2.5cap;
height: 100px;
}
</style>
<body>
<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
<div><span></span></div>
</body>

View File

@ -0,0 +1,15 @@
<!DOCTYPE html>
<meta charset="utf-8">
<title>CSS Values and Units reference: support for the cap unit</title>
<link rel="author" title="Jonathan Kew" href="mailto:jkew@mozilla.com">
<style>
div {
background: green;
height: 100px;
width: 100px;
}
</style>
<body>
<p>Test passes if there is a <strong>filled green square</strong> and <strong>no red</strong>.</p>
<div></div>
</body>