//========================================================================
//
// MarkedContentOutputDev.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright 2013 Igalia S.L.
// Copyright 2018-2020 Albert Astals Cid <aacid@kde.org>
//
//========================================================================

#include "MarkedContentOutputDev.h"
#include "GlobalParams.h"
#include "UnicodeMap.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "Annot.h"
#include <vector>

MarkedContentOutputDev::MarkedContentOutputDev(int mcidA) : currentFont(nullptr), currentText(nullptr), mcid(mcidA), pageWidth(0.0), pageHeight(0.0), unicodeMap(nullptr)
{
    currentColor.r = currentColor.g = currentColor.b = 0;
}

MarkedContentOutputDev::~MarkedContentOutputDev()
{
    if (currentFont)
        currentFont->decRefCnt();
    delete currentText;
}

void MarkedContentOutputDev::endSpan()
{
    if (currentText && currentText->getLength()) {
        // The TextSpan takes ownership of currentText and
        // increases the reference count for currentFont.
        textSpans.push_back(TextSpan(currentText, currentFont, currentColor));
    }
    currentText = nullptr;
}

void MarkedContentOutputDev::startPage(int pageNum, GfxState *state, XRef *xref)
{
    if (state) {
        pageWidth = state->getPageWidth();
        pageHeight = state->getPageHeight();
    } else {
        pageWidth = pageHeight = 0.0;
    }
}

void MarkedContentOutputDev::endPage()
{
    pageWidth = pageHeight = 0.0;
}

void MarkedContentOutputDev::beginMarkedContent(const char *name, Dict *properties)
{
    int id = -1;
    if (properties)
        properties->lookupInt("MCID", nullptr, &id);

    if (id == -1)
        return;

    // The stack keep track of MCIDs of nested marked content.
    if (inMarkedContent() || id == mcid)
        mcidStack.push_back(id);
}

void MarkedContentOutputDev::endMarkedContent(GfxState *state)
{
    if (inMarkedContent()) {
        mcidStack.pop_back();
        // The outer marked content sequence MCID was popped, ensure
        // that the last piece of text collected ends up in a TextSpan.
        if (!inMarkedContent())
            endSpan();
    }
}

bool MarkedContentOutputDev::needFontChange(const GfxFont *font) const
{
    if (currentFont == font)
        return false;

    if (!currentFont)
        return font != nullptr && font->isOk();

    if (font == nullptr)
        return true;

    // Two non-null valid fonts are the same if they point to the same Ref
    if (*currentFont->getID() == *font->getID())
        return false;

    return true;
}

void MarkedContentOutputDev::drawChar(GfxState *state, double xx, double yy, double dx, double dy, double ox, double oy, CharCode c, int nBytes, const Unicode *u, int uLen)
{
    if (!inMarkedContent() || !uLen)
        return;

    // Color changes are tracked here so the color can be chosen depending on
    // the render mode (for mode 1 stroke color is used), so there is no need
    // to implement both updateFillColor() and updateStrokeColor().
    bool colorChange = false;
    GfxRGB color;
    if ((state->getRender() & 3) == 1)
        state->getStrokeRGB(&color);
    else
        state->getFillRGB(&color);

    colorChange = (color.r != currentColor.r || color.g != currentColor.g || color.b != currentColor.b);

    // Check also for font changes.
    bool fontChange = needFontChange(state->getFont());

    // Save a span with the current changes.
    if (colorChange || fontChange) {
        endSpan();
    }

    // Perform the color/font changes.
    if (colorChange)
        currentColor = color;

    if (fontChange) {
        if (currentFont != nullptr) {
            currentFont->decRefCnt();
            currentFont = nullptr;
        }
        if (state->getFont() != nullptr) {
            currentFont = state->getFont();
            currentFont->incRefCnt();
        }
    }

    double sp, dx2, dy2, w1, h1, x1, y1;

    // Subtract char and word spacing from the (dx,dy) values
    sp = state->getCharSpace();
    if (c == (CharCode)0x20)
        sp += state->getWordSpace();
    state->textTransformDelta(sp * state->getHorizScaling(), 0, &dx2, &dy2);
    dx -= dx2;
    dy -= dy2;
    state->transformDelta(dx, dy, &w1, &h1);
    state->transform(xx, yy, &x1, &y1);

    // Throw away characters that are not inside the page boundaries.
    if (x1 + w1 < 0 || x1 > pageWidth || y1 + h1 < 0 || y1 > pageHeight)
        return;

    // Make a sanity check on character size. Note: (x != x) <-> isnan(x)
    if (x1 != x1 || y1 != y1 || w1 != w1 || h1 != h1)
        return;

    for (int i = 0; i < uLen; i++) {
        // Soft hyphen markers are skipped, as they are invisible unless
        // rendering is done to an actual device and the hyphenation hint
        // used. MarkedContentOutputDev extracts the *visible* text content.
        if (u[i] != 0x00AD) {
            // Add the UTF-8 sequence to the current text span.
            if (!unicodeMap)
                unicodeMap = globalParams->getTextEncoding();

            char buf[8];
            int n = unicodeMap->mapUnicode(u[i], buf, sizeof(buf));
            if (n > 0) {
                if (currentText == nullptr)
                    currentText = new GooString();
                currentText->append(buf, n);
            }
        }
    }
}

const TextSpanArray &MarkedContentOutputDev::getTextSpans() const
{
    return textSpans;
}
