Commit ed645202 authored by Rob Buis's avatar Rob Buis Committed by Commit Bot

[mathml] Implement script level attribute and font-size extension

MathML Core support the script level functionality both using a
font-size extension and as an attribute on MathML elements.

The scriptlevel attributes simply maps to the font-size extension [1].

The font-size extension follows the algorithm stated in [2].

In order to keep track of the internal scriptlevel [3] an internal property MathScriptLevel is added to the computed style.

Finally various stylesheet rules related to script level are taken as-is from the specification stylesheet [4].

[1] https://mathml-refresh.github.io/mathml-core/#the-displaystyle-and-scriptlevel-attributes
[2] https://mathml-refresh.github.io/mathml-core/#the-math-script-level-property
[3] https://mathml-refresh.github.io/mathml-core/#dfn-internal-scriptlevel
[4] https://mathml-refresh.github.io/mathml-core/#user-agent-stylesheet

Bug: 1023292
Change-Id: I47ed250a63049b15daff53f9c0942cf676b21acd
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2184131Reviewed-by: default avatarRune Lillesveen <futhark@chromium.org>
Reviewed-by: default avatarFrédéric Wang <fwang@igalia.com>
Commit-Queue: Rob Buis <rbuis@igalia.com>
Cr-Commit-Position: refs/heads/master@{#770955}
parent ac4ab44a
......@@ -1190,6 +1190,10 @@
"p3",
"rec2020",
// math-script-level
"add",
"scriptlevel",
// overscroll-behavior
// auto,
// contain
......
......@@ -65,15 +65,45 @@ mspace {
overflow: hidden !important;
}
/* Token elements */
mi {
text-transform: math-auto;
}
/* Fractions */
mfrac {
padding-inline-start: 1px;
padding-inline-end: 1px;
}
mfrac > * {
font-size: scriptlevel(auto);
math-style: inline;
}
mfrac > :nth-child(2) {
math-superscript-shift-style: inline;
}
/* Other rules for scriptlevel, displaystyle and math-superscript-shift-style */
msub > :not(:first-child),
msup > :not(:first-child),
msubsup > :not(:first-child),
mmultiscripts > :not(:first-child),
munder > :not(:first-child),
mover > :not(:first-child),
munderover > :not(:first-child) {
font-size: scriptlevel(add(1));
math-style: inline;
}
munder[accentunder="true" i] > :nth-child(2),
mover[accent="true" i] > :nth-child(2),
munderover[accentunder="true" i] > :nth-child(2),
munderover[accent="true" i] > :nth-child(3) {
font-size: inherit;
}
munder > :nth-child(2),
munderover > :nth-child(2),
mover[accent="true" i] > :first-child,
......@@ -81,8 +111,3 @@ munderover[accent="true" i] > :first-child {
math-superscript-shift-style: inline;
}
/* Token elements */
mi {
text-transform: math-auto;
}
......@@ -1340,6 +1340,39 @@ CSSValue* ConsumeCounter(CSSParserTokenRange& range,
return list;
}
CSSValue* ConsumeScriptLevel(CSSParserTokenRange& range,
const CSSParserContext& context) {
CSSValueID function_id = range.Peek().FunctionId();
DCHECK(function_id == CSSValueID::kScriptlevel);
CSSParserTokenRange args =
css_property_parser_helpers::ConsumeFunction(range);
if (args.AtEnd())
return nullptr;
CSSValue* parsed_value =
css_property_parser_helpers::ConsumeIdent<CSSValueID::kAuto>(args);
if (!parsed_value)
parsed_value = css_property_parser_helpers::ConsumeInteger(args, context);
if (!parsed_value) {
function_id = args.Peek().FunctionId();
if (function_id == CSSValueID::kAdd) {
auto* add_value = MakeGarbageCollected<CSSFunctionValue>(function_id);
CSSParserTokenRange add_args =
css_property_parser_helpers::ConsumeFunction(args);
if ((parsed_value = css_property_parser_helpers::ConsumeInteger(
add_args, context))) {
add_value->Append(*parsed_value);
parsed_value = add_value;
}
}
}
if (!parsed_value || !args.AtEnd())
return nullptr;
auto* script_level_value =
MakeGarbageCollected<CSSFunctionValue>(CSSValueID::kScriptlevel);
script_level_value->Append(*parsed_value);
return script_level_value;
}
CSSValue* ConsumeFontSize(CSSParserTokenRange& range,
const CSSParserContext& context,
css_property_parser_helpers::UnitlessQuirk unitless) {
......@@ -1348,6 +1381,9 @@ CSSValue* ConsumeFontSize(CSSParserTokenRange& range,
if (range.Peek().Id() >= CSSValueID::kXxSmall &&
range.Peek().Id() <= CSSValueID::kWebkitXxxLarge)
return css_property_parser_helpers::ConsumeIdent(range);
if (RuntimeEnabledFeatures::CSSMathStyleEnabled() &&
range.Peek().FunctionId() == CSSValueID::kScriptlevel)
return ConsumeScriptLevel(range, context);
return css_property_parser_helpers::ConsumeLengthOrPercent(
range, context, kValueRangeNonNegative, unitless);
}
......
......@@ -59,6 +59,7 @@
#include "third_party/blink/renderer/core/style/reference_clip_path_operation.h"
#include "third_party/blink/renderer/core/style/shape_clip_path_operation.h"
#include "third_party/blink/renderer/core/style/style_svg_resource.h"
#include "third_party/blink/renderer/platform/fonts/opentype/open_type_math_support.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
......@@ -293,6 +294,58 @@ StyleBuilderConverter::ConvertFontVariationSettings(StyleResolverState& state,
return settings;
}
float MathScriptScaleFactor(StyleResolverState& state) {
int a = state.ParentStyle()->MathScriptLevel();
int b = state.Style()->MathScriptLevel();
if (b == a)
return 1.0;
bool invertScaleFactor = false;
if (b < a) {
std::swap(a, b);
invertScaleFactor = true;
}
// Determine the scale factors from the inherited font.
float defaultScaleDown = 0.71;
int exponent = b - a;
float scaleFactor = 1.0;
HarfBuzzFace* parent_harfbuzz_face = state.ParentStyle()
->GetFont()
.PrimaryFont()
->PlatformData()
.GetHarfBuzzFace();
if (OpenTypeMathSupport::HasMathData(parent_harfbuzz_face)) {
float scriptPercentScaleDown =
OpenTypeMathSupport::MathConstant(
parent_harfbuzz_face,
OpenTypeMathSupport::MathConstants::kScriptPercentScaleDown)
.value_or(0);
// Note: zero can mean both zero for the math constant and the fallback.
if (!scriptPercentScaleDown)
scriptPercentScaleDown = defaultScaleDown;
float scriptScriptPercentScaleDown =
OpenTypeMathSupport::MathConstant(
parent_harfbuzz_face,
OpenTypeMathSupport::MathConstants::kScriptScriptPercentScaleDown)
.value_or(0);
// Note: zero can mean both zero for the math constant and the fallback.
if (!scriptScriptPercentScaleDown)
scriptScriptPercentScaleDown = defaultScaleDown * defaultScaleDown;
if (a <= 0 && b >= 2) {
scaleFactor *= scriptScriptPercentScaleDown;
exponent -= 2;
} else if (a == 1) {
scaleFactor *= scriptScriptPercentScaleDown / scriptPercentScaleDown;
exponent--;
} else if (b == 1) {
scaleFactor *= scriptPercentScaleDown;
exponent--;
}
}
scaleFactor *= pow(defaultScaleDown, exponent);
return invertScaleFactor ? 1 / scaleFactor : scaleFactor;
}
static float ComputeFontSize(const CSSToLengthConversionData& conversion_data,
const CSSPrimitiveValue& primitive_value,
const FontDescription::Size& parent_size) {
......@@ -356,14 +409,48 @@ FontDescription::Size StyleBuilderConverterBase::ConvertFontSize(
is_absolute);
}
static FontDescription::Size ConvertScriptLevelFontSize(
StyleResolverState& state,
const FontDescription::Size& parent_size,
const CSSFunctionValue& function_value) {
SECURITY_DCHECK(function_value.length() == 1);
const auto& css_value = function_value.Item(0);
if (const auto* list = DynamicTo<CSSValueList>(css_value)) {
DCHECK_EQ(list->length(), 1U);
const auto& relative_value = To<CSSPrimitiveValue>(list->Item(0));
state.Style()->SetMathScriptLevel(state.ParentStyle()->MathScriptLevel() +
relative_value.GetIntValue());
} else if (auto* identifier_value =
DynamicTo<CSSIdentifierValue>(css_value)) {
DCHECK(identifier_value->GetValueID() == CSSValueID::kAuto);
unsigned level = 0;
if (state.ParentStyle()->MathStyle() == EMathStyle::kInline)
level += 1;
state.Style()->SetMathScriptLevel(state.ParentStyle()->MathScriptLevel() +
level);
} else if (DynamicTo<CSSPrimitiveValue>(css_value)) {
state.Style()->SetMathScriptLevel(
To<CSSPrimitiveValue>(css_value).GetIntValue());
}
auto scale_factor = MathScriptScaleFactor(state);
state.Style()->SetHasGlyphRelativeUnits();
return FontDescription::Size(0, (scale_factor * parent_size.value),
parent_size.is_absolute);
}
FontDescription::Size StyleBuilderConverter::ConvertFontSize(
StyleResolverState& state,
const CSSValue& value) {
// FIXME: Find out when parentStyle could be 0?
auto parent_size = state.ParentStyle()
? state.ParentFontDescription().GetSize()
: FontDescription::Size(0, 0.0f, false);
if (const auto* function_value = DynamicTo<CSSFunctionValue>(value))
return ConvertScriptLevelFontSize(state, parent_size, *function_value);
return StyleBuilderConverterBase::ConvertFontSize(
value, state.FontSizeConversionData(),
// FIXME: Find out when parentStyle could be 0?
state.ParentStyle() ? state.ParentFontDescription().GetSize()
: FontDescription::Size(0, 0.0f, false));
value, state.FontSizeConversionData(), parent_size);
}
float StyleBuilderConverter::ConvertFontSizeAdjust(StyleResolverState& state,
......
......@@ -18,6 +18,7 @@
"mathcolor",
"mathsize",
"mathvariant",
"scriptlevel",
"width",
],
}
......@@ -34,7 +34,6 @@ static inline bool IsDisallowedMathSizeAttribute(const AtomicString& value) {
}
bool MathMLElement::IsPresentationAttribute(const QualifiedName& name) const {
// TODO(crbug.com/1023292): add support for displaystyle and scriptlevel.
if (name == html_names::kDirAttr || name == mathml_names::kMathsizeAttr ||
name == mathml_names::kMathcolorAttr ||
name == mathml_names::kMathbackgroundAttr ||
......@@ -49,8 +48,6 @@ void MathMLElement::CollectStyleForPresentationAttribute(
const QualifiedName& name,
const AtomicString& value,
MutableCSSPropertyValueSet* style) {
// TODO(crbug.com/1023292, crbug.com/1023296): add support for display,
// displaystyle and scriptlevel.
if (name == html_names::kDirAttr) {
if (IsValidDirAttribute(value)) {
AddPropertyToPresentationAttributeStyle(style, CSSPropertyID::kDirection,
......
......@@ -1128,5 +1128,13 @@
field_group: "*",
getter: "GetMathFractionBarThickness",
},
{
name: "MathScriptLevel",
inherited: true,
field_template: "primitive",
type_name: "short",
default_value: "0",
field_group: "*",
},
],
}
......@@ -1274,15 +1274,6 @@ crbug.com/626703 external/wpt/css/css-text/text-transform/math/text-transform-ma
crbug.com/626703 external/wpt/css/css-text/text-transform/math/text-transform-math-script-001.tentative.html [ Failure ]
crbug.com/626703 external/wpt/css/css-text/text-transform/math/text-transform-math-sans-serif-bold-italic-001.tentative.html [ Failure ]
crbug.com/626703 external/wpt/css/css-text/text-transform/math/text-transform-math-sans-serif-001.tentative.html [ Failure ]
crbug.com/626703 [ Linux ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-002.tentative.html [ Failure ]
crbug.com/626703 [ Mac ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-002.tentative.html [ Failure ]
crbug.com/626703 [ Win ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-002.tentative.html [ Failure ]
crbug.com/626703 [ Linux ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-003.tentative.html [ Failure ]
crbug.com/626703 [ Mac ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-003.tentative.html [ Failure ]
crbug.com/626703 [ Win ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-003.tentative.html [ Failure ]
crbug.com/626703 [ Linux ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-004.tentative.html [ Failure ]
crbug.com/626703 [ Mac ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-004.tentative.html [ Failure ]
crbug.com/626703 [ Win ] external/wpt/css/css-fonts/math-script-level-and-math-style/math-script-level-auto-and-math-style-004.tentative.html [ Failure ]
crbug.com/1043295 [ Fuchsia ] virtual/font-access/http/tests/font-access/font-access-window.html [ Skip ]
crbug.com/1043295 [ Fuchsia ] virtual/font-access/http/tests/font-access/font-access-workers.html [ Skip ]
......
This is a testharness.js-based test.
FAIL auto assert_equals: expected 355 but got 500
FAIL auto ; starting from level 7 assert_approx_equals: expected 181.90240316781995 +/- 0.1 but got 2000
FAIL add(<integer>) assert_equals: expected 100 but got 71
FAIL add(<integer>) ; starting from level 3 assert_equals: expected 100 but got 71
FAIL <integer> assert_equals: expected 100 but got 71
FAIL <integer> ; starting from level 50 assert_equals: expected 100 but got 71
Harness: the test ran to completion.
This is a testharness.js-based test.
FAIL scriptPercentScaleDown=80, scriptScriptPercentScaleDown=40 assert_approx_equals: Wrong font-size (id=scale80-40-scaledown ; level=0 ; i=0) expected 1073.733 +/- 1 but got 3000
FAIL scriptPercentScaleDown=0, scriptScriptPercentScaleDown=40 assert_approx_equals: Wrong font-size (id=scale0-40-scaledown ; level=0 ; i=0) expected 1073.733 +/- 1 but got 3000
FAIL scriptPercentScaleDown=80, scriptScriptPercentScaleDown=0 assert_approx_equals: Wrong font-size (id=scale80-0-scaledown ; level=0 ; i=0) expected 1073.733 +/- 1 but got 3000
FAIL No MATH table assert_approx_equals: Wrong font-size (id=default-scaledown ; level=0 ; i=0) expected 1073.733 +/- 1 but got 3000
Harness: the test ran to completion.
This is a testharness.js-based test.
PASS automatic scriptlevel on mfrac (displaystyle=true)
FAIL automatic scriptlevel on mfrac (displaystyle=false) assert_approx_equals: numerator expected 71 +/- 0.1 but got 100
PASS automatic scriptlevel on mfrac (displaystyle=false)
FAIL automatic scriptlevel on mroot assert_approx_equals: index expected 50.41 +/- 0.1 but got 100
FAIL automatic scriptlevel on msub assert_approx_equals: child 1 expected 71 +/- 0.1 but got 100
FAIL automatic scriptlevel on msup assert_approx_equals: child 1 expected 71 +/- 0.1 but got 100
FAIL automatic scriptlevel on msubsup assert_approx_equals: child 1 expected 71 +/- 0.1 but got 100
FAIL automatic scriptlevel on munder assert_approx_equals: child 1 expected 71 +/- 0.1 but got 100
FAIL automatic scriptlevel on mover assert_approx_equals: child 1 expected 71 +/- 0.1 but got 100
FAIL automatic scriptlevel on munderover assert_approx_equals: child 1 expected 71 +/- 0.1 but got 100
FAIL automatic scriptlevel on mmultiscripts assert_approx_equals: child 1 expected 71 +/- 0.1 but got 100
PASS automatic scriptlevel on msub
PASS automatic scriptlevel on msup
PASS automatic scriptlevel on msubsup
PASS automatic scriptlevel on munder
PASS automatic scriptlevel on mover
PASS automatic scriptlevel on munderover
PASS automatic scriptlevel on mmultiscripts
PASS automatic scriptlevel on munder (accentunder=true)
PASS automatic scriptlevel on mover (accent=true)
FAIL automatic scriptlevel on munderover (accentunder=true) assert_approx_equals: over expected 71 +/- 0.1 but got 100
FAIL automatic scriptlevel on munderover (accent=true) assert_approx_equals: under expected 71 +/- 0.1 but got 100
FAIL checking dynamic/case-insensitive accent/accentunder assert_approx_equals: under expected 71 +/- 0.1 but got 100
PASS automatic scriptlevel on munderover (accentunder=true)
PASS automatic scriptlevel on munderover (accent=true)
PASS checking dynamic/case-insensitive accent/accentunder
Harness: the test ran to completion.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment