summaryrefslogtreecommitdiff
path: root/tools/viewsvg
diff options
context:
space:
mode:
authorRazrFalcon <razrfalcon@gmail.com>2019-01-03 14:10:12 +0200
committerRazrFalcon <razrfalcon@gmail.com>2019-01-03 14:10:12 +0200
commitcf365efd4ab280226f6b2f388f1620ea88f72d1d (patch)
tree758bf4e1acaaecab37a7f90120129d179c486bb6 /tools/viewsvg
parent3afab96590fe632c7912b3748c37d25e47c2db3d (diff)
Replace examples/qt-demo with tools/viewsvg.
Diffstat (limited to 'tools/viewsvg')
-rw-r--r--tools/viewsvg/README.md21
-rw-r--r--tools/viewsvg/main.cpp15
-rw-r--r--tools/viewsvg/mainwindow.cpp59
-rw-r--r--tools/viewsvg/mainwindow.h23
-rw-r--r--tools/viewsvg/mainwindow.ui193
-rw-r--r--tools/viewsvg/svgview.cpp314
-rw-r--r--tools/viewsvg/svgview.h90
-rw-r--r--tools/viewsvg/viewsvg.pro23
8 files changed, 738 insertions, 0 deletions
diff --git a/tools/viewsvg/README.md b/tools/viewsvg/README.md
new file mode 100644
index 0000000..957f55b
--- /dev/null
+++ b/tools/viewsvg/README.md
@@ -0,0 +1,21 @@
+# viewsvg
+
+A simple SVG viewer using Qt and *resvg* C-API.
+
+## Dependencies
+
+- Qt >= 5.6
+
+## Run
+
+```bash
+# build C-API first
+cargo build --release --features "qt-backend" --manifest-path ../../capi/Cargo.toml
+# build viewsvg
+qmake
+make
+# run
+LD_LIBRARY_PATH=../../target/release ./viewsvg
+```
+
+See [BUILD.adoc](../../BUILD.adoc) for details.
diff --git a/tools/viewsvg/main.cpp b/tools/viewsvg/main.cpp
new file mode 100644
index 0000000..f900905
--- /dev/null
+++ b/tools/viewsvg/main.cpp
@@ -0,0 +1,15 @@
+#include <QApplication>
+
+#include "mainwindow.h"
+
+int main(int argc, char *argv[])
+{
+ QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
+
+ QApplication a(argc, argv);
+
+ MainWindow w;
+ w.show();
+
+ return a.exec();
+}
diff --git a/tools/viewsvg/mainwindow.cpp b/tools/viewsvg/mainwindow.cpp
new file mode 100644
index 0000000..10215b8
--- /dev/null
+++ b/tools/viewsvg/mainwindow.cpp
@@ -0,0 +1,59 @@
+#include <QMessageBox>
+#include <QTimer>
+#include <QFile>
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+
+MainWindow::MainWindow(QWidget *parent) :
+ QMainWindow(parent),
+ ui(new Ui::MainWindow)
+{
+ ui->setupUi(this);
+
+ SvgView::init();
+
+ ui->cmbBoxSize->setCurrentIndex(1);
+ ui->cmbBoxBackground->setCurrentIndex(1);
+
+ ui->svgView->setFitToView(true);
+ ui->svgView->setBackgound(SvgView::Backgound::White);
+
+ connect(ui->svgView, &SvgView::loadError, this, [this](const QString &msg){
+ QMessageBox::critical(this, "Error", msg);
+ });
+
+ QTimer::singleShot(5, this, &MainWindow::onStart);
+}
+
+MainWindow::~MainWindow()
+{
+ delete ui;
+}
+
+void MainWindow::onStart()
+{
+ ui->svgView->setFocus();
+
+ const auto args = QCoreApplication::arguments();
+ if (args.size() != 2) {
+ return;
+ }
+
+ ui->svgView->loadFile(args.at(1));
+}
+
+void MainWindow::on_cmbBoxSize_activated(int index)
+{
+ ui->svgView->setFitToView(index == 1);
+}
+
+void MainWindow::on_cmbBoxBackground_activated(int index)
+{
+ ui->svgView->setBackgound(SvgView::Backgound(index));
+}
+
+void MainWindow::on_chBoxDrawBorder_toggled(bool checked)
+{
+ ui->svgView->setDrawImageBorder(checked);
+}
diff --git a/tools/viewsvg/mainwindow.h b/tools/viewsvg/mainwindow.h
new file mode 100644
index 0000000..58eb4f6
--- /dev/null
+++ b/tools/viewsvg/mainwindow.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include <QMainWindow>
+
+namespace Ui { class MainWindow; }
+
+class MainWindow : public QMainWindow
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(QWidget *parent = nullptr);
+ ~MainWindow();
+
+private slots:
+ void onStart();
+ void on_cmbBoxBackground_activated(int index);
+ void on_chBoxDrawBorder_toggled(bool checked);
+ void on_cmbBoxSize_activated(int index);
+
+private:
+ Ui::MainWindow *ui;
+};
diff --git a/tools/viewsvg/mainwindow.ui b/tools/viewsvg/mainwindow.ui
new file mode 100644
index 0000000..b8a20a4
--- /dev/null
+++ b/tools/viewsvg/mainwindow.ui
@@ -0,0 +1,193 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainWindow</class>
+ <widget class="QMainWindow" name="MainWindow">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>787</width>
+ <height>424</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Demo</string>
+ </property>
+ <widget class="QWidget" name="centralWidget">
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="label_3">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Fixed" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Size:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="cmbBoxSize">
+ <item>
+ <property name="text">
+ <string>Original</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Fit to View</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QLabel" name="label_5">
+ <property name="text">
+ <string>Background:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="cmbBoxBackground">
+ <item>
+ <property name="text">
+ <string>None</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>White</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string>Check board</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>1</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chBoxDrawBorder">
+ <property name="text">
+ <string>Draw image border</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer_2">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>2</number>
+ </property>
+ <property name="topMargin">
+ <number>2</number>
+ </property>
+ <property name="rightMargin">
+ <number>2</number>
+ </property>
+ <property name="bottomMargin">
+ <number>2</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="SvgView" name="svgView">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="frameShape">
+ <enum>QFrame::StyledPanel</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>QFrame::Sunken</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ <layoutdefault spacing="6" margin="11"/>
+ <customwidgets>
+ <customwidget>
+ <class>SvgView</class>
+ <extends>QFrame</extends>
+ <header>svgview.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/tools/viewsvg/svgview.cpp b/tools/viewsvg/svgview.cpp
new file mode 100644
index 0000000..0fb844f
--- /dev/null
+++ b/tools/viewsvg/svgview.cpp
@@ -0,0 +1,314 @@
+#include <QDropEvent>
+#include <QFileInfo>
+#include <QMessageBox>
+#include <QMimeData>
+#include <QPainter>
+#include <QThread>
+#include <QTimer>
+
+#include "svgview.h"
+
+SvgViewWorker::SvgViewWorker(QObject *parent)
+ : QObject(parent)
+ , m_dpiRatio(qApp->screens().first()->devicePixelRatio())
+{
+}
+
+QRect SvgViewWorker::viewBox() const
+{
+ QMutexLocker lock(&m_mutex);
+ return m_renderer.viewBox();
+}
+
+QString SvgViewWorker::loadData(const QByteArray &data)
+{
+ QMutexLocker lock(&m_mutex);
+
+ m_renderer.load(data);
+ if (!m_renderer.isValid()) {
+ return m_renderer.errorString();
+ }
+
+ return QString();
+}
+
+QString SvgViewWorker::loadFile(const QString &path)
+{
+ QMutexLocker lock(&m_mutex);
+
+ m_renderer.load(path);
+ if (!m_renderer.isValid()) {
+ return m_renderer.errorString();
+ }
+
+ return QString();
+}
+
+void SvgViewWorker::render(const QSize &viewSize)
+{
+ Q_ASSERT(QThread::currentThread() != qApp->thread());
+
+ QMutexLocker lock(&m_mutex);
+
+ if (m_renderer.isEmpty()) {
+ return;
+ }
+
+ const auto s = m_renderer.defaultSize().scaled(viewSize, Qt::KeepAspectRatio);
+ QImage img(s * m_dpiRatio, QImage::Format_ARGB32_Premultiplied);
+ img.fill(Qt::transparent);
+
+ QPainter p;
+ p.begin(&img);
+ p.setRenderHint(QPainter::Antialiasing);
+ m_renderer.render(&p);
+ p.end();
+
+ img.setDevicePixelRatio(m_dpiRatio);
+
+ emit rendered(img);
+}
+
+static QImage genCheckedTexture()
+{
+ int l = 20;
+
+ QImage pix = QImage(l, l, QImage::Format_RGB32);
+ int b = pix.width() / 2.0;
+ pix.fill(QColor("#c0c0c0"));
+
+ QPainter p;
+ p.begin(&pix);
+ p.fillRect(QRect(0,0,b,b), QColor("#808080"));
+ p.fillRect(QRect(b,b,b,b), QColor("#808080"));
+ p.end();
+
+ return pix;
+}
+
+SvgView::SvgView(QWidget *parent)
+ : QFrame(parent)
+ , m_checkboardImg(genCheckedTexture())
+ , m_worker(new SvgViewWorker())
+ , m_resizeTimer(new QTimer(this))
+{
+ setAcceptDrops(true);
+ setMinimumSize(10, 10);
+
+ QThread *th = new QThread(this);
+ m_worker->moveToThread(th);
+ th->start();
+
+ const auto *screen = qApp->screens().first();
+ m_dpiRatio = screen->devicePixelRatio();
+
+ connect(m_worker, &SvgViewWorker::rendered, this, &SvgView::onRendered);
+
+ m_resizeTimer->setSingleShot(true);
+ connect(m_resizeTimer, &QTimer::timeout, this, &SvgView::requestUpdate);
+}
+
+SvgView::~SvgView()
+{
+ QThread *th = m_worker->thread();
+ th->quit();
+ th->wait(10000);
+ delete m_worker;
+}
+
+void SvgView::init()
+{
+ ResvgRenderer::initLog();
+}
+
+void SvgView::setFitToView(bool flag)
+{
+ m_isFitToView = flag;
+ requestUpdate();
+}
+
+void SvgView::setBackgound(SvgView::Backgound backgound)
+{
+ m_backgound = backgound;
+ update();
+}
+
+void SvgView::setDrawImageBorder(bool flag)
+{
+ m_isDrawImageBorder = flag;
+ update();
+}
+
+void SvgView::loadData(const QByteArray &ba)
+{
+ const QString errMsg = m_worker->loadData(ba);
+ afterLoad(errMsg);
+}
+
+void SvgView::loadFile(const QString &path)
+{
+ const QString errMsg = m_worker->loadFile(path);
+ afterLoad(errMsg);
+}
+
+void SvgView::afterLoad(const QString &errMsg)
+{
+ m_img = QImage();
+
+ if (errMsg.isEmpty()) {
+ m_isHasImage = true;
+ requestUpdate();
+ } else {
+ emit loadError(errMsg);
+ m_isHasImage = false;
+ update();
+ }
+}
+
+void SvgView::drawSpinner(QPainter &p)
+{
+ const int outerRadius = 20;
+ const int innerRadius = outerRadius * 0.45;
+
+ const int capsuleHeight = outerRadius - innerRadius;
+ const int capsuleWidth = capsuleHeight * 0.35;
+ const int capsuleRadius = capsuleWidth / 2;
+
+ for (int i = 0; i < 12; ++i) {
+ QColor color = Qt::black;
+ color.setAlphaF(1.0f - (i / 12.0f));
+ p.setRenderHint(QPainter::Antialiasing);
+ p.setPen(Qt::NoPen);
+ p.setBrush(color);
+ p.save();
+ p.translate(width()/2, height()/2);
+ p.rotate(m_angle - i * 30.0f);
+ p.drawRoundedRect(-capsuleWidth * 0.5, -(innerRadius + capsuleHeight), capsuleWidth,
+ capsuleHeight, capsuleRadius, capsuleRadius);
+ p.restore();
+ }
+}
+
+void SvgView::paintEvent(QPaintEvent *e)
+{
+ QPainter p(this);
+ const auto r = contentsRect();
+ p.setClipRect(r);
+
+ switch (m_backgound) {
+ case Backgound::None : break;
+ case Backgound::White : {
+ p.fillRect(contentsRect(), Qt::white);
+ } break;
+ case Backgound::CheckBoard : {
+ p.fillRect(contentsRect(), QBrush(m_checkboardImg));
+ } break;
+ }
+
+ if (m_img.isNull() && !m_timer.isActive()) {
+ p.setPen(Qt::black);
+ p.drawText(rect(), Qt::AlignCenter, "Drop an SVG image here.");
+ } else if (m_timer.isActive()) {
+ drawSpinner(p);
+ } else {
+ const QRect imgRect(0, 0, m_img.width() / m_dpiRatio, m_img.height() / m_dpiRatio);
+
+ p.translate(r.x() + (r.width() - imgRect.width())/ 2,
+ r.y() + (r.height() - imgRect.height()) / 2);
+
+ p.drawImage(0, 0, m_img);
+
+ if (m_isDrawImageBorder) {
+ p.setRenderHint(QPainter::Antialiasing, false);
+ p.setPen(Qt::green);
+ p.setBrush(Qt::NoBrush);
+ p.drawRect(imgRect);
+ }
+ }
+
+ QFrame::paintEvent(e);
+}
+
+void SvgView::dragEnterEvent(QDragEnterEvent *event)
+{
+ event->accept();
+}
+
+void SvgView::dragMoveEvent(QDragMoveEvent *event)
+{
+ event->accept();
+}
+
+void SvgView::dropEvent(QDropEvent *event)
+{
+ const QMimeData *mime = event->mimeData();
+ if (!mime->hasUrls()) {
+ event->ignore();
+ return;
+ }
+
+ for (const QUrl &url : mime->urls()) {
+ if (!url.isLocalFile()) {
+ continue;
+ }
+
+ QString path = url.toLocalFile();
+ QFileInfo fi = QFileInfo(path);
+
+ if (fi.isFile()) {
+ QString suffix = fi.suffix().toLower();
+ if (suffix == "svg" || suffix == "svgz") {
+ loadFile(path);
+ } else {
+ QMessageBox::warning(this, tr("Warning"),
+ tr("You can drop only SVG and SVGZ files."));
+ }
+ }
+ }
+
+ event->acceptProposedAction();
+}
+
+void SvgView::resizeEvent(QResizeEvent *)
+{
+ m_resizeTimer->start(200);
+}
+
+void SvgView::timerEvent(QTimerEvent *event)
+{
+ if (event->timerId() == m_timer.timerId()) {
+ m_angle = (m_angle + 30) % 360;
+ update();
+ } else {
+ QWidget::timerEvent(event);
+ }
+}
+
+void SvgView::requestUpdate()
+{
+ if (!m_isHasImage) {
+ return;
+ }
+
+ const auto s = m_isFitToView ? size() : m_worker->viewBox().size();
+
+ if (s * m_dpiRatio == m_img.size()) {
+ return;
+ }
+
+ m_timer.start(100, this);
+
+ // Run method in the m_worker thread scope.
+ QTimer::singleShot(1, m_worker, [=](){
+ m_worker->render(s);
+ });
+}
+
+void SvgView::onRendered(const QImage &img)
+{
+ m_timer.stop();
+
+ m_img = img;
+ update();
+}
+
diff --git a/tools/viewsvg/svgview.h b/tools/viewsvg/svgview.h
new file mode 100644
index 0000000..81bd5f7
--- /dev/null
+++ b/tools/viewsvg/svgview.h
@@ -0,0 +1,90 @@
+#pragma once
+
+#include <QFrame>
+#include <QMutex>
+#include <QBasicTimer>
+
+#include <ResvgQt.h>
+
+class SvgViewWorker : public QObject
+{
+ Q_OBJECT
+
+public:
+ SvgViewWorker(QObject *parent = nullptr);
+
+ QRect viewBox() const;
+
+public slots:
+ QString loadData(const QByteArray &data);
+ QString loadFile(const QString &path);
+ void render(const QSize &viewSize);
+
+signals:
+ void rendered(QImage);
+
+private:
+ const float m_dpiRatio;
+ mutable QMutex m_mutex;
+ ResvgRenderer m_renderer;
+};
+
+class SvgView : public QFrame
+{
+ Q_OBJECT
+
+public:
+ enum class Backgound
+ {
+ None,
+ White,
+ CheckBoard,
+ };
+
+ explicit SvgView(QWidget *parent = nullptr);
+ ~SvgView();
+
+ static void init();
+
+ void setFitToView(bool flag);
+ void setBackgound(Backgound backgound);
+ void setDrawImageBorder(bool flag);
+
+ void loadData(const QByteArray &data);
+ void loadFile(const QString &path);
+
+signals:
+ void loadError(QString);
+
+protected:
+ void paintEvent(QPaintEvent *);
+ void dragEnterEvent(QDragEnterEvent *event);
+ void dragMoveEvent(QDragMoveEvent *event);
+ void dropEvent(QDropEvent *event);
+ void resizeEvent(QResizeEvent *);
+ void timerEvent(QTimerEvent *);
+
+private:
+ void requestUpdate();
+ void afterLoad(const QString &errMsg);
+ void drawSpinner(QPainter &p);
+
+private slots:
+ void onRendered(const QImage &img);
+
+private:
+ const QImage m_checkboardImg;
+ SvgViewWorker * const m_worker;
+ QTimer * const m_resizeTimer;
+
+ QString m_path;
+ float m_dpiRatio = 1.0;
+ bool m_isFitToView = true;
+ Backgound m_backgound = Backgound::CheckBoard;
+ bool m_isDrawImageBorder = false;
+ bool m_isHasImage = false;
+ QImage m_img;
+
+ QBasicTimer m_timer;
+ int m_angle = 0;
+};
diff --git a/tools/viewsvg/viewsvg.pro b/tools/viewsvg/viewsvg.pro
new file mode 100644
index 0000000..a91bb66
--- /dev/null
+++ b/tools/viewsvg/viewsvg.pro
@@ -0,0 +1,23 @@
+QT += core gui widgets
+
+TARGET = viewsvg
+TEMPLATE = app
+CONFIG += C++11
+
+SOURCES += \
+ main.cpp \
+ mainwindow.cpp \
+ svgview.cpp
+
+HEADERS += \
+ mainwindow.h \
+ svgview.h
+
+FORMS += \
+ mainwindow.ui
+
+CONFIG(release, debug|release): LIBS += -L$$PWD/../../target/release/ -lresvg
+else:CONFIG(debug, debug|release): LIBS += -L$$PWD/../../target/debug/ -lresvg
+
+INCLUDEPATH += $$PWD/../../capi/include
+DEPENDPATH += $$PWD/../../capi/include