@@ -38,5 +46,6 @@
https://github.com/manisandro/gImageReader
- manisandro@gmail.com
-
\ No newline at end of file
+ http://the-web-site-with-translation-instructions/
+ manisandro@gmail.com
+
diff -Nru gimagereader-3.1.91/data/manual.html.in gimagereader-3.1.99/data/manual.html.in
--- gimagereader-3.1.91/data/manual.html.in 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/data/manual.html.in 2016-10-13 21:45:35.000000000 +0000
@@ -46,6 +46,47 @@
A detailed list of changes can be found in the commit log: https://github.com/manisandro/gImageReader/commits/master
+gImageReader 3.1.99 (Oct 13 2016):
+
+gImageReader 3.2 release candidate
+General improvements:
+
+ Catch critical tesseract errors which otherwise result in the application crashing
+ Improve spelling dictionary auto-installation logic
+ Allow choosing whether to store language files (language definitions, spelling dictionaries) in system-wide or user-local directories
+
+
+Plain text mode improvements:
+
+ Allow recognizing user-defined regions on multiple pages
+ Also tread \u2014 character as a hyphen
+ Make preserve paragraphs option correctly deal with trailing whitespace
+
+
+hOCR editor improvements:
+
+ Add "Add to dictionary" and "Ignore word" actions to spell-checking menu in hOCR editor
+ Exclude non-word characters from spell-checking
+ Allow merging adjacent word items
+ Allow adjusting bounding boxes of document elements by resizing the selection in the canvas
+ Allow removing arbitrary items from the document tree
+ Allow defining custom graphic regions from context-menu of the respective page item
+
+
+PDF export improvements:
+
+ Add previewing capability
+ Take into account baseline information to better position the words in the generated PDF
+ Add options to choose color format and compression of images written to PDF, allowing to greatly reduce the size of PDF
+ Correctly handle paper size and DPI
+ Improve logic for uniformizing word and line spacing
+ Make sure correct hypen character is used, allowing PDF applications to correctly find hyphenated words
+
+
+New and updated translations
+Various bug fixes
+Full details in commit log: https://github.com/manisandro/gImageReader/commits/master
+
gImageReader 3.1.91 (May 03 2016):
gImageReader 3.2 beta 2
@@ -170,7 +211,7 @@
To perform OCR on an image, the user needs to specify:
The input images (e.g. images to recognize),
- The recognition mode (e.g. plain text vs hOCR, PDF ,
+ The recognition mode (e.g. plain text vs hOCR, PDF )
The recognition language(s).
@@ -189,7 +230,7 @@
Alternatively, the automatic layout detection button , accessible from the main toolbar will attempt to automatically define appropriate recognition areas, as well as adjust the rotation of the image if necessary.
Selections can be deleted and reordered via the context menu which appears when right-clicking on them. It is also possible to resize existing selections by dragging the corners of the selection rectangle.
The selected portions of the image (or the entire image, if no selections are defined) can be recognized by pressing on the recognize button in the main toolbar . Alternatively, individual areas can be recognized by right-clicking a selection. From the selection context menu, it is also possible to redirect the recognized text to the clipboard, instead of the output pane.
- If multiple pages are selected for recognition, the program allows the user to choose between either recognizing the full area of each individual page, or performing a page-layout analysis on each page to automatically detect appropriate recognition areas.
+ If multiple pages are selected for recognition, the program allows the user to choose between either recognizing the full resp. manually selected area for each individual page, or performing a page-layout analysis on each page to automatically detect appropriate recognition areas.
Recognized text will appear in the output pane (unless the text was redirected to the clipboard), which is shown automatically as soon as some text was recognized.
If a spelling dictionary for the recognition language is available, automatic spell-checking will be enabled for the outputted text. The used spelling dictionary can be changed either from the language menu next to the recognize button , or from the menu which appears when right-clicking in the text area.
When additional text is recognized, it will either get appended, inserted at cursor position, or replace the previous content of the text buffer, depending on the mode selected in the append mode menu , which can be found in the output pane toolbar .
@@ -199,24 +240,29 @@
- In hOCR mode, the entire page of the selected source(s) is recognized.
+ In hOCR mode, always the entire page of the selected source(s) is recognized.
The recognition result is presented in the output pane as a tree-structure, divided in pages, paragraphs, textlines, words and graphics.
When an entry in the tree-structure is selected, the corresponding area is highlighted in the image. Additionally, formatting and layout properties of the entry are shown in the Properties tab below the document tree. The raw hOCR source is visible in the Source tab below the document tree.
The word text in the document tree can be edited by double-clicking the respective word entry. If a word is mis-spelled, it will be rendered red. Right-clicking a word in the document tree will show a menu with spelling suggestions.
- Properties for a selected entry can be modified by double clicking the desired property value in the Properties tab. Interesting actions for text entries are tweaking the bounding area, changing the language and modifying the font size. The language property also definies the spelling language used to check the respective word.
+ Properties for a selected entry can be modified by double clicking the desired property value in the Properties tab. Interesting actions for text entries are tweaking the bounding area, changing the language and modifying the font size. The language property also definies the spelling language used to check the respective word. The bounding area can also be edited by resizing the selection rectangle in the canvas.
+ Adjacent word items can be merged by rightclicking the respective selected items.
+ Arbitrary items can be removed from the document via right-click on the respective item.
+ New graphic areas can be defined by selecting the Add graphic region entry of the context menu of the respective page item and drawing a rectangle on the canvas.
The document tree can be saved as a hOCR HTML document via the Save as hOCR text button in the output pane toolbar . Existing documents can be imported via the Open hOCR file button in the output pane toolbar .
PDF files can be generated from the PDF export menu in the output pane toolbar . Two modes are available:
- PDF will generate a reconstructed PDF the same layout and graphics/pictures as the source document. When exporting, the user is prompted for the font family to use, whether to honour the font sizes detected by the OCR engine, and whether to attempt to homogenize the text line spacing.
+ PDF will generate a reconstructed PDF the same layout and graphics/pictures as the source document.
PDF with invisible text overlay will generate a PDF with the unmodified source image as background and invisible (but selectable) text overlayed above the respective source text in the image. This export mode is usefull for generating a document which is visually identical to the input, but with searchable and selectable text.
+ When exporting to PDF, the user is prompted for the font family to use, whether to honour the font sizes detected by the OCR engine, and whether to attempt to homogenize the text line spacing. Also, the user can select the color format, resolution and compression method to use for images in the PDF document to control the size of the generated output.
The program options can be accessed from the application menu , which opens when clicking the right-most button of the main toolbar . When running the application within the Gnome 3 desktop environment, the application menu is part of the top bar of the desktop shell.
Options allow setting the font of the output pane, as well as determining whether the application will notify about missing spelling dictionaries and new program versions.
When running the Gtk+ interface, the options also allow setting the orientation of the output pane (if vertical, it will occupy the right portion of the application, if horizontal, it will occupy the lower portion). When running the Qt interface, the position of the output pane can be freely moved around by dragging on the title bar of the output pane.
+ The language data location setting allows to control whether tesseract language definitions and spelling dictionaries are saved in system-wide (i.e. %ProgramFiles% under Windows or typically below /usr on Linux) or user-local (i.e. below the current user's home directory) directories. This is usefull if the user does not have writing privileges in system-wide locations.
Additionally, one can define new rules to match tesseract language definitions to a language (unfortunately, the tesseract language definitions do not include this information). The list of predefined rules can be seen in the Predefined language definitions section. Additional definitions can be added clicking on the Add button below. The rules for a language definition, which consists of three fields, are as follows:
Filename prefix : The filename of tesseract language data files is of the format <prefix>.traineddata , i.e. for English, the file is called eng.traineddata and the prefix is eng .
@@ -227,7 +273,7 @@
- The Tessdata manager , available from the drop-down menu of the recognize button in the main toolbar allows the user to manage the available recognition languages. On Linux, the tessdata manager requires PackageKit to work.
+ The Tessdata manager , available from the drop-down menu of the recognize button in the main toolbar allows the user to manage the available recognition languages.
To install the languages manually:
On Linux , it's sufficient to install the package corresponding to the language definition one wants to install via the package management application (the packages may be called something like tesseract-langpack-<lang> ).
diff -Nru gimagereader-3.1.91/debian/changelog gimagereader-3.1.99/debian/changelog
--- gimagereader-3.1.91/debian/changelog 2016-10-05 11:52:01.000000000 +0000
+++ gimagereader-3.1.99/debian/changelog 2016-10-14 00:07:21.000000000 +0000
@@ -1,5 +1,5 @@
-gimagereader (3.1.91-1~wilyppa3) wily; urgency=low
+gimagereader (3.1.99-1~wilyppa1) wily; urgency=low
- * gimagereader 3.1.91.
+ * gimagereader 3.1.99.
- -- Sandro Mani Wed, 05 Oct 2016 13:42:17 +0200
+ -- Sandro Mani Fri, 14 Oct 2016 00:20:40 +0200
diff -Nru gimagereader-3.1.91/debian/control gimagereader-3.1.99/debian/control
--- gimagereader-3.1.91/debian/control 2016-10-05 11:51:40.000000000 +0000
+++ gimagereader-3.1.99/debian/control 2016-10-13 23:06:06.000000000 +0000
@@ -16,7 +16,9 @@
libqt4-dev,
libqtspell-qt4-dev,
libpoppler-qt4-dev,
- libqjson-dev
+ libqjson-dev,
+ libpodofo-dev,
+ libjpeg-dev
Standards-Version: 3.9.4
Homepage: http://sourceforge.net/projects/gimagereader/
diff -Nru gimagereader-3.1.91/debian/patches/0001-Qt-Fix-qt4-build.patch gimagereader-3.1.99/debian/patches/0001-Qt-Fix-qt4-build.patch
--- gimagereader-3.1.91/debian/patches/0001-Qt-Fix-qt4-build.patch 1970-01-01 00:00:00.000000000 +0000
+++ gimagereader-3.1.99/debian/patches/0001-Qt-Fix-qt4-build.patch 2016-10-13 23:54:57.000000000 +0000
@@ -0,0 +1,91 @@
+From 7776eb5f6a783531ad4bca7260ccf603469cf649 Mon Sep 17 00:00:00 2001
+From: Sandro Mani
+Date: Fri, 14 Oct 2016 01:54:55 +0200
+Subject: [PATCH] [Qt] Fix qt4 build
+
+---
+ qt/src/Config.cc | 8 ++++++++
+ qt/src/OutputEditorHOCR.cc | 4 ++++
+ qt/src/OutputEditorHOCR.hh | 20 ++++++++++++++++++++
+ 3 files changed, 32 insertions(+)
+
+diff --git a/qt/src/Config.cc b/qt/src/Config.cc
+index 61cef19..9b63176 100644
+--- a/qt/src/Config.cc
++++ b/qt/src/Config.cc
+@@ -403,7 +403,11 @@ void Config::setDataLocations(int idx)
+ ui.lineEditSpellLocation->setText(dataDir.absoluteFilePath("myspell/dicts"));
+ #else
+ QDir dataDir("/usr/share");
++#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
++ unsetenv("TESSDATA_PREFIX");
++#else
+ qunsetenv("TESSDATA_PREFIX");
++#endif
+ if(QDir(dataDir.absoluteFilePath("myspell/dicts")).exists()) {
+ ui.lineEditSpellLocation->setText(dataDir.absoluteFilePath("myspell/dicts"));
+ } else {
+@@ -436,7 +440,11 @@ void Config::openTessdataDir()
+ QDir dataDir = QDir(QString("%1/../share/").arg(QApplication::applicationDirPath()));
+ qputenv("TESSDATA_PREFIX", dataDir.absolutePath().toLocal8Bit());
+ #else
++# if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
++ unsetenv("TESSDATA_PREFIX");
++# else
+ qunsetenv("TESSDATA_PREFIX");
++# endif
+ #endif
+ } else {
+ #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
+diff --git a/qt/src/OutputEditorHOCR.cc b/qt/src/OutputEditorHOCR.cc
+index 31e8d82..09a217b 100644
+--- a/qt/src/OutputEditorHOCR.cc
++++ b/qt/src/OutputEditorHOCR.cc
+@@ -258,7 +258,11 @@ OutputEditorHOCR::OutputEditorHOCR(DisplayerToolHOCR* tool)
+ m_pdfExportDialog = new QDialog(m_widget);
+ m_pdfExportDialogUi.setupUi(m_pdfExportDialog);
+ m_pdfExportDialogUi.comboBoxImageFormat->addItem(_("Color"), QImage::Format_RGB888);
++#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
++ m_pdfExportDialogUi.comboBoxImageFormat->addItem(_("Grayscale"), QImage::Format_Indexed8);
++#else
+ m_pdfExportDialogUi.comboBoxImageFormat->addItem(_("Grayscale"), QImage::Format_Grayscale8);
++#endif
+ m_pdfExportDialogUi.comboBoxImageFormat->addItem(_("Monochrome"), QImage::Format_Mono);
+ m_pdfExportDialogUi.comboBoxImageFormat->setCurrentIndex(-1);
+ m_pdfExportDialogUi.comboBoxImageCompression->addItem(_("Zip (lossless)"), PDFSettings::CompressZip);
+diff --git a/qt/src/OutputEditorHOCR.hh b/qt/src/OutputEditorHOCR.hh
+index 6795e89..373ee3d 100644
+--- a/qt/src/OutputEditorHOCR.hh
++++ b/qt/src/OutputEditorHOCR.hh
+@@ -90,8 +90,28 @@ private:
+ virtual double getAverageCharWidth() const = 0;
+ virtual double getTextWidth(const QString& text) const = 0;
+ protected:
++#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
++ QVector createGray8Table() const{
++ QVector colorTable(255);
++ for(int i = 0; i < 255; ++i) {
++ colorTable[i] = qRgb(i, i, i);
++ }
++ return colorTable;
++ }
++#endif
+ QImage convertedImage(const QImage& image, QImage::Format targetFormat) const{
++#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
++ if(image.format() == targetFormat) {
++ return image;
++ } else if(targetFormat == QImage::Format_Indexed8) {
++ static QVector gray8Table = createGray8Table();
++ return image.convertToFormat(targetFormat, gray8Table);
++ } else {
++ return image.convertToFormat(targetFormat);
++ }
++#else
+ return image.format() == targetFormat ? image : image.convertToFormat(targetFormat);
++#endif
+ }
+ };
+ class PoDoFoPDFPainter;
+--
+2.10.1
+
diff -Nru gimagereader-3.1.91/debian/patches/libjpeg.patch gimagereader-3.1.99/debian/patches/libjpeg.patch
--- gimagereader-3.1.91/debian/patches/libjpeg.patch 1970-01-01 00:00:00.000000000 +0000
+++ gimagereader-3.1.99/debian/patches/libjpeg.patch 2016-10-13 23:14:15.000000000 +0000
@@ -0,0 +1,30 @@
+diff -rupN gimagereader-3.1.99/CMakeLists.txt gimagereader-3.1.99-new/CMakeLists.txt
+--- gimagereader-3.1.99/CMakeLists.txt 2016-10-13 23:45:35.000000000 +0200
++++ gimagereader-3.1.99-new/CMakeLists.txt 2016-10-14 01:13:35.979475233 +0200
+@@ -75,7 +75,6 @@ IF("${INTERFACE_TYPE}" STREQUAL "gtk")
+ PKG_CHECK_MODULES(POPPLER REQUIRED poppler-glib)
+ PKG_CHECK_MODULES(JSONGLIB REQUIRED json-glib-1.0)
+ PKG_CHECK_MODULES(LIBXMLPP REQUIRED libxml++-2.6)
+- PKG_CHECK_MODULES(LIBJPEG REQUIRED libjpeg)
+ PKG_CHECK_MODULES(FONTCONFIG REQUIRED fontconfig)
+ INCLUDE_DIRECTORIES(
+ ${GTKMM_INCLUDE_DIRS}
+@@ -86,7 +85,6 @@ IF("${INTERFACE_TYPE}" STREQUAL "gtk")
+ ${POPPLER_INCLUDE_DIRS}
+ ${JSONGLIB_INCLUDE_DIRS}
+ ${LIBXMLPP_INCLUDE_DIRS}
+- ${LIBJPEG_INCLUDE_DIRS}
+ ${FONTCONFIG_INCLUDE_DIRS}
+ )
+ SET(gimagereader_LIBS
+@@ -98,9 +96,9 @@ IF("${INTERFACE_TYPE}" STREQUAL "gtk")
+ ${POPPLER_LDFLAGS}
+ ${JSONGLIB_LDFLAGS}
+ ${LIBXMLPP_LDFLAGS}
+- ${LIBJPEG_LDFLAGS}
+ ${PODOFO_LDFLAGS}
+ ${FONTCONFIG_LDFLAGS}
++ -ljpeg
+ )
+ SET(srcdir "gtk")
+ ELSEIF("${INTERFACE_TYPE}" STREQUAL "qt4")
diff -Nru gimagereader-3.1.91/debian/patches/series gimagereader-3.1.99/debian/patches/series
--- gimagereader-3.1.91/debian/patches/series 1970-01-01 00:00:00.000000000 +0000
+++ gimagereader-3.1.99/debian/patches/series 2016-10-13 23:55:12.000000000 +0000
@@ -0,0 +1,2 @@
+0001-Qt-Fix-qt4-build.patch
+libjpeg.patch
diff -Nru gimagereader-3.1.91/gtk/data/editor_hocr.ui gimagereader-3.1.99/gtk/data/editor_hocr.ui
--- gimagereader-3.1.91/gtk/data/editor_hocr.ui 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/data/editor_hocr.ui 2016-10-13 21:45:35.000000000 +0000
@@ -3,13 +3,40 @@
+
+ 50
+ 1200
+ 300
+ 10
+ 50
+
+
+ 50
+ 200
+ 100
+ 1
+ 10
+
+
+ 1
+ 99
+ 4.0000000002235172
+ 1
+ 10
+
+
+ 100
+ 90
+ 1
+ 10
+
False
PDF Export
False
center-on-parent
True
- dialog
+ normal
True
True
@@ -61,14 +88,13 @@
True
False
- 2
+ 4
2
-
+
True
False
- True
- Document font:
+ Output mode:
0
@@ -77,13 +103,14 @@
-
+
True
- True
- True
- True
- Sans 12
-
+ False
+ 0
+
+ - PDF
+ - PDF with invisible text overlay
+
1
@@ -91,13 +118,126 @@
-
- Use detected font sizes
+
True
- True
- False
- True
- True
+ False
+ 0
+ in
+
+
+ True
+ False
+ 12
+
+
+ True
+ False
+ 2
+ 2
+
+
+ True
+ False
+ True
+
+
+ 1
+ 0
+
+
+
+
+ True
+ False
+ Format:
+ 0
+
+
+ 0
+ 0
+
+
+
+
+ True
+ False
+ Compression:
+ 0
+
+
+ 0
+ 2
+
+
+
+
+ True
+ False
+ True
+
+
+ 1
+ 2
+
+
+
+
+ True
+ False
+ Compression quality:
+ 0
+
+
+ 0
+ 3
+
+
+
+
+ True
+ True
+ True
+ adjustment:pdfoptions.quality
+
+
+ 1
+ 3
+
+
+
+
+ True
+ False
+ DPI:
+ 0
+
+
+ 0
+ 1
+
+
+
+
+ True
+ True
+ adjustment:pdfoptions.dpi
+
+
+ 1
+ 1
+
+
+
+
+
+
+
+
+ True
+ False
+ Image settings:
+
+
0
@@ -106,17 +246,206 @@
-
- Uniformize line spacing
+
+ True
+ False
+ 0
+ in
+
+
+ True
+ False
+ 12
+
+
+ True
+ False
+ 2
+ 2
+
+
+ True
+ False
+ Document font:
+ 0
+
+
+ 0
+ 0
+
+
+
+
+ True
+ True
+ True
+ True
+ Sans 12
+
+
+
+ 1
+ 0
+
+
+
+
+ Uniformize line and word spacing
+ True
+ True
+ False
+ True
+ 0
+ True
+
+
+ 0
+ 3
+ 2
+
+
+
+
+ Use detected font sizes
+ True
+ True
+ False
+ True
+ 0
+ True
+
+
+ 0
+ 1
+ 2
+
+
+
+
+ True
+ False
+ False
+ 20
+ 2
+
+
+ True
+ False
+ 20
+ Preserve spaces wider than:
+ 0
+
+
+ False
+ True
+ 0
+
+
+
+
+ True
+ True
+ True
+ 2
+ 2
+ adjustment:pdfoptions.preserve
+ True
+ True
+
+
+ False
+ True
+ 1
+
+
+
+
+ True
+ False
+ characters
+
+
+ False
+ True
+ 2
+
+
+
+
+ 0
+ 4
+ 2
+
+
+
+
+ True
+ False
+ False
+
+
+ True
+ False
+ 20
+ Scale:
+ 0
+
+
+ True
+ True
+ 0
+
+
+
+
+ True
+ True
+ adjustment:pdfoptions.fontscale
+
+
+ True
+ True
+ 1
+
+
+
+
+ 0
+ 2
+ 2
+
+
+
+
+
+
+
+
+ True
+ False
+ Text settings:
+ 0
+
+
+
+
+ 0
+ 2
+ 2
+
+
+
+
+ Show preview
True
True
False
- True
+ 0
True
0
- 2
+ 3
2
@@ -139,6 +468,11 @@
False
edit-clear-symbolic
+
+ True
+ False
+ x-office-document-symbolic
+
True
False
@@ -149,24 +483,6 @@
False
document-save-as-symbolic
-
True
False
@@ -192,6 +508,7 @@
True
+ False
True
True
Save output
@@ -205,20 +522,14 @@
-
+
diff -Nru gimagereader-3.1.91/gtk/data/editor_text.ui gimagereader-3.1.99/gtk/data/editor_text.ui
--- gimagereader-3.1.91/gtk/data/editor_text.ui 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/data/editor_text.ui 2016-10-13 21:45:35.000000000 +0000
@@ -28,6 +28,40 @@
False
/org/gnome/gimagereader/ins_replace.png
+
True
False
@@ -239,40 +273,6 @@
-
@@ -440,7 +441,6 @@
True
True
False
- Select language
none
menu:output.stripcrlf
False
diff -Nru gimagereader-3.1.91/gtk/data/gimagereader.ui gimagereader-3.1.99/gtk/data/gimagereader.ui
--- gimagereader-3.1.91/gtk/data/gimagereader.ui 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/data/gimagereader.ui 2016-10-13 21:45:35.000000000 +0000
@@ -252,6 +252,11 @@
False
edit-delete-symbolic
+
+ True
+ False
+ view-refresh-symbolic
+
@@ -325,7 +330,7 @@
True
True
False
- Select language
+ Add Images
menu:sources.images.add
False
@@ -1280,7 +1285,8 @@
A graphical frontend to tesseract-ocr
https://github.com/manisandro/gImageReader/
Sandro Mani <manisandro@gmail.com>
- Chinese (HK): Timothy Lee <timothy.ty.lee@gmail.com>
+ Chinese (CN): Timothy Lee <timothy.ty.lee@gmail.com>
+Chinese (HK): Timothy Lee <timothy.ty.lee@gmail.com>
Chinese (TW): Timothy Lee <timothy.ty.lee@gmail.com>
Czech: Pavel Borecki <pavel.borecki@gmail.com>
French: Sandro Mani <manisandro@gmail.com>
@@ -1290,6 +1296,7 @@
Polish: Piotr Brol <peterb@inetia.pl>
Portuguese (BR): André Alencar <andre.tre@gmail.com>, Felipe Braga <fbobraga@gmail.com>
Russian: Oleg Moiseichuk <berroll@mail.ru>
+Slovenian: Bernard Banko <beernarrd@gmail.com>
Spanish: Antonio Trujillo <lunatc@gmail.com>
Swedish: Mats Olofsson <mats.olofsson@bokforlaget-alerta.se>
Turkish: Necdet Yucel <necdetyucel@gmail.com>
@@ -1460,7 +1467,7 @@
0
- 5
+ 9
2
@@ -1485,7 +1492,7 @@
0
- 6
+ 10
2
@@ -1500,7 +1507,7 @@
0
- 7
+ 11
2
@@ -1525,7 +1532,7 @@
0
- 8
+ 12
2
@@ -1568,7 +1575,7 @@
0
- 9
+ 13
2
@@ -1661,7 +1668,7 @@
0
- 10
+ 14
2
@@ -1719,6 +1726,92 @@
1
+
+
+ True
+ False
+ Language data locations:
+ 0
+
+
+ 0
+ 5
+
+
+
+
+ True
+ False
+ 10
+ Language definitions path:
+ 0
+
+
+ 0
+ 6
+
+
+
+
+ True
+ False
+ 10
+ Spelling dictionaries path:
+ 0
+
+
+ 0
+ 7
+
+
+
+
+ True
+ False
+ vertical
+
+
+ 0
+ 8
+ 2
+
+
+
+
+ True
+ False
+
+ - System-wide paths
+ - User paths
+
+
+
+ 1
+ 5
+
+
+
+
+ True
+ True
+ False
+
+
+ 1
+ 6
+
+
+
+
+ True
+ True
+ False
+
+
+ 1
+ 7
+
+
True
@@ -1947,6 +2040,20 @@
1
+
+
+ Refresh
+ True
+ True
+ True
+ image:tessdatamanager.refresh
+
+
+ True
+ True
+ 2
+
+
False
@@ -1983,6 +2090,7 @@
True
True
False
+ 1
False
@@ -2008,6 +2116,7 @@
button:tessdatamanager.apply
button:tessdatamanager.close
+ button:tessdatamanager.refresh
diff -Nru gimagereader-3.1.91/gtk/data/org.gnome.gimagereader.gschema.xml gimagereader-3.1.99/gtk/data/org.gnome.gimagereader.gschema.xml
--- gimagereader-3.1.91/gtk/data/org.gnome.gimagereader.gschema.xml 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/data/org.gnome.gimagereader.gschema.xml 2016-10-13 21:45:35.000000000 +0000
@@ -136,20 +136,65 @@
Default output editor
Default output editor.
-
+
+ 0
+ PDF export mode
+ PDF export mode.
+
+
"Sans 10"
Font to use for PDFs
Font to use for PDFs.
-
+
true
Use detected font sizes in PDFs
Whether to use detected font sizes when generating PDFs.
-
- true
+
+ false
Uniformize line spacing
Whether to uniformize line spacing when generating PDFs.
+
+ 4
+ Preserve spaces wider than
+ Preserve spaces wider than the specified number of characters when generating PDFs with uniformized spacing.
+
+
+ 0
+ Image color format for PDF output
+ Image color format for PDF output: color (0), grayscale (1) or monochrome (2).
+
+
+ 0
+ Compression for images in PDF output
+ Compression for images in PDF output: Zip (0) or Jpeg (1).
+
+
+ 90
+ Compression quality for images in PDF output
+ Compression quality for images in PDF output: [0-100].
+
+
+ false
+ Whether to display a preview when exporting to PDF
+ Whether to display a preview when exporting to PDF.
+
+
+ 0
+ Where to look for tessdata and spelling files
+ Whether to look in system-wide (0) or user paths (1) for tessdata and spelling files.
+
+
+ 100
+ Scaling factor for detected font sizes
+ Scaling factor for detected font sizes.
+
+
+ 300
+ Resolution at which to export images in the PDF
+ Resolution at which to export images in the PDF.
+
diff -Nru gimagereader-3.1.91/gtk/src/common.hh gimagereader-3.1.99/gtk/src/common.hh
--- gimagereader-3.1.91/gtk/src/common.hh 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/common.hh 2016-10-13 21:45:35.000000000 +0000
@@ -63,6 +63,7 @@
CastProxy(Gtk::Widget* widget) : m_widget(widget) {}
template operator T*(){ return static_cast(m_widget); }
template T* as(){ return static_cast(m_widget); }
+ Gtk::Widget* operator->(){ return m_widget; }
Gtk::Widget* m_widget;
};
@@ -76,6 +77,11 @@
m_builder->get_widget(name, widget);
return CastProxy(widget);
}
+ template
+ void get_derived(const Glib::ustring& name, T*& p) {
+ m_builder->get_widget_derived(name, p);
+ }
+
private:
Glib::RefPtr m_builder;
};
diff -Nru gimagereader-3.1.91/gtk/src/Config.cc gimagereader-3.1.99/gtk/src/Config.cc
--- gimagereader-3.1.91/gtk/src/Config.cc 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/Config.cc 2016-10-13 21:45:35.000000000 +0000
@@ -21,6 +21,8 @@
#include "MainWindow.hh"
#include "Utils.hh"
+#include
+
const std::vector Config::LANGUAGES = {
// {ISO 639-2, ISO 639-1, name}
{"afr", "af", "Afrikaans"}, // Afrikaans
@@ -321,6 +323,7 @@
CONNECT(m_addLangPrefix, focus_in_event, [this](GdkEventFocus*){ Utils::clear_error_state(m_addLangPrefix); return false; });
CONNECT(m_addLangName, focus_in_event, [this](GdkEventFocus*){ Utils::clear_error_state(m_addLangName); return false; });
CONNECT(m_addLangCode, focus_in_event, [this](GdkEventFocus*){ Utils::clear_error_state(m_addLangCode); return false; });
+ CONNECTS(MAIN->getWidget("combo:config.datadirs").as(), changed, [this](Gtk::ComboBox* combo){ setDataLocations(combo->get_active_row_number()); });
addSetting(new SwitchSettingT("dictinstall", MAIN->getWidget("check:config.settings.dictinstall")));
addSetting(new SwitchSettingT("updatecheck", MAIN->getWidget("check:config.settings.update")));
@@ -328,6 +331,7 @@
addSetting(new SwitchSettingT("systemoutputfont", MAIN->getWidget("checkbutton:config.settings.defaultoutputfont")));
addSetting(new FontSetting("customoutputfont", MAIN->getWidget("fontbutton:config.settings.customoutputfont")));
addSetting(new ComboSetting("outputorient", MAIN->getWidget("combo:config.settings.paneorient")));
+ addSetting(new ComboSetting("datadirs", MAIN->getWidget("combo:config.datadirs")));
}
Config::~Config()
@@ -360,6 +364,21 @@
return result;
}
+bool Config::useSystemDataLocations() const
+{
+ return MAIN->getWidget("combo:config.datadirs").as()->get_active_row_number() == 0;
+}
+
+std::string Config::tessdataLocation() const
+{
+ return MAIN->getWidget("entry:config.tessdatadir").as()->get_text();
+}
+
+std::string Config::spellingLocation() const
+{
+ return MAIN->getWidget("entry:config.spelldir").as()->get_text();
+}
+
void Config::showDialog()
{
toggleAddLanguage(true);
@@ -368,6 +387,70 @@
m_dialog->hide();
}
+void Config::setDataLocations(int idx)
+{
+ if(idx == 0) {
+#ifdef G_OS_WIN32
+ std::string dataDir = Glib::build_filename(pkgDir, "share");
+ Glib::setenv("TESSDATA_PREFIX", dataDir);
+#else
+ std::string dataDir("/usr/share");
+ Glib::unsetenv("TESSDATA_PREFIX");
+#endif
+ Glib::RefPtr dictDir = Gio::File::create_for_path(Glib::build_filename(dataDir, "myspell", "dicts"));
+ if(!dictDir->query_exists()) {
+ dictDir = Gio::File::create_for_path(Glib::build_filename(dataDir, "myspell"));
+ }
+ MAIN->getWidget("entry:config.spelldir").as()->set_text(dictDir->get_path());
+ } else {
+ std::string configDir = Glib::get_user_config_dir();
+ Glib::setenv("TESSDATA_PREFIX", Glib::build_filename(configDir, "tessdata"));
+ MAIN->getWidget("entry:config.spelldir").as()->set_text(Glib::build_filename(configDir, "enchant", "myspell"));
+ }
+ tesseract::TessBaseAPI tess;
+ tess.Init(nullptr, nullptr);
+ MAIN->getWidget("entry:config.tessdatadir").as()->set_text(tess.GetDatapath());
+}
+
+void Config::openTessdataDir()
+{
+ int idx = Gio::Settings::create(APPLICATION_ID)->get_int("datadirs");
+ if(idx == 0) {
+#ifdef G_OS_WIN32
+ std::string dataDir = Glib::build_filename(pkgDir, "share");
+ Glib::setenv("TESSDATA_PREFIX", dataDir);
+#else
+ Glib::unsetenv("TESSDATA_PREFIX");
+#endif
+ } else {
+ std::string configDir = Glib::get_user_config_dir();
+ Glib::setenv("TESSDATA_PREFIX", Glib::build_filename(configDir, "tessdata"));
+ }
+ tesseract::TessBaseAPI tess;
+ tess.Init(nullptr, nullptr);
+ Utils::openUri(Glib::filename_to_uri(tess.GetDatapath()));
+}
+
+void Config::openSpellingDir()
+{
+ int idx = Gio::Settings::create(APPLICATION_ID)->get_int("datadirs");
+ if(idx == 0) {
+#ifdef G_OS_WIN32
+ std::string dataDir = Glib::build_filename(pkgDir, "share");
+#else
+ std::string dataDir("/usr/share");
+#endif
+ Glib::RefPtr dictDir = Gio::File::create_for_path(Glib::build_filename(dataDir, "myspell", "dicts"));
+ if(!dictDir->query_exists()) {
+ dictDir = Gio::File::create_for_path(Glib::build_filename(dataDir, "myspell"));
+ }
+ Utils::openUri(Glib::filename_to_uri(dictDir->get_path()));
+ } else {
+ std::string configDir = Glib::get_user_config_dir();
+ Utils::openUri(Glib::filename_to_uri(Glib::build_filename(configDir, "enchant", "myspell")));
+ }
+}
+
void Config::toggleAddLanguage(bool forceHide)
{
bool addVisible = forceHide ? true : m_addLangBox->get_visible();
diff -Nru gimagereader-3.1.91/gtk/src/Config.hh gimagereader-3.1.99/gtk/src/Config.hh
--- gimagereader-3.1.91/gtk/src/Config.hh 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/Config.hh 2016-10-13 21:45:35.000000000 +0000
@@ -57,6 +57,13 @@
std::vector searchLangCultures(const Glib::ustring& code) const;
void showDialog();
+ bool useSystemDataLocations() const;
+ std::string tessdataLocation() const;
+ std::string spellingLocation() const;
+
+ static void openTessdataDir();
+ static void openSpellingDir();
+
private:
struct LangViewColumns : public Gtk::TreeModel::ColumnRecord {
Gtk::TreeModelColumn prefix;
@@ -89,6 +96,7 @@
void addLanguage();
void removeLanguage();
void langTableSelectionChanged();
+ void setDataLocations(int idx);
void toggleAddLanguage(bool forceHide = false);
};
diff -Nru gimagereader-3.1.91/gtk/src/ConfigSettings.hh gimagereader-3.1.99/gtk/src/ConfigSettings.hh
--- gimagereader-3.1.91/gtk/src/ConfigSettings.hh 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/ConfigSettings.hh 2016-10-13 21:45:35.000000000 +0000
@@ -121,6 +121,23 @@
Gtk::ComboBox* m_widget;
};
+class SpinSetting : public AbstractSetting {
+public:
+ SpinSetting(const Glib::ustring& key, Gtk::SpinButton* widget)
+ : AbstractSetting(key), m_widget(widget)
+ {
+ int value = get_default_settings()->get_int(m_key);
+ m_widget->set_value(value);
+ CONNECT(m_widget, value_changed, [this]{ serialize(); });
+ }
+ void serialize(){
+ get_default_settings()->set_int(m_key, m_widget->get_value());
+ }
+
+private:
+ Gtk::SpinButton* m_widget;
+};
+
class ListStoreSetting : public AbstractSetting {
public:
ListStoreSetting(const Glib::ustring& key, Glib::RefPtr liststore);
diff -Nru gimagereader-3.1.91/gtk/src/Displayer.cc gimagereader-3.1.99/gtk/src/Displayer.cc
--- gimagereader-3.1.91/gtk/src/Displayer.cc 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/Displayer.cc 2016-10-13 21:45:35.000000000 +0000
@@ -87,37 +87,23 @@
void Displayer::drawCanvas(const Cairo::RefPtr &ctx)
{
- if(!m_image){
+ if(!m_imageItem){
return;
}
Gtk::Allocation alloc = m_canvas->get_allocation();
- ctx->save();
- // Set up transformations
- ctx->translate(0.5 * alloc.get_width(), 0.5 * alloc.get_height());
- ctx->rotate(m_currentSource->angle);
- // Set source and apply all transformations to it
- if(!m_blurImage){
- ctx->scale(m_scale, m_scale);
- ctx->translate(-0.5 * m_image->get_width(), -0.5 * m_image->get_height());
- ctx->set_source(m_image, 0, 0);
- }else{
- ctx->scale(m_scale / m_blurScale, m_scale / m_blurScale);
- ctx->translate(-0.5 * m_blurImage->get_width(), -0.5 * m_blurImage->get_height());
- ctx->set_source(m_blurImage, 0, 0);
- }
- ctx->paint();
- // Draw selections
- ctx->restore();
ctx->translate(Utils::round(0.5 * alloc.get_width()), Utils::round(0.5 * alloc.get_height()));
ctx->scale(m_scale, m_scale);
+ m_imageItem->draw(ctx);
for(const DisplayerItem* item : m_items){
- item->draw(ctx);
+ if(item->visible()) {
+ item->draw(ctx);
+ }
}
}
void Displayer::positionCanvas()
{
- Geometry::Rectangle bb = getImageBoundingBox();
+ Geometry::Rectangle bb = getSceneBoundingRect();
m_canvas->set_size_request(Utils::round(bb.width * m_scale), Utils::round(bb.height * m_scale));
// Immediately resize viewport, so that adjustment values are correct below
m_viewport->size_allocate(m_viewport->get_allocation());
@@ -160,7 +146,7 @@
m_renderer = new ImageRenderer(filename);
if(source->resolution == -1) source->resolution = 100;
}
- Utils::set_spin_blocked(m_rotspin, source->angle, m_connection_rotSpinChanged);
+ Utils::set_spin_blocked(m_rotspin, source->angle / M_PI * 180., m_connection_rotSpinChanged);
Utils::set_spin_blocked(m_brispin, source->brightness, m_connection_briSpinChanged);
Utils::set_spin_blocked(m_conspin, source->contrast, m_connection_conSpinChanged);
Utils::set_spin_blocked(m_resspin, source->resolution, m_connection_resSpinChanged);
@@ -186,11 +172,12 @@
m_scale = 1.0;
m_scrollPos[0] = m_scrollPos[1] = 0.5;
if(m_tool) {
- m_tool->pageChanged();
+ m_tool->reset();
}
m_renderTimer.disconnect();
+ delete m_imageItem;
+ m_imageItem = nullptr;
m_image.clear();
- m_blurImage.clear();
delete m_renderer;
m_renderer = 0;
m_currentSource = nullptr;
@@ -237,6 +224,7 @@
m_viewport->get_window()->set_cursor(Gdk::Cursor::create(Gdk::TCROSS));
m_canvas->show();
m_scaleThread = Glib::Threads::Thread::create(sigc::mem_fun(this, &Displayer::scaleThread));
+ m_imageItem = new DisplayerImageItem;
if(!setCurrentPage(1)) {
setSources(std::vector());
@@ -256,6 +244,11 @@
return m_tool->getOCRAreas();
}
+bool Displayer::allowAutodetectOCRAreas() const
+{
+ return m_tool->allowAutodetectOCRAreas();
+}
+
void Displayer::autodetectOCRAreas()
{
m_tool->autodetectOCRAreas();
@@ -270,7 +263,6 @@
m_tool->resolutionChanged(factor);
}
}
- m_blurImage.clear();
m_currentSource->page = m_pageMap[m_pagespin->get_value_as_int()].second;
m_currentSource->brightness = m_brispin->get_value_as_int();
m_currentSource->contrast = m_conspin->get_value_as_int();
@@ -282,6 +274,8 @@
return false;
}
m_image = image;
+ m_imageItem->setImage(m_image);
+ m_imageItem->setRect(Geometry::Rectangle(-0.5 * m_image->get_width(), -0.5 * m_image->get_height(), m_image->get_width(), m_image->get_height()));
setAngle(m_rotspin->get_value());
if(m_scale < 1.0){
ScaleRequest request = {ScaleRequest::Scale, m_scale, m_currentSource->resolution, m_currentSource->page, m_currentSource->brightness, m_currentSource->contrast, m_currentSource->invert};
@@ -300,7 +294,7 @@
m_connection_zoomoneClicked.block(true);
Gtk::Allocation alloc = m_viewport->get_allocation();
- Geometry::Rectangle bb = getImageBoundingBox();
+ Geometry::Rectangle bb = getSceneBoundingRect();
double fit = std::min(alloc.get_width() / bb.width, alloc.get_height() / bb.height);
if(zoom == Zoom::In){
@@ -325,7 +319,7 @@
ScaleRequest request = {ScaleRequest::Scale, m_scale, m_currentSource->resolution, m_currentSource->page, m_currentSource->brightness, m_currentSource->contrast, m_currentSource->invert};
m_scaleTimer = Glib::signal_timeout().connect([this,request]{ sendScaleRequest(request); return false; }, 100);
}else{
- m_blurImage.clear();
+ m_imageItem->setImage(m_image);
}
positionCanvas();
@@ -338,9 +332,10 @@
if(m_image){
angle = angle < 0 ? angle + 360. : angle >= 360 ? angle - 360 : angle,
Utils::set_spin_blocked(m_rotspin, angle, m_connection_rotSpinChanged);
- angle *= 0.0174532925199;
+ angle *= M_PI / 180.;
double delta = angle - m_currentSource->angle;
m_currentSource->angle = angle;
+ m_imageItem->setRotation(angle);
if(m_tool) {
m_tool->rotationChanged(delta);
}
@@ -491,7 +486,42 @@
}
}
-Geometry::Rectangle Displayer::getImageBoundingBox() const
+void Displayer::addItem(DisplayerItem* item)
+{
+ if(!m_items.empty())
+ item->setZIndex(m_items.back()->zIndex() + 1);
+ m_items.push_back(item);
+ item->m_displayer = this;
+ invalidateRect(item->rect());
+}
+
+void Displayer::removeItem(DisplayerItem* item)
+{
+ if(item == m_activeItem) {
+ m_activeItem = nullptr;
+ }
+ m_items.erase(std::remove(m_items.begin(), m_items.end(), item), m_items.end());
+ item->m_displayer = nullptr;
+ invalidateRect(item->rect());
+}
+
+void Displayer::invalidateRect(const Geometry::Rectangle &rect)
+{
+ Gtk::Allocation alloc = m_canvas->get_allocation();
+ Geometry::Rectangle canvasRect = rect;
+ canvasRect.x = (canvasRect.x * m_scale + 0.5 * alloc.get_width()) - 2;
+ canvasRect.y = (canvasRect.y * m_scale + 0.5 * alloc.get_height()) - 2;
+ canvasRect.width = canvasRect.width * m_scale + 4;
+ canvasRect.height = canvasRect.height * m_scale + 4;
+ m_canvas->queue_draw_area(canvasRect.x, canvasRect.y, canvasRect.width, canvasRect.height);
+}
+
+void Displayer::resortItems()
+{
+ std::sort(m_items.begin(), m_items.end(), DisplayerItem::zIndexCmp);
+}
+
+Geometry::Rectangle Displayer::getSceneBoundingRect() const
{
int w = m_image->get_width();
int h = m_image->get_height();
@@ -546,7 +576,7 @@
break;
}else if(req.type == ScaleRequest::Scale){
m_scaleMutex.unlock();
- Cairo::RefPtr image = m_renderer->render(req.page, req.scale * req.resolution);
+ Cairo::RefPtr image = m_renderer->render(req.page, 2 * req.scale * req.resolution);
m_scaleMutex.lock();
if(!m_scaleRequests.empty() && m_scaleRequests.front().type == ScaleRequest::Abort){
@@ -578,8 +608,7 @@
if(!m_scaleRequests.empty() && m_scaleRequests.front().type == ScaleRequest::Abort){
m_scaleRequests.pop();
}else{
- m_blurImage = image;
- m_blurScale = scale;
+ m_imageItem->setImage(image);
m_canvas->queue_draw();
}
m_scaleMutex.unlock();
@@ -587,53 +616,143 @@
///////////////////////////////////////////////////////////////////////////////
-void DisplayerTool::addItemToCanvas(DisplayerItem* item)
+void DisplayerItem::setZIndex(int zIndex)
{
- m_displayer->m_items.push_back(item);
- invalidateRect(item->rect());
+ m_zIndex = zIndex;
+ if(m_displayer) {
+ m_displayer->invalidateRect(m_rect);
+ m_displayer->resortItems();
+ }
}
-void DisplayerTool::removeItemFromCanvas(DisplayerItem* item)
+void DisplayerItem::setRect(const Geometry::Rectangle &rect)
{
- if(item == m_displayer->m_activeItem) {
- m_displayer->m_activeItem = nullptr;
- }
- m_displayer->m_items.erase(std::remove(m_displayer->m_items.begin(), m_displayer->m_items.end(), item), m_displayer->m_items.end());
- invalidateRect(item->rect());
+ Geometry::Rectangle invalidateArea = m_rect.unite(rect);
+ m_rect = rect;
+ if(m_displayer)
+ m_displayer->invalidateRect(invalidateArea);
}
-void DisplayerTool::reorderCanvasItems()
+void DisplayerItem::setVisible(bool visible)
{
- std::sort(m_displayer->m_items.begin(), m_displayer->m_items.end(), DisplayerItem::zIndexCmp);
+ m_visible = visible;
+ update();
}
-Geometry::Rectangle DisplayerTool::getSceneBoundingRect() const
+void DisplayerItem::update()
{
- return m_displayer->getImageBoundingBox();
+ if(m_displayer)
+ m_displayer->invalidateRect(m_rect);
}
-void DisplayerTool::invalidateRect(const Geometry::Rectangle& rect)
+void DisplayerImageItem::draw(Cairo::RefPtr ctx) const
{
- Gtk::Allocation alloc = m_displayer->m_canvas->get_allocation();
- Geometry::Rectangle canvasRect = rect;
- canvasRect.x = (canvasRect.x * m_displayer->m_scale + 0.5 * alloc.get_width()) - 2;
- canvasRect.y = (canvasRect.y * m_displayer->m_scale + 0.5 * alloc.get_height()) - 2;
- canvasRect.width = canvasRect.width * m_displayer->m_scale + 4;
- canvasRect.height = canvasRect.height * m_displayer->m_scale + 4;
- m_displayer->m_canvas->queue_draw_area(canvasRect.x, canvasRect.y, canvasRect.width, canvasRect.height);
+ if(m_image) {
+ double sx = rect().width / m_image->get_width();
+ double sy = rect().height / m_image->get_height();
+ ctx->save();
+ ctx->rotate(m_rotation);
+ ctx->scale(sx, sy);
+ ctx->translate(rect().x / sx, rect().y / sy);
+ ctx->set_source(m_image, 0, 0);
+ ctx->paint();
+ ctx->restore();
+ }
}
-Geometry::Point DisplayerTool::mapToSceneClamped(const Geometry::Point& point)
+
+void DisplayerSelection::draw(Cairo::RefPtr ctx) const
{
- return m_displayer->mapToSceneClamped(point);
+ Gdk::RGBA bgcolor("#4A90D9");
+
+ double scale = displayer()->getCurrentScale();
+
+ double d = 0.5 / scale;
+ double x1 = Utils::round(rect().x * scale) / scale + d;
+ double y1 = Utils::round(rect().y * scale) / scale + d;
+ double x2 = Utils::round((rect().x + rect().width) * scale) / scale - d;
+ double y2 = Utils::round((rect().y + rect().height) * scale) / scale - d;
+ Geometry::Rectangle paintrect(x1, y1, x2 - x1, y2 - y1);
+ ctx->save();
+ // Semitransparent rectangle with frame
+ ctx->set_line_width(2. * d);
+ ctx->rectangle(paintrect.x, paintrect.y, paintrect.width, paintrect.height);
+ ctx->set_source_rgba(bgcolor.get_red(), bgcolor.get_green(), bgcolor.get_blue(), 0.25);
+ ctx->fill_preserve();
+ ctx->set_source_rgba(bgcolor.get_red(), bgcolor.get_green(), bgcolor.get_blue(), 1.0);
+ ctx->stroke();
+ ctx->restore();
}
-Cairo::RefPtr DisplayerTool::getImage(const Geometry::Rectangle& rect)
+bool DisplayerSelection::mousePressEvent(GdkEventButton *event)
{
- return m_displayer->getImage(rect);
+ if(event->button == 1) {
+ Geometry::Point p = displayer()->mapToSceneClamped(Geometry::Point(event->x, event->y));
+ double tol = 10.0 / displayer()->getCurrentScale();
+ m_resizeOffset = Geometry::Point(0., 0.);
+ if(std::abs(m_point.x - p.x) < tol){ // pointx
+ m_resizeHandlers.push_back(resizePointX);
+ m_resizeOffset.x = p.x - m_point.x;
+ }else if(std::abs(m_anchor.x - p.x) < tol){ // anchorx
+ m_resizeHandlers.push_back(resizeAnchorX);
+ m_resizeOffset.x = p.x - m_anchor.x;
+ }
+ if(std::abs(m_point.y - p.y) < tol){ // pointy
+ m_resizeHandlers.push_back(resizePointY);
+ m_resizeOffset.y = p.y - m_point.y;
+ }else if(std::abs(m_anchor.y - p.y) < tol){ // anchory
+ m_resizeHandlers.push_back(resizeAnchorY);
+ m_resizeOffset.y = p.y - m_anchor.y;
+ }
+ return true;
+ } else if(event->button == 3) {
+ showContextMenu(event);
+ }
+ return false;
+}
+
+bool DisplayerSelection::mouseReleaseEvent(GdkEventButton */*event*/)
+{
+ m_resizeHandlers.clear();
+ return false;
}
-double DisplayerTool::getDisplayScale() const
+bool DisplayerSelection::mouseMoveEvent(GdkEventMotion *event)
{
- return m_displayer->m_scale;
+ Geometry::Point p = displayer()->mapToSceneClamped(Geometry::Point(event->x, event->y));
+ if(m_resizeHandlers.empty()) {
+ double tol = 10.0 / displayer()->getCurrentScale();
+
+ bool left = std::abs(rect().x - p.x) < tol;
+ bool right = std::abs(rect().x + rect().width - p.x) < tol;
+ bool top = std::abs(rect().y - p.y) < tol;
+ bool bottom = std::abs(rect().y + rect().height - p.y) < tol;
+
+ if((top && left) || (bottom && right)){
+ displayer()->setCursor(Gdk::Cursor::create(MAIN->getWindow()->get_display(), "nwse-resize"));
+ }else if((top && right) || (bottom && left)){
+ displayer()->setCursor(Gdk::Cursor::create(MAIN->getWindow()->get_display(), "nesw-resize"));
+ }else if(top || bottom){
+ displayer()->setCursor(Gdk::Cursor::create(MAIN->getWindow()->get_display(), "ns-resize"));
+ }else if(left || right){
+ displayer()->setCursor(Gdk::Cursor::create(MAIN->getWindow()->get_display(), "ew-resize"));
+ }else{
+ displayer()->setCursor(Glib::RefPtr(0));
+ }
+ }
+ Geometry::Point movePos(p.x - m_resizeOffset.x, p.y - m_resizeOffset.y);
+ Geometry::Rectangle bb = displayer()->getSceneBoundingRect();
+ movePos.x = std::min(std::max(bb.x, movePos.x), bb.x + bb.width);
+ movePos.y = std::min(std::max(bb.y, movePos.y), bb.y + bb.height);
+ if(!m_resizeHandlers.empty()){
+ for(const ResizeHandler& handler : m_resizeHandlers){
+ handler(movePos, m_anchor, m_point);
+ }
+ setRect(Geometry::Rectangle(m_anchor, m_point));
+ m_signalGeometryChanged.emit(rect());
+ displayer()->ensureVisible(event->x, event->y);
+ return true;
+ }
+ return false;
}
+
diff -Nru gimagereader-3.1.91/gtk/src/Displayer.hh gimagereader-3.1.99/gtk/src/Displayer.hh
--- gimagereader-3.1.91/gtk/src/Displayer.hh 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/Displayer.hh 2016-10-13 21:45:35.000000000 +0000
@@ -29,6 +29,7 @@
#include
class DisplayerItem;
+class DisplayerImageItem;
class DisplayerTool;
class DisplayRenderer;
class Source;
@@ -42,20 +43,29 @@
int getCurrentPage() const{ return m_pagespin->get_value_as_int(); }
bool setCurrentPage(int page);
double getCurrentAngle() const{ return m_rotspin->get_value(); }
+ double getCurrentScale() const{ return m_scale; }
void setAngle(double angle);
int getCurrentResolution(){ return m_resspin->get_value_as_int(); }
void setResolution(int resolution);
std::string getCurrentImage(int& page) const;
+ Cairo::RefPtr getImage(const Geometry::Rectangle& rect) const;
+ Geometry::Rectangle getSceneBoundingRect() const;
+ Geometry::Point mapToSceneClamped(const Geometry::Point& p) const;
int getNPages(){ double min, max; m_pagespin->get_range(min, max); return int(max); }
bool hasMultipleOCRAreas();
std::vector> getOCRAreas();
+ bool allowAutodetectOCRAreas() const;
void autodetectOCRAreas();
void setCursor(Glib::RefPtr cursor);
void ensureVisible(double evx, double evy);
-private:
- friend class DisplayerTool;
+ void addItem(DisplayerItem* item);
+ void removeItem(DisplayerItem* item);
+ void invalidateRect(const Geometry::Rectangle& rect);
+ void resortItems();
+
+private:
enum class Zoom { In, Out, Fit, One };
Gtk::DrawingArea* m_canvas;
@@ -82,6 +92,7 @@
double m_scale = 1.0;
double m_scrollPos[2] = {0.5, 0.5};
DisplayerTool* m_tool = nullptr;
+ DisplayerImageItem* m_imageItem = nullptr;
std::vector m_items;
DisplayerItem* m_activeItem = nullptr;
double m_panPos[2] = {0., 0.};
@@ -102,9 +113,6 @@
bool mouseReleaseEvent(GdkEventButton* ev);
bool scrollEvent(GdkEventScroll* ev);
- Cairo::RefPtr getImage(const Geometry::Rectangle& rect) const;
- Geometry::Rectangle getImageBoundingBox() const;
- Geometry::Point mapToSceneClamped(const Geometry::Point& p) const;
void setZoom(Zoom zoom);
bool renderImage();
@@ -124,8 +132,6 @@
ScaleRequest(Request _type, double _scale = 0., int _resolution = 0, int _page = 0, int _brightness = 0, int _contrast = 0, bool _invert = 0)
: type(_type), scale(_scale), resolution(_resolution), page(_page), brightness(_brightness), contrast(_contrast), invert(_invert) {}
};
- Cairo::RefPtr m_blurImage;
- double m_blurScale;
Glib::Threads::Thread* m_scaleThread = nullptr;
Glib::Threads::Mutex m_scaleMutex;
Glib::Threads::Cond m_scaleCond;
@@ -139,11 +145,18 @@
class DisplayerItem {
public:
+ friend class Displayer;
virtual ~DisplayerItem(){}
- void setZIndex(int zIndex) { m_zIndex = zIndex; }
- void setRect(const Geometry::Rectangle& rect) { m_rect = rect; }
+ Displayer* displayer() const{ return m_displayer; }
+
+ void setZIndex(int zIndex);
+ double zIndex() const{ return m_zIndex; }
+ void setRect(const Geometry::Rectangle& rect);
const Geometry::Rectangle& rect() const{ return m_rect; }
+ void setVisible(bool visible);
+ bool visible() const{ return m_visible; }
+ void update();
virtual void draw(Cairo::RefPtr ctx) const = 0;
virtual bool mousePressEvent(GdkEventButton */*event*/) { return false; }
@@ -154,11 +167,82 @@
return lhs->m_zIndex < rhs->m_zIndex;
}
-protected:
- int m_zIndex = 0;
+private:
+ Displayer* m_displayer = nullptr;
Geometry::Rectangle m_rect;
+ int m_zIndex = 0;
+ bool m_visible = true;
+};
+
+class DisplayerImageItem : public DisplayerItem
+{
+public:
+ using DisplayerItem::DisplayerItem;
+ void draw(Cairo::RefPtr ctx) const override;
+ void setImage(Cairo::RefPtr image) { m_image = image; }
+ void setRotation(double rotation) { m_rotation = rotation; }
+
+protected:
+ Cairo::RefPtr m_image;
+ double m_rotation = 0.;
};
+class DisplayerSelection : public DisplayerItem
+{
+public:
+ DisplayerSelection(DisplayerTool* tool, const Geometry::Point& anchor)
+ : m_tool(tool), m_anchor(anchor), m_point(anchor)
+ {
+ setRect(Geometry::Rectangle(anchor, anchor));
+ }
+ void setPoint(const Geometry::Point& point){
+ m_point = point;
+ setRect(Geometry::Rectangle(m_anchor, m_point));
+ }
+ void setAnchorAndPoint(const Geometry::Point& anchor, const Geometry::Point& point) {
+ m_anchor = anchor;
+ m_point = point;
+ setRect(Geometry::Rectangle(m_anchor, m_point));
+ }
+ void rotate(const Geometry::Rotation &R){
+ m_anchor = R.rotate(m_anchor);
+ m_point = R.rotate(m_point);
+ setRect(Geometry::Rectangle(m_anchor, m_point));
+ }
+ void scale(double factor){
+ m_anchor = Geometry::Point(m_anchor.x * factor, m_anchor.y * factor);
+ m_point = Geometry::Point(m_point.x * factor, m_point.y * factor);
+ }
+ sigc::signal signal_geometry_changed(){
+ return m_signalGeometryChanged;
+ }
+
+ void draw(Cairo::RefPtr ctx) const override;
+ bool mousePressEvent(GdkEventButton *event) override;
+ bool mouseReleaseEvent(GdkEventButton *event) override;
+ bool mouseMoveEvent(GdkEventMotion *event) override;
+
+protected:
+ DisplayerTool* m_tool;
+
+ virtual void showContextMenu(GdkEventButton* /*event*/) {}
+
+private:
+ typedef void(*ResizeHandler)(const Geometry::Point&, Geometry::Point&, Geometry::Point&);
+
+ Geometry::Point m_anchor;
+ Geometry::Point m_point;
+ std::vector m_resizeHandlers;
+ Geometry::Point m_resizeOffset;
+ sigc::signal m_signalGeometryChanged;
+
+ static void resizeAnchorX(const Geometry::Point& pos, Geometry::Point& anchor, Geometry::Point& /*point*/){ anchor.x = pos.x; }
+ static void resizeAnchorY(const Geometry::Point& pos, Geometry::Point& anchor, Geometry::Point& /*point*/){ anchor.y = pos.y; }
+ static void resizePointX(const Geometry::Point& pos, Geometry::Point& /*anchor*/, Geometry::Point& point){ point.x = pos.x; }
+ static void resizePointY(const Geometry::Point& pos, Geometry::Point& /*anchor*/, Geometry::Point& point){ point.y = pos.y; }
+};
+
+
class DisplayerTool {
public:
DisplayerTool(Displayer* displayer) : m_displayer(displayer) {}
@@ -171,19 +255,12 @@
virtual void rotationChanged(double /*delta*/){}
virtual std::vector> getOCRAreas() = 0;
virtual bool hasMultipleOCRAreas() const{ return false; }
+ virtual bool allowAutodetectOCRAreas() const{ return false; }
virtual void autodetectOCRAreas(){}
+ virtual void reset(){}
protected:
Displayer* m_displayer;
-
- void addItemToCanvas(DisplayerItem* item);
- void removeItemFromCanvas(DisplayerItem* item);
- void reorderCanvasItems();
- Geometry::Rectangle getSceneBoundingRect() const;
- void invalidateRect(const Geometry::Rectangle& rect);
- Geometry::Point mapToSceneClamped(const Geometry::Point& point);
- Cairo::RefPtr getImage(const Geometry::Rectangle& rect);
- double getDisplayScale() const;
};
#endif // IMAGEDISPLAYER_HH
diff -Nru gimagereader-3.1.91/gtk/src/DisplayerToolHOCR.cc gimagereader-3.1.99/gtk/src/DisplayerToolHOCR.cc
--- gimagereader-3.1.91/gtk/src/DisplayerToolHOCR.cc 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/DisplayerToolHOCR.cc 2016-10-13 21:45:35.000000000 +0000
@@ -22,34 +22,6 @@
#include "Recognizer.hh"
#include "Utils.hh"
-class DisplayerToolHOCR::SelectionRect : public DisplayerItem {
-public:
- SelectionRect(DisplayerToolHOCR* tool) : m_tool(tool) {}
-
- void draw(Cairo::RefPtr ctx) const override {
- Gdk::RGBA bgcolor("#4A90D9");
-
- double scale = m_tool->getDisplayScale();
-
- double d = 0.5 / scale;
- double x1 = Utils::round(m_rect.x * scale) / scale + d;
- double y1 = Utils::round(m_rect.y * scale) / scale + d;
- double x2 = Utils::round((m_rect.x + m_rect.width) * scale) / scale - d;
- double y2 = Utils::round((m_rect.y + m_rect.height) * scale) / scale - d;
- Geometry::Rectangle rect(x1, y1, x2 - x1, y2 - y1);
- ctx->save();
- // Semitransparent rectangle with frame
- ctx->set_line_width(2. * d);
- ctx->rectangle(rect.x, rect.y, rect.width, rect.height);
- ctx->set_source_rgba(bgcolor.get_red(), bgcolor.get_green(), bgcolor.get_blue(), 0.25);
- ctx->fill_preserve();
- ctx->set_source_rgba(bgcolor.get_red(), bgcolor.get_green(), bgcolor.get_blue(), 1.0);
- ctx->stroke();
- ctx->restore();
- }
-private:
- DisplayerToolHOCR* m_tool;
-};
DisplayerToolHOCR::DisplayerToolHOCR(Displayer *displayer)
: DisplayerTool(displayer)
@@ -65,33 +37,80 @@
std::vector> DisplayerToolHOCR::getOCRAreas()
{
std::vector> surfaces;
- surfaces.push_back(getImage(getSceneBoundingRect()));
+ surfaces.push_back(m_displayer->getImage(m_displayer->getSceneBoundingRect()));
return surfaces;
}
+bool DisplayerToolHOCR::mousePressEvent(GdkEventButton* event)
+{
+ if(event->button == 1 && m_drawingSelection){
+ clearSelection();
+ m_selection = new DisplayerSelection(this, m_displayer->mapToSceneClamped(Geometry::Point(event->x, event->y)));
+ m_displayer->addItem(m_selection);
+ return true;
+ }
+ return false;
+}
+
+bool DisplayerToolHOCR::mouseMoveEvent(GdkEventMotion* event)
+{
+ if(m_selection && m_drawingSelection){
+ Geometry::Point p = m_displayer->mapToSceneClamped(Geometry::Point(event->x, event->y));
+ m_selection->setPoint(p);
+ m_displayer->ensureVisible(event->x, event->y);
+ return true;
+ }
+ return false;
+}
+
+bool DisplayerToolHOCR::mouseReleaseEvent(GdkEventButton* /*event*/)
+{
+ if(m_selection && m_drawingSelection){
+ if(m_selection->rect().width < 5. || m_selection->rect().height < 5.){
+ clearSelection();
+ }else{
+ Geometry::Rectangle sceneRect = m_displayer->getSceneBoundingRect();
+ Geometry::Rectangle r = m_selection->rect().translate(-sceneRect.x, -sceneRect.y);
+ m_signalSelectionDrawn.emit(Geometry::Rectangle(int(r.x), int(r.y), int(r.width), int(r.height)));
+ }
+ m_drawingSelection = false;
+ return true;
+ }
+ m_drawingSelection = false;
+ return false;
+}
+
void DisplayerToolHOCR::setSelection(const Geometry::Rectangle& rect)
{
+ m_drawingSelection = false;
+ Geometry::Rectangle sceneRect = m_displayer->getSceneBoundingRect();
+ Geometry::Rectangle r = rect.translate(sceneRect.x, sceneRect.y);
if(!m_selection) {
- m_selection = new SelectionRect(this);
- addItemToCanvas(m_selection);
+ m_selection = new DisplayerSelection(this, Geometry::Point(r.x, r.y));
+ CONNECT(m_selection, geometry_changed, [this](const Geometry::Rectangle& rect){ selectionChanged(rect); });
+ m_displayer->addItem(m_selection);
}
- Geometry::Rectangle sceneRect = getSceneBoundingRect();
- invalidateRect(m_selection->rect());
- m_selection->setRect(rect.translate(sceneRect.x, sceneRect.y));
- invalidateRect(m_selection->rect());
+ m_selection->setAnchorAndPoint(Geometry::Point(r.x, r.y), Geometry::Point(r.x + r.width, r.y + r.height));
}
Cairo::RefPtr DisplayerToolHOCR::getSelection(const Geometry::Rectangle& rect)
{
- Geometry::Rectangle sceneRect = getSceneBoundingRect();
- return getImage(rect.translate(sceneRect.x, sceneRect.y));
+ Geometry::Rectangle sceneRect = m_displayer->getSceneBoundingRect();
+ return m_displayer->getImage(rect.translate(sceneRect.x, sceneRect.y));
}
void DisplayerToolHOCR::clearSelection()
{
if(m_selection) {
- removeItemFromCanvas(m_selection);
+ m_displayer->removeItem(m_selection);
delete m_selection;
m_selection = nullptr;
}
}
+
+void DisplayerToolHOCR::selectionChanged(const Geometry::Rectangle& rect)
+{
+ Geometry::Rectangle sceneRect = m_displayer->getSceneBoundingRect();
+ Geometry::Rectangle r = rect.translate(-sceneRect.x, -sceneRect.y);
+ m_signalSelectionGeometryChanged.emit(Geometry::Rectangle(int(r.x), int(r.y), int(r.width), int(r.height)));
+}
diff -Nru gimagereader-3.1.91/gtk/src/DisplayerToolHOCR.hh gimagereader-3.1.99/gtk/src/DisplayerToolHOCR.hh
--- gimagereader-3.1.91/gtk/src/DisplayerToolHOCR.hh 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/DisplayerToolHOCR.hh 2016-10-13 21:45:35.000000000 +0000
@@ -30,18 +30,30 @@
DisplayerToolHOCR(Displayer* displayer);
~DisplayerToolHOCR();
- std::vector> getOCRAreas();
- virtual void pageChanged(){ clearSelection(); }
- virtual void resolutionChanged(double /*factor*/){ clearSelection(); }
- virtual void rotationChanged(double /*delta*/){ clearSelection(); }
+ std::vector> getOCRAreas() override;
+ void pageChanged() override{ clearSelection(); }
+ void resolutionChanged(double /*factor*/) override{ clearSelection(); }
+ void rotationChanged(double /*delta*/) override{ clearSelection(); }
+ void reset() override{ clearSelection(); }
+ bool mousePressEvent(GdkEventButton *event) override;
+ bool mouseMoveEvent(GdkEventMotion *event) override;
+ bool mouseReleaseEvent(GdkEventButton *event) override;
+
+ void activateDrawSelection(){ m_drawingSelection = true; }
void setSelection(const Geometry::Rectangle& rect);
Cairo::RefPtr getSelection(const Geometry::Rectangle& rect);
void clearSelection();
+ sigc::signal signal_selection_drawn(){ return m_signalSelectionDrawn; }
+ sigc::signal signal_selection_geometry_changed(){ return m_signalSelectionGeometryChanged; }
private:
- class SelectionRect;
- SelectionRect* m_selection = nullptr;
+ DisplayerSelection* m_selection = nullptr;
+ bool m_drawingSelection = false;
+ sigc::signal m_signalSelectionDrawn;
+ sigc::signal m_signalSelectionGeometryChanged;
+
+ void selectionChanged(const Geometry::Rectangle& rect);
};
#endif // DISPLAYERTOOLHOCR_HH
diff -Nru gimagereader-3.1.91/gtk/src/DisplayerToolSelect.cc gimagereader-3.1.99/gtk/src/DisplayerToolSelect.cc
--- gimagereader-3.1.91/gtk/src/DisplayerToolSelect.cc 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/DisplayerToolSelect.cc 2016-10-13 21:45:35.000000000 +0000
@@ -58,10 +58,9 @@
if((event->state & Gdk::CONTROL_MASK) == 0){
clearSelections();
}
- m_curSel = new DisplaySelection(this, 1 + m_selections.size(), mapToSceneClamped(Geometry::Point(event->x, event->y)));
+ m_curSel = new NumberedDisplayerSelection(this, 1 + m_selections.size(), m_displayer->mapToSceneClamped(Geometry::Point(event->x, event->y)));
m_curSel->setZIndex(1 + m_selections.size());
- reorderCanvasItems();
- addItemToCanvas(m_curSel);
+ m_displayer->addItem(m_curSel);
return true;
}
return false;
@@ -70,11 +69,9 @@
bool DisplayerToolSelect::mouseMoveEvent(GdkEventMotion* event)
{
if(m_curSel){
- Geometry::Point p = mapToSceneClamped(Geometry::Point(event->x, event->y));
- Geometry::Rectangle oldRect = m_curSel->rect();
+ Geometry::Point p = m_displayer->mapToSceneClamped(Geometry::Point(event->x, event->y));
m_curSel->setPoint(p);
m_displayer->ensureVisible(event->x, event->y);
- invalidateRect(oldRect.unite(m_curSel->rect()));
return true;
}
return false;
@@ -84,7 +81,7 @@
{
if(m_curSel){
if(m_curSel->rect().width < 5. || m_curSel->rect().height < 5.){
- removeItemFromCanvas(m_curSel);
+ m_displayer->removeItem(m_curSel);
delete m_curSel;
}else{
m_selections.push_back(m_curSel);
@@ -96,14 +93,9 @@
return false;
}
-void DisplayerToolSelect::pageChanged()
-{
- clearSelections();
-}
-
void DisplayerToolSelect::resolutionChanged(double factor)
{
- for(DisplaySelection* sel : m_selections){
+ for(NumberedDisplayerSelection* sel : m_selections){
sel->scale(factor);
}
}
@@ -111,7 +103,7 @@
void DisplayerToolSelect::rotationChanged(double delta)
{
Geometry::Rotation R(delta);
- for(DisplaySelection* sel : m_selections){
+ for(NumberedDisplayerSelection* sel : m_selections){
sel->rotate(R);
}
}
@@ -120,10 +112,10 @@
{
std::vector> images;
if(m_selections.empty()){
- images.push_back(getImage(getSceneBoundingRect()));
+ images.push_back(m_displayer->getImage(m_displayer->getSceneBoundingRect()));
}else{
- for(const DisplaySelection* sel : m_selections){
- images.push_back(getImage(sel->rect()));
+ for(const NumberedDisplayerSelection* sel : m_selections){
+ images.push_back(m_displayer->getImage(sel->rect()));
}
}
return images;
@@ -131,8 +123,8 @@
void DisplayerToolSelect::clearSelections()
{
- for(DisplaySelection* sel : m_selections) {
- removeItemFromCanvas(sel);
+ for(NumberedDisplayerSelection* sel : m_selections) {
+ m_displayer->removeItem(sel);
delete sel;
}
m_selections.clear();
@@ -141,33 +133,29 @@
void DisplayerToolSelect::removeSelection(int num)
{
- removeItemFromCanvas(m_selections[num - 1]);
+ m_displayer->removeItem(m_selections[num - 1]);
delete m_selections[num - 1];
m_selections.erase(m_selections.begin() + num - 1);
for(int i = 0, n = m_selections.size(); i < n; ++i){
m_selections[i]->setNumber(1 + i);
m_selections[i]->setZIndex(1 + i);
- reorderCanvasItems();
- invalidateRect(m_selections[i]->rect());
}
}
void DisplayerToolSelect::reorderSelection(int oldNum, int newNum)
{
- DisplaySelection* sel = m_selections[oldNum - 1];
+ NumberedDisplayerSelection* sel = m_selections[oldNum - 1];
m_selections.erase(m_selections.begin() + oldNum - 1);
m_selections.insert(m_selections.begin() + newNum - 1, sel);
for(int i = 0, n = m_selections.size(); i < n; ++i){
m_selections[i]->setNumber(1 + i);
m_selections[i]->setZIndex(1 + i);
- reorderCanvasItems();
- invalidateRect(m_selections[i]->rect());
}
}
-void DisplayerToolSelect::saveSelection(DisplaySelection* selection)
+void DisplayerToolSelect::saveSelection(NumberedDisplayerSelection* selection)
{
- Cairo::RefPtr img = getImage(selection->rect());
+ Cairo::RefPtr img = m_displayer->getImage(selection->rect());
std::string filename = Utils::make_output_filename(MAIN->getConfig()->getSetting>("selectionsavefile")->getValue());
FileDialogs::FileFilter filter = {_("PNG Images"), {"image/png"}, {"*.png"}};
filename = FileDialogs::save_dialog(_("Save Selection Image"), filename, filter);
@@ -189,7 +177,7 @@
double avgDeskew = 0.;
int nDeskew = 0;
std::vector rects;
- Cairo::RefPtr img = getImage(getSceneBoundingRect());
+ Cairo::RefPtr img = m_displayer->getImage(m_displayer->getSceneBoundingRect());
// Perform layout analysis
Utils::busyTask([this,&nDeskew,&avgDeskew,&rects,&img]{
@@ -237,9 +225,9 @@
}
}
for(int i = 0, n = rects.size(); i < n; ++i){
- m_selections.push_back(new DisplaySelection(this, 1 + i, Geometry::Point(rects[i].x, rects[i].y)));
+ m_selections.push_back(new NumberedDisplayerSelection(this, 1 + i, Geometry::Point(rects[i].x, rects[i].y)));
m_selections.back()->setPoint(Geometry::Point(rects[i].x + rects[i].width, rects[i].y + rects[i].height));
- addItemToCanvas(m_selections.back());
+ m_displayer->addItem(m_selections.back());
}
updateRecognitionModeLabel();
}
@@ -247,30 +235,30 @@
///////////////////////////////////////////////////////////////////////////////
-void DisplaySelection::showContextMenu(GdkEventButton* event){
+void NumberedDisplayerSelection::showContextMenu(GdkEventButton* event){
Gtk::Window* selmenu = MAIN->getWidget("window:selectionmenu");
Gtk::SpinButton* spin = MAIN->getWidget("spin:selectionmenu.order");
- spin->get_adjustment()->set_upper(m_selectTool->m_selections.size());
+ spin->get_adjustment()->set_upper(static_cast(m_tool)->m_selections.size());
spin->get_adjustment()->set_value(m_number);
Glib::RefPtr loop = Glib::MainLoop::create();
std::vector selmenuConnections = {
CONNECT(spin, value_changed, [&]{ reorderSelection(spin->get_value()); }),
CONNECT(MAIN->getWidget("button:selectionmenu.delete").as(), clicked, [&]{
loop->quit();
- m_selectTool->removeSelection(m_number);
+ static_cast(m_tool)->removeSelection(m_number);
}),
CONNECT(MAIN->getWidget("button:selectionmenu.recognize").as(), clicked, [&]{
loop->quit();
- MAIN->getRecognizer()->recognizeImage(m_selectTool->getImage(rect()), Recognizer::OutputDestination::Buffer);
+ MAIN->getRecognizer()->recognizeImage(displayer()->getImage(rect()), Recognizer::OutputDestination::Buffer);
}),
CONNECT(MAIN->getWidget("button:selectionmenu.clipboard").as(), clicked, [&]{
loop->quit();
- MAIN->getRecognizer()->recognizeImage(m_selectTool->getImage(rect()), Recognizer::OutputDestination::Clipboard);
+ MAIN->getRecognizer()->recognizeImage(displayer()->getImage(rect()), Recognizer::OutputDestination::Clipboard);
}),
CONNECT(MAIN->getWidget("button:selectionmenu.save").as(), clicked, [&]{
loop->quit();
selmenu->hide(); // Explicitly hide here to avoid conflicts with file dialog which pops up
- m_selectTool->saveSelection(this);
+ static_cast(m_tool)->saveSelection(this);
}),
CONNECT(selmenu, button_press_event, [&](GdkEventButton* ev){
Gtk::Allocation a = selmenu->get_allocation();
@@ -311,118 +299,40 @@
#endif
}
-void DisplaySelection::reorderSelection(int newNumber)
+void NumberedDisplayerSelection::reorderSelection(int newNumber)
{
- m_selectTool->reorderSelection(m_number, newNumber);
+ static_cast(m_tool)->reorderSelection(m_number, newNumber);
}
-void DisplaySelection::draw(Cairo::RefPtr ctx) const
+void NumberedDisplayerSelection::draw(Cairo::RefPtr ctx) const
{
+ DisplayerSelection::draw(ctx);
+
Gdk::RGBA fgcolor("#FFFFFF");
Gdk::RGBA bgcolor("#4A90D9");
- double scale = m_selectTool->getDisplayScale();
+ double scale = displayer()->getCurrentScale();
Glib::ustring idx = Glib::ustring::compose("%1", m_number);
double d = 0.5 / scale;
- double x1 = Utils::round(m_rect.x * scale) / scale + d;
- double y1 = Utils::round(m_rect.y * scale) / scale + d;
- double x2 = Utils::round((m_rect.x + m_rect.width) * scale) / scale - d;
- double y2 = Utils::round((m_rect.y + m_rect.height) * scale) / scale - d;
- Geometry::Rectangle rect(x1, y1, x2 - x1, y2 - y1);
+ double x1 = Utils::round(rect().x * scale) / scale + d;
+ double y1 = Utils::round(rect().y * scale) / scale + d;
+ double x2 = Utils::round((rect().x + rect().width) * scale) / scale - d;
+ double y2 = Utils::round((rect().y + rect().height) * scale) / scale - d;
+ Geometry::Rectangle paintrect(x1, y1, x2 - x1, y2 - y1);
ctx->save();
- // Semitransparent rectangle with frame
- ctx->set_line_width(2. * d);
- ctx->rectangle(rect.x, rect.y, rect.width, rect.height);
- ctx->set_source_rgba(bgcolor.get_red(), bgcolor.get_green(), bgcolor.get_blue(), 0.25);
- ctx->fill_preserve();
- ctx->set_source_rgba(bgcolor.get_red(), bgcolor.get_green(), bgcolor.get_blue(), 1.0);
- ctx->stroke();
// Text box
- double w = std::min(std::min(40. * d, rect.width), rect.height);
- ctx->rectangle(rect.x, rect.y, w - d, w - d);
+ double w = std::min(std::min(40. * d, paintrect.width), paintrect.height);
+ ctx->rectangle(paintrect.x, paintrect.y, w - d, w - d);
ctx->set_source_rgba(bgcolor.get_red(), bgcolor.get_green(), bgcolor.get_blue(), 1.0);
ctx->fill();
// Text
ctx->select_font_face("sans", Cairo::FONT_SLANT_NORMAL, Cairo::FONT_WEIGHT_BOLD);
ctx->set_font_size(0.7 * w);
Cairo::TextExtents ext; ctx->get_text_extents(idx, ext);
- ctx->translate(rect.x + .5 * w, rect.y + .5 * w);
+ ctx->translate(paintrect.x + .5 * w, paintrect.y + .5 * w);
ctx->translate(-ext.x_bearing - .5 * ext.width, -ext.y_bearing - .5 * ext.height);
ctx->set_source_rgba(fgcolor.get_red(), fgcolor.get_green(), bgcolor.get_blue(), 1.0);
ctx->show_text(idx);
ctx->restore();
}
-
-bool DisplaySelection::mousePressEvent(GdkEventButton *event)
-{
- if(event->button == 1) {
- Geometry::Point p = m_selectTool->mapToSceneClamped(Geometry::Point(event->x, event->y));
- double tol = 10.0 / m_selectTool->getDisplayScale();
- m_resizeOffset = Geometry::Point(0., 0.);
- if(std::abs(m_point.x - p.x) < tol){ // pointx
- m_resizeHandlers.push_back(resizePointX);
- m_resizeOffset.x = p.x - m_point.x;
- }else if(std::abs(m_anchor.x - p.x) < tol){ // anchorx
- m_resizeHandlers.push_back(resizeAnchorX);
- m_resizeOffset.x = p.x - m_anchor.x;
- }
- if(std::abs(m_point.y - p.y) < tol){ // pointy
- m_resizeHandlers.push_back(resizePointY);
- m_resizeOffset.y = p.y - m_point.y;
- }else if(std::abs(m_anchor.y - p.y) < tol){ // anchory
- m_resizeHandlers.push_back(resizeAnchorY);
- m_resizeOffset.y = p.y - m_anchor.y;
- }
- return true;
- } else if(event->button == 3) {
- showContextMenu(event);
- }
- return false;
-}
-
-bool DisplaySelection::mouseReleaseEvent(GdkEventButton */*event*/)
-{
- m_resizeHandlers.clear();
- return false;
-}
-
-bool DisplaySelection::mouseMoveEvent(GdkEventMotion *event)
-{
- Geometry::Point p = m_selectTool->mapToSceneClamped(Geometry::Point(event->x, event->y));
- if(m_resizeHandlers.empty()) {
- double tol = 10.0 / m_selectTool->getDisplayScale();
-
- bool left = std::abs(m_rect.x - p.x) < tol;
- bool right = std::abs(m_rect.x + m_rect.width - p.x) < tol;
- bool top = std::abs(m_rect.y - p.y) < tol;
- bool bottom = std::abs(m_rect.y + m_rect.height - p.y) < tol;
-
- if((top && left) || (bottom && right)){
- m_selectTool->m_displayer->setCursor(Gdk::Cursor::create(MAIN->getWindow()->get_display(), "nwse-resize"));
- }else if((top && right) || (bottom && left)){
- m_selectTool->m_displayer->setCursor(Gdk::Cursor::create(MAIN->getWindow()->get_display(), "nesw-resize"));
- }else if(top || bottom){
- m_selectTool->m_displayer->setCursor(Gdk::Cursor::create(MAIN->getWindow()->get_display(), "ns-resize"));
- }else if(left || right){
- m_selectTool->m_displayer->setCursor(Gdk::Cursor::create(MAIN->getWindow()->get_display(), "ew-resize"));
- }else{
- m_selectTool->m_displayer->setCursor(Glib::RefPtr(0));
- }
- }
- Geometry::Point movePos(p.x - m_resizeOffset.x, p.y - m_resizeOffset.y);
- Geometry::Rectangle bb = m_selectTool->getSceneBoundingRect();
- movePos.x = std::min(std::max(bb.x, movePos.x), bb.x + bb.width);
- movePos.y = std::min(std::max(bb.y, movePos.y), bb.y + bb.height);
- if(!m_resizeHandlers.empty()){
- Geometry::Rectangle oldRect = m_rect;
- for(const ResizeHandler& handler : m_resizeHandlers){
- handler(movePos, m_anchor, m_point);
- }
- m_rect = Geometry::Rectangle(m_anchor, m_point);
- m_selectTool->invalidateRect(m_rect.unite(oldRect));
- m_selectTool->m_displayer->ensureVisible(event->x, event->y);
- return true;
- }
- return false;
-}
diff -Nru gimagereader-3.1.91/gtk/src/DisplayerToolSelect.hh gimagereader-3.1.99/gtk/src/DisplayerToolSelect.hh
--- gimagereader-3.1.91/gtk/src/DisplayerToolSelect.hh 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/DisplayerToolSelect.hh 2016-10-13 21:45:35.000000000 +0000
@@ -22,7 +22,7 @@
#include "Displayer.hh"
-class DisplaySelection;
+class NumberedDisplayerSelection;
class DisplayerToolSelect : public DisplayerTool {
public:
@@ -31,48 +31,35 @@
bool mousePressEvent(GdkEventButton *event) override;
bool mouseMoveEvent(GdkEventMotion *event) override;
bool mouseReleaseEvent(GdkEventButton *event) override;
- void pageChanged() override;
void resolutionChanged(double factor) override;
void rotationChanged(double delta) override;
- std::vector> getOCRAreas();
- bool hasMultipleOCRAreas() const{ return !m_selections.empty(); }
- void autodetectOCRAreas(){ autodetectLayout(); }
+ std::vector> getOCRAreas() override;
+ bool hasMultipleOCRAreas() const override{ return !m_selections.empty(); }
+ bool allowAutodetectOCRAreas() const override{ return true; }
+ void autodetectOCRAreas() override{ autodetectLayout(); }
+ void reset() override{ clearSelections(); }
private:
- friend class DisplaySelection;
- DisplaySelection* m_curSel = nullptr;
- std::vector m_selections;
+ friend class NumberedDisplayerSelection;
+ NumberedDisplayerSelection* m_curSel = nullptr;
+ std::vector m_selections;
sigc::connection m_connectionAutolayout;
void clearSelections();
void removeSelection(int num);
void reorderSelection(int oldNum, int newNum);
- void saveSelection(DisplaySelection* selection);
+ void saveSelection(NumberedDisplayerSelection* selection);
void updateRecognitionModeLabel();
void autodetectLayout(bool noDeskew = false);
};
-class DisplaySelection : public DisplayerItem
+class NumberedDisplayerSelection : public DisplayerSelection
{
public:
- DisplaySelection(DisplayerToolSelect* selectTool, int number, const Geometry::Point& anchor)
- : m_selectTool(selectTool), m_number(number), m_anchor(anchor), m_point(anchor)
+ NumberedDisplayerSelection(DisplayerToolSelect* selectTool, int number, const Geometry::Point& anchor)
+ : DisplayerSelection(selectTool, anchor), m_number(number)
{
- m_rect = Geometry::Rectangle(anchor, anchor);
- }
- void setPoint(const Geometry::Point& point){
- m_point = point;
- m_rect = Geometry::Rectangle(m_anchor, m_point);
- }
- void rotate(const Geometry::Rotation &R){
- m_anchor = R.rotate(m_anchor);
- m_point = R.rotate(m_point);
- m_rect = Geometry::Rectangle(m_anchor, m_point);
- }
- void scale(double factor){
- m_anchor = Geometry::Point(m_anchor.x * factor, m_anchor.y * factor);
- m_point = Geometry::Point(m_point.x * factor, m_point.y * factor);
}
void setNumber(int number){
m_number = number;
@@ -81,26 +68,11 @@
void reorderSelection(int newNumber);
void draw(Cairo::RefPtr ctx) const override;
- bool mousePressEvent(GdkEventButton *event) override;
- bool mouseReleaseEvent(GdkEventButton *event) override;
- bool mouseMoveEvent(GdkEventMotion *event) override;
private:
- typedef void(*ResizeHandler)(const Geometry::Point&, Geometry::Point&, Geometry::Point&);
-
- DisplayerToolSelect* m_selectTool;
int m_number;
- Geometry::Point m_anchor;
- Geometry::Point m_point;
- std::vector m_resizeHandlers;
- Geometry::Point m_resizeOffset;
-
- void showContextMenu(GdkEventButton *event);
-
- static void resizeAnchorX(const Geometry::Point& pos, Geometry::Point& anchor, Geometry::Point& /*point*/){ anchor.x = pos.x; }
- static void resizeAnchorY(const Geometry::Point& pos, Geometry::Point& anchor, Geometry::Point& /*point*/){ anchor.y = pos.y; }
- static void resizePointX(const Geometry::Point& pos, Geometry::Point& /*anchor*/, Geometry::Point& point){ point.x = pos.x; }
- static void resizePointY(const Geometry::Point& pos, Geometry::Point& /*anchor*/, Geometry::Point& point){ point.y = pos.y; }
+
+ void showContextMenu(GdkEventButton *event) override;
};
#endif // DISPLAYERTOOLSELECT_HH
diff -Nru gimagereader-3.1.91/gtk/src/DisplayRenderer.cc gimagereader-3.1.99/gtk/src/DisplayRenderer.cc
--- gimagereader-3.1.91/gtk/src/DisplayRenderer.cc 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/DisplayRenderer.cc 2016-10-13 21:45:35.000000000 +0000
@@ -38,7 +38,7 @@
int n = surf->get_height() * surf->get_width();
uint8_t* data = surf->get_data();
-#pragma omp parallel for
+#pragma omp parallel for schedule(static)
for(int i = 0; i < n; ++i){
uint8_t& r = data[4*i + 2];
uint8_t& g = data[4*i + 1];
diff -Nru gimagereader-3.1.91/gtk/src/Geometry.hh gimagereader-3.1.99/gtk/src/Geometry.hh
--- gimagereader-3.1.91/gtk/src/Geometry.hh 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/Geometry.hh 2016-10-13 21:45:35.000000000 +0000
@@ -63,7 +63,7 @@
bool contains(const Point& p) const{
return p.x >= x && p.x <= x + width && p.y >= y && p.y <= y + height;
}
- bool overlaps(const Rectangle& r) {
+ bool overlaps(const Rectangle& r) const {
return x < r.x + r.width && x + width > r.x && y < r.y + r.height && y + height > r.y;
}
Rectangle unite(const Rectangle& r) const{
diff -Nru gimagereader-3.1.91/gtk/src/Image.cc gimagereader-3.1.99/gtk/src/Image.cc
--- gimagereader-3.1.91/gtk/src/Image.cc 1970-01-01 00:00:00.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/Image.cc 2016-10-13 21:45:35.000000000 +0000
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * Geometry.hh
+ * Copyright (C) 2016 Sandro Mani
+ *
+ * gImageReader 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.
+ *
+ * gImageReader 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 "Image.hh"
+#include
+#include
+
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+# define PIXEL_A(x) x[3]
+# define PIXEL_R(x) x[2]
+# define PIXEL_G(x) x[1]
+# define PIXEL_B(x) x[0]
+#else
+# define PIXEL_A(x) x[3]
+# define PIXEL_R(x) x[2]
+# define PIXEL_G(x) x[1]
+# define PIXEL_B(x) x[0]
+#endif
+
+static inline uint8_t clamp(int val) {
+ return val > 255 ? 255 : val < 0 ? 0 : val;
+}
+
+Image::Image(Cairo::RefPtr src, Format targetFormat)
+{
+ width = src->get_width();
+ height = src->get_height();
+ format = targetFormat;
+ int stride = src->get_stride();
+
+ if(format == Format_RGB24) {
+ sampleSize = 8;
+ bytesPerLine = 3 * width;
+ data = new uint8_t[width * height * 3];
+ #pragma omp parallel for schedule(static)
+ for(int y = 0; y < height; ++y) {
+ for(int x = 0; x < width; ++x) {
+ uint8_t* srcpx = &src->get_data()[y * stride + 4 * x];
+ uint8_t* dstpx = &data[y * bytesPerLine + 3 * x];
+ dstpx[0] = PIXEL_R(srcpx);
+ dstpx[1] = PIXEL_G(srcpx);
+ dstpx[2] = PIXEL_B(srcpx);
+ }
+ }
+ } else if(format == Format_Gray8 || format == Format_Mono) {
+ sampleSize = 8;
+ bytesPerLine = width;
+ data = new uint8_t[width * height];
+ #pragma omp parallel for schedule(static)
+ for(int y = 0; y < height; ++y) {
+ for(int x = 0; x < width; ++x) {
+ uint8_t* srcpx = &src->get_data()[y * stride + 4 * x];
+ data[y * bytesPerLine + x] = 0.21 * PIXEL_R(srcpx) + 0.72 * PIXEL_G(srcpx) + 0.07 * PIXEL_B(srcpx);
+ }
+ }
+ if(format == Format_Mono) {
+ sampleSize = 1;
+ bytesPerLine = width / 8 + (width % 8 != 0);
+ uint8_t* newdata = new uint8_t[height * bytesPerLine];
+ std::memset(newdata, 0, height * bytesPerLine);
+ // Dithering: https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
+ for(int y = 0; y < height; ++y) {
+ for(int x = 0; x < width; ++x) {
+ uint8_t oldpixel = data[y * width + x];
+ uint8_t newpixel = oldpixel > 127 ? 255 : 0;
+ int err = int(oldpixel) - int(newpixel);
+ if(newpixel == 255) {
+ newdata[y * bytesPerLine + x/8] |= 0x80 >> x%8;
+ }
+ if(x + 1 < width) { // right neighbor
+ uint8_t& pxr = data[y * width + (x + 1)];
+ pxr = clamp(pxr + ((err * 7) >> 4));
+ }
+ if(y + 1 == height) {
+ continue; // last line
+ }
+ if(x > 0) { // bottom left and bottom neighbor
+ uint8_t& pxbl = data[(y + 1) * width + (x - 1)];
+ pxbl = clamp(pxbl + ((err * 3) >> 4));
+ uint8_t& pxb = data[(y + 1) * width + x];
+ pxb = clamp(pxb + ((err * 5) >> 4));
+ }
+ if(x + 1 < width) { // bottom right neighbor
+ uint8_t& pxbr = data[(y + 1) * width + (x + 1)];
+ pxbr = clamp(pxbr + ((err * 1) >> 4));
+ }
+ }
+ }
+ delete[] data;
+ data = newdata;
+ }
+ }
+}
+
+void Image::writeJpeg(int quality, uint8_t*& buf, unsigned long& bufLen)
+{
+ buf = nullptr;
+ bufLen = 0;
+ if(format == Format_Mono) {
+ return; // not supported by jpeg
+ }
+
+ // https://github.com/LuaDist/libjpeg/blob/master/example.c
+ jpeg_compress_struct cinfo;
+ jpeg_error_mgr jerr;
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+ jpeg_mem_dest(&cinfo, &buf, &bufLen);
+ cinfo.image_width = width;
+ cinfo.image_height = height;
+ cinfo.input_components = format == Format_RGB24 ? 3 : 1;
+ cinfo.in_color_space = format == Format_RGB24 ? JCS_RGB : JCS_GRAYSCALE;
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_quality(&cinfo, quality, true);
+ jpeg_start_compress(&cinfo, true);
+ JSAMPROW row_pointer[1];
+ while (cinfo.next_scanline < cinfo.image_height) {
+ row_pointer[0] = &data[cinfo.next_scanline * bytesPerLine];
+ jpeg_write_scanlines(&cinfo, row_pointer, 1);
+ }
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+}
+
+Cairo::RefPtr Image::simulateFormat(Cairo::RefPtr src, Format format)
+{
+ int imgw = src->get_width();
+ int imgh = src->get_height();
+ int stride = src->get_stride();
+
+ if(format == Format_Gray8 || format == Format_Mono) {
+ Cairo::RefPtr dst = Cairo::ImageSurface::create(Cairo::FORMAT_ARGB32, imgw, imgh);
+ dst->flush();
+ #pragma omp parallel for schedule(static)
+ for(int y = 0; y < imgh; ++y) {
+ for(int x = 0; x < imgw; ++x) {
+ int offset = y * stride + 4 * x;
+ uint8_t* srcpx = &src->get_data()[offset];
+ uint8_t* dstpx = &dst->get_data()[offset];
+ uint8_t gray = 0.21 * PIXEL_R(srcpx) + 0.72 * PIXEL_G(srcpx) + 0.07 * PIXEL_B(srcpx);
+ PIXEL_A(dstpx) = 255;
+ PIXEL_R(dstpx) = gray;
+ PIXEL_G(dstpx) = gray;
+ PIXEL_B(dstpx) = gray;
+ }
+ }
+ if(format == Format_Mono) {
+ // Dithering: https://en.wikipedia.org/wiki/Floyd%E2%80%93Steinberg_dithering
+ for(int y = 0; y < imgh; ++y) {
+ for(int x = 0; x < imgw; ++x) {
+ uint8_t* px = &dst->get_data()[y * stride + 4 * x];
+ uint8_t newpixel = PIXEL_R(px) > 127 ? 255 : 0;
+ int err = int(PIXEL_R(px)) - int(newpixel);
+ PIXEL_R(px) = PIXEL_G(px) = PIXEL_B(px) = newpixel;
+ if(x + 1 < imgw) { // right neighbor
+ px = &dst->get_data()[y * stride + 4 * (x + 1)];
+ PIXEL_R(px) = PIXEL_G(px) = PIXEL_B(px) = clamp(PIXEL_R(px) + ((err * 7) >> 4));
+ }
+ if(y + 1 == imgh) {
+ continue; // last line
+ }
+ if(x > 0) { // bottom left and bottom neighbor
+ px = &dst->get_data()[(y + 1) * stride + 4 * (x - 1)];
+ PIXEL_R(px) = PIXEL_G(px) = PIXEL_B(px) = clamp(PIXEL_R(px) + ((err * 3) >> 4));
+ px = &dst->get_data()[(y + 1) * stride + 4 * x];
+ PIXEL_R(px) = PIXEL_G(px) = PIXEL_B(px) = clamp(PIXEL_R(px) + ((err * 5) >> 4));
+ }
+ if(x + 1 < imgw) { // bottom right neighbor
+ px = &dst->get_data()[(y + 1) * stride + 4 * (x + 1)];
+ PIXEL_R(px) = PIXEL_G(px) = PIXEL_B(px) = clamp(PIXEL_R(px) + ((err * 1) >> 4));
+ }
+ }
+ }
+ }
+ dst->mark_dirty();
+ return dst;
+ }
+ return src;
+}
+
+Cairo::RefPtr Image::scale(Cairo::RefPtr src, double scaleFactor)
+{
+ if(scaleFactor == 1.0) {
+ return src;
+ }
+ Cairo::RefPtr dst = Cairo::ImageSurface::create(src->get_format(), std::ceil(src->get_width() * scaleFactor), std::ceil(src->get_height() * scaleFactor));
+ Cairo::RefPtr ctx = Cairo::Context::create(dst);
+ ctx->scale(scaleFactor, scaleFactor);
+ ctx->set_source(src, 0, 0);
+ Cairo::RefPtr::cast_static(ctx->get_source())->set_filter(Cairo::FILTER_BEST);
+ ctx->paint();
+ dst->flush();
+ return dst;
+}
diff -Nru gimagereader-3.1.91/gtk/src/Image.hh gimagereader-3.1.99/gtk/src/Image.hh
--- gimagereader-3.1.91/gtk/src/Image.hh 1970-01-01 00:00:00.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/Image.hh 2016-10-13 21:45:35.000000000 +0000
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; indent-tabs-mode: t; c-basic-offset: 4; tab-width: 4 -*- */
+/*
+ * Geometry.hh
+ * Copyright (C) 2016 Sandro Mani
+ *
+ * gImageReader 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.
+ *
+ * gImageReader 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 IMAGE_HH
+#define IMAGE_HH
+
+#include
+
+class Image {
+public:
+ enum Format {Format_RGB24 = 0, Format_Gray8 = 1, Format_Mono = 2} format; // This order needs to be the same as combo:pdfoptions.imageformat
+ int width;
+ int height;
+ int sampleSize;
+ int bytesPerLine;
+ unsigned char* data = nullptr;
+
+ Image(Cairo::RefPtr src, Format targetFormat);
+ ~Image(){ delete[] data; }
+ void writeJpeg(int quality, uint8_t*& buf, unsigned long& bufLen);
+
+ static Cairo::RefPtr simulateFormat(Cairo::RefPtr src, Format format);
+ static Cairo::RefPtr scale(Cairo::RefPtr src, double scaleFactor);
+};
+
+
+
+#endif // IMAGEUTILS_HH
diff -Nru gimagereader-3.1.91/gtk/src/main.cc gimagereader-3.1.99/gtk/src/main.cc
--- gimagereader-3.1.91/gtk/src/main.cc 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/main.cc 2016-10-13 21:45:35.000000000 +0000
@@ -25,6 +25,7 @@
#include "common.hh"
#include "Application.hh"
+#include "Config.hh"
#include "CrashHandler.hh"
std::string pkgDir;
@@ -77,11 +78,16 @@
// Run the crash handler
CrashHandler app(argc, argv);
return app.run();
+ } else if(argc >= 2 && std::strcmp("tessdatadir", argv[1]) == 0) {
+ Config::openTessdataDir();
+ return 0;
+ } else if(argc >= 2 && std::strcmp("spellingdir", argv[1]) == 0) {
+ Config::openSpellingDir();
+ return 0;
} else {
// Run the normal application
#ifdef G_OS_WIN32
- Glib::setenv("TESSDATA_PREFIX", Glib::build_filename(pkgDir, "share"));
Glib::setenv("TWAINDSM_LOG", Glib::build_filename(pkgDir, "twain.log"));
std::freopen(Glib::build_filename(pkgDir, "gimagereader.log").c_str(), "w", stderr);
#endif
diff -Nru gimagereader-3.1.91/gtk/src/MainWindow.cc gimagereader-3.1.99/gtk/src/MainWindow.cc
--- gimagereader-3.1.91/gtk/src/MainWindow.cc 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/MainWindow.cc 2016-10-13 21:45:35.000000000 +0000
@@ -58,7 +58,7 @@
static Glib::Quark notificationHandleKey("handle");
-static void signalHandler(int sig)
+void MainWindow::signalHandler(int sig)
{
std::signal(sig, nullptr);
std::string filename;
@@ -99,7 +99,7 @@
}else{
std::cerr << "Terminated due to unknown reason:" << std::endl;
}
- signalHandler(SIGABRT);
+ MainWindow::signalHandler(SIGABRT);
}
#endif
@@ -177,7 +177,7 @@
m_newVerThread = Glib::Threads::Thread::create([this]{ getNewestVersion(); });
}
#else
- getWidget("check:config.settings.update").as()->hide();
+ getWidget("check:config.settings.update")->hide();
#endif
}
@@ -318,12 +318,7 @@
if(!Glib::file_test(manualFile, Glib::FILE_TEST_EXISTS)){
manualFile = Utils::make_absolute_path(Glib::build_filename(manualDir,"manual.html"));
}
- std::string manualURI = Glib::filename_to_uri(manualFile) + chapter;
-#ifdef G_OS_WIN32
- ShellExecute(nullptr, "open", manualURI.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
-#else
- gtk_show_uri(nullptr, manualURI.c_str(), GDK_CURRENT_TIME, 0);
-#endif
+ Utils::openUri(Glib::filename_to_uri(manualFile) + chapter);
}
void MainWindow::showConfig()
@@ -349,10 +344,11 @@
getWidget("paned:output").as()->remove(*m_outputEditor->getUI());
delete m_outputEditor;
}
+ delete m_displayerTool;
if(idx == 0) {
m_displayerTool = new DisplayerToolSelect(m_displayer);
m_outputEditor = new OutputEditorText();
- } else if(idx == 1) {
+ } else /*if(idx == 1)*/ {
m_displayerTool = new DisplayerToolHOCR(m_displayer);
m_outputEditor = new OutputEditorHOCR(static_cast(m_displayerTool));
}
@@ -461,27 +457,32 @@
}catch(const GtkSpell::Error& /*e*/){
if(getConfig()->getSetting("dictinstall")->getValue()){
NotificationAction actionDontShowAgain = {_("Don't show again"), [this]{ m_config->getSetting("dictinstall")->setValue(false); return true; }};
- NotificationAction actionInstall;
-#if defined(G_OS_UNIX)
- // Try initiating a DBUS connection for PackageKit
- Glib::RefPtr proxy;
- try{
- proxy = Gio::DBus::Proxy::create_for_bus_sync(Gio::DBus::BUS_TYPE_SESSION, "org.freedesktop.PackageKit",
- "/org/freedesktop/PackageKit", "org.freedesktop.PackageKit.Modify");
- actionInstall = MainWindow::NotificationAction{_("Install"), [this,proxy,lang]{ dictionaryAutoinstall(proxy, lang.code); return false; }};
- }catch(const Glib::Error&){
- actionInstall = {_("Help"), [this]{ showHelp("#InstallSpelling"); return false; }};
- g_warning("Could not find PackageKit on DBus, dictionary autoinstallation will not work");
+ NotificationAction actionInstall = NotificationAction{_("Install"), [this,lang]{ dictionaryAutoinstall(lang.code); return false; }};
+#ifdef G_OS_UNIX
+ if(getConfig()->useSystemDataLocations()) {
+ // Try initiating a DBUS connection for PackageKit
+ Glib::RefPtr proxy;
+ Glib::ustring service_owner;
+ try{
+ proxy = Gio::DBus::Proxy::create_for_bus_sync(Gio::DBus::BUS_TYPE_SESSION, "org.freedesktop.PackageKit",
+ "/org/freedesktop/PackageKit", "org.freedesktop.PackageKit.Modify");
+ service_owner = proxy->get_name_owner();
+ }catch(...){
+ }
+ if(!service_owner.empty()) {
+ actionInstall = MainWindow::NotificationAction{_("Install"), [this,proxy,lang]{ dictionaryAutoinstall(proxy, lang.code); return false; }};
+ } else {
+ actionInstall = {_("Help"), [this]{ showHelp("#InstallSpelling"); return false; }};
+ g_warning("Could not find PackageKit on DBus, dictionary autoinstallation will not work");
+ }
}
-#elif defined(G_OS_WIN32)
- actionInstall = NotificationAction{_("Install"), [this,lang]{ dictionaryAutoinstall(lang.code); return false; }};
#endif
addNotification(_("Spelling dictionary missing"), Glib::ustring::compose(_("The spellcheck dictionary for %1 is not installed"), lang.name), {actionInstall, actionDontShowAgain}, &m_notifierHandle);
}
}
}
-#if defined(G_OS_UNIX)
+#ifdef G_OS_UNIX
void MainWindow::dictionaryAutoinstall(Glib::RefPtr proxy, const Glib::ustring &code)
{
pushState(State::Busy, Glib::ustring::compose(_("Installing spelling dictionary for '%1'"), code));
@@ -508,13 +509,30 @@
getRecognizer()->updateLanguagesMenu();
popState();
}
-#elif defined(G_OS_WIN32)
-void MainWindow::dictionaryAutoinstall(const Glib::ustring& code)
+#endif
+
+void MainWindow::dictionaryAutoinstall(Glib::ustring code)
{
+ std::vector codes = m_config->searchLangCultures(code);
+ code = codes.empty() ? code : codes.front();
+
pushState(State::Busy, Glib::ustring::compose(_("Installing spelling dictionary for '%1'"), code));
Glib::ustring url= "https://cgit.freedesktop.org/libreoffice/dictionaries/tree/";
Glib::ustring plainurl = "https://cgit.freedesktop.org/libreoffice/dictionaries/plain/";
Glib::ustring urlcode = code;
+ std::string dictPath = getConfig()->spellingLocation();
+ Glib::RefPtr dictDir = Gio::File::create_for_path(dictPath);
+ bool dirExists = false;
+ try {
+ dirExists = dictDir->make_directory_with_parents();
+ } catch(...) {
+ dirExists = dictDir->query_exists();
+ }
+ if(!dirExists) {
+ popState();
+ Utils::message_dialog(Gtk::MESSAGE_ERROR, _("Error"), _("Failed to create directory for spelling dictionaries."));
+ return;
+ }
Glib::ustring messages;
Glib::RefPtr html = Utils::download(url, messages);
@@ -549,7 +567,7 @@
pushState(State::Busy, Glib::ustring::compose(_("Downloading '%1'..."), matchInfo.fetch(1)));
Glib::RefPtr data = Utils::download(plainurl + urlcode + "/" + matchInfo.fetch(1), messages);
if(data){
- std::ofstream file(Glib::build_filename(pkgDir, "share", "myspell", "dicts", matchInfo.fetch(1)), std::ios::binary);
+ std::ofstream file(Glib::build_filename(dictPath, matchInfo.fetch(1)), std::ios::binary);
if(file.is_open()){
file.write(reinterpret_cast(data->get_data()), data->size());
downloaded.append(Glib::ustring::compose("\n%1", matchInfo.fetch(1)));
@@ -568,4 +586,3 @@
Utils::message_dialog(Gtk::MESSAGE_ERROR, _("Error"), Glib::ustring::compose(_("No spelling dictionaries found for '%1'."), code));
}
}
-#endif
diff -Nru gimagereader-3.1.91/gtk/src/MainWindow.hh gimagereader-3.1.99/gtk/src/MainWindow.hh
--- gimagereader-3.1.91/gtk/src/MainWindow.hh 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/MainWindow.hh 2016-10-13 21:45:35.000000000 +0000
@@ -51,6 +51,7 @@
typedef void* Notification;
static MainWindow* getInstance(){ return s_instance; }
+ static void signalHandler(int signal);
MainWindow();
~MainWindow();
@@ -119,12 +120,11 @@
void getNewestVersion();
void checkVersion(const Glib::ustring& newver);
#endif
-#if defined(G_OS_UNIX)
+#ifdef G_OS_UNIX
void dictionaryAutoinstall(Glib::RefPtr proxy, const Glib::ustring& lang);
void dictionaryAutoinstallDone(Glib::RefPtr proxy, Glib::RefPtr& result);
-#elif defined(G_OS_WIN32)
- void dictionaryAutoinstall(const Glib::ustring& lang);
#endif
+ void dictionaryAutoinstall(Glib::ustring lang);
};
#endif
diff -Nru gimagereader-3.1.91/gtk/src/OutputEditor.hh gimagereader-3.1.99/gtk/src/OutputEditor.hh
--- gimagereader-3.1.91/gtk/src/OutputEditor.hh 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/OutputEditor.hh 2016-10-13 21:45:35.000000000 +0000
@@ -38,7 +38,7 @@
virtual ~OutputEditor() {}
virtual Gtk::Box* getUI() = 0;
- virtual ReadSessionData* initRead() = 0;
+ virtual ReadSessionData* initRead(tesseract::TessBaseAPI& tess) = 0;
virtual void read(tesseract::TessBaseAPI& tess, ReadSessionData* data) = 0;
virtual void readError(const Glib::ustring& errorMsg, ReadSessionData* data) = 0;
virtual void finalizeRead(ReadSessionData* data) { delete data; }
diff -Nru gimagereader-3.1.91/gtk/src/OutputEditorHOCR.cc gimagereader-3.1.99/gtk/src/OutputEditorHOCR.cc
--- gimagereader-3.1.91/gtk/src/OutputEditorHOCR.cc 2016-05-03 14:24:07.000000000 +0000
+++ gimagereader-3.1.99/gtk/src/OutputEditorHOCR.cc 2016-10-13 21:45:35.000000000 +0000
@@ -24,6 +24,15 @@
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
#include "DisplayerToolHOCR.hh"
#include "FileDialogs.hh"
@@ -38,6 +47,7 @@
const Glib::RefPtr OutputEditorHOCR::s_pageTitleRx = Glib::Regex::create("image\\s+'(.+)';\\s+bbox\\s+\\d+\\s+\\d+\\s+\\d+\\s+\\d+;\\s+pageno\\s+(\\d+);\\s+rot\\s+(\\d+\\.?\\d*);\\s+res\\s+(\\d+\\.?\\d*)");
const Glib::RefPtr OutputEditorHOCR::s_idRx = Glib::Regex::create("(\\w+)_\\d+_(\\d+)");
const Glib::RefPtr OutputEditorHOCR::s_fontSizeRx = Glib::Regex::create("x_fsize\\s+(\\d+)");
+const Glib::RefPtr OutputEditorHOCR::s_baseLineRx = Glib::Regex::create("baseline\\s+(-?\\d+\\.?\\d*)\\s+(-?\\d+)");
static inline Glib::ustring getAttribute(const xmlpp::Element* element, const Glib::ustring& name)
{
@@ -108,6 +118,158 @@
return getDocumentXML(&doc);
}
+
+class OutputEditorHOCR::TreeView : public Gtk::TreeView
+{
+public:
+ TreeView(BaseObjectType* cobject, const Glib::RefPtr& /*builder*/)
+ : Gtk::TreeView(cobject) {}
+
+ sigc::signal signal_context_menu_requested(){ return m_signal_context_menu; }
+
+protected:
+ bool on_button_press_event(GdkEventButton *button_event) {
+ bool rightclick = (button_event->type == GDK_BUTTON_PRESS && button_event->button == 3);
+
+ Gtk::TreePath path;
+ Gtk::TreeViewColumn* col;
+ int cell_x, cell_y;
+ get_path_at_pos(int(button_event->x), int(button_event->y), path, col, cell_x, cell_y);
+ if(!path) {
+ return false;
+ }
+ std::vector selection = get_selection()->get_selected_rows();
+ bool selected = std::find(selection.begin(), selection.end(), path) != selection.end();
+
+ if(!rightclick || (rightclick && !selected)) {
+ Gtk::TreeView::on_button_press_event(button_event);
+ }
+ if(rightclick) {
+ m_signal_context_menu.emit(button_event);
+ }
+ return true;
+ }
+
+private:
+ sigc::signal m_signal_context_menu;
+};
+
+
+class OutputEditorHOCR::CairoPDFPainter : public OutputEditorHOCR::PDFPainter
+{
+public:
+ CairoPDFPainter(Cairo::RefPtr context) : m_context(context) {
+ }
+ void setFontSize(double pointSize) override {
+ m_context->set_font_size(pointSize);
+ }
+ void drawText(double x, double y, const Glib::ustring& text) override {
+ m_context->move_to(x, y/* - ext.y_bearing*/);
+ m_context->show_text(text);
+ }
+ void drawImage(const Geometry::Rectangle& bbox, const Cairo::RefPtr& image, const PDFSettings& settings) override {
+ m_context->save();
+ m_context->move_to(bbox.x, bbox.y);
+ Cairo::RefPtr img = Image::simulateFormat(image, settings.colorFormat);
+ m_context->set_source(img, bbox.x, bbox.y);
+ m_context->paint();
+ m_context->restore();
+ }
+ double getAverageCharWidth() const override {
+ Cairo::TextExtents ext;
+ m_context->get_text_extents("x ", ext);
+ return ext.x_advance - ext.width; // spaces are ignored in width but counted in advance
+ }
+ double getTextWidth(const Glib::ustring& text) const override {
+ Cairo::TextExtents ext;
+ m_context->get_text_extents(text, ext);
+ return ext.x_advance;
+ }
+
+private:
+ Cairo::RefPtr m_context;
+};
+
+#if PODOFO_VERSION < PODOFO_MAKE_VERSION(0,9,3)
+namespace PoDoFo {
+class PdfImageCompat : public PoDoFo::PdfImage {
+ using PdfImage::PdfImage;
+public:
+ void SetImageDataRaw( unsigned int nWidth, unsigned int nHeight,
+ unsigned int nBitsPerComponent, PdfInputStream* pStream )
+ {
+ m_rRect.SetWidth( nWidth );
+ m_rRect.SetHeight( nHeight );
+
+ this->GetObject()->GetDictionary().AddKey( "Width", PdfVariant( static_cast(nWidth) ) );
+ this->GetObject()->GetDictionary().AddKey( "Height", PdfVariant( static_cast(nHeight) ) );
+ this->GetObject()->GetDictionary().AddKey( "BitsPerComponent", PdfVariant( static_cast(nBitsPerComponent) ) );
+
+ PdfVariant var;
+ m_rRect.ToVariant( var );
+ this->GetObject()->GetDictionary().AddKey( "BBox", var );
+
+ this->GetObject()->GetStream()->SetRawData( pStream, -1 );
+ }
+};
+}
+#endif
+
+class OutputEditorHOCR::PoDoFoPDFPainter : public OutputEditorHOCR::PDFPainter {
+public:
+ PoDoFoPDFPainter(PoDoFo::PdfDocument* document, PoDoFo::PdfPainter* painter, double scaleFactor, double imageScale)
+ : m_document(document), m_painter(painter), m_scaleFactor(scaleFactor), m_imageScale(imageScale)
+ {
+ m_pageHeight = m_painter->GetPage()->GetPageSize().GetHeight();
+ }
+ void setFontSize(double pointSize) override {
+ m_painter->GetFont()->SetFontSize(pointSize);
+ }
+ void drawText(double x, double y, const Glib::ustring& text) override {
+ PoDoFo::PdfString pdfString(reinterpret_cast(text.c_str()));
+ m_painter->DrawText(x * m_scaleFactor, m_pageHeight - y * m_scaleFactor, pdfString);
+ }
+ void drawImage(const Geometry::Rectangle& bbox, const Cairo::RefPtr& image, const PDFSettings& settings) override {
+#if PODOFO_VERSION >= PODOFO_MAKE_VERSION(0,9,3)
+ PoDoFo::PdfImage pdfImage(m_document);
+#else
+ PoDoFo::PdfImageCompat pdfImage(m_document);
+#endif
+ Cairo::RefPtr scaledImage = Image::scale(image, m_imageScale);
+ pdfImage.SetImageColorSpace(settings.colorFormat == Image::Format_RGB24 ? PoDoFo::ePdfColorSpace_DeviceRGB : PoDoFo::ePdfColorSpace_DeviceGray);
+ Image img(scaledImage, settings.colorFormat);
+ if(settings.compression == PDFSettings::CompressZip) {
+ PoDoFo::PdfMemoryInputStream is(reinterpret_cast(img.data), img.bytesPerLine * img.height);
+ pdfImage.SetImageData(img.width, img.height, img.sampleSize, &is, {PoDoFo::ePdfFilter_FlateDecode});
+ } else {
+ PoDoFo::PdfName dctFilterName(PoDoFo::PdfFilterFactory::FilterTypeToName(PoDoFo::ePdfFilter_DCTDecode));
+ pdfImage.GetObject()->GetDictionary().AddKey(PoDoFo::PdfName::KeyFilter, dctFilterName);
+ uint8_t* buf = nullptr;
+ unsigned long bufLen = 0;
+ img.writeJpeg(settings.compressionQuality, buf, bufLen);
+ PoDoFo::PdfMemoryInputStream is(reinterpret_cast(buf), bufLen);
+ pdfImage.SetImageDataRaw(img.width, img.height, img.sampleSize, &is);
+ std::free(buf);
+ }
+ m_painter->DrawImage(bbox.x * m_scaleFactor, m_pageHeight - (bbox.y + bbox.height) * m_scaleFactor, &pdfImage, m_scaleFactor / m_imageScale, m_scaleFactor / m_imageScale);
+ }
+ double getAverageCharWidth() const override {
+ return m_painter->GetFont()->GetFontMetrics()->CharWidth(static_cast('x')) / m_scaleFactor;
+ }
+ double getTextWidth(const Glib::ustring& text) const override {
+ PoDoFo::PdfString pdfString(reinterpret_cast(text.c_str()));
+ return m_painter->GetFont()->GetFontMetrics()->StringWidth(pdfString) / m_scaleFactor;
+ }
+
+private:
+ PoDoFo::PdfDocument* m_document;
+ PoDoFo::PdfPainter* m_painter;
+ double m_scaleFactor;
+ double m_imageScale;
+ double m_pageHeight;
+};
+
+
OutputEditorHOCR::OutputEditorHOCR(DisplayerToolHOCR* tool)
: m_builder("/org/gnome/gimagereader/editor_hocr.ui")
{
@@ -117,9 +279,9 @@
Gtk::Button* openButton = m_builder("button:hocr.open");
Gtk::Button* saveButton = m_builder("button:hocr.save");
Gtk::Button* clearButton = m_builder("button:hocr.clear");
- Gtk::MenuItem* savePdfItem = m_builder("menuitem:hocr.export.pdf");
- Gtk::MenuItem* savePdfOverlayItem = m_builder("menuitem:hocr.export.pdfoverlay");
- m_itemView = m_builder("treeview:hocr.items");
+ Gtk::Button* exportButton = m_builder("button:hocr.export");
+ m_itemView = nullptr;
+ m_builder.get_derived("treeview:hocr.items", m_itemView);
m_itemStore = Gtk::TreeStore::create(m_itemStoreCols);
m_itemView->set_model(m_itemStore);
Gtk::TreeViewColumn *itemViewCol = Gtk::manage(new Gtk::TreeViewColumn(""));
@@ -169,29 +331,87 @@
m_pdfExportDialog = m_builder("dialog:pdfoptions");
m_pdfExportDialog->set_transient_for(*MAIN->getWindow());
+ Gtk::ComboBox* imageFormatCombo = m_builder("combo:pdfoptions.imageformat");
+ Glib::RefPtr formatComboModel = Gtk::ListStore::create(m_formatComboCols);
+ imageFormatCombo->set_model(formatComboModel);
+ Gtk::TreeModel::Row row = *(formatComboModel->append());
+ row[m_formatComboCols.format] = Image::Format_RGB24;
+ row[m_formatComboCols.label] = _("Color");
+ row = *(formatComboModel->append());
+ row[m_formatComboCols.format] = Image::Format_Gray8;
+ row[m_formatComboCols.label] = _("Grayscale");
+ row = *(formatComboModel->append());
+ row[m_formatComboCols.format] = Image::Format_Mono;
+ row[m_formatComboCols.label] = _("Monochrome");
+ imageFormatCombo->pack_start(m_formatComboCols.label);
+ imageFormatCombo->set_active(-1);
+
+ Gtk::ComboBox* compressionCombo = m_builder("combo:pdfoptions.compression");
+ Glib::RefPtr compressionModel = Gtk::ListStore::create(m_compressionComboCols);
+ compressionCombo->set_model(compressionModel);
+ row = *(compressionModel->append());
+ row[m_compressionComboCols.mode] = PDFSettings::CompressZip;
+ row[m_compressionComboCols.label] = _("Zip (lossless)");
+ row = *(compressionModel->append());
+ row[m_compressionComboCols.mode] = PDFSettings::CompressJpeg;
+ row[m_compressionComboCols.label] = _("Jpeg (lossy)");
+ compressionCombo->pack_start(m_compressionComboCols.label);
+ compressionCombo->set_active(-1);
+
CONNECT(openButton, clicked, [this]{ open(); });
CONNECT(saveButton, clicked, [this]{ save(); });
CONNECT(clearButton, clicked, [this]{ clear(); });
- CONNECT(savePdfItem, activate, [this]{ Glib::signal_idle().connect_once([this]{ savePDF(); }); });
- CONNECT(savePdfOverlayItem, activate, [this]{ Glib::signal_idle().connect_once([this]{ savePDF(true); }); });
+ CONNECT(exportButton, clicked, [this]{ savePDF(); });
CONNECTP(MAIN->getWidget("fontbutton:config.settings.customoutputfont").as(), font_name, [this]{ setFont(); });
CONNECT(MAIN->getWidget("checkbutton:config.settings.defaultoutputfont").as(), toggled, [this]{ setFont(); });
- m_connectionSelectionChanged = CONNECT(m_itemView->get_selection(), changed, [this]{ showItemProperties(); });
+ m_connectionSelectionChanged = CONNECT(m_itemView->get_selection(), changed, [this]{ showItemProperties(currentItem()); });
m_connectionItemViewRowEdited = CONNECT(m_itemStore, row_changed, [this](const Gtk::TreeModel::Path&, const Gtk::TreeIter& iter){ itemChanged(iter); });
m_connectionPropViewRowEdited = CONNECT(m_propStore, row_changed, [this](const Gtk::TreeModel::Path&, const Gtk::TreeIter& iter){ propertyCellChanged(iter); });
- m_itemView->signal_button_press_event().connect([this](GdkEventButton* ev){ return handleButtonEvent(ev); }, false);
+ CONNECT(m_itemView, context_menu_requested, [this](GdkEventButton* ev){ showContextMenu(ev); });
+ CONNECT(m_tool, selection_geometry_changed, [this](const Geometry::Rectangle& rect){ updateCurrentItemBBox(rect); });
+ CONNECT(m_tool, selection_drawn, [this](const Geometry::Rectangle& rect) { addGraphicRection(rect); });
+
+ CONNECT(m_builder("combo:pdfoptions.mode").as(), changed, [this]{ updatePreview(); });
+ CONNECT(imageFormatCombo, changed, [this]{ imageFormatChanged(); updatePreview(); });
+ CONNECT(compressionCombo, changed, [this]{ imageCompressionChanged(); });
+ CONNECT(m_builder("fontbutton:pdfoptions").as(), font_set, [this]{ updatePreview(); });
+ CONNECT(m_builder("checkbox:pdfoptions.usedetectedfontsizes").as(), toggled, [this]{ updatePreview(); });
+ CONNECTS(m_builder("checkbox:pdfoptions.usedetectedfontsizes").as(), toggled, [this](Gtk::CheckButton* button){
+ updatePreview();
+ m_builder("box:pdfoptions.fontscale")->set_sensitive(button->get_active());
+ });
+ CONNECT(m_builder("spin:pdfoptions.fontscale").as(), value_changed, [this]{ updatePreview(); });
+ CONNECTS(m_builder("checkbox:pdfoptions.uniformlinespacing").as(), toggled, [this](Gtk::CheckButton* button){
+ updatePreview();
+ m_builder("box:pdfoptions.preserve")->set_sensitive(button->get_active());
+ });
+ CONNECT(m_builder("spin:pdfoptions.preserve").as(), value_changed, [this]{ updatePreview(); });
+ CONNECT(m_builder("checkbox:pdfoptions.preview").as(), toggled, [this]{ updatePreview(); });
if(MAIN->getConfig()->getSetting>("outputdir")->getValue().empty()){
MAIN->getConfig()->getSetting>("outputdir")->setValue(Utils::get_documents_dir());
}
- MAIN->getConfig()->addSetting(new FontSetting("hocrfont", m_builder("fontbutton:pdfoptions")));
- MAIN->getConfig()->addSetting(new SwitchSettingT("hocrusedetectedfontsizes", m_builder("checkbox:pdfoptions.usedetectedfontsizes")));
- MAIN->getConfig()->addSetting(new SwitchSettingT("hocruniformizelinespacing", m_builder("checkbox:pdfoptions.uniformlinespacing")));
+ MAIN->getConfig()->addSetting(new ComboSetting("pdfexportmode", m_builder("combo:pdfoptions.mode")));
+ MAIN->getConfig()->addSetting(new SpinSetting("pdfimagecompressionquality", m_builder("spin:pdfoptions.quality")));
+ MAIN->getConfig()->addSetting(new ComboSetting("pdfimagecompression", m_builder("combo:pdfoptions.compression")));
+ MAIN->getConfig()->addSetting(new ComboSetting("pdfimageformat", m_builder("combo:pdfoptions.imageformat")));
+ MAIN->getConfig()->addSetting(new SpinSetting("pdfimagedpi", m_builder("spin:pdfoptions.dpi")));
+ MAIN->getConfig()->addSetting(new FontSetting("pdffont", m_builder("fontbutton:pdfoptions")));
+ MAIN->getConfig()->addSetting(new SwitchSettingT("pdfusedetectedfontsizes", m_builder("checkbox:pdfoptions.usedetectedfontsizes")));
+ MAIN->getConfig()->addSetting(new SwitchSettingT("pdfuniformizelinespacing", m_builder("checkbox:pdfoptions.uniformlinespacing")));
+ MAIN->getConfig()->addSetting(new SpinSetting("pdfpreservespaces", m_builder("spin:pdfoptions.preserve")));
+ MAIN->getConfig()->addSetting(new SpinSetting("pdffontscale", m_builder("spin:pdfoptions.fontscale")));
+ MAIN->getConfig()->addSetting(new SwitchSettingT("pdfpreview", m_builder("checkbox:pdfoptions.preview")));
setFont();
}
+OutputEditorHOCR::~OutputEditorHOCR()
+{
+ delete m_currentParser;
+}
+
void OutputEditorHOCR::setFont()
{
if(MAIN->getWidget("checkbutton:config.settings.defaultoutputfont").as()->get_active()){
@@ -202,6 +422,33 @@
}
}
+void OutputEditorHOCR::imageFormatChanged()
+{
+ Image::Format format = (*m_builder("combo:pdfoptions.imageformat").as()->get_active())[m_formatComboCols.format];
+ if(format == Image::Format_Mono) {
+ m_builder("combo:pdfoptions.compression").as()->set_active(0);
+ m_builder("combo:pdfoptions.compression").as()->set_sensitive(false);
+ m_builder("label:pdfoptions.compression").as()->set_sensitive(false);
+ } else {
+ m_builder("combo:pdfoptions.compression").as()->set_sensitive(true);
+ m_builder("label:pdfoptions.compression").as()->set_sensitive(true);
+ }
+}
+
+void OutputEditorHOCR::imageCompressionChanged()
+{
+ PDFSettings::Compression compression = (*m_builder("combo:pdfoptions.compression").as()->get_active())[m_compressionComboCols.mode];
+ bool zipCompression = compression == PDFSettings::CompressZip;
+ m_builder("spin:pdfoptions.quality").as()->set_sensitive(!zipCompression);
+ m_builder("label:pdfoptions.quality").as()->set_sensitive(!zipCompression);
+}
+
+OutputEditorHOCR::ReadSessionData* OutputEditorHOCR::initRead(tesseract::TessBaseAPI &tess)
+{
+ tess.SetPageSegMode(tesseract::PSM_AUTO_ONLY);
+ return new HOCRReadSessionData;
+}
+
void OutputEditorHOCR::read(tesseract::TessBaseAPI &tess, ReadSessionData *data)
{
tess.SetVariable("hocr_font_info", "true");
@@ -246,10 +493,10 @@
Glib::ustring pageTitle = Glib::ustring::compose("image '%1'; bbox %2 %3 %4 %5; pageno %6; rot %7; res %8",
data.file, x1, y1, x2, y2, data.page, data.angle, data.resolution);
pageDiv->set_attribute("title", pageTitle);
- addPage(pageDiv, Gio::File::create_for_path(data.file)->get_basename(), data.page);
+ addPage(pageDiv, Gio::File::create_for_path(data.file)->get_basename(), data.page, true);
}
-void OutputEditorHOCR::addPage(xmlpp::Element* pageDiv, const Glib::ustring& filename, int page)
+void OutputEditorHOCR::addPage(xmlpp::Element* pageDiv, const Glib::ustring& filename, int page, bool cleanGraphics)
{
m_connectionItemViewRowEdited.block(true);
pageDiv->set_attribute("id", Glib::ustring::compose("page_%1", ++m_idCounter));
@@ -278,11 +525,40 @@
std::map langCache;
+ std::vector> graphicElements;
xmlpp::Element* element = getFirstChildElement(pageDiv, "div");
while(element) {
// Boxes without text are images
titleAttr = getAttribute(element, "title");
if(!addChildItems(getFirstChildElement(element), pageItem, langCache) && s_bboxRx->match(titleAttr, matchInfo)) {
+ x1 = std::atoi(matchInfo.fetch(1).c_str());
+ y1 = std::atoi(matchInfo.fetch(2).c_str());
+ x2 = std::atoi(matchInfo.fetch(3).c_str());
+ y2 = std::atoi(matchInfo.fetch(4).c_str());
+ graphicElements.push_back(std::make_pair(element, Geometry::Rectangle(x1, y1, x2-x1, y2-y1)));
+ }
+ element = getNextSiblingElement(element);
+ }
+
+ // Discard graphic elements which intersect with text block or which are too small
+ int numTextBlocks = pageItem->children().size();
+ for(const std::pair& pair : graphicElements) {
+ xmlpp::Element* element = pair.first;
+ const Geometry::Rectangle& bbox = pair.second;
+ bool deleteGraphic = false;
+ if(cleanGraphics) {
+ if(bbox.width < 10 || bbox.height < 10) {
+ deleteGraphic = true;
+ } else {
+ for(int i = 0; i < numTextBlocks; ++i) {
+ if(bbox.overlaps((*pageItem->children()[i])[m_itemStoreCols.bbox])){
+ deleteGraphic = true;
+ break;
+ }
+ }
+ }
+ }
+ if(!deleteGraphic) {
Gtk::TreeIter item = m_itemStore->append(pageItem->children());
item->set_value(m_itemStoreCols.text, Glib::ustring(_("Graphic")));
item->set_value(m_itemStoreCols.selected, true);
@@ -295,25 +571,34 @@
item->set_value(m_itemStoreCols.id, getAttribute(element, "id"));
item->set_value(m_itemStoreCols.itemClass, Glib::ustring("ocr_graphic"));
item->set_value(m_itemStoreCols.textColor, Glib::ustring("#000"));
- x1 = std::atoi(matchInfo.fetch(1).c_str());
- y1 = std::atoi(matchInfo.fetch(2).c_str());
- x2 = std::atoi(matchInfo.fetch(3).c_str());
- y2 = std::atoi(matchInfo.fetch(4).c_str());
item->set_value(m_itemStoreCols.bbox, Geometry::Rectangle(x1, y1, x2-x1, y2-y1));
+ } else {
+ element->get_parent()->remove_child(element);
}
- element = getNextSiblingElement(element);
}
pageItem->set_value(m_itemStoreCols.source, getElementXML(pageDiv));
m_itemView->expand_row(Gtk::TreePath(pageItem), true);
MAIN->setOutputPaneVisible(true);
m_modified = true;
m_connectionItemViewRowEdited.block(false);
+ m_builder("button:hocr.save")->set_sensitive(true);
+ m_builder("button:hocr.export")->set_sensitive(true);
+}
+
+Gtk::TreeIter OutputEditorHOCR::currentItem()
+{
+ std::vector items = m_itemView->get_selection()->get_selected_rows();
+ if(!items.empty()) {
+ return m_itemStore->get_iter(items[0]);
+ }
+ return Gtk::TreeIter();
}
bool OutputEditorHOCR::addChildItems(xmlpp::Element* element, Gtk::TreeIter parentItem, std::map& langCache)
{
bool haveWord = false;
while(element) {
+ xmlpp::Element* nextElement = getNextSiblingElement(element);
Glib::MatchInfo matchInfo;
Glib::ustring idAttr = getAttribute(element, "id");
if(s_idRx->match(idAttr, matchInfo)) {
@@ -341,7 +626,6 @@
}
if(title != "") {
Gtk::TreeIter item = m_itemStore->append(parentItem->children());
- item->set_value(m_itemStoreCols.text, title);
if(type == "ocrx_word" || addChildItems(getFirstChildElement(element), item, langCache)) {
item->set_value(m_itemStoreCols.selected, true);
item->set_value(m_itemStoreCols.id, getAttribute(element, "id"));
@@ -357,7 +641,18 @@
item->set_value(m_itemStoreCols.textColor, Glib::ustring("#000"));
item->set_value(m_itemStoreCols.editable, type == "ocrx_word");
haveWord = true;
- if(type == "ocrx_word") {
+ if(type == "ocr_line") {
+ if(s_baseLineRx->match(titleAttr, matchInfo)) {
+ item->set_value(m_itemStoreCols.baseLine, std::atoi(matchInfo.fetch(2).c_str()));
+ }
+ } else if(type == "ocrx_word") {
+ // Ensure correct hyphen char is used on last word of line
+ if(!nextElement) {
+ title = Glib::Regex::create("[-\u2014]\\s*$")->replace(title, 0, "-", static_cast(0));
+ element->remove_child(element->get_first_child());
+ element->add_child_text(title);
+ }
+
if(s_fontSizeRx->match(titleAttr, matchInfo)) {
item->set_value(m_itemStoreCols.fontSize, std::atof(matchInfo.fetch(1).c_str()));
}
@@ -370,84 +665,87 @@
if(m_spell.get_language() != spellingLang) {
m_spell.set_language(spellingLang);
}
- if(!m_spell.check_word(title)) {
+ if(!m_spell.check_word(trimWord(title))) {
item->set_value(m_itemStoreCols.textColor, Glib::ustring("#F00"));
}
}
+ item->set_value(m_itemStoreCols.text, title);
} else {
m_itemStore->erase(item);
}
}
}
- element = getNextSiblingElement(element);
+ element = nextElement;
}
return haveWord;
}
-xmlpp::Element* OutputEditorHOCR::getHOCRElementForItem(Gtk::TreeIter item, xmlpp::DomParser& parser) const
+void OutputEditorHOCR::showItemProperties(Gtk::TreeIter item)
{
+ m_connectionPropViewRowEdited.block(true);
+ m_propStore->clear();
+ m_connectionPropViewRowEdited.block(false);
+ m_sourceView->get_buffer()->set_text("");
+ m_tool->clearSelection();
+ m_currentPageItem = Gtk::TreePath();
+ m_currentItem = Gtk::TreePath();
+ m_currentElement = nullptr;
+ delete m_currentParser;
+ m_currentParser = nullptr;
if(!item) {
- return 0;
+ return;
}
-
- Glib::ustring id = (*item)[m_itemStoreCols.id];
- while(item->parent()) {
- item = item->parent();
+ m_currentItem = m_itemStore->get_path(item);
+ Gtk::TreeIter parentItem = item;
+ while(parentItem->parent()) {
+ parentItem = parentItem->parent();
}
- Glib::ustring text = (*item)[m_itemStoreCols.source];
- parser.parse_memory(text);
- xmlpp::Document* doc = parser.get_document();
+ m_currentPageItem = m_itemStore->get_path(parentItem);
+ Glib::ustring id = (*item)[m_itemStoreCols.id];
+ m_currentParser = new xmlpp::DomParser();
+ m_currentParser->parse_memory((*parentItem)[m_itemStoreCols.source]);
+ xmlpp::Document* doc = m_currentParser->get_document();
if(doc->get_root_node()) {
xmlpp::NodeSet nodes = doc->get_root_node()->find(Glib::ustring::compose("//*[@id='%1']", id));
- return nodes.empty() ? nullptr : dynamic_cast(nodes.front());
- } else {
- return nullptr;
+ m_currentElement = nodes.empty() ? nullptr : dynamic_cast(nodes.front());
}
-}
-
-void OutputEditorHOCR::showItemProperties()
-{
- Gtk::TreeIter item = m_itemView->get_selection()->get_selected();
- if(!item) {
- return;
+ if(!m_currentElement) {
+ delete m_currentParser;
+ m_currentParser = nullptr;
+ m_currentPageItem = Gtk::TreePath();
+ m_currentItem = Gtk::TreePath();
}
+
m_connectionPropViewRowEdited.block(true);
- m_propStore->clear();
- xmlpp::DomParser parser;
- xmlpp::Element* element = getHOCRElementForItem(item, parser);
- if(element) {
- for(xmlpp::Attribute* attrib : element->get_attributes()) {
- if(attrib->get_name() == "title") {
- for(Glib::ustring attr : Utils::string_split(attrib->get_value(), ';')) {
- attr = Utils::string_trim(attr);
- std::size_t splitPos = attr.find(" ");
- Gtk::TreeIter item = m_propStore->append();
- item->set_value(m_propStoreCols.parentAttr, Glib::ustring("title"));
- item->set_value(m_propStoreCols.name, Utils::string_trim(attr.substr(0, splitPos)));
- item->set_value(m_propStoreCols.value, Utils::string_trim(attr.substr(splitPos + 1)));
- }
- } else {
+ for(xmlpp::Attribute* attrib : m_currentElement->get_attributes()) {
+ if(attrib->get_name() == "title") {
+ for(Glib::ustring attr : Utils::string_split(attrib->get_value(), ';')) {
+ attr = Utils::string_trim(attr);
+ std::size_t splitPos = attr.find(" ");
Gtk::TreeIter item = m_propStore->append();
- item->set_value(m_propStoreCols.name, attrib->get_name());
- item->set_value(m_propStoreCols.value, attrib->get_value());
+ item->set_value(m_propStoreCols.parentAttr, Glib::ustring("title"));
+ item->set_value(m_propStoreCols.name, Utils::string_trim(attr.substr(0, splitPos)));
+ item->set_value(m_propStoreCols.value, Utils::string_trim(attr.substr(splitPos + 1)));
}
+ } else {
+ Gtk::TreeIter item = m_propStore->append();
+ item->set_value(m_propStoreCols.name, attrib->get_name());
+ item->set_value(m_propStoreCols.value, attrib->get_value());
}
- m_sourceView->get_buffer()->set_text(getElementXML(element));
-
- Glib::MatchInfo matchInfo;
- xmlpp::Element* pageElement = dynamic_cast(parser.get_document()->get_root_node());
- Glib::ustring titleAttr = getAttribute(element, "title");
- if(pageElement && pageElement->get_name() == "div" && setCurrentSource(pageElement) && s_bboxRx->match(titleAttr, matchInfo)) {
- int x1 = std::atoi(matchInfo.fetch(1).c_str());
- int y1 = std::atoi(matchInfo.fetch(2).c_str());
- int x2 = std::atoi(matchInfo.fetch(3).c_str());
- int y2 = std::atoi(matchInfo.fetch(4).c_str());
- m_tool->setSelection(Geometry::Rectangle(x1, y1, x2-x1, y2-y1));
- }
- } else {
- m_tool->clearSelection();
}
m_connectionPropViewRowEdited.block(false);
+ m_sourceView->get_buffer()->set_text(getElementXML(m_currentElement));
+
+ Glib::MatchInfo matchInfo;
+ xmlpp::Element* pageElement = dynamic_cast(m_currentParser->get_document()->get_root_node());
+ Glib::ustring titleAttr = getAttribute(m_currentElement, "title");
+ if(pageElement && pageElement->get_name() == "div" && setCurrentSource(pageElement) && s_bboxRx->match(titleAttr, matchInfo)) {
+ int x1 = std::atoi(matchInfo.fetch(1).c_str());
+ int y1 = std::atoi(matchInfo.fetch(2).c_str());
+ int x2 = std::atoi(matchInfo.fetch(3).c_str());
+ int y2 = std::atoi(matchInfo.fetch(4).c_str());
+ m_tool->setSelection(Geometry::Rectangle(x1, y1, x2-x1, y2-y1));
+ }
}
bool OutputEditorHOCR::setCurrentSource(xmlpp::Element* pageElement, int* pageDpi) const
@@ -488,12 +786,15 @@
void OutputEditorHOCR::itemChanged(const Gtk::TreeIter& iter)
{
+ if(m_itemStore->get_path(iter) != m_currentItem) {
+ return;
+ }
m_connectionItemViewRowEdited.block(true);
bool isWord = (*iter)[m_itemStoreCols.itemClass] == "ocrx_word";
bool selected = (*iter)[m_itemStoreCols.selected];
if( isWord && selected) {
// Update text
- updateItemText(iter);
+ updateCurrentItemText();
} else if(!selected) {
m_itemView->collapse_row(m_itemStore->get_path(iter));
}
@@ -506,69 +807,93 @@
Glib::ustring key = (*iter)[m_propStoreCols.name];
Glib::ustring value = (*iter)[m_propStoreCols.value];
if(!parentAttr.empty()) {
- updateItemAttribute(m_itemView->get_selection()->get_selected(), parentAttr, key, value);
+ updateCurrentItemAttribute(parentAttr, key, value);
} else {
- updateItemAttribute(m_itemView->get_selection()->get_selected(), key, "", value);
+ updateCurrentItemAttribute(key, "", value);
}
}
-void OutputEditorHOCR::updateItemText(Gtk::TreeIter item)
+void OutputEditorHOCR::updateCurrentItemText()
{
- Glib::ustring newText = (*item)[m_itemStoreCols.text];
- xmlpp::DomParser parser;
- xmlpp::Element* element = getHOCRElementForItem(item, parser);
- element->remove_child(element->get_first_child());
- element->add_child_text(newText);
- updateItem(item, parser, element);
+ if(m_currentItem) {
+ Gtk::TreeIter item = m_itemStore->get_iter(m_currentItem);
+ Glib::ustring newText = (*item)[m_itemStoreCols.text];
+ m_currentElement->remove_child(m_currentElement->get_first_child());
+ m_currentElement->add_child_text(newText);
+ updateCurrentItem();
+ }
}
-void OutputEditorHOCR::updateItemAttribute(Gtk::TreeIter item, const Glib::ustring& key, const Glib::ustring& subkey, const Glib::ustring& newvalue)
+void OutputEditorHOCR::updateCurrentItemAttribute(const Glib::ustring& key, const Glib::ustring& subkey, const Glib::ustring& newvalue, bool update)
{
- xmlpp::DomParser parser;
- xmlpp::Element* element = getHOCRElementForItem(item, parser);
- if(subkey.empty()) {
- element->set_attribute(key, newvalue);
- } else {
- Glib::ustring value = getAttribute(element, key);
- std::vector subattrs = Utils::string_split(value, ';');
- for(int i = 0, n = subattrs.size(); i < n; ++i) {
- Glib::ustring attr = Utils::string_trim(subattrs[i]);
- std::size_t splitPos = attr.find(" ");
- if(attr.substr(0, splitPos) == subkey) {
- subattrs[i] = subkey + " " + newvalue;
+ if(m_currentItem) {
+ if(subkey.empty()) {
+ m_currentElement->set_attribute(key, newvalue);
+ } else {
+ Glib::ustring value = getAttribute(m_currentElement, key);
+ std::vector subattrs = Utils::string_split(value, ';');
+ for(int i = 0, n = subattrs.size(); i < n; ++i) {
+ Glib::ustring attr = Utils::string_trim(subattrs[i]);
+ std::size_t splitPos = attr.find(" ");
+ if(attr.substr(0, splitPos) == subkey) {
+ subattrs[i] = subkey + " " + newvalue;
+ break;
+ }
+ }
+ m_currentElement->set_attribute(key, Utils::string_join(subattrs, "; "));
+ }
+ if(update) {
+ updateCurrentItem();
+ }
+ }
+}
+
+void OutputEditorHOCR::updateCurrentItemBBox(const Geometry::Rectangle &rect)
+{
+ if(m_currentItem) {
+ Gtk::TreeIter item = m_itemStore->get_iter(m_currentItem);
+ m_connectionItemViewRowEdited.block(true);
+ item->set_value(m_itemStoreCols.bbox, rect);
+ m_connectionItemViewRowEdited.block(false);
+ Glib::ustring bboxstr = Glib::ustring::compose("%1 %2 %3 %4", rect.x, rect.y, rect.x + rect.width, rect.y + rect.height);
+ for(Gtk::TreeIter it : m_propStore->children()) {
+ if((*it)[m_propStoreCols.name] == "bbox" and (*it)[m_propStoreCols.parentAttr] == "title") {
+ m_connectionPropViewRowEdited.block(true);
+ (*it)[m_propStoreCols.value] = bboxstr;
+ m_connectionPropViewRowEdited.block(false);
break;
}
}
- element->set_attribute(key, Utils::string_join(subattrs, "; "));
+ Gtk::TreeIter toplevelItem = m_itemStore->get_iter(m_currentPageItem);
+ toplevelItem->set_value(m_itemStoreCols.source, getDocumentXML(m_currentParser->get_document()));
+
+ m_sourceView->get_buffer()->set_text(getElementXML(m_currentElement));
}
- updateItem(item, parser, element);
}
-void OutputEditorHOCR::updateItem(Gtk::TreeIter item, xmlpp::DomParser& parser, const xmlpp::Element* element)
+void OutputEditorHOCR::updateCurrentItem()
{
- Glib::ustring spellLang = Utils::getSpellingLanguage(getAttribute(element, "lang"));
+ Gtk::TreeIter item = m_itemStore->get_iter(m_currentItem);
+ Glib::ustring spellLang = Utils::getSpellingLanguage(getAttribute(m_currentElement, "lang"));
if(m_spell.get_language() != spellLang) {
m_spell.set_language(spellLang);
}
m_connectionItemViewRowEdited.block(true); // prevent row edited signal
- if(m_spell.check_word((*item)[m_itemStoreCols.text])) {
+ if(m_spell.check_word(trimWord((*item)[m_itemStoreCols.text]))) {
item->set_value(m_itemStoreCols.textColor, Glib::ustring("#000"));
} else {
item->set_value(m_itemStoreCols.textColor, Glib::ustring("#F00"));
}
m_connectionItemViewRowEdited.block(false);
- Gtk::TreeIter toplevelItem = item;
- while(toplevelItem->parent()) {
- toplevelItem = toplevelItem->parent();
- }
- toplevelItem->set_value(m_itemStoreCols.source, getDocumentXML(parser.get_document()));
+ Gtk::TreeIter toplevelItem = m_itemStore->get_iter(m_currentPageItem);
+ toplevelItem->set_value(m_itemStoreCols.source, getDocumentXML(m_currentParser->get_document()));
- m_sourceView->get_buffer()->set_text(getElementXML(element));
+ m_sourceView->get_buffer()->set_text(getElementXML(m_currentElement));
Glib::MatchInfo matchInfo;
- xmlpp::Element* pageElement = dynamic_cast(parser.get_document()->get_root_node());
- Glib::ustring titleAttr = getAttribute(element, "title");
+ xmlpp::Element* pageElement = dynamic_cast(m_currentParser->get_document()->get_root_node());
+ Glib::ustring titleAttr = getAttribute(m_currentElement, "title");
if(pageElement && pageElement->get_name() == "div" && setCurrentSource(pageElement) && s_bboxRx->match(titleAttr, matchInfo)) {
int x1 = std::atoi(matchInfo.fetch(1).c_str());
int y1 = std::atoi(matchInfo.fetch(2).c_str());
@@ -580,56 +905,241 @@
m_modified = true;
}
-bool OutputEditorHOCR::handleButtonEvent(GdkEventButton* ev)
+void OutputEditorHOCR::removeCurrentItem()
{
+ if(m_currentElement) {
+ m_currentElement->get_parent()->remove_child(m_currentElement);
+ Gtk::TreeIter toplevelItem = m_itemStore->get_iter(m_currentPageItem);
+ toplevelItem->set_value(m_itemStoreCols.source, getDocumentXML(m_currentParser->get_document()));
+
+ m_itemStore->erase(m_itemStore->get_iter(m_currentItem));
+ m_currentItem = Gtk::TreePath();
+ }
+}
+
+void OutputEditorHOCR::addGraphicRection(const Geometry::Rectangle &rect)
+{
+ if(!m_currentParser) {
+ return;
+ }
+ xmlpp::Document* doc = m_currentParser->get_document();
+ xmlpp::Element* pageDiv = dynamic_cast(doc ? doc->get_root_node() : nullptr);
+ if(!pageDiv || pageDiv->get_name() != "div")
+ return;
+
+ // Determine a free block id
+ int pageId = 0;
+ int blockId = 0;
+ xmlpp::Element* blockEl = getFirstChildElement(pageDiv, "div");
+ while(blockEl) {
+ Glib::MatchInfo matchInfo;
+ Glib::ustring idAttr = getAttribute(blockEl, "id");
+ if(s_idRx->match(idAttr, matchInfo)) {
+ pageId = std::max(pageId, std::atoi(matchInfo.fetch(1).c_str()) + 1);
+ blockId = std::max(blockId, std::atoi(matchInfo.fetch(2).c_str()) + 1);
+ }
+ blockEl = getNextSiblingElement(blockEl);
+ }
+
+ // Add html element
+ xmlpp::Element* graphicElement = pageDiv->add_child("div");
+ graphicElement->set_attribute("title", Glib::ustring::compose("bbox %1 %2 %3 %4", rect.x, rect.y, rect.x + rect.width, rect.y + rect.height));
+ graphicElement->set_attribute("class", "ocr_carea");
+ graphicElement->set_attribute("id", Glib::ustring::compose("block_%1_%2", pageId, blockId));
+ Gtk::TreeIter toplevelItem = m_itemStore->get_iter(m_currentPageItem);
+ toplevelItem->set_value(m_itemStoreCols.source, getDocumentXML(m_currentParser->get_document()));
+
+ // Add tree item
+ Gtk::TreeIter item = m_itemStore->append(toplevelItem->children());
+ item->set_value(m_itemStoreCols.text, Glib::ustring(_("Graphic")));
+ item->set_value(m_itemStoreCols.selected, true);
+ item->set_value(m_itemStoreCols.editable, false);
+#if GTKMM_CHECK_VERSION(3,12,0)
+ item->set_value(m_itemStoreCols.icon, Gdk::Pixbuf::create_from_resource("/org/gnome/gimagereader/item_halftone.png"));
+#else
+ item->set_value(m_itemStoreCols.icon, Glib::wrap(gdk_pixbuf_new_from_resource("/org/gnome/gimagereader/item_halftone.png", 0)));
+#endif
+ item->set_value(m_itemStoreCols.id, getAttribute(graphicElement, "id"));
+ item->set_value(m_itemStoreCols.itemClass, Glib::ustring("ocr_graphic"));
+ item->set_value(m_itemStoreCols.textColor, Glib::ustring("#000"));
+ item->set_value(m_itemStoreCols.bbox, rect);
+
+ m_itemView->get_selection()->unselect_all();
+ m_itemView->get_selection()->select(item);
+ m_itemView->scroll_to_row(m_itemStore->get_path(item));
+}
+
+Glib::ustring OutputEditorHOCR::trimWord(const Glib::ustring& word, Glib::ustring* prefix, Glib::ustring* suffix)
+{
+ Glib::RefPtr re = Glib::Regex::create("^(\\W*)(.*\\w)(\\W*)$");
+ Glib::MatchInfo match_info;
+ if(re->match(word, -1, 0, match_info, static_cast(0))) {
+ if(prefix)
+ *prefix = match_info.fetch(1);
+ if(suffix)
+ *suffix = match_info.fetch(3);
+ return match_info.fetch(2);
+ }
+ return word;
+}
+
+void OutputEditorHOCR::mergeItems(const std::vector& items)
+{
+ Gtk::TreeIter it = m_itemStore->get_iter(items.front());
+ if(!it) {
+ return;
+ }
+
+ Geometry::Rectangle bbox = (*it)[m_itemStoreCols.bbox];
+ Glib::ustring text = (*it)[m_itemStoreCols.text];
+
+ xmlpp::Document* doc = m_currentParser->get_document();
+
+ for(int i = 1, n = items.size(); i < n; ++i) {
+ it = m_itemStore->get_iter(items[i]);
+ if(it) {
+ bbox = bbox.unite((*it)[m_itemStoreCols.bbox]);
+ text += (*it)[m_itemStoreCols.text];
+ Glib::ustring id = (*it)[m_itemStoreCols.id];
+ xmlpp::NodeSet nodes = doc->get_root_node()->find(Glib::ustring::compose("//*[@id='%1']", id));
+ xmlpp::Element* element = nodes.empty() ? nullptr : dynamic_cast(nodes.front());
+ element->get_parent()->remove_child(element);
+ m_itemStore->erase(it);
+ }
+ }
+ m_itemView->get_selection()->unselect_all();
+ m_itemView->get_selection()->select(items.front());
+
+ it = m_itemStore->get_iter(items.front());
+ (*it)[m_itemStoreCols.text] = text;
+ (*it)[m_itemStoreCols.bbox] = bbox;
+ updateCurrentItemText();
+ updateCurrentItemAttribute("title", "bbox", Glib::ustring::compose("%1 %2 %3 %4", bbox.x, bbox.y, bbox.x + bbox.width, bbox.y + bbox.height));
+ showItemProperties(m_itemStore->get_iter(m_currentItem));
+}
+
+void OutputEditorHOCR::showContextMenu(GdkEventButton* ev)
+{
+ std::vector items = m_itemView->get_selection()->get_selected_rows();
+ bool wordsSelected = true;
+ for(const Gtk::TreePath& path : items)
+ {
+ Gtk::TreeIter it = m_itemStore->get_iter(path);
+ if(it) {
+ Glib::ustring itemClass = (*it)[m_itemStoreCols.itemClass];
+ if(itemClass != "ocrx_word") {
+ wordsSelected = false;
+ break;
+ }
+ }
+ }
+ bool consecutive = true;
+ for(int i = 1, n = items.size(); i < n; ++i) {
+ Gtk::TreePath path = items[i];
+ path.prev();
+ if(path != items[i - 1]) {
+ consecutive = false;
+ break;
+ }
+ }
+ if(items.size() > 1 && consecutive && wordsSelected) {
+ Gtk::Menu menu;
+ Glib::RefPtr loop = Glib::MainLoop::create();
+ Gtk::MenuItem* mergeItem = Gtk::manage(new Gtk::MenuItem(_("Merge")));
+ menu.append(*mergeItem);
+ CONNECT(mergeItem, activate, [&]{ mergeItems(items); });
+ CONNECT(&menu, hide, [&]{ loop->quit(); });
+ menu.show_all();
+ menu.popup(ev->button, ev->time);
+ loop->run();
+ return;
+ } else if(items.size() > 1) {
+ return;
+ }
Gtk::TreePath path;
Gtk::TreeViewColumn* col;
int cell_x, cell_y;
m_itemView->get_path_at_pos(int(ev->x), int(ev->y), path, col, cell_x, cell_y);
if(!path) {
- return false;
+ return;
}
Gtk::TreeIter it = m_itemStore->get_iter(path);
if(!it) {
- return false;
+ return;
}
Glib::ustring itemClass = (*it)[m_itemStoreCols.itemClass];
- if(itemClass == "ocr_page" && ev->type == GDK_BUTTON_PRESS && ev->button == 3) {
+ if(itemClass == "ocr_page") {
// Context menu on page items with Remove option
- m_itemView->get_selection()->select(path);
Gtk::Menu menu;
Glib::RefPtr loop = Glib::MainLoop::create();
+ Gtk::MenuItem* addGraphicItem = Gtk::manage(new Gtk::MenuItem(_("Add graphic region")));
+ menu.append(*addGraphicItem);
+ menu.append(*Gtk::manage(new Gtk::SeparatorMenuItem));
Gtk::MenuItem* removeItem = Gtk::manage(new Gtk::MenuItem(_("Remove")));
menu.append(*removeItem);
- CONNECT(removeItem, activate, [&]{ m_itemStore->erase(it); });
+ CONNECT(removeItem, activate, [&]{
+ m_itemStore->erase(it);
+ m_connectionPropViewRowEdited.block(true);
+ m_propStore->clear();
+ m_connectionPropViewRowEdited.block(false);
+ m_builder("button:hocr.save")->set_sensitive(!m_itemStore->children().empty());
+ m_builder("button:hocr.export")->set_sensitive(!m_itemStore->children().empty());
+ });
+ CONNECT(addGraphicItem, activate, [this]{
+ m_tool->clearSelection();
+ m_tool->activateDrawSelection();
+ });
CONNECT(&menu, hide, [&]{ loop->quit(); });
menu.show_all();
menu.popup(ev->button, ev->time);
loop->run();
- return true;
- } else if(itemClass == "ocrx_word" && ev->type == GDK_BUTTON_PRESS && ev->button == 3) {
+ return;
+ } else {
// Context menu on word items with spelling suggestions, if any
- m_itemView->get_selection()->select(path);
Gtk::Menu menu;
Glib::RefPtr loop = Glib::MainLoop::create();
- for(const Glib::ustring& suggestion : m_spell.get_suggestions((*it)[m_itemStoreCols.text])) {
- Gtk::MenuItem* item = Gtk::manage(new Gtk::MenuItem(suggestion));
- CONNECT(item, activate, [this, suggestion, it] { (*it)[m_itemStoreCols.text] = suggestion; });
- menu.append(*item);
- }
- if(menu.get_children().empty()) {
- Gtk::MenuItem* item = Gtk::manage(new Gtk::MenuItem(_("No suggestions")));
- item->set_sensitive(false);
- menu.append(*item);
+ if(itemClass == "ocrx_word") {
+ Glib::ustring prefix, suffix, trimmed = trimWord((*it)[m_itemStoreCols.text], &prefix, &suffix);
+ for(const Glib::ustring& suggestion : m_spell.get_suggestions(trimmed)) {
+ Glib::ustring replacement = prefix + suggestion + suffix;
+ Gtk::MenuItem* item = Gtk::manage(new Gtk::MenuItem(replacement));
+ CONNECT(item, activate, [this, replacement, it] { (*it)[m_itemStoreCols.text] = replacement; });
+ menu.append(*item);
+ }
+ if(menu.get_children().empty()) {
+ Gtk::MenuItem* item = Gtk::manage(new Gtk::MenuItem(_("No suggestions")));
+ item->set_sensitive(false);
+ menu.append(*item);
+ }
+ if(!m_spell.check_word(trimWord((*it)[m_itemStoreCols.text]))) {
+ menu.append(*Gtk::manage(new Gtk::SeparatorMenuItem));
+ Gtk::MenuItem* additem = Gtk::manage(new Gtk::MenuItem(_("Add to dictionary")));
+ CONNECT(additem, activate, [this, it]{
+ m_spell.add_to_dictionary((*it)[m_itemStoreCols.text]);
+ it->set_value(m_itemStoreCols.textColor, Glib::ustring("#000"));
+ });
+ menu.append(*additem);
+ Gtk::MenuItem* ignoreitem = Gtk::manage(new Gtk::MenuItem(_("Ignore word")));
+ CONNECT(ignoreitem, activate, [this, it]{
+ m_spell.ignore_word((*it)[m_itemStoreCols.text]);
+ it->set_value(m_itemStoreCols.textColor, Glib::ustring("#000"));
+ });
+ menu.append(*ignoreitem);
+ }
+ menu.append(*Gtk::manage(new Gtk::SeparatorMenuItem));
}
+ Gtk::MenuItem* removeItem = Gtk::manage(new Gtk::MenuItem(_("Remove")));
+ menu.append(*removeItem);
+ CONNECT(removeItem, activate, [this]{ removeCurrentItem(); });
+
CONNECT(&menu, hide, [&]{ loop->quit(); });
menu.show_all();
menu.popup(ev->button, ev->time);
loop->run();
- return true;
+ return;
}
- return false;
+ return;
}
void OutputEditorHOCR::checkCellEditable(const Glib::ustring& path, Gtk::CellRenderer* renderer)
@@ -646,7 +1156,7 @@
return;
}
Glib::ustring dir = MAIN->getConfig()->getSetting>("outputdir")->getValue();
- FileDialogs::FileFilter filter = {_("hOCR HTML Files"), {"text/html"}, {"*.html"}};
+ FileDialogs::FileFilter filter = {_("hOCR HTML Files"), {"text/html","text/xml", "text/plain"}, {"*.html"}};
std::vector> files = FileDialogs::open_dialog(_("Open hOCR File"), dir, filter, false);
if(files.empty()) {
return;
@@ -671,7 +1181,7 @@
int page = 0;
while(div) {
++page;
- addPage(div, files.front()->get_basename(), page);
+ addPage(div, files.front()->get_basename(), page, false);
div = getNextSiblingElement(div, "div");
}
}
@@ -680,7 +1190,11 @@
{
std::string outname = filename;
if(outname.empty()){
- outname = Glib::build_filename(MAIN->getConfig()->getSetting>("outputdir")->getValue(), Glib::ustring(_("output")) + ".html");
+ std::vector sources = MAIN->getSourceManager()->getSelectedSources();
+ std::string ext, base;
+ std::string name = !sources.empty() ? sources.front()->displayname : _("output");
+ Utils::get_filename_parts(name, base, ext);
+ outname = Glib::build_filename(MAIN->getConfig()->getSetting>("outputdir")->getValue(), base + ".html");
FileDialogs::FileFilter filter = {_("hOCR HTML Files"), {"text/html"}, {"*.html"}};
outname = FileDialogs::save_dialog(_("Save hOCR Output..."), outname, filter);
@@ -716,38 +1230,89 @@
return true;
}
-void OutputEditorHOCR::savePDF(bool overlay)
+void OutputEditorHOCR::savePDF()
{
- if(!overlay) {
- int ret = m_pdfExportDialog->run();
+ m_preview = new DisplayerImageItem();
+ updatePreview();
+ MAIN->getDisplayer()->addItem(m_preview);
+ bool accepted = false;
+ PoDoFo::PdfStreamedDocument* document = nullptr;
+ PoDoFo::PdfFont* font = nullptr;
+#if PODOFO_VERSION >= PODOFO_MAKE_VERSION(0,9,3)
+ const PoDoFo::PdfEncoding* pdfEncoding = PoDoFo::PdfEncodingFactory::GlobalIdentityEncodingInstance();
+#else
+ const PoDoFo::PdfEncoding* pdfEncoding = new PoDoFo::PdfIdentityEncoding;
+#endif
+ double fontSize = 0;
+ while(true) {
+ accepted = m_pdfExportDialog->run() == Gtk::RESPONSE_OK;
m_pdfExportDialog->hide();
- if(ret != Gtk::RESPONSE_OK) {
- return;
+ if(!accepted) {
+ break;
+ }
+
+ std::vector sources = MAIN->getSourceManager()->getSelectedSources();
+ std::string ext, base;
+ std::string name = !sources.empty() ? sources.front()->displayname : _("output");
+ Utils::get_filename_parts(name, base, ext);
+ std::string outname = Glib::build_filename(MAIN->getConfig()->getSetting>("outputdir")->getValue(), base + ".pdf");
+ FileDialogs::FileFilter filter = {_("PDF Files"), {"application/pdf"}, {"*.pdf"}};
+ outname = FileDialogs::save_dialog(_("Save PDF Output..."), outname, filter);
+ if(outname.empty()){
+ accepted = false;
+ break;
+ }
+ MAIN->getConfig()->getSetting>("outputdir")->setValue(Glib::path_get_dirname(outname));
+
+ try {
+ document = new PoDoFo::PdfStreamedDocument(outname.c_str());
+ } catch(...) {
+ Utils::message_dialog(Gtk::MESSAGE_ERROR, _("Failed to save output"), _("Check that you have writing permissions in the selected folder."));
+ continue;
+ }
+ try {
+ Glib::ustring fontName = m_builder("fontbutton:pdfoptions").as()->get_font_name();
+ Pango::FontDescription fontDesc = Pango::FontDescription(fontName);
+ bool italic = fontDesc.get_style() == Pango::STYLE_OBLIQUE;
+ bool bold = fontDesc.get_weight() == Pango::WEIGHT_BOLD;
+ fontSize = fontDesc.get_size() / double(PANGO_SCALE);
+#if PODOFO_VERSION >= PODOFO_MAKE_VERSION(0,9,3)
+ font = document->CreateFontSubset(Utils::resolveFontName(fontDesc.get_family()).c_str(), bold, italic, false, pdfEncoding);
+#else
+ font = document->CreateFontSubset(Utils::resolveFontName(fontDesc.get_family()).c_str(), bold, italic, pdfEncoding);
+#endif
+ } catch(...) {
+ font = nullptr;
+ }
+ if(!font) {
+ Utils::message_dialog(Gtk::MESSAGE_ERROR, _("Error"), _("The PDF library does not support the selected font."));
+ document->Close();
+ delete document;
+ continue;
}
+ break;
}
- std::string outname = Glib::build_filename(MAIN->getConfig()->getSetting>("outputdir")->getValue(), Glib::ustring(_("output")) + ".pdf");
- FileDialogs::FileFilter filter = {_("PDF Files"), {"application/pdf"}, {"*.pdf"}};
- outname = FileDialogs::save_dialog(_("Save PDF Output..."), outname, filter);
- if(outname.empty()){
+
+ MAIN->getDisplayer()->removeItem(m_preview);
+ delete m_preview;
+ m_preview = nullptr;
+
+ if(!accepted) {
return;
}
- MAIN->getConfig()->getSetting>("outputdir")->setValue(Glib::path_get_dirname(outname));
- bool useDetectedFontSizes = m_builder("checkbox:pdfoptions.usedetectedfontsizes").as()->get_active();
- bool uniformizeLineSpacing = m_builder("checkbox:pdfoptions.uniformlinespacing").as()->get_active();
-
- int outputDpi = 300;
- Cairo::RefPtr surface = Cairo::PdfSurface::create(outname, outputDpi, outputDpi);
- Cairo::RefPtr context = Cairo::Context::create(surface);
+
+ PoDoFo::PdfPainter painter;
+
+ PDFSettings pdfSettings;
+ pdfSettings.colorFormat = (*m_builder("combo:pdfoptions.imageformat").as()->get_active())[m_formatComboCols.format];
+ pdfSettings.compression = (*m_builder("combo:pdfoptions.compression").as()->get_active())[m_compressionComboCols.mode];
+ pdfSettings.compressionQuality = m_builder("spin:pdfoptions.quality").as()->get_value();
+ pdfSettings.useDetectedFontSizes = m_builder("checkbox:pdfoptions.usedetectedfontsizes").as()->get_active();
+ pdfSettings.uniformizeLineSpacing = m_builder("checkbox:pdfoptions.uniformlinespacing").as()->get_active();
+ pdfSettings.preserveSpaceWidth = m_builder("spin:pdfoptions.preserve").as()->get_value();
+ pdfSettings.overlay = m_builder("combo:pdfoptions.mode").as()->get_active_row_number() == 1;
+ pdfSettings.detectedFontScaling = m_builder("spin:pdfoptions.fontscale").as()->get_value() / 100.;
std::vector failed;
- if(!overlay) {
- Glib::ustring fontName = m_builder("fontbutton:pdfoptions").as()->get_font_name();
- Pango::FontDescription fontDesc = Pango::FontDescription(fontName);
- Cairo::FontSlant fontSlant = fontDesc.get_style() == Pango::STYLE_OBLIQUE ? Cairo::FONT_SLANT_OBLIQUE : fontDesc.get_style() == Pango::STYLE_ITALIC ? Cairo::FONT_SLANT_ITALIC : Cairo::FONT_SLANT_NORMAL;
- Cairo::FontWeight fontWeight = fontDesc.get_weight() == Pango::WEIGHT_BOLD ? Cairo::FONT_WEIGHT_BOLD : Cairo::FONT_WEIGHT_NORMAL;
- double fontSize = fontDesc.get_size() / double(PANGO_SCALE);
- context->select_font_face(fontDesc.get_family(), fontSlant, fontWeight);
- context->set_font_size(fontSize * 300. / 72.);
- }
for(Gtk::TreeIter item : m_itemStore->children()) {
if(!(*item)[m_itemStoreCols.selected]) {
continue;
@@ -756,22 +1321,21 @@
xmlpp::DomParser parser;
parser.parse_memory((*item)[m_itemStoreCols.source]);
xmlpp::Document* doc = parser.get_document();
- int pageDpi = 0;
+ int pageDpi = 72;
if(doc->get_root_node() && doc->get_root_node()->get_name() == "div" && setCurrentSource(doc->get_root_node(), &pageDpi)) {
- surface->set_size((bbox.width * 72.) / pageDpi, (bbox.height * 72.) / pageDpi);
- context->save();
- context->scale(72. / double(pageDpi), 72. / double(pageDpi));
- printChildren(context, item, overlay, useDetectedFontSizes, uniformizeLineSpacing);
- if(overlay) {
- Cairo::RefPtr sel = m_tool->getSelection(bbox);
- context->save();
- context->move_to(bbox.x, bbox.y);
- context->set_source(sel, 0, 0);
- context->paint();
- context->restore();
+ double dpiScale = 72. / pageDpi;
+ double imageScale = m_builder("spin:pdfoptions.dpi").as()->get_value() / double(pageDpi);
+ PoDoFo::PdfPage* page = document->CreatePage(PoDoFo::PdfRect(0, 0, bbox.width * dpiScale, bbox.height * dpiScale));
+ painter.SetPage(page);
+ painter.SetFont(font);
+
+ PoDoFoPDFPainter pdfprinter(document, &painter, dpiScale, imageScale);
+ pdfprinter.setFontSize(fontSize);
+ printChildren(pdfprinter, item, pdfSettings);
+ if(pdfSettings.overlay) {
+ pdfprinter.drawImage(bbox, m_tool->getSelection(bbox), pdfSettings);
}
- context->restore();
- context->show_page();
+ painter.FinishPage();
} else {
failed.push_back((*item)[m_itemStoreCols.text]);
}
@@ -779,61 +1343,125 @@
if(!failed.empty()) {
Utils::message_dialog(Gtk::MESSAGE_ERROR, _("Errors occurred"), Glib::ustring::compose(_("The following pages could not be rendered:\n%1"), Utils::string_join(failed, "\n")));
}
+ document->Close();
+ delete document;
}
-void OutputEditorHOCR::printChildren(Cairo::RefPtr context, Gtk::TreeIter item, bool overlayMode, bool useDetectedFontSizes, bool uniformizeLineSpacing) const
+void OutputEditorHOCR::printChildren(PDFPainter& painter, Gtk::TreeIter item, const PDFSettings& pdfSettings) const
{
if(!(*item)[m_itemStoreCols.selected]) {
return;
}
Glib::ustring itemClass = (*item)[m_itemStoreCols.itemClass];
Geometry::Rectangle itemRect = (*item)[m_itemStoreCols.bbox];
- if(itemClass == "ocr_line" && uniformizeLineSpacing && !overlayMode) {
- double x = itemRect.x;
- double prevWordRight = itemRect.x;
- for(Gtk::TreeIter wordItem : item->children()) {
- if((*wordItem)[m_itemStoreCols.selected]) {
- Geometry::Rectangle wordRect = (*wordItem)[m_itemStoreCols.bbox];
- if(useDetectedFontSizes) {
- context->set_font_size((*wordItem)[m_itemStoreCols.fontSize] * 300. / 72.);
- }
- // If distance from previous word is large, keep the space
- Cairo::TextExtents ext;
- context->get_text_extents(Glib::ustring((*wordItem)[m_itemStoreCols.text]) + " ", ext);
- int spaceSize = ext.x_advance - ext.width; // spaces are ignored in width but counted in advance
- if(wordRect.x - prevWordRight > 4 * spaceSize) {
- x = wordRect.x;
+ if(itemClass == "ocr_par" && pdfSettings.uniformizeLineSpacing) {
+ double yInc = double(itemRect.height) / item->children().size();
+ double y = itemRect.y + yInc;
+ int baseLine = item->children().empty() ? 0 : (*(*item->children().begin()))[m_itemStoreCols.baseLine];
+ for(Gtk::TreeIter lineItem : item->children()) {
+ double x = itemRect.x;
+ double prevWordRight = itemRect.x;
+ for(Gtk::TreeIter wordItem : lineItem->children()) {
+ if((*wordItem)[m_itemStoreCols.selected]) {
+ Geometry::Rectangle wordRect = (*wordItem)[m_itemStoreCols.bbox];
+ if(pdfSettings.useDetectedFontSizes) {
+ painter.setFontSize((*wordItem)[m_itemStoreCols.fontSize] * pdfSettings.detectedFontScaling);
+ }
+ // If distance from previous word is large, keep the space
+ if(wordRect.x - prevWordRight > pdfSettings.preserveSpaceWidth * painter.getAverageCharWidth()) {
+ x = wordRect.x;
+ }
+ prevWordRight = wordRect.x + wordRect.width;
+ painter.drawText(x, y + baseLine, Glib::ustring((*wordItem)[m_itemStoreCols.text]));
+ x += painter.getTextWidth(Glib::ustring((*wordItem)[m_itemStoreCols.text]) + " ");
}
- prevWordRight = wordRect.x + wordRect.width;
- context->move_to(x, itemRect.y + itemRect.height/*wordRect.y - ext.y_bearing*/);
- context->set_source_rgb(0, 0, 0);
- context->show_text(Glib::ustring((*wordItem)[m_itemStoreCols.text]));
- x += ext.x_advance;
}
+ y += yInc;
}
- } else if(itemClass == "ocrx_word" && (overlayMode || !uniformizeLineSpacing)) {
- Cairo::TextExtents ext;
- if(useDetectedFontSizes) {
- context->set_font_size((*item)[m_itemStoreCols.fontSize] * 300. / 72.);
+ } else if(itemClass == "ocr_line" && !pdfSettings.uniformizeLineSpacing) {
+ int baseLine = (*item)[m_itemStoreCols.baseLine];
+ double y = itemRect.y + itemRect.height + baseLine;
+ for(Gtk::TreeIter wordItem : item->children()) {
+ Geometry::Rectangle wordRect = (*wordItem)[m_itemStoreCols.bbox];
+ if(pdfSettings.useDetectedFontSizes) {
+ painter.setFontSize((*wordItem)[m_itemStoreCols.fontSize] * pdfSettings.detectedFontScaling);
+ }
+ painter.drawText(wordRect.x, y, Glib::ustring((*wordItem)[m_itemStoreCols.text]));
}
- context->get_text_extents(Glib::ustring((*item)[m_itemStoreCols.text]), ext);
- if(overlayMode) {
- context->set_source_rgba(255, 255, 255, 0.01);
- }
- context->move_to(itemRect.x, itemRect.y - ext.y_bearing);
- context->show_text(Glib::ustring((*item)[m_itemStoreCols.text]));
- } else if(itemClass == "ocr_graphic" && !overlayMode) {
+ } else if(itemClass == "ocr_graphic" && !pdfSettings.overlay) {
Cairo::RefPtr sel = m_tool->getSelection(itemRect);
- context->save();
- context->move_to(itemRect.x, itemRect.y);
- context->set_source(sel, itemRect.x, itemRect.y);
- context->paint();
- context->restore();
+ painter.drawImage(itemRect, sel, pdfSettings);
} else {
for(Gtk::TreeIter child : item->children()) {
- printChildren(context, child, overlayMode, useDetectedFontSizes, uniformizeLineSpacing);
+ printChildren(painter, child, pdfSettings);
+ }
+ }
+}
+
+void OutputEditorHOCR::updatePreview()
+{
+ if(!m_preview) {
+ return;
+ }
+ bool visible = m_builder("checkbox:pdfoptions.preview").as()->get_active();
+ m_preview->setVisible(visible);
+ if(m_itemStore->children().empty()|| !visible) {
+ return;
+ }
+ Gtk::TreeIter item = currentItem();
+ if(!item) {
+ item = *m_itemStore->children().begin();
+ } else {
+ while(item->parent()) {
+ item = item->parent();
}
}
+
+ Geometry::Rectangle bbox = (*item)[m_itemStoreCols.bbox];
+ xmlpp::DomParser parser;
+ parser.parse_memory((*item)[m_itemStoreCols.source]);
+ xmlpp::Document* doc = parser.get_document();
+ int pageDpi = 72;
+ setCurrentSource(doc->get_root_node(), &pageDpi);
+
+ Cairo::RefPtr