diff options
author | 2015-11-19 13:02:43 -0800 | |
---|---|---|
committer | 2015-11-20 11:03:18 -0800 | |
commit | a1717271caac5e8ea3808c331d4141ac01a42134 (patch) | |
tree | 3f49ac6c386aa4166f5a82ecec2fd87458c88e25 | |
parent | ee35d738f46f01ce71ac8bde665d71ac3a35cbb9 (diff) |
Initial text support in new reorderer/renderer
Removes obsolete drawPosText codepath, and unifies text decoration behavior.
Change-Id: I9c563249ab688a3394445a0e7fe1b9d0661f6f7c
26 files changed, 544 insertions, 317 deletions
diff --git a/core/jni/android_graphics_Canvas.cpp b/core/jni/android_graphics_Canvas.cpp index 3d96fab9709d..e4e73a4b4c8a 100644 --- a/core/jni/android_graphics_Canvas.cpp +++ b/core/jni/android_graphics_Canvas.cpp @@ -544,39 +544,6 @@ private: float totalAdvance; }; -// Same values used by Skia -#define kStdStrikeThru_Offset (-6.0f / 21.0f) -#define kStdUnderline_Offset (1.0f / 9.0f) -#define kStdUnderline_Thickness (1.0f / 18.0f) - -void drawTextDecorations(Canvas* canvas, float x, float y, float length, const SkPaint& paint) { - uint32_t flags; - SkDrawFilter* drawFilter = canvas->getDrawFilter(); - if (drawFilter) { - SkPaint paintCopy(paint); - drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type); - flags = paintCopy.getFlags(); - } else { - flags = paint.getFlags(); - } - if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { - SkScalar left = x; - SkScalar right = x + length; - float textSize = paint.getTextSize(); - float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f); - if (flags & SkPaint::kUnderlineText_Flag) { - SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth; - SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth; - canvas->drawRect(left, top, right, bottom, paint); - } - if (flags & SkPaint::kStrikeThruText_Flag) { - SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth; - SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth; - canvas->drawRect(left, top, right, bottom, paint); - } - } -} - void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int contextCount, float x, float y, int bidiFlags, const Paint& origPaint, TypefaceImpl* typeface) { // minikin may modify the original paint @@ -586,8 +553,8 @@ void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int co MinikinUtils::doLayout(&layout, &paint, bidiFlags, typeface, text, start, count, contextCount); size_t nGlyphs = layout.nGlyphs(); - uint16_t* glyphs = new uint16_t[nGlyphs]; - float* pos = new float[nGlyphs * 2]; + std::unique_ptr<uint16_t[]> glyphs(new uint16_t[nGlyphs]); + std::unique_ptr<float[]> pos(new float[nGlyphs * 2]); x += MinikinUtils::xOffsetForTextAlign(&paint, layout); @@ -597,13 +564,9 @@ void drawText(Canvas* canvas, const uint16_t* text, int start, int count, int co bounds.offset(x, y); } - DrawTextFunctor f(layout, canvas, glyphs, pos, paint, x, y, bounds, layout.getAdvance()); + DrawTextFunctor f(layout, canvas, glyphs.get(), pos.get(), + paint, x, y, bounds, layout.getAdvance()); MinikinUtils::forFontRun(layout, &paint, f); - - drawTextDecorations(canvas, x, y, layout.getAdvance(), paint); - - delete[] glyphs; - delete[] pos; } static void drawTextChars(JNIEnv* env, jobject, jlong canvasHandle, jcharArray text, diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk index 0a57d509d129..cc68fb292a8f 100644 --- a/libs/hwui/Android.mk +++ b/libs/hwui/Android.mk @@ -37,6 +37,7 @@ hwui_src_files := \ AnimatorManager.cpp \ AssetAtlas.cpp \ Caches.cpp \ + Canvas.cpp \ CanvasState.cpp \ ClipArea.cpp \ DamageAccumulator.cpp \ diff --git a/libs/hwui/BakedOpRenderer.cpp b/libs/hwui/BakedOpRenderer.cpp index d2d3285b71f8..d13d7ef81e5b 100644 --- a/libs/hwui/BakedOpRenderer.cpp +++ b/libs/hwui/BakedOpRenderer.cpp @@ -24,6 +24,9 @@ #include "utils/GLUtils.h" #include "VertexBuffer.h" +#include <algorithm> +#include <math.h> + namespace android { namespace uirenderer { @@ -183,6 +186,10 @@ void BakedOpDispatcher::onBitmapOp(BakedOpRenderer& renderer, const BitmapOp& op renderer.renderGlop(state, glop); } +void BakedOpDispatcher::onLinesOp(BakedOpRenderer& renderer, const LinesOp& op, const BakedOpState& state) { + LOG_ALWAYS_FATAL("todo"); +} + void BakedOpDispatcher::onRectOp(BakedOpRenderer& renderer, const RectOp& op, const BakedOpState& state) { Glop glop; GlopBuilder(renderer.renderState(), renderer.caches(), &glop) @@ -270,6 +277,91 @@ void BakedOpDispatcher::onSimpleRectsOp(BakedOpRenderer& renderer, const SimpleR renderer.renderGlop(state, glop); } +static void renderTextShadow(BakedOpRenderer& renderer, FontRenderer& fontRenderer, + const TextOp& op, const BakedOpState& state) { + renderer.caches().textureState().activateTexture(0); + + PaintUtils::TextShadow textShadow; + if (!PaintUtils::getTextShadow(op.paint, &textShadow)) { + LOG_ALWAYS_FATAL("failed to query shadow attributes"); + } + + renderer.caches().dropShadowCache.setFontRenderer(fontRenderer); + ShadowTexture* texture = renderer.caches().dropShadowCache.get( + op.paint, (const char*) op.glyphs, + op.glyphCount, textShadow.radius, op.positions); + // If the drop shadow exceeds the max texture size or couldn't be + // allocated, skip drawing + if (!texture) return; + const AutoTexture autoCleanup(texture); + + const float sx = op.x - texture->left + textShadow.dx; + const float sy = op.y - texture->top + textShadow.dy; + + Glop glop; + GlopBuilder(renderer.renderState(), renderer.caches(), &glop) + .setRoundRectClipState(state.roundRectClipState) + .setMeshTexturedUnitQuad(nullptr) + .setFillShadowTexturePaint(*texture, textShadow.color, *op.paint, state.alpha) + .setTransform(state.computedState.transform, TransformFlags::None) + .setModelViewMapUnitToRect(Rect(sx, sy, sx + texture->width, sy + texture->height)) + .build(); + renderer.renderGlop(state, glop); +} + +void BakedOpDispatcher::onTextOp(BakedOpRenderer& renderer, const TextOp& op, const BakedOpState& state) { + FontRenderer& fontRenderer = renderer.caches().fontRenderer.getFontRenderer(); + + if (CC_UNLIKELY(PaintUtils::hasTextShadow(op.paint))) { + fontRenderer.setFont(op.paint, SkMatrix::I()); + renderTextShadow(renderer, fontRenderer, op, state); + } + + float x = op.x; + float y = op.y; + const Matrix4& transform = state.computedState.transform; + const bool pureTranslate = transform.isPureTranslate(); + if (CC_LIKELY(pureTranslate)) { + x = floorf(x + transform.getTranslateX() + 0.5f); + y = floorf(y + transform.getTranslateY() + 0.5f); + fontRenderer.setFont(op.paint, SkMatrix::I()); + fontRenderer.setTextureFiltering(false); + } else if (CC_UNLIKELY(transform.isPerspective())) { + fontRenderer.setFont(op.paint, SkMatrix::I()); + fontRenderer.setTextureFiltering(true); + } else { + // We only pass a partial transform to the font renderer. That partial + // matrix defines how glyphs are rasterized. Typically we want glyphs + // to be rasterized at their final size on screen, which means the partial + // matrix needs to take the scale factor into account. + // When a partial matrix is used to transform glyphs during rasterization, + // the mesh is generated with the inverse transform (in the case of scale, + // the mesh is generated at 1.0 / scale for instance.) This allows us to + // apply the full transform matrix at draw time in the vertex shader. + // Applying the full matrix in the shader is the easiest way to handle + // rotation and perspective and allows us to always generated quads in the + // font renderer which greatly simplifies the code, clipping in particular. + float sx, sy; + transform.decomposeScale(sx, sy); + fontRenderer.setFont(op.paint, SkMatrix::MakeScale( + roundf(std::max(1.0f, sx)), + roundf(std::max(1.0f, sy)))); + fontRenderer.setTextureFiltering(true); + } + + // TODO: Implement better clipping for scaled/rotated text + const Rect* clip = !pureTranslate ? nullptr : &state.computedState.clipRect; + Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); + + int alpha = PaintUtils::getAlphaDirect(op.paint) * state.alpha; + SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(op.paint); + TextDrawFunctor functor(&renderer, &state, x, y, pureTranslate, alpha, mode, op.paint); + + bool hasActiveLayer = false; // TODO + fontRenderer.renderPosText(op.paint, clip, (const char*) op.glyphs, op.glyphCount, x, y, + op.positions, hasActiveLayer ? &layerBounds : nullptr, &functor, true); // TODO: merging +} + void BakedOpDispatcher::onLayerOp(BakedOpRenderer& renderer, const LayerOp& op, const BakedOpState& state) { OffscreenBuffer* buffer = *op.layerHandle; diff --git a/libs/hwui/Canvas.cpp b/libs/hwui/Canvas.cpp new file mode 100644 index 000000000000..bc88c817ffc8 --- /dev/null +++ b/libs/hwui/Canvas.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "Canvas.h" + +#include <SkDrawFilter.h> + +namespace android { + +void Canvas::drawTextDecorations(float x, float y, float length, const SkPaint& paint) { + uint32_t flags; + SkDrawFilter* drawFilter = getDrawFilter(); + if (drawFilter) { + SkPaint paintCopy(paint); + drawFilter->filter(&paintCopy, SkDrawFilter::kText_Type); + flags = paintCopy.getFlags(); + } else { + flags = paint.getFlags(); + } + if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { + // Same values used by Skia + const float kStdStrikeThru_Offset = (-6.0f / 21.0f); + const float kStdUnderline_Offset = (1.0f / 9.0f); + const float kStdUnderline_Thickness = (1.0f / 18.0f); + + SkScalar left = x; + SkScalar right = x + length; + float textSize = paint.getTextSize(); + float strokeWidth = fmax(textSize * kStdUnderline_Thickness, 1.0f); + if (flags & SkPaint::kUnderlineText_Flag) { + SkScalar top = y + textSize * kStdUnderline_Offset - 0.5f * strokeWidth; + SkScalar bottom = y + textSize * kStdUnderline_Offset + 0.5f * strokeWidth; + drawRect(left, top, right, bottom, paint); + } + if (flags & SkPaint::kStrikeThruText_Flag) { + SkScalar top = y + textSize * kStdStrikeThru_Offset - 0.5f * strokeWidth; + SkScalar bottom = y + textSize * kStdStrikeThru_Offset + 0.5f * strokeWidth; + drawRect(left, top, right, bottom, paint); + } + } +} + +} // namespace android diff --git a/libs/hwui/Canvas.h b/libs/hwui/Canvas.h index 4bd4ac8d4c37..b585a2799cad 100644 --- a/libs/hwui/Canvas.h +++ b/libs/hwui/Canvas.h @@ -149,16 +149,12 @@ public: // Text /** * drawText: count is of glyphs - * totalAdvance is ignored in software renderering, used by hardware renderer for - * text decorations (underlines, strikethroughs). + * totalAdvance: used to define width of text decorations (underlines, strikethroughs). */ virtual void drawText(const uint16_t* glyphs, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) = 0; - /** drawPosText: count is of UTF16 characters, posCount is floats (2 * glyphs) */ - virtual void drawPosText(const uint16_t* text, const float* positions, int count, - int posCount, const SkPaint& paint) = 0; /** drawTextOnPath: count is of glyphs */ virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) = 0; @@ -171,6 +167,9 @@ public: * to be added to each glyph's position to get its absolute position. */ virtual bool drawTextAbsolutePos() const = 0; + +protected: + void drawTextDecorations(float x, float y, float length, const SkPaint& paint); }; }; // namespace android diff --git a/libs/hwui/DisplayListCanvas.cpp b/libs/hwui/DisplayListCanvas.cpp index f5e57355138c..759c12a3f214 100644 --- a/libs/hwui/DisplayListCanvas.cpp +++ b/libs/hwui/DisplayListCanvas.cpp @@ -423,18 +423,6 @@ void DisplayListCanvas::drawTextOnPath(const uint16_t* glyphs, int count, addDrawOp(op); } -void DisplayListCanvas::drawPosText(const uint16_t* text, const float* positions, - int count, int posCount, const SkPaint& paint) { - if (!text || count <= 0) return; - - int bytesCount = 2 * count; - positions = refBuffer<float>(positions, count * 2); - - DrawOp* op = new (alloc()) DrawPosTextOp(refText((const char*) text, bytesCount), - bytesCount, count, positions, refPaint(&paint)); - addDrawOp(op); -} - void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, @@ -450,6 +438,7 @@ void DisplayListCanvas::drawText(const uint16_t* glyphs, const float* positions, DrawOp* op = new (alloc()) DrawTextOp(text, bytesCount, count, x, y, positions, refPaint(&paint), totalAdvance, bounds); addDrawOp(op); + drawTextDecorations(x, y, totalAdvance, paint); } void DisplayListCanvas::drawRegion(const SkRegion& region, const SkPaint& paint) { diff --git a/libs/hwui/DisplayListCanvas.h b/libs/hwui/DisplayListCanvas.h index 609103b89644..bf98f79d4d5a 100644 --- a/libs/hwui/DisplayListCanvas.h +++ b/libs/hwui/DisplayListCanvas.h @@ -212,8 +212,6 @@ public: virtual void drawText(const uint16_t* glyphs, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) override; - virtual void drawPosText(const uint16_t* text, const float* positions, int count, - int posCount, const SkPaint& paint) override; virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) override; virtual bool drawTextAbsolutePos() const override { return false; } diff --git a/libs/hwui/DisplayListOp.h b/libs/hwui/DisplayListOp.h index 772aa72b7fdc..977b53c31f46 100644 --- a/libs/hwui/DisplayListOp.h +++ b/libs/hwui/DisplayListOp.h @@ -1278,24 +1278,6 @@ private: float mVOffset; }; -class DrawPosTextOp : public DrawSomeTextOp { -public: - DrawPosTextOp(const char* text, int bytesCount, int count, - const float* positions, const SkPaint* paint) - : DrawSomeTextOp(text, bytesCount, count, paint), mPositions(positions) { - /* TODO: inherit from DrawBounded and init mLocalBounds */ - } - - virtual void applyDraw(OpenGLRenderer& renderer, Rect& dirty) override { - renderer.drawPosText(mText, mBytesCount, mCount, mPositions, mPaint); - } - - virtual const char* name() override { return "DrawPosText"; } - -private: - const float* mPositions; -}; - class DrawTextOp : public DrawStrokableOp { public: DrawTextOp(const char* text, int bytesCount, int count, float x, float y, diff --git a/libs/hwui/FontRenderer.cpp b/libs/hwui/FontRenderer.cpp index ccf0b48cd4be..5f33cae2f91a 100644 --- a/libs/hwui/FontRenderer.cpp +++ b/libs/hwui/FontRenderer.cpp @@ -21,13 +21,20 @@ #include "Extensions.h" #include "Glop.h" #include "GlopBuilder.h" -#include "OpenGLRenderer.h" #include "PixelBuffer.h" #include "Rect.h" #include "renderstate/RenderState.h" #include "utils/Blur.h" #include "utils/Timing.h" + +#if HWUI_NEW_OPS +#include "BakedOpState.h" +#include "BakedOpRenderer.h" +#else +#include "OpenGLRenderer.h" +#endif + #include <algorithm> #include <cutils/properties.h> #include <SkGlyph.h> @@ -59,14 +66,25 @@ void TextDrawFunctor::draw(CacheTexture& texture, bool linearFiltering) { int transformFlags = pureTranslate ? TransformFlags::MeshIgnoresCanvasTransform : TransformFlags::None; Glop glop; +#if HWUI_NEW_OPS + GlopBuilder(renderer->renderState(), renderer->caches(), &glop) + .setRoundRectClipState(bakedState->roundRectClipState) + .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount()) + .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, bakedState->alpha) + .setTransform(bakedState->computedState.transform, transformFlags) + .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0)) + .build(); + renderer->renderGlop(*bakedState, glop); +#else GlopBuilder(renderer->mRenderState, renderer->mCaches, &glop) + .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState) .setMeshTexturedIndexedQuads(texture.mesh(), texture.meshElementCount()) .setFillTexturePaint(texture.getTexture(), textureFillFlags, paint, renderer->currentSnapshot()->alpha) .setTransform(*(renderer->currentSnapshot()), transformFlags) .setModelViewOffsetRect(0, 0, Rect(0, 0, 0, 0)) - .setRoundRectClipState(renderer->currentSnapshot()->roundRectClipState) .build(); renderer->renderGlop(glop); +#endif } /////////////////////////////////////////////////////////////////////////////// @@ -539,7 +557,7 @@ void FontRenderer::setFont(const SkPaint* paint, const SkMatrix& matrix) { } FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, float radius, const float* positions) { + int numGlyphs, float radius, const float* positions) { checkInit(); DropShadow image; @@ -558,7 +576,7 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, co mBounds = nullptr; Rect bounds; - mCurrentFont->measure(paint, text, startIndex, len, numGlyphs, &bounds, positions); + mCurrentFont->measure(paint, text, numGlyphs, &bounds, positions); uint32_t intRadius = Blur::convertRadiusToInt(radius); uint32_t paddedWidth = (uint32_t) (bounds.right - bounds.left) + 2 * intRadius; @@ -590,7 +608,7 @@ FontRenderer::DropShadow FontRenderer::renderDropShadow(const SkPaint* paint, co // text has non-whitespace, so draw and blur to create the shadow // NOTE: bounds.isEmpty() can't be used here, since vertical coordinates are inverted // TODO: don't draw pure whitespace in the first place, and avoid needing this check - mCurrentFont->render(paint, text, startIndex, len, numGlyphs, penX, penY, + mCurrentFont->render(paint, text, numGlyphs, penX, penY, Font::BITMAP, dataBuffer, paddedWidth, paddedHeight, nullptr, positions); // Unbind any PBO we might have used @@ -635,15 +653,15 @@ void FontRenderer::endPrecaching() { } bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, - const float* positions, Rect* bounds, TextDrawFunctor* functor, bool forceFinish) { + int numGlyphs, int x, int y, const float* positions, + Rect* bounds, TextDrawFunctor* functor, bool forceFinish) { if (!mCurrentFont) { ALOGE("No font set"); return false; } initRender(clip, bounds, functor); - mCurrentFont->render(paint, text, startIndex, len, numGlyphs, x, y, positions); + mCurrentFont->render(paint, text, numGlyphs, x, y, positions); if (forceFinish) { finishRender(); @@ -653,15 +671,15 @@ bool FontRenderer::renderPosText(const SkPaint* paint, const Rect* clip, const c } bool FontRenderer::renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path, - float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor) { + int numGlyphs, const SkPath* path, float hOffset, float vOffset, + Rect* bounds, TextDrawFunctor* functor) { if (!mCurrentFont) { ALOGE("No font set"); return false; } initRender(clip, bounds, functor); - mCurrentFont->render(paint, text, startIndex, len, numGlyphs, path, hOffset, vOffset); + mCurrentFont->render(paint, text, numGlyphs, path, hOffset, vOffset); finishRender(); return mDrawn; diff --git a/libs/hwui/FontRenderer.h b/libs/hwui/FontRenderer.h index 8172312e9a43..87cfe7ff98e0 100644 --- a/libs/hwui/FontRenderer.h +++ b/libs/hwui/FontRenderer.h @@ -44,13 +44,28 @@ namespace RSC { namespace android { namespace uirenderer { +#if HWUI_NEW_OPS +class BakedOpState; +class BakedOpRenderer; +#else class OpenGLRenderer; +#endif class TextDrawFunctor { public: - TextDrawFunctor(OpenGLRenderer* renderer, float x, float y, bool pureTranslate, + TextDrawFunctor( +#if HWUI_NEW_OPS + BakedOpRenderer* renderer, + const BakedOpState* bakedState, +#else + OpenGLRenderer* renderer, +#endif + float x, float y, bool pureTranslate, int alpha, SkXfermode::Mode mode, const SkPaint* paint) : renderer(renderer) +#if HWUI_NEW_OPS + , bakedState(bakedState) +#endif , x(x) , y(y) , pureTranslate(pureTranslate) @@ -61,7 +76,12 @@ public: void draw(CacheTexture& texture, bool linearFiltering); +#if HWUI_NEW_OPS + BakedOpRenderer* renderer; + const BakedOpState* bakedState; +#else OpenGLRenderer* renderer; +#endif float x; float y; bool pureTranslate; @@ -83,15 +103,13 @@ public: void precache(const SkPaint* paint, const char* text, int numGlyphs, const SkMatrix& matrix); void endPrecaching(); - // bounds is an out parameter bool renderPosText(const SkPaint* paint, const Rect* clip, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, int x, int y, const float* positions, - Rect* bounds, TextDrawFunctor* functor, bool forceFinish = true); + int numGlyphs, int x, int y, const float* positions, + Rect* outBounds, TextDrawFunctor* functor, bool forceFinish = true); - // bounds is an out parameter bool renderTextOnPath(const SkPaint* paint, const Rect* clip, const char *text, - uint32_t startIndex, uint32_t len, int numGlyphs, const SkPath* path, - float hOffset, float vOffset, Rect* bounds, TextDrawFunctor* functor); + int numGlyphs, const SkPath* path, + float hOffset, float vOffset, Rect* outBounds, TextDrawFunctor* functor); struct DropShadow { uint32_t width; @@ -103,8 +121,8 @@ public: // After renderDropShadow returns, the called owns the memory in DropShadow.image // and is responsible for releasing it when it's done with it - DropShadow renderDropShadow(const SkPaint* paint, const char *text, uint32_t startIndex, - uint32_t len, int numGlyphs, float radius, const float* positions); + DropShadow renderDropShadow(const SkPaint* paint, const char *text, int numGlyphs, + float radius, const float* positions); void setTextureFiltering(bool linearFiltering) { mLinearFiltering = linearFiltering; diff --git a/libs/hwui/OpReorderer.cpp b/libs/hwui/OpReorderer.cpp index 96cac7eedaf0..5e954ae6e971 100644 --- a/libs/hwui/OpReorderer.cpp +++ b/libs/hwui/OpReorderer.cpp @@ -671,6 +671,13 @@ void OpReorderer::onBitmapOp(const BitmapOp& op) { currentLayer().deferMergeableOp(mAllocator, bakedStateOp, OpBatchType::Bitmap, mergeId); } +void OpReorderer::onLinesOp(const LinesOp& op) { + BakedOpState* bakedStateOp = tryBakeOpState(op); + if (!bakedStateOp) return; // quick rejected + currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices); + +} + void OpReorderer::onRectOp(const RectOp& op) { BakedOpState* bakedStateOp = tryBakeOpState(op); if (!bakedStateOp) return; // quick rejected @@ -683,6 +690,17 @@ void OpReorderer::onSimpleRectsOp(const SimpleRectsOp& op) { currentLayer().deferUnmergeableOp(mAllocator, bakedStateOp, OpBatchType::Vertices); } +void OpReorderer::onTextOp(const TextOp& op) { + BakedOpState* bakedStateOp = tryBakeOpState(op); + if (!bakedStateOp) return; // quick rejected + + // TODO: better handling of shader (since we won't care about color then) + batchid_t batchId = op.paint->getColor() == SK_ColorBLACK + ? OpBatchType::Text : OpBatchType::ColorText; + mergeid_t mergeId = reinterpret_cast<mergeid_t>(op.paint->getColor()); + currentLayer().deferMergeableOp(mAllocator, bakedStateOp, batchId, mergeId); +} + void OpReorderer::saveForLayer(uint32_t layerWidth, uint32_t layerHeight, float contentTranslateX, float contentTranslateY, const Rect& repaintRect, diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp index 12c4607130ca..e386b1cacf74 100644 --- a/libs/hwui/OpenGLRenderer.cpp +++ b/libs/hwui/OpenGLRenderer.cpp @@ -1950,7 +1950,7 @@ void OpenGLRenderer::drawRect(float left, float top, float right, float bottom, } void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, - int bytesCount, int count, const float* positions, + int count, const float* positions, FontRenderer& fontRenderer, int alpha, float x, float y) { mCaches.textureState().activateTexture(0); @@ -1963,7 +1963,7 @@ void OpenGLRenderer::drawTextShadow(const SkPaint* paint, const char* text, // if shader-based correction is enabled mCaches.dropShadowCache.setFontRenderer(fontRenderer); ShadowTexture* texture = mCaches.dropShadowCache.get( - paint, text, bytesCount, count, textShadow.radius, positions); + paint, text, count, textShadow.radius, positions); // If the drop shadow exceeds the max texture size or couldn't be // allocated, skip drawing if (!texture) return; @@ -1991,57 +1991,6 @@ bool OpenGLRenderer::canSkipText(const SkPaint* paint) const { && PaintUtils::getXfermode(paint->getXfermode()) == SkXfermode::kSrcOver_Mode; } -void OpenGLRenderer::drawPosText(const char* text, int bytesCount, int count, - const float* positions, const SkPaint* paint) { - if (text == nullptr || count == 0 || mState.currentlyIgnored() || canSkipText(paint)) { - return; - } - - // NOTE: Skia does not support perspective transform on drawPosText yet - if (!currentTransform()->isSimple()) { - return; - } - - mRenderState.scissor().setEnabled(true); - - float x = 0.0f; - float y = 0.0f; - const bool pureTranslate = currentTransform()->isPureTranslate(); - if (pureTranslate) { - x = floorf(x + currentTransform()->getTranslateX() + 0.5f); - y = floorf(y + currentTransform()->getTranslateY() + 0.5f); - } - - FontRenderer& fontRenderer = mCaches.fontRenderer.getFontRenderer(); - fontRenderer.setFont(paint, SkMatrix::I()); - - int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha; - SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint); - - if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) { - drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, - alpha, 0.0f, 0.0f); - } - - // Pick the appropriate texture filtering - bool linearFilter = currentTransform()->changesBounds(); - if (pureTranslate && !linearFilter) { - linearFilter = fabs(y - (int) y) > 0.0f || fabs(x - (int) x) > 0.0f; - } - fontRenderer.setTextureFiltering(linearFilter); - - const Rect& clip(pureTranslate ? writableSnapshot()->getRenderTargetClip() : writableSnapshot()->getLocalClip()); - Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); - - TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); - if (fontRenderer.renderPosText(paint, &clip, text, 0, bytesCount, count, x, y, - positions, hasLayer() ? &bounds : nullptr, &functor)) { - dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform()); - mDirty = true; - } - -} - bool OpenGLRenderer::findBestFontTransform(const mat4& transform, SkMatrix* outMatrix) const { if (CC_LIKELY(transform.isPureTranslate())) { outMatrix->setIdentity(); @@ -2166,7 +2115,7 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float if (CC_UNLIKELY(PaintUtils::hasTextShadow(paint))) { fontRenderer.setFont(paint, SkMatrix::I()); - drawTextShadow(paint, text, bytesCount, count, positions, fontRenderer, + drawTextShadow(paint, text, count, positions, fontRenderer, alpha, oldX, oldY); } @@ -2195,17 +2144,22 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float Rect layerBounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); bool status; +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("unsupported"); + TextDrawFunctor functor(nullptr, nullptr, x, y, pureTranslate, alpha, mode, paint); +#else TextDrawFunctor functor(this, x, y, pureTranslate, alpha, mode, paint); +#endif // don't call issuedrawcommand, do it at end of batch bool forceFinish = (drawOpMode != DrawOpMode::kDefer); if (CC_UNLIKELY(paint->getTextAlign() != SkPaint::kLeft_Align)) { SkPaint paintCopy(*paint); paintCopy.setTextAlign(SkPaint::kLeft_Align); - status = fontRenderer.renderPosText(&paintCopy, clip, text, 0, bytesCount, count, x, y, + status = fontRenderer.renderPosText(&paintCopy, clip, text, count, x, y, positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish); } else { - status = fontRenderer.renderPosText(paint, clip, text, 0, bytesCount, count, x, y, + status = fontRenderer.renderPosText(paint, clip, text, count, x, y, positions, hasActiveLayer ? &layerBounds : nullptr, &functor, forceFinish); } @@ -2216,8 +2170,6 @@ void OpenGLRenderer::drawText(const char* text, int bytesCount, int count, float dirtyLayerUnchecked(layerBounds, getRegion()); } - drawTextDecorations(totalAdvance, oldX, oldY, paint); - mDirty = true; } @@ -2236,12 +2188,17 @@ void OpenGLRenderer::drawTextOnPath(const char* text, int bytesCount, int count, int alpha = PaintUtils::getAlphaDirect(paint) * currentSnapshot()->alpha; SkXfermode::Mode mode = PaintUtils::getXfermodeDirect(paint); +#if HWUI_NEW_OPS + LOG_ALWAYS_FATAL("unsupported"); + TextDrawFunctor functor(nullptr, nullptr, 0.0f, 0.0f, false, alpha, mode, paint); +#else TextDrawFunctor functor(this, 0.0f, 0.0f, false, alpha, mode, paint); +#endif const Rect* clip = &writableSnapshot()->getLocalClip(); Rect bounds(FLT_MAX / 2.0f, FLT_MAX / 2.0f, FLT_MIN / 2.0f, FLT_MIN / 2.0f); - if (fontRenderer.renderTextOnPath(paint, clip, text, 0, bytesCount, count, path, + if (fontRenderer.renderTextOnPath(paint, clip, text, count, path, hOffset, vOffset, hasLayer() ? &bounds : nullptr, &functor)) { dirtyLayer(bounds.left, bounds.top, bounds.right, bounds.bottom, *currentTransform()); mDirty = true; @@ -2375,56 +2332,6 @@ void OpenGLRenderer::drawPathTexture(PathTexture* texture, float x, float y, renderGlop(glop); } -// Same values used by Skia -#define kStdStrikeThru_Offset (-6.0f / 21.0f) -#define kStdUnderline_Offset (1.0f / 9.0f) -#define kStdUnderline_Thickness (1.0f / 18.0f) - -void OpenGLRenderer::drawTextDecorations(float underlineWidth, float x, float y, - const SkPaint* paint) { - // Handle underline and strike-through - uint32_t flags = paint->getFlags(); - if (flags & (SkPaint::kUnderlineText_Flag | SkPaint::kStrikeThruText_Flag)) { - SkPaint paintCopy(*paint); - - if (CC_LIKELY(underlineWidth > 0.0f)) { - const float textSize = paintCopy.getTextSize(); - const float strokeWidth = std::max(textSize * kStdUnderline_Thickness, 1.0f); - - const float left = x; - float top = 0.0f; - - int linesCount = 0; - if (flags & SkPaint::kUnderlineText_Flag) linesCount++; - if (flags & SkPaint::kStrikeThruText_Flag) linesCount++; - - const int pointsCount = 4 * linesCount; - float points[pointsCount]; - int currentPoint = 0; - - if (flags & SkPaint::kUnderlineText_Flag) { - top = y + textSize * kStdUnderline_Offset; - points[currentPoint++] = left; - points[currentPoint++] = top; - points[currentPoint++] = left + underlineWidth; - points[currentPoint++] = top; - } - - if (flags & SkPaint::kStrikeThruText_Flag) { - top = y + textSize * kStdStrikeThru_Offset; - points[currentPoint++] = left; - points[currentPoint++] = top; - points[currentPoint++] = left + underlineWidth; - points[currentPoint++] = top; - } - - paintCopy.setStrokeWidth(strokeWidth); - - drawLines(&points[0], pointsCount, &paintCopy); - } - } -} - void OpenGLRenderer::drawRects(const float* rects, int count, const SkPaint* paint) { if (mState.currentlyIgnored()) { return; diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h index 400c225b53a0..84bc9b059d33 100755 --- a/libs/hwui/OpenGLRenderer.h +++ b/libs/hwui/OpenGLRenderer.h @@ -193,8 +193,6 @@ public: void drawPoints(const float* points, int count, const SkPaint* paint); void drawTextOnPath(const char* text, int bytesCount, int count, const SkPath* path, float hOffset, float vOffset, const SkPaint* paint); - void drawPosText(const char* text, int bytesCount, int count, - const float* positions, const SkPaint* paint); void drawText(const char* text, int bytesCount, int count, float x, float y, const float* positions, const SkPaint* paint, float totalAdvance, const Rect& bounds, DrawOpMode drawOpMode = DrawOpMode::kImmediate); @@ -637,24 +635,11 @@ private: */ void drawConvexPath(const SkPath& path, const SkPaint* paint); - /** - * Draws text underline and strike-through if needed. - * - * @param text The text to decor - * @param bytesCount The number of bytes in the text - * @param totalAdvance The total advance in pixels, defines underline/strikethrough length - * @param x The x coordinate where the text will be drawn - * @param y The y coordinate where the text will be drawn - * @param paint The paint to draw the text with - */ - void drawTextDecorations(float totalAdvance, float x, float y, const SkPaint* paint); - /** * Draws shadow layer on text (with optional positions). * * @param paint The paint to draw the shadow with * @param text The text to draw - * @param bytesCount The number of bytes in the text * @param count The number of glyphs in the text * @param positions The x, y positions of individual glyphs (or NULL) * @param fontRenderer The font renderer object @@ -662,7 +647,7 @@ private: * @param x The x coordinate where the shadow will be drawn * @param y The y coordinate where the shadow will be drawn */ - void drawTextShadow(const SkPaint* paint, const char* text, int bytesCount, int count, + void drawTextShadow(const SkPaint* paint, const char* text, int count, const float* positions, FontRenderer& fontRenderer, int alpha, float x, float y); diff --git a/libs/hwui/RecordedOp.h b/libs/hwui/RecordedOp.h index ef0536761a11..127dca5be440 100644 --- a/libs/hwui/RecordedOp.h +++ b/libs/hwui/RecordedOp.h @@ -17,6 +17,7 @@ #ifndef ANDROID_HWUI_RECORDED_OP_H #define ANDROID_HWUI_RECORDED_OP_H +#include "font/FontUtil.h" #include "Matrix.h" #include "Rect.h" #include "RenderNode.h" @@ -42,10 +43,12 @@ struct Vertex; */ #define MAP_OPS(OP_FN) \ OP_FN(BitmapOp) \ + OP_FN(LinesOp) \ OP_FN(RectOp) \ OP_FN(RenderNodeOp) \ OP_FN(ShadowOp) \ OP_FN(SimpleRectsOp) \ + OP_FN(TextOp) \ OP_FN(BeginLayerOp) \ OP_FN(EndLayerOp) \ OP_FN(LayerOp) @@ -98,6 +101,10 @@ struct RenderNodeOp : RecordedOp { bool skipInOrderDraw = false; }; +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Standard Ops +//////////////////////////////////////////////////////////////////////////////////////////////////// + struct BitmapOp : RecordedOp { BitmapOp(BASE_PARAMS, const SkBitmap* bitmap) : SUPER(BitmapOp) @@ -106,6 +113,15 @@ struct BitmapOp : RecordedOp { // TODO: asset atlas/texture id lookup? }; +struct LinesOp : RecordedOp { + LinesOp(BASE_PARAMS, const float* points, const int floatCount) + : SUPER(LinesOp) + , points(points) + , floatCount(floatCount) {} + const float* points; + const int floatCount; +}; + struct RectOp : RecordedOp { RectOp(BASE_PARAMS) : SUPER(RectOp) {} @@ -148,6 +164,27 @@ struct SimpleRectsOp : RecordedOp { // Filled, no AA (TODO: better name?) const size_t vertexCount; }; +struct TextOp : RecordedOp { + TextOp(BASE_PARAMS, const glyph_t* glyphs, const float* positions, int glyphCount, + float x, float y) + : SUPER(TextOp) + , glyphs(glyphs) + , positions(positions) + , glyphCount(glyphCount) + , x(x) + , y(y) {} + const glyph_t* glyphs; + const float* positions; + const int glyphCount; + const float x; + const float y; +}; + +//////////////////////////////////////////////////////////////////////////////////////////////////// +// Layers +//////////////////////////////////////////////////////////////////////////////////////////////////// + + /** * Stateful operation! denotes the creation of an off-screen layer, * and that commands following will render into it. diff --git a/libs/hwui/RecordingCanvas.cpp b/libs/hwui/RecordingCanvas.cpp index 6ab253c2491f..61fa3844df27 100644 --- a/libs/hwui/RecordingCanvas.cpp +++ b/libs/hwui/RecordingCanvas.cpp @@ -230,12 +230,9 @@ void RecordingCanvas::drawColor(int color, SkXfermode::Mode mode) { void RecordingCanvas::drawPaint(const SkPaint& paint) { // TODO: more efficient recording? - Matrix4 identity; - identity.loadIdentity(); - addOp(new (alloc()) RectOp( mState.getRenderTargetClipBounds(), - identity, + Matrix4::identity(), mState.getRenderTargetClipBounds(), refPaint(&paint))); } @@ -244,9 +241,30 @@ void RecordingCanvas::drawPaint(const SkPaint& paint) { void RecordingCanvas::drawPoints(const float* points, int count, const SkPaint& paint) { LOG_ALWAYS_FATAL("TODO!"); } -void RecordingCanvas::drawLines(const float* points, int count, const SkPaint& paint) { - LOG_ALWAYS_FATAL("TODO!"); + +void RecordingCanvas::drawLines(const float* points, int floatCount, const SkPaint& paint) { + if (floatCount < 4) return; + floatCount &= ~0x3; // round down to nearest four + + Rect unmappedBounds(points[0], points[1], points[0], points[1]); + for (int i = 2; i < floatCount; i += 2) { + unmappedBounds.left = std::min(unmappedBounds.left, points[i]); + unmappedBounds.right = std::max(unmappedBounds.right, points[i]); + unmappedBounds.top = std::min(unmappedBounds.top, points[i + 1]); + unmappedBounds.bottom = std::max(unmappedBounds.bottom, points[i + 1]); + } + + // since anything AA stroke with less than 1.0 pixel width is drawn with an alpha-reduced + // 1.0 stroke, treat 1.0 as minimum. + unmappedBounds.outset(std::max(paint.getStrokeWidth(), 1.0f) * 0.5f); + + addOp(new (alloc()) LinesOp( + unmappedBounds, + *mState.currentSnapshot()->transform, + mState.getRenderTargetClipBounds(), + refPaint(&paint), refBuffer<float>(points, floatCount), floatCount)); } + void RecordingCanvas::drawRect(float left, float top, float right, float bottom, const SkPaint& paint) { addOp(new (alloc()) RectOp( Rect(left, top, right, bottom), @@ -388,17 +406,24 @@ void RecordingCanvas::drawNinePatch(const SkBitmap& bitmap, const android::Res_p } // Text -void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int count, +void RecordingCanvas::drawText(const uint16_t* glyphs, const float* positions, int glyphCount, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) { - LOG_ALWAYS_FATAL("TODO!"); -} -void RecordingCanvas::drawPosText(const uint16_t* text, const float* positions, int count, - int posCount, const SkPaint& paint) { - LOG_ALWAYS_FATAL("TODO!"); + if (!glyphs || !positions || glyphCount <= 0 || PaintUtils::paintWillNotDrawText(paint)) return; + glyphs = refBuffer<glyph_t>(glyphs, glyphCount); + positions = refBuffer<float>(positions, glyphCount * 2); + + addOp(new (alloc()) TextOp( + Rect(boundsLeft, boundsTop, boundsRight, boundsBottom), + *(mState.currentSnapshot()->transform), + mState.getRenderTargetClipBounds(), + refPaint(&paint), glyphs, positions, glyphCount, x, y)); + drawTextDecorations(x, y, totalAdvance, paint); } + void RecordingCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) { + // NOTE: can't use refPaint() directly, since it forces left alignment LOG_ALWAYS_FATAL("TODO!"); } diff --git a/libs/hwui/RecordingCanvas.h b/libs/hwui/RecordingCanvas.h index f26b0c827604..736cc9ecaf2a 100644 --- a/libs/hwui/RecordingCanvas.h +++ b/libs/hwui/RecordingCanvas.h @@ -178,8 +178,6 @@ public: virtual void drawText(const uint16_t* glyphs, const float* positions, int count, const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) override; - virtual void drawPosText(const uint16_t* text, const float* positions, int count, - int posCount, const SkPaint& paint) override; virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) override; virtual bool drawTextAbsolutePos() const override { return false; } @@ -221,6 +219,15 @@ private: return cachedPath; } + /** + * Returns a RenderThread-safe, const copy of the SkPaint parameter passed in (with deduping + * based on paint generation ID) + * + * Note that this forces Left_Align, since drawText glyph rendering expects left alignment, + * since alignment offsetting has been done at a higher level. This is done to essentially all + * copied paints, since the deduping can mean a paint is shared by drawText commands and other + * types (which wouldn't care about alignment). + */ inline const SkPaint* refPaint(const SkPaint* paint) { if (!paint) return nullptr; @@ -239,10 +246,11 @@ private: // In the unlikely event that 2 unique paints have the same hash we do a // object equality check to ensure we don't erroneously dedup them. if (cachedPaint == nullptr || *cachedPaint != *paint) { - cachedPaint = new SkPaint(*paint); - std::unique_ptr<const SkPaint> copy(cachedPaint); - mDisplayList->paints.push_back(std::move(copy)); + SkPaint* copy = new SkPaint(*paint); + copy->setTextAlign(SkPaint::kLeft_Align); + cachedPaint = copy; + mDisplayList->paints.emplace_back(copy); // replaceValueFor() performs an add if the entry doesn't exist mPaintMap.replaceValueFor(key, cachedPaint); refBitmapsInShader(cachedPaint->getShader()); diff --git a/libs/hwui/Rect.h b/libs/hwui/Rect.h index 0736a109e572..472aad711432 100644 --- a/libs/hwui/Rect.h +++ b/libs/hwui/Rect.h @@ -260,13 +260,6 @@ public: bottom = std::max(bottom, y); } - void expandToCoverRect(float otherLeft, float otherTop, float otherRight, float otherBottom) { - left = std::min(left, otherLeft); - top = std::min(top, otherTop); - right = std::max(right, otherRight); - bottom = std::max(bottom, otherBottom); - } - SkRect toSkRect() const { return SkRect::MakeLTRB(left, top, right, bottom); } diff --git a/libs/hwui/SkiaCanvas.cpp b/libs/hwui/SkiaCanvas.cpp index 6d3dfac4ba9c..96c1a7c18db9 100644 --- a/libs/hwui/SkiaCanvas.cpp +++ b/libs/hwui/SkiaCanvas.cpp @@ -131,8 +131,6 @@ public: const SkPaint& paint, float x, float y, float boundsLeft, float boundsTop, float boundsRight, float boundsBottom, float totalAdvance) override; - virtual void drawPosText(const uint16_t* text, const float* positions, int count, - int posCount, const SkPaint& paint) override; virtual void drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, float hOffset, float vOffset, const SkPaint& paint) override; @@ -152,7 +150,6 @@ private: void drawPoints(const float* points, int count, const SkPaint& paint, SkCanvas::PointMode mode); - void drawTextDecorations(float x, float y, float length, const SkPaint& paint); SkAutoTUnref<SkCanvas> mCanvas; std::unique_ptr<SkDeque> mSaveStack; // lazily allocated, tracks partial saves. @@ -712,22 +709,7 @@ void SkiaCanvas::drawText(const uint16_t* text, const float* positions, int coun static_assert(sizeof(SkPoint) == sizeof(float)*2, "SkPoint is no longer two floats"); mCanvas->drawPosText(text, count << 1, reinterpret_cast<const SkPoint*>(positions), paintCopy); -} - -void SkiaCanvas::drawPosText(const uint16_t* text, const float* positions, int count, int posCount, - const SkPaint& paint) { - SkPoint* posPtr = posCount > 0 ? new SkPoint[posCount] : NULL; - int indx; - for (indx = 0; indx < posCount; indx++) { - posPtr[indx].fX = positions[indx << 1]; - posPtr[indx].fY = positions[(indx << 1) + 1]; - } - - SkPaint paintCopy(paint); - paintCopy.setTextEncoding(SkPaint::kUTF16_TextEncoding); - mCanvas->drawPosText(text, count, posPtr, paintCopy); - - delete[] posPtr; + drawTextDecorations(x, y, totalAdvance, paint); } void SkiaCanvas::drawTextOnPath(const uint16_t* glyphs, int count, const SkPath& path, diff --git a/libs/hwui/TextDropShadowCache.cpp b/libs/hwui/TextDropShadowCache.cpp index b7a76baadff5..996ac8ebc447 100644 --- a/libs/hwui/TextDropShadowCache.cpp +++ b/libs/hwui/TextDropShadowCache.cpp @@ -30,8 +30,7 @@ namespace uirenderer { /////////////////////////////////////////////////////////////////////////////// hash_t ShadowText::hash() const { - uint32_t charCount = len / sizeof(char16_t); - uint32_t hash = JenkinsHashMix(0, len); + uint32_t hash = JenkinsHashMix(0, glyphCount); hash = JenkinsHashMix(hash, android::hash_type(radius)); hash = JenkinsHashMix(hash, android::hash_type(textSize)); hash = JenkinsHashMix(hash, android::hash_type(typeface)); @@ -40,10 +39,10 @@ hash_t ShadowText::hash() const { hash = JenkinsHashMix(hash, android::hash_type(scaleX)); if (text) { hash = JenkinsHashMixShorts( - hash, reinterpret_cast<const uint16_t*>(text), charCount); + hash, reinterpret_cast<const uint16_t*>(text), glyphCount); } if (positions) { - for (uint32_t i = 0; i < charCount * 2; i++) { + for (uint32_t i = 0; i < glyphCount * 2; i++) { hash = JenkinsHashMix(hash, android::hash_type(positions[i])); } } @@ -51,7 +50,7 @@ hash_t ShadowText::hash() const { } int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) { - int deltaInt = int(lhs.len) - int(rhs.len); + int deltaInt = int(lhs.glyphCount) - int(rhs.glyphCount); if (deltaInt != 0) return deltaInt; deltaInt = lhs.flags - rhs.flags; @@ -76,7 +75,7 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) { if (!lhs.text) return -1; if (!rhs.text) return +1; - deltaInt = memcmp(lhs.text, rhs.text, lhs.len); + deltaInt = memcmp(lhs.text, rhs.text, lhs.glyphCount * sizeof(glyph_t)); if (deltaInt != 0) return deltaInt; } @@ -84,7 +83,7 @@ int ShadowText::compare(const ShadowText& lhs, const ShadowText& rhs) { if (!lhs.positions) return -1; if (!rhs.positions) return +1; - return memcmp(lhs.positions, rhs.positions, lhs.len << 2); + return memcmp(lhs.positions, rhs.positions, lhs.glyphCount << 1); } return 0; @@ -168,16 +167,16 @@ void TextDropShadowCache::clear() { mCache.clear(); } -ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* text, uint32_t len, - int numGlyphs, float radius, const float* positions) { - ShadowText entry(paint, radius, len, text, positions); +ShadowTexture* TextDropShadowCache::get(const SkPaint* paint, const char* glyphs, int numGlyphs, + float radius, const float* positions) { + ShadowText entry(paint, radius, numGlyphs * 2, glyphs, positions); ShadowTexture* texture = mCache.get(entry); if (!texture) { SkPaint paintCopy(*paint); paintCopy.setTextAlign(SkPaint::kLeft_Align); - FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, text, 0, - len, numGlyphs, radius, positions); + FontRenderer::DropShadow shadow = mRenderer->renderDropShadow(&paintCopy, glyphs, numGlyphs, + radius, positions); if (!shadow.image) { return nullptr; diff --git a/libs/hwui/TextDropShadowCache.h b/libs/hwui/TextDropShadowCache.h index caf089f6d2a5..c4f3c5d96786 100644 --- a/libs/hwui/TextDropShadowCache.h +++ b/libs/hwui/TextDropShadowCache.h @@ -34,14 +34,14 @@ class Caches; class FontRenderer; struct ShadowText { - ShadowText(): len(0), radius(0.0f), textSize(0.0f), typeface(nullptr), + ShadowText(): glyphCount(0), radius(0.0f), textSize(0.0f), typeface(nullptr), flags(0), italicStyle(0.0f), scaleX(0), text(nullptr), positions(nullptr) { } // len is the number of bytes in text - ShadowText(const SkPaint* paint, float radius, uint32_t len, const char* srcText, + ShadowText(const SkPaint* paint, float radius, uint32_t glyphCount, const char* srcText, const float* positions): - len(len), radius(radius), positions(positions) { + glyphCount(glyphCount), radius(radius), positions(positions) { // TODO: Propagate this through the API, we should not cast here text = (const char16_t*) srcText; @@ -73,17 +73,16 @@ struct ShadowText { } void copyTextLocally() { - uint32_t charCount = len / sizeof(char16_t); - str.setTo((const char16_t*) text, charCount); + str.setTo((const char16_t*) text, glyphCount); text = str.string(); if (positions != nullptr) { positionsCopy.clear(); - positionsCopy.appendArray(positions, charCount * 2); + positionsCopy.appendArray(positions, glyphCount * 2); positions = positionsCopy.array(); } } - uint32_t len; + uint32_t glyphCount; float radius; float textSize; SkTypeface* typeface; @@ -136,7 +135,7 @@ public: */ void operator()(ShadowText& text, ShadowTexture*& texture) override; - ShadowTexture* get(const SkPaint* paint, const char* text, uint32_t len, + ShadowTexture* get(const SkPaint* paint, const char* text, int numGlyphs, float radius, const float* positions); /** diff --git a/libs/hwui/font/Font.cpp b/libs/hwui/font/Font.cpp index d680f990a0be..dc82041e8f89 100644 --- a/libs/hwui/font/Font.cpp +++ b/libs/hwui/font/Font.cpp @@ -291,20 +291,18 @@ CachedGlyphInfo* Font::getCachedGlyph(const SkPaint* paint, glyph_t textUnit, bo return cachedGlyph; } -void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len, +void Font::render(const SkPaint* paint, const char *text, int numGlyphs, int x, int y, const float* positions) { - render(paint, text, start, len, numGlyphs, x, y, FRAMEBUFFER, nullptr, + render(paint, text, numGlyphs, x, y, FRAMEBUFFER, nullptr, 0, 0, nullptr, positions); } -void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len, - int numGlyphs, const SkPath* path, float hOffset, float vOffset) { - if (numGlyphs == 0 || text == nullptr || len == 0) { +void Font::render(const SkPaint* paint, const char *text, int numGlyphs, + const SkPath* path, float hOffset, float vOffset) { + if (numGlyphs == 0 || text == nullptr) { return; } - text += start; - int glyphsCount = 0; SkFixed prevRsbDelta = 0; @@ -317,7 +315,7 @@ void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32 float pathLength = SkScalarToFloat(measure.getLength()); if (paint->getTextAlign() != SkPaint::kLeft_Align) { - float textWidth = SkScalarToFloat(paint->measureText(text, len)); + float textWidth = SkScalarToFloat(paint->measureText(text, numGlyphs * 2)); float pathOffset = pathLength; if (paint->getTextAlign() == SkPaint::kCenter_Align) { textWidth *= 0.5f; @@ -347,14 +345,14 @@ void Font::render(const SkPaint* paint, const char *text, uint32_t start, uint32 } } -void Font::measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, +void Font::measure(const SkPaint* paint, const char* text, int numGlyphs, Rect *bounds, const float* positions) { if (bounds == nullptr) { ALOGE("No return rectangle provided to measure text"); return; } bounds->set(1e6, -1e6, -1e6, 1e6); - render(paint, text, start, len, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions); + render(paint, text, numGlyphs, 0, 0, MEASURE, nullptr, 0, 0, bounds, positions); } void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) { @@ -378,10 +376,10 @@ void Font::precache(const SkPaint* paint, const char* text, int numGlyphs) { } } -void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, +void Font::render(const SkPaint* paint, const char* text, int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect* bounds, const float* positions) { - if (numGlyphs == 0 || text == nullptr || len == 0) { + if (numGlyphs == 0 || text == nullptr) { return; } @@ -395,7 +393,6 @@ void Font::render(const SkPaint* paint, const char* text, uint32_t start, uint32 }; RenderGlyph render = gRenderGlyph[(mode << 1) + !mIdentityTransform]; - text += start; int glyphsCount = 0; while (glyphsCount < numGlyphs) { diff --git a/libs/hwui/font/Font.h b/libs/hwui/font/Font.h index 3119d734bc2b..59518a1fb8ee 100644 --- a/libs/hwui/font/Font.h +++ b/libs/hwui/font/Font.h @@ -82,10 +82,10 @@ public: ~Font(); - void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, + void render(const SkPaint* paint, const char* text, int numGlyphs, int x, int y, const float* positions); - void render(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, + void render(const SkPaint* paint, const char* text, int numGlyphs, const SkPath* path, float hOffset, float vOffset); const Font::FontDescription& getDescription() const { @@ -113,11 +113,11 @@ private: void precache(const SkPaint* paint, const char* text, int numGlyphs); - void render(const SkPaint* paint, const char *text, uint32_t start, uint32_t len, + void render(const SkPaint* paint, const char *text, int numGlyphs, int x, int y, RenderMode mode, uint8_t *bitmap, uint32_t bitmapW, uint32_t bitmapH, Rect *bounds, const float* positions); - void measure(const SkPaint* paint, const char* text, uint32_t start, uint32_t len, + void measure(const SkPaint* paint, const char* text, int numGlyphs, Rect *bounds, const float* positions); void invalidateTextureCache(CacheTexture* cacheTexture = nullptr); diff --git a/libs/hwui/unit_tests/OpReordererTests.cpp b/libs/hwui/unit_tests/OpReordererTests.cpp index ec8048dae014..d76086c9cfcd 100644 --- a/libs/hwui/unit_tests/OpReordererTests.cpp +++ b/libs/hwui/unit_tests/OpReordererTests.cpp @@ -136,14 +136,14 @@ TEST(OpReorderer, simpleRejection) { } TEST(OpReorderer, simpleBatching) { - static int SIMPLE_BATCHING_LOOPS = 5; + const int LOOPS = 5; class SimpleBatchingTestRenderer : public TestRendererBase { public: void onBitmapOp(const BitmapOp& op, const BakedOpState& state) override { - EXPECT_TRUE(mIndex++ >= SIMPLE_BATCHING_LOOPS); + EXPECT_TRUE(mIndex++ >= LOOPS) << "Bitmaps should be above all rects"; } void onRectOp(const RectOp& op, const BakedOpState& state) override { - EXPECT_TRUE(mIndex++ < SIMPLE_BATCHING_LOOPS); + EXPECT_TRUE(mIndex++ < LOOPS) << "Rects should be below all bitmaps"; } }; @@ -153,7 +153,7 @@ TEST(OpReorderer, simpleBatching) { // Alternate between drawing rects and bitmaps, with bitmaps overlapping rects. // Rects don't overlap bitmaps, so bitmaps should be brought to front as a group. canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); - for (int i = 0; i < SIMPLE_BATCHING_LOOPS; i++) { + for (int i = 0; i < LOOPS; i++) { canvas.translate(0, 10); canvas.drawRect(0, 0, 10, 10, SkPaint()); canvas.drawBitmap(bitmap, 5, 0, nullptr); @@ -164,7 +164,35 @@ TEST(OpReorderer, simpleBatching) { OpReorderer reorderer(200, 200, *dl, sLightCenter); SimpleBatchingTestRenderer renderer; reorderer.replayBakedOps<TestDispatcher>(renderer); - EXPECT_EQ(2 * SIMPLE_BATCHING_LOOPS, renderer.getIndex()); // 2 x loops ops, because no merging (TODO: force no merging) + EXPECT_EQ(2 * LOOPS, renderer.getIndex()) + << "Expect number of ops = 2 * loop count"; // TODO: force no merging +} + +TEST(OpReorderer, textStrikethroughBatching) { + const int LOOPS = 5; + class TextStrikethroughTestRenderer : public TestRendererBase { + public: + void onRectOp(const RectOp& op, const BakedOpState& state) override { + EXPECT_TRUE(mIndex++ >= LOOPS) << "Strikethrough rects should be above all text"; + } + void onTextOp(const TextOp& op, const BakedOpState& state) override { + EXPECT_TRUE(mIndex++ < LOOPS) << "Text should be beneath all strikethrough rects"; + } + }; + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 2000, [](RecordingCanvas& canvas) { + SkPaint textPaint; + textPaint.setAntiAlias(true); + textPaint.setTextSize(20); + textPaint.setStrikeThruText(true); + for (int i = 0; i < LOOPS; i++) { + TestUtils::drawTextToCanvas(&canvas, "test text", textPaint, 10, 100 * (i + 1)); + } + }); + OpReorderer reorderer(200, 2000, *dl, sLightCenter); + TextStrikethroughTestRenderer renderer; + reorderer.replayBakedOps<TestDispatcher>(renderer); + EXPECT_EQ(2 * LOOPS, renderer.getIndex()) + << "Expect number of ops = 2 * loop count"; // TODO: force no merging } TEST(OpReorderer, renderNode) { diff --git a/libs/hwui/unit_tests/RecordingCanvasTests.cpp b/libs/hwui/unit_tests/RecordingCanvasTests.cpp index 22190f50cbe5..c23d47e3fab4 100644 --- a/libs/hwui/unit_tests/RecordingCanvasTests.cpp +++ b/libs/hwui/unit_tests/RecordingCanvasTests.cpp @@ -41,21 +41,110 @@ TEST(RecordingCanvas, emptyPlayback) { playbackOps(*dl, [](const RecordedOp& op) { ADD_FAILURE(); }); } -TEST(RecordingCanvas, testSimpleRectRecord) { +TEST(RecordingCanvas, drawLines) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setStrokeWidth(20); + float points[] = { 0, 0, 20, 10, 30, 40, 90 }; // NB: only 1 valid line + canvas.drawLines(&points[0], 7, paint); + }); + + ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; + auto op = dl->getOps()[0]; + ASSERT_EQ(RecordedOpId::LinesOp, op->opId); + EXPECT_EQ(4, ((LinesOp*)op)->floatCount) + << "float count must be rounded down to closest multiple of 4"; + EXPECT_EQ(Rect(-10, -10, 30, 20), op->unmappedBounds) + << "unmapped bounds must be size of line, outset by 1/2 stroke width"; +} + +TEST(RecordingCanvas, drawRect) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { canvas.drawRect(10, 20, 90, 180, SkPaint()); }); + ASSERT_EQ(1u, dl->getOps().size()) << "Must be exactly one op"; + auto op = *(dl->getOps()[0]); + ASSERT_EQ(RecordedOpId::RectOp, op.opId); + EXPECT_EQ(Rect(0, 0, 100, 200), op.localClipRect); + EXPECT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds); +} + +TEST(RecordingCanvas, drawText) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(20); + TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); + }); + int count = 0; playbackOps(*dl, [&count](const RecordedOp& op) { count++; - ASSERT_EQ(RecordedOpId::RectOp, op.opId); - ASSERT_EQ(Rect(0, 0, 100, 200), op.localClipRect); - ASSERT_EQ(Rect(10, 20, 90, 180), op.unmappedBounds); + ASSERT_EQ(RecordedOpId::TextOp, op.opId); + EXPECT_EQ(Rect(0, 0, 200, 200), op.localClipRect); + EXPECT_TRUE(op.localMatrix.isIdentity()); + EXPECT_TRUE(op.unmappedBounds.contains(25, 15, 50, 25)) + << "Op expected to be 25+ pixels wide, 10+ pixels tall"; }); ASSERT_EQ(1, count); } +TEST(RecordingCanvas, drawText_strikeThruAndUnderline) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(20); + for (int i = 0; i < 2; i++) { + for (int j = 0; j < 2; j++) { + paint.setUnderlineText(i != 0); + paint.setStrikeThruText(j != 0); + TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); + } + } + }); + + auto ops = dl->getOps(); + ASSERT_EQ(8u, ops.size()); + + int index = 0; + EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); // no underline or strikethrough + + EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); + EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough only + + EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); + EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline only + + EXPECT_EQ(RecordedOpId::TextOp, ops[index++]->opId); + EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // underline + EXPECT_EQ(RecordedOpId::RectOp, ops[index++]->opId); // strikethrough +} + +TEST(RecordingCanvas, drawText_forceAlignLeft) { + auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { + SkPaint paint; + paint.setAntiAlias(true); + paint.setTextSize(20); + paint.setTextAlign(SkPaint::kLeft_Align); + TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); + paint.setTextAlign(SkPaint::kCenter_Align); + TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); + paint.setTextAlign(SkPaint::kRight_Align); + TestUtils::drawTextToCanvas(&canvas, "test text", paint, 25, 25); + }); + + int count = 0; + playbackOps(*dl, [&count](const RecordedOp& op) { + count++; + ASSERT_EQ(RecordedOpId::TextOp, op.opId); + EXPECT_EQ(SkPaint::kLeft_Align, op.paint->getTextAlign()) + << "recorded drawText commands must force kLeft_Align on their paint"; + EXPECT_EQ(SkPaint::kGlyphID_TextEncoding, op.paint->getTextEncoding()); // verify TestUtils + }); + ASSERT_EQ(3, count); +} + TEST(RecordingCanvas, backgroundAndImage) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(100, 200, [](RecordingCanvas& canvas) { SkBitmap bitmap; @@ -109,7 +198,7 @@ TEST(RecordingCanvas, backgroundAndImage) { ASSERT_EQ(2, count); } -TEST(RecordingCanvas, saveLayerSimple) { +TEST(RecordingCanvas, saveLayer_simple) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { canvas.saveLayerAlpha(10, 20, 190, 180, 128, SkCanvas::kARGB_ClipLayer_SaveFlag); canvas.drawRect(10, 20, 190, 180, SkPaint()); @@ -143,7 +232,7 @@ TEST(RecordingCanvas, saveLayerSimple) { EXPECT_EQ(3, count); } -TEST(RecordingCanvas, saveLayerViewportCrop) { +TEST(RecordingCanvas, saveLayer_viewportCrop) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { // shouldn't matter, since saveLayer will clip to its bounds canvas.clipRect(-1000, -1000, 1000, 1000, SkRegion::kReplace_Op); @@ -167,7 +256,7 @@ TEST(RecordingCanvas, saveLayerViewportCrop) { EXPECT_EQ(3, count); } -TEST(RecordingCanvas, saveLayerRotateUnclipped) { +TEST(RecordingCanvas, saveLayer_rotateUnclipped) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); canvas.translate(100, 100); @@ -193,7 +282,7 @@ TEST(RecordingCanvas, saveLayerRotateUnclipped) { EXPECT_EQ(3, count); } -TEST(RecordingCanvas, saveLayerRotateClipped) { +TEST(RecordingCanvas, saveLayer_rotateClipped) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { canvas.save(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag); canvas.translate(100, 100); @@ -224,7 +313,7 @@ TEST(RecordingCanvas, saveLayerRotateClipped) { EXPECT_EQ(3, count); } -TEST(RecordingCanvas, testReorderBarrier) { +TEST(RecordingCanvas, insertReorderBarrier) { auto dl = TestUtils::createDisplayList<RecordingCanvas>(200, 200, [](RecordingCanvas& canvas) { canvas.drawRect(0, 0, 400, 400, SkPaint()); canvas.insertReorderBarrier(true); diff --git a/libs/hwui/utils/TestUtils.cpp b/libs/hwui/utils/TestUtils.cpp index 84230a72f1c2..dd6fc36b8042 100644 --- a/libs/hwui/utils/TestUtils.cpp +++ b/libs/hwui/utils/TestUtils.cpp @@ -36,5 +36,46 @@ SkColor TestUtils::interpolateColor(float fraction, SkColor start, SkColor end) | (int)((startB + (int)(fraction * (endB - startB)))); } +void TestUtils::drawTextToCanvas(TestCanvas* canvas, const char* text, + const SkPaint& inPaint, float x, float y) { + // copy to force TextEncoding (which JNI layer would have done) + SkPaint paint(inPaint); + paint.setTextEncoding(SkPaint::kGlyphID_TextEncoding); + + SkMatrix identity; + identity.setIdentity(); + SkSurfaceProps surfaceProps(0, kUnknown_SkPixelGeometry); + SkAutoGlyphCacheNoGamma autoCache(paint, &surfaceProps, &identity); + + float totalAdvance = 0; + std::vector<glyph_t> glyphs; + std::vector<float> positions; + Rect bounds; + while (*text != '\0') { + SkUnichar unichar = SkUTF8_NextUnichar(&text); + glyph_t glyph = autoCache.getCache()->unicharToGlyph(unichar); + autoCache.getCache()->unicharToGlyph(unichar); + + // push glyph and its relative position + glyphs.push_back(glyph); + positions.push_back(totalAdvance); + positions.push_back(0); + + // compute bounds + SkGlyph skGlyph = autoCache.getCache()->getUnicharMetrics(unichar); + Rect glyphBounds(skGlyph.fWidth, skGlyph.fHeight); + glyphBounds.translate(totalAdvance + skGlyph.fLeft, skGlyph.fTop); + bounds.unionWith(glyphBounds); + + // advance next character + SkScalar skWidth; + paint.getTextWidths(&glyph, sizeof(glyph), &skWidth, NULL); + totalAdvance += skWidth; + } + bounds.translate(x, y); + canvas->drawText(glyphs.data(), positions.data(), glyphs.size(), paint, x, y, + bounds.left, bounds.top, bounds.right, bounds.bottom, totalAdvance); +} + } /* namespace uirenderer */ } /* namespace android */ diff --git a/libs/hwui/utils/TestUtils.h b/libs/hwui/utils/TestUtils.h index f7f4f2d1ca10..f9fa242ec833 100644 --- a/libs/hwui/utils/TestUtils.h +++ b/libs/hwui/utils/TestUtils.h @@ -196,6 +196,9 @@ public: static SkColor interpolateColor(float fraction, SkColor start, SkColor end); + static void drawTextToCanvas(TestCanvas* canvas, const char* text, + const SkPaint& inPaint, float x, float y); + private: static void syncHierarchyPropertiesAndDisplayListImpl(RenderNode* node) { node->syncProperties(); |