1 /* This file is part of the KDE project
2
3 Copyright 2018 Dag Andersen <danders@get2net.dk>
4
5 This library is free software; you can redistribute it and/or
6 modify it under the terms of the GNU Library General Public
7 License as published by the Free Software Foundation; either
8 version 2 of the License, or (at your option) any later version.
9
10 This library is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Library General Public License for more details.
14
15 You should have received a copy of the GNU Library General Public License
16 along with this library; see the file COPYING.LIB. If not, write to
17 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
19 */
20
21
22 #include "OdfHelper.h"
23
24 #include "ChartDebug.h"
25
26 // Posix
27 #include <float.h> // For basic data types characteristics.
28
29 // Qt
30 #include <QPointF>
31 #include <QPainter>
32 #include <QSizeF>
33 #include <QTextDocument>
34 #include <QStandardItemModel>
35 #include <QUrl>
36
37 // Calligra
38 #include <KoShapeLoadingContext.h>
39 #include <KoOdfLoadingContext.h>
40 #include <KoEmbeddedDocumentSaver.h>
41 #include <KoStore.h>
42 #include <KoDocument.h>
43 #include <KoShapeSavingContext.h>
44 #include <KoViewConverter.h>
45 #include <KoXmlReader.h>
46 #include <KoXmlWriter.h>
47 #include <KoXmlNS.h>
48 #include <KoGenStyles.h>
49 #include <KoStyleStack.h>
50 #include <KoShapeRegistry.h>
51 #include <KoTextShapeData.h>
52 #include <KoTextDocumentLayout.h>
53 #include <KoCanvasBase.h>
54 #include <KoShapeManager.h>
55 #include <KoSelection.h>
56 #include <KoShapeBackground.h>
57 #include <KoInsets.h>
58 #include <KoShapeStrokeModel.h>
59 #include <KoColorBackground.h>
60 #include <KoShapeStroke.h>
61 #include <KoOdfWorkaround.h>
62 #include <KoTextDocument.h>
63 #include <KoUnit.h>
64 #include <KoShapePaintingContext.h>
65 #include <KoTextShapeDataBase.h>
66 #include <KoShapeShadow.h>
67 #include <KoBorder.h>
68 #include <KoHatchBackground.h>
69 #include <KoOdfGradientBackground.h>
70 #include <KoPatternBackground.h>
71 #include <KoGradientBackground.h>
72 #include <KoOdfGraphicStyles.h>
73 #include "kochart_global.h"
74
75 namespace KoChart {
76 namespace OdfHelper {
77
78
79 // HACK: To get correct position also for rotated titles
itemPosition(const KoShape * shape)80 QPointF itemPosition(const KoShape *shape)
81 {
82 return QPointF(shape->transformation().dx(), shape->transformation().dy());
83 }
84
85 //fo:font-weight attribute are normal, bold, 100, 200, 300, 400, 500, 600, 700, 800 or 900.
fromOdfFontWeight(const QString & odfweight)86 int fromOdfFontWeight(const QString &odfweight) {
87 if (odfweight.isEmpty() || odfweight == "normal") {
88 return QFont::Normal;
89 }
90 if (odfweight == "bold") {
91 return QFont::Bold;
92 }
93 bool ok;
94 int weight = odfweight.toInt(&ok);
95 if (!ok) {
96 return 50;
97 }
98 switch (weight) {
99 case 100: weight = 1; break;
100 case 200: weight = 17; break;
101 case 300: weight = 33; break;
102 case 400: weight = 50; break;
103 case 500: weight = 58; break;
104 case 600: weight = 66; break;
105 case 700: weight = 75; break;
106 case 800: weight = 87; break;
107 case 900: weight = 99; break;
108 default: weight = 50; break;
109 }
110 return weight;
111 }
112
toOdfFontWeight(int weight)113 QString toOdfFontWeight(int weight) {
114 QString w;
115 if (weight < 8) {
116 w = "100";
117 } else if (weight < 25) {
118 w = "200";
119 } else if (weight < 41) {
120 w = "300";
121 } else if (weight < 54) {
122 w = "normal";
123 } else if (weight < 62) {
124 w = "500";
125 } else if (weight < 70) {
126 w = "600";
127 } else if (weight < 81) {
128 w = "bold";
129 } else if (weight < 92) {
130 w = "800";
131 } else {
132 w = "900";
133 }
134 return w;
135 }
136
saveOdfFont(KoGenStyle & style,const QFont & font,const QColor & color)137 void saveOdfFont(KoGenStyle &style, const QFont& font, const QColor& color)
138 {
139 style.addProperty("fo:font-family", font.family(), KoGenStyle::TextType);
140 style.addPropertyPt("fo:font-size", font.pointSize(), KoGenStyle::TextType);
141 style.addProperty("fo:color", color.isValid() ? color.name() : "#000000", KoGenStyle::TextType);
142 style.addProperty("fo:font-weight", toOdfFontWeight(font.weight()), KoGenStyle::TextType);
143 style.addProperty("fo:font-style", font.italic() ? "italic" : "normal", KoGenStyle::TextType);
144 }
145
saveOdfFont(KoGenStyles & mainStyles,const QFont & font,const QColor & color)146 QString saveOdfFont(KoGenStyles& mainStyles,
147 const QFont& font,
148 const QColor& color)
149 {
150 KoGenStyle autoStyle(KoGenStyle::ParagraphAutoStyle, "chart", 0);
151 saveOdfFont(autoStyle, font, color);
152 return mainStyles.insert(autoStyle, "ch");
153 }
154
saveOdfTitleStyle(KoShape * title,KoGenStyle & style,KoShapeSavingContext & context)155 void saveOdfTitleStyle(KoShape *title, KoGenStyle &style, KoShapeSavingContext &context)
156 {
157 TextLabelData *titleData = qobject_cast<TextLabelData*>(title->userData());
158 Q_ASSERT(titleData);
159 QTextCursor cursor(titleData->document());
160 QFont titleFont = cursor.charFormat().font();
161 QColor color = cursor.charFormat().foreground().color();
162
163 saveOdfFont(style, titleFont, color);
164
165 // title->saveStyle(style, context) is protected,
166 // so we duplicate some code:
167 KoShapeStrokeModel *sm = title->stroke();
168 if (sm) {
169 sm->fillStyle(style, context);
170 } else {
171 style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
172 }
173 KoShapeShadow *s = title->shadow();
174 if (s) {
175 s->fillStyle(style, context);
176 }
177 QSharedPointer<KoShapeBackground> bg = title->background();
178 if (bg) {
179 bg->fillStyle(style, context);
180 } else {
181 style.addProperty("draw:fill", "none", KoGenStyle::GraphicType);
182 }
183
184 KoBorder *b = title->border();
185 if (b) {
186 b->saveOdf(style);
187 }
188
189 QMap<QByteArray, QString>::const_iterator it(title->additionalStyleAttributes().constBegin());
190 for (; it != title->additionalStyleAttributes().constEnd(); ++it) {
191 style.addProperty(it.key(), it.value(), KoGenStyle::ChartType);
192 }
193 style.addProperty("chart:auto-size", (titleData->resizeMethod() == KoTextShapeDataBase::AutoResize), KoGenStyle::ChartType);
194 }
195
saveOdfTitle(KoShape * title,KoXmlWriter & bodyWriter,const char * titleType,KoShapeSavingContext & context)196 void saveOdfTitle(KoShape *title, KoXmlWriter &bodyWriter, const char *titleType, KoShapeSavingContext &context)
197 {
198 // Don't save hidden titles, as that's the way of removing them
199 // from a chart.
200 if (!title->isVisible())
201 return;
202
203 TextLabelData *titleData = qobject_cast<TextLabelData*>(title->userData());
204 if (!titleData)
205 return;
206
207 bodyWriter.startElement(titleType);
208
209 KoGenStyle autoStyle(KoGenStyle::ChartAutoStyle, "chart", 0);
210 autoStyle.addPropertyPt("style:rotation-angle", 360 - title->rotation());
211 saveOdfTitleStyle(title, autoStyle, context);
212
213 // always save position and size and let consumer decide if they are used
214 QPointF position = itemPosition(title);
215 bodyWriter.addAttributePt("svg:x", position.x());
216 bodyWriter.addAttributePt("svg:y", position.y());
217
218 const QSizeF size = title->size();
219 bodyWriter.addAttributePt("svg:width", size.width());
220 bodyWriter.addAttributePt("svg:height", size.height());
221
222 bodyWriter.addAttribute("chart:style-name", context.mainStyles().insert(autoStyle, "ch"));
223
224 // lo (and odf?) does not support formatted text :(
225 bodyWriter.startElement("text:p");
226 bodyWriter.addTextNode(titleData->document()->toPlainText());
227 bodyWriter.endElement(); // text:p
228
229 // save calligra specific formatted text
230 bodyWriter.startElement("calligra:text");
231 titleData->saveOdf(context);
232 bodyWriter.endElement(); // calligra:text
233
234 bodyWriter.endElement(); // chart:title/subtitle/footer
235 }
236
getStyleProperty(const char * property,KoShapeLoadingContext & context)237 QString getStyleProperty(const char *property, KoShapeLoadingContext &context)
238 {
239 KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
240 QString value;
241
242 if (styleStack.hasProperty(KoXmlNS::draw, property)) {
243 value = styleStack.property(KoXmlNS::draw, property);
244 }
245
246 return value;
247 }
248
loadOdfFill(KoShape * title,KoShapeLoadingContext & context)249 QSharedPointer<KoShapeBackground> loadOdfFill(KoShape *title, KoShapeLoadingContext &context)
250 {
251 QString fill = getStyleProperty("fill", context);
252 QSharedPointer<KoShapeBackground> bg;
253 if (fill == "solid") {
254 bg = QSharedPointer<KoShapeBackground>(new KoColorBackground());
255 }
256 else if (fill == "hatch") {
257 bg = QSharedPointer<KoShapeBackground>(new KoHatchBackground());
258 } else if (fill == "gradient") {
259 QString styleName = getStyleProperty("fill-gradient-name", context);
260 KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient").value(styleName);
261 QString style;
262 if (e) {
263 style = e->attributeNS(KoXmlNS::draw, "style", QString());
264 }
265 if ((style == "rectangular") || (style == "square")) {
266 bg = QSharedPointer<KoShapeBackground>(new KoOdfGradientBackground());
267 } else {
268 QGradient *gradient = new QLinearGradient();
269 gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
270 bg = QSharedPointer<KoShapeBackground>(new KoGradientBackground(gradient));
271 }
272 } else if (fill == "bitmap") {
273 bg = QSharedPointer<KoShapeBackground>(new KoPatternBackground(context.imageCollection()));
274 #ifndef NWORKAROUND_ODF_BUGS
275 } else if (fill.isEmpty()) {
276 bg = QSharedPointer<KoShapeBackground>(KoOdfWorkaround::fixBackgroundColor(title, context));
277 return bg;
278 #endif
279 } else {
280 return QSharedPointer<KoShapeBackground>(0);
281 }
282
283 if (!bg->loadStyle(context.odfLoadingContext(), title->size())) {
284 return QSharedPointer<KoShapeBackground>(0);
285 }
286
287 return bg;
288 }
289
loadOdfStroke(KoShape * title,const KoXmlElement & element,KoShapeLoadingContext & context)290 KoShapeStrokeModel *loadOdfStroke(KoShape *title, const KoXmlElement &element, KoShapeLoadingContext &context)
291 {
292 KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
293 KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader();
294
295 QString stroke = getStyleProperty("stroke", context);
296 if (stroke == "solid" || stroke == "dash") {
297 QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader);
298
299 KoShapeStroke *stroke = new KoShapeStroke();
300
301 if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) {
302 QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient");
303 QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, title->size());
304 stroke->setLineBrush(brush);
305 } else {
306 stroke->setColor(pen.color());
307 }
308
309 #ifndef NWORKAROUND_ODF_BUGS
310 KoOdfWorkaround::fixPenWidth(pen, context);
311 #endif
312 stroke->setLineWidth(pen.widthF());
313 stroke->setJoinStyle(pen.joinStyle());
314 stroke->setLineStyle(pen.style(), pen.dashPattern());
315 stroke->setCapStyle(pen.capStyle());
316
317 return stroke;
318 #ifndef NWORKAROUND_ODF_BUGS
319 } else if (stroke.isEmpty()) {
320 QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader);
321 if (KoOdfWorkaround::fixMissingStroke(pen, element, context, title)) {
322 KoShapeStroke *stroke = new KoShapeStroke();
323
324 #ifndef NWORKAROUND_ODF_BUGS
325 KoOdfWorkaround::fixPenWidth(pen, context);
326 #endif
327 stroke->setLineWidth(pen.widthF());
328 stroke->setJoinStyle(pen.joinStyle());
329 stroke->setLineStyle(pen.style(), pen.dashPattern());
330 stroke->setCapStyle(pen.capStyle());
331 stroke->setColor(pen.color());
332
333 return stroke;
334 }
335 #endif
336 }
337
338 return 0;
339 }
340
loadOdfShadow(KoShape * title,KoShapeLoadingContext & context)341 KoShapeShadow *loadOdfShadow(KoShape *title, KoShapeLoadingContext &context)
342 {
343 Q_UNUSED(title);
344 KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
345 QString shadowStyle = getStyleProperty("shadow", context);
346 if (shadowStyle == "visible" || shadowStyle == "hidden") {
347 KoShapeShadow *shadow = new KoShapeShadow();
348 QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color"));
349 qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x"));
350 qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y"));
351 shadow->setOffset(QPointF(offsetX, offsetY));
352 qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius"));
353 shadow->setBlur(blur);
354
355 QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity");
356 if (! opacity.isEmpty() && opacity.right(1) == "%")
357 shadowColor.setAlphaF(opacity.leftRef(opacity.length() - 1).toFloat() / 100.0);
358 shadow->setColor(shadowColor);
359 shadow->setVisible(shadowStyle == "visible");
360
361 return shadow;
362 }
363 return 0;
364 }
365
loadOdfBorder(KoShape * title,KoShapeLoadingContext & context)366 KoBorder *loadOdfBorder(KoShape *title, KoShapeLoadingContext &context)
367 {
368 Q_UNUSED(title);
369 KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
370
371 KoBorder *border = new KoBorder();
372 if (border->loadOdf(styleStack)) {
373 return border;
374 }
375 delete border;
376 return 0;
377 }
378
loadOdfTitle(KoShape * title,KoXmlElement & titleElement,KoShapeLoadingContext & context)379 bool loadOdfTitle(KoShape *title, KoXmlElement &titleElement, KoShapeLoadingContext &context)
380 {
381 TextLabelData *titleData = qobject_cast<TextLabelData*>(title->userData());
382 if (!titleData)
383 return false;
384
385 // Following will always return false cause KoTextShapeData::loadOdf will try to load
386 // a frame while our text:p is not within a frame. So, let's just not call loadOdf then...
387 //title->loadOdf(titleElement, context);
388
389 QTextDocument* doc = titleData->document();
390 doc->setPlainText(QString()); // remove default text
391 QTextCursor cursor(doc);
392 QTextCharFormat charFormat = cursor.charFormat();
393
394 title->setSize(QSize(0,0));
395 title->setPosition(QPointF(0,0));
396
397 bool autoPosition = !(titleElement.hasAttributeNS(KoXmlNS::svg, "x") && titleElement.hasAttributeNS(KoXmlNS::svg, "y"));
398 bool autoSize = !(titleElement.hasAttributeNS(KoXmlNS::svg, "width") && titleElement.hasAttributeNS(KoXmlNS::svg, "height"));
399 qreal rotationAngle = title->rotation();
400 // Set the styles
401 if (titleElement.hasAttributeNS(KoXmlNS::chart, "style-name")) {
402 KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
403 styleStack.clear();
404 context.odfLoadingContext().fillStyleStack(titleElement, KoXmlNS::chart, "style-name", "chart");
405
406 styleStack.setTypeProperties("chart");
407 if (styleStack.hasProperty(KoXmlNS::style, "rotation-angle")) {
408 rotationAngle = 360 - KoUnit::parseValue(styleStack.property(KoXmlNS::style, "rotation-angle"));
409 if (rotationAngle != title->rotation()) {
410 title->rotate(rotationAngle);
411 }
412 }
413 if (styleStack.hasProperty(KoXmlNS::style, "auto-position")) {
414 autoPosition |= styleStack.property(KoXmlNS::style, "auto-position") == "true";
415 }
416 if (styleStack.hasProperty(KoXmlNS::style, "auto-size")) {
417 autoSize |= styleStack.property(KoXmlNS::style, "auto-size") == "true" ;
418 }
419 // title->loadStyle(titleElement, context) is protected
420 // so we duplicate some code:
421 styleStack.setTypeProperties("graphic");
422
423 title->setBackground(loadOdfFill(title, context));
424 title->setStroke(loadOdfStroke(title, titleElement, context));
425 title->setShadow(loadOdfShadow(title, context));
426 title->setBorder(loadOdfBorder(title, context));
427
428 styleStack.setTypeProperties("text");
429
430 QFont font = doc->defaultFont();
431 if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) {
432 const qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size"));
433 font.setPointSizeF(fontSize);
434 }
435 if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) {
436 const QString fontFamily = styleStack.property(KoXmlNS::fo, "font-family");
437 font.setFamily(fontFamily);
438 }
439 if (styleStack.hasProperty(KoXmlNS::fo, "font-style")) {
440 QString fontStyle = styleStack.property(KoXmlNS::fo, "font-style");
441 if (fontStyle == "italic") {
442 font.setItalic(true);
443 } else if (fontStyle == "oblique") {
444 font.setStyle(QFont::StyleOblique);
445 }
446 }
447 if (styleStack.hasProperty(KoXmlNS::fo, "font-weight")) {
448 QString fontWeight = styleStack.property(KoXmlNS::fo, "font-weight");
449 font.setWeight(fromOdfFontWeight(fontWeight));
450 }
451 doc->setDefaultFont(font);
452 charFormat.setFont(doc->defaultFont());
453
454 if (styleStack.hasProperty(KoXmlNS::fo, "color")) {
455 const QColor color(styleStack.property(KoXmlNS::fo, "color"));
456 charFormat.setForeground(color);
457 }
458 cursor.setCharFormat(charFormat);
459 }
460 title->setAdditionalStyleAttribute("chart:auto-position", autoPosition ? "true" : "false");
461 if (!autoPosition) {
462 QPointF pos;
463 pos.setX(KoUnit::parseValue(titleElement.attributeNS(KoXmlNS::svg, "x", QString())));
464 if (pos.x() < 0) {
465 pos.setX(0);
466 }
467 pos.setY(KoUnit::parseValue(titleElement.attributeNS(KoXmlNS::svg, "y", QString())));
468 if (pos.y() < 0) {
469 pos.setY(0);
470 }
471 title->setPosition(pos);
472 debugChartOdf<<"position:"<<"odf:"<<pos<<"title"<<title->position();
473 }
474 if (autoSize) {
475 titleData->setResizeMethod(KoTextShapeDataBase::AutoResize);
476 } else {
477 titleData->setResizeMethod(KoTextShapeDataBase::NoResize);
478 QSizeF size;
479 size.setWidth(KoUnit::parseValue(titleElement.attributeNS(KoXmlNS::svg, "width")));
480 size.setHeight(KoUnit::parseValue(titleElement.attributeNS(KoXmlNS::svg, "height")));
481 title->setSize(size);
482 debugChartOdf<<"size:"<<"odf:"<<size<<"title"<<title->size();
483 }
484
485 // load text
486 bool loaded = false;
487 if (context.documentResourceManager()) {
488 const KoXmlElement textElement = KoXml::namedItemNS(titleElement, KoXmlNS::calligra, "text");
489 if (!textElement.isNull()) {
490 loaded = titleData->loadOdf(textElement, context, 0, title);
491 title->setVisible(true);
492 }
493 }
494 if (!loaded) {
495 const KoXmlElement textElement = KoXml::namedItemNS(titleElement, KoXmlNS::text, "p");
496 if (!textElement.isNull()) {
497 cursor.insertText(textElement.text(), charFormat);
498 title->setVisible(true);
499 }
500 }
501 debugChartOdf<<title->position()<<title->size()<<titleData->document()->toPlainText();
502 return true;
503 }
504
505 } // Namespace OdfHelper
506 } // Namespace KoChart
507