//======================================================================== // // MarkedContentOutputDev.cc // // This file is licensed under the GPLv2 or later // // Copyright 2013 Igalia S.L. // Copyright 2018-2020 Albert Astals Cid // //======================================================================== #include "MarkedContentOutputDev.h" #include "GlobalParams.h" #include "UnicodeMap.h" #include "GfxState.h" #include "GfxFont.h" #include "Annot.h" #include 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; }