File indexing completed on 2024-05-12 04:35:06

0001 /* This file is part of the TikZKit project.
0002  *
0003  * Copyright (C) 2013 Dominik Haumann <dhaumann@kde.org>
0004  *
0005  * This library is free software; you can redistribute it and/or modify
0006  * it under the terms of the GNU Library General Public License as published
0007  * by the Free Software Foundation, either version 2 of the License, or
0008  * (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
0013  * GNU 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, see
0017  * <http://www.gnu.org/licenses/>.
0018  */
0019 
0020 #include "Value.h"
0021 
0022 #include <QRegularExpression>
0023 #include <QDebug>
0024 
0025 namespace tikz {
0026 
0027 QString Value::toString() const
0028 {
0029     if (! isValid()) {
0030         return QLatin1String("nan");
0031     }
0032 
0033     // use C locale with out thousand-',' separators
0034     QLocale locale = QLocale::c();
0035     locale.setNumberOptions(QLocale::OmitGroupSeparator);
0036     QString number = locale.toString(m_value, 'f', 20);
0037 
0038     // TikZ doesn't allow commas as separator
0039     Q_ASSERT(! number.contains(QLatin1Char(',')));
0040 
0041     // allow only a single '.' as floating point separator
0042     Q_ASSERT(number.count(QLatin1Char('.')) <= 1);
0043 
0044     int dotIndex = number.indexOf(QLatin1Char('.'));
0045 
0046     // Rounding errors typically result in several consecutive '9' chars.
0047     // Catch this case and fix it to get a nice reacable number.
0048     int nineIndex;
0049     if ((nineIndex = number.indexOf(QLatin1String("099999"))) > dotIndex) {
0050         number.truncate(nineIndex + 1);
0051         number.replace(nineIndex, 1, "1");
0052     } else if ((nineIndex = number.indexOf(QLatin1String("199999"))) > dotIndex) {
0053         number.truncate(nineIndex + 1);
0054         number.replace(nineIndex, 1, "2");
0055     } else if ((nineIndex = number.indexOf(QLatin1String("299999"))) > dotIndex) {
0056         number.truncate(nineIndex + 1);
0057         number.replace(nineIndex, 1, "3");
0058     } else if ((nineIndex = number.indexOf(QLatin1String("399999"))) > dotIndex) {
0059         number.truncate(nineIndex + 1);
0060         number.replace(nineIndex, 1, "4");
0061     } else if ((nineIndex = number.indexOf(QLatin1String("499999"))) > dotIndex) {
0062         number.truncate(nineIndex + 1);
0063         number.replace(nineIndex, 1, "5");
0064     } else if ((nineIndex = number.indexOf(QLatin1String("599999"))) > dotIndex) {
0065         number.truncate(nineIndex + 1);
0066         number.replace(nineIndex, 1, "6");
0067     } else if ((nineIndex = number.indexOf(QLatin1String("699999"))) > dotIndex) {
0068         number.truncate(nineIndex + 1);
0069         number.replace(nineIndex, 1, "7");
0070     } else if ((nineIndex = number.indexOf(QLatin1String("799999"))) > dotIndex) {
0071         number.truncate(nineIndex + 1);
0072         number.replace(nineIndex, 1, "8");
0073     } else if ((nineIndex = number.indexOf(QLatin1String("899999"))) > dotIndex) {
0074         number.truncate(nineIndex + 1);
0075         number.replace(nineIndex, 1, "9");
0076     } else if ((nineIndex = number.indexOf(QLatin1String(".999999"))) == dotIndex) {
0077         number.truncate(nineIndex);
0078         number = QString::number(number.toInt() + 1);
0079         dotIndex = -1;
0080     }
0081 
0082     // we have 20 digits after the '.', which is usually too much.
0083     // Therefore, search for '00000' after the '.', and if we find these
0084     // 5 consecutive zeros, just kill the rest.
0085     // Example: the number 3.567 turns into 3.56700000000000017053. After
0086     //          the truncation, it'll be '3.5670000000'
0087     if (dotIndex >= 0) {
0088         const int indexOfZeros = number.lastIndexOf(QLatin1String("00000"));
0089         if (indexOfZeros > dotIndex) {
0090             number.truncate(indexOfZeros);
0091         }
0092     }
0093 
0094     // beautify step I: remove trailing zeros, e.g. 3.5670000000 -> 3.567
0095     while (dotIndex >= 0 && number.endsWith(QLatin1Char('0'))) {
0096         number.truncate(number.length() - 1);
0097     }
0098 
0099     // beautify step II: remove trailing dot, if applicable, e.g. 3. -> 3
0100     if (number.endsWith(QLatin1Char('.'))) {
0101         number.truncate(number.length() - 1);
0102     }
0103 
0104     QString suffix;
0105     switch (m_unit) {
0106         case Unit::Point: suffix = "pt"; break;
0107         case Unit::Millimeter: suffix = "mm"; break;
0108         case Unit::Centimeter: suffix = "cm"; break;
0109         case Unit::Inch: suffix = "in"; break;
0110         default: Q_ASSERT(false);
0111     }
0112 
0113     return number + suffix;
0114 }
0115 
0116 Value Value::fromString(const QString & str)
0117 {
0118     // we require a valid number
0119     if (str == QLatin1String("nan")) {
0120         return invalid();
0121     }
0122 
0123     // format example: 12.50cm
0124     static QRegularExpression re("([-+]?\\d*\\.?\\d*)\\s*(\\w*)");
0125 
0126     QRegularExpressionMatch match = re.match(str);
0127 
0128     if (! match.hasMatch()) {
0129         Q_ASSERT(false);
0130         return Value();
0131     }
0132 
0133     const QString number = match.captured(1);
0134     const QString suffix = match.captured(2);
0135 
0136 //     qDebug() << str << "converts to:" << number << suffix;
0137 
0138     Unit u;
0139     if (suffix.isEmpty()) {
0140         // assume pt
0141         qDebug() << "no unit given, implicit conversion to pt: " << str;
0142         u = Unit::Point;
0143     } else if (suffix == "pt") {
0144         u = Unit::Point;
0145     } else if (suffix == "mm") {
0146         u = Unit::Millimeter;
0147     } else if (suffix == "cm") {
0148         u = Unit::Centimeter;
0149     } else if (suffix == "in") {
0150         u = Unit::Inch;
0151     } else {
0152         Q_ASSERT(false);
0153     }
0154 
0155     bool ok;
0156     const double val = number.toDouble(&ok);
0157     Q_ASSERT(ok);
0158 
0159     return Value(val, u);
0160 }
0161 
0162 }
0163 
0164 namespace QTest {
0165     // Value: template specialization for QTest::toString()
0166     template<>
0167     char *toString(const tikz::Value & value)
0168     {
0169         const QString str = "Value[" + value.convertTo(tikz::Unit::Point).toString() + "]";
0170         const QByteArray ba = str.toLatin1();
0171         return qstrdup(ba.data());
0172     }
0173 }
0174 
0175 // kate: indent-width 4; replace-tabs on;