From fd2a247a065548422b828d3055729e435a918f42 Mon Sep 17 00:00:00 2001 From: Javier Date: Sun, 19 Sep 2021 20:55:17 +0200 Subject: add InkML export support, fix bug with page layout in notebookview --- mainwindow.cc | 19 ++++++ mainwindow.h | 1 + notebookview.cc | 13 +++- notebookview.h | 1 + scribiu.pro | 4 +- stfexporter.cc | 183 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ stfexporter.h | 39 ++++++++++++ stftxtexport.cc | 90 ---------------------------- stftxtexport.h | 35 ----------- 9 files changed, 255 insertions(+), 130 deletions(-) create mode 100644 stfexporter.cc create mode 100644 stfexporter.h delete mode 100644 stftxtexport.cc delete mode 100644 stftxtexport.h diff --git a/mainwindow.cc b/mainwindow.cc index e7a73fa..d8a935c 100644 --- a/mainwindow.cc +++ b/mainwindow.cc @@ -154,6 +154,18 @@ void MainWindow::exportCurrentPageAsTXYP(const QString &file, bool relativeTime) f.close(); } +void MainWindow::exportCurrentPageAsInkML(const QString &file) +{ + QFile f(file); + if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text)) { + QMessageBox::warning(this, tr("Export page"), + tr("Could not export current page to '%s'").arg(file)); + return; + } + ui->notebookView->exportPageAsInkML(&f, ui->notebookView->curPage()); + f.close(); +} + void MainWindow::exportCurrentPaperReplayAsAac(const QString &file) { QString src = _media->currentSource().fileName(); @@ -349,6 +361,7 @@ void MainWindow::handleExport() filters << tr("Current page as PNG image (*.png)") << tr("Current page as SVG image (*.svg)") << tr("Current page as TXYP (*.txyp)") + << tr("Current page as InkML (*.inkml)") << tr("Current audio as AAC (*.aac)"); int filterIndex = settings.value("filetype").toInt(); QString filter = filters.value(filterIndex); @@ -378,6 +391,12 @@ void MainWindow::handleExport() exportCurrentPageAsTXYP(fileName, settings.value("txyp_relative_t", true).toBool()); break; case 3: + if (!fileName.endsWith(".inkml", Qt::CaseInsensitive)) { + fileName.append(".inkml"); + } + exportCurrentPageAsInkML(fileName); + break; + case 4: if (!fileName.endsWith(".aac", Qt::CaseInsensitive)) { fileName.append(".aac"); } diff --git a/mainwindow.h b/mainwindow.h index dc06243..2f5575c 100644 --- a/mainwindow.h +++ b/mainwindow.h @@ -46,6 +46,7 @@ public slots: void exportCurrentPageAsPng(const QString &file); void exportCurrentPageAsSvg(const QString &file); void exportCurrentPageAsTXYP(const QString &file, bool relativeTime); + void exportCurrentPageAsInkML(const QString &file); void exportCurrentPaperReplayAsAac(const QString &file); private slots: diff --git a/notebookview.cc b/notebookview.cc index 06d035b..f8394c0 100644 --- a/notebookview.cc +++ b/notebookview.cc @@ -18,7 +18,7 @@ #include #include -#include "stftxtexport.h" +#include "stfexporter.h" #include "notebookview.h" #define VIEW_MARGIN 2 @@ -152,10 +152,16 @@ void NotebookView::renderPage(QPainter *painter, int pageNum, const QRectF &targ void NotebookView::exportPageAsTXYP(QIODevice *device, int pageNum, bool relativeTime) { - StfTxtExport writer(_nb); + StfExporter writer(_nb); writer.exportToTXYP(device, pageNum, relativeTime); } +void NotebookView::exportPageAsInkML(QIODevice *device, int pageNum) +{ + StfExporter writer(_nb); + writer.exportToInkML(device, pageNum); +} + void NotebookView::requestPaperReplay(const QString &file, qint64 time) { emit paperReplayRequested(file, time); @@ -294,7 +300,8 @@ void NotebookView::calculateScale() void NotebookView::layoutPages() { - const int numRows = (_pages.size() + 1) / _numColumns; + const int numRows = _pages.size() / _numColumns + + (_pages.size() % _numColumns > 0 ? 1 : 0); const QSizeF pageSpace( _maxPageSize.width() + PAGE_SEPARATION, _maxPageSize.height() + PAGE_SEPARATION); diff --git a/notebookview.h b/notebookview.h index cced421..7d62fab 100644 --- a/notebookview.h +++ b/notebookview.h @@ -60,6 +60,7 @@ public: void renderPage(QPainter *painter, int pageNum, const QRectF &target = QRectF(), const QRectF &source = QRectF()) const; void exportPageAsTXYP(QIODevice *device, int pageNum, bool relativeTime); + void exportPageAsInkML(QIODevice *device, int pageNum); void requestPaperReplay(const QString &file, qint64 time); diff --git a/scribiu.pro b/scribiu.pro index c47865e..d25f2fc 100644 --- a/scribiu.pro +++ b/scribiu.pro @@ -24,12 +24,12 @@ SOURCES += main.cc \ notebookmodel.cc \ smartpensyncer.cc \ smartpen.cc bitreader.cc stfreader.cc \ + stfexporter.cc \ xmlutils.cc \ notebookview.cc \ afdnotebook.cc \ pageitem.cc \ stfgraphicsitem.cc \ - stftxtexport.cc \ paperreplay.cc \ afdpageaddress.cc \ stfstrokeitem.cc \ @@ -40,12 +40,12 @@ HEADERS += mainwindow.h \ notebookmodel.h \ smartpensyncer.h \ smartpen.h bitreader.h stfreader.h \ + stfexporter.h \ xmlutils.h \ notebookview.h \ afdnotebook.h \ pageitem.h \ stfgraphicsitem.h \ - stftxtexport.h \ paperreplay.h \ afdpageaddress.h \ stfstrokeitem.h \ diff --git a/stfexporter.cc b/stfexporter.cc new file mode 100644 index 0000000..84eaad7 --- /dev/null +++ b/stfexporter.cc @@ -0,0 +1,183 @@ +/* + * scribiu -- read notebooks and voice memos from Livescribe pens + * Copyright (C) 2021 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include +#include +#include + +#include "stfexporter.h" + +#define XMLNS_INK "http://www.w3.org/2003/InkML" + +class StfToTXYP : public StfReader::StrokeHandler { + QTextStream _out; + QPoint _lastP; + int _lastForce; + qint64 _startTime; + bool _relativeTime; + +public: + StfToTXYP(QIODevice *out, bool relativeTime) + : _out(out), _lastP(), _lastForce(0), _startTime(0), _relativeTime(relativeTime) { + _out << "T\tX\tY\tP\n"; + } + + bool startStroke(const QPoint& p, int force, qint64 time) { + if (_relativeTime && _startTime == 0) { + _startTime = time; + } + _out << (time - _startTime) << '\t' << p.x() << '\t' << p.y() << '\t' << force << '\n'; + _lastP = p; + _lastForce = force; + return true; + } + + bool strokePoint(const QPoint& p, int force, qint64 time) { + _out << (time - _startTime) << '\t' << p.x() << '\t' << p.y() << '\t' << force << '\n'; + _lastP = p; + _lastForce = force; + return true; + } + + bool endStroke(qint64 time) { + // Ensure there is a entry with force=0, in case the pen didn't provide it + if (_lastForce != 0) { + _out << (time - _startTime) << '\t' << _lastP.x() << '\t' << _lastP.y() << '\t' << 0 << '\n'; + _lastForce = 0; + } + return true; + } +}; + +class StfToInkML : public StfReader::StrokeHandler { + QXmlStreamWriter *_out; + QPoint _lastP; + qint64 _startTime; + +public: + StfToInkML(QXmlStreamWriter *out) + : _out(out), _lastP(), _startTime(0) { + } + + bool startStroke(const QPoint& p, int force, qint64 time) { + if (_startTime == 0) { + _startTime = time; + } + + _out->writeStartElement(XMLNS_INK, "trace"); + _out->writeAttribute("timeOffset", QString::number(time - _startTime)); + + _out->writeCharacters(QString("%1 %2").arg(p.x()).arg(p.y())); + + _lastP = p; + return true; + } + + bool strokePoint(const QPoint& p, int force, qint64 time) { + QPoint delta = p - _lastP; + + _out->writeCharacters(QString(", %1 %2").arg(delta.x()).arg(delta.y())); + + _lastP = p; + return true; + } + + bool endStroke(qint64 time) { + _out->writeEndElement(); + return true; + } +}; + +StfExporter::StfExporter(AfdNotebook *nb) + : _nb(nb) +{ +} + +void StfExporter::exportToTXYP(QIODevice *out, int pageNum, bool relativeTime) +{ + QStringList pens = _nb->penSerials(); + if (pens.isEmpty()) return; + + StfToTXYP h(out, relativeTime); + + exportPage(&h, pageNum); +} + +void StfExporter::exportToInkML(QIODevice *out, int pageNum) +{ + QStringList pens = _nb->penSerials(); + if (pens.isEmpty()) return; + + QXmlStreamWriter writer(out); + writer.setAutoFormatting(true); + writer.writeStartDocument(); + writer.writeDefaultNamespace(XMLNS_INK); + writer.writeStartElement(XMLNS_INK, "ink"); + +#if 0 /* No need to write out inkSource element, since default trace format is OK for now */ + writer.writeStartElement(XMLNS_INK, "inkSource"); + writer.writeAttribute("manufacturer", "Livescribe"); + writer.writeAttribute("description", "Dumped by Scribiu"); + writer.writeStartElement(XMLNS_INK, "traceFormat"); + writer.writeEmptyElement(XMLNS_INK, "channel"); + writer.writeAttribute("name", "X"); + writer.writeAttribute("type", "integer"); + writer.writeEmptyElement(XMLNS_INK, "channel"); + writer.writeAttribute("name", "Y"); + writer.writeAttribute("type", "integer"); + writer.writeEndElement(); + writer.writeEmptyElement(XMLNS_INK, "sampleRate"); + writer.writeAttribute("uniform", "true"); + writer.writeAttribute("value", "75"); + writer.writeEndElement(); +#endif + + StfToInkML h(&writer); + + exportPage(&h, pageNum); + + writer.writeEndElement(); + writer.writeEndDocument(); +} + +bool StfExporter::exportPage(StfReader::StrokeHandler *handler, int pageNum) +{ + QStringList pens = _nb->penSerials(); + if (pens.isEmpty()) return true; // No pen wrote on this page + + StfReader r; + r.setStrokeHandler(handler); + + foreach (const QString &pen, pens) { + QStringList strokeFiles = _nb->strokeFiles(pen, pageNum); + foreach (const QString &strokeFile, strokeFiles) { + QFile in(strokeFile); + if (!in.open(QIODevice::ReadOnly)) { + qWarning() << "Could not open stroke file:" << strokeFile; + continue; + } + + if (!r.parse(&in)) { + qWarning() << "Could not parse stroke file:" << strokeFile; + continue; + } + } + } + + return true; +} diff --git a/stfexporter.h b/stfexporter.h new file mode 100644 index 0000000..c67a75c --- /dev/null +++ b/stfexporter.h @@ -0,0 +1,39 @@ +/* + * scribiu -- read notebooks and voice memos from Livescribe pens + * Copyright (C) 2021 Javier S. Pedro + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef STFEXPORTER_H +#define STFEXPORTER_H + +#include "afdnotebook.h" + +class StfExporter +{ +public: + explicit StfExporter(AfdNotebook *nb); + + void exportToTXYP(QIODevice *out, int pageNum, bool relativeTime); + void exportToInkML(QIODevice *out, int pageNum); + +private: + bool exportPage(StfReader::StrokeHandler *handler, int pageNum); + +private: + AfdNotebook *_nb; +}; + +#endif // STFEXPORTER_H diff --git a/stftxtexport.cc b/stftxtexport.cc deleted file mode 100644 index e6793b2..0000000 --- a/stftxtexport.cc +++ /dev/null @@ -1,90 +0,0 @@ -/* - * scribiu -- read notebooks and voice memos from Livescribe pens - * Copyright (C) 2021 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include -#include -#include "stftxtexport.h" - -class StfToTXYP : public StfReader::StrokeHandler { - QTextStream _out; - QPoint _lastP; - int _lastForce; - qint64 _startTime; - bool _relativeTime; - -public: - StfToTXYP(QIODevice *out, bool relativeTime) - : _out(out), _lastP(), _lastForce(0), _startTime(0), _relativeTime(relativeTime) { - _out << "T\tX\tY\tP\n"; - } - - bool startStroke(const QPoint& p, int force, qint64 time) { - if (_relativeTime && _startTime == 0) { - _startTime = time; - } - _out << (time - _startTime) << '\t' << p.x() << '\t' << p.y() << '\t' << force << '\n'; - _lastP = p; - _lastForce = force; - return true; - } - - bool strokePoint(const QPoint& p, int force, qint64 time) { - _out << (time - _startTime) << '\t' << p.x() << '\t' << p.y() << '\t' << force << '\n'; - _lastP = p; - _lastForce = force; - return true; - } - - bool endStroke(qint64 time) { - // Ensure there is a entry with force=0, in case the pen didn't provide it - if (_lastForce != 0) { - _out << (time - _startTime) << '\t' << _lastP.x() << '\t' << _lastP.y() << '\t' << 0 << '\n'; - _lastForce = 0; - } - return true; - } -}; - -StfTxtExport::StfTxtExport(AfdNotebook *nb) - : _nb(nb) -{ -} - -void StfTxtExport::exportToTXYP(QIODevice *out, int pageNum, bool relativeTime) -{ - QStringList pens = _nb->penSerials(); - if (pens.isEmpty()) return; - - StfToTXYP h(out, relativeTime); - StfReader r; - r.setStrokeHandler(&h); - - QStringList strokeFiles = _nb->strokeFiles(pens.first(), pageNum); - foreach (const QString &strokeFile, strokeFiles) { - QFile in(strokeFile); - if (!in.open(QIODevice::ReadOnly)) { - qWarning() << "Could not open stroke file:" << strokeFile; - continue; - } - - if (!r.parse(&in)) { - qWarning() << "Could not parse stroke file:" << strokeFile; - continue; - } - } -} diff --git a/stftxtexport.h b/stftxtexport.h deleted file mode 100644 index b9ca821..0000000 --- a/stftxtexport.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * scribiu -- read notebooks and voice memos from Livescribe pens - * Copyright (C) 2021 Javier S. Pedro - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef STFTXTEXPORT_H -#define STFTXTEXPORT_H - -#include "afdnotebook.h" - -class StfTxtExport -{ -public: - explicit StfTxtExport(AfdNotebook *nb); - - void exportToTXYP(QIODevice *out, int pageNum, bool relativeTime); - -private: - AfdNotebook *_nb; -}; - -#endif // STFTXTEXPORT_H -- cgit v1.2.3