forked from mirrors/gecko-dev
Bug 1840075 - Implement support for the OpenType BASE (baselines) table, and use it to back canvas2d TextMetrics attributes and textBaseline alignment. r=gfx-reviewers,lsalzman
Differential Revision: https://phabricator.services.mozilla.com/D181882
This commit is contained in:
parent
ca6cc122c2
commit
c2251d42ce
24 changed files with 93 additions and 103 deletions
|
|
@ -4317,14 +4317,6 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||||
const nsAString& aRawText, float aX, float aY,
|
const nsAString& aRawText, float aX, float aY,
|
||||||
const Optional<double>& aMaxWidth, TextDrawOperation aOp,
|
const Optional<double>& aMaxWidth, TextDrawOperation aOp,
|
||||||
ErrorResult& aError) {
|
ErrorResult& aError) {
|
||||||
// Approximated baselines. In an ideal world, we'd read the baseline info
|
|
||||||
// directly from the font (where available). Alas we currently lack
|
|
||||||
// that functionality. These numbers are best guesses and should
|
|
||||||
// suffice for now. Both are fractions of the em ascent/descent from the
|
|
||||||
// alphabetic baseline.
|
|
||||||
const double kHangingBaselineDefault = 0.8; // fraction of ascent
|
|
||||||
const double kIdeographicBaselineDefault = 0.5; // fraction of descent
|
|
||||||
|
|
||||||
gfxFontGroup* currentFontStyle = GetCurrentFontStyle();
|
gfxFontGroup* currentFontStyle = GetCurrentFontStyle();
|
||||||
if (NS_WARN_IF(!currentFontStyle)) {
|
if (NS_WARN_IF(!currentFontStyle)) {
|
||||||
aError = NS_ERROR_FAILURE;
|
aError = NS_ERROR_FAILURE;
|
||||||
|
|
@ -4514,12 +4506,20 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||||
float offsetX = anchorX * totalWidth;
|
float offsetX = anchorX * totalWidth;
|
||||||
processor.mPt.x -= offsetX;
|
processor.mPt.x -= offsetX;
|
||||||
|
|
||||||
|
gfx::ShapedTextFlags runOrientation =
|
||||||
|
(processor.mTextRunFlags & gfx::ShapedTextFlags::TEXT_ORIENT_MASK);
|
||||||
|
nsFontMetrics::FontOrientation fontOrientation =
|
||||||
|
(runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED ||
|
||||||
|
runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT)
|
||||||
|
? nsFontMetrics::eVertical
|
||||||
|
: nsFontMetrics::eHorizontal;
|
||||||
|
|
||||||
// offset pt.y (or pt.x, for vertical text) based on text baseline
|
// offset pt.y (or pt.x, for vertical text) based on text baseline
|
||||||
gfxFloat baselineAnchor;
|
gfxFloat baselineAnchor;
|
||||||
|
|
||||||
switch (state.textBaseline) {
|
switch (state.textBaseline) {
|
||||||
case CanvasTextBaseline::Hanging:
|
case CanvasTextBaseline::Hanging:
|
||||||
baselineAnchor = fontMetrics.emAscent * kHangingBaselineDefault;
|
baselineAnchor = font->GetBaselines(fontOrientation).mHanging;
|
||||||
break;
|
break;
|
||||||
case CanvasTextBaseline::Top:
|
case CanvasTextBaseline::Top:
|
||||||
baselineAnchor = fontMetrics.emAscent;
|
baselineAnchor = fontMetrics.emAscent;
|
||||||
|
|
@ -4528,10 +4528,10 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||||
baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
|
baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
|
||||||
break;
|
break;
|
||||||
case CanvasTextBaseline::Alphabetic:
|
case CanvasTextBaseline::Alphabetic:
|
||||||
baselineAnchor = 0;
|
baselineAnchor = font->GetBaselines(fontOrientation).mAlphabetic;
|
||||||
break;
|
break;
|
||||||
case CanvasTextBaseline::Ideographic:
|
case CanvasTextBaseline::Ideographic:
|
||||||
baselineAnchor = -fontMetrics.emDescent * kIdeographicBaselineDefault;
|
baselineAnchor = font->GetBaselines(fontOrientation).mIdeographic;
|
||||||
break;
|
break;
|
||||||
case CanvasTextBaseline::Bottom:
|
case CanvasTextBaseline::Bottom:
|
||||||
baselineAnchor = -fontMetrics.emDescent;
|
baselineAnchor = -fontMetrics.emDescent;
|
||||||
|
|
@ -4542,11 +4542,8 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||||
|
|
||||||
// We can't query the textRun directly, as it may not have been created yet;
|
// We can't query the textRun directly, as it may not have been created yet;
|
||||||
// so instead we check the flags that will be used to initialize it.
|
// so instead we check the flags that will be used to initialize it.
|
||||||
gfx::ShapedTextFlags runOrientation =
|
|
||||||
(processor.mTextRunFlags & gfx::ShapedTextFlags::TEXT_ORIENT_MASK);
|
|
||||||
if (runOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL) {
|
if (runOrientation != gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL) {
|
||||||
if (runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED ||
|
if (fontOrientation == nsFontMetrics::eVertical) {
|
||||||
runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) {
|
|
||||||
// Adjust to account for mTextRun being shaped using center baseline
|
// Adjust to account for mTextRun being shaped using center baseline
|
||||||
// rather than alphabetic.
|
// rather than alphabetic.
|
||||||
baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
|
baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
|
||||||
|
|
@ -4567,10 +4564,7 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||||
-processor.mBoundingBox.Y() - baselineAnchor;
|
-processor.mBoundingBox.Y() - baselineAnchor;
|
||||||
double actualBoundingBoxDescent =
|
double actualBoundingBoxDescent =
|
||||||
processor.mBoundingBox.YMost() + baselineAnchor;
|
processor.mBoundingBox.YMost() + baselineAnchor;
|
||||||
double hangingBaseline =
|
auto baselines = font->GetBaselines(fontOrientation);
|
||||||
fontMetrics.emAscent * kHangingBaselineDefault - baselineAnchor;
|
|
||||||
double ideographicBaseline =
|
|
||||||
-fontMetrics.emDescent * kIdeographicBaselineDefault - baselineAnchor;
|
|
||||||
return new TextMetrics(
|
return new TextMetrics(
|
||||||
totalWidth, actualBoundingBoxLeft, actualBoundingBoxRight,
|
totalWidth, actualBoundingBoxLeft, actualBoundingBoxRight,
|
||||||
fontMetrics.maxAscent - baselineAnchor, // fontBBAscent
|
fontMetrics.maxAscent - baselineAnchor, // fontBBAscent
|
||||||
|
|
@ -4578,9 +4572,9 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||||
actualBoundingBoxAscent, actualBoundingBoxDescent,
|
actualBoundingBoxAscent, actualBoundingBoxDescent,
|
||||||
fontMetrics.emAscent - baselineAnchor, // emHeightAscent
|
fontMetrics.emAscent - baselineAnchor, // emHeightAscent
|
||||||
-fontMetrics.emDescent - baselineAnchor, // emHeightDescent
|
-fontMetrics.emDescent - baselineAnchor, // emHeightDescent
|
||||||
hangingBaseline,
|
baselines.mHanging - baselineAnchor,
|
||||||
-baselineAnchor, // alphabeticBaseline
|
baselines.mAlphabetic - baselineAnchor,
|
||||||
ideographicBaseline);
|
baselines.mIdeographic - baselineAnchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we did not actually calculate bounds, set up a simple bounding box
|
// If we did not actually calculate bounds, set up a simple bounding box
|
||||||
|
|
|
||||||
|
|
@ -4201,6 +4201,71 @@ void gfxFont::SanitizeMetrics(gfxFont::Metrics* aMetrics,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gfxFont::Baselines gfxFont::GetBaselines(Orientation aOrientation) {
|
||||||
|
// Approximated baselines for fonts lacking actual baseline data. These are
|
||||||
|
// fractions of the em ascent/descent from the alphabetic baseline.
|
||||||
|
const double kHangingBaselineDefault = 0.8; // fraction of ascent
|
||||||
|
const double kIdeographicBaselineDefault = -0.5; // fraction of descent
|
||||||
|
|
||||||
|
// If no BASE table is present, just return synthetic values immediately.
|
||||||
|
if (!mFontEntry->HasFontTable(TRUETYPE_TAG('B', 'A', 'S', 'E'))) {
|
||||||
|
// No baseline table; just synthesize them immediately.
|
||||||
|
const Metrics& metrics = GetMetrics(aOrientation);
|
||||||
|
return Baselines{
|
||||||
|
0.0, // alphabetic
|
||||||
|
kHangingBaselineDefault * metrics.emAscent, // hanging
|
||||||
|
kIdeographicBaselineDefault * metrics.emDescent // ideographic
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use harfbuzz to try to read the font's baseline metrics.
|
||||||
|
Baselines result{NAN, NAN, NAN};
|
||||||
|
hb_font_t* hbFont = gfxHarfBuzzShaper::CreateHBFont(this);
|
||||||
|
hb_direction_t hbDir = aOrientation == nsFontMetrics::eHorizontal
|
||||||
|
? HB_DIRECTION_LTR
|
||||||
|
: HB_DIRECTION_TTB;
|
||||||
|
hb_position_t position;
|
||||||
|
unsigned count = 0;
|
||||||
|
auto Fix2Float = [](hb_position_t f) -> gfxFloat { return f / 65536.0; };
|
||||||
|
if (hb_ot_layout_get_baseline(hbFont, HB_OT_LAYOUT_BASELINE_TAG_ROMAN, hbDir,
|
||||||
|
HB_OT_TAG_DEFAULT_SCRIPT,
|
||||||
|
HB_OT_TAG_DEFAULT_LANGUAGE, &position)) {
|
||||||
|
result.mAlphabetic = Fix2Float(position);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (hb_ot_layout_get_baseline(hbFont, HB_OT_LAYOUT_BASELINE_TAG_HANGING,
|
||||||
|
hbDir, HB_OT_TAG_DEFAULT_SCRIPT,
|
||||||
|
HB_OT_TAG_DEFAULT_LANGUAGE, &position)) {
|
||||||
|
result.mHanging = Fix2Float(position);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
if (hb_ot_layout_get_baseline(
|
||||||
|
hbFont, HB_OT_LAYOUT_BASELINE_TAG_IDEO_EMBOX_BOTTOM_OR_LEFT, hbDir,
|
||||||
|
HB_OT_TAG_DEFAULT_SCRIPT, HB_OT_TAG_DEFAULT_LANGUAGE, &position)) {
|
||||||
|
result.mIdeographic = Fix2Float(position);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
hb_font_destroy(hbFont);
|
||||||
|
// If we successfully read all three, we can return now.
|
||||||
|
if (count == 3) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synthesize the baselines that we didn't find in the font.
|
||||||
|
const Metrics& metrics = GetMetrics(aOrientation);
|
||||||
|
if (isnan(result.mAlphabetic)) {
|
||||||
|
result.mAlphabetic = 0.0;
|
||||||
|
}
|
||||||
|
if (isnan(result.mHanging)) {
|
||||||
|
result.mHanging = kHangingBaselineDefault * metrics.emAscent;
|
||||||
|
}
|
||||||
|
if (isnan(result.mIdeographic)) {
|
||||||
|
result.mIdeographic = kIdeographicBaselineDefault * metrics.emDescent;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
// Create a Metrics record to be used for vertical layout. This should never
|
// Create a Metrics record to be used for vertical layout. This should never
|
||||||
// fail, as we've already decided this is a valid font. We do not have the
|
// fail, as we've already decided this is a valid font. We do not have the
|
||||||
// option of marking it invalid (as can happen if we're unable to read
|
// option of marking it invalid (as can happen if we're unable to read
|
||||||
|
|
|
||||||
|
|
@ -1624,6 +1624,13 @@ class gfxFont {
|
||||||
return *mVerticalMetrics;
|
return *mVerticalMetrics;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Baselines {
|
||||||
|
gfxFloat mAlphabetic;
|
||||||
|
gfxFloat mHanging;
|
||||||
|
gfxFloat mIdeographic;
|
||||||
|
};
|
||||||
|
Baselines GetBaselines(Orientation aOrientation);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We let layout specify spacing on either side of any
|
* We let layout specify spacing on either side of any
|
||||||
* character. We need to specify both before and after
|
* character. We need to specify both before and after
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,10 @@ class MOZ_STACK_CLASS gfxOTSContext : public ots::OTSContext {
|
||||||
if (aTag == TRUETYPE_TAG('S', 'V', 'G', ' ')) {
|
if (aTag == TRUETYPE_TAG('S', 'V', 'G', ' ')) {
|
||||||
return mKeepSVG ? ots::TABLE_ACTION_PASSTHRU : ots::TABLE_ACTION_DROP;
|
return mKeepSVG ? ots::TABLE_ACTION_PASSTHRU : ots::TABLE_ACTION_DROP;
|
||||||
}
|
}
|
||||||
|
// Preserve BASE table; harfbuzz will sanitize it before using.
|
||||||
|
if (aTag == TRUETYPE_TAG('B', 'A', 'S', 'E')) {
|
||||||
|
return ots::TABLE_ACTION_PASSTHRU;
|
||||||
|
}
|
||||||
if (mKeepColorBitmaps && (aTag == TRUETYPE_TAG('C', 'B', 'D', 'T') ||
|
if (mKeepColorBitmaps && (aTag == TRUETYPE_TAG('C', 'B', 'D', 'T') ||
|
||||||
aTag == TRUETYPE_TAG('C', 'B', 'L', 'C'))) {
|
aTag == TRUETYPE_TAG('C', 'B', 'L', 'C'))) {
|
||||||
return ots::TABLE_ACTION_PASSTHRU;
|
return ots::TABLE_ACTION_PASSTHRU;
|
||||||
|
|
|
||||||
|
|
@ -4200,7 +4200,7 @@
|
||||||
|
|
||||||
- name: dom.textMetrics.baselines.enabled
|
- name: dom.textMetrics.baselines.enabled
|
||||||
type: RelaxedAtomicBool
|
type: RelaxedAtomicBool
|
||||||
value: false
|
value: true
|
||||||
mirror: always
|
mirror: always
|
||||||
|
|
||||||
- name: dom.textMetrics.emHeight.enabled
|
- name: dom.textMetrics.emHeight.enabled
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
[2d.text.measure.baselines.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [TIMEOUT, OK]
|
|
||||||
[Testing baselines]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.baseline.default.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [OK, TIMEOUT]
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.baseline.invalid.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [OK, TIMEOUT]
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.baseline.valid.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [OK, TIMEOUT]
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.draw.baseline.alphabetic.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [OK, TIMEOUT]
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.draw.baseline.bottom.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [OK, TIMEOUT]
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
[2d.text.draw.baseline.hanging.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [OK, TIMEOUT]
|
|
||||||
[Canvas test: 2d.text.draw.baseline.hanging]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
[2d.text.draw.baseline.ideographic.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [OK, TIMEOUT]
|
|
||||||
[Canvas test: 2d.text.draw.baseline.ideographic]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.draw.baseline.middle.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [OK, TIMEOUT]
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.draw.baseline.top.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [OK, TIMEOUT]
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.state.saverestore.textBaseline.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: [OK, TIMEOUT]
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.draw.baseline.hanging.html]
|
|
||||||
[OffscreenCanvas test: 2d.text.draw.baseline.hanging]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
@ -1,3 +0,0 @@
|
||||||
[2d.text.draw.baseline.hanging.worker.html]
|
|
||||||
[2d]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
[2d.text.draw.baseline.ideographic.html]
|
|
||||||
expected:
|
|
||||||
if (os == "android") and fission: TIMEOUT
|
|
||||||
[OffscreenCanvas test: 2d.text.draw.baseline.ideographic]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
[2d.text.draw.baseline.ideographic.worker.html]
|
|
||||||
[2d]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
[2d.text.measure.baselines.html]
|
|
||||||
[Testing baselines for OffscreenCanvas]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
[2d.text.measure.baselines.worker.html]
|
|
||||||
[Testing baselines for OffscreenCanvas]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
|
|
@ -33,9 +33,6 @@ prefs: [dom.security.featurePolicy.experimental.enabled:true, dom.security.featu
|
||||||
[CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "getContextAttributes()" with the proper type]
|
[CanvasRenderingContext2D interface: document.createElement("canvas").getContext("2d") must inherit property "getContextAttributes()" with the proper type]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[TextMetrics interface: attribute ideographicBaseline]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[VideoTrackList interface object length]
|
[VideoTrackList interface object length]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
@ -186,9 +183,6 @@ prefs: [dom.security.featurePolicy.experimental.enabled:true, dom.security.featu
|
||||||
[AudioTrack interface: attribute kind]
|
[AudioTrack interface: attribute kind]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[TextMetrics interface: attribute hangingBaseline]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[VideoTrackList interface: existence and properties of interface prototype object]
|
[VideoTrackList interface: existence and properties of interface prototype object]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
@ -201,9 +195,6 @@ prefs: [dom.security.featurePolicy.experimental.enabled:true, dom.security.featu
|
||||||
[VideoTrackList interface: existence and properties of interface prototype object's @@unscopables property]
|
[VideoTrackList interface: existence and properties of interface prototype object's @@unscopables property]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[TextMetrics interface: attribute alphabeticBaseline]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[VideoTrackList interface: attribute length]
|
[VideoTrackList interface: attribute length]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,21 +2,12 @@
|
||||||
[TextMetrics interface: attribute emHeightAscent]
|
[TextMetrics interface: attribute emHeightAscent]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[TextMetrics interface: attribute ideographicBaseline]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[OffscreenCanvasRenderingContext2D interface: attribute imageSmoothingQuality]
|
[OffscreenCanvasRenderingContext2D interface: attribute imageSmoothingQuality]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[TextMetrics interface: attribute emHeightDescent]
|
[TextMetrics interface: attribute emHeightDescent]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
[TextMetrics interface: attribute hangingBaseline]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[TextMetrics interface: attribute alphabeticBaseline]
|
|
||||||
expected: FAIL
|
|
||||||
|
|
||||||
[ImageData interface: attribute colorSpace]
|
[ImageData interface: attribute colorSpace]
|
||||||
expected: FAIL
|
expected: FAIL
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue