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