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 "xfa/fxfa/parser/cscript_layoutpseudomodel.h"
8
9#include <set>
10
11#include "fxjs/cfxjse_arguments.h"
12#include "third_party/base/stl_util.h"
13#include "xfa/fxfa/app/xfa_ffnotify.h"
14#include "xfa/fxfa/parser/cxfa_containerlayoutitem.h"
15#include "xfa/fxfa/parser/cxfa_contentlayoutitem.h"
16#include "xfa/fxfa/parser/cxfa_document.h"
17#include "xfa/fxfa/parser/cxfa_layoutitem.h"
18#include "xfa/fxfa/parser/cxfa_layoutprocessor.h"
19#include "xfa/fxfa/parser/cxfa_measurement.h"
20#include "xfa/fxfa/parser/cxfa_scriptcontext.h"
21#include "xfa/fxfa/parser/cxfa_traversestrategy_contentlayoutitem.h"
22#include "xfa/fxfa/parser/xfa_localemgr.h"
23#include "xfa/fxfa/parser/xfa_object.h"
24#include "xfa/fxfa/parser/xfa_utils.h"
25
26CScript_LayoutPseudoModel::CScript_LayoutPseudoModel(CXFA_Document* pDocument)
27    : CXFA_Object(pDocument,
28                  XFA_ObjectType::Object,
29                  XFA_Element::LayoutPseudoModel,
30                  CFX_WideStringC(L"layoutPseudoModel")) {}
31
32CScript_LayoutPseudoModel::~CScript_LayoutPseudoModel() {}
33
34void CScript_LayoutPseudoModel::Ready(CFXJSE_Value* pValue,
35                                      bool bSetting,
36                                      XFA_ATTRIBUTE eAttribute) {
37  CXFA_FFNotify* pNotify = m_pDocument->GetNotify();
38  if (!pNotify) {
39    return;
40  }
41  if (bSetting) {
42    ThrowSetReadyException();
43    return;
44  }
45  int32_t iStatus = pNotify->GetLayoutStatus();
46  pValue->SetBoolean(iStatus >= 2);
47}
48
49void CScript_LayoutPseudoModel::HWXY(CFXJSE_Arguments* pArguments,
50                                     XFA_LAYOUTMODEL_HWXY layoutModel) {
51  int32_t iLength = pArguments->GetLength();
52  if (iLength < 1 || iLength > 3) {
53    const FX_WCHAR* methodName = nullptr;
54    switch (layoutModel) {
55      case XFA_LAYOUTMODEL_H:
56        methodName = L"h";
57        break;
58      case XFA_LAYOUTMODEL_W:
59        methodName = L"w";
60        break;
61      case XFA_LAYOUTMODEL_X:
62        methodName = L"x";
63        break;
64      case XFA_LAYOUTMODEL_Y:
65        methodName = L"y";
66        break;
67    }
68    ThrowParamCountMismatchException(methodName);
69    return;
70  }
71  CXFA_Node* pNode = nullptr;
72  CFX_WideString wsUnit(L"pt");
73  int32_t iIndex = 0;
74  if (iLength >= 1) {
75    pNode = static_cast<CXFA_Node*>(pArguments->GetObject(0));
76  }
77  if (iLength >= 2) {
78    CFX_ByteString bsUnit = pArguments->GetUTF8String(1);
79    if (!bsUnit.IsEmpty()) {
80      wsUnit = CFX_WideString::FromUTF8(bsUnit.AsStringC());
81    }
82  }
83  if (iLength >= 3) {
84    iIndex = pArguments->GetInt32(2);
85  }
86  if (!pNode) {
87    return;
88  }
89  CXFA_LayoutProcessor* pDocLayout = m_pDocument->GetDocLayout();
90  if (!pDocLayout) {
91    return;
92  }
93
94  CXFA_Measurement measure;
95  CXFA_LayoutItem* pLayoutItem = pDocLayout->GetLayoutItem(pNode);
96  if (!pLayoutItem) {
97    return;
98  }
99  while (iIndex > 0 && pLayoutItem) {
100    pLayoutItem = pLayoutItem->GetNext();
101    iIndex--;
102  }
103  CFXJSE_Value* pValue = pArguments->GetReturnValue();
104  if (!pLayoutItem) {
105    pValue->SetFloat(0);
106    return;
107  }
108
109  CFX_RectF rtRect = pLayoutItem->GetRect(true);
110  switch (layoutModel) {
111    case XFA_LAYOUTMODEL_H:
112      measure.Set(rtRect.height, XFA_UNIT_Pt);
113      break;
114    case XFA_LAYOUTMODEL_W:
115      measure.Set(rtRect.width, XFA_UNIT_Pt);
116      break;
117    case XFA_LAYOUTMODEL_X:
118      measure.Set(rtRect.left, XFA_UNIT_Pt);
119      break;
120    case XFA_LAYOUTMODEL_Y:
121      measure.Set(rtRect.top, XFA_UNIT_Pt);
122      break;
123  }
124  XFA_UNIT unit = measure.GetUnit(wsUnit.AsStringC());
125  FX_FLOAT fValue = measure.ToUnit(unit);
126  fValue = FXSYS_round(fValue * 1000) / 1000.0f;
127  if (pValue)
128    pValue->SetFloat(fValue);
129}
130
131void CScript_LayoutPseudoModel::H(CFXJSE_Arguments* pArguments) {
132  HWXY(pArguments, XFA_LAYOUTMODEL_H);
133}
134
135void CScript_LayoutPseudoModel::W(CFXJSE_Arguments* pArguments) {
136  HWXY(pArguments, XFA_LAYOUTMODEL_W);
137}
138
139void CScript_LayoutPseudoModel::X(CFXJSE_Arguments* pArguments) {
140  HWXY(pArguments, XFA_LAYOUTMODEL_X);
141}
142
143void CScript_LayoutPseudoModel::Y(CFXJSE_Arguments* pArguments) {
144  HWXY(pArguments, XFA_LAYOUTMODEL_Y);
145}
146
147void CScript_LayoutPseudoModel::NumberedPageCount(CFXJSE_Arguments* pArguments,
148                                                  bool bNumbered) {
149  CXFA_LayoutProcessor* pDocLayout = m_pDocument->GetDocLayout();
150  if (!pDocLayout) {
151    return;
152  }
153  int32_t iPageCount = 0;
154  int32_t iPageNum = pDocLayout->CountPages();
155  if (bNumbered) {
156    for (int32_t i = 0; i < iPageNum; i++) {
157      CXFA_ContainerLayoutItem* pLayoutPage = pDocLayout->GetPage(i);
158      if (!pLayoutPage) {
159        continue;
160      }
161      CXFA_Node* pMasterPage = pLayoutPage->GetMasterPage();
162      if (pMasterPage->GetInteger(XFA_ATTRIBUTE_Numbered)) {
163        iPageCount++;
164      }
165    }
166  } else {
167    iPageCount = iPageNum;
168  }
169  CFXJSE_Value* pValue = pArguments->GetReturnValue();
170  if (pValue)
171    pValue->SetInteger(iPageCount);
172}
173
174void CScript_LayoutPseudoModel::PageCount(CFXJSE_Arguments* pArguments) {
175  NumberedPageCount(pArguments, true);
176}
177
178void CScript_LayoutPseudoModel::PageSpan(CFXJSE_Arguments* pArguments) {
179  int32_t iLength = pArguments->GetLength();
180  if (iLength != 1) {
181    ThrowParamCountMismatchException(L"pageSpan");
182    return;
183  }
184  CXFA_Node* pNode = nullptr;
185  if (iLength >= 1) {
186    pNode = static_cast<CXFA_Node*>(pArguments->GetObject(0));
187  }
188  if (!pNode) {
189    return;
190  }
191  CXFA_LayoutProcessor* pDocLayout = m_pDocument->GetDocLayout();
192  if (!pDocLayout) {
193    return;
194  }
195  CFXJSE_Value* pValue = pArguments->GetReturnValue();
196  CXFA_LayoutItem* pLayoutItem = pDocLayout->GetLayoutItem(pNode);
197  if (!pLayoutItem) {
198    pValue->SetInteger(-1);
199    return;
200  }
201  int32_t iLast = pLayoutItem->GetLast()->GetPage()->GetPageIndex();
202  int32_t iFirst = pLayoutItem->GetFirst()->GetPage()->GetPageIndex();
203  int32_t iPageSpan = iLast - iFirst + 1;
204  if (pValue)
205    pValue->SetInteger(iPageSpan);
206}
207
208void CScript_LayoutPseudoModel::Page(CFXJSE_Arguments* pArguments) {
209  PageImp(pArguments, false);
210}
211
212void CScript_LayoutPseudoModel::GetObjArray(CXFA_LayoutProcessor* pDocLayout,
213                                            int32_t iPageNo,
214                                            const CFX_WideString& wsType,
215                                            bool bOnPageArea,
216                                            CXFA_NodeArray& retArray) {
217  CXFA_ContainerLayoutItem* pLayoutPage = pDocLayout->GetPage(iPageNo);
218  if (!pLayoutPage) {
219    return;
220  }
221  if (wsType == L"pageArea") {
222    if (CXFA_Node* pMasterPage = pLayoutPage->m_pFormNode) {
223      retArray.Add(pMasterPage);
224    }
225    return;
226  }
227  if (wsType == L"contentArea") {
228    for (CXFA_LayoutItem* pItem = pLayoutPage->m_pFirstChild; pItem;
229         pItem = pItem->m_pNextSibling) {
230      if (pItem->m_pFormNode->GetElementType() == XFA_Element::ContentArea) {
231        retArray.Add(pItem->m_pFormNode);
232      }
233    }
234    return;
235  }
236  std::set<CXFA_Node*> formItems;
237  if (wsType.IsEmpty()) {
238    if (CXFA_Node* pMasterPage = pLayoutPage->m_pFormNode) {
239      retArray.Add(pMasterPage);
240    }
241    for (CXFA_LayoutItem* pItem = pLayoutPage->m_pFirstChild; pItem;
242         pItem = pItem->m_pNextSibling) {
243      if (pItem->m_pFormNode->GetElementType() == XFA_Element::ContentArea) {
244        retArray.Add(pItem->m_pFormNode);
245        if (!bOnPageArea) {
246          CXFA_NodeIteratorTemplate<CXFA_ContentLayoutItem,
247                                    CXFA_TraverseStrategy_ContentLayoutItem>
248          iterator(static_cast<CXFA_ContentLayoutItem*>(pItem->m_pFirstChild));
249          for (CXFA_ContentLayoutItem* pItemChild = iterator.GetCurrent();
250               pItemChild; pItemChild = iterator.MoveToNext()) {
251            if (!pItemChild->IsContentLayoutItem()) {
252              continue;
253            }
254            XFA_Element eType = pItemChild->m_pFormNode->GetElementType();
255            if (eType != XFA_Element::Field && eType != XFA_Element::Draw &&
256                eType != XFA_Element::Subform && eType != XFA_Element::Area) {
257              continue;
258            }
259            if (pdfium::ContainsValue(formItems, pItemChild->m_pFormNode))
260              continue;
261
262            formItems.insert(pItemChild->m_pFormNode);
263            retArray.Add(pItemChild->m_pFormNode);
264          }
265        }
266      } else {
267        if (bOnPageArea) {
268          CXFA_NodeIteratorTemplate<CXFA_ContentLayoutItem,
269                                    CXFA_TraverseStrategy_ContentLayoutItem>
270          iterator(static_cast<CXFA_ContentLayoutItem*>(pItem));
271          for (CXFA_ContentLayoutItem* pItemChild = iterator.GetCurrent();
272               pItemChild; pItemChild = iterator.MoveToNext()) {
273            if (!pItemChild->IsContentLayoutItem()) {
274              continue;
275            }
276            XFA_Element eType = pItemChild->m_pFormNode->GetElementType();
277            if (eType != XFA_Element::Field && eType != XFA_Element::Draw &&
278                eType != XFA_Element::Subform && eType != XFA_Element::Area) {
279              continue;
280            }
281            if (pdfium::ContainsValue(formItems, pItemChild->m_pFormNode))
282              continue;
283            formItems.insert(pItemChild->m_pFormNode);
284            retArray.Add(pItemChild->m_pFormNode);
285          }
286        }
287      }
288    }
289    return;
290  }
291  XFA_Element eType = XFA_Element::Unknown;
292  if (wsType == L"field") {
293    eType = XFA_Element::Field;
294  } else if (wsType == L"draw") {
295    eType = XFA_Element::Draw;
296  } else if (wsType == L"subform") {
297    eType = XFA_Element::Subform;
298  } else if (wsType == L"area") {
299    eType = XFA_Element::Area;
300  }
301  if (eType != XFA_Element::Unknown) {
302    for (CXFA_LayoutItem* pItem = pLayoutPage->m_pFirstChild; pItem;
303         pItem = pItem->m_pNextSibling) {
304      if (pItem->m_pFormNode->GetElementType() == XFA_Element::ContentArea) {
305        if (!bOnPageArea) {
306          CXFA_NodeIteratorTemplate<CXFA_ContentLayoutItem,
307                                    CXFA_TraverseStrategy_ContentLayoutItem>
308          iterator(static_cast<CXFA_ContentLayoutItem*>(pItem->m_pFirstChild));
309          for (CXFA_ContentLayoutItem* pItemChild = iterator.GetCurrent();
310               pItemChild; pItemChild = iterator.MoveToNext()) {
311            if (!pItemChild->IsContentLayoutItem())
312              continue;
313            if (pItemChild->m_pFormNode->GetElementType() != eType)
314              continue;
315            if (pdfium::ContainsValue(formItems, pItemChild->m_pFormNode))
316              continue;
317            formItems.insert(pItemChild->m_pFormNode);
318            retArray.Add(pItemChild->m_pFormNode);
319          }
320        }
321      } else {
322        if (bOnPageArea) {
323          CXFA_NodeIteratorTemplate<CXFA_ContentLayoutItem,
324                                    CXFA_TraverseStrategy_ContentLayoutItem>
325          iterator(static_cast<CXFA_ContentLayoutItem*>(pItem));
326          for (CXFA_ContentLayoutItem* pItemChild = iterator.GetCurrent();
327               pItemChild; pItemChild = iterator.MoveToNext()) {
328            if (!pItemChild->IsContentLayoutItem())
329              continue;
330            if (pItemChild->m_pFormNode->GetElementType() != eType)
331              continue;
332            if (pdfium::ContainsValue(formItems, pItemChild->m_pFormNode))
333              continue;
334            formItems.insert(pItemChild->m_pFormNode);
335            retArray.Add(pItemChild->m_pFormNode);
336          }
337        }
338      }
339    }
340    return;
341  }
342}
343
344void CScript_LayoutPseudoModel::PageContent(CFXJSE_Arguments* pArguments) {
345  int32_t iLength = pArguments->GetLength();
346  if (iLength < 1 || iLength > 3) {
347    ThrowParamCountMismatchException(L"pageContent");
348    return;
349  }
350  int32_t iIndex = 0;
351  CFX_WideString wsType;
352  bool bOnPageArea = false;
353  if (iLength >= 1) {
354    iIndex = pArguments->GetInt32(0);
355  }
356  if (iLength >= 2) {
357    CFX_ByteString bsType = pArguments->GetUTF8String(1);
358    wsType = CFX_WideString::FromUTF8(bsType.AsStringC());
359  }
360  if (iLength >= 3) {
361    bOnPageArea = pArguments->GetInt32(2) == 0 ? false : true;
362  }
363  CXFA_FFNotify* pNotify = m_pDocument->GetNotify();
364  if (!pNotify) {
365    return;
366  }
367  CXFA_LayoutProcessor* pDocLayout = m_pDocument->GetDocLayout();
368  if (!pDocLayout) {
369    return;
370  }
371  CXFA_NodeArray retArray;
372  GetObjArray(pDocLayout, iIndex, wsType, bOnPageArea, retArray);
373  CXFA_ArrayNodeList* pArrayNodeList = new CXFA_ArrayNodeList(m_pDocument);
374  pArrayNodeList->SetArrayNodeList(retArray);
375  pArguments->GetReturnValue()->SetObject(
376      pArrayNodeList, m_pDocument->GetScriptContext()->GetJseNormalClass());
377}
378
379void CScript_LayoutPseudoModel::AbsPageCount(CFXJSE_Arguments* pArguments) {
380  NumberedPageCount(pArguments, false);
381}
382
383void CScript_LayoutPseudoModel::AbsPageCountInBatch(
384    CFXJSE_Arguments* pArguments) {
385  CFXJSE_Value* pValue = pArguments->GetReturnValue();
386  if (pValue)
387    pValue->SetInteger(0);
388}
389
390void CScript_LayoutPseudoModel::SheetCountInBatch(
391    CFXJSE_Arguments* pArguments) {
392  CFXJSE_Value* pValue = pArguments->GetReturnValue();
393  if (pValue)
394    pValue->SetInteger(0);
395}
396
397void CScript_LayoutPseudoModel::Relayout(CFXJSE_Arguments* pArguments) {
398  CXFA_Node* pRootNode = m_pDocument->GetRoot();
399  CXFA_Node* pFormRoot = pRootNode->GetFirstChildByClass(XFA_Element::Form);
400  ASSERT(pFormRoot);
401  CXFA_Node* pContentRootNode = pFormRoot->GetNodeItem(XFA_NODEITEM_FirstChild);
402  CXFA_LayoutProcessor* pLayoutProcessor = m_pDocument->GetLayoutProcessor();
403  if (pContentRootNode) {
404    pLayoutProcessor->AddChangedContainer(pContentRootNode);
405  }
406  pLayoutProcessor->SetForceReLayout(true);
407}
408
409void CScript_LayoutPseudoModel::AbsPageSpan(CFXJSE_Arguments* pArguments) {
410  PageSpan(pArguments);
411}
412
413void CScript_LayoutPseudoModel::AbsPageInBatch(CFXJSE_Arguments* pArguments) {
414  if (pArguments->GetLength() != 1) {
415    ThrowParamCountMismatchException(L"absPageInBatch");
416    return;
417  }
418
419  CFXJSE_Value* pValue = pArguments->GetReturnValue();
420  if (pValue)
421    pValue->SetInteger(0);
422}
423
424void CScript_LayoutPseudoModel::SheetInBatch(CFXJSE_Arguments* pArguments) {
425  if (pArguments->GetLength() != 1) {
426    ThrowParamCountMismatchException(L"sheetInBatch");
427    return;
428  }
429
430  CFXJSE_Value* pValue = pArguments->GetReturnValue();
431  if (pValue)
432    pValue->SetInteger(0);
433}
434
435void CScript_LayoutPseudoModel::Sheet(CFXJSE_Arguments* pArguments) {
436  PageImp(pArguments, true);
437}
438
439void CScript_LayoutPseudoModel::RelayoutPageArea(CFXJSE_Arguments* pArguments) {
440}
441
442void CScript_LayoutPseudoModel::SheetCount(CFXJSE_Arguments* pArguments) {
443  NumberedPageCount(pArguments, false);
444}
445
446void CScript_LayoutPseudoModel::AbsPage(CFXJSE_Arguments* pArguments) {
447  PageImp(pArguments, true);
448}
449
450void CScript_LayoutPseudoModel::PageImp(CFXJSE_Arguments* pArguments,
451                                        bool bAbsPage) {
452  int32_t iLength = pArguments->GetLength();
453  if (iLength != 1) {
454    const FX_WCHAR* methodName;
455    if (bAbsPage) {
456      methodName = L"absPage";
457    } else {
458      methodName = L"page";
459    }
460    ThrowParamCountMismatchException(methodName);
461    return;
462  }
463  CXFA_Node* pNode = nullptr;
464  if (iLength >= 1) {
465    pNode = static_cast<CXFA_Node*>(pArguments->GetObject(0));
466  }
467  int32_t iPage = 0;
468  CFXJSE_Value* pValue = pArguments->GetReturnValue();
469  if (!pNode && pValue)
470    pValue->SetInteger(iPage);
471
472  CXFA_LayoutProcessor* pDocLayout = m_pDocument->GetDocLayout();
473  if (!pDocLayout) {
474    return;
475  }
476  CXFA_LayoutItem* pLayoutItem = pDocLayout->GetLayoutItem(pNode);
477  if (!pLayoutItem) {
478    pValue->SetInteger(-1);
479    return;
480  }
481  iPage = pLayoutItem->GetFirst()->GetPage()->GetPageIndex();
482  if (pValue)
483    pValue->SetInteger(bAbsPage ? iPage : iPage + 1);
484}
485
486void CScript_LayoutPseudoModel::ThrowSetReadyException() const {
487  ThrowException(L"Unable to set ready value.");
488}
489