blob: d815f41271cfff3e53e9edc64926bc6e528328d2 [file] [log] [blame]
//========================================================================
//
// MarkedContentOutputDev.cc
//
// This file is licensed under the GPLv2 or later
//
// Copyright 2013 Igalia S.L.
// Copyright 2018-2020, 2022 Albert Astals Cid <aacid@kde.org>
// Copyright 2021, 2023 Adrian Johnson <ajohnson@redneon.com>
// Copyright 2022 Oliver Sander <oliver.sander@tu-dresden.de>
//
//========================================================================
#include "MarkedContentOutputDev.h"
#include "GlobalParams.h"
#include "UnicodeMap.h"
#include "GfxState.h"
#include "GfxFont.h"
#include "Annot.h"
#include <cmath>
#include <vector>
MarkedContentOutputDev::MarkedContentOutputDev(int mcidA, const Object &stmObj) : currentFont(nullptr), currentText(nullptr), mcid(mcidA), pageWidth(0.0), pageHeight(0.0), unicodeMap(nullptr)
{
stmRef = stmObj.copy();
currentColor.r = currentColor.g = currentColor.b = 0;
}
MarkedContentOutputDev::~MarkedContentOutputDev()
{
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::beginForm(Object * /* obj */, Ref id)
{
formStack.push_back(id);
}
void MarkedContentOutputDev::endForm(Object * /* obj */, Ref id)
{
formStack.pop_back();
}
bool MarkedContentOutputDev::contentStreamMatch()
{
if (stmRef.isRef()) {
if (formStack.empty()) {
return false;
}
return formStack.back() == stmRef.getRef();
}
return formStack.empty();
}
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 && contentStreamMatch())) {
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 std::shared_ptr<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) {
currentFont = state->getFont();
}
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;
}
if (std::isnan(x1) || std::isnan(y1) || std::isnan(w1) || std::isnan(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;
}