Skip to content

Commit

Permalink
[mathml] minsize/maxsize
Browse files Browse the repository at this point in the history
This is implementing changes discussed in [1]:

- percentage minsize/maxsize should refer to the unstretched size,
  mo-minsize-maxsize-001.html is updated accordingly.

- minsize/maxsize adjustments are done by scaling the distance
  above and below the math axis proportionally.
  mo-axis-height-1.html is extended to verify calculations from
  [2]. These new formulas ensure that symmetry with respect to
  the math axis is preserved when minsize/maxsize is applied
  and two tests are added to verify that more directly.

- default value for minsize is 100% (the unstretched size) rather
  than 1em.

[1] w3c/mathml-core#103
[2] https://w3c.github.io/mathml-core/#layout-of-operators

Bug: 332931380
Change-Id: I63ca77c5d0b8934570916d589160da0c90722f08
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5402478
Reviewed-by: Ian Kilpatrick <[email protected]>
Commit-Queue: Frédéric Wang <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1283539}
  • Loading branch information
fred-wang authored and chromium-wpt-export-bot committed Apr 6, 2024
1 parent c15ab7c commit 13cabc9
Show file tree
Hide file tree
Showing 5 changed files with 209 additions and 34 deletions.
Binary file modified fonts/math/stretchy.woff
Binary file not shown.
169 changes: 156 additions & 13 deletions mathml/presentation-markup/operators/mo-axis-height-1.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,100 @@
window.addEventListener("load", () => { loadAllFonts().then(runTests); });

function runTests() {
const AxisHeight = 5000 * emToPx;

test(function() {
var v1 = 5000 * emToPx;
var moMiddle = (getBox("mo1").bottom + getBox("mo1").top) / 2;
assert_approx_equals(getBox("mo1").height,
14000 * emToPx, epsilon, "mo: size");
assert_approx_equals(getBox("baseline1").bottom - moMiddle,
v1, epsilon, "mo: axis height");
}, "AxisHeight (size variant)");
AxisHeight, epsilon, "mo: axis height");
}, "symmetric stretching with respect to the math axis (size variant)");

test(function() {
var v1 = 5000 * emToPx;
var moMiddle = (getBox("mo2").bottom + getBox("mo2").top) / 2;
assert_approx_equals(getBox("mo2").height,
2 * (getBox("target2").height - v1),
2 * (getBox("target2").height - AxisHeight),
epsilon, "mo: size");
assert_approx_equals(getBox("baseline2").bottom - moMiddle,
v1, epsilon, "mo: axis height");
}, "AxisHeight (glyph assembly)");
AxisHeight, epsilon, "mo: axis height");
}, "symmetric stretching with respect to the math axis (glyph assembly)");

test(function() {
const minsize = 14000 * emToPx;
const Tascent = minsize / 2 + AxisHeight;
const Tdescent = minsize - Tascent;
assert_approx_equals(getBox("baseline3").bottom - getBox("mo3").top, Tascent, epsilon, "mo ascent");
assert_approx_equals(getBox("mo3").bottom - getBox("baseline3").bottom, Tdescent, epsilon, "mo descent");
}, "Tascent = Tdescent = 0, minsize = 14em");

test(function() {
const minsize = 14000 * emToPx;
var Tascent = getBox("baseline4").bottom - getBox("target4").top;
assert_greater_than(Tascent, AxisHeight);
var Tdescent = getBox("target4").bottom - getBox("baseline4").bottom;
const T = Tascent + Tdescent;
Tascent = Math.max(0, Tascent - AxisHeight) * minsize / T + AxisHeight;
Tdescent = minsize - Tascent;
assert_approx_equals(getBox("baseline4").bottom - getBox("mo4").top, Tascent, epsilon, "mo ascent");
assert_approx_equals(getBox("mo4").bottom - getBox("baseline4").bottom, Tdescent, epsilon, "mo descent");
}, "Tascent = 6em > AxisHeight, Tdescent = 1em, symmetric = false, minsize = 14em");

test(function() {
const minsize = 14000 * emToPx;
var Tascent = getBox("baseline5").bottom - getBox("target5").top;
assert_less_than(Tascent, AxisHeight);
var Tdescent = getBox("target5").bottom - getBox("baseline5").bottom;
const T = Tascent + Tdescent;
Tascent = Math.max(0, Tascent - AxisHeight) * minsize / T + AxisHeight;
Tdescent = minsize - Tascent;
assert_approx_equals(getBox("baseline5").bottom - getBox("mo5").top, Tascent, epsilon, "mo ascent");
assert_approx_equals(getBox("mo5").bottom - getBox("baseline5").bottom, Tdescent, epsilon, "mo descent");
}, "Tascent = 4em < AxisHeight, Tdescent = 3em, symmetric = false, minsize = 14em");

test(function() {
const maxsize = 14000 * emToPx;
var Tascent = getBox("baseline6").bottom - getBox("target6").top;
assert_greater_than(Tascent, AxisHeight);
var Tdescent = getBox("target6").bottom - getBox("baseline6").bottom;
const T = Tascent + Tdescent;
Tascent = Math.max(0, Tascent - AxisHeight) * maxsize / T + AxisHeight;
Tdescent = maxsize - Tascent;
assert_approx_equals(getBox("baseline6").bottom - getBox("mo6").top, Tascent, epsilon, "mo ascent");
assert_approx_equals(getBox("mo6").bottom - getBox("baseline6").bottom, Tdescent, epsilon, "mo descent");
}, "Tascent = 6em > AxisHeight, Tdescent = 22em, symmetric = false, maxsize = 14em");

test(function() {
const maxsize = 14000 * emToPx;
var Tascent = getBox("baseline7").bottom - getBox("target7").top;
assert_less_than(Tascent, AxisHeight);
var Tdescent = getBox("target7").bottom - getBox("baseline7").bottom;
var T = Tascent + Tdescent;
Tascent = Math.max(0, Tascent - AxisHeight) * maxsize / T + AxisHeight;
Tdescent = maxsize - Tascent;
assert_approx_equals(getBox("baseline7").bottom - getBox("mo7").top, Tascent, epsilon, "mo ascent");
assert_approx_equals(getBox("mo7").bottom - getBox("baseline7").bottom, Tdescent, epsilon, "mo descent");
}, "Tascent = 4em < AxisHeight, Tdescent = 24em, symmetric = false, maxsize = 14em");

test(function() {
const minsize = 14000 * emToPx;
const Uascent = getBox("baseline8").bottom - getBox("target8").top;
const Udescent = getBox("target8").bottom - getBox("baseline8").bottom;
assert_less_than(2 * Math.max(Uascent - AxisHeight, Udescent + AxisHeight), minsize, "Sascent + Sdescent < minsize");
assert_approx_equals(getBox("mo8").height, minsize, epsilon, "mo size");
const MathAxis = getBox("baseline8").bottom - AxisHeight;
assert_approx_equals(MathAxis - getBox("mo8").top, getBox("mo8").bottom - MathAxis, epsilon, "mo is symmetric");
}, "symmetric stretching with respect to the math axis (minsize = 14em)");

test(function() {
const maxsize = 14000 * emToPx;
const Uascent = getBox("baseline9").bottom - getBox("target9").top;
const Udescent = getBox("target9").bottom - getBox("baseline9").bottom;
assert_greater_than(2 * Math.max(Uascent - AxisHeight, Udescent + AxisHeight), maxsize, "Sascent + Sdescent > maxsize");
assert_approx_equals(getBox("mo9").height, maxsize, epsilon, "mo size");
const MathAxis = getBox("baseline9").bottom - AxisHeight;
assert_approx_equals(MathAxis - getBox("mo9").top, getBox("mo9").bottom - MathAxis, epsilon, "mo is symmetric");
}, "symmetric stretching with respect to the math axis (maxsize = 14em)");

done();
}
Expand All @@ -56,20 +132,87 @@
<div id="log"></div>
<p>
<math style="font-family: axisheight5000-verticalarrow14000;">
<mspace id="baseline1" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mrow>
<mspace id="baseline1" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mo id="mo1" symmetric="true" style="color: green">&#x21A8;</mo>
<mspace style="background: gray" width="10px" height="50px"/>
<mpadded style="background: gray" width="10px" height="50px"><mn>1</mn></mpadded>
</mrow>
</math>
<math style="font-family: axisheight5000-verticalarrow14000;">
<mspace id="baseline2" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mrow>
<mspace id="baseline2" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mo id="mo2" symmetric="true" style="color: green">&#x21A8;</mo>
<mspace id="target2" style="background: gray" width="10px" height="200px"/>
<mpadded id="target2" style="background: gray" width="10px" height="200px"><mn>2</mn></mpadded>
</mrow>
</math>
</p>
<p>
<math style="font-family: axisheight5000-verticalarrow14000;">
<mspace id="baseline3" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mrow>
<mo id="mo3" minsize="14em" style="color: green">&#x21A8;</mo>
<mpadded id="target3" style="background: gray" width="10px" height="0px" depth="0px"><mn>3</mn></mpadded>
</mrow>
</math>
</p>
<p>
<math style="font-family: axisheight5000-verticalarrow14000;">
<mspace id="baseline4" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mrow>
<mo id="mo4" minsize="14em" style="color: green">&#x21A8;</mo>
<mpadded id="target4" style="background: gray" width="10px" height="6em" depth="1em"><mn>4</mn></mpadded>
</mrow>
</math>
<math style="font-family: axisheight5000-verticalarrow14000;">
<mspace id="baseline5" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mrow>
<mo id="mo5" minsize="14em" style="color: green">&#x21A8;</mo>
<mpadded id="target5" style="background: gray" width="10px" height="4em" depth="3em"><mn>5</mn></mpadded>
</mrow>
</math>
</p>
<p>
<math style="font-family: axisheight5000-verticalarrow14000;">
<mspace id="baseline6" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mrow>
<mo id="mo6" maxsize="14em" style="color: green">&#x21A8;</mo>
<mpadded id="target6" style="background: gray" width="10px" height="6em" depth="22em"><mn>6</mn></mpadded>
</mrow>
</math>
<math style="font-family: axisheight5000-verticalarrow14000;">
<mspace id="baseline7" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mrow>
<mo id="mo7" maxsize="14em" style="color: green">&#x21A8;</mo>
<mpadded id="target7" style="background: gray" width="10px" height="4em" depth="24em"><mn>7</mn></mpadded>
</mrow>
</math>
</p>


<p>
<math style="font-family: axisheight5000-verticalarrow14000;">
<mspace id="baseline8" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mrow>
<mo id="mo8" symmetric="true" minsize="14em" style="color: green">&#x21A8;</mo>
<mpadded id="target8" style="background: gray" width="10px" height="6em" depth="1em"><mn>8</mn></mpadded>
</mrow>
</math>
<math style="font-family: axisheight5000-verticalarrow14000;">
<mspace id="baseline9" style="background: blue" width="50px" height="1px"/>
<mpadded voffset="50px"><mspace style="background: cyan" width="50px" height="1px"/></mpadded>
<mrow>
<mo id="mo9" symmetric="true" maxsize="14em" style="color: green">&#x21A8;</mo>
<mpadded id="target9" style="background: gray" width="10px" height="6em" depth="24em"><mn>9</mn></mpadded>
</mrow>
</math>
</p>
</body>
</html>
60 changes: 41 additions & 19 deletions mathml/presentation-markup/operators/mo-minsize-maxsize-001.html
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
mo {
font-family: operators;
}
@font-face {
font-family: stretchy;
src: url("/fonts/math/stretchy.woff");
}
</style>
<script>
setup({ explicit_done: true });
Expand Down Expand Up @@ -48,17 +52,27 @@
test(function() {
assert_approx_equals(document.getElementById("percent_minsize").getBoundingClientRect().height, 12 * emToPx, epsilon, "percent minsize");
assert_approx_equals(document.getElementById("percent_maxsize").getBoundingClientRect().height, 3 * emToPx, epsilon, "percent maxsize");
}, `minsize/maxsize percentages are relative to the target size`);
}, `minsize/maxsize percentages are relative to the unstretched size`);

test(function() {
// These tests are not really strong:
// - The smallest glyph for this stretchy operator is a 1em square so
// it can't go under a minsize of 1em anyway.
// - The maxsize is theorically infinite, this only tests that a large
// value of 300em is clamped.
assert_approx_equals(document.getElementById("default_minsize").getBoundingClientRect().height, 1 * emToPx, epsilon, "default minsize is 1em");
// - The unstretched size is a lower bound for the stretched size, so
// specifying a lower minsize has no effect. This test only verifies
// that the default minsize is at most 100% the unstretched size.
const unstretched_size = 1 * emToPx;
assert_approx_equals(document.getElementById("default_minsize").getBoundingClientRect().height, unstretched_size, epsilon, "default minsize is 100%");

// Previous version of MathML Core were defining minsize as 1em rather
// than 100% the unstretched size. So try the same test with a .5em
// unstretched size.
const unstretched_size_2 = .5 * emToPx;
assert_approx_equals(document.getElementById("default_minsize_2").getBoundingClientRect().height, unstretched_size_2, epsilon, "default minsize is not 1em");

// - The target size is an upper bound for the stretched size, so
// specifying a larger maxsize has no effect. This test only
// verifies that the default maxsize is at least 300 times the
// unstretched size.
assert_approx_equals(document.getElementById("default_maxsize").getBoundingClientRect().height, 300 * emToPx, epsilon, "default maxsize is infinity");
}, `default minsize/maxsize percentages`);
}, `default minsize/maxsize values`);

done();
}
Expand All @@ -71,7 +85,7 @@
<mrow>
<mspace width="1em" height="5em" style="background: blue"/>
<mo id="negative_minsize" minsize="-10em" stretchy="true" symmetric="false"></mo>
<mn><!-- not space like --></mn>
<mpadded height="0" depth="0"><mn><!-- not space like --></mn></mpadded>
</mrow>
</math>
</p>
Expand All @@ -80,7 +94,7 @@
<mrow>
<mspace width="1em" height="5em" style="background: blue"/>
<mo id="maxsize_less_than_minsize" minsize="7em" maxsize="2em" stretchy="true" symmetric="false"></mo>
<mn><!-- not space like --></mn>
<mpadded height="0" depth="0"><mn><!-- not space like --></mn></mpadded>
</mrow>
</math>
</p>
Expand All @@ -89,7 +103,7 @@
<mrow>
<mspace width="1em" height="5em" style="background: blue"/>
<mo id="minsize_less_than_negative_maxsize" minsize="-2em" maxsize="-1em" stretchy="true" symmetric="false"></mo>
<mn><!-- not space like --></mn>
<mpadded height="0" depth="0"><mn><!-- not space like --></mn></mpadded>
</mrow>
</math>
</p>
Expand All @@ -98,25 +112,25 @@
<mrow>
<mspace id="zero_target_size_with_minsize_math_axis" width="1em" height="0em" style="background: blue"/>
<mo id="zero_target_size_with_minsize" minsize="2em" stretchy="true" symmetric="true"></mo>
<mn><!-- not space like --></mn>
<mpadded height="0" depth="0"><mn><!-- not space like --></mn></mpadded>
</mrow>
</math>
</p>
<p>
<math>
<mrow>
<mspace width="1em" height="6em" style="background: blue"/>
<mo id="percent_minsize" minsize="200%" stretchy="true" symmetric="false"></mo>
<mn><!-- not space like --></mn>
<mo id="percent_minsize" minsize="1200%" stretchy="true" symmetric="false"></mo>
<mpadded height="0" depth="0"><mn><!-- not space like --></mn></mpadded>
</mrow>
</math>
</p>
<p>
<math>
<mrow>
<mspace width="1em" height="6em" style="background: blue"/>
<mo id="percent_maxsize" maxsize="50%" stretchy="true" symmetric="false"></mo>
<mn><!-- not space like --></mn>
<mo id="percent_maxsize" maxsize="300%" stretchy="true" symmetric="false"></mo>
<mpadded height="0" depth="0"><mn><!-- not space like --></mn></mpadded>
</mrow>
</math>
</p>
Expand All @@ -125,7 +139,16 @@
<mrow>
<mspace width="1em" height=".5em" style="background: blue"/>
<mo id="default_minsize" stretchy="true" symmetric="false"></mo>
<mn><!-- not space like --></mn>
<mpadded height="0" depth="0"><mn><!-- not space like --></mn></mpadded>
</mrow>
</math>
</p>
<p>
<math>
<mrow>
<mspace width="1em" height=".25em" style="background: blue"/>
<mo style="font-family: stretchy" id="default_minsize_2" stretchy="true" symmetric="false"></mo>
<mpadded height="0" depth="0"><mn><!-- not space like --></mn></mpadded>
</mrow>
</math>
</p>
Expand All @@ -134,10 +157,9 @@
<mrow>
<mspace width="1em" height="300em" style="background: blue"/>
<mo id="default_maxsize" stretchy="true" symmetric="false"></mo>
<mn><!-- not space like --></mn>
<mpadded height="0" depth="0"><mn><!-- not space like --></mn></mpadded>
</mrow>
</math>
</p>

</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@

element = document.getElementById("minsize_remove");
element.removeAttribute("minsize");
assert_approx_equals(element.getBoundingClientRect().height, 1 * emToPx, epsilon, "remove");
const unstretched_size = 1 * emToPx;
assert_approx_equals(element.getBoundingClientRect().height, unstretched_size, epsilon, "remove");
}, `minsize`);

test(function() {
Expand Down
11 changes: 10 additions & 1 deletion mathml/tools/stretchy.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
import fontforge

# Create a WOFF font with glyphs for all the operator strings.
font = mathfont.create("stretchy", "Copyright (c) 2021 Igalia S.L.")
font = mathfont.create("stretchy", "Copyright (c) 2021-2024 Igalia S.L.")

font.math.AxisHeight = 0

# Set parameters for stretchy tests.
font.math.MinConnectorOverlap = mathfont.em // 2
Expand All @@ -27,6 +29,7 @@
# These two characters will be stretchable in both directions.
horizontalArrow = 0x295A # LEFTWARDS HARPOON WITH BARB UP FROM BAR
verticalArrow = 0x295C # UPWARDS HARPOON WITH BARB RIGHT FROM BAR
upDownArrowWithBase = 0x21A8 # UP DOWN ARROW WITH BASE

mathfont.createSizeVariants(font, aUsePUA=True, aCenterOnBaseline=False)

Expand All @@ -40,4 +43,10 @@
mathfont.createStretchy(font, verticalArrow, True)
mathfont.createStretchy(font, verticalArrow, False)

# U+21A8 stretches vertically using two size variants: a base glyph (of height
# half an em) and taller glyphs (of heights 1, 2, 3 and 4 em).
g = font.createChar(upDownArrowWithBase)
mathfont.drawRectangleGlyph(g, mathfont.em, mathfont.em/2, 0)
font[upDownArrowWithBase].verticalVariants = "uni21A8 v0 v1 v2 v3"

mathfont.save(font)

0 comments on commit 13cabc9

Please sign in to comment.