1 /* This file is part of the KDE project
2    Copyright (C) 2004-2006 David Faure <faure@kde.org>
3    Copyright (C) 2007 Jan Hambrecht <jaham@gmx.net>
4    Copyright (C) 2007-2008,2010-2011 Thorsten Zachmann <zachmann@kde.org>
5    Copyright (C) 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.com>
6 
7    This library is free software; you can redistribute it and/or
8    modify it under the terms of the GNU Library General Public
9    License as published by the Free Software Foundation; either
10    version 2 of the License, or (at your option) any later version.
11 
12    This library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16 
17    You should have received a copy of the GNU Library General Public License
18    along with this library; see the file COPYING.LIB.  If not, write to
19    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20  * Boston, MA 02110-1301, USA.
21 */
22 
23 #include "KoOdfGraphicStyles.h"
24 
25 #include <QBrush>
26 #include <QBuffer>
27 #include <QPen>
28 
29 #include <OdfDebug.h>
30 
31 #include <KoGenStyles.h>
32 #include <KoStyleStack.h>
33 #include <KoUnit.h>
34 #include <KoXmlNS.h>
35 #include <KoXmlWriter.h>
36 
37 #include "KoOdfStylesReader.h"
38 
saveOdfFillStyle(KoGenStyle & styleFill,KoGenStyles & mainStyles,const QBrush & brush)39 void KoOdfGraphicStyles::saveOdfFillStyle(KoGenStyle &styleFill, KoGenStyles& mainStyles, const QBrush & brush)
40 {
41     KoGenStyle::Type type = styleFill.type();
42     KoGenStyle::PropertyType propertyType = (type == KoGenStyle::GraphicStyle || type == KoGenStyle::GraphicAutoStyle ||
43                                              type == KoGenStyle::DrawingPageStyle || type == KoGenStyle::DrawingPageAutoStyle )
44                                             ? KoGenStyle::DefaultType : KoGenStyle::GraphicType;
45     switch (brush.style()) {
46     case Qt::Dense1Pattern:
47         styleFill.addProperty("draw:opacity", "6%", propertyType);
48         styleFill.addProperty("draw:fill", "solid", propertyType);
49         styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
50         break;
51     case Qt::Dense2Pattern:
52         styleFill.addProperty("draw:opacity", "12%", propertyType);
53         styleFill.addProperty("draw:fill", "solid", propertyType);
54         styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
55         break;
56     case Qt::Dense3Pattern:
57         styleFill.addProperty("draw:opacity", "37%", propertyType);
58         styleFill.addProperty("draw:fill", "solid", propertyType);
59         styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
60         break;
61     case Qt::Dense4Pattern:
62         styleFill.addProperty("draw:opacity", "50%", propertyType);
63         styleFill.addProperty("draw:fill", "solid", propertyType);
64         styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
65         break;
66     case Qt::Dense5Pattern:
67         styleFill.addProperty("draw:opacity", "63%", propertyType);
68         styleFill.addProperty("draw:fill", "solid", propertyType);
69         styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
70         break;
71     case Qt::Dense6Pattern:
72         styleFill.addProperty("draw:opacity", "88%", propertyType);
73         styleFill.addProperty("draw:fill", "solid", propertyType);
74         styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
75         break;
76     case Qt::Dense7Pattern:
77         styleFill.addProperty("draw:opacity", "94%", propertyType);
78         styleFill.addProperty("draw:fill", "solid", propertyType);
79         styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
80         break;
81     case Qt::LinearGradientPattern:
82     case Qt::RadialGradientPattern:
83     case Qt::ConicalGradientPattern:
84         styleFill.addProperty("draw:fill", "gradient", propertyType);
85         styleFill.addProperty("draw:fill-gradient-name", saveOdfGradientStyle(mainStyles, brush), propertyType);
86         break;
87     case Qt::HorPattern:
88     case Qt::VerPattern:
89     case Qt::CrossPattern:
90     case Qt::BDiagPattern:
91     case Qt::FDiagPattern:
92     case Qt::DiagCrossPattern:
93         styleFill.addProperty("draw:fill", "hatch", propertyType);
94         styleFill.addProperty("draw:fill-hatch-name", saveOdfHatchStyle(mainStyles, brush), propertyType);
95         break;
96     case Qt::SolidPattern:
97         styleFill.addProperty("draw:fill", "solid", propertyType);
98         styleFill.addProperty("draw:fill-color", brush.color().name(), propertyType);
99         if (! brush.isOpaque())
100             styleFill.addProperty("draw:opacity", QString("%1%").arg(brush.color().alphaF() * 100.0), propertyType);
101         break;
102     case Qt::NoBrush:
103     default:
104         styleFill.addProperty("draw:fill", "none", propertyType);
105         break;
106     }
107 }
108 
saveOdfStrokeStyle(KoGenStyle & styleStroke,KoGenStyles & mainStyles,const QPen & pen)109 void KoOdfGraphicStyles::saveOdfStrokeStyle(KoGenStyle &styleStroke, KoGenStyles &mainStyles, const QPen &pen)
110 {
111     // TODO implement all possibilities
112     switch (pen.style()) {
113     case Qt::NoPen:
114         styleStroke.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
115         return;
116     case Qt::SolidLine:
117         styleStroke.addProperty("draw:stroke", "solid", KoGenStyle::GraphicType);
118         break;
119     default: { // must be a dashed line
120         styleStroke.addProperty("draw:stroke", "dash", KoGenStyle::GraphicType);
121         // save stroke dash (14.14.7) which is severely limited, but still
122         KoGenStyle dashStyle(KoGenStyle::StrokeDashStyle);
123         dashStyle.addAttribute("draw:style", "rect");
124         QVector<qreal> dashes = pen.dashPattern();
125         dashStyle.addAttribute("draw:dots1", static_cast<int>(1));
126         dashStyle.addAttributePt("draw:dots1-length", dashes[0]*pen.widthF());
127         dashStyle.addAttributePt("draw:distance", dashes[1]*pen.widthF());
128         if (dashes.size() > 2) {
129             dashStyle.addAttribute("draw:dots2", static_cast<int>(1));
130             dashStyle.addAttributePt("draw:dots2-length", dashes[2]*pen.widthF());
131         }
132         QString dashStyleName = mainStyles.insert(dashStyle, "dash");
133         styleStroke.addProperty("draw:stroke-dash", dashStyleName, KoGenStyle::GraphicType);
134         break;
135     }
136     }
137 
138     if (pen.brush().gradient()) {
139         styleStroke.addProperty("calligra:stroke-gradient", saveOdfGradientStyle(mainStyles, pen.brush()), KoGenStyle::GraphicType);
140     }
141     else {
142         styleStroke.addProperty("svg:stroke-color", pen.color().name(), KoGenStyle::GraphicType);
143         styleStroke.addProperty("svg:stroke-opacity", QString("%1").arg(pen.color().alphaF()), KoGenStyle::GraphicType);
144     }
145     styleStroke.addPropertyPt("svg:stroke-width", pen.widthF(), KoGenStyle::GraphicType);
146 
147     switch (pen.joinStyle()) {
148     case Qt::MiterJoin:
149         styleStroke.addProperty("draw:stroke-linejoin", "miter", KoGenStyle::GraphicType);
150         break;
151     case Qt::BevelJoin:
152         styleStroke.addProperty("draw:stroke-linejoin", "bevel", KoGenStyle::GraphicType);
153         break;
154     case Qt::RoundJoin:
155         styleStroke.addProperty("draw:stroke-linejoin", "round", KoGenStyle::GraphicType);
156         break;
157     default:
158         styleStroke.addProperty("draw:stroke-linejoin", "miter", KoGenStyle::GraphicType);
159         styleStroke.addProperty("calligra:stroke-miterlimit", QString("%1").arg(pen.miterLimit()), KoGenStyle::GraphicType);
160         break;
161     }
162     switch (pen.capStyle()) {
163     case Qt::RoundCap:
164         styleStroke.addProperty("svg:stroke-linecap", "round", KoGenStyle::GraphicType);
165         break;
166     case Qt::SquareCap:
167         styleStroke.addProperty("svg:stroke-linecap", "square", KoGenStyle::GraphicType);
168         break;
169     default:
170         styleStroke.addProperty("svg:stroke-linecap", "butt", KoGenStyle::GraphicType);
171         break;
172     }
173 }
174 
saveOdfHatchStyle(KoGenStyles & mainStyles,const QBrush & brush)175 QString KoOdfGraphicStyles::saveOdfHatchStyle(KoGenStyles& mainStyles, const QBrush &brush)
176 {
177     KoGenStyle hatchStyle(KoGenStyle::HatchStyle /*no family name*/);
178     hatchStyle.addAttribute("draw:color", brush.color().name());
179     //hatchStyle.addAttribute( "draw:distance", m_distance ); not implemented into Stage
180     switch (brush.style()) {
181     case Qt::HorPattern:
182         hatchStyle.addAttribute("draw:style", "single");
183         hatchStyle.addAttribute("draw:rotation", 0);
184         break;
185     case Qt::BDiagPattern:
186         hatchStyle.addAttribute("draw:style", "single");
187         hatchStyle.addAttribute("draw:rotation", 450);
188         break;
189     case Qt::VerPattern:
190         hatchStyle.addAttribute("draw:style", "single");
191         hatchStyle.addAttribute("draw:rotation", 900);
192         break;
193     case Qt::FDiagPattern:
194         hatchStyle.addAttribute("draw:style", "single");
195         hatchStyle.addAttribute("draw:rotation", 1350);
196         break;
197     case Qt::CrossPattern:
198         hatchStyle.addAttribute("draw:style", "double");
199         hatchStyle.addAttribute("draw:rotation", 0);
200         break;
201     case Qt::DiagCrossPattern:
202         hatchStyle.addAttribute("draw:style", "double");
203         hatchStyle.addAttribute("draw:rotation", 450);
204         break;
205     default:
206         break;
207     }
208 
209     return mainStyles.insert(hatchStyle, "hatch");
210 }
211 
saveOdfGradientStyle(KoGenStyles & mainStyles,const QBrush & brush)212 QString KoOdfGraphicStyles::saveOdfGradientStyle(KoGenStyles &mainStyles, const QBrush &brush)
213 {
214     KoGenStyle gradientStyle;
215     if (brush.style() == Qt::RadialGradientPattern) {
216         const QRadialGradient *gradient = static_cast<const QRadialGradient*>(brush.gradient());
217         gradientStyle = KoGenStyle(KoGenStyle::RadialGradientStyle /*no family name*/);
218         gradientStyle.addAttributePercent("svg:cx", gradient->center().x() * 100);
219         gradientStyle.addAttributePercent("svg:cy", gradient->center().y() * 100);
220         gradientStyle.addAttributePercent("svg:r",  gradient->radius() * 100);
221         gradientStyle.addAttributePercent("svg:fx", gradient->focalPoint().x() * 100);
222         gradientStyle.addAttributePercent("svg:fy", gradient->focalPoint().y() * 100);
223     } else if (brush.style() == Qt::LinearGradientPattern) {
224         const QLinearGradient *gradient = static_cast<const QLinearGradient*>(brush.gradient());
225         gradientStyle = KoGenStyle(KoGenStyle::LinearGradientStyle /*no family name*/);
226         gradientStyle.addAttributePercent("svg:x1", gradient->start().x() * 100);
227         gradientStyle.addAttributePercent("svg:y1", gradient->start().y() * 100);
228         gradientStyle.addAttributePercent("svg:x2", gradient->finalStop().x() * 100);
229         gradientStyle.addAttributePercent("svg:y2", gradient->finalStop().y() * 100);
230     } else if (brush.style() == Qt::ConicalGradientPattern) {
231         const QConicalGradient * gradient = static_cast<const QConicalGradient*>(brush.gradient());
232         gradientStyle = KoGenStyle(KoGenStyle::ConicalGradientStyle /*no family name*/);
233         gradientStyle.addAttributePercent("svg:cx", gradient->center().x() * 100);
234         gradientStyle.addAttributePercent("svg:cy", gradient->center().y() * 100);
235         gradientStyle.addAttribute("draw:angle", QString("%1").arg(gradient->angle()));
236     }
237     const QGradient * gradient = brush.gradient();
238     if (gradient->spread() == QGradient::RepeatSpread)
239         gradientStyle.addAttribute("svg:spreadMethod", "repeat");
240     else if (gradient->spread() == QGradient::ReflectSpread)
241         gradientStyle.addAttribute("svg:spreadMethod", "reflect");
242     else
243         gradientStyle.addAttribute("svg:spreadMethod", "pad");
244 
245     if (! brush.transform().isIdentity()) {
246         gradientStyle.addAttribute("svg:gradientTransform", saveTransformation(brush.transform()));
247     }
248 
249     QBuffer buffer;
250     buffer.open(QIODevice::WriteOnly);
251     KoXmlWriter elementWriter(&buffer);    // TODO pass indentation level
252 
253     // save stops
254     QGradientStops stops = gradient->stops();
255     foreach(const QGradientStop & stop, stops) {
256         elementWriter.startElement("svg:stop");
257         elementWriter.addAttribute("svg:offset", QString("%1").arg(stop.first));
258         elementWriter.addAttribute("svg:stop-color", stop.second.name());
259         if (stop.second.alphaF() < 1.0)
260             elementWriter.addAttribute("svg:stop-opacity", QString("%1").arg(stop.second.alphaF()));
261         elementWriter.endElement();
262     }
263 
264     QString elementContents = QString::fromUtf8(buffer.buffer(), buffer.buffer().size());
265     gradientStyle.addChildElement("svg:stop", elementContents);
266 
267     return mainStyles.insert(gradientStyle, "gradient");
268 }
269 
loadOdfGradientStyle(const KoStyleStack & styleStack,const KoOdfStylesReader & stylesReader,const QSizeF & size)270 QBrush KoOdfGraphicStyles::loadOdfGradientStyle(const KoStyleStack &styleStack, const KoOdfStylesReader & stylesReader, const QSizeF &size)
271 {
272     QString styleName = styleStack.property(KoXmlNS::draw, "fill-gradient-name");
273     return loadOdfGradientStyleByName(stylesReader, styleName, size);
274 }
275 
percent(const KoXmlElement & element,const QString & ns,const QString & type,const QString & defaultValue,qreal absolute)276 qreal percent(const KoXmlElement &element, const QString &ns, const QString &type, const QString &defaultValue, qreal absolute)
277 {
278     qreal tmp = 0.0;
279     QString value = element.attributeNS(ns, type, defaultValue);
280     if (value.indexOf('%') > -1) { // percent value
281         tmp = value.remove('%').toDouble() / 100.0;
282     }
283     else { // fixed value
284         tmp = KoUnit::parseValue(value) / absolute;
285         // The following is done so that we get the same data as when we save/load.
286         // This is needed that we get the same values due to rounding differences
287         // of absolute and relative values.
288         QString value = QString("%1").arg(tmp * 100.0);
289         tmp = value.toDouble() / 100;
290     }
291 
292     return tmp;
293 }
294 
loadOdfGradientStyleByName(const KoOdfStylesReader & stylesReader,const QString & styleName,const QSizeF & size)295 QBrush KoOdfGraphicStyles::loadOdfGradientStyleByName(const KoOdfStylesReader &stylesReader, const QString &styleName, const QSizeF &size)
296 {
297     KoXmlElement* e = stylesReader.drawStyles("gradient").value(styleName);
298     if (! e)
299         return QBrush();
300 
301     QGradient * gradient = 0;
302     QTransform transform;
303 
304     if (e->namespaceURI() == KoXmlNS::draw && e->localName() == "gradient") {
305         // FIXME seems like oo renders the gradient start stop color at the center of the
306         // radial gradient, and the start color at the radius of the radial gradient
307         // whereas it is not mentioned in the spec how it should be rendered
308         // note that svg defines that exactly as the opposite as oo does
309         // so what should we do?
310         QString type = e->attributeNS(KoXmlNS::draw, "style", QString());
311         if (type == "radial") {
312             // Zagge: at the moment the only objectBoundingBox is supported:
313             // 18.539 svg:gradientUnits
314             // See §13.2.2 and §13.2.3 of [SVG].
315             // The default value for this attribute is objectBoundingBox.
316             // The only value of the svg:gradientUnits attribute is objectBoundingBox.
317 
318             qreal cx = KoUnit::parseValue(e->attributeNS(KoXmlNS::draw, "cx", QString()).remove('%'));
319             qreal cy = KoUnit::parseValue(e->attributeNS(KoXmlNS::draw, "cy", QString()).remove('%'));
320             gradient = new QRadialGradient(QPointF(cx * 0.01, cy * 0.01), sqrt(0.5));
321             gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
322         } else if (type == "linear" || type == "axial") {
323             QLinearGradient * lg = new QLinearGradient();
324             lg->setCoordinateMode(QGradient::ObjectBoundingMode);
325             // Dividing by 10 here because OOo saves as degree * 10
326             qreal angle = 90 + e->attributeNS(KoXmlNS::draw, "angle", "0").toDouble() / 10;
327             qreal radius = sqrt(0.5);
328 
329             qreal sx = cos(angle * M_PI / 180) * radius;
330             qreal sy = sin(angle * M_PI / 180) * radius;
331             lg->setStart(QPointF(0.5 + sx, 0.5 - sy));
332             lg->setFinalStop(QPointF(0.5 - sx, 0.5 + sy));
333             gradient = lg;
334         } else
335             return QBrush();
336 
337         qreal border = 0.01 * e->attributeNS(KoXmlNS::draw, "border", "0").remove('%').toDouble();
338         QGradientStops stops;
339         if (type != "axial") {
340             // In case of radial gradients the colors are reversed, because OOo saves them as the opposite of the SVG direction
341             // see bug 137639
342             QGradientStop start;
343             start.first = (type != "radial") ? border : 1.0 - border;
344             start.second = QColor(e->attributeNS(KoXmlNS::draw, "start-color", QString()));
345             start.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble());
346 
347             QGradientStop end;
348             end.first = (type != "radial") ? 1.0 : 0.0;
349             end.second = QColor(e->attributeNS(KoXmlNS::draw, "end-color", QString()));
350             end.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble());
351 
352             stops << start << end;
353         } else {
354             QGradientStop start;
355             start.first = 0.5 * border;
356             start.second = QColor(e->attributeNS(KoXmlNS::draw, "end-color", QString()));
357             start.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble());
358 
359             QGradientStop middle;
360             middle.first = 0.5;
361             middle.second = QColor(e->attributeNS(KoXmlNS::draw, "start-color", QString()));
362             middle.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "start-intensity", "100").remove('%').toDouble());
363 
364             QGradientStop end;
365             end.first = 1.0 - 0.5 * border;
366             end.second = QColor(e->attributeNS(KoXmlNS::draw, "end-color", QString()));
367             end.second.setAlphaF(0.01 * e->attributeNS(KoXmlNS::draw, "end-intensity", "100").remove('%').toDouble());
368 
369             stops << start << middle << end;
370         }
371 
372         gradient->setStops(stops);
373     } else if (e->namespaceURI() == KoXmlNS::svg) {
374         if (e->localName() == "linearGradient") {
375             QPointF start, stop;
376             start.setX(percent(*e, KoXmlNS::svg, "x1", "0%", size.width()));
377             start.setY(percent(*e, KoXmlNS::svg, "y1", "0%", size.height()));
378             stop.setX(percent(*e, KoXmlNS::svg, "x2", "100%", size.width()));
379             stop.setY(percent(*e, KoXmlNS::svg, "y2", "100%", size.height()));
380             gradient = new QLinearGradient(start, stop);
381         } else if (e->localName() == "radialGradient") {
382             QPointF center, focalPoint;
383             center.setX(percent(*e, KoXmlNS::svg, "cx", "50%", size.width()));
384             center.setY(percent(*e, KoXmlNS::svg, "cy", "50%", size.height()));
385             qreal r = percent(*e, KoXmlNS::svg, "r", "50%", sqrt(size.width() * size.width() + size.height() * size.height()));
386             focalPoint.setX(percent(*e, KoXmlNS::svg, "fx", QString(), size.width()));
387             focalPoint.setY(percent(*e, KoXmlNS::svg, "fy", QString(), size.height()));
388             gradient = new QRadialGradient(center, r, focalPoint );
389         }
390         if (! gradient)
391             return QBrush();
392 
393         gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
394 
395         QString strSpread(e->attributeNS(KoXmlNS::svg, "spreadMethod", "pad"));
396         if (strSpread == "repeat")
397             gradient->setSpread(QGradient::RepeatSpread);
398         else if (strSpread == "reflect")
399             gradient->setSpread(QGradient::ReflectSpread);
400         else
401             gradient->setSpread(QGradient::PadSpread);
402 
403         if (e->hasAttributeNS(KoXmlNS::svg, "gradientTransform"))
404             transform = loadTransformation(e->attributeNS(KoXmlNS::svg, "gradientTransform", QString()));
405 
406         QGradientStops stops;
407 
408         // load stops
409         KoXmlElement colorstop;
410         forEachElement(colorstop, (*e)) {
411             if (colorstop.namespaceURI() == KoXmlNS::svg && colorstop.localName() == "stop") {
412                 QGradientStop stop;
413                 stop.second = QColor(colorstop.attributeNS(KoXmlNS::svg, "stop-color", QString()));
414                 stop.second.setAlphaF(colorstop.attributeNS(KoXmlNS::svg, "stop-opacity", "1.0").toDouble());
415                 stop.first = colorstop.attributeNS(KoXmlNS::svg, "offset", "0.0").toDouble();
416                 stops.append(stop);
417             }
418         }
419         gradient->setStops(stops);
420     } else if (e->namespaceURI() == KoXmlNS::calligra) {
421         if (e->localName() == "conicalGradient") {
422             QPointF center;
423             center.setX(percent(*e, KoXmlNS::svg, "cx", "50%", size.width()));
424             center.setY(percent(*e, KoXmlNS::svg, "cy", "50%", size.height()));
425             qreal angle = KoUnit::parseValue(e->attributeNS(KoXmlNS::draw, "angle", QString()));
426             gradient = new QConicalGradient(center, angle);
427             gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
428 
429             QString strSpread(e->attributeNS(KoXmlNS::svg, "spreadMethod", "pad"));
430             if (strSpread == "repeat")
431                 gradient->setSpread(QGradient::RepeatSpread);
432             else if (strSpread == "reflect")
433                 gradient->setSpread(QGradient::ReflectSpread);
434             else
435                 gradient->setSpread(QGradient::PadSpread);
436 
437             if (e->hasAttributeNS(KoXmlNS::svg, "gradientTransform"))
438                 transform = loadTransformation(e->attributeNS(KoXmlNS::svg, "gradientTransform", QString()));
439 
440             QGradientStops stops;
441 
442             // load stops
443             KoXmlElement colorstop;
444             forEachElement(colorstop, (*e)) {
445                 if (colorstop.namespaceURI() == KoXmlNS::svg && colorstop.localName() == "stop") {
446                     QGradientStop stop;
447                     stop.second = QColor(colorstop.attributeNS(KoXmlNS::svg, "stop-color", QString()));
448                     stop.second.setAlphaF(colorstop.attributeNS(KoXmlNS::svg, "stop-opacity", "1.0").toDouble());
449                     stop.first = colorstop.attributeNS(KoXmlNS::svg, "offset", "0.0").toDouble();
450                     stops.append(stop);
451                 }
452             }
453             gradient->setStops(stops);
454         }
455     }
456 
457     if (! gradient)
458         return QBrush();
459 
460     QBrush resultBrush(*gradient);
461     resultBrush.setTransform(transform);
462 
463     delete gradient;
464     return resultBrush;
465 }
466 
loadOdfFillStyle(const KoStyleStack & styleStack,const QString & fill,const KoOdfStylesReader & stylesReader)467 QBrush KoOdfGraphicStyles::loadOdfFillStyle(const KoStyleStack &styleStack, const QString & fill,  const KoOdfStylesReader & stylesReader)
468 {
469     QBrush tmpBrush; // default brush for "none" is a Qt::NoBrush
470 
471     if (fill == "solid") {
472         tmpBrush.setStyle(Qt::SolidPattern);
473         if (styleStack.hasProperty(KoXmlNS::draw, "fill-color"))
474             tmpBrush.setColor(styleStack.property(KoXmlNS::draw, "fill-color"));
475         if (styleStack.hasProperty(KoXmlNS::draw, "opacity")) {
476             QString opacity = styleStack.property(KoXmlNS::draw, "opacity");
477             if (! opacity.isEmpty() && opacity.right(1) == "%") {
478                 float percent = opacity.leftRef(opacity.length() - 1).toFloat();
479                 QColor color = tmpBrush.color();
480                 color.setAlphaF(percent / 100.0);
481                 tmpBrush.setColor(color);
482             }
483         }
484         //TODO
485         if (styleStack.hasProperty(KoXmlNS::draw, "transparency")) {
486             QString transparency = styleStack.property(KoXmlNS::draw, "transparency");
487             if (transparency == "94%") {
488                 tmpBrush.setStyle(Qt::Dense1Pattern);
489             } else if (transparency == "88%") {
490                 tmpBrush.setStyle(Qt::Dense2Pattern);
491             } else if (transparency == "63%") {
492                 tmpBrush.setStyle(Qt::Dense3Pattern);
493 
494             } else if (transparency == "50%") {
495                 tmpBrush.setStyle(Qt::Dense4Pattern);
496 
497             } else if (transparency == "37%") {
498                 tmpBrush.setStyle(Qt::Dense5Pattern);
499 
500             } else if (transparency == "12%") {
501                 tmpBrush.setStyle(Qt::Dense6Pattern);
502 
503             } else if (transparency == "6%") {
504                 tmpBrush.setStyle(Qt::Dense7Pattern);
505 
506             } else
507                 debugOdf << " transparency is not defined into Stage :" << transparency;
508         }
509     } else if (fill == "hatch") {
510         QString style = styleStack.property(KoXmlNS::draw, "fill-hatch-name");
511         debugOdf << " hatch style is  :" << style;
512 
513         //type not defined by default
514         //try to use style.
515         KoXmlElement* draw = stylesReader.drawStyles("hatch").value(style);
516         if (draw) {
517             debugOdf << "We have a style";
518             int angle = 0;
519             if (draw->hasAttributeNS(KoXmlNS::draw, "rotation")) {
520                 angle = (draw->attributeNS(KoXmlNS::draw, "rotation", QString()).toInt()) / 10;
521                 debugOdf << "angle :" << angle;
522             }
523             if (draw->hasAttributeNS(KoXmlNS::draw, "color")) {
524                 //debugOdf<<" draw:color :"<<draw->attributeNS( KoXmlNS::draw,"color", QString() );
525                 tmpBrush.setColor(draw->attributeNS(KoXmlNS::draw, "color", QString()));
526             }
527             if (draw->hasAttributeNS(KoXmlNS::draw, "distance")) {
528                 //TODO: implement it into Stage
529             }
530             if (draw->hasAttributeNS(KoXmlNS::draw, "display-name")) {
531                 //TODO: implement it into Stage
532             }
533             if (draw->hasAttributeNS(KoXmlNS::draw, "style")) {
534                 //TODO: implement it into Stage
535                 QString styleHash = draw->attributeNS(KoXmlNS::draw, "style", QString());
536                 if (styleHash == "single") {
537                     switch (angle) {
538                     case 0:
539                     case 180:
540                         tmpBrush.setStyle(Qt::HorPattern);
541                         break;
542                     case 45:
543                     case 225:
544                         tmpBrush.setStyle(Qt::BDiagPattern);
545                         break;
546                     case 90:
547                     case 270:
548                         tmpBrush.setStyle(Qt::VerPattern);
549                         break;
550                     case 135:
551                     case 315:
552                         tmpBrush.setStyle(Qt::FDiagPattern);
553                         break;
554                     default:
555                         //todo fixme when we will have a kopaint
556                         debugOdf << " draw:rotation 'angle' :" << angle;
557                         break;
558                     }
559                 } else if (styleHash == "double") {
560                     switch (angle) {
561                     case 0:
562                     case 180:
563                     case 90:
564                     case 270:
565                         tmpBrush.setStyle(Qt::CrossPattern);
566                         break;
567                     case 45:
568                     case 135:
569                     case 225:
570                     case 315:
571                         tmpBrush.setStyle(Qt::DiagCrossPattern);
572                         break;
573                     default:
574                         //todo fixme when we will have a kopaint
575                         debugOdf << " draw:rotation 'angle' :" << angle;
576                         break;
577                     }
578 
579                 } else if (styleHash == "triple") {
580                     debugOdf << " it is not implemented :(";
581                 }
582             }
583         }
584     }
585 
586     return tmpBrush;
587 }
588 
parseDashEntrySize(QString & attr,qreal penWidth,qreal defaultValue=0.0)589 static qreal parseDashEntrySize(QString& attr, qreal penWidth, qreal defaultValue = 0.0){
590     qreal result = defaultValue;
591     if (attr.endsWith('%')) {
592         bool ok;
593         const int percent = attr.remove('%').toInt(&ok);
594         if (ok && percent >= 0) {
595             result = percent / 100.0;
596         }
597     } else {
598         result = KoUnit::parseValue(attr) / penWidth;
599     }
600     return result;
601 }
602 
loadOdfStrokeStyle(const KoStyleStack & styleStack,const QString & stroke,const KoOdfStylesReader & stylesReader)603 QPen KoOdfGraphicStyles::loadOdfStrokeStyle(const KoStyleStack &styleStack, const QString & stroke, const KoOdfStylesReader & stylesReader)
604 {
605     QPen tmpPen(Qt::NoPen); // default pen for "none" is a Qt::NoPen
606 
607     if (stroke == "solid" || stroke == "dash") {
608         // If solid or dash is set then we assume that the color is black and the penWidth
609         // is zero till defined otherwise with the following attributes.
610         tmpPen = QPen();
611 
612         if (styleStack.hasProperty(KoXmlNS::svg, "stroke-color"))
613             tmpPen.setColor(styleStack.property(KoXmlNS::svg, "stroke-color"));
614         if (styleStack.hasProperty(KoXmlNS::svg, "stroke-opacity")) {
615             QColor color = tmpPen.color();
616             QString opacity = styleStack.property(KoXmlNS::svg, "stroke-opacity");
617             if (opacity.endsWith('%'))
618                 color.setAlphaF(0.01 * opacity.remove('%').toDouble());
619             else
620                 color.setAlphaF(opacity.toDouble());
621             tmpPen.setColor(color);
622         }
623         if (styleStack.hasProperty(KoXmlNS::svg, "stroke-width"))
624             tmpPen.setWidthF(KoUnit::parseValue(styleStack.property(KoXmlNS::svg, "stroke-width")));
625         if (styleStack.hasProperty(KoXmlNS::draw, "stroke-linejoin")) {
626             QString join = styleStack.property(KoXmlNS::draw, "stroke-linejoin");
627             if (join == "bevel")
628                 tmpPen.setJoinStyle(Qt::BevelJoin);
629             else if (join == "round")
630                 tmpPen.setJoinStyle(Qt::RoundJoin);
631             else {
632                 tmpPen.setJoinStyle(Qt::MiterJoin);
633                 if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-miterlimit")) {
634                     QString miterLimit = styleStack.property(KoXmlNS::calligra, "stroke-miterlimit");
635                     tmpPen.setMiterLimit(miterLimit.toDouble());
636                 }
637             }
638         }
639         if (styleStack.hasProperty(KoXmlNS::svg, "stroke-linecap")) {
640             const QString cap = styleStack.property(KoXmlNS::svg, "stroke-linecap");
641             if (cap == "round")
642                 tmpPen.setCapStyle(Qt::RoundCap);
643             else if (cap == "square")
644                 tmpPen.setCapStyle(Qt::SquareCap);
645             else
646                 tmpPen.setCapStyle(Qt::FlatCap);
647         } else {
648             // default as per svg specification
649             tmpPen.setCapStyle(Qt::FlatCap);
650         }
651 
652         if (stroke == "dash" && styleStack.hasProperty(KoXmlNS::draw, "stroke-dash")) {
653             QString dashStyleName = styleStack.property(KoXmlNS::draw, "stroke-dash");
654 
655             // set width to 1 in case it is 0 as dividing by 0 gives infinity
656             qreal width = tmpPen.widthF();
657             if ( width == 0 ) {
658                 width = 1;
659             }
660 
661             KoXmlElement * dashElement = stylesReader.drawStyles("stroke-dash").value(dashStyleName);
662             if (dashElement) {
663                 QVector<qreal> dashes;
664                 if (dashElement->hasAttributeNS(KoXmlNS::draw, "dots1")) {
665                     QString distance( dashElement->attributeNS(KoXmlNS::draw, "distance", QString()) );
666                     qreal space = parseDashEntrySize(distance, width, 0.0);
667 
668                     QString dots1Length(dashElement->attributeNS(KoXmlNS::draw, "dots1-length", QString()));
669                     qreal dot1Length = parseDashEntrySize(dots1Length,width,1.0);
670 
671                     bool ok;
672                     int dots1 = dashElement->attributeNS(KoXmlNS::draw, "dots1").toInt(&ok);
673                     if (!ok) {
674                         dots1 = 1;
675                     }
676 
677                     for (int i = 0; i < dots1; i++) {
678                         dashes.append(dot1Length);
679                         dashes.append(space);
680                     }
681 
682                     if (dashElement->hasAttributeNS(KoXmlNS::draw, "dots2")) {
683                         QString dots2Length(dashElement->attributeNS(KoXmlNS::draw, "dots2-length", QString()));
684                         qreal dot2Length = parseDashEntrySize(dots2Length,width,1.0);
685 
686                         int dots2 = dashElement->attributeNS(KoXmlNS::draw, "dots2").toInt(&ok);
687                         if (!ok) {
688                             dots2 = 1;
689                         }
690 
691                         for (int i = 0; i < dots2; i++) {
692                             dashes.append(dot2Length);
693                             dashes.append(space);
694                         }
695                     }
696                     tmpPen.setDashPattern(dashes);
697                 }
698             }
699         }
700     }
701 
702     return tmpPen;
703 }
704 
loadTransformation(const QString & transformation)705 QTransform KoOdfGraphicStyles::loadTransformation(const QString &transformation)
706 {
707     QTransform transform;
708 
709     // Split string for handling 1 transform statement at a time
710     QStringList subtransforms = transformation.split(')', QString::SkipEmptyParts);
711     QStringList::ConstIterator it = subtransforms.constBegin();
712     QStringList::ConstIterator end = subtransforms.constEnd();
713     for (; it != end; ++it) {
714         QStringList subtransform = (*it).split('(', QString::SkipEmptyParts);
715 
716         subtransform[0] = subtransform[0].trimmed().toLower();
717         subtransform[1] = subtransform[1].simplified();
718         QRegExp reg("[,( ]");
719         QStringList params = subtransform[1].split(reg, QString::SkipEmptyParts);
720 
721         if (subtransform[0].startsWith(';') || subtransform[0].startsWith(','))
722             subtransform[0] = subtransform[0].right(subtransform[0].length() - 1);
723 
724         if (subtransform[0] == "rotate") {
725             // TODO find out what oo2 really does when rotating, it seems severely broken
726             if (params.count() == 3) {
727                 qreal x = KoUnit::parseValue(params[1]);
728                 qreal y = KoUnit::parseValue(params[2]);
729 
730                 transform.translate(x, y);
731                 // oo2 rotates by radians
732                 transform.rotate(params[0].toDouble()*180.0 / M_PI);
733                 transform.translate(-x, -y);
734             } else {
735                 // oo2 rotates by radians
736                 transform.rotate(params[0].toDouble()*180.0 / M_PI);
737             }
738         } else if (subtransform[0] == "translate") {
739             if (params.count() == 2) {
740                 qreal x = KoUnit::parseValue(params[0]);
741                 qreal y = KoUnit::parseValue(params[1]);
742                 transform.translate(x, y);
743             } else   // Spec : if only one param given, assume 2nd param to be 0
744                 transform.translate(KoUnit::parseValue(params[0]) , 0);
745         } else if (subtransform[0] == "scale") {
746             if (params.count() == 2)
747                 transform.scale(params[0].toDouble(), params[1].toDouble());
748             else    // Spec : if only one param given, assume uniform scaling
749                 transform.scale(params[0].toDouble(), params[0].toDouble());
750         } else if (subtransform[0] == "skewx")
751             transform.shear(tan(params[0].toDouble()), 0.0F);
752         else if (subtransform[0] == "skewy")
753             transform.shear(tan(params[0].toDouble()), 0.0F);
754         else if (subtransform[0] == "matrix") {
755             if (params.count() >= 6) {
756                 transform.setMatrix(params[0].toDouble(), params[1].toDouble(), 0,
757                     params[2].toDouble(), params[3].toDouble(), 0,
758                     KoUnit::parseValue(params[4]), KoUnit::parseValue(params[5]), 1);
759             }
760         }
761     }
762 
763     return transform;
764 }
765 
saveTransformation(const QTransform & transformation,bool appendTranslateUnit)766 QString KoOdfGraphicStyles::saveTransformation(const QTransform &transformation, bool appendTranslateUnit)
767 {
768     QString transform;
769     if (appendTranslateUnit)
770         transform = QString("matrix(%1 %2 %3 %4 %5pt %6pt)")
771                     .arg(transformation.m11()).arg(transformation.m12())
772                     .arg(transformation.m21()).arg(transformation.m22())
773                     .arg(transformation.dx()) .arg(transformation.dy());
774     else
775         transform = QString("matrix(%1 %2 %3 %4 %5 %6)")
776                     .arg(transformation.m11()).arg(transformation.m12())
777                     .arg(transformation.m21()).arg(transformation.m22())
778                     .arg(transformation.dx()) .arg(transformation.dy());
779 
780     return transform;
781 }
782 
783