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