~ubuntu-branches/ubuntu/precise/okular/precise-proposed

« back to all changes in this revision

Viewing changes to core/textpage.cpp

  • Committer: Package Import Robot
  • Author(s): Philip Muškovac
  • Date: 2011-12-23 22:53:33 UTC
  • mto: This revision was merged to the branch mainline in revision 13.
  • Revision ID: package-import@ubuntu.com-20111223225333-kkxbdqe29pr2glh5
Tags: upstream-4.7.95
Import upstream version 4.7.95

Show diffs side-by-side

added added

removed removed

Lines of Context:
39
39
        int offset_end;
40
40
};
41
41
 
42
 
static int qHash(const QRect &r)
43
 
{
44
 
    return r.left() * r.top() + r.right() * r.bottom();
45
 
}
46
 
 
47
42
/* text comparison functions */
48
43
 
49
44
bool CaseInsensitiveCmpFn( const QStringRef & from, const QStringRef & to,
213
208
    delete area;
214
209
}
215
210
 
 
211
struct WordWithCharacters
 
212
{
 
213
    WordWithCharacters(TinyTextEntity *w, const TextList &c)
 
214
     : word(w), characters(c)
 
215
    {
 
216
    }
 
217
    
 
218
    inline QString text() const
 
219
    {
 
220
        return word->text();
 
221
    }
 
222
    
 
223
    inline const NormalizedRect &area() const
 
224
    {
 
225
      return word->area;
 
226
    }
 
227
    
 
228
    TinyTextEntity *word;
 
229
    TextList characters;
 
230
};
 
231
typedef QList<WordWithCharacters> WordsWithCharacters;
 
232
 
216
233
/**
217
234
 * We will divide the whole page in some regions depending on the horizontal and
218
235
 * vertical spacing among different regions. Each region will have an area and an
219
 
 * associated TextList in sorted order.
 
236
 * associated WordsWithCharacters in sorted order.
220
237
*/
221
238
class RegionText
222
239
{
226
243
    {
227
244
    };
228
245
 
229
 
    RegionText(const TextList &list,const QRect &area)
230
 
        : m_region_text(list) ,m_area(area)
 
246
    RegionText(const WordsWithCharacters &wordsWithCharacters, const QRect &area)
 
247
        : m_region_wordWithCharacters(wordsWithCharacters), m_area(area)
231
248
    {
232
249
    }
233
250
    
234
251
    inline QString string() const
235
252
    {
236
253
        QString res;
237
 
        foreach(TinyTextEntity *te, m_region_text)
238
 
            res += te->text();
 
254
        foreach(const WordWithCharacters &word, m_region_wordWithCharacters)
 
255
            res += word.text();
239
256
        return res;
240
257
    }
241
258
 
242
 
    inline TextList text() const
 
259
    inline WordsWithCharacters text() const
243
260
    {
244
 
        return m_region_text;
 
261
        return m_region_wordWithCharacters;
245
262
    }
246
263
 
247
264
    inline QRect area() const
254
271
        m_area = area;
255
272
    }
256
273
 
257
 
    inline void setText(const TextList &text)
 
274
    inline void setText(const WordsWithCharacters &wordsWithCharacters)
258
275
    {
259
 
        m_region_text = text;
 
276
        m_region_wordWithCharacters = wordsWithCharacters;
260
277
    }
261
278
 
262
279
private:
263
 
    TextList m_region_text;
 
280
    WordsWithCharacters m_region_wordWithCharacters;
264
281
    QRect m_area;
265
282
};
266
283
 
992
1009
    return ret;
993
1010
}
994
1011
 
995
 
static bool compareTinyTextEntityX(TinyTextEntity* first, TinyTextEntity* second)
 
1012
static bool compareTinyTextEntityX(const WordWithCharacters &first, const WordWithCharacters &second)
996
1013
{
997
 
    QRect firstArea = first->area.roundedGeometry(1000,1000);
998
 
    QRect secondArea = second->area.roundedGeometry(1000,1000);
 
1014
    QRect firstArea = first.area().roundedGeometry(1000,1000);
 
1015
    QRect secondArea = second.area().roundedGeometry(1000,1000);
999
1016
 
1000
1017
    return firstArea.left() < secondArea.left();
1001
1018
}
1002
1019
 
1003
 
static bool compareTinyTextEntityY(TinyTextEntity* first, TinyTextEntity* second)
 
1020
static bool compareTinyTextEntityY(const WordWithCharacters &first, const WordWithCharacters &second)
1004
1021
{
1005
 
    QRect firstArea = first->area.roundedGeometry(1000,1000);
1006
 
    QRect secondArea = second->area.roundedGeometry(1000,1000);
 
1022
    const QRect firstArea = first.area().roundedGeometry(1000,1000);
 
1023
    const QRect secondArea = second.area().roundedGeometry(1000,1000);
1007
1024
 
1008
1025
    return firstArea.top() < secondArea.top();
1009
1026
}
1010
1027
 
1011
1028
/**
1012
 
 * Copies a TextList to m_words with the same pointer
 
1029
 * Sets a new world list. Deleting the contents of the old one
1013
1030
 */
1014
1031
void TextPagePrivate::setWordList(const TextList &list)
1015
1032
{
1018
1035
}
1019
1036
 
1020
1037
/**
1021
 
 * Copies from m_words to list with distinct pointers
1022
 
 */
1023
 
TextList TextPagePrivate::duplicateWordList() const
1024
 
{
1025
 
    TextList list;
1026
 
    for(int i = 0 ; i < m_words.length() ; i++)
1027
 
    {
1028
 
        TinyTextEntity* ent = m_words.at(i);
1029
 
        list.append( new TinyTextEntity( ent->text(),ent->area ) );
1030
 
    }
1031
 
    return list;
1032
 
}
1033
 
 
1034
 
/**
1035
1038
 * If the horizontal arm of one rectangle fully contains the other (example below)
1036
1039
 *  --------         ----         -----  first
1037
1040
 *    ----         --------       -----  second
1093
1096
 * Remove all the spaces in between texts. It will make all the generators
1094
1097
 * same, whether they save spaces(like pdf) or not(like djvu).
1095
1098
 */
1096
 
void TextPagePrivate::removeSpace()
 
1099
static void removeSpace(TextList *words)
1097
1100
{
1098
 
    TextList::Iterator it = m_words.begin(), itEnd = m_words.end();
 
1101
    TextList::Iterator it = words->begin(), itEnd = words->end();
1099
1102
    const QString str(' ');
1100
1103
 
1101
 
    it = m_words.begin(), itEnd = m_words.end();
1102
1104
    while ( it != itEnd )
1103
1105
    {
1104
1106
        if((*it)->text() == str)
1105
1107
        {
1106
 
            delete *it;
1107
 
            it = m_words.erase(it);
 
1108
            it = words->erase(it);
1108
1109
        }
1109
1110
        else
1110
1111
        {
1114
1115
}
1115
1116
 
1116
1117
/**
1117
 
 * We will the TinyTextEntity from m_words and try to create
1118
 
 * words from there.
 
1118
 * We will read the TinyTextEntity from characters and try to create words from there.
 
1119
 * Note: characters might be already characters for some generators, but we will keep
 
1120
 * the nomenclature characters for the generator produced data. The resulting
 
1121
 * WordsWithCharacters memory has to be managed by the caller, both the 
 
1122
 * WordWithCharacters::word and WordWithCharacters::characters contents
1119
1123
 */
1120
 
QHash<QRect, RegionText> TextPagePrivate::makeWordFromCharacters()
 
1124
static WordsWithCharacters makeWordFromCharacters(const TextList &characters, int pageWidth, int pageHeight)
1121
1125
{
1122
1126
    /**
1123
 
     * At first we will copy m_words to tmpList. Then, we will traverse the
1124
 
     * tmpList and try to create words from the TinyTextEntities in tmpList.
 
1127
     * We will traverse characters and try to create words from the TinyTextEntities in it.
1125
1128
     * We will search TinyTextEntity blocks and merge them until we get a
1126
1129
     * space between two consecutive TinyTextEntities. When we get a space
1127
1130
     * we can take it as a end of word. Then we store the word as a TinyTextEntity
1128
1131
     * and keep it in newList.
1129
1132
 
1130
 
     * We also keep a mapping between every element in newList and word. We create a
1131
 
     * RegionText named regionWord and create a hash key from the TinyTextEntity
1132
 
     * rectangle area of the element in newList. So, we can get the TinyTextEntities from
1133
 
     * which every element(word) of newList is generated. It will be necessary later
1134
 
     * when we will divide the word into characters.
 
1133
     * We create a RegionText named regionWord that contains the word and the characters associated with it and
 
1134
     * a rectangle area of the element in newList. 
1135
1135
 
1136
 
     * Finally we copy the newList to m_words.
1137
1136
     */
1138
 
 
1139
 
    QHash<QRect, RegionText> word_chars_map;
1140
 
    const TextList tmpList = m_words;
1141
 
    TextList newList;
1142
 
 
1143
 
    TextList::ConstIterator it = tmpList.begin(), itEnd = tmpList.end(), tmpIt;
 
1137
    WordsWithCharacters wordsWithCharacters;
 
1138
 
 
1139
    TextList::ConstIterator it = characters.begin(), itEnd = characters.end(), tmpIt;
1144
1140
    int newLeft,newRight,newTop,newBottom;
1145
 
    const int pageWidth = m_page->m_page->width();
1146
 
    const int pageHeight = m_page->m_page->height();
1147
1141
    int index = 0;
1148
1142
 
1149
1143
    for( ; it != itEnd ; it++)
1151
1145
        QString textString = (*it)->text();
1152
1146
        QString newString;
1153
1147
        QRect lineArea = (*it)->area.roundedGeometry(pageWidth,pageHeight),elementArea;
1154
 
        TextList word;
 
1148
        TextList wordCharacters;
1155
1149
        tmpIt = it;
1156
1150
        int space = 0;
1157
1151
 
1158
 
        while(!space )
 
1152
        while (!space)
1159
1153
        {
1160
 
            if(textString.length())
 
1154
            if (textString.length())
1161
1155
            {
1162
1156
                newString.append(textString);
1163
1157
 
1164
1158
                // when textString is the start of the word
1165
 
                if(tmpIt == it)
 
1159
                if (tmpIt == it)
1166
1160
                {
1167
1161
                    NormalizedRect newRect(lineArea,pageWidth,pageHeight);
1168
 
                    word.append(new TinyTextEntity(textString.normalized
 
1162
                    wordCharacters.append(new TinyTextEntity(textString.normalized
1169
1163
                                                   (QString::NormalizationForm_KC), newRect));
1170
1164
                }
1171
1165
                else
1172
1166
                {
1173
1167
                    NormalizedRect newRect(elementArea,pageWidth,pageHeight);
1174
 
                    word.append(new TinyTextEntity(textString.normalized
 
1168
                    wordCharacters.append(new TinyTextEntity(textString.normalized
1175
1169
                                                   (QString::NormalizationForm_KC), newRect));
1176
1170
                }
1177
1171
            }
1178
1172
 
1179
 
            it++;
 
1173
            ++it;
1180
1174
 
1181
1175
            /*
1182
1176
             we must have to put this line before the if condition of it==itEnd
1183
1177
             otherwise the last character can be missed
1184
1178
             */
1185
 
            if(it == itEnd) break;
 
1179
            if (it == itEnd) break;
1186
1180
            elementArea = (*it)->area.roundedGeometry(pageWidth,pageHeight);
1187
 
            if(!doesConsumeY(elementArea,lineArea,60))
 
1181
            if (!doesConsumeY(elementArea, lineArea, 60))
1188
1182
            {
1189
 
                it--;
 
1183
                --it;
1190
1184
                break;
1191
1185
            }
1192
1186
 
1200
1194
 
1201
1195
            space = elementArea.left() - lineArea.right();
1202
1196
 
1203
 
            if(space > 0 || space < 0)
 
1197
            if (space != 0)
1204
1198
            {
1205
1199
                it--;
1206
1200
                break;
1220
1214
        }
1221
1215
 
1222
1216
        // if newString is not empty, save it
1223
 
        if(newString.length())
 
1217
        if (!newString.isEmpty())
1224
1218
        {
1225
 
            const NormalizedRect newRect(lineArea,pageWidth,pageHeight);
1226
 
            newList.append(new TinyTextEntity(newString.normalized
1227
 
                                              (QString::NormalizationForm_KC), newRect ));
1228
 
            const QRect rect = newRect.geometry(pageWidth,pageHeight);
1229
 
            const RegionText regionWord(word,rect);
1230
 
 
1231
 
            // there may be more than one element in the same key
1232
 
            word_chars_map.insertMulti(rect,regionWord);
 
1219
            const NormalizedRect newRect(lineArea, pageWidth, pageHeight);
 
1220
            TinyTextEntity *word = new TinyTextEntity(newString.normalized(QString::NormalizationForm_KC), newRect);
 
1221
            wordsWithCharacters.append(WordWithCharacters(word, wordCharacters));
1233
1222
 
1234
1223
            index++;
1235
1224
        }
1236
1225
 
1237
1226
        if(it == itEnd) break;
1238
1227
    }
1239
 
 
1240
 
    setWordList(newList);
1241
 
 
1242
 
    return word_chars_map;
 
1228
    
 
1229
    return wordsWithCharacters;
1243
1230
}
1244
1231
 
1245
1232
/**
1246
1233
 * Create Lines from the words and sort them
1247
1234
 */
1248
 
void TextPagePrivate::makeAndSortLines(const TextList &wordsTmp, SortedTextList *lines, LineRect *line_rects)
 
1235
QList< QPair<WordsWithCharacters, QRect> > makeAndSortLines(const WordsWithCharacters &wordsTmp, int pageWidth, int pageHeight)
1249
1236
{
1250
1237
    /**
1251
1238
     * We cannot assume that the generator will give us texts in the right order.
1257
1244
     * 2. Create textline where there is y overlap between TinyTextEntity 's
1258
1245
     * 3. Within each line sort the TinyTextEntity 's by x0(left)
1259
1246
     */
 
1247
    
 
1248
    QList< QPair<WordsWithCharacters, QRect> > lines;
1260
1249
 
1261
1250
    /*
1262
1251
     Make a new copy of the TextList in the words, so that the wordsTmp and lines do
1263
1252
     not contain same pointers for all the TinyTextEntity.
1264
1253
     */
1265
 
    TextList words;
1266
 
    for(int i = 0 ; i < wordsTmp.length() ; i++)
1267
 
    {
1268
 
        TinyTextEntity* ent = wordsTmp.at(i);
1269
 
        words.append( new TinyTextEntity( ent->text(),ent->area ) );
1270
 
    }
 
1254
    QList<WordWithCharacters> words = wordsTmp;
1271
1255
 
1272
1256
    // Step 1
1273
1257
    qSort(words.begin(),words.end(),compareTinyTextEntityY);
1274
1258
 
1275
1259
    // Step 2
1276
 
    TextList::Iterator it = words.begin(), itEnd = words.end();
1277
 
    const int pageWidth = m_page->m_page->width();
1278
 
    const int pageHeight = m_page->m_page->height();
 
1260
    QList<WordWithCharacters>::Iterator it = words.begin(), itEnd = words.end();
1279
1261
 
1280
1262
    //for every non-space texts(characters/words) in the textList
1281
1263
    for( ; it != itEnd ; it++)
1282
1264
    {
1283
 
        const QRect elementArea = (*it)->area.roundedGeometry(pageWidth,pageHeight);
 
1265
        const QRect elementArea = (*it).area().roundedGeometry(pageWidth,pageHeight);
1284
1266
        bool found = false;
1285
1267
 
1286
 
        for( int i = 0 ; i < lines->length() ; i++)
 
1268
        for( int i = 0 ; i < lines.length() ; i++)
1287
1269
        {
1288
1270
            /* the line area which will be expanded
1289
1271
               line_rects is only necessary to preserve the topmin and bottommax of all
1290
1272
               the texts in the line, left and right is not necessary at all
1291
1273
            */
1292
 
            QRect &lineArea = (*line_rects)[i];
 
1274
            QRect &lineArea = lines[i].second;
1293
1275
            const int text_y1 = elementArea.top() ,
1294
1276
                      text_y2 = elementArea.top() + elementArea.height() ,
1295
1277
                      text_x1 = elementArea.left(),
1305
1287
             */
1306
1288
            if(doesConsumeY(elementArea,lineArea,70))
1307
1289
            {
1308
 
                TextList &line = (*lines)[i];
 
1290
                WordsWithCharacters &line = lines[i].first;
1309
1291
                line.append(*it);
1310
1292
 
1311
1293
                const int newLeft = line_x1 < text_x1 ? line_x1 : text_x1;
1325
1307
         */
1326
1308
        if(!found)
1327
1309
        {
1328
 
            TextList tmp;
 
1310
            WordsWithCharacters tmp;
1329
1311
            tmp.append((*it));
1330
 
            lines->append(tmp);
1331
 
            line_rects->append(elementArea);
 
1312
            lines.append(QPair<WordsWithCharacters, QRect>(tmp, elementArea));
1332
1313
        }
1333
1314
    }
1334
1315
 
1335
1316
    // Step 3
1336
 
    for(int i = 0 ; i < lines->length() ; i++)
 
1317
    for(int i = 0 ; i < lines.length() ; i++)
1337
1318
    {
1338
 
        TextList &list = (*lines)[i];
1339
 
        qSort(list.begin(),list.end(),compareTinyTextEntityX);
 
1319
        WordsWithCharacters &list = lines[i].first;
 
1320
        qSort(list.begin(), list.end(), compareTinyTextEntityX);
1340
1321
    }
 
1322
    
 
1323
    return lines;
1341
1324
}
1342
1325
 
1343
1326
/**
1344
1327
 * Calculate Statistical information from the lines we made previously
1345
1328
 */
1346
 
void TextPagePrivate::calculateStatisticalInformation(const SortedTextList &lines, const LineRect &line_rects, int *word_spacing, int *line_spacing, int *col_spacing)
 
1329
static void calculateStatisticalInformation(const QList<WordWithCharacters> &words, int pageWidth, int pageHeight, int *word_spacing, int *line_spacing, int *col_spacing)
1347
1330
{
1348
1331
    /**
1349
1332
     * For the region, defined by line_rects and lines
1351
1334
     * 2. Make character statistical analysis to differentiate between
1352
1335
     *   word spacing and column spacing.
1353
1336
     */
 
1337
    
 
1338
    /**
 
1339
     * Step 0
 
1340
     */
 
1341
    const QList< QPair<WordsWithCharacters, QRect> > sortedLines = makeAndSortLines(words, pageWidth, pageHeight);
1354
1342
 
1355
1343
    /**
1356
1344
     * Step 1
1357
1345
     */
1358
1346
    QMap<int,int> line_space_stat;
1359
 
    for(int i = 0 ; i < line_rects.length(); i++)
 
1347
    for(int i = 0 ; i < sortedLines.length(); i++)
1360
1348
    {
1361
 
        const QRect rectUpper = line_rects.at(i);
 
1349
        const QRect rectUpper = sortedLines.at(i).second;
1362
1350
 
1363
 
        if(i+1 == line_rects.length()) break;
1364
 
        const QRect rectLower = line_rects.at(i+1);
 
1351
        if(i+1 == sortedLines.length()) break;
 
1352
        const QRect rectLower = sortedLines.at(i+1).second;
1365
1353
 
1366
1354
        int linespace = rectLower.top() - (rectUpper.top() + rectUpper.height());
1367
1355
        if(linespace < 0) linespace =-linespace;
1393
1381
    QList< QList<QRect> > space_rects;
1394
1382
    QList<QRect> max_hor_space_rects;
1395
1383
 
1396
 
    int pageWidth = m_page->m_page->width(), pageHeight = m_page->m_page->height();
1397
 
 
1398
1384
    // Space in every line
1399
 
    for(int i = 0 ; i < lines.length() ; i++)
 
1385
    for(int i = 0 ; i < sortedLines.length() ; i++)
1400
1386
    {
1401
 
        TextList list = lines.at(i);
 
1387
        const WordsWithCharacters list = sortedLines.at(i).first;
1402
1388
        QList<QRect> line_space_rects;
1403
1389
        int maxSpace = 0, minSpace = pageWidth;
1404
1390
 
1405
1391
        // for every TinyTextEntity element in the line
1406
 
        TextList::Iterator it = list.begin(), itEnd = list.end();
 
1392
        WordsWithCharacters::ConstIterator it = list.begin(), itEnd = list.end();
1407
1393
        QRect max_area1,max_area2;
1408
1394
        QString before_max, after_max;
1409
1395
 
1410
1396
        // for every line
1411
1397
        for( ; it != itEnd ; it++ )
1412
1398
        {
1413
 
            const QRect area1 = (*it)->area.roundedGeometry(pageWidth,pageHeight);
 
1399
            const QRect area1 = (*it).area().roundedGeometry(pageWidth,pageHeight);
1414
1400
            if( it+1 == itEnd ) break;
1415
1401
 
1416
 
            const QRect area2 = (*(it+1))->area.roundedGeometry(pageWidth,pageHeight);
 
1402
            const QRect area2 = (*(it+1)).area().roundedGeometry(pageWidth,pageHeight);
1417
1403
            int space = area2.left() - area1.right();
1418
1404
 
1419
1405
            if(space > maxSpace)
1421
1407
                max_area1 = area1;
1422
1408
                max_area2 = area2;
1423
1409
                maxSpace = space;
1424
 
                before_max = (*it)->text();
1425
 
                after_max = (*(it+1))->text();
 
1410
                before_max = (*it).text();
 
1411
                after_max = (*(it+1)).text();
1426
1412
            }
1427
1413
 
1428
1414
            if(space < minSpace && space != 0) minSpace = space;
1505
1491
    *col_spacing = col_space_stat.key(*col_spacing);
1506
1492
 
1507
1493
    // if there is just one line in a region, there is no point in dividing it
1508
 
    if(lines.length() == 1)
 
1494
    if(sortedLines.length() == 1)
1509
1495
        *word_spacing = *col_spacing;
1510
1496
}
1511
1497
 
1512
1498
/**
1513
1499
 * Implements the XY Cut algorithm for textpage segmentation
 
1500
 * The resulting RegionTextList will contain RegionText whose WordsWithCharacters::word and
 
1501
 * WordsWithCharacters::characters are reused from wordsWithCharacters (i.e. no new nor delete happens in this function)
1514
1502
 */
1515
 
RegionTextList TextPagePrivate::XYCutForBoundingBoxes(int tcx, int tcy)
 
1503
static RegionTextList XYCutForBoundingBoxes(const QList<WordWithCharacters> &wordsWithCharacters, const NormalizedRect &boundingBox, int pageWidth, int pageHeight)
1516
1504
{
1517
 
    const int pageWidth = m_page->m_page->width();
1518
 
    const int pageHeight = m_page->m_page->height();
1519
1505
    RegionTextList tree;
1520
 
    QRect contentRect(m_page->m_page->boundingBox().geometry(pageWidth,pageHeight));
1521
 
    const TextList words = duplicateWordList();
1522
 
    const RegionText root(words,contentRect);
 
1506
    QRect contentRect(boundingBox.geometry(pageWidth,pageHeight));
 
1507
    const RegionText root(wordsWithCharacters, contentRect);
1523
1508
 
1524
1509
    // start the tree with the root, it is our only region at the start
1525
1510
    tree.push_back(root);
1526
1511
 
1527
 
    int i = 0, j, k;
1528
 
    int countLoop = 0;
 
1512
    int i = 0;
1529
1513
 
1530
1514
    // while traversing the tree has not been ended
1531
1515
    while(i < tree.length())
1537
1521
         * 1. calculation of projection profiles
1538
1522
         */
1539
1523
        // allocate the size of proj profiles and initialize with 0
1540
 
        int size_proj_y = node.area().height() ;
1541
 
        int size_proj_x = node.area().width() ;
 
1524
        int size_proj_y = node.area().height();
 
1525
        int size_proj_x = node.area().width();
1542
1526
        //dynamic memory allocation
1543
1527
        QVarLengthArray<int> proj_on_xaxis(size_proj_x);
1544
1528
        QVarLengthArray<int> proj_on_yaxis(size_proj_y);
1545
1529
 
1546
 
        for( j = 0 ; j < size_proj_y ; j++ ) proj_on_yaxis[j] = 0;
1547
 
        for( j = 0 ; j < size_proj_x ; j++ ) proj_on_xaxis[j] = 0;
 
1530
        for( int j = 0 ; j < size_proj_y ; ++j ) proj_on_yaxis[j] = 0;
 
1531
        for( int j = 0 ; j < size_proj_x ; ++j ) proj_on_xaxis[j] = 0;
1548
1532
 
1549
 
        TextList list = node.text();
 
1533
        const QList<WordWithCharacters> list = node.text();
1550
1534
 
1551
1535
        // Calculate tcx and tcy locally for each new region
1552
 
        if(countLoop++)
1553
 
        {
1554
 
            SortedTextList lines;
1555
 
            LineRect line_rects;
1556
 
            int word_spacing, line_spacing, column_spacing;
1557
 
 
1558
 
            makeAndSortLines(list, &lines, &line_rects);
1559
 
            calculateStatisticalInformation(lines, line_rects, &word_spacing, &line_spacing, &column_spacing);
1560
 
            for(int i = 0 ; i < lines.length() ; i++)
1561
 
            {
1562
 
                qDeleteAll(lines.at(i));
1563
 
            }   
1564
 
            lines.clear();
1565
 
 
1566
 
            tcx = word_spacing * 2;
1567
 
            tcy = line_spacing * 2;
1568
 
        }
 
1536
        int word_spacing, line_spacing, column_spacing;
 
1537
        calculateStatisticalInformation(list, pageWidth, pageHeight, &word_spacing, &line_spacing, &column_spacing);
 
1538
 
 
1539
        const int tcx = word_spacing * 2;
 
1540
        const int tcy = line_spacing * 2;
1569
1541
 
1570
1542
        int maxX = 0 , maxY = 0;
1571
 
        int avgX = 0 ;
 
1543
        int avgX = 0;
1572
1544
        int count;
1573
1545
 
1574
1546
        // for every text in the region
1575
 
        for( j = 0 ; j < list.length() ; j++ )
 
1547
        for(int j = 0 ; j < list.length() ; ++j )
1576
1548
        {
1577
 
            TinyTextEntity *ent = list.at(j);
1578
 
            QRect entRect = ent->area.geometry(pageWidth,pageHeight);
 
1549
            TinyTextEntity *ent = list.at(j).word;
 
1550
            const QRect entRect = ent->area.geometry(pageWidth, pageHeight);
1579
1551
 
1580
1552
            // calculate vertical projection profile proj_on_xaxis1
1581
 
            for(k = entRect.left() ; k <= entRect.left() + entRect.width() ; k++)
 
1553
            for(int k = entRect.left() ; k <= entRect.left() + entRect.width() ; ++k)
1582
1554
            {
1583
1555
                if( ( k-regionRect.left() ) < size_proj_x && ( k-regionRect.left() ) >= 0 )
1584
1556
                    proj_on_xaxis[k - regionRect.left()] += entRect.height();
1585
1557
            }
1586
1558
 
1587
1559
            // calculate horizontal projection profile in the same way
1588
 
            for(k = entRect.top() ; k <= entRect.top() + entRect.height() ; k++)
 
1560
            for(int k = entRect.top() ; k <= entRect.top() + entRect.height() ; ++k)
1589
1561
            {
1590
1562
                if( ( k-regionRect.top() ) < size_proj_y && ( k-regionRect.top() ) >= 0 )
1591
1563
                    proj_on_yaxis[k - regionRect.top()] += entRect.width();
1592
1564
            }
1593
1565
        }
1594
1566
 
1595
 
        for( j = 0 ; j < size_proj_y ; j++ )
 
1567
        for( int j = 0 ; j < size_proj_y ; ++j )
1596
1568
        {
1597
1569
            if (proj_on_yaxis[j] > maxY)
1598
1570
                maxY = proj_on_yaxis[j];
1599
1571
        }
1600
1572
 
1601
1573
        avgX = count = 0;
1602
 
        for( j = 0 ; j < size_proj_x ; j++ )
 
1574
        for( int j = 0 ; j < size_proj_x ; ++j )
1603
1575
        {
1604
1576
            if(proj_on_xaxis[j] > maxX) maxX = proj_on_xaxis[j];
1605
1577
            if(proj_on_xaxis[j])
1633
1605
        regionRect.setBottom(old_top + yend);
1634
1606
 
1635
1607
        int tnx = (int)((double)avgX * 10.0 / 100.0 + 0.5), tny = 0;
1636
 
        for( j = 0 ; j < size_proj_x ; j++ )
 
1608
        for( int j = 0 ; j < size_proj_x ; ++j )
1637
1609
            proj_on_xaxis[j] -= tnx;
1638
 
        for(j = 0 ; j < size_proj_y ; j++)
 
1610
        for( int j = 0 ; j < size_proj_y ; ++j )
1639
1611
            proj_on_yaxis[j] -= tny;
1640
1612
 
1641
1613
        /**
1645
1617
        int begin = -1, end = -1;
1646
1618
 
1647
1619
        // find all hor_gaps and find the maximum between them
1648
 
        for(j = 1 ; j < size_proj_y ; j++)
 
1620
        for(int j = 1 ; j < size_proj_y ; ++j)
1649
1621
        {
1650
1622
            //transition from white to black
1651
1623
            if(begin >= 0 && proj_on_yaxis[j-1] <= 0
1670
1642
        int gap_ver = -1, pos_ver = -1;
1671
1643
 
1672
1644
        //find all the ver_gaps and find the maximum between them
1673
 
        for(j = 1 ; j < size_proj_x ; j++)
 
1645
        for(int j = 1 ; j < size_proj_x ; ++j)
1674
1646
        {
1675
1647
            //transition from white to black
1676
1648
            if(begin >= 0 && proj_on_xaxis[j-1] <= 0
1740
1712
            continue;
1741
1713
        }
1742
1714
 
1743
 
        TextList list1,list2;
1744
 
        TinyTextEntity* ent;
1745
 
        QRect entRect;
 
1715
        WordsWithCharacters list1,list2;
1746
1716
 
1747
1717
        // horizontal cut, topRect and bottomRect
1748
1718
        if(cut_hor)
1749
1719
        {
1750
 
            for( j = 0 ; j < list.length() ; j++ )
 
1720
            for( int j = 0 ; j < list.length() ; ++j )
1751
1721
            {
1752
 
                ent = list.at(j);
1753
 
                entRect = ent->area.geometry(pageWidth,pageHeight);
 
1722
                const WordWithCharacters word = list.at(j);
 
1723
                const QRect wordRect = word.area().geometry(pageWidth,pageHeight);
1754
1724
 
1755
 
                if(topRect.intersects(entRect))
1756
 
                    list1.append(ent);
 
1725
                if(topRect.intersects(wordRect))
 
1726
                    list1.append(word);
1757
1727
                else
1758
 
                    list2.append(ent);
 
1728
                    list2.append(word);
1759
1729
            }
1760
1730
 
1761
1731
            RegionText node1(list1,topRect);
1768
1738
        //vertical cut, leftRect and rightRect
1769
1739
        else if(cut_ver)
1770
1740
        {
1771
 
            for( j = 0 ; j < list.length() ; j++ )
 
1741
            for( int j = 0 ; j < list.length() ; ++j )
1772
1742
            {
1773
 
                ent = list.at(j);
1774
 
                entRect = ent->area.geometry(pageWidth,pageHeight);
 
1743
                const WordWithCharacters word = list.at(j);
 
1744
                const QRect wordRect = word.area().geometry(pageWidth,pageHeight);
1775
1745
 
1776
 
                if(leftRect.intersects(entRect))
1777
 
                    list1.append(ent);
1778
 
                else list2.append(ent);
 
1746
                if(leftRect.intersects(wordRect))
 
1747
                    list1.append(word);
 
1748
                else 
 
1749
                    list2.append(word);
1779
1750
            }
1780
1751
 
1781
1752
            RegionText node1(list1,leftRect);
1786
1757
        }
1787
1758
    }
1788
1759
 
1789
 
    TextList tmp;
1790
 
    for(i = 0 ; i < tree.length() ; i++)
1791
 
    {
1792
 
        tmp += tree.at(i).text();
1793
 
    }
1794
 
    // set tmp as new m_words
1795
 
    setWordList(tmp);
1796
 
 
1797
1760
    return tree;
1798
1761
}
1799
1762
 
1800
1763
/**
1801
 
 * Add spaces in between words in a line
 
1764
 * Add spaces in between words in a line. It reuses the pointers passed in tree and might add new ones. You will need to take care of deleting them if needed
1802
1765
 */
1803
 
void TextPagePrivate::addNecessarySpace(RegionTextList tree)
 
1766
WordsWithCharacters addNecessarySpace(RegionTextList tree, int pageWidth, int pageHeight)
1804
1767
{
1805
1768
    /**
1806
1769
     * 1. Call makeAndSortLines before adding spaces in between words in a line
1807
1770
     * 2. Now add spaces between every two words in a line
1808
 
     * 3. Finally, extract all the space separated texts from each region and
1809
 
     *    make m_words nice again.
 
1771
     * 3. Finally, extract all the space separated texts from each region and return it
1810
1772
     */
1811
1773
 
1812
 
    const int pageWidth = m_page->m_page->width();
1813
 
    const int pageHeight = m_page->m_page->height();
1814
 
 
1815
1774
    // Only change the texts under RegionTexts, not the area
1816
1775
    for(int j = 0 ; j < tree.length() ; j++)
1817
1776
    {
1818
1777
        RegionText &tmpRegion = tree[j];
1819
 
        SortedTextList lines;
1820
 
        LineRect line_rects;
1821
1778
 
1822
1779
        // Step 01
1823
 
        makeAndSortLines(tmpRegion.text(), &lines, &line_rects);
 
1780
        QList< QPair<WordsWithCharacters, QRect> > sortedLines = makeAndSortLines(tmpRegion.text(), pageWidth, pageHeight);
1824
1781
 
1825
1782
        // Step 02
1826
 
        for(int i = 0 ; i < lines.length() ; i++)
 
1783
        for(int i = 0 ; i < sortedLines.length() ; i++)
1827
1784
        {
1828
 
            TextList &list = lines[i];
 
1785
            WordsWithCharacters &list = sortedLines[i].first;
1829
1786
            for(int k = 0 ; k < list.length() ; k++ )
1830
1787
            {
1831
 
                const QRect area1 = list.at(k)->area.roundedGeometry(pageWidth,pageHeight);
 
1788
                const QRect area1 = list.at(k).area().roundedGeometry(pageWidth,pageHeight);
1832
1789
                if( k+1 >= list.length() ) break;
1833
1790
 
1834
 
                const QRect area2 = list.at(k+1)->area.roundedGeometry(pageWidth,pageHeight);
 
1791
                const QRect area2 = list.at(k+1).area().roundedGeometry(pageWidth,pageHeight);
1835
1792
                const int space = area2.left() - area1.right();
1836
1793
 
1837
1794
                if(space != 0)
1845
1802
                    const QString spaceStr(" ");
1846
1803
                    const QRect rect(QPoint(left,top),QPoint(right,bottom));
1847
1804
                    const NormalizedRect entRect(rect,pageWidth,pageHeight);
1848
 
                    TinyTextEntity *ent = new TinyTextEntity(spaceStr,entRect);
 
1805
                    TinyTextEntity *ent1 = new TinyTextEntity(spaceStr, entRect);
 
1806
                    TinyTextEntity *ent2 = new TinyTextEntity(spaceStr, entRect);
 
1807
                    WordWithCharacters word(ent1, QList<TinyTextEntity*>() << ent2);
1849
1808
 
1850
 
                    list.insert(k+1,ent);
 
1809
                    list.insert(k+1, word);
1851
1810
 
1852
1811
                    // Skip the space
1853
1812
                    k++;
1855
1814
            }
1856
1815
        }
1857
1816
 
1858
 
        TextList tmpList;
1859
 
        for(int i = 0 ; i < lines.length() ; i++)
 
1817
        WordsWithCharacters tmpList;
 
1818
        for(int i = 0 ; i < sortedLines.length() ; i++)
1860
1819
        {
1861
 
            tmpList += lines.at(i);
 
1820
            tmpList += sortedLines.at(i).first;
1862
1821
        }
1863
1822
        tmpRegion.setText(tmpList);
1864
1823
    }
1865
1824
 
1866
1825
    // Step 03
1867
 
    TextList tmp;
 
1826
    WordsWithCharacters tmp;
1868
1827
    for(int i = 0 ; i < tree.length() ; i++)
1869
1828
    {
1870
1829
        tmp += tree.at(i).text();
1871
1830
    }
1872
 
    setWordList(tmp);
1873
 
}
1874
 
 
1875
 
/**
1876
 
 * Break Words into Characters, takes Entities from m_words and for each of
1877
 
 * them insert the character entities in tmp. Finally, copies tmp back to m_words
1878
 
 */
1879
 
void TextPagePrivate::breakWordIntoCharacters(const QHash<QRect, RegionText> &word_chars_map)
1880
 
{
1881
 
    const QString spaceStr(" ");
1882
 
    TextList tmp;
1883
 
    const int pageWidth = m_page->m_page->width();
1884
 
    const int pageHeight = m_page->m_page->height();
1885
 
 
1886
 
    for(int i = 0 ; i < m_words.length() ; i++)
1887
 
    {
1888
 
        TinyTextEntity *ent = m_words.at(i);
1889
 
        const QRect rect = ent->area.geometry(pageWidth,pageHeight);
1890
 
 
1891
 
        // the spaces contains only one character, so we can skip them
1892
 
        if(ent->text() == spaceStr)
1893
 
            tmp.append( new TinyTextEntity(ent->text(),ent->area) );
1894
 
        else
1895
 
        {
1896
 
            RegionText word_text;
1897
 
 
1898
 
            QHash<QRect, RegionText>::const_iterator it = word_chars_map.find(rect);
1899
 
            while( it != word_chars_map.end() && it.key() == rect )
1900
 
            {
1901
 
                word_text = it.value();
1902
 
                    
1903
 
                if (ent->text() == word_text.string())
1904
 
                    break;
1905
 
                    
1906
 
                ++it;
1907
 
            }
1908
 
            tmp.append(word_text.text());
1909
 
        }
1910
 
    }
1911
 
    setWordList(tmp);
1912
 
}
1913
 
 
 
1831
    return tmp;
 
1832
}
1914
1833
 
1915
1834
/**
1916
1835
 * Correct the textOrder, all layout recognition works here
1917
1836
 */
1918
1837
void TextPagePrivate::correctTextOrder()
1919
1838
{
 
1839
    const int pageWidth = m_page->m_page->width();
 
1840
    const int pageHeight = m_page->m_page->height();
 
1841
 
 
1842
    TextList characters = m_words;
 
1843
 
1920
1844
    /**
1921
1845
     * Remove spaces from the text
1922
1846
     */
1923
 
    removeSpace();
 
1847
    removeSpace(&characters);
1924
1848
 
1925
1849
    /**
1926
1850
     * Construct words from characters
1927
1851
     */
1928
 
    const QHash<QRect, RegionText> word_chars_map = makeWordFromCharacters();
1929
 
 
1930
 
    SortedTextList lines;
1931
 
    LineRect line_rects;
1932
 
    /**
1933
 
     * Create arbitrary lines from words and sort them according to X and Y position
1934
 
     */
1935
 
    makeAndSortLines(m_words, &lines, &line_rects);
1936
 
 
1937
 
    /**
1938
 
     * Calculate statistical information which will be needed later for algorithm implementation
1939
 
     */
1940
 
    int word_spacing, line_spacing, col_spacing;
1941
 
    calculateStatisticalInformation(lines, line_rects, &word_spacing, &line_spacing, &col_spacing);
1942
 
    for(int i = 0 ; i < lines.length() ; i++)
1943
 
    {
1944
 
       qDeleteAll(lines.at(i));
1945
 
    }
1946
 
    lines.clear();
 
1852
    const QList<WordWithCharacters> wordsWithCharacters = makeWordFromCharacters(characters, pageWidth, pageHeight);
1947
1853
 
1948
1854
    /**
1949
1855
     * Make a XY Cut tree for segmentation of the texts
1950
1856
     */
1951
 
    const RegionTextList tree = XYCutForBoundingBoxes(word_spacing * 2, line_spacing * 2);
 
1857
    const RegionTextList tree = XYCutForBoundingBoxes(wordsWithCharacters, m_page->m_page->boundingBox(), pageWidth, pageHeight);
1952
1858
 
1953
1859
    /**
1954
1860
     * Add spaces to the word
1955
1861
     */
1956
 
    addNecessarySpace(tree);
 
1862
    const WordsWithCharacters listWithWordsAndSpaces = addNecessarySpace(tree, pageWidth, pageHeight);
1957
1863
 
1958
1864
    /**
1959
1865
     * Break the words into characters
1960
1866
     */
1961
 
    breakWordIntoCharacters(word_chars_map);
 
1867
    TextList listOfCharacters;
 
1868
    foreach(const WordWithCharacters &word, listWithWordsAndSpaces)
 
1869
    {
 
1870
        delete word.word;
 
1871
        listOfCharacters.append(word.characters);
 
1872
    }
 
1873
    setWordList(listOfCharacters);
1962
1874
}
1963
1875
 
1964
1876
TextEntity::List TextPage::words(const RegularAreaRect *area, TextAreaInclusionBehaviour b) const