1//========================================================================
2//
3// MarkedContentOutputDev.cc
4//
5// This file is licensed under the GPLv2 or later
6//
7// Copyright 2013 Igalia S.L.
8// Copyright 2018-2020, 2022 Albert Astals Cid <aacid@kde.org>
9// Copyright 2021, 2023 Adrian Johnson <ajohnson@redneon.com>
10// Copyright 2022 Oliver Sander <oliver.sander@tu-dresden.de>
11//
12//========================================================================
13
14#include "MarkedContentOutputDev.h"
15#include "GlobalParams.h"
16#include "UnicodeMap.h"
17#include "GfxState.h"
18#include "GfxFont.h"
19#include "Annot.h"
20#include <cmath>
21#include <vector>
22
23MarkedContentOutputDev::MarkedContentOutputDev(int mcidA, const Object &stmObj) : currentFont(nullptr), currentText(nullptr), mcid(mcidA), pageWidth(0.0), pageHeight(0.0), unicodeMap(nullptr)
24{
25 stmRef = stmObj.copy();
26 currentColor.r = currentColor.g = currentColor.b = 0;
27}
28
29MarkedContentOutputDev::~MarkedContentOutputDev()
30{
31 delete currentText;
32}
33
34void MarkedContentOutputDev::endSpan()
35{
36 if (currentText && currentText->getLength()) {
37 // The TextSpan takes ownership of currentText and
38 // increases the reference count for currentFont.
39 textSpans.push_back(x: TextSpan(currentText, currentFont, currentColor));
40 }
41 currentText = nullptr;
42}
43
44void MarkedContentOutputDev::startPage(int pageNum, GfxState *state, XRef *xref)
45{
46 if (state) {
47 pageWidth = state->getPageWidth();
48 pageHeight = state->getPageHeight();
49 } else {
50 pageWidth = pageHeight = 0.0;
51 }
52}
53
54void MarkedContentOutputDev::endPage()
55{
56 pageWidth = pageHeight = 0.0;
57}
58
59void MarkedContentOutputDev::beginForm(Object * /* obj */, Ref id)
60{
61 formStack.push_back(x: id);
62}
63
64void MarkedContentOutputDev::endForm(Object * /* obj */, Ref id)
65{
66 formStack.pop_back();
67}
68
69bool MarkedContentOutputDev::contentStreamMatch()
70{
71 if (stmRef.isRef()) {
72 if (formStack.empty()) {
73 return false;
74 }
75 return formStack.back() == stmRef.getRef();
76 }
77 return formStack.empty();
78}
79
80void MarkedContentOutputDev::beginMarkedContent(const char *name, Dict *properties)
81{
82 int id = -1;
83 if (properties) {
84 properties->lookupInt(key: "MCID", alt_key: nullptr, value: &id);
85 }
86
87 if (id == -1) {
88 return;
89 }
90
91 // The stack keep track of MCIDs of nested marked content.
92 if (inMarkedContent() || (id == mcid && contentStreamMatch())) {
93 mcidStack.push_back(x: id);
94 }
95}
96
97void MarkedContentOutputDev::endMarkedContent(GfxState *state)
98{
99 if (inMarkedContent()) {
100 mcidStack.pop_back();
101 // The outer marked content sequence MCID was popped, ensure
102 // that the last piece of text collected ends up in a TextSpan.
103 if (!inMarkedContent()) {
104 endSpan();
105 }
106 }
107}
108
109bool MarkedContentOutputDev::needFontChange(const std::shared_ptr<const GfxFont> &font) const
110{
111 if (currentFont == font) {
112 return false;
113 }
114
115 if (!currentFont) {
116 return font != nullptr && font->isOk();
117 }
118
119 if (font == nullptr) {
120 return true;
121 }
122
123 // Two non-null valid fonts are the same if they point to the same Ref
124 if (*currentFont->getID() == *font->getID()) {
125 return false;
126 }
127
128 return true;
129}
130
131void 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)
132{
133 if (!inMarkedContent() || !uLen) {
134 return;
135 }
136
137 // Color changes are tracked here so the color can be chosen depending on
138 // the render mode (for mode 1 stroke color is used), so there is no need
139 // to implement both updateFillColor() and updateStrokeColor().
140 bool colorChange = false;
141 GfxRGB color;
142 if ((state->getRender() & 3) == 1) {
143 state->getStrokeRGB(rgb: &color);
144 } else {
145 state->getFillRGB(rgb: &color);
146 }
147
148 colorChange = (color.r != currentColor.r || color.g != currentColor.g || color.b != currentColor.b);
149
150 // Check also for font changes.
151 bool fontChange = needFontChange(font: state->getFont());
152
153 // Save a span with the current changes.
154 if (colorChange || fontChange) {
155 endSpan();
156 }
157
158 // Perform the color/font changes.
159 if (colorChange) {
160 currentColor = color;
161 }
162
163 if (fontChange) {
164 currentFont = state->getFont();
165 }
166
167 double sp, dx2, dy2, w1, h1, x1, y1;
168
169 // Subtract char and word spacing from the (dx,dy) values
170 sp = state->getCharSpace();
171 if (c == (CharCode)0x20) {
172 sp += state->getWordSpace();
173 }
174 state->textTransformDelta(x1: sp * state->getHorizScaling(), y1: 0, x2: &dx2, y2: &dy2);
175 dx -= dx2;
176 dy -= dy2;
177 state->transformDelta(x1: dx, y1: dy, x2: &w1, y2: &h1);
178 state->transform(x1: xx, y1: yy, x2: &x1, y2: &y1);
179
180 // Throw away characters that are not inside the page boundaries.
181 if (x1 + w1 < 0 || x1 > pageWidth || y1 + h1 < 0 || y1 > pageHeight) {
182 return;
183 }
184
185 if (std::isnan(x: x1) || std::isnan(x: y1) || std::isnan(x: w1) || std::isnan(x: h1)) {
186 return;
187 }
188
189 for (int i = 0; i < uLen; i++) {
190 // Soft hyphen markers are skipped, as they are invisible unless
191 // rendering is done to an actual device and the hyphenation hint
192 // used. MarkedContentOutputDev extracts the *visible* text content.
193 if (u[i] != 0x00AD) {
194 // Add the UTF-8 sequence to the current text span.
195 if (!unicodeMap) {
196 unicodeMap = globalParams->getTextEncoding();
197 }
198
199 char buf[8];
200 int n = unicodeMap->mapUnicode(u: u[i], buf, bufSize: sizeof(buf));
201 if (n > 0) {
202 if (currentText == nullptr) {
203 currentText = new GooString();
204 }
205 currentText->append(str: buf, lengthA: n);
206 }
207 }
208 }
209}
210
211const TextSpanArray &MarkedContentOutputDev::getTextSpans() const
212{
213 return textSpans;
214}
215

source code of poppler/poppler/MarkedContentOutputDev.cc