File indexing completed on 2024-04-21 04:41:52

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 }