Files
tubestation/layout/generic/TextDrawTarget.h
Marian-Vasile Laza da24ed0855 Backed out 7 changesets (bug 1728050, bug 1728251, bug 1728232, bug 1542929, bug 1714138, bug 1728258) for causing reftest failures. CLOSED TREE
Backed out changeset 41e053201cd3 (bug 1714138)
Backed out changeset 16ae9f5b4aa0 (bug 1728258)
Backed out changeset 414aa7ff05fe (bug 1542929)
Backed out changeset 7217ecf149c4 (bug 1728251)
Backed out changeset 405ff2ed2110 (bug 1728251)
Backed out changeset 3eec9e6c7218 (bug 1728232)
Backed out changeset 66038c15fe54 (bug 1728050)
2021-09-07 09:00:26 +03:00

613 lines
23 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#ifndef TextDrawTarget_h
#define TextDrawTarget_h
#include "mozilla/gfx/2D.h"
#include "mozilla/layers/RenderRootStateManager.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/layers/WebRenderBridgeChild.h"
#include "mozilla/webrender/WebRenderAPI.h"
#include "mozilla/layers/StackingContextHelper.h"
#include "mozilla/layers/IpcResourceUpdateQueue.h"
namespace mozilla {
namespace layout {
using namespace gfx;
// This class is a fake DrawTarget, used to intercept text draw calls, while
// also collecting up the other aspects of text natively.
//
// When using advanced-layers in nsDisplayText's constructor, we construct this
// and run the full painting algorithm with this as the DrawTarget. This is
// done to avoid having to massively refactor gecko's text painting code (which
// has lots of components shared between other rendering algorithms).
//
// In some phases of the painting algorithm, we can grab the relevant values
// and feed them directly into TextDrawTarget. For instance, selections,
// decorations, and shadows are handled in this manner. In those cases we can
// also short-circuit the painting algorithm to save work.
//
// In other phases, the computed values are sufficiently buried in complex
// code that it's best for us to just intercept the final draw calls. This
// is how we handle computing the glyphs of the main text and text-emphasis
// (see our overloaded FillGlyphs implementation).
//
// To be clear: this is a big hack. With time we hope to refactor the codebase
// so that all the elements of text are handled directly by TextDrawTarget,
// which is to say everything is done like we do selections and shadows now.
// This design is a good step for doing this work incrementally.
//
// This is also likely to be a bit buggy (missing or misinterpreted info)
// while we further develop the design.
//
// TextDrawTarget doesn't yet support all features. See mHasUnsupportedFeatures
// for details.
class TextDrawTarget : public DrawTarget {
public:
explicit TextDrawTarget(wr::DisplayListBuilder& aBuilder,
wr::IpcResourceUpdateQueue& aResources,
const layers::StackingContextHelper& aSc,
layers::RenderRootStateManager* aManager,
nsDisplayItem* aItem, nsRect& aBounds,
bool aCallerDoesSaveRestore = false)
: mCallerDoesSaveRestore(aCallerDoesSaveRestore), mBuilder(aBuilder) {
Reinitialize(aResources, aSc, aManager, aItem, aBounds);
}
// Prevent this from being copied
TextDrawTarget(const TextDrawTarget& src) = delete;
TextDrawTarget& operator=(const TextDrawTarget&) = delete;
~TextDrawTarget() { MOZ_ASSERT(mFinished); }
void Reinitialize(wr::IpcResourceUpdateQueue& aResources,
const layers::StackingContextHelper& aSc,
layers::RenderRootStateManager* aManager,
nsDisplayItem* aItem, nsRect& aBounds) {
mResources = &aResources;
mSc = &aSc;
mManager = aManager;
mHasUnsupportedFeatures = false;
mHasShadows = false;
SetPermitSubpixelAA(!aItem->IsSubpixelAADisabled());
// Compute clip/bounds
auto appUnitsPerDevPixel =
aItem->Frame()->PresContext()->AppUnitsPerDevPixel();
LayoutDeviceRect layoutBoundsRect =
LayoutDeviceRect::FromAppUnits(aBounds, appUnitsPerDevPixel);
LayoutDeviceRect layoutClipRect = layoutBoundsRect;
mBoundsRect = wr::ToLayoutRect(layoutBoundsRect);
// Add 1 pixel of dirty area around clip rect to allow us to paint
// antialiased pixels beyond the measured text extents.
layoutClipRect.Inflate(1);
mSize = IntSize::Ceil(layoutClipRect.Width(), layoutClipRect.Height());
mClipStack.ClearAndRetainStorage();
mClipStack.AppendElement(layoutClipRect);
mBackfaceVisible = !aItem->BackfaceIsHidden();
if (!mCallerDoesSaveRestore) {
mBuilder.Save();
}
}
void FoundUnsupportedFeature() { mHasUnsupportedFeatures = true; }
bool CheckHasUnsupportedFeatures() {
MOZ_ASSERT(mCallerDoesSaveRestore);
#ifdef DEBUG
MOZ_ASSERT(!mFinished);
mFinished = true;
#endif
return mHasUnsupportedFeatures;
}
bool Finish() {
MOZ_ASSERT(!mCallerDoesSaveRestore);
#ifdef DEBUG
mFinished = true;
#endif
if (mHasUnsupportedFeatures) {
mBuilder.Restore();
return false;
}
mBuilder.ClearSave();
return true;
}
wr::FontInstanceFlags GetWRGlyphFlags() const { return mWRGlyphFlags; }
void SetWRGlyphFlags(wr::FontInstanceFlags aFlags) { mWRGlyphFlags = aFlags; }
class AutoRestoreWRGlyphFlags {
public:
~AutoRestoreWRGlyphFlags() {
if (mTarget) {
mTarget->SetWRGlyphFlags(mFlags);
}
}
void Save(TextDrawTarget* aTarget) {
// This allows for recursive saves, in case the flags need to be modified
// under multiple conditions (i.e. transforms and synthetic italics),
// since the flags will be restored to the first saved value in the
// destructor on scope exit.
if (!mTarget) {
// Only record the first save with the original flags that will be
// restored.
mTarget = aTarget;
mFlags = aTarget->GetWRGlyphFlags();
} else {
// Ensure that this is actually a recursive save to the same target
MOZ_ASSERT(
mTarget == aTarget,
"Recursive save of WR glyph flags to different TextDrawTargets");
}
}
private:
TextDrawTarget* mTarget = nullptr;
wr::FontInstanceFlags mFlags = {0};
};
// This overload just stores the glyphs/font/color.
void FillGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
const Pattern& aPattern,
const DrawOptions& aOptions) override {
// Make sure we're only given boring color patterns
MOZ_RELEASE_ASSERT(aOptions.mCompositionOp == CompositionOp::OP_OVER);
MOZ_RELEASE_ASSERT(aOptions.mAlpha == 1.0f);
MOZ_RELEASE_ASSERT(aPattern.GetType() == PatternType::COLOR);
// Make sure the font exists, and can be serialized
MOZ_RELEASE_ASSERT(aFont);
if (!aFont->CanSerialize()) {
FoundUnsupportedFeature();
return;
}
auto* colorPat = static_cast<const ColorPattern*>(&aPattern);
auto color = wr::ToColorF(colorPat->mColor);
MOZ_ASSERT(aBuffer.mNumGlyphs);
auto glyphs = Range<const wr::GlyphInstance>(
reinterpret_cast<const wr::GlyphInstance*>(aBuffer.mGlyphs),
aBuffer.mNumGlyphs);
// MSVC won't let us use offsetof on the following directly so we give it a
// name with typedef
typedef std::remove_reference<decltype(aBuffer.mGlyphs[0])>::type GlyphType;
// Compare gfx::Glyph and wr::GlyphInstance to make sure that they are
// structurally equivalent to ensure that our cast above was ok
static_assert(
std::is_same<decltype(aBuffer.mGlyphs[0].mIndex),
decltype(glyphs[0].index)>() &&
std::is_same<decltype(aBuffer.mGlyphs[0].mPosition.x),
decltype(glyphs[0].point.x)>() &&
std::is_same<decltype(aBuffer.mGlyphs[0].mPosition.y),
decltype(glyphs[0].point.y)>() &&
offsetof(GlyphType, mIndex) == offsetof(wr::GlyphInstance, index) &&
offsetof(GlyphType, mPosition) ==
offsetof(wr::GlyphInstance, point) &&
offsetof(decltype(aBuffer.mGlyphs[0].mPosition), x) ==
offsetof(decltype(glyphs[0].point), x) &&
offsetof(decltype(aBuffer.mGlyphs[0].mPosition), y) ==
offsetof(decltype(glyphs[0].point), y) &&
std::is_standard_layout<
std::remove_reference<decltype(aBuffer.mGlyphs[0])>>::value &&
std::is_standard_layout<
std::remove_reference<decltype(glyphs[0])>>::value &&
sizeof(aBuffer.mGlyphs[0]) == sizeof(glyphs[0]) &&
sizeof(aBuffer.mGlyphs[0].mPosition) == sizeof(glyphs[0].point),
"glyph buf types don't match");
wr::GlyphOptions glyphOptions;
glyphOptions.render_mode =
wr::ToFontRenderMode(aOptions.mAntialiasMode, GetPermitSubpixelAA());
glyphOptions.flags = mWRGlyphFlags;
mManager->WrBridge()->PushGlyphs(mBuilder, glyphs, aFont, color, *mSc,
mBoundsRect, ClipRect(), mBackfaceVisible,
&glyphOptions);
}
void PushClipRect(const Rect& aRect) override {
LayoutDeviceRect rect = LayoutDeviceRect::FromUnknownRect(aRect);
rect = rect.Intersect(mClipStack.LastElement());
mClipStack.AppendElement(rect);
}
void PopClip() override { mClipStack.RemoveLastElement(); }
IntSize GetSize() const override { return mSize; }
void AppendShadow(const wr::Shadow& aShadow, bool aInflate) {
mBuilder.PushShadow(mBoundsRect, ClipRect(), mBackfaceVisible, aShadow,
aInflate);
mHasShadows = true;
}
void TerminateShadows() {
if (mHasShadows) {
mBuilder.PopAllShadows();
mHasShadows = false;
}
}
void AppendSelectionRect(const LayoutDeviceRect& aRect,
const DeviceColor& aColor) {
auto rect = wr::ToLayoutRect(aRect);
auto color = wr::ToColorF(aColor);
mBuilder.PushRect(rect, ClipRect(), mBackfaceVisible, color);
}
// This function is basically designed to slide into the decoration drawing
// code of nsCSSRendering with minimum disruption, to minimize the
// chances of implementation drift. As such, it mostly looks like a call
// to a skia-style StrokeLine method: two end-points, with a thickness
// and style. Notably the end-points are *centered* in the block direction,
// even though webrender wants a rect-like representation, where the points
// are on corners.
//
// So we mangle the format here in a single centralized place, where neither
// webrender nor nsCSSRendering has to care about this mismatch.
//
// NOTE: we assume the points are axis-aligned, and aStart should be used
// as the top-left corner of the rect.
void AppendDecoration(const Point& aStart, const Point& aEnd,
const float aThickness, const bool aVertical,
const DeviceColor& aColor, const uint8_t aStyle) {
auto pos = LayoutDevicePoint::FromUnknownPoint(aStart);
LayoutDeviceSize size;
if (aVertical) {
pos.x -= aThickness / 2; // adjust from center to corner
size = LayoutDeviceSize(aThickness, aEnd.y - aStart.y);
} else {
pos.y -= aThickness / 2; // adjust from center to corner
size = LayoutDeviceSize(aEnd.x - aStart.x, aThickness);
}
wr::Line decoration;
decoration.bounds = wr::ToLayoutRect(LayoutDeviceRect(pos, size));
decoration.wavyLineThickness = 0; // dummy value, unused
decoration.color = wr::ToColorF(aColor);
decoration.orientation = aVertical ? wr::LineOrientation::Vertical
: wr::LineOrientation::Horizontal;
switch (aStyle) {
case NS_STYLE_TEXT_DECORATION_STYLE_SOLID:
decoration.style = wr::LineStyle::Solid;
break;
case NS_STYLE_TEXT_DECORATION_STYLE_DOTTED:
decoration.style = wr::LineStyle::Dotted;
break;
case NS_STYLE_TEXT_DECORATION_STYLE_DASHED:
decoration.style = wr::LineStyle::Dashed;
break;
// Wavy lines should go through AppendWavyDecoration
case NS_STYLE_TEXT_DECORATION_STYLE_WAVY:
// Double lines should be lowered to two solid lines
case NS_STYLE_TEXT_DECORATION_STYLE_DOUBLE:
default:
MOZ_CRASH("TextDrawTarget received unsupported line style");
}
mBuilder.PushLine(ClipRect(), mBackfaceVisible, decoration);
}
// Seperated out from AppendDecoration because Wavy Lines are completely
// different, and trying to merge the concept is more of a mess than it's
// worth.
void AppendWavyDecoration(const Rect& aBounds, const float aThickness,
const bool aVertical, const DeviceColor& aColor) {
wr::Line decoration;
decoration.bounds =
wr::ToLayoutRect(LayoutDeviceRect::FromUnknownRect(aBounds));
decoration.wavyLineThickness = aThickness;
decoration.color = wr::ToColorF(aColor);
decoration.orientation = aVertical ? wr::LineOrientation::Vertical
: wr::LineOrientation::Horizontal;
decoration.style = wr::LineStyle::Wavy;
mBuilder.PushLine(ClipRect(), mBackfaceVisible, decoration);
}
layers::WebRenderBridgeChild* WrBridge() { return mManager->WrBridge(); }
layers::WebRenderLayerManager* WrLayerManager() {
return mManager->LayerManager();
}
Maybe<wr::ImageKey> DefineImage(const IntSize& aSize, uint32_t aStride,
SurfaceFormat aFormat, const uint8_t* aData) {
wr::ImageKey key = mManager->WrBridge()->GetNextImageKey();
wr::ImageDescriptor desc(aSize, aStride, aFormat);
Range<uint8_t> bytes(const_cast<uint8_t*>(aData), aStride * aSize.height);
if (mResources->AddImage(key, desc, bytes)) {
return Some(key);
}
return Nothing();
}
void PushImage(wr::ImageKey aKey, const Rect& aBounds, const Rect& aClip,
wr::ImageRendering aFilter, const wr::ColorF& aColor) {
if (!aClip.Intersects(GeckoClipRect().ToUnknownRect())) {
return;
}
mBuilder.PushImage(wr::ToLayoutRect(aBounds), wr::ToLayoutRect(aClip), true,
aFilter, aKey, true, aColor);
}
private:
wr::LayoutRect ClipRect() {
return wr::ToLayoutRect(mClipStack.LastElement());
}
LayoutDeviceRect GeckoClipRect() { return mClipStack.LastElement(); }
// Whether anything unsupported was encountered. This will result in this
// text being emitted as a blob, which means subpixel-AA can't be used and
// that performance will probably be a bit worse. At this point, we've
// properly implemented everything that shows up a lot, so you can assume
// that the remaining things we don't implement are fairly rare. The complete
// set of things that we don't implement are as follows:
//
// * Unserializable Fonts: WR lives across an IPC boundary
// * Text-Combine-Upright Squishing: no one's really bothered to impl it yet
// * Text-Stroke: not a real standard (exists for webcompat)
// * SVG Glyphs: not a real standard (we got overzealous with svg)
// * Color Glyphs (Emoji) With Transparency: requires us to apply transparency
// with a composited layer (a single emoji can be many single-color glyphs)
//
// The transparent colored-glyphs issue is probably the most valuable to fix,
// since ideally it would also result in us fixing transparency for all
// intersecting glyphs (which currently look bad with or without webrender,
// so there's no fallback like with emoji). Specifically, transparency
// looks bad for "cursive" fonts where glyphs overlap at the seams. Since
// this is more common for non-latin scripts (e.g. मनीष), this amounts to us
// treating non-latin scripts poorly... unless they're emoji. Yikes!
bool mHasUnsupportedFeatures = false;
// The caller promises to call Save/Restore on the builder as needed.
bool mCallerDoesSaveRestore = false;
#ifdef DEBUG
bool mFinished = false;
#endif
// Whether PopAllShadows needs to be called
bool mHasShadows = false;
// Things used to push to webrender
wr::DisplayListBuilder& mBuilder;
wr::IpcResourceUpdateQueue* mResources;
const layers::StackingContextHelper* mSc;
layers::RenderRootStateManager* mManager;
// Computed facts
IntSize mSize;
wr::LayoutRect mBoundsRect;
AutoTArray<LayoutDeviceRect, 3> mClipStack;
bool mBackfaceVisible;
wr::FontInstanceFlags mWRGlyphFlags = {0};
// The rest of this is dummy implementations of DrawTarget's API
public:
DrawTargetType GetType() const override {
return DrawTargetType::SOFTWARE_RASTER;
}
BackendType GetBackendType() const override {
return BackendType::WEBRENDER_TEXT;
}
bool IsRecording() const override { return true; }
already_AddRefed<SourceSurface> Snapshot() override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return nullptr;
}
already_AddRefed<SourceSurface> IntoLuminanceSource(
LuminanceType aLuminanceType, float aOpacity) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return nullptr;
}
void Flush() override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void DrawSurface(SourceSurface* aSurface, const Rect& aDest,
const Rect& aSource, const DrawSurfaceOptions& aSurfOptions,
const DrawOptions& aOptions) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void DrawFilter(FilterNode* aNode, const Rect& aSourceRect,
const Point& aDestPoint,
const DrawOptions& aOptions) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void DrawSurfaceWithShadow(SourceSurface* aSurface, const Point& aDest,
const DeviceColor& aColor, const Point& aOffset,
Float aSigma, CompositionOp aOperator) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void ClearRect(const Rect& aRect) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void CopySurface(SourceSurface* aSurface, const IntRect& aSourceRect,
const IntPoint& aDestination) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void FillRect(const Rect& aRect, const Pattern& aPattern,
const DrawOptions& aOptions = DrawOptions()) override {
MOZ_RELEASE_ASSERT(aPattern.GetType() == PatternType::COLOR);
if (!aRect.Intersects(GeckoClipRect().ToUnknownRect())) {
return;
}
auto rect = wr::ToLayoutRect(LayoutDeviceRect::FromUnknownRect(aRect));
auto color =
wr::ToColorF(static_cast<const ColorPattern&>(aPattern).mColor);
mBuilder.PushRect(rect, ClipRect(), mBackfaceVisible, color);
}
void StrokeRect(const Rect& aRect, const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions) override {
MOZ_RELEASE_ASSERT(aPattern.GetType() == PatternType::COLOR &&
aStrokeOptions.mDashLength == 0);
wr::LayoutSideOffsets widths = {
aStrokeOptions.mLineWidth, aStrokeOptions.mLineWidth,
aStrokeOptions.mLineWidth, aStrokeOptions.mLineWidth};
wr::ColorF color =
wr::ToColorF(static_cast<const ColorPattern&>(aPattern).mColor);
wr::BorderSide sides[4] = {{color, wr::BorderStyle::Solid},
{color, wr::BorderStyle::Solid},
{color, wr::BorderStyle::Solid},
{color, wr::BorderStyle::Solid}};
wr::BorderRadius radius = {{0, 0}, {0, 0}, {0, 0}, {0, 0}};
LayoutDeviceRect rect = LayoutDeviceRect::FromUnknownRect(aRect);
rect.Inflate(aStrokeOptions.mLineWidth / 2);
if (!rect.Intersects(GeckoClipRect())) {
return;
}
wr::LayoutRect bounds = wr::ToLayoutRect(rect);
mBuilder.PushBorder(bounds, ClipRect(), true, widths,
Range<const wr::BorderSide>(sides, 4), radius);
}
void StrokeLine(const Point& aStart, const Point& aEnd,
const Pattern& aPattern, const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void Stroke(const Path* aPath, const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void Fill(const Path* aPath, const Pattern& aPattern,
const DrawOptions& aOptions) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void StrokeGlyphs(ScaledFont* aFont, const GlyphBuffer& aBuffer,
const Pattern& aPattern,
const StrokeOptions& aStrokeOptions,
const DrawOptions& aOptions) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void Mask(const Pattern& aSource, const Pattern& aMask,
const DrawOptions& aOptions) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void MaskSurface(const Pattern& aSource, SourceSurface* aMask, Point aOffset,
const DrawOptions& aOptions) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
bool Draw3DTransformedSurface(SourceSurface* aSurface,
const Matrix4x4& aMatrix) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void PushClip(const Path* aPath) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void PushDeviceSpaceClipRects(const IntRect* aRects,
uint32_t aCount) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
void PushLayer(bool aOpaque, Float aOpacity, SourceSurface* aMask,
const Matrix& aMaskTransform, const IntRect& aBounds,
bool aCopyBackground) override {
// Fine to pretend we do this
}
void PopLayer() override {
// Fine to pretend we do this
}
already_AddRefed<SourceSurface> CreateSourceSurfaceFromData(
unsigned char* aData, const IntSize& aSize, int32_t aStride,
SurfaceFormat aFormat) const override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return nullptr;
}
already_AddRefed<SourceSurface> OptimizeSourceSurface(
SourceSurface* aSurface) const override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return nullptr;
}
already_AddRefed<SourceSurface> CreateSourceSurfaceFromNativeSurface(
const NativeSurface& aSurface) const override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return nullptr;
}
already_AddRefed<DrawTarget> CreateSimilarDrawTarget(
const IntSize& aSize, SurfaceFormat aFormat) const override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return nullptr;
}
bool CanCreateSimilarDrawTarget(const IntSize& aSize,
SurfaceFormat aFormat) const override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return false;
}
virtual RefPtr<DrawTarget> CreateClippedDrawTarget(
const Rect& aBounds, SurfaceFormat aFormat) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return nullptr;
}
already_AddRefed<PathBuilder> CreatePathBuilder(
FillRule aFillRule) const override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return nullptr;
}
already_AddRefed<FilterNode> CreateFilter(FilterType aType) override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return nullptr;
}
already_AddRefed<GradientStops> CreateGradientStops(
GradientStop* aStops, uint32_t aNumStops,
ExtendMode aExtendMode) const override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
return nullptr;
}
void DetachAllSnapshots() override {
MOZ_CRASH("TextDrawTarget: Method shouldn't be called");
}
};
} // namespace layout
} // namespace mozilla
#endif