1 /* This file is part of the KDE project
2 
3    Copyright 2007 Johannes Simon <johannes.simon@gmail.com>
4    Copyright 2009 Inge Wallin    <ingwa@lysator.liu.se>
5 
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the GNU Library General Public
8    License as published by the Free Software Foundation; either
9    version 2 of the License, or (at your option) any later version.
10 
11    This library is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14    Library General Public License for more details.
15 
16    You should have received a copy of the GNU Library General Public License
17    along with this library; see the file COPYING.LIB.  If not, write to
18    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19    Boston, MA 02110-1301, USA.
20 */
21 
22 // Own
23 #include "Surface.h"
24 
25 // Qt
26 #include <QPointF>
27 #include <QBrush>
28 #include <QPen>
29 
30 // Calligra
31 #include <KoXmlReader.h>
32 #include <KoXmlWriter.h>
33 #include <KoXmlNS.h>
34 #include <KoOdfStylesReader.h>
35 #include <KoShapeLoadingContext.h>
36 #include <KoShapeSavingContext.h>
37 #include <KoOdfLoadingContext.h>
38 #include <KoStyleStack.h>
39 #include <KoOdfGraphicStyles.h>
40 #include <KoGenStyles.h>
41 #include <KoOdfWorkaround.h>
42 
43 #include <KoImageData.h>
44 #include <KoUnit.h>
45 
46 // KChart
47 #include <KChartCartesianCoordinatePlane>
48 #include <KChartBackgroundAttributes>
49 #include <KChartFrameAttributes>
50 
51 // KoChart
52 #include "PlotArea.h"
53 #include "ChartDebug.h"
54 
55 
56 using namespace KoChart;
57 
58 class Surface::Private
59 {
60 public:
61     Private(PlotArea *parent);
62     ~Private();
63 
64     PlotArea *plotArea;
65 
66     QPointF  position;
67     int      width;
68 
69     QBrush   brush;
70     QPen     framePen;
71 
72     KChart::CartesianCoordinatePlane *kdPlane;
73 };
74 
Private(PlotArea * parent)75 Surface::Private::Private(PlotArea *parent)
76     : plotArea(parent)
77 {
78 }
79 
~Private()80 Surface::Private::~Private()
81 {
82 }
83 
84 
85 // ================================================================
86 
87 
Surface(PlotArea * parent)88 Surface::Surface(PlotArea *parent)
89     : d(new Private(parent))
90 {
91     Q_ASSERT(parent);
92 
93     // FIXME: Make this class capable of storing floor-specific
94     // attributes as well. Right now, it's really only used
95     // and designed to load and save the chart's wall.
96     d->kdPlane = d->plotArea->kdCartesianPlane();
97     Q_ASSERT(d->kdPlane);
98 }
99 
~Surface()100 Surface::~Surface()
101 {
102     delete d;
103 }
104 
105 
position() const106 QPointF Surface::position() const
107 {
108     return d->position;
109 }
110 
setPosition(const QPointF & position)111 void Surface::setPosition(const QPointF &position)
112 {
113     d->position = position;
114 }
115 
width() const116 int Surface::width() const
117 {
118     return d->width;
119 }
120 
setWidth(int width)121 void Surface::setWidth(int width)
122 {
123     d->width = width;
124 }
125 
brush() const126 QBrush Surface::brush() const
127 {
128     return d->brush;
129 }
130 
setBrush(const QBrush & brush)131 void Surface::setBrush(const QBrush &brush)
132 {
133     d->brush = brush;
134 }
135 
framePen() const136 QPen Surface::framePen() const
137 {
138     return d->framePen;
139 }
140 
setFramePen(const QPen & pen)141 void Surface::setFramePen(const QPen &pen)
142 {
143     d->framePen = pen;
144 }
145 
loadOdf(const KoXmlElement & surfaceElement,KoShapeLoadingContext & context)146 bool Surface::loadOdf(const KoXmlElement &surfaceElement,
147                        KoShapeLoadingContext &context)
148 {
149     // Get the current style stack and save it's state.
150     KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
151 
152     bool brushLoaded = false;
153 
154     if (surfaceElement.hasAttributeNS(KoXmlNS::chart, "style-name")) {
155         KChart::BackgroundAttributes backgroundAttributes = d->kdPlane->backgroundAttributes();
156         KChart::FrameAttributes frameAttributes = d->kdPlane->frameAttributes();
157 
158         // Add the chart style to the style stack.
159         styleStack.clear();
160         context.odfLoadingContext().fillStyleStack(surfaceElement, KoXmlNS::chart, "style-name", "chart");
161 
162         styleStack.setTypeProperties("graphic");
163 
164         // If there is a "stroke" property, then get the stroke style
165         // and set the pen accordingly.
166         if (styleStack.hasProperty(KoXmlNS::draw, "stroke")) {
167             frameAttributes.setVisible(true);
168 
169             QString  stroke = styleStack.property(KoXmlNS::draw, "stroke");
170             QPen pen(Qt::NoPen);
171             if (stroke == "solid" || stroke == "dash")
172                 pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke,
173                                                              context.odfLoadingContext().stylesReader());
174 
175             frameAttributes.setPen(pen);
176         }
177 
178         // If there is a "fill" property, then get the fill style, and
179         // set the brush for the surface accordingly.
180         if (styleStack.hasProperty(KoXmlNS::draw, "fill")) {
181             backgroundAttributes.setVisible(true);
182 
183             QBrush   brush;
184             QString  fill = styleStack.property(KoXmlNS::draw, "fill");
185             if (fill == "solid" || fill == "hatch") {
186                 brushLoaded = true;
187                 brush = KoOdfGraphicStyles::loadOdfFillStyle(styleStack, fill,
188                                                              context.odfLoadingContext().stylesReader());
189             }
190             else if (fill == "gradient") {
191                 brushLoaded = true;
192                 brush = KoOdfGraphicStyles::loadOdfGradientStyle(styleStack, context.odfLoadingContext().stylesReader(), QSizeF(5.0, 60.0));
193             }
194             else if (fill == "bitmap") {
195                 brushLoaded = true;
196                 brush = loadOdfPatternStyle(styleStack, context.odfLoadingContext(), QSizeF(5.0, 60.0));
197             }
198 
199             backgroundAttributes.setBrush(brush);
200         }
201 
202         // Finally actually set the attributes.
203         d->kdPlane->setBackgroundAttributes(backgroundAttributes);
204         d->kdPlane->setFrameAttributes(frameAttributes);
205     }
206 
207 #ifndef NWORKAROUND_ODF_BUGS
208     if (!brushLoaded) {
209         KChart::BackgroundAttributes backgroundAttributes = d->kdPlane->backgroundAttributes();
210         QColor fillColor = KoOdfWorkaround::fixMissingFillColor(surfaceElement, context);
211         if (fillColor.isValid()) {
212             backgroundAttributes.setVisible(true);
213             backgroundAttributes.setBrush(fillColor);
214             d->kdPlane->setBackgroundAttributes(backgroundAttributes);
215         }
216     }
217 #endif
218 
219     return true;
220 }
221 
saveOdf(KoShapeSavingContext & context,const char * elementName)222 void Surface::saveOdf(KoShapeSavingContext &context, const char *elementName)
223 {
224     KoXmlWriter  &bodyWriter = context.xmlWriter();
225     KoGenStyles  &mainStyles = context.mainStyles();
226     KoGenStyle    style      = KoGenStyle(KoGenStyle::GraphicAutoStyle, "chart");
227 
228     // elementName is chart:floor or chart:wall
229     bodyWriter.startElement(elementName);
230 
231     QBrush backgroundBrush;
232     if (d->kdPlane->backgroundAttributes().isVisible())
233         backgroundBrush = d->kdPlane->backgroundAttributes().brush();
234     QPen framePen(Qt::NoPen);
235     if (d->kdPlane->frameAttributes().isVisible())
236         framePen = d->kdPlane->frameAttributes().pen();
237 
238     KoOdfGraphicStyles::saveOdfFillStyle(style, mainStyles, backgroundBrush);
239     KoOdfGraphicStyles::saveOdfStrokeStyle(style, mainStyles, framePen);
240 
241     bodyWriter.addAttribute("chart:style-name", mainStyles.insert(style, "ch"));
242 
243     bodyWriter.endElement(); // chart:floor or chart:wall
244 }
245 
loadOdfPatternStyle(const KoStyleStack & styleStack,KoOdfLoadingContext & context,const QSizeF & size)246 QBrush Surface::loadOdfPatternStyle(const KoStyleStack &styleStack,
247                                     KoOdfLoadingContext &context, const QSizeF &size)
248 {
249     QString styleName = styleStack.property(KoXmlNS::draw, "fill-image-name");
250 
251     KoXmlElement* e = context.stylesReader().drawStyles("fill-image")[styleName];
252     if (! e)
253         return QBrush();
254 
255     const QString href = e->attributeNS(KoXmlNS::xlink, "href", QString());
256 
257     if (href.isEmpty())
258         return QBrush();
259 
260     QString strExtension;
261     const int result = href.lastIndexOf('.');
262     if (result >= 0) {
263         strExtension = href.mid(result + 1); // As we are using KoPicture, the extension should be without the dot.
264     }
265     QString filename(href);
266 
267     KoImageData data;
268     data.setImage(href, context.store());
269     if (data.errorCode() != KoImageData::Success)
270         return QBrush();
271 
272     // read the pattern repeat style
273     QString style = styleStack.property(KoXmlNS::style, "repeat");
274     debugChart << "pattern style =" << style;
275 
276     QSize imageSize = data.image().size();
277 
278     if (style == "stretch") {
279         imageSize = size.toSize();
280     } else {
281         // optional attributes which can override original image size
282         if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-height") && styleStack.hasProperty(KoXmlNS::draw, "fill-image-width")) {
283             QString height = styleStack.property(KoXmlNS::draw, "fill-image-height");
284             qreal newHeight = 0.0;
285             if (height.endsWith('%'))
286                 newHeight = 0.01 * height.remove('%').toDouble() * imageSize.height();
287             else
288                 newHeight = KoUnit::parseValue(height);
289             QString width = styleStack.property(KoXmlNS::draw, "fill-image-width");
290             qreal newWidth = 0.0;
291             if (width.endsWith('%'))
292                 newWidth = 0.01 * width.remove('%').toDouble() * imageSize.width();
293             else
294                 newWidth = KoUnit::parseValue(width);
295             if (newHeight > 0.0)
296                 imageSize.setHeight(static_cast<int>(newHeight));
297             if (newWidth > 0.0)
298                 imageSize.setWidth(static_cast<int>(newWidth));
299         }
300     }
301 
302     debugChart << "shape size =" << size;
303     debugChart << "original image size =" << data.image().size();
304     debugChart << "resulting image size =" << imageSize;
305 
306     QBrush resultBrush(QPixmap::fromImage(data.image()).scaled(imageSize));
307 
308     if (style == "repeat") {
309         QTransform matrix;
310         if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point")) {
311             // align pattern to the given size
312             QString align = styleStack.property(KoXmlNS::draw, "fill-image-ref-point");
313             debugChart << "pattern align =" << align;
314             if (align == "top-left")
315                 matrix.translate(0, 0);
316             else if (align == "top")
317                 matrix.translate(0.5*size.width(), 0);
318             else if (align == "top-right")
319                 matrix.translate(size.width(), 0);
320             else if (align == "left")
321                 matrix.translate(0, 0.5*size.height());
322             else if (align == "center")
323                 matrix.translate(0.5*size.width(), 0.5*size.height());
324             else if (align == "right")
325                 matrix.translate(size.width(), 0.5*size.height());
326             else if (align == "bottom-left")
327                 matrix.translate(0, size.height());
328             else if (align == "bottom")
329                 matrix.translate(0.5*size.width(), size.height());
330             else if (align == "bottom-right")
331                 matrix.translate(size.width(), size.height());
332         }
333         if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-x")) {
334             QString pointX = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-x");
335             matrix.translate(0.01 * pointX.remove('%').toDouble() * imageSize.width(), 0);
336         }
337         if (styleStack.hasProperty(KoXmlNS::draw, "fill-image-ref-point-y")) {
338             QString pointY = styleStack.property(KoXmlNS::draw, "fill-image-ref-point-y");
339             matrix.translate(0, 0.01 * pointY.remove('%').toDouble() * imageSize.height());
340         }
341         resultBrush.setTransform(matrix);
342     }
343 
344     return resultBrush;
345 }
346