diff --git a/INSTALL b/INSTALL index dc66d282..8276bc3c 100644 --- a/INSTALL +++ b/INSTALL @@ -1,12 +1,12 @@ SVG Cleaner depends on: -- Qt >=4.6 +- Qt >= 4.6 - 7zip For Ubuntu you can use this ppa: ppa:svg-cleaner-team/svgcleaner -For Windows(XP,Vista,7) you can use our installer hosted on github: -https://github.com/RazrFalcon/SVGCleaner/downloads +For Windows(XP,Vista,7) and MacOS X you can use our installer, hosted on SourceForge: +https://sourceforge.net/projects/svgcleaner/files/ To compile from source: 1) qmake diff --git a/debian/changelog b/debian/changelog index 2859fdc5..b6ed5cb9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,17 @@ +svgcleaner (0.5.1) natty; urgency=low + + * Added a new function: "Group elements by style properties". + * Added 'rotate( [ ])' matrix processing. + * Fixed xmlns:xlink prefix removing. + * Fixed stdDeviation processing. + * Fixed program freezing with --keep-comments flag. + * Fixed processing of --version flag. + * Fixed prolog removing. + * Fixed style attributes grouping to one attribute. + + -- Raizner Evgeniy Sun, 30 Jun 2013 02:00:00 +0200 + + svgcleaner (0.5) natty; urgency=low * Engine rewrited from Perl to C++(Qt). diff --git a/debian/rules b/debian/rules old mode 100644 new mode 100755 index 41181207..55ddeaf7 --- a/debian/rules +++ b/debian/rules @@ -8,15 +8,14 @@ #export DH_VERBOSE=1 include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/class/qmake.mk build: build-stamp build-stamp: dh_testdir # Add here commands to compile the package. - qmake-qt4 src/cli/svgcleaner-cli.pro CONFIG+="release" - make - qmake-qt4 src/gui/svgcleaner-gui.pro CONFIG+="release" + qmake-qt4 -config release make touch build-stamp @@ -63,4 +62,4 @@ binary-arch: build install dh_builddeb binary: binary-indep binary-arch -.PHONY: build clean binary-indep binary-arch binary install clean +.PHONY: build clean binary-indep binary-arch binary install clean \ No newline at end of file diff --git a/debian/svgcleaner.install b/debian/svgcleaner.install deleted file mode 100644 index 8cbb9cbc..00000000 --- a/debian/svgcleaner.install +++ /dev/null @@ -1,11 +0,0 @@ -svgcleaner-cli usr/bin -svgcleaner-gui usr/bin -presets/Soft.preset usr/share/svgcleaner/presets -presets/Normal.preset usr/share/svgcleaner/presets -presets/Optimal.preset usr/share/svgcleaner/presets -svgcleaner_cs.qm usr/share/svgcleaner/translations -svgcleaner_ru.qm usr/share/svgcleaner/translations -svgcleaner_uk.qm usr/share/svgcleaner/translations -svgcleaner_de.qm usr/share/svgcleaner/translations -icons/svgcleaner.svg usr/share/icons/hicolor/scalable/apps -svgcleaner.desktop /usr/share/applications diff --git a/src/cli/svgcleaner-cli.pro b/src/cli/cli.pro similarity index 71% rename from src/cli/svgcleaner-cli.pro rename to src/cli/cli.pro index d55a27ce..7a45ca32 100644 --- a/src/cli/svgcleaner-cli.pro +++ b/src/cli/cli.pro @@ -1,9 +1,9 @@ -QT += core xml - -TARGET = svgcleaner-cli -CONFIG += console -CONFIG -= app_bundle +QT += core xml +TARGET = svgcleaner-cli +CONFIG += console +CONFIG -= app_bundle +DESTDIR =../../bin TEMPLATE = app SOURCES += main.cpp \ diff --git a/src/cli/keys.cpp b/src/cli/keys.cpp index 23f822e5..82185b62 100644 --- a/src/cli/keys.cpp +++ b/src/cli/keys.cpp @@ -21,7 +21,7 @@ Keys::Keys() keyHash.insert(Key::KeepDuplicatedDefs, false); keyHash.insert(Key::KeepSinglyGradients, false); keyHash.insert(Key::KeepTinyGaussianBlur, false); - keyHash.insert(Key::StdDeviation, 0.2); + keyHash.insert(Key::StdDeviation, 0.1); keyHash.insert(Key::KeepSvgVersion, false); keyHash.insert(Key::KeepUnreferencedIds, false); @@ -37,6 +37,7 @@ Keys::Keys() keyHash.insert(Key::KeepFillProps, false); keyHash.insert(Key::KeepGradientCoordinates, false); keyHash.insert(Key::KeepUnusedXLinks, false); + keyHash.insert(Key::SkipElemByStyleGroup, false); keyHash.insert(Key::KeepAbsolutePaths, false); keyHash.insert(Key::KeepUnusedSymbolsFromPath, false); @@ -49,6 +50,7 @@ Keys::Keys() keyHash.insert(Key::SkipRRGGBBToRGB, false); keyHash.insert(Key::KeepBasicShapes, false); keyHash.insert(Key::KeepTransforms, false); + keyHash.insert(Key::SkipTransformsApplying, false); keyHash.insert(Key::KeepUnsortedDefs, false); keyHash.insert(Key::SkipRoundingNumbers, false); diff --git a/src/cli/keys.h b/src/cli/keys.h index d3f9e1a8..95ef31f5 100644 --- a/src/cli/keys.h +++ b/src/cli/keys.h @@ -1,9 +1,8 @@ #ifndef KEYS_H #define KEYS_H -#include -#include -#include +#include +#include namespace Key { static const QString KeepProlog = "--keep-prolog"; @@ -39,6 +38,7 @@ namespace Key { static const QString KeepFillProps = "--keep-fill-props"; static const QString KeepGradientCoordinates = "--keep-grad-coords"; static const QString KeepUnusedXLinks = "--keep-unused-xlinks"; + static const QString SkipElemByStyleGroup = "--skip-style-group"; static const QString KeepAbsolutePaths = "--keep-absolute-paths"; static const QString KeepUnusedSymbolsFromPath = "--keep-unused-symbols"; @@ -51,11 +51,12 @@ namespace Key { static const QString SkipRRGGBBToRGB = "--skip-rrggbb-to-rgb"; static const QString KeepBasicShapes = "--keep-basic-shapes"; static const QString KeepTransforms = "--keep-transform"; + static const QString SkipTransformsApplying = "--skip-transform-appl"; static const QString KeepUnsortedDefs = "--keep-unsorted-defs"; static const QString SkipRoundingNumbers = "--skip-rounding-numbers"; - static const QString TransformPrecision = "--transfs-precision"; - static const QString CoordsPrecision = "--coords-precision"; - static const QString AttributesPrecision = "--attributes-precision"; + static const QString TransformPrecision = "--transfs-prec"; + static const QString CoordsPrecision = "--coords-prec"; + static const QString AttributesPrecision = "--attributes-prec"; static const QString Indent = "--indent"; @@ -80,7 +81,7 @@ class Keys void parceOptions(const QStringList &list); private: - QHash keyHash; + QVariantHash keyHash; Keys(); Keys(Keys const&); diff --git a/src/cli/main.cpp b/src/cli/main.cpp index 8e7a6d5e..9ffea88b 100644 --- a/src/cli/main.cpp +++ b/src/cli/main.cpp @@ -24,7 +24,7 @@ void showHelp() qDebug() << "By default, all options are on, and you can only disable it."; qDebug() << "Options:"; qDebug() << " -h --help Show this text."; - qDebug() << " --version Show version."; + qDebug() << " -v --version Show version."; qDebug() << "Elements:"; qDebug() << " --keep-prolog Disable xml prolog removing."; @@ -45,7 +45,7 @@ void showHelp() qDebug() << " --keep-singly-grads Do not merge 'linearGradient' into 'radialGradient',\n" " when 'linearGradient' linked only to one 'radialGradient'."; qDebug() << " --keep-gaussian-blur Disable removing 'feGaussianBlur' filters with 'stdDeviation' lower then '--std-dev'."; - qDebug() << " --std-dev=<0.01..0.5> Set minimum value for 'stdDeviation' in 'feGaussianBlur' element [default: 0.2]."; + qDebug() << " --std-dev=<0.01..0.5> Set minimum value for 'stdDeviation' in 'feGaussianBlur' element [default: 0.1]."; qDebug() << "Attributes:"; qDebug() << " --keep-version Keep SVG version number."; @@ -93,12 +93,13 @@ QString prepareSvg(QString str) str.replace("")); + str.remove(QRegExp("<\\!DOCTYPE.*\\.dtd('|\")>(\n|)")); return str; } bool processFile(const QString &firstFile, const QString &secondFile) { + QFile inputFile(firstFile); qDebug() << "The initial file size is: " + QString::number(inputFile.size()); if (!inputFile.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -155,10 +156,11 @@ bool processFile(const QString &firstFile, const QString &secondFile) remover.removeGroups(); if (!Keys::get().flag(Key::KeepAbsolutePaths)) replacer.processPaths(); + if (!Keys::get().flag(Key::SkipElemByStyleGroup)) + replacer.groupElementsByStyles(); if (!Keys::get().flag(Key::SkipRoundingNumbers)) replacer.roundDefs(); - if (!Keys::get().flag(Key::JoinStyleAttributes)) - replacer.splitStyleAttr(); + replacer.splitStyleAttr(); replacer.finalFixes(); QFile outFile(secondFile); @@ -166,10 +168,15 @@ bool processFile(const QString &firstFile, const QString &secondFile) qDebug() << "Error: could not open out file for write."; return false; } + // TODO: check is out file smaller than original QTextStream out(&outFile); // remove unneeded new lines in text elements, created by default xml format, // but wrong in svg - out << inputSvg.toString(Keys::get().intNumber(Key::Indent)).replace(">\n(\n|)")); + outStr.replace(">\n +#include + #include "tools.h" #include "paths.h" #include "keys.h" // http://www.w3.org/TR/SVG/paths.html -SegmentList::SegmentList() -{ - m_position = -1; - m_isRound = !Keys::get().flag(Key::SkipRoundingNumbers); -} - -void SegmentList::append(Segment data) -{ - m_data << data; -} - -bool SegmentList::next() -{ - if (m_data.count()-1 > m_position) { - m_position++; - return true; - } else { - m_position = -1; - return false; - } -} +// TODO: add path approximation +// fjudy2001_02_cleaned +// warszawianka_Betel.svg -void SegmentList::restart() +Segment::Segment() { - m_position = -1; + m_isApplyRound = !Keys::get().flag(Key::SkipRoundingNumbers); } -void SegmentList::removeCurrent() +QString Segment::string(qreal value) const { - m_data.removeAt(m_position); - m_position--; + return (m_isApplyRound) ? Tools::roundNumber(value) : QString::number(value); } -int SegmentList::position() +void Segment::setTransform(const QString &text) { - return m_position; -} - -QString SegmentList::string(qreal value) -{ - QString str; - if (m_isRound) - str = Tools::roundNumber(value); - else - str = QString::number(value); - return str; -} - -void SegmentList::appendLastPoint(qreal x, qreal y) -{ - m_lastPoint.setX(m_lastPoint.x() + x); - m_lastPoint.setY(m_lastPoint.y() + y); -} - -QPointF SegmentList::lastPoint() -{ - return m_lastPoint; -} - -void SegmentList::setLastPoint(qreal x, qreal y) -{ - m_lastPoint = QPointF(x, y); + Transform tr(text); + if (command == Command::MoveTo || command == Command::LineTo || command == Command::SmoothQuadratic) { + tr.setOldXY(x, y); + x = tr.newX(); + y = tr.newY(); + } else if (command == Command::CurveTo) { + tr.setOldXY(x, y); + x = tr.newX(); + y = tr.newY(); + + tr.setOldXY(x1, y1); + x1 = tr.newX(); + y1 = tr.newY(); + + tr.setOldXY(x2, y2); + x2 = tr.newX(); + y2 = tr.newY(); + } else if (command == Command::SmoothCurveTo) { + tr.setOldXY(x, y); + x = tr.newX(); + y = tr.newY(); + + tr.setOldXY(x2, y2); + x2 = tr.newX(); + y2 = tr.newY(); + } else if (command == Command::Quadratic) { + tr.setOldXY(x, y); + x = tr.newX(); + y = tr.newY(); + + tr.setOldXY(x1, y1); + x1 = tr.newX(); + y1 = tr.newY(); + } } -//void SegmentList::setTransform(const QString &text) -//{ -// Transform tr(text); -// QString cmd = command(); -// // TODO: how to transform h/v -// if (cmd != Command::HorizontalLineTo -// && cmd != Command::VerticalLineTo && cmd != Command::ClosePath) { - -// if (m_data.at(m_position).contains(SegmentList::X)) { -// tr.setXY(number(SegmentList::X), number(SegmentList::Y)); -// setValue(SegmentList::X, tr.newX()); -// setValue(SegmentList::Y, tr.newY()); -// } - -// if (m_data.at(m_position).contains(SegmentList::X1)) { -// tr.setXY(number(SegmentList::X1), number(SegmentList::Y1)); -// setValue(SegmentList::X1, tr.newX()); -// setValue(SegmentList::Y1, tr.newY()); -// } - -// if (m_data.at(m_position).contains(SegmentList::X2)) { -// tr.setXY(number(SegmentList::X2), number(SegmentList::Y2)); -// setValue(SegmentList::X2, tr.newX()); -// setValue(SegmentList::Y2, tr.newY()); -// } -// } -//} - -void SegmentList::toRelative() +void Segment::toRelative(qreal xLast, qreal yLast) { - Segment currSegment = segment(); - if (!currSegment.abs) + if (!absolute) return; - // TODO: refract - QString cmd = currSegment.command; - if (cmd == Command::MoveTo || cmd == Command::LineTo || cmd == Command::SmoothQuadratic) { - currSegment.x = currSegment.x - m_lastPoint.x(); - currSegment.y = currSegment.y - m_lastPoint.y(); - } else if (cmd == Command::CurveTo) { - currSegment.x = currSegment.x - m_lastPoint.x(); - currSegment.y = currSegment.y - m_lastPoint.y(); - - currSegment.x1 = currSegment.x1 - m_lastPoint.x(); - currSegment.y1 = currSegment.y1 - m_lastPoint.y(); - - currSegment.x2 = currSegment.x2 - m_lastPoint.x(); - currSegment.y2 = currSegment.y2 - m_lastPoint.y(); - } else if (cmd == Command::SmoothCurveTo) { - currSegment.x = currSegment.x - m_lastPoint.x(); - currSegment.y = currSegment.y - m_lastPoint.y(); - - currSegment.x2 = currSegment.x2 - m_lastPoint.x(); - currSegment.y2 = currSegment.y2 - m_lastPoint.y(); - } else if (cmd == Command::Quadratic) { - currSegment.x = currSegment.x - m_lastPoint.x(); - currSegment.y = currSegment.y - m_lastPoint.y(); - - currSegment.x1 = currSegment.x1 - m_lastPoint.x(); - currSegment.y1 = currSegment.y1 - m_lastPoint.y(); - } else if (cmd == Command::HorizontalLineTo) { - currSegment.x = currSegment.x - m_lastPoint.x(); - } else if (cmd == Command::VerticalLineTo) { - currSegment.y = currSegment.y - m_lastPoint.y(); - } else if (cmd == Command::EllipticalArc) { - currSegment.x = currSegment.x - m_lastPoint.x(); - currSegment.y = currSegment.y - m_lastPoint.y(); + if (command == Command::HorizontalLineTo) { + x = x - xLast; + } else if (command == Command::VerticalLineTo) { + y = y - yLast; + } else { + x = x - xLast; + y = y - yLast; + if (command == Command::CurveTo) { + x1 = x1 - xLast; + y1 = y1 - yLast; + x2 = x2 - xLast; + y2 = y2 - yLast; + } else if (command == Command::SmoothCurveTo) { + x2 = x2 - xLast; + y2 = y2 - yLast; + } else if (command == Command::Quadratic) { + x1 = x1 - xLast; + y1 = y1 - yLast; + } } - - currSegment.abs = false; - updateSegment(currSegment); + absolute = false; } -void SegmentList::toAbsolute() +void Segment::toAbsolute(qreal xLast, qreal yLast) { - Segment currSegment = segment(); - if (currSegment.abs) + if (absolute) return; - QString cmd = currSegment.command; - if (cmd == Command::MoveTo || cmd == Command::LineTo || cmd == Command::SmoothQuadratic) { - currSegment.x = currSegment.x + m_lastPoint.x(); - currSegment.y = currSegment.y + m_lastPoint.y(); - } else if (cmd == Command::CurveTo) { - currSegment.x = currSegment.x + m_lastPoint.x(); - currSegment.y = currSegment.y + m_lastPoint.y(); - - currSegment.x1 = currSegment.x1 + m_lastPoint.x(); - currSegment.y1 = currSegment.y1 + m_lastPoint.y(); - - currSegment.x2 = currSegment.x2 + m_lastPoint.x(); - currSegment.y2 = currSegment.y2 + m_lastPoint.y(); - } else if (cmd == Command::SmoothCurveTo) { - currSegment.x = currSegment.x + m_lastPoint.x(); - currSegment.y = currSegment.y + m_lastPoint.y(); - - currSegment.x2 = currSegment.x2 + m_lastPoint.x(); - currSegment.y2 = currSegment.y2 + m_lastPoint.y(); - } else if (cmd == Command::Quadratic) { - currSegment.x = currSegment.x + m_lastPoint.x(); - currSegment.y = currSegment.y + m_lastPoint.y(); - - currSegment.x1 = currSegment.x1 + m_lastPoint.x(); - currSegment.y1 = currSegment.y1 + m_lastPoint.y(); - } else if (cmd == Command::HorizontalLineTo) { - currSegment.x = currSegment.x + m_lastPoint.x(); - } else if (cmd == Command::VerticalLineTo) { - currSegment.y = currSegment.y + m_lastPoint.y(); - } else if (cmd == Command::EllipticalArc) { - currSegment.x = currSegment.x + m_lastPoint.x(); - currSegment.y = currSegment.y + m_lastPoint.y(); + if (command == Command::HorizontalLineTo) { + x = x + xLast; + } else if (command == Command::VerticalLineTo) { + y = y + yLast; + } else { + x = x + xLast; + y = y + yLast; + if (command == Command::CurveTo) { + x1 = x1 + xLast; + y1 = y1 + yLast; + x2 = x2 + xLast; + y2 = y2 + yLast; + } else if (command == Command::SmoothCurveTo) { + x2 = x2 + xLast; + y2 = y2 + yLast; + } else if (command == Command::Quadratic) { + x1 = x1 + xLast; + y1 = y1 + yLast; + } } - - currSegment.abs = true; - updateSegment(currSegment); -} - -Segment SegmentList::segment() -{ - return m_data.at(m_position); + absolute = true; } -void SegmentList::updateSegment(const Segment &seg) +QString Segment::toString() const { - m_data.replace(m_position, seg); -} - -QString SegmentList::genPath() -{ - Segment seg = segment(); - QString cmd = seg.command; QString out; - if (cmd == Command::MoveTo || cmd == Command::LineTo || cmd == Command::SmoothQuadratic) { - out = Tools::roundNumber(seg.x) % "," % Tools::roundNumber(seg.y); - } else if (cmd == Command::CurveTo) { - out = string(seg.x1) % "," % string(seg.y1) % " " - % string(seg.x2) % "," % string(seg.y2) % " " - % string(seg.x) % "," % string(seg.y); - } else if (cmd == Command::HorizontalLineTo) { - out = string(seg.x); - } else if (cmd == Command::VerticalLineTo) { - out = string(seg.y); - } else if (cmd == Command::SmoothCurveTo) { - out = string(seg.x2) % "," % string(seg.y2) % " " - % string(seg.x) % "," % string(seg.y); - } else if (cmd == Command::Quadratic) { - out = string(seg.x1) % "," % string(seg.y1) % " " - % string(seg.x) % "," % string(seg.y); - } else if (cmd == Command::EllipticalArc) { - out = string(seg.rx) % "," % string(seg.ry) % " " - % QString::number(seg.xAxisRotation) % " " - % QString::number(seg.largeArc) % "," - % QString::number(seg.sweep) % " " - % string(seg.x) % "," % string(seg.y); + if (command == Command::MoveTo || command == Command::LineTo || command == Command::SmoothQuadratic) { + out = Tools::roundNumber(x) % "," % Tools::roundNumber(y); + } else if (command == Command::CurveTo) { + out = string(x1) % "," % string(y1) % " " + % string(x2) % "," % string(y2) % " " + % string(x) % "," % string(y); + } else if (command == Command::HorizontalLineTo) { + out = string(x); + } else if (command == Command::VerticalLineTo) { + out = string(y); + } else if (command == Command::SmoothCurveTo) { + out = string(x2) % "," % string(y2) % " " + % string(x) % "," % string(y); + } else if (command == Command::Quadratic) { + out = string(x1) % "," % string(y1) % " " + % string(x) % "," % string(y); + } else if (command == Command::EllipticalArc) { + out = string(rx) % "," % string(ry) % " " + % QString::number(xAxisRotation) % " " + % QString::number(largeArc) % "," + % QString::number(sweep) % " " + % string(x) % "," % string(y); } return out; } @@ -232,7 +149,7 @@ QString SegmentList::genPath() void Path::processPath(SvgElement elem) { - QString inPath = elem.attribute("d"); + const QString inPath = elem.attribute("d"); if (inPath.contains("nan")) { elem.removeAttribute("d"); @@ -243,22 +160,13 @@ void Path::processPath(SvgElement elem) // can't be converted to relative coordinates // some kind of bug if (inPath.contains("A")) { - if (QString(inPath).remove(QRegExp("[^a-AZ-z]")) == "MA") + if (QString(inPath).remove(QRegExp("[^a-AZ-z]")) == "MA") { return; + } } splitToSegments(inPath); - // TODO: apply transform to filter -// if (!Keys::get().flag(Key::ApplyTransforms)) { -// if (elem.hasAttribute("transform")) { -// segmentsToAbsolute(); -// m_segment.restart(); -// while (m_segment.next()) -// m_segment.setTransform(elem.attribute("transform")); -// elem.removeAttribute("transform"); -// } - segmentsToAbsolute(); segmentsToRelative(); @@ -267,10 +175,14 @@ void Path::processPath(SvgElement elem) // merge segments to path QString outPath = segmentsToPath(); - elem.setAttribute("d", outPath); + // set old path, if new is longer + if (outPath.size() > inPath.size()) + elem.setAttribute("d", inPath); + else + elem.setAttribute("d", outPath); } -void Path::setSegments(const SegmentList &list) +void Path::setSegments(const QList &list) { m_segmentList = list; } @@ -315,8 +227,8 @@ void Path::splitToSegments(const QString &path) prevNonDigit = c; } - QString currCmd; // convert to segments + QString currCmd; while (!data.isEmpty()) { bool isNewCmd = false; if (data.first().at(0).isLetter()) { @@ -328,22 +240,29 @@ void Path::splitToSegments(const QString &path) Segment segment; segment.command = lowerCmd; - segment.abs = currCmd.at(0).isUpper(); + segment.absolute = currCmd.at(0).isUpper(); segment.srcCmd = isNewCmd; - // TODO: add count check to prevent crash if ( lowerCmd == Command::MoveTo || lowerCmd == Command::LineTo || lowerCmd == Command::SmoothQuadratic) { + while (data.size() < 2) + data << "0"; segment.x = data.takeFirst().toDouble(); segment.y = data.takeFirst().toDouble(); } else if (lowerCmd == Command::HorizontalLineTo) { + while (data.size() < 1) + data << "0"; segment.x = data.takeFirst().toDouble(); segment.y = 0; } else if (lowerCmd == Command::VerticalLineTo) { + while (data.size() < 1) + data << "0"; segment.x = 0; segment.y = data.takeFirst().toDouble(); } else if (lowerCmd == Command::CurveTo) { + while (data.size() < 6) + data << "0"; segment.x1 = data.takeFirst().toDouble(); segment.y1 = data.takeFirst().toDouble(); segment.x2 = data.takeFirst().toDouble(); @@ -351,21 +270,27 @@ void Path::splitToSegments(const QString &path) segment.x = data.takeFirst().toDouble(); segment.y = data.takeFirst().toDouble(); } else if (lowerCmd == Command::SmoothCurveTo) { + while (data.size() < 4) + data << "0"; segment.x2 = data.takeFirst().toDouble(); segment.y2 = data.takeFirst().toDouble(); segment.x = data.takeFirst().toDouble(); segment.y = data.takeFirst().toDouble(); } else if (lowerCmd == Command::Quadratic) { + while (data.size() < 4) + data << "0"; segment.x1 = data.takeFirst().toDouble(); segment.y1 = data.takeFirst().toDouble(); segment.x = data.takeFirst().toDouble(); segment.y = data.takeFirst().toDouble(); } else if (lowerCmd == Command::EllipticalArc) { + while (data.size() < 7) + data << "0"; segment.rx = data.takeFirst().toDouble(); segment.ry = data.takeFirst().toDouble(); segment.xAxisRotation = data.takeFirst().toDouble(); - segment.largeArc = data.takeFirst().toDouble(); - segment.sweep = data.takeFirst().toDouble(); + segment.largeArc = data.takeFirst().toDouble(); + segment.sweep = data.takeFirst().toDouble(); segment.x = data.takeFirst().toDouble(); segment.y = data.takeFirst().toDouble(); } @@ -374,69 +299,68 @@ void Path::splitToSegments(const QString &path) } } +// TODO: if last point equal to first - convert segment into z void Path::processSegments() { // TODO: add other commands conv // convert "lineto" to "horizontal/vertical lineto" equivalent if (!Keys::get().flag(Key::KeepLinesTo)) { - m_segmentList.restart(); - while (m_segmentList.next()) { - Segment seg = m_segmentList.segment(); + for (int i = 0; i < m_segmentList.count(); ++i) { + Segment seg = m_segmentList.at(i); QString currCmd = seg.command; if (currCmd == Command::LineTo) { if (seg.x == 0 && seg.y != 0) { seg.command = Command::VerticalLineTo; - m_segmentList.updateSegment(seg); + m_segmentList.replace(i, seg); } else if (seg.x != 0 && seg.y == 0) { seg.command = Command::HorizontalLineTo; - m_segmentList.updateSegment(seg); + m_segmentList.replace(i, seg); } } } } if (!Keys::get().flag(Key::KeepEmptySegments)) { - m_segmentList.restart(); - while (m_segmentList.next()) { - Segment seg = m_segmentList.segment(); + for (int i = 0; i < m_segmentList.count(); ++i) { + Segment seg = m_segmentList.at(i); const QString cmd = seg.command; // remove empty segments 'm0,0' (except first) - if (cmd == Command::MoveTo && m_segmentList.position() > 0) { + if (cmd == Command::MoveTo && i > 0) { if (seg.x == 0 && seg.y == 0) - m_segmentList.removeCurrent(); + m_segmentList.removeAt(i); } else if (cmd == Command::CurveTo) { if ( (seg.x1 == 0 && seg.y1 == 0) && (seg.x2 == 0 && seg.y2 == 0) && (seg.x == 0 && seg.y == 0)) - m_segmentList.removeCurrent(); + m_segmentList.removeAt(i); } else if (cmd == Command::LineTo || cmd == Command::SmoothQuadratic) { if (seg.x == 0 && seg.y == 0) - m_segmentList.removeCurrent(); + m_segmentList.removeAt(i); } else if (cmd == Command::HorizontalLineTo) { if (seg.x == 0) - m_segmentList.removeCurrent(); + m_segmentList.removeAt(i); } else if (cmd == Command::VerticalLineTo) { if (seg.y == 0) - m_segmentList.removeCurrent(); + m_segmentList.removeAt(i); } else if (cmd == Command::SmoothCurveTo) { if ( (seg.x2 == 0 && seg.y2 == 0) && (seg.x == 0 && seg.y == 0)) - m_segmentList.removeCurrent(); + m_segmentList.removeAt(i); } else if (cmd == Command::Quadratic) { if ( seg.x1 == 0 && seg.y1 == 0 && seg.x == 0 && seg.y == 0) - m_segmentList.removeCurrent(); + m_segmentList.removeAt(i); } else if (cmd == Command::EllipticalArc) { if (seg.x == 0 && seg.y == 0) - m_segmentList.removeCurrent(); + m_segmentList.removeAt(i); } } } @@ -445,22 +369,24 @@ void Path::processSegments() // NOTE: didn't work without relative conv void Path::segmentsToAbsolute() { - m_segmentList.setLastPoint(0, 0); - m_segmentList.restart(); - while (m_segmentList.next()) { - Segment segment = m_segmentList.segment(); + qreal lastX = 0; + qreal lastY = 0; + for (int i = 0; i < m_segmentList.count(); ++i) { + Segment segment = m_segmentList.at(i); if (segment.command != Command::ClosePath) { - if (segment.abs) { + if (segment.absolute) { qreal x = segment.x; qreal y = segment.y; if (x == 0 && segment.command == Command::VerticalLineTo) - x = m_segmentList.lastPoint().x(); + x = lastX; else if (y == 0 && segment.command == Command::HorizontalLineTo) - y = m_segmentList.lastPoint().y(); - m_segmentList.setLastPoint(x, y); + y = lastY; + lastX = x; + lastY = y; } else { - m_segmentList.toAbsolute(); - m_segmentList.appendLastPoint(segment.x, segment.y); + m_segmentList[i].toAbsolute(lastX, lastY); + lastX += segment.x; + lastY += segment.y; } } } @@ -468,23 +394,19 @@ void Path::segmentsToAbsolute() void Path::segmentsToRelative() { - m_segmentList.setLastPoint(0, 0); - m_segmentList.restart(); - while (m_segmentList.next()) { - Segment segment = m_segmentList.segment(); + qreal lastX = 0; + qreal lastY = 0; + for (int i = 0; i < m_segmentList.count(); ++i) { + Segment segment = m_segmentList.at(i); if (segment.command != Command::ClosePath) { - if (segment.abs) { - m_segmentList.toRelative(); - segment = m_segmentList.segment(); - qreal x = segment.x; - qreal y = segment.y; - if (segment.command == Command::HorizontalLineTo) - y = 0; - else if (segment.command == Command::VerticalLineTo) - x = 0; - m_segmentList.appendLastPoint(x, y); + if (segment.absolute) { + m_segmentList[i].toRelative(lastX, lastY); + segment = m_segmentList.at(i); + lastX += segment.x; + lastY += segment.y; } else { - m_segmentList.setLastPoint(segment.x, segment.y); + lastX = segment.x; + lastY = segment.y; } } } @@ -495,29 +417,28 @@ QString Path::segmentsToPath() QString outPath; QString prevCom; bool isPrevComAbs = false; - m_segmentList.restart(); - while (m_segmentList.next()) { - Segment segment = m_segmentList.segment(); + for (int i = 0; i < m_segmentList.count(); ++i) { + Segment segment = m_segmentList.at(i); QString currCmd = segment.command; // check is previous command is the same as next bool writeCmd = true; if (currCmd == prevCom && !prevCom.isEmpty()) { - if (segment.abs == isPrevComAbs - && (!segment.srcCmd && segment.command == Command::MoveTo)) + if (segment.absolute == isPrevComAbs + && !(segment.srcCmd && segment.command == Command::MoveTo)) writeCmd = false; } prevCom = currCmd; - isPrevComAbs = segment.abs; + isPrevComAbs = segment.absolute; if (writeCmd) { - if (segment.abs) - outPath += segment.command.toUpper() + " "; + if (segment.absolute) + outPath += segment.command.toUpper() % " "; else - outPath += segment.command + " "; + outPath += segment.command % " "; } - outPath += m_segmentList.genPath() + " "; + outPath += m_segmentList.at(i).toString() % " "; } - outPath.remove(QRegExp("\\ +$")); + outPath.chop(1); if (!Keys::get().flag(Key::KeepUnusedSymbolsFromPath)) outPath = trimPath(outPath); @@ -535,28 +456,27 @@ QString Path::trimPath(const QString &path) bool isArc = false; for (int i = 0; i < path.size(); ++i) { QChar curr = path.at(i); - if (curr == ',' || curr == ' ') { if (i < path.size()-1 && i > 0) { QChar prev = path.at(i-1); QChar next = path.at(i+1); - bool flag = true; + bool keep = true; // example: m 100 -> m100 if (prev.isLetter() && curr == ' ') - flag = false; + keep = false; // example: 100 c -> 100c if (curr == ' ' && next.isLetter()) - flag = false; + keep = false; // example: 42,-42 -> 42-42 - if (curr == ',' && next == '-' /*|| next == '.')*/) - flag = false; + if (curr == ',' && next == '-') + keep = false; // example: 42 -42 -> 42-42 if (prev.isNumber() && curr == ' ' && next == '-' && !isArc) - flag = false; + keep = false; - if (flag) - outPath += curr; + if (keep) + outPath += curr; } } else { if (curr.toLower() == 'a') diff --git a/src/cli/paths.h b/src/cli/paths.h index 80e4df12..7966e6bc 100644 --- a/src/cli/paths.h +++ b/src/cli/paths.h @@ -1,8 +1,8 @@ #ifndef PATHS_H #define PATHS_H -#include -#include +#include +#include #include "tools.h" @@ -19,11 +19,20 @@ namespace Command { static const QString ClosePath = "z"; } -struct Segment +class Segment { +public: + Segment(); + QString toString() const; + QString string(qreal value) const; + void setTransform(const QString &text); + void toRelative(qreal xLast, qreal yLast); + void toAbsolute(qreal xLast, qreal yLast); + QList toCurve(qreal prevX, qreal prevY); + + QString command; - // absolute - bool abs; + bool absolute; // is this command defined in source path bool srcCmd; qreal x; @@ -37,33 +46,9 @@ struct Segment int xAxisRotation; int largeArc; int sweep; -}; - -class SegmentList -{ -public: - SegmentList(); - bool next(); - int position(); - QString genPath(); - QString string(qreal value); - void append(Segment data); - void appendLastPoint(qreal x, qreal y); - QPointF lastPoint(); - void removeCurrent(); - void restart(); - void setLastPoint(qreal x, qreal y); -// void setTransform(const QString &text); - void toRelative(); - void toAbsolute(); - Segment segment(); - void updateSegment(const Segment &seg); private: - int m_position; - bool m_isRound; - QPointF m_lastPoint; - QList m_data; + bool m_isApplyRound; }; class Path @@ -71,12 +56,12 @@ class Path public: explicit Path() {} void processPath(SvgElement elem); - void setSegments(const SegmentList &list); + void setSegments(const QList &list); QString segmentsToPath(); void segmentsToRelative(); private: - SegmentList m_segmentList; + QList m_segmentList; void splitToSegments(const QString &path); void processSegments(); void segmentsToAbsolute(); diff --git a/src/cli/remover.cpp b/src/cli/remover.cpp index 59396546..5ccdb017 100644 --- a/src/cli/remover.cpp +++ b/src/cli/remover.cpp @@ -3,6 +3,10 @@ #include "keys.h" #include "remover.h" +// TODO: remove equal styles in used element and it use +// TODO: remove items which out of viewbox +// Anonymous_butterfly_and_flowers.svg + Remover::Remover(QDomDocument dom) { m_dom = dom; @@ -12,9 +16,23 @@ Remover::Remover(QDomDocument dom) void Remover::cleanSvgElementAttribute() { + bool isXlinkUsed = false; + QList nodeList = m_svgElem.childElemList(); + while (!nodeList.empty()) { + SvgElement currElem = nodeList.takeFirst(); + if (currElem.hasAttribute("xmlns:xlink")) { + isXlinkUsed = true; + break; + } + if (currElem.hasChildNodes()) + nodeList << currElem.childElemList(); + } + QString strIgnore = "xmlns|width|height|viewBox|enable-background|fill.*|stroke.*|style"; if (Keys::get().flag(Key::KeepSvgVersion)) strIgnore += "|version"; + if (!isXlinkUsed) + strIgnore += "|xmlns:xlink"; foreach (const QString &attr, m_svgElem.attributesList()) { if (!attr.contains(QRegExp("^(" + strIgnore + ")$"))) { m_svgElem.removeAttribute(attr); @@ -22,7 +40,7 @@ void Remover::cleanSvgElementAttribute() } // dirty way, but svg cannot be processed by default style cleaning func, // because in svg node we cannot remove default values - if (m_svgElem.attribute("style") == "display:inline") + if (m_svgElem.style() == "display:inline") m_svgElem.removeAttribute("style"); } @@ -35,16 +53,16 @@ void Remover::removeUnusedDefs() foreach (const SvgElement &elem, m_defsElem.childElemList()) if (elem.tagName() != "clipPath") - defsIdList << elem.attribute("id"); + defsIdList << elem.id(); QList nodeList = m_svgElem.childElemList(); while (!nodeList.empty()) { - SvgElement currElem = nodeList.takeFirst().toElement(); + SvgElement currElem = nodeList.takeFirst(); if (currElem.hasAttribute("xlink:href")) defsIdList.remove(currElem.attribute("xlink:href").remove("#")); if (currElem.hasAttribute("style")) { - QStringList tmpList = currElem.attribute("style").split(";").filter("url"); + QStringList tmpList = currElem.style().split(";").filter("url"); for (int i = 0; i < tmpList.count(); ++i) { QString url = tmpList.at(i); url.replace(RegEx::xlinkUrl, ""); @@ -57,7 +75,7 @@ void Remover::removeUnusedDefs() } foreach (const SvgElement &elem, m_defsElem.childElemList()) { - if (defsIdList.contains(elem.attribute("id"))) + if (defsIdList.contains(elem.id())) m_defsElem.removeChild(elem); } } @@ -75,7 +93,7 @@ void Remover::removeUnusedXLinks() xlinkSet << currElem.attribute("xlink:href").remove("#"); } if (currElem.hasAttribute("id")) - idSet << currElem.attribute("id"); + idSet << currElem.id(); if (currElem.hasChildNodes()) list << currElem.childElemList(); @@ -83,7 +101,7 @@ void Remover::removeUnusedXLinks() QSet::iterator it = idSet.begin(); while (it != idSet.end()) { xlinkSet.remove(*it); - it++; + ++it; } list = m_svgElem.childElemList(); @@ -108,11 +126,11 @@ void Remover::removeDuplicatedDefs() StringHash xlinkToReplace; QDomNodeList defsList = m_defsElem.childNodes(); for (int i = 0; i < defsList.count(); ++i) { - QDomElement currElem = defsList.at(i).toElement(); + SvgElement currElem = defsList.at(i).toElement(); if (!currElem.hasChildNodes()) { for (int j = 0; j < defsList.count(); ++j) { - QDomElement currElem2 = defsList.at(j).toElement(); - if (currElem2.attribute("id") != currElem.attribute("id") + SvgElement currElem2 = defsList.at(j).toElement(); + if (currElem2.id() != currElem.id() && currElem2.tagName() == currElem.tagName() && !currElem2.hasChildNodes()) { QSet currAttrList; @@ -123,13 +141,13 @@ void Remover::removeDuplicatedDefs() if (!currAttrList.isEmpty()) { if (Tools::isAttrEqual(currElem, currElem2, currAttrList)) { - if (xlinkToReplace.values().contains(currElem2.attribute("id"))) { + if (xlinkToReplace.values().contains(currElem2.id())) { for (int k = 0; k < xlinkToReplace.keys().count(); ++k) { - if (xlinkToReplace.value(xlinkToReplace.keys().at(k)) == currElem2.attribute("id")) - xlinkToReplace.insert(xlinkToReplace.keys().at(k), currElem.attribute("id")); + if (xlinkToReplace.value(xlinkToReplace.keys().at(k)) == currElem2.id()) + xlinkToReplace.insert(xlinkToReplace.keys().at(k), currElem.id()); } } - xlinkToReplace.insert(currElem2.attribute("id"), currElem.attribute("id")); + xlinkToReplace.insert(currElem2.id(), currElem.id()); m_defsElem.removeChild(currElem2); j--; } @@ -148,7 +166,7 @@ void Remover::updateXLinks(StringHash hash) SvgElement currElem = list.takeFirst().toElement(); if (currElem.hasAttribute("style")) { - QString style = currElem.attribute("style"); + QString style = currElem.style(); QStringList tmpList = style.split(";"); bool changed = false; for (int i = 0; i < tmpList.count(); ++i) { @@ -161,7 +179,7 @@ void Remover::updateXLinks(StringHash hash) } } if (changed) - currElem.setAttribute("style", tmpList.join(";")); + currElem.setStyle(tmpList.join(";")); } if (currElem.hasAttribute("xlink:href")) { foreach (const QString &key, hash.keys()) { @@ -195,7 +213,7 @@ void Remover::removeUnreferencedIds() // collect all id's if (currElem.hasAttribute("id")) - m_allIdList << currElem.attribute("id"); + m_allIdList << currElem.id(); for (int i = 0; i < xlinkAttrList.count(); ++i) { @@ -211,7 +229,7 @@ void Remover::removeUnreferencedIds() } if (currElem.hasAttribute("style")) { - QString style = currElem.attribute("style"); + QString style = currElem.style(); QStringList styleList = style.split(";").filter("url"); for (int j = 0; j < styleList.count(); ++j) m_allLinkList << QString(styleList.at(j)).remove(RegEx::xlinkUrl); @@ -240,7 +258,7 @@ void Remover::removeUnreferencedIds() while (!list.empty()) { SvgElement currElem = list.takeFirst().toElement(); - if (m_allIdList.contains(currElem.attribute("id"))) + if (m_allIdList.contains(currElem.id())) currElem.removeAttribute("id"); if (m_allIdList.contains(currElem.attribute("clip-path"))) @@ -292,6 +310,8 @@ void Remover::removeElements() removeThisNode = true; else if (currTag == "desc") removeThisNode = true; + else if (currTag == "defs" && currElem.childNodes().isEmpty()) + removeThisNode = true; else if (currTag == "image" && !currElem.attribute("xlink:href").contains("base64")) removeThisNode = true; else if (currElem.isReferenced() && !currElem.hasAttribute("id") @@ -315,13 +335,40 @@ void Remover::removeElements() } + // distributions-pentubuntu.svg + // FIXME: switch style attr have to be cleaned before it, and other attr have to be removed + qreal stdDevLimit = Keys::get().doubleNumber(Key::StdDeviation); list = Tools::childNodeList(m_dom); while (!list.empty()) { SvgElement currElem = list.takeFirst().toElement(); - if (currElem.tagName() == "switch") { - QStringList attrList = currElem.attributesList(); - if (attrList.isEmpty() || (attrList.count() == 1 && attrList.first() == "id")) - ungroupSwitch(currElem); +// if (currElem.tagName() == "switch") { +// QStringList attrList = currElem.attributesList(); +// if (attrList.isEmpty() || (attrList.count() == 1 && attrList.first() == "id")) { + // remove groups +// foreach (SvgElement elem, currElem.childElemList()) { +// if (elem.isGroup()) +// currElem.removeChild(elem); +// } +// ungroupSwitch(currElem); +// } + /*} else */if (currElem.tagName() == "feGaussianBlur") { + // remove "feGaussianBlur" element with "stdDeviation" value + // lower than "--std-deviation-limit" + if (!Keys::get().flag(Key::KeepTinyGaussianBlur)) { + if (currElem.parentNode().childNodes().count() == 1) { + // 'stdDeviation' can contains not only one value + // we process when it contains only one value + if (!currElem.attribute("stdDeviation").contains(QRegExp(",| "))) { + bool ok = true; + if (currElem.attribute("stdDeviation").toDouble(&ok) <= stdDevLimit) { + Q_ASSERT(ok == true); + QDomNode node = m_defsElem.removeChild(currElem.parentNode()); + Q_ASSERT(node.isNull() == false); + // TODO: maybe remove xlink, but its slow... + } + } + } + } } if (currElem.hasChildNodes()) list << currElem.childNodeList(); @@ -341,72 +388,68 @@ bool Remover::isInvisibleElementsExist(SvgElement elem) //remove elements "rect", "pattern" and "image" with height or width <= 0 if (elem.tagName().contains(QRegExp("rect|pattern|image"))) { if (elem.hasAttributes(QStringList() << "width" << "height")) { + QRectF rect = Tools::viewBoxRect(m_svgElem); bool ok = false; - qreal width = Tools::convertUnitsToPx(elem.attribute("width")).toDouble(&ok); + qreal width = Tools::convertUnitsToPx(elem.attribute("width"), rect.width()).toDouble(&ok); Q_ASSERT(ok == true); - qreal height = Tools::convertUnitsToPx(elem.attribute("height")).toDouble(&ok); + qreal height = Tools::convertUnitsToPx(elem.attribute("height"), rect.height()).toDouble(&ok); Q_ASSERT(ok == true); if (width <= 0 || height <= 0) return true; } } + StringHash hash = elem.styleHash(); + // TODO: finish // remove elements with opacity="0" -// if (elem.isStyleContains("opacity:")) -// return true; + if (hash.contains("opacity")) { + bool ok = true; + if (elem.styleHash().value("opacity").toDouble(&ok) == 0) { + Q_ASSERT_X(ok == true, "error", qPrintable(elem.styleHash().value("opacity"))); + return true; + } + } // remove elements with "display=none" - if (elem.isStyleContains("display:none")) + if (hash.value("display") == "none") return true; // remove "path" elements with empty "d" attr - if (elem.tagName() == "path") { - if (elem.attribute("d").isEmpty()) { + if (elem.tagName() == "path") + if (elem.attribute("d").isEmpty()) return true; - } + + // A negative value is an error. A value of zero disables rendering of this element. + if (elem.tagName() == "use") { + if (elem.hasAttribute("width")) + if (elem.attribute("width").toDouble() == 0) + return true; + if (elem.hasAttribute("height")) + if (elem.attribute("height").toDouble() == 0) + return true; } // remove "polygon", "polyline" elements with empty "points" attr - if (elem.tagName().contains(QRegExp("polygon|polyline"))) { - if (elem.attribute("points").isEmpty()) { + if (elem.tagName().contains(QRegExp("polygon|polyline"))) + if (elem.attribute("points").isEmpty()) return true; - } - } // remove "circle" elements with "r" <= 0 - if (elem.tagName() == "circle") { - if (elem.attribute("r").toDouble() <= 0) { + if (elem.tagName() == "circle") + if (elem.attribute("r").toDouble() <= 0) return true; - } - } // remove "ellipse" elements with "rx|ry" <= 0 - if (elem.tagName() == "ellipse") { + if (elem.tagName() == "ellipse") if ( elem.attribute("rx").toFloat() <= 0 - || elem.attribute("ry").toFloat() <= 0) { + || elem.attribute("ry").toFloat() <= 0) return true; - } - } // remove empty "text" elements - if (elem.tagName() == "text") { - if (elem.text().isEmpty()) { + if (elem.tagName() == "text") + if (elem.text().isEmpty()) return true; - } - } - - // remove "feGaussianBlur" element with "stdDeviation" value lower than "--std-deviation-limit" - // FIXME: have to delete - // TODO: stdDeviation can contains two values - if (!Keys::get().flag(Key::KeepTinyGaussianBlur)) { - if (elem.tagName() == "feGaussianBlur" && elem.parentNode().childNodes().count() == 0) { - if (elem.attribute("stdDeviation").toFloat() - <= Keys::get().doubleNumber(Key::StdDeviation)) { - return true; - } - } - } // remove "switch" with no attributes or with only "id" attribute if (elem.tagName() == "switch" && !elem.hasChildNodes()) { @@ -420,12 +463,16 @@ bool Remover::isInvisibleElementsExist(SvgElement elem) } // TODO: remove 'class' attr which has linked to empty object +// aaha_Gear.svg void Remover::removeAttributes() { QList list = Tools::childElemList(m_dom); while (!list.empty()) { SvgElement currElem = list.takeFirst().toElement(); + // NOTE: sodipodi:type="inkscape:offset" supported only by inkscape, + // and its creates problems in other renderers + // remove "inkscape.*", but not "inkscape:path-effect" if (!Keys::get().flag(Key::KeepInkscapeAttributes)) cleanAttribute(currElem, QRegExp("inkscape:(?!path-effect).*")); @@ -469,11 +516,6 @@ void Remover::cleanAttribute(SvgElement elem, QRegExp rx) } } -// TODO: move similar styles to group -// Anonymous_Romania_Map.svg -// capellan2000_Escudo_Nacional_Dominicano.svg -// zlatkodesign_Sniper_optic_cleaned.svg - QList styleHashList; StringHash parentHash; void Remover::processStyleAttr(SvgElement elem) @@ -481,11 +523,12 @@ void Remover::processStyleAttr(SvgElement elem) if (elem.isNull()) elem = m_svgElem; + m_usedElemList = Tools::usedElemList(m_svgElem); + StringHash currHash = elem.styleHash(); styleHashList << currHash; - for (int i = 0; i < currHash.count(); ++i) { + for (int i = 0; i < currHash.count(); ++i) parentHash.insert(currHash.keys().at(i), currHash.value(currHash.keys().at(i))); - } QList nodeList = elem.childElemList(); while (!nodeList.empty()) { @@ -499,7 +542,7 @@ void Remover::processStyleAttr(SvgElement elem) else { foreach (QString attr, Props::styleAttrList) currElem.removeAttribute(attr); - currElem.setAttribute("style", Tools::styleHashToString(hash)); + currElem.setStyle(Tools::styleHashToString(hash)); } } @@ -526,7 +569,6 @@ void Remover::cleanStyle(const SvgElement &elem, StringHash *hash) if (attr.contains("font") || attr.contains("text")) hash->remove(attr); } - hash->remove("line-height"); hash->remove("writing-mode"); } else { // remove default text style values @@ -586,6 +628,11 @@ void Remover::cleanStyle(const SvgElement &elem, StringHash *hash) } } + + // remove clip-rule if no clip-path + if (hash->contains("clip-rule") && !hash->contains("clip-path")) + hash->remove("clip-rule"); + // 'enable-background' is only applicable to container elements if (!Props::containers.contains(elem.tagName())) hash->remove("enable-background"); @@ -608,7 +655,8 @@ void Remover::cleanStyle(const SvgElement &elem, StringHash *hash) value = hash->value(key); } if (value.contains(QRegExp("^[0-9\\.]+$"))) { - hash->insert(key, Tools::roundNumber(value.toDouble(), Tools::ATTRIBUTES)); + QString number = Tools::roundNumber(value.toDouble(), Tools::ATTRIBUTES); + hash->insert(key, number); } } } @@ -623,16 +671,19 @@ void Remover::cleanStyle(const SvgElement &elem, StringHash *hash) hash->insert("stroke", Tools::trimColor(hash->value("stroke"))); if (hash->contains("stop-color")) hash->insert("stop-color", Tools::trimColor(hash->value("stop-color"))); + if (hash->contains("color")) + hash->insert("color", Tools::trimColor(hash->value("color"))); + if (hash->contains("flood-color")) + hash->insert("flood-color", Tools::trimColor(hash->value("flood-color"))); } - if (!parentHash.contains("stroke-width")) { + if (!parentHash.contains("stroke-width")) removeDefaultValue(hash, "stroke-width"); - } - // TODO: why we ignores fill and stroke? - // needed for andreas_Bureau_de_change.svg + // remove style props which already defined in parent style + // ignore used/linked elements and opacity foreach (const QString &attr, parentHash.keys()) { - if (attr != "fill" && attr != "stroke" && attr != "opacity") { + if (attr != "opacity" && !m_usedElemList.contains(elem.id())) { if (hash->contains(attr)) if (hash->value(attr) == parentHash.value(attr)) hash->remove(attr); @@ -661,25 +712,14 @@ void Remover::removeDefaultValue(StringHash *hash, const QString &name) } // TODO: remove "symbol" -// FIXME: fails with comments, because it's a null QDomElement -// Anonymous_dartboard_cleaned.svg - // TODO: apply transform from group // rasor_SQL_Backup.svg void Remover::removeGroups() { // get all 'use' links to prevent ungouping element linked to this use + m_usedElemList = Tools::usedElemList(m_svgElem); QList nodeList = m_svgElem.childElemList(); - while (!nodeList.empty()) { - SvgElement currElem = nodeList.takeFirst(); - if (currElem.tagName() == "use") { - if (currElem.hasAttribute("xlink:href")) - m_useLinkList << currElem.attribute("xlink:href").remove("#"); - } - if (currElem.hasChildNodes()) - nodeList << currElem.childElemList(); - } nodeList = m_svgElem.childElemList(); while (!nodeList.empty()) { @@ -687,7 +727,7 @@ void Remover::removeGroups() StringHash currStyle = currElem.styleHash(); // ungroup only if: current group is not linked to any 'use', // not contains mask, clip-path, filter, opacity attributes - if (currElem.tagName() == "g" && !m_useLinkList.contains(currElem.attribute("id")) + if (currElem.tagName() == "g" && !m_usedElemList.contains(currElem.id()) && !currStyle.contains("mask") && !currStyle.contains("clip-path") && !currStyle.contains("opacity") && !currStyle.contains("filter")) { @@ -698,21 +738,33 @@ void Remover::removeGroups() // ungrouping is pretty brutal, so we need to fix some issues nodeList = m_svgElem.childElemList(); QList gNodeList; - for (int i = 1; i < nodeList.count(); ++i) { - SvgElement prevElem = nodeList.at(i-1); - SvgElement currElem = nodeList.at(i); - gNodeList << prevElem; - if (prevElem.tagName() == currElem.tagName() && currElem.tagName() == "g" - && prevElem.hasAttribute("tramsform") && currElem.hasAttribute("tramsform")) { - if (prevElem.attribute("style") != currElem.attribute("style") - && !currElem.hasAttribute("style") && currElem.attribute("style").contains("opacity")) { - if (gNodeList.count() > 1) - mergeGroups(gNodeList); - gNodeList.clear(); + SvgElement lastGNode; + while (!nodeList.empty()) { + SvgElement currElem = nodeList.takeFirst(); + + if (currElem.isGroup()) { + if (lastGNode.isNull()) { + gNodeList << currElem; + lastGNode = currElem; + } else { + bool flag = true; + if (currElem.style() == lastGNode.style() + && currElem.hasAttribute("style") && !currElem.style().contains("opacity") + && currElem.attribute("transform") == lastGNode.attribute("transform")) { + gNodeList << currElem; + } else { + flag = false; + } + + if (!flag || nodeList.isEmpty()) { + if (gNodeList.count() > 1) + mergeGroups(gNodeList); + lastGNode.clear(); + gNodeList.clear(); + } } } else { - if (gNodeList.count() > 1) - mergeGroups(gNodeList); + lastGNode.clear(); gNodeList.clear(); } } @@ -732,7 +784,7 @@ void Remover::removeGroup(SvgElement elem) StringHash currStyle = currElem.styleHash(); // ungroup only if: current group is not linked to any 'use', // not contains mask, clip-path, filter, opacity attributes - if (currElem.tagName() == "g" && !m_useLinkList.contains(currElem.attribute("id")) + if (currElem.tagName() == "g" && !m_usedElemList.contains(currElem.id()) && !currStyle.contains("mask") && !currStyle.contains("clip-path") && !currStyle.contains("opacity") && !currStyle.contains("filter")) { // TODO: ignore current group, but process childs @@ -760,12 +812,12 @@ SvgElement Remover::genGroup(SvgElement currElem, SvgElement parentGroup) QStringList styles; SvgElement parentElem = currElem.parentNode().toElement(); if (parentGroup.hasAttribute("id")) - idAttr = parentGroup.attribute("id"); + idAttr = parentGroup.id(); while (parentElem.tagName() != "svg") { if (parentElem.hasAttribute("transform")) transformAttr = parentElem.attribute("transform") + " " + transformAttr; if (parentElem.hasAttribute("style")) - styles.prepend(parentElem.attribute("style")); + styles.prepend(parentElem.style()); parentElem = parentElem.parentNode().toElement(); } @@ -796,7 +848,7 @@ SvgElement Remover::genGroup(SvgElement currElem, SvgElement parentGroup) if (!transformAttr.isEmpty()) newGElem.setAttribute("transform", transformAttr); if (!styleAttr.isEmpty()) - newGElem.setAttribute("style", styleAttr); + newGElem.setStyle(styleAttr); if (!idAttr.isEmpty()) newGElem.setAttribute("id", idAttr); return m_svgElem.insertBefore(newGElem, parentGroup).toElement(); @@ -808,7 +860,8 @@ void Remover::mergeGroups(QList gNodeList) { SvgElement newGElem = m_dom.createElement("g"); newGElem = m_svgElem.insertBefore(newGElem, gNodeList.first()).toElement(); - newGElem.setAttribute("style", gNodeList.first().attribute("style")); + newGElem.setStyle(gNodeList.first().style()); + newGElem.setAttribute("transform", gNodeList.first().attribute("transform")); Q_ASSERT(newGElem.isNull() == false); for (int i = 0; i < gNodeList.count(); ++i) { SvgElement cElem = gNodeList.at(i); diff --git a/src/cli/remover.h b/src/cli/remover.h index 938aed95..abaa2442 100644 --- a/src/cli/remover.h +++ b/src/cli/remover.h @@ -30,7 +30,7 @@ class Remover void cleanAttribute(SvgElement elem, QRegExp rx); void ungroupSwitch(SvgElement elem); - QSet m_useLinkList; + QSet m_usedElemList; SvgElement genGroup(SvgElement currElem, SvgElement parentGroup); void mergeGroups(QList gNodeList); }; diff --git a/src/cli/replacer.cpp b/src/cli/replacer.cpp index d7c222ab..755449cb 100644 --- a/src/cli/replacer.cpp +++ b/src/cli/replacer.cpp @@ -1,13 +1,19 @@ -#include -#include - -#include - #include "replacer.h" #include "keys.h" // TODO: trim id names // TODO: remove empty spaces at end of line in text elem +// TODO: round font size to int +// TODO: round style attributes +// TODO: replace equal 'fill', 'stroke', 'stop-color', 'flood-color' and 'lighting-color' attr +// with 'color' attr + +// TODO: read about 'marker' +// TODO: try to group similar elems to use +// gaerfield_data-center.svg + +// TODO: If ‘x1’ = ‘x2’ and ‘y1’ = ‘y2’, then the area to be painted will be painted as +// a single color using the color and opacity of the last gradient stop. Replacer::Replacer(QDomDocument dom) { @@ -37,6 +43,7 @@ void Replacer::convertSizeToViewbox() } // TODO: remove identical paths +// Anonymous_man_head.svg void Replacer::processPaths() { QList list = m_svgElem.childElemList(); @@ -121,6 +128,7 @@ void Replacer::convertUnits() } } +// FIXME: gaim-4.svg void Replacer::convertCDATAStyle() { QDomNodeList styleNodeList = m_dom.elementsByTagName("style"); @@ -167,7 +175,7 @@ void Replacer::convertCDATAStyle() QString newStyle = Tools::styleHashToString(newHash); if (!newStyle.isEmpty()) - currElem.setAttribute("style", newStyle); + currElem.setStyle(newStyle); currElem.removeAttribute("class"); } @@ -214,7 +222,8 @@ void Replacer::joinStyleAttr() QString newStyle; foreach (const QString &attr, Props::styleAttrList) { if (currElem.hasAttribute(attr)) { - styleHash.insert(attr, currElem.attribute(attr)); + // insert all style attr to hash and remove spaces at end of attr value + styleHash.insert(attr, currElem.attribute(attr).remove(QRegExp(" +$"))); currElem.removeAttribute(attr); } } @@ -224,7 +233,7 @@ void Replacer::joinStyleAttr() newStyle = Tools::styleHashToString(styleHash); if (!newStyle.isEmpty()) - currElem.setAttribute("style", newStyle); + currElem.setStyle(newStyle); } if (currElem.hasChildNodes()) @@ -246,8 +255,13 @@ void Replacer::fixWrongAttr() } } - // fix wrong 'rx', 'ry' attributes in 'rect' elem - if (currTag == "rect") { + if (currTag == "use") { + if (currElem.attribute("width").toDouble() < 0) + currElem.setAttribute("width", "0"); + if (currElem.attribute("height").toDouble() < 0) + currElem.setAttribute("height", "0"); + } else if (currTag == "rect") { + // fix wrong 'rx', 'ry' attributes in 'rect' elem // remove, if one of 'r' is null if ((currElem.hasAttribute("rx") && currElem.hasAttribute("ry")) && (currElem.attribute("rx") == 0 || currElem.attribute("ry") == 0)) { @@ -277,6 +291,7 @@ void Replacer::fixWrongAttr() // TODO: Gradient offset values less than 0 (or less than 0%) are rounded up to 0%. Gradient offset // values greater than 1 (or greater than 100%) are rounded down to 100%. +// TODO: remove empty defs here void Replacer::finalFixes() { QList list = m_svgElem.childElemList(); @@ -284,18 +299,24 @@ void Replacer::finalFixes() SvgElement currElem = list.takeFirst().toElement(); QString currTag = currElem.tagName(); - // fix wrong 'rx', 'ry' attributes in 'rect' elem - if (currTag == "rect") { - if (currElem.attribute("rx").toDouble() == currElem.attribute("ry").toDouble()) - currElem.removeAttribute("ry"); + if (!Keys::get().flag(Key::KeepNotAppliedAttributes)) { + if (currTag == "rect") { + if (currElem.attribute("rx").toDouble() == currElem.attribute("ry").toDouble()) + currElem.removeAttribute("ry"); + } else if (currTag == "use") { + if (currElem.attribute("x") == "0") + currElem.removeAttribute("x"); + if (currElem.attribute("y") == "0") + currElem.removeAttribute("y"); + } } if (!Keys::get().flag(Key::KeepGradientCoordinates)) { if (currTag == "linearGradient" && (currElem.hasAttribute("x2") || currElem.hasAttribute("y2"))) { if (currElem.attribute("x1") == currElem.attribute("x2")) { - currElem.setAttribute("x2", 0); currElem.removeAttribute("x1"); + currElem.setAttribute("x2", 0); } if (currElem.attribute("y1") == currElem.attribute("y2")) { currElem.removeAttribute("y1"); @@ -374,7 +395,7 @@ void Replacer::roundDefs() if (currElem.hasAttribute(attr)) { QString value = currElem.attribute(attr); // process list based attributes - if (attr == "stdDeviation" || attr == "baseFrequency") { + if (attr == "stdDeviation" || attr == "baseFrequency" || attr == "dx") { QStringList tmpList = value.split(QRegExp("(,|) "), QString::SkipEmptyParts); QString tmpStr; foreach (const QString &text, tmpList) { @@ -387,7 +408,8 @@ void Replacer::roundDefs() } else { bool ok; QString attrVal = Tools::roundNumber(value.toDouble(&ok), Tools::TRANSFORM); - Q_ASSERT_X(ok == true, value.toAscii(), "wrong unit type"); + Q_ASSERT_X(ok == true, value.toAscii(), + qPrintable("wrong unit type in " + currElem.id())); currElem.setAttribute(attr, attrVal); } } @@ -424,6 +446,7 @@ void Replacer::roundDefs() } // TODO: try to convert thin rect to line-to path +// view-calendar-list.svg // http://www.w3.org/TR/SVG/shapes.html void Replacer::convertBasicShapes() @@ -451,7 +474,8 @@ void Replacer::convertBasicShapes() .arg(QString::number(x1)) .arg(QString::number(y1)); - currElem.removeAttributes(QStringList() << "x" << "y" << "width" << "height"); + currElem.removeAttributes(QStringList() << "x" << "y" << "width" << "height" + << "rx" << "ry"); } else { qreal x = currElem.attribute("x").toDouble(); @@ -503,7 +527,7 @@ void Replacer::convertBasicShapes() // + QString("A %1,%2 0 1 0 %3,%4 ").arg(rx).arg(ry).arg(x2).arg(y) // + QString("A %1,%2 0 1 0 %3,%4").arg(rx).arg(ry).arg(x1).arg(y); } else if (ctag == "polyline" || ctag == "polygon") { - SegmentList segmentList; + QList segmentList; // TODO: smart split // orangeobject_background-ribbon.svg QStringList tmpList @@ -512,7 +536,7 @@ void Replacer::convertBasicShapes() bool ok; Segment seg; seg.command = Command::MoveTo; - seg.abs = true; + seg.absolute = true; seg.srcCmd = false; seg.x = tmpList.at(j).toDouble(&ok); Q_ASSERT(ok == true); @@ -523,7 +547,7 @@ void Replacer::convertBasicShapes() if (ctag == "polygon") { Segment seg; seg.command = Command::ClosePath; - seg.abs = false; + seg.absolute = false; seg.srcCmd = true; segmentList.append(seg); } @@ -548,14 +572,16 @@ void Replacer::convertBasicShapes() void Replacer::splitStyleAttr() { QList list = Tools::childElemList(m_dom); + bool flag = Keys::get().flag(Key::JoinStyleAttributes); while (!list.empty()) { SvgElement currElem = list.takeFirst().toElement(); - - if (currElem.hasAttribute("style")) { - StringHash hash = currElem.styleHash(); - foreach (QString key, hash.keys()) - currElem.setAttribute(key, hash.value(key)); - currElem.removeAttribute("style"); + if (!flag || currElem.tagName().contains("feFlood")) { + if (currElem.hasAttribute("style")) { + StringHash hash = currElem.styleHash(); + foreach (QString key, hash.keys()) + currElem.setAttribute(key, hash.value(key)); + currElem.removeAttribute("style"); + } } if (currElem.hasChildNodes()) @@ -601,9 +627,251 @@ SvgElement Replacer::findLinearGradient(const QString &id) QList list = m_defsElem.childElemList(); while (!list.empty()) { SvgElement currElem = list.takeFirst(); - if (currElem.tagName() == "linearGradient" && currElem.attribute("id") == id) { + if (currElem.tagName() == "linearGradient" && currElem.id() == id) { return currElem; } } return SvgElement(); } + +/* + * Tries to group elements with equal style properties. + * After grouping we can remove these styles from original element, and thus simplify our svg. + * + * For example: + * + * (before) + * + * + * + * + * (after) + * + * + * + * + * + * + */ +void Replacer::groupElementsByStyles(SvgElement parentElem) +{ + // first start + if (parentElem.isNull()) { + // elem linked to 'use' have to store style properties only in elem, not in group + m_usedElemList = Tools::usedElemList(m_svgElem); + groupElementsByStyles(m_svgElem); + } + + // check whether there styles in child elements + int styleCount = 0; + foreach (const SvgElement &elem, parentElem.childElemList()) { + if (elem.hasAttribute("style")) + styleCount++; + } + + RepetitionList styleRepetList; + if (styleCount > 1) { + // get list of all unique style properties sorted by the number of coincidences + styleRepetList = findRepetitionList(parentElem.childElemList()); + } else { + // if no styles - trying to find and process groups in this element + QList list = parentElem.childElemList(); + while (!list.isEmpty()) { + SvgElement currElem = list.takeFirst(); + if (currElem.isGroup()) + groupElementsByStyles(currElem); + } + return; + } + + if (styleRepetList.isEmpty()) + return; + + if (styleRepetList.first().second == 1) { + SvgElement singleElem; + foreach (const SvgElement &elem, parentElem.childElemList()) { + if (!m_usedElemList.contains(elem.id())) { + if (singleElem.isNull()) + singleElem = elem; + else + return; + } + } + if (!singleElem.isNull()) { +// StringHash hash = singleElem.styleHash(); +// StringHash parentHash = parentElem.styleHash(); +// foreach (const QString &attr, hash.keys()) { +// qDebug() << attr; +// if (parentHash.contains(attr)) +// parentHash.remove(attr); +// } +// parentElem.setStyle(Tools::styleHashToString(parentHash)); + parentElem.removeAttribute("style"); + } + + return; + } + + const QList list = parentElem.childElemList(); + // store all processed groups, to prevent retreatment processing + QList precessedGList; + SvgElement mainGElem; + // process child elements + while (!styleRepetList.isEmpty()) { + // stop when no repetitions are left + if (styleRepetList.first().second == 1) + break; + + QString currStyle = styleRepetList.first().first; + // if current style property repeats equal to child elements count + if (styleRepetList.first().second == list.count()) { + // create new group if parent elem is not a group + if (mainGElem.isNull()) { + if (parentElem.isGroup()) { + mainGElem = parentElem; + } else { + mainGElem = m_dom.createElement("g"); + mainGElem = parentElem.insertBefore(mainGElem, list.first()).toElement(); + } + } + mainGElem.appendStyle(currStyle); + Q_ASSERT(mainGElem.isNull() == false); + + // move elem to group + foreach (SvgElement elem, list) { + QStringList tmpList = elem.style().split(";", QString::SkipEmptyParts); + tmpList.removeOne(currStyle); + QString newStyle = tmpList.join(";"); + elem.setStyle(newStyle); + mainGElem.appendChild(elem); + } + } else { + QList childList = parentElem.childElemList(); + QList similarElemList; + while (!childList.empty()) { + SvgElement currElem = childList.takeFirst(); + if (currElem.isGroup() && !precessedGList.contains(currElem)) { + groupElementsByStyles(currElem); + precessedGList << currElem; + } + // NOTE: the slowest line + QStringList currStyleList + = currElem.style().split(";", QString::SkipEmptyParts); + + bool isElemSimilar = false; + if (currStyleList.contains(currStyle) + && currElem.tagName() != "use" + && !m_usedElemList.contains(currElem.id())) + { + similarElemList << ElemListPair(currElem, currStyleList); + isElemSimilar = true; + } + if (!isElemSimilar || childList.isEmpty()) { + if (similarElemList.count() > 1) { + QStringList stylesToRemove; + stylesToRemove << currStyle; + // also remove style props which equal for current elem list + for (int j = 1; j < styleRepetList.count(); ++j) { + bool flag = true; + foreach (ElemListPair pair, similarElemList) { + if (!pair.second.contains(styleRepetList.at(j).first)) { + flag = false; + break; + } + } + if (flag) + stylesToRemove << styleRepetList.at(j).first; + } + + // add first most popular style, which not exist before + // NOTE: actualy can add not only first + RepetitionList tmpRList = findRepetitionList(similarElemList); + for (int k = 0; k < tmpRList.count(); ++k) { + if (!tmpRList.at(k).first + .contains(QRegExp("stroke|fill|opacity|marker|font-size"))) { + if (!stylesToRemove.contains(tmpRList.at(k).first)) { + if (tmpRList.at(k).second > 1) { + stylesToRemove << tmpRList.at(k).first; + break; + } + } + } + } + + if (!stylesToRemove.isEmpty()) { + SvgElement newGElem = m_dom.createElement("g"); + SvgElement firstElem = similarElemList.first().first; + newGElem = parentElem.insertBefore(newGElem, firstElem).toElement(); + newGElem.setStyle(stylesToRemove.join(";")); + Q_ASSERT(newGElem.isNull() == false); + + foreach (ElemListPair pair, similarElemList) { + QStringList tmpList = pair.second; + foreach (const QString &text, stylesToRemove) + tmpList.removeAll(text); + QString newStyle = tmpList.join(";"); + pair.first.setStyle(newStyle); + newGElem.appendChild(pair.first); + } + } + } + similarElemList.clear(); + } + } + } + styleRepetList.removeFirst(); + } +} + +RepetitionList Replacer::findRepetitionList(const QList &list) +{ + QList styleList; + foreach (const SvgElement &elem, list) { + if (!m_usedElemList.contains(elem.id())) + styleList << elem.style().split(";", QString::SkipEmptyParts); + } + return genRepetitionList(styleList); +} + +RepetitionList Replacer::findRepetitionList(QList list) +{ + QList styleList; + foreach (const ElemListPair &pair, list) { + if (!m_usedElemList.contains(pair.first.id())) + styleList << pair.second; + } + return genRepetitionList(styleList); +} + +bool repetitionListSort(const RepetitionItem &s1, const RepetitionItem &s2) +{ + return s1.second > s2.second; +} + +// create list of used unique styles sorted by the number of coincidences +RepetitionList Replacer::genRepetitionList(const QList &list) +{ + RepetitionList rList; + foreach (const QStringList &styleList, list) { + foreach (const QString &currStyle, styleList) { + // TODO: 'opacity' can be grouped, sometimes + // TODO: test for fill:url + if (!currStyle.contains(QRegExp("^opacity|clip-path|font-weight|filter|mask|fill:url"))) { + // find style in list + int pos = -1; + for (int j = 0; j < rList.count(); ++j) { + if (rList.at(j).first == currStyle) { + pos = j; + break; + } + } + if (pos == -1) + rList.append(RepetitionItem(currStyle, 1)); + else + rList[pos].second = rList.at(pos).second + 1; + } + } + } + qSort(rList.begin(), rList.end(), repetitionListSort); + return rList; +} diff --git a/src/cli/replacer.h b/src/cli/replacer.h index 190698d2..ae0de33c 100644 --- a/src/cli/replacer.h +++ b/src/cli/replacer.h @@ -4,6 +4,10 @@ #include "paths.h" #include "tools.h" +typedef QPair ElemListPair; +typedef QPair RepetitionItem; +typedef QList RepetitionList; + class Replacer { public: @@ -23,13 +27,19 @@ class Replacer void mergeGradients(); void finalFixes(); void calcElemAttrCount(const QString &text); + void groupElementsByStyles(SvgElement parentElem = SvgElement()); + void applyTransformMatrices(); private: QDomDocument m_dom; SvgElement m_svgElem; SvgElement m_defsElem; + QSet m_usedElemList; SvgElement findLinearGradient(const QString &id); + RepetitionList findRepetitionList(const QList &list); + RepetitionList findRepetitionList(QList list); + RepetitionList genRepetitionList(const QList &list); }; #endif // REPLACER_H diff --git a/src/cli/tools.cpp b/src/cli/tools.cpp index 21f4fc23..82fcae7a 100644 --- a/src/cli/tools.cpp +++ b/src/cli/tools.cpp @@ -1,13 +1,12 @@ -#include -#include - -#include +#include #include "keys.h" #include "tools.h" Transform::Transform(const QString &text) { + if (text.isEmpty()) + return; m_points = mergeMatrixes(text); // set default values @@ -49,8 +48,7 @@ void Transform::setOldXY(qreal prevX, qreal prevY) qreal Transform::newX() { - qreal value = m_baseX; - value = (m_xMove + m_xScale*(cos(m_angle)*m_baseX - sin(m_angle)*m_baseY)); + qreal value = m_xMove + m_xScale*(cos(m_angle)*m_baseX - sin(m_angle)*m_baseY); if (m_isXMirror) value = -value; return value; @@ -58,68 +56,17 @@ qreal Transform::newX() qreal Transform::newY() { - qreal value = m_baseX; - value = (m_yMove + m_yScale*(sin(m_angle)*m_baseX + cos(m_angle)*m_baseY)); + qreal value = m_yMove + m_yScale*(sin(m_angle)*m_baseX + cos(m_angle)*m_baseY); if (m_isYMirror) value = -value; return value; } -// TODO: remove not important values, like -// translate(-7 0) -> translate(-7) -QString Transform::simplified() -{ - QString transform; - QList pt = m_points; - - // [1 0 0 1 tx ty] = translate - if (pt.at(0) == 1 && pt.at(1) == 0 && pt.at(2) == 0 && pt.at(3) == 1) { - transform = QString("translate(%1 %2)") - .arg(Tools::roundNumber(pt.at(4), Tools::TRANSFORM), - Tools::roundNumber(pt.at(5), Tools::TRANSFORM)); - if (transform == "translate(0 0)") - transform.clear(); - } // [sx 0 0 sy 0 0] = scale - else if (pt.at(1) == 0 && pt.at(2) == 0 && pt.at(4) == 0 && pt.at(5) == 0) { - transform = QString("scale(%1 %2)") - .arg(Tools::roundNumber(pt.at(0), Tools::TRANSFORM), - Tools::roundNumber(pt.at(3), Tools::TRANSFORM)); - if (transform == "scale(0 0)") - transform.clear(); - } // [cos(a) sin(a) -sin(a) cos(a) 0 0] = rotate - else if (pt.at(0) == pt.at(3) && pt.at(1) > 0 && pt.at(2) < 0 && pt.at(4) == 0 && pt.at(5) == 0) { - transform = QString("rotate(%1)") - .arg(Tools::roundNumber(acos(pt.at(0))*(180/M_PI), Tools::TRANSFORM)); - if (transform == "rotate(0)") - transform.clear(); - } // [1 0 tan(a) 1 0 0] = skewX - else if (pt.at(0) == 1 && pt.at(1) == 0 && pt.at(3) == 1 && pt.at(4) == 0 && pt.at(5) == 0) { - transform = QString("skewX(%1)") - .arg(Tools::roundNumber(atan(pt.at(2))*(180/M_PI), Tools::TRANSFORM)); - if (transform == "skewX(0)") - transform.clear(); - } // [1 tan(a) 0 1 0 0] = skewY - else if (pt.at(0) == 1 && pt.at(2) == 0 && pt.at(3) == 1 && pt.at(4) == 0 && pt.at(5) == 0) { - transform = QString("skewY(%1)") - .arg(Tools::roundNumber(atan(pt.at(1))*(180/M_PI), Tools::TRANSFORM)); - if (transform == "skewY(0)") - transform.clear(); - } else { - transform = "matrix("; - for (int i = 0; i < pt.count(); ++i) - transform += Tools::roundNumber(pt.at(i), Tools::TRANSFORM) + " "; - transform.chop(1); - transform += ")"; - if (transform == "matrix(0 0 0 0 0 0)") - transform.clear(); - } - return transform; -} - // http://www.w3.org/TR/SVG/coords.html#EstablishingANewUserSpace -QList Transform::mergeMatrixes(const QString &text) +QList Transform::mergeMatrixes(QString text) { - QStringList transList = text.split(QRegExp("\\) "), QString::SkipEmptyParts); + text.remove(QRegExp("^ +| +$")); + QStringList transList = text.split(QRegExp("\\) +"), QString::SkipEmptyParts); QList transMatrixList; for (int i = 0; i < transList.count(); ++i) { @@ -135,13 +82,18 @@ QList Transform::mergeMatrixes(const QString &text) Q_ASSERT(ok == true); } + // transform rotate( [ ]) to + // translate(, ) rotate() translate(-, -) + if (transformType == "rotate" && points.count() == 3) { + return mergeMatrixes(QString("translate(%1 %2) rotate(%3) translate(-%1 -%2)") + .arg(points.at(0)).arg(points.at(1)).arg(points.at(2))); + } + TransMatrix matrix; matrix(0,0) = 1; matrix(1,1) = 1; matrix(2,2) = 1; - // TODO: rotate( [ ]) - if (transformType == "matrix") { matrix(0,0) = points.at(0); matrix(0,1) = points.at(2); @@ -171,8 +123,8 @@ QList Transform::mergeMatrixes(const QString &text) } else if (transformType == "skewY") { matrix(1,0) = tan(points.at(0)); } else { - qDebug() << text; - qDebug() << "wrong trans"; + qDebug() << transformType; + qDebug() << "Error: wrong transform matrix"; exit(0); } transMatrixList << matrix; @@ -188,23 +140,208 @@ QList Transform::mergeMatrixes(const QString &text) return pointList; } +QString Transform::simplified() const +{ + if (m_points.isEmpty()) + return ""; + + QString transform; + QList pt = m_points; + + // [1 0 0 1 tx ty] = translate + if (pt.at(0) == 1 && pt.at(1) == 0 && pt.at(2) == 0 && pt.at(3) == 1) { + if (pt.at(5) != 0) { + transform = QString("translate(%1 %2)") + .arg(Tools::roundNumber(pt.at(4), Tools::TRANSFORM), + Tools::roundNumber(pt.at(5), Tools::TRANSFORM)); + + } else if (pt.at(4) != 0) { + transform = QString("translate(%1)") + .arg(Tools::roundNumber(pt.at(4), Tools::TRANSFORM)); + } + } // [sx 0 0 sy 0 0] = scale + else if (pt.at(1) == 0 && pt.at(2) == 0 && pt.at(4) == 0 && pt.at(5) == 0) { + if (pt.at(0) != pt.at(3)) { + transform = QString("scale(%1 %2)") + .arg(Tools::roundNumber(pt.at(0), Tools::TRANSFORM), + Tools::roundNumber(pt.at(3), Tools::TRANSFORM)); + } else { + transform = QString("scale(%1)") + .arg(Tools::roundNumber(pt.at(0), Tools::TRANSFORM)); + } + } // [cos(a) sin(a) -sin(a) cos(a) 0 0] = rotate + else if (pt.at(0) == pt.at(3) && pt.at(1) > 0 && pt.at(2) < 0 && pt.at(4) == 0 && pt.at(5) == 0) { + transform = QString("rotate(%1)") + .arg(Tools::roundNumber(acos(pt.at(0))*(180/M_PI), Tools::TRANSFORM)); + if (transform == "rotate(0)") + transform.clear(); + } // [1 0 tan(a) 1 0 0] = skewX + else if (pt.at(0) == 1 && pt.at(1) == 0 && pt.at(3) == 1 && pt.at(4) == 0 && pt.at(5) == 0) { + transform = QString("skewX(%1)") + .arg(Tools::roundNumber(atan(pt.at(2))*(180/M_PI), Tools::TRANSFORM)); + if (transform == "skewX(0)") + transform.clear(); + } // [1 tan(a) 0 1 0 0] = skewY + else if (pt.at(0) == 1 && pt.at(2) == 0 && pt.at(3) == 1 && pt.at(4) == 0 && pt.at(5) == 0) { + transform = QString("skewY(%1)") + .arg(Tools::roundNumber(atan(pt.at(1))*(180/M_PI), Tools::TRANSFORM)); + if (transform == "skewY(0)") + transform.clear(); + } else { + transform = "matrix("; + for (int i = 0; i < pt.count(); ++i) + transform += Tools::roundNumber(pt.at(i), Tools::TRANSFORM) + " "; + transform.chop(1); + transform += ")"; + if (transform == "matrix(0 0 0 0 0 0)") + transform.clear(); + } + return transform; +} + +qreal Transform::scaleFactor() +{ + return m_xScale * m_yScale; +} + // New class -Tools::Tools() +QList SvgElement::childElemList() +{ + return Tools::childElemList(this->toElement()); +} + +QList SvgElement::childNodeList() +{ + return Tools::childNodeList(this->toElement()); +} + +bool SvgElement::isReferenced() +{ + return Props::referencedElements.contains(tagName()); +} + +bool SvgElement::isText() const +{ + return Props::textElements.contains(tagName()); +} + +bool SvgElement::isContainer() const +{ + return Props::containers.contains(tagName()); +} + +bool SvgElement::isGroup() const +{ + return (tagName() == "g"); +} + + +bool SvgElement::hasAttributeByList(const QStringList &list) +{ + for (int i = 0; i < list.count(); ++i) + if (hasAttribute(list.at(i))) + return true; + return false; +} + +bool SvgElement::hasAttributeByList(const QSet &list) +{ + foreach (const QString &attr, list) { + if (hasAttribute(attr)) + return true; + } + return false; +} + +bool SvgElement::hasAttributes(const QStringList &list) +{ + for (int i = 0; i < list.count(); ++i) + if (!hasAttribute(list.at(i))) + return false; + return true; +} + +bool SvgElement::isStyleContains(const QString &text) +{ + if (hasAttribute("style")) + if (attribute("style").contains(text)) + return true; + return false; +} + +void SvgElement::removeAttributes(const QStringList &list) { + for (int i = 0; i < list.count(); ++i) + removeAttribute(list.at(i)); } -// TODO: speedup! +QStringList SvgElement::attributesList() +{ + QDomNamedNodeMap attrMap = attributes(); + QStringList list; + for (int i = 0; i < attrMap.count(); ++i) + list << attrMap.item(i).toAttr().name(); + return list; +} + +StringHash SvgElement::styleHash() +{ + return Tools::splitStyle(attribute("style")); +} + +void SvgElement::setStyle(const QString &text) +{ + setAttribute("style", text); +} + +void SvgElement::appendStyle(const QString &text) +{ + if (hasAttribute("style")) { + StringHash hash = styleHash(); + QString param = QString(text).remove(QRegExp(":.*")); + if (!hash.contains(param)) + setStyle(attribute("style") + ";" + text); + else { + hash.remove(param); + if (hash.isEmpty()) + setStyle(text); + else + setStyle(Tools::styleHashToString(hash) + ";" + text); + } + } else + setStyle(text); +} + +void SvgElement::setAttribute(const QString &name, const QVariant &value) +{ + if (value.toString().isEmpty()) + removeAttribute(name); + else + QDomElement::setAttribute(name, value.toString()); +} + +QString SvgElement::style() const +{ + return attribute("style"); +} + +QString SvgElement::id() const +{ + return attribute("id"); +} + +// New class + QString Tools::roundNumber(qreal value, RoundType type) { + // check is number is integer double fractpart, intpart; fractpart = modf(value, &intpart); if (fractpart == 0) return QString::number(value); - // TODO: to global int precision; - if (type == TRANSFORM) precision = Keys::get().intNumber(Key::TransformPrecision); else if (type == ATTRIBUTES) @@ -215,21 +352,26 @@ QString Tools::roundNumber(qreal value, RoundType type) QString text; text = QString::number(value, 'f', precision); - if (text.contains(".")) { - // 1.100 -> 1.1 - while (text.at(text.count()-1) == '0') - text.remove(text.count()-1, 1); - // 1. -> 1 - if (text.at(text.count()-1) == '.') - text.remove(text.count()-1, 1); - - // 0.1 -> .1 - if (text.mid(0, 2) == "0.") - text.remove(0, 1); - // -0.1 -> -.1 - else if (text.mid(0, 3) == "-0.") - text.remove(1, 1); + // 1.100 -> 1.1 + while (text.at(text.count()-1) == '0') + text.chop(1); + // 1. -> 1 + if (text.at(text.count()-1) == '.') { + text.chop(1); + // already integer + return text; } + // 3.00001 -> 3 + // TODO: finish +// if (text.contains(QString(".").leftJustified(precision, '0'))) +// return QString::number(intpart); + + // 0.1 -> .1 + if (text.mid(0, 2) == "0.") + text.remove(0, 1); + // -0.1 -> -.1 + else if (text.mid(0, 3) == "-0.") + text.remove(1, 1); if (text.contains("e")) text.replace("e-0", "e-"); @@ -457,7 +599,7 @@ QVariantHash Tools::initDefaultStyleHash() hash.insert("writing-mode", "lr-tb"); hash.insert("glyph-orientation-vertical", "auto"); hash.insert("glyph-orientation-horizontal", "0deg"); - hash.insert("direction", "normal"); + hash.insert("direction", "ltr"); hash.insert("text-anchor", "start"); hash.insert("dominant-baseline", "auto"); hash.insert("alignment-baseline", "auto"); @@ -488,46 +630,54 @@ QVariantHash Tools::initDefaultStyleHash() hash.insertMulti("stop-color", "#000000"); hash.insertMulti("stop-color", "#000"); hash.insertMulti("stop-color", "black"); + hash.insert("block-progression", "tb"); return hash; } -QDomNode Tools::findDefsNode(const QDomNode SvgElement) +QSet Tools::usedElemList(const SvgElement &svgNode) { - QDomNodeList tmpList = SvgElement.childNodes(); - for (int i = 0; i < tmpList.count(); ++i) { - if (tmpList.at(i).toElement().tagName() == "defs") { - return tmpList.at(i); + Q_ASSERT(svgNode.tagName() == "svg"); + + QSet usedList; + QList nodeList = Tools::childElemList(svgNode); + while (!nodeList.empty()) { + SvgElement currElem = nodeList.takeFirst(); + if (currElem.tagName() == "use") { + if (currElem.hasAttribute("xlink:href")) + usedList << currElem.attribute("xlink:href").remove("#"); } + if (currElem.hasChildNodes()) + nodeList << currElem.childElemList(); } - return QDomNode(); + return usedList; } -QList SvgElement::childElemList() -{ - QList outList; - QDomNodeList tmpList = childNodes(); - for (int i = 0; i < tmpList.count(); ++i) - outList << tmpList.at(i).toElement(); - return outList; -} - -QList SvgElement::childNodeList() -{ - QList outList; - QDomNodeList tmpList = childNodes(); - for (int i = 0; i < tmpList.count(); ++i) - outList << tmpList.at(i); - return outList; -} - -bool SvgElement::isReferenced() +QRectF Tools::viewBoxRect(const SvgElement &svgNode) { - return Props::referencedElements.contains(tagName()); + Q_ASSERT(svgNode.tagName() == "svg"); + QRectF rect; + if (svgNode.hasAttribute("viewBox")) { + QStringList list = svgNode.attribute("viewBox").split(" "); + rect.setRect(list.at(0).toDouble(), list.at(1).toDouble(), + list.at(2).toDouble(), list.at(3).toDouble()); + } else if (svgNode.hasAttribute("width") && svgNode.hasAttribute("height")) { + rect.setRect(0, 0, svgNode.attribute("width").toDouble(), + svgNode.attribute("height").toDouble()); + } else { + qDebug() << "Warning: can not detect viewBox"; + } + return rect; } -bool SvgElement::isText() const +QDomNode Tools::findDefsNode(const QDomNode SvgElement) { - return Props::textElements.contains(tagName()); + QDomNodeList tmpList = SvgElement.childNodes(); + for (int i = 0; i < tmpList.count(); ++i) { + if (tmpList.at(i).toElement().tagName() == "defs") { + return tmpList.at(i); + } + } + return QDomNode(); } QList Tools::childNodeList(QDomNode node) @@ -543,8 +693,10 @@ QList Tools::childElemList(QDomNode node) { QList outList; QDomNodeList tmpList = node.childNodes(); - for (int i = 0; i < tmpList.count(); ++i) - outList << tmpList.at(i).toElement(); + for (int i = 0; i < tmpList.count(); ++i) { + if (tmpList.at(i).isElement()) + outList << tmpList.at(i).toElement(); + } return outList; } @@ -578,59 +730,6 @@ bool Tools::isAttrEqual(QDomElement elem1, QDomElement elem2, const QSet &list) -{ - foreach (const QString &attr, list) { - if (hasAttribute(attr)) - return true; - } - return false; -} - -bool SvgElement::hasAttributes(const QStringList &list) -{ - for (int i = 0; i < list.count(); ++i) - if (!hasAttribute(list.at(i))) - return false; - return true; -} - -bool SvgElement::isStyleContains(const QString &text) -{ - if (hasAttribute("style")) - if (attribute("style").contains(text)) - return true; - return false; -} - -void SvgElement::removeAttributes(const QStringList &list) -{ - for (int i = 0; i < list.count(); ++i) - removeAttribute(list.at(i)); -} - -QStringList SvgElement::attributesList() -{ - QDomNamedNodeMap attrMap = attributes(); - QStringList list; - for (int i = 0; i < attrMap.count(); ++i) - list << attrMap.item(i).toAttr().name(); - return list; -} - -StringHash SvgElement::styleHash() -{ - return Tools::splitStyle(attribute("style")); -} - QString Tools::convertUnitsToPx(const QString &text, qreal baseValue) { if (!text.contains(QRegExp(RegEx::lengthTypes))) diff --git a/src/cli/tools.h b/src/cli/tools.h index 1efe02bc..471493e4 100644 --- a/src/cli/tools.h +++ b/src/cli/tools.h @@ -1,24 +1,25 @@ #ifndef TOOLS_H #define TOOLS_H -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include +// NOTE: only one dependence from gui +#include typedef QMap StringMap; typedef QHash StringHash; typedef QGenericMatrix<3,3,qreal> TransMatrix; namespace RegEx { -static const QString lengthTypes = "em|ex|px|in|cm|mm|pt|pc|%"; -static const QRegExp xlinkUrl = QRegExp(".*url\\(#|\\).*"); + static const QString lengthTypes = "em|ex|px|in|cm|mm|pt|pc|%"; + static const QRegExp xlinkUrl = QRegExp(".*url\\(#|\\).*"); } - class SvgElement : public QDomElement { public: @@ -35,7 +36,14 @@ class SvgElement : public QDomElement QList childNodeList(); bool isReferenced(); bool isText() const; + bool isContainer() const; + bool isGroup() const; StringHash styleHash(); + void setStyle(const QString &text); + void appendStyle(const QString &text); + void setAttribute(const QString &name, const QVariant &value); + QString style() const; + QString id() const; }; class Transform @@ -45,7 +53,8 @@ class Transform void setOldXY(qreal prevX, qreal prevY); qreal newX(); qreal newY(); - QString simplified(); + QString simplified() const; + qreal scaleFactor(); private: QList m_points; @@ -59,13 +68,13 @@ class Transform qreal m_xMove; qreal m_yMove; - QList mergeMatrixes(const QString &text); + QList mergeMatrixes(QString text); }; class Tools { public: - explicit Tools(); + explicit Tools() {} enum RoundType { COORDINATES, TRANSFORM, ATTRIBUTES }; static bool isAttrEqual(QDomElement elem1, QDomElement node2, const QSet &atrr); static QDomNode findDefsNode(const QDomNode SvgElement); @@ -79,9 +88,13 @@ class Tools static StringHash splitStyle(QString style); static void sortNodes(QList *nodeList); static QVariantHash initDefaultStyleHash(); + static QSet usedElemList(const SvgElement &svgNode); + static QRectF viewBoxRect(const SvgElement &svgNode); }; // TODO: add percentages attr list +// TODO: move all this to SVGElement class +// TODO: sort in order of max using namespace Props { static const QSet fillList = QSet() << "fill" << "fill-rule" << "fill-opacity"; static const QSet strokeList = QSet() @@ -118,7 +131,8 @@ static const QSet digitList = QSet() << "fx" << "fy" << "cx" << "cy" << "offset"; static const QSet filterDigitList = QSet() - << "stdDeviation" << "baseFrequency" << "k" << "k1" << "k2" << "k3" << "specularConstant"; + << "stdDeviation" << "baseFrequency" << "k" << "k1" << "k2" << "k3" << "specularConstant" + << "dx" << "dy"; static const QSet defsList = QSet() << "altGlyphDef" << "clipPath" << "cursor" << "filter" << "linearGradient" diff --git a/src/gui/cleanerthread.cpp b/src/gui/cleanerthread.cpp index e861cea0..7f8fed2f 100755 --- a/src/gui/cleanerthread.cpp +++ b/src/gui/cleanerthread.cpp @@ -127,6 +127,8 @@ SVGInfo CleanerThread::info() info.errString = tr("Output folder does not exist."); else if (scriptOutput.contains("Error: it's a not well-formed SVG file.")) info.errString = tr("It's a not well-formed SVG file."); + else + info.errString = tr("Crashed"); } return info; diff --git a/src/gui/svgcleaner-gui.pro b/src/gui/gui.pro similarity index 90% rename from src/gui/svgcleaner-gui.pro rename to src/gui/gui.pro index 9599d330..f8724917 100644 --- a/src/gui/svgcleaner-gui.pro +++ b/src/gui/gui.pro @@ -1,8 +1,9 @@ QT += core gui svg TEMPLATE = app -unix:TARGET = svgcleaner-gui -windows:TARGET = SVGCleaner -mac:TARGET = SVGCleaner +DESTDIR =../../bin +TARGET = SVGCleaner +unix:TARGET = svgcleaner-gui +mac:TARGET = SVGCleaner SOURCES += main.cpp \ thumbwidget.cpp \ diff --git a/src/gui/mainwindow.cpp b/src/gui/mainwindow.cpp index 198f7546..5baf9894 100755 --- a/src/gui/mainwindow.cpp +++ b/src/gui/mainwindow.cpp @@ -71,7 +71,6 @@ void MainWindow::on_actionWizard_triggered() WizardDialog wizard; if (wizard.exec()) { arguments = wizard.threadArguments(); - qDebug() << arguments.args; actionStart->setEnabled(true); } } @@ -186,7 +185,7 @@ void MainWindow::startNext() CleanerThread *cleaner = qobject_cast(sender()); if (position < arguments.inputFiles.count() && actionPause->isVisible() && actionStop->isEnabled()) { - QMetaObject::invokeMethod(cleaner, "startNext", + QMetaObject::invokeMethod(cleaner, "startNext", Qt::QueuedConnection, Q_ARG(QString, arguments.inputFiles.at(position)), Q_ARG(QString, arguments.outputFiles.at(position))); position++; diff --git a/src/gui/someutils.cpp b/src/gui/someutils.cpp index 30fd00e5..c788a434 100755 --- a/src/gui/someutils.cpp +++ b/src/gui/someutils.cpp @@ -1,4 +1,5 @@ #include +#include #include #include "someutils.h" @@ -34,13 +35,30 @@ QString SomeUtils::prepareTime(const float ms) return timeStr.remove(QRegExp("^(00. )*")); } -QString SomeUtils::findFile(const QString &name, const QString &defaultFolder) +QString SomeUtils::findBinFile(const QString &name) { - if (QFile( "../SVGCleaner/" + name).exists()) // Qt Creator shadow build - return "../SVGCleaner/" + name; - else if (QFile(name).exists()) // next to exe. Usual build/Windows. - return name; - else if (QFile(defaultFolder + name).exists()) // custom path - return defaultFolder + name; - return name; + QStringList names; + names << name << name + ".exe"; + foreach (const QString &name, names) { + // next to GUI + if (QFile("./" + name).exists()) + return "./" + name; + // MacOS package + if (QFile(QApplication::applicationDirPath() + "/" + name).exists()) + return QApplication::applicationDirPath() + "/" + name; + // Linux default install + if (QFile("/usr/bin/" + name).exists()) + return "/usr/bin/" + name; + } + return ""; +} + +QString SomeUtils::genSearchFolderList() +{ + QString paths; + paths += QApplication::applicationDirPath() + "/\n"; +#ifdef Q_OS_LINUX + paths += "/usr/bin/\n"; +#endif + return paths; } diff --git a/src/gui/someutils.h b/src/gui/someutils.h index 4922ccc6..0ad39857 100755 --- a/src/gui/someutils.h +++ b/src/gui/someutils.h @@ -10,8 +10,9 @@ class SomeUtils : public QObject public: explicit SomeUtils(QObject *parent = 0); - static QString findFile(const QString &name, const QString &defaultFolder); + static QString findBinFile(const QString &name); static QString prepareSize(const float bytes); static QString prepareTime(const float ms); + static QString genSearchFolderList(); }; #endif // SOMEUTILS_H diff --git a/src/gui/wizarddialog.cpp b/src/gui/wizarddialog.cpp index dfcf9601..7164743d 100755 --- a/src/gui/wizarddialog.cpp +++ b/src/gui/wizarddialog.cpp @@ -21,6 +21,7 @@ WizardDialog::WizardDialog(QWidget *parent) : setupUi(this); loadSettings(); setupGUI(); + setupToolTips(); adjustSize(); } @@ -145,19 +146,28 @@ void WizardDialog::setupGUI() loadFiles(); } +void WizardDialog::setupToolTips() +{ + chBoxGroupByStyle->setToolTip(tr("For example") + ":\n\n(" + tr("before") + ")\n" + "\n\n" + "\n\n(" + tr("after") + ")\n" + "\n \n " + "\n \n"); +} + void WizardDialog::radioSelected() { frameOutDir->setVisible(radioBtn1->isChecked()); frameRename->setVisible(radioBtn2->isChecked()); lblOverwrite->setVisible(radioBtn3->isChecked()); QRadioButton *rBtn = static_cast(sender()); - settings->setValue("Wizard/Type",rBtn->accessibleName()); + settings->setValue("Wizard/Type", rBtn->accessibleName()); } void WizardDialog::createExample() { - lblExample->setText(tr("For example: ")+lineEditPrefix->text() - +tr("filename" )+lineEditSuffix->text()+".svg"); + lblExample->setText(tr("For example") + ": " + lineEditPrefix->text() + + tr("filename" ) + lineEditSuffix->text() + ".svg"); } void WizardDialog::loadPresets() @@ -223,16 +233,8 @@ ToThread WizardDialog::threadArguments() threadArgs.inputFiles = getInFiles(); threadArgs.outputFiles = genOutFiles(); threadArgs.compressLevel = compressValue(); -#ifdef Q_OS_WIN - threadArgs.cliPath = "svgcleaner-cli.exe"; - threadArgs.zipPath = "7za.exe"; -#elif defined (Q_OS_MAC) - threadArgs.cliPath = QApplication::applicationDirPath() + "/svgcleaner-cli"; - threadArgs.zipPath = QApplication::applicationDirPath() + "/7za"; -#else - threadArgs.cliPath = SomeUtils::findFile("svgcleaner-cli", "/usr/bin/"); - threadArgs.zipPath = SomeUtils::findFile("7z", "/usr/bin/"); -#endif + threadArgs.cliPath = SomeUtils::findBinFile("svgcleaner-cli"); + threadArgs.zipPath = SomeUtils::findBinFile("7za"); return threadArgs; } @@ -392,10 +394,16 @@ bool WizardDialog::checkForWarnings() } else if (fileList.isEmpty()) { createWarning(tr("An input folder did not contain any svg, svgz files.")); check = false; - // TODO: this - /*} else if (!checkFor("7za")) { - createWarning(tr("You have to install 7-Zip to use SVG Cleaner.")); - check = false; */ + } else if (SomeUtils::findBinFile("svgcleaner-cli").isEmpty()) { + QMessageBox::critical(this, tr("Error"), + tr("The 'svgcleaner-cli' executable is not found in these folders:\n") + + SomeUtils::genSearchFolderList()); + check = false; + } else if (SomeUtils::findBinFile("7za").isEmpty()) { + QMessageBox::warning(this, tr("Warning"), + tr("The '7za' executable is not found in these folders:\n") + + SomeUtils::genSearchFolderList() + "\n\n" + + tr("You can not handle the SVGZ files.")); } else if (QFileInfo(lineEditOutDir->text()).isDir() && !QFileInfo(lineEditOutDir->text()).isWritable()) { createWarning(tr("Selected output folder is not writable.")); @@ -406,7 +414,7 @@ bool WizardDialog::checkForWarnings() void WizardDialog::createWarning(const QString &text) { - QMessageBox::warning(this, tr("Warning"), text,QMessageBox::Ok); + QMessageBox::warning(this, tr("Warning"), text, QMessageBox::Ok); } void WizardDialog::on_linePresetName_textChanged(const QString &text) diff --git a/src/gui/wizarddialog.h b/src/gui/wizarddialog.h index 6f56ef0e..f7ea87e9 100755 --- a/src/gui/wizarddialog.h +++ b/src/gui/wizarddialog.h @@ -45,6 +45,7 @@ class WizardDialog : public QDialog, private Ui::WizardDialog void resetToDefault(); void saveSettings(); void setupGUI(); + void setupToolTips(); void deleteThreads(); private slots: diff --git a/src/gui/wizarddialog.ui b/src/gui/wizarddialog.ui index beea21d8..06d3ef31 100755 --- a/src/gui/wizarddialog.ui +++ b/src/gui/wizarddialog.ui @@ -44,7 +44,7 @@ - 0 + 2 @@ -690,7 +690,7 @@ 0 - 0 + -22 549 475 @@ -1000,7 +1000,7 @@ std-dev - 0.20 + 0.10 0.010000000000000 @@ -1012,7 +1012,7 @@ 0.010000000000000 - 0.200000000000000 + 0.100000000000000 @@ -1056,8 +1056,8 @@ 0 0 - 563 - 453 + 503 + 446 @@ -1339,6 +1339,19 @@ + + + + skip-style-group + + + Group elements by style properties + + + true + + + @@ -1380,7 +1393,7 @@ 0 0 598 - 439 + 225 @@ -1613,8 +1626,8 @@ 0 0 - 563 - 453 + 534 + 312 @@ -1934,8 +1947,8 @@ 0 0 - 563 - 453 + 360 + 29 diff --git a/svgcleaner.pro b/svgcleaner.pro new file mode 100644 index 00000000..c7c9fba9 --- /dev/null +++ b/svgcleaner.pro @@ -0,0 +1,29 @@ +TEMPLATE = subdirs +SUBDIRS = src/cli src/gui +CONFIG += ordered + +unix { + isEmpty (PREFIX):PREFIX = /usr + + INSTALLS += bin desktop logo presets translations + + desktop.path = $$PREFIX/share/applications + desktop.files += svgcleaner.desktop + + logo.path = $$PREFIX/share/icons/hicolor/scalable/apps + logo.files += icons/svgcleaner.svg + + presets.path = $$PREFIX/share/svgcleaner/presets + presets.files += presets/Soft.preset \ + presets/Normal.preset \ + presets/Optimal.preset + + translations.path = $$PREFIX/share/svgcleaner/translations + translations.files += bin/svgcleaner_cs.qm \ + bin/svgcleaner_ru.qm \ + bin/svgcleaner_uk.qm \ + bin/svgcleaner_de.qm + + bin.path = $$PREFIX/bin + bin.files += bin/svgcleaner-cli bin/svgcleaner-gui +} diff --git a/translations/svgcleaner_cs.ts b/translations/svgcleaner_cs.ts index 7b7853d3..969304b8 100644 --- a/translations/svgcleaner_cs.ts +++ b/translations/svgcleaner_cs.ts @@ -79,6 +79,7 @@ CleanerThread + Crashed Spadlo @@ -245,12 +246,12 @@ Zastavit úklid - + Compare view: on Srovnávací pohled: zapnuto - + Compare view: off Srovnávací pohled: vypnuto @@ -258,22 +259,22 @@ SomeUtils - + B B - + KiB KiB - + MiB MiB - + %1h %2m %3s %4ms %1h %2m %3s %4ms @@ -365,7 +366,6 @@ - For example: Například: @@ -445,7 +445,12 @@ Odstranit verzi jazyka SVG - + + Group elements by style properties + + + + Merge multiply matrices into one and simplify it Sloučit více matic do jedné a zjednodušit ji @@ -500,43 +505,43 @@ Odstranit odkazy (XLink), které ukazují na neexistující prvek - + Keep existing paths data Zachovat data existujících cest - - + + Remove unnecessary whitespace between commands and coordinates Odstranit nepotřebná prázdná místa mezi příkazy a souřadnicemi - + Convert cubic curve segments into shorthand equivalents when possible Převést části kubických křivek do obdoby zkratky, když je to možné - + Convert quadratic curve segments into shorthand equivalents when possible Převést části kvadratických křivek do obdoby zkratky, když je to možné - + Convert width/height into a viewBox when possible Převést šířku/výšku do viewBox, když je to možné - + Convert style properties into SVG attributes Převést vlastnosti stylů do vlastností SVG - + Convert colors to #RRGGBB format Převést barvy do formátu #RRGGBB - + Convert colors to #RGB format when possible Převést barvy do formátu #RGB, když je to možné @@ -546,17 +551,17 @@ Hledat soubory SVG v podsložkách - + Recalculate coordinates and remove transform attributes when possible Přepočítat souřadnice a odstranit vlastnosti proměny, když je to možné - + Sort elements by name inside the defs section Třídit prvky podle názvu v sekci vymezení - + The indentation for the pretty print style: Odsazení pro hezký tiskový styl: @@ -596,47 +601,47 @@ Odstranit vlastnosti ze jmenných prostorů pomocí následujících grafických editorů: - + Convert absolute paths into relative ones Převést absolutní cesty na relativní - + Remove empty segments Odstranit prázdné čáry a části křivek - + Convert lines into horizontal/vertical equivalents when possible Převést čáry na vodorovné/svislé obdoby, když je to možné - + Convert basic shapes into paths Převést základní tvary na cesty - + Round numbers to a given precision Zaokrouhlit čísla na zadanou přesnost - + inside transform attributes: Uvnitř vlastností proměny: - + inside coordinate attributes: Uvnitř vlastností souřadnic: - + inside other attributes: Uvnitř jiných vlastností: - + Convert straight curves into lines when possible Převést rovné křivky do čar, když je to možné @@ -657,7 +662,7 @@ - + filename Název souboru @@ -697,110 +702,149 @@ Uložit - + prefix Předpona - + suffix Přípona - + Presets Přednastavení - + Elements Prvky - + Attributes Vlastnosti - + Optimization Vyladění - + Output Výstup - + Warning! The original files will be destroyed! Varování! Původní soubory budou zničeny! - + You have to set a prefix or a suffix for this save method. Pro tento způsob ukládání musíte nastavit předponu nebo příponu. - + Paths Cesty - + Main Hlavní - + + + For example + + + + + before + + + + + after + + + + Select an input folder Vybrat vstupní složku - + Select an output folder Vybrat výstupní složku - + An input folder is not selected. Vstupní složka není vybrána. - + An output folder is not selected. Výstupní složka není vybrána. - + An input folder is not exist. Vstupní složka neexistuje. - + An input folder did not contain any svg, svgz files. Vstupní složka neobsahoval žádné soubory svg, svgz. - + + Error + + + + + The 'svgcleaner-cli' executable is not found in these folders: + + + + + + The '7za' executable is not found in these folders: + + + + + + You can not handle the SVGZ files. + + + + Selected output folder is not writable. Vybraná výstupní složka není zapisovatelná. - + You have to set preset name. Musíte nastavit název přednastavení. - + This preset already exists. Overwrite? Toto přednastavení již existuje. Přepsat? - - + + + Warning Varování diff --git a/translations/svgcleaner_de.ts b/translations/svgcleaner_de.ts index a4521e2f..34caec93 100644 --- a/translations/svgcleaner_de.ts +++ b/translations/svgcleaner_de.ts @@ -22,7 +22,7 @@ Generally, SVG files produced by vector editors contain a lot of unused elements and attributes that just blow up their size without providing better visible quality. - Für Gewöhnlich enthalten SVG-Dateien, die mit SVG-Editoren erstellt wurden, viele ungenutze Elemente und Attribute im Quelltext, die lediglich ihre Größe beeinflussen, nicht jedoch ihre Qualität. + Für gewöhnlich enthalten SVG-Dateien, die mit SVG-Editoren erstellt wurden, viele ungenutze Elemente und Attribute im Quelltext, die lediglich ihre Größe beeinflussen, nicht jedoch ihre Qualität. SVG Cleaner could help you to clean up your SVG files from unnecessary data. It has a lot of options for cleanup and optimization, works in batch mode, provides threaded processing on the multicore processors and basically does two things:<br />- removing elements and attributes that don't contribute to the final rendering;<br />- making those elements and attributes in use more compact. @@ -30,7 +30,7 @@ Images cleaned by SVG Cleaner are typically 10-60 percent smaller than the original ones. - In der regel schrumpfen mit SVG Cleaner optimierte Bilddateien auf 10-60% ihrer Ausgangsgröße. + In der Regel schrumpfen mit SVG Cleaner optimierte Bilddateien auf 10-60% ihrer Ausgangsgröße. Important! The internal image viewer in SVG Cleaner uses the QtSvg module for rendering SVG images. Qt supports only the static features of SVG 1.2 Tiny, and that imposes a number of restrictions on rendering of advanced features. For instance, elements such as clipPath, mask, filters etc. will not be rendered at all. @@ -38,42 +38,42 @@ We apologize for any bugs in advance. Please send bug reports to - Wir bitten etwaig auftretenden Programmfehler entschuldigen. Bitte senden Sie uns fehlerberichte an + Wir bitten etwaig auftretenden Programmfehler zu entschuldigen. Bitte senden Sie uns Fehlerberichte an Developers: - + Entwickler: Previous developers: - + Frühere Entwickler: Special thanks: - + Besonderer Dank: Logo design: - + Logo-Design: Translators: - + Übersetzer: CleanerThread Crashed - + Abgestürzt Input file does not exist. - + Die Eingabedatei existiert nicht. Output folder does not exist. - + Der Ausgabe-Ordner existiert nicht. It's a not well-formed SVG file. @@ -626,11 +626,11 @@ Overwrite? Remove processing instruction - + Entferne die Verarbeitungs-Angaben Remove XLinks which pointed to nonexistent element - + Entferne Xlinks, die auf nicht existierende Elemente zeigen Remove empty segments @@ -638,6 +638,40 @@ Overwrite? Merge multiply matrices into one and simplify it + Führe mehrfahe Matrizen in eine zusammen udn vereinfache sie + + + Group elements by style properties + + + + For example + + + + before + + + + after + + + + Error + + + + You can not handle the SVGZ files. + + + + The 'svgcleaner-cli' executable is not found in these folders: + + + + + The '7za' executable is not found in these folders: + diff --git a/translations/svgcleaner_ru.ts b/translations/svgcleaner_ru.ts index 287918c0..63df56e0 100644 --- a/translations/svgcleaner_ru.ts +++ b/translations/svgcleaner_ru.ts @@ -79,6 +79,7 @@ CleanerThread + Crashed Очистка завершилась неудачно @@ -245,12 +246,12 @@ Stop cleaning - + Compare view: on Сравнение изображений: включено - + Compare view: off Сравнение изображений: выключено @@ -258,22 +259,22 @@ SomeUtils - + B Б - + KiB КиБ - + MiB МиБ - + %1h %2m %3s %4ms %1ч %2м %3с %4мс @@ -375,13 +376,12 @@ - + filename filename - For example: Например: @@ -496,12 +496,12 @@ Удалять элементы из пространств имен графических редакторов: - + Merge multiply matrices into one and simplify it Объединять матрицы трансформаций и упрощать их - + Remove empty segments Удалять пустые сегменты @@ -601,204 +601,250 @@ Удалять XLinks, которые указывают на несуществующие елементы - + + Group elements by style properties + Группировать элементы с подобными стилями + + + Keep existing paths data Оставлять существующие данные путей - - + + Remove unnecessary whitespace between commands and coordinates Удалять лишние пробелы между командами и координатами - + Convert absolute paths into relative ones Преобразовывать абсолютные пути в относительные - + Convert lines into horizontal/vertical equivalents when possible Преобразовывать линии в короткие гор./верт. эквиваленты - + Convert straight curves into lines when possible Преобразовывать прямые кривые в линии - + Convert cubic curve segments into shorthand equivalents when possible Преобразовывать сегменты кубических кривых в короткие эквиваленты - + Convert quadratic curve segments into shorthand equivalents when possible Преобразовывать сегменты квадратичных кривых в короткие эквиваленты - + Convert width/height into a viewBox when possible Преобразовывать атрибуты width и height во viewBox - + Convert style properties into SVG attributes Преобразовывать свойства стилей в атрибуты SVG - + Convert colors to #RRGGBB format Преобразовывать цвета в формат #RRGGBB - + Convert colors to #RGB format when possible Преобразовывать цвета в формат #RGB, когда возможно - + Convert basic shapes into paths Преобразовывать основные фигуры в пути - + Recalculate coordinates and remove transform attributes when possible Перерасчитывать координаты и удалять атрибуты трансформации - + Sort elements by name inside the defs section Сортировать по имени элементы из секции defs - + Round numbers to a given precision Округлять числа до заданной точности - + inside transform attributes: внутри атрибутов трансформации: - + inside coordinate attributes: внутри атрибутов координат: - + inside other attributes: внутри остальных атрибутов: - + The indentation for the pretty print style: Размер отступа в структуре данных: - + prefix префикс - + suffix суффикс - + Main Главная - + Presets Пресеты - + Elements Элементы - + Attributes Атрибуты - + Paths Пути - + Optimization Оптимизация - + Output Вывод - + Warning! The original files will be destroyed! Внимание! Исходные файлы будут уничтожены! - + + + For example + Пример + + + + before + до + + + + after + после + + + Select an input folder Выберите исходную папку - + Select an output folder Выберите итоговую папку - + An input folder is not selected. Не выбрана исходная папка. - + An output folder is not selected. Не выбрана итоговая папка. - + You have to set a prefix or a suffix for this save method. Необходимо установить префикс или суффикс. - + An input folder is not exist. Исходная папка не существует. - + An input folder did not contain any svg, svgz files. Исходная папка не содержит файлов svg и svgz. - + + Error + Ошибка + + + + You can not handle the SVGZ files. + Вы не сможете обрабатывать SVGZ файлы. + + + Selected output folder is not writable. Выбранная итоговая папка недоступна для записи. - - + + + Warning Предупреждение - + + The 'svgcleaner-cli' executable is not found in these folders: + + Исполняемый файл 'svgcleaner-cli' не найден в данных директориях: + + + + + The '7za' executable is not found in these folders: + + Исполняемый файл '7za' не найден в данных директориях: + + + + You have to set preset name. Необходимо задать имя пресета. - + This preset already exists. Overwrite? Данный пресет уже существует. Перезаписать? diff --git a/translations/svgcleaner_uk.ts b/translations/svgcleaner_uk.ts index 8d316387..b2caa9de 100644 --- a/translations/svgcleaner_uk.ts +++ b/translations/svgcleaner_uk.ts @@ -80,6 +80,7 @@ CleanerThread + Crashed Не підлягає обробці @@ -246,12 +247,12 @@ Завершити обробку - + Compare view: on Порівняння зображень: увімкнено - + Compare view: off Порівняння зображень: вимкнено @@ -259,22 +260,22 @@ SomeUtils - + B Б - + KiB КіБ - + MiB МіБ - + %1h %2m %3s %4ms %1г %2х %3с %4мс @@ -376,13 +377,12 @@ - + filename filename - For example: Наприклад: @@ -497,12 +497,12 @@ Видаляти елементи із просторів імен графічних редакторів: - + Merge multiply matrices into one and simplify it Об'єднувати декілька матриць трансформацій в одну та спрощувати її - + Remove empty segments Видаляти порожні сегменти ліній і кривих @@ -602,204 +602,250 @@ Видаляти XLinks, які вказують на неіснуючий елемент - + + Group elements by style properties + Згрупувати елементи з подібними стилями + + + Keep existing paths data Залишати існуючи дані шляхів - - + + Remove unnecessary whitespace between commands and coordinates Видаляти зайві пробіли між командами і координатами - + Convert absolute paths into relative ones Конвертувати абсолютні шляхи у відносні - + Convert lines into horizontal/vertical equivalents when possible Конвертувати лінії у короткі гор./верт. еквіваленти - + Convert straight curves into lines when possible Конвертувати прямі криві у лінії - + Convert cubic curve segments into shorthand equivalents when possible Конвертувати сегменти кубічних кривих у короткі еквіваленти - + Convert quadratic curve segments into shorthand equivalents when possible Конвертувати сегменти квадратичних кривих у короткі еквіваленти - + Convert width/height into a viewBox when possible Конвертувати атрибути width і height у viewBox - + Convert style properties into SVG attributes Конвертувати властивості стилів в атрибути SVG - + Convert colors to #RRGGBB format Конвертувати кольори у формат #RRGGBB - + Convert colors to #RGB format when possible Конвертувати кольори у формат #RGB, якщо можливо - + Convert basic shapes into paths Конвертувати основні фігури у шляхи - + Recalculate coordinates and remove transform attributes when possible Перераховувати координати і видаляти атрибути трансформації - + Sort elements by name inside the defs section Сортувати за ім'ям елементи із секції defs - + Round numbers to a given precision Округляти числа до заданої точності - + inside transform attributes: всередині атрибутів трансформації: - + inside coordinate attributes: всередині атрибутів координат: - + inside other attributes: всередині інших атрибутів: - + The indentation for the pretty print style: Розмір відступу у структурі даних: - + prefix префікс - + suffix суфікс - + Main Головна - + Presets Передустановки - + Elements Елементи - + Attributes Атрибути - + Paths Шляхи - + Optimization Оптимізація - + Output Вихідний формат - + Warning! The original files will be destroyed! Увага! Оригінальні файли будуть знищені! - + + + For example + Наприклад + + + + before + до + + + + after + після + + + Select an input folder Обрати вхідну папку - + Select an output folder Обрати вихідну папку - + An input folder is not selected. Не обрано вхідну папку. - + An output folder is not selected. Не обрано вихідну папку. - + You have to set a prefix or a suffix for this save method. Необхідно задати додаваємий префікс та/або суфікс. - + An input folder is not exist. Задана вхідна папка не існує. - + An input folder did not contain any svg, svgz files. Вхідна папка не містить жодних SVG-файлів. - + + Error + Помилка + + + + You can not handle the SVGZ files. + Ви не зможете обробляти SVGZ файли. + + + Selected output folder is not writable. Обрана вихідна папка недоступна для запису. - - + + + Warning Застереження - + + The 'svgcleaner-cli' executable is not found in these folders: + + Файл 'svgcleaner-cli' не знайден у цих папках: + + + + + The '7za' executable is not found in these folders: + + Файл '7za' не знайден у цих папках: + + + + You have to set preset name. Потрібно задати ім'я передустановки. - + This preset already exists. Overwrite? Ця передустановка вже існує. diff --git a/translations/translations.pri b/translations/translations.pri index 1fe74d11..27532a8f 100644 --- a/translations/translations.pri +++ b/translations/translations.pri @@ -1,16 +1,26 @@ -TRANSLATIONS += ../../translations/svgcleaner_cs.ts \ - ../../translations/svgcleaner_ru.ts \ - ../../translations/svgcleaner_uk.ts \ - ../../translations/svgcleaner_de.ts - -CODECFORTR = UTF-8 - isEmpty(QMAKE_LRELEASE) { !exists($$QMAKE_LRELEASE) { QMAKE_LRELEASE = lrelease } } +unix { + # fix for ArchLinux + DEP_CHECK = $$system(which lrelease) + contains($$DEP_CHECK, "no lrelease in") { + QMAKE_LRELEASE = lrelease-qt4 + } +} + +TR_PATH=../../translations + +TRANSLATIONS += $$TR_PATH/svgcleaner_cs.ts \ + $$TR_PATH/svgcleaner_ru.ts \ + $$TR_PATH/svgcleaner_uk.ts \ + $$TR_PATH/svgcleaner_de.ts + +CODECFORTR = UTF-8 + updateqm.input = TRANSLATIONS updateqm.output = ${QMAKE_FILE_BASE}.qm -updateqm.commands = $$QMAKE_LRELEASE -silent ${QMAKE_FILE_IN} -qm ${QMAKE_FILE_BASE}.qm +updateqm.commands = $$QMAKE_LRELEASE -silent ${QMAKE_FILE_IN} -qm $$DESTDIR/${QMAKE_FILE_BASE}.qm updateqm.CONFIG += no_link target_predeps QMAKE_EXTRA_COMPILERS += updateqm