FX Bool considered harmful, part 3
[pdfium.git] / core / src / fpdftext / fpdf_text_search.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 "../../include/fpdfapi/fpdf_pageobj.h"
8 #include "../../include/fpdfapi/fpdf_page.h"
9 class CPDF_TextStream
10 {
11 public:
12     CPDF_TextStream(CFX_WideTextBuf& buffer, bool bUseLF, CFX_PtrArray* pObjArray);
13     ~CPDF_TextStream() {}
14     bool ProcessObject(const CPDF_TextObject* pObj, bool bFirstLine);
15     CFX_WideTextBuf&    m_Buffer;
16     bool                                m_bUseLF;
17     CFX_PtrArray*               m_pObjArray;
18     const CPDF_TextObject*      m_pLastObj;
19 };
20 CPDF_TextStream::CPDF_TextStream(CFX_WideTextBuf& buffer, bool bUseLF, CFX_PtrArray* pObjArray) : m_Buffer(buffer)
21 {
22     m_pLastObj = NULL;
23     m_bUseLF = bUseLF;
24     m_pObjArray = pObjArray;
25 }
26 bool FPDFText_IsSameTextObject(const CPDF_TextObject* pTextObj1, const CPDF_TextObject* pTextObj2)
27 {
28     if (!pTextObj1 || !pTextObj2) {
29         return false;
30     }
31     CFX_FloatRect rcPreObj(pTextObj2->m_Left, pTextObj2->m_Bottom, pTextObj2->m_Right, pTextObj2->m_Top);
32     CFX_FloatRect rcCurObj(pTextObj1->m_Left, pTextObj1->m_Bottom, pTextObj1->m_Right, pTextObj1->m_Top);
33     if (rcPreObj.IsEmpty() && rcCurObj.IsEmpty()) {
34         return true;
35     }
36     if (!rcPreObj.IsEmpty() || !rcCurObj.IsEmpty()) {
37         rcPreObj.Intersect(rcCurObj);
38         if (rcPreObj.IsEmpty()) {
39             return false;
40         }
41         if (FXSYS_fabs(rcPreObj.Width() - rcCurObj.Width()) > rcCurObj.Width() / 2) {
42             return false;
43         }
44         if (pTextObj2->GetFontSize() != pTextObj1->GetFontSize()) {
45             return false;
46         }
47     }
48     int nPreCount = pTextObj2->CountItems();
49     int nCurCount = pTextObj1->CountItems();
50     if (nPreCount != nCurCount) {
51         return false;
52     }
53     for (int i = 0; i < nPreCount; i++) {
54         CPDF_TextObjectItem itemPer, itemCur;
55         pTextObj2->GetItemInfo(i, &itemPer);
56         pTextObj1->GetItemInfo(i, &itemCur);
57         if (itemCur.m_CharCode != itemPer.m_CharCode) {
58             return false;
59         }
60     }
61     return true;
62 }
63 int GetCharWidth(FX_DWORD charCode, CPDF_Font* pFont)
64 {
65     if(charCode == -1) {
66         return 0;
67     }
68     int w = pFont->GetCharWidthF(charCode);
69     if(w == 0) {
70         CFX_ByteString str;
71         pFont->AppendChar(str, charCode);
72         w = pFont->GetStringWidth(str, 1);
73         if(w == 0) {
74             FX_RECT BBox;
75             pFont->GetCharBBox(charCode, BBox);
76             w = BBox.right - BBox.left;
77         }
78     }
79     return w;
80 }
81 int FPDFText_ProcessInterObj(const CPDF_TextObject* pPrevObj, const CPDF_TextObject* pObj)
82 {
83     if(FPDFText_IsSameTextObject(pPrevObj, pObj)) {
84         return -1;
85     }
86     CPDF_TextObjectItem item;
87     int nItem = pPrevObj->CountItems();
88     pPrevObj->GetItemInfo(nItem - 1, &item);
89     FX_WCHAR preChar = 0, curChar = 0;
90     CFX_WideString wstr = pPrevObj->GetFont()->UnicodeFromCharCode(item.m_CharCode);
91     if(wstr.GetLength()) {
92         preChar = wstr.GetAt(0);
93     }
94     FX_FLOAT last_pos = item.m_OriginX;
95     int nLastWidth = GetCharWidth(item.m_CharCode, pPrevObj->GetFont());
96     FX_FLOAT last_width = nLastWidth * pPrevObj->GetFontSize() / 1000;
97     last_width = FXSYS_fabs(last_width);
98     pObj->GetItemInfo(0, &item);
99     wstr = pObj->GetFont()->UnicodeFromCharCode(item.m_CharCode);
100     if(wstr.GetLength()) {
101         curChar = wstr.GetAt(0);
102     }
103     int nThisWidth = GetCharWidth(item.m_CharCode, pObj->GetFont());
104     FX_FLOAT this_width = nThisWidth * pObj->GetFontSize() / 1000;
105     this_width = FXSYS_fabs(this_width);
106     FX_FLOAT threshold = last_width > this_width ? last_width / 4 : this_width / 4;
107     CFX_AffineMatrix prev_matrix, prev_reverse;
108     pPrevObj->GetTextMatrix(&prev_matrix);
109     prev_reverse.SetReverse(prev_matrix);
110     FX_FLOAT x = pObj->GetPosX(), y = pObj->GetPosY();
111     prev_reverse.Transform(x, y);
112     if (FXSYS_fabs(y) > threshold * 2) {
113         return 2;
114     }
115     threshold = (FX_FLOAT)(nLastWidth > nThisWidth ? nLastWidth : nThisWidth);
116     threshold = threshold > 400 ? (threshold < 700 ? threshold / 4 :  threshold / 5) : (threshold / 2);
117     threshold *= nLastWidth > nThisWidth ? FXSYS_fabs(pPrevObj->GetFontSize()) : FXSYS_fabs(pObj->GetFontSize());
118     threshold /= 1000;
119     if (FXSYS_fabs(last_pos + last_width - x) > threshold && curChar != L' ' && preChar != L' ')
120         if(curChar != L' ' && preChar != L' ') {
121             if((x - last_pos - last_width) > threshold || (last_pos - x - last_width) > threshold) {
122                 return 1;
123             }
124             if(x < 0 && (last_pos - x - last_width) > threshold) {
125                 return 1;
126             }
127             if((x - last_pos - last_width) > this_width || (x - last_pos - this_width) > last_width ) {
128                 return 1;
129             }
130         }
131     if(last_pos + last_width > x + this_width && curChar == L' ') {
132         return 3;
133     }
134     return 0;
135 }
136 bool CPDF_TextStream::ProcessObject(const CPDF_TextObject* pObj, bool bFirstLine)
137 {
138     CPDF_Font* pFont = pObj->GetFont();
139     CFX_AffineMatrix matrix;
140     pObj->GetTextMatrix(&matrix);
141     int item_index = 0;
142     if (m_pLastObj) {
143         int result = FPDFText_ProcessInterObj(m_pLastObj, pObj);
144         if (result == 2) {
145             int len = m_Buffer.GetLength();
146             if (len && m_bUseLF && m_Buffer.GetBuffer()[len - 1] == L'-') {
147                 m_Buffer.Delete(len - 1, 1);
148                 if (m_pObjArray) {
149                     m_pObjArray->RemoveAt((len - 1) * 2, 2);
150                 }
151             } else {
152                 if (bFirstLine) {
153                     return true;
154                 }
155                 if (m_bUseLF) {
156                     m_Buffer.AppendChar(L'\r');
157                     m_Buffer.AppendChar(L'\n');
158                     if (m_pObjArray) {
159                         for (int i = 0; i < 4; i ++) {
160                             m_pObjArray->Add(NULL);
161                         }
162                     }
163                 } else {
164                     m_Buffer.AppendChar(' ');
165                     if (m_pObjArray) {
166                         m_pObjArray->Add(NULL);
167                         m_pObjArray->Add(NULL);
168                     }
169                 }
170             }
171         } else if (result == 1) {
172             m_Buffer.AppendChar(L' ');
173             if (m_pObjArray) {
174                 m_pObjArray->Add(NULL);
175                 m_pObjArray->Add(NULL);
176             }
177         } else if (result == -1) {
178             m_pLastObj = pObj;
179             return false;
180         } else if (result == 3) {
181             item_index = 1;
182         }
183     }
184     m_pLastObj = pObj;
185     int nItems = pObj->CountItems();
186     FX_FLOAT Ignorekerning = 0;
187     for(int i = 1; i < nItems - 1; i += 2) {
188         CPDF_TextObjectItem item;
189         pObj->GetItemInfo(i, &item);
190         if (item.m_CharCode == (FX_DWORD) - 1) {
191             if(i == 1) {
192                 Ignorekerning = item.m_OriginX;
193             } else if(Ignorekerning > item.m_OriginX) {
194                 Ignorekerning = item.m_OriginX;
195             }
196         } else {
197             Ignorekerning = 0;
198             break;
199         }
200     }
201     FX_FLOAT spacing = 0;
202     for (; item_index < nItems; item_index ++) {
203         CPDF_TextObjectItem item;
204         pObj->GetItemInfo(item_index, &item);
205         if (item.m_CharCode == (FX_DWORD) - 1) {
206             CFX_WideString wstr = m_Buffer.GetWideString();
207             if (wstr.IsEmpty() || wstr.GetAt(wstr.GetLength() - 1) == L' ') {
208                 continue;
209             }
210             FX_FLOAT fontsize_h = pObj->m_TextState.GetFontSizeH();
211             spacing = -fontsize_h * (item.m_OriginX - Ignorekerning) / 1000;
212             continue;
213         }
214         FX_FLOAT charSpace = pObj->m_TextState.GetObject()->m_CharSpace;
215         if(nItems > 3 && !spacing) {
216             charSpace = 0;
217         }
218         if((spacing || charSpace) && item_index > 0) {
219             int last_width = 0;
220             FX_FLOAT fontsize_h = pObj->m_TextState.GetFontSizeH();
221             FX_DWORD space_charcode = pFont->CharCodeFromUnicode(' ');
222             FX_FLOAT threshold = 0;
223             if (space_charcode != -1) {
224                 threshold = fontsize_h * pFont->GetCharWidthF(space_charcode) / 1000 ;
225             }
226             if(threshold > fontsize_h / 3) {
227                 threshold = 0;
228             } else {
229                 threshold /= 2;
230             }
231             if (threshold == 0) {
232                 threshold = fontsize_h;
233                 int this_width = FXSYS_abs(GetCharWidth(item.m_CharCode, pFont));
234                 threshold = this_width > last_width ? (FX_FLOAT)this_width : (FX_FLOAT)last_width;
235                 int nDivide = 6;
236                 if (threshold < 300) {
237                     nDivide = 2;
238                 } else if (threshold < 500) {
239                     nDivide = 4;
240                 } else if (threshold < 700) {
241                     nDivide = 5;
242                 }
243                 threshold = threshold / nDivide;
244                 threshold = fontsize_h * threshold / 1000;
245             }
246             if(charSpace > 0.001) {
247                 spacing += matrix.TransformDistance(charSpace);
248             } else if(charSpace < -0.001) {
249                 spacing -= matrix.TransformDistance(FXSYS_fabs(charSpace));
250             }
251             if (threshold && (spacing && spacing >= threshold) ) {
252                 m_Buffer.AppendChar(L' ');
253                 if (m_pObjArray) {
254                     m_pObjArray->Add(NULL);
255                     m_pObjArray->Add(NULL);
256                 }
257             }
258             if (item.m_CharCode == (FX_DWORD) - 1) {
259                 continue;
260             }
261             spacing = 0;
262         }
263         CFX_WideString unicode_str = pFont->UnicodeFromCharCode(item.m_CharCode);
264         if (unicode_str.IsEmpty()) {
265             m_Buffer.AppendChar((FX_WCHAR)item.m_CharCode);
266             if (m_pObjArray) {
267                 m_pObjArray->Add((void*)pObj);
268                 m_pObjArray->Add((void*)(intptr_t)item_index);
269             }
270         } else {
271             m_Buffer << unicode_str;
272             if (m_pObjArray) {
273                 for (int i = 0; i < unicode_str.GetLength(); i ++) {
274                     m_pObjArray->Add((void*)pObj);
275                     m_pObjArray->Add((void*)(intptr_t)item_index);
276                 }
277             }
278         }
279     }
280     return false;
281 }
282 void _PDF_GetTextStream_Unicode(CFX_WideTextBuf& buffer, CPDF_PageObjects* pPage, bool bUseLF,
283                                 CFX_PtrArray* pObjArray)
284 {
285     CPDF_TextStream textstream(buffer, bUseLF, pObjArray);
286     FX_POSITION pos = pPage->GetFirstObjectPosition();
287     while (pos) {
288         CPDF_PageObject* pObject = pPage->GetNextObject(pos);
289         if (pObject == NULL) {
290             continue;
291         }
292         if (pObject->m_Type != PDFPAGE_TEXT) {
293             continue;
294         }
295         textstream.ProcessObject((CPDF_TextObject*)pObject, false);
296     }
297 }
298 CFX_WideString PDF_GetFirstTextLine_Unicode(CPDF_Document* pDoc, CPDF_Dictionary* pPage)
299 {
300     CFX_WideTextBuf buffer;
301     buffer.EstimateSize(0, 1024);
302     CPDF_Page page;
303     page.Load(pDoc, pPage);
304     CPDF_ParseOptions options;
305     options.m_bTextOnly = true;
306     options.m_bSeparateForm = false;
307     page.ParseContent(&options);
308     CPDF_TextStream textstream(buffer, false, NULL);
309     FX_POSITION pos = page.GetFirstObjectPosition();
310     while (pos) {
311         CPDF_PageObject* pObject = page.GetNextObject(pos);
312         if (pObject->m_Type != PDFPAGE_TEXT) {
313             continue;
314         }
315         if (textstream.ProcessObject((CPDF_TextObject*)pObject, true)) {
316             break;
317         }
318     }
319     return buffer.GetWideString();
320 }