/*
 * Copyright 2010 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "GrTextContext.h"
#include "GrBlurUtils.h"
#include "GrContext.h"
#include "GrDrawContext.h"
#include "GrFontScaler.h"

#include "SkAutoKern.h"
#include "SkDrawFilter.h"
#include "SkDrawProcs.h"
#include "SkGlyphCache.h"
#include "SkGpuDevice.h"
#include "SkGrPriv.h"
#include "SkTextBlob.h"
#include "SkTextMapStateProc.h"
#include "SkTextToPathIter.h"

GrTextContext::GrTextContext(GrContext* context, const SkSurfaceProps& surfaceProps)
    : fFallbackTextContext(nullptr)
    , fContext(context)
    , fSurfaceProps(surfaceProps) {
}

GrTextContext::~GrTextContext() { delete fFallbackTextContext; }

void GrTextContext::drawText(GrDrawContext* dc, GrRenderTarget* rt,
                             const GrClip& clip, const GrPaint& paint,
                             const SkPaint& skPaint, const SkMatrix& viewMatrix,
                             const char text[], size_t byteLength,
                             SkScalar x, SkScalar y, const SkIRect& clipBounds) {
    if (fContext->abandoned()) {
        return;
    }

    GrTextContext* textContext = this;
    do {
        if (textContext->canDraw(rt, clip, paint, skPaint, viewMatrix)) {
            textContext->onDrawText(dc, rt, clip, paint, skPaint, viewMatrix,
                                    text, byteLength, x, y, clipBounds);
            return;
        }
        textContext = textContext->fFallbackTextContext;
    } while (textContext);

    // fall back to drawing as a path
    this->drawTextAsPath(dc, rt, clip, skPaint, viewMatrix,
                         text, byteLength, x, y, clipBounds);
}

void GrTextContext::drawPosText(GrDrawContext* dc, GrRenderTarget* rt,
                                const GrClip& clip, const GrPaint& paint,
                                const SkPaint& skPaint, const SkMatrix& viewMatrix,
                                const char text[], size_t byteLength,
                                const SkScalar pos[], int scalarsPerPosition,
                                const SkPoint& offset, const SkIRect& clipBounds) {
    if (fContext->abandoned()) {
        return;
    }

    GrTextContext* textContext = this;
    do {
        if (textContext->canDraw(rt, clip, paint, skPaint, viewMatrix)) {
            textContext->onDrawPosText(dc, rt, clip, paint, skPaint, viewMatrix,
                                       text, byteLength, pos,
                                       scalarsPerPosition, offset, clipBounds);
            return;
        }
        textContext = textContext->fFallbackTextContext;
    } while (textContext);

    // fall back to drawing as a path
    this->drawPosTextAsPath(dc, rt, clip, skPaint, viewMatrix, text, byteLength, pos,
                            scalarsPerPosition, offset, clipBounds);
}

bool GrTextContext::ShouldDisableLCD(const SkPaint& paint) {
    if (!SkXfermode::AsMode(paint.getXfermode(), nullptr) ||
        paint.getMaskFilter() ||
        paint.getRasterizer() ||
        paint.getPathEffect() ||
        paint.isFakeBoldText() ||
        paint.getStyle() != SkPaint::kFill_Style)
    {
        return true;
    }
    return false;
}

uint32_t GrTextContext::FilterTextFlags(const SkSurfaceProps& surfaceProps, const SkPaint& paint) {
    uint32_t flags = paint.getFlags();

    if (!paint.isLCDRenderText() || !paint.isAntiAlias()) {
        return flags;
    }

    if (kUnknown_SkPixelGeometry == surfaceProps.pixelGeometry() || ShouldDisableLCD(paint)) {
        flags &= ~SkPaint::kLCDRenderText_Flag;
        flags |= SkPaint::kGenA8FromLCD_Flag;
    }

    return flags;
}

void GrTextContext::drawTextBlob(GrDrawContext* dc, GrRenderTarget* rt,
                                 const GrClip& clip, const SkPaint& skPaint,
                                 const SkMatrix& viewMatrix, const SkTextBlob* blob,
                                 SkScalar x, SkScalar y,
                                 SkDrawFilter* drawFilter, const SkIRect& clipBounds) {
    SkPaint runPaint = skPaint;

    SkTextBlob::RunIterator it(blob);
    for (;!it.done(); it.next()) {
        size_t textLen = it.glyphCount() * sizeof(uint16_t);
        const SkPoint& offset = it.offset();
        // applyFontToPaint() always overwrites the exact same attributes,
        // so it is safe to not re-seed the paint for this reason.
        it.applyFontToPaint(&runPaint);

        if (drawFilter && !drawFilter->filter(&runPaint, SkDrawFilter::kText_Type)) {
            // A false return from filter() means we should abort the current draw.
            runPaint = skPaint;
            continue;
        }

        runPaint.setFlags(FilterTextFlags(fSurfaceProps, runPaint));

        GrPaint grPaint;
        if (!SkPaintToGrPaint(fContext, runPaint, viewMatrix, &grPaint)) {
            return;
        }

        switch (it.positioning()) {
        case SkTextBlob::kDefault_Positioning:
            this->drawText(dc, rt, clip, grPaint, runPaint, viewMatrix, (const char *)it.glyphs(),
                           textLen, x + offset.x(), y + offset.y(), clipBounds);
            break;
        case SkTextBlob::kHorizontal_Positioning:
            this->drawPosText(dc, rt, clip, grPaint, runPaint, viewMatrix, (const char*)it.glyphs(),
                              textLen, it.pos(), 1, SkPoint::Make(x, y + offset.y()), clipBounds);
            break;
        case SkTextBlob::kFull_Positioning:
            this->drawPosText(dc, rt, clip, grPaint, runPaint, viewMatrix, (const char*)it.glyphs(),
                              textLen, it.pos(), 2, SkPoint::Make(x, y), clipBounds);
            break;
        default:
            SkFAIL("unhandled positioning mode");
        }

        if (drawFilter) {
            // A draw filter may change the paint arbitrarily, so we must re-seed in this case.
            runPaint = skPaint;
        }
    }
}

void GrTextContext::drawTextAsPath(GrDrawContext* dc, GrRenderTarget* rt,
                                   const GrClip& clip,
                                   const SkPaint& skPaint, const SkMatrix& viewMatrix,
                                   const char text[], size_t byteLength, SkScalar x, SkScalar y,
                                   const SkIRect& clipBounds) {
    SkTextToPathIter iter(text, byteLength, skPaint, true);

    SkMatrix    matrix;
    matrix.setScale(iter.getPathScale(), iter.getPathScale());
    matrix.postTranslate(x, y);

    const SkPath* iterPath;
    SkScalar xpos, prevXPos = 0;

    while (iter.next(&iterPath, &xpos)) {
        matrix.postTranslate(xpos - prevXPos, 0);
        if (iterPath) {
            const SkPaint& pnt = iter.getPaint();
            GrBlurUtils::drawPathWithMaskFilter(fContext, dc, rt, clip, *iterPath,
                                                pnt, viewMatrix, &matrix, clipBounds, false);
        }
        prevXPos = xpos;
    }
}

void GrTextContext::drawPosTextAsPath(GrDrawContext* dc, GrRenderTarget* rt,
                                      const GrClip& clip,
                                      const SkPaint& origPaint, const SkMatrix& viewMatrix,
                                      const char text[], size_t byteLength,
                                      const SkScalar pos[], int scalarsPerPosition,
                                      const SkPoint& offset, const SkIRect& clipBounds) {
    // setup our std paint, in hopes of getting hits in the cache
    SkPaint paint(origPaint);
    SkScalar matrixScale = paint.setupForAsPaths();

    SkMatrix matrix;
    matrix.setScale(matrixScale, matrixScale);

    // Temporarily jam in kFill, so we only ever ask for the raw outline from the cache.
    paint.setStyle(SkPaint::kFill_Style);
    paint.setPathEffect(nullptr);

    SkDrawCacheProc     glyphCacheProc = paint.getDrawCacheProc();
    SkAutoGlyphCache    autoCache(paint, &fSurfaceProps, nullptr);
    SkGlyphCache*       cache = autoCache.getCache();

    const char*        stop = text + byteLength;
    SkTextAlignProc    alignProc(paint.getTextAlign());
    SkTextMapStateProc tmsProc(SkMatrix::I(), offset, scalarsPerPosition);

    // Now restore the original settings, so we "draw" with whatever style/stroking.
    paint.setStyle(origPaint.getStyle());
    paint.setPathEffect(origPaint.getPathEffect());

    while (text < stop) {
        const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);
        if (glyph.fWidth) {
            const SkPath* path = cache->findPath(glyph);
            if (path) {
                SkPoint tmsLoc;
                tmsProc(pos, &tmsLoc);
                SkPoint loc;
                alignProc(tmsLoc, glyph, &loc);

                matrix[SkMatrix::kMTransX] = loc.fX;
                matrix[SkMatrix::kMTransY] = loc.fY;
                GrBlurUtils::drawPathWithMaskFilter(fContext, dc, rt, clip, *path, paint,
                                                    viewMatrix, &matrix, clipBounds, false);
            }
        }
        pos += scalarsPerPosition;
    }
}

// *** change to output positions?
int GrTextContext::MeasureText(SkGlyphCache* cache, SkDrawCacheProc glyphCacheProc,
                                const char text[], size_t byteLength, SkVector* stopVector) {
    SkFixed     x = 0, y = 0;
    const char* stop = text + byteLength;

    SkAutoKern  autokern;

    int numGlyphs = 0;
    while (text < stop) {
        // don't need x, y here, since all subpixel variants will have the
        // same advance
        const SkGlyph& glyph = glyphCacheProc(cache, &text, 0, 0);

        x += autokern.adjust(glyph) + glyph.fAdvanceX;
        y += glyph.fAdvanceY;
        ++numGlyphs;
    }
    stopVector->set(SkFixedToScalar(x), SkFixedToScalar(y));

    SkASSERT(text == stop);
    
    return numGlyphs;
}

static void GlyphCacheAuxProc(void* data) {
    GrFontScaler* scaler = (GrFontScaler*)data;
    SkSafeUnref(scaler);
}

GrFontScaler* GrTextContext::GetGrFontScaler(SkGlyphCache* cache) {
    void* auxData;
    GrFontScaler* scaler = nullptr;

    if (cache->getAuxProcData(GlyphCacheAuxProc, &auxData)) {
        scaler = (GrFontScaler*)auxData;
    }
    if (nullptr == scaler) {
        scaler = new GrFontScaler(cache);
        cache->setAuxProc(GlyphCacheAuxProc, scaler);
    }

    return scaler;
}
