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