Cleanup some numeric code.
[pdfium.git] / core / src / fpdftext / fpdf_text.cpp
1 // Copyright 2014 PDFium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 // Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com
6
7 #include "../../../third_party/base/nonstd_unique_ptr.h"
8 #include "../../include/fpdfapi/fpdf_page.h"
9 #include "../../include/fpdfapi/fpdf_pageobj.h"
10 #include "../../include/fpdfapi/fpdf_resource.h"
11 #include "../../include/fpdftext/fpdf_text.h"
12 #include "../../include/fxcrt/fx_bidi.h"
13 #include "../../include/fxcrt/fx_ucd.h"
14 #include "text_int.h"
15 #include "txtproc.h"
16
17 CFX_ByteString CharFromUnicodeAlt(FX_WCHAR unicode,
18                                   int destcp,
19                                   const FX_CHAR* defchar) {
20   if (destcp == 0) {
21     if (unicode < 0x80) {
22       return CFX_ByteString((char)unicode);
23     }
24     const FX_CHAR* altstr = FCS_GetAltStr(unicode);
25     return CFX_ByteString(altstr ? altstr : defchar);
26   }
27   char buf[10];
28   int iDef = 0;
29   int ret = FXSYS_WideCharToMultiByte(destcp, 0, (wchar_t*)&unicode, 1, buf, 10,
30                                       NULL, &iDef);
31   if (ret && !iDef) {
32     return CFX_ByteString(buf, ret);
33   }
34   const FX_CHAR* altstr = FCS_GetAltStr(unicode);
35   return CFX_ByteString(altstr ? altstr : defchar);
36 }
37 CTextPage::CTextPage() {}
38 CTextPage::~CTextPage() {
39   int i;
40   for (i = 0; i < m_BaseLines.GetSize(); i++) {
41     CTextBaseLine* pBaseLine = (CTextBaseLine*)m_BaseLines.GetAt(i);
42     delete pBaseLine;
43   }
44   for (i = 0; i < m_TextColumns.GetSize(); i++) {
45     CTextColumn* pTextColumn = (CTextColumn*)m_TextColumns.GetAt(i);
46     delete pTextColumn;
47   }
48 }
49 void CTextPage::ProcessObject(CPDF_PageObject* pObject) {
50   if (pObject->m_Type != PDFPAGE_TEXT) {
51     return;
52   }
53   CPDF_TextObject* pText = (CPDF_TextObject*)pObject;
54   CPDF_Font* pFont = pText->m_TextState.GetFont();
55   int count = pText->CountItems();
56   FX_FLOAT* pPosArray = FX_Alloc2D(FX_FLOAT, count, 2);
57   pText->CalcCharPos(pPosArray);
58
59   FX_FLOAT fontsize_h = pText->m_TextState.GetFontSizeH();
60   FX_FLOAT fontsize_v = pText->m_TextState.GetFontSizeV();
61   FX_DWORD space_charcode = pFont->CharCodeFromUnicode(' ');
62   FX_FLOAT spacew = 0;
63   if (space_charcode != -1) {
64     spacew = fontsize_h * pFont->GetCharWidthF(space_charcode) / 1000;
65   }
66   if (spacew == 0) {
67     spacew = fontsize_h / 4;
68   }
69   if (pText->m_TextState.GetBaselineAngle() != 0) {
70     int cc = 0;
71     CFX_AffineMatrix matrix;
72     pText->GetTextMatrix(&matrix);
73     for (int i = 0; i < pText->m_nChars; i++) {
74       FX_DWORD charcode = pText->m_nChars == 1
75                               ? (FX_DWORD)(uintptr_t)pText->m_pCharCodes
76                               : pText->m_pCharCodes[i];
77       if (charcode == (FX_DWORD)-1) {
78         continue;
79       }
80       FX_RECT char_box;
81       pFont->GetCharBBox(charcode, char_box);
82       FX_FLOAT char_left =
83           pPosArray ? pPosArray[cc * 2]
84                     : char_box.left * pText->m_TextState.GetFontSize() / 1000;
85       FX_FLOAT char_right =
86           pPosArray ? pPosArray[cc * 2 + 1]
87                     : char_box.right * pText->m_TextState.GetFontSize() / 1000;
88       FX_FLOAT char_top =
89           char_box.top * pText->m_TextState.GetFontSize() / 1000;
90       FX_FLOAT char_bottom =
91           char_box.bottom * pText->m_TextState.GetFontSize() / 1000;
92       cc++;
93       FX_FLOAT char_origx, char_origy;
94       matrix.Transform(char_left, 0, char_origx, char_origy);
95       matrix.TransformRect(char_left, char_right, char_top, char_bottom);
96       CFX_ByteString str;
97       pFont->AppendChar(str, charcode);
98       InsertTextBox(NULL, char_origy, char_left, char_right, char_top,
99                     char_bottom, spacew, fontsize_v, str, pFont);
100     }
101     FX_Free(pPosArray);
102     return;
103   }
104   FX_FLOAT ratio_h = fontsize_h / pText->m_TextState.GetFontSize();
105   for (int ii = 0; ii < count * 2; ii++) {
106     pPosArray[ii] *= ratio_h;
107   }
108   FX_FLOAT baseline = pText->m_PosY;
109   CTextBaseLine* pBaseLine = NULL;
110   FX_FLOAT topy = pText->m_Top;
111   FX_FLOAT bottomy = pText->m_Bottom;
112   FX_FLOAT leftx = pText->m_Left;
113   int cc = 0;
114   CFX_ByteString segment;
115   int space_count = 0;
116   FX_FLOAT last_left = 0, last_right = 0, segment_left = 0, segment_right = 0;
117   for (int i = 0; i < pText->m_nChars; i++) {
118     FX_DWORD charcode = pText->m_nChars == 1
119                             ? (FX_DWORD)(uintptr_t)pText->m_pCharCodes
120                             : pText->m_pCharCodes[i];
121     if (charcode == (FX_DWORD)-1) {
122       continue;
123     }
124     FX_FLOAT char_left = pPosArray[cc * 2];
125     FX_FLOAT char_right = pPosArray[cc * 2 + 1];
126     cc++;
127     if (char_left < last_left || (char_left - last_right) > spacew / 2) {
128       pBaseLine = InsertTextBox(pBaseLine, baseline, leftx + segment_left,
129                                 leftx + segment_right, topy, bottomy, spacew,
130                                 fontsize_v, segment, pFont);
131       segment_left = char_left;
132       segment = "";
133     }
134     if (space_count > 1) {
135       pBaseLine = InsertTextBox(pBaseLine, baseline, leftx + segment_left,
136                                 leftx + segment_right, topy, bottomy, spacew,
137                                 fontsize_v, segment, pFont);
138       segment = "";
139     } else if (space_count == 1) {
140       pFont->AppendChar(segment, ' ');
141     }
142     if (segment.GetLength() == 0) {
143       segment_left = char_left;
144     }
145     segment_right = char_right;
146     pFont->AppendChar(segment, charcode);
147     space_count = 0;
148     last_left = char_left;
149     last_right = char_right;
150   }
151   if (segment.GetLength())
152     pBaseLine = InsertTextBox(pBaseLine, baseline, leftx + segment_left,
153                               leftx + segment_right, topy, bottomy, spacew,
154                               fontsize_v, segment, pFont);
155   FX_Free(pPosArray);
156 }
157 CTextBaseLine* CTextPage::InsertTextBox(CTextBaseLine* pBaseLine,
158                                         FX_FLOAT basey,
159                                         FX_FLOAT leftx,
160                                         FX_FLOAT rightx,
161                                         FX_FLOAT topy,
162                                         FX_FLOAT bottomy,
163                                         FX_FLOAT spacew,
164                                         FX_FLOAT fontsize_v,
165                                         CFX_ByteString& str,
166                                         CPDF_Font* pFont) {
167   if (str.GetLength() == 0) {
168     return NULL;
169   }
170   if (pBaseLine == NULL) {
171     int i;
172     for (i = 0; i < m_BaseLines.GetSize(); i++) {
173       CTextBaseLine* pExistLine = (CTextBaseLine*)m_BaseLines.GetAt(i);
174       if (pExistLine->m_BaseLine == basey) {
175         pBaseLine = pExistLine;
176         break;
177       }
178       if (pExistLine->m_BaseLine < basey) {
179         break;
180       }
181     }
182     if (pBaseLine == NULL) {
183       pBaseLine = new CTextBaseLine;
184       pBaseLine->m_BaseLine = basey;
185       m_BaseLines.InsertAt(i, pBaseLine);
186     }
187   }
188   CFX_WideString text;
189   const FX_CHAR* pStr = str;
190   int len = str.GetLength(), offset = 0;
191   while (offset < len) {
192     FX_DWORD ch = pFont->GetNextChar(pStr, len, offset);
193     CFX_WideString unicode_str = pFont->UnicodeFromCharCode(ch);
194     if (unicode_str.IsEmpty()) {
195       text += (FX_WCHAR)ch;
196     } else {
197       text += unicode_str;
198     }
199   }
200   pBaseLine->InsertTextBox(leftx, rightx, topy, bottomy, spacew, fontsize_v,
201                            text);
202   return pBaseLine;
203 }
204 void CTextPage::WriteOutput(CFX_WideStringArray& lines, int iMinWidth) {
205   FX_FLOAT lastheight = -1;
206   FX_FLOAT lastbaseline = -1;
207   FX_FLOAT MinLeftX = 1000000;
208   FX_FLOAT MaxRightX = 0;
209   int i;
210   for (i = 0; i < m_BaseLines.GetSize(); i++) {
211     CTextBaseLine* pBaseLine = (CTextBaseLine*)m_BaseLines.GetAt(i);
212     FX_FLOAT leftx, rightx;
213     if (pBaseLine->GetWidth(leftx, rightx)) {
214       if (leftx < MinLeftX) {
215         MinLeftX = leftx;
216       }
217       if (rightx > MaxRightX) {
218         MaxRightX = rightx;
219       }
220     }
221   }
222   for (i = 0; i < m_BaseLines.GetSize(); i++) {
223     CTextBaseLine* pBaseLine = (CTextBaseLine*)m_BaseLines.GetAt(i);
224     pBaseLine->MergeBoxes();
225   }
226   for (i = 1; i < m_BaseLines.GetSize(); i++) {
227     CTextBaseLine* pBaseLine = (CTextBaseLine*)m_BaseLines.GetAt(i);
228     CTextBaseLine* pPrevLine = (CTextBaseLine*)m_BaseLines.GetAt(i - 1);
229     if (pBaseLine->CanMerge(pPrevLine)) {
230       pPrevLine->Merge(pBaseLine);
231       delete pBaseLine;
232       m_BaseLines.RemoveAt(i);
233       i--;
234     }
235   }
236   if (m_bAutoWidth) {
237     int* widths = FX_Alloc(int, m_BaseLines.GetSize());
238     for (i = 0; i < m_BaseLines.GetSize(); i++) {
239       widths[i] = 0;
240       CTextBaseLine* pBaseLine = (CTextBaseLine*)m_BaseLines.GetAt(i);
241       int TotalChars = 0;
242       FX_FLOAT TotalWidth = 0;
243       int minchars;
244       pBaseLine->CountChars(TotalChars, TotalWidth, minchars);
245       if (TotalChars) {
246         FX_FLOAT charwidth = TotalWidth / TotalChars;
247         widths[i] = (int)((MaxRightX - MinLeftX) / charwidth);
248       }
249       if (widths[i] > 1000) {
250         widths[i] = 1000;
251       }
252       if (widths[i] < minchars) {
253         widths[i] = minchars;
254       }
255     }
256     int AvgWidth = 0, widthcount = 0;
257     for (i = 0; i < m_BaseLines.GetSize(); i++)
258       if (widths[i]) {
259         AvgWidth += widths[i];
260         widthcount++;
261       }
262     AvgWidth = int((FX_FLOAT)AvgWidth / widthcount + 0.5);
263     int MaxWidth = 0;
264     for (i = 0; i < m_BaseLines.GetSize(); i++)
265       if (MaxWidth < widths[i]) {
266         MaxWidth = widths[i];
267       }
268     if (MaxWidth > AvgWidth * 6 / 5) {
269       MaxWidth = AvgWidth * 6 / 5;
270     }
271     FX_Free(widths);
272     if (iMinWidth < MaxWidth) {
273       iMinWidth = MaxWidth;
274     }
275   }
276   for (i = 0; i < m_BaseLines.GetSize(); i++) {
277     CTextBaseLine* pBaseLine = (CTextBaseLine*)m_BaseLines.GetAt(i);
278     pBaseLine->MergeBoxes();
279   }
280   if (m_bKeepColumn) {
281     FindColumns();
282   }
283   for (i = 0; i < m_BaseLines.GetSize(); i++) {
284     CTextBaseLine* pBaseLine = (CTextBaseLine*)m_BaseLines.GetAt(i);
285     if (lastheight >= 0) {
286       FX_FLOAT dy = lastbaseline - pBaseLine->m_BaseLine;
287       if (dy >= (pBaseLine->m_MaxFontSizeV) * 1.5 || dy >= lastheight * 1.5) {
288         lines.Add(L"");
289       }
290     }
291     lastheight = pBaseLine->m_MaxFontSizeV;
292     lastbaseline = pBaseLine->m_BaseLine;
293     CFX_WideString str;
294     pBaseLine->WriteOutput(str, MinLeftX, MaxRightX - MinLeftX, iMinWidth);
295     lines.Add(str);
296   }
297 }
298 void NormalizeCompositeChar(FX_WCHAR wChar, CFX_WideString& sDest) {
299   wChar = FX_GetMirrorChar(wChar, TRUE, FALSE);
300   FX_WCHAR* pDst = NULL;
301   FX_STRSIZE nCount = FX_Unicode_GetNormalization(wChar, pDst);
302   if (nCount < 1) {
303     sDest += wChar;
304     return;
305   }
306   pDst = new FX_WCHAR[nCount];
307   FX_Unicode_GetNormalization(wChar, pDst);
308   for (int nIndex = 0; nIndex < nCount; nIndex++) {
309     sDest += pDst[nIndex];
310   }
311   delete[] pDst;
312 }
313 void NormalizeString(CFX_WideString& str) {
314   if (str.GetLength() <= 0) {
315     return;
316   }
317   CFX_WideString sBuffer;
318   nonstd::unique_ptr<CFX_BidiChar> pBidiChar(new CFX_BidiChar);
319   CFX_WordArray order;
320   FX_BOOL bR2L = FALSE;
321   int32_t start = 0, count = 0, i = 0;
322   int nR2L = 0, nL2R = 0;
323   for (i = 0; i < str.GetLength(); i++) {
324     if (pBidiChar->AppendChar(str.GetAt(i))) {
325       CFX_BidiChar::Direction ret = pBidiChar->GetBidiInfo(&start, &count);
326       order.Add(start);
327       order.Add(count);
328       order.Add(ret);
329       if (!bR2L) {
330         if (ret == CFX_BidiChar::RIGHT) {
331           nR2L++;
332         } else if (ret == CFX_BidiChar::LEFT) {
333           nL2R++;
334         }
335       }
336     }
337   }
338   if (pBidiChar->EndChar()) {
339     CFX_BidiChar::Direction ret = pBidiChar->GetBidiInfo(&start, &count);
340     order.Add(start);
341     order.Add(count);
342     order.Add(ret);
343     if (!bR2L) {
344       if (ret == CFX_BidiChar::RIGHT) {
345         nR2L++;
346       } else if (ret == CFX_BidiChar::LEFT) {
347         nL2R++;
348       }
349     }
350   }
351   if (nR2L > 0 && nR2L >= nL2R) {
352     bR2L = TRUE;
353   }
354   if (bR2L) {
355     int count = order.GetSize();
356     for (int j = count - 1; j > 0; j -= 3) {
357       int ret = order.GetAt(j);
358       int start = order.GetAt(j - 2);
359       int count1 = order.GetAt(j - 1);
360       if (ret == 2 || ret == 0) {
361         for (int i = start + count1 - 1; i >= start; i--) {
362           NormalizeCompositeChar(str[i], sBuffer);
363         }
364       } else {
365         i = j;
366         FX_BOOL bSymbol = FALSE;
367         while (i > 0 && order.GetAt(i) != 2) {
368           bSymbol = !order.GetAt(i);
369           i -= 3;
370         }
371         int end = start + count1;
372         int n = 0;
373         if (bSymbol) {
374           n = i + 6;
375         } else {
376           n = i + 3;
377         }
378         if (n >= j) {
379           for (int m = start; m < end; m++) {
380             sBuffer += str[m];
381           }
382         } else {
383           i = j;
384           j = n;
385           for (; n <= i; n += 3) {
386             int start = order.GetAt(n - 2);
387             int count1 = order.GetAt(n - 1);
388             int end = start + count1;
389             for (int m = start; m < end; m++) {
390               sBuffer += str[m];
391             }
392           }
393         }
394       }
395     }
396   } else {
397     int count = order.GetSize();
398     FX_BOOL bL2R = FALSE;
399     for (int j = 0; j < count; j += 3) {
400       int ret = order.GetAt(j + 2);
401       int start = order.GetAt(j);
402       int count1 = order.GetAt(j + 1);
403       if (ret == 2 || (j == 0 && ret == 0 && !bL2R)) {
404         int i = j + 3;
405         while (bR2L && i < count) {
406           if (order.GetAt(i + 2) == 1) {
407             break;
408           } else {
409             i += 3;
410           }
411         }
412         if (i == 3) {
413           j = -3;
414           bL2R = TRUE;
415           continue;
416         }
417         int end = str.GetLength() - 1;
418         if (i < count) {
419           end = order.GetAt(i) - 1;
420         }
421         j = i - 3;
422         for (int n = end; n >= start; n--) {
423           NormalizeCompositeChar(str[i], sBuffer);
424         }
425       } else {
426         int end = start + count1;
427         for (int i = start; i < end; i++) {
428           sBuffer += str[i];
429         }
430       }
431     }
432   }
433   str.Empty();
434   str += sBuffer;
435 }
436 static FX_BOOL IsNumber(CFX_WideString& str) {
437   for (int i = 0; i < str.GetLength(); i++) {
438     FX_WCHAR ch = str[i];
439     if (!std::isdigit(ch) && ch != '-' && ch != '+' && ch != '.' && ch != ' ')
440       return FALSE;
441   }
442   return TRUE;
443 }
444 void CTextPage::FindColumns() {
445   int i;
446   for (i = 0; i < m_BaseLines.GetSize(); i++) {
447     CTextBaseLine* pBaseLine = (CTextBaseLine*)m_BaseLines.GetAt(i);
448     for (int j = 0; j < pBaseLine->m_TextList.GetSize(); j++) {
449       CTextBox* pTextBox = (CTextBox*)pBaseLine->m_TextList.GetAt(j);
450       CTextColumn* pColumn = FindColumn(pTextBox->m_Right);
451       if (pColumn == NULL) {
452         pColumn = new CTextColumn;
453         pColumn->m_Count = 1;
454         pColumn->m_AvgPos = pTextBox->m_Right;
455         pColumn->m_TextPos = -1;
456         m_TextColumns.Add(pColumn);
457       } else {
458         pColumn->m_AvgPos =
459             (pColumn->m_Count * pColumn->m_AvgPos + pTextBox->m_Right) /
460             (pColumn->m_Count + 1);
461         pColumn->m_Count++;
462       }
463     }
464   }
465   int mincount = m_BaseLines.GetSize() / 4;
466   for (i = 0; i < m_TextColumns.GetSize(); i++) {
467     CTextColumn* pTextColumn = (CTextColumn*)m_TextColumns.GetAt(i);
468     if (pTextColumn->m_Count >= mincount) {
469       continue;
470     }
471     delete pTextColumn;
472     m_TextColumns.RemoveAt(i);
473     i--;
474   }
475   for (i = 0; i < m_BaseLines.GetSize(); i++) {
476     CTextBaseLine* pBaseLine = (CTextBaseLine*)m_BaseLines.GetAt(i);
477     for (int j = 0; j < pBaseLine->m_TextList.GetSize(); j++) {
478       CTextBox* pTextBox = (CTextBox*)pBaseLine->m_TextList.GetAt(j);
479       if (IsNumber(pTextBox->m_Text)) {
480         pTextBox->m_pColumn = FindColumn(pTextBox->m_Right);
481       }
482     }
483   }
484 }
485 CTextColumn* CTextPage::FindColumn(FX_FLOAT xpos) {
486   for (int i = 0; i < m_TextColumns.GetSize(); i++) {
487     CTextColumn* pColumn = (CTextColumn*)m_TextColumns.GetAt(i);
488     if (pColumn->m_AvgPos < xpos + 1 && pColumn->m_AvgPos > xpos - 1) {
489       return pColumn;
490     }
491   }
492   return NULL;
493 }
494 void CTextPage::BreakSpace(CPDF_TextObject* pTextObj) {}
495 CTextBaseLine::CTextBaseLine() {
496   m_Top = -100000;
497   m_Bottom = 100000;
498   m_MaxFontSizeV = 0;
499 }
500 CTextBaseLine::~CTextBaseLine() {
501   for (int i = 0; i < m_TextList.GetSize(); i++) {
502     CTextBox* pText = (CTextBox*)m_TextList.GetAt(i);
503     delete pText;
504   }
505 }
506 void CTextBaseLine::InsertTextBox(FX_FLOAT leftx,
507                                   FX_FLOAT rightx,
508                                   FX_FLOAT topy,
509                                   FX_FLOAT bottomy,
510                                   FX_FLOAT spacew,
511                                   FX_FLOAT fontsize_v,
512                                   const CFX_WideString& text) {
513   if (m_Top < topy) {
514     m_Top = topy;
515   }
516   if (m_Bottom > bottomy) {
517     m_Bottom = bottomy;
518   }
519   if (m_MaxFontSizeV < fontsize_v) {
520     m_MaxFontSizeV = fontsize_v;
521   }
522   int i;
523   for (i = 0; i < m_TextList.GetSize(); i++) {
524     CTextBox* pText = (CTextBox*)m_TextList.GetAt(i);
525     if (pText->m_Left > leftx) {
526       break;
527     }
528   }
529   CTextBox* pText = new CTextBox;
530   pText->m_Text = text;
531   pText->m_Left = leftx;
532   pText->m_Right = rightx;
533   pText->m_Top = topy;
534   pText->m_Bottom = bottomy;
535   pText->m_SpaceWidth = spacew;
536   pText->m_FontSizeV = fontsize_v;
537   pText->m_pColumn = NULL;
538   m_TextList.InsertAt(i, pText);
539 }
540 FX_BOOL GetIntersection(FX_FLOAT low1,
541                         FX_FLOAT high1,
542                         FX_FLOAT low2,
543                         FX_FLOAT high2,
544                         FX_FLOAT& interlow,
545                         FX_FLOAT& interhigh);
546 FX_BOOL CTextBaseLine::CanMerge(CTextBaseLine* pOther) {
547   FX_FLOAT inter_top, inter_bottom;
548   if (!GetIntersection(m_Bottom, m_Top, pOther->m_Bottom, pOther->m_Top,
549                        inter_bottom, inter_top)) {
550     return FALSE;
551   }
552   FX_FLOAT inter_h = inter_top - inter_bottom;
553   if (inter_h < (m_Top - m_Bottom) / 2 &&
554       inter_h < (pOther->m_Top - pOther->m_Bottom) / 2) {
555     return FALSE;
556   }
557   FX_FLOAT dy = (FX_FLOAT)FXSYS_fabs(m_BaseLine - pOther->m_BaseLine);
558   for (int i = 0; i < m_TextList.GetSize(); i++) {
559     CTextBox* pText = (CTextBox*)m_TextList.GetAt(i);
560     for (int j = 0; j < pOther->m_TextList.GetSize(); j++) {
561       CTextBox* pOtherText = (CTextBox*)pOther->m_TextList.GetAt(j);
562       FX_FLOAT inter_left, inter_right;
563       if (!GetIntersection(pText->m_Left, pText->m_Right, pOtherText->m_Left,
564                            pOtherText->m_Right, inter_left, inter_right)) {
565         continue;
566       }
567       FX_FLOAT inter_w = inter_right - inter_left;
568       if (inter_w < pText->m_SpaceWidth / 2 &&
569           inter_w < pOtherText->m_SpaceWidth / 2) {
570         continue;
571       }
572       if (dy >= (pText->m_Bottom - pText->m_Top) / 2 ||
573           dy >= (pOtherText->m_Bottom - pOtherText->m_Top) / 2) {
574         return FALSE;
575       }
576     }
577   }
578   return TRUE;
579 }
580 void CTextBaseLine::Merge(CTextBaseLine* pOther) {
581   for (int i = 0; i < pOther->m_TextList.GetSize(); i++) {
582     CTextBox* pText = (CTextBox*)pOther->m_TextList.GetAt(i);
583     InsertTextBox(pText->m_Left, pText->m_Right, pText->m_Top, pText->m_Bottom,
584                   pText->m_SpaceWidth, pText->m_FontSizeV, pText->m_Text);
585   }
586 }
587 FX_BOOL CTextBaseLine::GetWidth(FX_FLOAT& leftx, FX_FLOAT& rightx) {
588   int i;
589   for (i = 0; i < m_TextList.GetSize(); i++) {
590     CTextBox* pText = (CTextBox*)m_TextList.GetAt(i);
591     if (pText->m_Text != L" ") {
592       break;
593     }
594   }
595   if (i == m_TextList.GetSize()) {
596     return FALSE;
597   }
598   CTextBox* pText = (CTextBox*)m_TextList.GetAt(i);
599   leftx = pText->m_Left;
600   for (i = m_TextList.GetSize() - 1; i >= 0; i--) {
601     CTextBox* pText = (CTextBox*)m_TextList.GetAt(i);
602     if (pText->m_Text != L" ") {
603       break;
604     }
605   }
606   pText = (CTextBox*)m_TextList.GetAt(i);
607   rightx = pText->m_Right;
608   return TRUE;
609 }
610 void CTextBaseLine::MergeBoxes() {
611   int i = 0;
612   while (1) {
613     if (i >= m_TextList.GetSize() - 1) {
614       break;
615     }
616     CTextBox* pThisText = (CTextBox*)m_TextList.GetAt(i);
617     CTextBox* pNextText = (CTextBox*)m_TextList.GetAt(i + 1);
618     FX_FLOAT dx = pNextText->m_Left - pThisText->m_Right;
619     FX_FLOAT spacew = (pThisText->m_SpaceWidth == 0.0)
620                           ? pNextText->m_SpaceWidth
621                           : pThisText->m_SpaceWidth;
622     if (spacew > 0.0 && dx < spacew * 2) {
623       pThisText->m_Right = pNextText->m_Right;
624       if (dx > spacew * 1.5) {
625         pThisText->m_Text += L"  ";
626       } else if (dx > spacew / 3) {
627         pThisText->m_Text += L' ';
628       }
629       pThisText->m_Text += pNextText->m_Text;
630       pThisText->m_SpaceWidth =
631           pNextText->m_SpaceWidth == 0.0 ? spacew : pNextText->m_SpaceWidth;
632       m_TextList.RemoveAt(i + 1);
633       delete pNextText;
634     } else {
635       i++;
636     }
637   }
638 }
639 void CTextBaseLine::WriteOutput(CFX_WideString& str,
640                                 FX_FLOAT leftx,
641                                 FX_FLOAT pagewidth,
642                                 int iTextWidth) {
643   int lastpos = -1;
644   for (int i = 0; i < m_TextList.GetSize(); i++) {
645     CTextBox* pText = (CTextBox*)m_TextList.GetAt(i);
646     int xpos;
647     if (pText->m_pColumn) {
648       xpos =
649           (int)((pText->m_pColumn->m_AvgPos - leftx) * iTextWidth / pagewidth +
650                 0.5);
651       xpos -= pText->m_Text.GetLength();
652     } else {
653       xpos = (int)((pText->m_Left - leftx) * iTextWidth / pagewidth + 0.5);
654     }
655     if (xpos <= lastpos) {
656       xpos = lastpos + 1;
657     }
658     for (int j = lastpos + 1; j < xpos; j++) {
659       str += ' ';
660     }
661     CFX_WideString sSrc(pText->m_Text);
662     NormalizeString(sSrc);
663     str += sSrc;
664     str += ' ';
665     lastpos = xpos + pText->m_Text.GetLength();
666   }
667 }
668 void CTextBaseLine::CountChars(int& count, FX_FLOAT& width, int& minchars) {
669   minchars = 0;
670   for (int i = 0; i < m_TextList.GetSize(); i++) {
671     CTextBox* pText = (CTextBox*)m_TextList.GetAt(i);
672     if (pText->m_Right - pText->m_Left < 0.002) {
673       continue;
674     }
675     count += pText->m_Text.GetLength();
676     width += pText->m_Right - pText->m_Left;
677     minchars += pText->m_Text.GetLength() + 1;
678   }
679 }
680 #define PI 3.1415926535897932384626433832795
681 static void CheckRotate(CPDF_Page& page, CFX_FloatRect& page_bbox) {
682   int total_count = 0, rotated_count[3] = {0, 0, 0};
683   FX_POSITION pos = page.GetFirstObjectPosition();
684   while (pos) {
685     CPDF_PageObject* pObj = page.GetNextObject(pos);
686     if (pObj->m_Type != PDFPAGE_TEXT) {
687       continue;
688     }
689     total_count++;
690     CPDF_TextObject* pText = (CPDF_TextObject*)pObj;
691     FX_FLOAT angle = pText->m_TextState.GetBaselineAngle();
692     if (angle == 0.0) {
693       continue;
694     }
695     int degree = (int)(angle * 180 / PI + 0.5);
696     if (degree % 90) {
697       continue;
698     }
699     if (degree < 0) {
700       degree += 360;
701     }
702     int index = degree / 90 % 3 - 1;
703     if (index < 0) {
704       continue;
705     }
706     rotated_count[index]++;
707   }
708   if (total_count == 0) {
709     return;
710   }
711   CFX_AffineMatrix matrix;
712   if (rotated_count[0] > total_count * 2 / 3) {
713     matrix.Set(0, -1, 1, 0, 0, page.GetPageHeight());
714   } else if (rotated_count[1] > total_count * 2 / 3) {
715     matrix.Set(-1, 0, 0, -1, page.GetPageWidth(), page.GetPageHeight());
716   } else if (rotated_count[2] > total_count * 2 / 3) {
717     matrix.Set(0, 1, -1, 0, page.GetPageWidth(), 0);
718   } else {
719     return;
720   }
721   page.Transform(matrix);
722   page_bbox.Transform(&matrix);
723 }
724 void PDF_GetPageText_Unicode(CFX_WideStringArray& lines,
725                              CPDF_Document* pDoc,
726                              CPDF_Dictionary* pPage,
727                              int iMinWidth,
728                              FX_DWORD flags) {
729   lines.RemoveAll();
730   if (pPage == NULL) {
731     return;
732   }
733   CPDF_Page page;
734   page.Load(pDoc, pPage);
735   CPDF_ParseOptions options;
736   options.m_bTextOnly = TRUE;
737   options.m_bSeparateForm = FALSE;
738   page.ParseContent(&options);
739   CFX_FloatRect page_bbox = page.GetPageBBox();
740   if (flags & PDF2TXT_AUTO_ROTATE) {
741     CheckRotate(page, page_bbox);
742   }
743   CTextPage texts;
744   texts.m_bAutoWidth = flags & PDF2TXT_AUTO_WIDTH;
745   texts.m_bKeepColumn = flags & PDF2TXT_KEEP_COLUMN;
746   texts.m_bBreakSpace = TRUE;
747   FX_POSITION pos = page.GetFirstObjectPosition();
748   while (pos) {
749     CPDF_PageObject* pObject = page.GetNextObject(pos);
750     if (!(flags & PDF2TXT_INCLUDE_INVISIBLE)) {
751       CFX_FloatRect rect(pObject->m_Left, pObject->m_Bottom, pObject->m_Right,
752                          pObject->m_Top);
753       if (!page_bbox.Contains(rect)) {
754         continue;
755       }
756     }
757     texts.ProcessObject(pObject);
758   }
759   texts.WriteOutput(lines, iMinWidth);
760 }
761 void PDF_GetPageText(CFX_ByteStringArray& lines,
762                      CPDF_Document* pDoc,
763                      CPDF_Dictionary* pPage,
764                      int iMinWidth,
765                      FX_DWORD flags) {
766   lines.RemoveAll();
767   CFX_WideStringArray wlines;
768   PDF_GetPageText_Unicode(wlines, pDoc, pPage, iMinWidth, flags);
769   for (int i = 0; i < wlines.GetSize(); i++) {
770     CFX_WideString wstr = wlines[i];
771     CFX_ByteString str;
772     for (int c = 0; c < wstr.GetLength(); c++) {
773       str += CharFromUnicodeAlt(wstr[c], FXSYS_GetACP(), "?");
774     }
775     lines.Add(str);
776   }
777 }
778 void PDF_GetTextStream_Unicode(CFX_WideTextBuf& buffer,
779                                CPDF_Document* pDoc,
780                                CPDF_Dictionary* pPage,
781                                FX_DWORD flags) {
782   buffer.EstimateSize(0, 10240);
783   CPDF_Page page;
784   page.Load(pDoc, pPage);
785   CPDF_ParseOptions options;
786   options.m_bTextOnly = TRUE;
787   options.m_bSeparateForm = FALSE;
788   page.ParseContent(&options);
789   GetTextStream_Unicode(buffer, &page, TRUE, NULL);
790 }