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