File indexing completed on 2024-05-12 16:33:34

0001 /* This file is part of the KDE project
0002 
0003    Copyright 2018 Dag Andersen <danders@get2net.dk>
0004 
0005    This library is free software; you can redistribute it and/or
0006    modify it under the terms of the GNU Library General Public
0007    License as published by the Free Software Foundation; either
0008    version 2 of the License, or (at your option) any later version.
0009 
0010    This library is distributed in the hope that it will be useful,
0011    but WITHOUT ANY WARRANTY; without even the implied warranty of
0012    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0013    Library General Public License for more details.
0014 
0015    You should have received a copy of the GNU Library General Public License
0016    along with this library; see the file COPYING.LIB.  If not, write to
0017    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0018    Boston, MA 02110-1301, USA.
0019 */
0020 
0021 
0022 #include "OdfHelper.h"
0023 
0024 #include "ChartDebug.h"
0025 
0026 // Posix
0027 #include <float.h> // For basic data types characteristics.
0028 
0029 // Qt
0030 #include <QPointF>
0031 #include <QPainter>
0032 #include <QSizeF>
0033 #include <QTextDocument>
0034 #include <QStandardItemModel>
0035 #include <QUrl>
0036 
0037 // Calligra
0038 #include <KoShapeLoadingContext.h>
0039 #include <KoOdfLoadingContext.h>
0040 #include <KoEmbeddedDocumentSaver.h>
0041 #include <KoStore.h>
0042 #include <KoDocument.h>
0043 #include <KoShapeSavingContext.h>
0044 #include <KoViewConverter.h>
0045 #include <KoXmlReader.h>
0046 #include <KoXmlWriter.h>
0047 #include <KoXmlNS.h>
0048 #include <KoGenStyles.h>
0049 #include <KoStyleStack.h>
0050 #include <KoShapeRegistry.h>
0051 #include <KoTextShapeData.h>
0052 #include <KoTextDocumentLayout.h>
0053 #include <KoCanvasBase.h>
0054 #include <KoShapeManager.h>
0055 #include <KoSelection.h>
0056 #include <KoShapeBackground.h>
0057 #include <KoInsets.h>
0058 #include <KoShapeStrokeModel.h>
0059 #include <KoColorBackground.h>
0060 #include <KoShapeStroke.h>
0061 #include <KoOdfWorkaround.h>
0062 #include <KoTextDocument.h>
0063 #include <KoUnit.h>
0064 #include <KoShapePaintingContext.h>
0065 #include <KoTextShapeDataBase.h>
0066 #include <KoShapeShadow.h>
0067 #include <KoBorder.h>
0068 #include <KoHatchBackground.h>
0069 #include <KoOdfGradientBackground.h>
0070 #include <KoPatternBackground.h>
0071 #include <KoGradientBackground.h>
0072 #include <KoOdfGraphicStyles.h>
0073 #include "kochart_global.h"
0074 
0075 namespace KoChart {
0076 namespace OdfHelper {
0077 
0078 
0079 // HACK: To get correct position also for rotated titles
0080 QPointF itemPosition(const KoShape *shape)
0081 {
0082     return QPointF(shape->transformation().dx(), shape->transformation().dy());
0083 }
0084 
0085 //fo:font-weight attribute are normal, bold, 100, 200, 300, 400, 500, 600, 700, 800 or 900.
0086 int fromOdfFontWeight(const QString &odfweight) {
0087     if (odfweight.isEmpty() || odfweight == "normal") {
0088         return QFont::Normal;
0089     }
0090     if (odfweight == "bold") {
0091         return QFont::Bold;
0092     }
0093     bool ok;
0094     int weight = odfweight.toInt(&ok);
0095     if (!ok) {
0096         return 50;
0097     }
0098     switch (weight) {
0099         case 100: weight = 1; break;
0100         case 200: weight = 17; break;
0101         case 300: weight = 33; break;
0102         case 400: weight = 50; break;
0103         case 500: weight = 58; break;
0104         case 600: weight = 66; break;
0105         case 700: weight = 75; break;
0106         case 800: weight = 87; break;
0107         case 900: weight = 99; break;
0108         default: weight = 50; break;
0109     }
0110     return weight;
0111 }
0112 
0113 QString toOdfFontWeight(int weight) {
0114     QString w;
0115     if (weight < 8) {
0116         w = "100";
0117     } else if (weight < 25) {
0118         w = "200";
0119     } else if (weight < 41) {
0120         w = "300";
0121     } else if (weight < 54) {
0122         w = "normal";
0123     } else if (weight < 62) {
0124         w = "500";
0125     } else if (weight < 70) {
0126         w = "600";
0127     } else if (weight < 81) {
0128         w = "bold";
0129     } else if (weight < 92) {
0130         w = "800";
0131     } else {
0132         w = "900";
0133     }
0134     return w;
0135 }
0136 
0137 void saveOdfFont(KoGenStyle &style, const QFont& font, const QColor& color)
0138 {
0139     style.addProperty("fo:font-family", font.family(), KoGenStyle::TextType);
0140     style.addPropertyPt("fo:font-size", font.pointSize(), KoGenStyle::TextType);
0141     style.addProperty("fo:color", color.isValid() ? color.name() : "#000000", KoGenStyle::TextType);
0142     style.addProperty("fo:font-weight", toOdfFontWeight(font.weight()), KoGenStyle::TextType);
0143     style.addProperty("fo:font-style", font.italic() ? "italic" : "normal", KoGenStyle::TextType);
0144 }
0145 
0146 QString saveOdfFont(KoGenStyles& mainStyles,
0147                     const QFont& font,
0148                     const QColor& color)
0149 {
0150     KoGenStyle autoStyle(KoGenStyle::ParagraphAutoStyle, "chart", 0);
0151     saveOdfFont(autoStyle, font, color);
0152     return mainStyles.insert(autoStyle, "ch");
0153 }
0154 
0155 void saveOdfTitleStyle(KoShape *title, KoGenStyle &style, KoShapeSavingContext &context)
0156 {
0157     TextLabelData *titleData = qobject_cast<TextLabelData*>(title->userData());
0158     Q_ASSERT(titleData);
0159     QTextCursor cursor(titleData->document());
0160     QFont titleFont = cursor.charFormat().font();
0161     QColor color = cursor.charFormat().foreground().color();
0162 
0163     saveOdfFont(style, titleFont, color);
0164 
0165     // title->saveStyle(style, context) is protected,
0166     // so we duplicate some code:
0167     KoShapeStrokeModel *sm = title->stroke();
0168     if (sm) {
0169         sm->fillStyle(style, context);
0170     } else {
0171         style.addProperty("draw:stroke", "none", KoGenStyle::GraphicType);
0172     }
0173     KoShapeShadow *s = title->shadow();
0174     if (s) {
0175         s->fillStyle(style, context);
0176     }
0177     QSharedPointer<KoShapeBackground> bg = title->background();
0178     if (bg) {
0179         bg->fillStyle(style, context);
0180     } else {
0181         style.addProperty("draw:fill", "none", KoGenStyle::GraphicType);
0182     }
0183 
0184     KoBorder *b = title->border();
0185     if (b) {
0186         b->saveOdf(style);
0187     }
0188 
0189     QMap<QByteArray, QString>::const_iterator it(title->additionalStyleAttributes().constBegin());
0190     for (; it != title->additionalStyleAttributes().constEnd(); ++it) {
0191         style.addProperty(it.key(), it.value(), KoGenStyle::ChartType);
0192     }
0193     style.addProperty("chart:auto-size", (titleData->resizeMethod() == KoTextShapeDataBase::AutoResize), KoGenStyle::ChartType);
0194 }
0195 
0196 void saveOdfTitle(KoShape *title, KoXmlWriter &bodyWriter, const char *titleType, KoShapeSavingContext &context)
0197 {
0198     // Don't save hidden titles, as that's the way of removing them
0199     // from a chart.
0200     if (!title->isVisible())
0201         return;
0202 
0203     TextLabelData *titleData = qobject_cast<TextLabelData*>(title->userData());
0204     if (!titleData)
0205         return;
0206 
0207     bodyWriter.startElement(titleType);
0208 
0209     KoGenStyle autoStyle(KoGenStyle::ChartAutoStyle, "chart", 0);
0210     autoStyle.addPropertyPt("style:rotation-angle", 360 - title->rotation());
0211     saveOdfTitleStyle(title, autoStyle, context);
0212 
0213     // always save position and size and let consumer decide if they are used
0214     QPointF position = itemPosition(title);
0215     bodyWriter.addAttributePt("svg:x", position.x());
0216     bodyWriter.addAttributePt("svg:y", position.y());
0217 
0218     const QSizeF size = title->size();
0219     bodyWriter.addAttributePt("svg:width", size.width());
0220     bodyWriter.addAttributePt("svg:height", size.height());
0221 
0222     bodyWriter.addAttribute("chart:style-name", context.mainStyles().insert(autoStyle, "ch"));
0223 
0224     // lo (and odf?) does not support formatted text :(
0225     bodyWriter.startElement("text:p");
0226     bodyWriter.addTextNode(titleData->document()->toPlainText());
0227     bodyWriter.endElement(); // text:p
0228 
0229     // save calligra specific formatted text
0230     bodyWriter.startElement("calligra:text");
0231     titleData->saveOdf(context);
0232     bodyWriter.endElement(); // calligra:text
0233 
0234     bodyWriter.endElement(); // chart:title/subtitle/footer
0235 }
0236 
0237 QString getStyleProperty(const char *property, KoShapeLoadingContext &context)
0238 {
0239     KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
0240     QString value;
0241 
0242     if (styleStack.hasProperty(KoXmlNS::draw, property)) {
0243         value = styleStack.property(KoXmlNS::draw, property);
0244     }
0245 
0246     return value;
0247 }
0248 
0249 QSharedPointer<KoShapeBackground> loadOdfFill(KoShape *title, KoShapeLoadingContext &context)
0250 {
0251     QString fill = getStyleProperty("fill", context);
0252     QSharedPointer<KoShapeBackground> bg;
0253     if (fill == "solid") {
0254         bg = QSharedPointer<KoShapeBackground>(new KoColorBackground());
0255     }
0256     else if (fill == "hatch") {
0257         bg = QSharedPointer<KoShapeBackground>(new KoHatchBackground());
0258     } else if (fill == "gradient") {
0259         QString styleName = getStyleProperty("fill-gradient-name", context);
0260         KoXmlElement *e = context.odfLoadingContext().stylesReader().drawStyles("gradient").value(styleName);
0261         QString style;
0262         if (e) {
0263             style = e->attributeNS(KoXmlNS::draw, "style", QString());
0264         }
0265         if ((style == "rectangular") || (style == "square")) {
0266             bg = QSharedPointer<KoShapeBackground>(new KoOdfGradientBackground());
0267         } else {
0268             QGradient *gradient = new QLinearGradient();
0269             gradient->setCoordinateMode(QGradient::ObjectBoundingMode);
0270             bg = QSharedPointer<KoShapeBackground>(new KoGradientBackground(gradient));
0271         }
0272     } else if (fill == "bitmap") {
0273         bg = QSharedPointer<KoShapeBackground>(new KoPatternBackground(context.imageCollection()));
0274 #ifndef NWORKAROUND_ODF_BUGS
0275     } else if (fill.isEmpty()) {
0276         bg = QSharedPointer<KoShapeBackground>(KoOdfWorkaround::fixBackgroundColor(title, context));
0277         return bg;
0278 #endif
0279     } else {
0280         return QSharedPointer<KoShapeBackground>(0);
0281     }
0282 
0283     if (!bg->loadStyle(context.odfLoadingContext(), title->size())) {
0284         return QSharedPointer<KoShapeBackground>(0);
0285     }
0286 
0287     return bg;
0288 }
0289 
0290 KoShapeStrokeModel *loadOdfStroke(KoShape *title, const KoXmlElement &element, KoShapeLoadingContext &context)
0291 {
0292     KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
0293     KoOdfStylesReader &stylesReader = context.odfLoadingContext().stylesReader();
0294 
0295     QString stroke = getStyleProperty("stroke", context);
0296     if (stroke == "solid" || stroke == "dash") {
0297         QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, stroke, stylesReader);
0298 
0299         KoShapeStroke *stroke = new KoShapeStroke();
0300 
0301         if (styleStack.hasProperty(KoXmlNS::calligra, "stroke-gradient")) {
0302             QString gradientName = styleStack.property(KoXmlNS::calligra, "stroke-gradient");
0303             QBrush brush = KoOdfGraphicStyles::loadOdfGradientStyleByName(stylesReader, gradientName, title->size());
0304             stroke->setLineBrush(brush);
0305         } else {
0306             stroke->setColor(pen.color());
0307         }
0308 
0309 #ifndef NWORKAROUND_ODF_BUGS
0310         KoOdfWorkaround::fixPenWidth(pen, context);
0311 #endif
0312         stroke->setLineWidth(pen.widthF());
0313         stroke->setJoinStyle(pen.joinStyle());
0314         stroke->setLineStyle(pen.style(), pen.dashPattern());
0315         stroke->setCapStyle(pen.capStyle());
0316 
0317         return stroke;
0318 #ifndef NWORKAROUND_ODF_BUGS
0319     } else if (stroke.isEmpty()) {
0320         QPen pen = KoOdfGraphicStyles::loadOdfStrokeStyle(styleStack, "solid", stylesReader);
0321         if (KoOdfWorkaround::fixMissingStroke(pen, element, context, title)) {
0322             KoShapeStroke *stroke = new KoShapeStroke();
0323 
0324 #ifndef NWORKAROUND_ODF_BUGS
0325             KoOdfWorkaround::fixPenWidth(pen, context);
0326 #endif
0327             stroke->setLineWidth(pen.widthF());
0328             stroke->setJoinStyle(pen.joinStyle());
0329             stroke->setLineStyle(pen.style(), pen.dashPattern());
0330             stroke->setCapStyle(pen.capStyle());
0331             stroke->setColor(pen.color());
0332 
0333             return stroke;
0334         }
0335 #endif
0336     }
0337 
0338     return 0;
0339 }
0340 
0341 KoShapeShadow *loadOdfShadow(KoShape *title, KoShapeLoadingContext &context)
0342 {
0343     Q_UNUSED(title);
0344     KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
0345     QString shadowStyle = getStyleProperty("shadow", context);
0346     if (shadowStyle == "visible" || shadowStyle == "hidden") {
0347         KoShapeShadow *shadow = new KoShapeShadow();
0348         QColor shadowColor(styleStack.property(KoXmlNS::draw, "shadow-color"));
0349         qreal offsetX = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-x"));
0350         qreal offsetY = KoUnit::parseValue(styleStack.property(KoXmlNS::draw, "shadow-offset-y"));
0351         shadow->setOffset(QPointF(offsetX, offsetY));
0352         qreal blur = KoUnit::parseValue(styleStack.property(KoXmlNS::calligra, "shadow-blur-radius"));
0353         shadow->setBlur(blur);
0354 
0355         QString opacity = styleStack.property(KoXmlNS::draw, "shadow-opacity");
0356         if (! opacity.isEmpty() && opacity.right(1) == "%")
0357             shadowColor.setAlphaF(opacity.leftRef(opacity.length() - 1).toFloat() / 100.0);
0358         shadow->setColor(shadowColor);
0359         shadow->setVisible(shadowStyle == "visible");
0360 
0361         return shadow;
0362     }
0363     return 0;
0364 }
0365 
0366 KoBorder *loadOdfBorder(KoShape *title, KoShapeLoadingContext &context)
0367 {
0368     Q_UNUSED(title);
0369     KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
0370 
0371     KoBorder *border = new KoBorder();
0372     if (border->loadOdf(styleStack)) {
0373         return border;
0374     }
0375     delete border;
0376     return 0;
0377 }
0378 
0379 bool loadOdfTitle(KoShape *title, KoXmlElement &titleElement, KoShapeLoadingContext &context)
0380 {
0381     TextLabelData *titleData = qobject_cast<TextLabelData*>(title->userData());
0382     if (!titleData)
0383         return false;
0384 
0385     // Following will always return false cause KoTextShapeData::loadOdf will try to load
0386     // a frame while our text:p is not within a frame. So, let's just not call loadOdf then...
0387     //title->loadOdf(titleElement, context);
0388 
0389     QTextDocument* doc = titleData->document();
0390     doc->setPlainText(QString()); // remove default text
0391     QTextCursor cursor(doc);
0392     QTextCharFormat charFormat = cursor.charFormat();
0393 
0394     title->setSize(QSize(0,0));
0395     title->setPosition(QPointF(0,0));
0396 
0397     bool autoPosition = !(titleElement.hasAttributeNS(KoXmlNS::svg, "x") && titleElement.hasAttributeNS(KoXmlNS::svg, "y"));
0398     bool autoSize = !(titleElement.hasAttributeNS(KoXmlNS::svg, "width") && titleElement.hasAttributeNS(KoXmlNS::svg, "height"));
0399     qreal rotationAngle = title->rotation();
0400     // Set the styles
0401     if (titleElement.hasAttributeNS(KoXmlNS::chart, "style-name")) {
0402         KoStyleStack &styleStack = context.odfLoadingContext().styleStack();
0403         styleStack.clear();
0404         context.odfLoadingContext().fillStyleStack(titleElement, KoXmlNS::chart, "style-name", "chart");
0405 
0406         styleStack.setTypeProperties("chart");
0407         if (styleStack.hasProperty(KoXmlNS::style, "rotation-angle")) {
0408             rotationAngle = 360 - KoUnit::parseValue(styleStack.property(KoXmlNS::style, "rotation-angle"));
0409             if (rotationAngle != title->rotation()) {
0410                 title->rotate(rotationAngle);
0411             }
0412         }
0413         if (styleStack.hasProperty(KoXmlNS::style, "auto-position")) {
0414             autoPosition |= styleStack.property(KoXmlNS::style, "auto-position") == "true";
0415         }
0416         if (styleStack.hasProperty(KoXmlNS::style, "auto-size")) {
0417             autoSize |= styleStack.property(KoXmlNS::style, "auto-size") == "true" ;
0418         }
0419         // title->loadStyle(titleElement, context) is protected
0420         // so we duplicate some code:
0421         styleStack.setTypeProperties("graphic");
0422 
0423         title->setBackground(loadOdfFill(title, context));
0424         title->setStroke(loadOdfStroke(title, titleElement, context));
0425         title->setShadow(loadOdfShadow(title, context));
0426         title->setBorder(loadOdfBorder(title, context));
0427 
0428         styleStack.setTypeProperties("text");
0429 
0430         QFont font = doc->defaultFont();
0431         if (styleStack.hasProperty(KoXmlNS::fo, "font-size")) {
0432             const qreal fontSize = KoUnit::parseValue(styleStack.property(KoXmlNS::fo, "font-size"));
0433             font.setPointSizeF(fontSize);
0434         }
0435         if (styleStack.hasProperty(KoXmlNS::fo, "font-family")) {
0436             const QString fontFamily = styleStack.property(KoXmlNS::fo, "font-family");
0437             font.setFamily(fontFamily);
0438         }
0439         if (styleStack.hasProperty(KoXmlNS::fo, "font-style")) {
0440             QString fontStyle = styleStack.property(KoXmlNS::fo, "font-style");
0441             if (fontStyle == "italic") {
0442                 font.setItalic(true);
0443             } else if (fontStyle == "oblique") {
0444                 font.setStyle(QFont::StyleOblique);
0445             }
0446         }
0447         if (styleStack.hasProperty(KoXmlNS::fo, "font-weight")) {
0448             QString fontWeight = styleStack.property(KoXmlNS::fo, "font-weight");
0449             font.setWeight(fromOdfFontWeight(fontWeight));
0450         }
0451         doc->setDefaultFont(font);
0452         charFormat.setFont(doc->defaultFont());
0453 
0454         if (styleStack.hasProperty(KoXmlNS::fo, "color")) {
0455             const QColor color(styleStack.property(KoXmlNS::fo, "color"));
0456             charFormat.setForeground(color);
0457         }
0458         cursor.setCharFormat(charFormat);
0459     }
0460     title->setAdditionalStyleAttribute("chart:auto-position", autoPosition ? "true" : "false");
0461     if (!autoPosition) {
0462         QPointF pos;
0463         pos.setX(KoUnit::parseValue(titleElement.attributeNS(KoXmlNS::svg, "x", QString())));
0464         if (pos.x() < 0) {
0465             pos.setX(0);
0466         }
0467         pos.setY(KoUnit::parseValue(titleElement.attributeNS(KoXmlNS::svg, "y", QString())));
0468         if (pos.y() < 0) {
0469             pos.setY(0);
0470         }
0471         title->setPosition(pos);
0472         debugChartOdf<<"position:"<<"odf:"<<pos<<"title"<<title->position();
0473     }
0474     if (autoSize) {
0475         titleData->setResizeMethod(KoTextShapeDataBase::AutoResize);
0476     } else {
0477         titleData->setResizeMethod(KoTextShapeDataBase::NoResize);
0478         QSizeF size;
0479         size.setWidth(KoUnit::parseValue(titleElement.attributeNS(KoXmlNS::svg, "width")));
0480         size.setHeight(KoUnit::parseValue(titleElement.attributeNS(KoXmlNS::svg, "height")));
0481         title->setSize(size);
0482         debugChartOdf<<"size:"<<"odf:"<<size<<"title"<<title->size();
0483     }
0484 
0485     // load text
0486     bool loaded = false;
0487     if (context.documentResourceManager()) {
0488         const KoXmlElement textElement = KoXml::namedItemNS(titleElement, KoXmlNS::calligra, "text");
0489         if (!textElement.isNull()) {
0490             loaded = titleData->loadOdf(textElement, context, 0, title);
0491             title->setVisible(true);
0492         }
0493     }
0494     if (!loaded) {
0495         const KoXmlElement textElement = KoXml::namedItemNS(titleElement, KoXmlNS::text, "p");
0496         if (!textElement.isNull()) {
0497             cursor.insertText(textElement.text(), charFormat);
0498             title->setVisible(true);
0499         }
0500     }
0501     debugChartOdf<<title->position()<<title->size()<<titleData->document()->toPlainText();
0502     return true;
0503 }
0504 
0505 } // Namespace OdfHelper
0506 } // Namespace KoChart