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