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