mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-26 22:32:46 +00:00
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
@ -4317,14 +4317,6 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||
const nsAString& aRawText, float aX, float aY,
|
||||
const Optional<double>& aMaxWidth, TextDrawOperation aOp,
|
||||
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();
|
||||
if (NS_WARN_IF(!currentFontStyle)) {
|
||||
aError = NS_ERROR_FAILURE;
|
||||
@ -4514,12 +4506,20 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||
float offsetX = anchorX * totalWidth;
|
||||
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
|
||||
gfxFloat baselineAnchor;
|
||||
|
||||
switch (state.textBaseline) {
|
||||
case CanvasTextBaseline::Hanging:
|
||||
baselineAnchor = fontMetrics.emAscent * kHangingBaselineDefault;
|
||||
baselineAnchor = font->GetBaselines(fontOrientation).mHanging;
|
||||
break;
|
||||
case CanvasTextBaseline::Top:
|
||||
baselineAnchor = fontMetrics.emAscent;
|
||||
@ -4528,10 +4528,10 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||
baselineAnchor = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
|
||||
break;
|
||||
case CanvasTextBaseline::Alphabetic:
|
||||
baselineAnchor = 0;
|
||||
baselineAnchor = font->GetBaselines(fontOrientation).mAlphabetic;
|
||||
break;
|
||||
case CanvasTextBaseline::Ideographic:
|
||||
baselineAnchor = -fontMetrics.emDescent * kIdeographicBaselineDefault;
|
||||
baselineAnchor = font->GetBaselines(fontOrientation).mIdeographic;
|
||||
break;
|
||||
case CanvasTextBaseline::Bottom:
|
||||
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;
|
||||
// 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_VERTICAL_MIXED ||
|
||||
runOrientation == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT) {
|
||||
if (fontOrientation == nsFontMetrics::eVertical) {
|
||||
// Adjust to account for mTextRun being shaped using center baseline
|
||||
// rather than alphabetic.
|
||||
baselineAnchor -= (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
|
||||
@ -4567,10 +4564,7 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||
-processor.mBoundingBox.Y() - baselineAnchor;
|
||||
double actualBoundingBoxDescent =
|
||||
processor.mBoundingBox.YMost() + baselineAnchor;
|
||||
double hangingBaseline =
|
||||
fontMetrics.emAscent * kHangingBaselineDefault - baselineAnchor;
|
||||
double ideographicBaseline =
|
||||
-fontMetrics.emDescent * kIdeographicBaselineDefault - baselineAnchor;
|
||||
auto baselines = font->GetBaselines(fontOrientation);
|
||||
return new TextMetrics(
|
||||
totalWidth, actualBoundingBoxLeft, actualBoundingBoxRight,
|
||||
fontMetrics.maxAscent - baselineAnchor, // fontBBAscent
|
||||
@ -4578,9 +4572,9 @@ TextMetrics* CanvasRenderingContext2D::DrawOrMeasureText(
|
||||
actualBoundingBoxAscent, actualBoundingBoxDescent,
|
||||
fontMetrics.emAscent - baselineAnchor, // emHeightAscent
|
||||
-fontMetrics.emDescent - baselineAnchor, // emHeightDescent
|
||||
hangingBaseline,
|
||||
-baselineAnchor, // alphabeticBaseline
|
||||
ideographicBaseline);
|
||||
baselines.mHanging - baselineAnchor,
|
||||
baselines.mAlphabetic - baselineAnchor,
|
||||
baselines.mIdeographic - baselineAnchor);
|
||||
}
|
||||
|
||||
// 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
|
||||
// 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
|
||||
|
@ -1624,6 +1624,13 @@ class gfxFont {
|
||||
return *mVerticalMetrics;
|
||||
}
|
||||
|
||||
struct Baselines {
|
||||
gfxFloat mAlphabetic;
|
||||
gfxFloat mHanging;
|
||||
gfxFloat mIdeographic;
|
||||
};
|
||||
Baselines GetBaselines(Orientation aOrientation);
|
||||
|
||||
/**
|
||||
* We let layout specify spacing on either side of any
|
||||
* 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', ' ')) {
|
||||
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') ||
|
||||
aTag == TRUETYPE_TAG('C', 'B', 'L', 'C'))) {
|
||||
return ots::TABLE_ACTION_PASSTHRU;
|
||||
|
@ -4200,7 +4200,7 @@
|
||||
|
||||
- name: dom.textMetrics.baselines.enabled
|
||||
type: RelaxedAtomicBool
|
||||
value: false
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
- 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]
|
||||
expected: FAIL
|
||||
|
||||
[TextMetrics interface: attribute ideographicBaseline]
|
||||
expected: FAIL
|
||||
|
||||
[VideoTrackList interface object length]
|
||||
expected: FAIL
|
||||
|
||||
@ -186,9 +183,6 @@ prefs: [dom.security.featurePolicy.experimental.enabled:true, dom.security.featu
|
||||
[AudioTrack interface: attribute kind]
|
||||
expected: FAIL
|
||||
|
||||
[TextMetrics interface: attribute hangingBaseline]
|
||||
expected: FAIL
|
||||
|
||||
[VideoTrackList interface: existence and properties of interface prototype object]
|
||||
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]
|
||||
expected: FAIL
|
||||
|
||||
[TextMetrics interface: attribute alphabeticBaseline]
|
||||
expected: FAIL
|
||||
|
||||
[VideoTrackList interface: attribute length]
|
||||
expected: FAIL
|
||||
|
||||
|
@ -2,21 +2,12 @@
|
||||
[TextMetrics interface: attribute emHeightAscent]
|
||||
expected: FAIL
|
||||
|
||||
[TextMetrics interface: attribute ideographicBaseline]
|
||||
expected: FAIL
|
||||
|
||||
[OffscreenCanvasRenderingContext2D interface: attribute imageSmoothingQuality]
|
||||
expected: FAIL
|
||||
|
||||
[TextMetrics interface: attribute emHeightDescent]
|
||||
expected: FAIL
|
||||
|
||||
[TextMetrics interface: attribute hangingBaseline]
|
||||
expected: FAIL
|
||||
|
||||
[TextMetrics interface: attribute alphabeticBaseline]
|
||||
expected: FAIL
|
||||
|
||||
[ImageData interface: attribute colorSpace]
|
||||
expected: FAIL
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user