//========================================================================
//
// 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;
}
