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