File indexing completed on 2024-05-05 08:53:24
0001 /* This file is part of the KDE project 0002 Copyright (C) 2010-2015 Jarosław Staniek <staniek@kde.org> 0003 0004 This library is free software; you can redistribute it and/or 0005 modify it under the terms of the GNU Library General Public 0006 License as published by the Free Software Foundation; either 0007 version 2 of the License, or (at your option) any later version. 0008 0009 This library is distributed in the hope that it will be useful, 0010 but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 Library General Public License for more details. 0013 0014 You should have received a copy of the GNU Library General Public License 0015 along with this library; see the file COPYING.LIB. If not, write to 0016 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0017 * Boston, MA 02110-1301, USA. 0018 */ 0019 0020 #include "KReportUtils.h" 0021 #include "KReportUnit.h" 0022 #include "KReportItemBase.h" 0023 #include "KReportLineStyle.h" 0024 0025 #include <KProperty> 0026 0027 #include <QDomDocument> 0028 #include <QDomElement> 0029 0030 #include <float.h> 0031 0032 QString KReportUtils::attr(const QDomElement &el, const QString &attrName, 0033 const QString &defaultValue) 0034 { 0035 const QString val = el.attribute(attrName); 0036 return val.isEmpty() ? defaultValue : val; 0037 } 0038 0039 QByteArray KReportUtils::attr(const QDomElement &el, const QString &attrName, 0040 const QByteArray &defaultValue) 0041 { 0042 const QByteArray val = el.attribute(attrName).toLatin1(); 0043 return val.isEmpty() ? defaultValue : val; 0044 } 0045 0046 bool KReportUtils::attr(const QDomElement &el, const QString &attrName, bool defaultValue) 0047 { 0048 const QString val = el.attribute(attrName); 0049 return val.isEmpty() ? defaultValue : QVariant(val).toBool(); 0050 } 0051 0052 int KReportUtils::attr(const QDomElement &el, const QString &attrName, int defaultValue) 0053 { 0054 const QString val = el.attribute(attrName); 0055 if (val.isEmpty()) { 0056 return defaultValue; 0057 } 0058 bool ok; 0059 const int result = QVariant(val).toInt(&ok); 0060 return ok ? result : defaultValue; 0061 } 0062 0063 qreal KReportUtils::attr(const QDomElement &el, const QString &attrName, qreal defaultValue) 0064 { 0065 const QString val = el.attribute(attrName); 0066 return KReportUnit::parseValue(val, defaultValue); 0067 } 0068 0069 QColor KReportUtils::attr(const QDomElement &el, const QString &attrName, const QColor &defaultValue) 0070 { 0071 const QString val = el.attribute(attrName); 0072 if (val.isEmpty()) { 0073 return defaultValue; 0074 } 0075 return QColor(val); 0076 } 0077 0078 qreal KReportUtils::attrPercent(const QDomElement& el, const QString &attrName, qreal defaultValue) 0079 { 0080 QString str(el.attribute(attrName)); 0081 if (str.isEmpty() || !str.endsWith(QLatin1Char('%'))) { 0082 return defaultValue; 0083 } 0084 str.chop(1); 0085 bool ok; 0086 const qreal result = QVariant(str).toReal(&ok) / 100.0; 0087 if (!ok) { 0088 return defaultValue; 0089 } 0090 return result; 0091 } 0092 0093 Qt::PenStyle KReportUtils::penStyle(const QString& str, Qt::PenStyle defaultValue) 0094 { 0095 const QByteArray s(str.toLatin1()); 0096 if (s == "nopen" || s == "none") { 0097 return Qt::NoPen; 0098 } else if (s == "solid") { 0099 return Qt::SolidLine; 0100 } else if (s == "dash" || s == "wave" /*we have nothing better for now*/) { 0101 return Qt::DashLine; 0102 } else if (s == "dot" || s == "dotted") { 0103 return Qt::DotLine; 0104 } else if (s == "dashdot" || s == "dot-dash") { 0105 return Qt::DashDotLine; 0106 } else if (s == "dashdotdot" || s == "dot-dot-dash") { 0107 return Qt::DashDotDotLine; 0108 } else { 0109 return defaultValue; 0110 } 0111 } 0112 0113 Qt::Alignment KReportUtils::verticalAlignment(const QString &str, Qt::Alignment defaultValue) 0114 { 0115 const QByteArray s(str.toLatin1()); 0116 if (s == "center") { 0117 return Qt::AlignVCenter; 0118 } else if (s == "top") { 0119 return Qt::AlignTop; 0120 } else if (s == "bottom") { 0121 return Qt::AlignBottom; 0122 } else { 0123 return defaultValue; 0124 } 0125 } 0126 0127 Qt::Alignment KReportUtils::horizontalAlignment(const QString &str, Qt::Alignment defaultValue) 0128 { 0129 const QByteArray s(str.toLatin1()); 0130 if (s == "center") { 0131 return Qt::AlignHCenter; 0132 } else if (s == "right") { 0133 return Qt::AlignRight; 0134 } else if (s == "left") { 0135 return Qt::AlignLeft; 0136 } else { 0137 return defaultValue; 0138 } 0139 } 0140 0141 QString KReportUtils::verticalToString(Qt::Alignment alignment) 0142 { 0143 if (alignment.testFlag(Qt::AlignVCenter)) { 0144 return QLatin1String("center"); 0145 } else if (alignment.testFlag(Qt::AlignTop)) { 0146 return QLatin1String("top"); 0147 } else if (alignment.testFlag(Qt::AlignBottom)) { 0148 return QLatin1String("bottom"); 0149 } 0150 return QString(); 0151 } 0152 0153 QString KReportUtils::horizontalToString(Qt::Alignment alignment) 0154 { 0155 if (alignment.testFlag(Qt::AlignHCenter)) { 0156 return QLatin1String("center"); 0157 } else if (alignment.testFlag(Qt::AlignLeft)) { 0158 return QLatin1String("left"); 0159 } else if (alignment.testFlag(Qt::AlignRight)) { 0160 return QLatin1String("right"); 0161 } 0162 return QString(); 0163 } 0164 0165 QString KReportUtils::readNameAttribute(const QDomElement &el, const QString &defaultValue) 0166 { 0167 return attr(el, QLatin1String("report:name"), defaultValue); 0168 } 0169 0170 QSizeF KReportUtils::readSizeAttributes(const QDomElement &el, const QSizeF &defaultValue) 0171 { 0172 QSizeF val; 0173 val.setWidth(attr(el, QLatin1String("svg:width"), defaultValue.width())); 0174 if (val.width() < 0.0) { 0175 val.setWidth(defaultValue.width()); 0176 } 0177 val.setHeight(attr(el, QLatin1String("svg:height"), defaultValue.height())); 0178 if (val.height() < 0.0) { 0179 val.setHeight(defaultValue.height()); 0180 } 0181 return val; 0182 } 0183 0184 QRectF KReportUtils::readRectAttributes(const QDomElement &el, const QRectF &defaultValue) 0185 { 0186 QRectF val; 0187 val.setX(attr(el, QLatin1String("svg:x"), defaultValue.x())); 0188 val.setY(attr(el, QLatin1String("svg:y"), defaultValue.y())); 0189 val.setSize(readSizeAttributes(el, defaultValue.size())); 0190 return val; 0191 } 0192 0193 qreal KReportUtils::readZAttribute(const QDomElement &el, qreal defaultValue) 0194 { 0195 return KReportUtils::attr(el, QLatin1String("report:z-index"), defaultValue); 0196 } 0197 0198 int KReportUtils::readPercent(const QDomElement& el, const QString &attrName, int defaultPercentValue, bool *ok) 0199 { 0200 QString percent(el.attribute(attrName)); 0201 if (percent.isEmpty()) { 0202 if (ok) 0203 *ok = true; 0204 return defaultPercentValue; 0205 } 0206 if (!percent.endsWith(QLatin1Char('%'))) { 0207 if (ok) 0208 *ok = false; 0209 return 0; 0210 } 0211 percent.chop(1); 0212 if (ok) 0213 *ok = true; 0214 return percent.toInt(ok); 0215 } 0216 0217 QString KReportUtils::readSectionTypeNameAttribute(const QDomElement &el, const QString &defaultValue) 0218 { 0219 return attr(el, QLatin1String("report:section-type"), defaultValue); 0220 } 0221 0222 //! @return string representation of @a value, cuts of zeros; precision is set to 2 0223 static QString roundValueToString(qreal value) 0224 { 0225 QString s(QString::number(value, 'g', 2)); 0226 if (s.endsWith(QLatin1String(".00"))) 0227 return QString::number(qRound(value)); 0228 return s; 0229 } 0230 0231 //! Used by readFontAttributes() 0232 static QFont::Capitalization readFontCapitalization(const QByteArray& fontVariant, const QByteArray& textTransform) 0233 { 0234 if (fontVariant == "small-caps") 0235 return QFont::SmallCaps; 0236 if (textTransform == "uppercase") 0237 return QFont::AllUppercase; 0238 if (textTransform == "lowercase") 0239 return QFont::AllLowercase; 0240 if (textTransform == "capitalize") 0241 return QFont::Capitalize; 0242 // default, "normal" 0243 return QFont::MixedCase; 0244 } 0245 0246 void KReportUtils::readFontAttributes(const QDomElement& el, QFont *font) 0247 { 0248 Q_ASSERT(font); 0249 const QFont::Capitalization cap = readFontCapitalization( 0250 attr(el, QLatin1String("fo:font-variant"), QByteArray()), 0251 attr(el, QLatin1String("fo:text-transform"), QByteArray())); 0252 font->setCapitalization(cap); 0253 0254 // weight 0255 const QByteArray fontWeight(attr(el, QLatin1String("fo:font-weight"), QByteArray())); 0256 int weight = -1; 0257 if (fontWeight == "bold") { 0258 weight = QFont::Bold; 0259 } 0260 if (fontWeight == "normal") { 0261 weight = QFont::Normal; 0262 } 0263 else if (!fontWeight.isEmpty()) { 0264 // Remember : Qt and CSS/XSL doesn't have the same scale. It's 100-900 instead of Qt's 0-100 0265 // See http://www.w3.org/TR/2001/REC-xsl-20011015/slice7.html#font-weight 0266 // and http://www.w3.org/TR/CSS2/fonts.html#font-boldness 0267 bool ok; 0268 qreal boldness = fontWeight.toUInt(&ok); 0269 if (ok) { 0270 boldness = qMin(boldness, 900.0); 0271 boldness = qMax(boldness, 100.0); 0272 weight = (boldness - 100.0) * 0.12375 /*== 99/800*/; // 0..99 0273 } 0274 } 0275 if (weight >= 0) { 0276 font->setWeight(weight); 0277 } 0278 0279 font->setItalic(attr(el, QLatin1String("fo:font-style"), QByteArray()) == "italic"); 0280 font->setFixedPitch(attr(el, QLatin1String("style:font-pitch"), QByteArray()) == "fixed"); 0281 font->setFamily(attr(el, QLatin1String("fo:font-family"), font->family())); 0282 font->setKerning(attr(el, QLatin1String("style:letter-kerning"), font->kerning())); 0283 0284 // underline 0285 const QByteArray underlineType( 0286 attr(el, QLatin1String("style:text-underline-type"), QByteArray())); 0287 font->setUnderline(!underlineType.isEmpty() 0288 && underlineType 0289 != "none"); // double or single (we don't recognize them) 0290 0291 // stricken-out 0292 const QByteArray strikeOutType(attr(el, QLatin1String("style:text-line-through-type"), QByteArray())); 0293 font->setStrikeOut(!strikeOutType.isEmpty() && strikeOutType != "none"); // double or single (we don't recognize them) 0294 0295 //! @todo support fo:font-size-rel? 0296 //! @todo support fo:font-size in px 0297 font->setPointSizeF(KReportUtils::attr(el, QLatin1String("fo:font-size"), font->pointSizeF())); 0298 0299 // letter spacing 0300 // §7.16.2 of [XSL] http://www.w3.org/TR/xsl11/#letter-spacing 0301 font->setLetterSpacing(QFont::PercentageSpacing, 0302 100.0 * KReportUtils::attrPercent( 0303 el, QLatin1String("fo:letter-spacing"), font->letterSpacing())); 0304 } 0305 0306 void KReportUtils::writeFontAttributes(QDomElement *el, const QFont &font) 0307 { 0308 Q_ASSERT(el); 0309 switch (font.capitalization()) { 0310 case QFont::SmallCaps: 0311 el->setAttribute(QLatin1String("fo:font-variant"), QLatin1String("small-caps")); 0312 break; 0313 case QFont::MixedCase: 0314 // default: "normal", do not save 0315 break; 0316 case QFont::AllUppercase: 0317 el->setAttribute(QLatin1String("fo:text-transform"), QLatin1String("uppercase")); 0318 break; 0319 case QFont::AllLowercase: 0320 el->setAttribute(QLatin1String("fo:text-transform"), QLatin1String("lowercase")); 0321 break; 0322 case QFont::Capitalize: 0323 el->setAttribute(QLatin1String("fo:text-transform"), QLatin1String("capitalize")); 0324 break; 0325 } 0326 0327 // Remember : Qt and CSS/XSL doesn't have the same scale. It's 100-900 instead of Qt's 0-100 0328 // See http://www.w3.org/TR/2001/REC-xsl-20011015/slice7.html#font-weight 0329 // and http://www.w3.org/TR/CSS2/fonts.html#font-boldness 0330 if (font.weight() == QFont::Light) { 0331 el->setAttribute(QLatin1String("fo:font-weight"), 200); 0332 } 0333 else if (font.weight() == QFont::Normal) { 0334 // Default 0335 //el->setAttribute("fo:font-weight", "normal"); // 400 0336 } 0337 else if (font.weight() == QFont::DemiBold) { 0338 el->setAttribute(QLatin1String("fo:font-weight"), 600); 0339 } 0340 else if (font.weight() == QFont::Bold) { 0341 el->setAttribute(QLatin1String("fo:font-weight"), QLatin1String("bold")); // 700 0342 } 0343 else if (font.weight() == QFont::Black) { 0344 el->setAttribute(QLatin1String("fo:font-weight"), 900); 0345 } 0346 else { 0347 el->setAttribute(QLatin1String("fo:font-weight"), qBound(10, font.weight(), 90) * 10); 0348 } 0349 // italic, default is "normal" 0350 if (font.italic()) { 0351 el->setAttribute(QLatin1String("fo:font-style"), QLatin1String("italic")); 0352 } 0353 // pitch, default is "variable" 0354 if (font.fixedPitch()) { 0355 el->setAttribute(QLatin1String("style:font-pitch"), QLatin1String("fixed")); 0356 } 0357 if (!font.family().isEmpty()) { 0358 el->setAttribute(QLatin1String("fo:font-family"), font.family()); 0359 } 0360 // kerning, default is "true" 0361 KReportUtils::setAttribute(el, QLatin1String("style:letter-kerning"), font.kerning()); 0362 // underline, default is "none" 0363 if (font.underline()) { 0364 el->setAttribute(QLatin1String("style:text-underline-type"), QLatin1String("single")); 0365 } 0366 // stricken-out, default is "none" 0367 if (font.strikeOut()) { 0368 el->setAttribute(QLatin1String("style:text-line-through-type"), QLatin1String("single")); 0369 } 0370 el->setAttribute(QLatin1String("fo:font-size"), font.pointSize()); 0371 0372 // letter spacing, default is "normal" 0373 // §7.16.2 of [XSL] http://www.w3.org/TR/xsl11/#letter-spacing 0374 if (font.letterSpacingType() == QFont::PercentageSpacing) { 0375 // A value of 100 will keep the spacing unchanged; a value of 200 will enlarge 0376 // the spacing after a character by the width of the character itself. 0377 if (font.letterSpacing() != 100.0) { 0378 el->setAttribute(QLatin1String("fo:letter-spacing"), roundValueToString(font.letterSpacing()) + QLatin1Char('%')); 0379 } 0380 } 0381 else { 0382 // QFont::AbsoluteSpacing 0383 // A positive value increases the letter spacing by the corresponding pixels; a negative value decreases the spacing. 0384 el->setAttribute(QLatin1String("fo:letter-spacing"), roundValueToString(font.letterSpacing())); 0385 } 0386 } 0387 0388 0389 void KReportUtils::buildXMLRect(QDomElement *entity, const QPointF &pos, const QSizeF &size) 0390 { 0391 Q_ASSERT(entity); 0392 0393 KReportUtils::setAttribute(entity, pos); 0394 KReportUtils::setAttribute(entity, size ); 0395 } 0396 0397 void KReportUtils::buildXMLTextStyle(QDomDocument *doc, QDomElement *entity, const KReportTextStyleData &ts) 0398 { 0399 Q_ASSERT(doc); 0400 Q_ASSERT(entity); 0401 QDomElement element = doc->createElement(QLatin1String("report:text-style")); 0402 0403 element.setAttribute(QLatin1String("fo:background-color"), ts.backgroundColor.name()); 0404 element.setAttribute(QLatin1String("fo:foreground-color"), ts.foregroundColor.name()); 0405 element.setAttribute(QLatin1String("fo:background-opacity"), QString::number(ts.backgroundOpacity) + QLatin1Char('%')); 0406 KReportUtils::writeFontAttributes(&element, ts.font); 0407 0408 entity->appendChild(element); 0409 } 0410 0411 void KReportUtils::buildXMLLineStyle(QDomDocument *doc, QDomElement *entity, const KReportLineStyle &ls) 0412 { 0413 Q_ASSERT(doc); 0414 Q_ASSERT(entity); 0415 QDomElement element = doc->createElement(QLatin1String("report:line-style")); 0416 0417 element.setAttribute(QLatin1String("report:line-color"), ls.color().name()); 0418 element.setAttribute(QLatin1String("report:line-weight"), ls.weight()); 0419 0420 QString l; 0421 switch (ls.penStyle()) { 0422 case Qt::NoPen: 0423 l = QLatin1String("nopen"); 0424 break; 0425 case Qt::SolidLine: 0426 l = QLatin1String("solid"); 0427 break; 0428 case Qt::DashLine: 0429 l = QLatin1String("dash"); 0430 break; 0431 case Qt::DotLine: 0432 l = QLatin1String("dot"); 0433 break; 0434 case Qt::DashDotLine: 0435 l = QLatin1String("dashdot"); 0436 break; 0437 case Qt::DashDotDotLine: 0438 l = QLatin1String("dashdotdot"); 0439 break; 0440 default: 0441 l = QLatin1String("solid"); 0442 } 0443 element.setAttribute(QLatin1String("report:line-style"), l); 0444 0445 entity->appendChild(element); 0446 } 0447 0448 void KReportUtils::addPropertyAsAttribute(QDomElement* e, KProperty* p) 0449 { 0450 Q_ASSERT(e); 0451 Q_ASSERT(p); 0452 const QString name = QLatin1String("report:") + QString::fromLatin1(p->name().toLower()); 0453 0454 switch (p->type()) { 0455 case QVariant::Int: 0456 e->setAttribute(name, p->value().toInt()); 0457 break; 0458 case QVariant::Double: 0459 e->setAttribute(name, p->value().toDouble()); 0460 break; 0461 case QVariant::Bool: 0462 e->setAttribute(name, p->value().toBool()); 0463 break; 0464 default: 0465 e->setAttribute(name, p->value().toString()); 0466 break; 0467 } 0468 } 0469 0470 bool KReportUtils::setPropertyValue(KProperty *p, const QDomElement &e) 0471 { 0472 const QString name = QStringLiteral("report:") + QString::fromLatin1(p->name()); 0473 if (!e.hasAttribute(name)) { 0474 return false; 0475 } 0476 QVariant value = e.attribute(name); // string 0477 if (!value.convert(p->type())) { 0478 return false; 0479 } 0480 p->setValue(value); 0481 return true; 0482 } 0483 0484 void KReportUtils::setAttribute(QDomElement *e, const QString &attribute, double value) 0485 { 0486 Q_ASSERT(e); 0487 QString s; 0488 s.setNum(value, 'f', DBL_DIG); 0489 e->setAttribute(attribute, s + QLatin1String("pt")); 0490 } 0491 0492 void KReportUtils::setAttribute(QDomElement *e, const QPointF &value) 0493 { 0494 Q_ASSERT(e); 0495 KReportUtils::setAttribute(e, QLatin1String("svg:x"), value.x()); 0496 KReportUtils::setAttribute(e, QLatin1String("svg:y"), value.y()); 0497 } 0498 0499 void KReportUtils::setAttribute(QDomElement *e, const QSizeF &value) 0500 { 0501 Q_ASSERT(e); 0502 KReportUtils::setAttribute(e, QLatin1String("svg:width"), value.width()); 0503 KReportUtils::setAttribute(e, QLatin1String("svg:height"), value.height()); 0504 } 0505 0506 void KReportUtils::setAttribute(QDomElement *e, const QString &attribute, bool value) 0507 { 0508 e->setAttribute(attribute, value ? QStringLiteral("true") : QStringLiteral("false")); 0509 } 0510 0511 bool KReportUtils::parseReportTextStyleData(const QDomElement & elemSource, KReportTextStyleData *ts) 0512 { 0513 Q_ASSERT(ts); 0514 if (elemSource.tagName() != QLatin1String("report:text-style")) 0515 return false; 0516 ts->backgroundColor = QColor(elemSource.attribute( 0517 QLatin1String("fo:background-color"), QLatin1String("#ffffff"))); 0518 ts->foregroundColor = QColor(elemSource.attribute( 0519 QLatin1String("fo:foreground-color"), QLatin1String("#000000"))); 0520 0521 bool ok; 0522 ts->backgroundOpacity = KReportUtils::readPercent( 0523 elemSource, QLatin1String("fo:background-opacity"), 100, &ok); 0524 if (!ok) { 0525 return false; 0526 } 0527 KReportUtils::readFontAttributes(elemSource, &ts->font); 0528 return true; 0529 } 0530 0531 bool KReportUtils::parseReportLineStyleData(const QDomElement & elemSource, KReportLineStyle *ls) 0532 { 0533 Q_ASSERT(ls); 0534 if (elemSource.tagName() == QLatin1String("report:line-style")) { 0535 ls->setColor(QColor(elemSource.attribute(QLatin1String("report:line-color"), QLatin1String("#ffffff")))); 0536 ls->setWeight(elemSource.attribute(QLatin1String("report:line-weight"), QLatin1String("0.0")).toDouble()); 0537 0538 QString l = elemSource.attribute(QLatin1String("report:line-style"), QLatin1String("nopen")); 0539 if (l == QLatin1String("nopen")) { 0540 ls->setPenStyle(Qt::NoPen); 0541 } else if (l == QLatin1String("solid")) { 0542 ls->setPenStyle(Qt::SolidLine); 0543 } else if (l == QLatin1String("dash")) { 0544 ls->setPenStyle(Qt::DashLine); 0545 } else if (l == QLatin1String("dot")) { 0546 ls->setPenStyle(Qt::DotLine); 0547 } else if (l == QLatin1String("dashdot")) { 0548 ls->setPenStyle(Qt::DashDotLine); 0549 } else if (l == QLatin1String("dashdotdot")) { 0550 ls->setPenStyle(Qt::DashDotDotLine); 0551 } 0552 return true; 0553 } 0554 return false; 0555 } 0556 0557 class PageIds : private QHash<QString, QPageSize::PageSizeId> 0558 { 0559 public: 0560 PageIds() {} 0561 QPageSize::PageSizeId id(const QString &key) { 0562 if (isEmpty()) { 0563 for (int i = 0; i < QPageSize::LastPageSize; ++i) { 0564 QString key(QPageSize::key(static_cast<QPageSize::PageSizeId>(i))); 0565 if (key.isEmpty()) { 0566 break; 0567 } 0568 insert(key, static_cast<QPageSize::PageSizeId>(i)); 0569 } 0570 } 0571 return value(key); 0572 } 0573 }; 0574 0575 Q_GLOBAL_STATIC(PageIds, s_pageIds) 0576 0577 QPageSize::PageSizeId KReportUtils::pageSizeId(const QString &key) 0578 { 0579 return s_pageIds->id(key); 0580 } 0581 0582 QPageSize KReportUtils::pageSize(const QString &key) 0583 { 0584 return QPageSize(s_pageIds->id(key)); 0585 }