Subversion Repositories Scribus

Rev

Rev 24665 | Rev 25004 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
24374 jghali 1
/*
2
For general Scribus (>=1.3.2) copyright and licensing information please refer
3
to the COPYING file provided with the program. Following this notice may exist
4
a copyright and/or license notice that predates the release of Scribus 1.3.2
5
for which a new license (GPL+exception) is in place.
6
*/
7
 
8
#include "pdftextrecognition.h"
9
 
24378 jghali 10
constexpr double PdfTextRegion::boundingBoxShape[32];
11
 
24374 jghali 12
/*
13
*       constructor, initialize the textRegions vector and set the addChar mode
14
*/
15
PdfTextRecognition::PdfTextRecognition()
16
{
24376 jghali 17
        m_pdfTextRegions.push_back(PdfTextRegion());
18
        activePdfTextRegion = &(m_pdfTextRegions.back());
24374 jghali 19
        setCharMode(AddCharMode::ADDFIRSTCHAR);
20
}
21
 
22
/*
23
*       nothing to do in the destructor yet
24
*/
25
PdfTextRecognition::~PdfTextRecognition()
26
{
27
}
28
 
29
/*
30
*       add a new text region and make it the active region
31
*/
32
void PdfTextRecognition::addPdfTextRegion()
33
{
24376 jghali 34
        m_pdfTextRegions.push_back(PdfTextRegion());
35
        activePdfTextRegion = &(m_pdfTextRegions.back());
24374 jghali 36
        setCharMode(PdfTextRecognition::AddCharMode::ADDFIRSTCHAR);
37
}
38
 
39
/*
40
*       function called via integration with poppler's addChar callback. It decides how to add the charter based on the mode that is set
41
*/
42
void PdfTextRecognition::addChar(GfxState* state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, POPPLER_CONST_082 Unicode* u, int uLen)
43
{
44
 
45
        switch (this->m_addCharMode)
46
        {
47
        case AddCharMode::ADDFIRSTCHAR:
48
                AddFirstChar(state, x, y, dx, dy, originX, originY, code, nBytes, u, uLen);
49
                break;
50
        case AddCharMode::ADDBASICCHAR:
51
                AddBasicChar(state, x, y, dx, dy, originX, originY, code, nBytes, u, uLen);
52
                break;
53
        case AddCharMode::ADDCHARWITHNEWSTYLE:
54
                AddCharWithNewStyle(state, x, y, dx, dy, originX, originY, code, nBytes, u, uLen);
55
                break;
56
        case AddCharMode::ADDCHARWITHPREVIOUSSTYLE:
57
                AddCharWithPreviousStyle(state, x, y, dx, dy, originX, originY, code, nBytes, u, uLen);
58
                break;
59
        }
60
}
61
 
62
/*
63
*       basic test to see if the point lies in a new line or region
64
*/
65
bool PdfTextRecognition::isNewLineOrRegion(QPointF newPosition)
66
{
24376 jghali 67
        return (activePdfTextRegion->collinear(activePdfTextRegion->lastXY.y(), activePdfTextRegion->pdfTextRegionLines.back().baseOrigin.y()) &&
68
                !activePdfTextRegion->collinear(newPosition.y(), activePdfTextRegion->lastXY.y()))
69
                || (activePdfTextRegion->collinear(newPosition.y(), activePdfTextRegion->lastXY.y())
70
                        && !activePdfTextRegion->isCloseToX(newPosition.x(), activePdfTextRegion->lastXY.x()));
24374 jghali 71
}
72
 
73
 
74
/*
75
*       basic functionality to be performed when addChar is called
76
*       FIXME: what to do when uLen != 1
77
*/
78
PdfGlyph PdfTextRecognition::AddCharCommon(GfxState* state, double x, double y, double dx, double dy, Unicode const* u, int uLen)
79
{
24382 jghali 80
        const double * ctm = state->getCTM();
81
        QTransform trans(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
82
        QPointF charDelta1 = trans.map(QPointF(0.0, 0.0));
83
        QPointF charDelta2 = trans.map(QPointF(dx, dy));
84
        QPointF charDelta = charDelta2 - charDelta1;
85
 
24374 jghali 86
        //qDebug() << "AddBasicChar() '" << u << " : " << uLen;
87
        PdfGlyph newGlyph;
24382 jghali 88
        newGlyph.dx = charDelta.x();
89
        newGlyph.dy = charDelta.y();
24374 jghali 90
 
91
        // Convert the character to UTF-16 since that's our SVG document's encoding
92
 
93
        if (uLen > 1)
94
                qDebug() << "FIXME: AddBasicChar() '" << u << " : " << uLen;
95
        newGlyph.code = static_cast<char16_t>(u[uLen - 1]);
96
        newGlyph.rise = state->getRise();
97
        return newGlyph;
98
}
99
 
100
/*
101
*       Tell the text region to add a glyph so that line segments and regions be created
102
*       If the character being added is the first character in a textregion or after a change in positioning or styles or the end of a line
103
*       The success == TextRegion::LineType::FAIL test is an invariant test that should never pass. if a rogue glyph is detected then it means there is a bug in the logic probably in TextRegion::addGlyphAtPoint or TextRegion::linearTest or TextRegion::moveToPoint
104
*/
105
PdfGlyph PdfTextRecognition::AddFirstChar(GfxState* state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, Unicode const* u, int uLen)
106
{
107
        //qDebug() << "AddFirstChar() '" << u << " : " << uLen;
108
        PdfGlyph newGlyph = PdfTextRecognition::AddCharCommon(state, x, y, dx, dy, u, uLen);
24376 jghali 109
        activePdfTextRegion->glyphs.push_back(newGlyph);
24374 jghali 110
        setCharMode(AddCharMode::ADDBASICCHAR);
111
 
112
        //only need to be called for the very first point
24382 jghali 113
        const double * ctm = state->getCTM();
114
        QTransform trans(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
115
        QPointF glyphPos = trans.map(QPointF(x, y));
116
 
117
        auto success = activePdfTextRegion->addGlyphAtPoint(glyphPos, newGlyph);
24374 jghali 118
        if (success == PdfTextRegion::LineType::FAIL)
119
                qDebug("FIXME: Rogue glyph detected, this should never happen because the cursor should move before glyphs in new regions are added.");
120
        return newGlyph;
121
}
122
 
123
/*
124
*       just add a character to the textregion without doing anything special
125
*/
126
PdfGlyph PdfTextRecognition::AddBasicChar(GfxState* state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, Unicode const* u, int uLen)
127
{
24382 jghali 128
        const double * ctm = state->getCTM();
129
        QTransform trans(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
130
 
24374 jghali 131
        PdfGlyph newGlyph = AddCharCommon(state, x, y, dx, dy, u, uLen);
24382 jghali 132
        activePdfTextRegion->lastXY = trans.map(QPointF(x, y));
24376 jghali 133
        activePdfTextRegion->glyphs.push_back(newGlyph);
24374 jghali 134
        return newGlyph;
135
}
136
 
137
/*
138
*       Apply a new style to this glyph ands glyphs that follow and add it to the style stack
139
*       TODO: Currently not implemented, just stub code
140
*/
141
PdfGlyph PdfTextRecognition::AddCharWithNewStyle(GfxState* state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, Unicode const* u, int uLen)
142
{
143
        //qDebug() << "AddCharWithNewStyle() '" << u << " : " << uLen;
144
        auto newGlyph = AddCharCommon(state, x, y, dx, dy, u, uLen);
24376 jghali 145
        activePdfTextRegion->glyphs.push_back(newGlyph);
24374 jghali 146
        return newGlyph;
147
}
148
 
149
/*
150
*       return to the previous style on the style stack
151
*       TODO: Currently not implemented, just stub code
152
*/
153
PdfGlyph PdfTextRecognition::AddCharWithPreviousStyle(GfxState* state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, Unicode const* u, int uLen)
154
{
155
        //qDebug() << "AddCharWithPreviousStyle() '" << u << " : " << uLen;
156
        auto newGlyph = AddCharCommon(state, x, y, dx, dy, u, uLen);
24376 jghali 157
        activePdfTextRegion->glyphs.push_back(newGlyph);
24374 jghali 158
        return newGlyph;
159
}
160
 
161
/*
162
*       functions to do fuzzy testing on the proximity of points to one another and in relation to the textregion
163
*       FIXME: There should be a parameter in the UI to set the matching tolerance but hard code for now
164
*/
165
 
166
/*
167
*       In geometry, collinearity of a set of points is the property of their lying on a single line. A set of points with this property is said to be collinear.
168
*       In greater generality, the term has been used for aligned objects, that is, things being "in a line" or "in a row".
169
*       PDF never deviates from the line when it comes to collinear, but allow for 1pixel of divergence
170
*/
171
bool PdfTextRegion::collinear(qreal a, qreal b)
172
{
173
        return abs(a - b) < 1 ? true : false;
174
}
175
 
176
/*
177
*       like collinear but we allow a deviation of 6 text widths from between positions or 1 text width from the textregion's x origin
178
*   FIXME: This should use the char width not linespacing which is y
179
*/
180
bool PdfTextRegion::isCloseToX(qreal x1, qreal x2)
181
{
182
        return (abs(x2 - x1) <= lineSpacing * 6) || (abs(x1 - this->pdfTextRegionBasenOrigin.x()) <= lineSpacing);
183
}
184
 
185
/*
186
*       like collinear but we allow a deviation of 3 text heights downwards but none upwards
187
*/
188
bool PdfTextRegion::isCloseToY(qreal y1, qreal y2)
189
{
190
        return (y2 - y1) >= 0 && y2 - y1 <= lineSpacing * 3;
191
}
192
 
193
/*
194
*       less than, page upwards, the last y value but bot more than the line spacing less, could also use the base line of the last line to be more accurate
195
*/
196
bool PdfTextRegion::adjunctLesser(qreal testY, qreal lastY, qreal baseY)
197
{
198
        return (testY > lastY
199
                && testY <= baseY + lineSpacing
200
                && lastY <= baseY + lineSpacing);
201
}
202
 
203
/*
204
*       greater, page downwards, than the last y value but not more than 3/4 of a line space below baseline
205
*/
206
bool PdfTextRegion::adjunctGreater(qreal testY, qreal lastY, qreal baseY)
207
{
208
        return (testY <= lastY
209
                && testY >= baseY - lineSpacing * 0.75
210
                && lastY != baseY);
211
}
212
 
213
/*
214
*       Test to see if the point is part of the current block of text or is part of a new block of text(FrameworkLineTests::FAIL).
215
*       checks to see if it's the first point, on the same line, super and sub script, returning to baseline from super script or if we are on a new line.
216
*       matching is fuzzy allowing for multiple linespaces and text indentation. right hand justifications still needs to be dealt with as well as identifying if we are on a new paragraph
217
*       tests are weaker if we are on the first and moving to the second lines of text because we don't have enough information about how the text in the region
218
*       is formatted and in those cases the linespace is taken to be twice the glyph width.
219
*       FIXME: This needs fixing when font support is added and the ascending and descending values for the font should be used instead of the glyphs width.
220
*       TODO: support LineType::STYLESUBSCRIPT
221
*       TODO: support NEWLINE new paragraphs with multiple linespaces and indented x insteads of just ignoring the relative x position
222
*       TODO: I don't know if the invariant qDebug cases should always report an error or only do so when DEBUG_TEXT_IMPORT is defined. My feeling is they should always report because it meanms something has happened that shouldn't have and it's useful feedback.
223
*/
224
PdfTextRegion::LineType PdfTextRegion::linearTest(QPointF point, bool xInLimits, bool yInLimits)
225
{
226
        if (collinear(point.y(), lastXY.y()))
227
        {
228
                if (collinear(point.x(), lastXY.x()))
229
                        return LineType::FIRSTPOINT;
230
                else if (xInLimits)
231
                        return LineType::SAMELINE;
232
#ifdef DEBUG_TEXT_IMPORT
233
                else
234
                        qDebug() << "FIRSTPOINT/SAMELINE oops:" << "point:" << point << " pdfTextRegioBasenOrigin:" << pdfTextRegionBasenOrigin << " baseline:" << this->lineBaseXY << " lastXY:" << lastXY << " linespacing:" << lineSpacing << " pdfTextRegionLines.size:" << pdfTextRegionLines.size();
235
#endif
236
        }
237
        else if (adjunctLesser(point.y(), lastXY.y(), lineBaseXY.y()))
238
                return LineType::STYLESUPERSCRIPT;
239
        else if (adjunctGreater(point.y(), lastXY.y(), lineBaseXY.y()))
240
        {
241
                if (collinear(point.y(), lineBaseXY.y()))
242
                        return LineType::STYLENORMALRETURN;
243
                else
244
                        return LineType::STYLESUPERSCRIPT;
245
        }
246
        else if (isCloseToX(point.x(), pdfTextRegionBasenOrigin.x()))
247
        {
24382 jghali 248
                if (isCloseToY(lastXY.y(), point.y()) && !collinear(point.y(), lastXY.y()))
24374 jghali 249
                {
250
                        if (pdfTextRegionLines.size() >= 2)
251
                                return LineType::NEWLINE;
252
                        else if (pdfTextRegionLines.size() == 1)
253
                                return LineType::NEWLINE;
254
#ifdef DEBUG_TEXT_IMPORT
255
                        else
256
                                qDebug() << "NEWLINE oops2:" << "point:" << point << " pdfTextRegionBasenOrigin:" << pdfTextRegionBasenOrigin << " baseline:" << this->lineBaseXY << " lastXY:" << lastXY << " linespacing:" << lineSpacing << " pdfTextRegionLines.size:" << pdfTextRegionLines.size();
257
#endif
258
                }
259
#ifdef DEBUG_TEXT_IMPORT
260
                else
261
                        qDebug() << "NEWLINE oops:" << "point:" << point << " pdfTextRegioBasenOrigin:" << pdfTextRegionBasenOrigin << " baseline:" << this->lineBaseXY << " lastXY:" << lastXY << " linespacing:" << lineSpacing << " textPdfRegionLines.size:" << pdfTextRegionLines.size();
262
#endif
263
        }
264
#ifdef DEBUG_TEXT_IMPORT //This isn't an invariant case like the others, we actually expect this to happen some of the time
265
        qDebug() << "FAILED with oops:" << "point:" << point << " pdfTextRegioBasenOrigin:" << pdfTextRegionBasenOrigin << " baseline:" << this->lineBaseXY << " lastXY:" << lastXY << " linespacing:" << lineSpacing << " textPdfRegionLines.size:" << pdfTextRegionLines.size();
266
#endif
267
        return LineType::FAIL;
268
}
269
 
270
/*
271
*       Perform some fuzzy checks to see if newPoint can reasonably be ascribed to the current textframe.
272
*       FIXME: It may be that move and addGlyph need different versions of isCloseToX and isCloseToY but keep them the same just for now
273
*/
274
PdfTextRegion::LineType PdfTextRegion::isRegionConcurrent(QPointF newPoint)
275
{
276
        if (glyphs.empty())
277
        {
278
                lineBaseXY = newPoint;
279
                lastXY = newPoint;
280
        }
281
 
282
        bool xInLimits = isCloseToX(newPoint.x(), lastXY.x());
24382 jghali 283
        bool yInLimits = isCloseToY(lastXY.y(), newPoint.y());
24374 jghali 284
        LineType pass = linearTest(newPoint, xInLimits, yInLimits);
285
        return pass;
286
}
287
 
288
/*
289
*       Move the position of the cursor to a new point,
290
*       test if that point is within the current textframe or within a new textframe.
291
*       initialize the textregion and setup lines and segments
24665 jghali 292
*       TODO: iscloseto x and y may need to be different from addGlyph but use the common isRegionbConcurrent for now
24374 jghali 293
*               need to check to see if we are creating a new paragraph or not.
294
*               basically if the cursor is returned to x origin before it reached x width.
295
*               Also needs to have support for rotated text, but I expect I'll add this by removing the text rotation
296
*               from calls to movepoint and addGlyph and instead rotating the whole text region as a block
297
*/
298
PdfTextRegion::LineType PdfTextRegion::moveToPoint(QPointF newPoint)
299
{
300
        //qDebug() << "moveToPoint: " << newPoint;
301
 
302
        if (glyphs.empty())
303
        {
304
                lineBaseXY = newPoint;
305
                lastXY = newPoint;
306
        }
307
        LineType mode = isRegionConcurrent(newPoint);
308
        if (mode == LineType::FAIL)
309
                return mode;
310
 
311
        PdfTextRegionLine* pdfTextRegionLine = nullptr;
312
        if (mode == LineType::NEWLINE || mode == LineType::FIRSTPOINT)
313
        {
314
                if (mode != LineType::FIRSTPOINT || pdfTextRegionLines.empty())
315
                        pdfTextRegionLines.push_back(PdfTextRegionLine());
316
 
317
                pdfTextRegionLine = &pdfTextRegionLines.back();
318
                pdfTextRegionLine->baseOrigin = newPoint;
319
                if (mode == LineType::NEWLINE)
320
                {
321
                        pdfTextRegionLine->maxHeight = abs(newPoint.y() - lastXY.y());
322
                        if (pdfTextRegionLines.size() == 2)
323
                                lineSpacing = abs(newPoint.y() - lastXY.y()) + 1;
324
                }
325
        }
326
 
327
        pdfTextRegionLine = &pdfTextRegionLines.back();
328
        if ((mode == LineType::FIRSTPOINT && pdfTextRegionLine->segments.empty()) || mode == LineType::NEWLINE
329
                || mode != LineType::FIRSTPOINT && pdfTextRegionLine->segments[0].glyphIndex != pdfTextRegionLine->glyphIndex)
330
        {
331
                PdfTextRegionLine newSegment = PdfTextRegionLine();
332
                pdfTextRegionLine->segments.push_back(newSegment);
333
        }
334
        PdfTextRegionLine* segment = &pdfTextRegionLine->segments.back();
335
        segment->baseOrigin = newPoint;
336
        segment->maxHeight = (mode == LineType::STYLESUPERSCRIPT) ?
337
                abs(lineSpacing - (newPoint.y() - lastXY.y())) :
338
                pdfTextRegionLines.back().maxHeight;
339
 
340
        if (mode != LineType::NEWLINE && mode != LineType::FIRSTPOINT)
341
        {
342
                pdfTextRegionLines.back().segments.back().width = abs(pdfTextRegionLines.back().segments.back().baseOrigin.x() - newPoint.x());
343
                pdfTextRegionLine = &pdfTextRegionLines.back();
344
                pdfTextRegionLine->width = abs(pdfTextRegionLine->baseOrigin.x() - newPoint.x());
345
        }
346
 
347
        maxHeight = abs(pdfTextRegionBasenOrigin.y() - newPoint.y()) > maxHeight ? abs(pdfTextRegionBasenOrigin.y() - newPoint.y()) : maxHeight;
348
        lastXY = newPoint;
349
 
350
        return mode;
351
}
352
 
353
/*
354
*       Add a new glyph to the current line segment, lines and segments should already have been setup by the
355
*       moveto function which should generally be called prior to addGlyph to setup the lines and segments correctly.
24665 jghali 356
*       does some basic calculations to determine and save widths and heights and linespacings of texts etc...
357
*       FIXME: these need to be changed to use the mode average of all glyps added to the text frame instead of just picking the first ones we come across
24374 jghali 358
*               the mode average can also be used to determine the base font style when fonts are added
359
*               left and right hand margins however need to use the maximum and minimum, support for right hand justification
360
*               and centered text needs to be added as we only support left and fully justified at the moment.
361
*               Approximated heights and widths and linespaces need to use the correct font data when font support has been added,
362
*               but for now just use the x advance value. using font data should also allow for the support of rotated text that may use a mixture of x and y advance
363
*/
364
PdfTextRegion::LineType PdfTextRegion::addGlyphAtPoint(QPointF newGlyphPoint, PdfGlyph newGlyph)
365
{
366
        QPointF movedGlyphPoint = QPointF(newGlyphPoint.x() + newGlyph.dx, newGlyphPoint.y() + newGlyph.dy);
367
        if (glyphs.size() == 1)
368
        {
369
                lineSpacing = newGlyph.dx * 3;
370
                lastXY = newGlyphPoint;
371
                lineBaseXY = newGlyphPoint;
372
        }
373
 
374
        LineType mode = isRegionConcurrent(newGlyphPoint);
375
        if (mode == LineType::FAIL)
376
                return mode;
377
 
378
        maxHeight = abs(pdfTextRegionBasenOrigin.y() - movedGlyphPoint.y()) + lineSpacing > maxHeight ? abs(pdfTextRegionBasenOrigin.y() - movedGlyphPoint.y()) + lineSpacing : maxHeight;
379
 
380
        PdfTextRegionLine* pdfTextRegionLine = &pdfTextRegionLines.back();
381
        if (mode == LineType::NEWLINE || mode == LineType::FIRSTPOINT)
382
        {
383
                pdfTextRegionLine->glyphIndex = glyphs.size() - 1;
384
                pdfTextRegionLine->baseOrigin = QPointF(pdfTextRegionBasenOrigin.x(), newGlyphPoint.y());
385
        }
386
 
387
        PdfTextRegionLine* segment = &pdfTextRegionLine->segments.back();
388
        segment->width = abs(movedGlyphPoint.x() - segment->baseOrigin.x());
389
        segment->glyphIndex = glyphs.size() - 1;
390
        qreal thisHeight = pdfTextRegionLines.size() > 1 ?
391
                abs(newGlyphPoint.y() - pdfTextRegionLines[pdfTextRegionLines.size() - 2].baseOrigin.y()) :
392
                newGlyph.dx;
393
 
394
        segment->maxHeight = thisHeight > segment->maxHeight ? thisHeight : segment->maxHeight;
395
        pdfTextRegionLine->maxHeight = pdfTextRegionLine->maxHeight > thisHeight ? pdfTextRegionLine->maxHeight : thisHeight;
396
        pdfTextRegionLine->width = abs(movedGlyphPoint.x() - pdfTextRegionLine->baseOrigin.x());
397
 
398
        maxWidth = pdfTextRegionLine->width > maxWidth ? pdfTextRegionLine->width : maxWidth;
399
        if (pdfTextRegionLine->segments.size() == 1)
400
                lineBaseXY = pdfTextRegionLine->baseOrigin;
401
 
402
        lastXY = movedGlyphPoint;
403
 
404
        return mode;
405
}
406
 
407
/*
408
*       Render the text region to the frame,
409
*       nothing clever for now, just apply the whole block of text to the textNode
410
*       TODO: Add support for fonts and styles based on line segments
411
*       add support for rotated text
412
*/
413
void PdfTextRegion::renderToTextFrame(PageItem* textNode)
414
{
415
        textNode->setWidthHeight(this->maxWidth, this->maxHeight);
416
        QString bodyText = "";
417
        for (int glyphIndex = this->pdfTextRegionLines.begin()->glyphIndex; glyphIndex <= this->pdfTextRegionLines.back().segments.back().glyphIndex; glyphIndex++)
418
                bodyText += glyphs[glyphIndex].code;
419
 
420
        textNode->itemText.insertChars(bodyText);
421
        textNode->frameTextEnd();
422
}
423
 
424
/*
425
*       Quick test to see if this is a virgin textregion
426
*/
427
bool PdfTextRegion::isNew()
428
{
429
        return pdfTextRegionLines.empty() ||
430
                glyphs.empty();
431
}
432
 
433
 
434
PdfTextOutputDev::PdfTextOutputDev(ScribusDoc* doc, QList<PageItem*>* Elements, QStringList* importedColors, int flags) : SlaOutputDev(doc, Elements, importedColors, flags)
435
{
436
        // Nothing to do at the moment
437
}
438
 
439
PdfTextOutputDev::~PdfTextOutputDev()
440
{
441
        // Nothing to do at the moment
442
}
443
 
444
/*
445
 *      Updates current text position and move to a position and or add a new glyph at the previous position.
446
 *      NOTE: The success == TextRegion::LineType::FAIL test is an invariant test that should never pass. if a rogue glyph is detected then it means there is a bug in the logic probably in TextRegion::addGlyphAtPoint or TextRegion::linearTest or TextRegion::moveToPoint
447
 *      FIXME: render the textframe, this should be done after the document has finished loading the current page so all the layout fix-ups can be put in-place first
448
 *      FIXME: textRegion needs to support moveBackOneGlyph instead of my manual implementation in this function.
449
 */
450
void PdfTextOutputDev::updateTextPos(GfxState* state)
451
{
24382 jghali 452
        const double * ctm = state->getCTM();
453
        QTransform trans(ctm[0], ctm[1], ctm[2], ctm[3], ctm[4], ctm[5]);
454
 
455
        QPointF newPosition = trans.map(QPointF(state->getCurX(), state->getCurY()));
24376 jghali 456
        PdfTextRegion *activePdfTextRegion = m_pdfTextRecognition.activePdfTextRegion;
24374 jghali 457
 
24376 jghali 458
        if (activePdfTextRegion->isNew())
24374 jghali 459
        {
460
                activePdfTextRegion->pdfTextRegionBasenOrigin = newPosition;
461
                m_pdfTextRecognition.setCharMode(PdfTextRecognition::AddCharMode::ADDFIRSTCHAR);
462
        }
463
        else
464
        {
465
                // if we've will move to a new line or new text region then update the current text region with the last glyph, this ensures all textlines and textregions have terminating glyphs.
466
                if (m_pdfTextRecognition.isNewLineOrRegion(newPosition))
467
                {
468
                        QPointF glyphPosition = activePdfTextRegion->lastXY;
469
                        activePdfTextRegion->lastXY.setX(activePdfTextRegion->lastXY.x() - activePdfTextRegion->glyphs.back().dx);
470
                        if (activePdfTextRegion->addGlyphAtPoint(glyphPosition, activePdfTextRegion->glyphs.back()) == PdfTextRegion::LineType::FAIL)
471
                                qDebug("FIXME: Rogue glyph detected, this should never happen because the cursor should move before glyphs in new regions are added.");
472
#ifdef DEBUG_TEXT_IMPORT
473
                        else
474
                                qDebug() << "Newline should be next";
475
#endif
476
                }
477
        }
478
        PdfTextRegion::LineType linePdfTestResult = activePdfTextRegion->moveToPoint(newPosition);
479
        if (linePdfTestResult == PdfTextRegion::LineType::FAIL)
480
        {
481
#ifdef DEBUG_TEXT_IMPORT
482
                qDebug("updateTextPos: renderPdfTextFrame() + m_pdfTextRecognition.addPdfTextRegion()");
483
#endif
484
                renderTextFrame();
485
                m_pdfTextRecognition.addPdfTextRegion();
486
                updateTextPos(state);
487
        }
488
}
489
/*
490
*       render the textregion to a new PageItem::TextFrame, currently some hackjish defaults have been implemented there are a number of FIXMEs and TODOs
491
*       FIXME: Paragraphs need to be implemented properly  this needs to be applied to the charstyle of the default pstyle
492
*       FIXME xcord and ycord need to be set properly based on GfxState and the page transformation matrix
493
*       TODO: Implement paragraph styles
494
*       TODO: Implement character styles and fonts.
495
*       TODO Decide if we should be setting the clipshape of the POoLine values as is the case with other import implementations
496
*/
497
void PdfTextOutputDev::renderTextFrame()
498
{
499
        //qDebug() << "_flushText()    m_doc->currentPage()->xOffset():" << m_doc->currentPage()->xOffset();
24376 jghali 500
        auto activePdfTextRegion = m_pdfTextRecognition.activePdfTextRegion;
24374 jghali 501
        if (activePdfTextRegion->glyphs.empty())
502
                return;
503
 
504
        qreal xCoor = m_doc->currentPage()->xOffset() + activePdfTextRegion->pdfTextRegionBasenOrigin.x();
24382 jghali 505
        qreal yCoor = m_doc->currentPage()->yOffset() + activePdfTextRegion->pdfTextRegionBasenOrigin.y() - activePdfTextRegion->lineSpacing; // don't know if y is top down or bottom up
24374 jghali 506
#ifdef DEBUG_TEXT_IMPORT
507
        qDebug() << "rendering new frame at:" << xCoor << "," << yCoor << " With lineheight of: " << activePdfTextRegion->lineSpacing << "Height:" << activePdfTextRegion->maxHeight << " Width:" << activePdfTextRegion->maxWidth;
508
#endif
509
        int z = m_doc->itemAdd(PageItem::TextFrame, PageItem::Rectangle, xCoor, yCoor, 40, 40, 0, CommonStrings::None, CommonStrings::None);
510
        PageItem* textNode = m_doc->Items->at(z);
511
 
24382 jghali 512
        ParagraphStyle pStyle;
24374 jghali 513
        pStyle.setLineSpacingMode(pStyle.AutomaticLineSpacing);
514
        pStyle.setHyphenationMode(pStyle.AutomaticHyphenation);
24383 jghali 515
 
516
        CharStyle& cStyle = pStyle.charStyle();
517
        cStyle.setScaleH(1000.0);
518
        cStyle.setScaleV(1000.0);
519
        cStyle.setHyphenChar(SpecialChars::BLANK.unicode());
520
 
24652 jghali 521
        // #16567: Override user prefs
522
        textNode->setColumns(1);
523
 
24382 jghali 524
        Selection tempSelection(nullptr, false);
525
        tempSelection.addItem(textNode);
526
        m_doc->itemSelection_ApplyParagraphStyle(pStyle, &tempSelection);
24374 jghali 527
        finishItem(textNode);
24382 jghali 528
 
24374 jghali 529
        //_setFillAndStrokeForPdf(state, text_node);
530
        textNode->ClipEdited = true;
531
        textNode->FrameType = 3;
532
        textNode->setLineEnd(m_lineEnd);
533
        textNode->setLineJoin(m_lineJoin);
534
        textNode->setTextFlowMode(PageItem::TextFlowDisabled);
535
        textNode->setLineTransparency(1.0);
536
        textNode->setFillColor(CommonStrings::None);
537
        textNode->setLineColor(CommonStrings::None);
24654 jghali 538
        textNode->setLineWidth(0.0);
24374 jghali 539
        textNode->setFillShade(m_currFillShade);
540
 
541
 
542
        /* Oliver Stieber 2020-06-11 Set text matrix... This need to be done so that the global world view that we rite out glyphs to is transformed correctly by the context matrix for each glyph, possibly anyhow.
543
        needs the way in which we are handling transformations for the page to be more concrete before this code can be implemented either here or somewhere else
544
        FIXME: Setting the text matrix isn't supported at the moment
545
        QTransform text_transform(_text_matrix);
546
        text_transform.setMatrix(text_transform.m11(), text_transform.m12(), 0,
547
                text_transform.m21(), text_transform.m22(), 0,
548
                first_glyph.position.x(), first_glyph.position.y(), 1);
549
        gchar *transform = sp_svg_transform_write(text_transform);
550
        text_node->setAttribute("transform", transform);
551
        g_free(transform);
552
        */
553
 
554
        /*
555
        * This code sets the font and style in a very simplistic way, it's been commented out as it needs to be updated to be used within PdfTextRecognition &co.
24654 jghali 556
        int shade = 100;
24374 jghali 557
        QString CurrColorText = getColor(state->getFillColorSpace(), state->getFillColor(), &shade);
558
        applyTextStyleToCharStyle(pStyle.charStyle(), _glyphs[0].style->getFont().family(), CurrColorText, _glyphs[0].style->getFont().pointSizeF());// *_font_scaling);
559
        */
560
 
561
        textNode->itemText.setDefaultStyle(pStyle);
562
        textNode->invalid = true;
563
        activePdfTextRegion->renderToTextFrame(textNode);
564
        textNode->itemText.insertChars(SpecialChars::PARSEP, true);
565
 
566
        /*
567
        *       This code can be used to set PoLine instead of setting the FrameShape if setting the PoLine is the more correct way of doing things.
568
        *       I have no idea of what the PoLine is at this time except for it changes when the shape is set and appears to be unit scales as opposed to percentage scaled
569
        FPointArray boundingBoxShape;
570
        boundingBoxShape.resize(0);
571
        boundingBoxShape.svgInit();
572
        //doubles to create a shape, it's 100% textframe width by 100% textframe height
573
 
574
        boundingBoxShape.svgMoveTo(TextRegion::boundingBoxShape[0], TextRegion::boundingBoxShape[1]);
575
        for (int a = 0; a < 16; a += 2)
576
        {
577
                boundingBoxShape.append(FPoint(TextRegion::boundingBoxShape[a * 2], TextRegion::boundingBoxShape[a * 2 + 1]));
578
        }
579
        boundingBoxShape.scale(textNode->width() / 100.0, textNode->height() / 100.0);
580
        */
581
        textNode->SetFrameShape(32, PdfTextRegion::boundingBoxShape);
582
        textNode->ContourLine = textNode->PoLine.copy();
583
 
584
        m_Elements->append(textNode);
585
        if (m_groupStack.count() != 0)
586
        {
587
                m_groupStack.top().Items.append(textNode);
588
                applyMask(textNode);
589
        }
590
}
591
 
592
/*
593
*       code mostly taken from importodg.cpp which also supports some line styles and more fill options etc...
594
*/
595
void PdfTextOutputDev::finishItem(PageItem* item)
596
{
597
        item->ClipEdited = true;
598
        item->FrameType = 3;
599
        /*code can be enabled when PoLine is set or when the shape is set as that sets PoLine
600
        FPoint wh = getMaxClipF(&item->PoLine);
601
        item->setWidthHeight(wh.x(), wh.y());
602
        item->Clip = flattenPath(item->PoLine, item->Segments);
603
        */
604
        item->OldB2 = item->width();
605
        item->OldH2 = item->height();
606
        item->updateClip();
607
        item->OwnPage = m_doc->OnPage(item);
608
}
609
 
610
void PdfTextOutputDev::drawChar(GfxState* state, double x, double y, double dx, double dy, double originX, double originY, CharCode code, int nBytes, POPPLER_CONST_082 Unicode* u, int uLen)
611
{
612
        // TODO Implement the clipping operations. At least the characters are shown.
613
        int textRenderingMode = state->getRender();
614
        // Invisible or only used for clipping
615
        if (textRenderingMode == 3)
616
                return;
617
        if (textRenderingMode < 8)
618
        {
619
                m_pdfTextRecognition.addChar(state, x, y, dx, dy, originX, originY, code, nBytes, u, uLen);
620
        }
621
}
622
 
623
void PdfTextOutputDev::beginTextObject(GfxState* state)
624
{
625
        pushGroup();
24376 jghali 626
        if (!m_pdfTextRecognition.activePdfTextRegion->pdfTextRegionLines.empty())
24374 jghali 627
        {
628
#ifdef DEBUG_TEXT_IMPORT
629
                qDebug("beginTextObject: m_textRecognition.addTextRegion()");
630
#endif
631
                m_pdfTextRecognition.addPdfTextRegion();
632
        }
633
}
634
 
635
void PdfTextOutputDev::endTextObject(GfxState* state)
636
{
24737 jghali 637
        auto* activePdfTextRegion = m_pdfTextRecognition.activePdfTextRegion;
638
        if (!activePdfTextRegion->pdfTextRegionLines.empty() && activePdfTextRegion->glyphs.size() > 0)
24374 jghali 639
        {
640
                // Add the last glyph to the textregion
24737 jghali 641
                QPointF glyphXY = activePdfTextRegion->lastXY;
642
                activePdfTextRegion->lastXY.setX(activePdfTextRegion->lastXY.x() - activePdfTextRegion->glyphs.back().dx);
643
                if (activePdfTextRegion->addGlyphAtPoint(glyphXY, activePdfTextRegion->glyphs.back()) == PdfTextRegion::LineType::FAIL)
24374 jghali 644
                {
645
                        qDebug("FIXME: Rogue glyph detected, this should never happen because the cursor should move before glyphs in new regions are added.");
646
                }
647
#ifdef DEBUG_TEXT_IMPORT
648
                qDebug("endTextObject: renderTextFrame");
649
#endif
650
                renderTextFrame();
651
        }
24737 jghali 652
        else if (!activePdfTextRegion->pdfTextRegionLines.empty())
24374 jghali 653
                qDebug("FIXME:Rogue textblock");
654
 
655
        m_pdfTextRecognition.setCharMode(PdfTextRecognition::AddCharMode::ADDFIRSTCHAR);
656
 
657
        SlaOutputDev::endTextObject(state);
658
}
659
 
660
/*
661
* update the font for the next block of glyphs.
662
* just a stub for now
663
*/
664
void PdfTextOutputDev::updateFont(GfxState* state)
665
{
666
 
667
}
668
/*
669
* NOTE: Override these for now and do nothing so they don't get picked up and rendered as vectors by the base class,
670
        though in the long run we may actually want that unless they can be implemented in a similar way to the text import getChar in which case overloading the makes perfect sense.
671
*/
672
GBool PdfTextOutputDev::beginType3Char(GfxState* state, double x, double y, double dx, double dy, CharCode code, POPPLER_CONST_082 Unicode* u, int uLen)
673
{
674
        //stub
675
        return gTrue;
676
}
677
void  PdfTextOutputDev::endType3Char(GfxState* state)
678
{
679
        //stub
680
}
681
void  PdfTextOutputDev::type3D0(GfxState* state, double wx, double wy)
682
{
683
        //stub
684
}
685
void  PdfTextOutputDev::type3D1(GfxState* state, double wx, double wy, double ll, double lly, double urx, double ury)
686
{
687
        //stub
688
}