Bug 1531223 - Add support for the 'ic' font-relative unit. r=emilio

This is a "simplified" implementation of 'ic', similar to what Safari Preview
currently supports: it only considers the advance of U+6C34 if found in the
first available font, and otherwise falls back to the default of 1em.

(The spec allows for this "in cases where it is impossible or impractical to
determine the ideographic advance measure".)

Differential Revision: https://phabricator.services.mozilla.com/D132818
This commit is contained in:
Jonathan Kew 2021-12-08 17:07:05 +00:00
parent 12a0df87a4
commit 9477e32f16
28 changed files with 102 additions and 39 deletions

View File

@ -323,7 +323,7 @@ void gfxDWriteFont::ComputeMetrics(AntialiasOption anAAOption) {
case FontSizeAdjust::Tag::IcHeight: {
bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) ==
FontSizeAdjust::Tag::IcHeight;
gfxFloat advance = GetCharAdvance(0x6C34, vertical);
gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical);
aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0;
break;
}
@ -435,6 +435,8 @@ void gfxDWriteFont::ComputeMetrics(AntialiasOption anAAOption) {
mMetrics->zeroWidth = GetCharAdvance('0');
mMetrics->ideographicWidth = GetCharAdvance(kWaterIdeograph);
mMetrics->underlineOffset = fontMetrics.underlinePosition * mFUnitsConvFactor;
mMetrics->underlineSize = fontMetrics.underlineThickness * mFUnitsConvFactor;
mMetrics->strikeoutOffset =

View File

@ -158,7 +158,7 @@ static inline gfxRect ScaleGlyphBounds(const IntRect& aBounds,
* exists. aWidth/aBounds is only set when this returns a non-zero glyph id.
* This is just for use during initialization, and doesn't use the width cache.
*/
uint32_t gfxFT2FontBase::GetCharExtents(char aChar, gfxFloat* aWidth,
uint32_t gfxFT2FontBase::GetCharExtents(uint32_t aChar, gfxFloat* aWidth,
gfxRect* aBounds) {
FT_UInt gid = GetGlyph(aChar);
int32_t width;
@ -249,7 +249,7 @@ void gfxFT2FontBase::InitMetrics() {
case FontSizeAdjust::Tag::IcHeight: {
bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) ==
FontSizeAdjust::Tag::IcHeight;
gfxFloat advance = GetCharAdvance(0x6C34, vertical);
gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical);
aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0;
break;
}
@ -294,6 +294,7 @@ void gfxFT2FontBase::InitMetrics() {
mMetrics.maxAdvance = spaceWidth;
mMetrics.aveCharWidth = spaceWidth;
mMetrics.zeroWidth = spaceWidth;
mMetrics.ideographicWidth = emHeight;
const gfxFloat xHeight = 0.5 * emHeight;
mMetrics.xHeight = xHeight;
mMetrics.capHeight = mMetrics.maxAscent;
@ -469,6 +470,12 @@ void gfxFT2FontBase::InitMetrics() {
mMetrics.zeroWidth = -1.0; // indicates not found
}
if (GetCharExtents(kWaterIdeograph, &width)) {
mMetrics.ideographicWidth = width;
} else {
mMetrics.ideographicWidth = -1.0;
}
// If we didn't get a usable x-height or cap-height above, try measuring
// specific glyphs. This can be affected by hinting, leading to erratic
// behavior across font sizes and system configuration, so we prefer to
@ -532,11 +539,12 @@ void gfxFT2FontBase::InitMetrics() {
// printf("font name: %s %f\n", NS_ConvertUTF16toUTF8(GetName()).get(), GetStyle()->size);
// printf ("pango font %s\n", pango_font_description_to_string (pango_font_describe (font)));
fprintf (stderr, "Font: %s\n", NS_ConvertUTF16toUTF8(GetName()).get());
fprintf (stderr, "Font: %s\n", GetName().get());
fprintf (stderr, " emHeight: %f emAscent: %f emDescent: %f\n", mMetrics.emHeight, mMetrics.emAscent, mMetrics.emDescent);
fprintf (stderr, " maxAscent: %f maxDescent: %f\n", mMetrics.maxAscent, mMetrics.maxDescent);
fprintf (stderr, " internalLeading: %f externalLeading: %f\n", mMetrics.externalLeading, mMetrics.internalLeading);
fprintf (stderr, " spaceWidth: %f aveCharWidth: %f xHeight: %f\n", mMetrics.spaceWidth, mMetrics.aveCharWidth, mMetrics.xHeight);
fprintf (stderr, " ideographicWidth: %f\n", mMetrics.ideographicWidth);
fprintf (stderr, " uOff: %f uSize: %f stOff: %f stSize: %f\n", mMetrics.underlineOffset, mMetrics.underlineSize, mMetrics.strikeoutOffset, mMetrics.strikeoutSize);
#endif
}

View File

@ -65,7 +65,7 @@ class gfxFT2FontBase : public gfxFont {
void UnlockFTFace();
private:
uint32_t GetCharExtents(char aChar, gfxFloat* aWidth,
uint32_t GetCharExtents(uint32_t aChar, gfxFloat* aWidth,
gfxRect* aBounds = nullptr);
// Get advance (and optionally bounds) of a single glyph from FreeType,

View File

@ -545,7 +545,8 @@ double gfxFontconfigFontEntry::GetAspect(uint8_t aSizeAdjustBasis) {
case FontSizeAdjust::Tag::IcHeight: {
bool vertical = FontSizeAdjust::Tag(aSizeAdjustBasis) ==
FontSizeAdjust::Tag::IcHeight;
gfxFloat advance = font->GetCharAdvance(0x6C34, vertical);
gfxFloat advance = font->GetCharAdvance(gfxFont::kWaterIdeograph,
vertical);
return advance > 0 ? advance / metrics.emHeight : 1.0;
}
default:

View File

@ -3796,15 +3796,16 @@ void gfxFont::SanitizeMetrics(gfxFont::Metrics* aMetrics,
// usual font tables (which can happen in the case of a legacy bitmap or Type1
// font for which the platform-specific backend used platform APIs instead of
// sfnt tables to create the horizontal metrics).
UniquePtr<const gfxFont::Metrics> gfxFont::CreateVerticalMetrics() {
void gfxFont::CreateVerticalMetrics() {
const uint32_t kHheaTableTag = TRUETYPE_TAG('h', 'h', 'e', 'a');
const uint32_t kVheaTableTag = TRUETYPE_TAG('v', 'h', 'e', 'a');
const uint32_t kPostTableTag = TRUETYPE_TAG('p', 'o', 's', 't');
const uint32_t kOS_2TableTag = TRUETYPE_TAG('O', 'S', '/', '2');
uint32_t len;
UniquePtr<Metrics> metrics = MakeUnique<Metrics>();
::memset(metrics.get(), 0, sizeof(Metrics));
mVerticalMetrics = MakeUnique<Metrics>();
auto* metrics = mVerticalMetrics.get();
::memset(metrics, 0, sizeof(Metrics));
// Some basic defaults, in case the font lacks any real metrics tables.
// TODO: consider what rounding (if any) we should apply to these.
@ -3869,6 +3870,7 @@ UniquePtr<const gfxFont::Metrics> gfxFont::CreateVerticalMetrics() {
}
// Read real vertical metrics if available.
metrics->ideographicWidth = -1.0;
gfxFontEntry::AutoTable vheaTable(mFontEntry, kVheaTableTag);
if (vheaTable && mFUnitsConvFactor >= 0.0) {
const MetricsHeader* vhea = reinterpret_cast<const MetricsHeader*>(
@ -3888,6 +3890,7 @@ UniquePtr<const gfxFont::Metrics> gfxFont::CreateVerticalMetrics() {
metrics->maxDescent = halfExtent;
SET_SIGNED(externalLeading, vhea->lineGap);
}
metrics->ideographicWidth = GetCharAdvance(kWaterIdeograph, true);
}
}
@ -3948,8 +3951,6 @@ UniquePtr<const gfxFont::Metrics> gfxFont::CreateVerticalMetrics() {
metrics->maxHeight = metrics->maxAscent + metrics->maxDescent;
metrics->xHeight = metrics->emHeight / 2;
metrics->capHeight = metrics->maxAscent;
return std::move(metrics);
}
gfxFloat gfxFont::SynthesizeSpaceWidth(uint32_t aCh) {

View File

@ -1632,11 +1632,14 @@ class gfxFont {
gfxFloat aveCharWidth;
gfxFloat spaceWidth;
gfxFloat zeroWidth; // -1 if there was no zero glyph
gfxFloat ideographicWidth; // -1 if kWaterIdeograph is not supported
gfxFloat ZeroOrAveCharWidth() const {
return zeroWidth >= 0 ? zeroWidth : aveCharWidth;
}
};
// Unicode character used as basis for 'ic' unit:
static constexpr uint32_t kWaterIdeograph = 0x6C34;
typedef nsFontMetrics::FontOrientation Orientation;
@ -1645,7 +1648,7 @@ class gfxFont {
return GetHorizontalMetrics();
}
if (!mVerticalMetrics) {
mVerticalMetrics = CreateVerticalMetrics();
CreateVerticalMetrics();
}
return *mVerticalMetrics;
}
@ -1959,7 +1962,7 @@ class gfxFont {
protected:
virtual const Metrics& GetHorizontalMetrics() = 0;
mozilla::UniquePtr<const Metrics> CreateVerticalMetrics();
void CreateVerticalMetrics();
// Template parameters for DrawGlyphs/DrawOneGlyph, used to select
// simplified versions of the methods in the most common cases.
@ -2204,7 +2207,7 @@ class gfxFont {
RefPtr<mozilla::gfx::ScaledFont> mAzureScaledFont;
// For vertical metrics, created on demand.
mozilla::UniquePtr<const Metrics> mVerticalMetrics;
mozilla::UniquePtr<Metrics> mVerticalMetrics;
// Table used for MathML layout.
mozilla::UniquePtr<gfxMathTable> mMathTable;

View File

@ -154,7 +154,7 @@ void gfxGDIFont::Initialize() {
case FontSizeAdjust::Tag::IcHeight: {
bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) ==
FontSizeAdjust::Tag::IcHeight;
gfxFloat advance = GetCharAdvance(0x6C34, vertical);
gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical);
aspect = advance > 0.0 ? advance / mMetrics->emHeight : 1.0;
break;
}
@ -350,6 +350,16 @@ void gfxGDIFont::Initialize() {
mMetrics->zeroWidth = -1.0; // indicates not found
}
wchar_t ch = kWaterIdeograph;
ret = GetGlyphIndicesW(dc.GetDC(), &ch, 1, &glyph,
GGI_MARK_NONEXISTING_GLYPHS);
if (ret != GDI_ERROR && glyph != 0xFFFF) {
GetTextExtentPoint32W(dc.GetDC(), &ch, 1, &size);
mMetrics->ideographicWidth = ROUND(size.cx);
} else {
mMetrics->ideographicWidth = -1.0;
}
SanitizeMetrics(mMetrics, GetFontEntry()->mIsBadUnderlineFont);
} else {
mFUnitsConvFactor = 0.0; // zero-sized font: all values scale to zero

View File

@ -305,7 +305,9 @@ hb_position_t gfxHarfBuzzShaper::GetGlyphHAdvance(hb_codepoint_t glyph) const {
uint16_t(metrics->metrics[glyph].advanceWidth));
}
hb_position_t gfxHarfBuzzShaper::GetGlyphVAdvance(hb_codepoint_t glyph) const {
hb_position_t gfxHarfBuzzShaper::GetGlyphVAdvance(hb_codepoint_t glyph) {
InitializeVertical();
if (!mVmtxTable) {
// Must be a "vertical" font that doesn't actually have vertical metrics;
// use a fixed advance.

View File

@ -43,7 +43,7 @@ class gfxHarfBuzzShaper : public gfxFontShaper {
// get harfbuzz glyph advance, in font design units
hb_position_t GetGlyphHAdvance(hb_codepoint_t glyph) const;
hb_position_t GetGlyphVAdvance(hb_codepoint_t glyph) const;
hb_position_t GetGlyphVAdvance(hb_codepoint_t glyph);
void GetGlyphVOrigin(hb_codepoint_t aGlyph, hb_position_t* aX,
hb_position_t* aY) const;

View File

@ -311,7 +311,7 @@ void gfxMacFont::InitMetrics() {
case FontSizeAdjust::Tag::IcHeight: {
bool vertical = FontSizeAdjust::Tag(mStyle.sizeAdjustBasis) ==
FontSizeAdjust::Tag::IcHeight;
gfxFloat advance = GetCharAdvance(0x6C34, vertical);
gfxFloat advance = GetCharAdvance(kWaterIdeograph, vertical);
aspect = advance > 0.0 ? advance / mAdjustedSize : 1.0;
break;
}
@ -375,6 +375,13 @@ void gfxMacFont::InitMetrics() {
}
mSpaceGlyph = glyphID;
mMetrics.ideographicWidth = GetCharWidth(cmap, kWaterIdeograph, &glyphID,
cgConvFactor);
if (glyphID == 0) {
// Indicate "not found".
mMetrics.ideographicWidth = -1.0;
}
if (IsSyntheticBold()) {
mMetrics.spaceWidth += GetSyntheticBoldOffset();
mMetrics.aveCharWidth += GetSyntheticBoldOffset();

View File

@ -1441,6 +1441,7 @@ GeckoFontMetrics Gecko_GetFontMetrics(const nsPresContext* aPresContext,
return {ToLength(NS_round(metrics.xHeight * d2a)),
ToLength(NS_round(metrics.zeroWidth * d2a)),
ToLength(NS_round(metrics.capHeight * d2a)),
ToLength(NS_round(metrics.ideographicWidth * d2a)),
ToLength(NS_round(metrics.maxAscent * d2a))};
}

View File

@ -485,6 +485,7 @@ struct GeckoFontMetrics {
mozilla::Length mXSize;
mozilla::Length mChSize; // negatives indicate not found.
mozilla::Length mCapHeight; // negatives indicate not found.
mozilla::Length mIcWidth; // negatives indicate not found.
mozilla::Length mAscent;
};

View File

@ -20,6 +20,8 @@ pub struct FontMetrics {
pub zero_advance_measure: Option<Length>,
/// The cap-height of the font.
pub cap_height: Option<Length>,
/// The ideographic-width of the font.
pub ic_width: Option<Length>,
/// The ascent of the font (a value is always available for this).
pub ascent: Length,
}
@ -30,6 +32,7 @@ impl Default for FontMetrics {
x_height: None,
zero_advance_measure: None,
cap_height: None,
ic_width: None,
ascent: Length::new(0.0),
}
}

View File

@ -1017,6 +1017,11 @@ impl FontMetricsProvider for GeckoFontMetricsProvider {
} else {
None
},
ic_width: if gecko_metrics.mIcWidth.px() >= 0. {
Some(gecko_metrics.mIcWidth)
} else {
None
},
ascent: gecko_metrics.mAscent,
}
}

View File

@ -47,6 +47,7 @@ pub enum SortKey {
Deg,
Em,
Ex,
Ic,
Px,
Rem,
Sec,

View File

@ -185,6 +185,7 @@ impl generic::CalcNodeLeaf for Leaf {
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

@ -62,6 +62,9 @@ pub enum FontRelativeLength {
/// A "cap" value: https://drafts.csswg.org/css-values/#cap
#[css(dimension)]
Cap(CSSFloat),
/// An "ic" value: https://drafts.csswg.org/css-values/#ic
#[css(dimension)]
Ic(CSSFloat),
/// A "rem" value: https://drafts.csswg.org/css-values/#rem
#[css(dimension)]
Rem(CSSFloat),
@ -96,6 +99,7 @@ impl FontRelativeLength {
FontRelativeLength::Ex(v) |
FontRelativeLength::Ch(v) |
FontRelativeLength::Cap(v) |
FontRelativeLength::Ic(v) |
FontRelativeLength::Rem(v) => v == 0.,
}
}
@ -106,6 +110,7 @@ impl FontRelativeLength {
FontRelativeLength::Ex(v) |
FontRelativeLength::Ch(v) |
FontRelativeLength::Cap(v) |
FontRelativeLength::Ic(v) |
FontRelativeLength::Rem(v) => v < 0.,
}
}
@ -122,12 +127,13 @@ impl FontRelativeLength {
(&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(..) | Cap(..) | Rem(..) => {},
Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {},
}
debug_unreachable!("Forgot to handle unit in try_sum()")
},
@ -254,6 +260,24 @@ impl FontRelativeLength {
});
(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);
let metrics =
query_font_metrics(context, base_size, FontMetricsOrientation::MatchContextPreferVertical);
let reference_size = metrics.ic_width.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:
//
@ -566,6 +590,7 @@ impl NoCalcLength {
"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() => {
@ -726,12 +751,13 @@ impl PartialOrd for FontRelativeLength {
(&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(..) | Cap(..) | Rem(..) => {},
Em(..) | Ex(..) | Ch(..) | Cap(..) | Ic(..) | Rem(..) => {},
}
debug_unreachable!("Forgot an arm in partial_cmp?")
},
@ -749,6 +775,7 @@ impl Mul<CSSFloat> for FontRelativeLength {
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

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

View File

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