1 /*
2  * This file is part of the LibreOffice project.
3  *
4  * This Source Code Form is subject to the terms of the Mozilla Public
5  * License, v. 2.0. If a copy of the MPL was not distributed with this
6  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
7  *
8  * This file incorporates work covered by the following license notice:
9  *
10  *   Licensed to the Apache Software Foundation (ASF) under one or more
11  *   contributor license agreements. See the NOTICE file distributed
12  *   with this work for additional information regarding copyright
13  *   ownership. The ASF licenses this file to you under the Apache
14  *   License, Version 2.0 (the "License"); you may not use this file
15  *   except in compliance with the License. You may obtain a copy of
16  *   the License at http://www.apache.org/licenses/LICENSE-2.0 .
17  */
18 
19 package org.libreoffice.report.pentaho.layoutprocessor;
20 
21 import org.libreoffice.report.OfficeToken;
22 import org.libreoffice.report.pentaho.OfficeNamespaces;
23 import org.libreoffice.report.pentaho.model.ImageElement;
24 
25 import java.util.logging.Logger;
26 
27 import org.jfree.layouting.util.AttributeMap;
28 import org.jfree.report.DataSourceException;
29 import org.jfree.report.JFreeReportInfo;
30 import org.jfree.report.ReportDataFactoryException;
31 import org.jfree.report.ReportProcessingException;
32 import org.jfree.report.data.GlobalMasterRow;
33 import org.jfree.report.data.ReportDataRow;
34 import org.jfree.report.expressions.FormulaExpression;
35 import org.jfree.report.flow.FlowController;
36 import org.jfree.report.flow.ReportTarget;
37 import org.jfree.report.flow.layoutprocessor.LayoutController;
38 import org.jfree.report.flow.layoutprocessor.LayoutControllerUtil;
39 import org.jfree.report.structure.Element;
40 import org.jfree.report.structure.Node;
41 import org.jfree.report.structure.Section;
42 import org.jfree.report.util.TextUtilities;
43 
44 import org.pentaho.reporting.libraries.base.util.ObjectUtilities;
45 import org.pentaho.reporting.libraries.formula.Formula;
46 import org.pentaho.reporting.libraries.formula.lvalues.LValue;
47 import org.pentaho.reporting.libraries.formula.parser.ParseException;
48 
49 /**
50  * Produces an image. The image-structures itself (draw:frame and so on) are not generated here. This element produces a
51  * place-holder element and relies on the output target to compute a sensible position for the element. The report
52  * definition does not give any hints about the size of the image, so we have to derive this from the surrounding
53  * context.
54  *
55  * @since 05.03.2007
56  */
57 public class ImageElementLayoutController
58         extends AbstractReportElementLayoutController
59 {
60 
61     private static final Logger LOGGER = Logger.getLogger(ImageElementLayoutController.class.getName());
62     private ImageElementContext context;
63 
64     @Override
delegateContentGeneration(final ReportTarget target)65     protected LayoutController delegateContentGeneration(final ReportTarget target)
66             throws ReportProcessingException, ReportDataFactoryException,
67             DataSourceException
68     {
69         final ImageElement imageElement = (ImageElement) getNode();
70         final FormulaExpression formulaExpression = imageElement.getFormula();
71         if (formulaExpression == null)
72         {
73             // A static image is easy. At least at this level. Don't ask about the weird things we have to do in the
74             // output targets...
75             final String linkTarget = imageElement.getImageData();
76             generateImage(target, linkTarget, imageElement.getScaleMode(), imageElement.isPreserveIRI());
77         }
78         else
79         {
80             final Object value =
81                     LayoutControllerUtil.evaluateExpression(getFlowController(), imageElement, formulaExpression);
82             generateImage(target, value, imageElement.getScaleMode(), imageElement.isPreserveIRI());
83         }
84         return join(getFlowController());
85     }
86 
generateImage(final ReportTarget target, final Object linkTarget, final String scale, final boolean preserveIri)87     private void generateImage(final ReportTarget target,
88             final Object linkTarget,
89             final String scale,
90             final boolean preserveIri)
91             throws ReportProcessingException, DataSourceException
92     {
93         if (linkTarget == null)
94         {
95             return;
96         }
97 
98         final AttributeMap image = new AttributeMap();
99         image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, Element.NAMESPACE_ATTRIBUTE, JFreeReportInfo.REPORT_NAMESPACE);
100         image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, Element.TYPE_ATTRIBUTE, OfficeToken.IMAGE);
101         image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, OfficeToken.SCALE, scale);
102         image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, OfficeToken.PRESERVE_IRI, String.valueOf(preserveIri));
103         image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, "image-context", createContext());
104         image.setAttribute(JFreeReportInfo.REPORT_NAMESPACE, OfficeToken.IMAGE_DATA, linkTarget);
105         target.startElement(image);
106         target.endElement(image);
107     }
108 
createContext()109     protected ImageElementContext createContext()
110     {
111         if (context == null)
112         {
113 
114             // Step 1: Find the parent cell.
115             final LayoutController cellController = findParentCell();
116             if (cellController == null)
117             {
118                 LOGGER.warning("Image is not contained in a table. Unable to calculate the image-size.");
119                 return null;
120             }
121             final Element tableCell = (Element) cellController.getNode();
122             final int rowSpan = TextUtilities.parseInt((String) tableCell.getAttribute(OfficeNamespaces.TABLE_NS, "number-rows-spanned"), 1);
123             final int colSpan = TextUtilities.parseInt((String) tableCell.getAttribute(OfficeNamespaces.TABLE_NS, "number-columns-spanned"), 1);
124             if (rowSpan < 1 || colSpan < 1)
125             {
126                 LOGGER.warning("Rowspan or colspan for image-size calculation was invalid.");
127                 return null;
128             }
129 
130             final LayoutController rowController = cellController.getParent();
131             if (rowController == null)
132             {
133                 LOGGER.warning("Table-Cell has no parent. Unable to calculate the image-size.");
134                 return null;
135             }
136             final Section tableRow = (Section) rowController.getNode();
137             // we are now making the assumption, that the row is a section, that contains the table-cell.
138             // This breaks the ability to return nodes or to construct reports on the fly, but the OO-report format
139             // is weird anyway and won't support such advanced techniques for the next few centuries...
140             final int columnPos = findNodeInSection(tableRow, tableCell, OfficeToken.COVERED_TABLE_CELL);
141             if (columnPos == -1)
142             {
143                 LOGGER.warning("Table-Cell is not a direct child of the table-row. Unable to calculate the image-size.");
144                 return null;
145             }
146 
147             final LayoutController tableController = rowController.getParent();
148             if (tableController == null)
149             {
150                 LOGGER.warning("Table-Row has no Table. Unable to calculate the image-size.");
151                 return null;
152             }
153 
154             final Section table = (Section) tableController.getNode();
155             // ok, we got a table, so as next we have to search for the columns now.
156             final Section columns = (Section) table.findFirstChild(OfficeNamespaces.TABLE_NS, OfficeToken.TABLE_COLUMNS);
157             if (columns.getNodeCount() <= columnPos + colSpan)
158             {
159                 // the colspan is too large. The table definition is therefore invalid. We do not try to fix this.
160                 LOGGER.warning(
161                         "The Table's defined columns do not match the col-span or col-position. Unable to calculate the image-size.");
162                 return null;
163             }
164 
165             final ImageElementContext context = new ImageElementContext(colSpan, rowSpan);
166             addColumnStyles(context, columns, columnPos, colSpan);
167             // finally search the styles for the row now.
168             final int rowPos = findNodeInSection(table, tableRow, null);
169             if (rowPos == -1)
170             {
171                 LOGGER.warning("Table-Cell is not a direct child of the table-row. Unable to calculate the image-size.");
172                 return null;
173             }
174 
175             addRowStyles(context, table, rowPos, rowSpan);
176             this.context = context;
177         }
178         return this.context;
179     }
180 
findNodeInSection(final Section tableRow, final Element tableCell, final String secondType)181     private int findNodeInSection(final Section tableRow,
182             final Element tableCell,
183             final String secondType)
184     {
185         int retval = 0;
186         final Node[] nodes = tableRow.getNodeArray();
187         final String namespace = tableCell.getNamespace();
188         final String type = tableCell.getType();
189         for (final Node node : nodes)
190         {
191             if (!(node instanceof Element))
192             {
193                 continue;
194             }
195             final Element child = (Element) node;
196             if (!ObjectUtilities.equal(child.getNamespace(), namespace) || (!ObjectUtilities.equal(child.getType(), type) && (secondType == null || !ObjectUtilities.equal(child.getType(), secondType))))
197             {
198                 continue;
199             }
200 
201             if (node == tableCell)
202             {
203                 return retval;
204             }
205             retval += 1;
206         }
207         return -1;
208     }
209 
findParentCell()210     private LayoutController findParentCell()
211     {
212         LayoutController parent = getParent();
213         while (parent != null)
214         {
215             final Object node = parent.getNode();
216             if (node instanceof Element)
217             {
218                 final Element element = (Element) node;
219                 if (OfficeNamespaces.TABLE_NS.equals(element.getNamespace()) && "table-cell".equals(element.getType()))
220                 {
221                     return parent;
222                 }
223             }
224             parent = parent.getParent();
225         }
226         return null;
227     }
228 
229     @Override
isValueChanged()230     public boolean isValueChanged()
231     {
232         final ImageElement imageElement = (ImageElement) getNode();
233         final FormulaExpression formulaExpression = imageElement.getFormula();
234         if (formulaExpression == null)
235         {
236             final FlowController controller = getFlowController();
237             final GlobalMasterRow masterRow = controller.getMasterRow();
238             final ReportDataRow reportDataRow = masterRow.getReportDataRow();
239             return reportDataRow.getCursor() == 0;
240         }
241 
242         try
243         {
244             final Formula formula = formulaExpression.getCompiledFormula();
245             final LValue lValue = formula.getRootReference();
246             return FormatValueUtility.isReferenceChanged(this, lValue);
247         }
248         catch (ParseException e)
249         {
250             return false;
251         }
252     }
253 
addColumnStyles(final ImageElementContext context, final Section columns, final int columnPos, final int colSpan)254     void addColumnStyles(final ImageElementContext context, final Section columns, final int columnPos, final int colSpan)
255     {
256         final Node[] columnDefs = columns.getNodeArray();
257         int columnCounter = 0;
258         for (Node columnDef : columnDefs)
259         {
260             final Element column = (Element) columnDef;
261 
262             if (!ObjectUtilities.equal(column.getNamespace(), OfficeNamespaces.TABLE_NS) || !ObjectUtilities.equal(column.getType(), OfficeToken.TABLE_COLUMN))
263             {
264                 continue;
265             }
266             if (columnCounter >= columnPos)
267             {
268                 final String colStyle = (String) column.getAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME);
269                 context.setColStyle(columnCounter - columnPos, colStyle);
270             }
271 
272             columnCounter += 1;
273 
274             if (columnCounter >= (columnPos + colSpan))
275             {
276                 break;
277             }
278 
279         }
280     }
281 
addRowStyles(final ImageElementContext context, final Section table, final int rowPos, final int rowSpan)282     void addRowStyles(final ImageElementContext context, final Section table, final int rowPos, final int rowSpan)
283     {
284         final Node[] rows = table.getNodeArray();
285         int rowCounter = 0;
286         for (Node row1 : rows)
287         {
288             final Element row = (Element) row1;
289 
290             if (!ObjectUtilities.equal(row.getNamespace(), OfficeNamespaces.TABLE_NS) || !ObjectUtilities.equal(row.getType(), OfficeToken.TABLE_ROW))
291             {
292                 continue;
293             }
294             if (rowCounter >= rowPos)
295             {
296                 final String rowStyle = (String) row.getAttribute(OfficeNamespaces.TABLE_NS, OfficeToken.STYLE_NAME);
297                 context.setRowStyle(rowCounter - rowPos, rowStyle);
298             }
299 
300             rowCounter += 1;
301 
302             if (rowCounter >= (rowPos + rowSpan))
303             {
304                 break;
305             }
306         }
307     }
308 }
309